Wireshark TS | 超时重传时间不翻倍

前言

看图猜故障系列,来自于技术群讨论的又一个问题,数据包如下图,No.4900 原始数据包,No.4901 间隔208ms第一次重传,No.4903 间隔208ms第二次重传。

问题就是数据包第二次超时重传,间隔时间为什么没有翻倍。

问题分析

说实话,刚看到这个数据包文件截图时,记忆中竟然没有一丝相似的故障现象,略感纳闷。实际解读,也没有特别反常的迹象,只是在间隔 384s 后,客户端所发送的数据段由于得不到 ACK 确认,进行了重传的过程。当然由于第二次重传的超时时间未翻倍的原因,所以首先我并没有把这些重传全部定义为是超时重传。

但既然是一个数据段的重传,没有其他输入的信息,那么可以先简单模拟超时重传,对比看下。

plain 复制代码
# cat test.pkt 
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.1 write(4,...,100) = 100

+0 `sleep 5`
#

执行脚本,同时通过 tcpdump 抓取数据包,现象如下。

可以看到发出数据段后,第一次重传间隔时间为213ms,而第二次重传间隔时间为436ms,也就是说都符合超时重传的现象,超时时间是正常翻倍了。

plain 复制代码
# packetdrill test.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:25:38.983501 tun0  In  IP 192.0.2.1.34827 > 192.168.209.238.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
22:25:38.983536 tun0  Out IP 192.168.209.238.8080 > 192.0.2.1.34827: Flags [S.], seq 3745491915, ack 1, win 64240, options [mss 1460], length 0
22:25:38.993637 tun0  In  IP 192.0.2.1.34827 > 192.168.209.238.8080: Flags [.], ack 1, win 10000, length 0
22:25:39.093744 tun0  Out IP 192.168.209.238.8080 > 192.0.2.1.34827: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:25:39.307178 tun0  Out IP 192.168.209.238.8080 > 192.0.2.1.34827: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:25:39.743184 tun0  Out IP 192.168.209.238.8080 > 192.0.2.1.34827: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:25:40.607187 tun0  Out IP 192.168.209.238.8080 > 192.0.2.1.34827: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:25:42.335186 tun0  Out IP 192.168.209.238.8080 > 192.0.2.1.34827: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
#

当然,由于第一个测试并没有支持 SACK,因此继续可以增加尝试测试。

plain 复制代码
# cat test.pkt 
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,nop,nop,sackOK>
+0 > S. 0:0(0) ack 1 <...>
+0.01 < . 1:1(0) ack 1 win 10000
+0 accept(3, ..., ...) = 4

+0.1 write(4,...,100) = 100

+0 `sleep 5`
#

执行脚本,同时通过 tcpdump 抓取数据包,现象如下。

可以看到发出数据段后,第一次重传间隔时间为213ms,而第二次重传间隔时间为216ms,和问题截图一模一样的现象,第二次重传的超时时间并未翻倍。

plain 复制代码
# packetdrill test.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:35:37.983481 tun0  In  IP 192.0.2.1.54829 > 192.168.44.251.8080: Flags [S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 0
22:35:37.983508 tun0  Out IP 192.168.44.251.8080 > 192.0.2.1.54829: Flags [S.], seq 2010255923, ack 1, win 64240, options [mss 1460,nop,nop,sackOK], length 0
22:35:37.993598 tun0  In  IP 192.0.2.1.54829 > 192.168.44.251.8080: Flags [.], ack 1, win 10000, length 0
22:35:38.093722 tun0  Out IP 192.168.44.251.8080 > 192.0.2.1.54829: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:35:38.307185 tun0  Out IP 192.168.44.251.8080 > 192.0.2.1.54829: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:35:38.523213 tun0  Out IP 192.168.44.251.8080 > 192.0.2.1.54829: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:35:38.975187 tun0  Out IP 192.168.44.251.8080 > 192.0.2.1.54829: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:35:39.839188 tun0  Out IP 192.168.44.251.8080 > 192.0.2.1.54829: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:35:41.567190 tun0  Out IP 192.168.44.251.8080 > 192.0.2.1.54829: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
#

