计算机网络——TCP连接管理(三次握手和四次挥手)

计算机网络——TCP连接管理(三次握手和四次挥手)


TCP/IP模型各层数据单元

来自上一层的数据单元称为 服务数据单元(SDU),来自上一层的数据单元加上本层协议首部称为 协议数据单元(PDU)

应用层——报文(message)

  • 以文本传输为例,应用程序中的原始数据是字符序列(相当于OSI中的应用层)。
  • 字符序列通过编码转化(如UTF-8)变为为二进制序列(相当于OSI中的表示层)。
  • 接下来进行何时建立通信连接和发送数据的管理(相当于OSI中的会话层)。

传输层——报文段(segment)

  • 采用TCP协议的传输层根据应用层的指示,负责 与指定端口的应用程序建立连接、发送数据和断开连接

  • 为了实现该功能,需要在应用层数据单元前端附加一个TCP首部。

  • 最大报文段大小(MSS):报文段最理想大小是在网络层中不会被分片处理的最大数据长度。以太网和PPP链路层协议都具有1500字节的 MTU(见下文),除去IP首部的开销(通常40字节),因此 MSS 的典型值是1460字节。
    在这里插入图片描述
    :ACK、SYN、FIN 等标志位具体含义请点击 链接 查看。

网络层——数据报(Datagram)

  • 采用IP协议的网络层负责 将来自传输层的数据发送到指定IP地址的主机

  • 为了实现该功能,需要在传输层数据单元前端附加一个IP首部。

  • 数据链路层有 最大传输单元(MTU) 限制(一般是1500字节),若来自传输层的数据单元过大则在网络层会被拆分成几个片段,每个 分片(fragment) 都有一个IP首部。

  • 网络层提交给数据链路层的数据单元,无论是分片还是没被拆分的数据报都统称为 分组(packet)
    在这里插入图片描述
    :各部分具体含义请点击 链接 查看。

数据链路层——帧(frame)

  • 数据链路层负责 将来自网络层的数据发送到指定MAC地址的网络设备(如网卡、路由器)
  • 为了实现该功能,需要在网络层数据单元前端加一个以太网包首部。

物理层——比特流(bit)

  • 在物理层会将来自数据链路层的帧数据以比特流的形式在物理信道中传输。

建立连接——三次握手

在这里插入图片描述
建立连接前,客户端处于 CLOSED 状态,服务端处于 LISTEN 状态,只能客户端先发起建立连接请求。

第一次握手

客户端

  • 主动打开(active open):向服务端发送 SYN 报文段(SYN=1, SN=client_isn, OPT=client_mss),请求建立连接。
    • client_isn 是客户端初始序号,动态生成,用于实现可靠传输,client_sn-client_isn=客户端已发送字节数。
      • SYN 报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次客户端再向服务端发送的报文端中 SN=client_isn+1 。
      • 除了 SYN 报文段和 ACK-SYN 报文段,其他所有后续报文段的序号 SN 值都等于上次接收的 ACK 报文段中的确认号 AN 值。
    • client_mss 是客户端最大报文段长度,在 TCP 首部的选项和填充部分,会在客户端与服务端的 MSS 中选择一个较小值使用。
  • ② 变为 SYN_SENT 状态,然后等待服务端 ACK 报文段。

第二次握手

服务端

  • ① 接收来自客户端的 SYN 报文段,得知客户端发送能力正常。
  • 被动打开(passive open):向客户端发送 SYN-ACK 报文段(ACK=1, AN=client_isn+1, SYN=1, SN=server_isn, OPT=server_mss),应答来自客户端的建立连接请求并向客户端发起建立连接请求。
    • SN=server_isn 是服务端初始序号;ACK-SYN 报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次服务端再向客户端发送的报文端中 SN=server_isn+1 。
    • OPT=server_mss 是服务端最大报文段长度。
    • AN=client_isn+1 是确认号,表明服务端接下来要开始接收来自客户端的第 client_isn+1 个字节的有效数据。
  • ③ 变为 SYN_RCVD 状态,并等待客户端 ACK 报文段。

