我们使用Linux作为服务器操作系统时,为了达到高并发处理能力,充分利用机器性能,经常会进行一些内核参数的调整优化,但不合理的调整常常也会引起意想不到的其他问题,本文就一次Linux服务器丢包故障的处理过程,结合Linux内核参数说明和TCP/IP协议栈相关的理论,介绍一些常见的丢包故障定位方法和解决思路。



问题现象

本次故障的反馈现象是:从办公网访问公网服务器不稳定,服务器某些端口访问经常超时,但Ping测试显示客户端与服务器的链路始终是稳定低延迟的。

通过在服务器端抓包,发现还有几个特点:

  • 从办公网访问服务器有多个客户端,是同一个出口IP,有少部分是始终能够稳定连接的,另一部分间歇访问超时或延迟很高
  • 同一时刻的访问,无论哪个客户端的数据包先到达,服务端会及时处理部分客户端的SYN请求,对另一部分客户端的SYN包“视而不见”,如tcpdump数据所示,源端口为56909的SYN请求没有得到响应,同一时间源端口为50212的另一客户端SYN请求马上得到响应。

Shell

$ sudo tcpdump -i eth0 port 22 and "tcp[tcpflags] & (tcp-syn) != 0"
18:56:37.404603 IP CLIENT.56909 > SERVER.22: Flags [S], seq 1190606850, win 29200, options [mss 1448,sackOK,TS val 198321481 ecr 0,nop,wscale 7], length 0
18:56:38.404582 IP CLIENT.56909 > SERVER.22: Flags [S], seq 1190606850, win 29200, options [mss 1448,sackOK,TS val 198321731 ecr 0,nop,wscale 7], length 0
18:56:40.407289 IP CLIENT.56909 > SERVER.22: Flags [S], seq 1190606850, win 29200, options [mss 1448,sackOK,TS val 198322232 ecr 0,nop,wscale 7], length 0
18:56:44.416108 IP CLIENT.56909 > SERVER.22: Flags [S], seq 1190606850, win 29200, options [mss 1448,sackOK,TS val 198323234 ecr 0,nop,wscale 7], length 0
18:56:45.100033 IP CLIENT.50212 > SERVER.22: Flags [S], seq 4207350463, win 65535, options [mss 1366,nop,wscale 5,nop,nop,TS val 821068631 ecr 0,sackOK,eol], length 0
18:56:45.100110 IP SERVER.22 > CLIENT.50212: Flags [S.], seq 1281140899, ack 4207350464, win 27960, options [mss 1410,sackOK,TS val 1709997543 ecr 821068631,nop,wscale 7], length 0
18:56:52.439086 IP CLIENT.56909 > SERVER.22: Flags [S], seq 1190606850, win 29200, options [mss 1448,sackOK,TS val 198325240 ecr 0,nop,wscale 7], length 0
18:57:08.472825 IP CLIENT.56909 > SERVER.22: Flags [S], seq 1190606850, win 29200, options [mss 1448,sackOK,TS val 198329248 ecr 0,nop,wscale 7], length 0
18:57:40.535621 IP CLIENT.56909 > SERVER.22: Flags [S], seq 1190606850, win 29200, options [mss 1448,sackOK,TS val 198337264 ecr 0,nop,wscale 7], length 0
18:57:40.535698 IP SERVER.22 > CLIENT.56909: Flags [S.], seq 3621462255, ack 1190606851, win 27960, options [mss 1410,sackOK,TS val 1710011402ecr 198337264,nop,wscale 7], length 0


排查过程

服务器能正常接收到数据包,问题可以限定在两种可能:部分客户端发出的数据包本身异常;服务器处理部分客户端的数据包时触发了某种机制丢弃了数据包。因为出问题的客户端能够正常访问公网上其他服务,后者的可能性更大。

有哪些情况会导致Linux服务器丢弃数据包?



防火墙拦截

服务器端口无法连接,通常就是查看防火墙配置了,虽然这里已经确认同一个出口IP的客户端有的能够正常访问,但也不排除配置了DROP特定端口范围的可能性。



如何确认

查看iptables filter表,确认是否有相应规则会导致此丢包行为:

Shell

$ sudo iptables-save -t filter

这里容易排除防火墙拦截的可能性。



连接跟踪表溢出

除了防火墙本身配置DROP规则外,与防火墙有关的还有连接跟踪表nf_conntrack,Linux为每个经过内核网络栈的数据包,生成一个新的连接记录项,当服务器处理的连接过多时,连接跟踪表被打满,服务器会丢弃新建连接的数据包。

如何确认
通过dmesg可以确认是否有该情况发生:

Shell

$ dmesg |grep nf_conntrack

如果输出值中有“nf_conntrack: table full, dropping packet”,说明服务器nf_conntrack表已经被打满。

通过/proc文件系统查看nf_conntrack表实时状态:

Shell

# 查看nf_conntrack表最大连接数
$ cat /proc/sys/net/netfilter/nf_conntrack_max
65536
# 查看nf_conntrack表当前连接数
$ cat /proc/sys/net/netfilter/nf_conntrack_count
7611

当前连接数远没有达到跟踪表最大值,排除这个因素。

如何解决

如果确认服务器因连接跟踪表溢出而开始丢包,首先需要查看具体连接判断是否正遭受DOS攻击,如果是正常的业务流量造成,可以考虑调整nf_conntrack的参数:

nf_conntrack_max决定连接跟踪表的大小,默认值是65535,可以根据系统内存大小计算一个合理值:CONNTRACK_MAX = RAMSIZE(in bytes)/16384/(ARCH/32),如32G内存可以设置1048576;

nf_conntrack_buckets决定存储conntrack条目的哈希表大小,默认值是nf_conntrack_max的1/4,延续这种计算方式:BUCKETS = CONNTRACK_MAX/4,如32G内存可以设置262144;

nf_conntrack_tcp_timeout_established决定ESTABLISHED状态连接的超时时间,默认值是5天,可以缩短到1小时,即3600。

Shell

$ sysctl -w net.netfilter.nf_conntrack_max=1048576
$ sysctl -w net.netfilter.nf_conntrack_buckets=262144
$ sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=3600