メインコンテンツまでスキップ

3. HTTP 経由の UDP トンネリング

  1. HTTP 経由の UDP トンネリング

HTTP 経由の UDP トンネルのネゴシエーションを可能にするために、本文書は "connect-udp" HTTP アップグレードトークンを定義する。結果として得られる UDP トンネルは、セクション 5 で定義される形式の HTTP データグラムとともに Capsule プロトコル([HTTP-DGRAM] のセクション 3.2 を参照)を使用する。

単一の HTTP ストリームに関連付けられた UDP トンネルを開始するために、クライアントは "connect-udp" アップグレードトークンを含むリクエストを発行する。トンネルのターゲットは、URI テンプレートの "target_host" および "target_port" 変数を介して、クライアントによって UDP プロキシに示される;セクション 2 を参照。

"target_host" は、DNS 名、IPv6 リテラル、および IPv4 リテラルの使用をサポートする。IPv6 スコープ付きアドレス指定ゾーン識別子はサポートされていないことに注意すること。[URI] の用語 IPv6address、IPv4address、reg-name、および port を使用して、"target_host" および "target_port" 変数は、[ABNF] の表記法を使用して、図 2 の形式に準拠しなければならない (MUST)。さらに:

  • "target_host" および "target_port" 変数は両方とも空であってはならない (MUST NOT)。

  • "target_host" に IPv6 リテラルが含まれている場合、コロン (":") はパーセントエンコードされなければならない (MUST)。たとえば、ターゲットホストが "2001:db8::42" の場合、URI では "2001%3Adb8%3A%3A42" としてエンコードされる。

  • "target_port" は、1 から 65535(両端を含む)の整数を表さなければならない (MUST)。

target_host = IPv6address / IPv4address / reg-name target_port = port

               図 2: URI テンプレート変数の形式

UDP プロキシリクエストを送信する際、クライアントは URI テンプレート展開を実行して、リクエストのパスとクエリを決定しなければならない (SHALL)。

リクエストが成功した場合、UDP プロキシは、トンネルが閉じられるまで、受信した HTTP データグラムを UDP パケットに変換し、その逆も行うことを約束する。

Capsule プロトコルの定義([HTTP-DGRAM] のセクション 3.2 を参照)により、UDP プロキシリクエストはメッセージコンテンツを運ばない。同様に、成功した UDP プロキシレスポンスもメッセージコンテンツを運ばない。

3.1. UDP プロキシ処理

UDP プロキシリクエストを受信すると:

  • 受信者が別の HTTP プロキシを使用するように設定されている場合、受信者は中継者として機能し、リクエストを別の HTTP サーバーに転送する。このような中継者は、リクエストを受信したバージョンとは異なる HTTP バージョンを使用してリクエストを転送する場合、リクエストのエンコーディングがバージョンによって異なるため(下記参照)、リクエストを再エンコードする必要がある場合があることに注意すること。

  • それ以外の場合、受信者は UDP プロキシとして機能する。受信者は、リクエストヘッダーから再構築した URI から "target_host" および "target_port" 変数を抽出し、それらのパーセントエンコーディングをデコードし、要求されたターゲットへの UDP ソケットを直接開くことによってトンネルを確立する。

TCP とは異なり、UDP はコネクションレスである。UDP ソケットを開く UDP プロキシは、宛先が到達可能かどうかを知る方法がない。したがって、ターゲットからのパケットを待たずにリクエストに応答する必要がある。ただし、"target_host" が DNS 名である場合、UDP プロキシは HTTP リクエストに応答する前に DNS 解決を実行しなければならない (MUST)。このプロセス中にエラーが発生した場合、UDP プロキシはリクエストを拒否しなければならず (MUST)、適切な Proxy-Status ヘッダーフィールド [PROXY-STATUS] を使用して詳細を送信すべきである (SHOULD)。たとえば、DNS 解決がエラーを返した場合、プロキシは [PROXY-STATUS] のセクション 2.3.2 の dns_error プロキシエラータイプを使用できる。

UDP プロキシは、オペレーティングシステムがサポートしている場合、接続済み UDP ソケットを使用できる。これにより、UDP プロキシはカーネルに依存して、正しい 5 タプルに一致する UDP パケットのみを送信させることができる。UDP プロキシが非接続ソケットを使用する場合、受信したパケットの IP 送信元アドレスと UDP 送信元ポートを検証して、それらがクライアントのリクエストと一致することを確認しなければならない (MUST)。一致しないパケットは、UDP プロキシによって破棄されなければならない (MUST)。

ソケットの寿命はリクエストストリームに結び付けられている。UDP プロキシは、リクエストストリームが開いている間、ソケットを開いたままにしなければならない (MUST)。UDP プロキシがオペレーティングシステムからソケットがもはや使用できないと通知された場合、UDP プロキシはリクエストストリームを閉じなければならない (MUST)。たとえば、これは ICMP Destination Unreachable メッセージが受信されたときに発生する可能性がある;[ICMP6] のセクション 3.1 を参照。UDP プロキシは、非アクティブ期間のためにソケットを閉じることを選択してもよい (MAY) が、ソケットを閉じる際にはリクエストストリームを閉じなければならない (MUST)。非アクティブ期間後にソケットを閉じる UDP プロキシは、2 分未満の期間を使用すべきではない (SHOULD NOT);[BEHAVE] のセクション 4.3 を参照。

成功したレスポンス(セクション 3.3 および 3.5 で定義)は、UDP プロキシが要求されたターゲットへのソケットを開き、UDP ペイロードをプロキシする意思があることを示す。成功したレスポンス以外のレスポンスは、リクエストが失敗したことを示す;したがって、クライアントはリクエストを中止しなければならない (MUST)。

