前言
最近工作遇到了一个疑似端口耗尽的问题,系统整理下这类问题如何排查、如何避免。
基础知识
每个TCP或者UDP连接由一个四元组构成唯一标识:源IP地址、源端口号、目的IP、目的端口号,因此当四元组中其他三个都保持不变时(通常源ip、目的ip、目的端口不变),源端口号可能会耗尽。例如:当一台服务器作为客户端请求某一个外部主机上的服务时,产生的每个连接的源IP地址、目标IP地址、目的端口都不会变,源端口号都是系统自动分配的一个端口号。因此如果对外建立的连接数,超过了本机可分配的端口数,则这台服务器无法再与外部服务建立新的请求。
需要注意的是,一个端口可以同时用于源端口和目的端口,比如服务器可以有一个 TCP 连接到外部服务器的 80 端口,同时本地端口可能是 12345。在这种情况下,四元组可能是这样的:(你的服务器的 IP, 12345,外部服务器的 IP, 80)。同时,你的服务器可以监听端口 12345 来等待来自不同服务器的传入连接。如果有另一个连接发起,其四元组是:(外部服务器的 IP, 端口N, 你的服务器的 IP, 12345),即使端口 12345 正在被上述的连接作为源端口使用,这个新的连接也是允许的,因为它的四元组是唯一的。此时12345被分别用于两个连接的源端口和目的端口。
检测方法
既然一般来说是源端口号耗尽,则观察每个链接,其目的ip、目的端口应该都是一致的。而源ip则一般都是本机ip,变化不大,因此可以忽略。我们只关注目的ip+目的端口即可。
下面是检测脚本,注意下面的脚本只检测了ipv4的连接。
#!/bin/bash
# 获取本地端口分配范围
LOW_PORT=$(sysctl net.ipv4.ip_local_port_range | awk '{print $3}')
HIGH_PORT=$(sysctl net.ipv4.ip_local_port_range | awk '{print $4}')
TOTAL_PORTS=$((HIGH_PORT - LOW_PORT + 1))
echo "Local port range is: $LOW_PORT - $HIGH_PORT (Total: $TOTAL_PORTS ports)"
# 使用 ss 获取源IP、目的IP、目的端口完全相同的所有连接,可以仅仅看了ipv4
conn_count=$(ss -tan4 |
grep -v 'Local' |
awk -F'[ :]+' '{printf "%-14s %s:%-8s\n", $4, $6, $7}' |
sort |
uniq -c |
sort -nr |
awk '$1>1 {printf "%-14s %-22s count:%s\n", $2, $3, $1}' |
head -10)
# 如果没有找到任何重复的连接,输出相应信息并退出
if [ -z "$conn_count" ]; then
echo "No duplicate connections found."
exit 0
fi
echo "Top duplicate connections (Destination IP:Port count):\n$conn_count"
解决办法
连接复用
比如http2,或者使用http2协议的grpc协议,都会链接复用,即同时发起的多次调用,都只会使用一个连接。
限制连接数
在golang中,可以通过如下的方式来限制连接数。
func main() {
// 新建一个client,限制连接数为1024
client := http.Client{
Transport: &http.Transport{
MaxConnsPerHost: 1024,
},
}
// 修改全局的默认client
http.DefaultClient.Transport = &http.Transport{
MaxConnsPerHost: 1024,
}
}
因此可以看到,默认的Client,或者一个不经过任何配置的Client,很有可能在大压力下将自己的连接数用尽,要警惕这一点!
设置系统参数
在一个连接四次挥手的时候,client最后会处于TIME_WAIT
状态等待一段时间,最后再关闭连接。因此Linux提供了一些配置,可以让处于TIME_WAIT
状态的连接被内核快速回收,进而使用其端口号建立新的连接。
下面的都是系统配置,对于这些配置,都可以通过sysctl {变量名}
来查看配置,通过修改/etc/sysctl.conf
文件并使用sysctl -p
来应用配置,下面不再重复这两个步骤。
net.ipv4.tcp_tw_reuse
在 /etc/sysctl.conf
文件中添加:
net.ipv4.tcp_tw_reuse = 1
然后运行以下命令以应用配置:
sysctl -p
net.ipv4.tcp_tw_recycle
这个配置允许内核回收处于TIME_WAIT
状态的连接。但是在较新的 Linux 内核中已被弃用,因为它可能会导致 NAT 问题。修改配置方法和上述一致。
net.ipv4.ip_local_port_range
这个配置决定了可用端口的范围,一般都是10000 65535,一般不太需要调整。
net.ipv4.tcp_tw_timeout
还有一种办法是修改client处于TIME_WAIT
状态的时间,即让连接自动尽快释放。不过一般不建议调整这些参数,这里仅仅做知识补全。
net.ipv4.tcp_tw_timeout
参数控制 TIME_WAIT
状态的超时时间的参数,但这个参数并不是所有的linux发行版都支持。
net.ipv4.tcp_max_tw_buckets
该参数设置了系统中 TIME_WAIT
状态的最大连接数。如果超过这个值,TIME_WAIT
连接将立即被清除并输出警告信息。