このページの目次

TCP/IP プロトコルスタック

カバー範囲: IPv4/IPv6 送受信パス → TCP 状態マシン → 輻輳制御 (CUBIC/BBR) → フロー制御 (スライドウィンドウ) → ソケットバッファ (sk_wmem/sk_rmem) → TCP ファストパス カーネルバージョン: 2.6 ~ 6.x

概要

Linux TCP/IP スタックは、カーネル内で最も最適化が進んでいるサブシステムの1つです。高速パス(確立済みソケット、順序どおり到着するパケット)では、1パケットあたりの処理は数十命令程度です。本稿では、TCP の受信高速パスと輻輳制御に焦点を当てます。

IPv4 送受信

ip_rcv → ip_local_deliver(受信)

// net/ipv4/ip_input.c
ip_rcv()
  ├─ 検証: version==4, チェックサム OK, 長さ妥当
  ├─ netfilter NF_INET_PRE_ROUTING (iptables raw/mangle)
  └─ ip_rcv_finish()
      ├─ ルーティングテーブル参照: fib_lookup() → dst_entry
      │   → RTN_LOCAL: ローカル宛 → ip_local_deliver()
      │   → RTN_UNICAST: フォワーディング → ip_forward()
      └─ ip_local_deliver()
          ├─ netfilter NF_INET_LOCAL_IN (iptables filter)
          └─ ip_local_deliver_finish()
              └─ ipprot->handler → tcp_v4_rcv() / udp_rcv()

ip_queue_xmit(送信)

// net/ipv4/ip_output.c
__ip_queue_xmit(sk)
  ├─ ルーティング参照: ip_route_output_ports() → dst_entry + ソースIP
  ├─ IP ヘッダ構築: プロトコル, TTL, DFフラグ, ... 
  ├─ netfilter NF_INET_LOCAL_OUT
  └─ ip_local_out()
      ├─ netfilter NF_INET_POST_ROUTING
      └─ dev_queue_xmit() → QoS qdisc → NIC ドライバ ndo_start_xmit()

TCP 高速パス: tcp_rcv_established

// net/ipv4/tcp_input.c
// TCP 受信の最もホットなパス(確立済みソケット、順序どおり到着するパケット)

tcp_rcv_established(sk, skb)
  // fast path(90%以上のパケットがここを通る):
  ├─ ヘッダ予測チェック:
  │   ├─ PSHビット? → スキップ
  │   ├─ 順序どおり到着? (seq == rcv_nxt) ✓
  │   ├─ ウィンドウが非ゼロ? ✓
  │   └─ URG/RST/SYN? → スキップ
  │
  ├─ データを受信キューにコピー: tcp_queue_rcv()
  │   └─ skb_copy_datagram_msg() → ユーザーバッファへ直接? (MSG_DONTWAIT の場合)
  │
  ├─ rcv_nxt とウィンドウ告知を更新
  │
  └─ データがキューに到着済みなら → sk_data_ready() → epoll/read をウェイクアップ

  // slow path:
  └─ OOO(順序不同)/ SACK / ウィンドウ満杯 / FIN / RST → tcp_validate_incoming()

TCP 状態マシン

TCP 状態マシン: 3ウェイハンドシェイクからアクティブ/パッシブクローズへ CLOSED LISTEN SYN_RECV ESTABLISHED bind + listen accept SYN active close (FIN) FIN_WAIT1 FIN_WAIT2 CLOSE_WAIT (相手がクローズ、ローカルはまだクローズしていない) ACK CLOSING TIME_WAIT (2MSL) LAST_ACK CLOSED ESTABLISHED 以降の2つのパス: アクティブクローズは FIN_WAIT1→FIN_WAIT2→TIME_WAIT(2MSL) を経るか、同時クローズの場合は CLOSING を経る。 パッシブクローズ(相手が先に FIN を送信)は CLOSE_WAIT→LAST_ACK→CLOSED を経る。

輻輳制御

// net/ipv4/tcp_cong.c
// プラグイン可能な輻輳制御モジュール:
struct tcp_congestion_ops {
    void (*cong_avoid)(struct sock *sk, u32 ack, u32 acked);
    void (*ssthresh)(struct sock *sk);    // パケット損失時にスロースタート閾値をリセット
    u32  (*undo_cwnd)(struct sock *sk);   // 偽の再送検出 → cwnd をロールバック
};

// CUBIC (デフォルト):
//   cwnd = C * (t - K)^3 + W_max
//   K = cube_root(W_max * beta / C)
//   利点: 高速な回復, 公平性, 高速リンクでのパフォーマンス

// BBR (Bottleneck Bandwidth and RTT, Google):
//   パケット損失に依存せず、BDP (Bandwidth-Delay Product) に基づく
//   測定: 最大 BW (直近10ラウンド) と最小 RTT (直近10秒)
//   目標: inflight ≈ BDP を維持
//   利点: バッファ膨張 (bufferbloat) があるリンクで CUBIC より大幅に優れる

TCP ソケットバッファのチューニング

# 送信バッファ (デフォルト 16KB, 最大値まで自動拡張)
cat /proc/sys/net/ipv4/tcp_wmem  # min default max
# 受信バッファ
cat /proc/sys/net/ipv4/tcp_rmem

# 自動チューニング (デフォルト有効):
#   sk_wmem と sk_rmem は RTT と帯域幅の探査に基づき自動拡張される
#   → 高 BDP リンクでは手動で大きなバッファを設定する必要はない

デバッグ

# TCP 接続状態
ss -tianp   # cwnd, rtt, rcv_mss, 全統計情報を含む

# 輻輳制御
sysctl net.ipv4.tcp_congestion_control
cat /proc/sys/net/ipv4/tcp_available_congestion_control

# パケット損失/再送統計
nstat -a | grep -E 'TcpRetrans|TcpExt'
cat /proc/net/snmp | grep Tcp

# TCP 状態のトレース
bpftrace -e 'kprobe:tcp_set_state { printf("%s: %d -> %d\n", comm, arg1, arg2); }'

参考

  • ソースコード⁠: net/ipv4/tcp_input.c, net/ipv4/tcp_output.c, net/ipv4/tcp_cong.c, net/ipv4/tcp_cubic.c, net/ipv4/tcp_bbr.c
  • RFC: RFC 793 (TCP), RFC 5681 (Congestion Control), RFC 8312 (CUBIC)
  • LWN: "TCP fast path", "BBR congestion control"

キーワード: tcp_rcv_established, fast path, CUBIC, BBR, congestion control, socket buffer, TCP state machine