要約

  • SOCKS5のUDP ASSOCIATEは、方式交渉を終えたTCP接続上で中継状態を作り、そのTCP接続が終わると同時に状態を失効させた。TCPが与えたのは寿命であり、UDPデータの信頼性ではない。
  • サーバーが返す中継先、期待されるクライアントIP、各データグラム内の遠隔宛先、選択済み認証方式、断片再構成は別々の判断だった。どれも単独では利用者や最終配送を証明しない。

UDPの沈黙には一つの意味がなかった

中継にデータグラムが来なくなったとき、クライアントが終了したとは限らない。アプリケーションが静かなだけかもしれず、経路が切れた可能性もある。反対に、パケットが来続けていても、最初に認証されたプロセスが送っているとは限らない。

そこでRFC 1928は、UDP自身に存在しない終了の意味をTCPから借りた。UDP ASSOCIATE要求を受け取ったTCP接続が終了すれば、UDP associationも終了する。中継ポートに後続パケットが到着しても、それだけで過去の許可は延長されない。

これはUDPをTCPへ載せる方式ではない。業務データは引き続き個別のUDPデータグラムであり、損失、重複、順序逆転は残る。TCPは方式選択と状態生成を運び、その生存がローカルな制御文脈の寿命を示した。

設計の価値は小ささにある。無接続の転送に観測可能な開始と終了を与えながら、ストリームの配送保証まで発明しなかった。

最初のデータグラムより前に三段階があった

クライアントはTCPで利用可能な認証方式を提示し、サーバーが一つを選び、その方式固有の交換を終える。その後に初めてCONNECT、BIND、UDP ASSOCIATEのいずれかを要求できた。

UDP ASSOCIATE要求には、クライアントがUDP送信に使う予定のアドレスとポートを入れられる。サーバーはそれをassociationの制限に利用できる。まだ値を知らないクライアントは両方をゼロにする必要があった。

ゼロは「誰でも使用可」ではない。要求側が具体的な送信元を提示できないというだけであり、サーバーは接続や観測、ローカルポリシーから有効な送信元を定めなければならない。

成功応答のBND.ADDRとBND.PORTは、クライアントがSOCKS UDP要求を送る中継入口を示す。複数インターフェースを持つサーバーなら、TCPで到達したアドレスと異なる値を返し得る。この値は最終宛先の名前ではない。

一つのassociationに複数の宛先が入った

クライアントから中継への各データグラムは、予約フィールド、FRAG、アドレス種別、宛先アドレス、宛先ポート、データを持つ。遠隔宛先はassociation全体ではなく、個々の封筒に記載された。

したがって、中継は透明な管ではない。データグラムごとに宛先を読み、ポリシーを適用し、SOCKSヘッダーを外して送る。遠隔ホストから返信が来れば、同じ文法で送信元を包み直してクライアントへ渡す。

ここで三つのアドレスを区別する必要がある。TCP制御接続で使ったサーバーアドレス、サーバーが返したUDP中継入口、そして各封筒の遠隔宛先である。「プロキシのアドレス」一つだけを記録すると、どの判断が失敗したかが消える。

中継がデータグラムを受け入れたことも、遠隔アプリケーションが処理したことの証明ではない。ローカル送信、経路配送、遠隔受信、業務結果にはそれぞれ別の証人がいる。

送信元IPの一致は本人確認ではなかった

RFC 1928は、UDP relayがSOCKS serverから期待するクライアントIPを取得し、それ以外の送信元から届いたデータグラムを黙って捨てるよう要求した。別アドレスの第三者が既存associationへ流量を注入するのを抑える境界である。

ただし、IPアドレスは人でもプロセスでもない。NATの背後で多数が共有し、移動で変化し、同一ホスト上の別プログラムも利用できる。比較できるのは中継で観測した送信元と保存値だけだ。

認証は先行する方式交渉が担った。RFC 1929のusername/password方式は資格情報をサブネゴシエーション中に平文で運び、盗聴可能な環境での利用を推奨しない。RFC 1961のGSS-API方式は、認証に加えてメッセージ完全性と任意の秘匿性を交渉できた。

同じSOCKS5という表示でも、選択方式によって根拠は異なる。方式名、実際の保護レベル、保護対象範囲を保存しない監視は、プロトコル番号をセキュリティ証明にすり替える。

FRAGは任意実装の短い記憶だった

SOCKS UDPヘッダーでFRAG=0は、そのデータグラムが独立していることを意味する。1から127は断片順序を表し、上位ビットが最終断片を示す。これはIPのFragment Offsetではなく、中継文法の内部状態である。

断片対応は必須ではなかった。非対応実装は、ゼロ以外のFRAGを持つデータグラムを捨てなければならない。対応実装はreassembly queueとtimerを持ち、期限切れなら断片を放棄する。処理済み最大値より小さい番号が新たに来た場合もqueueを初期化する。timerは5秒未満にできず、仕様は可能な限り断片化を避けるよう勧めた。

ここにACKや再送保証はない。完成したqueueは次の送信を可能にするだけであり、配送完了書ではない。不完全な状態を有限時間で捨てること自体が、無制限な記憶を拒む設計だった。

障害分析では、送信元不一致、FRAG非対応、順序後退、期限切れ、宛先ポリシー拒否、外向き送信後の損失を別々に数える必要がある。すべてを「UDP失敗」に畳むと、修正主体まで分からなくなる。

TCP終了は孤立した権限を残さなかった

トラフィックがある間だけassociationを残す案は、知っている中継ポートへパケットを送れる者に事実上の更新権を与える。idle timeoutだけに任せる案は、静かな正規利用と死んだクライアントを区別できない。

TCP制御接続は完全な真実ではないが、サーバー自身が作成、監視、終了できる状態である。そこへ方式結果とUDP entryを結び付け、接続消失時に失効させれば、曖昧さのコストは新規交渉へ戻る。

短いTCP障害が正常なUDPを止めること、移動時にsource bindingも壊れること、制御socketが資源を消費することは現実の負担である。しかし境界はテスト可能で、再接続によって回復できる。誰が送ったか分からないパケットで許可を延命するより、責任の所在が明確だった。

IPv4とIPv6の橋もアプリケーション層に留まった

2001年のInformational文書RFC 3089は、SOCKSをIPv6/IPv4 gatewayに使う方法を記した。両側の通信をアプリケーション層で終端し、dual-stack gatewayへ名前解決を委譲することで、IPv6アドレスを格納できない古いアプリケーションも異なるアドレス族へ到達できた。

この利用はCONNECT、BIND、UDP ASSOCIATEを継承したが、SOCKSをIP routerにはしなかった。要求名、解決結果、制御association、転送データは区別されたままである。

歴史的な教訓は、中央中継に万能の権限を与えることではない。共通仕様は検証可能な最小文法を定め、実行判断は各中継に残し、その結果を隣の層へ勝手に拡張しない。動いているコードの権限は、観測できる境界までである。

出典