使用 ss 快速识别半开连接,关键在于精准过滤连接状态:ss -ton state fin-wait-1、ss -ton state close-wait、ss -tan state syn-recv 都是常用命令;其中 orphaned 非零通常可以直接确认内核级 socket 泄漏,而 CLOSE-WAIT 和 FIN-WAIT-2 长时间卡住,则多半说明应用没有主动关闭连接。

怎么用 ss 快速识别半开连接
半开连接(例如 FIN-WAIT-1、CLOSE-WAIT、SYN-RECV)并不只是“还没彻底断开”这么简单,本质上是连接生命周期停留在中间状态,通常意味着对端出现异常,或者本端在关闭流程中没有处理完整。排查时也不要直接用 ss -t | grep CLOSE 这类模糊匹配,因为状态字段的位置并不固定,很容易漏掉目标连接,甚至产生误判。
ss -ton state fin-wait-1:只输出 FIN-WAIT-1 状态,配合-o可以查看超时剩余时间,判断是否已经超时但还未回收ss -ton state close-wait:如果 CLOSE-WAIT 大量堆积,说明本端收到 FIN 后没有调用close(),常见于应用read()返回 0 后忘记shutdownss -tan state syn-recv:SYN-RECV才是真正的半连接(三次握手未完成),如果持续超过 100,基本可以判断为 SYN Flood,或者net.core.somaxconn设置过小
为什么 ss -s 的 orphaned 值非零就是硬泄漏
ss -s 第一行的 orphaned 数值属于内核级指标:进程已经退出,但 socket 仍然挂在内核中没有释放。它与用户态可见的 Total 不重叠,也不受 TIME-WAIT 影响。只要 orphaned > 0,基本就可以确认存在泄漏,这不是配置问题,而是代码缺陷。
- 常见原因:子进程继承了父进程的 socket fd 却没有显式
close;信号中断导致 cleanup 被跳过;使用了fork()但没有在子进程中关闭监听 socket - 验证方式:通过
cat /proc/net/sockstat查看sockets: used和orphan行,并对比它们是否随时间持续增长 - 注意:
net.ipv4.tcp_max_orphans只是保护阈值,设置得过高只会延迟 OOM,不能从根本上解决问题
如何区分半开连接是攻击还是程序 bug
SYN-RECV 升高并不一定就是被攻击,需要结合来源 IP 分布和连接持续时间一起判断:
- 如果
ss -tan state syn-recv输出中的源 IP 非常分散(几十到上百个不同 C 段),并且每个连接的存活时间接近 60–120 秒(内核默认tcp_synack_retries的超时周期),大概率是 SYN Flood - 如果源 IP 集中在一两个内网段,而且连接存在时间很短(<5 秒)却反复出现,更可能是客户端异常断连,再加上服务端没有设置
SO_LINGER或没有正确处理read() == 0 - 使用
ss -tan state syn-recv -i查看rto和retrans:如果重传次数大于 3 且 RTO 持续增大,说明网络层丢包比较严重,不是单纯的协议栈问题
排查时最容易忽略的两个点
先看两个经常被忽略的细节:第一,ss -t 默认只查看 TCP,不会把 UDP 带出来,而不少“半开”现象其实更容易出现在 UDP 场景中,比如 DNS 请求发出后长时间没有响应,既不重试,也没有清理;第二,所有状态过滤的写法都必须严格使用小写加连字符,state time-wait 才是正确写法,像 state TIME_WAIT 或 state timewait 这类写法都会直接静默失败,不会报错,也不会有任何输出。
真正卡住的半开连接,往往隐藏在 CLOSE-WAIT 和 FIN-WAIT-2 中——前者表示你收到了 FIN 却没有发 FIN,后者表示你发出了 FIN 却没有等到 ACK,这两个状态都不会自动超时清理,必须依靠应用主动处理。
