ESTABLISHED状态的连接收到 SYN 会回复什么?

通过阅读这篇文章 , 你会了解到这些知识

  • ESTABLISHED 状态的连接收到乱序包会回复什么
  • Challenge ACK 的概念
  • ACK 报文限速是什么鬼
  • SystemTap 工具在 linux 内核追踪中的使用
  • 包注入神器 scapy 的使用
  • RST 攻击的原理
  • killcx 等工具利用 RST 攻击的方式来杀掉连接的原理
接下来开始文章的内容 。
scapy 实验复现现象实验步骤如下:
在机器 A(10.211.55.10) 使用 nc 启动一个服务程序 , 监听 9090 端口 , 如下所示 。
nc -4 -l 9090机器 A 上同步使用 tcpdump 抓包 , 其中 -S 表示显示绝对序列号 。
sudo tcpdump -i any port 9090 -nn-S在机器 B 使用 nc 命令连接机器 A 的 nc 服务器 , 输入 "hello"。
nc 10.211.55.10 9090使用 netstat 可以看到此次连接的信息 。
Proto Recv-Q Send-Q Local AddressForeign AddressStatePID/Program nametcp00 10.211.55.10:909010.211.55.20:50718ESTABLISHED 9029/nc在机器 B 上使用 scapy , 模拟发送 SYN 包 , scapy 脚本如下所示 。
send(IP(dst="10.211.55.10")/TCP(sport=50718, dport=9090, seq=10, flags='S'))源端口号 sport 使用此次连接的临时端口号 50718 , 序列号随便写一个 , 这里 seq 为 10 。
执行 scapy 执行上面的代码 , tcpdump 中显示的包结果如下 。
// nc 终端中 hello 请求包18:41:51.956735 IP 10.211.55.20.50718 > 10.211.55.10.9090: Flags [P.], seq 3219267420:3219267426, ack 2848436085, win 229, options [nop,nop,TS val 1094540820 ecr 12823113], length 618:41:51.956787 IP 10.211.55.10.9090 > 10.211.55.20.50718: Flags [.], ack 3219267426, win 227, options [nop,nop,TS val 12827910 ecr 1094540820], length 0// scapy 的 SYN 包18:44:32.373331 IP 10.211.55.20.50718 > 10.211.55.10.9090: Flags [S], seq 10, win 8192, length 018:44:32.373366 IP 10.211.55.10.9090 > 10.211.55.20.50718: Flags [.], ack 3219267426, win 227, options [nop,nop,TS val 12988327 ecr 1094540820], length 0可以看到 , 对于一个 SEQ 为随意的 SYN 包 , TCP 回复了正确的 ACK 包 , 其确认号为 3219267426 。
从 rfc793 文档中也可以看到:
Linux 内核对于收到的乱序 SYN 报文 , 会回复一个携带了正确序列号和确认号的 ACK 报文 。
这个 ACK 被称之为 Challenge ACK 。
我们后面要介绍的杀掉连接工具 killcx 的原理 , 正是是基于这一点 。
原因分析为了方便说明 , 我们记发送 SYN 报文的一端为 A , 处于 ESTABLISHED 状态接收 SYN 报文的一端为 B , B 对收到的 SYN 包回复 ACK 的原因是想让对端 A 确认之前的连接是否已经失效 , 以便做出一些处理 。
对于 A 而已 , 如果之前的连接还在 , 对于收到的 ACK 包 , 正常处理即可 , 不再讨论 。
如果 A 之前的此条连接已经不在了 , 此次 SYN 包是想发起新的连接 , 对于收到的 ACK 包 , 会立即回复一个 RST , 且 RST 包的序列号就等于 ACK 包的序列号 , B 收到这个合法的 RST 包以后 , 就会将连接释放 。 A 此时若想继续与 B 创建连接 , 则可以选择再次发送 SYN 包 , 重新建连 , 如下图所示 。
ESTABLISHED状态的连接收到 SYN 会回复什么?文章插图
接下来我们来看内核源码的处理 ,
内核源码分析在这之前 , 我们需要先了解 SystemTap 工具的使用 。 SystemTap 是 Linux 中非常强大的调试探针工具 , 类似于 java 中的 javaagent instrument , 可以获取一个内核函数运行时的入参变量、返回值、调用堆栈 , 甚至可以直接修改变量的值 。 这个工具详细的使用这里不展开 , 感兴趣的同学可以自行 Google 。
接下来我们来使用 SystemTap 这个工具来给内核插入 probe 探针 , 以 3.10.0 内核为例 , 内核中回复的 ack 的函数在 net/ipv4/tcp_output.c 的 tcp_send_ack 中实现 。 我们给这个函数插入调用探针 , 在端口号为 9090 时打印调用堆栈 。 新建一个 ack_test.stp 文件 , 部分代码如下所示 。
%{#include #include #include #include %}function tcp_src_port:long(sk:long){ return __tcp_sock_sport(sk)}function tcp_dst_port:long(sk:long){ return __tcp_sock_dport(sk)}function tcp_src_addr:long(sk:long){ return ntohl(__ip_sock_saddr(sk))}function tcp_dst_addr:long(sk:long){ return ntohl(__ip_sock_daddr(sk))}function str_addr:string(addr, port) {return sprintf("%d.%d.%d.%d:%d",(addrsrc_port = tcp_src_port($sk);dst_addr = tcp_dst_addr($sk);dst_port = tcp_dst_port($sk);if (dst_port == 9090 || src_port == 9090){printf("send ack : %s:->%s\n",str_addr(src_addr, src_port),str_addr(dst_addr, dst_port));print_backtrace();}}使用 stap 命令执行上面的脚本
sudo stap -g ack_test.stp