前言
默认情况下,Wireshark 的 TCP 解析器会跟踪每个 TCP 会话的状态,并在检测到问题或潜在问题时提供额外的信息。在第一次打开捕获文件时,会对每个 TCP 数据包进行一次分析,数据包按照它们在数据包列表中出现的顺序进行处理。可以通过 "Analyze TCP sequence numbers" TCP 解析首选项启用或禁用此功能。
TCP 分析展示
在数据包文件中进行 TCP 分析时,关于 "TCP Dup ACK" 一般是如下显示的,包括:
- Packet List 窗口中的 Info 信息列,以 [TCP Dup ACK #] 黑底红字进行标注;
- Packet Details 窗口中的 TCP 协议树下,在 [SEQ/ACK analysis] -> [TCP Analysis Flags] 中定义该 TCP 数据包的分析说明。
TCP Dup ACK 定义
实际在 TCP 分析中,关于 TCP Dup ACK 的定义也相对来说简单,包括如下:
- TCP 段大小为 0
- 窗口大小非零且没有改变,或者有有效的 SACK 数据
- 下一个期望的 Seq Num 和 LastACK Num 是非 0 的(即连接已经建立)
- 没有设置 SYN、FIN、RST
plain
Set when all of the following are true:
The segment size is zero.
The window size is non-zero and hasn't changed, or there is valid SACK data.
The next expected sequence number and last-seen acknowledgment number are non-zero (i.e., the connection has been established).
SYN, FIN, and RST are not set.
具体的代码如下,总的来说这段代码的用于准确检测 TCP 重复 ACK 的情况,并记录相关信息以支持后续的重传机制分析,是 TCP 可靠传输的重要组成部分。这段代码的主要逻辑如下,如果所有下述条件均满足,则认为该数据包是一个重复确认包。
- 检查 TCP 段大小是否为 0;
- 检查窗口大小是否不为 0;
- 检查窗口大小与同方向之前窗口大小是否相同;
- 检查 Seq Num 是否等于同方向之前下一个期望的 Seq Num;
- 检查 ACK Num 是否等于同方向之前的LastACK Num;
- 检查当前数据包是否不是 SYN/FIN/RST 数据包。
plain
/* DUPLICATE ACK
* It is a duplicate ack if window/seq/ack is the same as the previous
* segment and if the segment length is 0
*/
if( seglen==0
&& window
&& window==tcpd->fwd->window
&& seq==tcpd->fwd->tcp_analyze_seq_info->nextseq
&& ack==tcpd->fwd->tcp_analyze_seq_info->lastack
&& (flags&(TH_SYN|TH_FIN|TH_RST))==0 ) {
/* MPTCP tolerates duplicate acks in some circumstances, see RFC 8684 4. */
if(tcpd->mptcp_analysis && (tcpd->mptcp_analysis->mp_operations!=tcpd->fwd->mp_operations)) {
/* just ignore this DUPLICATE ACK */
} else {
tcpd->fwd->tcp_analyze_seq_info->dupacknum++;
if(!tcpd->ta) {
tcp_analyze_get_acked_struct(pinfo->num, seq, ack, TRUE, tcpd);
}
tcpd->ta->flags|=TCP_A_DUPLICATE_ACK;
tcpd->ta->dupack_num=tcpd->fwd->tcp_analyze_seq_info->dupacknum;
tcpd->ta->dupack_frame=tcpd->fwd->tcp_analyze_seq_info->lastnondupack;
}
}
- next expected sequence number,为 nextseq,定义为 highest seen nextseq。
- lastack,定义为 Last seen ack for the reverse flow。
Packetdrill 示例
根据上述 TCP Dup ACK
定义和代码说明,通过 packetdrill 模拟丢包现象即可,因缺失中间一段数据,在收到后一段数据后,就会触发产生 TCP Dup ACK
数据包。
plain
# cat tcp_dup_ack.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 < P. 41:61(20) ack 1 win 15000
#
经 Wireshark 展示如下,可以看到满足判断条件后,No.7 标识 [TCP Dup ACK 5#1]
,意味着 No.7 与 No.5 重复,发生一次。
如果想触发多次重复的 Dup ACK,可增加几次后续数据段即可,如下
plain
# cat tcp_dup_ack_02.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 < P. 41:61(20) ack 1 win 15000
+0 < P. 61:81(20) ack 1 win 15000
+0 < P. 81:101(20) ack 1 win 15000
#
经 Wireshark 展示如下,No.7 标识 [TCP Dup ACK 5#1]
,No.9 标识 [TCP Dup ACK 5#2]
,No.11 标识 [TCP Dup ACK 5#3]
,共重复三次。
实例
关于 TCP Dup ACK
的实例,实际日常抓包中经常会看到,是比较常见的一种 TCP 分析信息,多数情况会发生在乱序或丢包场景中。在不同的场景中,也会伴生着出现像是 TCP Previous segment not caputred
、TCP Fast Retransmission
、TCP Spurious Retransmission
等信息,当然有时也会单独出现。
- TCP Dup ACK + TCP Fast Retransmission
很常见的数据段丢包场景,No.1969 Seq Num 为 1431,提示 TCP 之前的数据分段未被捕获到,也即丢失了一个长度为 1430 字节的数据分段(Seq Num 1、Next Seq Num 1431),因此在收到 No.1969-1971 三个数据分段后,客户端会依次响应 No.1972-1974 三个 ACK 数据包,包含 SACK 信息,并标识为 [TCP Dup ACK]
,重复三次#1-#3,这样服务器端触发产生了之前丢失分段的快速重传,即 No.1975 数据包。
- TCP Dup ACK + TCP Retransmission
一个看起来实际像是丢包,实际为乱序的场景。该数据包为客户端所抓取,No.33 和 No.34 为客户端所发送的数据分段,但在收到服务器端所返回的 No.36 Ack Num 11217 以及 SACK SLE=12168 SRE=12239 说明未收到 No.33,而收到了 No.34 数据包段,因此在客户端由 RACK 触发了重传了 No.33 数据包,也就是 No.37 Seq Num 11217,而在之后才收到了服务器端的 No.38 和 No.39 两个 Ack,其中 No.39 标识 [TCP Dup ACK]
,No.38 Ack Num 为 12239 , 而 No.39 Ack Num 也为 12239 + SACK SLE=11217 SRE=12168,说明在服务器端收到了两个同样的分段 Seq 11217 + Len 951 的数据段,也就是说 No.38 ACK 确认的是 No.33 原始数据段,而 No.39 ACK 确认的是 No.37 重传数据段。
- TCP Dup ACK + TCP Spurious Retransmission
一个 RTO 超时重传的场景,该数据包为客户端所抓取,对于服务器端所发送的 No.6 数据段,客户端由于延迟确认等原因,间隔 209ms 才发出 No.7 Ack 确认了该数据段,但同时此时在服务器端又因为 RTO 超时重传,对 No.6 进行了重传,也就是 No.8 数据包,标识 [TCP Spurious Retransmission],继而触发客户端发送了 No.9 ACK 数据包,标识 [TCP Dup ACK]
。
- TCP Dup ACK + TCP Out-Of-Order
一种数据包乱序的场景,该抓包点在客户端本地,服务器端所发送的数据分段在到达时即已发生了乱序,No.5 标识为 [TCP Previous segment not captured],之前应该还有两个 Len=1460 的分段,No.6 为其中一个乱序数据段,标识为 [TCP Out-Of-Order] ,之后客户端 No.7 Ack 确认了 No.5,标识为 [TCP Dup ACK]
,同时 SACK SLE=2921 SRE=4201,No.8 Ack Num 1461 + SACK SLE=2921 SRE=4201 确认了 No.6,之后才收到另外一个乱序数据段 No.9,最后回复 No.10 Ack Num 4201 。
- 看不到 TCP Out-Of-Order 的乱序
同样一种数据包乱序的场景,但是看不到乱序标识,这是正常的,只是抓包点的问题。该数据包为客户端所抓取,因为看到的客户端 No.3414 Len 1573 实际会大于 MTU 1380,但是在出本地网卡后,会变成两个数据包,一个 Len 1380,另一个 Len 193,再加 No.3415 Len 213,一共三个数据包到达了服务器,此时可能在中间网络传输中发生了乱序,数据分段顺序变成了 Len 193、Len 213、Len 1380,这样前两个数据包会触发服务器产生 No.3416-3417 ACK 数据包,标识为 [TCP Dup ACK]
,其中 No.3416 SLE=17956 SRE=18149 表明收到了 Len 193 的数据分段,No.3417 SLE=17956 SRE=18362 表明又收到了 Len 213 的数据分段,而在之后的 No.3420 ACK Num 18362 才表示收到了 Seq Num 18362 之前所有的数据分段。
总结
考虑到数据包会出现乱序、丢包、重传等各类不同的场景,产生 TCP Dup ACK
的情形自然也是五花八门,具体问题具体分析。