TCP
TCP(Transmission Control Protocol)完全ガイド
はじめに
TCP(Transmission Control Protocol)は、インターネットを支える最も重要なトランスポート層プロトコルの一つである。1981年にRFC 793として標準化されて以来、Webブラウジング、メール送受信、ファイル転送、データベース通信など、信頼性の高いデータ転送が必要なあらゆる場面で使用されている。
本記事では、TCPの基本概念から内部アーキテクチャ、状態遷移、フロー制御、輻輳制御、セキュリティ、パフォーマンスチューニング、そして最新の拡張仕様に至るまで、TCPの全容を体系的に解説する。実際のネットワーク設定例やトラブルシューティング手法も交えながら、実務で活用できる知識を提供する。
第1章: TCPの基本概念とOSI参照モデルにおける位置づけ
1.1 OSI参照モデルとTCP/IPモデル
ネットワーク通信を理解するためには、まずプロトコルの階層構造を理解する必要がある。
OSI参照モデル(7層):
| 層 | 名称 | 役割 | プロトコル例 |
|---|---|---|---|
| 第7層 | アプリケーション層 | ユーザーインターフェース | HTTP, FTP, SMTP, DNS |
| 第6層 | プレゼンテーション層 | データ形式の変換 | SSL/TLS, JPEG, ASCII |
| 第5層 | セッション層 | セッション管理 | NetBIOS, RPC |
| 第4層 | トランスポート層 | エンドツーエンド通信 | TCP, UDP |
| 第3層 | ネットワーク層 | ルーティング | IP, ICMP, ARP |
| 第2層 | データリンク層 | フレーム転送 | Ethernet, Wi-Fi |
| 第1層 | 物理層 | 電気信号・光信号 | ケーブル, 光ファイバー |
TCP/IPモデル(4層):
| 層 | 名称 | OSI対応 | プロトコル例 |
|---|---|---|---|
| 第4層 | アプリケーション層 | 第5〜7層 | HTTP, FTP, SMTP |
| 第3層 | トランスポート層 | 第4層 | TCP, UDP |
| 第2層 | インターネット層 | 第3層 | IP, ICMP |
| 第1層 | ネットワークインターフェース層 | 第1〜2層 | Ethernet, Wi-Fi |
TCPはトランスポート層に位置し、IPの上位プロトコルとして動作する。IPが「ベストエフォート」でパケットを配送するのに対し、TCPは信頼性のある順序付きデータ転送を保証する。
1.2 TCPの基本特性
TCPには以下の基本特性がある:
- コネクション指向(Connection-Oriented): 通信開始前に3ウェイハンドシェイクで接続を確立する
- 信頼性のあるデータ転送(Reliable Delivery): 確認応答(ACK)と再送制御により、データの欠損を防ぐ
- 順序保証(Ordered Delivery): シーケンス番号により、データの順序を保証する
- フロー制御(Flow Control): 受信側のバッファ容量に応じて送信速度を調整する
- 輻輳制御(Congestion Control): ネットワークの混雑状況に応じて送信速度を調整する
- 全二重通信(Full-Duplex): 双方向で同時にデータを送受信できる
- バイトストリーム(Byte Stream): メッセージ境界を持たないバイトストリームとしてデータを扱う
1.3 TCPとUDPの比較
| 特性 | TCP | UDP |
|---|---|---|
| コネクション | コネクション指向 | コネクションレス |
| 信頼性 | あり(ACK・再送) | なし |
| 順序保証 | あり | なし |
| フロー制御 | あり | なし |
| 輻輳制御 | あり | なし |
| ヘッダサイズ | 20〜60バイト | 8バイト |
| 速度 | 相対的に遅い | 相対的に速い |
| 用途 | Web、メール、ファイル転送 | DNS、VoIP、ストリーミング、ゲーム |
1.4 TCPが使用される代表的なプロトコルとポート番号
| プロトコル | ポート番号 | 用途 |
|---|---|---|
| HTTP | 80 | Webページの転送 |
| HTTPS | 443 | 暗号化されたWeb通信 |
| FTP | 20, 21 | ファイル転送 |
| SSH | 22 | セキュアリモートアクセス |
| SMTP | 25, 587 | メール送信 |
| POP3 | 110 | メール受信 |
| IMAP | 143, 993 | メール受信(高機能) |
| MySQL | 3306 | データベース接続 |
| PostgreSQL | 5432 | データベース接続 |
| Redis | 6379 | インメモリデータストア |
第2章: TCPセグメントの構造
2.1 TCPヘッダの詳細
TCPセグメントは、TCPヘッダとペイロード(データ)で構成される。TCPヘッダは最小20バイト、オプションを含めると最大60バイトになる。
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 | |C|E|U|A|P|R|S|F| |
| Offset| Rsrvd |W|C|R|C|S|S|Y|I| Window |
| | |R|E|G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2.2 各フィールドの詳細説明
送信元ポート番号(Source Port)- 16ビット
送信側アプリケーションのポート番号。0〜65535の範囲。クライアント側では通常、エフェメラルポート(一時ポート、Linuxでは32768〜60999)が自動的に割り当てられる。
# Linuxでエフェメラルポートの範囲を確認
$ cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999
# エフェメラルポートの範囲を変更
$ sudo sysctl -w net.ipv4.ip_local_port_range="10000 65535"
宛先ポート番号(Destination Port)- 16ビット
受信側アプリケーションのポート番号。Well-Knownポート(0〜1023)はIANAにより管理されている。
シーケンス番号(Sequence Number)- 32ビット
送信するデータの最初のバイトに割り当てられる番号。コネクション確立時にISN(Initial Sequence Number)がランダムに選ばれ、以降はデータのバイト数分だけインクリメントされる。
例: ISN = 1000, 500バイトのデータを送信
セグメント1: Seq = 1000, Data = 500バイト
セグメント2: Seq = 1500, Data = 500バイト
セグメント3: Seq = 2000, Data = 500バイト
確認応答番号(Acknowledgment Number)- 32ビット
次に受信を期待するバイトのシーケンス番号。ACKフラグが1のときのみ有効。累積確認応答(Cumulative ACK)方式を採用しており、この番号より前のすべてのバイトの受信完了を意味する。
例: Ack = 1500 は、シーケンス番号1499までのデータを受信完了したことを示す
データオフセット(Data Offset)- 4ビット
TCPヘッダの長さを4バイト単位で示す。最小値5(20バイト)、最大値15(60バイト)。
予約(Reserved)- 4ビット
将来の使用のために予約されたフィールド。すべて0でなければならない。
制御フラグ(Control Flags)- 8ビット
| フラグ | 名称 | 説明 |
|---|---|---|
| CWR | Congestion Window Reduced | 輻輳ウィンドウの縮小を通知(RFC 3168) |
| ECE | ECN-Echo | ECN(Explicit Congestion Notification)のエコー(RFC 3168) |
| URG | Urgent | 緊急データの存在を示す |
| ACK | Acknowledgment | 確認応答番号が有効であることを示す |
| PSH | Push | 受信側にデータを即座にアプリケーションに渡すよう要求 |
| RST | Reset | コネクションの強制リセット |
| SYN | Synchronize | コネクション確立の開始(シーケンス番号の同期) |
| FIN | Finish | コネクションの正常終了 |
ウィンドウサイズ(Window Size)- 16ビット
受信側が受け入れ可能なデータ量をバイト単位で示す。フロー制御に使用される。最大値は65535バイトだが、ウィンドウスケーリングオプションにより最大1GBまで拡張可能。
チェックサム(Checksum)- 16ビット
TCPヘッダ、データ、およびIPヘッダの一部(擬似ヘッダ)に対する整合性チェック。送信側で計算し、受信側で検証する。
擬似ヘッダの構造(IPv4):
+--------+--------+--------+--------+
| Source Address |
+--------+--------+--------+--------+
| Destination Address |
+--------+--------+--------+--------+
| zero | PTCL | TCP Length |
+--------+--------+--------+--------+
緊急ポインタ(Urgent Pointer)- 16ビット
URGフラグが1のとき有効。緊急データの最後のバイトの位置を示す。実務ではほとんど使用されない。
2.3 TCPオプション
TCPオプションはヘッダの可変長部分に含まれ、プロトコルの機能を拡張する。
| オプション | Kind | 長さ | 説明 |
|---|---|---|---|
| End of Options | 0 | 1 | オプションリストの終端 |
| No-Operation (NOP) | 1 | 1 | パディング用 |
| MSS | 2 | 4 | 最大セグメントサイズの通知 |
| Window Scale | 3 | 3 | ウィンドウスケーリング係数 |
| SACK Permitted | 4 | 2 | 選択的確認応答の使用許可 |
| SACK | 5 | 可変 | 選択的確認応答のブロック情報 |
| Timestamps | 8 | 10 | タイムスタンプ(RTT計測・PAWS) |
MSS(Maximum Segment Size)
TCPセグメントの最大ペイロードサイズ。SYNパケットでのみ交渉される。通常、Ethernet環境では1460バイト(MTU 1500 - IPヘッダ20 - TCPヘッダ20)。
# MSS値の確認例(tcpdumpで確認)
$ sudo tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0' -v
# 出力例: ... options [mss 1460,sackOK,TS val 123456 ecr 0,nop,wscale 7]
ウィンドウスケーリング(Window Scale)
16ビットのウィンドウサイズフィールドを拡張する。スケールファクターnの場合、実際のウィンドウサイズは Window Size × 2^n となる。
例: Window Size = 32768, Scale Factor = 7
実際のウィンドウサイズ = 32768 × 128 = 4,194,304 バイト(約4MB)
# ウィンドウスケーリングの設定(Linux)
$ sudo sysctl -w net.ipv4.tcp_window_scaling=1
# 現在の設定を確認
$ sysctl net.ipv4.tcp_window_scaling
SACK(Selective Acknowledgment)
累積ACKでは、途中のセグメントが欠落した場合、それ以降のすべてのセグメントを再送する必要がある。SACKは受信済みの不連続なデータブロックを通知することで、必要なセグメントのみを再送可能にする。
例: セグメント1(Seq=1000), 2(Seq=2000), 3(Seq=3000), 4(Seq=4000)を送信
セグメント2が消失した場合:
SACK無し: ACK=2000 → セグメント2,3,4をすべて再送
SACK有り: ACK=2000, SACK=3000-5000 → セグメント2のみを再送
# SACKの有効化(Linux)
$ sudo sysctl -w net.ipv4.tcp_sack=1
タイムスタンプ(Timestamps)
2つの値を持つ:TSval(Timestamp Value)とTSecr(Timestamp Echo Reply)。
用途1: RTT(Round-Trip Time)の正確な計測
送信側: TSval = 100 で送信
受信側: TSecr = 100 で返答
送信側: RTT = 現在時刻 - TSvalの送信時刻
用途2: PAWS(Protection Against Wrapped Sequences) シーケンス番号は32ビット(約4GB)で一巡する。高速ネットワークではシーケンス番号が短時間で一巡する可能性があるため、タイムスタンプを使用して古いセグメントを判別する。
# タイムスタンプの有効化(Linux)
$ sudo sysctl -w net.ipv4.tcp_timestamps=1
2.4 MSS、MTU、パスMTU探索
MTU(Maximum Transmission Unit): データリンク層で転送可能な最大フレームサイズ。Ethernetのデフォルトは1500バイト。
MSS(Maximum Segment Size): TCPペイロードの最大サイズ。MSS = MTU - IPヘッダ - TCPヘッダ。
パスMTU探索(Path MTU Discovery): 通信経路上の最小MTUを発見する仕組み。IPヘッダのDF(Don't Fragment)ビットを設定してパケットを送信し、途中のルーターがフラグメントできない場合にICMP "Fragmentation Needed" メッセージを返送することで、適切なMTUを学習する。
# 現在のMTU確認
$ ip link show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
# MTUの変更
$ sudo ip link set dev eth0 mtu 9000 # ジャンボフレーム
# パスMTU探索の確認
$ tracepath -n 192.168.1.1
1?: [LOCALHOST] pmtu 1500
1: 192.168.1.1 0.234ms reached
Resume: pmtu 1500
第3章: TCPコネクション管理
3.1 3ウェイハンドシェイク(コネクション確立)
TCPコネクションの確立は、3ウェイハンドシェイクと呼ばれる3段階のプロセスで行われる。
クライアント サーバー
| |
| ① SYN (Seq=x) |
|----------------------------------->|
| |
| ② SYN+ACK (Seq=y, Ack=x+1) |
|<-----------------------------------|
| |
| ③ ACK (Seq=x+1, Ack=y+1) |
|----------------------------------->|
| |
| ===== コネクション確立 ===== |
| |
各ステップの詳細:
ステップ1: SYN(クライアント → サーバー)
- クライアントがSYNフラグをセットしたセグメントを送信
- ISN(Initial Sequence Number)をランダムに選択(例: Seq=1000)
- MSS、ウィンドウスケーリング、SACK許可などのオプションを含む
- クライアントの状態:
CLOSED→SYN_SENT
ステップ2: SYN+ACK(サーバー → クライアント)
- サーバーがSYN+ACKフラグをセットしたセグメントを送信
- サーバー独自のISNを選択(例: Seq=5000)
- クライアントのSYNに対するACK(Ack=1001)を含む
- サーバーの状態:
LISTEN→SYN_RECEIVED
ステップ3: ACK(クライアント → サーバー)
- クライアントがACKセグメントを送信(Ack=5001)
- このセグメントにはデータを含めることが可能
- クライアントの状態:
SYN_SENT→ESTABLISHED - サーバーの状態:
SYN_RECEIVED→ESTABLISHED
# tcpdumpで3ウェイハンドシェイクを観察
$ sudo tcpdump -i eth0 -S 'host 192.168.1.100 and port 80'
# 出力例:
# 10:00:00.000 IP client.50000 > server.80: Flags [S], seq 1000, win 65535, options [mss 1460,...], length 0
# 10:00:00.001 IP server.80 > client.50000: Flags [S.], seq 5000, ack 1001, win 65535, options [mss 1460,...], length 0
# 10:00:00.002 IP client.50000 > server.80: Flags [.], ack 5001, win 65535, length 0
3.2 ISN(Initial Sequence Number)の生成
ISNはセキュリティ上の理由から予測不可能でなければならない。予測可能なISNはTCPシーケンス番号予測攻撃(TCP Sequence Prediction Attack)に悪用される可能性がある。
Linuxでの ISN生成方式:
- RFC 6528に基づくアルゴリズムを使用
ISN = M + F(localhost, localport, remotehost, remoteport, secretkey)- M: 4マイクロ秒ごとにインクリメントされるカウンター
- F: 暗号学的ハッシュ関数(MD5ベース)
3.3 SYNキュー(半開きコネクションキュー)とAcceptキュー
サーバーは2つのキューを管理する:
SYNキュー(半開きコネクションキュー):
- SYNを受信し、SYN+ACKを送信した後、最終ACKを待っている状態のコネクションを保持
SYN_RECEIVED状態のコネクションが格納される
Acceptキュー(完了コネクションキュー):
- 3ウェイハンドシェイクが完了し、
ESTABLISHED状態になったコネクションを保持 - アプリケーションが
accept()を呼び出すまで待機
# SYNキューのサイズ設定(Linux)
$ sudo sysctl -w net.ipv4.tcp_max_syn_backlog=4096
# Acceptキューのサイズ(listen()のbacklog引数で制御)
# アプリケーション側の設定例(Nginx)
# listen 80 backlog=2048;
# キューの状態確認
$ ss -ltn
# State Recv-Q Send-Q Local Address:Port Peer Address:Port
# LISTEN 0 128 0.0.0.0:80 0.0.0.0:*
# Recv-Q: Acceptキュー内の接続数
# Send-Q: Acceptキューの最大サイズ(backlog)
# SYN_RECEIVED状態の接続数確認
$ ss -tn state syn-recv | wc -l
3.4 SYN Cookies
SYNフラッド攻撃に対する防御機構。SYNキューを使用せずにコネクション情報をSYN+ACKのシーケンス番号にエンコードする。
# SYN Cookiesの有効化
$ sudo sysctl -w net.ipv4.tcp_syncookies=1
# SYN Cookiesの状態確認
$ sysctl net.ipv4.tcp_syncookies
SYN Cookiesの仕組み:
- サーバーがSYNを受信
- SYNキューにエントリを作成する代わりに、コネクション情報をハッシュしてISNとしてエンコード
- SYN+ACKを送信
- クライアントからACKを受信した際、ACK番号からコネクション情報を復元
- 正当なACKであれば、コネクションを確立
制限事項:
- TCPオプション(ウィンドウスケーリング、タイムスタンプなど)が制限される
- SACKが使用できない場合がある
3.5 TCP Fast Open(TFO)
TCP Fast Open(RFC 7413)は、3ウェイハンドシェイク中にデータを送信可能にすることで、コネクション確立のレイテンシを削減する拡張機能。
従来のTCP:
┌─────────┐ ┌─────────┐
│ クライアント│ │ サーバー │
└────┬────┘ └────┬────┘
│ SYN │
│───────────────────────────────>│ 1 RTT
│ SYN+ACK │
│<───────────────────────────────│
│ ACK + データ │
│───────────────────────────────>│ 2 RTT(データ到達)
│ │
TCP Fast Open:
┌─────────┐ ┌─────────┐
│ クライアント│ │ サーバー │
└────┬────┘ └────┬────┘
│ SYN + TFOクッキー + データ │
│───────────────────────────────>│ 1 RTT(データ到達)
│ SYN+ACK + データ │
│<───────────────────────────────│
│ ACK │
│───────────────────────────────>│
│ │
# TCP Fast Openの有効化(Linux)
# 0: 無効, 1: クライアントのみ, 2: サーバーのみ, 3: 両方
$ sudo sysctl -w net.ipv4.tcp_fastopen=3
# TFOの統計確認
$ cat /proc/net/tcp_fastopen
3.6 4ウェイハンドシェイク(コネクション終了)
TCPコネクションの正常終了は、4ウェイハンドシェイクで行われる。各方向のコネクションは独立して閉じられる(ハーフクローズ)。
クライアント サーバー
| |
| ① FIN (Seq=u) |
|----------------------------------->|
| |
| ② ACK (Ack=u+1) |
|<-----------------------------------|
| |
| (サーバーは残りのデータを送信可能)|
| |
| ③ FIN (Seq=v) |
|<-----------------------------------|
| |
| ④ ACK (Ack=v+1) |
|----------------------------------->|
| |
| TIME_WAIT (2MSL待機) |
| |
各状態の遷移:
- クライアント:
ESTABLISHED→FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT→CLOSED - サーバー:
ESTABLISHED→CLOSE_WAIT→LAST_ACK→CLOSED
同時クローズ(Simultaneous Close): 両方が同時にFINを送信した場合:
- 両方:
ESTABLISHED→FIN_WAIT_1→CLOSING→TIME_WAIT→CLOSED
3.7 TIME_WAIT状態
FINを最初に送信した側(アクティブクローズ側)は、最後のACKを送信した後、TIME_WAIT状態に入る。この状態は2MSL(Maximum Segment Lifetime)の間維持される。
TIME_WAITの目的:
- 最後のACKが消失した場合に再送できるようにする
- 古いコネクションの遅延セグメントが新しいコネクションに誤って受信されることを防ぐ
# MSLの確認(Linuxではデフォルト60秒)
$ sysctl net.ipv4.tcp_fin_timeout
net.ipv4.tcp_fin_timeout = 60
# TIME_WAIT状態のソケット数確認
$ ss -tan state time-wait | wc -l
# TIME_WAIT状態のソケットの詳細
$ ss -tan state time-wait
# TIME_WAITの再利用設定
# tcp_tw_reuse: TIME_WAITソケットをクライアント側で新しい接続に再利用
$ sudo sysctl -w net.ipv4.tcp_tw_reuse=1
# TIME_WAIT状態の最大数
$ sudo sysctl -w net.ipv4.tcp_max_tw_buckets=16384
高負荷サーバーでのTIME_WAIT問題:
大量の短寿命コネクションを処理するサーバーでは、TIME_WAITソケットが蓄積し、ポート枯渇が発生する可能性がある。
# TIME_WAIT対策の設定例
$ cat <<'EOF' | sudo tee /etc/sysctl.d/99-tcp-timewait.conf
# TIME_WAITソケットの再利用を有効化
net.ipv4.tcp_tw_reuse = 1
# TIME_WAIT状態の最大数を設定
net.ipv4.tcp_max_tw_buckets = 32768
# FINタイムアウトの短縮
net.ipv4.tcp_fin_timeout = 30
# エフェメラルポート範囲の拡大
net.ipv4.ip_local_port_range = 10000 65535
EOF
# 設定の適用
$ sudo sysctl --system
3.8 RST(リセット)による接続終了
RSTセグメントは、以下の場合に送信される:
- 存在しないポートへの接続試行: サーバーのポートがリスン状態にない場合
- コネクションの異常終了: アプリケーションが
SO_LINGERオプションでタイムアウト0を設定した場合 - 半開きコネクションの検出: 一方が再起動した後、もう一方がデータを送信した場合
# SO_LINGER設定によるRST送信(プログラム例)
# Pythonの場合:
# import socket, struct
# sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
# sock.close() # RSTが送信される
# RSTパケットの観察
$ sudo tcpdump -i eth0 'tcp[tcpflags] & tcp-rst != 0'
第4章: TCP状態遷移図
4.1 TCP状態遷移の全体像
TCPコネクションは、以下の11の状態を遷移する:
+---------+ ---------\ アクティブオープン
| CLOSED | \ -----------
+---------+<---------\ \ SYN送信
| ^ \ \ --------
パッシブオープン | | CLOSE \ \
---------- | | ---------- \ \
ソケット作成 | | ソケット削除 \ \
V | \ V
+---------+ +---------+
| LISTEN | | SYN_SENT|
+---------+ +---------+
SYN受信/ | | | |
SYN+ACK送信 | | SYN受信/ | |
-------- | | SYN+ACK送信 | |
| | -------- | |
V V V |
+---------+ +---------+|
|SYN_RCVD | |SYN_RCVD ||
+---------+ +---------+|
ACK受信/ | | SYN+ACK受信/ |
-------- | | ACK送信 |
| | -------- |
V | V
+---------+ +---------+
| ESTAB | | ESTAB |
+---------+ +---------+
4.2 完全な状態遷移表
| 現在の状態 | イベント | アクション | 次の状態 |
|---|---|---|---|
| CLOSED | アプリ: パッシブオープン | ソケット作成 | LISTEN |
| CLOSED | アプリ: アクティブオープン | SYN送信 | SYN_SENT |
| LISTEN | SYN受信 | SYN+ACK送信 | SYN_RECEIVED |
| LISTEN | アプリ: データ送信 | SYN送信 | SYN_SENT |
| SYN_SENT | SYN+ACK受信 | ACK送信 | ESTABLISHED |
| SYN_SENT | SYN受信 | SYN+ACK送信 | SYN_RECEIVED |
| SYN_RECEIVED | ACK受信 | - | ESTABLISHED |
| SYN_RECEIVED | アプリ: CLOSE | FIN送信 | FIN_WAIT_1 |
| ESTABLISHED | アプリ: CLOSE | FIN送信 | FIN_WAIT_1 |
| ESTABLISHED | FIN受信 | ACK送信 | CLOSE_WAIT |
| FIN_WAIT_1 | ACK受信 | - | FIN_WAIT_2 |
| FIN_WAIT_1 | FIN受信 | ACK送信 | CLOSING |
| FIN_WAIT_1 | FIN+ACK受信 | ACK送信 | TIME_WAIT |
| FIN_WAIT_2 | FIN受信 | ACK送信 | TIME_WAIT |
| CLOSING | ACK受信 | - | TIME_WAIT |
| TIME_WAIT | 2MSL経過 | ソケット削除 | CLOSED |
| CLOSE_WAIT | アプリ: CLOSE | FIN送信 | LAST_ACK |
| LAST_ACK | ACK受信 | ソケット削除 | CLOSED |
4.3 各状態の詳細説明
CLOSED
コネクションが存在しない初期状態。
LISTEN
サーバーがクライアントからのSYN要求を待っている状態。listen()システムコールにより遷移。
# LISTEN状態のソケット確認
$ ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:80 0.0.0.0:* nginx
LISTEN 0 128 0.0.0.0:443 0.0.0.0:* nginx
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* sshd
SYN_SENT
クライアントがSYNを送信し、SYN+ACKを待っている状態。
SYN_RECEIVED
サーバーがSYNを受信し、SYN+ACKを送信し、ACKを待っている状態。
# SYN_RECEIVED状態の確認(SYNフラッド攻撃の検知に有用)
$ ss -tn state syn-recv
$ netstat -an | grep SYN_RECV | wc -l
ESTABLISHED
コネクションが確立され、データ転送が可能な状態。
# ESTABLISHED状態の接続確認
$ ss -tn state established
$ ss -tnp state established | grep nginx
FIN_WAIT_1
アクティブクローズ側がFINを送信し、ACKを待っている状態。
FIN_WAIT_2
FINに対するACKを受信し、相手側のFINを待っている状態。
# FIN_WAIT_2タイムアウトの設定
$ sudo sysctl -w net.ipv4.tcp_fin_timeout=30
CLOSING
同時クローズが発生した場合の状態。FINを送信し、相手のFINも受信したが、自分のFINに対するACKをまだ受信していない状態。
TIME_WAIT
最後のACKを送信した後、2MSL(通常120秒)待機する状態。
CLOSE_WAIT
パッシブクローズ側がFINを受信し、ACKを送信した状態。アプリケーションがclose()を呼び出すまでこの状態が維持される。
# CLOSE_WAIT状態の確認(アプリケーションのバグの可能性を示す)
$ ss -tn state close-wait
# CLOSE_WAITが大量に存在する場合、アプリケーションがソケットを適切に
# クローズしていない可能性がある
LAST_ACK
パッシブクローズ側がFINを送信し、最後のACKを待っている状態。
4.4 ソケット状態の監視コマンド
# ssコマンドによる状態別集計
$ ss -tan | awk 'NR>1 {state[$1]++} END {for(s in state) print s, state[s]}' | sort -k2 -rn
ESTAB 1523
TIME-WAIT 342
LISTEN 15
CLOSE-WAIT 3
FIN-WAIT-2 1
# netstatによる状態別集計
$ netstat -an | grep tcp | awk '{print $6}' | sort | uniq -c | sort -rn
# 特定のプロセスの接続状態
$ ss -tnp | grep "pid=1234"
# リアルタイム監視
$ watch -n 1 'ss -tan | awk "NR>1 {state[\$1]++} END {for(s in state) print s, state[s]}" | sort -k2 -rn'
第5章: データ転送メカニズム
5.1 信頼性のあるデータ転送
TCPは以下のメカニズムにより信頼性のあるデータ転送を実現する:
- シーケンス番号: 各バイトに一意の番号を割り当て、順序を管理
- 確認応答(ACK): 受信したデータの確認を送信側に通知
- 再送制御: ACKが返らない場合にデータを再送
- チェックサム: データの整合性を検証
5.2 スライディングウィンドウ
スライディングウィンドウは、送信側と受信側の間でデータフローを効率的に管理するメカニズムである。
送信側のウィンドウ:
送信済み・ 送信可能 送信不可
ACK受信済み (ウィンドウ内) (ウィンドウ外)
├──────────┼──────────────────┼──────────────┤
1 2 3 │ 4 5 6 7 8 9 │ 10 11 12 13
│ │
└─ ウィンドウ左端 └─ ウィンドウ右端
(送信ベース)
受信側のウィンドウ:
受信済み・ 受信可能 受信不可
ACK送信済み (ウィンドウ内) (ウィンドウ外)
├──────────┼──────────────────┼──────────────┤
1 2 3 │ 4 5 6 7 8 9 │ 10 11 12 13
ウィンドウのスライド:
初期状態: [4 5 6 7 8 9] ウィンドウサイズ = 6
セグメント4,5のACK受信後:
[6 7 8 9 10 11] ウィンドウが右にスライド
5.3 遅延ACK(Delayed ACK)
すべてのセグメントに対して即座にACKを送信するのではなく、一定時間(通常40〜200ms)待機してからACKを送信する最適化手法。
利点:
- ACKパケット数の削減
- ピギーバック(データと一緒にACKを送信)の機会が増える
欠点:
- 小さなデータの送信時にレイテンシが増加する可能性
- Nagleアルゴリズムとの組み合わせで問題が発生する場合がある
# 遅延ACKの設定(Linux)
# tcp_delack_min: 遅延ACKの最小遅延(ミリ秒)
# デフォルトは40ms
# 遅延ACKを無効化(ソケットオプション)
# setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &flag, sizeof(flag));
5.4 Nagleアルゴリズム
小さなセグメントの送信を抑制し、ネットワーク効率を向上させるアルゴリズム(RFC 896)。
ルール:
- 送信可能なデータがMSS以上であれば即座に送信
- 未確認のデータがなければ即座に送信
- それ以外の場合、ACKを受信するまで待機してデータを蓄積
Nagle無効:
クライアント → サーバー: "H" (1バイト + 40バイトヘッダ)
クライアント → サーバー: "e" (1バイト + 40バイトヘッダ)
クライアント → サーバー: "l" (1バイト + 40バイトヘッダ)
...
Nagle有効:
クライアント → サーバー: "Hello World" (11バイト + 40バイトヘッダ)
# Nagleアルゴリズムの無効化(TCP_NODELAY)
# リアルタイム性が重要なアプリケーション(ゲーム、SSH、対話型通信)で使用
# Pythonの例:
# sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
# Nginxの設定:
# tcp_nodelay on; # デフォルトでon
# MySQLの設定:
# [mysqld]
# skip-name-resolve
# tcp_nodelay = ON
遅延ACKとNagleアルゴリズムの相互作用問題:
1. クライアントが小さなデータ(A)を送信
2. サーバーが受信、遅延ACKのためACKを保留(200ms待機)
3. クライアントはNagleアルゴリズムにより、ACKが来るまで次のデータ(B)を送信しない
4. 200ms後にサーバーがACKを送信
5. クライアントがデータ(B)を送信
→ 不要な200msの遅延が発生(Nagle-Delayed ACK問題)
解決策:
TCP_NODELAYでNagleを無効化TCP_QUICKACKで遅延ACKを無効化TCP_CORK(Linux固有)で明示的にバッファリングを制御
5.5 再送制御
RTO(Retransmission Timeout)
ACKが一定時間内に返らない場合、セグメントを再送する。タイムアウト値(RTO)はRTT(Round-Trip Time)の測定値に基づいて動的に計算される。
RTOの計算(RFC 6298):
SRTT(Smoothed RTT):
SRTT = (1 - α) × SRTT + α × RTT (α = 1/8)
RTTVAR(RTT Variation):
RTTVAR = (1 - β) × RTTVAR + β × |SRTT - RTT| (β = 1/4)
RTO:
RTO = SRTT + max(G, K × RTTVAR) (G = クロック粒度, K = 4)
制約:
RTO ≥ 1秒(RFC推奨最小値)
RTO ≤ 120秒(一般的な最大値)
# RTTとRTOの確認
$ ss -ti dst 192.168.1.100
# 出力例: rtt:10.5/5.25 rto:220 ...
# rtt: SRTT/RTTVAR(ミリ秒)
# rto: 現在のRTO値(ミリ秒)
# 初期RTOの設定(Linux 2.6.33以降)
$ sudo ip route change default via 192.168.1.1 rto_min 100ms
# 再送回数の設定
$ sudo sysctl -w net.ipv4.tcp_retries1=3 # ソフトエラーの閾値
$ sudo sysctl -w net.ipv4.tcp_retries2=15 # ハードエラー(接続断)の閾値
指数バックオフ(Exponential Backoff)
再送のたびにRTOを2倍にする:
1回目の再送: RTO
2回目の再送: 2 × RTO
3回目の再送: 4 × RTO
4回目の再送: 8 × RTO
...
最大: 120秒
高速再送(Fast Retransmit)
3つの重複ACK(同じACK番号のACKを3回受信)を受信した場合、RTOを待たずに即座にセグメントを再送する。
送信側 受信側
| Seq=1000 (データ1) |
|──────────────────────────────────>|
| Seq=2000 (データ2) ← 消失! |
|──────────X |
| Seq=3000 (データ3) |
|──────────────────────────────────>|
| ACK=2000 (重複1) |
|<──────────────────────────────────|
| Seq=4000 (データ4) |
|──────────────────────────────────>|
| ACK=2000 (重複2) |
|<──────────────────────────────────|
| Seq=5000 (データ5) |
|──────────────────────────────────>|
| ACK=2000 (重複3) |
|<──────────────────────────────────|
| |
| ★ 3つの重複ACK → 高速再送! |
| Seq=2000 (データ2 再送) |
|──────────────────────────────────>|
| ACK=6000 |
|<──────────────────────────────────|
5.6 フロー制御(Flow Control)
フロー制御は、送信側が受信側のバッファ容量を超えてデータを送信しないようにするメカニズム。
受信ウィンドウ(rwnd)
受信側はACKセグメントのウィンドウフィールドで、受信可能なデータ量を通知する。
例: バッファサイズ = 4096バイト
時点1: rwnd = 4096 (バッファ空)
送信側がデータ2048バイト送信
時点2: rwnd = 2048 (バッファ半分使用)
アプリケーションが1024バイト読み取り
時点3: rwnd = 3072 (アプリ読み取り後)
ゼロウィンドウ(Zero Window)
受信側のバッファが満杯になると、ウィンドウサイズ0を通知する。送信側はデータ送信を停止する。
# ゼロウィンドウの観察
$ sudo tcpdump -i eth0 -v 'tcp[14:2] = 0'
ウィンドウプローブ(Window Probe / Persist Timer)
ゼロウィンドウ状態から回復するために、送信側は定期的にウィンドウプローブ(1バイトのセグメント)を送信して、受信側のウィンドウサイズを確認する。
送信側 受信側
| |
| Window = 0 |
|<──────────────────────────────────|
| |
| (パーシストタイマー満了) |
| |
| プローブ (1バイト) |
|──────────────────────────────────>|
| Window = 0 |
|<──────────────────────────────────|
| |
| (パーシストタイマー × 2) |
| |
| プローブ (1バイト) |
|──────────────────────────────────>|
| Window = 4096 |
|<──────────────────────────────────|
| |
| ★ ウィンドウが開いた → データ送信再開 |
Silly Window Syndrome(SWS)の防止
受信側が非常に小さなウィンドウ(数バイト)を通知すると、効率が著しく低下する(小さなセグメントの大量送信)。
送信側の対策: Nagleアルゴリズム
- 小さなデータを蓄積してまとめて送信
受信側の対策: Clarkの解決策
- ウィンドウサイズがMSSまたはバッファの半分以上になるまで、ウィンドウ更新を通知しない
# 受信バッファサイズの設定
$ sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 6291456"
# 最小値 デフォルト値 最大値
第6章: 輻輳制御(Congestion Control)
6.1 輻輳制御の概要
輻輳制御は、ネットワーク全体の安定性を維持するために、送信側が送信速度を調整するメカニズムである。フロー制御が「受信側の能力」に基づくのに対し、輻輳制御は「ネットワークの状況」に基づく。
実際の送信ウィンドウ:
送信ウィンドウ = min(cwnd, rwnd)
cwnd: 輻輳ウィンドウ(Congestion Window)- ネットワークの状況に基づく
rwnd: 受信ウィンドウ(Receive Window)- 受信側のバッファに基づく
6.2 スロースタート(Slow Start)
コネクション確立直後、ネットワークの帯域幅が不明なため、低い送信速度から開始して指数的に増加させる。
初期値: cwnd = IW(Initial Window)
通常は 10 × MSS(RFC 6928、Linux 3.0以降)
各RTTごとの増加:
RTT 1: cwnd = 10 MSS → 送信可能 = 10セグメント
RTT 2: cwnd = 20 MSS → 送信可能 = 20セグメント
RTT 3: cwnd = 40 MSS → 送信可能 = 40セグメント
RTT 4: cwnd = 80 MSS → 送信可能 = 80セグメント
...
cwndがssthresh(スロースタート閾値)に達したら、
輻輳回避フェーズに移行
# 初期ウィンドウサイズの確認
$ sudo ip route show
# 出力に initcwnd が表示される場合がある
# 初期ウィンドウサイズの設定
$ sudo ip route change default via 192.168.1.1 initcwnd 10
# ssthreshの確認
$ ss -ti dst 192.168.1.100
# 出力例: ... ssthresh:65535 ...
6.3 輻輳回避(Congestion Avoidance)
cwndがssthreshに達した後、cwndを線形に増加させる。
各RTTごとの増加:
cwnd = cwnd + MSS × (MSS / cwnd)
近似的に: 1 RTTごとに約1 MSS増加
RTT n: cwnd = 80 MSS
RTT n+1: cwnd = 81 MSS
RTT n+2: cwnd = 82 MSS
...
6.4 輻輳検知と応答
タイムアウトによる検知
パケットロス(タイムアウト)発生時:
ssthresh = cwnd / 2
cwnd = 1 MSS(または IW)
スロースタートからやり直し
高速再送による検知(3つの重複ACK)
3つの重複ACK受信時:
ssthresh = cwnd / 2
→ 以降の処理は輻輳制御アルゴリズムにより異なる
6.5 TCP Reno
最も基本的な輻輳制御アルゴリズム。
高速回復(Fast Recovery):
3つの重複ACK受信時:
ssthresh = cwnd / 2
cwnd = ssthresh + 3 × MSS
追加の重複ACKを受信するたびに:
cwnd = cwnd + MSS
新しいACKを受信したら:
cwnd = ssthresh(輻輳回避フェーズに移行)
cwnd
^
| /\ /\
| / \ / \
| / \ / \
| / \ / -------- ← 輻輳回避(線形増加)
| / \ /
| / \/ ← 高速回復
| /
| / ← スロースタート
| / (指数増加)
| /
|/
+─────────────────────────────────> 時間
↑パケットロス
6.6 TCP CUBIC
Linux 2.6.19以降のデフォルトの輻輳制御アルゴリズム。BIC-TCPの改良版で、高帯域幅・高遅延(BDP: Bandwidth-Delay Product が大きい)ネットワークに最適化されている。
CUBICの特徴:
- ウィンドウ増加関数が3次関数(cubic function)
- パケットロス時のウィンドウサイズ(Wmax)を記憶
- 時間ベースのウィンドウ増加(RTTに依存しない)
W(t) = C × (t - K)³ + Wmax
C: スケーリングファクター(通常0.4)
t: 最後のウィンドウ縮小からの経過時間
K: W(0) = βWmax から Wmax に到達する時間
K = ∛(Wmax × β / C)
β: 縮小係数(通常0.7)
Wmax: パケットロス時のウィンドウサイズ
cwnd
^
| ___________
| / \
| / \
| Wmax ---------| |
| / | \
| / | \
| / | \
| / | \
| _____/ | |
| / | |
|___/ | |
+────────────────|───────────────────> 時間
↑
パケットロス
# 現在の輻輳制御アルゴリズムの確認
$ sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = cubic
# 利用可能なアルゴリズムの確認
$ sysctl net.ipv4.tcp_available_congestion_control
net.ipv4.tcp_available_congestion_control = reno cubic
# 許可されたアルゴリズムの確認
$ sysctl net.ipv4.tcp_allowed_congestion_control
net.ipv4.tcp_allowed_congestion_control = reno cubic
# 輻輳制御アルゴリズムの変更
$ sudo sysctl -w net.ipv4.tcp_congestion_control=cubic
6.7 TCP BBR(Bottleneck Bandwidth and Round-trip propagation time)
Googleが開発した輻輳制御アルゴリズム(2016年)。従来のロスベースのアルゴリズムとは異なり、ボトルネック帯域幅とRTTを直接モデル化する。
BBRの基本原理:
- BtlBw(Bottleneck Bandwidth): ボトルネックリンクの帯域幅を推定
- RTprop(Round-trip propagation time): 最小RTTを追跡
- 最適動作点:
delivery rate = BtlBwかつinflight = BDP = BtlBw × RTprop
BBRの4つのフェーズ:
- Startup: 帯域幅を指数的に探索(スロースタートに類似)
- Drain: Startup中に蓄積されたキューを排出
- ProbeBW: 帯域幅の変化を定期的にプローブ
- ProbeRTT: 最小RTTを定期的に測定
# BBRの有効化(Linux 4.9以降)
$ sudo modprobe tcp_bbr
$ sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
# BBRの永続的な設定
$ cat <<'EOF' | sudo tee /etc/sysctl.d/99-tcp-bbr.conf
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
$ sudo sysctl --system
# BBRの統計確認
$ ss -ti dst 192.168.1.100
# 出力例: bbr:(bw:100Mbps,mrtt:10,pacing_gain:1.25,cwnd_gain:2)
BBR vs CUBIC の比較:
| 特性 | CUBIC | BBR |
|---|---|---|
| 輻輳検知 | パケットロスベース | モデルベース(帯域幅・RTT) |
| バッファブロート対応 | 弱い | 強い |
| 浅いバッファ環境 | 性能低下 | 良好 |
| 高遅延ネットワーク | 回復が遅い | 迅速な適応 |
| 公平性 | 良好 | 改善中(BBRv2) |
| カーネル要件 | 標準 | Linux 4.9+ |
6.8 ECN(Explicit Congestion Notification)
ルーターがパケットロスの代わりにIPヘッダのECNフィールドをマーキングすることで、輻輳を明示的に通知するメカニズム。
# ECNの有効化
$ sudo sysctl -w net.ipv4.tcp_ecn=1
# 0: 無効, 1: サーバー・クライアント両方有効, 2: サーバーのみ有効
IPヘッダのECNフィールド(2ビット):
00: Non-ECT(ECN非対応)
01: ECT(1)(ECN対応トランスポート)
10: ECT(0)(ECN対応トランスポート)
11: CE(Congestion Experienced)← ルーターがマーキング
通信フロー:
1. 送信側がECT(0)またはECT(1)をマーキングしてパケット送信
2. ルーターが輻輳を検知するとCEにマーキング
3. 受信側がCEを検出、TCPヘッダのECEフラグをセットしてACK送信
4. 送信側がECEを受信、CWRフラグをセットしてcwndを縮小
第7章: TCPのパフォーマンスチューニング
7.1 帯域幅遅延積(BDP: Bandwidth-Delay Product)
BDPは、ネットワーク上に「飛行中」のデータ量を表す。最適なスループットを得るためには、送信ウィンドウがBDP以上である必要がある。
BDP = 帯域幅 × RTT
例1: 1Gbps、RTT = 1ms
BDP = 1,000,000,000 × 0.001 / 8 = 125,000バイト(約122KB)
例2: 100Mbps、RTT = 100ms(大陸間通信)
BDP = 100,000,000 × 0.1 / 8 = 1,250,000バイト(約1.2MB)
例3: 10Gbps、RTT = 50ms
BDP = 10,000,000,000 × 0.05 / 8 = 62,500,000バイト(約60MB)
7.2 ソケットバッファサイズの設定
# 受信バッファサイズ(最小・デフォルト・最大)
$ sysctl net.ipv4.tcp_rmem
net.ipv4.tcp_rmem = 4096 131072 6291456
# 送信バッファサイズ(最小・デフォルト・最大)
$ sysctl net.ipv4.tcp_wmem
net.ipv4.tcp_wmem = 4096 16384 4194304
# コアのバッファサイズ(全プロトコル共通)
$ sysctl net.core.rmem_max
$ sysctl net.core.wmem_max
$ sysctl net.core.rmem_default
$ sysctl net.core.wmem_default
# BDPに基づく設定例(100Mbps、RTT=100ms → BDP≈1.2MB)
$ sudo sysctl -w net.ipv4.tcp_rmem="4096 131072 4194304"
$ sudo sysctl -w net.ipv4.tcp_wmem="4096 131072 4194304"
$ sudo sysctl -w net.core.rmem_max=4194304
$ sudo sysctl -w net.core.wmem_max=4194304
7.3 自動チューニング(Auto-Tuning)
Linuxはデフォルトでバッファサイズの自動チューニングが有効になっている。
# 自動チューニングの確認
$ sysctl net.ipv4.tcp_moderate_rcvbuf
net.ipv4.tcp_moderate_rcvbuf = 1 # 1 = 有効
# メモリ使用量の制限
$ sysctl net.ipv4.tcp_mem
net.ipv4.tcp_mem = 378861 505149 757722
# 低水位(ページ数) プレッシャー閾値 高水位
# メモリプレッシャー状態ではバッファの割り当てが制限される
7.4 高パフォーマンスサーバーの設定例
# /etc/sysctl.d/99-tcp-performance.conf
# === バッファサイズ ===
# 受信バッファ(最小 4KB, デフォルト 128KB, 最大 16MB)
net.ipv4.tcp_rmem = 4096 131072 16777216
# 送信バッファ
net.ipv4.tcp_wmem = 4096 131072 16777216
# コアバッファ最大値
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# === コネクション管理 ===
# SYNバックログ
net.ipv4.tcp_max_syn_backlog = 8192
# 接続追跡テーブルサイズ
net.netfilter.nf_conntrack_max = 1048576
# ソケットバックログ
net.core.somaxconn = 65535
# SYN Cookies有効化
net.ipv4.tcp_syncookies = 1
# === TIME_WAIT対策 ===
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_tw_buckets = 65536
# === ポート範囲 ===
net.ipv4.ip_local_port_range = 1024 65535
# === キープアライブ ===
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 60
net.ipv4.tcp_keepalive_probes = 5
# === 輻輳制御 ===
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq
# === その他最適化 ===
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_mtu_probing = 1
# 設定の適用
$ sudo sysctl --system
# 設定の確認
$ sysctl -a | grep tcp
7.5 キープアライブ(TCP Keep-Alive)
アイドル状態のコネクションが生存しているかを確認するためのメカニズム。
# キープアライブの設定
$ sysctl net.ipv4.tcp_keepalive_time # 最初のプローブまでの待機時間(デフォルト: 7200秒)
$ sysctl net.ipv4.tcp_keepalive_intvl # プローブ間隔(デフォルト: 75秒)
$ sysctl net.ipv4.tcp_keepalive_probes # プローブ回数(デフォルト: 9回)
# キープアライブの動作:
# 1. コネクションが7200秒(2時間)アイドルになる
# 2. キープアライブプローブを送信
# 3. 応答がなければ75秒ごとにプローブを再送
# 4. 9回連続で応答がなければコネクションを切断
#
# 切断までの最大時間: 7200 + 75 × 9 = 7875秒(約2時間11分)
# 推奨設定(Webサーバー向け)
$ sudo sysctl -w net.ipv4.tcp_keepalive_time=600
$ sudo sysctl -w net.ipv4.tcp_keepalive_intvl=60
$ sudo sysctl -w net.ipv4.tcp_keepalive_probes=5
7.6 Linuxカーネルパラメータ一覧
| パラメータ | デフォルト値 | 説明 |
|---|---|---|
| tcp_rmem | 4096 131072 6291456 | 受信バッファサイズ(最小/デフォルト/最大) |
| tcp_wmem | 4096 16384 4194304 | 送信バッファサイズ |
| tcp_window_scaling | 1 | ウィンドウスケーリング |
| tcp_timestamps | 1 | タイムスタンプ |
| tcp_sack | 1 | SACK |
| tcp_congestion_control | cubic | 輻輳制御アルゴリズム |
| tcp_max_syn_backlog | 128/1024 | SYNキューサイズ |
| tcp_syncookies | 1 | SYN Cookies |
| tcp_tw_reuse | 0/2 | TIME_WAIT再利用 |
| tcp_fin_timeout | 60 | FIN_WAIT_2タイムアウト |
| tcp_keepalive_time | 7200 | キープアライブ開始時間 |
| tcp_keepalive_intvl | 75 | キープアライブ間隔 |
| tcp_keepalive_probes | 9 | キープアライブ試行回数 |
| tcp_fastopen | 0/1 | TCP Fast Open |
| tcp_retries1 | 3 | ソフトエラー閾値 |
| tcp_retries2 | 15 | ハードエラー閾値 |
| tcp_ecn | 0/2 | ECN |
| tcp_mtu_probing | 0 | パスMTU探索 |
| tcp_moderate_rcvbuf | 1 | 自動バッファチューニング |
| ip_local_port_range | 32768 60999 | エフェメラルポート範囲 |
| somaxconn | 128/4096 | Acceptキュー最大サイズ |
第8章: TCPセキュリティ
8.1 TCPに対する主な攻撃手法
SYNフラッド攻撃(SYN Flood Attack)
送信元IPアドレスを偽装した大量のSYNパケットを送信し、サーバーのSYNキューを枯渇させる攻撃。
攻撃者 (偽装IP: ランダム)
│ SYN (src=1.1.1.1) ──→ サーバー [SYNキュー +1]
│ SYN (src=2.2.2.2) ──→ サーバー [SYNキュー +2]
│ SYN (src=3.3.3.3) ──→ サーバー [SYNキュー +3]
│ ... サーバー [SYNキュー 満杯!]
│ → 正規のSYNが受け付けられない
防御策:
# SYN Cookiesの有効化
$ sudo sysctl -w net.ipv4.tcp_syncookies=1
# SYNバックログの増加
$ sudo sysctl -w net.ipv4.tcp_max_syn_backlog=65536
# SYN再送回数の削減
$ sudo sysctl -w net.ipv4.tcp_synack_retries=2
# iptablesによるSYNレート制限
$ sudo iptables -A INPUT -p tcp --syn -m limit --limit 100/s --limit-burst 200 -j ACCEPT
$ sudo iptables -A INPUT -p tcp --syn -j DROP
# SYNフラッドの検知
$ ss -tn state syn-recv | wc -l
$ netstat -s | grep "SYNs to LISTEN"
TCP Reset攻撃(TCP RST Attack)
正規のTCPコネクションに偽装RSTパケットを注入してコネクションを切断する攻撃。
防御策:
- RFC 5961の実装(チャレンジACK)
- IPsecやTCPレベルの認証(TCP-AO)
TCPシーケンス番号予測攻撃
ISNを予測して、偽装パケットをコネクションに注入する攻撃。
防御策:
- ランダムなISN生成(RFC 6528)
- TCP-AO(TCP Authentication Option)
TCP接続ハイジャック
確立済みのTCPコネクションを乗っ取る攻撃。
8.2 TCP-AO(TCP Authentication Option)
RFC 5925で定義された、TCPセグメントの認証メカニズム。旧来のTCP MD5 Signature Option(RFC 2385)の後継。
# TCP-AOの設定(Linux 5.14以降)
# BGPセッションの保護に使用される例
$ sudo ip tcp_metrics add 192.168.1.1 ao-keyid 1 ao-rnextkeyid 1 \
ao-key "secret-key-here" ao-algo hmac(sha256)
8.3 ファイアウォール設定
# iptablesによるTCPフィルタリング
# 無効なTCPフラグの組み合わせを検出・ブロック
# すべてのフラグがセットされたパケット(XMAS Scan)
$ sudo iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
# すべてのフラグがクリアされたパケット(NULL Scan)
$ sudo iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
# SYN+FINの組み合わせ(不正)
$ sudo iptables -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
# SYN+RSTの組み合わせ(不正)
$ sudo iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
# 新しい接続でSYNフラグがないパケット
$ sudo iptables -A INPUT -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
# 接続追跡(Connection Tracking)
$ sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
$ sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
# nftablesでの同等設定
$ sudo nft add rule inet filter input tcp flags & (syn|fin) == syn|fin drop
$ sudo nft add rule inet filter input tcp flags & (syn|rst) == syn|rst drop
8.4 TCPとTLSの関係
TCPはトランスポート層の信頼性を提供するが、データの機密性や完全性は保証しない。これらはTLS(Transport Layer Security)により提供される。
従来のTCP + TLS:
TCP 3ウェイハンドシェイク(1 RTT)
↓
TLSハンドシェイク(1-2 RTT)
↓
データ転送
合計: 2-3 RTT
TCP Fast Open + TLS 1.3:
TCP SYN + TFOクッキー + TLSクライアントハロー(0 RTT)
↓
データ転送
合計: 1 RTT(0-RTTリジュームの場合は0 RTT)
第9章: TCPのトラブルシューティング
9.1 診断ツール
tcpdump
# 基本的なTCPパケットキャプチャ
$ sudo tcpdump -i eth0 -nn tcp
# 特定ホスト・ポートのキャプチャ
$ sudo tcpdump -i eth0 -nn host 192.168.1.100 and port 443
# SYNパケットのみ
$ sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-syn != 0'
# RSTパケットのみ
$ sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0'
# ウィンドウサイズ0のパケット(ゼロウィンドウ)
$ sudo tcpdump -i eth0 -nn 'tcp[14:2] = 0'
# 再送パケットの検出(シーケンス番号が前のパケットと同じ)
$ sudo tcpdump -i eth0 -nn -S tcp # -Sで絶対シーケンス番号表示
# pcapファイルに保存
$ sudo tcpdump -i eth0 -w capture.pcap -nn host 192.168.1.100
# pcapファイルの読み込み
$ tcpdump -r capture.pcap -nn
ss(Socket Statistics)
# すべてのTCP接続
$ ss -tan
# LISTEN状態のソケット
$ ss -tlnp
# 特定の状態のソケット
$ ss -tn state established
$ ss -tn state time-wait
$ ss -tn state close-wait
# 詳細情報(タイマー、RTT、cwnd等)
$ ss -ti dst 192.168.1.100
# 出力例:
# cubic wscale:7,7 rto:204 rtt:1.5/0.75 ato:40 mss:1448 pmtu:1500
# rcvmss:1448 advmss:1448 cwnd:10 bytes_sent:1234 bytes_acked:1234
# bytes_received:5678 segs_out:100 segs_in:200 data_segs_out:50
# data_segs_in:100 send 77.2Mbps lastsnd:100 lastrcv:50 lastack:50
# pacing_rate 154Mbps delivery_rate 50Mbps
# 状態別統計
$ ss -s
Total: 1523
TCP: 1234 (estab 1100, closed 50, orphaned 2, timewait 50)
Wireshark
Wiresharkはパケットキャプチャのグラフィカルツールであり、TCPストリームの詳細な分析に有用。
# Wiresharkで開くためのキャプチャ
$ sudo tcpdump -i eth0 -w /tmp/capture.pcap -s 0 host 192.168.1.100
# tsharkでのコマンドライン分析
$ tshark -r capture.pcap -Y "tcp.analysis.retransmission" -T fields \
-e frame.number -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport \
-e tcp.seq -e tcp.ack
# TCP再送の統計
$ tshark -r capture.pcap -q -z io,stat,1,"tcp.analysis.retransmission"
9.2 一般的な問題と対処法
CLOSE_WAITの蓄積
# 問題: CLOSE_WAITソケットが大量に存在
$ ss -tn state close-wait | wc -l
5000
# 原因: アプリケーションがソケットをクローズしていない
# 対処: アプリケーションのバグを修正
# プロセスの特定
$ ss -tnp state close-wait
# CLOSE-WAIT 0 0 192.168.1.10:45678 10.0.0.1:80 users:(("java",pid=12345,fd=789))
TIME_WAITの枯渇
# 問題: TIME_WAITソケットが大量に存在
$ ss -tn state time-wait | wc -l
50000
# 対処:
$ sudo sysctl -w net.ipv4.tcp_tw_reuse=1
$ sudo sysctl -w net.ipv4.tcp_fin_timeout=15
$ sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"
再送の多発
# 再送統計の確認
$ netstat -s | grep -i retrans
1234 segments retransmited
567 fast retransmits
# 再送率の計算
$ cat /proc/net/snmp | grep Tcp
# TcpRetransSegs / TcpOutSegs × 100 = 再送率(%)
# 1%以上は要調査、5%以上は深刻
# 再送の原因調査
# 1. ネットワーク品質の問題 → pingでパケットロスを確認
$ ping -c 100 192.168.1.100
# 2. バッファ不足 → バッファサイズの調整
# 3. 輻輳 → 輻輳制御アルゴリズムの変更
9.3 パフォーマンス測定ツール
# iperf3でのスループット測定
# サーバー側
$ iperf3 -s
# クライアント側
$ iperf3 -c 192.168.1.100 -t 30 -P 4
# -t: 測定時間(秒)
# -P: 並列ストリーム数
# 出力例:
# [SUM] 0.00-30.00 sec 3.28 GBytes 941 Mbits/sec sender
# [SUM] 0.00-30.00 sec 3.28 GBytes 940 Mbits/sec receiver
# pingでRTT測定
$ ping -c 10 192.168.1.100
# 出力: rtt min/avg/max/mdev = 0.5/1.2/2.0/0.4 ms
# mtrでネットワーク経路の品質確認
$ mtr -rw 192.168.1.100
第10章: TCPの進化と最新動向
10.1 QUIC(Quick UDP Internet Connections)
QUICは、Googleが開発しUDP上で動作するトランスポートプロトコル。HTTP/3のトランスポート層として採用されている。TCPの課題を解決するために設計された。
TCP vs QUICの比較:
| 特性 | TCP + TLS | QUIC |
|---|---|---|
| ハンドシェイク | TCP(1RTT) + TLS(1-2RTT) = 2-3RTT | 1RTT(0-RTTリジューム可能) |
| ヘッドオブラインブロッキング | あり | なし(ストリーム多重化) |
| 接続マイグレーション | 不可(IP変更で切断) | 可能(Connection ID使用) |
| 暗号化 | オプション | 必須(組み込み) |
| カーネル実装 | あり | ユーザースペース |
| 輻輳制御 | カーネル内 | ユーザースペース(柔軟) |
10.2 Multipath TCP(MPTCP)
複数のネットワークインターフェース(Wi-Fi + セルラーなど)を同時に使用してスループットを向上させる拡張。
# MPTCPの有効化(Linux 5.6以降)
$ sudo sysctl -w net.mptcp.enabled=1
# MPTCPの確認
$ ss -M
10.3 TCP BBRv2
BBRの改良版。公平性の問題やロスの多い環境での動作が改善されている。
10.4 TCP BBRv3
BBRv3はさらなる改良を加え、ロス耐性とRTT公平性を向上させている。Linux 6.x系列での統合が進行中。
第11章: 実践シナリオ
11.1 Webサーバー(Nginx)のTCP最適化
# /etc/nginx/nginx.conf
events {
worker_connections 65535;
use epoll;
multi_accept on;
}
http {
# TCP最適化
sendfile on;
tcp_nopush on; # Nagleアルゴリズムの最適化(レスポンスヘッダとボディをまとめて送信)
tcp_nodelay on; # キープアライブ接続でのNagle無効化
# キープアライブ設定
keepalive_timeout 65;
keepalive_requests 10000;
# バッファ設定
client_body_buffer_size 128k;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
# アップストリームへのキープアライブ
upstream backend {
server 10.0.0.1:8080;
keepalive 256;
keepalive_timeout 60s;
keepalive_requests 1000;
}
}
11.2 データベース接続のTCP設定
# PostgreSQLの接続設定(postgresql.conf)
# tcp_keepalives_idle = 600 # キープアライブ開始までの秒数
# tcp_keepalives_interval = 60 # キープアライブ間隔
# tcp_keepalives_count = 5 # キープアライブ回数
# MySQLの接続設定(my.cnf)
# [mysqld]
# net_read_timeout = 30
# net_write_timeout = 60
# wait_timeout = 28800
# interactive_timeout = 28800
11.3 コンテナ環境でのTCP設定
# Kubernetes Pod のsysctl設定
apiVersion: v1
kind: Pod
metadata:
name: tcp-optimized-pod
spec:
securityContext:
sysctls:
- name: net.ipv4.tcp_keepalive_time
value: "600"
- name: net.ipv4.tcp_keepalive_intvl
value: "60"
- name: net.ipv4.tcp_keepalive_probes
value: "5"
- name: net.core.somaxconn
value: "65535"
containers:
- name: app
image: myapp:latest
まとめ
TCPは40年以上の歴史を持つプロトコルでありながら、現在もインターネット通信の基盤として不可欠な存在である。本記事で解説した内容を振り返ると:
- 基本概念: コネクション指向、信頼性保証、順序保証、全二重通信
- セグメント構造: ヘッダフィールド、フラグ、オプション(MSS、SACK、タイムスタンプ)
- コネクション管理: 3ウェイハンドシェイク、4ウェイハンドシェイク、TIME_WAIT、TCP Fast Open
- 状態遷移: 11の状態と遷移条件
- データ転送: スライディングウィンドウ、再送制御、フロー制御
- 輻輳制御: スロースタート、CUBIC、BBR、ECN
- パフォーマンスチューニング: BDP、バッファサイズ、カーネルパラメータ
- セキュリティ: SYNフラッド対策、ファイアウォール設定
- トラブルシューティング: tcpdump、ss、Wireshark
- 最新動向: QUIC、MPTCP、BBRv2/v3
TCPは一見シンプルなプロトコルに見えるが、その内部には高度な制御メカニズムが詰まっている。SREやインフラエンジニアにとって、TCPの深い理解はシステムの信頼性とパフォーマンスを確保するための必須スキルである。
参考文献
- RFC 793 - Transmission Control Protocol (1981)
- RFC 5681 - TCP Congestion Control (2009)
- RFC 6298 - Computing TCP's Retransmission Timer (2011)
- RFC 6528 - Defending against Sequence Number Attacks (2012)
- RFC 6928 - Increasing TCP's Initial Window (2013)
- RFC 7413 - TCP Fast Open (2014)
- RFC 8312 - CUBIC for Fast Long-Distance Networks (2018)
- RFC 9000 - QUIC: A UDP-Based Multiplexed and Secure Transport (2021)
- RFC 9293 - Transmission Control Protocol (TCP) - Updated Specification (2022)
- "TCP/IP Illustrated, Volume 1" - W. Richard Stevens
- "Computer Networking: A Top-Down Approach" - Kurose, Ross
- Linux Kernel Documentation: https://www.kernel.org/doc/Documentation/networking/
- BBR Congestion Control: https://research.google/pubs/pub45646/