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には以下の基本特性がある:

  1. コネクション指向(Connection-Oriented): 通信開始前に3ウェイハンドシェイクで接続を確立する
  2. 信頼性のあるデータ転送(Reliable Delivery): 確認応答(ACK)と再送制御により、データの欠損を防ぐ
  3. 順序保証(Ordered Delivery): シーケンス番号により、データの順序を保証する
  4. フロー制御(Flow Control): 受信側のバッファ容量に応じて送信速度を調整する
  5. 輻輳制御(Congestion Control): ネットワークの混雑状況に応じて送信速度を調整する
  6. 全二重通信(Full-Duplex): 双方向で同時にデータを送受信できる
  7. バイトストリーム(Byte Stream): メッセージ境界を持たないバイトストリームとしてデータを扱う

1.3 TCPとUDPの比較

特性TCPUDP
コネクションコネクション指向コネクションレス
信頼性あり(ACK・再送)なし
順序保証ありなし
フロー制御ありなし
輻輳制御ありなし
ヘッダサイズ20〜60バイト8バイト
速度相対的に遅い相対的に速い
用途Web、メール、ファイル転送DNS、VoIP、ストリーミング、ゲーム

1.4 TCPが使用される代表的なプロトコルとポート番号

プロトコルポート番号用途
HTTP80Webページの転送
HTTPS443暗号化されたWeb通信
FTP20, 21ファイル転送
SSH22セキュアリモートアクセス
SMTP25, 587メール送信
POP3110メール受信
IMAP143, 993メール受信(高機能)
MySQL3306データベース接続
PostgreSQL5432データベース接続
Redis6379インメモリデータストア

第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ビット

フラグ名称説明
CWRCongestion Window Reduced輻輳ウィンドウの縮小を通知(RFC 3168)
ECEECN-EchoECN(Explicit Congestion Notification)のエコー(RFC 3168)
URGUrgent緊急データの存在を示す
ACKAcknowledgment確認応答番号が有効であることを示す
PSHPush受信側にデータを即座にアプリケーションに渡すよう要求
RSTResetコネクションの強制リセット
SYNSynchronizeコネクション確立の開始(シーケンス番号の同期)
FINFinishコネクションの正常終了

