ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

iptables 防 SYN Flood 实战:从三次握手原理到生产环境部署

iptables 防 SYN Flood 实战:从三次握手原理到生产环境部署 服务器突然连不上SSH 卡在Trying ...半天没反应第一反应是宽带抽风第二反应就是中招了。打开终端去看请求能通但就是握手握不上——这种症状十有八九是 SYN flood。我在业务系统上被这么打过几次之后才真正意识到 iptables 不只是配几个端口放行那么简单它还是 Linux 下应对 DoS 攻击的第一道廉价防线。本文用一套完整可复用的思路把用 iptables 防 SYN flood 这件事讲透包括三层握手为什么会被打垮、确认攻击的观察手段、规则怎么设计、参数怎么计算以及生产环境下的边界在哪里。写给那些希望用纯软件手段在系统层面缓解 DoS 压力、又不打算马上上硬件防火墙的运维、开发和个人站长。1. 先弄清楚SYN flood 到底是怎么把机器“堵死”的1.1 三次握手背后的半连接队列要理解 SYN flood先看一次正常的 TCP 握手。客户端发SYN服务端内核收到后在内存的 syn queue半连接队列里记录一个“待完成”的条目同时回SYNACK客户端再回ACK服务端把记录从半连接队列移到 accept queue连接建立。半连接队列只是一个中间状态正常情况下里面的条目存在几毫秒到几十毫秒就换出去了。问题就出在这个短暂的等待窗口。攻击者伪造海量SYN包发过来服务端一一回应SYNACK但因为伪造源地址根本不存在那些SYNACK永远等不到对应的ACK。于是半连接队列里的条目越积越多直到填满内核参数tcp_max_syn_backlog规定的上限。队列满之后内核没有空间记录新的握手状态后面真正的用户请求哪怕只晚到了零点几秒同样进不来。用一个生活类比餐厅门口只有一个接待通道正常情况下一组顾客登记完就进去通道腾出来。SYN flood 相当于一大群根本不打算进店的“幽灵顾客”把通道堵得严严实实后面真实排队的客人永远到不了前台。1.2 确认被攻击的几个关键信号开始配置防护之前先要确认当前机器到底是不是正在被 SYN flood 打。不要靠猜用下面这几条命令看实际数据。第一条统计当前处于SYN_RECV状态的连接数量ss -nt state syn-recv | wc -lSYN_RECV是服务端发出SYNACK后等待对方ACK的状态。正常业务的服务器这个数字个位数到两位数都很常见如果持续几百上千并且一直不掉基本可以确认握手队列异常。第二条看内核协议栈统计信息netstat -s | grep -i SYN关注SYNs to LISTEN sockets dropped这一项。攻击过程中这个数字会持续快速增长。如果一分钟内增加几千上万说明内核已经在拼命丢弃来不及处理的 SYN 包。第三条看系统日志dmesg -T | tail -50当半连接队列溢出时内核会打印类似TCP: request_sock_TCP: Possible SYN flooding on port 80的告警。看到这条基本就是实锤了。把这些观测手段写进运维笔记里很重要。我在实际处理中见过不少同事上来就配 iptables规则敲了一大堆但连攻击特征都没确认最后也无法验证规则是否真的生效。判断攻击是第一步验证防护效果则是闭环的最后一步两者同样关键。2. iptables 防护三板斧连接状态、限速、按 IP 记账2.1 用连接状态给合法流量开后门iptables 本身是无状态的每条规则单独匹配数据包。但它可以配合内核的 conntrack连接跟踪模块让规则“看懂”ESTABLISHED和RELATED状态。这是所有 iptables 防护配置的第一步也是最重要的前提。最开始的规则长这样iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT含义是凡是已经建立连接的回包ESTABLISHED或者与现有连接相关的辅助连接RELATED典型如 FTP 数据通道全部直接放行。这样后续任何针对新连接的限制规则都只影响“从外面发起的新连接”而不是把正在通信的会话拦腰截断。这个设计逻辑值得多说一句。SYN flood 攻击的入口是“新连接建立过程”所以我们的所有拦截手段都针对新 SYN 包但服务器上的正常出站响应会被误伤所以先把状态放行的规则放在最前面让 iptables 在匹配到后续 SYN 限制规则之前就直接放行已有连接。规则顺序在 iptables 里是严格从上到下执行的一旦ACCEPT命中后续DROP就轮不到了这个顺序必须守死。另外要明确一个原则SYN flood 场景下丢包用DROP不用REJECT。REJECT会回一个RST或ICMP不可达等于告诉攻击方“这个 IP 是活的而且你打到我了”攻击者反而可以据此调整策略DROP则是不做任何反应攻击方的 SYN 请求石沉大海对攻击者来说最烦的就是没有反馈。2.2 用 limit 模块做全局限速确认攻击手法之后最直接有效的一招是限制单位时间内进入规则链的 SYN 包数量。经典规则iptables -A INPUT -p tcp --syn -m limit --limit 30/s --limit-burst 60 -j ACCEPT iptables -A INPUT -p tcp --syn -j DROP第一句话的意思是每秒最多放 30 个 SYN 包进入后面的处理流程但允许短时间内突破到每秒的突发量对应--limit-burst 60。等同于一个令牌桶桶初始容量是 60 个令牌每个匹配的 SYN 包消耗一个桶空之前可以按突发放行之后按每秒 30 个的速度重新填充令牌。第二句话是兜底规则没被限速规则命中的 SYN 包直接丢。两条规则一加起来的效果就是——每秒超过 30 的新连接 SYN 请求进入处理流程其余全部丢弃。30/s这个数字不是拍脑袋定的需要结合业务正常流量来算。假设你的网站高峰期 QPS 约 20正常情况下每个请求建立一条连接绝大多数场景下SYN/s也就 10~20 左右。设定 30/s 留了余量既能保障正常用户又能在攻击发生时瞬间丢弃大部分恶意包。如果你运维的是对连接量级更敏感的中间件比如负载均衡、数据库可以把参数按真实基线再调。一个经验是宁可先把limit调高也不要一上来调得很低。我早期就吃过亏把--limit设成了5/s结果高峰期正常的端口扫描加用户连接直接把真实流量也挡了用户反馈“网站时好时坏”。先观察、后收紧再配合计数器验证这样才能找到业务能忍受的下限。2.3 用 recent 模块按来源 IP 精确记账limit模块是全局限速对攻击者分散在大量源 IP 时无能为力。这时要用recent模块实现针对每个 IP 的“按户记账”。典型规则组合iptables -A INPUT -p tcp --syn -m recent --name synflood --set iptables -A INPUT -p tcp --syn -m recent --name synflood --update --seconds 60 --hitcount 30 -j DROP原理是这样的--set把当前发起 SYN 的源 IP 记到名为synflood的名单里--update检查该 IP 在过去 60 秒内是否是第 30 次以上触发 SYN。如果命中说明这个源 IP 的 SYN 频率异常直接丢弃。对每个 IP 来说前 29 个 SYN 在窗口期是放行的第 30 个开始被丢。这套机制的本质是“给每个 IP 发一个计数牌”频率维度上识别恶意源。它对那些固定源 IP 的疯狂扫描特别有效但必须理解它的局限攻击者伪造大量不同源 IP 时每个 IP 计数都不高recent 就形同虚设。所以实际部署时upload 上往往要和 limit 搭配一个负责“全局总量”一个负责“单 IP 频率”两者结合覆盖不同维度的攻击模式。recent模块还有一个容易被忽略的点它依赖一个固定大小的哈希表来记录 IP 和命中时间默认ip_list_tot较小默认 100如果攻击源 IP 数量巨大超过哈希表容量新的 IP 记不进去规则就失效了。调整方式是通过加载模块参数指定更大的表容量例如modprobe ipt_recent ip_list_tot1000生产环境里这个参数可以按机器资源适当放大但也要警惕表越大每条规则匹配时查表开销越高CPU 消耗也随之上升。具体多少需要结合机器配置一般 1000~5000 是常见区间。3. 实操从检测到一套完整防护配置3.1 实战部署一套可直接参考的加固脚本前面分析了原理接下来直接给一份可落地的脚本。假设场景是 Web 服务监听 80 和 443SSH 监听 22需要根据你的实际端口调整。# 1. 清空现有规则避免历史规则干扰 iptables -F iptables -X iptables -Z # 2. 设置默认策略输入丢弃输出和转发放行 iptables -P INPUT DROP iptables -P OUTPUT ACCEPT iptables -P FORWARD ACCEPT # 3. 放行回环接口本机内部通信不允许配置错误把自己的本地服务挡了 iptables -A INPUT -i lo -j ACCEPT # 4. 放行已有连接及相关连接 iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 5. 按需放行服务端口 iptables -A INPUT -p tcp --dport 22 -j ACCEPT iptables -A INPUT -p tcp --dport 80 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT # 6. 放行 ICMPping 用于连通性检查攻击时也可以借此观测状态 iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 10/s -j ACCEPT # 7. SYN 限速全局维度 iptables -A INPUT -p tcp --syn -m limit --limit 30/s --limit-burst 60 -j ACCEPT iptables -A INPUT -p tcp --syn -j DROP # 8. 单 IP 频率限制recent 维度 iptables -A INPUT -p tcp --syn -m recent --name synflood --set iptables -A INPUT -p tcp --syn -m recent --name synflood --update --seconds 60 --hitcount 30 -j DROP # 9. 丢弃无效连接跟踪状态防止畸形包绕过状态检查 iptables -A INPUT -m state --state INVALID -j DROP这份脚本有几个细节需要展开解释。默认策略INPUT DROP是一个强烈的选择。好处是没显式放行的流量全被丢弃任何新出现的端口都不会裸露在外。代价是一旦你自己忘了放行某个必要端口比如 DNS 需要的 53/UDP、NTP 的 123/UDP排查会很难受。建议在一台测试机上先跑通确认所有业务依赖的端口都已放行再整体推上生产。INVALID状态为什么要丢conntrack 会对每个包做状态判断有些包既不属于已建立连接也不可能是新连接的合法握手包比如序列号跨越过大、TCP 标志位组合荒唐的包这些是扫描器和畸形包惯用的手法专门用来探测规则边界。INVALID就是它们的标签直接丢弃可以省掉后续规则匹配的 CPU 开销。我实测中攻击流量里这类畸形态占比不低丢掉它们能明显减少日志刷屏。要注意脚本里recent的两条规则写在limit之后。因为limit已经先放行了一部分 SYN进入 recent 记账的流量窗口实际上是被限过速的这样可以避免 recent 模块的哈希表被短时巨大流量冲爆。3.2 内核参数配合让 iptables 事半功倍纯 iptables 规则能挡掉很多包但内核协议栈本身的消化能力决定了抗压上限。调整下面这些参数能让防护更上一层# 增大半连接队列容量允许更多握手状态排队 net.ipv4.tcp_max_syn_backlog 4096 # 开启 SYN Cookies内核在队列满时直接用加密算法生成握手 cookie net.ipv4.tcp_syncookies 1 # 减少 SYNACK 重试次数缩短半连接条目的存活时间 net.ipv4.tcp_synack_retries 1 # 增大 accept queue 长度提升连接就绪后的处理能力 net.core.somaxconn 1024写入/etc/sysctl.conf后执行sysctl -p生效。SYN Cookies 这里展开讲一下。它的原理是当半连接队列满时内核不再依赖队列条目来记录握手状态而是在SYNACK里通过 Hash 计算隐含一个 Cookie。如果对方真的返回了正确 ACK内核通过校验 Cookie 重建状态无需占用队列空间。这使得即使队列被占满合法用户依然能完成握手。代价是它改变了一部分 TCP 行为极端情况下某些协议栈不支持时可能握手失败但现代系统默认开启后我没遇到过兼容性问题。参数调整不是越大越好。tcp_max_syn_backlog设到多大取决于内存和实际业务并发。每一条半连接状态大概占用几百字节一个 4GB 内存的轻量服务器调到 4096 是比较稳妥的继续加大未必带来收益反而占内存。tcp_synack_retries调成 1 则意味着握手失败后只重试一次攻击时这种半连接状态能更快被清理但正常网络丢包率稍高的场景下可能会影响用户的首连成功率需要结合机房网络质量权衡。3.3 如何验证规则真的在干活规则配上去之后最怕的是“配了但没生效”或者“生效了但误伤太重”。验证手段分三层。第一层看规则命中的计数器iptables -L -n -v --line-numbers输出里每个规则都有pkts列显示匹配过的包数。攻击发生时观察 SYN 限速规则的pkts在不在疯涨以及DROP规则的pkts是否在上升。如果上升说明规则在拦截但要注意相对速率如果拦截速度很慢比如一分钟只增加几十个包说明限速参数相对攻击量偏高了如果DROP量远大于ACCEPT量说明流量结构明显异常。第二层看服务实际可用性。模拟几个正常用户请求curl、浏览器、SSH确认业务照常工作。如果正常用户也被丢包说明防护参数过严需要放宽limit或hitcount。第三层才是可选的本地压力测试。我建议在测试环境里用工具对自建靶机发起 SYN 流量比如 hping3用来观察规则链的拦截效果和内核日志变化。注意只能在私有网络环境内打自己控制的机器公网目标做任何形式的压测都是不合适的这个边界要守住。测试结束后立即删除测试规则、恢复干净的状态。4. 别迷信 iptables方案的边界与生产级演进4.1 为什么纯 iptables 挡不住真正的“大流量”很多人配置完 iptables 就以为万事大吉但现实是如果攻击流量达到一定量级iptables 本身也会成为瓶颈。关键在于iptables 的过滤机制发生在内核协议栈的 netfilter 钩子点每一个包到没到应用层之前CPU 都要先处理匹配规则。攻击流量即便最终会被 DROP但包仍然要经过内存拷贝、协议栈解析和规则匹配。当流量达到上百万 PPS每秒包数时单机 CPU 就可能被打满规则链本身的处理能力反成瓶颈。这就好比你安排了一个保安在门口检查通行证保安速度再快人流量大到一定程度时排队的人也会把门口挤爆——保安没被击垮通道本身击垮了。所以对 iptables 的定位要清醒它适合“中低速率、以资源耗尽为目标”的 SYN flood在攻击尚未把链路带宽占满时能有效缓解。但对动辄几十 Gbps 的大流量攻击单机任何软件措施都是徒劳必须依赖链路侧的清洗设备或高防服务把攻击流量引流掉。4.2 SYNPROXY内核态代劳握手的进阶打法如果不想上硬件设备内核的SYNPROXY模块是 iptables 方案中最接近“大流量硬抗”的招数。它的思路很妙不再让真实的协议栈直接面对攻击方的 SYN而是由 SYNPROXY 在内核态先替服务器完成三次握手。握手成功后再把“已建立”的连接状态转发给真实服务。攻击方的 SYN 包根本到不了应用层协议栈从源头上规避了半连接队列耗尽的问题。启用条件内核版本至少 4.4且需要加载xt_synproxy和nf_conntrack_sync相关模块。开启方式是在规则里显式调用 SYNPROXY 目标modprobe xt_synproxy iptables -t raw -A PREROUTING -p tcp --dport 80 --syn -j CT --notrack iptables -A INPUT -p tcp --dport 80 -m state --state INVALID,UNTRACKED -j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460 iptables -A INPUT -p tcp --dport 80 -m state --state INVALID,UNTRACKED -j DROPraw表里的CT --notrack会绕过 conntrack 对 SYN 包的跟踪把这部分流量直接交给 SYNPROXY 处理INVALID,UNTRACKED状态匹配后进入 SYNPROXY 目标由它完成代理握手。我用过的环境里SYNPROXY 对单机防护能力提升明显对资源型 SYN flood 能扛住比纯 iptables 规则高一个量级的流量。但它也不是没有代价因为握手是代理完成的它会改变真实连接的 TCP 时间戳和窗口缩放行为个别对 TCP 参数非常敏感的老旧客户端可能异常另外它默认只处理 TCP 代理握手不会过滤应用层数据跟后续的 WEB 防护仍需配合。要在测试环境充分验证兼容性后再上生产。4.3 生产环境的组合拳思路真正面对线上业务的防护不能只靠一台机器的 iptables。以我经历过的实践来看一套可用的生产防护往往分层第一层机房或云平台的流量清洗/高防负责把超过链路容量的流量拦在外面这是最重的防线第二层云安全组或机房防火墙策略限制端口暴露面配黑白名单第三层服务器本机 iptables兜底应对漏网的中小流量配合内核参数做好系统级韧性第四层应用层Nginx/负载均衡的连接数和速率限制处理那些穿透了网络层防御的正常 HTTP 攻击比如慢速连接耗尽资源。每一层解决不同量级的攻击iptables 只负责其中一层。把它配好自然重要但永远不要把它当成唯一的盾牌。运维的自我修养是知道每一条规则能拦住什么也知道自己拦不住什么。5. 常见问题与排坑实录5.1 我踩过的几个坑列出来给你避开坑一规则顺序没理清DROP 永远不生效。iptables 按顺序匹配一旦前面的ACCEPT命中了所有 SYN后面的 SYN 限速规则和 DROP 就形同虚设。我在新服务器上部署时就遇到过顺手写了一条iptables -A INPUT -j ACCEPT放行全部流量结果整张防护规则全部无效。排查后删掉了这条问题解决。经验是写规则前先把业务所需端口整理清楚只针对端口放行不要出现“放行所有”的兜底规则回环和已建立连接除外。坑二limit 参数调太低把正常用户挡在外面。有一次我为了“加强防护”把一个日活并不低的站点限速调到5/s结果高峰期正常用户二次建立连接都失败客服群直接炸了。限速参数必须基于业务基线来设先看正常流量的SYN/s高峰再定。我的做法是先用tcpdump -i eth0 tcp[tcpflags] tcp-syn ! 0 -c 1000抓包统计日常 SYN 速率然后在这个数值上乘一个系数作为 limit 的初始值上线后根据计数器再微调。坑三没放行RELATED状态FTP 等主动模式连接全部失败。如果只放行ESTABLISHED而没放行RELATEDFTP 的数据通道被服务器主动建立的第二个连接会被当新连接直接丢弃。这种问题不会影响 Web 但会阴你一把。规则里--state ESTABLISHED,RELATED要写全。坑四recent 模块的哈希表容量不足攻击源一多规则就失效。默认ip_list_tot只有 100一次源 IP 上千的攻击会让记录表溢出。调大模块参数能缓解但也要意识到 iptables 记账的本质限制了它能精确处理的 IP 数量级。真要面对海量源 IP不要指望 recent 能兜底直接考虑高防清洗更实际。5.2 排错方向速查表现象可能原因先查哪里SSH 都连不上默认策略 DROP 且没放行 22 端口iptables -L -n查看 INPUT 链网站时好时坏limit 限速过紧或 SYN Cookies 异常iptables -L -v观察 ACCEPT 与 DROP 计数比例iptables 规则新增报错目标规则与已有规则顺序冲突或模块未加载先iptables -F保持链为空再逐条添加conntrack 表满连接跟踪条目太多cat /proc/sys/net/netfilter/nf_conntrack_max和conntrack -S规则计数增长但业务仍异常攻击包经 offload 路径绕过 netfilter 或量级超限观察nstat -az整体协议栈丢包和 CPU 软中断利用率recent 规则无效哈希表容量满或未与 limit 配合dmesg查 recent 模块告警调整ip_list_tot这些坑大多数不是规则写错而是对 iptables 的工作模型理解不够。每次排错完我都会把原因和实验过程记下来。积累到一定程度你会发现大部分看起来玄乎的网络问题最终都能在“谁在什么顺序上匹配了什么包”这个框架里找到答案。写在最后的一点个人经验iptables 做 SYN flood 防护这件事我在多个生产环境里验证过也吃过头铁不看基线导致误伤的亏。现在的习惯是新环境一律先跑一周日志模式把所有命中规则或丢弃行为记录在案确认没有误伤后再开启 DROP上线后每天看一眼计数器把变化量写进值班报告。这套流程看起来麻烦但能避免绝大多数线上事故。另外劝各位一句规则尽量写成脚本纳入版本管理不要每次靠手敲。手敲规则多敲几次就容易乱乱的时候往往正是被人盯着打的时候。最后再分享一个小细节——iptables 的规则要定期iptables-save备份重启之后没持久化就全没了。这个坑年轻人踩得多老鸟偶尔也会栽。
返回列表