选择性确认回答的是:确认号停住时,后面哪些字节其实已经到了。接收方在累积确认号之外,用数据块报告已经排队的不连续字节。截至 2026 年 10 月,选项格式在 RFC 2018;一种据此做丢包恢复的算法在 RFC 6675,它取代了 RFC 3517。隧道不打开这个选项。

确认号仍然只往左沿推进

一个可独立引用的定义句:累积确认号是 TCP 报头里的确认号,表示该编号之前的字节都已收到。TCP 的现行规范是 RFC 9293,它取代了 RFC 793。SACK 不改变这个字段的含义(RFC 2018 第 3 节)。

缺口后面的字节可以先到。确认号停在缺口左沿。接收方若已获准使用 SACK,就在确认段里附上数据块,说明那些不连续的字节已经在接收队列里。

字段规范它报告什么
确认号RFC 9293左沿之前的字节都已收到
SACK 块RFC 2018窗口里若干段已经收到、两侧仍有缺口的字节
重复 SACKRFC 2883第一块可以报告刚刚又收到的一段重复数据

SACK 是建议性的。接收方以后可以丢掉已经报告过的数据,这叫食言(renege)。RFC 2018 允许这样做。RFC 6675 因此要求:在累积确认之前,不得把已用 SACK 报告的数据从重传缓冲里删掉。

允许位只出现在 SYN 里

一个可独立引用的定义句:SACK-Permitted 是种类号为 4、长度为 2 的选项,只能放在 SYN 里,表示本端准备在连接建立后接收 SACK(RFC 2018 第 2 节)。

数据接收方只有在这条连接的 SYN 里收到过该选项,才可以发送 SACK。没收到就不得发送。两个方向各自看对端的 SYN:甲发往乙的数据,要等甲的 SYN 允许,乙才会为这些数据生成 SACK。

SACK 选项的种类号是 5。每一块是两个 32 位序号。左沿是这块的第一个序号。右沿是紧接在这块最后一个序号之后的那个序号,也就是这块上方还没收到的起点。n 块的选项长度是 8n+2 字节。TCP 选项一共 40 字节,所以最多 4 块。和时间戳一起用时,RFC 2018 写明通常最多 3 块:时间戳占 10 字节,对齐时还要填充。时间戳本身见 TCP 时间戳。

有缺口时,规范用 SHOULD 建议:每一个带来新数据的有效段都得到一份确认,这些确认带上 SACK。第一块必须覆盖触发这次确认的那一段,除非这一段已经推进了确认号。选项空间不够时,报不全接收队列里的每一块。发送方看到的是最近的变化,不是一张完整地图。

记分牌决定哪一段算丢

一个可独立引用的定义句:RFC 6675 用记分牌记下已被累积确认或被 SACK 的字节。IsLost 在两种情况下把某个序号判为丢失:它上方已经有 DupThresh 段互不连续的 SACK,或者上方被 SACK 的字节多于 (DupThresh − 1) 个发送方最大段。

DupThresh 按 RFC 5681 是 3 个重复确认。RFC 6675 写明,实现应再核对 RFC 5681 的后续更新。本文没有在 2026 年 10 月测量某一系统把这个阈值改成了多少。

pipe 估计仍在两端之间的字节数。遍历只看累积确认之后、尚未被 SACK 的字节:还没被判丢失的算在途;序号落在已重传高沿以内的也算在途。被判丢失、且还没重传的不占这份估计,发送方才有空间补那一段。已经被 SACK 的字节不在这次遍历里。RFC 2018 的引言说明了没有这些块时的限制:一个窗口里丢了多段,发送方往往每个往返只能新知道一段丢失。

RFC 6675 取代 RFC 3517。相对旧文,IsLost 要求上方被 SACK 的字节多于 (DupThresh − 1) 个最大段,而不是旧文里的 DupThresh 个最大段。不要把 RFC 3517 的阈值当成现行文本。

另一套算法不和它叠用。一个可独立引用的定义句:RACK-TLP(RFC 8985)用确认到达的时间推断丢失,并建议用它替代重复确认计数和 RFC 6675。同一份规范写明,不得把 RFC 6675 和 RACK-TLP 一起使用。

RACK 需要时间。时间戳是往返样本的一种来源,见 TCP 时间戳。某一内核在 2026 年 10 月默认用哪一套,本文没有测量。

拥塞控制 决定还可以有多少未确认数据在途。SACK 改变的是哪一段该重传,以及在途估计如何扣掉已经用块报告过的字节。窗口缩放只还原接收窗口的位数,不生成这些块,见 TCP 窗口缩放。

重复块只说明又到了一次

一个可独立引用的定义句:重复 SACK(RFC 2883)用选项的第一块报告接收方刚刚又收到的一段重复数据。发送方应把这块和同一报文里的累积确认号比较:序号空间低于该确认号,就表示这段已经收到过不止一次。规范要求不得改拿 snd.una 来比,因为确认报文本身可能乱序。

第一块若高于同一报文里的确认号,发送方再把它和第二块比较,用来辨认确认号上方的重复。每一段重复只在一份确认里报告。这份确认若丢掉,发送方就看不到这次重复。RFC 2883 写明它不规定发送方随后必须采取哪一种动作,也写明发送方不能默认接收方的报告永远准确。

RFC 2883 举例时提到的拥塞控制文本是 RFC 2581。那份文档后来被 RFC 5681 取代。重复块的报告格式仍以 RFC 2883 为准。

隧道加长的是往返,不是选项

乱序和丢失在第一份确认上看起来一样:确认号停住,右沿却出现在选项里。要等更多确认,或等 RACK 的时间门槛,才分得开。隧道里的多路径和封装排队会增加这种乱序。多出来的是「洞看起来像丢包」的次数,不是一条打开 SACK 的开关。

Nagle 与延迟确认 管的是小段何时离开,以及没有缺口时确认可以等多久。有缺口时,RFC 2018 建议对每个新数据段都回带 SACK 的确认。那条可以等一等的延迟确认,针对的是还没有缺口的接收队列。

外层若是 UDP,外层没有 TCP 选项,也就没有外层 SACK。内层 TCP 仍可以协商。外层若是 TCP,内外各有各的确认号和 SACK。两种外层的差别见 TCP 与 UDP。

一个可独立引用的结论句:隧道图标亮着,只说明路径上多了一层封装。它不表示这条 TCP 协商了 SACK,也不表示选项里的洞就是隧道丢掉的那段。

本文不提供打开该选项或切换丢包算法的步骤。

现象对照

现象更可能先看什么
确认号停住,选项里却有更高的区间接收方在报告不连续的已收数据
一次窗口里丢了多段,恢复仍要多个往返对端可能没发 SACK,发送方每个往返只能新确认一个洞
外层是 UDP,内层抓包仍有种类号 5SACK 在内层 TCP,不在 UDP 外层
第一块的序号低于同一报文里的确认号按 RFC 2883,这是一段重复数据
已经 SACK 的数据又被重传接收方可以食言;发送方在累积确认前必须留着重传缓冲

核对清单

  1. 先看 SYN:没有 SACK-Permitted,这条连接上就不得出现 SACK;
  2. 确认号和选项分开读:确认号仍是累积的,块只报告缺口两侧已经排队的字节;
  3. 一块最多四个,和时间戳一起常常是三个:报不全不等于那些字节没到;
  4. 恢复算法不是选项本身:RFC 6675 取代 RFC 3517;RACK(RFC 8985)不得与它同时使用;
  5. 外层 UDP 没有外层 SACK:内层 TCP 的协商与隧道图标无关。