通过两次实验对比,可知仅仅是增加了 SACK 的支持,就产生了不一样的结果,同时结合数据包的重传时间,可以判断出第一次重传并不是普通的超时重传,而在它发出之后,看到的第二次重传,才是真正的第一次超时重传,之后的也才是 RTO 不断翻倍后的超时重传过程。

那么第一次重传是什么?在 linux 系统中,它实际是一个 TLP 数据包,在SACK开启下,TLP 会重传强制传输还没有收到 ACK 确认的报文里面的最后一个报文或者未发送的新报文。 而在上面的测试以及问题截图场景中,也比较特殊,因为只发送了一个报文,所以第一次重传也就是发送了 TLP 定义中的最后一个报文,乍看起来,确实像是在进行超时重传。

TLP 介绍

Tail Loss Probe (TLP)是一个发送端算法,主要目的是使用快速重传取代RTO超时重传来处理尾包丢失场景。如果TCP尾包丢失,如果依靠RTO超时进行重传会带来比较大的延迟,进而影响用户体验。

那么如果一个TCP连接没有在一段时间内没有收到ACK报文,TLP会强制传输还没有收到ACK确认的报文里面的最后一个报文或者未发送的新报文(传输的这个报文就叫做loss probe)。

tcp_early_retrans - INTEGER

Enable Early Retransmit (ER), per RFC 5827. ER lowers the threshold for triggering fast retransmit when the amount of outstanding data is small and when no previously unsent data can be transmitted (such that limited transmit could be used). Also controls the use of Tail loss probe (TLP) that converts RTOs occurring due to tail losses into fast recovery (draft-dukkipati-tcpm-tcp-loss-probe-01).

Possible values:

0 disables ER

1 enables ER

2 enables ER but delays fast recovery and fast retransmit by a fourth of RTT. This mitigates connection falsely recovers when network has a small degree of reordering (less than 3 packets).

3 enables delayed ER and TLP.

4 enables TLP only.

Default: 3

证明实验

在 linux 中 tcp_early_retrans 参数默认值为 3,也就是同时启用了 Delayed ER 和 TLP。

plain 复制代码
# sysctl -a|grep early_retrans
net.ipv4.tcp_early_retrans = 3
# 

如果想证明确实和 TLP 相关,可以从三个方面测试下:

  1. 关闭 SACK;(上述实验已测试)
  2. net.ipv4.tcp_early_retrans 值修改为 4,仅开启 TLP;
  3. net.ipv4.tcp_early_retrans 值修改为 0,关闭 ER 以及 TLP。

首先仅开启 TLP 的情况,继续测试在 SACK 开启的脚本。

plain 复制代码
# sysctl -q net.ipv4.tcp_early_retrans=4
# sysctl -a|grep early_retrans
net.ipv4.tcp_early_retrans = 4
# 

执行脚本,同时通过 tcpdump 抓取数据包,现象如下,可以看到与默认值 3 的测试结果一致,说明了和 ER 重传无关,与 TLP 可能有关(严谨的说法,因为也有可能是某某重传引起)。

