美文网首页网络
【tcp】关于 TCP FIN_WAIT2状态的一个细节问题

【tcp】关于 TCP FIN_WAIT2状态的一个细节问题

作者: Bogon | 来源:发表于2022-08-13 23:38 被阅读0次

    一个关于TCP挥手断开的细节的讨论,越发感到TCP协议真的是非常令人讨厌,这个协议已经成了人们装逼的谈资,就是因为它非常复杂,且毫无确定性可言!
    如果你能说出它的任何细节方面的前因后果,那你一定就是牛人了,但这其实毫无意义。

    截一张TCP状态机的局部,来自RFC793 [page 22]

    image.png

    当触发超时会主动关闭连接,这里涉及到了四次挥手,作为关闭方会发送fin,对端内核会回应ack,这时候客户端从fin-wait1到fin-wait2,而服务端在close-wait状态,等待触发close syscall系统调用。
    服务端什么时候触发close动作?需要等待服务端业务逻辑执行完毕。

    image.png

    这个图非常权威。
    我们注意到FINWAIT-2这个状态,它的转移条件只有一个,即收到对端的FIN,然后进入TIMEWAIT。

    那么问题来了,如果对端死活不发送FIN,本端会一直待在FINWAIT-2状态吗?

    按照TCP全双工的概念推论,答案显然是:收到对端的FIN之前,本端会一直保持FINWAIT-2状态。

    这是合理的,也是符合TCP规范的,因为TCP是一个双向全双工的传输协议,本端发送FIN仅仅意味着本端到对端这个方向上的传输结束了,而对端到本端的传输依然可以继续,直到对端也发送一个FIN过来。
    所以说我们看到断开连接的挥手动作是4次,其实就是两个来回,每一个来回关闭一个方向的数据传输。

    非常完美的解释,非常完美的规范!

    但是….TCP总是伴随着这么些烦人的但是。

    如果对端故意不发送FIN,且也不传输数据,那么意味着本端始终处在FINWAIT-2状态而资源无法释放,这不正是一个DDoS的典型场景吗?
    但是如果不这么做,也不符合TCP双向全双工独立控制的规范啊!
    是规范重要还是现实中的问题重要?

    我们仔细想想迄今为止有多少TCP连接时关闭一半的,即客户端始发方向关闭连接,而服务端依然在传输大量数据这种情形,似乎几乎是没有的。
    相反,几乎很多的C/S模式的TCP连接都是单向的,比如文件下载,至始至终,数据几乎都是从服务器往客户端发送,在最初的客户端请求文件结束后,事实上就相当于客户端始发方向的连接已经被关闭了!

    因此,为了实现双向全双工的语义,完全可以在应用层做,完全没有必要在挥手关闭连接的逻辑上照本宣科而较真儿,事实上,我认为,TCP当初这么设计就是错误的,至少是不合理的,除了增加了复杂性之外,毫无意义。
    也许当初设计协议的都是学院派,始终保持着一种对完备性的笃信和追求,所以既然TCP取自传输控制之名,那么就必须完成传输与控制之完备性的逻辑,也许换个名字会好些。

    从Telnet、FTP、到Apache,Nginx,几乎所有的TCP服务的实现均遵循了收到客户端的FIN之后立即发送FIN这么一个不成文的事实,也就是说,对于主动关闭的一方,当它发送完FIN进入FINWAIT-2状态后,可以在预期的时间内收到对端的FIN从而进入TIMEWAIT状态,而且这个所谓的“预期的时间”不会太长,以秒计算,因此给定一个超时时间是明智的。

    因此,针对上面问题“如果对端死活不发送FIN,本端会一直待在FINWAIT-2状态吗?”的回答我把可能的答案罗列:

    收到对端的FIN之前,本端会一直保持FINWAIT-2状态(标准的要求)
    收到对端的FIN之前,本端会保持FINWAIT-2状态一段足够的时间,超过此时间,连接即释放(现实的要求)

    我们看到,历史选择了现实而摒弃了理想。Linux任意使用2.2内核以上的发行版,看tcp的manual,其中:

    tcp_fin_timeout (integer; default: 60; since Linux 2.2)
    This specifies how many seconds to wait for a final FIN packet before the socket is forcibly closed. This is
    strictly a violation of the TCP specification, but required to prevent denial-of-service attacks. In Linux 2.2,
    the default value was 180.

    现在FINWAIT-2为什么会有个超时时间的问题已经解释清楚了,接下来的问题是,如果FINWAIT-2的timer超时了,这个TCP连接将何去何从?

    我事先还真没有了解过实现的细节,但是按照我对这个问题逻辑的理解,我认为timer到期后连接应该被销毁,顺便给对端发送一个reset。

    既然在预期的时间内对端没有发送FIN(是的,FIN会丢失,但是TCP也会重传,另外,网线也可能被剪断),那么说明对端是“违约”的,至少是不符合常理的,对待不遵守游戏规则的,当然也不需要规则内的措施,直接释放连接是本端的原则,而发送reset则是针对对端“违约”的告知,就是想告诉对端“你违约了,明白吗?”

    我和两位同事楼下抽根烟讨论了这个问题,一位同事持有不同意见,认为不会发送reset,事实证明他是对的,确实不会发送reset。

    如果TCP对端违约,按照这个理念,超时后把连接资源默默释放即可,不必再与之对话!

    从社会工程学的角度来看,如果你只是为了说一句“你错了”,而发送一个reset,搞不好就会被绕进去,所以不理它就是了。

    好了,最后一个问题,在FINWAIT-2超时之后,连接还会进入TIMEWAIT状态吗?

    我认为是不会的,连接会直接消失。
    但是一位同事通过代码确认了一个不同的意见,他认为在经历了FINWAIT-2之后,即FINWAIT-2的timer到期后,连接依然会进入到TIMEWAIT状态,其通过tcp_time_wait函数的调用路径可以确认,调用参数为:

    tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
    

    我始终是持怀疑态度的,一堆堆的if分支,不去实际run的话,也许你能讲清楚的那个分支反而是99%进不去的分支,所以我依然选择设计实验来验证。

    两台机器,一台作为Client,IP地址为192.168.44.138,另一个为监听了22端口的Server,IP地址为192.168.44.111。

    我在Client上配置以下的iptables规则,以阻止Server发送的FIN到达Client的TCP处理逻辑,以模拟对端永远不回复FIN导致FINWAIT-2到期的情形:

    iptables -A INPUT -s 192.168.44.111 -p tcp --tcp-flags SYN,FIN,RST FIN  -j DROP
    

    然后把Client的fin_timeout设置短一些:

    sysctl -w net.ipv4.tcp_fin_timeout=5    
    

    最后我在Client上发起一个连接并随即Ctrl-]+q关闭,以使一个TCP连接进入FINWAIT-2状态
    iptables规则会阻止对端的FIN,因此本端将进入FINWAIT-2而不是TIMEWAIT。

    # telnet 192.168.44.111 22
    Trying 192.168.44.111...
    Connected to 192.168.44.111.
    Escape character is '^]'.
    SSH-2.0-OpenSSH_7.4
    ^]
    telnet> q
    Connection closed.
    
    

    迅速观察netstat:

    # netstat -anpt|grep 111
    tcp        0      0 192.168.44.138:53068    192.168.44.111:22       ESTABLISHED 96417/telnet        
    
    # netstat -anpt|grep 111
    tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -           
            
    # netstat -anpt|grep 111
    tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -                
      
    # netstat -anpt|grep 111
    tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -                   
    
    # netstat -anpt|grep 111
    tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -                   
    
    # netstat -anpt|grep 111
    tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -                   
    
    # netstat -anpt|grep 111
    tcp        0      0 192.168.44.138:53068    192.168.44.111:22       FIN_WAIT2   -                   
    
    

    大概5秒钟,连接灰飞烟灭,什么也没有剩下,连接并没有进入到TIMEWAIT,与此同时tcpdump抓包,也没有看到任何reset。
    实验很简单,但是却说明了问题。

    你没看错,连接在FINWAIT-2超时后并不会进入TIMEWAIT状态,也不会发送reset,而是直接默默消失。

    关于tcp_time_wait这个函数的代码非常恼人,只说几个细节:

    1. 只要调用tcp_time_wait,TCP连接状态就会变成TCP_TIME_WAIT;

    2. 如果以TCP_FIN_WAIT2参数调用tcp_time_wait,则TCP_FIN_WAIT2作为substate处理对端的FIN;

    3. 不管是TCP_FIN_WAIT2还是TCP_TIME_WAIT,均是将TCP连接从Establish哈希链表摘除,重新分配TW item链接进入哈希表。

    关于TCP的任何东西,代码、文档、文章和测试case脚本,都是让人感慨的:

    TIME-WAIT太多的问题
    PAWS问题
    PAWS与NAT的问题
    Nagle问题
    Nagle与CORK问题
    同时打开,同时关闭问题
    Reno,CUBIC的问题
    慢启动,慢慢慢
    keepalive问题
    tcp repair问题
    滑动窗口问题
    ….

    你知道TIME-WAIT持续多久吗?你真的知道吗?

    很多人会回答120秒,很多人会回答2MSL,很多人不知道什么是MSL,2MSL为什么是120秒而不是360秒,我要是说这是根据光速以及地球的周长算出来的你信吗?
    事实上确实是和地球周长有关的。
    如果是在火星上,TCP的TIME-WAIT超时值一定至少半小时。

    可是,对于Linux系统,上面的说法全是谎言,在Linux上,TIMEWAIT的定义是:

    #define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT
                      * state, about 60 seconds */
    

    你不信吗?试试看呗,还是刚才那个Low逼实验,这次把iptables规则去掉,Ctrl-]+q之后,观察TIMEWAIT的时间,观察期间把你的Windows系统右下角的钟表打开,看看是不是60秒后TIME-WAIT连接就消失了。

    Why?为什么TCP的实现一而再地违反所谓的规范?到底有没有规范?到底什么是MUST的,什么是MAY的。

    我同样不喜欢HTTP,确切的说,是HTTP 1.x,同样的原因,它太松散了。
    请问HTTP的头部最长有多长?标准并没有明确的规定,读到\r\n\r\n为结束,但是Web服务器的实现却规定了。
    不然呢?不然一个恶意的客户端可以产生100T的头部,瞬间耗尽服务器的所有资源。

    所以我就很看好HTTP 2.0,它解决了这个问题。

    谈谈企业招聘和面试。

    懂TCP有什么了不起吗?并没有。
    可是TCP几乎是各家必考的内容,令人不解的是,其实很多面试官也不懂,一群不懂TCP的人招了另一群不懂TCP的人,这并不耽误所有的人持续跪舔TCP!

    精不精通不重要,懂就行,TCP是行话,大家一说TCP就知道都在一个坑里找食的。

    IP层的东西难道不重要吗?

    有人碰到一个问题,说是在一台机器上确认TCP的init cwnd就是10,然而一个进程发包的时候只能发出去1个包

    我让他赶紧ip route ls tab all,结果发现了一条路由:

    ..... initcwnd 1
    
    image.png

    这不就是问题的根源吗?很多人不知道initcwnd还能针对路由来指定。不知道吧,哈哈,你也你知道,但你知道这是为什么吗?

    什么是路由?TCP是不管路由的。但是TCP的拥塞控制却完全离不开路由。

    我们都知道,一条路和另一条路的拥塞程度是完全不同的,IP层的路由正是基于这个拥塞程度的不同,想办法用参数告诉你,哪条路更好走一些,仅此而已。悲哀的是,端到端的TCP并没有“路”的概念。

    参考

    解决Linux服务器 FIN_WAIT2 连接过多的问题
    https://blog.51cto.com/professor/1725386

    TCP之RST
    https://www.yuque.com/infuq/others/yyvoa7

    Linux处理TIME_WAIT和FIN_WAIT_2状态
    https://www.cnblogs.com/Gsealy/p/14537774.html

    相关文章

      网友评论

        本文标题:【tcp】关于 TCP FIN_WAIT2状态的一个细节问题

        本文链接:https://www.haomeiwen.com/subject/rwhdmrtx.html