https://huoding.com/2014/11/06/383

前些天,一堆人在 TCPCopy 社区里闲扯蛋,有人提了一个问题:FIN_WAIT1 能持续多久?引发了一场讨论,期间我得到斌哥和多位朋友的点化,受益良多。

让我们热热身,通过一张旧图来回忆一下 TCP 关闭连接时的情况:

TCP Close

看图可知,主动关闭的一方发出 FIN,同时进入 FIN_WAIT1 状态,被动关闭的一方响应 ACK,从而使主动关闭的一方迁移至 FIN_WAIT2 状态,接着被动关闭的一方同样会发出 FIN,主动关闭的一方响应 ACK,同时迁移至 TIME_WAIT 状态。

回到开头的问题:FIN_WAIT1 能持续多久?一般情况下,服务器间的 ACK 确认是非常快的,以至于我们凭肉眼往往观察不到 FIN_WAIT1 的存在,不过网上也有很多案例表明在某些情况下 FIN_WAIT1 会持续很长时间,从而诱发问题。

最常见的误解是认为 tcp_fin_timeout 控制 FIN_WAIT1 的过期,从名字上看也很像,但实际上它控制的是FIN_WAIT2 的过期时间,官方文档是这样说的:

The length of time an orphaned (no longer referenced by any application)connection will remain in the FIN_WAIT_2 state before it is aborted at the local end. While a perfectly valid “receive only” state for an un-orphaned connection, an orphaned connection in FIN_WAIT_2 state could otherwise wait forever for the remote to close its end of the connection.
Cf. tcp_max_orphans
Default: 60 seconds

让我们通过一个实验来说明问题(服务端:10.16.15.107;客户端:10.16.15.109):

  1. 在服务端监听 1234 端口:「nc -l 1234」
  2. 在客户端连接服务端:「nc 10.16.15.107 1234」
    此时客户端连接进入 ESTABLISHED 状态
  3. 在服务端拦截响应:「iptables -A OUTPUT -d 10.16.15.109 -j DROP」
  4. 在客户端开启抓包:「tcpdump -nn -i any port 1234」
  5. 在客户端通过「ctrl + c」断开连接。
    此时客户端连接进入 FIN_WAIT1 状态

随时可以通过「netstat -ant | grep :1234」来观察状态,最终抓包结果如下:

TCP Fin

第一个 FIN 是我们按「ctrl + c」断开连接时触发的,因为我们在服务端通过 iptables 拦截了发送给客户端的响应,所以对应的 ACK 被丢弃,随后客户端执行了若干次重试。

此外,通过观察时间我们还能发现,第一次重试在 200ms 左右;第二次是在 400ms 左右;第三次是在 800ms 左右;以此类推,每次的时间翻倍。

实际上,控制客户端重发FIN的关键参数是 tcp_orphan_retries(此时客户端处于FIN_WAIT1状态),官方文档是这样说的:

This value influences the timeout of a locally closed TCP connection, when RTO retransmissions remain unacknowledged. See tcp_retries2 for more details.
The default value is 8. 

If your machine is a loaded WEB server, you should think about lowering this value, such sockets may consume significant resources. Cf. tcp_max_orphans.

如果你用 sysctl 查询 tcp_orphan_retries 是 0,那么实际等同于 8,看代码:

/* Calculate maximal number or retries on an orphaned socket. */
static int tcp_orphan_retries(struct sock *sk, int alive)
{int retries = sysctl_tcp_orphan_retries; /* May be zero. *//* We know from an ICMP that something is wrong. */if (sk->sk_err_soft && !alive)retries = 0;/* However, if socket sent something recently, select some safe* number of retries. 8 corresponds to >100 seconds with* minimal RTO of 200msec. */if (retries == 0 && alive)retries = 8;return retries;
}

于是乎我们可以得出结论,如果你的系统负载较重,有很多 FIN_WAIT1,那么可以考虑通过降低 tcp_orphan_retries 来解决问题,具体设置多少视网络条件而定。

