一、现象

今天web应用突然停了,ping服务器的地址发现也没有响应,而通过其内网的其他主机通过telnet可以正常连接上(只允许内网telnet的,以备紧急情况)。通过该服务器ping外网,发现正常。赶紧查看/var/log/message日志,发现报如下信息:

1Aug 28 09:19:50 web8 kernel: ip_conntrack: table full, dropping packet.
2Aug 28 09:19:51 web8 kernel: printk: 3 messages suppressed.
3Aug 28 09:19:51 web8 kernel: ip_conntrack: table full, dropping packet.
4Aug 28 11:42:24 web8 kernel: printk: 4 messages suppressed.

看到ip_conntrack这个熟悉的名字,知道一定是防火墙出现了问题。先停掉iptables,web应用恢复。重启启动iptables,web服务仍然正常。想到会不会是因为用了fail2ban造成了把公司的ip列到了黑名单里。不过看了下,该机并未设置fail2ban。

1netstat -an|grep tcp|wc -l

查看了下连接数也正常。再看last记录,看有没有异常IP入侵,也未见异常(有,别人也清掉了,不过这个步骤还是不能少的)。这便略微放下了心。从网上找出 错原因。

网上打到的结果其实在message日志里也已经告诉我们了,IP_conntrack表已满。而ip_conntrack是什么呢?

IP_conntrack表示连接跟踪数据库(conntrack database),代表NAT机器跟踪连接的数目,连接跟踪表能容纳多少记录是被一个变量控制的,他可由内核中的ip- sysctl函数配置。每一个跟踪连接表会占用350字节的内核存储空间,时间一长就会把默认的空间填满。

kernel 用 ip_conntrack 模块来记录 iptables 网络包的状态,并保存到 table 里(这个 table 在内存里),如果网络状况繁忙,比如高连接,高并发连接等会导致逐步占用这个 table 可用空间,一般这个 table 很大不容易占满并且可以自己清理,table的记录会一直呆在 table 里占用空间直到源 IP 发一个 RST 包,但是如果出现被攻击、错误的网络配置、有问题的路由/路由器、有问题的网卡等情况的时候,就会导致源 IP 发的这个 RST 包收不到,这样就积累在 table里,越积累越多直到占满,满了以后 iptables 就会丢包,出现外部无法连接服务器的情况。

二、解决思路

而解决方法,我总结了三种方法:

方法1、增加参数设置

增加 ip_conntrack_max的大小:

1echo 131072 > /proc/sys/net/ipv4/ip_conntrack_max
2或者
3sysctl -w  net.ipv4.netfilter.ip_conntrack_max=131072

我看了我几台服务器,该值的默认大小都是65535,如果想重启后仍然有效,需要在sysctl.conf文件最后加入

1net.ipv4.netfilter.ip_conntrack_max=131072

还有一个参数 ip_conntrack_tcp_timeout_established 需要注意,默认情况下 timeout 是5天(432000秒):

1# cat /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_established
2432000
3# sysctl -w net.ipv4.netfilter.ip_conntrack_tcp_timeout_established=180
4net.ipv4.netfilter.ip_conntrack_tcp_timeout_established = 180

我这里设置的是180秒,这个要根据应用不同,灵活设置。

注:上面的方法当时设置后生效,不过在我service iptables restart重启防火墙后,ip_conntrack_max又变成了默认65535 。

方法2、卸载 ip_conntrack 模块

到在 /etc/sysconfig/iptables-config 文件里删除或者注释掉 ip_conntrack_netbios_ns 后重启系统:

1 vi /etc/sysconfig/iptables-config
2#IPTABLES_MODULES="ip_conntrack_netbios_ns"
3 shutdown -r now

这个是从网上搜到的结果,个人感觉,不重启通过rmmod命令也可以卸载模块(需内核支持),然后重启iptables应该也可以的。

方法3、利用hping将ip连接状态设为closed

 1#!/bin/bash
 2if [ -z ] ; then
 3exit
 4fi
 5grep -E "^tcp .ESTABLISHED src= " /proc/net/ip_conntrack | while read line ; do
 6S_IP=`echo $line | awk ''`
 7S_SOCK=`echo $line | awk ''`
 8D_IP=`echo $line | awk ''`
 9D_SOCK=`echo $line | awk ''`
10echo "$S_IP:$S_SOCK $D_IP:$D_SOCK"
11hping2 $D_IP -R -s $S_SOCK -p $D_SOCK -a $S_IP -k -c 1 >/dev/null 2>/dev/null &
12done

用法:sh clr_conns.sh x.x.x.x ,x.x.x.x为要置为closed连接的ip

查看ip_conntrack连接的方法:

1cat /proc/net/ip_conntrack |wc -l
2查看当前总的连接数
3cat /proc/net/ip_conntrack | cut -d ' ' -f 10 | cut -d '=' -f 2 | sort | uniq -c | sort -nr | head -n 5
4找出前五位连接最多的主机

