图解 TCP:SYN 报文何时会被丢弃?——tcp_tw_recycle 陷阱与半连接/全连接队列溢出排查实战
文档教程知识库【免费下载链接】CS-Base图解计算机网络、操作系统、计算机组成、数据库共 1000 张图 50 万字破除晦涩难懂的计算机基础知识让天下没有难懂的八股文 在线阅读https://xiaolincoding.com项目地址https://gitcode.com/GitHub_Trending/cs/CS-Base点击查看免费下载导读本文基于 CS-Base 仓库中 SYN 报文什么时候情况下会被丢弃 的核心内容展开系统梳理 SYN 报文被服务端忽略的两大类典型场景开启tcp_tw_recycle且处于 NAT 环境下的 per-host PAWS 误杀以及TCP 半连接队列 / 全连接队列溢出导致的握手报文丢弃。读完本文你将掌握 PAWS 机制与 TCP 时间戳的工作原理、SYN 队列与 accept 队列的溢出判定方法、ss/netstat排查命令以及从内核参数到 Web 服务 backlog 的完整调优手段能够在生产环境出现客户端疯狂重传 SYN 却始终连不上时快速定位根因。问题现象服务端收到 SYN 却不回 SYNACK在真实生产环境中偶尔会遇到这样的诡异场景客户端向服务端发起 TCP 连接但连接始终无法建立。通过抓包分析发现服务端明明收到了 SYN 报文TCP 第一次握手却没有回复 SYNACKTCP 第二次握手说明 SYN 报文被服务端静默忽略随后客户端不断超时重传 SYN直到达到最大重传次数后放弃。结合实战经验与内核行为SYN 报文被丢弃通常可以归为两类开启tcp_tw_recycle参数且客户端处于 NAT 环境下触发 per-host PAWS 机制误判将合法 SYN 当作过期报文丢弃TCP 两个队列满了——半连接队列SYN 队列或全连接队列accept 队列溢出内核直接丢弃后续到达的 SYN。下面分别深入剖析这两种场景的来龙去脉、内核原理与解决方案。场景一坑爹的 tcp_tw_recycle —— NAT 环境下的 SYN 误杀1.1 从 TIME_WAIT 说起端口资源与两个核心作用TCP 四次挥手过程中主动断开连接方会进入TIME_WAIT状态该状态持续2MSL后才转变为 CLOSED。在 Linux 下TIME_WAIT 的持续时间为60 秒意味着这 60 秒内该端口一直被连接占用。端口资源是有限的。Linux 默认的客户端可用端口范围为32768~61000可以通过net.ipv4.ip_local_port_range内核参数调整# 查看当前客户端可用端口范围 sysctl net.ipv4.ip_local_port_range # 示例输出32768 61000如果客户端连接发起方的 TIME_WAIT 连接过多占满所有端口资源那么它无法再对「目的 IP 目的 PORT」都相同的服务器发起新连接但只要目标服务器不同已占用的端口依然可以复用。这是因为内核定位一条 TCP 连接依靠的是四元组源 IP、源端口、目的 IP、目的端口端口相同而四元组不同并不会产生冲突。TIME_WAIT 状态并非摆设它有两个关键作用防止具有相同四元组的旧数据包被新连接接收避免历史连接中的迟到数据污染新连接保证被动关闭方能被正确关闭即保证最后一次 ACK 能被被动关闭方收到从而帮助其正常进入 CLOSED 状态。关于端口复用与 TIME_WAIT 的更深入讨论可参阅仓库内 客户端的端口可以重复使用吗。1.2 两个快速回收参数tcp_tw_reuse 与 tcp_tw_recycle为了缓解 TIME_WAIT 占用端口的问题Linux 提供了两个内核参数默认均为关闭参数作用适用方net.ipv4.tcp_tw_reuse客户端调用connect()时若内核选中的端口已被相同四元组的连接占用且该连接处于 TIME_WAIT 且持续超过1 秒则重用该连接仅连接发起方客户端net.ipv4.tcp_tw_recycle允许处于 TIME_WAIT 状态的连接被快速回收不限但存在安全隐患两个参数生效有一个前提必须开启 TCP 时间戳即net.ipv4.tcp_timestamps1默认即为 1。1.3 PAWS 机制防止序列号绕回的守护者tcp_timestamps开启后PAWSProtection Against Wrapped Sequences防止序列号绕回机制会自动开启。它的作用是防止 TCP 包中的序列号发生绕回。正常来说每个 TCP 包都有唯一的 SEQ 号数据包重传时会复用 SEQ 号接收方通过 SEQ 号判断数据包唯一性并识别重传。但 TCP 的 SEQ 号只有 32 bit从 0 开始递增溢出后重新从 0 递增。一旦 SEQ 溢出单纯依靠 SEQ 号无法标识数据包的唯一性例如某个数据包 A 发生重传在 SEQ 号耗尽再次递增到 A 时第一次发出的 A 包因网络延迟才到达服务端服务端会误认为延迟到达的 A 包是正确数据并接收反而把正常第三次发送的 SEQ 为 A 的数据包丢弃造成数据传输错误。PAWS 的解决方案是开启tcp_timestamps后一台机器发出的所有 TCP 包都会携带发送时刻的时间戳。连接双方各自维护最近一次收到的数据包的时间戳Recent TSval每收到一个新数据包就将其时间戳与 Recent TSval 比较——如果发现时间戳不是递增的说明该数据包是过期的直接丢弃。这样延迟到达的旧 A 包就能被正确识别并丢弃。1.4 per-host PAWSNAT 环境下的误杀开关同时开启tcp_tw_recycle和tcp_timestamps后服务端会开启一种per-host按主机 IP的 PAWS 机制它对「对端 IP」做 PAWS 检查而不是对「IP 端口」四元组做检查。问题恰恰出在这里。如果客户端环境经过NAT 网关内网所有机器的出网 IP 都相同服务端视角下它们就像同一个客户端无法区分。per-host PAWS 利用 TCP option 中 timestamp 字段的增长来判断数据是否串扰而 timestamp 取自各客户端自身的 CPU tick客户端 A 通过 NAT 与服务端建立 TCP 连接服务端主动关闭并快速回收 TIME_WAIT 连接随后客户端 B 也通过同一 NAT 网关与服务端建立连接对外 IP 相同如果客户端 B 的 timestamp 比客户端 A 的 timestamp 小服务端 per-host PAWS 机制就会认为 B 的数据是过期串扰包直接丢弃 B 发来的 SYN。也就是说tcp_tw_recycle在 NAT 网络下极不安全如果它按四元组做 PAWS 检查而非按 IP 做检查就不会存在这个问题。也正因如此Linux 4.12 版本后直接移除了tcp_tw_recycle参数。实战提醒不要轻信网上开启 tcp_tw_recycle 优化 TCP的陈旧建议该参数在 NAT 环境下会造成线上大面积连接失败且在新内核中已不存在。现代内核下客户端侧请改用tcp_tw_reuse并优先排查业务层连接复用。场景二半连接队列满了 —— SYN 洪泛与 syncookies2.1 内核维护的两个队列TCP 三次握手期间Linux 内核会维护两个队列半连接队列也称SYN 队列服务端收到客户端 SYN 后内核将连接存储进该队列并响应 SYNACK队列内的连接处于SYN_RECV状态全连接队列也称accept 队列服务端收到第三次握手 ACK 后内核把连接从半连接队列移除创建完整连接并放入该队列等待进程调用accept()取出。关于两个队列的数据结构与设计动机仓库内 没有 accept能建立 TCP 连接吗 有详细图解虽然都叫队列但全连接队列本质是链表取连接 O(1)半连接队列被设计成哈希表按四元组查找 O(1)避免 O(n) 遍历。2.2 半连接队列溢出的表现当服务端遭受SYN 攻击SYN Flood半连接队列被打满后续到达的 SYN 包都会被直接丢弃正常用户连接无法建立。但是——如果开启了 syncookies 功能即使半连接队列满了也不会丢弃 SYN 包。syncookies 的做法是服务器根据当前状态计算出一个 cookie 值放在发出的 SYNACK 报文中客户端返回 ACK 时带上该值服务端取出验证若合法则直接认为连接建立成功。tcp_syncookies参数主要有三个取值值含义0关闭该功能1仅当 SYN 半连接队列放不下时启用推荐用于应对 SYN 攻击2无条件开启2.3 防御 SYN 攻击的三种方式方式一增大半连接队列关键结论增大半连接队列不能只调tcp_max_syn_backlog还必须一同增大somaxconn和 backlog即增大全连接队列否则只增大tcp_max_syn_backlog是无效的。修改 Linux 内核参数# 增大半连接队列上限 sysctl -w net.ipv4.tcp_max_syn_backlog1024 # 增大全连接队列上限 sysctl -w net.core.somaxconn1024增大 backlog 的方式因 Web 服务而异例如 Nginx 在配置文件中设置listen指令的backlog# nginx.conf 中修改监听 socket 的 backlog listen 8088 backlog1024;注意修改参数后必须重启 Nginx 服务因为半连接队列和全连接队列都是在listen()系统调用时初始化的。方式二开启 tcp_syncookies# 应对 SYN 攻击时设置为 1 即可 sysctl -w net.ipv4.tcp_syncookies1方式三减少 SYNACK 重传次数服务端受 SYN 攻击时会有大量处于SYN_RECV状态的连接持续重传 SYNACK重传次数达到上限后才断开。减少 SYNACK 重传次数可以加快这些半连接的释放# 设置 SYNACK 的最大重传次数默认 5 sysctl -w net.ipv4.tcp_synack_retries2场景三全连接队列满了 —— accept 不及时与队列过小3.1 全连接队列溢出的现象服务端并发处理大量请求时如果 accept 队列过小或应用程序调用accept()不及时就会造成全连接队列满后续连接被丢弃表现为服务端请求数量上不去。注意区分两种状态下的ss输出语义在LISTEN 状态下Recv-Q/Send-Q表示的是Recv-Q当前 accept 队列的大小即已完成三次握手、等待服务端accept()的 TCP 连接个数Send-Q当前 accept 队列的最大长度上图中监听 8088 端口的服务最大长度为 128。ss -lnt # 输出示例 # State Recv-Q Send-Q Local Address:Port Peer Address:Port # LISTEN 0 128 0.0.0.0:8088 *:*如果 Recv-Q 的大小超过 Send-Q就说明发生了 accept 队列满的情况。3.2 全连接队列的排查与调优可以通过netstat -s查看全连接队列溢出的累计次数# 全连接队列溢出次数累计值 netstat -s | grep overflowed # 示例输出 # 4343 times the listen queue of a socket overflowed如果配合watch -d每 2 秒刷新一次发现该数字持续增长说明全连接队列正在频繁溢出。要解决问题可以从两方面入手调大 accept 队列的最大长度通过同时调大backlog和somaxconn参数全连接队列最大值取决于二者的最小值即min(somaxconn, backlog)详见下文源码分析检查系统或代码为什么调用accept()不及时排查应用进程是否被阻塞、线程池是否耗尽等业务侧问题。3.3 tcp_abort_on_overflow溢出后的处置策略全连接队列满时丢弃连接只是 Linux 的默认行为。仓库内 TCP 半连接队列和全连接队列满了会发生什么又该如何应对 指出还可以通过tcp_abort_on_overflow参数选择向客户端发送 RST值行为0全连接队列满时丢弃客户端第三次握手的 ACK并重传 SYNACK重传超限会删除对应半连接默认值建议保持1全连接队列满时直接发送 RST 给客户端中止握手通常应当保持为 0当全连接队列短暂溢出后空出位置客户端重传的请求报文因携带 ACK仍能触发服务端成功建立连接从而提升突发流量下的连接建立成功率。只有非常确定全连接队列会长期溢出时才设置为 1 以尽快通知客户端。纵深从内核源码看半连接队列最大值的真相4.1 网上说法不准确tcp_max_syn_backlog 决定半连接队列大小是错的很多资料声称增大tcp_max_syn_backlog即可增大半连接队列这是不准确的。仓库内 TCP 半连接队列和全连接队列 通过实战 源码分析证明半连接队列的最大长度并不单单由tcp_max_syn_backlog决定。以 Linux 2.6.32 内核为例TCP 第一次握手收到 SYN 包时因队列长度关系而丢弃报文共有三个条件半连接队列满了且未开启tcp_syncookies则丢弃全连接队列满了且没有重传 SYNACK 的连接请求多于 1 个则丢弃未开启tcp_syncookies且max_syn_backlog - 当前半连接队列长度 (max_syn_backlog 2)则丢弃。全连接队列最大值由sk_max_ack_backlog决定它在listen()源码中初始化即min(somaxconn, backlog)而半连接队列的理论最大值max_qlen_log与两个因素相关当max_syn_backlog min(somaxconn, backlog)时max_qlen_log min(somaxconn, backlog) * 2当max_syn_backlog min(somaxconn, backlog)时max_qlen_log max_syn_backlog * 2。4.2 理论最大值不等于实际上限即便计算出理论半连接最大值SYN_RECV状态连接的实际个数也可能达不到该值只要当前半连接队列长度超过max_syn_backlog - (max_syn_backlog 2)即超过max_syn_backlog的 3/4条件 3 成立SYN 依然会被丢弃。实验数据表明理论值为 256 时实际在 193 个半连接处就开始丢包。因此实际处于SYN_RECV状态的最大个数分两种情况当前半连接队列未超过理论最大值但超过max_syn_backlog - (max_syn_backlog 2)则实际上限为max_syn_backlog - (max_syn_backlog 2)当前半连接队列超过理论最大值则实际上限为理论最大值。不同内核版本的算法不同例如 Linux 5.0.0 中理论半连接最大值就等于全连接队列最大值但队列溢出的三个条件依然存在。所以不要死记某个版本的数值重要的是掌握通过源码自我分析的方法。4.3 syncookies 的边界为什么不能完全取代半连接队列syncookies 可以绕过半连接队列建立连接那么能不能直接用 cookies 方案取代半连接队列答案是否定的不保存连接信息cookies 通过通信双方的 IP、端口、时间戳、MSS 等信息实时计算编码在 TCP 报头seq中服务端不保存状态因此若第二次握手报文在传输中丢失服务端不会重发消耗 CPU编解码 cookies 比较耗 CPU。攻击者可构造大量携带伪造 cookies 的 ACK 包ACK 攻击让服务端耗费 CPU 解码验证后才丢弃导致 CPU 资源耗尽、无法响应正常请求。排查命令速查表目标命令判断依据全连接队列当前长度/上限ss -lntLISTEN 状态Recv-Q/Send-QRecv-Q 超过 Send-Q 即溢出全连接队列溢出次数netstat -s \| grep overflowed累计值持续增长说明正在溢出半连接队列长度netstat -nt \| grep -i IP:端口 \| grep -i SYN_RECV \| wc -l统计 SYN_RECV 状态连接数半连接队列溢出次数netstat -s \| grep -i SYNs to LISTEN sockets dropped累计值持续增长说明正在溢出队列溢出实时监控watch -d netstat -s \| grep overflowed高亮显示变化的数字相关内核参数sysctl net.ipv4.tcp_max_syn_backlog net.core.somaxconn net.ipv4.tcp_syncookies net.ipv4.tcp_synack_retries确认当前取值总结SYN 报文被服务端忽略本质上是两类原因tcp_tw_recycle NAT 环境的 per-host PAWS 误杀服务端按对端 IP而非四元组做 PAWS 时间戳递增检查NAT 网关后不同客户端的 timestamp 参差不齐导致合法 SYN 被当作过期包丢弃。该参数已在 Linux 4.12 移除切勿再在生产环境开启需要回收 TIME_WAIT 时客户端侧应使用tcp_tw_reuse。TCP 两个队列溢出半连接队列满时后续 SYN 被丢弃可通过增大tcp_max_syn_backlogsomaxconn backlog、开启tcp_syncookies、减少tcp_synack_retries三类手段缓解全连接队列满时后续连接被丢弃可通过调大 backlog 与 somaxconn、排查accept()不及时问题解决。注意半连接队列的大小并非由tcp_max_syn_backlog单独决定它与全连接队列上限min(somaxconn, backlog)密切相关且不同内核版本算法不同。排查时遵循先看ss队列水位再看netstat -s溢出计数最后核对内核参数与业务 accept 节奏的顺序即可快速定位 SYN 被丢弃的根因。相关的更多细节可继续阅读仓库内 TCP 半连接队列和全连接队列满了会发生什么又该如何应对、没有 accept能建立 TCP 连接吗、在 TIME_WAIT 状态的 TCP 连接收到 SYN 后会发生什么 与 客户端的端口可以重复使用吗。赞分享文档教程知识库【免费下载链接】CS-Base图解计算机网络、操作系统、计算机组成、数据库共 1000 张图 50 万字破除晦涩难懂的计算机基础知识让天下没有难懂的八股文 在线阅读https://xiaolincoding.com项目地址https://gitcode.com/GitHub_Trending/cs/CS-Base点击查看免费下载相关推荐Hister systemd服务配置开机自启与日志管理完整指南Hister systemd服务配置开机自启与日志管理完整指南 Hister 是一款私有搜索引擎Your own search engine它把浏览历史文档教程知识库3个技巧解决TiDB连接查询陷阱子查询/左连接/内连接异常结果全解析3个技巧解决TiDB连接查询陷阱子查询/左连接/内连接异常结果全解析 TiDB 是一个分布式关系型数据库兼容 MySQL 协议提供水平扩展能力支持高并发数据库分布式数据库后端OLAP漏洞标题[高危] 连接队列整数溢出漏洞漏洞标题 高危 连接队列整数溢出漏洞 影响版本v1.1及以下 测试环境Ubuntu 20.04 x86_64 复现步骤 1. 启动endlessh m网络安全运维上一篇如何自定义你的MacBook Pro触控栏MTMR配置文件完全指南下一篇探索更稳健的Json解析GsonFactory深度剖析与应用推荐创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →