想解决问题,关键在于识别和定义问题
首先强调,以下所说的快速重传指的是基础快速重传,也就是常说的经由三个Dup ACK 所触发出来的快速重传。
在某些场景下,对于哪些 ACK 判定是 Dup ACK,Wireshark 和 Linux 两者之间会有所差异,以下用实验来具体说明。
第一个实验,在 TCP 三次握手完成之后,首先客户端发送了 100 字节数据,服务器端正常接收并响应 ACK,此后服务器端连续写入 4 个 100 字节大小的数据段进行发送,模拟第一个数据段丢失,因此客户端只响应 3 个 ACK。
# cat tcp_dupack_013.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.01 < P. 1:101(100) ack 1 win 10000
+0 > . 1:1(0) ack 101
+0 write(4, ..., 100) = 100
+0 write(4, ..., 100) = 100
+0 write(4, ..., 100) = 100
+0 write(4, ..., 100) = 100
+0.01 < . 101:101(0) ack 1 win 10000
+0.01 < . 101:101(0) ack 1 win 10000
+0.01 < . 101:101(0) ack 1 win 10000
+0 `sleep 1`
#
通过 tcpdump 抓包如下,可以发现在客户端 3 个 ACK 之后,服务器进行了超时重传 Seq 1:101 数据包。
# packetdrill tcp_dupack_013.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:11:24.927080 tun0 In IP 192.0.2.1.43547 > 192.168.228.10.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
21:11:24.927190 tun0 Out IP 192.168.228.10.8080 > 192.0.2.1.43547: Flags [S.], seq 2470878422, ack 1, win 64240, options [mss 1460], length 0
21:11:24.937372 tun0 In IP 192.0.2.1.43547 > 192.168.228.10.8080: Flags [.], ack 1, win 10000, length 0
21:11:24.947439 tun0 In IP 192.0.2.1.43547 > 192.168.228.10.8080: Flags [P.], seq 1:101, ack 1, win 10000, length 100: HTTP
21:11:24.947463 tun0 Out IP 192.168.228.10.8080 > 192.0.2.1.43547: Flags [.], ack 101, win 64140, length 0
21:11:24.947541 tun0 Out IP 192.168.228.10.8080 > 192.0.2.1.43547: Flags [P.], seq 1:101, ack 101, win 64140, length 100: HTTP
21:11:24.947548 tun0 Out IP 192.168.228.10.8080 > 192.0.2.1.43547: Flags [P.], seq 101:201, ack 101, win 64140, length 100: HTTP
21:11:24.947553 tun0 Out IP 192.168.228.10.8080 > 192.0.2.1.43547: Flags [P.], seq 201:301, ack 101, win 64140, length 100: HTTP
21:11:24.947557 tun0 Out IP 192.168.228.10.8080 > 192.0.2.1.43547: Flags [P.], seq 301:401, ack 101, win 64140, length 100: HTTP
21:11:24.957650 tun0 In IP 192.0.2.1.43547 > 192.168.228.10.8080: Flags [.], ack 1, win 10000, length 0
21:11:24.967571 tun0 In IP 192.0.2.1.43547 > 192.168.228.10.8080: Flags [.], ack 1, win 10000, length 0
21:11:24.977576 tun0 In IP 192.0.2.1.43547 > 192.168.228.10.8080: Flags [.], ack 1, win 10000, length 0
21:11:25.162659 tun0 Out IP 192.168.228.10.8080 > 192.0.2.1.43547: Flags [P.], seq 1:101, ack 101, win 64140, length 100: HTTP
21:11:25.614668 tun0 Out IP 192.168.228.10.8080 > 192.0.2.1.43547: Flags [P.], seq 1:101, ack 101, win 64140, length 100: HTTP
21:11:25.998908 ? Out IP 192.168.228.10.8080 > 192.0.2.1.43547: Flags [R.], seq 401, ack 101, win 64140, length 0
21:11:25.998935 ? In IP 192.0.2.1.43547 > 192.168.228.10.8080: Flags [R.], seq 101, ack 1, win 10000, length 0
#
通过 Wireshark 展示如下,就会发现一个问题,客户端响应的 3 个 ACK,也就是 No.10-12,都会被 Wireshark 认定为 Dup ACK,也就是 No.3 ACK 的重复 ACK,因此理论上这 3 个 Dup ACK 会触发服务器端进行快速重传,但从 No.13 数据包来看,并不是快速重传数据包,而是一个超时重传数据包,也就是原始数据包 No.6 的超时重传,间隔 RTO 212ms+。
这也就是本篇文章想说的问题,Wireshark 和 Linux 之 Dup ACK 判断差异,Wireshark 认定是 3 个 Dup ACK,而 Linux 认定不是 3 个 Dup ACK,而只是 2 个 Dup ACK,因此未能正常触发出快速重传。
# cat tcp_dupack_014.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.01 < P. 1:101(100) ack 1 win 10000
+0 > . 1:1(0) ack 101
+0 write(4, ..., 100) = 100
+0 write(4, ..., 100) = 100
+0 write(4, ..., 100) = 100
+0 write(4, ..., 100) = 100
+0 write(4, ..., 100) = 100
+0.01 < . 101:101(0) ack 1 win 10000
+0.01 < . 101:101(0) ack 1 win 10000
+0.01 < . 101:101(0) ack 1 win 10000
+0.01 < . 101:101(0) ack 1 win 10000
+0 `sleep 1`
#
# packetdrill tcp_dupack_014.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:25:50.306949 tun0 In IP 192.0.2.1.44651 > 192.168.12.27.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
21:25:50.306973 tun0 Out IP 192.168.12.27.8080 > 192.0.2.1.44651: Flags [S.], seq 2983522746, ack 1, win 64240, options [mss 1460], length 0
21:25:50.317128 tun0 In IP 192.0.2.1.44651 > 192.168.12.27.8080: Flags [.], ack 1, win 10000, length 0
21:25:50.327358 tun0 In IP 192.0.2.1.44651 > 192.168.12.27.8080: Flags [P.], seq 1:101, ack 1, win 10000, length 100: HTTP
21:25:50.327377 tun0 Out IP 192.168.12.27.8080 > 192.0.2.1.44651: Flags [.], ack 101, win 64140, length 0
21:25:50.327488 tun0 Out IP 192.168.12.27.8080 > 192.0.2.1.44651: Flags [P.], seq 1:101, ack 101, win 64140, length 100: HTTP
21:25:50.327497 tun0 Out IP 192.168.12.27.8080 > 192.0.2.1.44651: Flags [P.], seq 101:201, ack 101, win 64140, length 100: HTTP
21:25:50.327505 tun0 Out IP 192.168.12.27.8080 > 192.0.2.1.44651: Flags [P.], seq 201:301, ack 101, win 64140, length 100: HTTP
21:25:50.327510 tun0 Out IP 192.168.12.27.8080 > 192.0.2.1.44651: Flags [P.], seq 301:401, ack 101, win 64140, length 100: HTTP
21:25:50.327514 tun0 Out IP 192.168.12.27.8080 > 192.0.2.1.44651: Flags [P.], seq 401:501, ack 101, win 64140, length 100: HTTP
21:25:50.337572 tun0 In IP 192.0.2.1.44651 > 192.168.12.27.8080: Flags [.], ack 1, win 10000, length 0
21:25:50.347528 tun0 In IP 192.0.2.1.44651 > 192.168.12.27.8080: Flags [.], ack 1, win 10000, length 0
21:25:50.357529 tun0 In IP 192.0.2.1.44651 > 192.168.12.27.8080: Flags [.], ack 1, win 10000, length 0
21:25:50.367531 tun0 In IP 192.0.2.1.44651 > 192.168.12.27.8080: Flags [.], ack 1, win 10000, length 0
21:25:50.367550 tun0 Out IP 192.168.12.27.8080 > 192.0.2.1.44651: Flags [P.], seq 1:101, ack 101, win 64140, length 100: HTTP
21:25:50.582659 tun0 Out IP 192.168.12.27.8080 > 192.0.2.1.44651: Flags [P.], seq 1:101, ack 101, win 64140, length 100: HTTP
21:25:51.022666 tun0 Out IP 192.168.12.27.8080 > 192.0.2.1.44651: Flags [P.], seq 1:101, ack 101, win 64140, length 100: HTTP
21:25:51.382571 ? Out IP 192.168.12.27.8080 > 192.0.2.1.44651: Flags [R.], seq 501, ack 101, win 64140, length 0
21:25:51.382604 ? In IP 192.0.2.1.44651 > 192.168.12.27.8080: Flags [R.], seq 101, ack 1, win 10000, length 0
#
为什么 Linux 对于 Dup ACK 的判定会不一样,因此实际上可以观察 No.3 和 No.11 的差异,两者的 ACK Num 虽然都是一样,但是 Seq 却不一样,Seq 1 和 Seq 101 的区别,也就是这个地方使得 Linux 认定 No.11 是一个 Window Update 数据包。
需要注意的是,这个 Window Update 数据包,并不是指的接收窗口更新数据包,而是发送窗口更新数据包。相关发送窗口更新的处理函数为 tcp_ack_update_window -> tcp_may_update_window ,如下。
其中 after(ack_seq, tp->snd_wl1) 满足条件,也就是 Seq 比之前接收到的最大 Seq 还要大,判定为 FLAG_WIN_UPDATE,而非 Dup ACK 数据包。
/* Update our send window.
*
* Window update algorithm, described in RFC793/RFC1122 (used in linux-2.2
* and in FreeBSD. NetBSD's one is even worse.) is wrong.
*/
staticinttcp_ack_update_window(struct sock *sk, conststruct sk_buff *skb, u32 ack,
u32 ack_seq)
{
struct tcp_sock *tp = tcp_sk(sk);
int flag = 0;
u32 nwin = ntohs(tcp_hdr(skb)->window);
if (likely(!tcp_hdr(skb)->syn))
nwin <<= tp->rx_opt.snd_wscale;
if (tcp_may_update_window(tp, ack, ack_seq, nwin)) {
flag |= FLAG_WIN_UPDATE;
tcp_update_wl(tp, ack_seq);
...
}
/* Check that window update is acceptable.
* The function assumes that snd_una<=ack<=snd_next.
*/
staticinlinebooltcp_may_update_window(conststruct tcp_sock *tp,
const u32 ack, const u32 ack_seq,
const u32 nwin)
{
return after(ack, tp->snd_una) ||
after(ack_seq, tp->snd_wl1) ||
(ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd);
}
往期推荐
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...