通过另外一个方法也可以查看该表中记录的当前的连接:

 1[root@web7 ~]# head /proc/slabinfo
 2slabinfo - version: 2.1
 3# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
 4ip_conntrack_expect      0      0    136   28    1 : tunables  120   60    8 : slabdata      0      0      0
 5ip_conntrack       29030  29653    304   13    1 : tunables   54   27    8 : slabdata   2281   2281     55
 6blktapif_cache         0      0    200   19    1 : tunables  120   60    8 : slabdata      0      0      0
 7blkif_cache            0      0    200   19    1 : tunables  120   60    8 : slabdata      0      0      0
 8bridge_fdb_cache      31     59     64   59    1 : tunables  120   60    8 : slabdata      1      1      0
 9rpc_buffers            8      8   2048    2    1 : tunables   24   12    8 : slabdata      4      4      0
10rpc_tasks              8     10    384   10    1 : tunables   54   27    8 : slabdata      1      1      0
11rpc_inode_cache        6     10    768    5    1 : tunables   54   27    8 : slabdata      2      2      0

其中29030即为当前的连数。

三、反思与查找问题的根源

利用找出前五位的方法查到当前表中记录的连接最多的主机:

1cat /proc/net/ip_conntrack | cut -d ' ' -f 10 | cut -d '=' -f 2 | sort | uniq -c | sort -nr | head -n 5
2  29457 192.168.1.38
3    260 111.1.44.25
4    189 113.119.105.24
5    155 11.236.174.164
6    150 58.246.37.238
7注:以上的公网IP是我伪造的,如有雷同,纯属巧合,切勿对号入座。

发现当前连接最多的竟是本机自身的内网IP地址(29457个),

 1 more /proc/net/ip_conntrack
 2tcp      6 72 TIME_WAIT src=192.168.1.38 dst=192.168.1.107 sport=52397 dport=3128 packets=9 bytes=822 src=192.168.1.107 dst=192.168..38 sport=3128 dport=52397 packets=9 byt
 3es=4977 [ASSURED] mark=0 secmark=0 use=1
 4tcp      6 119 TIME_WAIT src=192.168.1.38 dst=192.168.1.107 sport=47004 dport=3128 packets=6 bytes=706 src=192.168.1.107 dst=192.168.1.38 sport=3128 dport=47004 packets=6 by
 5tes=1880 [ASSURED] mark=0 secmark=0 use=1
 6tcp      6 104 TIME_WAIT src=192.168.1.38 dst=192.168.1.200 sport=47341 dport=5050 packets=5 bytes=1032 src=192.168.1.200 dst=192.168.1.38 sport=5050 dport=47341 packets=5 b
 7ytes=1283 [ASSURED] mark=0 secmark=0 use=1
 8tcp      6 104 TIME_WAIT src=192.168.1.38 dst=192.168.1.107 sport=39091 dport=3128 packets=5 bytes=737 src=192.168.1.107 dst=192.168.1.38 sport=3128 dport=39091 packets=5 by
 9tes=464 [ASSURED] mark=0 secmark=0 use=1
10tcp      6 103 TIME_WAIT src=192.168.1.38 dst=192.168.1.107 sport=38981 dport=3128 packets=6 bytes=606 src=192.168.1.107 dst=192.168.1.38 sport=3128 dport=38981 packets=6 by
11tes=1772 [ASSURED] mark=0 secmark=0 use=1

可以看到其中主为是内容为1.107的主机和其该主机连,且状态主要为TIME_WAIT,而1.107为公司的squid服务器。通过脚本

1netstat -an | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

发现squid服务器上有二万多TIME_WAIT。

而张宴的博文有一篇《减少Linux下Squid服务器的TIME_WAIT套接字数量》对squid的大量TIME_WAIT给出的说明如下:

Linux下高并发的Squid服务器,TCP TIME_WAIT套接字数量经常达到两、三万,服务器很容易被拖死。通过修改Linux内核参数,可以减少Squid服务器的TIME_WAIT套接字数量。

 1vi /etc/sysctl.conf
 2增加以下几行:
 3net.ipv4.tcp_fin_timeout = 30
 4net.ipv4.tcp_keepalive_time = 1200
 5net.ipv4.tcp_syncookies = 1
 6net.ipv4.tcp_tw_reuse = 1
 7net.ipv4.tcp_tw_recycle = 1
 8net.ipv4.ip_local_port_range = 1024    65000
 9net.ipv4.tcp_max_syn_backlog = 8192
10net.ipv4.tcp_max_tw_buckets = 5000

说明:

  • net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击, 默认为0,表示关闭;
  • net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
  • net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。net.ipv4.tcp_fin_timeout = 30 表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。
  • net.ipv4.tcp_keepalive_time = 1200 表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。
  • net.ipv4.ip_local_port_range = 1024 65000 表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1024到65000。
  • net.ipv4.tcp_max_syn_backlog = 8192 表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
  • net.ipv4.tcp_max_tw_buckets = 5000 表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000,改为5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于Squid,效果 却不大。此项参数可以控制TIME_WAIT套接字的最大数量,避免Squid服务器被大量的TIME_WAIT套接字拖死。

执行以下命令使配置生效:/sbin/sysctl -p

四、总结

最终找到了问题的原因,是因为内部的squid服务器造成了本机的拒绝服务。而通过方法1和 sysctl内核参数优化该业务机及squid主机,通过观察发现其表记录稳 定在三万记录左右。