hayashier Tech Blogs
  • hayashier Tech Blogs
  • Author's Books
    • 実践Redis入門 (日本語版)
    • 実践Redis入門 (한국어판)
  • Top Contents
    • Dive Deep Redis
    • Dive Deep Memcached
    • Kubernetes 入門
    • TCP 入門
    • TLS 入門
    • GPG 入門
    • サービス障害が発生した場合の対応方法
    • よく使うトラブルシューティング コマンド実行例 まとめ
    • コピペで使えるELBのアクセスログ解析による事象分析 (ShellScript, Athena)
  • Containers
    • Docker 入門
    • Nomad 導入
    • Dockerを利用してさっと検証環境構築
  • Kubernetes
    • Kubernetes 入門
    • Kubernetes 導入 with Amazon Linux 2
    • EKSを利用してKubernetesでSpring MVCをデプロイ (NLB + Auto Scaling)
  • Load Balancer
    • ALB 認証 導入
    • TLS extensions support with ALB
    • ELB(CLB,ALB,NLB)の種類ごとのHTTPレスポンスの違い
    • ELB(CLB) で WebSocket 通信
  • RDBMS
    • PostgreSQL DBA 入門
    • RDBMS Benchmark Get Started
    • RDBMS サンプルデータ生成 Get Started
    • RDS PostgreSQL Extensions Get Started
    • RDBMS Engine Inspection for Troubleshooting
  • Redis
    • Dive Deep Redis ~ 入門から実装の確認まで
    • Dive Deep Redis Internals ~ GETコマンド実行時の動作 ~
    • RedisのString型は今でも本当に512MBが上限か?
    • Redis 公式ドキュメント まとめ
    • Redis / Memcached Source Code Reading - Overview -
  • Memcached
    • Dive Deep Memcached ~ 入門から実装の確認まで ~
    • Dive Deep Memcached ~ SETコマンド実行時の動作 ~
    • Memcached 公式ドキュメント まとめ
    • memtier_benchmark + memcached-tool の導入
    • Redis / Memcached Source Code Reading - Overview -
  • Hadoop
    • Hadoop Get Started
  • Networking
    • TCP 入門
    • TLS 入門
    • ksnctf: HTTPS is secure, Writeup (TLS 通信解読)
    • オンプレ側ルーター(Cisco 1812J, Juniper SRX210, YAMAHA RTX 1210)から Direct Connect へ BGP 設定
  • Software
    • アルゴリズムとデータ構造 入門
    • デザインパターン 入門
    • ソフトウェアテスト 入門
  • System Admin
    • Shell Script 入門
    • サービス障害が発生した場合の対応方法
    • よく使うトラブルシューティング コマンド実行例 まとめ
    • コピペで使えるELBのアクセスログ解析による事象分析 (ShellScript, Athena)
    • GPG 入門
    • Operation Misc
  • Development
    • ローカル環境のプログラミング言語のバージョンを切り替え macOS
    • /usr/local/Cellar/pyenv/1.2.21/libexec/pyenv: No such file or directoryのエラーの対処方法
  • AWS
    • AWS Misc
    • AWS CLI, AWS SDKのリトライ処理の実装について
    • AWS CLI バージョンアップでエラー発生を解消
    • Elastic Beanstalkで稼働しているアプリケーション(Ruby, Sinatra)をAmazon Linux AMIからAmazon Linux2へ移行
    • Elastic Beanstalkでインスタンス入れ替え後にnginxのデフォルトの画面が表示されてしまう問題の対応
    • Amazon Lightsail に SSL 証明書設置 with Let's Encrypt (自動更新)
    • Amazon Lightsailで10分で作るお手軽Markdownで書く独自ドメインのブログサイト構築
    • Lambdaをローカルでテスト(with Docker)
    • ECS + ALB でダウンタイムなしでデプロイ
    • `Repository packages-microsoft-com-prod is listed more than once in the configuration`のメッセージの解消方法
  • Others
    • Pandoc 導入
    • textlint + prh による文章校正
    • 紙書籍をPDFに変換
    • Sphinx 導入
    • さくっとPocketのブックマークをはてなブックマークに移行
    • Macが突然起動しなくなった話
    • Macでターミナルが開かない (zsh編)
    • ホスト型 IDS Tripwire とネットワーク型 IDS Snort の導入 with CentOS 6
    • JMeter 導入
    • Squid 導入 with Amazon Linux AMI
    • Spring MVCを導入 (+ MySQL, Redis)
    • 外資系企業で働いている場合の確定申告方法 (RSU考慮)
