Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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種類があります:

  1. ローカルポートフォワーディング - ローカルポートへの接続をリモートサーバー経由で別のホストに転送
  2. リモートポートフォワーディング - リモートサーバーのポートへの接続をローカルホストに転送
  3. ダイナミックポートフォワーディング - SOCKSプロキシとして動作

このコミットでは、direct-tcpipチャンネルを使用したローカルポートフォワーディングの実装が追加されました。

技術的詳細

実装されたAPI

新しく実装されたAPIは以下の通りです:

  1. Dial(n, addr string) (net.Conn, error) - 簡単なダイアル関数
  2. DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) - TCP専用のダイアル関数
  3. 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の複雑さによるものと考えられます。

関連リンク

参考にした情報源リンク