ip_local_port_range 范围用尽后 bind: Address already in use 的进程定位脚本

1次阅读

bind: Address already in use 错误未必是端口被监听占用,更可能是 ip_local_port_range 耗尽导致 connect() 自动 bind 失败;此时 ss -s 显示 TIME_WaiT 数接近端口范围上限,且无对应监听进程。

ip_local_port_range 范围用尽后 bind: Address already in use 的进程定位脚本

为什么 bind: Address already in use 不一定是端口真被占了

当看到这个错误,第一反应常是用 netstat -tuln | grep :PORTlsof -i :PORT 查进程,但若问题出在 ip_local_port_range 耗尽,这些命令大概率查不到“占用者”——因为根本没进程在监听那个端口,而是系统找不到可用的临时端口来分配给新连接(比如 connect() 时自动 bind)。此时 ss -s 显示的 tw(TIME_WAIT)数往往异常高,且 /proc/sys/net/ipv4/ip_local_port_range 的范围本身又很窄(如默认 32768 60999,仅约 28K 端口),高并发短连接场景下极易打满。

快速确认 ip_local_port_range 是否已耗尽

执行以下命令组合判断:

cat /proc/sys/net/ipv4/ip_local_port_range ss -s | grep -i "timewait|established"

关键看两点:

  • ss -stw 数量接近或超过 ip_local_port_range 上下界之差(例如 60999 − 32768 + 1 = 28232),基本可判定耗尽
  • 同时 established 数量不高,说明不是长连接占满,而是短连接快速进入 TIME_WAIT 后未及时回收
  • 注意:net.ipv4.tcp_tw_reusenet.ipv4.tcp_fin_timeout 的值会影响实际回收速度,但它们不改变端口池大小

定位真正“吃掉”本地端口的进程(非监听型)

真正占端口的是处于 TIME_WAITESTABLISHEDSYN_SENT 等状态的 socket,需按源端口聚合统计。用下面这个一行脚本找出本地端口使用最多的前 10 个进程:

ss -tnp 2>/dev/null | awk '{if(NF==7) print $7}' | cut -d',' -f2 | cut -d':' -f2 | sort | uniq -c | sort -nr | head -10

说明:

  • ss -tnp 列出所有 TCP 连接及对应进程(需 root 权限,否则 $7 为空)
  • 字段解析依赖输出格式:第 7 字段形如 users:(("curl",pid=12345,fd=3)),先取括号内部分,再切出 pid
  • 若权限不足,改用 ss -tun(无 -p),再结合 /proc/*/fd/ 反查,但效率低很多
  • 结果中 pid 频次高,说明该进程发起了大量短连接(如 http 客户端、健康检查探针)

避免脚本误报:区分 bind() 失败和 connect() 失败

bind: Address already in use 在客户端代码里通常来自 connect() 内部自动 bind 失败,而非显式调用 bind()。这意味着:

  • 日志或 strace 中看到该错误,不要只盯 bind() 系统调用,更要查 connect() 返回前的 getsockname() 或内核分配逻辑
  • 应用层若手动 bind() 到固定端口,失败才真代表端口被占;但绝大多数客户端不这么做
  • strace 示例:strace -e trace=bind,connect,socket -p PID 2>&1 | grep -E "(bind|connect).*= -1",重点看 connect 是否触发 ENOBUFS 或 EADDRINUSE

真正难处理的,是那些没留日志、不暴露 fd、又高频建连的后台服务——它们不会出现在 netstat -tuln 里,但能把整个 ip_local_port_range 挤成一张废纸。

text=ZqhQzanResources