背景

在实际生产环境中,SYN Flood 攻击曾多次出现。虽然之前已解决过相关问题,但一直未进行系统整理。直到上周五该问题再次发生,利用周末时间查阅资料深入研究后,特整理此文。

养成撰写博客或整理文档的习惯至关重要。将问题转化为文章的过程,迫使自身先理解透彻、理清思路。这往往需要花费大量时间查阅资料,不仅是对自己和读者负责,也是技术能力提升的重要途径。

什么是 SYN Flood

SYN Flood 是当前最流行的 DoS(拒绝服务攻击)与 DDoS(分布式拒绝服务攻击)方式之一。它利用 TCP 协议缺陷,发送大量伪造的 TCP 连接请求,塞满 TCP 等待连接队列,导致资源耗尽(CPU 满负荷或内存不足),使正常业务请求无法建立连接,从而达到攻击目的。

谈及 TCP 协议,必然涉及三次握手。SYN Flood 攻击正是利用 IPv4 中 TCP 协议的三次握手过程进行的:

  1. 发起方发送 TCP SYN 包到对方。
  2. 对方收到后发送 TCP SYN+ACK 包回来。
  3. 发起方再发送 TCP ACK 包回去。

至此,三次握手结束。我们将 TCP 连接的发起方称为 TCP 客户机(TCP Client),接收方称为 TCP 服务器(TCP Server)

值得注意的是,当 TCP 服务器收到 TCP SYN 请求包时,在发送 TCP SYN+ACK 包回 TCP 客户机前,服务器需先分配一个数据区专门服务于这个即将形成的 TCP 连接。一般把收到 SYN 包而还未收到 ACK 包时的连接状态称为 半开连接(Half-open Connection)

在最常见的 SYN Flood 攻击中,攻击者(TCP 客户机)在短时间内发送大量的 TCP SYN 包给受害者(TCP 服务器)。根据上述机制,受害者会为每个 TCP SYN 包分配一个特定的数据区。只要这些 SYN 包具有不同的源地址(这一点对于攻击者来说很容易伪造),这将给 TCP 服务器系统造成很大的负担,最终导致系统不能正常工作。

防御机制:SYN Cookies

那如何防御呢?

通常我们会打开 TCP Cookie 功能。这里的 Cookie 指的是 SYN Cookie。在目前以 IPv4 为支撑的网络协议环境中,SYN Flood 是一种危险且常见的 DoS 攻击方式。能够有效防范 SYN Flood 攻击的手段并不多,而 SYN Cookie 是其中最著名的一种。该原理由 D.J. Bernstein 和 Eric Schenk 发明,包括 Linux 在内的很多操作系统上都有各种实现。

查阅 kernel documentation,里面有关于 syn_cookies 的官方介绍:

Note, that syncookies is fallback facility.    
It MUST NOT be used to help highly loaded servers to stand    
against legal connection rate. If you see SYN flood warnings    
in your logs, but investigation shows that they occur    
because of overload with legal connections, you should tune    
another parameters until this warning disappear.    
See: tcp_max_syn_backlog, tcp_synack_retries, tcp_abort_on_overflow.    

syncookies seriously violate TCP protocol, do not allow    
to use TCP extensions, can result in serious degradation    
of some services (f.e. SMTP relaying), visible not by you,    
but your clients and relays, contacting you. While you see    
SYN flood warnings in logs not being really flooded, your server    
is seriously misconfigured.

官方明确说明:当看到有 SYN flood warning 的时候,并不一定真的是被 Flood 攻击了,有可能是服务器没有正确配置。 同理,有时服务器出现此报警也并不一定是真的有攻击,这时就需要调整内核参数。

下图展示了通过 dmesg 或系统 syslog 查看到的有关 SYN flooding 的报错信息:

image.png

内核参数调优

