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// 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年とかのコードなので、ライブラリとかも変わってしまって動かないのかな(´・ω・`)。
実は実装するの(+理解するの)に結構苦労したのだけど、出来てみるとそんなにコードも多いわけでもなかった(自分がアホなだけ)。