第三次握手

客户端

  • ① 接收来自服务端的 SYN-ACK 报文段,得知服务端发送能力和接收能力都正常。
  • ② 向客户端发送 ACK 报文段(ACK=1, AN=server_isn+1, SN=client_isn+1, MESSAGE=message),应答来自服务端的建立连接请求。
    • SN=client_isn+1 是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止客户端向服务端发送的第 (client_isn+1)-clien_isn+1=2 个字节的有效数据。
      • 有效数据(笔者自撰名词,方便读者理解):一般有效数据指的是应用层的报文数据,不过 SYN 报文段、 ACK-SYN 报文段和 FIN 报文段虽然没有携带报文数据,但认为发送了1个字节的有效数据。
    • AN=server_isn+1 是确认号,表明客户端接下来要开始接收来自服务端的第 server_isn+1 个字节的有效数据。
    • MESSAGE=message:此时可以在报文段中携带客户端到服务端的报文数据;该 ACK 报文段消耗的序号个数=message_length(注意message_length可以等于0,即不携带有效数据,此时 ACK 报文段不消耗序号),下次客户端再向服务端发送的报文段中 SN=client_isn+1+message_length 。
  • ③ 变为 ESTABLISHED 状态,client——>server 数据流建立。

服务端

  • ① 接收来自客户端的 ACK 报文段,得知客户端接收能力正常。
  • ② 变为 ESTABLISHED 状态,server——>client 数据流也建立。

断开连接——四次挥手

在这里插入图片描述
断开连接前,客户端和服务端都处于 ESTABLISHED 状态,两者谁都可以先发起断开连接请求。以下假设客户端先发起断开连接请求。

第一次挥手

客户端

  • ① 向服务端发送 FIN 报文段(FIN=1, SN=client_sn),请求断开连接。
    • SN=client_sn 是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止客户端向服务端发送的第 client_sn-clien_isn+1 个字节的有效数据;
      • FIN 报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次客户端再向服务端发送的报文端中 SN=client_isn+1 。
  • ② 客户端变为 FIN_WAIT1 状态,等待服务端 ACK 报文段。

第二次挥手

服务端

  • ① 接收来自客户端的 FIN 报文段。
  • ② 向客户端发送 ACK 报文段(ACK=1, AN=client_sn+1, SN=server_sn_wave2),应答客户端的断开连接请求。
    • SN=server_sn_wave2 是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止服务端向客户端发送的第 server_sn_wave2-clien_isn+1 个字节的有效数据。
    • AN=client_sn+1 是确认号,表明服务端接下来要开始接收来自客户端的第 client_sn+1 个字节的有效数据。
  • ③ 变为 CLOSE_WAIT 状态。

客户端

  • ① 接收来自服务端的 ACK 包。
  • ② 变为 FIN_WAIT2 状态,等待服务端关闭连接请求(FIN)报文段。

第三次挥手

服务端

  • ①(服务端想断开连接时)向客户端发送 FIN 报文段(FIN=1, SN=server_sn),请求断开连接。
    • SN=server_sn 是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止服务端向客户端发送的第 server_sn-clien_isn+1 个字节的有效数据。
      • FIN 报文段虽然不能携带数据,但是会消耗一个序号(相当于发送了1个字节的有效数据),下次服务端再向客户端发送的报文端中 SN=client_isn+2 (若断开连接成功,则服务端不会再向客户端发送下一个报文段)。
      • 第二次挥手和第三次挥手之间,服务端又向客户端发送了 server_sn - server_sn_wave2 个字节的有效数据。
  • ② 变为 LAST_ACK 状态,等待客户端的 ACK 报文段。

第四次挥手