官方文档中提到,当出现此问题的时候,可以调整内核的三个参数:tcp_max_syn_backlogtcp_synack_retriestcp_abort_on_overflow

  • tcp_max_syn_backlog
    该变量告诉你在内存中可以缓存多少个 SYN 请求。该变量需要打开 tcp_syncookies 才有效。如果服务器负载很高,可以尝试提高该变量的值。
  • tcp_synack_retries
    该变量用于 TCP 三次握手机制中第二次握手。当收到客户端发来的 SYN 连接请求后,服务端将回复 SYN+ACK 包,这时服务端处于 SYN_RCVD 状态,并等待客户端发来的回复 ACK 包。如果服务端没有收到客户端的 ACK 包,会重新发送 SYN+ACK 包,直到收到客户端的 ACK 包。该变量设置发送 SYN+ACK 包的次数,超过这个次数,服务端将放弃连接。默认值是 5。
  • tcp_abort_on_overflow
    该变量的值是个布尔值,默认值为 0(FALSE 关闭)。如果开启,当服务端接收新连接的速度变慢时,服务端会发送 RST 包(reset 包)给客户端,令客户端重新连接。这意味着如果突然发生溢出,将重获连接。仅当你真的确定不能通过调整监听进程使接收连接的速度变快,可以启用该选项。该选项会影响到客户的连接。

实际解决方案

实际测试发现,只更改上述几个参数有时是不管用的。目前的解决办法是调整以下几个内核参数,实践证明调整之后暂时未再复现问题。而官方文档中提到的 retryoverflow 的两个参数,建议还是不要调整,用默认的就好,以免产生副作用。

修改 /etc/sysctl.conf 文件:

# vim /etc/sysctl.conf 
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_max_syn_backlog = 2048

保存退出后,执行以下命令使配置生效:

# sysctl -p

参数说明:

  • net.ipv4.tcp_syncookies = 1
    表示开启 SYN Cookies。当出现 SYN 等待队列溢出时,启用 cookies 来处理,可防范少量 SYN 攻击。默认为 0,表示关闭。
  • net.ipv4.tcp_tw_reuse = 1
    表示开启重用。允许将 TIME-WAIT sockets 重新用于新的 TCP 连接。默认为 0,表示关闭;为 1 表示开启。
  • net.ipv4.tcp_tw_recycle = 1
    表示开启 TCP 连接中 TIME-WAIT sockets 的快速回收。默认为 0,表示关闭;为 1 表示开启。
  • net.ipv4.tcp_fin_timeout
    修改系统默认的 TIMEOUT 时间,这里根据服务器的实际情况设置

日志信息分析

另外细心的朋友可能发现了,报错信息:Possible SYN flooding on port 13370. Sending cookies. 后面跟了句 Check SNMP counters

这句当时差点被误导,因为服务器上正好跑了一个 SNMP 抓流量的服务,开始以为是它导致的。后来一想那是 UDP 协议,和 TCP 没关系。查阅 kernel 代码 发现,原来那是 print 打印的固定 info 输出:

static bool tcp_syn_flood_action(const struct sock *sk,
                const struct sk_buff *skb,
                const char *proto)
{
    struct request_sock_queue *queue = &inet_csk(sk)->icsk_accept_queue;
    const char *msg = "Dropping request";
    bool want_cookie = false;
    struct net *net = sock_net(sk);
#ifdef CONFIG_SYN_COOKIES
    if (net->ipv4.sysctl_tcp_syncookies) {
        msg = "Sending cookies";
        want_cookie = true;
        __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPREQQFULLDOCOOKIES);
    } else
#endif
        __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPREQQFULLDROP);
    if (!queue->synflood_warned &&
    net->ipv4.sysctl_tcp_syncookies != 2 &&
    xchg(&queue->synflood_warned, 1) == 0)
        pr_info("%s: Possible SYN flooding on port %d. %s.  Check SNMP counters.\n",
            proto, ntohs(tcp_hdr(skb)->dest), msg);
    return want_cookie;
}

参考资料


说明: 文中提到的 net.ipv4.tcp_tw_recycle 参数在 Linux Kernel 4.12 及更高版本中已被移除,不再支持。在新版内核系统中配置该参数将无效,请根据实际操作系统版本进行调整。