SOCKS リレーにおける全二重フローとバックプレッシャー
想定した応答サイズではなく、独立したバイトストリーム、部分的な I/O、有界バッファ、バックプレッシャーを中心にリレーのフローを設計します。
SOCKS ハンドシェイクは成功しました。次にリレーはクライアントから読み取り、宛先へ書き込み、完全な応答を待ってから、ようやくクライアントから再び読み取ります。この順序は、まったく正しいアプリケーションをデッドロックさせかねません。
TCP トンネルが確立されると、SOCKS はその内部のメッセージを定義しません。宛先が先に送信するかもしれません。双方が独立に送信するかもしれません。片側が短いレコードを送ってから、相手の応答を待つかもしれません。
推測したトランザクションではなく、バイトを中継する
受信呼び出しは、フラグとソケットの状態に応じて利用可能なバイトを返します。一般的なリレーに対して、アプリケーションの応答が完了したとは伝えません。任意の大きなバッファが埋まるのを待つと、相手が続行するために必要な小さなメッセージを転送できなくなることがあります。
両方向を処理するには、アプリケーションの既存の並行モデルを使います。イベントループ、非同期 I/O、または意図的に選んだ別の設計です。重要なのは、一方方向の進展が、他方に架空のメッセージ境界を要求しないことです。
2 つの有界な保留バッファを保つ
クライアントから読み取ったデータがまだ宛先に届くのを待っている間に、宛先からのデータがクライアントに届くのを待っている場合があります。それらの保留バイトを別々に追跡します。
送信は要求より少ないバイトしか書けないことがあります。書けなかった部分を保持し、捨てたりバッファ全体を再送したりしないでください。一時的に書き込みできないソケットは、正しい準備完了イベントを待つ理由であり、回り続ける理由ではありません。
バッファを制限します。宛先が遅い場合、無制限のクライアントデータを読み続けると、待ち行列がリレーのメモリに移るだけです。バックプレッシャーとは、対応する保留バッファが満杯になったら読み取りを止め、書き込みで空きができたら再開することです。
片方向の終了には方針が必要
TCP は、もう一方の方向にまだ有効なデータがある状態で、送信方向を閉じることがあります。バッファされたバイトを排出した後にその状態をどう伝えるかを決め、順序立ったクローズをエラーや期限と区別します。
これらのどれも、リレーがアプリケーションのペイロードを復号したり解析したりすることを要求しません。必要なのは正しいストリーム処理です。バッファサイズを増やすだけでは、間違った側を待つリレーや、すべての読み取りが 1 つの要求全体に対応するとみなすリレーは直りません。