Wireshark TS | TCP 零窗口探测时间

0 阅读8分钟

前言

来自于网友的一个问题,是有关《TCP Analysis Flags 系列》中《TCP Analysis Flags 之 TCP ZeroWindowProbe》一个案例的讨论,话说当时我也没有注意这么细微的地方,因此简单讨论并进一步验证了下,算是一个新的小知识点。

问题背景

回顾一下 TCP 零窗口的概念,在 Wireshark TCP 分析中和 TCP 零窗口相关的实际上有三种信息,分别是:TCP ZeroWindowTCP ZeroWindowProbeTCP ZeroWindowProbeAck 。实际运行环境中,有时是单独出现的,譬如 TCP ZeroWindow ,有时是一起出现的,也就是出现了零窗口,之后就会出现需要为恢复窗口而进行的零窗口探测和零窗口探测确认。

其中一个案例如下:首先客户端发送数据,发现服务器接收窗口满了,则在 No.4 和 No.6 上标识 [TCP Window Full] ,此时服务器端 No.7 因为 Win 为 0 且未设置 SYN、FIN、RST 的情况下,标识为 [TCP ZeroWindow],之后陷入等待,大概 2 秒+后,客户端发送了 No.8 [TCP ZeroWindowProbe] 用于确认服务器端接收窗口是否恢复,服务器紧接着回复确认 No.9,表示仍处于零窗口未恢复,标识为 [TCP ZeroWindowProbeAck] + [TCP ZeroWindow] ,又再过了 300ms 后,服务器端发送 No.10 Win 此时更新为 14600,表示接收窗口已恢复,标识成 [TCP Window Update] ,至此完成一次完整的零窗口出现、探测及恢复过程。

问题是 No.8 和 No.7 的间隔时间,也就是 2 秒+,是如何得来的,也就是什么时候会发送 TCP ZeroWindowProbe 数据包。


问题分析

在出现 TCP 零窗口后,什么时候开始发送 TCP ZeroWindowProbe 数据包,这也就指的是第一次发送 TCP ZeroWindowProbe 数据包的时间。

首先说为什么发送端会发 TCP 零窗口探测包,自然是本端有数据需要发送,但如果发送失败,则需要检查是否需要启动零窗口探测定时器,而在满足一定条件下,会启动零窗口探测定时器,如果发生了超时则发送零窗口探测包,用来检查接收端的窗口大小是否仍为 0 或者是其他一个更新值,反之想如果本端一直没有数据需要发送,那额外关心接收端窗口是否为 0 ,也没啥必要,接收端如果窗口由 0 恢复成一个可用值,自然会发送 Window Update 数据包进行通知。

所以答案也就比较明显了,第一次发送 TCP ZeroWindowProbe 数据包,实际上是发送端有需要发送的数据,但由于接收端零窗口所以发送失败,之后启动零窗口探测定时器,在超时后发送零窗口探测包,这个间隔时间就是应用写入数据的时间再加上零窗口探测定时器第一次超时的时间, 和超时重传定时器一样,零窗口探测定时器也使用 RTO 和指数退避来计算超时时间。

验证时间,首先验证如果发送端一直没有数据发送,是否会存在一个定时器,在超时的时候就发送 TCP ZeroWindowProbe 数据包。

# cat tcp_troubleshooting_7_001.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

+0.01 write(4, ..., 1000) = 1000
+0.01 < . 1:1(0) ack 1001 win 1000

+0.01 write(4, ..., 1000) = 1000
+0.01 < . 1:1(0) ack 2001 win 0

+0 `sleep 30`
#

执行脚本,同时通过 tcpdump 抓取数据包,现象如下,在客户端发送 Win 为 0 的 ACK 数据包后,一直到间隔 30 秒程序结束的时候,服务器端也就是发送端没有任何数据包发送。