问题分析到这里原本可以完美谢幕,但是因为 TCP 有缺陷,导致 FIN_WAIT1 可能被用来发起 DoS 攻击,所以我们就再唠十块钱儿的,看看到底是怎么回事儿:

利用FIN_WAIT1的Dos攻击:

假设服务端上有一个大文件,攻击者连接服务端发起请求,但是却不接收数据,于是乎就造成一种现象:客户端接收队列满,导致服务端不得不通过「zero window probes」来循环检测客户端是否有可用空间,以至于 tcp_orphan_retries 也没有用,因为服务端活活被憋死了,发不出 FIN 来,从而永远卡在 FIN_WAIT1。演示代码如下:

#!/usr/bin/env pythonimport socket
import timehost = 'www.domain.com'
port = 80
path = '/a/big/file'sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
sock.send("GET %s HTTP/1.0\r\nHost: %s\r\n\r\n" % (path, host))time.sleep(1000)

说明:通常文件大小以 100K 为佳,具体取决于 tcp_rmem / tcp_wmem 的大小。

怎么办?病急乱投医,重启服务!可惜没用,因为 FIN_WAIT1 已经脱离的服务的管辖范围,所以重启服务是没有用的,如果一定要重启,你只能重启服务器!

好在内核已经考虑到了此类问题,它提供了 tcp_max_orphans 参数,用来控制 orphans 的最大值,需要注意的是,和用来控制 TIME_WAIT 的最大值的 tcp_max_tw_buckets 参数一样,除非你遇到了 DoS 攻击,否则最好不要降低它。

花絮:我曾经试图寻找一些工具来杀掉 FIN_WAIT1 连接,如果你要杀掉一个 TCP 连接,那么需要知道相应的 ACK 和 SEQ,然后才可以 RESET 连接。为了获取 ACK 和 SEQ,一些工具采用的是被动机制,它通过监听匹配的数据包来获取需要的数据,代表是 tcpkill;另一些工具采用的是主动机制,它通过伪造请求来获取需要的数据,代表是killcx,如果有兴趣的话不妨试试它们。

最后,再次感谢 TCPCopy 社区!如果你从本文学到些许知识,那么这份荣幸属于 TCPCopy社区,如果你在本文发现谬误之处,那么全因本人笨拙,还望不吝赐教。

此条目由 老王 发表在 Technical 分类目录,并贴了 Linux 、 TCP 标签。将 固定链接 加入收藏夹。

《关于FIN_WAIT1》上有6条评论

  1. 新一在2014-11-0620:55:02说道:

    学习了。现在这样子的dddos都有软件可以防御了。

    回复 ↓
  2. freeeyes在2014-11-1013:44:07说道:

    引用:
    假设服务端上有一个大文件,攻击者连接服务端发起请求,但是却不接收数据,于是乎就造成一种现象:客户端接收队列满,导致服务端不得不通过「zero window probes」来循环检测客户端是否有可用空间,以至于 tcp_orphan_retries 也没有用,因为服务端活活被憋死了,发不出 FIN 来,从而永远卡在 FIN_WAIT1

    问题:
    如果在这样的情况下,客户端不recv服务器的数据,服务器send会造成EAGAIN错误返回给调用层,这里只需要有逻辑处理EAGAIN等待次数。如果在一定次数下对方还是窗口为0,服务器代码可以主动清理这个socket fd。
    这样的话,就不会有过多的死链的问题。

    不知道我的描述是否正确,请教博主是否可以这样理解?

    回复 ↓
    • 西山在2014-11-1221:06:10说道:

      你的想法可以实践一下,首先每次send 是发送到本地tcp 栈里面,所以开始你是成功的。 到了最后本地队列满了,epoll 就不会主动通知你可写了。 最关键是关闭socket, 正常情况的close 没办法避免这个问题,但是可以用丢弃数据的办法来关闭socket 避免fin_wait_1 情况,这个在群里讨论的时候,我已经说过了。

  3. 小金在2014-12-0122:04:40说道:

    有个问题:前面说“客户端连接进入 FIN_WAIT1 状态”,后面又说“如果你的服务器负载较重,有很多 FIN_WAIT1”,是不是矛盾了。
    按博主实验中的情况,是客户端主动断开连接进入了 FIN_WAIT1 状态,并不是服务端。此时客户端等待服务端发送ack,由于服务端通过 iptables 拦截了发送给客户端的响应,此时会导致客户端一直停留在 FIN_WAIT1 状态吧?为何又说“服务器负载较重,有很多 FIN_WAIT1”呢?不是很明白这其中的逻辑。

    回复 ↓
    • 老王在2014-12-0316:01:52说道:

      我的行文好像不太一致,导致你迷惑了,FIN_WAIT1 即可能出现在服务端,也可能出现在客户端,关键看哪一端主动关闭连接。常见的如 Nginx 服务器,如果使用的是短连接,那么服务端会主动关闭连接,如果使用的是长连接,那么可能就是客户端主动关闭连接了。

