累积确认只能告诉发送方“X 之前都收到了”,洞之后有没有到呢?SACK 选项把“这一段我收到了”也告诉发送方。这篇在丢包的回环上解码 ACK 里的 SACK 块,核对重传是不是正好从洞开始、只补丢掉的段。
net.inet.tcp.sack: 1 net.inet.tcp.sack_maxholes: 128 net.inet.tcp.sack_globalmaxholes: 65536 (net.inet.tcp.sack_globalholes 在本机不存在) SYN 里的 SACK-permitted(kind 4,2 字节)是双方协商的:两边都带才能在 ACK 里使用 SACK 选项(kind 5)——w37 / w38 已经看到本机和 example.com 都带了
net.inet.tcp.sack = 1,每个连接最多记录 128 个洞,全局上限 65536 个。
传输 4 MiB 用了 0.82 秒;发送方 tcp_info:发出 266 个包,重传 4 个(65328 字节,占发送字节的 1.53%);设定的丢包率是 3% 接收方发出的纯 ACK(抓到的记录,含 dummynet 重复)164 条,其中带 SACK 选项的 72 条;SACK 块个数的分布:1 块 × 72 一个例子:累积确认号(相对)= 212317,SACK 块(相对序号):[228649, 244981) 长 16332 字节 所有带 SACK 的 ACK 里,SACK 块位于累积确认号之上(中间有洞)的 72 / 72 条;块的最大个数 1(TCP 选项空间只有 40 字节,带时间戳时头部 12 字节,每块 8 字节,所以最多 3 块)
SYN 里协商 SACK-permitted 之后,接收方的 ACK 可以带最多 3 个(有时间戳时)块,每块是已收到的一段 [左, 右)。累积确认号停在第一个洞的开头。
带 SACK 的 ACK 指向的“第一个洞”(去重后)有 3 个,其中发送方确实重传了从这个序号开始的段的有 3 个;发送方被重传过的段一共 3 个
被重传的段大小:{16332: 3}——每个重传的都是丢掉的那个整段,没有把洞之后已经收到的数据重发一遍
对比“累积确认、没有 SACK”的做法(只能从洞开始往后重传,或者等超时一个一个试):本次发送方实际重传 65328 字节 = 4 个完整的段;洞之后已经被接收方收到的字节有很多(SACK 块里报告了),发送方因此知道不必重传它们发送方根据 SACK 知道哪些段已到,只重传丢掉的整段(这次是 3 个洞、重传 3 个整段)。
第一个 SACK 块的左边缘低于累积确认号的 ACK(DSACK 报告)0 条;这次没有出现
用第一个块报告重复数据,能发现不必要的重传。这次没出现。