Skip to content
Published on

TCPネットワークスタック完全攻略 — 状態マシン、輻輳制御 (Cubic vs BBR)、Nagle、Delayed ACK、そしてQUICへの進化 (2025)

Authors

0. 知っているようで知らないTCP

毎日のHTTPリクエストの裏には常にTCPがある。次の問いに答えられるだろうか。

  • ss -tanTIME_WAITはなぜ30秒残るのか?
  • "connection reset by peer"と"broken pipe"の違いは?
  • 1KBメッセージを毎秒100個送るのに4秒もかかることがあるのはなぜか?
  • 10Gbpsネットワークでiperfが1Gbpsしか出ない理由は?
  • GoogleはなぜTCPではなくUDP上にQUICを作ったのか?

この記事がその答え。TCPの状態マシン、輻輳制御の進化、悪名高い相互作用バグ、そしてQUIC時代の到来まで。

1. TCPの状態マシン — 11状態の旅

1.1 接続確立: 3-Way Handshake

Client              Server
  |  SYN (seq=x)      |
  |----------------->|  [LISTEN -> SYN_RCVD]
  | SYN+ACK(y,x+1)   |
  |<-----------------|
  |  ACK (ack=y+1)   |
  |----------------->|  [SYN_RCVD -> ESTABLISHED]

なぜ3回か?双方向のISN (Initial Sequence Number) 同期のため。各方向ごとに独立のISNを相手に通知し、確認を受ける必要がある。

1.2 接続終了: 4-Way Handshake

A                B
 |   FIN        |
 |------------->|  [ESTABLISHED -> CLOSE_WAIT]
 |   ACK        |
 |<-------------|
 [FIN_WAIT_1 -> FIN_WAIT_2]
 |   FIN        |
 |<-------------|  [CLOSE_WAIT -> LAST_ACK]
 |   ACK        |
 |------------->|
 [FIN_WAIT_2 -> TIME_WAIT]  [LAST_ACK -> CLOSED]

4回なのはTCPが全二重 (full-duplex) だから。各方向を独立に閉じる。

1.3 TIME_WAIT — 誤解されがちな状態

2MSL (Maximum Segment Lifetime) 待つ — 通常30秒〜2分。理由:

  1. 遅延パケットが新接続に混入するのを防ぐ: 同じポート対で素早く再接続すると、前の接続の遅延パケットが届きうる。
  2. 最終ACKの喪失対策: 相手がFINを再送した場合にACKで応答する必要がある。CLOSED状態ではRSTを返してしまう。

1.4 TIME_WAIT爆発

クライアントが大量の短命接続を開閉するとTIME_WAITが数万個溜まり、ephemeral portが枯渇する。

間違った対処: TIME_WAITを0にする (危険、データ汚染の恐れ)。

正しい対処:

  • net.ipv4.tcp_tw_reuse=1: 安全な条件下でephemeral portを再利用。
  • Keep-Alive接続 (HTTP/1.1 persistent、HTTP/2 multiplexing)。
  • Connection pool (DBドライバ、HTTPクライアント)。

1.5 CLOSE_WAIT — アプリバグの兆候

TIME_WAITは正常だが、CLOSE_WAITが溜まるのはアプリのバグ。相手がFINを送ったのにclose()を呼んでいない。

$ ss -tan | grep CLOSE_WAIT | wc -l
50000   # ソケットリーク中

原因: try-finallyの欠落、ファイルディスクリプタ管理の例外処理漏れ。

2. TCPの信頼性メカニズム

2.1 シーケンス番号とACK

全バイトにシーケンス番号。受信者は「次に期待するバイト番号」をACKで返す。

送信: [1000][1001][1002][1003]
受信: ACK=1004  (1000-1003受領、次は1004)

途中で損失すると受信者は同じACKを繰り返し (duplicate ACK)、3つのdup ACKでFast Retransmit発動。

2.2 Retransmission — RTOとFast Retransmit

  • RTO: RTT測定に基づく動的タイムアウト (Jacobson, 1988)。
  • Fast Retransmit: 3 dup ACKでタイムアウトを待たず即再送。
  • SACK: 「1000-2000と3000-4000受領、2000-3000が欠落」を精密に通知。

2.3 フロー制御 — Receive Window

受信者はrwndを広告。送信者はunacked_bytes < rwndの間だけ送信。

2.4 Window Scaling

TCPの16ビットwindow = 65,535バイト。10Gbps x 100ms RTT = 125MB必要。RFC 1323でshift分だけrwndを拡大。

sysctl net.ipv4.tcp_window_scaling
sysctl net.core.rmem_max
sysctl net.ipv4.tcp_rmem

3. 輻輳制御

3.1 1986年のCongestion Collapse

UC Berkeley〜LBLリンクが32Kbpsから40bpsへ — 1000倍の低下。原因は再送の嵐。Van Jacobsonの1988年論文がTCPに輻輳制御を導入した。

3.2 Congestion Window (cwnd)

送信可能 = min(rwnd, cwnd)

rwndは受信者が通知、cwndは送信者がネットワーク容量を推定。

3.3 Slow Start

