FIN-WAIT-1过多实战
基础知识
对于FIN-WAIT-1是什么,有什么用,请查看tcpip的内核篇。这里主要介绍conntrack、nc
命令以及iptables
命令。
NC
使用nc需要安装yum install nmap-ncat
包,其官方地址:http://netcat.sourceforge.net/ 主要的参数如下:
1 | -l 监听模式 |
扫描端口:
1 | [root@localhost ~]# nc -z -v 192.168.1.60 22 |
服务器监听端口,先在server上开启一个端口:
1 | [root@master ~]# nc -l -p 2000 < ipinfo.txt |
在client上运行命令:
1 | [root@localhost ~]# nc 192.168.1.60 2000 |
这时可以看到client上面有输出了ipinfo.txt的内容,再在client输入123这个内容,在server上面也是有同步输出的。
iptables
iptables的LOG 将封包相关讯息纪录在 /var/log 中,进行完此处理动作后,将会继续比对其规则。例如: iptables -I OUTPUT -m state --state NEW -j LOG --log-prefix "[OUTPUT] new: "
在/var/log/message日志中就有以下日志:
1 | Jun 14 04:28:47 master kernel: [OUTPUT] new: IN= OUT=eth0 SRC=192.168.1.60 DST=14.215.177.39 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=47485 DF PROTO=TCPSPT=36736 DPT=80 WINDOW=29200 RES=0x00 SYN URGP=0 |
conntrack
安装yum install conntrack-tools -y
连接跟踪工具。安装完成之后,可以使用conntrack命令
来查看系统的连接情况。iptables log
其实是借用此功能实现的。
1 | 统计总的连接跟踪数 -L 表示列表,-o 表示以扩展格式显示 |
跟内核相关的参数有:
1 | [root@master ~]# sysctl -a |& grep conntrack |egrep "max|count|buckets|timeout_" |
- net.netfilter.nf_conntrack_count,表示当前连接跟踪数;
- net.netfilter.nf_conntrack_max,表示最大连接跟踪数;
- net.netfilter.nf_conntrack_buckets,表示连接跟踪表的大小。
如果message里面有看到 “nf_conntrack: table full” 的错误时,就表明 nf_conntrack_max 太小了。
一般情况下,连接跟踪对象大小为 376,链表项大小为 16,那么连接跟踪表占用的内存大小为:nf_conntrack_max* 连接跟踪对象大小 + nf_conntrack_buckets* 链表项大小=65536*376B + 16384*16B=23.75MB
另外可以看到nf_conntrack_tcp_timeout_fin_wait
的时间为120秒,如果系统里面有大量的FIN-WAIT-1/-2的话,可以减少该值,其他的timeout类似。
实验部分
准备工作
在server、client分别安装yum install conntrack-tools -y
连接跟踪工具。然后在server上面将conntrack tcp timeout设置得短点:sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=20
,以及下放iptables规则:
1 | iptables -P INPUT ACCEPT |
开始测试
在server端开启监听端口nc -l -p 8088
,再打开一个tab页,运行tailf /var/log/messages |grep 8088
来查看iptables的日志,然后在client运行nc 192.168.1.60 8088
,最后在server运行:
1 | [root@master ~]# conntrack -L |& grep 8088 |
可以看到此连接已经是ESTABLISHED状态了。使用lsof也可以看到此 状态。
不要做任何操作,等待20秒。再运行命令:
1 | [root@master ~]# conntrack -L |& grep 8088 |
可以看到,并没有conntrack里面没有8088端口的状态,可想而知,内核已经将此连接给断掉了,但是lsof还是显示ESTABLISHED状态。
从ss -tn
的输出中可以看到状态变成了FIN-WAIT-2,为什么是这个状态呢?我们看一下messages日志:
1 | [root@master ~]# tailf /var/log/messages |grep 8088 |
可以看到前三条是建立链接的请求。而是第四条的时候,此连接状态直接变成了OUTPUT INVALID
,由于上面的iptable的iptables -A OUTPUT -m state --state INVALID -j DROP
是注释的,包是可以发出去的,那为什么最后一条 内核又认为是一个新的请求呢?先思考下。
client是可以正常收到FIN包,之后会回复ACK确认包,但在server看来,这是一条新的请求过来了,但是我已经停止服务8088端口的,所以服务器就不到响应了。下面是抓包看下的内容:
1 | 23:25:01.808314 IP 192.168.1.61.43712 > 192.168.1.60.8088: Flags [S], seq 1414242879, win 29200, options [mss 1460,sackOK,TS val 398212597 ecr 0,nop,wscale 7], length 0 |
为什么kill进程后socket一直处于FIN_WAIT_1状态,https://yq.aliyun.com/articles/704262,源码级别,有兴趣可以查看。
线上问题实例
在线上一台服务器出现了too many open files的报错(线上业务,对报错信息做了修改,其重点是socket的报错):
1 | [2019/06/06 13:36:00] [E] syncHttpClient: Get https://1.1.1.1:59211/: dial tcp 1.1.1.1:59211: socket: too many open files |
第一反应是去查看ulimit -n
,发现设置的值是12W,并没有什么问题。但去查看tcp连接数的时候就看出异常出来了。FIN-WAIT-1数达到了6W多次,很不正常。
1 | [op@localhost ~]$ ss -s |
为什么不正常呢?这是由于四次挥手时,谁主动发起FIN包,谁就进入了FIN-WAIT-1的状态。从上输出可以看到,都是服务器本身发起的FIN包(假设2.2.2.2为服务器的IP)。所以在怀疑有人在对22222端口做攻击,这时使用iptables来drop掉这个流量,服务器就恢复正常了。
C1M
所谓的C1M指的是服务器要达到100M的并发连接,在这一篇里面,全部参考的是http://www.blogjava.net/yongboy/archive/2013/04/09/397559.html,因为我不是开发,其里面的代码我是看不懂的,我从运维角度来记录一下我的实验过程。
准备篇
分为测试端和服务器端,都选用较为熟悉的64位Centos 7.1。
服务器端
服务器端程序依赖libev框架,需要提前编译,然后存放到相应位置。目前最新版本为4.25。注意,这个libev跟libevent是不一样的,是2个东西。
首先编译libev
1 | wget http://dist.schmorp.de/libev/libev-4.25.tar.gz |
编译完成之后,先记录一下libev.a这个模块的位置。如果在make install没有找到位置,可以使用find来查看,如下是部署过程:
1 | [root@master ~]# wget https://gist.githubusercontent.com/yongboy/5318930/raw/ccf8dc236da30fcf4f89567d567eaf295b363d47/server.c |
客户端篇
client程序使用libevent框架,因其使用简单,提供丰富易用接口,但需要提前下载,手动安装:
1 | wget https://github.com/downloads/libevent/libevent/libevent-2.1.10-stable.tar.gz |
安装完成之后,下载clinet1.c文件,需要再修改一下SERVERADDR的参数,就是服务端的IP,如 下是操作步骤:
1 | [root@localhost ~]# wget https://gist.githubusercontent.com/yongboy/5318994/raw/f0cc8650c3350df1578dd986a38f4700152cd976/client1.c |
运行client1之后发现找不到libevent-2.1.so.6,那就再find一下吧。
1 | [root@localhost ~]# find / -name "libevent-2.1.so.6" |
开始测试篇
开始测试前的准备
我们需要先观察一下各类参数,server的参数:
1 | [root@master ~]# netstat -s |grep -i listen |
client的参数如下:
1 | [root@localhost ~]# netstat -s |grep -i listen |
开始测试:文件打开数问题
在server服务器上面运行./server
之后,同时在client端运行./client
,可以 在server看到online user 1018
之后就不在变化了,说明并发用户数是在1018个,如下是server的显示内容:
1 | [root@master ~]# ./server |
可以看到结果是./server
这个进程建立的连接数有1018个,而lsof -p
看到的打开文件数是1031,开启服务,但没有人来访问的文件数是13,那1031-13=1018
个,是可以对应上程序输出的。
在client端的输出如下:
1 | [root@localhost ~]# ./client1 |
可以看到跟server的连接为1018个,跟server的显示一样。同时看client的输出 ,在访问/test/1000时,close的连接为0,但是在访问/test/1200之后,close的数量就达到了182个,说明正常的连接有1200-182=1018个。
遇到2个问题点:
- 一直没有出现
Too many open files
这个提示,从server端则可以看到有SYN flooding攻击,其他再无异常了。
1 | [root@master ~]# dmesg |grep request_sock_TCP |
- 在server端可以看到半连接以及全连接的数量在正常连接阶段都会增加,但是在client请求数增长到3000以上时,这2个值就不会发现变化了。
但不管怎么样,这次测试出来的连接数只是达到1018个,可以得出结论跟打开文件数有关,按前文对/etc/security/limits.conf
设置之后,必须要重启服务器才能生效,使用ulimit -SHn
设置并没有生效,这点请注意。
第二次测试:最大连接数问题
重启服务器之后,再次测试,看到最终的online user 28225
,这其实对最大连接数有关系,如下,默认开启端口是32768~60999,这样就一共28231个连接,这是相对clinet来说的,如果不清楚的同学可以看前一篇文章,原理篇的内容。
1 | [root@localhost ~]# sysctl -a 2>/dev/null |grep port |grep range |
所以要模拟大并发的情况,可以有2个方法,一是增加可用端口的范围,二是增加IP地址。由于要指定绑定源IP去通信,再下载一个测试程序,如下:
1 | [root@localhost ~]# wget https://gist.githubusercontent.com/yongboy/5324779/raw/f29c964fcd67fefc3ce66e487a44298ced611cdc/client2.c |
可能还会遇到fs.file-max的问题的,根据需要进行调整。
tcp_mem问题
tcp的发送、接收都是有缓冲区的,在大并发的情况下,此参数必须优化。可以参考:http://www.blogjava.net/yongboy/archive/2013/04/11/397677.html
参考链接
100万并发连接服务器笔记系列:http://www.blogjava.net/yongboy/category/54842.html