Powered by GitBook
On this page
  • TCP 入門
  • 機能
  • 状態遷移図
  • データ構造
  • その他
  • Linuxカーネル
  • UDPとの比較
  1. Networking

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受信の段階においては、単にセグメントの順序が入れ替わっているだけという可能性があるため、できるだけ不要な再送を避けるようになっている

  • 注記: クライアントとサーバー間で途中で中継しているノード上でキャプチャを行うと、フィルター等していない場合にはinbound/outbound方向で重複して同じパケットをキャプチャすることがあります。その場合、Wiresharkで確認すると、[TCP Retransmission]などのようにTCPが再送しているかのように表示される場合があります。実際に、TCPの再送の場合には、パケットキャプチャの際に、IPレイヤーでIdentificationという部分があり、パケットが異なるとこのIdentificationも変わります。TCP再送の場合にもIdentificationが変わります。そのため、同じパケットの重複した観測か、TCPの再送かの区別はこちらを参考にすることができます。私の観測範囲では、Identificationはパケットごとに増加していくようですが、RFC上では一意であることが定義されており、増加することについては定義されていないようです。

再送タイマー - 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によって確認可能

    • Top 10 Networking Features in Windows Server 2019: #8 A Faster, Safer Internet

まとめ

  • 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)

以下のように実行することで、現在適用されている輻輳制御アルゴリズムや、現在の輻輳ウィンドウサイズなどがわかる。

$ ss -n -i
:
tcp                                         ESTAB                                        0                                             36                                                                                                            172.31.1.16:22                                                                                      205.251.233.183:18773                                          
	 cubic wscale:6,7 rto:308 rtt:107.1/0.418 ato:40 mss:1198 pmtu:9001 rcvmss:1198 advmss:8949 cwnd:4 ssthresh:4 bytes_acked:46797 bytes_received:8377 segs_out:142 segs_in:195 data_segs_out:126 data_segs_in:84 send 357.9Kbps pacing_rate 429.5Kbps delivery_rate 438.0Kbps busy:6724ms unacked:1 retrans:0/5 rcv_rtt:136 rcv_space:26847 rcv_ssthresh:41611 minrtt:106.1                                             
tcp                                         ESTAB                                        0                                             0                                                                                                             172.31.1.16:48056                                                                                     172.31.157.57:443                                            
	 cubic wscale:6,7 rto:204 rtt:2.526/0.497 ato:40 mss:1460 pmtu:9001 rcvmss:1460 advmss:8961 cwnd:10 bytes_acked:3701 bytes_received:5607 segs_out:485 segs_in:490 data_segs_out:28 data_segs_in:10 send 46.2Mbps lastsnd:184200 lastrcv:7084200 lastack:2824 pacing_rate 92.5Mbps delivery_rate 10.7Mbps app_limited busy:112ms rcv_space:26883 rcv_ssthresh:42527 minrtt:2.14                                        
tcp                                         ESTAB                                        0                                             0                                                                                                             172.31.1.16:54290                                                                                    172.31.144.160:443                                            
	 cubic wscale:6,7 rto:212 rtt:9.373/14.582 ato:40 mss:1460 pmtu:9001 rcvmss:1460 advmss:8961 cwnd:10 bytes_acked:4157 bytes_received:5863 segs_out:15 segs_in:20 data_segs_out:7 data_segs_in:11 send 12.5Mbps lastsnd:104 lastrcv:104 lastack:44 pacing_rate 24.9Mbps delivery_rate 12.1Mbps app_limited busy:64ms rcv_space:26883 rcv_ssthresh:45695 minrtt:1.911 
  • Reference

    • 基本から学ぶ TCPと輻輳制御 ……押さえておきたい輻輳制御アルゴリズム

    • TCP技術入門 - 進化を続ける基本プロトコル

    • TCP各バージョンの輻輳制御の比較

