前言
默认情况下,Wireshark 的 TCP 解析器会跟踪每个 TCP 会话的状态,并在检测到问题或潜在问题时提供额外的信息。在第一次打开捕获文件时,会对每个 TCP 数据包进行一次分析,数据包按照它们在数据包列表中出现的顺序进行处理。可以通过 "Analyze TCP sequence numbers" TCP 解析首选项启用或禁用此功能。
TCP 分析展示
在数据包文件中进行 TCP 分析时,关于 "TCP Spurious Retransmission" 一般是如下显示的,包括:
- Packet List 窗口中的 Info 信息列,以 [TCP Spurious Retransmission] 黑底红字进行标注;
- Packet Details 窗口中的 TCP 协议树下,在 [SEQ/ACK analysis] -> [TCP Analysis Flags] 中定义该 TCP 数据包的分析说明。
- 考虑到 TCP 乱序、重传场景的复杂性,专家信息在重传的判断上,前面都会有一个(suspected),表示疑似,说明并不是百分百正确,属于 Note 注意。
- 另在专家信息中,对于 TCP Spurious Retransmission 标志位分析同时会增加两种 Note,包括 Spurious Retransmission 和 Retransmission。
TCP Spurious Retransmission 定义
文档中关于 TCP Spurious Retransmission
的定义看起来简单,但实际考虑到 TCP 乱序、重传场景的复杂性,在 TCP 分析中对于 TCP Spurious Retransmission
是与 TCP Out-Of-Order
、TCP Fast Retransmission
、TCP Retransmission
等在一起判断标记乱序或重传类型,而在不少场景还会有判断出错的问题,当然 Wireshark 考虑到这种情况,也有手动修正的选项,这正好也侧面证明了上面的说法,关于 TCP 乱序、重传的复杂性。
TCP Spurious Retransmission
的定义如下,当以下所有条件都为真时设置:
- SYN 或者 FIN 标志位设置
- 不是 Keep-Alive 数据包
- TCP 段长度大于零
- 该 TCP 流的数据已被确认,也就是说,反方向之前的 LastACK Num 已被设置
- 该数据包的 Next Seq Num 小于或等于反方向之前的 LastACK Num
替代 Fast Retransmission
、Out-Of-Order
和 Retransmission
。
plain
Checks for a retransmission based on analysis data in the reverse direction. Set when all of the following are true:
The SYN or FIN flag is set.
This is not a keepalive packet.
The segment length is greater than zero.
Data for this flow has been acknowledged. That is, the last-seen acknowledgment number has been set.
The next sequence number is less than or equal to the last-seen acknowledgment number.
Supersedes "Fast Retransmission", "Out-Of-Order", and "Retransmission".
结合实际源码+场景,文档中定义有错误,第1个条件(SYN 或者 FIN 标志位设置)和第3个条件(TCP 段长度大于零)应该是或的关系,而不是与的关系。
具体的代码如下,总的来说这段代码是 Wireshark 中 TCP 分析模块的一部分,用于检测和标识 TCP 数据包中的各种重传类型。它的主要功能是根据当前数据包的序列号、长度、标志位以及之前收到的 TCP 数据包的信息,判断当前数据包是否属于重传,如果是则进一步确定它属于哪种重传类型。
根据分析 TCP 数据包的各种特征,对重传数据包进行分类,有助于更好地理解 TCP 连接中的重传行为,对于诊断网络问题很有帮助。在排除了 KeepAlive 数据包后,如果所有下述条件均满足,则认为该数据包是一个虚假重传包。
- 检查 TCP 段大小是否大于 0;
- 检查反方向之前的 LastACK Num 是否不为 0;
- 检查数据包的 Next Seq Num(seq+seglen)是否小于或等于反方向之前的 LastACK Num。
plain
/* RETRANSMISSION/FAST RETRANSMISSION/OUT-OF-ORDER
* If the segment contains data (or is a SYN or a FIN) and
* if it does not advance the sequence number, it must be one
* of these three.
* Only test for this if we know what the seq number should be
* (tcpd->fwd->nextseq)
*
* Note that a simple KeepAlive is not a retransmission
*/
if (seglen>0 || flags&(TH_SYN|TH_FIN)) {
gboolean seq_not_advanced = tcpd->fwd->tcp_analyze_seq_info->nextseq
&& (LT_SEQ(seq, tcpd->fwd->tcp_analyze_seq_info->nextseq));
guint64 t;
guint64 ooo_thres;
if(tcpd->ta && (tcpd->ta->flags&TCP_A_KEEP_ALIVE) ) {
goto finished_checking_retransmission_type;
}
/* This segment is *not* considered a retransmission/out-of-order if
* the segment length is larger than one (it really adds new data)
* the sequence number is one less than the previous nextseq and
* (the previous segment is possibly a zero window probe)
*
* We should still try to flag Spurious Retransmissions though.
*/
if (seglen > 1 && tcpd->fwd->tcp_analyze_seq_info->nextseq - 1 == seq) {
seq_not_advanced = FALSE;
}
/* Check for spurious retransmission. If the current seq + segment length
* is less than or equal to the current lastack, the packet contains
* duplicate data and may be considered spurious.
*/
if ( seglen > 0
&& tcpd->rev->tcp_analyze_seq_info->lastack
&& LE_SEQ(seq + seglen, tcpd->rev->tcp_analyze_seq_info->lastack) ) {
if(!tcpd->ta){
tcp_analyze_get_acked_struct(pinfo->num, seq, ack, TRUE, tcpd);
}
tcpd->ta->flags|=TCP_A_SPURIOUS_RETRANSMISSION;
goto finished_checking_retransmission_type;
}
...
}
finished_checking_retransmission_type:
- next expected sequence number,为 nextseq,定义为 highest seen nextseq。
- lastack,定义为 Last seen ack for the reverse flow。
Packetdrill 示例
根据上述 TCP Spurious Retransmission
定义和代码说明,通过 packetdrill 模拟在收到对一个数据分段 ACK 已确认的情况下,仍然再次重传同一个数据分段即可,就会认为是 TCP Spurious Retransmission
。
plain
# cat tcp_spurious_retrans.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 16000 <mss 1460>
+0 > S. 0:0(0) ack 1 <...>
+0.01 < . 1:1(0) ack 1 win 16000
+0 accept(3, ..., ...) = 4
+0 < P. 1:21(20) ack 1 win 15000
+0 > . 1:1(0) ack 21 <...>
+0 < P. 21:41(20) ack 1 win 15000
#
经 Wireshark 展示如下,可以看到满足判断条件后,No.6 标识 [TCP Spurious Retransmission]
,是因为客户端发送的数据分段 No.4 Seq Num 1 + Len 20,已由服务器 No.5 ACK 21 确认收到了该数据分段,但是在之后客户端又再次发送了同样一个数据分段 No.6 Seq Num 1 + Len 20,此时 Wireshark 就会认为 No.6 是虚假重传,而 No.7 是一次重复确认。
实例
关于 TCP Spurious Retransmission
的实例,实际日常抓包中不算少见,是一种比较好理解的 TCP 分析标志。在不同的场景中,也会伴生着出现像是 TCP Dup ACK
、TCP ACKed unseen segment
、TCP Previous segment not captured
等信息。
- TCP Spurious Retransmission + TCP Dup ACK
一个 ACK 响应慢的的场景,根据 Length 54 长度,可知该数据包跟踪文件是在客户端捕获,IRTT 为 45.7ms,但在客户端收到服务器端 No.181 的数据分段后,理论应该最多几 ms 内说响应 ACK,但是直到 209ms 之后才回复 No.182 ACK,此时在服务器端已产生了超时重传,也就是 No.183,所以在客户端收到 No.183 同样的一个数据分段后,即标识为 [TCP Spurious Retransmission]
, 而此次客户端不到 1ms 就响应了 ACK,也就是 [TCP Dup ACK]
。
- TCP Spurious Retransmission + TCP ACKed unseen segment
一个 ACK 丢失的场景,根据客户端 No.6 数据包 ACK Num 9 可知已经收到了服务器端发送的 Seq Num 9 之前的所有数据分段,但往上到 No.4 却没有看到相关数据分段,当然只是没有捕获到,实际上客户端已收到,因此 No.6 标识成 [TCP ACKed unseen segment]
,但为什么看到了 No.6 ACK 了,服务器端仍然重传了 No.7 。实际上这是抓包点的问题,或者说此数据包跟踪文件不是在服务器端上抓的,譬如在中间端抓取,捕获到了 No.6 ACK,但是这个 ACK 再往服务器去传输时丢失了,因此服务器在没收到 ACK 的情况下,重传了 No.7 Seq Num 1 + Len 8 的数据分段,也因此被标识成 [TCP Spurious Retransmission]
。
- 错误的 TCP Spurious Retransmission
错误的 TCP 虚假重传场景,如下所示,No.5-6 发送端所发送的数据分段,因个别未捕获到,标识为 [TCP Previous segment not caputred]
,但接收端 No.9 的 ACK Num 9881 说明已确认接收了序号 9881 之前的数据段,但在 No.11 发送端又重新发送了 Seq Num 4940 + Len 1368 的数据分段,因此符合条件,标识为 [TCP Spurious Retransmission]
。
一切看起来挺合理,但为什么说是判断错误呢?凡事深想一层,干活多做一步,再瞅瞅 ip.id ,你会发现有些问题。发送端 No.11 的 ip.id 为 29559,这不是应该是在 No.4 和 No.5 之间的一个数据包嘛,所以呢,No.11 不是重传,它是原本的数据分段,也因此它应该被标识成 [TCP Out-Of-Order]
。
但是为什么会出现这样的情况,乱序的数据段出现在了 ACK 确认之后,虽然不是完全确认抓包的环境,但基本上可以猜测是交换机镜像或者 TAP 出了问题,更有可能是后者造成的乱序。
总结
考虑到数据包会出现乱序、重传等各类不同的场景,产生 TCP Spurious Retransmission
的情形自然也是五花八门,具体问题具体分析。