Why

为什么要终结一个已经存在的 tcp 连接?

  1. 强制实施新策略,如安全组、多活规则等。传统的防火墙基于 conntrack 模块(有状态),对于已经存在的连接一般不会实施新策略,比如我们想要即刻关掉某端口的对外开放,新策略只会对新连接生效,老连接则会忽略新策略,与我们预期不符。该场景下,我们可以人为终结不符合预期的连接来落实新策略
  2. 负载均衡,尤其是长连接。某些场景下,负载不均衡了,我们可以通过终结某些连接,让 client 重新发起请求,给 server一次重新负载均衡的机会。

How

你可能会说了,我直接代码内 close,四步挥手不就终结 tcp 连接了吗?是,这是 tcp 终结的正常渠道,但是一般应用不会自己去实现这个接口,更别提把这个接口暴露出来。我们的方案最好是让应用无感知(就不需要代码改造),才能多快好省地落地。所以可以依靠另一个 tcp 终结的渠道:Reset报文。通过在旁路,发送 Reset 报文,实现应用无感知的 tcp 连接终结。具体实现方式有以下几种:

tcpkill

Reset报文也不是随便发发就能终结 tcp 连接的,需要落在接收端接受窗口内,接收端才会接受这个 Reset 报文并终结自己这个连接1

tcpkill 通过 libpcap2 嗅探到我们关注(通过 tcpdump 表达式体现)的连接的报文,提取 sequence 并构造 Reset 报文发送出去3,具体代码4概要如下:

    // 构造报文
    libnet_build_ip(TCP_H, 0, 0, 0, 64, IPPROTO_TCP,
            ip->ip_dst.s_addr, ip->ip_src.s_addr,
            NULL, 0, buf);
    libnet_build_tcp(ntohs(tcp->th_dport), ntohs(tcp->th_sport),
             0, 0, TH_RST, 0, 0, NULL, 0, buf + IP_H);

    // 获取对方需要的序号和窗口大小
    seq = ntohl(tcp->th_ack);
    win = ntohs(tcp->th_win);
    
    ip = (struct libnet_ip_hdr *)buf;
    tcp = (struct libnet_tcp_hdr *)(ip + 1);
    
    for (i = 0; i < Opt_severity; i++) {
        ip->ip_id = libnet_get_prand(PRu16);
        seq += (i * win);
        tcp->th_seq = htonl(seq);
        
        libnet_do_checksum(buf, IPPROTO_TCP, TCP_H);
        
        if (libnet_write_ip(*sock, buf, sizeof(buf)) < 0)
            warn("write_ip");
        
        fprintf(stderr, "%s R %lu:%lu(0) win 0\n", ctext, seq, seq);
    }

这里的 Opt_severity 设置是因为有些连接很快速,我们嗅探到报文并构造 Reset 报文的时候,原连接可能已经有好几次报文交互了,所以可以通过构造后续几个窗口的报文,来确保连接有更大的概率能被终结。

具体案例:server 监听 9090 端口,client 连接之并有报文交互,tcpkill 双向终结连接

tcpkill 通过嗅探报文来获得合法的 sequence,那么如果这个连接不活跃,嗅探不到报文怎么办?且看下文。

killcx/hping3

从 RFC5961 4.2 节5可以推出,当一个连接处于 established 状态的时候,若收到 syn 包则会返回一个包含本端预期 sequence 的所谓的 “challenge ack” 包(可以和 Linux 源码互相印证6789),有了这个合法 sequence,就可以发送 Reset 报文终结连接了。killcx10和 hping311 都可以做到这件事。
以 hping3 为例:server 监听 9099 端口,client 连接之,但无报文交互,hping3 终结 client 连接(非双向终结)

ebpf

无论是 tcpkill 还是 killcx/hping3,可编程性都比较弱,并且不是内核原生支持,限制较多。其实我们完全可以用 ebpf 来做这个事,只是对内核版本有一定要求,篇幅有限,这块我们放到下一篇文章讲。

引用


MageekChiu
4.4k 声望1.7k 粉丝

T