关于FIN_WAIT1
前些天,一堆人在 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):
在服务端监听 1234 端口:「nc -l 1234」
在客户端连接服务端:「nc 10.16.15.107 1234」
此时客户端连接进入 ESTABLISHED 状态在服务端拦截响应:「iptables -A OUTPUT -d 10.16.15.109 -j DROP」
在客户端开启抓包:「tcpdump -nn -i any port 1234」
在客户端通过「ctrl + c」断开连接
此时客户端连接进入 FIN_WAIT1 状态
随时可以通过「netstat -ant | grep :1234」来观察状态,最终抓包结果如下:

TCP Fin
第一个 FIN 是我们按「ctrl + c」断开连接时触发的,因为我们在服务端通过 iptables 拦截了发送给客户端的响应,所以对应的 ACK 被丢弃,随后执行了若干次重试。
此外,通过观察时间我们还能发现,第一次重试在 200ms 左右;第二次是在 400ms 左右;第三次是在 800ms 左右;以此类推,每次的时间翻倍。
实际上,控制这一行为的关键参数是 tcp_orphan_retries,官方文档是这样说的:
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 攻击,所以我们就再唠十块钱儿的,看看到底是怎么回事儿:
假设服务端上有一个大文件,攻击者连接服务端发起请求,但是却不接收数据,于是乎就造成一种现象:客户端接收队列满,导致服务端不得不通过「zero window probes」来循环检测客户端是否有可用空间,以至于 tcp_orphan_retries 也没有用,因为服务端活活被憋死了,发不出 FIN 来,从而永远卡在 FIN_WAIT1。演示代码如下:
#!/usr/bin/env python import socket import time host = '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社区,如果你在本文发现谬误之处,那么全因本人笨拙,还望不吝赐教。
关于FIN_WAIT1的更多相关文章
- FIN_WAIT1 能持续多久?你知道吗
FIN_WAIT1 能持续多久?你知道吗 2016-01-12 运维帮 原文:http://blogread.cn/it/article/7215?f=wb&luicode=10000359 ...
- 服务器大量的fin_wait1 状态长时间存在原因分析
有一台服务器,出现很多的fin_wait1状态的socket. 环境: [root@localhost ~]# uname -aLinux localhost.localdomain 2.6.32-3 ...
- 服务器大量的fin_wait1 状态长时间存在原因分析-1
上文描述了在出现大量fin-wait-1出现的原因,占用的内存等,这里讲一下如何处理这种情况. 首先,fin发送之后,有可能会丢弃,那么发送多少次这样的fin包呢?fin包的重传,也会采用退避方式,在 ...
- 漫谈TCP
不得不承认,tcp是一个非常复杂的协议.它包含了RFC793及之后的一些协议.能把tcp的所有方面面面具到地说清楚,本身就是个很复杂的事情.如果再讲得枯燥,那么就会更让人昏昏欲睡了.本文希望能尽量用稍 ...
- 3.awk数组详解及企业实战案例
awk数组详解及企业实战案例 3.打印数组: [root@nfs-server test]# awk 'BEGIN{array[1]="zhurui";array[2]=" ...
- TCP十一种状态
2.全部11种状态 2.1.客户端独有的:(1)SYN_SENT (2)FIN_WAIT1 (3)FIN_WAIT2 (4)CLOSING (5)TIME_WAIT . 2.2.服务器独有的:(1)L ...
- ngx_http_core_module模块.md
Directives aio Syntax: aio on | off | threads[=pool]; Default: aio off; Context: http, server, locat ...
- TCP/IP是一种十一状态
1.建立连接协议(三次握手) 三次握手过程说明: 1. 在最开始,客户端和服务器都是处于CLOSED状态 2.服务器会创建sockert开始监听,服务器状态LISTEN 3.客户端向服务器端发送SY ...
- TCP的三次握手(建立连接)和四次挥手(关闭连接)
参照: http://course.ccniit.com/CSTD/Linux/reference/files/018.PDF http://hi.baidu.com/raycomer/item/94 ...
随机推荐
- display:inline、block、inline-block的区别(转)
display:block就是将元素显示为块级元素. block元素的特点是: 总是在新行上开始: 高度,行高以及顶和底边距都可控制: 宽度缺省是它的容器的100%,除非设定一个宽度 <div& ...
- Set Linux starts in multi-user mode as default.
ref: How to start Linux in multiuser mode rather than boot directly into XWindows (X11) steps: Log i ...
- Java study 1:The note of studying Socket which based UDP
UDP concept: UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参 ...
- [个人原创]关于java中对象排序的一些探讨(三)
这篇文章由十八子将原创,转载请注明,并标明博客地址:http://www.cnblogs.com/shibazijiang/ 对对象排序也可以使用Guava中的Ordering类. 构造Orderin ...
- Hibernate中HQL语句中list与iterate区别
session.createQuery("from Classes").list() session.createQuery("from Classes").i ...
- 卸载mysql时,如何卸载干净!
相信很多朋友在使用mysql的过程中都会遇到这样的问题,安装过程出错,或者想要换个版本,或者不想使用了,这个时候我们都需要完全卸载mysql呢?下面,就来谈一谈我的经验. 1.控制面板——>所有 ...
- HBase 学习之一 <<HBase使用客户端API动态创建Hbase数据表并在Hbase下导出执行>>
HBase使用客户端API动态创建Hbase数据表并在Hbase下导出执行 ----首先感谢网络能够给我提供一个开放的学习平台,如果没有网上的技术爱好者提供 ...
- nodejs新手教程中upload file的问题
可参见: http://cnodejs.org/topic/50234890f767cc9a51f88481 request.setEncoding("utf8");应注释掉.
- Android 多线程:使用Thread和Handler
当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分 ...
- __PUBLIC__模板文件路径配置
__PUBLIC__ 默认指向 项目文件下的Public文件夹 注意: __PUBLIC__不是一个常量,只在模板中进行替换,__ROOT__是一个常量 项目文件路径 defined('__PUBLI ...