Linux 网络问题系统化排查方案
一、问题背景
现象:
- 客户端上看:到某服务器端口的
ESTABLISHEDTCP 连接大约有 3 万 - 服务器上看:对应的连接只有 3000 条左右
怀疑:大量“幽灵连接”只存在于客户端视角,服务器已经不认账,或者双方根本统计的不是同一类东西。
目标:给出一套系统化排查步骤,澄清:
- 两边是不是在数同一种连接
- 如果真有“客户端 ESTABLISHED、服务器没有”的连接,它们是怎么产生的
- 对应有哪些配置/架构上的解决方案
二、第一步:先把“数法”统一(修正 ss 用法)
这一步非常关键,否则后面全是玄学。
2.1 一律用 ss,不用 netstat
ss 比 netstat 更快更准确,语法也更强。我们约定:
- 只看 TCP:
-t - 不做域名解析:
-n - 过滤状态:
state established - 加上 IP/端口方向(
src/dst+sport/dport)
2.2 客户端:统计“到服务器 X:PORT 的 ESTABLISHED”
假设服务器是:
- 服务器 IP:
111.200.212.155 - 端口:
6449
在 客户端 上统计所有连到这个服务器端口的 ESTABLISHED:
1ss -tan 'state established and dst 111.200.212.155 and dport = :6449' | wc -l
说明:
dst 111.200.212.155:对端(远端)IPdport = :6449:对端端口 = 6449- 整个 filter 用单引号包住,避免被 shell 乱拆
也可以先不 wc -l,看具体连接:
1ss -tanp 'state established and dst 111.200.212.155 and dport = :6449'
加 -p 可以看到哪个进程开了连接。
2.3 服务器:统计“本地 6449 端口上的 ESTABLISHED”
在 服务器 上,监听端口是本地的“源端口”,所以要换成 sport:
1# 所有到本机 6449 的 ESTABLISHED 连接(不区分客户端 IP)2ss -tan 'state established and sport = :6449' | wc -l
如果你只关心某个客户端 IP,比如 10.0.0.123:
1ss -tan 'state established and src 10.0.0.123 and sport = :6449' | wc -l
总结一下端口方向:
- 在客户端看服务器:用
dport - 在服务器看自己监听端口:用
sport
2.4 避免的典型错误写法
错误示例(会触发 bison bellows 那种错误):
1ss -tan "111.200.212.155" dport = :6449 state established
问题:
111.200.212.155没有加src/dst前缀,语法非法- 多个 token 没用引号包起来,shell 和
ss都看不懂你想要啥
结论:所有复杂 filter 都老老实实放进单引号里,并注明 src/dst、sport/dport。
三、第二步:确认“幽灵连接”是否真的只存在客户端
有了统一的统计方法之后,开始验证“3w vs 3k”是不是事实。
- 在客户端查一条具体连接:
1ss -tan 'state established and dst 111.200.212.155 and dport = :6449' | head -5
拿其中一条,记住四元组:
client_ip:client_port -> 111.200.212.155:6449
- 在服务器上查这条连接:
1ss -tan "state established and src client_ip and sport = :6449"
再用 grep client_port 过滤一下:
1ss -tan 'state established and src client_ip and sport = :6449' | grep client_port
- 如果 能查到:这条并不是幽灵连接,说明差异更多来自“统计口径”
- 如果 查不到:这条就是典型“客户端认为 ESTABLISHED,但服务器没这条”的连接
重复几次,确认这种“只存在客户端的连接”是否普遍存在。
四、第三步:检查服务器是否被连接洪峰/队列溢出打爆
当连接建立得很快、服务器 accept 处理不过来时,会导致:
- SYN 队列 / accept 队列溢出
- 开启
syncookies - 部分连接半途掉在三次握手中
这会直接制造“客户自觉已连接,服务器没记住”的场景。
4.1 检查 listen 队列溢出统计
在服务器上执行:
1netstat -s | egrep -i 'listen|SYNs to LISTEN'
关注类似这些字段:
times the listen queue of a socket overflowedSYNs to LISTEN sockets dropped
如果数字 > 0,就说明你的监听队列确实被打爆过。
4.2 检查是否有 SYN 洪水日志(syncookies)
1dmesg | grep -i "possible SYN flooding"
看到类似:
possible SYN flooding on port 6449. Sending cookies.
说明 Linux 内核认为这个端口的 SYN 流量太猛,开始启用 syncookies,有些连接会“救”回来,有些会直接牺牲掉 ACK。
五、第四步:检查 conntrack/NAT 是否在中间“多记账”
如果你的路径中有:
- 本机 iptables 做 NAT
- 中间某节点是 Linux 防火墙 / 路由 / 负载均衡
它们会用 conntrack 跟踪“连接”,而 conntrack 的“连接数”经常比真实 socket 多。
在怀疑节点上看:
1cat /proc/sys/net/netfilter/nf_conntrack_count2cat /proc/sys/net/netfilter/nf_conntrack_max
- 如果
count接近甚至超过max,可能会导致丢包、状态错乱 - conntrack 里的很多
ESTABLISHED其实已经没有对应的 socket 了,只是在“等超时回收”
这类问题本质是“统计对象不同”:一个在看 socket,一个在看 conntrack entry,天然不会相等。
六、第五步:用 tcpdump 抓一条幽灵连接的握手
要搞清楚谁在撒谎,抓一条典型“幽灵连接”的包最靠谱。
在服务器上执行(换成真实 IP/端口):
1tcpdump -i eth0 "host client_ip and host 111.200.212.155 and port 6449" -vv
重点看:
- 客户端有没有发出三次握手的最后那个
ACK - 服务器有无返回
RST/FIN - 中间是否有包被某个设备吃掉(比如 ACK 没到服务器)
看到完整的 SYN → SYN+ACK → ACK,且之后服务器没 socket,那就多半是队列溢出 / syncookies 导致的握手异常。
七、对应解决方案整理
根据上面几类情况,解决方案可以分几层。
7.1 服务器侧:让“真正建立的连接”多起来
- 调大 backlog / SYN 队列
- 应用层 listen 调大 backlog(例如
listen(fd, 4096)) - 系统参数:
- 应用层 listen 调大 backlog(例如
1net.core.somaxconn = 40962net.ipv4.tcp_max_syn_backlog = 4096
- 记得
sysctl -p生效
- 提升 accept 处理吞吐
- 不要单线程 accept
- 用多进程/多线程/事件驱动模型(nginx、haproxy 已经做得不错)
- 确保
ulimit -n足够大,fd 不会先爆
- 合理使用 syncookies
- 不要关掉(防攻击)
- 但要通过加 backlog、优化应用,让它少被触发
7.2 网络侧:conntrack/NAT 调优
- 增大
nf_conntrack_max,避免 conntrack 表爆掉 - 为无关协议设置合理的超时时间,减少“尸体连接”
- 对确定不需要追踪的流量使用
NOTRACK,减轻 conntrack 压力
7.3 客户端 / 协议层:别堆一座“僵尸连接山”
- 应用层超时 + 心跳
- 连接建立后,若 T 秒内没有收到首条响应,主动关闭
- 长时间 idle 的连接定期发 ping/pong,不回应则超时断开
- TCP keepalive(辅助)
- 对长连接启用
SO_KEEPALIVE - 调低
TCP_KEEPIDLE、TCP_KEEPINTVL、TCP_KEEPCNT,让死连接不要挂几个小时
- 对长连接启用
- 减少连接总量
- 使用连接池/HTTP keepalive/HTTP/2/HTTP/3
- 限制单客户端对同一服务的最大并发连接数
- 合理规划
ip_local_port_range和ulimit -n
八、小结:排查流程 checklist
- 统一统计方式
- 两边都用
ss -tan 'state established and ...' - 客户端:
dst + dport;服务器:src + sport - 坚决不用“裸 IP + 乱拼 token”的写法
- 两边都用
- 验证“幽灵连接”是否真实存在
- 客户端拿一条连接四元组
- 服务器用同样四元组查,确认是否真查不到
- 检查服务器是否存在队列溢出 / SYN flood 迹象
netstat -s看 listen 队列溢出统计dmesg看possible SYN flooding日志
- 检查 conntrack/NAT 差异
- 明确你是在数 socket 还是在数 conntrack entry
- 看
nf_conntrack_countvsnf_conntrack_max
- 用 tcpdump 抓一条幽灵连接的握手过程
- 看 ACK 是否真正达到服务器
- 看服务器是否立即回 RST
- 根据结果调整
- backlog、accept 模型、conntrack 参数
- 客户端超时/心跳/连接池