[インデックス 10080] ファイルの概要
このコミットは、Go言語の実験的なSSHパッケージ(exp/ssh
)において、インタラクティブなSSHセッションを扱うための主要な型をCmd
からSession
へと変更するものです。これにより、SSHチャネルの抽象化が改善され、将来的にdirect-tcpip
やx11
といった他の種類のチャネルをサポートするための基盤が構築されます。
コミット
commit 5791233461d9eaef94f8a29cee7a1933a5c015d2
Author: Dave Cheney <dave@cheney.net>
Date: Mon Oct 24 19:13:55 2011 -0400
exp/ssh: introduce Session to replace Cmd for interactive commands
This CL replaces the Cmd type with a Session type representing
interactive channels. This lays the foundation for supporting
other kinds of channels like direct-tcpip or x11.
client.go:
* replace chanlist map with slice.
* generalize stdout and stderr into a single type.
* unexport ClientChan to clientChan.
doc.go:
* update ServerConfig/ServerConn documentation.
* update Client example for Session.
message.go:
* make channelExtendedData more like channelData.
session.go:
* added Session which replaces Cmd.
R=agl, rsc, n13m3y3r, gustavo
CC=golang-dev
https://golang.org/cl/5302054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5791233461d9eaef94f8a29cee7a1933a5c015d2
元コミット内容
このコミットは、Go言語のexp/ssh
パッケージにおいて、インタラクティブなコマンド実行やシェルセッションを扱うためのCmd
型を廃止し、より汎用的なSession
型を導入します。この変更の主な目的は、SSHプロトコルが提供する多様なチャネルタイプ(例: direct-tcpip
、x11
転送など)を将来的にサポートするための、より堅牢で拡張性の高い基盤を構築することです。
具体的な変更点としては、以下のファイルに影響があります。
client.go
:- チャネルリストの管理方法が
map
からslice
に変更され、効率と予測可能性が向上します。 - 標準出力(stdout)と標準エラー出力(stderr)を扱うための型が単一の汎用的な型に統合されます。
ClientChan
型がパッケージ外部から直接アクセスできないclientChan
(小文字始まり)にアンエクスポートされます。これは、Session
型を通じてチャネル操作を行うように設計が変更されたためです。
- チャネルリストの管理方法が
doc.go
:ServerConfig
とServerConn
に関するドキュメントが更新されます。- クライアント側のSSHセッションの例が、新しい
Session
型を使用するように修正されます。
messages.go
:channelExtendedData
メッセージの構造がchannelData
メッセージの構造に近づけられ、より一貫性のあるデータ表現になります。具体的には、Data string
フィールドがPayload []byte
に変更されます。
session.go
:- 新たに
Session
型が定義され、インタラクティブなSSHチャネルのすべての機能(環境変数の設定、擬似端末の要求、コマンド実行、シェル起動など)をカプセル化します。
- 新たに
変更の背景
SSHプロトコルは、単にリモートコマンドを実行するだけでなく、ポートフォワーディング(direct-tcpip
、forwarded-tcpip
)、X11転送、エージェント転送など、様々な種類の「チャネル」を多重化して利用できる強力な機能を持っています。
このコミット以前のexp/ssh
パッケージでは、インタラクティブなコマンド実行やシェルセッションに特化したCmd
型が使用されていました。しかし、この設計では、SSHプロトコルが提供する他のチャネルタイプを統一的に扱うことが困難でした。
この変更の背景には、以下のような課題認識があったと考えられます。
- 拡張性の欠如:
Cmd
型はインタラクティブセッションに特化しており、direct-tcpip
(クライアントからリモートへのポートフォワーディング)やx11
(X Window Systemの転送)のような、異なる性質を持つチャネルをサポートするための抽象化が不足していました。 - APIの一貫性: SSHチャネルは本質的に多重化されたストリームであり、それぞれが特定の目的を持っています。
Cmd
という特定の用途に限定された型ではなく、より汎用的なSession
という概念を導入することで、SSHチャネル全体のAPI設計に一貫性を持たせることができます。 - コードの再利用性:
Session
型を導入することで、チャネルの共通的なライフサイクル管理やデータフロー処理をSession
内に集約し、異なるチャネルタイプ間でコードを再利用しやすくなります。 - RFC 4254への準拠と将来性: RFC 4254はSSH接続におけるチャネルの確立と管理について詳細に記述しています。
Session
型への移行は、このRFCの精神により忠実に従い、将来的なプロトコル拡張や新機能の追加に対応しやすい設計を目指すものです。
このコミットは、GoのSSHパッケージがより成熟し、SSHプロトコルの全機能をより柔軟に、かつGoらしいイディオムで提供するための重要な一歩と言えます。
前提知識の解説
このコミットを理解するためには、以下の前提知識が役立ちます。
1. SSHプロトコルとチャネル
SSH(Secure Shell)は、ネットワークを介して安全にコンピュータを操作するためのプロトコルです。SSHは単一のTCP接続上で複数の論理的な「チャネル」を多重化して使用します。これにより、一つのSSH接続で同時に複数の異なるサービス(例: シェルセッション、ファイル転送、ポートフォワーディングなど)を提供できます。
- チャネル (Channel): SSH接続上で確立される論理的な通信路です。各チャネルは独立したデータストリームを持ち、特定の目的のために使用されます。
- セッションチャネル (Session Channel): 最も一般的なチャネルタイプで、リモートシェル、コマンド実行、サブシステム(例:
sftp
)の起動、擬似端末(pty)の割り当て、環境変数の設定などに使用されます。このコミットでCmd
からSession
に置き換えられる対象です。 - X11チャネル: X Window Systemのグラフィカルアプリケーションをリモートで実行するためのチャネルです。
- Direct-TCP/IPチャネル: クライアント側からリモートホストを介して別のTCPサービスに接続するためのポートフォワーディング(ローカルフォワード)に使用されます。
- Forwarded-TCP/IPチャネル: リモートホスト側からクライアントを介して別のTCPサービスに接続するためのポートフォワーディング(リモートフォワード)に使用されます。
- セッションチャネル (Session Channel): 最も一般的なチャネルタイプで、リモートシェル、コマンド実行、サブシステム(例:
2. Go言語のexp
パッケージ
Go言語の標準ライブラリには、exp
(experimental)というプレフィックスを持つパッケージが存在することがあります。これらは、まだ安定版ではないが、将来的に標準ライブラリに取り込まれる可能性のある実験的な機能やAPIを提供します。exp/ssh
もその一つであり、開発途上であることを示唆しています。exp
パッケージのAPIは、安定版になるまでに変更される可能性があります。
3. Go言語のio
パッケージとインターフェース
Go言語のio
パッケージは、I/O操作のための基本的なインターフェース(io.Reader
, io.Writer
, io.Closer
など)を定義しています。これらのインターフェースは、様々なI/Oソース(ファイル、ネットワーク接続、メモリバッファなど)に対して統一的な操作を提供するために広く利用されます。
io.Reader
:Read
メソッドを持つインターフェース。データを読み出すための抽象化。io.Writer
:Write
メソッドを持つインターフェース。データを書き込むための抽象化。io.ReadCloser
:io.Reader
とio.Closer
を組み合わせたインターフェース。io.WriteCloser
:io.Writer
とio.Closer
を組み合わせたインターフェース。
このコミットでは、Session
型がStdin io.WriteCloser
, Stdout io.ReadCloser
, Stderr io.Reader
を持つことで、標準的なGoのI/Oインターフェースを通じてSSHセッションの入出力を扱うことができるようになります。
4. Go言語のsync
パッケージと並行処理
Go言語は並行処理を強力にサポートしており、sync
パッケージはミューテックス(sync.Mutex
)などの同期プリミティブを提供します。chanlist
の変更でsync.Mutex
が使用されているのは、複数のゴルーチンからチャネルリストへの同時アクセスを安全に制御するためです。
5. RFC 4253とRFC 4254
- RFC 4253 (The Secure Shell (SSH) Transport Layer Protocol): SSH接続の基盤となるトランスポート層プロトコルについて定義しています。鍵交換、暗号化、データ整合性などが含まれます。
- RFC 4254 (The Secure Shell (SSH) Connection Protocol): SSHトランスポート層上でどのようにチャネルが確立され、多重化されるかについて定義しています。セッションチャネル、X11転送、ポートフォワーディングなどの詳細が含まれます。このコミットの
Session
型は、特にRFC 4254のセクション6「Interactive Session」に記述されている内容を実装しています。
これらの知識を持つことで、コミットの意図と技術的な詳細をより深く理解することができます。
技術的詳細
このコミットの技術的な核心は、SSHチャネルの抽象化をCmd
からSession
へと進化させる点にあります。これは、単なる名前の変更ではなく、内部的なチャネル管理、I/O処理、およびAPI設計に大きな影響を与えています。
1. Session
型の導入とCmd
の廃止
session.go
の新規追加: このファイルにSession
型が定義されます。type Session struct { Stdin io.WriteCloser Stdout io.ReadCloser Stderr io.Reader *clientChan // the channel backing this session started bool // started is set to true once a Shell or Exec is invoked. }
Session
は、Stdin
,Stdout
,Stderr
という標準的なI/Oインターフェースを持ち、Goの他のI/O操作とシームレスに連携できます。また、内部的にclientChan
を埋め込むことで、基盤となるSSHチャネルの機能にアクセスします。started
フィールドは、Exec
またはShell
が一度だけ呼び出されるべきであるというSSHセッションの特性を強制するために使用されます。Cmd
型の削除:client.go
からCmd
型が完全に削除されます。これにより、インタラクティブセッションの責任がSession
型に一元化されます。
2. clientChan
へのリファクタリングとアンエクスポート
ClientChan
からclientChan
へ:client.go
にあったClientChan
型がclientChan
(小文字始まり)にリネームされ、パッケージ外部からは直接アクセスできなくなります。これは、Session
型がclientChan
をラップし、より高レベルなAPIを提供するという設計意図を反映しています。- I/Oチャネルの汎用化: 以前の
ClientChan
はstdinWriter
,stdoutReader
,stderrReader
という具体的なI/O型を持っていましたが、新しいclientChan
はより汎用的なdata
,dataExt
,win
というチャネルを持ちます。
これにより、type clientChan struct { id, peersId uint32 data chan []byte // receives the payload of channelData messages dataExt chan []byte // receives the payload of channelExtendedData messages win chan int // receives window adjustments msg chan interface{} // incoming messages }
clientChan
はSSHチャネルの生データを扱い、その解釈とI/OインターフェースへのマッピングはSession
型とその内部のchanWriter
/chanReader
に委ねられます。
3. chanlist
のmap
からslice
への変更
client.go
のchanlist
: 以前はmap[uint32]*ClientChan
でチャネルを管理していましたが、slice
([]*clientChan
)に変更されます。
この変更は、チャネルIDの割り当てと管理をより効率的かつ予測可能にするためのものです。type chanlist struct { sync.Mutex chans []*clientChan }
newChan
メソッドは、スライス内の空きスロットを探すか、スライスの末尾に新しいチャネルを追加します。remove
メソッドは、スライスからチャネルを削除する代わりに、該当するスロットをnil
に設定します。これにより、スライスの再割り当てを避けることができます。
4. channelExtendedData
の変更
messages.go
:channelExtendedData
メッセージのData string
フィールドがPayload []byte
に変更されます。
これは、SSHプロトコルメッセージのペイロードが通常バイト列として扱われるため、より正確な表現です。type channelExtendedData struct { PeersId uint32 Datatype uint32 Payload []byte `ssh:"rest"` }
ssh:"rest"
タグは、残りのバイト列がこのフィールドに割り当てられることを示唆しています。また、client.go
のmainLoop
内で、channelExtendedData
のDatatype
が1
(stderr)の場合のみペイロードを処理するように明示的なチェックが追加されています。これはRFC 4254 5.2節に準拠した動作です。
5. I/Oヘルパー型の導入
chanWriter
とchanReader
:client.go
にstdinWriter
,stdoutReader
,stderrReader
に代わる汎用的なchanWriter
とchanReader
が導入されます。
これらの型は、type chanWriter struct { win chan int // receives window adjustments id uint32 // this channel's id rwin int // current rwin size packetWriter // for sending channelDataMsg } type chanReader struct { data chan []byte // receives data from remote id uint32 packetWriter // for sending windowAdjustMsg buf []byte }
Session
のStdin
,Stdout
,Stderr
インターフェースの実装として機能し、基盤となるclientChan
からのデータ送受信とウィンドウ調整メッセージの処理を担当します。これにより、I/OロジックがclientChan
から分離され、よりモジュール化された設計になっています。
これらの技術的な変更は、SSHチャネルの管理とI/O処理をより柔軟かつ汎用的にすることで、将来的な拡張性を高めることを目的としています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイルに集中しています。
-
src/pkg/exp/ssh/client.go
:Cmd
型および関連するstdinWriter
,stdoutReader
,stderrReader
型の定義が削除されました。ClientChan
型がclientChan
にリネームされ、アンエクスポートされました。ClientConn
のchanlist
フィールドがmap[uint32]*ClientChan
から[]*clientChan
に変更されました。ClientConn.OpenChan
メソッドがClientConn.openChan
にリネームされ、*clientChan
を返すようになりました。ClientConn.mainLoop
内のチャネルメッセージ処理ロジックが、新しいclientChan
の構造とSession
のI/O処理に合わせて変更されました。特に、channelCloseMsg
とchannelEOFMsg
の処理、およびchannelExtendedData
のDatatype
チェックが追加されました。chanlist
のnewChan
,getChan
,remove
メソッドの実装が、スライスベースの管理に合わせて変更されました。- 新しい
chanWriter
とchanReader
型が定義され、Write
およびRead
メソッドが実装されました。
-
src/pkg/exp/ssh/session.go
: (新規ファイル)Session
型が定義されました。この型はStdin
,Stdout
,Stderr
のI/Oインターフェースと、埋め込みの*clientChan
を持ちます。Session
型に、SSHセッション固有の操作(Setenv
,RequestPty
,Exec
,Shell
)をカプセル化するメソッドが追加されました。これらのメソッドは内部的にsendChanReq
を呼び出し、SSHチャネルリクエストを送信します。ClientConn
にNewSession()
メソッドが追加され、新しいSession
インスタンスを生成し、対応するchanWriter
とchanReader
を割り当てる役割を担います。
-
src/pkg/exp/ssh/doc.go
:- パッケージのドキュメントとクライアントの使用例が、
Session
型を使用するように更新されました。これにより、新しいAPIの利用方法が示されます。
- パッケージのドキュメントとクライアントの使用例が、
-
src/pkg/exp/ssh/messages.go
:channelExtendedData
構造体のData string
フィールドがPayload []byte
に変更されました。
これらの変更は、SSHチャネルの抽象化を根本的に見直し、より柔軟で拡張性の高いSession
ベースの設計へと移行したことを示しています。
コアとなるコードの解説
session.go
の Session
型
// Session implements an interactive session described in
// "RFC 4254, section 6".
type Session struct {
// Writes to Stdin are made available to the remote command's standard input.
// Closing Stdin causes the command to observe an EOF on its standard input.
Stdin io.WriteCloser
// Reads from Stdout and Stderr consume from the remote command's standard
// output and error streams, respectively.
// There is a fixed amount of buffering that is shared for the two streams.
// Failing to read from either may eventually cause the command to block.
// Closing Stdout unblocks such writes and causes them to return errors.
Stdout io.ReadCloser
Stderr io.Reader
*clientChan // the channel backing this session
started bool // started is set to true once a Shell or Exec is invoked.
}
Session
型は、SSHプロトコルにおけるインタラクティブセッション(RFC 4254, section 6)を表現します。
Stdin
,Stdout
,Stderr
: Goの標準的なI/Oインターフェースを実装しており、リモートコマンドの標準入出力にアクセスするための手段を提供します。これにより、Goの他のI/Oユーティリティ(例:io.Copy
,bufio.Reader
)と組み合わせて使用できます。*clientChan
:Session
が内部的に使用するSSHチャネルの低レベルな表現です。Session
型はclientChan
のメソッドを直接呼び出すことができます(埋め込みフィールドのため)。started
: セッションが既にExec
またはShell
コマンドで開始されているかどうかを示すフラグです。SSHプロトコルでは、一つのセッションチャネルで一度だけコマンドを実行するかシェルを起動することが一般的です。
ClientConn.NewSession()
メソッド
// NewSession returns a new interactive session on the remote host.
func (c *ClientConn) NewSession() (*Session, os.Error) {
ch, err := c.openChan("session") // "session"タイプのチャネルを開く
if err != nil {
return nil, err
}
return &Session{
Stdin: &chanWriter{ // StdinはchanWriterで実装
packetWriter: ch,
id: ch.id,
win: ch.win,
},
Stdout: &chanReader{ // StdoutはchanReaderで実装
packetWriter: ch,
id: ch.id,
data: ch.data,
},
Stderr: &chanReader{ // StderrもchanReaderで実装(dataExtチャネルを使用)
packetWriter: ch,
id: ch.id,
data: ch.dataExt,
},
clientChan: ch, // 基盤となるclientChanを埋め込む
}, nil
}
ClientConn
(SSHクライアント接続を表す)のNewSession
メソッドは、新しいインタラクティブなSSHセッションを確立するためのエントリポイントです。
- まず、内部的に
c.openChan("session")
を呼び出し、SSHサーバーに対して「セッション」タイプのチャネルを開くよう要求します。 - 成功した場合、新しく作成された
clientChan
(ch
)を基盤として、Session
構造体を初期化して返します。 Session
のStdin
,Stdout
,Stderr
フィールドは、それぞれchanWriter
とchanReader
のインスタンスで初期化されます。これらのヘルパー型は、clientChan
が提供する低レベルなチャネル(win
,data
,dataExt
)を、Goの標準I/Oインターフェース(io.WriteCloser
,io.ReadCloser
,io.Reader
)にマッピングする役割を担います。
Session.Exec()
メソッド
// Exec runs cmd on the remote host. Typically, the remote
// server passes cmd to the shell for interpretation.
// A Session only accepts one call to Exec or Shell.
func (s *Session) Exec(cmd string) os.Error {
if s.started {
return os.NewError("session already started")
}
cmdLen := stringLength([]byte(cmd))
payload := make([]byte, cmdLen)
marshalString(payload, []byte(cmd))
s.started = true
return s.sendChanReq(channelRequestMsg{
PeersId: s.id,
Request: "exec",
WantReply: true,
RequestSpecificData: payload,
})
}
Exec
メソッドは、リモートホスト上で指定されたコマンドを実行します。
s.started
フラグをチェックし、セッションが既に開始されていないことを確認します。これにより、一つのセッションチャネルで複数のコマンドを実行しようとする誤用を防ぎます。- 実行するコマンド文字列をバイト列に変換し、SSHチャネルリクエストメッセージのペイロードとして準備します。
s.started
をtrue
に設定します。s.sendChanReq
を呼び出し、SSHサーバーに対して"exec"
タイプのリクエストを送信します。このリクエストには、実行するコマンドのペイロードが含まれます。WantReply: true
は、サーバーからの応答(成功/失敗)を期待することを示します。
これらのコードは、SSHプロトコルのチャネル管理とセッションのライフサイクルをGoのイディオムに沿って抽象化し、ユーザーがSSHセッションを簡単に操作できるように設計されています。
関連リンク
- Go言語の
exp/ssh
パッケージのソースコード(当時のバージョンに近いもの):- https://cs.opensource.google/go/go/+/refs/tags/go1.0.3:src/pkg/exp/ssh/ (Go 1.0.3の時点でのexp/ssh)
- RFC 4254: The Secure Shell (SSH) Connection Protocol:
- Go言語の
io
パッケージのドキュメント:
参考にした情報源リンク
- Go言語の公式ドキュメント
- RFC 4254: The Secure Shell (SSH) Connection Protocol
- SSHプロトコルに関する一般的な技術解説記事