当TIME_WAIT状态的TCP正常挥手,收到SYN后…
摘要:今天就来讨论下这个问题,在TCP正常挥手过程中,处于TIME_WAIT状态的连接,收到相同四元组的SYN后会发生什么?
本文分享自华为云社区《在TIME_WAIT状态的TCP连接,收到SYN后会发生什么?》,作者:小林coding。
周末跟朋友讨论了一些TCP的问题,在查阅《Linux服务器高性能编程》这本书的时候,发现书上写了这么一句话:
书上说,处于TIME_WAIT状态的连接,在收到相同四元组的SYN后,会回RST报文,对方收到后就会断开连接。
书中作者只是提了这么一句话,没有给予源码或者抓包图的证据。
起初,我看到也觉得这个逻辑也挺符合常理的,但是当我自己去啃了TCP源码后,发现并不是这样的。
所以,今天就来讨论下这个问题,「在TCP正常挥手过程中,处于TIME_WAIT状态的连接,收到相同四元组的SYN后会发生什么?」
问题现象如下图,左边是服务端,右边是客户端:
先说结论
在跟大家分析TCP源码前,我先跟大家直接说下结论。
针对这个问题,关键是要看 SYN 的「序列号和时间戳」是否合法,因为处于 TIME_WAIT 状态的连接收到 SYN 后,会判断 SYN 的「序列号和时间戳」是否合法,然后根据判断结果的不同做不同的处理。
先跟大家说明下, 什么是「合法」的 SYN?
- 合法 SYN:客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要大,并且 SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要大。
- 非法 SYN:客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要小,或者 SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要小。
上面 SYN 合法判断是基于双方都开启了 TCP 时间戳机制的场景,如果双方都没有开启 TCP 时间戳机制,则 SYN 合法判断如下:
- 合法 SYN:客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要大。
- 非法 SYN:客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要小。
收到合法 SYN
如果处于 TIME_WAIT 状态的连接收到「合法的 SYN 」后,就会重用此四元组连接,跳过 2MSL 而转变为 SYN_RECV 状态,接着就能进行建立连接过程。
用下图作为例子,双方都启用了 TCP 时间戳机制,TSval 是发送报文时的时间戳:
上图中,在收到第三次挥手的FIN报文时,会记录该报文的TSval(21),用ts_recent变量保存。然后会计算下一次期望收到的序列号,本次例子下一次期望收到的序列号就是301,用rcv_nxt变量保存。
处于TIME_WAIT状态的连接收到SYN后,因为SYN的seq(400)大于rcv_nxt(301),并且SYN的TSval(30)大于ts_recent(21),所以是一个「合法的SYN」,于是就会重用此四元组连接,跳过2MSL而转变为SYN_RECV状态,接着就能进行建立连接过程。
收到非法的SYN
如果处于TIME_WAIT状态的连接收到「非法的SYN」后,就会再回复一个第四次挥手的ACK报文,客户端收到后,发现并不是自己期望收到确认号(acknum),就回RST报文给服务端。
用下图作为例子,双方都启用了TCP时间戳机制,TSval是发送报文时的时间戳:
上图中,在收到第三次挥手的FIN报文时,会记录该报文的TSval(21),用ts_recent变量保存。然后会计算下一次期望收到的序列号,本次例子下一次期望收到的序列号就是301,用rcv_nxt变量保存。
处于TIME_WAIT状态的连接收到SYN后,因为SYN的seq(200)小于rcv_nxt(301),所以是一个「非法的SYN」,就会再回复一个与第四次挥手一样的ACK报文,客户端收到后,发现并不是自己期望收到确认号,就回RST报文给服务端。
客户端等待一段时间还是没收到SYN+ACK后,就会超时重传SYN报文,重传次数达到最大值后,就会断开连接。
PS:这里先埋一个疑问,处于TIME_WAIT状态的连接,收到RST会断开连接吗?
源码分析
下面源码分析是基于Linux4.2版本的内核代码。
Linux内核在收到TCP报文后,会执行tcp_v4_rcv函数,在该函数和TIME_WAIT状态相关的主要代码如下:
inttcp_v4_rcv(structsk_buff*skb)
{
structsock*sk;
...
//收到报文后,会调用此函数,查找对应的sock
sk=__inet_lookup_skb(&tcp_hashinfo,skb,__tcp_hdrlen(th),th->source,
th->dest,sdif,&refcounted);
if(!sk)
gotono_tcp_socket; process:
//如果连接的状态为time_wait,会跳转到do_time_wait
if(sk->sk_state==TCP_TIME_WAIT)
gotodo_time_wait; ... do_time_wait:
...
//由tcp_timewait_state_process函数处理在time_wait状态收到的报文
switch(tcp_timewait_state_process(inet_twsk(sk),skb,th)){
//如果是TCP_TW_SYN,那么允许此SYN重建连接
//即允许TIM_WAIT状态跃迁到SYN_RECV
caseTCP_TW_SYN:{
structsock*sk2=inet_lookup_listener(....);
if(sk2){
....
gotoprocess;
}
}
//如果是TCP_TW_ACK,那么,返回记忆中的ACK
caseTCP_TW_ACK:
tcp_v4_timewait_ack(sk,skb);
break;
//如果是TCP_TW_RST直接发送RESET包
caseTCP_TW_RST:
tcp_v4_send_reset(sk,skb);
inet_twsk_deschedule_put(inet_twsk(sk));
gotodiscard_it;
//如果是TCP_TW_SUCCESS则直接丢弃此包,不做任何响应
caseTCP_TW_SUCCESS:;
}
gotodiscard_it;
}
该代码的过程:
- 接收到报文后,会调用__inet_lookup_skb()函数查找对应的sock结构;
- 如果连接的状态是TIME_WAIT,会跳转到do_time_wait处理;
- 由tcp_timewait_state_process()函数来处理收到的报文,处理后根据返回值来做相应的处理。
先跟大家说下,如果收到的SYN是合法的,tcp_timewait_state_process()函数就会返回TCP_TW_SYN,然后重用此连接。如果收到的SYN是非法的,tcp_timewait_state_process()函数就会返回TCP_TW_ACK,然后会回上次发过的ACK。
接下来,看tcp_timewait_state_process()函数是如何判断SYN包的。
enumtcp_tw_status
tcp_timewait_state_process(structinet_timewait_sock*tw,structsk_buff*skb,
conststructtcphdr*th)
{
...
//paws_reject为false,表示没有发生时间戳回绕
//paws_reject为true,表示发生了时间戳回绕
boolpaws_reject=false; tmp_opt.saw_tstamp=0;
//TCP头中有选项且旧连接开启了时间戳选项
if(th->doff>(sizeof(*th)>>2)&&tcptw->tw_ts_recent_stamp){
//解析选项
tcp_parse_options(twsk_net(tw),skb,&tmp_opt,0,NULL); if(tmp_opt.saw_tstamp){
...
//检查收到的报文的时间戳是否发生了时间戳回绕
paws_reject=tcp_paws_reject(&tmp_opt,th->rst);
}
} .... //是SYN包、没有RST、没有ACK、时间戳没有回绕,并且序列号也没有回绕,
if(th->syn&&!th->rst&&!th->ack&&!paws_reject&&
(after(TCP_SKB_CB(skb)->seq,tcptw->tw_rcv_nxt)||
(tmp_opt.saw_tstamp&&//新连接开启了时间戳
(s32)(tcptw->tw_ts_recent-tmp_opt.rcv_tsval)<0))){//时间戳没有回绕
//初始化序列号
u32isn=tcptw->tw_snd_nxt+65535+2;
if(isn==0)
isn++;
TCP_SKB_CB(skb)->tcp_tw_isn=isn;
returnTCP_TW_SYN;//允许重用TIME_WAIT四元组重新建立连接
}
if(!th->rst){
//如果时间戳回绕,或者报文里包含ack,则将TIMEWAIT状态的持续时间重新延长
if(paws_reject||th->ack)
inet_twsk_schedule(tw,&tcp_death_row,TCP_TIMEWAIT_LEN,
TCP_TIMEWAIT_LEN); //返回TCP_TW_ACK,发送上一次的ACK
returnTCP_TW_ACK;
}
inet_twsk_put(tw);
returnTCP_TW_SUCCESS;
}
如果双方启用了TCP时间戳机制,就会通过tcp_paws_reject()函数来判断时间戳是否发生了回绕,也就是「当前收到的报文的时间戳」是否大于「上一次收到的报文的时间戳」:
- 如果大于,就说明没有发生时间戳绕回,函数返回false。
- 如果小于,就说明发生了时间戳回绕,函数返回true。
从源码可以看到,当收到SYN包后,如果该SYN包的时间戳没有发生回绕,也就是时间戳是递增的,并且SYN包的序列号也没有发生回绕,也就是SYN的序列号「大于」下一次期望收到的序列号。就会初始化一个序列号,然后返回TCP_TW_SYN,接着就重用该连接,也就跳过2MSL而转变为SYN_RECV状态,接着就能进行建立连接过程。
如果双方都没有启用TCP时间戳机制,就只需要判断SYN包的序列号有没有发生回绕,如果SYN的序列号大于下一次期望收到的序列号,就可以跳过2MSL,重用该连接。
如果SYN包是非法的,就会返回TCP_TW_ACK,接着就会发送与上一次一样的ACK给对方。
在TIME_WAIT状态,收到RST会断开连接吗?
在前面我留了一个疑问,处于TIME_WAIT状态的连接,收到RST会断开连接吗?
会不会断开,关键看net.ipv4.tcp_rfc1337这个内核参数(默认情况是为0):
- 如果这个参数设置为0,收到RST报文会提前结束TIME_WAIT状态,释放连接。
- 如果这个参数设置为1,就会丢掉RST报文。
源码处理如下:
enumtcp_tw_status
tcp_timewait_state_process(structinet_timewait_sock*tw,structsk_buff*skb,
conststructtcphdr*th)
{
....
//rst报文的时间戳没有发生回绕
if(!paws_reject&&
(TCP_SKB_CB(skb)->seq==tcptw->tw_rcv_nxt&&
(TCP_SKB_CB(skb)->seq==TCP_SKB_CB(skb)->end_seq||th->rst))){ //处理rst报文
if(th->rst){
//不开启这个选项,当收到RST时会立即回收tw,但这样做是有风险的
if(twsk_net(tw)->ipv4.sysctl_tcp_rfc1337==0){
kill:
//删除tw定时器,并释放tw
inet_twsk_deschedule_put(tw);
returnTCP_TW_SUCCESS;
}
}else{
//将TIMEWAIT状态的持续时间重新延长
inet_twsk_reschedule(tw,TCP_TIMEWAIT_LEN);
} ...
returnTCP_TW_SUCCESS;
}
}
TIME_WAIT状态收到RST报文而释放连接,这样等于跳过2MSL时间,这么做还是有风险。
sysctl_tcp_rfc1337这个参数是在rfc1337文档提出来的,目的是避免因为TIME_WAIT状态收到RST报文而跳过2MSL的时间,文档里也给出跳过2MSL时间会有什么潜在问题。
TIME_WAIT状态之所以要持续2MSL时间,主要有两个目的:
- 防止历史连接中的数据,被后面相同四元组的连接错误的接收;
- 保证「被动关闭连接」的一方,能被正确的关闭;
详细的为什么要设计TIME_WAIT状态,我在这篇有详细说明:如果 TIME_WAIT 状态持续时间过短或者没有,会有什么问题?
虽然TIME_WAIT状态持续的时间是有一点长,显得很不友好,但是它被设计来就是用来避免发生乱七八糟的事情。
《UNIX网络编程》一书中却说道:TIME_WAIT是我们的朋友,它是有助于我们的,不要试图避免这个状态,而是应该弄清楚它。
所以,我个人觉得将net.ipv4.tcp_rfc1337设置为1会比较安全。
总结
在TCP正常挥手过程中,处于TIME_WAIT状态的连接,收到相同四元组的SYN后会发生什么?
如果双方开启了时间戳机制:
- 如果客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要大,并且SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要大。那么就会重用该四元组连接,跳过 2MSL 而转变为 SYN_RECV 状态,接着就能进行建立连接过程。
- 如果客户端的 SYN 的「序列号」比服务端「期望下一个收到的序列号」要小,或者SYN 的「时间戳」比服务端「最后收到的报文的时间戳」要小。那么就会再回复一个第四次挥手的 ACK 报文,客户端收到后,发现并不是自己期望收到确认号,就回 RST 报文给服务端。
在TIME_WAIT状态,收到RST会断开连接吗?
- 如果net.ipv4.tcp_rfc1337参数为0,则提前结束TIME_WAIT状态,释放连接。
- 如果net.ipv4.tcp_rfc1337参数为1,则会丢掉该RST报文。
当TIME_WAIT状态的TCP正常挥手,收到SYN后…的更多相关文章
- 在 TIME_WAIT 状态的 TCP 连接,收到 SYN 后会发生什么?
周末跟朋友讨论了一些 TCP 的问题,在查阅<Linux 服务器高性能编程>这本书的时候,发现书上写了这么一句话: 书上说,处于 TIME_WAIT 状态的连接,在收到相同四元组的 SYN ...
- 好一个Time_Wait状态(TCP/IP)
首先简单介绍一下Time_Wait是个什么鬼: 在TCP/IP协议中,我们都知道有三次握手四次挥手的过程,先来一个简单的图: 各个状态和基本的过程想必了解过TCP/IP协议的人都清楚,本次介绍的主题只 ...
- TCP服务端收到syn但是不回复syn ack问题分析
文章转载自:https://blog.csdn.net/jueshengtianya/article/details/52130667 最近在分析客户的一个问题时遇到了一种奇怪的情况,客户在服务端开启 ...
- close_wait状态和time_wait状态(TCP连接)
1.CLOSE_WAIT的简单解决方案 不久前,我的Socket Client程序遇到了一个非常尴尬的错误.它本来应该在一个socket长连接上持续不断地向服务器发送数据,如果socket连接断开,那 ...
- 关于tcp中time_wait状态的4个问题
time_wait是个常问的问题.tcp网络编程中最不easy理解的也是它的time_wait状态,这也说明了tcp/ip四次挥手中time_wait状态的重要性. 以下通过4个问题来描写叙述它 问题 ...
- [转]Linux服务器上11种网络连接状态 和 TCP三次握手/四次挥手详解
一.Linux服务器上11种网络连接状态: 图:TCP的状态机 通常情况下:一个正常的TCP连接,都会有三个阶段:1.TCP三次握手;2.数据传送;3.TCP四次挥手. 注:以下说明最好能结合”图:T ...
- tcp十种状态;关于tcp中time_wait状态(2MSL问题)
tcp十种状态 注意: 当一端收到一个FIN,内核让read返回0来通知应用层另一端已经终止了向本端的数据传送 发送FIN通常是应用层对socket进行关闭的结果 关于tcp中time_wait状态的 ...
- TCP/IP TIME_WAIT状态原理
原文转载:http://elf8848.iteye.com/blog/1739571 IME_WAIT状态原理 ---------------------------- 通信双方建立TCP连接后,主动 ...
- tcp netstat用法 TIME_WAIT状态解析 MTU以及MSS
带着问题写博客 问题1:使用netstat查看有源TCP连接的状态时,经常会看到established状态,那么还有哪些状态,这些状态是如何变化的呢? 问题2:TIME_WAIT状态存在的必要? 问题 ...
随机推荐
- Cesium入门6 - Adding Imagery - 添加图层
Cesium入门6 - Adding Imagery - 添加图层 Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com ...
- Node内部架构图
1.Node内部架构图 先来看一下Node节点的内部实现架构图. 首先最上层入口是Restful风格和javaTcp风格的API入口,RestFul请求映射到处理器RestControl.JavaAp ...
- Java枚举-通过值查找对应的枚举
一.背景 Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等. 最近工作中,对接了很多其他的系统,发现对接的同一个系统 ...
- django之分页算法实现(Paginator)
导入模块:from django.core.paginator import Paginator 一.Paginator的基本用法: from django.core.paginator import ...
- React Transition css动画案例解析
实现React Transition Css动画效果 首先在项目工程中引入react-transition-group: npm install react-transition-group --sa ...
- Execution default-resources of goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources failed.
说明 今天发现这个错误,然后整体检查了一下代码,没有发现任何错误,最后没法只有来一步一步排查. 解决 确定pom文件是否有问题 如上图,有红色波浪线,代表错误,请检查并解决,还有版本是否冲突,最好把不 ...
- Android基本控件Spinner的简单使用【转】
Android基本控件Spinner的简单使用 感谢大佬:https://blog.csdn.net/bingocoder/article/details/80469939 学习过了Textview, ...
- NSPredicate类,指定过滤器的条件---董鑫
/* 比较和逻辑运算符 就像前面的例子中使用了==操作符,NSPredicate还支持>, >=, <, <=, !=, <>,还支持AND, OR, NOT(或写 ...
- JavaGuide--Java篇
本文避免重复造轮子,也是从JavaGuider中提取出来方便日后查阅的手册 参考链接: JavaGuider:https://javaguide.cn/java/basis/java-basic-qu ...
- 范数||x||(norm)笔记
1. 范数的含义和定义 范数是具有"长度"概念的函数.在线性代数.泛函分析及相关领域,是一个函数,它为向量空间内的所有向量赋予非零的正的长度或大小.另一方面,半范数可以为非零的向量 ...