[インデックス 10365] ファイルの概要
コミット
- コミットハッシュ:
7af553ab52cf0c79b906b70b9f4b11b2c926fbe1
- 作成者: Dave Cheney dave@cheney.net
- 作成日: 2011年11月13日 21:05:35 -0500
- コミットメッセージ: "exp/ssh: add direct-tcpip client support"
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/816f12285cabe978738bae5070b09290c447150f
元コミット内容
このコミットは、Go言語の実験的SSH実装(exp/ssh)にTCP/IPポートフォワーディング機能を追加しました。具体的には、SSHクライアントがリモートサーバーを介してプロキシ化されたnet.Conn
接続を作成できるようになりました。
変更内容:
src/pkg/exp/ssh/Makefile
へのtcpip.go
の追加- 146行の新しいファイル
src/pkg/exp/ssh/tcpip.go
の作成 - 合計で148行の追加と1行の削除
変更の背景
2011年当時、Go言語はまだ比較的新しい言語で、標準ライブラリには多くの実験的機能が含まれていました。SSH(Secure Shell)プロトコルのサポートも、exp/ssh
パッケージとして実験的に提供されていました。
Dave Cheneyは、Go言語のSSH実装において重要な貢献者の一人で、この時期に以下のような重要な機能を追加していました:
- 公開鍵認証のサポート
- インタラクティブセッションの改善
- そして、このコミットで追加されたTCP/IPポートフォワーディング(direct-tcpip)機能
このコミットは、Go言語のSSH実装を本格的なSSHクライアントとして使用可能にするための重要な機能追加でした。
前提知識の解説
SSH(Secure Shell)プロトコル
SSH(Secure Shell)は、ネットワーク越しにリモートサーバーに安全にアクセスするためのプロトコルです。暗号化通信を提供し、認証、コマンド実行、ファイル転送などの機能を提供します。
RFC 4254とSSH Connection Protocol
RFC 4254は「The Secure Shell (SSH) Connection Protocol」を定義しており、SSH接続の上でチャンネルを多重化し、さまざまなサービスを提供する方法を規定しています。
direct-tcpipチャンネル
direct-tcpip
は、RFC 4254で定義されているチャンネルタイプの一つで、SSHクライアントがSSHサーバーを介してリモートホストのTCPポートに接続する際に使用されます。これにより、ローカルポートフォワーディングが実現されます。
TCP/IPポートフォワーディング
ポートフォワーディングは、ネットワーク接続をリダイレクトする技術です。SSHにおけるポートフォワーディングには以下の3種類があります:
- ローカルポートフォワーディング - ローカルポートへの接続をリモートサーバー経由で別のホストに転送
- リモートポートフォワーディング - リモートサーバーのポートへの接続をローカルホストに転送
- ダイナミックポートフォワーディング - SOCKSプロキシとして動作
このコミットでは、direct-tcpip
チャンネルを使用したローカルポートフォワーディングの実装が追加されました。
技術的詳細
実装されたAPI
新しく実装されたAPIは以下の通りです:
Dial(n, addr string) (net.Conn, error)
- 簡単なダイアル関数DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error)
- TCP専用のダイアル関数dial(laddr string, lport int, raddr string, rport int) (*tcpchan, error)
- 内部的なダイアル実装
データ構造
実装では以下のような型が定義されています:
tcpchan
- SSH上でのTCPチャンネルを表現する型tcpchanconn
-net.Conn
インターフェースを実装する型channelOpenDirectMsg
- direct-tcpipチャンネルのオープン要求メッセージ
RFC 4254準拠の実装
direct-tcpip
チャンネルの作成は、RFC 4254 7.2で規定されているメッセージ構造に従って実装されています:
type channelOpenDirectMsg struct {
ChanType string
PeersId uint32
PeersWindow uint32
MaxPacketSize uint32
raddr string
rport uint32
laddr string
lport uint32
}
コアとなるコードの変更箇所
1. Makefileの更新
# src/pkg/exp/ssh/Makefile
GOFILES=\
client_auth.go\
common.go\
messages.go\
- transport.go\
server.go\
server_shell.go\
session.go\
+ tcpip.go\
+ transport.go\
2. 新しいファイルの作成
src/pkg/exp/ssh/tcpip.go
という新しいファイルが作成され、146行のコードが追加されました。
コアとなるコードの解説
1. Dial関数の実装
func (c *ClientConn) Dial(n, addr string) (net.Conn, error) {
raddr, err := net.ResolveTCPAddr(n, addr)
if err != nil {
return nil, err
}
return c.DialTCP(n, nil, raddr)
}
この関数は、文字列形式のアドレスを受け取り、TCPアドレスに解決してからDialTCP
を呼び出します。使いやすさのために提供されていますが、DNS解決がクライアント側で行われるため、プライバシーの観点から問題がある場合があります。
2. DialTCP関数の実装
func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) {
if laddr == nil {
laddr = &net.TCPAddr{
IP: net.IPv4zero,
Port: 0,
}
}
ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port)
if err != nil {
return nil, err
}
return &tcpchanconn{
tcpchan: ch,
laddr: laddr,
raddr: raddr,
}, nil
}
この関数は、net.TCPAddr
を使用してより詳細な制御を提供します。ローカルアドレスが指定されていない場合は、デフォルトのゼロIPアドレスとポート0を使用します。
3. dial関数の実装
func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tcpchan, error) {
// RFC 4254 7.2
type channelOpenDirectMsg struct {
ChanType string
PeersId uint32
PeersWindow uint32
MaxPacketSize uint32
raddr string
rport uint32
laddr string
lport uint32
}
// ... implementation
}
この関数は、実際のSSHプロトコルレベルでのdirect-tcpip
チャンネルの作成を行います。RFC 4254で規定されているメッセージ構造を使用して、リモートサーバーに接続要求を送信します。
4. net.Connインターフェースの実装
type tcpchanconn struct {
*tcpchan
laddr, raddr net.Addr
}
func (t *tcpchanconn) LocalAddr() net.Addr {
return t.laddr
}
func (t *tcpchanconn) RemoteAddr() net.Addr {
return t.raddr
}
tcpchanconn
型は、net.Conn
インターフェースを完全に実装しており、既存のGo言語のネットワーキングAPIと互換性があります。
5. タイムアウト処理
func (t *tcpchanconn) SetReadTimeout(nsec int64) error {
return errors.New("ssh: tcpchan: timeout not supported")
}
func (t *tcpchanconn) SetWriteTimeout(nsec int64) error {
return errors.New("ssh: tcpchan: timeout not supported")
}
興味深いことに、このバージョンではタイムアウト機能はサポートされておらず、呼び出すとエラーが返されます。これは、SSH over TCPの複雑さによるものと考えられます。
関連リンク
- RFC 4254 - The Secure Shell (SSH) Connection Protocol
- Dave Cheney's blog post about SSH client features
- Go weekly.2011-11-18 release notes