当连接数多时,经常出现大量FIN_WAIT1,可以修改 /etc/sysctl.conf
修改
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_keepalive_time = 30
net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0
然后:
/sbin/sysctl -p
使之生效
#######################################################################################
apache服务器的time_wait过多 fin_wait1过多等问题
1。time_wait状态过多。
   通常表现为apache服务器负载高,w命令显示load average可能上百,但是web服务基本没有问题。同时ssh能够登陆,但是反应非常迟钝。
原因:最可能的原因是httpd.conf里面keepalive没有开,导致每次请求都要建立新的tcp连接,请求完成以后关闭,增加了很多 time_wait的状态。另,keepalive可能会增加一部分内存的开销,但是问题不大。也有一些文章讨论到了sysctl里面一些参数的设置可以改善这个问题,但是这就舍本逐末了。
2。fin_wait1状态过多。fin_wait1状态是在server端主动要求关闭tcp连接,并且主动发送fin以后,等待client端回复ack时候的状态。fin_wait1的产生原因有很多,需要结合netstat的状态来分析。
netstat -nat|awk '{print awk $NF}'|sort|uniq -c|sort -n
上面的命令可以帮助分析哪种tcp状态数量异常
netstat -nat|grep ":80"|awk '{print $5}' |awk -F: '{print $1}' | sort| uniq -c|sort -n
则可以帮助你将请求80服务的client ip按照连接数排序。
回到fin_wait1这个话题,如果发现fin_wait1状态很多,并且client ip分布正常,那可能是有人用肉鸡进行ddos攻击、又或者最近的程序改动引起了问题。一般说来后者可能性更大,应该主动联系程序员解决。
但是如果有某个ip连接数非常多,就值得注意了,可以考虑用iptables直接封了他。
Linux下查看Apache的请求数
在Linux下查看Apache的负载情况,以前也说过,最简单有有效的方式就是查看Apache Server Status(如何开启Apache Server Status点这里),在没有开启Apache Server Status的情况下,或安装的是其他的Web Server,比如Nginx的时候,下面的命令就体现出作用了。
ps -ef|grep httpd|wc -l命令
#ps -ef|grep httpd|wc -l
1388
统计httpd进程数,连个请求会启动一个进程,使用于Apache服务器。
表示Apache能够处理1388个并发请求,这个值Apache可根据负载情况自动调整,我这组服务器中每台的峰值曾达到过2002。
netstat -nat|grep -i "80"|wc -l命令
#netstat -nat|grep -i "80"|wc -l
4341
netstat -an会打印系统当前网络链接状态,而grep -i “80″是用来提取与80端口有关的连接的, wc -l进行连接数统计。
最终返回的数字就是当前所有80端口的请求总数。
netstat -na|grep ESTABLISHED|wc -l命令
#netstat -na|grep ESTABLISHED|wc -l
376
netstat -an会打印系统当前网络链接状态,而grep ESTABLISHED 提取出已建立连接的信息。 然后wc -l统计。
最终返回的数字就是当前所有80端口的已建立连接的总数。
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'命令
#netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
FIN_WAIT_1 286
FIN_WAIT_2 960
SYN_SENT 3
LAST_ACK 32
CLOSING 1
CLOSED 36
SYN_RCVD 144
TIME_WAIT 2520
ESTABLISHED 352
这条语句是在张宴那边看到,据说是从新浪互动社区事业部技术总监王老大那儿获得的,非常不错。返回参数的说明如下:
SYN_RECV表示正在等待处理的请求数;
ESTABLISHED表示正常数据传输状态;
TIME_WAIT表示处理完毕,等待超时结束的请求数。
Tag: 调优, 性能, 优化