# packetdrill tcp_troubleshooting_7_001.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:48:38.138939 tun0  In  IP 192.0.2.1.46799 > 192.168.240.208.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
21:48:38.138957 tun0  Out IP 192.168.240.208.8080 > 192.0.2.1.46799: Flags [S.], seq 1013233344, ack 1, win 64240, options [mss 1460], length 0
21:48:38.149022 tun0  In  IP 192.0.2.1.46799 > 192.168.240.208.8080: Flags [.], ack 1, win 10000, length 0
21:48:38.159099 tun0  Out IP 192.168.240.208.8080 > 192.0.2.1.46799: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP
21:48:38.169120 tun0  In  IP 192.0.2.1.46799 > 192.168.240.208.8080: Flags [.], ack 1001, win 1000, length 0
21:48:38.179200 tun0  Out IP 192.168.240.208.8080 > 192.0.2.1.46799: Flags [P.], seq 1001:2001, ack 1, win 64240, length 1000: HTTP
21:48:38.189235 tun0  In  IP 192.0.2.1.46799 > 192.168.240.208.8080: Flags [.], ack 2001, win 0, length 0
21:49:08.192143 ?     In  IP 192.0.2.1.46799 > 192.168.240.208.8080: Flags [R.], seq 1, ack 2001, win 0, length 0
#

Wireshark 展示如下,发送端并没有发送 TCP ZeroWindowProbe 数据包。

然后验证我们之前的结论,在零窗口数据包间隔 10 ms 之后尝试写入数据进行发送。

# cat tcp_troubleshooting_7_002.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

+0.01 write(4, ..., 1000) = 1000
+0.01 < . 1:1(0) ack 1001 win 1000

+0.01 write(4, ..., 1000) = 1000
+0.01 < . 1:1(0) ack 2001 win 0

+0.01 write(4, ..., 1000) = 1000

+0 `sleep 10`
#

执行脚本,同时通过 tcpdump 抓取数据包,现象如下,在客户端发送 Win 为 0 的 ACK 数据包后,间隔 225ms 后服务器端也就是发送端发送了零窗口探测包,因为没有模拟零窗口探测确认包,所以不断超时不断重传。

# packetdrill tcp_troubleshooting_7_002.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:53:27.538957 tun0  In  IP 192.0.2.1.53331 > 192.168.85.178.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
21:53:27.538984 tun0  Out IP 192.168.85.178.8080 > 192.0.2.1.53331: Flags [S.], seq 2916513971, ack 1, win 64240, options [mss 1460], length 0
21:53:27.549054 tun0  In  IP 192.0.2.1.53331 > 192.168.85.178.8080: Flags [.], ack 1, win 10000, length 0
21:53:27.559250 tun0  Out IP 192.168.85.178.8080 > 192.0.2.1.53331: Flags [P.], seq 1:1001, ack 1, win 64240, length 1000: HTTP
21:53:27.569314 tun0  In  IP 192.0.2.1.53331 > 192.168.85.178.8080: Flags [.], ack 1001, win 1000, length 0
21:53:27.579453 tun0  Out IP 192.168.85.178.8080 > 192.0.2.1.53331: Flags [P.], seq 1001:2001, ack 1, win 64240, length 1000: HTTP
21:53:27.589505 tun0  In  IP 192.0.2.1.53331 > 192.168.85.178.8080: Flags [.], ack 2001, win 0, length 0
21:53:27.814656 tun0  Out IP 192.168.85.178.8080 > 192.0.2.1.53331: Flags [.], ack 1, win 64240, length 0
21:53:28.270658 tun0  Out IP 192.168.85.178.8080 > 192.0.2.1.53331: Flags [.], ack 1, win 64240, length 0
21:53:29.134659 tun0  Out IP 192.168.85.178.8080 > 192.0.2.1.53331: Flags [.], ack 1, win 64240, length 0
21:53:30.862656 tun0  Out IP 192.168.85.178.8080 > 192.0.2.1.53331: Flags [.], ack 1, win 64240, length 0
21:53:34.350653 tun0  Out IP 192.168.85.178.8080 > 192.0.2.1.53331: Flags [.], ack 1, win 64240, length 0
21:53:37.602379 ?     In  IP 192.0.2.1.53331 > 192.168.85.178.8080: Flags [R.], seq 1, ack 2001, win 0, length 0
#

Wireshark 展示如下,No.8 为发送端发送的 TCP 零窗口探测包,间隔时间为 225ms,也就是间隔 10ms 尝试写入数据,再加上发送失败后,所启动的零窗口探测定时器发生超时的 212ms。

零窗口探测数据包,在 Wireshark 分析中显示是 TCP Keep-Alive 数据包的原因,在《TCP Analysis Flags 之 TCP ZeroWindowProbe》有所提及。