例

####### 輻輳制御の状態確認 以下の1つ目のTCPプロトコルのソケットについては輻輳制御アルゴリズムにCUBICを使用して輻輳制御のウィンドウサイズがcwndより10であることがわかる。すなわち、一度に送信できるパケット数は10個

$ ss -n -i
Netid State      Recv-Q Send-Q  Local Address:Port                 Peer Address:Port              
u_str ESTAB      0      0                   * 10708                           * 10707              
u_str ESTAB      0      0                   * 11360                           * 11359              
u_str ESTAB      0      0                   * 11365                           * 11366              
u_str ESTAB      0      0                   * 10707                           * 10708              
u_str ESTAB      0      0      /var/run/dbus/system_bus_socket 11366                           * 11365              
u_str ESTAB      0      0                   * 11359                           * 11360              
u_str ESTAB      0      0                   * 123453234                       * 123453233          
u_str ESTAB      0      0                   * 875517                          * 875516             
u_str ESTAB      0      0                   * 875516                          * 875517             
u_str ESTAB      0      0                   * 123453233                       * 123453234          
tcp   ESTAB      474    0         172.31.4.26:50214               52.119.160.96:443                
	 cubic wscale:6,7 rto:208 rtt:5.418/9.534 ato:40 mss:1460 cwnd:10 ssthresh:7 bytes_acked:3632 bytes_received:5945 segs_out:16 segs_in:17 send 21.6Mbps lastsnd:332 lastrcv:332 lastack:328 pacing_rate 25.9Mbps retrans:0/1 rcv_space:26883
tcp   ESTAB      474    0         172.31.4.26:50204               52.119.160.96:443                
	 cubic wscale:6,7 rto:204 rtt:3.538/6.082 ato:40 mss:1460 cwnd:7 ssthresh:7 bytes_acked:9338 bytes_received:6893 segs_out:26 segs_in:24 send 23.1Mbps lastsnd:368 lastrcv:332 lastack:332 pacing_rate 27.7Mbps retrans:0/1 rcv_space:26883
tcp   ESTAB      0      1592      172.31.4.26:22                 205.251.233.178:9039               
	 cubic wscale:6,7 rto:324 rtt:120.774/3.5 ato:40 mss:1198 cwnd:10 bytes_acked:3269 bytes_received:4445 segs_out:37 segs_in:36 send 793.5Kbps lastrcv:44 lastack:44 pacing_rate 1.6Mbps unacked:10 rcv_rtt:118 rcv_space:26847
tcp   ESTAB      0      0         172.31.4.26:55850              185.156.179.225:80                 
	 cubic wscale:6,7 rto:412 rtt:208.015/8.173 ato:40 mss:1448 cwnd:2 ssthresh:2 bytes_acked:524452 bytes_received:2382698 segs_out:16780 segs_in:11765 send 111.4Kbps lastsnd:40820 lastrcv:40588 lastack:40588 pacing_rate 133.6Kbps retrans:0/63 rcv_rtt:326155 rcv_space:36888
:

####### 初期の輻輳制御のウィンドウサイズの変更 初期の輻輳制御のウィンドウサイズのデフォルト値は1。モバイル環境等の貧弱な環境では、輻輳制御のウィンドウサイズが大きくなるには何度もACKのやりとりをしないといけず、難しい。

$ ip route show
default via 172.31.0.1 dev eth0 
169.254.169.254 dev eth0 
172.31.0.0/20 dev eth0  proto kernel  scope link  src 172.31.4.26 

以下のように初期の輻輳制御のウィンドウサイズは10に変更することができる。すると、常にこのパケット数より多くの通信を頻繁に行うことになり、ネットワーク全体が混雑した状況になる可能性がある。一度に送受信するパケットサイズが相対的に小さければ、メリットはない。

$ ip route change default via 172.31.0.1 dev eth0 initcwnd 10
RTNETLINK answers: Operation not permitted
[ec2-user@ip-172-31-4-26 ~]$ sudo ip route change default via 172.31.0.1 dev eth0 initcwnd 10
[ec2-user@ip-172-31-4-26 ~]$ ip route show
default via 172.31.0.1 dev eth0  initcwnd 10
169.254.169.254 dev eth0 
172.31.0.0/20 dev eth0  proto kernel  scope link  src 172.31.4.26 

