TCP 入門
TCP 入門
普段、Webサイトなどを運営しているといった場合に、おおよそアプリケーションを動作させる分には、アプリケーションレイヤーより下の部分については意識しなくても良いこともあるかもしれません。しかしながら、複数のコンポーネントを組み合わせたり、大規模になってきたり、特殊な環境、および複雑な問題に出くわすようになってきたりすると、それらを対処するためには、TCPなどのレイヤーについても、しっかりと理解しておく必要があります(雑に書き殴っているため、後半になるほどまとまりきっていませんが、ご容赦ください)。
以下の項目に分けて概要を説明します。
機能
状態遷移図
データ構造
その他
機能
概要
TCPの機能について、大きく以下の観点で説明していきます。
コネクション管理
シーケンス番号
再送制御
順序制御
ポート番号
ウィンドウ制御
フロー制御
輻輳制御
詳細
コネクション管理
目的: コネクション上でデータとACKをやりとりすることで信頼性を保証
大きく以下の流れ
コネクション確立 : 3-way handoshake
データ転送
コネクション切断: 4-Way Connection Closure
コネクションの切断
コネクションの終了は片方ずつ行う(ハーフクローズ)
TCPのデータ転送が全二重に行われている場合、一方のコネクションが先に転送し終えたとしても、もう一方のデータ転送が継続している可能性があるため
ハーフクローズによるコネクション切断が完了するまでに4つのパケットがやり取りされることになる
FINの送信側は最後のACKを受信した後、一定のタイムアウト期間を待ってからコネクションを切断する
パッシブクローズ側のFINの送信側からのFINの再送が行われない、つまりアクティブクローズ側のACKが正確にパッシブクローズ側の送信側へと返送されたことを確認する意味が込められている
クローズ処理
アクティブクローズ
FINパケットを送信する側のクローズ処理
パッシブクローズ
その後に実施される相手側のクローズ処理
TCP接続がタイムアウトする原因の例 (*)
ルーティング設定やリンクの状態に問題
FW/SG/ACLのルール(AWSではNACLやSG、プライベートサブネット内のパブリックIPをもったネットワークインターフェースのノードへの接続など)
TCPのキューが溢れている
TCP request queue(最初のSYNフレームだけを受け取る)が満杯でsyncookieが無効時(net.ipv4.tcp_syncookiesが0)
TCP accept queue(3-way handshakeが完了)が満杯時(net.ipv4.tcp_abort_on_overflowが0だとRSTを返す)
tcp_tw_recycleやtcp_timestampsパラメータが有効時で、SYNフレームのタイムスタンプが同じSource IPから増分的ではない。
FW/SGのIP接続のconntrack
シーケンス番号
目的: セグメントの順番を保証。セグメントの消失を検出。
送信側
データを送信する際に、送信するデータ量のバイト数を加える。
受信側
次に受信したいシーケンス番号をACKに格納して確認応答番号として送信側に返す
送受信双方でシーケンス番号を参照しながらデータのやり取りをおこなうために、コネクション確立時に送受信両方のTCPモジュールでシーケンス番号の初期値とACK番号の初期値を一致させておく必要がある。
再送制御
目的: 消失したパケットを補償
retransmissionの発生条件 (*)
方法1: 再送タイマー(RTO失効時)
TCPではセグメントの送出からそれに対応するACKが返されるまでの時間をRTTとして継続的に測定
送出するシーケンス番号のセグメントに対してタイマーをセットし、これに対応するACKがRTOを経過しても届かないのであれば、当該セグメントは消失したものと判断し再送を行う
TCPではデータ消失をネットワークの輻輳が原因と考える
方法2: ACKを利用(重複ACK)
fast retransmission
受信側は次に受け取るべきシーケンス番号をACKに載せて返す、すなわち、まだ受け取っていないシーケンス番号を返す。
受信側は無受信のセグメントがある場合、当該セグメントが届くまで同じシーケンス番号を記載したACKを返送し続ける。
送信側は、受信したACKに続いてさらに3回連続して同じACKを受け取ると、当該セグメントは消失したと判断し、再送タイマーのタイムアウトを待たずに再送処理を行う。
1,2回の重複ACK受信の段階においては、単にセグメントの順序が入れ替わっているだけという可能性があるため、できるだけ不要な再送を避けるようになっている
再送タイマー - RTOの決定方法
方法1: SRTT
以下の計算式
SRTT = α * SRTT + (1-α)*RTT
RTO = β * SRTT
変数
α: 平滑化係数(推奨値: 0.8~0.9)
β: 遅延変動係数(推奨値: 1.3~2.0)
RTTを計測するたびにSRTTを更新しながら、それに対していくらか大きい値をRTOとして用いる
計測したRTTのばらつきが考慮されていないため、ばらつきが大きくなると、急速なRTTの変化に対応できず、RTOがRTTよりも小さくなり、正確にセグメントが届いている場合でも誤ってタイムアウトと判断し、不要な再送処理を行う問題あり
方法2: Jacobsonによる新しいアルゴリズム
RTTの分散を利用
以下の計算式
Err = RTT - SRTT
SRTT = SRTT + g1 * Err
v = v + g2 * (|Err|-v)
RTO = SRTT + 4 * v
標準偏差ではなく平均偏差を利用する理由は、標準偏差の計算には平方根の計算が伴うためであり、平均偏差を利用することにより計算量を大幅にへらすため
RTOの計算において平均偏差の4倍をSRTTに加えている理由は、ほとんどのRTTは平均偏差の4倍以内に収まるという仮定に基づいているため
現在多くの実装がこの手法を採用
ACKの返答をもってRTTの計算が行われるが、ACKが返答されるまでに時間がかかり、無駄な再送をしてしまった場合、再送セグメントと当初のACKを用いて計算されてしまうケースがある
Karnのアルゴリズム
このような誤動作を防ぐために、再送したセグメントについてはRTTの計算を行わない
そうすると、RTOがRTTの変動に適切に追従できなくなる。そのため、安全側の対策として、再送が生じた際にはRTOを2倍にする。
再送が連続して生じた場合にはRTOが指数関数的に増加し、最大64秒まで増加し、それを超えても再送に失敗した場合は、ネットワークや受信側ホストに異常が発生していると判断し、強制的にコネクションを切断する。
インターネットのふるまいがおかしくなるとRTTの推測とタイムアウト時間の計算を分離する
順序制御
目的: 受け取ったセグメントが正しい順番となるように整理することで復元を可能にし、通信の信頼性を確保
TCPヘッダーに付与されたシーケンス番号を受信側で参照、記憶しておき、受け取ったセグメントを整理する。
ポート番号
目的: アプリケーションごとの通信セッションやTCP/UDPのコネクションの識別のために使用
トランスポート層プロトコルの情報をIPヘッダーに記載する領域としてプロトコル番号があり、どのトランスポート層プロトコルを利用するべきかを判断し、上位のTCPもしくはUDPのモジュールへと引き継ぐ。引き継がれた先のプロトコルによって、TCPヘッダー上で指定されたポート番号を使用する。
サーバー側
アプリケーションによって決まったポート番号を利用
クライアント側
任意のポート番号を利用
OSによって割り振られる
同じアプリケーションでも複数のコネクションを確立可能にする
ウィンドウ制御
swndを増減させながら、未送信のデータを転送していく方式
スライディングウィンドウの考え方を採用
rwndとcwndを比較し、小さい方がswndとして採用
ウィンドウ制御
フロー制御
受信側から通知される受け入れ可能なウィンドウサイズrwndに基づいて送信ウィンドウサイズswndを決定
受動的な制御
輻輳制御
ネットワークの輻輳をできるだけ抑えつつ、できるだけ効率よくデータを転送できるように輻輳ウィンドウサイズcwndを決定
能動的な制御
フロー制御
目的: 通信相手先のことを考慮して、受信側の機器側にあるバッファの容量を超えるデータが一度に送られてきて溢れてデータの消失を起きないようにする
受信側が許容可能なデータ量を受信ウィンドウサイズ(rwnd)として、送信側に通知しながら、送信側が送るデータ量(swnd)を調整する制御
サーバー側から受信したrwndが27008で、MSSが1460の場合、一度に一つのウィンドウ内でクライアント側が送るセグメント数は27008/1460≒19パケットになる。
輻輳制御
目的: ネットワーク全体のことを考慮して、輻輳を起こさないようにする。一度輻輳状態に陥ると、データが失われやすくなり、再送が頻発するようになるため、輻輳が悪化するという悪循環に陥り、輻輳崩壊を起こす。そのため、TCP/IPネットワーク上に送出されるパケット数を削減する。
以下の2つの処理を与えることで、できるだけ効率よくデータ転送する
輻輳が起きない限界近くまで転送量を上げる
輻輳検出後の再開時にできるだけ転送量を下げすぎないようにする
通信パケットのend-to-end遅延
種類
処理遅延
経路上のネットワーク機器における遅延
キューイング遅延
各ノードでのバッファメモリー上での転送待ち時間
伝搬遅延
ノード間リンクの信号伝搬に要する部鶴的な遅延
処理遅延や伝搬遅延は、ほぼ一定。キューイング遅延は、メモリー上にどれだけデータが蓄積されているかによって大きく変動
Delay-based輻輳制御ではRTT増大の原因が経路上におけるキューイング遅延の増大によるものであると解釈
制御手法
方法1: Loss-based
最も典型的
基本的な機能
ACKを受け取った後に、次のデータを送る
一度に送信するデータ量を徐々に増加させていく
データ消失を検知したら、送出量を減らす
送信ウィンドウサイズswndは、相手から通知される受信ウィンドウサイズrwndと送信側が持つ輻輳ウィンドウサイズcwndに基づいて決定される。基本的には小さい方が優先される。
最適なcwndは輻輳が起きる前のcwndと起きた後のcwndの間にあると推測
輻輳を検出したらいったんcwndを落として、再度増加させながらデータを送信することで最適な値周辺となるように制御
以下のアルゴリズムを使い分けることでcwndを制御
スロースタート
輻輳回避
高速リカバリー
方法2: Delay-based
RTTを利用
方法3: Loss-based + Delayed-based
Hybridの制御方式
スロースタート
目的: それぞれのアプリケーションが通信開始とともに大量のデータを送信しはじめると、すぐに輻輳が起きることが予想されるためこれを防ぐ
送信側はcwndを1セグメントサイズに設定して送信し、それに対するACKを受け取るとcwndを1セグメント増加させるため、指数的に増加する。
cwnd = cwnd + mss
受信側から通知されたウィンドウサイズrwndに達するまでこの方式に基づき転送量を上げていく
最初はMSSの値だけ送る事ができる。
セグメントの送信からACKを受信する1RTTごとに見た場合、cwndは指数関数的に増加していくように見える
時間の経過とともにデータ転送量が大幅に増える
セグメントの消失後にもこの状態でセグメントを送り続けると、再び輻輳が起こる可能性
####### スロースタートリスタート(SSR)
決められた時間アイドル状態だった場合に輻輳ウィンドウのサイズをリセットする
ネットワークの状態は接続が待機している間に変化しているかもしれず、輻輳を回避するため
HTTPキープアライブ接続のように、長期間生存し、送信とアイドル時間が交互に発生するようなTCP接続のパフォーマンスに著しい影響を与える。その場合は無効化することが推奨される。
net.ipv4.tcp_slow_start_after_idle
パラメータで有効化
輻輳回避
目的: セグメント消失時のスロースタートにおける大幅なデータ転送量を抑え、輻輳の再発を防ぐ
再送が起きた時点でのcwndの半分の値をスロースタートの閾値ssthreshとして設定し、cwndがssthreshに達した後は、ACKを受け取るたびに増やす輻輳ウィンドウの増加分を線形にゆるやかに増加する。
cwnd = cwnd + mss / cwnd
輻輳の検出時に、毎回スロースタートから再開すると転送効率が良くない
高速リカバリー
目的: 輻輳の検出後、再送時のcwndを小さくしすぎないように工夫
輻輳の程度が小さい場合に有効
タイムアウトによる再送は、輻輳の程度が大きいため、スロースタートからの再開が適している
輻輳が経度の場合の再送制御として重複ACKの受信を契機とする方法がある]
アルゴリズム種類
####### Nagleアルゴリズム
背景
Telnet等のアプリケーションが1バイトなどの非常に小さな単位のデータを繰り返し送信しようとする動作に対処
TCPヘッダー 20バイト + IPヘッダー 20バイト + イーサネットのヘッダー 14バイト、FCS 4バイト が付与されるため、オーバーヘッドが大きくなり、通信速度が低速な環境では影響が大きい
輻輳制御関連手法の先駆け
仕組み
送信側で未送信データをバッファに蓄積していく
以下のいずれかの条件を満たした際にデータを送信
未送信データ蓄積量がMSS以上
過去の送信パケットでACK未受信のものがなくなる
タイムアウト
TCP/IPネットワークにおける輻輳崩壊の危険性とそれに対する解決策の導入という観点で、移行の技術に与えた影響に意義あり
PSHビットが立っている場合でも適用される
RFC1122で実装は必須だが、リアルタイム処理の必要なプロトコルのためにオフにすることも可能
シリーウィンドウシンドローム(SWS)の問題回避が可能
SWS
受信側はバッファに余裕ができた時点で、非ゼロのウィンドウサイズを通知する。これを送信側が受け取ると、セグメントの送出を再開
ゼロウィンドウ検査: 非ゼロウィンドウサイズを通知するACKが消失するとデッドロック状態になるので、送信側は定期的にウィンドウサイズが拡大したか問い合わせる
十分なバッファサイズの回復を待たずに、小さなウィンドウサイズを受信側が通知することで、送信側が小さなセグメントを送り続けることによりSWSが引き起こされる
####### 輻輳制御アルゴリズム ######## Loss-based
ロスベース方式のTCPの輻輳制御アルゴリズムはエンドノード側が通信経路の状況を把握しない前提で、単純なアルゴリズムにて転送量の制御を行っている。この制御を大まかに解説すると、輻輳が起こらずにパケットが喪失しなかった場合はネットワークの帯域に余裕があるとみなして輻輳ウィンドウサイズを増加させて転送速度を上げる。また逆に、輻輳が起こり、パケットが喪失した場合は輻輳ウィンドウサイズを減少させて転送速度を下げて、輻輳を回避するという流れが基本となっている。このように輻輳ウィンドウサイズの増減を繰り返して転送速度を調整することにより安定した通信を行うが、安定した通信を行うためには多少のパケットロスを前提としていて、パケットロスで通信経路の状況を判断するというのが基本的な考え方である。
Tahoe
背景: 1986年10月ごろにARPANETに接続されたNSFnetにおいて実際に輻輳崩壊を観測
輻輳制御アルゴリズムの導入
スロースタート + 輻輳回避 + (高速再転送)
状況に応じてデータ送出量を調整するという考え方を導入
これまでは各端末がデータを送信する機会を得ることを中心に考えていた
輻輳が軽度な高速再転送後にcwndを最小値から再開するため、効率が悪い側面がある
スロースタート段階と輻輳回避段階に分けられるウィンドウサイズ制御
まず、閾値(以下ssthresh)を定めるために、輻輳が起こるまでパケット送信をおこない、輻輳が起こった時点の輻輳ウィンドウサイズの2分の1(cwnd/2)をssthreshと定める。それ以降は、ssthreshまでスロースタート段階で、ACKを受け取るごとに1ずつにウィンドウサイズを指数的に増加させて、ssthresh以上になると輻輳回避段階に移行し輻輳ウィンドウサイズ分の1(1/cwnd)ずつ線形的に輻輳ウィンドウサイズを増加させる。
この輻輳回避段階で再び輻輳が起こった場合は輻輳ウィンドウサイズを最小の1にまで落としてスロースタート段階からおなじ工程を繰り返すようになっている。
速度を落としすぎてしまう
Fast Retransmitアルゴリズムといった高速再送アルゴリズム
Fast Retransmitアルゴリズムはロストしたパケットを再送タイマのタイムアウトを待たずに、迅速にパケットを再送するアルゴリズムである。受信側ノードから再送要求する確認応答パケット(ACKパケット)が重複して3つ到着した場合はデータパケットが破棄された可能性が高いと判断し再送タイマのタイムアウトを待たずに、当該パケットを迅速に転送する。その後はスロースタート段階に移行
Reno
背景: Tahoeの高速再転送後のcwndが最小値から再開する効率の悪さを改善
Tahoeの次のバージョン
Fast Recoveryアルゴリズムは、輻輳が発生したときの輻輳ウィンドウサイズの2分の1(cwnd/2)の値をssthreshに設定し、後に輻輳が発生した場合は輻輳ウィンドウサイズを1まで下げずにssthreshから輻輳回避段階に入るアルゴリズム
過剰な輻輳ウィンドウサイズの減少を避けられるため高速な転送が可能
高速再転送後にも継続してデータを送信し続けるように動作
スロースタート + 輻輳回避 + (高速再転送 + 高速リカバリー)
複数のセグメント消失時に、最初のセグメント再送後もしばらくはこれを要求するACKが送られてくるため、次の消失セグメントの重複ACKを受け取ろうにも時間がかかり、最終的にはタイムアウトになる場合もある
NewReno
背景: Renoの複数セグメントの消失時に、最初ではない消失セグメントの重複ACKを受け取るまでに時間がかかる問題に対応
複数の消失セグメントの再送に対応
RenoのFast Recoveryアルゴリズムのパケットロスト率の高い場合に対してのアルゴリズムの不具合を修正
RenoのFast Retransmitアルゴリズムでは、1つのパケットが破棄されたときにでも再送信モードに入ってしまうため、このとき新しいパケットの送信が停止される。このようなパケットの破棄が密集して発生すると、スループットが極端に落ちてしまうといった不具合があった
NewRenoでは、この不具合を解消し、1回の再送信モードで複数のパケットを再送信するように改良された。
NewReno以降の輻輳制御アルゴリズムのリファレンスモデルとして利用される
NewRenoとの親和性はTCP friendly, TCP compatibilityと呼ばれ、NewReno以降の輻輳制御アルゴリズムに対する要求条件の一つになっている
AIMDとして一般化できる
その時点で送信された最大のシーケンス番号を記録するパラメーターとしてrecoverを導入
再送セグメントに対するACKの要求シーケンス番号と比較することで、次の消失セグメントを判断可能
recover > ACK
未送信のセグメントを要求するものではない
再送処理や高速リカバリーのアルゴリズムなどに従って動作している段階
recover < ACK
未送信のセグメントが要求されている
再送処理が完了したと判断し、輻輳回避段階へと移る
スロースタート + 輻輳回避 + (高速再転送 + 改良された高速リカバリー)
特に根拠なくssthreshの値を半減する
HighSpeed
ロングファットパイプ向け(高速かつ遠距離な通信路)
Condestion avoidance状態におけるcwndの増加が大きく、またRecovery状態におけるcwndの回復が早いという特徴
上記動作は、cwndが一定値Wthreshより大きいか、パケットロス率pが一定値Pthreshより小さいときのみ実行されるため、HighSpeedが帯域を一方向的に専有することはないように配慮
AIMDを拡張した輻輳制御アルゴリズム
パラメーターα、βの値を輻輳ウィンドウサイズの関数として表す
Scalable
ロングファットパイプ向け
Congestion avoidance状態においても、指数関数的にcwndを増加させるという特徴
輻輳ウィンドウサイズ増加量を常に一定とすることで、従来手法における輻輳ウィンドウサイズが大きくなるほど輻輳ウィンドウサイズ増加速度が低下する課題の解決を図っている
BIC
ロングファットパイプ向け
ロングファットパイプだと、パケットロス時のウィンドウサイズの小さくなる幅が大きいので元の値まで戻る収束時間が伸びる。そのような環境でも適したリカバリー方法
同じロングファットパイプ向けのアルゴリズムであるScalableやHighSpeedはRTT公平性が著しく悪い問題に対して取り組み
RTT公平性とは,RTTが大きく異なるフロー間でのスループットの公平性を指す概念
HighSpeed TCPやScalable TCPでは,RTTが異なるフローがボトルネックリンクを共有した際に,RTTが小さいフローがRTTが大きいフローを追い出し,ほとんど通信できなくしてしまうという問題
以下の特徴
安定性
スケーラビリティ
RTT公平性 <- これが最大の目的。高速ネットワーク上でテールドロップキューを持つルーターにおいて複数コネクションのパケットが同時に廃棄された場合などに問題となるケースが顕在化
既存アルゴリズムとの親和性
輻輳ウィンドウサイズの変化
直前にパケット廃棄が発生した時点の輻輳ウインドウサイズ(W_max)に対する,現在の輻輳ウインドウサイズの大きさに応じてフェーズを切り替え
輻輳ウィンドウサイズ <= Wmax
増加させる2つのフェーズ
加算的な増加
輻輳ウィンドウサイズを急激に増加させることで、スケーラビリティとRTT公平性を高める
二分探索
徐々に輻輳ウィンドウサイズを増加させ、過剰なパケットロスを起こさないようにする
直前のパケットロスが発生した時点の輻輳ウィンドウサイズWmaxに対する、現在の輻輳ウィンドウサイズの大きさに応じてフェーズを切り替える
輻輳ウィンドウサイズ > Wmax
Max probing(最大値探索)
輻輳ウィンドウサイズ増加関数がWmaxとなる点に対して対象となるように輻輳ウィンドウサイズを増加させることで、次のパケットロスを探索
課題
狭帯域であったり低遅延なネットワーク環境においては帯域幅を不当に消費してしまう
輻輳ウィンドウサイズ増加の手順が加算的な増加、二分探索、Max probingという異なった複数のフェーズからなるため、プロトコルの解析が複雑であり、性能予測やネットワーク設計が困難
Hybla
NewRenoによるcwndとスループットはRTTが多くなるほど急激に小さくなることが知られており、衛星通信のようなRTTが大きい通信路においてもスループットが低下しないようにOpen状態における更新式を修正
CUBIC
現在主流となっている輻輳制御アルゴリズムの一つ
BICの改良バージョン
BICにおける輻輳ウィンドウサイズ増加関数を3次関数で置き換えることで、フェールの切り替えなどを省略し、シンプル化
輻輳ウィンドウサイズの増加量が2つの連続した輻輳イベント、すなわち、パケットロスに伴う高速リカバリー開始の間の時間間隔のみで表される
輻輳ウィンドウサイズ増加速度がRTTに依存しないため、RTT公平性の向上に寄与
RTTが小さい場合には、輻輳ウィンドウサイズ増加量を抑えるように設計されているため、既存TCPとの親和性が高い
課題
パケットロスが発生しない限り際限なく輻輳ウィンドウサイズを増加させていくため、転送経路上のルーター等のバッファをパケットロスが発生するまで積極的に埋め尽くしてしまう
ネットワーク上のバッファが多いと、キューイング遅延の増大を引き起こす
メモリーの低価格化に伴う、ルーターやスイッチ等のネットワーク機器に搭載されるバッファメモリーのサイズが増加傾向で問題の顕在化
バッファサイズが大きい
メリット: パケットロスが起こりにくい。バースト耐性
デメリット: バッファ遅延の増大。バッファブロート
バッファブロート対策手法
パケットを中継するネットワーク機器側における対策
AQM(Active Queue Management)
バッファエモリーすなわちキューがいっぱいになる前に、前もって積極的にパケットロスを始めることでバッファ溢れを起こさないようにする
アルゴリズム例
RED(Random Early Detection)
キュー長がある閾値を超過した場合には入力パケットを廃棄し始める。キュー長が長いほど、パケットロス率を高くすることで、バッファ溢れの発生を防ぐ
重み付けRED
輻輳イベントを契機にデータ送信量を調整するため、原理的に輻輳の発生を避けられない
近年はデータ転送における信頼性が向上したことから、パケットロスの主な要因はバッファ溢れによるもの
ランダムなパケットロスに起因する重複ACKと、輻輳に起因する重複ACKを個別できないため、無線通信において過剰に送信レートが低くなる
######### 輻輳回避アルゴリズム
AIMD(Additive Increase/Multiplicative Decrease)
最小分散制御
下方的増加と倍数的減少
パケットロスが生じると輻輳ウィンドウサイズを半分にし、その後パケット往復ごとに決まった量だけゆっくりとウィンドウを増加させる。
保守的
PRR(Proportional Rate Reduction)
比例的レート縮小
Linux 3.2以上のデフォルト
######## Delayed-based
遅延ベース方式は、RTTの計測値(実測値)と推測値(計算値)を用いて、計測値が推測値を下回れば空いているとみなしウィンドウサイズを大きくし、計測値が推測値を上回れば混雑してきたとみなし、輻輳ウィンドウサイズを小さくするといったアルゴリズムになっている。そのため、輻輳が起こる前に適切な輻輳ウィンドウサイズに調節し、通信速度を制御できるため、ロスベース方式よりもパケットロスが少ない上、帯域を有効的に使用することが可能になっている。
Vegas
ウィンドウサイズ制御にRTTを利用し、考え方を変えた !
ルータやスイッチ等のネットワーク機器に搭載されるバッファメモリのサイズが増加してきた
バッファあふれが発生するまでデータ送信量をどんどん増加させていき,送出されたパケットが各ネットワーク機器のバッファを埋め尽くすことによる影響が,RTTの増大として表れる
タイムアウトによるデータ再送なども考慮すると,Loss-based輻輳制御アルゴリズムはバッファ遅延増大を悪化させやすい傾向が非常に強い
詳細な通信経路の状態を推測する機能が搭載されて、混雑の具合に応じた転送速度の調節が可能になっている。
通信経路の状態の把握にはラウンド・トリップ・タイム(以下RTT)を利用する。
Vegasの輻輳制御アルゴリズムはデータ転送しながらRTTを計測し、Actual ThrougputとExpected Througputの2つのスループットを比較する。Actual Througputは実際の転送した際にかかった計測値であり、Expected Througputは転送結果から推測値である。
これらの値を比較し、『Actual>Expected』になった場合はネットワークが混んできたとみなして輻輳ウィンドウサイズを1減らし転送速度を落とす。
値が『Actual<Expected』になった場合はネットワークが空いてきたとみなして輻輳ウィンドウサイズを1増やして転送速度をあげる。
そして、『Actual=Expected』の場合はネットワークが安定しているとみなして転送速度を維持する。
RTTをもとに推定した通信路中のバッファ量Diffを唯一の指標とし、送信データ量を制御
ネットワークの混雑度合いを予測
そのため、セグメントの消失は格段に減少し、安定して高いスループットを実現
輻輳ウィンドウの更新式 !
cwnd = cwnd + 1(Diff < αv の場合)
通信路中にバッファされているパケットはほとんどなく、輻輳の可能性は低い
cwnd = cwnd - 1(Diff > βv の場合)
通信路中にバッファされているパケットがたくさんあり、輻輳の可能性が高い
cwnd = cwnd(上記以外)
αv, βv送信バッファ内に保持するセグメントの下限値と上限値。αvとβvの間にDiffが収まるようにcwndを制御。典型的には、αv=1 βv=3
Diff = (cwnd/RTTmin - cwnd/RTT)*RTTmin
アグレッシブ性が非常に低く、RTTが増大するとすぐに輻輳ウィンドウサイズを減少させてしまうため、Loss-based輻輳制御アルゴリズムに追い出されやすく、それらと共存することが難しい
従来から一般的に利用されてきた手法がLoss-basedのNewRenoで、その大体がCUBICであることを考えると、実際にインターネットで利用するのは難しかった
FAST TCP
広帯域環境向け
BBR
現在主流となっている輻輳制御アルゴリズムの一つ
Linuxカーネル4.9以降で利用可能
従来主流だったLoss-based輻輳制御におけるパケットロスを契機とした輻輳検知では遅すぎる
パケットロスに気付いてから何とかする物なので、受け側が輻輳に気付いた時には既にスイッチで沢山のパケットが溢れている。そこから再送するのでパケットが揃うまでにはかなりの時間がかかり、レイテンシを気にする用途では使い辛い
送信側がこれらを扱える事が明らかなネットワークでは、以下を使ってパケットロスを未然に防ぐという事が行われる
TCPでは、Delay-basedもしくはこれを組み合わせた輻輳制御
IPの拡張には輻輳した機器から即座に送信側にその事を通知するECN
Ethernetの拡張には輻輳が起きそうな時に特定の送信側に対して沈黙を要求するPFCが用意
その代わりに、パケットがバッファに蓄積され始める直前、つまりネットワークの帯域はフルに活用しつつ、バッファ遅延を発生させないという状態を理想的な状況とする
スループットとRTTを常にモニタリングし、データ送出量とRTTの関係を把握しながらデータ送信速度を調節することで、ネットワークが処理可能な範囲内での最大スループットを出すことを目指す
2つの指標を利用
RTprop(round-trip propagation time)
RTTのことであり、ACKを用いて計測した値
BtlBw(bottleneck bandwidth)
ボトルネックリンク帯域のことであり、TCPのスループットを決定づけるのはボトルネックリンクにおける転送速度だから。
inflightが一定の値を超過すると、データ送信量はそれ異常増えなくなる。これがボトルネックリンクとなり、TCPのスループットがこのボトルネックリンクに律速される。
inglishtを増加させても、データ送信量はBltBwを超えることはないが、RTTは増加していき、ボトルネックリンクのバッファにパケットが蓄積していき、キューイング遅延が増えていく。その後、バッファサイズを超過してパケットロスが発生する。
BBRは BDP(Bandwidth-Delay Product) =
inflight=BltBw*RTprop
となる状態を目指す。RTpropの推定
TCPではあるパケットを送信したあと、当該パケットに対するACKを受信するまでの時間を計測し、それをRTTとする
RTTt = RTpropt + ηt (ηt: キューイング遅延などによるノイズ)
RTprop(推定) = RTprop + min(ηt) = min(RTTt) T-WR <= t <= T (WR:タイムウィンドウ。一般的には数十秒程度の値を設定。過去数十秒程度に区切るのはネットワークトポロジーの変化に対応するため)
過去数十秒程度に測定されたRTTの値の中で、その最小値をRTpropとする
BtlBw
RTTと異なり、TCPにはボトルネック帯域を計測する仕組みは存在しないが、BBRではdeliveryRate、すなわちデータ到達レートを利用
パケットの送出時刻と送出データ量を記憶しておき、ACKを受信した際に、RTTと合わせてトプ立つデータ量を計算。ある時間ウィンドウに到達すぃたデータ量としてdeliveryRateを求め、これを利用してBltBwを推定
BtlBW(推定) = max(deliveryRatei) T-WB <= t <= T (WB:タイムウィンドウ。基本的にはRTTの6~10倍に設定。ネットワークトポロジーの変化に対応するため)
######## Loss-based + Delayed-based
ハイブリット方式は上記のロスベース方式と遅延ベース方式をあわせた方式である。ロスベース方式と遅延ベース方式を環境により使い分けることが可能なため、遅延ベース方式の欠点である、ロスベース方式との競合によるスループットの低下をカバーできるようになっている。
Westwood
主に無線通信向け
NewRenoの特に根拠がないsshreshの半減する挙動を改良
ACKの受信間隔から推定したend-to-endの帯域をもとに、Recovery遷移時のssthreshを更新する方法(fast recovery)を提案
更新式
ssthresh = BWE * RTTbase
cwnd = ssthresh + 3*mss
BWEはACKから推定したend-to-endの帯域、RTTbaseはRTTの最小値
Loss状態に繊維する場合も同様にssthreshを更新
ssthresh = BWE * RTTbase
cwnd = mss
Veno
Vegas + Reno
主に無線通信向け
Vegasで導入されたDiffを用いて輻輳度合いを推定することで、従来のAIMDの輻輳とランダムなパケットロスの区別ができない問題を回避
更新式
N = Diff * RTTbase = (cwnd/RTTbase - cwnd/RTT) * RTTbase
H-TCP
ロングファットパイプ向け
AIMDのパラメータであるαおよびβを、RTT等を用いて計算する点が特徴的
Illinois
ロングファットパイプ向け
従来のロングファットパイプ向けのLoss-based型輻輳制御アルゴリズムは、cwndが大きくなるに伴いその増分が大きくなるため、輻輳発生時大量のパケットが廃棄される課題があり、こちらの問題に対応
AIMDのパラメーターであるαとβの値をRTTに応じて調整することで、輻輳が発生する可能性が高い領域ではcwndの増分が小さくなるように制御
BICの問題提議や解決アプローチにに類似
YeAH
ロングファットパイプ向け
YeAHは,NewRenoと,その他のアグレッシブな輻輳制御アルゴリズム(CUBIC,HighSpeed,Scalable,H-TCP)を状況に応じて使い分ける
SlowとFastの2つのモードを使い分けることで以下を実現
ロングファットパイプにおいて帯域利用効率が高い
急激なcwndの増加によるネットワークへの過剰な負荷を避ける
Renoと公平に帯域を共有できる
RTTが異なるフロー間で公平に帯域を共有できる
ランダムなパケットロスに対して性能がロバスト
バッファが小さいリンクが存在しても高い性能を発揮
モード
Fastモード
Q < QmaxかつL<1/φを満たすとき
ScalableやH-TCPのようなアグレッシブな輻輳制御アルゴリズムと同様に振る舞う
Slowモード
NewRenoと同様に振る舞う
Q > Qmaxのときは、通信路駐にバッファされているデータ量を減少させるために、RTTごとにcwndをQずつ小さくする点が異なる
Slowモードで動作した回数とFastモードで動作した回数を計算
バッファを一切考慮しないアルゴリズムと共存しているかを推定し、結果に応じて処理を切り替える
NewReno等のバッファを一切考慮しない輻輳制御アルゴリズムと共存する場合、YeAHが減少させたデータバッファを食いつぶしてしまう
Compound TCP(CTCP)
Windows VistaとWindows Server 2008で初採用
Windows系OSがVegasに移行するための前準備として登場した。登場した経緯としてRenoとVegasの競合問題が挙げられる。VegasはReno、New Renoなど競合することにより、適切なウィンドウサイズの調整が行うことができず、RTTの増加とウィンドウサイズの低下によるスループットの低下が指摘されている。このように、VegasはReno、New Renoに帯域を奪われてしまうため、公平な制御が行われないといった欠点を有しているが、CTCPでは、この欠点を補うためにネットワークの環境を判別しVegasとNew Renoの輻輳制御を切り替える機能を有していので競合を避けることが可能になっている。 ちなみに、CTCPは2006年に発売されたWindows Vistaに初めて搭載されたが、Vegasモードでウィンドウサイズが大きくならないというバグが発見され問題になった。そのため、画期的な採用であったはずのCTCPのデビューはMicrosoftの失態によって良いイメージが残らなかったのが皮肉な話である。
Windows Server 2019のデフォルトはCUBIC
PowerShellで
Get-NetTCPSetting | Select SettingName, CongestionProvider
によって確認可能
まとめ
Reno/NewReno(1990) ---------------> YeAH(2007)
Vegas(1995) |
Westwood(2001) |
HighSpeed(2002) ---|
Scalable(2003) ---|
H-TCP(2004) ---|
BIC(2004) -> CUBIC(2005)---|
Hybla(2005)
Illinois(2006)
BBR(2016)
以下のように実行することで、現在適用されている輻輳制御アルゴリズムや、現在の輻輳ウィンドウサイズなどがわかる。
Reference
例
####### 輻輳制御の状態確認 以下の1つ目のTCPプロトコルのソケットについては輻輳制御アルゴリズムにCUBICを使用して輻輳制御のウィンドウサイズがcwndより10であることがわかる。すなわち、一度に送信できるパケット数は10個
####### 初期の輻輳制御のウィンドウサイズの変更 初期の輻輳制御のウィンドウサイズのデフォルト値は1。モバイル環境等の貧弱な環境では、輻輳制御のウィンドウサイズが大きくなるには何度もACKのやりとりをしないといけず、難しい。
以下のように初期の輻輳制御のウィンドウサイズは10に変更することができる。すると、常にこのパケット数より多くの通信を頻繁に行うことになり、ネットワーク全体が混雑した状況になる可能性がある。一度に送受信するパケットサイズが相対的に小さければ、メリットはない。
状態遷移図
送信者側(アクティブクローズ)側で受信者側のFINに対してACKを返した後、すぐにはCLOSEDにならずにTIME-WAIT状態でMST(最大セグメント生存時間)の2倍の時間(2分間)を待つ
ソケットのgracefully shutdownを保証する不可欠な要素
ネットワーク上で起きうるパケットロストや遅延に対し、同じ送信者・受信者間で行われた別の接続が混ざらないようにするための工夫
TIME-WAIT状態というのは待ち状態であるため、高負荷環境下ではできるだけ減らしたいことがある
net.ipv4.tcp_tw_reuse(or net.ipv4.tcp_tw_recycle)を有効にすることで効果あり
実際はほとんどのOSは、最適化のため1分程度をTIME_WAIT状態で待機するように実装されている
FINとACKを同時に含んでいる場合は中間状態としてのFIN WAIT-2状態とCLOSING状態ではなく、直ちにTIMEWAIT状態に遷移する
データ構造
TCPヘッダー
フォーマット
シーケンス番号/確認応答番号
シーケンス番号は、セグメントの順番の保証もしくはセグメントの消失を検出するために、送信するデータを1バイト単位でカウントします。
例えば、シーケンス番号が1001でデータサイズが200バイトの場合、受信側は1200までのシーケンス番号のデータを受け取ったことになるので、次に受け取るシーケンス番号は1201なので、確認応答番号として1201を設定します。
コントロールフラグ
SYN
ACK
FIN
RST
URG
アプリケーションが通常のデータよりも緊急度の高いデータ、例えばアプリケーションに対する割り込みデータなどを帯域外で送るために用意されたモード
最近のアプリケーションは、URGフラグで優先付けをするより、新規コネクションを確立し、そちらを使ってデータを送る方が多い。
PSH
受信側で受け取ったデータをバッファリングせずに、すぐにアプリケーションにわたすように指示するために利用
受信側のTCPとアプリケーションの間にバッファリングするような必然性はなく、一般的にはただちにアプリケーションに渡され、この機能は意味があるものと考えられていない
明示的輻輳告知(ECN)関連 : IPがTCPと連動してECNによる輻輳制御を行う場合にのみ有効
CWR(Congestion Window Reduced)
輻輳ウィンドウの減少を通知するフィールド
ECEフィールドとセットで使用される
ECE(ECN-Echo)
輻輳が発生したことを通知するときに用いられる
データ消失の検知はネットワーク層、通知はトランスポート層という役割分担
パケットの消失はIP層にて検出され、IPヘッダーにおけるECN(Explicit Congestion Notification)フラグが有効になる
中継するルータが関与する輻輳制御方式
TCPの仕組みによる輻輳制御の方法は通信の両端における通信の状態に応じて輻輳ウィンドウの大きさを変化させることが中心
レイヤー間のパケットの受け渡しの際に、TCPヘッダーのECEフラグが有効化され、送信元に通知される
Data Offset
TCPヘッダーのサイズ
ヘッダーサイズは20KB+オプションで40KB追加されることがあり変動する。
4bitなので15まで表される。実際には4で割った値までが格納できるので、TCPヘッダーとしては60バイトまで格納可能であることを表される
Window
Send BufferとReceive BufferのうちのReceive buffersのサイズを表す
フロー制御に基本的に使用
関連RFC
RSTフラグが発生する条件 (*)
もう一方の側でサービスがshut downしている (クラッシュ等。ハーフオープンコネクションをクローズする場合)
もう一方の側でサービスがlistenしていない (すなわち、ソケットがない。存在しないポートへのコネクションの要求が来た場合)
単にRSTで閉じるアプリケーションもある (NginxのGraceful Shutdownの際など)
TLSコネクション開始の際に、クライアント、サーバー間のTLSのバージョンが一致しない
コネクションを中断した場合 (何らかの理由でコネクションを中断しなければならなくなった場合。FINの代わりにRSTを送る)
ファイアウォールがパケットをドロップせずに拒否
その他よくあるシナリオ
SO_LINGERが有効かつ、linger timeが0。現状のプロセスがソケットへの最後の参照となり、close()システムコールを呼び出す
TCP receive bufferが読み込みデータをより多く持っている場合で、ソケットへの最後の参照であるアプリケーションがソケットを閉じる
現在のTCP接続の状態がFIN_WAIT_2であり、TCP linger2 timeが0未満の場合、ソケットへの最後の参照であるアプリケーションがソケットを閉じる
ある側がTCP接続を完全に閉じ(シャットダウンを受け取り、シャットダウンを送る)。ペイロードデータ(tcp.length > 0)を含むTCPフレームを受け取る
sysctl_tcp_abort_on_overflowが有効の場合で、新規ソケットの作成に失敗時(メモリー不足時やaccept queueが満杯時)
(ALB/CLBの場合. もう一方のサービスのコネクションが終了する状況が発生する場合の原因の具体例)
ELBのアイドルタイムアウトがバックエンドのKeepAliveTimeout/Timeoutの値より小さい場合
mod_reqtimeout有効時
KeepAliveTimeout/Timeoutの値をRequestReadTimeoutヘッダーの20秒で上書きして、ELBのアイドルタイムアウトより小さくなる可能性(Apache 2.4以降デフォルトで有効)
TCP_DEFER_ACCEPTオプション有効時(AcceptFilter http none, AcceptFilter https none以外の値になっているとき) Apache 2.4以降
Event MPMを有効にし、全workerがbusy状態
TCPオプションフィールド
/include/net/tcp.h (Linux Kernel: ver. 4.14.225)
NOP(No Operation)
Data Offsetがウィンドウのバイトから4で割った値を格納するが、例えば、24バイトのように割り切れない場合はPaddingして割り切られるように調整する。No OperationはこのようなPaddingとして使用される
MSS(最大セグメントサイズ)
転送時のセグメントサイズの最大値を通知して、転送サイズを制限する。
MSSを制限することで不要なセグメントの分割を避けられる。逆に大きな値を設定することで、同一物理ネットワーク上での効率良い転送が可能となる。
MSSはあくまでもTCPコネクションで扱うセグメントの最大値を決めているだけであり、実際の物理ネットワークの最大転送単位(MTU)の制限によるIPデータグラムの分割は避けられない
MTU / MSS 違い (*)
MTU
データリンクの種類ごとに決められた最大のフレーム長
ネットワークの通信経理上に存在する機器間には、イーサネット(1500バイト)やPPPoE(1492バイト)、ATM(9180バイト)などのさまざまなデータリンクで接続されている
一回のデータ転送にて送信可能なIPデータグラムの最大値
データリンク層で定義されるため、この値はIP層のヘッダーまでを含む
ワイヤー上での最大転送量
MSS
TCPが格納するユーザデータで受信可能なセグメントサイズの最大値
TCPでは最初にMSSを決定してから通信を開始
MSSはMTUからIPやTCPヘッダーの大きさを差し引いた値として、アプリケーションデータがMSS以下となるように分割/調整される
広告されるもので、ネゴシエーションするものではない。
双方向でMSSが異なることもある
TCPのペイロード(TCPセグメントのサイズの最大値)
TCPは経路中のMTUを知る必要があり、ICMPを利用した経路MTU探索(Path MTU Discovery)が行われる。
フラグメント禁止ビットを設定してフラグメンテーションを防止する方法もあるが、その場合、データグラムがMTUよりも大きいと転送できなくなる
Path MTU Discoveryも仕組み的にはフラグメント禁止ビットを利用して事前に転送できる最大のMSSを把握し、その通信路でのMTUになると仮定する。
最も小さいMTUに合わせてMSSを決定する。
経路MTU探索を行わない場合、IPによってフラグメンテーションをさらに行わなければならず、処理が増える。
MTUという制約のもとでより効率的にデータ転送をおこなうためには、TCPが事前に適切なMSSを設定する必要がある。
Path MTU Discovery
IP パケットには DF(Don't Fragment)フラグというフラグがあり、このフラグがセットされているパケットは、ルータによる分割をしてはならない、ということになっています。
ルータがより小さい MTU を持つ回線を使ってパケットを転送しようとした時に、「分割できない」という事は、これ以上、パケットを転送する事ができない、という事を意味します。
こういった事態が発生していることを、パケットの送信元に伝えるために ICMP が使われます。
Type 3(Destination Unreachable Message)の Code 4(fragmentation needed and DF set)という ICMP がルータから送信元に送られ、この中に「○○○バイトだったら転送できるんだけど...」という情報が入っています。
送信元がこれを受け取ったら、そのサイズに合わせて IP パケットを再送します。
最終的に相手にパケットが届くまで、ICMP の情報に合わせて小さくしていけば、経路上の最小 MTU が見つかる、という仕組みです。
References
ウィンドウスケールオプション
長距離かつ広帯域で安定したネットワークではWINDOWフィールドの2バイト(16ビット)では最大値は65,535で足りない。ウィンドウは実際にはオクテット単位で動作
指定したビット分だけシフトさせる。
1GBまで増加可能
65535 * 16384 (14 scale factor) = 1,073,725,440 bytes (1 Gbps)
Window Scale は 2 の 14 乗が上限
216 * 224 Bytes = 2**30 Bytes = 1GB
中間ノードや、ルーター、ファイアウォールがこのオプションの値を書き換えたり、完全に除去することがある。
利用可能な帯域幅を活用できていない場合は、ウィンドウサイズのやりとりを確認するところから始めてみると良い
net.ipv4.tcp_window_scaling
のパラメータで有効化
タイムスタンプオプション
TCPコネクションの確立時にこのオプションを使うかどうかを合意する
セグメントの転送が完了すると、タイムスタンプ値が更新
メリット
RTT計測のための一つの手段として用意
送信側はすべてのセグメントにタイムスタンプオプションを入れて送信し、受信側はACKを送り返すときに、タイムスタンプエコー応答フィールドに元のセグメントのタイムスタンプをコピーする
ウィンドウ毎ではなく、セグメントごとのRTTの計算が可能になる。
PAWS(Protection against Wrapped Sequence Numbers)
周回シーケンス番号の防止
reorderingを判別する要素にsequence numberだけでなく、timestamp値を使用
timestampが含まれているパケットを受信すると、このtimestampを記録しておいてconnectionごとに管理
今のタイムスタンプ値よりも小さなタイムスタンプ値を持つ同じシーケンス番号のセグメントが送られてきた場合は、適切に処理をすれば良い
デメリット
脆弱性の原因になる
TCP timestampレスポンスを利用することで、リモートホストの起動時間を推測したり、TCP timestampの挙動からOSのフィンガープリントになりうる
ALB/CLBでは無効化できない
パフォーマンス面で大きな懸念があることが理由
net.ipv4.tcp_timestamps
のパラメータで有効化
SACK(Selective Acknowledgement)
累積確認応答では、ACKに入る確認応答番号は到着した連続するセグメントの最後のシーケンス番号+1
最初のセグメントが消失すると後続のセグメントが受信されていても、ACKが返す確認応答番号は最初のセグメントで送られてくる
タイムアウトに依る再送でもバッファ中のセグメントの再送を確実には止められない
不連続なセグメントブロックに対する確認応答ができる仕組み
ほとんどのOSでデフォルトで有効。両サイドでサポートされていなければならない。
TCP Fast Open(TFO)
TCP 3-way handshakeが大きなレイテンシーと考え、このオーバーヘッドを削減することを目的
SYNパケット内でのデータ転送を許可することによって実現
Linux Kernel 3.7以上でクライアントとサーバ両方でサポート
デメリット
SYNパケット内で送信できるデータには最大のサイズが存在するため、限られた種類のHTTPリクエストしか送信できない
暗号化されたCookieが必要なので、初回接続時には通常の3-way handshakeを行う必要がある。
その他
TCP Keep-alive (*)
TCP Keep-alive
L4レベル
ユースケース
Probe
コネクションが利用可能かの確認
OSレベルでほとんど常に有効になっている。Windowsではデフォルトで2時間おき
コネクションの維持
たいていアプリケーション側で有効にするもので、デフォルトではオフになっていることが多い
HTTP Keep-alive
L7レベル
ユースケース
HTTPレスポンス後、それが利用するTCP接続をオープンにしたまま維持する
Connection: Keep-aliveのヘッダーを参照
HTTP/1.1ではデフォルトで有効
HTTP/1.0ではデフォルトで Connection: Close
BDP(Bandwidth Delay Product)
帯域幅と遅延時間(RTT)の積、すなわちBDPは、ネットワークパスに転送されているデータ(パケット)の量を表す
帯域幅を高めるには、RTTを下げるか、receiver window sizeを増加させる
RTTはpeer間の物理的な距離に依存しているため、下げることは難しい。そのため、帯域幅を増やすには、receiver window sizeを増加させる必要がある
ある時点において転送しているが、ACKされていないデータの最大量
範囲を超えない程度に、ネットワークパスをフルに活用したい
大きなBDPのネットワークをlong fat network(LFN)と呼ぶ
ウィンドウサイズが16bit(65,535Byte)では足らなくなるが、Window Scale Optionを解決
LFN中のパケットロスはスループットに大きく影響する
1つでもセグメントロスがあると、fast retransmit, fast recovery algorithm等が利用できる。
2つ以上のセグメントロスがあると、これでは不十分でパケットロスがあると、パイプは排出され、スロースタートが再度始まり、再度満たされるようになるまでに複数回のRTTが必要
1つのウィンドウ内で複数のパケットが失われることに対して、Selective acknowledgments (SACKs)を導入
TCPではウィンドウ毎にRTTを図り不十分な精度なので、timstampオプションを利用して、セグメントごとのRTTを測定可能にする
シーケンス番号が32bit(4,294,967,296 byte)では足らなくなる
maximum segment lifetime (MSL) である程度解決。推奨値は2分(2MSLは4分)だが、多くの実装は30秒
MSL未満でシーケンス番号の重複があった場合に問題 -> PAWSで解決
Reference
タイマーシステム
タイムアウトタイマー
データ転送時に設定されて、データ転送に対するタイムアウトを検出
持続タイマー
継続的に起動されているタイマーで、ゼロウィンドウ検査などが行われる
終了時タイマー
TIME-WAITからCLOSED状態に繊維するための待ち時間のタイマー。2MSLの時間(多くは2分間)待つ。
キープアライブタイマー
TCPの無応答時間に対するタイマー
パケットロス
最善のTCPパフォーマンスをより引き出すために必要(Loss-basedの輻輳制御アルゴリズムにおいて)
捨てられたパケットはフィードバック機構として働き、受信側と送信側に送信速度を調整し、ネットワークの許容量を超えることを防ぎ、レイテンシを最小化
パケットロスを許容できるようなアプリケーションもある
音声、動画、ゲーム状態の更新等 -> 通常UDPの方が適切
必ずしも悪いものではない
チューニング
サーバ設定
TCP初期ウィンドウの増加
スロースタートリスタートの無効化
ウィンドウスケーリングの有効化
TCP Fast Open
アプリケーション
できるだけ少ない数のパケットを送信
不要なデータ送信を除去
送信データの圧縮
より近い場所からの送信
TCP接続の再利用
Linuxカーネル
ソケット
プロセス間通信の抽象化
IPアドレスとポート番号のペアで接続のエンドポイントを定義
接続を開始し、受け入れる
データの送受信
Gracefulに接続を閉じる
アプリケーションやプロセスは確立するTCP接続上でACCEPTを呼ぶ必要がある
プロセスは、ソケットに対して後続するread/writeコールに使われるファイルディスクリプターを返す
システムコール
socket()
エンドポイントでソケットを作成
サーバー
bind()
アプリケーションをソケットにバインド
listen()
サーバーがポート上でリッスンし始める
accept()
サーバーは、接続上でデータ転送を開始する接続を受け入れる
クライアント
connect()
クライアントがサーバーと接続を確立
write()
/read()
サーバーがデータを読み書きする
close()
接続を閉じる
TCP backlog / TCP syn backlog
TCP syn backlog(SYNキュー)
SYN-RECV状態のパケットを格納
入ってきたSYNパケットを保存
SYN+ACKパケットを送ることに責任を持ち、タイムアウトでリトライ
net.ipv4.tcp_synack_retries の回数。今現在は、TCPリトライ間隔 = 1 * 2^(N-1)
デフォルト値は5回なので、1+2+4+8+16=31秒後に最後のリトライが行われる。そのため、最終のタイムアウトは31+32秒の63秒後になる。
指定の回数を行った後、SYN backlogから削除される。
SYN+ACKパケットを送った後ACKを待機。受け取り次第取り除きAcceptキューに加える
syn backlogのサイズ
syn backlogのサイズはbacklog値を8〜
net.ipv4.tcp_max_syn_backlog
の範囲に収めた後、一つ上の2のべき乗の値に切り上げた値になる。例えば、backlog長が200ならsyn backlogサイズは256、backlog長が256ならsyn backlogサイズは512となるsyn backlogのサイズを大きくしようとするには、以下の点に注意する。
(a)
net.ipv4.tcp_max_syn_backlog
を大きくする(b) listen()で指定するbacklog値を大きくする(apacheならListenBackLogディレクティブで指定できる)
(c) backlogの値自体は
net.core.somaxconn
に切り詰められるため、net.core.somaxconn
の方も大きくしておく
SYN_RECVを貯められる数
net.ipv4.tcp_syncookies = 0
の場合SYN_RECVは以下のいずれかのうち、小さい方の数まで登録できる。
syn backlogのサイズまで
net.ipv4.tcp_max_syn_backlog
の3/4まで
これを越えると、新たにSYN_RECVは登録せず廃棄する。このため、新たにTCP接続を確立できない。
backlog=128,
tcp_max_syn_backlog=512
の場合syn backlogのサイズはbacklog値を8〜
net.ipv4.tcp_max_syn_backlog
の範囲に収めた後、一つ上の2のべき乗の値に切り上げた値になるので256tcp_max_syn_backlog
の4分の3は384なので、SYN_RECVは256まで登録される。
net.ipv4.tcp_max_syn_backlog
を大きくしても、net.core.somaxconn
が小さいと、backlogも小さく切り詰められ、syn backlogのサイズも小さくなるので、SYN_RECVの最大登録数が思ったように増えない
net.ipv4.tcp_syncookies = 1
の場合SYN-RECVはsyn backlogのサイズまで登録される
SYN-RECVがsyn backlogのサイズを越えると、SYN Flood攻撃と判断し、SYN cookiesの動作が始まる。
TCP_DEFER_ACCEPTはSYN-RECV状態をより長く維持するため、キューの制限に影響する。
値を大きくしすぎると、SYNキューの各スロットがメモリを余計に消費する。
ss -n state syn-recv sport = :80 | grep -v Netid | wc -l
コマンドで80番ポートのSYNキュー中のスロットの数を確認できる。SYN Flood攻撃などでオーバーフローさせられる
SYN Cookieを利用することで、SYN+ACKをステートレスに生成することができ、入ってきたSYNを保持しておく必要がなくなる。
Linux 4.4より前は古いロックの方法のため遅かったが、Linux 4.4からは秒間で数百万のSYN Cookieを送ることができる。
デフォルトではSYNキューがいっぱいになったソケットに対して有効になる
TCP backlog(Acceptキュー)
ESTABLISHED状態のパケットを格納
listen()システムコールでLISTEN状態にあるソケットが持つキュー
アプリケーションによって選ばれる準備ができている
すなわち、まだaccept()されていない接続が保存されている。
accept()を読んだとき、ソケットはデキューし、アプリケーションへ渡すことができる。
TCP_DEFER_ACCEPTやTCP_FASTOPENのような機能で動作は変わる。
backlogのサイズ
backlogのキュー長はlisten()時に指定されるbacklog引数の値に設定される。ただし、指定値がsysctlの
net.core.somaxconn
より大きかった場合は、net.core.somaxconn
の値に切り詰められる。すなわち、min(net.core.somaxconn, ListenBacklog)
一般的なLinuxディストリビューションのデフォルト設定値は128です。Apacheウェブサーバーの場合は、listen()時に指定されているbacklogのデフォルト値が511です。しかし、このカーネルパラメータがhard limitですので、アプリケーションで511に指定していても、実際に割り当てられているlisten backlogの数は128個になります。
TCP backlogが溢れている場合
クライアントは最後のACKを投げた時点でSYN-SENTからESTABLISHED状態になる
サーバーはLISTENからSYN-RECV状態になったまま。クライアントからのACKを受け取ることができずドロップする(
net.ipv4.tcp_abort_on_overflow=0
のとき)クライアントは同じデータを再送し、リトライ回数は
net.ipv4.tcp_retries2
で指定するnet.ipv4.tcp_abort_on_overflow=1
を指定していると、TCP backlogがあふれるとRSTを返し、再送も起こらない。
Webサーバーが遅いとき、Accept Queueが溢れ、かつ
net.ipv4.tcp_abort_on_overflow=0
でSYNに対するACKをdropしている可能性があるApache上では、
apr_socket_recv: Connection reset by peer (104)/ apr_socket_recv: Connection timed out (110)
のエラーになることもあるApache
Multi-Processing Module (MPM) はデフォルトではスレッドではなくpre-forkingなので、各サーバーがリクエストを処理し、親プロセスがサーバープールのサイズを管理
各子プロセスは一度に1つのTCP接続のみを受け付ける
プロセスの数はServerLimitディレクティブによって制限し、デフォルトでは256
回避策
以下の値を増加
Apache
ServerLimit
MaxClients= MaxRequestWorkers
Linux
ListenBackLog
net.core.somaxconn
まとめ
SYN受信時に、SYNキューが溢れている場合
net.ipv4.tcp_syncookies=0
の場合、新規SYNは捨てられるnet.ipv4.tcp_syncookies=1
の場合Acceptキューが溢れている、かつ、
req_young_len > 1
の場合、新規SYNパケットは捨てられる。req_young_len
は、SYNキューの中でSYN/ACKパケットを再送していない接続数
それ以外の場合、新規SYNパケットに対して、syncookiesを生成し、悪意のあるパケットと見なす
ACK受信時に、Acceptキューが溢れている場合
tcp_abort_on_overflow=1
の場合、TCP/IPスタックはRSTパケットで返答し、接続はSYNキューから取り除かれる。tcp_abort_on_overflow=0
の場合、スタックは接続をACK済みとみなし、すなわち、このACKパケットは無視され、接続はSYNキューのままにしておく。その後すぐに、タイマーが開始され、SYN/ACKパケットを再度送る。クライアントは再度ACKパケットをおくることができる。もし、最大リトライ回数(リトライ回数はnet.ipv4.tcp_retries2
で指定)に達すると、RSTパケットで応答され、接続がSYNキューから取り除かれる。
その他
サーバー側で
ss -plnt sport = :80 | cat
コマンドを実行したときのRecv-Qが80番ポートのAcceptキュー中のソケットの数、Send-Qがbacklogの数in-bound queue / out-bound queue
in-bound queueで溢れて捨てられるパケットは、アプリケーションでは全く認知できないので、大規模なパケット処理が必要なサーバーでは、適度にin-bound queueの長さを増加させなければなりません。
out-bound queueに送信されるパケット量がサーバーアプリケーションで調整できる
参照
strace, ss, tcpdumpコマンド等を通してbacklogの挙動を分析
TCP_DEFER_ACCEPT
Linux KernelがサポートするTCPオプション
setsockopt()で有効化。
ACCEPTシステムコール(accept())のタイミングをできるだけ遅らせる。 2.6.32以降
実際のデータが来るまでアプリケーションから隠す
CPUやメモリーを保護する。
tcp_synack_retries
のパラメータは使用しないApache 2.4ではハードコーディングされた30秒の間、SYN_RECVの状態のまま
setsockopt(4, SOL_TCP, TCP_DEFER_ACCEPT, [30], 1) = 0
2回目のSYN+ACKのリトライに対して、クライアントからのACKでソフトウェアが起動し、ソケットはESTABLISHED状態になる
Syn Flood Attack対策 (RFC 4987)
3-Way Handshake後にデータが到着するまでリスナ (Apache)が動作しない
データを待つタイムアウト 30 秒。ハードコーディング
Apacheの設定のAcceptFilterにも注意
Linux では “none” 以外の値を設定すると、TCP_DEER_ACCEPT が有効になる
AcceptFilter http none
AcceptFilter https none
apache 2.2/2.4 のデフォルトは “AcceptFitler data” となっており、TCP_DEFER_ACCEPT が既定で有効。
2.6.31 以前の linux kernel を使用している場合
OS の TCP_DEFER_ACCEPT が使用される場合、SYN+ACKを既定回数の net.ipv4.tcp_synack_retries が繰り返された後、アプリケーションレイヤに処理が渡されずにコネクションが破棄される。
ELB から見ると唐突に RST が応答される(よくない)
Default の net.ipv4.tcp_synack_retries は 5 で、約93秒 (3+6+12+24+48)間TCPアイドル状態が続いてもOK
TCPリトライ間隔 = 3 * 2^(N-1)
Linux 2.6.18段階でRTOは3秒なので、3+6+12+24+48+96=189秒
今現在は、1+2+4+8+16秒といった具合で、TCPリトライ間隔 = 1 * 2^(N-1) になっている。
ELB タイムアウトを 300秒とした場合、net.ipv4.tcp_synack_retries を 7 と設定すれば良い(3+6+12+24+48+96+192)
apache 2.2/2.4 のデフォルトは “AcceptFitler data” となっており、TCP_DEFER_ACCEPT が既定で有効。
apache 側でタイムアウト(Timeout directive)を長くしても、apache に処理が移っていないため、効果が無い。
つまりこれが Timeout を延ばしても解消できなかった理由。
対応方法
net.ipv4.tcp_synack_retries を増やす
or ”AcceptFilter none”として、Timeout を長くする
現在
OS の TCP_DEFER_ACCEPT が使用される場合、30秒(apache でハードコーディングされている)経過すると apache に処理が移る。
バックエンドからコネクション切断させないためには、
httpd24(apache2.4.x)の場合
mod_reqtimeout がデフォルトでロードされる(apachectl -M で確認)
無通信のコネクションは 「TCP_DEFER_ACCEPT: 30 秒」+「RequestReadTimeout header のデフォルト値: 20 秒」の計 50 秒で切断される。
① TCP_DEFER_ACCEPT を無効化しない場合:
TCP_DEFER_ACCEPT 30 秒 + RequestReadTimout header の合計 > ELB Idle Timeout に設定
② TCP_DEFER_ACCEPT を無効化する場合:
RequestReadTimout header > ELB Idle Timeout に設定
httpd(apache2.2.x)の場合
mod_reqtimeout はデフォルトでロードされない。
無通信のコネクションは 「TCP_DEFER_ACCEPT: 30 秒」+「Timeout directive のデフォルト値: 60 秒」の計 90 秒で切断される。
① TCP_DEFER_ACCEPT を無効化しない場合:
TCP_DEFER_ACCEPT 30 秒 + Timeout の合計 > ELB Idle Timeout に設定
② TCP_DEFER_ACCEPT を無効化する場合:
Timeout > ELB Idle Timeout に設定
KeepAliveTimeout > ELB Idle Timeout の推奨は従来通り。
Socket linger option
lingerオプションはclose()の返還を即時実行せず、つまりnon-blockingに返却せずに、blocking mannerで処理する
アプリケーションは、close()時、lingerオプションが有効なら与えられたパラメータの時間までblockされる。この間、カーネルはソケットバッファに残っているデータを送ろうと努力します。与えられた時間内にこうした処理が完了すると、すなわち正常処理されたら、一般的な方法と同様にカーネルはFINを送り、ソケットも最終的にTIME_WAIT状態になります。
与えられた時間内に正常処理されなければ、カーネルはFINの代わりにRSTを送り、ソケットは直ちに破壊される。TIME_WAIT状態のソケットも残らない
lingerオプションはデータをpeerに完全に伝達するために、アプリケーションにより制御を与える1つのオプション
一般的な処理において、ソケットはカーネルによってgracefully shutdownするのが正しい
TIME_WAIT状態のソケットを減らすためのものではない
パラメーター一覧
確認環境は以下
TCP関連のパラメータは以下のものが確認可能。
詳細
net.ipv4.tcp_tw_reuse / net.ipv4.tcp_tw_recycle
送信者側(アクティブクローズ)側で受信者側のFINに対してACKを返した後、すぐにはCLOSEDにならずにTIME-WAIT状態でMST(最大セグメント生存時間)の2倍の時間を待つ
ネットワーク上で起きうるパケットロストや遅延に対し、同じ送信者・受信者間で行われた別の接続が混ざらないようにするための工夫
TIME-WAIT状態というのは待ち状態であるため、高負荷環境下ではできるだけ減らしたいことがある
net.ipv4.tcp_tw_reuse(or net.ipv4.tcp_tw_recycle)を有効にすることで効果あり
net.ipv4.tcp_tw_recycle
Linux Kernel 4.12で廃止
パケットのタイムスタンプを TIME_WAIT に入ったときの最後のものと比較し、古ければパケットをドロップ、新しければ新しい接続にそのまま使う
内向きのリクエストであっても即、古いと見なされたパケットをドロップしてしまう
タイムスタンプはリモート側でパケットに付与されるもので、unixtime などの絶対時間ではなく uptime (マシンの起動時間=相対時間)
タイムスタンプが単調に増えていくという前提が成り立たない環境では、この処理が安全とは言えない
一番多いケースだと NAT や LB の背後にあるサーバと通信する(=1つのソース IP に複数マシンが紐づく)場合
クライアントがNAT環境である場合、一部のクライアントからのSYNパケットが失われる可能性がある
インターネットのクライアントは、NAT環境である確率が大きい。3G/LTEなどのモバイルネットワーク環境は一種の巨大なNAT環境。中国と同様にIPアドレスが足りない環境では、NATをたくさん使用する。この場合、多数のクライアントからサーバーへ接続できない障害が発生することがある
Linux 4.10からタイムスタンプオフセットがランダム化され、コネクションごとにタイムスタンプがランダム化されるようになった
タイムスタンプが単調に増加していく前提は成り立たなくなった
NLBのヘルスチェックの失敗原因になる
tcp_tw_recycleオプションを有効にするには、まず、TCP timestampとnet.ipv4.tcp_tw_reuseを有効にする必要がある
net.ipv4.tcp_tw_reuse
TIME-WAIT 状態のソケットでもタイムスタンプが1秒でも新しければ、新しい接続で再利用できるようになる
接続が外向きに限られること、なおかつ比較対象のタイムスタンプを付与しているのが自マシンであることから安全と言える
特定の状況でのみTIME-WAIT状態のソケットを再利用します。この判断ロジックには、TCP timestampという拡張オプションを使用
tcp_tw_reuseオプションを有効にするには、TCP timestampオプションも有効にする必要がある
TCP timestampを使用すると、TIME_WAIT状態のソケットに通信が行われた最後の時間を記録できる
tcp_tw_reuseオプションを有効にした状態で、クライアントソケットを作成すると、TIME_WAIT状態のソケットのうち、現在のtimestampよりも確実に小さい値のtimestampを持つソケットは再利用できる
timestampの単位は秒なので、TIME_WAIT状態に遷移してから1秒後のソケットは再利用できるようになる
tcp_tw_reuseオプションは、通信を行う双方でTCP timestampオプションが設定されていると有効になる
net.ipv4.tcp_fin_timeout
FIN_WAIT_2のタイムアウト設定は
net.ipv4.tcp_fin_timeout
のパラメータで決まるTIME_WAITの待ち時間は
include/net/tcp.h
中でTCP_TIMEWAIT_LEN
という定数で設定されている定数
net.ipv4.tcp_max_tw_buckets
TIME_WAIT状態のソケット数の制限
TIME_WAIT状態のソケット数がこれより多くなることができず、でにこの設定値だけのTIME_WAIT状態のソケットがあった場合、TIME_WAIT状態に遷移すべきソケットはもはや待機されず破壊される
つまり、gracefully shutdownをせずに閉じてしまい、サーバーアプリケーションはこれを認知できない
このような場合、一般的にソケットは破壊され、/var/log/messagesのようなところに、以下ようなログメッセージが残る
TCP: time wait bucket table overflow
このように破壊されたソケットは、TCPのgracefully shutdownの原則に反しますが、サーバーのパフォーマンスや容量に大きな影響を与えない
しかし、ソケットがgracefully shutdownになっていないので、サーバーからクライアントへ送り出されていないデータも消える
この場合、クライアントでは不完全なデータを受信することになる
したがって、できるだけソケットはgracefully shutdownにする必要があり、リクエスト量が多いサーバーでは、この値を適切に上げておいた方がよい
メモリ量も重要なtrade-off要因ですので、比較的メモリ量が十分なサーバーであれば、適当に上げても問題ない
特定のforeign addressとforeign portを持つソケットがTIME_WAIT状態にあるとき、同じアドレスとポートでクライアントから接続要請があっても、SYNを受信すると、このソケットを再利用するようになっている
TIME_WAIT状態のソケット数が、local portを先取りして生成できるクライアントソケットの数を制限
他のサーバーに同時照会できる接続数を減らす
最悪の場合、他のサーバーで1分以内のクエリ数が保有できるephemeral port数より多いときは、HTTP接続の時点で要請は失敗する
対策方法
アプリケーションでconnection poolを使用
パフォーマンスと柔軟性の面で最も推奨している方法で、アプリケーションを修復
net.ipv4.tcp_tw_reuseオプションを使用
使用できるlocal port数が不足している場合、現在のTIME_WAIT状態のソケットのうち、プロトコル上、使っても問題ないと思われるソケットを再利用
net.ipv4.tcp_tw_recycleオプションを使用
TIME_WAIT状態にとどまる時間を変更して、TIME_WAIT状態のソケット数を減らす。1分の代わりにRTO(Retransmission Timeout)の時間だけTIME_WAIT状態に停留する時間が軽減されるが、Linuxでは200msまでの時間を与えることができる。(最小RTOが200ms)
Socket lingerオプションで非常に短時間をパラメータとして使用
FINの代わりにRSTを送信するように誘導し、このときソケットを破壊してソケットがTIME_WAIT状態にとどまらないようにする
lingerオプションはデータをpeerに完全に伝達するために、アプリケーションにより制御を与える1つのオプションであり、TIME-WAITのソケットを減らすためのものではない
net.ipv4.tcp_tw_recycle, socket lingerオプションは、net.ipv4.tcp_tw_reuseよりも過激な方法なので非推奨
net.core.netdev_max_backlog
ネットワークデバイス別にカーネルが処理できるように貯めておくqueueのサイズを設定
カーネルのパケット処理速度がこのqueueに追加されるパケットの導入速度よりも低ければ、まだqueueに追加されていないパケットは捨てられる
net.ipv4.ip_local_port_range
カーネルは、ephemeral portを作成するときに、この範囲内に使用しないポートを選んで割り当てることになる
2つの値は、それぞれ使用するポート範囲の開始と終了を表す
TCP接続は非常に結合度の低いネットワーク環境を想定しています。そのため、ネットワークの状況によって、パケットの順序が入れ替わったり、流出したりするなどの処理のために、ソケット終了時にもなるべくゆっくり終了(gracefully shutdown)するようになっています。これはつまり、ソケットが使用するリソースを可能な限り遅く返却するということです。ephemeral portも含まれます。
特に、クライアントソケットからclose()システムコールで先にソケットを閉じた場合、ソケットはTIME_WAIT状態にとどまることになります。この間、ソケットに割り当てられているephemeral portは使用ができず、その分、同時に保有できるクライアントソケットの数は制限されます。
ALB/CLBでバックエンドへの接続には1,025~65,535のephemeral port全範囲を使用するので、VPCのネットワークACLやOS上のIPtableなどのファイアウォールで部分的に許可するといった運用をしている場合動作に影響が及ぶ場合がある。
足りないと、Apach Benchだと、
apr_socket_connect(): Cannot assign requested address (99)
のようなエラーが返されるab -c 3000 -n 25000 http://52.56.153.195/index.php
変更方法
echo "32768 65535" > /proc/sys/net/ipv4/ip_local_port_range
もしくは、
/etc/sysctl.conf
で永続化
net.ipv4.tcp_syncookies
SYN クッキーは、ソケットのListen backlogが埋め尽くされてもコネクションを確立し続けられるようにするための方法
DDoS 攻撃など、大量の SYN パケットがサーバーに送信された場合にその負荷を小さくするために利用される
そのような状況下で、サーバーは SYN パケットの情報をカーネル内部で保持せず、 SYN パケットの復元に必要な最低限の情報 (SYN クッキー) を SYN+ACK に埋め込む。
これに対し、 SYN+ACK を受け取ったクライアントは、 SYN クッキーを埋め込んだ ACK を送信する。
これより、 DDoS 攻撃を受けた場合にも、サーバーは正常なクライアントを判別し、通信を続行することが可能となる。
実際に使用された場合には
kernel: possible SYN flooding on port X.
といったメッセージが /var/log/messages に記録される。SYN クッキーは一部の TCP オプションを破棄するなどの問題がある。すなわち、パフォーマンスに影響する可能性がある
SYN+ACKパケットはDropしたSYNパケットによって発生するので、クライアントが送ったTCP Optionsをサーバーが把握する手段がない
上記メッセージが期待したものかの確認には以下のコマンドが利用できる
netstat -nta | egrep "State|X"
ss -nta '( dport = :X )'
Linux ではデフォルトで SYN クッキーは有効 (net.ipv4.tcp_syncookies が 1) に設定されますが、もし、正常なトラフィックの負荷のみで SYN クッキーが発行される場合には、 tcp_syncookies を 0 に設定し、 tcp_max_syn_backlog をチューニングすることで問題を回避する必要がある。
Reference
tcp_max_syn_backlog
SYN パケットを受け取った際に以下の部分で評価され、 3 つの条件が成立する場合に SYN パケットが破棄されます。
SYN クッキーが無効 (net.ipv4.tcp_syncookies が 0)
accept されていないソケット数が tcp_max_syn_backlog の 3/4 を超える
SYN パケットの送信元と以前に通信していない
SYN パケットに対して accept システムコールが追いついていないことから、トラフィックが多い場合に発生
そのような場合にも SYN パケットが破棄されないようにするためには、必要に応じて tcp_max_syn_backlog を大きく設定する必要性
一方、こちらのオプションが評価される場合は SYN クッキーは無効になっており、 DDoS 攻撃などの影響を受ける可能性があるので注意 net/ipv4/tcp_input.c
tcp_max_tw_buckets
TIME_WAIT の状態のソケットを保持する最大数を指定するために使用
もし、 TIME_WAIT のソケット数が tcp_max_tw_buckets を超えた場合、 TIME_WAIT のソケットは作成されない。
TIME_WAIT の状態は、パッシブクローズを行う通信先からの FIN パケットの再送に対して、 ACK を送信するために必要とされる。
tcp_max_tw_buckets の変化によって影響が発生している場合、以下のいずれかの問題が発生している事が考えられる。 Case ID: 2116253733
FIN パケットの再送が発生する確率は低いため、おそらく後者の問題が発生している可能性
可能性
インスタンスタイプを小さくすると、 TIME_WAIT のソケットが残存しなくなり、正常に通信を終了できない
インスタンスタイプを大きくすると、 TIME_WAIT のソケット数が増加し、クライアント側でポートの再利用が遅くなる
回避策
前者の場合、 tcp_max_tw_buckets を大きく設定
後者の場合も TCP の仕様上、正常に通信を行うためには TIME_WAIT の状態が必要となり、ドキュメントにも記載の通り、 tcp_max_tw_buckets を小さくすることは推奨されていない
したがって、別途ポートの再利用に関して対策が必要
対策と致しましては、以下の 4 つの組み合わせが同じパケットが同じサーバーに割り当てられないように、ロードバランサを設置し、後段のサーバーをスケールアウトすることが有用
送信元 IP アドレス
送信先 IP アドレス
送信元ポート番号
送信先ポート番号
スケールアウトした場合には、各サーバーに割り当てられる SYN パケット数が減少するため、 DDoS 攻撃を考慮しつつ tcp_max_syn_backlog をチューニングするといった作業が不要になる
net.ipv4.tcp_rfc1337
TCPのTIME-WAIT Assassination Hazardsに対する対応策
https://tools.ietf.org/html/rfc1337
Linux Kernel 4.9では、RSTフラグが来たときにtime_wait timer(2MSL)が再起動してしまい、長く続いてしまうバグがあった
それ以降は、RSTフラグが来たときに、2MSLの間継続するようになっている
https://lore.kernel.org/patchwork/patch/985363/
ELBでは有効
TCP接続の終了完了後、ELBが接続終了のInitiatorなら、ソケットは2MSLの時間だけ、TIME_WAITの時間だけソケットをそのままにしておく
NAT GatewayやNATデバイス配下からきていれば、2MSLの失効前に同じタプルがNATによって再利用される
シーケンス番号が、最後に記録されたシーケンス番号+1より大きければ、新規SYNパケットは新しい接続を作成するために受け入れられる
そうでなければ、challengingなACKで応答する
challengingなACKは、元の送信者にsourceにRSTを生成するような次のパケットに期待する正確なシーケンス番号で応答することを要求する
回避策
source portがランダム化されている場合、NATデバイス上でsource portの範囲を増加
NATデバイスで一つ以上のIPを利用するか、2つ以上のNAT Gatewayを利用
HTTP keep-aliveを送ることによって、サーバー/ELBがクライアント接続を閉じることを防ぐ
60秒前に異なるクライアントから同じsource portが利用されることを防ぐNATインスタンスを使用
TCP keepalive
tcp_keepalive_time
keepalive有効時にkeepaliveメッセージを送り出す頻度
デフォルトで7200秒(2時間)
後段にNAT Gatewayがあるような状況では、NAT Gatewayのアイドルタイムアウトが350秒なので、300秒などの値に小さくしておく
tcp_keepalive_probes
接続がbrokenしていると判斷するまでにTCPが送り出すkeepaliveのprobeの数
tcp_keepalive_intvl
probeを送り出す頻度
probeを送り始めてから
tcp_keepalive_intvl * tcp_keepalive_probes
の間応答しなければkillされるデフォルトで75秒
リトライの約11分後に接続はabortされる
TCPソケットバッファサイズ
パラメータ例
net.core.rmem_default
net.core.wmem_default
net.core.rmem_max
net.core.wmem_max
net.ipv4.tcp_rmem
net.ipv4.tcp_wmem
個別のTCPソケットに指定される値
rmemはreceive(read)bufferの大きさ、wmemはsend(write)bufferのサイズ
net.coreプレフィックスが付いたカーネルパラメータ
TCPを含むすべての種類のソケットに基本設定されているバッファのサイズ
サフィックスdefaultは、そのデフォルト値で、maxはソケットが保有できる最大サイズ
net.ipv4プレフィックスが付いたカーネルパラメータ
TCPソケットの部分
この設定値はipv6でも適用されますが、それはLinuxで一部のipv4カーネルパラメータがipv6まで適用されるため
ipv6にも適用されるカーネルパラメータ:net.ipv4.ip_, net.ipv4.ip_local_portrange, net.ipv4.tcp, net.ipv4.icmp_*
各カーネルパラメータは、min / default / max 3つの整数値に設定できる
minは、TCP memory pressure状態のときにソケットに割り当てられるバッファのサイズを表し、maxはTCPソケットが保有できる最大サイズを表す
デフォルト値は、TCP receive windowサイズを決定する際に最も重要なことに参照される値
net.ipv4.tcp_mem
カーネルでTCP用に使用できるメモリサイズ
この値はTCPソケット全体の値
min / pressure / maxの値を指定
pressureはmemory pressureのthreshold値です。
TCPソケット全体で使用されるメモリがこの値を超えると、TCP memory pressureの状態になり、以降ソケットは指定されたmin値のメモリバッファのサイズを持つようになる
注意すべき点は、なるべくこのカーネルパラメータの設定値を変更しない
起動時にシステムのメモリに合わせて自動設定(auto-tuned)されている
輻輳制御
スロースタート
####### net.ipv4.tcp_slow_start_after_idle
有効時はcongestion window sizeを増加させたソケットであっても、特定時間内にidle(通信がない)状態に持続されると、再びslow startを使用してinitial congestion window sizeからcongestion window sizeを増加させる必要があ
無効時は一定時間通信がなくてもcongestion window sizeが維持される
輻輳制御アルゴリズム関連
net.ipv4.tcp_available_congestion_control
利用可能な輻輳制御アルゴリズムのリスト。
輻輳制御アルゴリズムは、pluggableなカーネルモジュールになっていて、以下で確認できる。
確認したカーネルモジュールは、以下のように追加可能
以下の方法でも追加可能
net.ipv4.tcp_congestion_control
現在設定している輻輳制御アルゴリズム。新規の接続で使用されるデフォルトで適用される。
tcp_allowed_congestion_control
非特権プロセスで利用できる輻輳制御アルゴリズムの選択肢を表示/設定
Reference
ファイル数
fs.file-max
Linuxでシステム全体が保有できる最大ファイル数を設定
Linuxをはじめとする一般的なUnixソケットはファイルのような扱いを受ける
システム全体で保有できるファイル数が制限されていれば、ソケットの全体数にも影響を与える
この値を超えると、open()システムコールでToo many open filesのようなエラーが発生
プロセス別の制限設定であるuser limit値からopen filesがプロセスが保有できるソケットを含むファイルの数を確認可能
open filesがプロセスが保有できるソケットを含むファイルの数(ここでは1024)
ulimit -SHn 65535
などのように変更可能
fs.file-nr
このパラメータは一般的なパラメータではなく、現在開いているファイルのステータスを表す
以下のように確認可能
3つの値はそれぞれ、現在開いているファイルの数、現在開いているが使用していないファイルの数、開くことができるファイルの最大数
システム全体に対する数値
設定例
TCP帯域幅を増加
receive window sizeを増やす
アプリケーションで認知できないネットワークパケットの損失を防止
in-bound queueサイズを増やす
別サーバーに再クエリする場合(例:プロキシサーバー)は性能が制約されるような状況
tcp_tw_reuse=を考慮。ただし、TIME_WAIT状態のソケットは、一般的にサーバーソケットの場合なら気にする必要はない
UDPとの比較
機能を除外したことに意義がある
UDPデータグラムは明確な境界を持ち、各データグラムは単一のIPパケットで運ばれ、アプリケーションは一度の読み込みで完全なメッセージを取得
プロトコルの設計上の判断はすべて、その上で動作するアプリケーションに委ねられている
NAT
NATではデータを配信するためにルーティングテーブルの管理をする必要がある。一方でUDPには状態がなく、根本的な行き違いがある。
状態を持たないUDPの状態をNATが保持する必要がある。
一方のピアがデータグラムの送信を中止する場合に備えて、UDPのルーティングレコードはタイマーによって失効させるが、一概にどの程度の時間が良いかの答えはない
経路上のNAT装置の変換レコード削除タイマーを定期的にリセットするために、双方向のキープアライブパケットを導入するのがベストプラクティス
NATトラバーサル
パブリックネットワークからプライベートネットワークにデータを送信する場合、エントリを持っていることはほぼない
TURN、STUN、ICEなどのトラバース技術を用いて、両UDPピア間のエンドツーエンド接続を確立しないといけない
Last updated