# TCP 基础

思考

  • TCP 是什么?
  • 三次握手?
  • 四次挥手?
  • 为什么是三次握手,四次挥手?

# TCP 是什么

# 定义

TCP 是一种面向连接的单播协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓的“连接”,其实是客户端和服务器的内存里保存的一份关于对方的信息,如 ip 地址、端口号等

TCP 可以看成是一种字节流,它会处理 IP 层或以下的层的丢包、重复以及错误问题。在连接的建立过程中,双方需要

TCP 提供了一种可靠、面向连接、字节流、传输层的服务,采用三次握手建立一个连接。采用 4 次挥手来关闭一个连接

# 组成

一个 TCP 连接由一个4元组构成,分别是两个IP地址两个端口号。一个 TCP 连接通常分为三个阶段:启动数据传输退出(关闭)

头部

tcp-001

  • 源端口:记录本机的端口
  • 目的端口:记录目标主机的端口
  • Sequence Number:是记录包的序号,TCP 会按照报文字节进行编号,它是用来解决包在网络中乱序的问题
  • Acknowledgement Number:确认序列号,是用于向发送方确认已经收到了哪些包,用来解决不丢包的问题
  • TCP Flag:就是包的类型,主要是用于操控 TCP 状态机的
  • Window:滑动窗口,主要是用来解决流控的
SYN: 用于初如化一个连接的序列号
ACK: 确认,使得确认号有效
RST: 重置连接
FIN: 该报文段的发送方已经结束向对方发送数据

# TCP 连接

# 三次握手

客户端和服务端通信前要进行连接,“3 次握手”的作用就是双方都能明确自己和对方的收、发能力是正常的

tcp-002

第一次握手:

客户端发送网络包,服务端收到了。服务端得出结论:客户端的发送能力、服务端的接收能力是正常的

第二次握手

服务端发包,客户端收到了。客户端得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的

第三次握手

客户端继续发包,服务端收到了。服务端得出结论:客户端的接收、发送能力,服务端的发送、接收能力是正常的。

  1. 客户端发送一个SYN段,并指明客户端的初始序列号,即ISN(c)
  2. 服务端发送自己的SYN段作为应答,同样指明自己的ISN(s)。为了确认客户端的SYN,将 ISN(c)+1作为ACK数值。这样,每发送一个SYN,序列号就会加 1。 如果有丢失的情况,则会重传。
  3. 为了确认服务器端的SYN,客户端将ISN(s)+1作为返回的ACK数值

# 四次挥手

第一次挥手

客户端发起 FIN 包(FIN = 1),客户端进入FIN_WAIT_1状态。TCP 规定,即使 FIN 包不携带数据,也要消耗一个序号。

第二次挥手

服务器端收到 FIN 包,发出确认包 ACK(ack = x + 1),并带上自己的序号 seq=y,服务器端进入了CLOSE_WAIT状态。这个时候客户端已经没有数据要发送了,不过服务器端有数据发送的话,客户端依然需要接收。客户端接收到服务器端发送的ACK后,进入了FIN_WAIT_2状态

第三次挥手

服务器端数据发送完毕后,向客户端发送FIN包(seq=z ack=x+1),半连接状态下服务器可能又发送了一些数据,假设发送 seq 为 w。服务器此时进入了LAST_ACK状态

第四次挥手

客户端收到服务器的FIN包后,发出确认包(ACK=1,ack=z+1),此时客户端就进入了 TIME_WAIT 状态。注意此时 TCP 连接还没有释放,必须经过 2*MSL 后,才进入 CLOSED 状态。而服务器端收到客户端的确认包 ACK 后就进入了 CLOSED 状态,可以看出服务器端结束 TCP 连接的时间要比客户端早一些。

tcp-003

# TCP 高级

# ISN

三次握手的一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。

如果 ISN 是固定的,攻击者很容易猜出后续的确认号。

ISN = M + F(localhost, localport, remotehost, remoteport)

M 是一个计时器,每隔 4 毫秒加 1。 F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 hash 算法不能被外部轻易推算得出

# SYN flood 攻击

最基本的DoS攻击就是利用合理的服务请求来占用过多的服务资源,从而使合法用户无法得到服务的响应。syn flood属于Dos攻击的一种。

如果恶意的向某个服务器端口发送大量的 SYN 包,则可以使服务器打开大量的半开连接,分配TCB(Transmission Control Block), 从而消耗大量的服务器资源,同时也使得正常的连接请求无法被相应。当开放了一个 TCP 端口后,该端口就处于 Listening 状态,不停地监视发到该端口的 Syn 报文,一旦接收到 Client 发来的 Syn 报文,就需要为该请求分配一个 TCB,通常一个 TCB 至少需要 280 个字节,在某些操作系统中 TCB 甚至需要 1300 个字节,并返回一个 SYN ACK 命令,立即转为 SYN-RECEIVED 即半开连接状态。系统会为此耗尽资源。

解决方案

1.无效连接的监视释放:

监视系统的半开连接和不活动连接,当达到一定阈值时拆除这些连接,从而释放系统资源。这种方法对于所有的连接一视同仁,而且由于 SYN Flood 造成的半开连接数量很大,正常连接请求也被淹没在其中被这种方式误释放掉,因此这种方法属于入门级的 SYN Flood 方法

2.延缓 TCB 分配方法: 消耗服务器资源主要是因为当 SYN 数据报文一到达,系统立即分配 TCB,从而占用了资源。而 SYN Flood 由于很难建立起正常连接,因此,当正常连接建立起来后再分配TCB则可以有效地减轻服务器资源的消耗。常见的方法是使用Syn CacheSyn Cookie技术

# Syn Cache 技术

系统在收到一个 SYN 报文时,在一个专用 HASH 表中保存这种半连接信息,直到收到正确的回应 ACK 报文再分配 TCB。这个开销远小于 TCB 的开销。当然还需要保存序列号

Syn Cookie 技术则完全不使用任何存储资源,这种方法比较巧妙,它使用一种特殊的算法生成 Sequence Number,这种算法考虑到了对方的 IP、端口、己方 IP、端口的固定信息,以及对方无法知道而己方比较固定的一些信息,如 MSS(Maximum Segment Size,最大报文段大小,指的是 TCP 报文的最大数据报长度,其中不包括 TCP 首部长度。)、时间等,在收到对方的 ACK 报文后,重新计算一遍,看其是否与对方回应报文中的(Sequence Number-1)相同,从而决定是否分配 TCB 资源

# 连接队列

在外部请求到达时,被服务程序最终感知到前,连接可能处于SYN_RCVD状态或是ESTABLISHED状态,但还未被应用程序接受。对应地,服务器端也会维护两种队列,处于SYN_RCVD状态的半连接队列,而处于ESTABLISHED状态但仍未被应用程序accept的为全连接队列。如果这两个队列满了之后,就会出现各种丢包的情形

tcp-004

# 半连接队列

在三次握手协议中,服务器维护一个半连接队列,该队列为每个客户端的 SYN 包开设一个条目(服务端在接收到 SYN 包的时候,就已经创建了request_sock结构,存储在半连接队列中),该条目表明服务器已收到 SYN 包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于SYN_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态

# 全连接队列

当第三次握手时,当 server 接收到 ACK 包之后,会进入一个新的叫accept的队列。

当 accept 队列满了之后,即使 client 继续向 server 发送 ACK 的包,也会不被响应,此时 ListenOverflows+1,同时 server 通过 tcp_abort_on_overflow 来决定如何返回,0 表示直接丢弃该 ACK,1 表示发送 RST 通知 client; 相应的,client 则会分别返回 read timeout 或者 connection reset by peer。另外,tcp_abort_on_overflow是 0 的话,server 过一段时间再次发送 syn+ack 给 client(也就是重新走握手的第二步),如果 client 超时等待比较短,就很容易异常了。而客户端收到多个 SYN ACK包,则会认为之前的 ACK 丢包了。于是促使客户端再次发送ACK ,在 accept 队列有空闲的时候最终完成连接。若accept队列始终满员,则最终客户端收到 RST包(此时服务端发送 syn+ack 的次数超出了tcp_synack_retries

# 滑动窗口

# 拥塞控制

# 常见问题

# 为什么是三次握手,四次挥手?

其实在 TCP 握手的时候,接收端发送 SYN+ACK 的包是将一个 ACK 和一个 SYN 合并到一个包中,所以减少了一次包的发送,三次完成握手。

对于四次挥手,因为 TCP 是全双工通信,·在主动关闭方发送 FIN 包后,接收端可能还要发送数据,不能立即关闭服务器端到客户端的数据通道,所以也就不能将服务器端的FIN 包与对客户端的 ACK 包合并发送,只能先确认 ACK,然后服务器待无需发送数据时再发送 FIN 包,所以四次挥手时必须是四次数据包的交互

# 为什么 TIME_WAIT 状态需要经过 2MSL 才能返回到 CLOSE 状态?

MSL 指的是报文在网络中最大生存时间。在客户端发送对服务器端的 FIN 的确认包 ACK 后,这个 ACK 包是有可能不可达的,服务器端如果收不到 ACK 的话需要重新发送 FIN 包。

所以客户端发送 ACK 后需要留出 2MSL 时间(ACK 到达服务器 + 服务器发送 FIN 重传包,一来一回)等待确认服务器端确实收到了 ACK 包。

也就是说客户端如果等待 2MSL 时间也没有收到服务器端的重传包 FIN,说明可以确认服务器已经收到客户端发送的 ACK。

还有第 2 个理由,避免新旧连接混淆。

在客户端发送完最后一个 ACK 报文段后,在经过 2MSL 时间,就可以使本连接持续的时间内所产生的所有报文都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文。

你要知道,有些自作主张的路由器会缓存 IP 数据包,如果连接重用了,那么这些延迟收到的包就有可能会跟新连接混在一起

# TCP和UDP的区别

tcp-005

# 词汇

MSS: Maximum Segment Size 最大报文段大小,指的是TCP报文的最大数据报长度,其中不包括TCP首部长度
陕ICP备20004732号-3