关于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):

  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 左右;以此类推,每次的时间延长一倍。

实际上,控制这一行为的关键参数是 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 来解决问题,设置为 1 应该是个不错的选择。

问题分析到这里原本可以完美谢幕,但是因为内核有缺陷,导致 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 属于 orphan,已经脱离的服务的管辖范围,所以重启服务是没有用的,如果一定要重启,你只能重启服务器!

理论上讲,这属于内核缺陷,幸运的是如果你搜索「how to kill a tcp connection」的话,能找到一些可以选择的工具,比如:tcpkill 和 killcx。

如果你要杀掉一个 TCP 连接,那么需要知道相应的 ACK 和 SEQ,然后才可以 RESET 连接。为了获取 ACK 和 SEQ,tcpkill 采用的是被动机制,它通过监听匹配的数据包来获取需要的数据;killcx 采用的是主动机制,它通过伪造 SYN 包来获取需要的数据。

从前面的案例我们知道,一旦攻击者让服务端卡在 FIN_WAIT1,就不再有数据交互,所以采取被动机制的 tcpkill 在本案例中没有效果,而采取主动机制的 killcx 管用。

安装 killcx 的时候,注意 Perl 的版本不能低于 5.10.0,再搭配几个依赖的模块就能用了:

shell> perl -MCPAN -e shell

cpan> install Net::RawIP
cpan> install Net::Pcap
cpan> install NetPacket::Ethernet

使用 killcx 的时候,需要注意的是因为我们要杀的是 FIN_WAIT1 连接,所以需要使用 loopback 接口,这点再官方文档里有提及:

Note that in many cases, you will get much better results by using lo (loopback interface), specially if the connection is not yet or no longer in the ESTABLISHED state, for instance SYN_RECV or TIME_WAIT.

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

<think>好的,用户现在问的是关于TCP状态FIN_WAIT1的情况。之前用户提供了大量的TCP连接信息,我之前帮他分析了SYN_RECV、LISTEN、CLOSE_WAIT等状态,现在他可能遇到了更多关于TCP状态的问题,或者想进一步了解其他状态。 首先,我需要回顾FIN_WAIT1的定义和常见场景。FIN_WAIT1TCP连接关闭过程中的一个状态,属于主动关闭方在发送FIN包后进入的状态。根据之前的分析,用户的应用可能存在未正确关闭连接的问题,尤其是CLOSE_WAIT状态较多,这可能与FIN_WAIT1有关联吗?比如,如果主动关闭方发送FIN后没有收到ACK,可能会卡在FIN_WAIT1,但这通常和网络问题或对方响应有关。 接下来,用户的需求可能是想了解为什么会出现FIN_WAIT1,或者他们的系统中是否有这样的状态,如何处理。需要解释这个状态的含义、正常情况下的持续时间,以及异常情况下的可能原因和解决方案。 用户可能不太清楚TCP关闭流程的细节,所以需要用简单明了的语言解释,并结合之前的分析,比如CLOSE_WAIT是对方发送FIN后本地未关闭,而FIN_WAIT1是本地发送FIN后等待确认。同时,用户可能需要知道如何检测和解决FIN_WAIT1堆积的问题,比如检查网络状况、对方服务是否正常、系统参数调整等。 另外,用户可能没有直接给出具体的连接示例,但根据之前的上下文,他们可能是在处理服务间的连接问题,特别是本地回环或外部IP的连接。需要建议他们如何监控和诊断,比如使用netstat或ss命令,查看相关日志,或者调整TCP超时参数。 最后,需要确保回答结构清晰,分点说明,并给出实际的命令示例,帮助用户操作。同时,关联之前提到的CLOSE_WAIT问题,指出两者在连接关闭流程中的不同角色,帮助用户全面理解整个TCP状态机制。</think>以下是关于TCP连接状态`FIN_WAIT1`的详细解析: --- ### 一、基础定义 #### 1. **状态归属** - 属于**主动关闭方**的状态(主动发起TCP连接关闭的一端) - 位于TCP四次挥手的第一阶段 #### 2. **触发条件** 当应用调用`close()`或`shutdown(SHUT_WR)`后,系统发送`FIN`报文,此时进入`FIN_WAIT1`状态 --- ### 二、状态转换原理 #### 1. **正常流程** $$ \begin{aligned} &\text{主动关闭方} && \text{被动关闭方} \\ &\text{FIN} \rightarrow && \rightarrow \text{进入CLOSE_WAIT} \\ &\text{ACK} \leftarrow && \leftarrow \text{发送ACK} \\ & \text{进入FIN_WAIT2} && \\ &\text{FIN} \leftarrow && \leftarrow \text{发送FIN} \\ &\text{ACK} \rightarrow && \rightarrow \text{进入LAST_ACK} \\ & \text{进入TIME_WAIT} && \\ \end{aligned} $$ #### 2. **异常场景** - **未收到ACK**:长时间(默认60秒)未收到对方的`ACK`,系统会重传`FIN` - **同时关闭**:双方同时发送`FIN`会进入`FIN_WAIT1`,收到对方`FIN`后直接进入`CLOSING` --- ### 三、典型问题诊断 #### 1. **大量FIN_WAIT1堆积** - **现象**:`netstat`/`ss`输出中持续存在`FIN_WAIT1` - **常见原因**: ```mermaid graph TD A[FIN_WAIT1堆积] --> B[网络丢包] A --> C[对方未响应ACK] C --> D[对方服务崩溃] C --> E[中间设备拦截] ``` #### 2. **排查方法** ```bash # 查看FIN_WAIT1连接详情 ss -antop state fin-wait-1 | grep ':19008' # 抓包分析(关键字段:FIN+ACK交互) tcpdump -i eth0 'tcp[tcpflags] & (tcp-fin|tcp-ack) != 0 and port 19008' ``` --- ### 四、优化建议 #### 1. **参数调优** ```bash # 减少FIN重试次数(默认15次) sysctl -w net.ipv4.tcp_orphan_retries=3 # 缩短FIN_WAIT1超时(默认60秒) sysctl -w net.ipv4.tcp_fin_timeout=30 ``` #### 2. **应用层改进** - 避免短连接高频创建/销毁 - 使用连接池复用TCP连接 - 确保`close()`调用前完成数据发送 --- ### 五、与CLOSE_WAIT的关联分析 | 状态对比 | FIN_WAIT1 | CLOSE_WAIT | |----------------|----------------------------------|--------------------------------| | **角色** | 主动关闭方 | 被动关闭方 | | **触发动作** | 本地发送FIN后 | 收到远端FIN后 | | **问题本质** | 网络问题或对方无响应 | 本地应用未调用close() | | **处理优先级** | 中(可能自动恢复) | 高(需立即修复代码) | --- ### 六、扩展案例 #### 案例:Kubernetes服务频繁断连 - **现象**:Pod重启时出现`FIN_WAIT1`堆积 - **根因**: - 上游服务未正确处理`Connection: close`头 - kube-proxy规则更新延迟导致ACK丢失 - **解决方案**: ```yaml # 服务配添加优雅关闭 lifecycle: preStop: exec: command: ["sleep", "30"] ``` --- 建议结合具体业务场景分析,如需深入诊断可提供`ss -s`统计输出或`tcpdump`抓包文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值