TCP 选项与扩展
TCP 选项机制
TCP 首部固定 20 字节,但通过**选项(Options)**机制可扩展至 60 字节。选项采用 TLV(Type-Length-Value) 编码:
+--------+--------+--------+--------+...
| Type | Length | Value |
| 1 byte | 1 byte | (Length-2) bytes |
+--------+--------+--------+--------+...
- Type:选项类型(如 2=MSS, 3=Window Scale)
- Length:整个选项的字节数(包括 Type 和 Length 字段)
- Value:选项数据
关键限制:选项空间仅 40 字节(60-20),迫使扩展必须紧凑编码。
选项仅在三次握手时通过 SYN 报文协商,连接建立后不可动态变更。
MSS(Maximum Segment Size,选项类型 2)
MSS 是 TCP 报文段中数据部分的最大长度,不包括 TCP 首部。
MSS = MTU - IP 首部(20) - TCP 首部(20) = 1500 - 40 = 1460 字节
协商过程:
- 客户端 SYN:"我的 MSS 是 1460"
- 服务端 SYN-ACK:"我的 MSS 是 1460"
- 双方取较小值作为实际 MSS
如果路径 MTU 更小(如 PPPoE MTU=1492、VPN 隧道):
- 理想情况:PMTUD 探测路径 MTU,自动调整
- fallback:TCP 发现 IP 层分片或收到 ICMP "需要分片",降低 MSS
窗口缩放(Window Scale,选项类型 3,RFC 7323)
原始 Window 字段 16 位,最大 65535 字节。窗口缩放通过 Shift Count(0~14) 突破限制:
实际窗口 = Window 字段值 × 2^ShiftCount
协商过程:
- 客户端 SYN:"我支持窗口缩放,Shift Count = 8"
- 服务端 SYN-ACK:"我也支持,Shift Count = 8"
- 后续报文中 Window 字段需左移 8 位
最大实际窗口:65535 × 2^14 ≈ 1GB
现代操作系统默认启用(Linux net.ipv4.tcp_window_scaling=1)。
SACK(Selective ACK,选项类型 5,RFC 2018)
SACK 允许接收方精确通告已收到的乱序段边界,发送方仅重传真正丢失的段。
SACK 选项格式(最多 3~4 个块,受 40 字节限制):
+--------+--------+--------+--------+--------+--------+...
| Type | Length | Left Edge 1 | Right Edge 1 | ... |
| 5 | N | 4 bytes | 4 bytes | ... |
+--------+--------+--------+--------+--------+--------+...
示例:段 1(1000~1099)和段 3(1200~1299)收到,段 2(1100~1199)丢失:
ACK=1100(累积确认,期望段 2)
SACK: 1200~1299(段 3 已收到)
发送方知道只需重传段 2,避免重传段 3。
现代操作系统默认启用 SACK(Linux net.ipv4.tcp_sack=1)。
时间戳(Timestamp,选项类型 8,RFC 7323)
10 字节选项,包含两个 4 字节字段:
- TSval(Timestamp Value):发送方当前时间戳
- TSecr(Timestamp Echo Reply):回显对方上次发送的 TSval
两大用途:
1. 更精确的 RTT 测量
发送方发送: TSval = T1
接收方回复: TSecr = T1
发送方收到 ACK: RTT = 当前时间 - T1
每个报文有独立时间戳,ACK 对应关系明确,窗口大、ACK 稀少时仍能频繁采样。
2. PAWS(Protection Against Wrapped Sequence numbers)
高速网络中序列号可能 3 秒回绕。PAWS 利用时间戳区分新旧报文:
- 接收方记录最近收到的时间戳
- 新报文的时间戳必须 ≥ 记录值
- 时间戳过旧的报文丢弃
TCP Fast Open(TFO,选项类型 34,RFC 7413)
正常三次握手需要 1 个 RTT 才能发送数据。TFO 允许在首次 SYN 中携带数据,减少 1 个 RTT:
TFO Cookie:
- 服务端生成(基于客户端 IP、密钥等)
- 客户端缓存(通常几分钟)
- 服务端验证 Cookie 有效后才接受 SYN 中的数据
适用场景:Web 短连接(REST API)、频繁重连的应用。
其他重要选项
| 选项 | 类型 | 作用 |
|---|---|---|
| NOP | 1 | 填充对齐(无操作) |
| EOL | 0 | 选项列表结束 |
| SACK-Permitted | 4 | 握手时声明支持 SACK |
| TCP-AO | 29 | 认证选项(替代 MD5) |
本篇小结
- TCP 选项 TLV 编码,最大 40 字节,握手时协商
- MSS(2):协商最大段大小,典型 1460
- Window Scale(3):Shift Count 0~14,突破 64KB 窗口限制
- SACK(5):精确通告乱序段,仅重传丢失段
- Timestamp(8):精确测 RTT + PAWS 防序列号回绕
- TFO(34):SYN 带数据,减少 1 RTT
- 现代系统默认启用 Window Scale + SACK + Timestamp
动手实践
Wireshark 抓包观察 SYN 选项:
- 右键 SYN 报文 → "TCP Stream" → 查看选项详情
- 记录 MSS、Window Scale、SACK、Timestamp、TFO
查看 Linux 选项开关:
sysctl net.ipv4.tcp_window_scaling sysctl net.ipv4.tcp_sack sysctl net.ipv4.tcp_timestamps查看 TFO 设置:
sysctl net.ipv4.tcp_fastopen思考:为什么选项只能在握手时协商,而不能在连接中动态变更?如果窗口缩放可以在中途调整,会有什么技术难题?