TCP Analysis Flags 之 TCP Spurious Retransmission

前言

默认情况下,Wireshark 的 TCP 解析器会跟踪每个 TCP 会话的状态,并在检测到问题或潜在问题时提供额外的信息。在第一次打开捕获文件时,会对每个 TCP 数据包进行一次分析,数据包按照它们在数据包列表中出现的顺序进行处理。可以通过 "Analyze TCP sequence numbers" TCP 解析首选项启用或禁用此功能。

TCP 分析展示

在数据包文件中进行 TCP 分析时,关于 "TCP Spurious Retransmission" 一般是如下显示的,包括:

  1. Packet List 窗口中的 Info 信息列,以 [TCP Spurious Retransmission] 黑底红字进行标注;
  2. Packet Details 窗口中的 TCP 协议树下,在 [SEQ/ACK analysis] -> [TCP Analysis Flags] 中定义该 TCP 数据包的分析说明。
  1. 考虑到 TCP 乱序、重传场景的复杂性,专家信息在重传的判断上,前面都会有一个(suspected),表示疑似,说明并不是百分百正确,属于 Note 注意。
  2. 另在专家信息中,对于 TCP Spurious Retransmission 标志位分析同时会增加两种 Note,包括 Spurious Retransmission 和 Retransmission。

TCP Spurious Retransmission 定义

文档中关于 TCP Spurious Retransmission 的定义看起来简单,但实际考虑到 TCP 乱序、重传场景的复杂性,在 TCP 分析中对于 TCP Spurious Retransmission 是与 TCP Out-Of-OrderTCP Fast RetransmissionTCP Retransmission 等在一起判断标记乱序或重传类型,而在不少场景还会有判断出错的问题,当然 Wireshark 考虑到这种情况,也有手动修正的选项,这正好也侧面证明了上面的说法,关于 TCP 乱序、重传的复杂性。

TCP Spurious Retransmission 的定义如下,当以下所有条件都为真时设置:

  • SYN 或者 FIN 标志位设置
  • 不是 Keep-Alive 数据包
  • TCP 段长度大于零
  • 该 TCP 流的数据已被确认,也就是说,反方向之前的 LastACK Num 已被设置
  • 该数据包的 Next Seq Num 小于或等于反方向之前的 LastACK Num

替代 Fast RetransmissionOut-Of-OrderRetransmission

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:
  1. next expected sequence number,为 nextseq,定义为 highest seen nextseq。
  2. 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 ACKTCP ACKed unseen segmentTCP Previous segment not captured 等信息。

  1. 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]

  1. 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]

  1. 错误的 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 的情形自然也是五花八门,具体问题具体分析。

相关推荐
Hacker_LaoYi16 分钟前
TCP/IP协议图--TCP/IP基础
网络·网络协议·tcp/ip
CodeGrindstone43 分钟前
Muduo网络库剖析 --- 架构设计
网络·c++·网络协议·tcp/ip
爱学测试的李木子1 小时前
Jmeter —— jmeter利用取样器中http发送请求
网络协议·jmeter·http
夏子曦1 小时前
网络——Socket与WebSocket
网络·websocket·网络协议
IPFoxy6664 小时前
动态代理如何加强安全性
大数据·服务器·开发语言·网络·网络协议·tcp/ip·php
小袁顶风作案8 小时前
Ubuntu桥接模式设置静态IP
网络·tcp/ip·桥接模式
搬砖的果果11 小时前
HTTP代理有那些常见的安全协议?
服务器·python·网络协议·tcp/ip
Looper033111 小时前
【Shell 脚本实现 HTTP 请求的接收、解析、处理逻辑】
网络·网络协议·http
eddieHoo11 小时前
HTTP、RPC
网络协议·http·rpc