Skip to main content

TCP

David LiuAbout 4 min

TCP

MTU: Maxitum Transmission Unit 最大传输单元 MSS: Maxitum Segment Size 最大分段大小 由于以太网EthernetII最大的数据帧是1518Bytes

标志位:

  • SYN:建立链接

  • FIN:中止链接

  • ACK:确认32位确认序号有效。

    注意,ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文

  • RST:“复位”报文

    1. 目的端口无监听。当连接请求到达时,目的端口没有进程正在监听,TCP会产生一个复位报文。(在UDP中,则产生一个ICMP端口不可达的信息)
    2. 异常终止链接。可以通过发送个复位报文段而不是FIN来中途释放一个连接,这种行为称为异常释放( abortive release)。
      • 如窗口探测3次都发现接受窗口为0
      • 连接中,收到错误的标志信号,如SYN等
      • 客户端的连接被关闭,客户的内核就会回 RST 报文,服务端收到后就会释放连接。
  • URG:

  • PSH:

    有点过时

连接管理

建立连接 - 三次握手

TCP 三次握手

为什么每次建立 TCP 连接时,初始化的序列号都要求不一样呢?

主要原因有两个方面:

  • 为了防止历史报文被下一个相同四元组的连接接收(主要方面);
  • 为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收;

TCP 半连接和全连接队列

在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

  • 半连接队列,也称 SYN 队列;
  • 全连接队列,也称 accept 队列;

正常流程

不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,默认情况都会丢弃报文。

SYN 攻击

SYN 攻击方式最直接的表现就会把 TCP 半连接队列打满,这样当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。

避免 SYN 攻击方式,可以有以下四种方法:

  • 调大 netdev_max_backlog;
  • 增大 TCP 半连接队列;
  • 开启 tcp_syncookies;
  • 减少 SYN+ACK 重传次数

tcp_syncookies

具体过程:

  • 当 「 SYN 队列」满之后,后续服务端收到 SYN 包,不会丢弃,而是根据算法,计算出一个 cookie 值;
  • 将 cookie 值放到第二次握手报文的「序列号」里,然后服务端回第二次握手给客户端;
  • 服务端接收到客户端的应答报文时,服务端会检查这个 ACK 包的合法性。如果合法,将该连接对象放入到「 Accept 队列」。
  • 最后应用程序通过调用 accpet() 接口,从「 Accept 队列」取出的连接。

可以看到,当开启了 tcp_syncookies 了,即使受到 SYN 攻击而导致 SYN 队列满时,也能保证正常的连接成功建立。

net.ipv4.tcp_syncookies 参数主要有以下三个值:

  • 0 值,表示关闭该功能;
  • 1 值,表示仅当 SYN 半连接队列放不下时,再启用它;
  • 2 值,表示无条件开启功能;

那么在应对 SYN 攻击时,只需要设置为 1 即可。

断开连接 - 四次挥手

客户端主动关闭连接 —— TCP 四次挥手

重传机制

  • 超时重传
  • 快速重传
  • SACK方法
  • D-SACK方法

滑动窗口

为解决这个问题,TCP 引入了窗口这个概念。即使在往返时间较长的情况下,它也不会降低网络通信的效率。

那么有了窗口,就可以指定窗口大小,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值

窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。

图中的 ACK 600 确认应答报文丢失,也没关系,因为可以通过下一个确认应答进行确认,只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答

流量控制

RcvWindow=RcvBuffer-[LastByteRcvd-LastByteRead]

拥塞控制

  • 慢启动
  • 拥塞避免
  • 拥塞发生
  • 快速恢复