人工智能,能治工人
基于 packetdrill TCP 三次握手脚本,通过构造模拟服务器端场景,测试 TCP RTO 定时器相关的一些知识点。
基础脚本
# cat tcp_rto_time_000.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
#
基础测试
可以通过 TCP_INFO 打印 TCP 连接信息,包括 RTT 和 RTO,修改脚本如下。
# cat tcp_rto_time_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 %{print (tcpi_rtt, tcpi_rto)}%
#
运行脚本,可得知 RTT 和 RTO 的值,其中 RTO 为 212ms。
# packetdrill tcp_rto_time_001.pkt
10100 212000
#
RTO 定时器管理
在 TCP 连接的初始阶段,如果没有可用的往返时间(RTT)样本,那么重传超时(RTO)将被设置为一个预定义的初始值,即TCP_TIMEOUT_INIT,默认设置为 1 秒。这意味着,如果在 1 秒内没有收到对方的确认,那么 TCP 将重传数据包。这个初始值是为了在没有任何 RTT 信息的情况下提供一个合理的超时时间。
随着连接的进行,TCP 会根据实际的 RTT 样本动态调整 RTO 的值,以更准确地反映网络的延迟情况。每当一个包含数据的 TCP 数据包发送出去的时候(包括重传),如果之前 RTO 定时器没有运行,则会重启 RTO 定时器,并设置定时时间为 RTO。
针对 TCP 连接数据传输阶段,增加写入数据段发送 TCP 数据包,并在 1 秒之后打印 RTO 值,脚本如下。
# cat tcp_rto_time_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 %{print (tcpi_rtt, tcpi_rto)}%
+0.01 write(4, ..., 100) = 100
+1 %{print (tcpi_rtt, tcpi_rto)}%
+0 `sleep 3`
#
当 TCP 数据包发送出去的时,因为之前 RTO 定时器没有运行,所以会重启 RTO 定时器,并设置 212ms 为 RTO。如果没有得到 ACK 确认的情况下,那么会在 212ms 超时后进行第一次超时重传并重启定时器,此时 RTO 翻倍为 424ms ,当在 424ms 再次超时后进行第二次超时重传并重启定时器,此时 RTO 翻倍为 848ms,之后不断重复。
而脚本中的在发送数据 1 秒之后打印 RTO 值,即在第二次超时重传之后,该值为 848ms,预期一致。
# packetdrill tcp_rto_time_002.pkt
10134 212000
10134 848000
#
# 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
22:08:19.417304 tun0 In IP 192.0.2.1.43063 > 192.168.109.29.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
22:08:19.417345 tun0 Out IP 192.168.109.29.8080 > 192.0.2.1.43063: Flags [S.], seq 932406760, ack 1, win 64240, options [mss 1460], length 0
22:08:19.427441 tun0 In IP 192.0.2.1.43063 > 192.168.109.29.8080: Flags [.], ack 1, win 10000, length 0
22:08:19.437580 tun0 Out IP 192.168.109.29.8080 > 192.0.2.1.43063: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:08:19.653010 tun0 Out IP 192.168.109.29.8080 > 192.0.2.1.43063: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:08:20.093013 tun0 Out IP 192.168.109.29.8080 > 192.0.2.1.43063: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:08:20.957065 tun0 Out IP 192.168.109.29.8080 > 192.0.2.1.43063: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:08:22.684995 tun0 Out IP 192.168.109.29.8080 > 192.0.2.1.43063: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:08:23.465251 tun0 Out IP 192.168.109.29.8080 > 192.0.2.1.43063: Flags [F.], seq 101, ack 1, win 64240, length 0
22:08:23.465271 tun0 In IP 192.0.2.1.43063 > 192.168.109.29.8080: Flags [R.], seq 1, ack 1, win 10000, length 0
#
每当一个包含数据的 TCP 数据包发送出去的时候(包括重传),如果之前 RTO 定时器已经运行了,则并不会重启 RTO 定时器,也就是说如果之前有等待 ACK 的数据包,则并不会重启 RTO 定时器。
举例说的话,如果同时有两个数据包发出,第一个数据包发送时,之前 RTO 定时器没有运行,则会重启 RTO 定时器,并设置定时时间为 RTO,这时再发出第二个数据包时,由于之前已经有 RTO 定时器运行了,则不会再重启 RTO 定时器。此时如果都没有等到 ACK 时,则会先超时重传第一个数据包,第一个数据包重传成功也就是收到 ACK 确认后,则会重新设置 RTO 定时器并重传第二个数据包。
参照上述例子,修改脚本如下。
# cat tcp_rto_time_003.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 %{print (tcpi_rtt, tcpi_rto)}%
+0.01 write(4, ..., 100) = 100
+0.05 write(4, ..., 100) = 100
+1 %{print (tcpi_rtt, tcpi_rto)}%
+0 `sleep 3`
#
可以看到第一个数据包发送时,设置定时时间为 RTO 212ms,而间隔 50ms 后再发出第二个数据包时,由于之前已经有 RTO 定时器运行了,则不会再重启 RTO 定时器。此时如果都没有等到 ACK 时,也就是第一个数据包 RTO 超时后(间隔 212ms+),则会先超时重传第一个数据包,同时 RTO 翻倍,如重传未成功则继续不断超时重传。
# packetdrill tcp_rto_time_003.pkt
10123 212000
10123 848000
#
# 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
22:15:43.976557 tun0 In IP 192.0.2.1.53153 > 192.168.127.205.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
22:15:43.976584 tun0 Out IP 192.168.127.205.8080 > 192.0.2.1.53153: Flags [S.], seq 3079048657, ack 1, win 64240, options [mss 1460], length 0
22:15:43.986665 tun0 In IP 192.0.2.1.53153 > 192.168.127.205.8080: Flags [.], ack 1, win 10000, length 0
22:15:43.996833 tun0 Out IP 192.168.127.205.8080 > 192.0.2.1.53153: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:15:44.046896 tun0 Out IP 192.168.127.205.8080 > 192.0.2.1.53153: Flags [P.], seq 101:201, ack 1, win 64240, length 100: HTTP
22:15:44.212241 tun0 Out IP 192.168.127.205.8080 > 192.0.2.1.53153: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:15:44.660245 tun0 Out IP 192.168.127.205.8080 > 192.0.2.1.53153: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:15:45.524260 tun0 Out IP 192.168.127.205.8080 > 192.0.2.1.53153: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:15:47.252252 tun0 Out IP 192.168.127.205.8080 > 192.0.2.1.53153: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:15:48.076924 ? Out IP 192.168.127.205.8080 > 192.0.2.1.53153: Flags [F.], seq 201, ack 1, win 64240, length 0
22:15:48.076946 ? In IP 192.0.2.1.53153 > 192.168.127.205.8080: Flags [R.], seq 1, ack 1, win 10000, length 0
#
继续修改脚本,增加第一个数据包重传成功的 ACK 确认。
# cat tcp_rto_time_004.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 %{print (tcpi_rtt, tcpi_rto)}%
+0.01 write(4, ..., 100) = 100
+0.05 write(4, ..., 100) = 100
+1 %{print (tcpi_rtt, tcpi_rto)}%
+0 < . 1:1(0) ack 101 win 10000
+0 %{print (tcpi_rtt, tcpi_rto)}%
+0 `sleep 3`
#
可以看到第一个数据包重传成功也就是收到 ACK 确认后,则会重新设置 RTO 定时器并立马重传第二个数据包。
# packetdrill tcp_rto_time_004.pkt
10133 212000
10133 848000
10133 848000
#
# 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
22:31:01.684537 tun0 In IP 192.0.2.1.42029 > 192.168.27.182.8080: Flags [S], seq 0, win 10000, options [mss 1000], length 0
22:31:01.684573 tun0 Out IP 192.168.27.182.8080 > 192.0.2.1.42029: Flags [S.], seq 2095908332, ack 1, win 64240, options [mss 1460], length 0
22:31:01.694666 tun0 In IP 192.0.2.1.42029 > 192.168.27.182.8080: Flags [.], ack 1, win 10000, length 0
22:31:01.704778 tun0 Out IP 192.168.27.182.8080 > 192.0.2.1.42029: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:31:01.754844 tun0 Out IP 192.168.27.182.8080 > 192.0.2.1.42029: Flags [P.], seq 101:201, ack 1, win 64240, length 100: HTTP
22:31:01.920234 tun0 Out IP 192.168.27.182.8080 > 192.0.2.1.42029: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:31:02.356353 tun0 Out IP 192.168.27.182.8080 > 192.0.2.1.42029: Flags [P.], seq 1:101, ack 1, win 64240, length 100: HTTP
22:31:02.754911 tun0 In IP 192.0.2.1.42029 > 192.168.27.182.8080: Flags [.], ack 101, win 10000, length 0
22:31:02.754940 tun0 Out IP 192.168.27.182.8080 > 192.0.2.1.42029: Flags [P.], seq 101:201, ack 1, win 64240, length 100: HTTP
22:31:03.604240 tun0 Out IP 192.168.27.182.8080 > 192.0.2.1.42029: Flags [P.], seq 101:201, ack 1, win 64240, length 100: HTTP
22:31:05.332254 tun0 Out IP 192.168.27.182.8080 > 192.0.2.1.42029: Flags [P.], seq 101:201, ack 1, win 64240, length 100: HTTP
22:31:05.782674 ? Out IP 192.168.27.182.8080 > 192.0.2.1.42029: Flags [F.], seq 201, ack 1, win 64240, length 0
22:31:05.782709 ? In IP 192.0.2.1.42029 > 192.168.27.182.8080: Flags [R.], seq 1, ack 101, win 10000, length 0
#
此时需要注意的是,如果第二个数据包开始第一次重传后,如果重传仍未成功,它又会在多长时间后进行第二次超时重传,可以看到上面例子中,间隔时间仍是 848ms+。
实际在 RTO 超时重传成功后,也就是收到 ACK 确认之后,之后再发送的数据 RTO 会有两种情况:
- 如果重传成功后,没有采集到新的 RTT,那么仍会使用当前的 RTO 作为超时时间;
- 如果重传成功后,采集到新的 RTT,那么则使用新计算出来的 RTO 作为超时时间。
对于第一种情况,也就是上面的示例,第一个数据包重传成功后,并没有认为是采集到了新的 RTT(因为不能依据这个 TCP 数据包的 ACK 信息来更新 RTT 值),而当时 RTO 已经翻倍到了 848ms,因此在进行第二个数据包的重传时,设置的超时时间仍为 848ms。
对于第二种情况,可以增加一次新的数据交互,从而采集到新的 RTT,修改脚本如下。
# cat tcp_rto_time_005.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 %{print (tcpi_rtt, tcpi_rto)}%
+0.01 write(4, ..., 100) = 100
+1 %{print (tcpi_rtt, tcpi_rto)}%
+0 < . 1:1(0) ack 101 win 10000
+0.01 write(4, ..., 100) = 100
+0.01 < . 1:1(0) ack 201 win 10000
+0.01 write(4, ..., 100) = 100
+0 %{print (tcpi_rtt, tcpi_rto)}%
+0 `sleep 3`
#
可以看到第一个数据包重传成功时,当时的 RTO 为 848ms,之后再写入了一个数据段,并且成功得到 ACK 响应,这时采集到了新的 RTT,则计算出来新的 RTO 时间。
再次写入一个数据段,在未得到 ACK 确认后,间隔 212ms 后进行了第一次超时重传,这个 212ms 就是采集到新的 RTT 重新计算出来的新 RTO 时间。
# cat tcp_rto_time_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 %{print (tcpi_rtt, tcpi_rto)}%
#
0
往期推荐
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...