kimi at 2008-09-01 03:01:08 in Apache, Linux
解决linux下大量的time_wait问题
vi /etc/sysctl.conf
编辑/etc/sysctl.conf文件,增加三行:
引用
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.ip_local_port_range = 1024    65000
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_max_tw_buckets = 5000 
net.ipv4.route.gc_timeout = 100 
net.ipv4.tcp_syn_retries = 1 
net.ipv4.tcp_synack_retries = 1
说明:
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,表示关闭。
再执行以下命令,让修改结果立即生效:
引用 
/sbin/sysctl -p

用以下语句看了一下服务器的TCP状态:

引用 
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
返回结果如下:
ESTABLISHED 1423
FIN_WAIT1 1
FIN_WAIT2 262
SYN_SENT 1
TIME_WAIT 962
效果:处于TIME_WAIT状态的sockets从原来的10000多减少到1000左右。处于SYN_RECV等待处理状态的sockets为0,原来的为50~300。

通过上面的设置以后,你可能会发现一个新的问题,就是netstat时可能会出现这样的警告:

引用 
warning, got duplicate tcp line
这正是上面允许tcp复用产生的警告,不过这不算是什么问题,总比不允许复用而给服务器带来很大的负载合算的多
尽管如此,还是有解决办法的:
1、 安装rpm包:
[root@root2 opt]# rpm -Uvh net-tools-1.60-62.1.x86_64.rpm 
Preparing...                ########################################### [100%]
  1:net-tools              ########################################### [100%]
[root@root2 opt]#
对于下载的是源码的rpm则需要使用以下方法安装:
2、 安装rpm源码包方法:
a)         安装src.rpm:
# [root@root1 opt]# rpm -i net-tools-1.60-62.1.src.rpm
……
b)        制作rpm安装包:
[root@root1 opt]# cd /usr/src/redhat/SPECS/
[root@root1 SPECS]# rpmbuild -bb net-tools.spec
c)        rpm包的升级安装:
[root@root1 SPECS]# pwd
/usr/src/redhat/SPECS
[root@root1 SPECS]# cd ../RPMS/x86_64/
[root@root1 x86_64]# rpm -Uvh net-tools-1.60-62.1.x86_64.rpm
3、 再使用netstat来检查时系统正常: 
说明:
  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套接字拖死。 
  net.ipv4.route.gc_timeout = 100  路由缓存刷新频率, 当一个路由失败后多长时间跳到另一个
默认是300 
  net.ipv4.tcp_syn_retries = 1  对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃。不应该大于255,默认值是5,对应于180秒左右。

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