状態遷移図

                              +---------+ ---------\      active OPEN
                              |  CLOSED |            \    -----------
                              +---------+<---------\   \   create TCB
                                |     ^              \   \  snd SYN
                   passive OPEN |     |   CLOSE        \   \
                   ------------ |     | ----------       \   \
                    create TCB  |     | delete TCB         \   \
                                V     |                      \   \
                              +---------+            CLOSE    |    \
                              |  LISTEN |          ---------- |     |
                              +---------+          delete TCB |     |
                   rcv SYN      |     |     SEND              |     |
                  -----------   |     |    -------            |     V
 +---------+      snd SYN,ACK  /       \   snd SYN          +---------+
 |         |<-----------------           ------------------>|         |
 |   SYN   |                    rcv SYN                     |   SYN   |
 |   RCVD  |<-----------------------------------------------|   SENT  |
 |         |                    snd ACK                     |         |
 |         |------------------           -------------------|         |
 +---------+   rcv ACK of SYN  \       /  rcv SYN,ACK       +---------+
   |           --------------   |     |   -----------
   |                  x         |     |     snd ACK
   |                            V     V
   |  CLOSE                   +---------+
   | -------                  |  ESTAB  |
   | snd FIN                  +---------+
   |                   CLOSE    |     |    rcv FIN
   V                  -------   |     |    -------
 +---------+          snd FIN  /       \   snd ACK          +---------+
 |  FIN    |<-----------------           ------------------>|  CLOSE  |
 | WAIT-1  |------------------                              |   WAIT  |
 +---------+          rcv FIN  \                            +---------+
   | rcv ACK of FIN   -------   |                            CLOSE  |
   | --------------   snd ACK   |                           ------- |
   V        x                   V                           snd FIN V
 +---------+                  +---------+                   +---------+
 |FINWAIT-2|                  | CLOSING |                   | LAST-ACK|
 +---------+                  +---------+                   +---------+
   |                rcv ACK of FIN |                 rcv ACK of FIN |
   |  rcv FIN       -------------- |    Timeout=2MSL -------------- |
   |  -------              x       V    ------------        x       V
    \ snd ACK                 +---------+delete TCB         +---------+
     ------------------------>|TIME WAIT|------------------>| CLOSED  |
                              +---------+                   +---------+

                      TCP Connection State Diagram
                               Figure 6.
  • 送信者側(アクティブクローズ)側で受信者側の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ヘッダー

  • フォーマット

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          Source Port          |       Destination Port        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        Sequence Number                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Acknowledgment Number                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Data |       |U|E|U|A|P|R|S|F|                               |
   | Offset| Reser |R|C|R|C|S|S|Y|I|            Window             |
   |       |  ved  |G|E|G|K|H|T|N|N|                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Checksum            |         Urgent Pointer        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             data                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • シーケンス番号/確認応答番号

    • シーケンス番号は、セグメントの順番の保証もしくはセグメントの消失を検出するために、送信するデータを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

    • RFC 793 - Transmission Control Protocol

    • RFC 3168 - The Addition of Explicit Congestion Notification (ECN) to IP

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)

/*
 *      TCP option
 */

#define TCPOPT_NOP              1       /* Padding */
#define TCPOPT_EOL              0       /* End of options */
#define TCPOPT_MSS              2       /* Segment size negotiating */
#define TCPOPT_WINDOW           3       /* Window scaling */
#define TCPOPT_SACK_PERM        4       /* SACK Permitted */
#define TCPOPT_SACK             5       /* SACK Block */
#define TCPOPT_TIMESTAMP        8       /* Better RTT estimations/PAWS */
#define TCPOPT_MD5SIG           19      /* MD5 Signature (RFC2385) */
#define TCPOPT_FASTOPEN         34      /* Fast open (RFC7413) */
#define TCPOPT_EXP              254     /* Experimental */

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

  • Path MTU Discovery が原因の通信障害は分りづらい

