要经验,不要经验主义
基于 packetdrill TCP 三次握手脚本,通过构造模拟服务器端场景,继续测试基础快速重传相关。
基础脚本
# cat tcp_fast_retransmit_1_000.pkt
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1000>
+0 > S. 0:0(0) ack 1 <...>
+0.01 < . 1:1(0) ack 1 win 10000
+0 accept(3, ..., ...) = 4
#
快速重传
TCP 快速重传是一种机制,用于在检测到数据丢失时快速触发重传,以提高数据传输效率。当 TCP 接收端收到一个失序的报文段时,它会发送重复的确认(ACK)给发送端。如果发送端连续收到三个重复的 ACK ,就会认为某个报文段可能已经丢失,从而立即重传该报文段,而无需等待超时计时器到期。这种机制可以显著减少因丢包导致的传输延迟,同时与拥塞控制机制配合,避免因重传引发网络拥塞加剧。
考虑到目前真实环境更多都是基于 SACK 开启下的各类重传(包括但不限于 RACK、ER、TLP 等等),因此在无 SACK 的场景下的快速重传,我个人把它定义为基础快速重传。
基础测试
数据接收端
首先观察下丢包场景下数据接收端响应 ACK 的发送行为,计划注入 6 个 MSS 1000 字节大小的数据段,但模拟两个数据段丢失,假设是第 2 个和第 4 个数据段。
# cat tcp_fast_retransmit_1_007.pkt
`ethtool -K tun0 tso off
ethtool -K tun0 gso off`
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1000>
+0 > S. 0:0(0) ack 1 <...>
+0.01 < . 1:1(0) ack 1 win 10000
+0 accept(3, ..., ...) = 4
+0.01 < P. 1:1001(1000) ack 1 win 10000
+0 < P. 2001:3001(1000) ack 1 win 10000
+0 < P. 4001:5001(1000) ack 1 win 10000
+0 < P. 5001:6001(1000) ack 1 win 10000
#
运行脚本观察发送端现象,会触发出 3 个 Dup ACK。
# packetdrill tcp_fast_retransmit_1_007.pkt
#
# tcpdump -i any -nn port 8080
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
22:31:45.026968 tun0 In IP 192.0.2.1.52979 > 192.168.77.65.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
22:31:45.027001 tun0 Out IP 192.168.77.65.8080 > 192.0.2.1.52979: Flags [S.], seq 294502670, ack 1, win 64240, options [mss 1460], length 0
22:31:45.037074 ? In IP 192.0.2.1.52979 > 192.168.77.65.8080: Flags [.], ack 1, win 10000, length 0
22:31:45.047127 ? In IP 192.0.2.1.52979 > 192.168.77.65.8080: Flags [P.], seq 1:1001, ack 1, win 10000, length 1000: HTTP
22:31:45.047145 ? Out IP 192.168.77.65.8080 > 192.0.2.1.52979: Flags [.], ack 1001, win 64000, length 0
22:31:45.047154 ? In IP 192.0.2.1.52979 > 192.168.77.65.8080: Flags [P.], seq 2001:3001, ack 1, win 10000, length 1000: HTTP
22:31:45.047159 ? Out IP 192.168.77.65.8080 > 192.0.2.1.52979: Flags [.], ack 1001, win 64000, length 0
22:31:45.047164 ? In IP 192.0.2.1.52979 > 192.168.77.65.8080: Flags [P.], seq 4001:5001, ack 1, win 10000, length 1000: HTTP
22:31:45.047166 ? Out IP 192.168.77.65.8080 > 192.0.2.1.52979: Flags [.], ack 1001, win 64000, length 0
22:31:45.047170 ? In IP 192.0.2.1.52979 > 192.168.77.65.8080: Flags [P.], seq 5001:6001, ack 1, win 10000, length 1000: HTTP
22:31:45.047172 ? Out IP 192.168.77.65.8080 > 192.0.2.1.52979: Flags [.], ack 1001, win 64000, length 0
22:31:45.047241 ? Out IP 192.168.77.65.8080 > 192.0.2.1.52979: Flags [R.], seq 1, ack 1001, win 64000, length 0
22:31:45.047262 ? In IP 192.0.2.1.52979 > 192.168.77.65.8080: Flags [R.], seq 6001, ack 1, win 10000, length 0
#
那么理论会触发出数据发送端的基础快速重传,修改脚本如下。
# cat tcp_fast_retransmit_1_008.pkt
`ethtool -K tun0 tso off
ethtool -K tun0 gso off`
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1000>
+0 > S. 0:0(0) ack 1 <...>
+0.01 < . 1:1(0) ack 1 win 10000
+0 accept(3, ..., ...) = 4
+0.01 < P. 1:1001(1000) ack 1 win 10000
+0 < P. 2001:3001(1000) ack 1 win 10000
+0 < P. 4001:5001(1000) ack 1 win 10000
+0 < P. 5001:6001(1000) ack 1 win 10000
+0.01 < P. 1001:2001(1000) ack 1 win 10000
#
接收端在收到 Seq 1001:2001 的重传数据包后,直接响应了 ACK 数据包 AckNum 3001,这意味着接收端之前收到的乱序数据包 Seq 2001:3001 并没有丢弃,而是缓存起来了。
对于接收端,发出 ACK 数据包 AckNum 3001 就是说下一个希望接收 Seq 为 3001 的数据段,而对于发送端,收到这个 ACK 数据包,也就明白之前 Seq 3001:4001 的数据段也发生了丢失。
# packetdrill tcp_fast_retransmit_1_008.pkt
#
# tcpdump -i any -nn port 8080
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
22:33:47.198978 tun0 In IP 192.0.2.1.55827 > 192.168.129.221.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
22:33:47.199005 tun0 Out IP 192.168.129.221.8080 > 192.0.2.1.55827: Flags [S.], seq 206416240, ack 1, win 64240, options [mss 1460], length 0
22:33:47.209075 tun0 In IP 192.0.2.1.55827 > 192.168.129.221.8080: Flags [.], ack 1, win 10000, length 0
22:33:47.219137 ? In IP 192.0.2.1.55827 > 192.168.129.221.8080: Flags [P.], seq 1:1001, ack 1, win 10000, length 1000: HTTP
22:33:47.219153 ? Out IP 192.168.129.221.8080 > 192.0.2.1.55827: Flags [.], ack 1001, win 64000, length 0
22:33:47.219162 ? In IP 192.0.2.1.55827 > 192.168.129.221.8080: Flags [P.], seq 2001:3001, ack 1, win 10000, length 1000: HTTP
22:33:47.219166 ? Out IP 192.168.129.221.8080 > 192.0.2.1.55827: Flags [.], ack 1001, win 64000, length 0
22:33:47.219172 ? In IP 192.0.2.1.55827 > 192.168.129.221.8080: Flags [P.], seq 4001:5001, ack 1, win 10000, length 1000: HTTP
22:33:47.219176 ? Out IP 192.168.129.221.8080 > 192.0.2.1.55827: Flags [.], ack 1001, win 64000, length 0
22:33:47.219182 ? In IP 192.0.2.1.55827 > 192.168.129.221.8080: Flags [P.], seq 5001:6001, ack 1, win 10000, length 1000: HTTP
22:33:47.219185 ? Out IP 192.168.129.221.8080 > 192.0.2.1.55827: Flags [.], ack 1001, win 64000, length 0
22:33:47.229140 ? In IP 192.0.2.1.55827 > 192.168.129.221.8080: Flags [P.], seq 1001:2001, ack 1, win 10000, length 1000: HTTP
22:33:47.229157 ? Out IP 192.168.129.221.8080 > 192.0.2.1.55827: Flags [.], ack 3001, win 62000, length 0
22:33:47.229235 ? Out IP 192.168.129.221.8080 > 192.0.2.1.55827: Flags [R.], seq 1, ack 3001, win 63000, length 0
22:33:47.229280 ? In IP 192.0.2.1.55827 > 192.168.129.221.8080: Flags [R.], seq 2001, ack 1, win 10000, length 0
#
那么继续模拟重传 Seq 3001:4001 的数据段,修改脚本如下。
# cat tcp_fast_retransmit_1_009.pkt
`ethtool -K tun0 tso off
ethtool -K tun0 gso off`
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1000>
+0 > S. 0:0(0) ack 1 <...>
+0.01 < . 1:1(0) ack 1 win 10000
+0 accept(3, ..., ...) = 4
+0.01 < P. 1:1001(1000) ack 1 win 10000
+0 < P. 2001:3001(1000) ack 1 win 10000
+0 < P. 4001:5001(1000) ack 1 win 10000
+0 < P. 5001:6001(1000) ack 1 win 10000
+0.01 < P. 1001:2001(1000) ack 1 win 10000
+0.01 < P. 3001:4001(1000) ack 1 win 10000
#
接收端在收到 Seq 3001:4001 的重传数据包后,直接响应了 ACK 数据包 AckNum 6001,这同样说明接收端之前收到的乱序数据包 Seq 4001:6001 也都没有丢弃,而是缓存起来了,至此所有的数据段都正常接收并进行了确认。
# packetdrill tcp_fast_retransmit_1_009.pkt
#
# tcpdump -i any -nn port 8080
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
21:55:35.519055 tun0 In IP 192.0.2.1.42777 > 192.168.153.142.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
21:55:35.519131 tun0 Out IP 192.168.153.142.8080 > 192.0.2.1.42777: Flags [S.], seq 1066490332, ack 1, win 64240, options [mss 1460], length 0
21:55:35.529258 ? In IP 192.0.2.1.42777 > 192.168.153.142.8080: Flags [.], ack 1, win 10000, length 0
21:55:35.539330 ? In IP 192.0.2.1.42777 > 192.168.153.142.8080: Flags [P.], seq 1:1001, ack 1, win 10000, length 1000: HTTP
21:55:35.539356 ? Out IP 192.168.153.142.8080 > 192.0.2.1.42777: Flags [.], ack 1001, win 64000, length 0
21:55:35.539366 ? In IP 192.0.2.1.42777 > 192.168.153.142.8080: Flags [P.], seq 2001:3001, ack 1, win 10000, length 1000: HTTP
21:55:35.539370 ? Out IP 192.168.153.142.8080 > 192.0.2.1.42777: Flags [.], ack 1001, win 64000, length 0
21:55:35.539375 ? In IP 192.0.2.1.42777 > 192.168.153.142.8080: Flags [P.], seq 4001:5001, ack 1, win 10000, length 1000: HTTP
21:55:35.539378 ? Out IP 192.168.153.142.8080 > 192.0.2.1.42777: Flags [.], ack 1001, win 64000, length 0
21:55:35.539384 ? In IP 192.0.2.1.42777 > 192.168.153.142.8080: Flags [P.], seq 5001:6001, ack 1, win 10000, length 1000: HTTP
21:55:35.539389 ? Out IP 192.168.153.142.8080 > 192.0.2.1.42777: Flags [.], ack 1001, win 64000, length 0
21:55:35.549410 ? In IP 192.0.2.1.42777 > 192.168.153.142.8080: Flags [P.], seq 1001:2001, ack 1, win 10000, length 1000: HTTP
21:55:35.549487 ? Out IP 192.168.153.142.8080 > 192.0.2.1.42777: Flags [.], ack 3001, win 62000, length 0
21:55:35.559351 ? In IP 192.0.2.1.42777 > 192.168.153.142.8080: Flags [P.], seq 3001:4001, ack 1, win 10000, length 1000: HTTP
21:55:35.559385 ? Out IP 192.168.153.142.8080 > 192.0.2.1.42777: Flags [.], ack 6001, win 59000, length 0
21:55:35.559459 ? Out IP 192.168.153.142.8080 > 192.0.2.1.42777: Flags [R.], seq 1, ack 6001, win 64000, length 0
21:55:35.559488 ? In IP 192.0.2.1.42777 > 192.168.153.142.8080: Flags [R.], seq 4001, ack 1, win 10000, length 0
#
如上实验,模拟的是第 2 个和第 4 个数据段的丢失,同样,假设是第 3 个和第 5 个数据段丢失,又或者是别的丢失场景,实际处理过程也是一样。
实验现象也说明了一些东西,基础快速重传实际不像一些资料上所说的有问题,像是不知道重传是重传一个,还是重传所有的问题,确切来说发送端是清楚的。至于说没有 SACK 的支持下,仍是可以通过 ACK 清楚哪个丢失了从而进行重传,只不过效率比较低,需要依靠一个又一个 ACK 来告知再重传,而有了 SACK 的支持,实际上也是通过 ACK&SACK 信息告知,从而清楚哪几个数据段丢失了,之后才能更有效率的进行重传。
数据发送端
再观察下丢包场景下数据发送端的数据发送行为,如果仍是连续发送 6 个 MSS 1000 字节大小的数据段,模拟两个数据段丢失,其中一个是第 2 个数据段,如下脚本,会发现无论另一个丢失的分段是第 3-6 中的哪一个,返回的 ACK 实际都是一样的,即 Dup ACK,也就是这种情况下对于发送端是无法得知接收端到底是收到了第 3-6 中的哪 3 个数据段。
# cat tcp_fast_retransmit_1_010.pkt
`ethtool -K tun0 tso off
ethtool -K tun0 gso off`
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1000>
+0 > S. 0:0(0) ack 1 <...>
+0.01 < . 1:1(0) ack 1 win 10000
+0 accept(3, ..., ...) = 4
+0.01 write(4, ..., 6000) = 6000
+0.01 < . 1:1(0) ack 1001 win 10000
+0 < . 1:1(0) ack 1001 win 10000
+0 < . 1:1(0) ack 1001 win 10000
+0 < . 1:1(0) ack 1001 win 10000
#
运行脚本观察发送端的重传现象,在收到 3 个 Dup ACK 的情况下,会触发出基础快速重传。
# packetdrill tcp_fast_retransmit_1_010.pkt
#
# tcpdump -i any -nn port 8080
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
22:12:04.726952 tun0 In IP 192.0.2.1.53799 > 192.168.29.171.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
22:12:04.726978 tun0 Out IP 192.168.29.171.8080 > 192.0.2.1.53799: Flags [S.], seq 4229715043, ack 1, win 64240, options [mss 1460], length 0
22:12:04.737098 ? In IP 192.0.2.1.53799 > 192.168.29.171.8080: Flags [.], ack 1, win 10000, length 0
22:12:04.747314 ? Out IP 192.168.29.171.8080 > 192.0.2.1.53799: Flags [.], seq 1:1001, ack 1, win 64240, length 1000: HTTP
22:12:04.747318 ? Out IP 192.168.29.171.8080 > 192.0.2.1.53799: Flags [P.], seq 1001:2001, ack 1, win 64240, length 1000: HTTP
22:12:04.747324 ? Out IP 192.168.29.171.8080 > 192.0.2.1.53799: Flags [.], seq 2001:3001, ack 1, win 64240, length 1000: HTTP
22:12:04.747325 ? Out IP 192.168.29.171.8080 > 192.0.2.1.53799: Flags [P.], seq 3001:4001, ack 1, win 64240, length 1000: HTTP
22:12:04.747328 ? Out IP 192.168.29.171.8080 > 192.0.2.1.53799: Flags [.], seq 4001:5001, ack 1, win 64240, length 1000: HTTP
22:12:04.747329 ? Out IP 192.168.29.171.8080 > 192.0.2.1.53799: Flags [P.], seq 5001:6001, ack 1, win 64240, length 1000: HTTP
22:12:04.757438 ? In IP 192.0.2.1.53799 > 192.168.29.171.8080: Flags [.], ack 1001, win 10000, length 0
22:12:04.757494 ? In IP 192.0.2.1.53799 > 192.168.29.171.8080: Flags [.], ack 1001, win 10000, length 0
22:12:04.757505 ? In IP 192.0.2.1.53799 > 192.168.29.171.8080: Flags [.], ack 1001, win 10000, length 0
22:12:04.757512 ? In IP 192.0.2.1.53799 > 192.168.29.171.8080: Flags [.], ack 1001, win 10000, length 0
22:12:04.757535 ? Out IP 192.168.29.171.8080 > 192.0.2.1.53799: Flags [.], seq 1001:2001, ack 1, win 64240, length 1000: HTTP
22:12:04.757639 ? Out IP 192.168.29.171.8080 > 192.0.2.1.53799: Flags [F.], seq 6001, ack 1, win 64240, length 0
22:12:04.757665 ? In IP 192.0.2.1.53799 > 192.168.29.171.8080: Flags [R.], seq 1, ack 1001, win 10000, length 0
#
修改脚本,模拟收到基础快速重传的响应 ACK,譬如 ACK Num 3001 ,继续观察发送端的重传现象。
# cat tcp_fast_retransmit_1_011.pkt
`ethtool -K tun0 tso off
ethtool -K tun0 gso off`
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1000>
+0 > S. 0:0(0) ack 1 <...>
+0.01 < . 1:1(0) ack 1 win 10000
+0 accept(3, ..., ...) = 4
+0.01 write(4, ..., 6000) = 6000
+0.01 < . 1:1(0) ack 1001 win 10000
+0 < . 1:1(0) ack 1001 win 10000
+0 < . 1:1(0) ack 1001 win 10000
+0 < . 1:1(0) ack 1001 win 10000
+0.01 < . 1:1(0) ack 3001 win 10000
#
可以看到发送端在收到 ACK 数据包 AckNum 3001 之后,紧接着就重传了 Seq 3001:4001 的数据包。
# cat tcp_fast_retransmit_1_007.pkt
`ethtool -K tun0 tso off
ethtool -K tun0 gso off`
0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 < S 0:0(0) win 10000 <mss 1000>
+0 > S. 0:0(0) ack 1 <...>
+0.01 < . 1:1(0) ack 1 win 10000
+0 accept(3, ..., ...) = 4
+0.01 < P. 1:1001(1000) ack 1 win 10000
+0 < P. 2001:3001(1000) ack 1 win 10000
+0 < P. 4001:5001(1000) ack 1 win 10000
+0 < P. 5001:6001(1000) ack 1 win 10000
#
0
最后当然还可以追加,模拟注入确认所有数据段的 ACK 数据包 AckNum 6001 ,这个就不再继续实验了。
实验总结
通过数据接收端和数据发送端的结合实验验证,进一步了解了无 SACK 场景下的基础快速重传行为。
往期推荐
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...