<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>PAWS on Zero Day Notes</title>
        <link>https://blog.5772447.xyz/tags/paws/</link>
        <description>Recent content in PAWS on Zero Day Notes</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh</language>
        <lastBuildDate>Sun, 26 Jul 2026 07:00:00 +0800</lastBuildDate><atom:link href="https://blog.5772447.xyz/tags/paws/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>开启 tcp_tw_recycle 后，NAT 后方部分用户为何间歇性连不上？一次 SYN 被内核静默丢弃的排查记录</title>
            <link>https://blog.5772447.xyz/posts/3a0c5c5f/</link>
            <pubDate>Sun, 26 Jul 2026 07:00:00 +0800</pubDate>
            <guid>https://blog.5772447.xyz/posts/3a0c5c5f/</guid>
            <description>&lt;h2 id=&#34;问题背景&#34;&gt;&lt;a href=&#34;#%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;问题背景&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;上线一个新的对外 API 网关后，我们在压测阶段观察到服务器上 TIME_WAIT 连接常年维持在数万条，运维同事担心占满端口、拖慢回收，便照着网上一篇&amp;quot;TCP 内核调优&amp;quot;的老帖子，把 &lt;code&gt;net.ipv4.tcp_tw_recycle&lt;/code&gt; 和 &lt;code&gt;tcp_tw_reuse&lt;/code&gt; 一并设成了 1，还随手写进了 &lt;code&gt;/etc/sysctl.conf&lt;/code&gt; 持久化。改完当时压测正常，TIME_WAIT 数字确实降了下来，大家皆大欢喜。&lt;/p&gt;&#xA;&lt;p&gt;可上线一周后，客服陆续转来投诉：一部分用户反映 App 首页&amp;quot;时好时坏&amp;quot;,有时刷不出来、有时秒开；同一个人换个 4G 网络又正常了。奇怪的是这批用户高度集中在几家大企业和连锁门店——也就是&amp;quot;很多人共用一个出口公网 IP&amp;quot;的场景。这类间歇性、只影响部分人群的故障,是运维里最难缠的一种。&lt;/p&gt;&#xA;&lt;h2 id=&#34;故障现象&#34;&gt;&lt;a href=&#34;#%e6%95%85%e9%9a%9c%e7%8e%b0%e8%b1%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;故障现象&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;梳理出来的现象有几个鲜明特征：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;故障&lt;strong&gt;只影响一部分客户端&lt;/strong&gt;,且这批客户端往往来自同一批出口 IP(企业 NAT、运营商大网 NAT）后方；家庭宽带、单用户手机热点几乎不受影响。&lt;/li&gt;&#xA;&lt;li&gt;表现为&lt;strong&gt;建连阶段就失败&lt;/strong&gt;：App 侧报连接超时,浏览器 F12 里请求长时间 pending 后 &lt;code&gt;ERR_CONNECTION_TIMED_OUT&lt;/code&gt;,而不是 5xx 或 RST——说明请求根本没被应用处理。&lt;/li&gt;&#xA;&lt;li&gt;同一出口 IP 下,&lt;strong&gt;有的连接成功、有的失败&lt;/strong&gt;,失败呈间歇性,重试几次往往能连上。&lt;/li&gt;&#xA;&lt;li&gt;服务器端 CPU、内存、连接数、后端应用日志全都正常,应用层完全&amp;quot;无感&amp;quot;——它压根没收到这些请求。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在服务器上抓包 &lt;code&gt;tcpdump -i any &#39;tcp[tcpflags] &amp;amp; tcp-syn != 0&#39;&lt;/code&gt; 时看到诡异一幕：客户端的 SYN 明明到了网卡,服务器却&lt;strong&gt;不回 SYN-ACK,也不回 RST,直接吞掉&lt;/strong&gt;。客户端只能傻等,直到超时。&lt;/p&gt;&#xA;&lt;h2 id=&#34;排查过程&#34;&gt;&lt;a href=&#34;#%e6%8e%92%e6%9f%a5%e8%bf%87%e7%a8%8b&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;排查过程&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;第一步:确认丢包发生在内核而非应用。&lt;/strong&gt; 既然抓包能看到 SYN 进来、应用却没日志,说明内核在 TCP 握手前就把包丢了。查 &lt;code&gt;netstat -s&lt;/code&gt; 的 TCP 统计,发现两个计数器在持续快速增长：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ netstat -s | grep -i -E &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;passive|timestamp|SYN&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ... passive connection rejections because of time stamp&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ... packets rejects in established connections because of timestamp&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;packets rejects ... because of timestamp&lt;/code&gt; 这一句直接点题——内核因为&lt;strong&gt;时间戳检查&lt;/strong&gt;拒绝了数据包。这把矛头指向了 TCP 时间戳(RFC 1323)和 PAWS 机制。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第二步:锁定 PAWS + tcp_tw_recycle 的组合。&lt;/strong&gt; 复习一下机制:开启 &lt;code&gt;tcp_timestamps&lt;/code&gt; 后,内核用 PAWS(Protect Against Wrapped Sequences)防止序列号回绕,会记录每个连接对端的时间戳,拒收时间戳比已记录值更小的报文。&lt;/p&gt;&#xA;&lt;p&gt;关键在于 &lt;code&gt;tcp_tw_recycle&lt;/code&gt;:它开启后,为了快速回收 TIME_WAIT,内核会把&amp;quot;最近见过的时间戳&amp;quot;&lt;strong&gt;按对端 IP(per-host)缓存到路由项&lt;/strong&gt;里,而不是 per-connection。于是问题来了——&lt;strong&gt;当多个真实客户端藏在同一个 NAT 出口 IP 后面&lt;/strong&gt;,它们各自的开机时间不同、时间戳时钟互相独立。服务器只认这个源 IP 上&amp;quot;最后见过的最大时间戳&amp;quot;,一旦某个客户端的时间戳恰好小于另一个客户端刚刚上报的值,它的 SYN 就会被 PAWS 判定为&amp;quot;旧包&amp;quot;而&lt;strong&gt;静默丢弃&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;这完美解释了所有现象:NAT 后方用户共享 IP → 时间戳乱序 → 部分 SYN 被丢 → 间歇性、只影响 NAT 群体、应用无感。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第三步:实验验证。&lt;/strong&gt; 拿两台开机时间差异大的测试机,配到同一台 NAT 网关后共享出口 IP,交替向服务器发起连接,服务器端 &lt;code&gt;passive connection rejections because of time stamp&lt;/code&gt; 计数器应声跳动,故障稳定复现。反之临时执行 &lt;code&gt;sysctl -w net.ipv4.tcp_tw_recycle=0&lt;/code&gt; 后,同样的交替连接全部成功,计数器不再增长。根因坐实。&lt;/p&gt;&#xA;&lt;h2 id=&#34;解决方案&#34;&gt;&lt;a href=&#34;#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;解决方案&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;止血非常直接——&lt;strong&gt;关闭 tcp_tw_recycle&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;4&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 立即生效&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sysctl -w net.ipv4.tcp_tw_recycle&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 从 /etc/sysctl.conf 删除或注释该行,防止重启复活&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# net.ipv4.tcp_tw_recycle = 1   &amp;lt;-- 删掉&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sysctl -p&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;关闭后计数器立刻停止增长,NAT 后方用户投诉当天清零。&lt;/p&gt;&#xA;&lt;p&gt;那当初想解决的 TIME_WAIT 问题怎么办?正确姿势是:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;tcp_tw_reuse=1&lt;/code&gt; 可以保留&lt;/strong&gt;:它只影响**主动发起连接的一方(客户端角色)**复用 TIME_WAIT 端口,基于 per-connection 时间戳判断,对 NAT 场景安全。&lt;/li&gt;&#xA;&lt;li&gt;服务端作为&lt;strong&gt;被连接方&lt;/strong&gt;,TIME_WAIT 本就该由内核自然回收,靠调大 &lt;code&gt;ip_local_port_range&lt;/code&gt;、缩短 &lt;code&gt;tcp_fin_timeout&lt;/code&gt; 等参数远比 &lt;code&gt;tw_recycle&lt;/code&gt; 稳妥。&lt;/li&gt;&#xA;&lt;li&gt;真正要削减 TIME_WAIT,治本是&lt;strong&gt;上游用长连接/连接池&lt;/strong&gt;(如 Nginx &lt;code&gt;upstream keepalive&lt;/code&gt;),从源头减少短连接数量。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;需要说明:&lt;code&gt;tcp_tw_recycle&lt;/code&gt; 因为这个坑,已在 &lt;strong&gt;Linux 4.12 内核彻底移除&lt;/strong&gt;。如果你还在维护 3.x/4.4 一类老内核(CentOS 7 等),务必检查这个参数。&lt;/p&gt;&#xA;&lt;h2 id=&#34;根因分析&#34;&gt;&lt;a href=&#34;#%e6%a0%b9%e5%9b%a0%e5%88%86%e6%9e%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;根因分析&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;根因是&lt;strong&gt;对内核参数语义的误解 + NAT 环境的水土不服&lt;/strong&gt;。&lt;code&gt;tcp_tw_recycle&lt;/code&gt; 的时间戳缓存是 per-host 而非 per-connection,这在&amp;quot;一个 IP = 一个主机&amp;quot;的年代尚可,但在 NAT/CGNAT 普及的今天,&amp;ldquo;一个 IP 后面藏着成百上千台时钟各异的主机&amp;quot;是常态,per-host 假设彻底崩塌,直接导致合法 SYN 被误杀。而丢包发生在内核 PAWS 检查阶段、不落应用日志、只打薄薄一个 netstat 计数器,隐蔽性极强,是典型的&amp;quot;沉默杀手&amp;rdquo;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;预防措施&#34;&gt;&lt;a href=&#34;#%e9%a2%84%e9%98%b2%e6%8e%aa%e6%96%bd&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;预防措施&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;不要照抄网上的&amp;quot;内核调优大全&amp;quot;&lt;/strong&gt;:每个 sysctl 参数都要理解语义和适用边界再上线,尤其是涉及 TCP 状态机的。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;明确区分 &lt;code&gt;tcp_tw_reuse&lt;/code&gt; 与 &lt;code&gt;tcp_tw_recycle&lt;/code&gt;&lt;/strong&gt;:前者(客户端复用,安全)可用,后者(NAT 杀手)禁用;新内核已无 recycle,老内核务必置 0。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;把 &lt;code&gt;netstat -s&lt;/code&gt; 纳入监控&lt;/strong&gt;:对 &lt;code&gt;passive connection rejections because of time stamp&lt;/code&gt;、&lt;code&gt;packets rejects ... because of timestamp&lt;/code&gt; 等计数器做增长告警,这类内核级丢包是应用监控的盲区。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;变更留痕 + 灰度&lt;/strong&gt;:sysctl 调整应走变更流程、小范围灰度并观察 NAT 客户群指标,而非全量直接改。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;削减 TIME_WAIT 走正道&lt;/strong&gt;:优先长连接/连接池,而不是靠激进的 TIME_WAIT 快速回收参数。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;总结&#34;&gt;&lt;a href=&#34;#%e6%80%bb%e7%bb%93&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;总结&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;这次故障的教训在于:一个看似人畜无害的&amp;quot;TCP 调优&amp;quot;参数,在 NAT 普及的真实网络里会把合法用户的 SYN 悄悄丢掉,且现象诡异、只打一个冷门计数器。排查的突破口是 &lt;code&gt;netstat -s&lt;/code&gt; 里那句&amp;quot;rejects because of timestamp&amp;quot;,它把问题从&amp;quot;网络玄学&amp;quot;一步拉回到 PAWS + per-host 时间戳缓存的确定性机制上。记住一条原则:&lt;strong&gt;&lt;code&gt;tcp_tw_recycle&lt;/code&gt; 在任何有 NAT 的环境都不要开&lt;/strong&gt;——而现在,几乎没有环境是没有 NAT 的。&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
