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

[インデックス 11167] ファイルの概要

このコミットは、Go言語の実験的なパッケージ exp/proxy を新規に導入するものです。このパッケージは、様々なプロキシ(特にSOCKSv5)を介したネットワーク接続のトンネリングをクライアント側でサポートすることを目的としています。これはAPIの基礎を築くための初期スケッチであり、完全な実装ではありません。

コミット

commit 423a09760bf90fe16dc03251792360daf8a99de0
Author: Adam Langley <agl@golang.org>
Date:   Sat Jan 14 10:44:35 2012 -0500

    exp/proxy: new package
    
    exp/proxy provides client support for tunneling connections through
    various proxies.
    
    This is an initial, incomplete sketch of the code to lay down an
    API.
    
    R=golang-dev, r, r, bradfitz, rsc
    CC=golang-dev
    https://golang.org/cl/5490062

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/423a09760bf90fe16dc03251792360daf8a99de0

元コミット内容

exp/proxy: new package

exp/proxy provides client support for tunneling connections through
various proxies.

This is an initial, incomplete sketch of the code to lay down an
API.

R=golang-dev, r, r, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/5490062

変更の背景

このコミットの背景には、Go言語の標準ライブラリにプロキシ経由でのネットワーク接続機能を追加するというニーズがあります。多くの企業ネットワークや特定の環境では、インターネットへの直接接続が制限されており、HTTPプロキシやSOCKSプロキシなどを介して通信を行う必要があります。この exp/proxy パッケージは、そのような環境下でGoアプリケーションが外部リソースにアクセスできるようにするための基盤を提供することを目的としています。

コミットメッセージにある「initial, incomplete sketch of the code to lay down an API」という記述から、この時点ではまだプロキシ機能の全体像が固まっておらず、まずは基本的なAPI設計とSOCKSv5プロキシの最小限の実装を行うことで、今後の開発の足がかりとしようとしていることが伺えます。

前提知識の解説

このコミットを理解するためには、以下のネットワークおよびGo言語に関する基本的な知識が必要です。

  • プロキシ (Proxy): クライアントとサーバーの間に入って通信を中継するサーバーのことです。セキュリティ、匿名性、キャッシュ、アクセス制御などの目的で利用されます。
    • HTTPプロキシ: HTTP通信を中継するプロキシ。
    • SOCKSプロキシ: TCP/UDP通信を中継する汎用的なプロキシ。SOCKSv5は認証やIPv6、UDPのサポートなど、SOCKSv4よりも高機能です。
  • トンネリング (Tunneling): あるプロトコルを別のプロトコルでカプセル化して送信する技術です。プロキシ経由の接続では、クライアントからプロキシへの接続と、プロキシから最終的な宛先への接続という2段階の通信が行われ、プロキシがトンネルの役割を果たします。
  • net.Conn インターフェース: Go言語の net パッケージで定義されている、ネットワーク接続を表すインターフェースです。ReadWriteClose などのメソッドを持ち、TCP/UDP接続などを抽象化します。
  • net.Dial 関数: net パッケージの関数で、指定されたネットワーク(例: "tcp")とアドレス(例: "example.com:80")に対してネットワーク接続を確立します。
  • net.SplitHostPort 関数: ホスト名とポート番号を分離する関数です。例: "example.com:80" -> ("example.com", "80")。
  • net.ParseIP / net.ParseCIDR 関数: IPアドレスやCIDR表記のIPアドレス範囲をパースする関数です。
  • os.Getenv 関数: 環境変数の値を取得する関数です。プロキシ設定は通常、HTTP_PROXY, HTTPS_PROXY, NO_PROXY などの環境変数で指定されます。
  • net/url パッケージ: URLのパースや操作を行うためのパッケージです。プロキシのアドレスはURL形式で指定されることが多いため、このパッケージが利用されます。
  • Goの実験的パッケージ (exp): Go言語の標準ライブラリには、将来的に標準ライブラリに取り込まれる可能性のある実験的な機能が exp ディレクトリ以下に置かれることがあります。これらのパッケージはAPIが安定していない可能性があり、変更されることがあります。

技術的詳細

exp/proxy パッケージは、ネットワーク接続を確立するための抽象化として Dialer インターフェースを導入しています。これにより、直接接続、特定のホストをバイパスするプロキシ、SOCKS5プロキシなど、様々な接続方法を統一的に扱うことが可能になります。

主要なコンポーネントは以下の通りです。

  1. Dialer インターフェース:

    type Dialer interface {
        Dial(network, addr string) (c net.Conn, err error)
    }
    

    これは、net.Dial と同様に、指定されたネットワークとアドレスに接続を確立するメソッドを持つインターフェースです。プロキシ経由の接続もこのインターフェースを通じて行われます。

  2. Direct プロキシ: direct.go で定義されており、プロキシを介さずに直接ネットワーク接続を行う Dialer の実装です。これは、プロキシが不要な場合や、プロキシ設定のデフォルトとして使用されます。

  3. PerHost プロキシ: per_host.go で定義されており、特定のホストやIPアドレス範囲に対しては別の Dialer (バイパスプロキシ) を使用し、それ以外の場合はデフォルトの Dialer を使用するという、ホストごとのルーティング機能を提供します。これは、NO_PROXY 環境変数で指定されるような、プロキシを介さない例外リストを処理するために利用されます。 AddFromString メソッドは、カンマ区切りの文字列からバイパスルール(IPアドレス、CIDR範囲、ドメインゾーン、ホスト名)を解析して追加します。

  4. SOCKS5 プロキシ: socks5.go で定義されており、SOCKSv5プロトコルを実装した Dialer です。ユーザー名とパスワードによる認証もサポートしています。SOCKSv5プロトコルのハンドシェイク(バージョンネゴシエーション、認証方法の選択、認証、接続要求)を実装しています。

  5. 環境変数からのプロキシ設定 (FromEnvironment): proxy.go で定義されている FromEnvironment 関数は、all_proxy および no_proxy 環境変数を読み取り、それらの設定に基づいて適切な Dialer を構築します。

    • all_proxy: すべての接続に使用するプロキシのURLを指定します。
    • no_proxy: プロキシを介さずに直接接続するホストのリストを指定します。
  6. URLからのプロキシ設定 (FromURL): proxy.go で定義されている FromURL 関数は、与えられたURL(例: socks5://user:password@proxy.example.com:1080)から Dialer を生成します。RegisterDialerType を使用して、新しいプロキシスキーム(例: "http", "https")のハンドラを登録することも可能です。

コアとなるコードの変更箇所

このコミットでは、以下の新しいファイルが追加されています。

  • src/pkg/exp/proxy/Makefile: exp/proxy パッケージのビルド設定ファイル。
  • src/pkg/exp/proxy/direct.go: 直接接続を行う Direct Dialerの実装。
  • src/pkg/exp/proxy/per_host.go: ホストごとのプロキシルーティングを行う PerHost Dialerの実装。
  • src/pkg/exp/proxy/per_host_test.go: PerHost Dialerのテストコード。
  • src/pkg/exp/proxy/proxy.go: Dialer インターフェースの定義、環境変数からのプロキシ設定読み込み (FromEnvironment)、URLからのDialer生成 (FromURL)、およびDialerタイプの登録機能 (RegisterDialerType) を含む主要なファイル。
  • src/pkg/exp/proxy/proxy_test.go: FromURL 関数のテストコード。
  • src/pkg/exp/proxy/socks5.go: SOCKSv5プロトコルを実装した SOCKS5 Dialerの実装。

これらのファイルはすべて新規追加であり、既存のコードベースへの変更は含まれていません。

コアとなるコードの解説

src/pkg/exp/proxy/proxy.go

  • Dialer インターフェース: ネットワーク接続を抽象化する中心的なインターフェース。

  • FromEnvironment() 関数:

    func FromEnvironment() Dialer {
        allProxy := os.Getenv("all_proxy")
        if len(allProxy) == 0 {
            return Direct // all_proxyが設定されていなければ直接接続
        }
    
        proxyURL, err := url.Parse(allProxy)
        if err != nil {
            return Direct // URLパースエラーなら直接接続
        }
        proxy, err := FromURL(proxyURL, Direct) // all_proxyで指定されたプロキシを構築
        if err != nil {
            return Direct // プロキシ構築エラーなら直接接続
        }
    
        noProxy := os.Getenv("no_proxy")
        if len(noProxy) == 0 {
            return proxy // no_proxyが設定されていなければ構築したプロキシをそのまま返す
        }
    
        perHost := NewPerHost(proxy, Direct) // no_proxy設定を処理するためPerHostを構築
        perHost.AddFromString(noProxy)
        return perHost
    }
    

    この関数は、環境変数 all_proxyno_proxy を解析し、それに基づいて適切な Dialer を返します。all_proxy が設定されていればそのプロキシを使用し、no_proxy が設定されていれば PerHost Dialerを使って例外処理を行います。

  • FromURL(u *url.URL, forward Dialer) (Dialer, error) 関数: URLスキーム(例: "socks5")に基づいて対応する Dialer を生成します。このコミット時点では "socks5" スキームのみが組み込みでサポートされています。RegisterDialerType で登録されたカスタムスキームも処理できます。

src/pkg/exp/proxy/direct.go

  • Direct 変数:
    var Direct = direct{}
    
    direct 型のゼロ値で初期化された Direct 変数。これはプロキシを介さない直接接続を表すシングルトンとして機能します。
  • func (direct) Dial(network, addr string) (net.Conn, error): net.Dial を直接呼び出すことで、プロキシなしの接続を確立します。

src/pkg/exp/proxy/per_host.go

  • PerHost 構造体: def (デフォルトのDialer) と bypass (バイパス用のDialer) を持ち、さらにバイパス対象のネットワーク、IPアドレス、ゾーン、ホスト名のリストを保持します。
  • NewPerHost(defaultDialer, bypass Dialer) *PerHost: PerHost Dialerのコンストラクタ。
  • Dial(network, addr string) (c net.Conn, err error): 接続先のアドレスを解析し、dialerForRequest メソッドを使って適切な Dialer (デフォルトかバイパスか) を選択し、その DialerDial メソッドを呼び出します。
  • dialerForRequest(host string) Dialer: ホスト名またはIPアドレスがバイパスリストに含まれるかどうかをチェックし、bypass Dialerまたは def Dialerを返します。
  • AddFromString(s string): カンマ区切りの文字列からバイパスルールを解析し、AddIP, AddNetwork, AddZone, AddHost メソッドを呼び出して内部リストに追加します。

src/pkg/exp/proxy/socks5.go

  • SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error): SOCKSv5プロキシの Dialer を構築する関数。プロキシのアドレス、認証情報、およびプロキシ自体への接続に使用する forward Dialer(通常は Direct)を受け取ります。
  • socks5 構造体: SOCKSv5プロキシの接続情報(ユーザー名、パスワード、プロキシのアドレス、転送用Dialer)を保持します。
  • Dial(network, addr string) (net.Conn, error): SOCKSv5プロトコルに従ってプロキシ経由で接続を確立します。
    1. プロキシへの接続: まず s.forward.Dial を使ってSOCKSv5プロキシサーバー自体に接続します。
    2. SOCKSv5ハンドシェイク:
      • 認証方法のネゴシエーション: クライアントはサポートする認証方法(認証なし、ユーザー名/パスワード認証など)をプロキシに通知します。
      • 認証: プロキシがユーザー名/パスワード認証を要求した場合、クライアントは認証情報を送信します。
      • 接続要求: クライアントは、最終的な接続先のアドレスとポート、および接続タイプ(CONNECTコマンド)をプロキシに送信します。
    3. 応答の処理: プロキシからの応答を読み取り、接続が成功したか、エラーが発生したかを確認します。

この実装は、SOCKSv5プロトコルの基本的なフロー(RFC 1928)に厳密に従っています。

関連リンク

参考にした情報源リンク