ウィンドウスケールオプション

  • 長距離かつ広帯域で安定したネットワークでは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

    • 24.3 Long Fat Pipes

タイマーシステム

  • タイムアウトタイマー

    • データ転送時に設定されて、データ転送に対するタイムアウトを検出

  • 持続タイマー

    • 継続的に起動されているタイマーで、ゼロウィンドウ検査などが行われる

  • 終了時タイマー

    • 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のべき乗の値に切り上げた値になるので256

          • tcp_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に送信されるパケット量がサーバーアプリケーションで調整できる

  • 参照

    • SYN packet handling in the wild

    • TCP backlog が溢れたときに何が起こるか

    • Linuxカーネルメモ

    • Linuxのbacklogについて調べてみる

      • 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状態のソケットを減らすためのものではない

パラメーター一覧

確認環境は以下

$ uname -r
4.14.214-160.339.amzn2.x86_64

TCP関連のパラメータは以下のものが確認可能。

$ sudo sysctl -a | grep tcp
net.ipv4.tcp_abort_on_overflow = 0
net.ipv4.tcp_adv_win_scale = 1
net.ipv4.tcp_allowed_congestion_control = cubic reno
net.ipv4.tcp_app_win = 31
net.ipv4.tcp_autocorking = 1
net.ipv4.tcp_available_congestion_control = cubic reno
net.ipv4.tcp_available_ulp = 
net.ipv4.tcp_base_mss = 1024
net.ipv4.tcp_challenge_ack_limit = 1000
net.ipv4.tcp_congestion_control = cubic
net.ipv4.tcp_dsack = 1
net.ipv4.tcp_early_demux = 1
net.ipv4.tcp_early_retrans = 3
net.ipv4.tcp_ecn = 2
net.ipv4.tcp_ecn_fallback = 1
net.ipv4.tcp_fack = 0
net.ipv4.tcp_fastopen = 1
net.ipv4.tcp_fastopen_blackhole_timeout_sec = 3600
net.ipv4.tcp_fastopen_key = 00000000-00000000-00000000-00000000
net.ipv4.tcp_fin_timeout = 60
net.ipv4.tcp_frto = 2
net.ipv4.tcp_fwmark_accept = 0
net.ipv4.tcp_invalid_ratelimit = 500
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_l3mdev_accept = 0
net.ipv4.tcp_limit_output_bytes = 262144
net.ipv4.tcp_low_latency = 0
net.ipv4.tcp_max_orphans = 8192
net.ipv4.tcp_max_reordering = 300
net.ipv4.tcp_max_syn_backlog = 128
net.ipv4.tcp_max_tw_buckets = 8192
net.ipv4.tcp_mem = 22416	29888	44832
net.ipv4.tcp_min_rtt_wlen = 300
sysctl: net.ipv4.tcp_min_snd_mss = 48
reading key "net.ipv6.conf.all.stable_secret"net.ipv4.tcp_min_tso_segs = 2

net.ipv4.tcp_moderate_rcvbuf = 1
net.ipv4.tcp_mtu_probing = 0
net.ipv4.tcp_no_metrics_save = 0
net.ipv4.tcp_notsent_lowat = 4294967295
net.ipv4.tcp_orphan_retries = 0
net.ipv4.tcp_pacing_ca_ratio = 120
net.ipv4.tcp_pacing_ss_ratio = 200
net.ipv4.tcp_probe_interval = 600
net.ipv4.tcp_probe_threshold = 8
net.ipv4.tcp_recovery = 1
net.ipv4.tcp_reordering = 3
net.ipv4.tcp_retrans_collapse = 1
net.ipv4.tcp_retries1 = 3
net.ipv4.tcp_retries2 = 15
net.ipv4.tcp_rfc1337 = 0
net.ipv4.tcp_rmem = 4096	87380	6291456
net.ipv4.tcp_sack = 1
net.ipv4.tcp_slow_start_after_idle = 1
net.ipv4.tcp_stdurg = 0
net.ipv4.tcp_syn_retries = 6
net.ipv4.tcp_synack_retries = 5
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_thin_linear_timeouts = 0
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_tso_win_divisor = 3
net.ipv4.tcp_tw_reuse = 0
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_wmem = 4096	20480	4194304
net.ipv4.tcp_workaround_signed_windows = 0
sysctl: reading key "net.ipv6.conf.default.stable_secret"
sysctl: reading key "net.ipv6.conf.eth0.stable_secret"
sysctl: reading key "net.ipv6.conf.lo.stable_secret"
sunrpc.tcp_fin_timeout = 15
sunrpc.tcp_max_slot_table_entries = 65536
sunrpc.tcp_slot_table_entries = 2
sunrpc.transports = tcp 1048576
sunrpc.transports = tcp-bc 1048576