关于FIN_WAIT1---TCP四次挥手的状态相关推荐

  1. 《TCP三次捂手、四次挥手和状态转换详解》

    前言: 本文将讲解TCP三次捂手建立连接,到四次挥手断开连接的过程,并且配合TCP状态转换图解释.解释SYN .seq .ack.FIN的含义和关系.建议读者先理解三捂和四挥后在结合状态转换图看,网络 ...

  2. TCP四次挥手不同情况的研究(正常状态,三次挥手,以及CLOSING和RST)

    文章目录 TCP四次挥手不同情况的研究 1.新手加油站 1.1 TCP四次握手流程图 1.2 TCP头部 1.3 四次握手流程详解 2.针对挥手不同情况的实验和研究 2.1 正常情况 2.2 三次挥手 ...

  3. tcp 四次挥手_TCP三次握手和四次挥手

    名词解释 SYN:发起一个新连接 ACK:确认序号有效 FIN:释放一个连接 1,TCP三次握手 第一次握手:客户端发送syn包(syn=x)到服务器,并进入SYN_SEND状态,等待服务器确认: 第 ...

  4. tcp当主动发出syn_一文读懂TCP四次挥手工作原理及面试常见问题汇总

    简述 本文主要介绍TCP四次挥手的工作原理,以及在面试中常见的问题. 字段含义 seq序号:Sequence Number,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行 ...

  5. tcp四次挥手,为什么是四次?

      上一篇博客说了三次握手为什么是是三次(点这里),那么现在就介绍一下四次挥手.大家都知道TCP是全双工的,再建立连接时的三次握手中的SYN和ACK一起发送,这里就会有疑问,为什么在四次挥手的时候没有 ...

  6. 经典面试题之 TCP三次握手 和 TCP四次挥手过程----详解

    TCP三次握手过程: 第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认:SYN:同步序列编号(Synchronize Sequence Nu ...

  7. 被面试官问懵:TCP 四次挥手收到乱序的 FIN 包会如何处理?

    摘要:收到个读者的问题,他在面试的时候,被搞懵了,因为面试官问了他这么一个网络问题. 本文分享自华为云社区<TCP 四次挥手收到乱序的 FIN 包会如何处理?>,作者:小林coding . ...

  8. 计算机网络之TCP四次挥手

    文章目录 计算机网络之TCP四次握手 1.TCP四次挥手过程 2.任何一方执行close()操作即可产生挥手操作为什么要等待呢 3.说说 TCP 四次挥手过程 4.TCP挥手为什么需要四次呢 5. T ...

  9. TCP四次挥手及原因

    一.TCP四次挥手 MSL是TCP报文里面最大生存时间,它是任何报文段被丢弃前在网络内的最长时间. 第一次挥手:A->B,A向B发出释放连接请求的报文,其中FIN(终止位) = 1,seq(序列 ...

  10. 灵魂拷问:TCP 四次挥手,可以变成三次吗?

    上周有位读者面试时,被问到:TCP 四次挥手中,能不能把第二次的 ACK 报文, 放到第三次 FIN 报文一起发送? 虽然我们在学习 TCP 挥手时,学到的是需要四次来完成 TCP 挥手,但是在一些情 ...

最新文章

  1. python整数类型-Python整数类型及其运算
  2. java注释的简单_Java简单注解
  3. Unable to create '/media/sf_nginx/H2_20151103/H2/.git/index.lock': File exists.
  4. 【Java】函数式编程思想-Lambda表达式
  5. php字符串与数字比较,PHP容易被忽略而出错陷阱 数字与字符串之间的比较
  6. linux求生之路字体乱码,Linux中文字符出现乱码怎么办
  7. 转行HTML5前端开发,该怎么学才能最快入门
  8. 哈工大2015秋 编译原理课程实验1:词法分析
  9. 卷积神经网络之迁移学习
  10. 【从C到C++学习笔记】内联成员函数/成员函数重载及缺省参数
  11. 搜款网根据关键词取商品列表 API 返回值说明
  12. 快递电子面单批量打印接口对接demo-JAVA
  13. 小程序替换二维码logo并添加文字
  14. 纵即逝的烟花蓄势于纸
  15. 中国宠物协会会员查询介绍
  16. 做视频自媒体必备的工具,手机电脑端都有哦
  17. GitHub代码托管
  18. 做PPT设计半年赚8万,我是怎样做到的?
  19. BUU刷题记录(四)
  20. Web前端-Ajax(下)

热门文章

  1. 龙族幻想最新东京机器人位置_龙族幻想东京攻略异闻怎么触发_龙族幻想东京攻略异闻详细攻略_求知软件网...
  2. 2019年年终个人总结
  3. 淘宝、飞猪、闲鱼全都挂了!阿里程序员要被祭天了?
  4. windows下安装masscan
  5. 数据探索与数据预处理的实验报告
  6. 大话设计模式——外观模式
  7. 网络流之最大流算法——EK算法(通俗讲解)
  8. 微信 拒绝共享位置服务器,微信共享位置结束不了怎么办?如何解决问题?
  9. 我想离开浪浪山,是不是该出去闯闯。
  10. mysql逻辑结构博客_mysql梳理2