---
title: TCP/IP プロトコルスタック
url: https://doc.liz6.com/ja/linux-kernel/06-network-subsystem/02-TCP-IP
locale: ja
area: linux-kernel
tags:
- linux-kernel
- network-subsystem
date: 2026-06-30
modified: 2026-07-16
description: 'カバー範囲: IPv4/IPv6 送受信パス → TCP 状態マシン → 輻輳制御 (CUBIC/BBR) → フロー制御 (スライドウィンドウ) → ソケットバッファ (sk_wmem/sk_rmem) → TCP ファストパス カーネルバージョン: 2.6 ~ 6.x'
---

# 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（受信）

```c
// 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（送信）

```c
// 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

```c
// 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 状態マシン

<svg viewBox="0 0 720 420" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="TCP 状態マシン: 3ウェイハンドシェイクからアクティブ/パッシブクローズへ">
  <defs>
    <marker id="tcpFsmArrow" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <rect width="720" height="420" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">TCP 状態マシン: 3ウェイハンドシェイクからアクティブ/パッシブクローズへ</text>

  <!-- row1: 接続確立パス -->
  <rect x="20" y="54" width="90" height="36" rx="8" fill="#e2e8f0"/>
  <text x="65" y="77" text-anchor="middle" font-size="12" font-weight="700" fill="#334155">CLOSED</text>
  <rect x="140" y="54" width="100" height="36" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="190" y="77" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">LISTEN</text>
  <rect x="270" y="54" width="110" height="36" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="325" y="77" text-anchor="middle" font-size="12" font-weight="700" fill="#3730a3">SYN_RECV</text>
  <rect x="410" y="54" width="150" height="36" rx="8" fill="#4f46e5"/>
  <text x="485" y="77" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">ESTABLISHED</text>

  <line x1="110" y1="72" x2="137" y2="72" stroke="#475569" stroke-width="1.6" marker-end="url(#tcpFsmArrow)"/>
  <text x="125" y="46" text-anchor="middle" font-size="10.5" fill="#64748b">bind + listen</text>
  <line x1="240" y1="72" x2="267" y2="72" stroke="#475569" stroke-width="1.6" marker-end="url(#tcpFsmArrow)"/>
  <text x="255" y="46" text-anchor="middle" font-size="10.5" fill="#64748b">accept</text>
  <line x1="380" y1="72" x2="407" y2="72" stroke="#475569" stroke-width="1.6" marker-end="url(#tcpFsmArrow)"/>
  <text x="395" y="46" text-anchor="middle" font-size="10.5" fill="#64748b">SYN</text>

  <!-- ESTABLISHED から分岐 -->
  <line x1="485" y1="90" x2="310" y2="147" stroke="#475569" stroke-width="1.6" marker-end="url(#tcpFsmArrow)"/>
  <text x="395" y="118" text-anchor="middle" font-size="10.5" fill="#64748b">active close (FIN)</text>
  <line x1="485" y1="90" x2="617" y2="147" stroke="#475569" stroke-width="1.6" marker-end="url(#tcpFsmArrow)"/>

  <!-- row2: クローズ分岐 -->
  <rect x="250" y="150" width="110" height="36" rx="8" fill="#ffedd5"/>
  <text x="305" y="173" text-anchor="middle" font-size="12" font-weight="700" fill="#9a3412">FIN_WAIT1</text>
  <rect x="390" y="150" width="110" height="36" rx="8" fill="#ffedd5"/>
  <text x="445" y="173" text-anchor="middle" font-size="12" font-weight="700" fill="#9a3412">FIN_WAIT2</text>
  <rect x="540" y="150" width="160" height="44" rx="8" fill="#ffedd5"/>
  <text x="620" y="169" text-anchor="middle" font-size="12" font-weight="700" fill="#9a3412">CLOSE_WAIT</text>
  <text x="620" y="184" text-anchor="middle" font-size="9.5" fill="#c2410c">(相手がクローズ、ローカルはまだクローズしていない)</text>

  <line x1="360" y1="168" x2="387" y2="168" stroke="#475569" stroke-width="1.6" marker-end="url(#tcpFsmArrow)"/>
  <text x="373" y="160" text-anchor="middle" font-size="10.5" fill="#64748b">ACK</text>

  <!-- row3: 終了処理 -->
  <rect x="250" y="230" width="110" height="36" rx="8" fill="#ffedd5"/>
  <text x="305" y="253" text-anchor="middle" font-size="12" font-weight="700" fill="#9a3412">CLOSING</text>
  <rect x="390" y="230" width="140" height="36" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="460" y="253" text-anchor="middle" font-size="12" font-weight="700" fill="#115e59">TIME_WAIT (2MSL)</text>
  <rect x="580" y="230" width="110" height="36" rx="8" fill="#ffedd5"/>
  <text x="635" y="253" text-anchor="middle" font-size="12" font-weight="700" fill="#9a3412">LAST_ACK</text>

  <line x1="305" y1="186" x2="305" y2="227" stroke="#475569" stroke-width="1.6" marker-end="url(#tcpFsmArrow)"/>
  <line x1="445" y1="186" x2="459" y2="227" stroke="#475569" stroke-width="1.6" marker-end="url(#tcpFsmArrow)"/>
  <line x1="360" y1="248" x2="387" y2="248" stroke="#475569" stroke-width="1.6" marker-end="url(#tcpFsmArrow)"/>
  <line x1="620" y1="194" x2="635" y2="227" stroke="#475569" stroke-width="1.6" marker-end="url(#tcpFsmArrow)"/>

  <!-- row4: パッシブクローズの最終状態 -->
  <rect x="580" y="310" width="110" height="36" rx="8" fill="#e2e8f0"/>
  <text x="635" y="333" text-anchor="middle" font-size="12" font-weight="700" fill="#334155">CLOSED</text>
  <line x1="635" y1="266" x2="635" y2="307" stroke="#475569" stroke-width="1.6" marker-end="url(#tcpFsmArrow)"/>

  <rect x="20" y="356" width="680" height="50" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="36" y="376" font-size="12.5" fill="#3730a3">ESTABLISHED 以降の2つのパス: アクティブクローズは FIN_WAIT1→FIN_WAIT2→TIME_WAIT(2MSL) を経るか、同時クローズの場合は CLOSING を経る。</text>
  <text x="36" y="396" font-size="12.5" fill="#3730a3">パッシブクローズ（相手が先に FIN を送信）は CLOSE_WAIT→LAST_ACK→CLOSED を経る。</text>
</svg>

## 輻輳制御

```c
// 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 ソケットバッファのチューニング

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

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

## デバッグ

```bash
# 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*
