Goでsshのクライアントコマンドを作ってるのだが、それにx11 forwarding機能を実装したかったのでいろいろと調べてみた。 で、以下のような通信の流れになっているので、それを踏まえて実装してみた。
- sshクライアント=>sshサーバ: sshクライアント側からsshサーバ側に、「SSH_MSG_CHANNEL_REQUEST」でx11-reqを送る(RFC4254)
- sshサーバ: x11の初期化処理が行われる(sshサーバ側のDISPLAY環境変数にlocalhost:6000+x11ディスプレイ番号が入り、ポートが開く)
- sshサーバ => sshクライアント: sshサーバ側でx11を使うアプリケーションを動作させると、sshサーバ側のDISPLAY環境変数にxプロトコルのデータを送る(RFC4254)(sshクライアント側では、そのデータの受け皿を用意しておく必要がある)
- sshクライアント: sshサーバ側から送られてきたxプロトコルのデータを受け付け、sshクライアント側のDISPLAY環境変数宛にSocket通信でデータ転送をする(sshクライアント側で、そのSocketへの転送処理をする必要がある)
ざっくり書くとこんな感じ。
で、以下のようなコードで実装ができる(動作するコードは こちら に全文を上げてる)。
ssh_term_x11forwarding.go
golang
// sshサーバ側から受け付けたx11プロトコルの通信をそのままSocketに転送する関数
func forwardX11Socket(channel ssh.Channel) {
conn, err := net.Dial("unix", os.Getenv("DISPLAY"))
if err != nil {
return
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
io.Copy(conn, channel)
conn.(*net.UnixConn).CloseWrite()
wg.Done()
}()
go func() {
io.Copy(channel, conn)
channel.CloseWrite()
wg.Done()
}()
wg.Wait()
conn.Close()
channel.Close()
}
// x11-req Requestで送信するリクエストの中身を生成するためのStruct
type x11request struct {
SingleConnection bool
AuthProtocol string
AuthCookie string
ScreenNumber uint32
}
func main() {
(snip)
// x11-req Requestで送信するデータを作成(AuthCookieはランダムな値)
payload := x11request{
SingleConnection: false,
AuthProtocol: string("MIT-MAGIC-COOKIE-1"),
AuthCookie: string(NewSHA1Hash()),
ScreenNumber: uint32(0),
}
// x11-req Requestを送信
ok, err := session.SendRequest("x11-req", true, ssh.Marshal(payload))
if err == nil && !ok {
fmt.Println(errors.New("ssh: x11-req failed"))
} else {
// sshサーバ側から送られるx11プロトコルのデータを受け付ける処理
x11channels := client.HandleChannelOpen("x11")
go func() {
for ch := range x11channels {
channel, _, err := ch.Accept()
if err != nil {
continue
}
go forwardX11Socket(channel)
}
}()
}
}
こっち にある動作するコードを使って接続するとx11 forwarding
を使ってログインシェルに接続されるので、xeyesとか実行すると動作するのが確認できる。
実装にあたり PythonのParamikoのコード とかも参考にしてみたのだけど、これだとどうも動かないのが気になる。2012年とかのコードなので、ライブラリとかも変わってしまって動かないのかな(´・ω・`)。
実は実装するの(+理解するの)に結構苦労したのだけど、出来てみるとそんなにコードも多いわけでもなかった(自分がアホなだけ)。