UDP プロキシは、HTTP データグラムを UDP ソケットに転送する際、IP 層での断片化(フラグメンテーション)を導入してはならない (MUST NOT);過度に大きなデータグラムは黙ってドロップされる。IPv4 では、経路上の断片化を防ぐために、可能であれば Don't Fragment (DF) ビットを設定しなければならない (MUST)。将来の拡張により、これらの要件が削除される可能性がある。

UDP プロキシの実装者は、[UDP-USAGE] のガイダンスを読むことで利益を得るだろう。

3.2. HTTP/1.1 リクエスト

HTTP/1.1 [HTTP/1.1] を使用する場合、UDP プロキシリクエストは以下の要件を満たす:

  • メソッドは "GET" でなければならない (SHALL)。

  • リクエストは、UDP プロキシのオリジンを含む単一の Host ヘッダーフィールドを含まなければならない (SHALL)。

  • リクエストは、値が "Upgrade" の Connection ヘッダーフィールドを含まなければならない (SHALL)(この要件は [HTTP] のセクション 7.6.1 に従い、大文字と小文字を区別しないことに注意)。

  • リクエストは、値が "connect-udp" の Upgrade ヘッダーフィールドを含まなければならない (SHALL)。

これらの制限に準拠しない UDP プロキシリクエストは不正である。このような不正なリクエストの受信者は、エラーで応答しなければならず (MUST)、400 (Bad Request) ステータスコードを使用すべきである (SHOULD)。

たとえば、クライアントが URI テンプレート "https://example.org/.well-known/masque/udp/\{target_host\}/\{target_port\}/" で設定されており、ターゲット 192.0.2.6:443 への UDP プロキシトンネルを開きたい場合、以下のリクエストを送信できる:

GET https://example.org/.well-known/masque/udp/192.0.2.6/443/ HTTP/1.1 Host: example.org Connection: Upgrade Upgrade: connect-udp Capsule-Protocol: ?1

                図 3: HTTP/1.1 リクエストの例

HTTP/1.1 では、本プロトコルは GET メソッドを使用して WebSocket プロトコル [WEBSOCKET] の設計を模倣する。

3.3. HTTP/1.1 レスポンス

UDP プロキシは、以下の要件で応答することにより、成功したレスポンスを示さなければならない (SHALL):

  • レスポンスの HTTP ステータスコードは 101 (Switching Protocols) でなければならない (SHALL)。

  • レスポンスは、値が "Upgrade" の Connection ヘッダーフィールドを含まなければならない (SHALL)(この要件は [HTTP] のセクション 7.6.1 に従い、大文字と小文字を区別しないことに注意)。

  • レスポンスは、値が "connect-udp" の単一の Upgrade ヘッダーフィールドを含まなければならない (SHALL)。

  • レスポンスは、Capsule プロトコルを開始する HTTP レスポンスの要件を満たさなければならない (SHALL);[HTTP-DGRAM] のセクション 3.2 を参照。

これらの要件のいずれかが満たされない場合、クライアントはこのプロキシ試行を失敗として扱い、接続を中止しなければならない (MUST)。

たとえば、UDP プロキシは以下のように応答できる:

HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: connect-udp Capsule-Protocol: ?1

                図 4: HTTP/1.1 レスポンスの例

3.4. HTTP/2 および HTTP/3 リクエスト

HTTP/2 [HTTP/2] または HTTP/3 [HTTP/3] を使用する場合、UDP プロキシリクエストは HTTP Extended CONNECT を使用する。これには、サーバーが [EXT-CONNECT2] および [EXT-CONNECT3] で指定された HTTP Setting を送信すること、およびリクエストが以下の要件を持つ HTTP 疑似ヘッダーフィールドを使用することが必要である:

  • :method 疑似ヘッダーフィールドは "CONNECT" でなければならない (SHALL)。

  • :protocol 疑似ヘッダーフィールドは "connect-udp" でなければならない (SHALL)。

  • :authority 疑似ヘッダーフィールドは、UDP プロキシのオーソリティを含まなければならない (SHALL)。

  • :path および :scheme 疑似ヘッダーフィールドは空であってはならない (SHALL NOT)。それらの値は、URI テンプレート展開プロセスが完了した後の URI テンプレートからのスキームとパスを含まなければならない (SHALL)。

これらの制限に準拠しない UDP プロキシリクエストは不正である([HTTP/2] のセクション 8.1.1 および [HTTP/3] のセクション 4.1.2 を参照)。

たとえば、クライアントが URI テンプレート "https://example.org/.well-known/masque/udp/\{target_host\}/\{target_port\}/" で設定されており、ターゲット 192.0.2.6:443 への UDP プロキシトンネルを開きたい場合、以下のリクエストを送信できる:

HEADERS :method = CONNECT :protocol = connect-udp :scheme = https :path = /.well-known/masque/udp/192.0.2.6/443/ :authority = example.org capsule-protocol = ?1

                  図 5: HTTP/2 リクエストの例

3.5. HTTP/2 および HTTP/3 レスポンス

UDP プロキシは、以下の要件で応答することにより、成功したレスポンスを示さなければならない (SHALL):

  • レスポンスの HTTP ステータスコードは 2xx(成功)の範囲でなければならない (SHALL)。

  • レスポンスは、Capsule プロトコルを開始する HTTP レスポンスの要件を満たさなければならない (SHALL);[HTTP-DGRAM] のセクション 3.2 を参照。

これらの要件のいずれかが満たされない場合、クライアントはこのプロキシ試行を失敗として扱い、リクエストを中止しなければならない (MUST)。

たとえば、UDP プロキシは以下のように応答できる:

HEADERS :status = 200 capsule-protocol = ?1

                 図 6: HTTP/2 レスポンスの例