TCP 连接建立的三次握手过程可以携带数据吗?前几天实验室的群里扔出了这样一个问题:TCP 连接建立的三次握手过程可以携带数据吗?突然发现自己还真不清楚这个问题,平日里用 tcpdump 或者 Wireshark 抓包时,从来没留意过第三次握手的 ACK 包有没有数据。于是赶紧用 nc 配合 tcpdump 抓了几次包想检验一下。但是经过了多次实验,确实都发现第三次握手的包没有其它数据(后文解释)。后来的探究中发现这个过程有问题,遂整理探究过程和结论汇成本文,以供后来者参考。 先来张三次握手的图(下面这张图来自网络,若侵犯了作者权利,请联系我删除): RFC793 文档里带有 SYN 标志的过程包是不可以携带数据的,也就是说三次握手的前两次是不可以携带数据的(逻辑上看,连接还没建立,携带数据好像也有点说不过去)。重点就是第三次握手可不可以携带数据。 先说结论:TCP 协议建立连接的三次握手过程中的第三次握手允许携带数据。 对照着上边的 TCP 状态变化图的连接建立部分,我们看下 RFC793 文档的说法。RFC793 文档给出的说法如下(省略不重要的部分): 重点是这句 “Data or controls which were queued for transmission may be included”,也就是说标准表示,第三次握手的 ACK 包是可以携带数据。那么 Linux 的内核协议栈是怎么做的呢?侯捷先生说过,“源码面前,了无秘密”。最近恰逢 Kernel 4.0 正式版发布,那就追查下这个版本的内核协议栈的源码吧。 在探索源码前,我们假定读者对 Linux 的基本 socket 编程很熟悉,起码对连接的流程比较熟悉(可以参考这篇文章《浅谈服务端编程》最前边的 socket 连接过程图)。至于 socket 接口和协议栈的挂接,可以参阅《socket 接口与内核协议栈的挂接》。 首先, 第三次握手的包是由连接发起方(以下简称客户端)发给端口监听方(以下简称服务端)的,所以只需要找到内核协议栈在一个连接处于 SYN-RECV(图中的 SYN_RECEIVED)状态时收到包之后的处理过程即可。经过一番搜索后找到了,位于 net\ipv4 目录下 tcp_input.c 文件中的 tcp_rcv_state_process 函数处理这个过程。如图: 这个函数实际上是个 TCP 状态机,用于处理 TCP 连接处于各个状态时收到数据包的处理工作。这里有几个并列的 switch 语句,因为函数很长,所以比较容易看错层次关系。下图是精简了无需关注的代码之后 SYN-RECV 状态的处理过程: 一定要注意这两个 switch 语句是并列的。所以当 TCP_SYN_RECV 状态收到合法规范的二次握手包之后,就会立即把 socket 状态设置为 TCP_ESTABLISHED 状态,执行到下面的 TCP_ESTABLISHED 状态的 case 时,会继续处理其包含的数据(如果有)。 上面表明了,当客户端发过来的第三次握手的 ACK 包含有数据时,服务端是可以正常处理的。那么客户端那边呢?那看看客户端处于 SYN-SEND 状态时,怎么发送第三次 ACK 包吧。如图: tcp_rcv_synsent_state_process 函数的实现比较长,这里直接贴出最后的关键点: 一目了然吧?if 条件不满足直接回复单独的 ACK 包,如果任意条件满足的话则使用 inet_csk_reset_xmit_timer 函数设置定时器等待短暂的时间。这段时间如果有数据,随着数据发送 ACK,没有数据回复 ACK。 之前的疑问算是解决了。 但是,那三个条件是什么?什么情况会导致第三次握手包可能携带数据呢?或者说,想抓到一个第三次握手带有数据的包,需要怎么做?别急,本博客向来喜欢刨根问底,且听下文一一道来。 条件 1:sk- |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|