网络实战案例集
协议告诉你系统应该怎样工作,证据链告诉你它究竟在哪里偏离。九个生产故障,从现场现象一路追到内核队列、报文与应用。
案例索引
01TCP 半连接 / 全连接队列溢出握手像是成功了,应用却迟迟 accept 不到+
现现象
服务启动、流量突增或应用停顿时,大批客户端连接超时;有的客户端已经进入 ESTABLISHED,服务端业务线程却没有处理到连接。机器 CPU、内存甚至应用处理耗时都可能看似正常。
根根因
accept() 的连接塞满 accept queue;也可能是 SYN 洪泛或资源压力挤满 request queue。只调大 backlog 会延后爆点,却不会修复线程泄漏、事件循环卡顿或冷启动。理原理
listen(backlog) 的值还会被 net.core.somaxconn 封顶。证排查证据链
① 先看监听 socket 的 accept queue
$ ss -lnt '( sport = :8080 )' State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 129 128 0.0.0.0:8080 0.0.0.0:*
仅对监听 socket:Recv-Q 是等待 accept() 的当前连接数,Send-Q 是 accept queue 上限。二者持续贴近,不等于“网络慢”,而是应用没及时接走连接。它们不直接显示半连接队列。
② 分开看 SYN_RECV 与累计计数器的增量
$ ss -Hnt state syn-recv '( sport = :8080 )' | wc -l 317 $ nstat -az | egrep 'TcpExt(ListenOverflows|ListenDrops|TCPReqQFullDoCookies|TCPReqQFullDrop|SyncookiesSent)' TcpExtListenOverflows 842 TcpExtListenDrops 901 TcpExtTCPReqQFullDoCookies 56 TcpExtTCPReqQFullDrop 0
ListenOverflows 强烈指向 accept queue 溢出;ListenDrops 范围更宽,内存分配失败等也可能令它单独增加。计数按丢包/重试累计,不是失败连接数。SYN cookies 开启时,不能只拿 tcp_max_syn_backlog 推算半连接容量。
③ 把内核容量、应用 backlog 和线程现场对齐
$ sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog \
net.ipv4.tcp_abort_on_overflow
$ ps -L -p "$PID" -o pid,tid,stat,pcpu,wchan:24,comm
$ tcpdump -ni any -s 0 -w queue.pcap 'tcp port 8080'抓包用于确认 SYN/SYN+ACK/ACK 的重试时间线;同时查事件循环、线程栈、GC/停顿和 accept 速率。只抓物理口可能漏掉 Nginx→本机应用的 lo 流量。
界预防与参数边界
somaxconn 默认值在 Linux 5.4 起从 128 提高到 4096;最终仍以本机 sysctl 和应用实际 backlog 为准。tcp_abort_on_overflow=1 会把慢 accept 变成 RST,可能直接伤害客户端,只能在理解重试语义后采用,不是扩容开关。预防重点是监控队列利用率与溢出增量、控制启动突发,并修复 accept 延迟。参考来源:就是要你懂 TCP:半连接队列和全连接队列 · TCP 队列实战案例
↑ 返回顶部02RST 来源鉴别端点发的,还是 LB / 防火墙伪造的?+
现现象
连接在某条请求、某个响应大小或固定空闲时间后被 Connection reset by peer 中断。接收端抓到一个地址、端口和序号都“像对端”的 RST,但对端日志找不到关闭动作。
根根因
理原理
证排查证据链
① 两端同时抓完整流,而不是只抓 RST
# 客户端与服务端都执行;先校准时钟并记录抓包点 $ tcpdump -ni any -s 0 -vvv -w side-a.pcap \ 'host 203.0.113.20 and tcp port 443' # 快速巡检所有 RST $ tcpdump -ni any -s 0 -vvv \ 'tcp[tcpflags] & tcp-rst != 0'
若 RST 只在接收端出现、发送端抓包没有,而时钟和抓包范围可靠,链路中间注入的可能性显著上升。双端都看到则继续查主机内核与应用。
② 导出旁证字段,和同方向正常包比较
$ tshark -r side-a.pcap -Y 'tcp.flags.reset == 1' -T fields \ -e frame.time_epoch -e ip.src -e ip.dst -e ip.ttl -e ip.id \ -e eth.src -e tcp.srcport -e tcp.dstport -e tcp.seq -e tcp.ack
| 字段 | 正确读法 |
|---|---|
| TTL | 与同方向正常包明显不同才是线索;初始 TTL 只能猜测,不能单独定案。 |
| IPv4 ID | 序列突变可提示伪造,但它也可能为 0、随机或按流生成;IPv6 没有该字段。 |
| Seq / Ack | 确认是否落入接收窗口。比较两端时关闭相对序号显示,或统一换算。 |
| 源 MAC | 只代表当前抓包点相邻二层发送者;经过路由后不再是原始发送主机。 |
③ 主机内核计数与设备日志交叉验证
$ nstat -az | egrep 'TcpOutRsts|TcpExtTCPAbortOn(Data|Close|Memory|Timeout|Linger)'
$ traceroute 203.0.113.20
# 再按时间、五元组查询 LB / 防火墙 / IPS 日志TcpOutRsts 表示本机内核尝试发送 RST,不保证已经上线路,也不能直接定位进程。固定空闲时长、固定内容或响应大小,是查询中间设备策略的关键条件。
界预防与参数边界
SO_LINGER {on,0} 是 abortive close:可能丢弃未发送数据并发 RST,不是普通的“快速关闭”。生产环境应统一 LB/NAT 空闲超时与连接池寿命,并保留可按五元组检索的策略日志。参考来源:到底是谁 reset 了你的连接 · iptables 监控 reset 的连接信息
↑ 返回顶部03PMTUD 黑洞与隧道 MTU小包能通,大文件或 TLS 握手却卡死+
现现象
ping、TCP 握手和小请求正常;SCP、大响应或某个 TLS 报文开始后,同一大段不断重传,小 ACK/keepalive 仍能通过。容器、VPN、VXLAN/GRE/IPsec 场景尤其常见。
根根因
理原理
证排查证据链
① 先画实际出接口与每层 MTU
$ ip route get 203.0.113.20
$ ip -d link show
$ tracepath 203.0.113.20
# 重点对比 pod eth0 / veth / bridge / tunnel / 物理口VXLAN、GRE、IPsec 等封装会占用额外字节。只看容器内的 1500,不能排除宿主或底层网络只有 1450/1400。
② 用 DF ping 二分可通过的 payload
$ ping -4 -M do -s 1472 -c 3 203.0.113.20 From 192.0.2.1: icmp_seq=1 Frag needed and DF set (mtu = 1450) $ ping -4 -M do -s 1422 -c 3 203.0.113.20 64 bytes from 203.0.113.20: icmp_seq=1 ttl=52 time=18.4 ms
-s 是 ICMP payload,不是 IP 总长:无 IPv4 选项时约再加 28 字节;IPv6 约加 48 字节。远端禁 Echo 时以 tracepath 和实际 TCP 证据为准。
③ 多点抓包找“最后出现、首次消失”的位置
$ tcpdump -ni any -s 0 -vvv -w mtu.pcap \
'icmp or icmp6 or (host 203.0.113.20 and tcp port 443)'
# IPv4 关键反馈:ICMP type 3 / code 4
# IPv6 关键反馈:ICMPv6 type 2 Packet Too Big宿主抓包受 TSO/GSO/GRO 影响,可能显示超过 MTU 的大 skb 或假 checksum error;这不等于线上真的发出了巨帧,应在隧道外或外部抓包点复核。
界预防与参数边界
# 仅示意转发边界;上线前审计方向、表和回滚 iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \ -j TCPMSS --clamp-mss-to-pmtu # TCP PLPMTUD:0 关闭;1 检测到 ICMP 黑洞后启用;2 始终启用 sysctl net.ipv4.tcp_mtu_probing net.ipv4.tcp_base_mssMSS clamp 只改 TCP SYN 中的 MSS,修不了 UDP/QUIC,也不能替代 ICMPv6。模式 2 以
tcp_base_mss 为初始 MSS;tcp_mtu_probing 仅覆盖 TCP,不是全协议修复。04DNS resolver 路径分叉dig 正常,应用却解析失败或去了错误地址+
现现象
dig name @server 能得到正确答案,ping、curl 或业务进程却报 unknown host、命中另一个地址,甚至没有任何 53 端口流量。加上或去掉域名末尾的点,耗时和答案又变了。
根根因
/etc/hosts、本地缓存、systemd-resolved 或 glibc stub resolver;也可能由 JVM、Go、musl、c-ares 自己实现。rotate、search/ndots 和负缓存会继续放大差异。理原理
dig 是直接构造 DNS 查询的诊断工具,而多数原生应用调用 getaddrinfo(),其数据源与顺序由 NSS 和运行库决定。证排查证据链
① 先复现“应用路径”,再对照 DNS 工具
$ getent ahosts api.example.internal
192.0.2.40 STREAM api.example.internal
$ dig api.example.internal
$ dig api.example.internal. @192.0.2.53
# 末尾点号表示绝对域名,避免附加 search 后缀getent ahosts 更接近应用的 getaddrinfo()。getent hosts、nslookup 与 dig 也不应被当成完全等价的实现。
② 沿 NSS 配置逐站核对
$ grep '^hosts:' /etc/nsswitch.conf hosts: files myhostname resolve [!UNAVAIL=return] dns $ grep -n 'api.example.internal' /etc/hosts $ readlink -f /etc/resolv.conf /run/systemd/resolve/stub-resolv.conf $ sed -n '1,80p' /etc/resolv.conf search svc.cluster.local cluster.local options ndots:5 timeout:2 attempts:2 rotate
rotate 会轮换 nameserver 的起点,不是“第一台失败再切换”的同义词。ndots:n 决定先尝试原名还是先附加 search;后缀越多、重试越多,单次逻辑查询可能膨胀成很多网络查询。
③ 用系统调用和抓包确认“有没有发、发给谁”
$ strace -f -tt -s 200 \ -e trace=openat,connect,sendto,recvfrom,poll,ppoll \ curl -v https://api.example.internal/ $ tcpdump -ni any -s 0 -vvv 'port 53'
若先从 hosts/缓存返回,抓不到 DNS 包是正常现象;若请求发给 127.0.0.53,继续查本地代理的上游。抓包时同时看 A/AAAA、EDNS OPT、RCODE、TC 位、重试间隔和是否 TCP fallback。
④ 分清权威链与本机路径
$ dig +trace api.example.com
# 适合检查委派链;不会重现 hosts、NSS、缓存、search
# 或应用内置 resolver 的行为界预防与参数边界
dig +trace 成功不代表业务解析链健康。参考来源:就是要你懂 DNS:域名解析相关问题 · nslookup OK but ping fail · EDNS 不兼容案例
↑ 返回顶部05Kubernetes DNS 典型约 5 秒A / AAAA + ClusterIP DNAT + conntrack 的历史竞态+
现现象
Pod 内解析偶发多出一段接近 5 秒的等待,重试后成功;抓包可能看到 A 与 AAAA 同时发出,却只收到其中一个响应。它不是 DNS 协议规定的“固定 5 秒”,只是 glibc 默认超时常产生的典型形状。
根根因
理原理
connect() 本身不发包,A/AAAA 两个报文可能用相同源端点几乎同时穿过 Service DNAT;连接跟踪要为转换后的流建立无冲突状态,竞态失败就可能丢掉后插入的一条。证排查证据链
① 先量出等待发生在哪个系统调用
$ cat /etc/resolv.conf
nameserver 10.96.0.10
search app.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
$ strace -f -tt -s 200 \
-e trace=connect,sendto,sendmmsg,recvfrom,poll,ppoll \
getent ahosts redis.app.svc.cluster.local
12:00:00.101 sendmmsg(... A ..., ... AAAA ...) = 2
12:00:00.104 recvfrom(... AAAA response ...) = 141
12:00:00.105 poll(..., 5000) = 0 (Timeout)等待值可被 options timeout:、libc、Go/JVM 或应用超时修改;所以正文只称“典型约 5 秒”。
② 在 Pod、Node 与 CoreDNS 侧对同一事务多点抓包
$ tcpdump -ni any -s 0 -vvv -w dns.pcap 'udp port 53'
$ tshark -r dns.pcap -Y dns -T fields \
-e frame.time_epoch -e ip.src -e udp.srcport \
-e dns.id -e dns.qry.name -e dns.qry.type -e dns.flags.response
# QTYPE 1=A,28=AAAA;按 dns.id 和源端口配对问答只在 Pod 抓到“一问无答”不够;要确认请求进入节点后是否经过 DNAT、CoreDNS 是否收到,以及响应在哪一段消失。
③ 用 conntrack 增量区分“插入冲突”与“表满”
$ conntrack -S
cpu=0 found=... insert=... insert_failed=37 drop=...
$ sysctl net.netfilter.nf_conntrack_count \
net.netfilter.nf_conntrack_max
$ dmesg -T | grep -i 'nf_conntrack.*table full'insert_failed 在复现窗口同步增加,才支持插入冲突假设。nf_conntrack_count 接近 max 且出现 table full 是容量耗尽;两者不能混称“conntrack 表满”。
④ 同时排除更常见的 5 秒来源
ndots:5+ 多个 search 域扩增查询;- CoreDNS CPU/队列过载或上游超时;
- 网络只丢 AAAA 响应、MTU/分片问题;
- 应用连接超时恰好也是 5 秒,而 DNS 已经很快完成。
界预防与参数边界
single-request(2.10+)改为串行,single-request-reopen(2.9+)在同一查找的 A/AAAA 同端口应答异常时关闭旧 socket 再发第二问,但并非所有 libc 支持。它们可作为经验证的缓解,不是跨平台标准。NodeLocal DNSCache 能减少 Pod→Service DNAT/UDP conntrack 路径,通常降低相关竞态概率,但不保证治好所有 5 秒。优先升级已修复相关竞态的内核/平台,并保留查询延迟、CoreDNS 和 conntrack 指标。参考来源:Docker 中的 DNS 解析过程 · Kubernetes service 与 kube-proxy
↑ 返回顶部06分层丢包定位NIC ring → softnet → netfilter → socket → listen+
现现象
应用超时、重传或吞吐下降;tcpdump 有时能看到请求,应用却没收到,有时连 tcpdump 都抓不到。笼统地“调大 buffer”后,问题可能短暂缓解,也可能只是把丢包变成长排队。
根根因
理原理
证排查证据链
① 从最靠近硬件的一层开始
$ ip -s -s link show dev eth0 $ ethtool -g eth0 $ ethtool -S eth0 | egrep -i 'drop|miss|over|fifo|error|no.?buf' $ cat /proc/interrupts $ cat /proc/softirqs | grep NET_RX
ethtool -S 字段由驱动定义,rx_missed_errors、rx_no_buffer 等并非每块网卡都有。先查驱动文档,再解释哪个环节增加。
② 看每 CPU softnet 队列
$ head -4 /proc/net/softnet_stat 0004f938 00000000 00000000 ... 0012a7b1 0000002a 00000019 ... # 每行对应一个 CPU,字段为十六进制 # 前三列:processed / backlog 满而 dropped / budget 或时间用尽的 time_squeeze
第二列增长才支持调整 net.core.netdev_max_backlog;第三列增长提示 NAPI 一轮预算/时间不足,可再结合 net.core.netdev_budget、IRQ 分布和 CPU si%。不要硬背后续列,它们随内核演进。
③ 到协议栈、socket 与应用消费速度
$ nstat -az | egrep 'Udp(InErrors|RcvbufErrors)|TcpExt(TCPRcvQDrop|TCPBacklogDrop|TCPMemoryPressures|IPReversePathFilter)' $ ss -tinm '( sport = :8080 )' skmem:(r123456,rb131072,t0,tb2626560,f0,w0,o0,bl0,d18)
建立连接的 Recv-Q 是应用未读取的字节,不等于已经丢包。ss -m 中 r 接近 rb 表示接收缓冲吃紧,d 是 socket drop;还要确认应用线程是否在及时读取。
④ 最后核对状态防火墙与监听队列
$ sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max $ conntrack -S $ dmesg -T | grep -i conntrack $ ss -lnt '( sport = :8080 )' $ nstat -az | egrep 'TcpExtListen(Overflows|Drops)'
⑤ 正确理解抓包点
tcpdump 抓到入站包,只能证明它到达 packet tap;包仍可能在 netfilter、路由、TCP 或 socket 后续被丢。反过来,NIC ring 满、XDP drop 或 tap 之前的丢包可能完全抓不到。多点抓包要找“最后出现”和“首次消失”,而不是用一个抓包点证明端到端送达。
界预防与参数边界
参考来源:网络丢包 · Linux Network Stack · nginx 性能和软中断
↑ 返回顶部07用 ss -ti 读 TCP 慢问题cwnd、RTT、RTO、ssthresh、BDP 与零窗口+
现现象
链路标称 1 Gbit/s,单连接却只有几 Mbit/s;或业务时快时慢、Send-Q 堆积。ping 看起来尚可,盲目放大 socket buffer 或更换拥塞算法后也未必改善。
根根因
cwnd、对端接收窗口、发送缓冲任一项限制;高 RTT 令窗口增长和丢包恢复变慢。还可能是应用供数不足、对端不及时读取形成零窗口、pacing 限速,或真正的丢包/RTO。理原理
min(cwnd×MSS, 对端接收窗口, 可用发送缓冲) ÷ RTT;BDP 是跑满链路所需的目标在途量,不是吞吐保证。证排查证据链
① 对同一连接连续采样,而非只截一帧
$ ss -tinm '( dst 203.0.113.20 and dport = :443 )' ESTAB 0 524288 192.0.2.10:49152 203.0.113.20:443 cubic wscale:7,7 rto:420 rtt:82.1/11.4 mss:1448 cwnd:10 ssthresh:18 bytes_sent:... bytes_acked:... retrans:0/7 send 1.4Mbps pacing_rate 2.8Mbps delivery_rate 1.2Mbps skmem:(r0,rb131072,t0,tb2626560,f0,w524288,o0,bl0,d0)
| 字段 | 怎么读 |
|---|---|
| rtt:a/b | 平滑 RTT / RTT 方差,单位 ms;82 ms 链路上 cwnd=10、MSS=1448,仅约 14 KiB 在途。 |
| rto | 重传超时估计,单位 ms。没有丢包时它不参与正常发送,不能仅凭“大”判为根因。 |
| cwnd | 通常以 segment 计,乘当前 MSS 才近似换算为字节。 |
| ssthresh | 慢启动与拥塞避免的分界,不是永久吞吐上限;cwnd 之后仍可线性增长。 |
| send / delivery_rate | 内核估算,需连续采样并和 bytes_acked、应用吞吐对照。 |
② 用 BDP 检查窗口量级
# 100 Mbit/s × 80 ms = 8 Mbit ≈ 1 MB BDP $ python3 -c 'print(100e6/8*0.080)' 1000000.0 # 若 cwnd × MSS 只有约 14 KiB,就不可能仅靠该连接跑满 100 Mbit/s
仍要同时确认对端通告窗口、窗口扩大协商、发送/接收缓冲和应用是否持续供数。BDP 只给量级,不说明为什么窗口小。
③ 区分快重传、超时与 loss probe
$ nstat -az | egrep 'TcpRetransSegs|TcpExt(TCPTimeouts|TCPLossProbes)'
TcpRetransSegs 1842
TcpExtTCPTimeouts 97
TcpExtTCPLossProbes 63
$ tcpdump -ni any -s 0 -w slow.pcap 'host 203.0.113.20 and tcp port 443'计数器要看流量归一化后的增量。抓包再区分重复 ACK/SACK 后的快速恢复、等待 RTO 后的重传、乱序,以及抓包点丢包造成的“伪重传”。
④ 识别零窗口:网络没堵,是接收应用没读
$ tshark -r slow.pcap \
-Y 'tcp.analysis.zero_window || tcp.analysis.zero_window_probe' \
-T fields -e frame.time_relative -e ip.src -e tcp.window_size_value
$ ss -o state established '( dst 203.0.113.20 )'
# timer:persist 表示发送端正在做零窗口探测发送端常见 Send-Q 堆积;接收端则可能看到 Recv-Q 或 skmem r 接近 rb。根因通常回到接收应用消费慢,而非扩大拥塞窗口。
界预防与参数边界
SO_SNDBUF/SO_RCVBUF 反而可能关闭或限制自动调优。参考来源:就是要你懂 TCP:性能问题 · TCP 窗口与 Buffer · TCP 传输速度案例
↑ 返回顶部08TIME_WAIT、CLOSE_WAIT 与临时端口先分清“谁在等谁”,再谈连接耗尽+
现现象
ss -s 显示成千上万 TIME_WAIT 或 CLOSE_WAIT;代理向单一上游建立新连接时报 Cannot assign requested address,服务重启则可能报 Address already in use。团队把它们统称为“端口不够”。
根根因
- TIME_WAIT:通常是本端主动关闭,短连接模型令相同方向的状态大量积累;它本身是可靠关闭与旧报文隔离机制。
- CLOSE_WAIT:本端已经收到 FIN,正在等本地应用 close;短暂出现正常,持续增长才强烈指向生命周期泄漏或线程卡住。
- 临时端口耗尽:常发生在“单一本地 IP → 单一目标 IP:Port”的四元组空间,或代理提前
bind(ip,0)让端口无法跨目标复用。
理原理
证排查证据链
① 按状态、进程与对端聚合,而不是只看总数
$ ss -s
$ ss -Htan | awk '{s[$1]++} END {for (k in s) print k,s[k]}' | sort
$ ss -Htanp state close-wait | head
$ ss -Htan | awk '$1=="TIME-WAIT"{print $5}' | sort | uniq -c | sort -nr | head
# 注意:ss 加 state 过滤后会省略 State 列,届时对端地址在 $4 而非 $5先确认哪一侧主动关闭、集中到哪些目标、由哪个进程持有。TIME_WAIT 出现在服务器并不自动等于异常,也可能是服务器主动关闭大量短连接。
② 核对可分配端口与保留区间
$ sysctl net.ipv4.ip_local_port_range net.ipv4.ip_local_reserved_ports net.ipv4.ip_local_port_range = 32768 60999 $ ss -Htan state established '( dst 203.0.113.20 and dport = :443 )' | wc -l $ ss -Htan state time-wait '( dst 203.0.113.20 and dport = :443 )' | wc -l
直接 connect() 在单目标空间耗尽时常返回 EADDRNOTAVAIL;显式 bind() 冲突更常见 EADDRINUSE,但语言框架可能改写错误文本。
③ 不要漏掉文件描述符和应用 close 路径
$ cat /proc/$PID/limits | grep 'open files' $ find /proc/$PID/fd -maxdepth 1 -type l 2>/dev/null | wc -l $ ss -Htanp state close-wait '( sport = :8080 )'
CLOSE_WAIT 的关键不是 TCP 参数,而是找到没有走到 close 的代码、阻塞线程或失效的连接池回收逻辑。
④ 检查代理是否过早占用源端口
Linux 4.2+ 提供 IP_BIND_ADDRESS_NO_PORT:先绑定源地址但推迟到 connect() 时才选端口,使相同源端口在四元组不同的连接间复用。它适用于必须先选源 IP 的代理,不是所有客户端的必需选项。
界预防与参数边界
tcp_fin_timeout控制孤儿FIN_WAIT_2,不控制 TIME_WAIT。tcp_max_tw_buckets是 DoS 防护上限,调低会直接销毁 TIME_WAIT socket,不是常规优化。tcp_tw_reuse在现代内核是整数 0/1/2,旧内核默认值与语义可能不同;不要给跨版本统一“设 1”的建议。SO_REUSEADDR/SO_REUSEPORT主要影响 bind/listen 语义,不是客户端临时端口耗尽的通用修复。
参考来源:就是要你懂 TCP:握手和挥手 · 一台服务器最多能创建多少 TCP 连接
↑ 返回顶部09tcp_tw_recycle + NAT + PAWS“参数越激进越好”的历史反面教材+
现现象
旧 Linux 服务端在 NAT/LVS 后出现概率性 SYN 无响应:同一公网出口下有些客户端能连、有些不能,停流量约一分钟后又恢复;端口监听、路由和防火墙规则均正常。
根根因
net.ipv4.tcp_tw_recycle=1 后,为加速 TIME_WAIT 回收而使用按远端地址缓存的 timestamp/PAWS 假设。NAT 后多个真实客户端共享一个公网源 IP,但它们的 TCP TSval 不保证跨主机单调,合法 SYN 因而被当成旧报文拒绝。理原理
证排查证据链
① 先确认它确实是老内核和旧参数
$ uname -r 3.10.0-1160.el7.x86_64 $ sysctl net.ipv4.tcp_tw_recycle net.ipv4.tcp_timestamps net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_timestamps = 1 # Linux 4.12+ 应返回参数不存在;不要凭空“创建”它
② 看 PAWS 被动拒绝计数的故障窗口增量
$ nstat -az | grep -i paws
TcpExtPAWSPassive 960535
$ netstat -s | grep -i 'passive connections rejected because of time stamp'
960535 passive connections rejected because of time stamp计数器名称和展示文本随内核/iproute2 版本变化,应以本机 nstat -az 实际输出为准。历史累计值很大没有意义,必须与失败请求同步增长。
③ 把 NAT 后的 SYN timestamp 与重试时间线对齐
$ tcpdump -ni any -s 0 -vvv -w paws.pcap \ 'host 198.51.100.40 and tcp[tcpflags] & tcp-syn != 0' $ tshark -r paws.pcap -Y 'tcp.flags.syn == 1' -T fields \ -e frame.time_epoch -e ip.src -e tcp.srcport \ -e tcp.options.timestamp.tsval -e tcp.analysis.retransmission
服务端看到相同源 IP 的不同连接携带回退的 TSval,随后该 SYN 无 SYN+ACK,并与 PAWSPassive 增量重合,才形成完整证据。仅看到 SYN 重传还可能是队列、ACL 或普通丢包。
界预防与参数边界
tcp_tw_recycle 在 Linux 4.12 被删除;现代内核里它不是“默认关闭但还能打开”,而是根本不存在。老系统的正确处理是关闭该参数并计划升级。不要用关闭 tcp_timestamps、强行缩短 TIME_WAIT 或 SO_LINGER=0 来掩盖问题。tcp_tw_reuse 是另一项功能,删除 recycle 不等于删除 reuse。这起事故的通用教训是:任何减少状态保留的优化,都必须验证它依赖的唯一性假设是否会被 NAT、LB 和多路径打破。参考来源:为什么你的 SYN 包被丢:net.ipv4.tcp_tw_recycle
↑ 返回顶部