最近、高負荷と DDoS の防止のためにサーバーを構成する方法について説明しました。今日は、time_wait キューのトラブルについてお話します。ネットワークと積極的に連携するサービスを開発する人は、TCP プロトコルの機能に踏み込むことができます。つまり、多くの (またはすべての空き) ポートを TIME_WAIT 状態に移行します。インターネット上には表面的な情報がたくさんあり、正確ではない情報もたくさんあります。これらの状況が何であるかを検討し、それらから抜け出すための可能な方法を決定します.
TCP プロトコル:接続を閉じる
以下は、典型的な TCP 接続のライフ サイクル図です:
TCP ライフタイム スキーム
全体としては考慮しませんが、私たちにとって最も重要な部分、つまり接続を閉じることに焦点を当てます。接続の閉鎖を開始した当事者は「アクティブ」と呼ばれ、2 番目の当事者は「パッシブ」と呼ばれます。そして、どちらが接続のイニシエーターであったかは問題ではありません。
「パッシブ」側から見ると、すべてがシンプルです。 FIN パケットを受信すると、システムは適切な ACK パケットで応答する必要がありますが、データの送信を続行する権利があります。 FIN パケットを受信したため、パッシブ側の接続は CLOSE_WAIT 状態になっています。準備ができたら、応答 FIN パケットが送信され、その後、当事者は ACK パケットを待ちます。応答 FIN に対する ACK を受信すると、パッシブ側の接続が閉じられます。
「アクティブ」側の観点からは、すべてがやや複雑になります。 FIN パケットを送信した後、アクティブ側は FIN_WAIT_1 に入ります。さらに、次の 3 つの状況が考えられます。
<オール>図と説明からわかるように、アクティブ側はセッションの最後のパケットを送信します (パッシブ FIN への ACK)。彼女はこのパケットが受信されたかどうかを確認できないため、ステータスは TIME_WAIT になります。この状態では、接続は 2 * MSL (最大パケット ライフタイム) である必要があります:パッシブ側へのパケット配信時間 + 可能な応答パケットの配信時間。実際には、現在、TIME_WAIT タイマーは 1 ~ 2 分に設定されています。このタイマーが期限切れになると、接続は閉じられたと見なされます。
アウトバウンド接続の TIME_WAIT トラブル
オペレーティング システムの接続は、ローカル IP、ローカル ポート、リモート IP、リモート ポートの 4 つのパラメータによって識別されます。リモート サービスにアクティブに接続/切断しているクライアントがあるとします。 IP とリモート ポートの両方が変更されないため、新しい接続ごとに新しいローカル ポートが割り当てられます。クライアントが TCP セッションの最後のアクティブ側であった場合、この接続は TIME_WAIT 状態でしばらくブロックされます。ポート検疫よりも速く接続が確立された場合、次に接続が試行されたときに、クライアントは EADDRNOTAVAIL エラー (errno =99) を受け取ります。
アプリケーションが異なるサービスにアクセスし、エラーが発生しない場合でも、TIME_WAIT キューが大きくなり、システム リソースが占有されます。 TIME_WAIT 状態の接続は netstat で確認できます。ss ユーティリティ (-s キーを使用) で一般化された情報を確認すると便利です。
できること:
- Linux の TIME_WAIT 間隔は、カーネルを再コンパイルしないと変更できません。インターネットでは、パラメータ net.ipv4.tcp_fin_timeout への参照を「一部のシステムでは、TIME_WAIT に影響する」という文言で見つけることができます。ただし、これらのシステムが何であるかは不明です。ドキュメントによると、パラメーターは応答 FIN パケットの最大待機時間を決定します。つまり、接続が FIN_WAIT_2 で費やす時間を制限しますが、TIME_WAIT では制限しません。
- 開く接続数を減らします。このエラーは、クラスター内のネットワーク対話中に最も頻繁に観察されます。この場合、Keep-Alive を使用するのが賢明です。
- サービスを設計するとき、TIME_WAIT を反対側にシフトすることは理にかなっているかもしれません。そのためには、可能であれば TCP 接続の閉鎖を開始しないようにする必要があります。
- 接続数を減らすのが難しい場合は、複数のポートでリモート サービスを開始し、それらに順番にアクセスすることをお勧めします。
- カーネル パラメータ「net.ipv4.ip_local_port_range」は、発信接続に使用されるポートの範囲を設定します。より広い範囲 - 1 つのリモート サービスで利用できる接続が増えます。
- タフで非常に危険な方法:パラメータ net.ipv4.tcp_max_tw_buckets の値を、ip_local_port_range からの範囲内の IP の数よりも少ない値に減らします。このパラメーターは、TIME_WAIT キューの最大サイズを設定し、DOS 攻撃から保護するために使用されます。この「トリック」は、正しい解決策が開発されるまで一時的に使用できます。
- パラメータ net.ipv4.tcp_tw_reuse を有効にします。このパラメーターは、発信接続に TIME_WAIT 状態の接続を使用できるようにします。
- パラメーター net.ipv4.tcp_tw_recycle を有効にします。
- SO_LINGER モードを使用します (setsockopt で設定)。この場合、TCP セッションは閉じられず (FIN パケットの交換)、破棄されます。リセットを実行したいパーティは、RST パケットを送信します。このパケットを受信すると、接続は終了したと見なされます。ただし、プロトコルによれば、RST パケットの送信は、エラーが発生した場合 (この接続に明らかに関係のないデータを受信した場合) にのみ行う必要があります。
サーバー上のTIME_WAIT
サーバー上で TIME_WAIT キューが拡大する主な危険は、リソース不足です。
それにもかかわらず、NAT クライアントを使用する場合 (多数のサーバー クライアントが 1 つの IP の背後に配置されている場合) は、不快な事態が発生する可能性があります。ファイアウォールのポート検疫時間が短い場合、サーバーは同じポートから接続要求を受信する可能性が高く、接続はまだ閉じられていません (TIME_WAIT にあります)。この場合、次の 2 ~ 3 つのシナリオが考えられます。
- (可能性が低い) 顧客は SEQ 番号を推測しますが、その可能性は非常に低いです。この場合、動作は未定義です。
- クライアントは、正しくないパケット (サーバーの観点から見ると、SEQ 番号) を含むパケットを送信します。サーバーは、クライアントが理解できなくなった最後の ACK パケットで応答します。クライアントは通常、この ACK に RST を送信し、数秒待ってから新しい接続を試みます。サーバーで「net.ipv4.tcp_rfc1337」パラメーターが無効になっている場合 (デフォルトではオフ)、新しい試行は成功します。ただし、主にタイムアウトが原因で、パフォーマンスの低下が見られます。
- p.2 で説明されている状況で、パラメータ net.ipv4.tcp_rfc1337 が有効になっている場合、サーバーはクライアントの RST パケットを無視します。同じポートからサーバーへの接続を繰り返し試行すると失敗します。クライアントはサービスを利用できなくなります。
サーバー側でできること
<オール>- L7 バランサを使用すると、すべてのパケットが同じ IP から送信され、TIME_WAIT 接続で「ヒット」が発生しますが、この場合、tcp_tw_recycle を安全に有効にできます。
- L3 バランサーを使用する場合、サーバーは送信元 IP アドレスを認識します。同時に、IP-HASH バランシングは、1 つの NAT のすべての接続を 1 つのサーバーに転送するため、衝突の可能性も高くなります。この点では、ラウンドロビンの方が信頼性が高くなります。
- 可能であれば、ネットワーク内で NAT を使用しないでください。必要に応じて、1 対 1 の翻訳を優先することをお勧めします。
- 複数のポートでサービスをホストすることにより、利用可能な接続の数を増やすことができます。たとえば、WEB サーバーの場合、80 番目のポート 1 つではなく、ポートのプールで負荷を分散できます。
カーネル パラメータ net.ipv4.tcp_tw_reuse および net.ipv4.tcp_tw_recycle
Linux カーネルには、TCP プロトコルの要件に違反することを可能にする 2 つのパラメーターがあり、予定より早く TIME_WAIT から接続を解放します。これらのオプションは両方とも、拡張 TCP タイムスタンプ (相対的なタイムスタンプでパケットをマークする) に基づいています。
net.ipv4.tcp_tw_reuse を使用すると、TIME_WAIT で接続を新しい発信接続に使用できます。この場合、新しい TCP 接続のタイムスタンプは、前のセッションの最後の値よりも 1 桁大きくなるはずです。この場合、サーバーは、現在の接続から前の接続の「遅延」パケットを区別できます。ほとんどの場合、パラメーターの使用は安全です。 TIME_WAIT にあるはずの接続でパケットを見逃さないように決定するパスに沿って「追跡」ファイアウォールがある場合、問題が発生する可能性があります。
net.ipv4.tcp_tw_recycle は、TIME_WAIT キューの接続時間を RTO (再送信タイムアウト) 値まで短縮します。この値は、ラウンドトリップ時間 (RTT) とこの値の広がりに基づいて計算されます。同時に、最後の TCP タイムスタンプ値がカーネルに保存され、値が小さいパケットは単純に破棄されます。このオプションは、クライアントからの TCP タイムスタンプが変換中に「スキップ」された場合に、NAT の背後にあるクライアントがサービスを利用できないようにします (NAT がそれらを削除したり、独自のものに置き換えたりしても、問題はありません)。外部デバイスの設定を予測することはできないため、インターネットからアクセス可能なサーバーにこのオプションを含めることは強くお勧めしません。さらに、NAT がない (または 1-in-1 オプションが使用されている) 「内部」サーバーでは、このオプションは安全です。