TCP建立连接的相关问题

1.TCP三次握手过程和状态变迁

  TCP是面向连接的协议,使用TCP前必须先建立连接,建立连接是通过三次握手进行的。

  

  (1)在一开始的时候,客户端和服务端都是处于CLOSED状态,先是服务端主动监听某个端口,处于LISTEN状态。

  (2)客户端会随机初始化序号(client_isn),将此序号置于TCP首部的序号字段中,同时把SYN标志位置为1,表示SYN报文。接着把第一个SYN报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于SYN_SENT状态。

  客户端发送的SYN报文

  

   (3)服务端收到客户端SYN报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入TCP首部的序号字段中,其次把TCP首部的确认应答号字段填入client_isn+1,接着把SYN和ACK标志位都置为1,最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于SYN-RCVD状态。

  服务端发给客户端SYN+ACK报文

  

   (4)客户端收到服务端报文之后,还要向服务端回应最后一个应答报文,首先该应答报文TCP首部ACK标志位置为1,其次确认应答号字段填server_isn+1,最后把该报文发送给服务端,这次报文可以携带客户端到服务器的数据,之后客户端处于ESTABLISHED状态。

  客户端发给服务端的ACK报文

  

   (5)服务器收到客户端的应答报文之后,也进入ESTABLISHED状态。

  前两次握手不可以携带数据,第三次握手是可以携带数据的。一旦完成三次握手,双方就处于ESTABLISHED状态,此时连接就已建立完成,客户端和服务器端就可以相互发送数据了。

2.为什么是三次握手?而不是两次或者四次?

  (1)三次握手才可以阻止历史重复连接的初始化(主要原因)

  三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。

  网络环境是复杂的,由于网络堵塞等原因,先发送的数据包并不一定是最先到达目标主机的,客户端连续发送多次SYN建立连接的报文,在网络堵塞的情况下:

  • 一个旧的SYN报文比最新的SYN报文早到达服务端;
  • 那么此时服务端就会回送一个SYN+ACK报文给客户端;
  • 客户端收到后根据自身上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送一个RST报文给服务端,表示中止这一次连接。

  如果是两次握手的话,就不能判断当前连接是否是历史连接,三次握手可以在客户端准备发送第三次报文时,有足够的上下文来判断当前连接是否是历史连接:

  1> 如果是历史连接,则第三次握手发送的报文是RST报文,中止连接;

  2> 如果不是历史连接,则第三次发送的报文是ACK报文,通信双方会成功建立连接。

  所以,TCP使用三次握手建立连接的主要原因是防止历史连接初始化了连接。  

  (2)三次握手才可以同步双方的初始序列号

  TCP协议的通信双方,都必须维护一个序列号,序列号是可靠传输的一个关键因素,它的作用是:

  • 接收方可以去除重复的数据;
  • 接收方根据数据包的序列号按序接收;
  • 可以标识发送出去的数据包中,哪些是已经被双方收到的。

  当客户端发送携带初始序列号的SYN报文时,需要服务端回一个ACK应答报文,表示客户端的SYN报文已被成功接收,那当服务端发哦是能够初始序列号给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能保证双方的初始序列号能被可靠的同步。

  四次握手也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了三次握手。 而两次握手只保证了一方的初始序列号能被对方成功接收,没有办法保证双方的初始序列号都能被确认接收。

  

  (3)三次握手才可以避免资源浪费

  如果只有两次握手,当客户端的SYN请求连接在网络中阻塞,客户端没有接收到ACK报文就会重新发送SYN,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的ACK确认信号,所以每收到一个SYN就只能先主动建立一个连接,这样服务器就会建立冗余的无效连接,造成不必要的资源浪费。

  也就是说,两次握手在造成消息滞留的情况下,服务器重复接收无用的连接请求SYN报文,而造成重复分配资源。

  总结:  

  不使用两次握手和四次握手的原因:

  • 两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
  • 四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。

 3.为什么客户端和服务端的初始序列号的ISN是不相同的?

  因为网络中的报文会延迟、复制重发、有可能丢失,这样会造成的不同连接之间产生相互影响,所以为了避免相互影响,客户端和服务端的初始序列号是随机且不同的。  

 4.初始序列号ISN是如何随机产生的?

   最开始时ISN是基于时钟的,每4毫秒+1,转一圈要4.55个小时。

  RFC1948中提出了较好的初始化序列号ISN随机生成算法。ISN=M+F(localhost, localport,remotehost, remoteport)。其中M是一个计时器,这个计时器每个4毫秒加1;F是一个Hash算法,根据源IP、目的IP、源端口、目标端口生成一个随机数值,要保证Hash算法不能被外部轻易推算得出,用MD5算法是一个比较好的选择。

 5.既然IP会分片,为什么TCP还需要MSS呢?

  MTU:一个网络包的最大长度,以太网中一般是1500字节;

  MSS:除去IP和TCP头部之后,一个网络包所能容纳的TCP数据的最大长度。

   

  如果TCP的整个报文(头部+数据)交给IP进行分片的话:

  当IP层有一个超过MTU大小的数据(TCP头部+TCP数据)要发送,那么IP层就要进行分片,把数据分片成若干片,保证每一个分片都小于MTU。把一份IP数据进行分片以后,由目标主机的IP层进行重新组装后,再交给上一层传输层。但是如果一个IP分片丢失整个IP报文的所有分片都要重传,因为IP层没有超时重传机制,它由传输层的TCP来负责超时和重传。当接收方发现TCP报文(头部+数据)的某一片丢失后,则不会响应ACK给对方,那么发送方的TCP在超时后,就会重发整个TCP报文(头部+数据)。所以,由IP层进行分片传输的话是非常没有效率的。

  为了达到最佳的传输效能,TCP协议在建立连接的时候通常需要协商双方的MSS值,当TCP层发现数据超过MSS后,则就会先进行分片,当然由它形成的IP包的长度也就会大于MTU,自然也就不用IP分片。

  握手协商MSS:

  

   经过TCP分片后,如果一个TCP分片丢失后,进行重发时也是以MSS为单位,而不用重传所有的分片,大大增大了重传的效率。

