TCP 头部选项的理解

TCP Options

TCP 报头通常包含 20 字节信息。但是同时可以携带额外的选项用来说明额外的信息(下图 Options)。

TCP-header.png

每一组选项包含 1 个字节的类型标识,1 个字节的长度声明,n 个字节的具体数据,最大不超过 40 字节。

Kind Length Value
1 Byte 1 Byte (Length - 2) Bytes

以下是典型的几个选项说明:

Kind Length Name Reference Desc
0 1 EOL RFC 793 选项列表结束标识
1 1 NOP RFC 793 用于作为填充
2 4 MSS RFC 793 最大报文长度
3 3 WSOPT RFC 1323 窗口扩大系数
4 2 SACK-Permitted RFC 2018 标识支持SACK
5 可变 SACK RFC 2018 SACK Block
8 10 TSPOT RFC 1323 Timestamps
19 18 TCP-MD5 RFC 2385 MD5认证
28 4 UTO RFC 5482 User Timeout
29 可变 TCP-AO RFC 5925 算法认证
253/254 可变 Experimental RFC 4727 实验性保留
End of Option List

列表结束标识,简称 EOL,仅包含 Kind 标识,1 个字节。用于隔开报头和报文数据。

NO-Operation

无操作 NOP。该选项仅包含 Kind 标识 1 个字节,用来进行对齐填充。因为报头长度需要满足 32bits(4Bytes) 倍数。

Maximum Segment Size

最大报文长度声明,简称 MSS。不仅仅是通知对端需要发送小于该值的报文,且不会接受报文长度大于该值的数据报。这个选项只会出现在 SYN 包中,如果在握手时该选项不存在,那么会使用默认值 536Bytes,最大值为 2^16-1 = 65535,长度 2 个字节。在一般的 ipv4 网络上,以太网 MTU = 1500Bytes, MSS 典型值 = 1500 - 20(ip header)- 20(tcp header) = 1460。

Window Scale

窗口缩放大小。见滑动窗口说明。

SACK-Permitted

SACK 快速重传允许标识。SACK 见快速重传说明。

SACK-Block

SACK 阻塞段标识。见快速重传说明。

Timestamp

该选项总长度 10Bytes。包含 16bits TimestampValue(TSval) 和 16bits TimestampEchoReply(TSecr)。如果双方都支持 timestamp,那发送方会在发送报文时写入当前时间戳,接受方会在 ACK 时候将对手时间戳写入 TSecr。基于 timestamp,可以方便地计算 RTT、防止序列号回绕、支持 tcp_tw_reuse 等。

User Timeout

该选项用以提示对手方,本方等待 ACK 的时间。如果指定时间内没有收到 ACK 会,会连接断开。

TCP-MD5 & TCP-AO

主要用来 TCP Spoofing Attacks 的防范。不展开。

以下回顾说明几个头部选项涉及到的概念。

拓展:滑动窗口

在 TCP 头部有个窗口大小,形容了接收端的处理能力,这是为了更好地协调双发通信的节奏,就是控制了 TCP 通信的流量。这个部分占头部的 16bits,也就是发送端可以声明的最大缓冲区大小 65535Bytes(当然现在一般是不够了)。

接收窗口 & 拥塞窗口

发送端缓存了一批数据,这批数据通过接受端返回的窗口大小可以区分为 已被确认已发送未被确认可发送不可发送 四个类型。抛开 已被确认 的数据部分,已发送未被确认可发送 数据的范围大小由接收端的接收窗口和本方的拥塞窗口的最小大小限制,一般称这部分是发送窗口。通过减去 已发送未被确认 的数据得到,这一部分可以继续发送,称为可用窗口。余下的一部分缓存数据由于接收端处理能力不够(窗口大小不够)暂时不能发送。
接收窗口 RWND:描述了接收方当前的剩余处理能力,按字节大小计算。
拥塞窗口 CWND:描述了发送方考虑网络状况,对自己限流的情况,按可传输的 MSS 段来计算。

假设场景:

  1. 接受端缓冲区大小为 1000,在握手 ACK 时通知了对方。
  2. 发送方发送了 400Bytes ,接收端的 ACK 数据报 Win=1000-400=600
  3. 发送方继续发送 400Bytes,接受端缓存仍未读取,ACK 数据报 Win=600-400=200
  4. 发送方此时最大可以发送 200Bytes,需要等待发送方窗口恢复才可继续发送。
零窗口嗅探

从之前假设的场景可以看到,如果接收方因为高压力等原因数据处理较慢,返回给发送方一个 Win=0 的 ACK。而由于接收方窗口大小是由 ACK 包返回的,但发送方已经不能再发送数据了,这会让发送方一直处于等待状态?
在发送方收到 Win=0 的提示后,会触发一个 Len=0 Seq=Max(ACK)-1 的数据包,让接收方确认窗口恢复。

窗口缩放

发送方会将接收方返回的窗口进行缩放, 0~14。数值 n 代表左移位数,即最终缩放大小为 2 的 n 次方。原本 TCP header 中 window size 长度为 16 bit,即最大的标识区间是 65535 Bytes,通过 scale 可以将大小最大拓展成 65535 << 14。由于 TCP 窗口是固定的,通过握手时确定大小,所以这个选项仅在 SYN 包中存在。

拥塞处理
慢启动

控制拥塞窗口的方法有很多,通常的一种是慢启动方案。期初分配较小的窗口值,通过交互时候的不断通信来提升拥塞窗口大小。

  • linux 一般默认的 initcwnd = 10
  • 每次收到 ACK cwnd + 1
  • 由于 cwnd 在每次收到 ACK 后扩大 1,所以一个 RTT 内相当于 cwnd << 1 也就是进行了翻倍。
拥塞避免

由于慢启动 cwnd 增长太快,当达到 ssthresh 时候会切换方案。

  • 不再按每次 ACK 进行 cwnd + 1
  • 收到 ACK 时候,cwnd = cwnd + 1/cwnd,这样在大约在一个 RTT 内 cwnd + 1,好处在于会将指数型增长的 cwnd 变成线性增长。在收到三个重复 ACK 时候会视为轻度拥堵,此时会直接进行拥塞避免状态:
  1. 降低 ssthresh,ssthresh = cwnd >> 1
  2. cwnd = ssthresh

拓展:SACK 快速重传机制

TCP 回复 ACK 时候携带的确认序列号意义为 小于该序列号的所有数据报已收到。假设有以下场景:

发送方 假设状态 接收方返回信息
1-100 received ACK=101
101-200 miss
201-300 received ACK=101
301-400 received ACK=101
401-500 received ACK=101
101-200 retry ACK=501
201-300 maybe retry ACK=501

通常情况,包的重发会等待超时时间,并且会在不成时不短翻倍。再开启 SACK 之后,会通过连续收到三个相同的 ACK 来判断数据报丢失来出触发重发。SACK 包含多个已收到的连续字节区间信息以提示发送方重发数据。这可以提高重发的效率,并且只需要重发丢失区间的信息即可:

发送方 假设状态 接收方返回信息
1-100 received ACK=101
101-200 miss
201-300 miss
301-400 received ACK=101,SACK=<301,401>
401-500 received ACK=101,SACK=<301,501>
501-600 received ACK=101,SACK=<301,601>
101-200 fast retransmission ACK=201,SACK=<301,601>
201-300 fast retransmission ACK=601

可能由于网络延迟或者 ACK 回复丢失,导致 ACK 号大于 SACK 区间(D-SACK)并不需要针对区间重发。

码路加油
显示评论