探讨自建高防 CDN 应对 UDP 洪水攻击的系统内核参数加固
摘要:# UDP洪水来了,你的自建高防CDN真能扛住吗?聊聊内核那些“硬核”调优 我前两天刚帮一个朋友看他的游戏服务器,那场面,简直了。 他花了不少钱自建了一套高防CDN,平时防防CC、拦拦HTTP洪水,感觉还挺稳。结果上周被人用UDP洪水怼了一波,直接打穿…
UDP洪水来了,你的自建高防CDN真能扛住吗?聊聊内核那些“硬核”调优
我前两天刚帮一个朋友看他的游戏服务器,那场面,简直了。
他花了不少钱自建了一套高防CDN,平时防防CC、拦拦HTTP洪水,感觉还挺稳。结果上周被人用UDP洪水怼了一波,直接打穿——不是防护规则没生效,是承载防护的那几台调度机自己先扛不住了,CPU直接100%,内核报错刷屏。他当时就懵了:“我防护都上了,怎么机器自己先跪了?”
说白了,很多自建高防的方案,设计时心思全花在应用层规则上了,却忘了给最底层的系统——尤其是内核——穿件“防弹衣”。 UDP洪水这玩意儿,它不跟你讲道理,不建立连接,就是海量的垃圾包往你的IP和端口猛灌。你的应用可能还没反应过来,系统的协议栈、网络队列、内存这些底层资源就先被冲垮了。
今天咱不聊那些花里胡哨的防护策略,就扎扎实实地盘一盘,为了应对UDP洪水,你的Linux系统内核参数到底该怎么调。这活儿有点干,但真出了事,能救你于水火。
为什么UDP洪水这么“毒”?
先得搞明白对手。TCP攻击(比如SYN Flood)好歹还有个“三次握手”的流程,系统能通过半连接队列(syn backlog)之类的地方设防。UDP呢?它是无连接的。一个数据包过来,内核就得:
- 检查端口有没有应用在监听。
- 没有?那就回个
ICMP Destination Unreachable(目标不可达)。 - 然后把这个包扔掉。
问题就出在这第二步和第三步。当洪水般的垃圾UDP包涌向一堆随机端口时,内核就陷入了疯狂“找监听-回ICMP-丢包”的死循环。这个循环本身消耗CPU和内存资源,而大量发送ICMP响应又会加剧网络拥堵,形成恶性循环。更狠的攻击者还会伪造源IP,让你回的ICMP包打到无辜的“反射器”上,这就是臭名昭著的UDP反射放大攻击。
所以,加固的思路很清晰:减少无效工作、限制资源消耗、加快垃圾处理速度。
内核加固:给系统穿上“紧身衣”
下面这些参数,你可以在/etc/sysctl.conf文件里修改,然后sysctl -p生效。我尽量用大白话解释清楚每个是干嘛的。
第一道防线:管住ICMP,别当“老好人”
UDP洪水希望你回ICMP,咱偏不。
# 直接关掉对未监听UDP端口的ICMP响应。这是最关键的一步!
net.ipv4.icmp_echo_ignore_all = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
# 限制ICMP消息的发送速率,别被洪水带着跑
net.ipv4.icmp_ratelimit = 100
net.ipv4.icmp_ratemask = 88089
(注意:icmp_echo_ignore_all = 1会同时屏蔽Ping,可能影响监控。生产环境慎用,或者用更精细的iptables规则替代。)
更推荐的做法是用iptables在防火墙层面精准控制:
# 只允许来自可信网络的ICMP,对未监听UDP端口的所有入站包直接DROP,不回复
iptables -A INPUT -p udp --dport <你的业务端口> -j ACCEPT
iptables -A INPUT -p udp -m state --state NEW -j DROP
说白了,就是告诉系统:“不是找我的正经业务流量,看一眼就扔,别费劲回话了。”
第二道防线:扩大“收件箱”,加快“处理速度”
想象系统的网络栈是个快递分拣中心。洪水来了,就是海量垃圾包裹瞬间堆满门口。我们要做的是:把临时存放包裹的院子(队列)搞大点,同时让分拣员(内核)处理得快一点。
# 增大网络设备 backlog(队列长度),别让包在网卡层面就丢了
net.core.netdev_max_backlog = 100000
# 增大系统全局的 socket 监听队列长度
net.core.somaxconn = 65535
# 针对UDP,增大接收缓冲区的大小,让内核能暂存更多包等待处理
net.core.rmem_default = 262144
net.core.rmem_max = 67108864
net.ipv4.udp_rmem_min = 16384
# 同样,增大发送缓冲区,虽然UDP洪水主要针对接收
net.core.wmem_default = 262144
net.core.wmem_max = 67108864
net.ipv4.udp_wmem_min = 16384
第三道防线:优化协议栈,减少“内心戏”
内核处理每个网络包都有开销。我们需要调整一些协议栈参数,让它别对UDP包做太多“无用功”。
# 禁用TCP时间戳等无关优化,集中资源(对纯UDP业务场景)
net.ipv4.tcp_timestamps = 0
# 快速回收TIME-WAIT状态的socket,虽然主要是TCP,但保持系统清爽
net.ipv4.tcp_tw_recycle = 0 # 注意:NAT环境下可能有问题,通常建议设为0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
# 最重要的一条:减少内核在UDP包上的处理“回合”
# 将 `udp_mem` 调整到适合你内存的大小,三个值分别是最小压力值、压力阈值、最大值。
net.ipv4.udp_mem = 786432 1048576 1572864
别忘了,还有连接追踪(conntrack)这个坑
即使是无连接的UDP,Linux内核默认也会用nf_conntrack模块来跟踪它,以实现状态防火墙等功能。在UDP洪水下,这会导致conntrack表瞬间爆满,新连接无法建立。
# 查看当前conntrack表大小和占用
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# 如果UDP业务不需要状态跟踪,最粗暴的方法是直接卸载模块(重启可能失效)
# modprobe -r nf_conntrack_udp4 nf_conntrack
# 更稳妥的是,增大conntrack表的最大值,并降低超时时间
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_udp_timeout = 30 # 降低UDP conntrack超时(默认30秒可缩短)
实战心法:调优不是玄学,是观测与平衡
参数给了一堆,但千万别直接抄了就往生产环境扔。内核调优是个精细活儿,需要平衡。
-
内存是代价:
net.core.rmem_max、udp_mem这些参数调大了,意味着内核要拿出更多内存来当网络缓冲区。你机器内存总共才8G,你给UDP缓冲区设了4G,那纯属找死。一般建议,所有网络缓冲区的总和不超过系统物理内存的1/4。 -
监控是眼睛:调之前、调之后,必须用眼睛盯着。
sar -n DEV 1:看网卡吞吐量、丢包率(drop字段)。netstat -su:查看UDP层的详细统计信息,特别是“packet receive errors”和“receive buffer errors”。dstat --top-io --top-bio:看哪些进程在疯狂消耗I/O(网络I/O也是I/O)。cat /proc/net/snmp | grep -i udp:更底层的UDP协议栈计数。
-
压力测试是试金石:用
hping3、npign或者更专业的scapy自己构造UDP流量,在小规模环境先打一波,观察系统指标和业务是否正常。“没有经过压力测试的调优,都是纸上谈兵。”
最后说点大实话
自建高防CDN应对UDP洪水,内核调优是地基,是最后一道物理防线。它不能替代前端的流量清洗、带宽扩容、DNS调度这些高层策略。它的核心目标是:在清洗系统识别并拦截恶意流量之前,保证你的调度机、转发节点自己不要先崩溃。
很多团队一提到防护,就想着买更贵的硬件、上更多的节点。这当然没错,但往往忽略了,同样的硬件,经过精细调优的系统,其抗压能力可能有数倍甚至数十倍的提升。这就好比给你一辆赛车,你没调校悬挂和变速箱,直接上赛道,肯定跑不过精心调校过的普通车。
行了,参数和思路都摆在这儿了。具体怎么调,还得看你自家的业务流量和服务器体质。别怕麻烦,拿出做实验的精神,一点点试。毕竟,真等到攻击来的那一刻,你能依赖的,只有这些平时打磨好的、毫不起眼的底层配置。
你的源站,今天穿“防弹衣”了吗?

