TCP 流中的冗余确认号

网络工程 通讯协议 包分析
2021-07-23 06:29:59

我目前正在使用 Google 的 packetdrill制作用于对 FreeBSD 的 TCP/IP 堆栈进行回归测试的测试脚本但是,我遇到了一个涉及序列号的小问题。这是来自其中一个测试脚本的一小部分,用于验证 Early Retransmit 是否正常工作 -

// Three way handshake
0.100 < S 0:0(0) win 32792 <mss 1460,sackOK,nop,nop,nop,wscale 7>
0.100 > S. 0:0(0) ack 1 <...>
0.200 < . 1:1(0) ack 1 win 257
0.200 accept(3, ..., ...) = 4

// Our application writes 2920 bytes 
0.200 write(4, ..., 2920) = 2920
0.200 > P. 1:2921(2920) ack 1
0.300 < .  1:1(0) ack 1 win 257 <sack 1461:2921,nop,nop>
0.325 > .  1:1461(1460) ack 1  // delayed Early Retransmit at RTT/4 = 25ms
0.425 < .  1:1(0) ack 2921 win 257

0.500 close(4) = 0
0.500 > F. 2921:2921(0) ack 1
0.600 < F. 1:1(0) ack 2922 win 257
0.601 > .  2922:2922(0) ack 2

虽然测试很容易理解,但我想不通的是,为什么我们PUSH的同时又发送了一个确认号1,因为在三向握手中,对方已经发送了一个ACK开始序列号为 1。这不应该是正确的行吗?

0.200 > P. 1:2921(2920) ack 2
0.300 < .  2:2(0) ack 1 win 257 <sack 1461:2921,nop,nop>

也可以看出,在0.425秒时,对方再次发送了一个相同序列号1:1ACK
完整的测试脚本可以在这里找到
我本可以在 packetdrill 的邮件列表中发布这个问题,但我认为这是我的基础问题。

供快速参考~

  • 起始小数表示以秒为单位的绝对计时。
  • <>表示数据包的方向,<用于接收和>注入。
  • S表示SYN
  • P表示PUSH
  • F表示FIN
  • . 表示ACK
  • sack表示选择性确认。
  • accept(),close()write()是适当的系统调用。
1个回答

快速回答是第一次发送ack号后,总是发送。它总是反映下一个预期的序列号,因此除非新数据段到达,否则它将被重复。换句话说,如果堆栈发送确认传入字节n的数据包,并且由于某种原因在有更多传入数据要确认之前发送了一个新数据包,则堆栈将再次确认字节n

为什么与 PUSH 一起我们再次发送确认编号 1

至于ack是1而不是2,那是因为ack是数据字节数而不是数据包数,当一侧发送2920字节时,另一侧似乎没有发送任何数据。SYN、PSH 和 FIN 将计为一个字节,因此 SYN-ACK 或 FIN-ACK 也将计为一个字节。由于开头只有一个 SYN,因此 ACK 编号将对应于预期的下一个字节的从 1 开始的索引。换句话说,ack 1意思是“我在等待你的第一个数据字节”。

packetlife.net 上有一个很好的解释