传输层要拦下异常新连接,唯一的正解是“分级治理”:先用粗阈值兜底,再用细粒度规则做二次裁决,把“不确定”放行给应用层去背锅。
这听起来像废话,但所有误杀惨案都源于一个共同点把传输层当成了“全能裁判”,TCP握手阶段能拿到的信息实在太少:一个SYN包,一个序列号,一个IP端口对,仅凭这点信息判断“这个新连接是不是坏人”,本质上是在猜,不同业务的连接特征千差万别,拿一套固定规则套所有流量,必然误伤。
传输层拦异常连接,先搞懂“误杀”是怎么发生的
连接跟踪表(conntrack)是传输层拦截的第一道关卡,Linux内核为每个新连接建立一条记录,状态从SYN_SENT、SYN_RECV走到ESTABLISHED,当表满了,新连接会被直接丢弃不管你是攻击流量还是正常用户,一视同仁。
连接跟踪表:善意门卫为何拦错人
业内专家指出,生产环境下连接跟踪表的最大隐患不是“容量不够”,而是“回收不及时”,默认超时时间在SYN_SENT状态约120秒,SYN_RECV状态约60秒(不同内核版本有差异),一个分布式扫描器每秒发几千个SYN包,每包都占一个表项,几十秒就把表撑爆。
换到攻击者的视角:他们不需要把连接“做成”,只需要让表项一直堆积,真正用户的新连接到达时,连排队的机会都没有,直接被内核丢进黑洞,你以为是攻击绕过了防护,其实是自己把自己关在了门外。
查表命令:排查时先看表是否满了。
cat /proc/sys/net/netfilter/nf_conntrack_count cat /proc/sys/net/netfilter/nf_conntrack_max
两者差值低于10%就要警惕,再看丢弃计数,确认是否在持续增长:
grep -c "drop" /var/log/kern.log
半连接队列占满才是“沉默的杀手”
很多人只盯conntrack,忽略了SYN队列,TCP协议栈为每个监听端口维护两条队列:半连接队列(syn queue)和全连接队列(accept queue),新连接必须先排进半连接队列,完成三次握手后才挪到全连接队列,等应用层调用accept。
攻击者伪造源IP发SYN,半连接队列瞬间占满,正常用户的请求只能排到队尾,这就是经典的SYN Flood,但诊断时要区分细节:全连接队列溢出通常表现为“Connection reset”,半连接队列溢出更多表现为“连接超时”,搞反了方向,调了一堆参数却打在空处。
查看全连接队列溢出:
netstat -s | grep -i "listen queue"
如果这个计数高,先检查应用层是否处理不过来,不一定全是网络攻击。
tcp半连接队列溢出怎么排查,用数据说话而不是感觉
很多运维同学遇到连接超时,第一反应是“加机器”,结果SLB后面每个节点都在被吞连接,扩容只稀释了问题,没解决根因,排查按三步走。
第一步:用ss命令拉开连接全景
ss -s
输出里有TCP各类状态的计数,如果SYN-SENT和SYN-RECV状态连接数长期高位,且总数超过半连接队列上限,基本确定是在淹队。
第二步:看队列溢出计数器
netstat -s | grep -i "SYNs to LISTEN sockets dropped"
这个计数器是“半连接队列丢包计数”,它持续增长,说明内核确实在丢弃SYN,对比每秒新增连接数(Nginx的connections和requests),如果请求量没有暴涨而丢弃在涨,那攻击特征就比较明显了。
第三步:区分“攻击”还是“瞬时尖峰”
这步最容易踩坑,真实用户的秒杀活动也会撑满半连接队列,让人误判成攻击,区分方法很原始但有效:看SYN包的单源分布,抓包三分钟,统计SYN源IP数量,如果TOP10源IP贡献了相当比例的SYN且全是新IP段,那是扫描器;如果成百上千个IP均匀分布,更可能是正常流量涌入。
这样的判断虽然朴素,但比看每秒SYN总数准得多,真正的攻击往往伴随“源IP分布集中”和“目标端口固定”两个特征。
linux服务器被syn攻击怎么解决,又不想挡掉正常用户
这是最核心的问题,理想方案是“接得住、判得准、放得行”,生产环境验证过的手段,按优先级排列如下。
内核参数:从“一刀切”到“限流降级”
默认内核对半连接队列满的处理是直接丢SYN,你可以改成“选择性丢弃”:
sysctl -w net.ipv4.tcp_syncookies=1 sysctl -w net.ipv4.tcp_max_syn_backlog=65536 sysctl -w net.ipv4.tcp_synack_retries=2 sysctl -w net.ipv4.tcp_abort_on_overflow=0
tcp_syncookies的意义在于:半连接队列满时,内核为每个SYN计算一个cookie,通过SYN+ACK返回给客户端,客户端回ACK时带上cookie,服务端校验通过就直接建立连接,不再依赖半连接队列,这样正常用户只要完成握手就能进来,攻击者拿不到或不愿回ACK,自然被过滤。
但syncookies不是银弹:对某些TCP扩展(时间戳、SACK)支持不完整,极端场景下会退化性能,所以大型系统更倾向让前端负载均衡器承担SYN代理,后端节点保持常规参数。
SYN代理:把风险挡在业务链路之外
行业共识认为,LVS的SYN代理或商用防火墙的SYN Proxy,是最平滑的拦截方式,它对客户端伪装成真实服务器完成握手,对后端把代理的连接交出去,后端只看到“干净”的连接,误杀率极低,因为决策点前置到代理层,后端不感知攻击。
不过高可用方案都有代价:SYN代理需要维护自己的状态表,硬件成本随着并发量上升,如果业务量不大,内核调优就够了。
动态阈值而非静态封禁
不少团队希望直接在传输层封IP,但在出口NAT后面常有几百个真实用户共用一个IP,封一个“攻击源”等于挡掉整个公司,更好的做法是动态速率限制:对单IP的新连接速率设置阈值,比如每100ms握手失败超过N次就临时限速,而不是直接drop。
Nginx层可以用limit_conn_zone做连接数限制,iptables用hashlimit做速率控制:
iptables -A INPUT -p tcp --dport 80 -m hashlimit --hashlimit-upto 10/sec --hashlimit-name http_limit -j ACCEPT iptables -A INPUT -p tcp --dport 80 -j DROP
注意静态阈值需要根据业务的正常上线连接速率反复调整,设得太松等于没拦,太紧则把正常用户挡在门外,有条件尽量用自动缩放的限速策略,比如基于最近5秒的握手成功率动态调整阈值。
传输层拦截异常连接方案对比:自研vs设备vs内核调优
遇到问题先别急着上设备,按运维成本从低到高排序:
| 方案 | 误杀风险 | 生效层级 | 适用规模 |
|---|---|---|---|
| 内核参数调优 | 低 | TCP协议栈 | 单机十万级并发 |
| iptables限速 | 中 | 网络层/传输层 | 中小规模集群 |
| 前端SYN代理 | 低 | 网关层 | 大规模分布式 |
| 商用抗D设备 | 中 | 旁路+串联 | 高防场景 |
别把“运维成本高”理解成坏事,为了不误杀,花在配置、压测、观察上的功夫是值得的。如果业务自身流量不大,内核参数调优是性价比最高的起点,没必要第一波就上重型设备。
传输层拦截异常新连接的实操检查清单
作为收尾,把实操路径整理成清单,按顺序核对即可:
- 确认当前连接跟踪表水位:
nf_conntrack_count对比nf_conntrack_max,低于70%为健康 - 开启syncookies,设置
tcp_max_syn_backlog,观察丢弃计数是否归零 - 压测环境模拟SYN Flood,记录正常业务能接受的最大延迟和错误率
- 上线阶梯限速规则:先每IP限制每秒10个新连接,持续观察误杀率
- 保留至少两个维度的日志(Nginx access log加TCP抓包),便于回看攻击细节
- 为每个规则设计回滚开关,确保误杀时能分钟级恢复
最后说明一个认知偏差:传输层拦截的价值在于“缓冲”,不在于“终结”,真正决定一个连接是否正常的信号,比如登录口令、请求路径、User-Agent,都藏在应用层,把传输层规则做得过严,等于修路时封掉所有入口,反倒让需要诊断的流量进不来,合理的策略是在传输层挡住明显异常,把模糊地带交给应用层决策。
Q&A:传输层新连接拦截常见疑惑
Q:tcp半连接队列溢出一定意味着被攻击吗?
A:不一定,应用层accept处理过慢(比如业务逻辑里接了数据库超时),也会让全连接队列堆积,最终倒灌压垮半连接队列,先查看CPU负载和Nginx的backlog配置,再做攻击判断。
Q:syncookies开启后对业务有什么副作用?
A:少数极端场景下,使用了TCP时间戳、SACK选项的旧客户端会握手失败,如果业务客户端版本陈旧,先灰度开启,观察握手失败率,另外syncookies下TCP快速重传的某些优化会降级,对时延敏感型业务需谨慎。
Q:nginx高并发连接数优化还要调哪些参数?
A:除了传输层的半连接队列,Nginx本身的worker_connections和listen指令后的backlog参数也要同步调大,Linux下默认的listen backlog只有128,单机压测时很容易忽略这个瓶颈。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/787133.html