初期cwnd = 10 MSS。ACKごとにcwnd += 1 MSS、RTTごとに倍増。

cwnd: 10 -> 20 -> 40 -> 80 -> 160

名前は「slow」だが実質指数成長。

3.4 Congestion Avoidance

ssthresh到達後、cwnd += 1 MSS per RTT (線形増加)。

3.5 損失検知

  • 3 Dup ACK (Fast Retransmit): cwnd半減。
  • Timeout: cwnd = 1 MSS、Slow Startからやり直し。

これがAIMD (Additive Increase, Multiplicative Decrease)、Renoの核心。

3.6 Reno から Cubic へ

高速長距離ではRenoは保守的すぎる。CUBIC (Linuxデフォルト) はcwndを時間の3次関数で増加させ、直前の損失点まで迅速に復帰する。

3.7 BBR — Googleの革命 (2016)

従来アルゴリズムは「損失 = 輻輳」と仮定。現代のネットではWiFiノイズやバッファブロートによる非輻輳損失が多い。

BBR (Bottleneck Bandwidth and RTT) のアイデア:

「損失ではなく、実際の帯域とRTTを直接測定してcwndを決めよう」

  • 周期的に送信レートを上げて帯域を探索。
  • 最小RTTを記憶 (バッファブロート検出)。
  • cwnd = BW x RTTに近づけ、キューイングを最小化。

3.8 BBRの成果

Google google.comとYouTubeのBBR移行後:

  • YouTube再バッファリング4%減。
  • google.com応答時間減。
  • 開発途上国ユーザーに特に効果。

Linux 4.9+に搭載。sysctl net.ipv4.tcp_congestion_control=bbrで有効化。

3.9 BBRの公平性論争

BBR v1はCubicと共存すると帯域を多く取る — 公平性問題。BBRv2 (2019)、v3 (2023) で改善。

アルゴリズム特徴用途
RenoAIMD、古典レガシー
Cubic3次関数増加Linuxデフォルト、WAN
BBRBW/RTT直接測定高速 + 非輻輳損失
DCTCPECNマーキングデータセンター
CoPA低遅延優先ビデオ会議

4. Nagle と Delayed ACK — 最悪の組み合わせ

4.1 Nagleアルゴリズム

小パケットの山はオーバーヘッドが大きい (40バイトヘッダ / 1バイトデータ = 2.5%)。

Nagle: 「ACK未到着の小パケットがあれば、新しい小データはACKを待ってまとめて送れ」。

setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));

効率に良いが遅延が増える。

4.2 Delayed ACK

受信側も効率のためACKを即送信せず、次のデータに piggyback するか最大200ms待つ (Linuxデフォルトは40ms)。

4.3 Nagle + Delayed ACK = 40ms遅延

A: 最初の小パケット送信
B: ACK保留 (もっと来るかも)
A: 2つ目の小パケット、Nagleが ACKを待機
   -> Nagle:ACK来ない、待とう」
   -> Delayed ACK: 「データ来ない、40ms後にACK   -> 40ms後ACK到着
A: 2つ目送信

リアルタイムアプリ (Telnet, SSH, リモートゲーム) では明示的にTCP_NODELAY必須。

4.4 TCP_CORK — 逆方向

「バッファに溜めて一気に送れ」。sendfile + TCP_CORKがnginxの静的ファイル最適化。

int cork = 1;
setsockopt(fd, IPPROTO_TCP, TCP_CORK, &cork, sizeof(int));
writev(fd, iov, 10);
cork = 0;
setsockopt(fd, IPPROTO_TCP, TCP_CORK, &cork, sizeof(int));

5. TCP Fast Open — Handshakeをスキップ

HTTPリクエストごとに最低1.5 RTTの handshake コスト。

5.1 TFO (RFC 7413, 2014)

初回接続でサーバがcookieを発行。以降はクライアントがSYNにcookie + リクエストを乗せる。

1回目:  SYN -> SYN+ACK(cookie) -> ACK -> GET
2回目:  SYN(cookie, GET) -> SYN+ACK(data)

5.2 普及しなかった理由

  • ミドルボックス (FW、NAT) が非標準SYNを破棄。
  • サーバ側cookie状態管理の負担。
  • HTTP/2のconnection reuseで緩和済み。

QUICの0-RTT handshakeが問題を回避。

6. よくあるエラーの本当の意味

6.1 Connection refused

SYNにRST応答 — 相手ポートがlistenしていない。

6.2 Connection reset by peer

ESTABLISHED中にRST受信 — プロセスクラッシュ、SO_LINGER 0、FWのactive reset。

6.3 Broken pipe

相手が既にクローズしたソケットにwrite。

6.4 Connection timed out

SYNを何度送っても応答なし (5-7回再送、60秒+)。ブラックホールFWや相手ダウン。

6.5 No route to host

ルーティングテーブルに経路なし — VPN切断、ルーティング設定ミス。

7. 実務チューニング — 主要sysctl

7.1 Backlog とキュー

net.core.somaxconn            # listen backlog上限
net.ipv4.tcp_max_syn_backlog  # SYN_RCVDキュー
net.core.netdev_max_backlog   # NIC -> カーネルキュー

