本文分享自华为云社区《在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 报文,客户端收到后,发现并不是自己期望收到确认号(ack num),就回 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 会断开连接吗?

源码分析

下面源码分析是基于 Linux 4.2 版本的内核代码。

Linux 内核在收到 TCP 报文后,会执行 tcp_v4_rcv 函数,在该函数和 TIME_WAIT 状态相关的主要代码如下:

int tcp_v4_rcv(struct sk_buff *skb)
{struct sock *sk;...//收到报文后,会调用此函数,查找对应的 socksk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,th->dest, sdif, &refcounted);if (!sk)goto no_tcp_socket;process://如果连接的状态为 time_wait,会跳转到 do_time_waitif (sk->sk_state == TCP_TIME_WAIT)goto do_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_RECVcase TCP_TW_SYN: {struct sock *sk2 = inet_lookup_listener(....);if (sk2) {....goto process;}}// 如果是TCP_TW_ACK,那么,返回记忆中的ACKcase TCP_TW_ACK:tcp_v4_timewait_ack(sk, skb);break;// 如果是TCP_TW_RST直接发送RESET包case TCP_TW_RST:tcp_v4_send_reset(sk, skb);inet_twsk_deschedule_put(inet_twsk(sk));goto discard_it;// 如果是TCP_TW_SUCCESS则直接丢弃此包,不做任何响应case TCP_TW_SUCCESS:;}goto discard_it;
}

该代码的过程:

  1. 接收到报文后,会调用 __inet_lookup_skb() 函数查找对应的 sock 结构;
  2. 如果连接的状态是 TIME_WAIT,会跳转到 do_time_wait 处理;
  3. 由 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 包的。

enum tcp_tw_status
tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb,const struct tcphdr *th)
{...//paws_reject 为 false,表示没有发生时间戳回绕//paws_reject 为 true,表示发生了时间戳回绕bool paws_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))) { //时间戳没有回绕// 初始化序列号u32 isn = tcptw->tw_snd_nxt + 65535 + 2; if (isn == 0)isn++;TCP_SKB_CB(skb)->tcp_tw_isn = isn;return TCP_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, 发送上一次的 ACKreturn TCP_TW_ACK;}inet_twsk_put(tw);return TCP_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 报文。

源码处理如下:

enum tcp_tw_status
tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb,const struct tcphdr *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定时器,并释放twinet_twsk_deschedule_put(tw);return TCP_TW_SUCCESS;}} else {//将 TIMEWAIT 状态的持续时间重新延长inet_twsk_reschedule(tw, TCP_TIMEWAIT_LEN);}...return TCP_TW_SUCCESS;}
}

TIME_WAIT 状态收到 RST 报文而释放连接,这样等于跳过 2MSL 时间,这么做还是有风险。

sysctl_tcp_rfc1337 这个参数是在 rfc 1337 文档提出来的,目的是避免因为 TIME_WAIT 状态收到 RST 报文而跳过 2MSL 的时间,文档里也给出跳过 2MSL 时间会有什么潜在问题。

TIME_WAIT 状态之所以要持续 2MSL 时间,主要有两个目的:

  • 防止历史连接中的数据,被后面相同四元组的连接错误的接收;
  • 保证「被动关闭连接」的一方,能被正确的关闭;