詳細

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_tw_recycle は廃止されました ― その危険性を理解する

net.ipv4.tcp_fin_timeout

  • FIN_WAIT_2のタイムアウト設定はnet.ipv4.tcp_fin_timeoutのパラメータで決まる

  • TIME_WAITの待ち時間はinclude/net/tcp.h中でTCP_TIMEWAIT_LENという定数で設定されている定数

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT
                                  * state, about 60 seconds     */

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接続の時点で要請は失敗する

    • 対策方法

        1. アプリケーションでconnection poolを使用

        2. パフォーマンスと柔軟性の面で最も推奨している方法で、アプリケーションを修復

        1. net.ipv4.tcp_tw_reuseオプションを使用

        2. 使用できるlocal port数が不足している場合、現在のTIME_WAIT状態のソケットのうち、プロトコル上、使っても問題ないと思われるソケットを再利用

        1. net.ipv4.tcp_tw_recycleオプションを使用

        2. TIME_WAIT状態にとどまる時間を変更して、TIME_WAIT状態のソケット数を減らす。1分の代わりにRTO(Retransmission Timeout)の時間だけTIME_WAIT状態に停留する時間が軽減されるが、Linuxでは200msまでの時間を与えることができる。(最小RTOが200ms)

        1. Socket lingerオプションで非常に短時間をパラメータとして使用

        2. FINの代わりにRSTを送信するように誘導し、このときソケットを破壊してソケットがTIME_WAIT状態にとどまらないようにする

        3. 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

  • LinuxサーバーのTCPネットワークのパフォーマンスを決定するカーネルパラメータ – 2編

  • 変更方法

    • 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

  • kernel: Possible SYN flooding on port #. Sending cookies.

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

   6395			if (!net->ipv4.sysctl_tcp_syncookies &&
   6396			    (net->ipv4.sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
   6397			     (net->ipv4.sysctl_max_syn_backlog >> 2)) &&
   6398			    !tcp_peer_is_proven(req, dst)) {
   6399				/* Without syncookies last quarter of
   6400				 * backlog is filled with destinations,
   6401				 * proven to be alive.
   6402				 * It means that we continue to communicate
   6403				 * to destinations, already remembered
   6404				 * to the moment of synflood.
   6405				 */
   6406				pr_drop_req(req, ntohs(tcp_hdr(skb)->source),
   6407					    rsk_ops->family);
   6408				goto drop_and_release;
   6409			}

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が維持される

  • LinuxサーバーのTCPネットワークのパフォーマンスを決定するカーネルパラメータ – 1編

輻輳制御アルゴリズム関連

net.ipv4.tcp_available_congestion_control

利用可能な輻輳制御アルゴリズムのリスト。

net.ipv4.tcp_available_congestion_control = cubic reno

輻輳制御アルゴリズムは、pluggableなカーネルモジュールになっていて、以下で確認できる。

$ ls /lib/modules/`uname -r`/kernel/net/ipv4/ | grep tcp
tcp_bbr.ko
tcp_dctcp.ko
tcp_diag.ko

確認したカーネルモジュールは、以下のように追加可能

# sysctl -w net.ipv4.tcp_congestion_control="bbr"
# sysctl net.ipv4.tcp_available_congestion_control
net.ipv4.tcp_available_congestion_control = bbr cubic reno

以下の方法でも追加可能

$ echo "bbr" > /proc/sys/net/ipv4/tcp_congestion_control 

net.ipv4.tcp_congestion_control

