彻底弄懂TCP协议:从三次握手说起

hosts
hosts
hosts
12
文章
0
评论
2020-09-0414:24:45 评论 155

说到TCP协议,相信大家肯定都比较熟悉,对于TCP协议也是能够说出个所以然来,但是TCP协议又是一个非常复杂的协议,其中有不少细节点让人头痛。接下来就是来讲讲TCP的一些让人头痛的技术细节和疑难杂症。那么从哪开始说起呢,当然是从三次握手和四次挥手说起,可能大家都知道TCP是三次交互完成连接,四次交互来断开一个连接,那么为什么三次挥手和四次挥手呢?反过来行不行?

1. TCP三次握手、四次挥手

先给出两张图

彻底弄懂TCP协议:从三次握手说起

TCP三次握手、四次挥手时序图

彻底弄懂TCP协议:从三次握手说起

TCP协议状态机

要弄清TCP建立连接需要几次交互才行,我们需要弄清建立连接进行初始化的目的是什么。TCP进行握手初始化一个连接的目标是:分配资源、初始化序列号(通知peer对端我的初始化序列号是多少),知道初始化连接的目标,那么要达成这个目标的过程就简单了,握手过程可以简化为下面的四次交互:

  1. client端首先发送一个SYN包告诉server端我的初始序列号是X;
  2. server端收到SYN包后回复给client一个ACK确认包,告诉client说我收到了
  3. 接着server端也需要告诉client端自己的初始化序列号,于是server端也发送一个SYN包告诉client我的初始化序列号是Y
  4. client收到后,回复server一个ACK确认包说我知道了。

整个过程4次交互即可完成初始化,但是,细心的同学会发现两个问题:

  • server发送SYN包是作为发起连接的SYN包,还是作为相应发起者的SYN包呢?则么区分?比较容易混淆
  • server的ACK确认包和接下来的SYN包可以合成一个SYN ACK包一起发送,没必要分单独发送,这样省了一次交互同时也解决了问题【1】,这样TCP建立一个连接,三次握手在进行最少次交互的情况下完成了Peer两端的资源分配和初始化序列号的交换。

大部分情况下建立连接需要进行三次握手,也不一定都是三次,有可能出现四次握手来建立连接的。如下图,当Peer两端同时发起SYN来建立连接的时候,就出现了四次握手来建立连接的(对于有些TCP/IP的实现,可能不支持这种同时打开的情况)。

彻底弄懂TCP协议:从三次握手说起

在三次握手过程中,可能会有一下疑问:

  • 初始化序列号X、Y是可以是写死固定的吗,为什么不能?
  • 加入client发送一个SYN包给server后就挂了或是不管了,这个时候这个连接处于什么状态呢?会超时吗?为什么呢?

TCP进行断开连接的目标是:回收资源、终止数据传输。由于TCP是全双工的,需要Peer两端分别各自拆除自己通向peer对端的方向的通信信道。这样需要四次挥手分别拆除通信信道,就比较清晰明了了。

  1. client发送一个FIN包来告诉server我已经没数据要发给server了;
  2. server收到后回复一个ACK确认包说我知道了;
  3. 然后server在自己也没有数据发送给client后,server也发送一个FIN包给client告诉client我也已经没有数据发送client了
  4. client收到后,就会回复一个ACK确认包说我知道了

到此,四次挥手,这个TCP连接就可以完全拆除了。在四次挥手的过程中,不难发现有如下疑问:

  • client和server同时发起断开连接的FIN包会怎么样呢,TCP状态是怎么转移的?
  • 左侧图中的四次挥手过程中,server端的ACK确认包能不能和接下来的FIN包合并成一个包呢,这样四次挥手就变成三次挥手了。
  • 四次挥手过程中,首先断开连接的一端,在回复最后一个ACK后,为什么要进行TIME_WAIT呢(超时设置是2*MSL,RCF793定义了额MSL为2分钟,Linux设置成了30s),在TIME_WAIT的时候又不能释放资源,白白让资源占用那么长时间,能不能省了TIME_WAIT呢,为什么?

2. TCP 连接的初始化序列号能否固定

如果初始化序列号可以固定,我们来看看会出现什么问题。建设ISN固定为1,client和server建立好一条TCP连接后,client连续给server发了10个包,这10个包不知怎么被链路上的路由器缓存了(路由器会毫无先兆得缓存或者丢弃任何数据包),这个时候碰巧client挂掉了,然后client的序列号变成了5。

接着,之前被路由器缓存的10个数据包全部被路由到server端了,server给client回复确认号10,这个时候,client整个都不好了,这是什么情况?我的序列号才到5,你怎么给我的确认号是10了,整个都乱了。RFC793中,建议ISN和一个件的时钟绑在一起,这个时钟会在每4微秒对ISN做加一操作,直到超过2^32,又从0开始,这需要4小时才会产生ISN回绕问题,这几乎可以保证每个新连接的ISN不会和旧的连接的ISN产生冲突。这种递增方式的ISN,很容易让攻击者猜测到TCP连接的ISN,现实的实现大多是在一个基准值的基础上进行随机的。