客户端

  • ① 接收来自服务端的 FIN 报文段。
  • ② 向服务端发送 ACK 报文段(ACK=1, AN=server_sn+1, SN=client_sn+1),应答服务端断开连接请求。
    • client_sn+1 是序号,表明当前报文段发送的有效数据首字节是从请求建立连接到现在为止客户端向客户端发送的第 (client_isn+1)-clien_isn+1 个字节的有效数据。
    • AN=server_sn+1 是确认号,表明服务端接下来要开始接收来自客户端的第 client_sn+1 个字节的有效数据。
  • ③ 变为 TIME_WAIT 状态,等待2MSL时间后进入 CLOSED 状态,至此 client——>server 数据流被关闭。

服务端

  • ① 接收来自客户端的 ACK 报文段。
  • ② 变为 CLOSED 状态,至此 server——>client 数据流被关闭。

三次握手和四次挥手过程常见问题

该小节内容主要参考 链接,笔者根据自己的理解重新整理问题和组织回答。

为什么建立连接需要“三次”握手?

  • 客户端和服务端之间建立的TCP是全双工通信,双方都要确保对方发送能力和接收能力正常。
  • 一次握手后,服务端得知客户端发送能力正常。
  • 二次握手后,客户端得知服务端接收能力和发送能力正常。
  • 三次握手后,服务端得知客户端接收能力正常。

为什么需要 TCP 序号(SN)?

在数据传输阶段,SN 用于保证数据的可靠传输
发送端可以通过 SN 确认报文段是否已经被接收端接收

  • 主机B向主机A发送一个数据报文段,假设 SN = b_sn
  • 主机A收到后会返回返回一个 ACK 报文段作为确认应答,其中 确认号 = b_sn + data_length; data_length = IP数据报中数据总长度 - IP首部长度 - TCP首部长度
  • 主机B收到正确的 ACK 报文段,则认为数据已经成功被主机A接收;否则认为数据已经丢失。

接受端可以通过 SN 判断是否应该丢弃报文段

  • 主机B收到正确的 ACK 报文段后,再次发送的数据报文段中应该 SN = b_sn + data_length
  • 主机A会判断再次收到的数据报文段中 SN 值是否正确,正确则接收,不正确则丢弃。

为什么 TCP 初始序号(ISN)是动态生成的?

  • 客户端的某个套接字(IP+port)和服务端的某个套接字可以建立多次连接,每次连接动态生成 ISN 可以标识不同的连接。假设由于网络阻塞,上次连接发送端的数据包隔一段时间才传送到接收端,由于 ISN 不同接收端可以识别并丢弃该数据包。
  • 服务端通过特定算法生成的 ISN 可以用来防御 SYN 攻击,称为 SYN cookie(见下文)。
  • 若 ISN 是固定(已知)的,则攻击者很容易知道已经发送了多少字节的数据(b_sn + data_length - ISN)。

什么是 SYN 攻击?

TCP连接管理协议的漏洞

  • TCP协议规定,服务器接收到一个 SYN 后,就开始为这个即将建立的连接(此时的状态称为 半开连接 Half-open Connection)分配资源(如分配并初始化连接变量和缓存),半开连接在半连接队列中管理。
  • 然后服务器发送一个 SYN-ACK 进行响应,并等待来自客户端的 ACK 报文端。
  • 若服务器一直接收不到客户端的 ACK,在尝试重传几次后,最终(通常在一分多钟之后)会终止该半开连接并回收资源。
  • TCP连接管理协议中的漏洞在于未确定客户端的合法性就为其分配资源。

SYN 攻击(又称 SYN 泛洪攻击)

  • 攻击者在客户端伪造大量 IP 地址并不断向服务端发送 SYN,却不会响应服务端。
  • 服务端为这些半开连接分配资源,导致服务器的连接资源(半连接队列)被消耗殆尽,从而使得正常的 SYN 请求被丢弃,引起网络拥塞甚至系统瘫痪。