6.什么是SYN攻击?如何避免SYN攻击?

  

   假设攻击者短时间内伪造不同IP地址的SYN报文,服务端每接收到一个SYN报文,就进入SYN_RCVD状态,但服务端发送出去的ACK+SYN报文无法得到未知IP的ACK应答,久而久之就会占满服务端的SYN接收队列(未连接队列),使得服务器不能为正常用户服务。

  避免攻击的方式:

  (1)通过修改Linux内核参数,控制队列大小和当队列满时应做什么处理。当网卡接收到数据包的速度大于内核处理的速度时,会有一个队列保存这些数据包,超出处理能时,对新的SYN直接回RST,丢弃连接。

  (2)对Linux内核的SYN队列(未完成连接建立)与Accept队列(已完成连接的建立)的工作流程:

  

  •  当服务端接收到客户端的SYN报文时,会将其加入到内核的SYN队列中;
  • 发送SYN+ACK给客户端,等待客户端回应ACK报文;
  • 服务端接收到ACK报文后,从SYN队列移除放入Accept队列;
  • 应用通过调用accept()的socket接口,从Accept队列取出的连接。

  

   如果应用程序过慢的话,就会导致Accept队列被占满。

  

   如果不断受到SYN攻击,就会导致SYN队列被占满。

  

  •  当SYN队列满之后,后续服务器收到SYN包,不再进入SYN队列;
  • 计算出一个cookie值,再以SYN+ACK中的序列号返回客户端;
  • 服务端接收到客户端的应答报文时,服务器会检查这个ACK包的合法性,如果合法,直接放入到Accept队列;
  • 最后应用调用accept()中socket接口,从Accept队列取出的连接。

参考文献:https://mp.weixin.qq.com/s/ihDCVCI4jm24XDZ9bCTfqQ

posted @ 2020-05-03 17:08  飞鸟迷失天空  阅读(1124)  评论(1编辑  收藏  举报