nginx listen 80 backlog=65535にはカーネル上限も必要。

7.2 TIME_WAIT

net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_fin_timeout=30
net.ipv4.ip_local_port_range

7.3 Keep-alive

net.ipv4.tcp_keepalive_time=600
net.ipv4.tcp_keepalive_intvl=60
net.ipv4.tcp_keepalive_probes=3

デフォルト2時間はLB背後には長すぎる。

7.4 バッファ

net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.ipv4.tcp_rmem="4096 131072 16777216"
net.ipv4.tcp_wmem="4096 131072 16777216"

BDPに合わせる。10Gbps x 50ms = 62MB。

7.5 輻輳制御

net.ipv4.tcp_congestion_control=bbr
net.core.default_qdisc=fq

8. QUIC — TCPを捨てる

8.1 TCPの根本的限界

  1. Head-of-Line Blocking: 1パケット損失で後続全ストリーム停止。HTTP/2も同じTCP上で継承。
  2. Handshakeコスト: TCP 3-way + TLS 1.2 2-RTT = 3 RTT。
  3. ミドルボックスの硬直化: 新TCPオプションはISP FWで弾かれる。TFOやMPTCPが失敗した理由。
  4. カーネル依存: TCP改善にカーネルアップグレードが必要。

8.2 QUICの解法 — UDP上のユーザー空間トランスポート

Google実験 (2012) -> IETF標準 (RFC 9000, 2021) -> HTTP/3の基盤。

  • UDP上 — ミドルボックスには通常UDPに見える。
  • ユーザー空間ライブラリ — アプリと一緒に配布。
  • TLS 1.3統合。
  • 真のストリーム多重化 — ストリーム間のHoLなし。

8.3 0-RTT Handshake

再接続時は以前のsession ticketで初回パケットにデータを乗せる。

初回:   QUIC handshake (1 RTT)
再接続: データ即送信 (0 RTT)

8.4 Connection Migration

Connection IDで識別するため、IPが変わっても接続維持。モバイルでWiFi <-> 4G切替が無停止。

8.5 HTTP/3 = HTTP over QUIC

HTTP意味論はHTTP/2と同じ、トランスポートのみQUICに置換。主要ブラウザとCDN (Cloudflare, Akamai, Fastly) が対応。

8.6 QUICの欠点

  • CPU使用量増 (暗号化必須、ユーザー空間)。
  • ミドルボックス互換性 (一部FWはUDP制限)。
  • 実装複雑度。
  • 観察困難 — 暗号化ペイロード、ツール少。

Google、Metaの社内計測: HTTP/3はモバイルで約10%遅延減、デスクトップは差小。

9. 実戦デバッグツールキット

9.1 接続状態確認

ss -tan
ss -tan state established
ss -tnp | grep :443
ss -s

9.2 パケットキャプチャ

tcpdump -i any -w capture.pcap 'port 443'
wireshark capture.pcap

9.3 輻輳制御の観察

ss -ti

出力例:

ESTAB  ... cubic cwnd:10 ssthresh:7 bytes_acked:1234 bytes_received:5678 rtt:25.3/3.1 rcv_rtt:25.1 delivered:10 ...

cwndが小さくretransが多ければ輻輳発生中。

9.4 bpftrace

bpftrace -e 'kprobe:tcp_retransmit_skb { printf("retrans pid=%d\n", pid); }'

9.5 Mtr

mtr -r -c 100 example.com

各ホップの損失率 + RTTでISP品質診断。

10. おわりに — 50年のTCP、そしてその先

TCPは1974年のVint CerfとBob Kahnの論文に始まる。マイルストーン:

  • 1988: Jacobson輻輳制御。
  • 1992: Window Scaling。
  • 1996: SACK。
  • 2006: Cubic。
  • 2016: BBR。
  • 2021: QUIC (RFC)。

QUICはトランスポート層をユーザー空間へ移した。アプリごとに独自のトランスポート戦略を持て、カーネル更新なしに進化できる。Facebookのmvfst、Cloudflareのquiche、GoogleのgQUICが次の10年のインターネットを形作る。一方でTCPは消えない。トラフィックの80%+は今もTCP。

次回はTLS/SSLとPKIの内部 — 証明書チェイン、暗号スイート、0-RTTのreplay危険、QUICのTLS統合、そしてポスト量子暗号の到来。

参考資料

  • RFC 9293 — Transmission Control Protocol (2022改訂)。
  • Van Jacobson — "Congestion Avoidance and Control" (SIGCOMM, 1988)。
  • Cardwell et al — "BBR: Congestion-Based Congestion Control" (ACM Queue, 2016)。
  • RFC 9000 — QUIC。
  • RFC 9114 — HTTP/3。
  • "Computer Networks: A Systems Approach" — Peterson & Davie。
  • Brendan Gregg — Linux Performanceブログ。
  • Marek Majkowski (Cloudflare) — TCP内部関連記事。
  • "High Performance Browser Networking" — Ilya Grigorik (O'Reilly, 2013)。