ウィンドウサイズ(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 Options01オプションリストの終端
No-Operation (NOP)11パディング用
MSS24最大セグメントサイズの通知
Window Scale33ウィンドウスケーリング係数
SACK Permitted42選択的確認応答の使用許可
SACK5可変選択的確認応答のブロック情報
Timestamps810タイムスタンプ(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許可などのオプションを含む
  • クライアントの状態: CLOSEDSYN_SENT

ステップ2: SYN+ACK(サーバー → クライアント)

  • サーバーがSYN+ACKフラグをセットしたセグメントを送信
  • サーバー独自のISNを選択(例: Seq=5000)
  • クライアントのSYNに対するACK(Ack=1001)を含む
  • サーバーの状態: LISTENSYN_RECEIVED

ステップ3: ACK(クライアント → サーバー)

  • クライアントがACKセグメントを送信(Ack=5001)
  • このセグメントにはデータを含めることが可能
  • クライアントの状態: SYN_SENTESTABLISHED
  • サーバーの状態: SYN_RECEIVEDESTABLISHED
# 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の仕組み:

  1. サーバーがSYNを受信
  2. SYNキューにエントリを作成する代わりに、コネクション情報をハッシュしてISNとしてエンコード
  3. SYN+ACKを送信
  4. クライアントからACKを受信した際、ACK番号からコネクション情報を復元
  5. 正当な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待機)                |
        |                                    |

各状態の遷移:

  • クライアント: ESTABLISHEDFIN_WAIT_1FIN_WAIT_2TIME_WAITCLOSED
  • サーバー: ESTABLISHEDCLOSE_WAITLAST_ACKCLOSED

同時クローズ(Simultaneous Close): 両方が同時にFINを送信した場合:

  • 両方: ESTABLISHEDFIN_WAIT_1CLOSINGTIME_WAITCLOSED

3.7 TIME_WAIT状態

FINを最初に送信した側(アクティブクローズ側)は、最後のACKを送信した後、TIME_WAIT状態に入る。この状態は2MSL(Maximum Segment Lifetime)の間維持される。

TIME_WAITの目的:

  1. 最後のACKが消失した場合に再送できるようにする
  2. 古いコネクションの遅延セグメントが新しいコネクションに誤って受信されることを防ぐ
# 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セグメントは、以下の場合に送信される:

  1. 存在しないポートへの接続試行: サーバーのポートがリスン状態にない場合
  2. コネクションの異常終了: アプリケーションがSO_LINGERオプションでタイムアウト0を設定した場合
  3. 半開きコネクションの検出: 一方が再起動した後、もう一方がデータを送信した場合
# 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
LISTENSYN受信SYN+ACK送信SYN_RECEIVED
LISTENアプリ: データ送信SYN送信SYN_SENT
SYN_SENTSYN+ACK受信ACK送信ESTABLISHED
SYN_SENTSYN受信SYN+ACK送信SYN_RECEIVED
SYN_RECEIVEDACK受信-ESTABLISHED
SYN_RECEIVEDアプリ: CLOSEFIN送信FIN_WAIT_1
ESTABLISHEDアプリ: CLOSEFIN送信FIN_WAIT_1
ESTABLISHEDFIN受信ACK送信CLOSE_WAIT
FIN_WAIT_1ACK受信-FIN_WAIT_2
FIN_WAIT_1FIN受信ACK送信CLOSING
FIN_WAIT_1FIN+ACK受信ACK送信TIME_WAIT
FIN_WAIT_2FIN受信ACK送信TIME_WAIT
CLOSINGACK受信-TIME_WAIT
TIME_WAIT2MSL経過ソケット削除CLOSED
CLOSE_WAITアプリ: CLOSEFIN送信LAST_ACK
LAST_ACKACK受信ソケット削除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は以下のメカニズムにより信頼性のあるデータ転送を実現する:

  1. シーケンス番号: 各バイトに一意の番号を割り当て、順序を管理
  2. 確認応答(ACK): 受信したデータの確認を送信側に通知
  3. 再送制御: ACKが返らない場合にデータを再送
  4. チェックサム: データの整合性を検証

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つのフェーズ:

  1. Startup: 帯域幅を指数的に探索(スロースタートに類似)
  2. Drain: Startup中に蓄積されたキューを排出
  3. ProbeBW: 帯域幅の変化を定期的にプローブ
  4. 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 の比較:

特性CUBICBBR
輻輳検知パケットロスベースモデルベース(帯域幅・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_rmem4096 131072 6291456受信バッファサイズ(最小/デフォルト/最大)
tcp_wmem4096 16384 4194304送信バッファサイズ
tcp_window_scaling1ウィンドウスケーリング
tcp_timestamps1タイムスタンプ
tcp_sack1SACK
tcp_congestion_controlcubic輻輳制御アルゴリズム
tcp_max_syn_backlog128/1024SYNキューサイズ
tcp_syncookies1SYN Cookies
tcp_tw_reuse0/2TIME_WAIT再利用
tcp_fin_timeout60FIN_WAIT_2タイムアウト
tcp_keepalive_time7200キープアライブ開始時間
tcp_keepalive_intvl75キープアライブ間隔
tcp_keepalive_probes9キープアライブ試行回数
tcp_fastopen0/1TCP Fast Open
tcp_retries13ソフトエラー閾値
tcp_retries215ハードエラー閾値
tcp_ecn0/2ECN
tcp_mtu_probing0パスMTU探索
tcp_moderate_rcvbuf1自動バッファチューニング
ip_local_port_range32768 60999エフェメラルポート範囲
somaxconn128/4096Acceptキュー最大サイズ

第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 + TLSQUIC
ハンドシェイクTCP(1RTT) + TLS(1-2RTT) = 2-3RTT1RTT(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年以上の歴史を持つプロトコルでありながら、現在もインターネット通信の基盤として不可欠な存在である。本記事で解説した内容を振り返ると:

  1. 基本概念: コネクション指向、信頼性保証、順序保証、全二重通信
  2. セグメント構造: ヘッダフィールド、フラグ、オプション(MSS、SACK、タイムスタンプ)
  3. コネクション管理: 3ウェイハンドシェイク、4ウェイハンドシェイク、TIME_WAIT、TCP Fast Open
  4. 状態遷移: 11の状態と遷移条件
  5. データ転送: スライディングウィンドウ、再送制御、フロー制御
  6. 輻輳制御: スロースタート、CUBIC、BBR、ECN
  7. パフォーマンスチューニング: BDP、バッファサイズ、カーネルパラメータ
  8. セキュリティ: SYNフラッド対策、ファイアウォール設定
  9. トラブルシューティング: tcpdump、ss、Wireshark
  10. 最新動向: 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/