plain 复制代码
# 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
10:12:36.563494 tun0  In  IP 192.0.2.1.50647 > 192.168.165.47.8080: Flags [S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 0
10:12:36.563526 tun0  Out IP 192.168.165.47.8080 > 192.0.2.1.50647: Flags [S.], seq 3170095082, ack 1, win 64240, options [mss 1460,nop,nop,sackOK], length 0
10:12:36.573627 tun0  In  IP 192.0.2.1.50647 > 192.168.165.47.8080: Flags [.], ack 1, win 10000, length 0
10:12:36.673761 tun0  Out IP 192.168.165.47.8080 > 192.0.2.1.50647: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
10:12:36.887178 tun0  Out IP 192.168.165.47.8080 > 192.0.2.1.50647: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
10:12:37.103192 tun0  Out IP 192.168.165.47.8080 > 192.0.2.1.50647: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
10:12:37.535174 tun0  Out IP 192.168.165.47.8080 > 192.0.2.1.50647: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
10:12:38.399184 tun0  Out IP 192.168.165.47.8080 > 192.0.2.1.50647: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
10:12:40.127180 tun0  Out IP 192.168.165.47.8080 > 192.0.2.1.50647: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
#

继续关闭 TLP 的情况,仍然是测试 SACK 开启的脚本。

plain 复制代码
# sysctl -q net.ipv4.tcp_early_retrans=0
# sysctl -a|grep early_retrans
net.ipv4.tcp_early_retrans = 0
# 

执行脚本,同时通过 tcpdump 抓取数据包,现象如下,可以看到与关闭 SACK 的首次测试结果一致,标准的超时重传现象。对于关闭 TLP 和开启 TLP 的两次实验结果,可得知第一次重传数据包确实是 TLP 报文。

plain 复制代码
# 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
10:17:24.423493 tun0  In  IP 192.0.2.1.35821 > 192.168.117.180.8080: Flags [S], seq 0, win 10000, options [mss 1000,nop,nop,sackOK], length 0
10:17:24.423527 tun0  Out IP 192.168.117.180.8080 > 192.0.2.1.35821: Flags [S.], seq 1548070828, ack 1, win 64240, options [mss 1460,nop,nop,sackOK], length 0
10:17:24.433632 tun0  In  IP 192.0.2.1.35821 > 192.168.117.180.8080: Flags [.], ack 1, win 10000, length 0
10:17:24.533759 tun0  Out IP 192.168.117.180.8080 > 192.0.2.1.35821: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
10:17:24.751193 tun0  Out IP 192.168.117.180.8080 > 192.0.2.1.35821: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
10:17:25.183189 tun0  Out IP 192.168.117.180.8080 > 192.0.2.1.35821: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
10:17:26.047181 tun0  Out IP 192.168.117.180.8080 > 192.0.2.1.35821: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
10:17:27.775172 tun0  Out IP 192.168.117.180.8080 > 192.0.2.1.35821: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
#

问题总结

实际上对于重传触发出来的原因,或者说重传的归类,远远不止众所周知的超时重传、快速重传这两种,后续再慢慢展开分享吧。

相关推荐
2501_915106321 小时前
使用 Sniffmaster TCP 抓包和 Wireshark 网络分析
网络协议·tcp/ip·ios·小程序·uni-app·wireshark·iphone
那就回到过去2 小时前
MSTP路由协议简介
网络·网络协议·tcp/ip·ensp
阿钱真强道3 小时前
12 JetLinks MQTT直连设备事件上报实战(继电器场景)
linux·服务器·网络·数据库·网络协议
乾元3 小时前
终端安全(EDR):用深度学习识别未知勒索软件
运维·人工智能·网络协议·安全·网络安全·自动化·安全架构
九.九3 小时前
CANN HCOMM 底层机制深度解析:集合通信算法实现、RoCE 网络协议栈优化与多级同步原语
网络·网络协议·算法
Vect__6 小时前
基于线程池从零实现TCP计算器网络服务
c++·网络协议·tcp/ip
wenzhangli711 小时前
OoderAgent SDK(0.6.6) UDP通讯与协议测试深度解析
网络·网络协议·udp
咖丨喱15 小时前
IP校验和算法解析与实现
网络·tcp/ip·算法
小李独爱秋17 小时前
计算机网络经典问题透视:蜂窝网络切换如何“扼杀”你的TCP连接?
网络·网络协议·tcp/ip·计算机网络·php·信息与通信
路由侠内网穿透.17 小时前
本地部署代码托管解决方案 Gitea 并实现外部访问( Windows 版本)
运维·服务器·网络协议·gitea