虽然 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后…相关推荐

  1. 在 TIME_WAIT 状态的 TCP 连接,收到 SYN 后会发生什么?

    周末跟朋友讨论了一些 TCP 的问题,在查阅<Linux 服务器高性能编程>这本书的时候,发现书上写了这么一句话: 书上说,处于 TIME_WAIT 状态的连接,在收到相同四元组的 SYN ...

  2. TCP服务端收到syn但是不回复syn ack问题分析

    参考:http://bbs.jointforce.com/topic/18471 最近在分析客户的一个问题时遇到了一种奇怪的情况,客户在服务端开启了某个端口,但是在客户端telnet确一直不通.通过在 ...

  3. 处于TIME_WAIT状态下收到syn包(臆想篇,望指正)

    处于TIME_WAIT状态下收到syn包 首先分析之前的四次挥手的过程: 服务端主动关闭链接,假设挥手的最后一个ack=301(表明期望这个socket的下一个报的seq为301),然后服务端处于TI ...

  4. TCP三次握手四次挥手及time_wait状态解析

    TCP的建立--三次握手 1.服务器必须准备好接受外来的连接.通常通过调用socket,bind,listen这三个函数来完成,我们称之为被动打开(passive open). 2. 客户端通过调用c ...

  5. 面试官问:大量的 TIME_WAIT 状态 TCP 连接,对业务有什么影响?怎么处理?

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 几个方面: 问题描述:什么现象?什么影响? 问题分析 解决 ...

  6. 大量的 TIME_WAIT 状态 TCP 连接,对业务有什么影响?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | ningg.top/computer-basi ...

  7. TCP/IP详解--TIME_WAIT状态存在的原因

    1. 实际问题         初步查看发现,无法对外新建TCP连接时,线上服务器存在大量处于TIME_WAIT状态的TCP连接(最多的一次为单机10w+,其中引起报警的那个模块产生的TIME_WAI ...

  8. 网络:TCP通讯之 time_wait 状态

    基于TCP协议的通讯流程 1.TCP建立连接 2.TCP断开连接 3.TCP状态转换 TCP状态解释: SYN-RECVD:再收到和发送一个连接请求后等待对方对连接请求的确认 ESTABLISHED: ...

  9. TCP面试常见题:time_wait状态产生的原因,危害,如何避免

    http://blog.csdn.net/u013616945/article/details/77510925 MSL(Maximum Segment Lifetime)最大报文生存时间       ...

最新文章

  1. activemq 消息阻塞优化和消息确认机制优化
  2. 三分钟教你快速选择机器视觉传感器
  3. 如何确认mongodb数据插入是否成功_go连接mongodb
  4. 通用数据级别权限的框架设计与实现(3)-数据列表的权限过滤
  5. 这是人家大一新生开发的工具!网友:我好菜
  6. HarmonyOS之AI能力·IM类意图识别
  7. ios 隔空投安装ipa_ipa文件是什么?怎么安装ipa文件到苹果手机上?
  8. 【TypeScript系列教程07】变量声明
  9. reentrantlock非公平锁不会随机挂起线程?_程序员必须要知道的ReentrantLock 及 AQS 实现原理...
  10. 【原】文本挖掘——特征选择
  11. python模块之codecs: 自然语言编码转换
  12. java线程基础(一些常见多线程用法)
  13. 关于PC浏览器js提示兼容问题的代码 “您的浏览器版本太低了,已经和时代脱轨了“ 强制锁定极速模式
  14. 查看twitter浏览记录_如何查看Twitter提及的通知,但不喜欢或转发
  15. Boosting Crowd Counting via Multifaceted Attention
  16. Prometheus监控以及告警配置
  17. 带宽、吞吐量与线速的关系(转载)
  18. 5种常见的重复形式,平面设计手法进阶学习
  19. 经典Vue面试题一起学起来
  20. 计算机打数据执行保护删除不掉,运行某应用程序时,出现数据执行保护 (DEP)提示的解决方案...

热门文章

  1. (35)Gulp 构建任务组合
  2. 工具 | 终于等到你!地表最强工具来袭!
  3. JavaScript稀疏数组
  4. Bootstrap 源代码之行内代码
  5. es6 调用 Iterator 接口的场合
  6. java实验四结果,java实验四异常处理.doc
  7. 视觉SLAM笔记(3) 视觉SLAM框架
  8. A*算法(一)算法导言
  9. ae编程语言as_计算机基础以及编程语言
  10. 惯性导航算法_自动驾驶关键技术报告:惯性导航和背后的芯片大战