3. 初始化连接的 SYN 超时问题

client发送SYN包给server后挂了,server回给client的SYN-ACK一直没收到client的ACK确认,这个时候这个连接既没有建立起来,也不能算失败。这就需要一个超时时间让server将这个连接断开,否则这个连接就会异之占用server的SYN连接队列中的一个位置,大量这样的连接就会将server的SYN连接队列耗尽,让正常的连接无法得到处理。目前,Linux下默认会进行5次重发SYN-ACK包,重试的时间间隔从1s开始,下次的重试间隔时间是前一次的两倍,5次重试时间间隔为1s,2s,4s,8s,16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要1s+2s+4s+8s+16s+32s=63s,TCP才会把这个连接断开。

由于,SYN超时需要63秒,那么就给攻击者一个攻击服务器的机会,攻击者会在短时间内发送大量的SYN包给server(俗称SYN flood攻击),用于耗尽server 的SYN队列。对于应对SYN过多的问题,Linux提供了几个TCP参数:tcp_syncookies、tcp_synack_retries、tcp_max_syn_backlog、tcp_abort_on_overflow 来调整应对。

 4. TCP 的 Peer 两端同时断开连接

由上面的“TCP状态机”图可以看出,TCP的Peer端在收到对端的FIN包前发出了FIN包,那么该Peer的状态就变成了FIN_WAIT1,Peer在FIN_WAIT1状态下收到对端Peer对自己FIN包的ACK包的话,那么peer状态就变成FIN_WAIT2,Peer在FIN_WAIT2下收到对端Peer的FIN包,在确认已经收到对端Peer全部的Data数据包后,就响应一个ACK给对端的Peer,然后自己进入TIME_WAIT状态。

但是如果Peer在FIN_WAIT1状态下首先收到对端Peer的FIN包的话,那么该Peer在确认已经收到对端Peer全部的Data数据包后,就响应一个ACK给对端的Peer,然后自己进入CLOSEING状态,Peer在CLOSEING状态下收到自己的FIN包的ACK包的话,那么就进入TIME_WAIT状态。于是,TCP的Peer两端同时发起FIN包进行断开连接,那么两端Peer科恩那个出现完全一样的状态转移FIN_WAIT1——>CLOSEING——>TIME_WAIT,也就会client和server最后同时进入TIME_WAIT状态。同时关闭连接的状态转移如下图所示:

彻底弄懂TCP协议:从三次握手说起

5. 四次挥手能不能变成三次挥手??

答案是可能的。TCP是全双工通信,client在自己已经不会在新的数据要发送给server后,可以发送FIN信号告知server,这边已经终止client到对端server那边得数据传输。但是,这个时候对端server可以继续往client这边发送数据包。于是,两端数据传输的终止在时序上是对立并且可能会象个比较长时间,这个时候就必须最少需要2+2=4次挥手来终止这个连接。但是,如果server在收到client的FIN包后,在也没有数据需要发送给client了,那么对client的ACK包和server自己的FIN包就可以合并成为一个包发送过去,这样四次挥手就可以变成三次了。

6. TCP的头号痛症 TIME_WAIT状态

要说明TIME_WAIT的问题,需要解答一下几个问题

一、peer两端,哪一端会进入TIME_WAIT状态,为什么

相信大家都知道,TCP主动关闭连接的那一方会最后进入TIME_WAIT,那么怎么界定主动关闭方呢?是否主动关闭是由FIN包的先后决定的,就是在自己没收到对端peer的FIN包之前自己发出了FIN包,那么自己就是主动关闭连接的那一方。对于问题4中描述的情况,那么peer两边都是主动关闭的一方,两边都会进入TIME_WAIT。为什么主动关闭的一方进行TIME_WAIT呢,被动关闭的进入TIME_WAIT可以不呢?下面来看看TCP四次挥手可以简单分为下面三个过程:

过程一:主动关闭方发送FIN;

过程二:被动关闭方收到主动关闭方的FIN后发送该FIN的ACK,被动关闭方发送FIN;

过程三:主动关闭方收到被动关闭方的FIN后发送该FIN的ACK,被动关闭方等待自己的FIN的ACK。

问题就在过程三中,据TCP协议规定,不对ACK进行ACK,如果主动关闭方不进入TIME_WAIT,那么主动关闭方在发送完ACK就走了的话,如果最后发送的ACK在路由过程中丢掉了,最后没能到被动关闭方,这个时候被动关闭方没收到自己的FIN的ACK就不能关闭连接,接着被动关闭方会超时重发FIN包,但是这个时候已经没有对端会给该FIN回ACK,被动关闭方就无法进行正常的关闭连接了,所以主动关闭方需要进入TIME_WAIT以便能够重发丢掉的被动关闭方FIN的ACK

二、TIME_WAIT 状态是用来解决或避免什么问题呢?

 

hosts
  • 本文由 发表于 2020-09-0414:24:45
  • 转载请务必保留本文链接:https://gsxio.com/os/net/tcp.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: