[インデックス 10490] ファイルの概要
このコミットは、Go言語の実験的なSSHパッケージ exp/ssh
に対して、direct-tcpip
転送機能のシンプルな機能テストを追加するものです。具体的には、SSHトンネルを介してHTTPおよびHTTPSリクエストをプロキシするテストが導入されています。
コミット
commit f2c858749a7d5c50c85fad64e7bba2c34ecae3e9
Author: Dave Cheney <dave@cheney.net>
Date: Wed Nov 23 09:38:11 2011 -0500
exp/ssh: add simple tcpip.go functional tests
R=rsc, agl, n13m3y3r, bradfitz
CC=golang-dev
https://golang.org/cl/5385041
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f2c858749a7d5c50c85fad64e7bba2c34ecae3e9
元コミット内容
exp/ssh: add simple tcpip.go functional tests
R=rsc, agl, n13m3y3r, bradfitz
CC=golang-dev
https://golang.org/cl/5385041
変更の背景
このコミットの主な背景は、Go言語の exp/ssh
パッケージにおける direct-tcpip
転送機能の堅牢性を確保することにあります。exp/ssh
はGoでSSHクライアントおよびサーバーを実装するための実験的なパッケージであり、direct-tcpip
はSSHプロトコルが提供する主要な機能の一つです。この機能は、SSHクライアントがSSHサーバーに対して、特定の宛先ホストとポートへのTCP接続を確立するよう要求し、その接続をSSHトンネルを介してクライアントに転送することを可能にします。
このような重要な機能が正しく動作することを保証するためには、実際のネットワーク環境に近い形でテストを行う機能テストが不可欠です。特に、HTTPやHTTPSのような一般的なアプリケーションプロトコルがSSHトンネルを介して正しく動作するかどうかを確認することは、パッケージの実用性を評価する上で重要です。このコミットは、そのための基本的な機能テストを追加することで、将来的なバグの発見やリグレッションの防止に貢献することを目的としています。
前提知識の解説
SSH (Secure Shell)
SSHは、ネットワークを介してコンピュータを安全に操作するためのプロトコルです。主にリモートログインやコマンド実行、ファイル転送などに使用されますが、ポートフォワーディング(トンネリング)機能も提供します。SSHは、公開鍵暗号方式や共通鍵暗号方式を用いて通信を暗号化し、認証を行うことで、盗聴や改ざんを防ぎます。
SSHポートフォワーディング (トンネリング)
SSHのポートフォワーディングは、ネットワークトラフィックをSSH接続を介して安全に転送する技術です。これにより、ファイアウォールを迂回したり、暗号化されていないプロトコルを安全に利用したりすることが可能になります。ポートフォワーディングには主に以下の3種類があります。
- ローカルポートフォワーディング (Local Port Forwarding): クライアント側のポートをSSHサーバー経由でリモートの宛先に転送します。
- リモートポートフォワーディング (Remote Port Forwarding): SSHサーバー側のポートをクライアント経由でリモートの宛先に転送します。
- ダイナミックポートフォワーディング (Dynamic Port Forwarding): クライアント側にSOCKSプロキシを立て、SSHサーバー経由で任意の宛先に接続します。
このコミットでテストされている direct-tcpip
は、SSHプロトコルにおける「チャンネル」の一種で、クライアントがSSHサーバーに対して直接TCP接続を要求するものです。これは、ローカルポートフォワーディングの内部的なメカニズムとして利用されることが多く、SSHクライアントが特定の宛先(例: google.com:80
)への接続をSSHサーバーに依頼し、その接続をSSHトンネル経由でクライアントに提供するものです。
Go言語の exp/ssh
パッケージ
exp/ssh
は、Go言語の標準ライブラリの一部として提供されている、SSHプロトコルを実装するための実験的なパッケージです。exp
というプレフィックスは「experimental(実験的)」を意味し、APIがまだ安定しておらず、将来的に変更される可能性があることを示唆しています。このパッケージは、SSHクライアントとサーバーの両方をGoで構築するための機能を提供します。
Go言語の net/http
および net
パッケージ
net/http
: Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。ウェブアプリケーションの構築やHTTPリクエストの送信に広く使用されます。net
: Go言語の標準ライブラリで、ネットワークI/Oのプリミティブを提供します。TCP/UDP接続の確立、IPアドレスの解決など、低レベルのネットワーク操作を扱います。
機能テスト (Functional Tests)
機能テストは、ソフトウェアの特定の機能が要件通りに動作するかどうかを検証するテストの一種です。単体テストが個々のコンポーネントや関数を独立してテストするのに対し、機能テストはシステム全体または主要なサブシステムが連携して正しく動作することを確認します。この場合、SSHトンネルを介したHTTP/HTTPS通信という、exp/ssh
パッケージの主要な機能がエンドツーエンドで動作するかを検証しています。
技術的詳細
このコミットで追加されたテスト tcpip_func_test.go
は、exp/ssh
パッケージの direct-tcpip
転送機能を利用して、HTTPおよびHTTPSリクエストをプロキシするシナリオを検証します。
テストの核心は、Goの net/http
パッケージが提供する http.Client
の Transport
フィールドをカスタマイズする点にあります。http.Client
は通常、内部的に http.DefaultTransport
を使用してHTTPリクエストを送信しますが、Transport
フィールドにカスタムの http.RoundTripper
インターフェースを実装したオブジェクトを設定することで、リクエストの送信方法を制御できます。
このテストでは、http.Transport
構造体をカスタムの Dial
関数と共に使用しています。http.Transport
の Dial
フィールドは、HTTPリクエストを送信する際にTCP接続を確立するために呼び出される関数を指定します。通常、この関数は net.Dial
を呼び出して直接ネットワーク接続を行いますが、ここではSSH接続オブジェクト conn
の Dial
メソッドを使用するようにオーバーライドされています。
tr := &http.Transport{
Dial: func(n, addr string) (net.Conn, error) {
return conn.Dial(n, addr)
},
}
client := &http.Client{
Transport: tr,
}
これにより、client.Get(url)
が呼び出された際に、HTTPリクエストの基盤となるTCP接続がSSHトンネルを介して確立されるようになります。具体的には、conn.Dial(n, addr)
が呼び出されると、exp/ssh
パッケージはSSHサーバーに対して direct-tcpip
チャンネルを開くよう要求し、指定された addr
(例: google.com:80
)への接続をSSHサーバーに確立させます。SSHサーバーはその接続からのデータをSSHトンネル経由でクライアントに転送し、クライアントはそれを net/http
クライアントに提供します。
テストは、TestTCPIPHTTP
と TestTCPIPHTTPS
の2つの関数で構成されており、それぞれ http://google.com
と https://encrypted.google.com/
へのリクエストをSSHトンネル経由で送信し、エラーが発生しないことを確認します。テストの実行には、sshuser
と sshpass
というフラグを通じてSSHのユーザー名とパスワードが提供される必要があります。これは、テストが実際のSSHサーバーへの接続を試みるためです。
コアとなるコードの変更箇所
追加されたファイルは src/pkg/exp/ssh/tcpip_func_test.go
です。
diff --git a/src/pkg/exp/ssh/tcpip_func_test.go b/src/pkg/exp/ssh/tcpip_func_test.go
new file mode 100644
index 0000000000..261297241e
--- /dev/null
+++ b/src/pkg/exp/ssh/tcpip_func_test.go
@@ -0,0 +1,59 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+// direct-tcpip functional tests
+
+import (
+ "net"
+ "net/http"
+ "testing"
+)
+
+func TestTCPIPHTTP(t *testing.T) {
+ if *sshuser == "" {
+ t.Log("ssh.user not defined, skipping test")
+ return
+ }
+ // google.com will generate at least one redirect, possibly three
+ // depending on your location.
+ doTest(t, "http://google.com")
+}
+
+func TestTCPIPHTTPS(t *testing.T) {
+ if *sshuser == "" {
+ t.Log("ssh.user not defined, skipping test")
+ return
+ }
+ doTest(t, "https://encrypted.google.com/")
+}
+
+func doTest(t *testing.T, url string) {
+ config := &ClientConfig{
+ User: *sshuser,
+ Auth: []ClientAuth{
+ ClientAuthPassword(password(*sshpass)),
+ },
+ }
+ conn, err := Dial("tcp", "localhost:22", config)
+ if err != nil {
+ t.Fatalf("Unable to connect: %s", err)
+ }
+ defer conn.Close()
+ tr := &http.Transport{
+ Dial: func(n, addr string) (net.Conn, error) {
+ return conn.Dial(n, addr)
+ },
+ }
+ client := &http.Client{
+ Transport: tr,
+ }
+ resp, err := client.Get(url)
+ if err != nil {
+ t.Fatalf("unable to proxy: %s", err)
+ }
+ // got a body without error
+ t.Log(resp)
+}
コアとなるコードの解説
新しく追加された src/pkg/exp/ssh/tcpip_func_test.go
ファイルには、以下の主要な要素が含まれています。
-
パッケージ宣言とインポート:
package ssh
:exp/ssh
パッケージの一部としてテストが定義されています。import ("net", "net/http", "testing")
: ネットワーク操作、HTTPクライアント、テストフレームワークに必要なパッケージをインポートしています。
-
TestTCPIPHTTP
関数:if *sshuser == ""
: テストを実行するためにSSHユーザー名が設定されているかを確認します。設定されていない場合はテストをスキップします。これは、実際のSSHサーバーへの接続が必要なためです。doTest(t, "http://google.com")
:doTest
ヘルパー関数を呼び出し、http://google.com
へのHTTPリクエストをSSHトンネル経由で送信します。
-
TestTCPIPHTTPS
関数:if *sshuser == ""
: 同様にSSHユーザー名の存在を確認します。doTest(t, "https://encrypted.google.com/")
:doTest
ヘルパー関数を呼び出し、https://encrypted.google.com/
へのHTTPSリクエストをSSHトンネル経由で送信します。
-
doTest
ヘルパー関数:config := &ClientConfig{...}
: SSHクライアントの設定を定義します。ユーザー名 (*sshuser
) とパスワード (password(*sshpass)
) を使用して認証を行います。password
関数は、おそらくパスワード文字列をClientAuthPassword
が期待する形式に変換するためのヘルパーです。conn, err := Dial("tcp", "localhost:22", config)
:exp/ssh
パッケージのDial
関数を使用して、ローカルホストのポート22(標準的なSSHポート)にSSH接続を確立します。エラーが発生した場合はテストを失敗させます。defer conn.Close()
: 関数が終了する際にSSH接続を確実にクローズするためのdefer
ステートメントです。tr := &http.Transport{ Dial: func(n, addr string) (net.Conn, error) { return conn.Dial(n, addr) }, }
: ここが最も重要な部分です。http.Transport
を初期化し、そのDial
フィールドをカスタム関数でオーバーライドしています。このカスタム関数は、通常のTCP接続の代わりに、確立されたSSH接続 (conn
) のDial
メソッドを使用してネットワーク接続を確立します。これにより、net/http
クライアントからのすべての接続要求がSSHトンネルを介してルーティングされるようになります。client := &http.Client{ Transport: tr, }
: カスタムのTransport
を持つhttp.Client
を作成します。resp, err := client.Get(url)
: 作成したクライアントを使用して、指定されたURL (http://google.com
またはhttps://encrypted.google.com/
) にGETリクエストを送信します。このリクエストはSSHトンネルを介して行われます。if err != nil { t.Fatalf("unable to proxy: %s", err) }
: リクエスト中にエラーが発生した場合、テストを失敗させます。t.Log(resp)
: 成功した場合、レスポンスオブジェクトをログに出力します。これは、テストが成功したことを示すためのものです。
このテストコードは、exp/ssh
パッケージが提供する direct-tcpip
転送機能が、一般的なHTTP/HTTPSトラフィックに対して正しく機能することを確認するための、シンプルかつ効果的な機能テストの例となっています。
関連リンク
- Go CL 5385041: https://golang.org/cl/5385041
参考にした情報源リンク
- Go言語の
net/http
パッケージドキュメント: https://pkg.go.dev/net/http - Go言語の
net
パッケージドキュメント: https://pkg.go.dev/net - SSHプロトコルに関する一般的な情報 (RFC 4254 - The Secure Shell (SSH) Connection Protocol): https://datatracker.ietf.org/doc/html/rfc4254 (特に
direct-tcpip
チャンネルに関するセクション) - Go言語の
exp/ssh
パッケージドキュメント (当時のバージョンに基づく): 現在はgolang.org/x/crypto/ssh
に統合されていますが、当時のexp/ssh
の概念は共通しています。- 現在の
golang.org/x/crypto/ssh
ドキュメント: https://pkg.go.dev/golang.org/x/crypto/ssh
- 現在の
- Go言語のテストに関するドキュメント: https://go.dev/doc/code#testing
- Dave Cheney氏のブログやGoに関する記事: このコミットの作者であるDave Cheney氏はGoコミュニティで著名な人物であり、彼のブログ記事はGoのベストプラクティスや内部動作について多くの情報を提供しています。