检查 SYN 攻击

  • 当服务器上看到大量的半连接状态,特别是源 IP 地址是随机的,基本上可以判断这是一起 SYN 泛洪攻击。
  • 在 Linux/Unix 上可以使用系统自带的 netstat 命令来检查 netstat -n -p TCP | grep SYN_RECV

有效防御 SYN 攻击的手段——SYN cookie

  • SYN cookie 技术是对 TCP 连接管理协议中漏洞的修补完善:
  • 第一次握手,服务端接收到一个 SYN 后,不会为该报文段生成一个半开连接(即不分配连接资源)。
  • 第二次握手,服务端生成一个初始 TCP 序列号,该序列号是 SYN 报文段中的源和目的 IP 与 port 以及仅有服务器知道的秘密数的一个复杂函数(散列函数),这种精心制作的 ISN 称为 cookie ;服务端向客户端发送一个 ACK-SYN ,其中 SN=cookie 。
  • 第三次握手,若客户端是合法的则向服务端返回一个 ACK ,并分配连接资源 。若服务端接收的 ACK 中 AN 值等于 cookie+1 ,才会分配连接资源。

为什么建立连接时只有第三次握手可以携带数据?

  • 第一次握手时,服务端会“接收”来自客户端的 SYN ,将 SYN 先缓存到服务器。若 SYN 中携带了大量的报文数据,一是需要耗费服务端更多的缓存空间,二是服务端还不清楚客户端是否合法(发送和接收能力都正常),不会“接受”这些数据。
  • 这也会使得 TCP 连接管理协议出现新漏洞,若有人恶意攻击服务端,只需要向服务端发送携带大量数据的 SYN ,服务端的缓存空间很快会被消耗殆尽。注意,SYN 泛洪攻击是消耗服务端的连接资源——半连接队列;这里是消耗服务端的缓存空间。
  • 由于第一次握手客户端不能携带请求数据,第二次握手时服务端也就没必要携带响应数据。
  • 第三次握手前,客户端已经知道服务端接收和发送能力正常,client——>server 数据流已经建立,因此可以向服务端发送的 ACK 报文段里可以携带数据。

为什么断开连接需要挥手“四次”,而不是“三次”?

  • 该问题其实是在问,为什么第二次挥手和第三次挥手不能像建立连接时那样合成一个 ACK-FIN 报文段发送。
  • 假设A主机和B主机建立了 TCP 连接。A主机发出断开连接请求时,可能B主机还有一些数据要发送给A主机,不能立刻也断开连接;因此,不能像建立连接时那样,将 ACK 报文段和 FIN 报文段合二为一。
  • 由于 TCP 连接是全双工通信,第二次挥手后,A——>B 数据流不再有数据传输,但是 B——>A 数据流还在正常工作,称为半关闭(half-close)。

为什么第四次挥手时要等待 2MSL 的时间再进入 CLOSED 状态?

  • MSL(Maximum Segment Lifetime,报文段最大生存时间)是一个未被接受的报文段在网络中被丢弃前存活的最大时间。
  • 保证建立新连接时网络中不存在上次连接时发送的数据包
    进入 CLOSED 状态意味着可以建立新连接,等待 >MSL 的时间再进入 CLOSED 状态可以保证建立新连接后,网络中不会存在上次连接时发送出去的数据包。若网络中同时存在发送端在两次连接中发出的数据包,对接收端接收数据可能会有影响。
  • 保证第四次挥手发送的 ACK 能到达接收端
    第四次挥手发送的 ACK 可能会出现丢包,另一端接收不到 ACK 会重新发送 FIN 。等待 2MSL 的时间可以应对该情况,重发 ACK ,保证另一端能正常关闭连接。

参考

[1] 图解TCP/IP(第5版)
[2] TCP报文格式详解
[3] IP数据包格式
[4] 报文,数据报,分片,分组,帧的区别
[5] 报文、报文段、分组、包、数据报、帧、数据流的概念区别
[6] 面试官,不要再问我三次握手和四次挥手