現在設定している輻輳制御アルゴリズム。新規の接続で使用されるデフォルトで適用される。

net.ipv4.tcp_congestion_control = cubic

tcp_allowed_congestion_control

非特権プロセスで利用できる輻輳制御アルゴリズムの選択肢を表示/設定

net.ipv4.tcp_allowed_congestion_control = cubic reno
  • Reference

    • TCP

ファイル数

fs.file-max

  • Linuxでシステム全体が保有できる最大ファイル数を設定

  • Linuxをはじめとする一般的なUnixソケットはファイルのような扱いを受ける

    • システム全体で保有できるファイル数が制限されていれば、ソケットの全体数にも影響を与える

    • この値を超えると、open()システムコールでToo many open filesのようなエラーが発生

  • プロセス別の制限設定であるuser limit値からopen filesがプロセスが保有できるソケットを含むファイルの数を確認可能

    • open filesがプロセスが保有できるソケットを含むファイルの数(ここでは1024)

    • ulimit -SHn 65535などのように変更可能

$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 3859
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 3859
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

fs.file-nr

  • このパラメータは一般的なパラメータではなく、現在開いているファイルのステータスを表す

  • 以下のように確認可能

    • 3つの値はそれぞれ、現在開いているファイルの数、現在開いているが使用していないファイルの数、開くことができるファイルの最大数

    • システム全体に対する数値

$ sysctl fs.file-nr
fs.file-nr = 832	0	98160

設定例

TCP帯域幅を増加

receive window sizeを増やす

$ sysctl -w net.ipv4.tcp_window_scaling="1"
$ sysctl -w net.core.rmem_default="253952"
$ sysctl -w net.core.wmem_default="253952"
$ sysctl -w net.core.rmem_max="16777216"
$ sysctl -w net.core.wmem_max="16777216"
$ sysctl -w net.ipv4.tcp_rmem="253952 253952 16777216"
$ sysctl -w net.ipv4.tcp_wmem="253952 253952 16777216"

アプリケーションで認知できないネットワークパケットの損失を防止

in-bound queueサイズを増やす

$ sysctl -w net.core.netdev_max_backlog="30000"
$ sysctl -w net.core.somaxconn="1024"
$ sysctl -w net.ipv4.tcp_max_syn_backlog="1024"
$ ulimit -SHn 65535

別サーバーに再クエリする場合(例:プロキシサーバー)は性能が制約されるような状況

tcp_tw_reuse=を考慮。ただし、TIME_WAIT状態のソケットは、一般的にサーバーソケットの場合なら気にする必要はない

$ sysctl -w net.ipv4.tcp_max_tw_buckets="1800000"
$ sysctl -w ipv4.tcp_timestamps="1"
$ sysctl -w net.ipv4.tcp_tw_reuse="1"
  • LinuxサーバーのTCPネットワークのパフォーマンスを決定するカーネルパラメータ – 3編

UDPとの比較

  • 機能を除外したことに意義がある

  • UDPデータグラムは明確な境界を持ち、各データグラムは単一のIPパケットで運ばれ、アプリケーションは一度の読み込みで完全なメッセージを取得

  • プロトコルの設計上の判断はすべて、その上で動作するアプリケーションに委ねられている

  • NAT

    • NATではデータを配信するためにルーティングテーブルの管理をする必要がある。一方でUDPには状態がなく、根本的な行き違いがある。

    • 状態を持たないUDPの状態をNATが保持する必要がある。

      • 一方のピアがデータグラムの送信を中止する場合に備えて、UDPのルーティングレコードはタイマーによって失効させるが、一概にどの程度の時間が良いかの答えはない

      • 経路上のNAT装置の変換レコード削除タイマーを定期的にリセットするために、双方向のキープアライブパケットを導入するのがベストプラクティス

    • NATトラバーサル

      • パブリックネットワークからプライベートネットワークにデータを送信する場合、エントリを持っていることはほぼない

        • TURN、STUN、ICEなどのトラバース技術を用いて、両UDPピア間のエンドツーエンド接続を確立しないといけない

  • High Performance Browser Networking

PreviousHadoop Get StartedNextTLS 入門

Last updated 1 month ago