写点什么

TCP 协议认知篇

用户头像
邱学喆
关注
发布于: 4 小时前
TCP协议认知篇

一.概述

通过阅读 TCP/IP 详解 卷 1:协议,对 TCP 的协议进一步理解,以及整理。便以后续查阅用途

二. 数据结构

序号

用来标识从 TCP 发送端向 TCP 接受端发送的数据字节流,它标识在这个报文段中的第一个数据字节。即意味着要传输的所有数据的每一个字节记上编号,这样子可以有效处理数据顺序问题。例如要发送“welcome to tcp”,假设拆分成三个 TCP 报文段,第一个报文段为“welcome”,第二个报文段“to”,第三个报文段“tcp”,进行发送。当接收端先收到第三个报文段时,在接收到第一个、第二个报文段时。如果不加上序号的话,接收端会处理成 tcp welcome to 数据后交给应用层处理,这样子就导致了数据顺序混乱问题。


初始序号 ISN 问题,在建立连接时,ISN 值是怎么计算出来的;目前没有找到较权威的文章去介绍其计算原理。但不影响我们对其的理解。假设 ISN 是随机值或者固定值,通过三次握手建立连接,发送方与接收方对 ISN 值已经达成一致认可。


另外值得注意的是,SYN 段、FIN 段、RST 段会占用一个字节

确认序号

确认序号表示发送方已经成功接收到的字节,但不包含确认序号所致的字节。这里有点拗口,举个场景:A 节点向 B 节点发送序号为 0,字节数为 1024 个字节。B 收到数据后,会告诉 A 节点确认序号为 1024;所以确认序号可以理解期望接受的下一个字节编号的数据。

但值得注意的是,只有当 ACK 被置为 1 时,确认序号才有用。

通常 TCP 在接收到数据时并不立即发送 ACK;相反,它推迟发送,以便将 ACK 于需要沿该方向发送的数据一起发送过去,这种现象为数据捎带 ACK。绝大多数实现采用的时延为 200ms。在介绍 Nagle 算法时,会重点讲解;

标志位

  • URG 紧急指针(urgent pointer)有效。


  • ACK 确认序号有效。

  • PSH 接收方应该尽快将这个报文段交给应用层。

说明接收方接收到数据,并不会立即将该数据给到应用层去处理。而是等到指定时机,才会让应用层去处理该数据。所谓的时机就是该标志位置位 1 的时候;然而目前大多数的 API 并没有向应用程序提供通知其 TCP 设置 PUSH 标志的方法。那么什么时候该 PUSH 标志位会被置位 1 呢?

  • RST 表示复位,用来异常的关闭连接。一般来说,无论何时一个报文段发往基准的连接出现错误,TCP 都会发出一个复位报文段。这里的”基准的连接“是指由目的 IP 地址和目的端口以及源 IP 和源端口指明的连接。

  • 连接不存在的端口,服务器会发送 RST 报文。一般在应用系统出现 Connection refused 错误信息。

  • 异常终止一个连接,使用其有两个优点:1.丢弃任何待发数据并立即发送复位报文段;2.RST 的接收方会区分另一端执行的是异常关闭还是正常关闭。我们可以通过“linger on close”选项(SO_LINGER)提供了异常关闭的能力

  • 针对半打开连接现象,接收方已复位报文作为应答,关闭连接;所以一般来说会出现 connect reset by peer 错误。

  • SYN 同步序号用来发起一个连接。

  • FIN 发送端完成发送任务。

紧急指针

紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。T C P 的紧急方式是发送端向另一端发送紧急数据的一种方式。

选项

最常见的可选字段为最长报文大小,又称为 MSS,它指明本段所能接受的最大长度的报文段。并不是越大越好,一旦超过 IP 协议指定的阈值,就会将其进行分片。假设 IP 的阈值为 1024, 现在要发送总数据量为 3072,MSS 为 1536,那么拆分层两个 TCP 报文段,每个报文长度都是 1536,那么在 IP 层会进行分片,分四次进行发送;数据量不变,MSS 改为了 1024,那么会拆分层三个 TCP 报文段,每个报文的长度为 1024,IP 层不需要分片,发送三次即可完成交互。

所以 MSS 设置,可以有效提高网络利用率;

三.建立与终止

时序图

TCP 状态图

这里忽略了同时打开以及复位的导致相关的状态切换。

有关 2MSL 的问题,可以查考其文章:为什么tcp的TIME_WAIT状态要维持2MSL

TIME_WAIT 状态的主要目的有两个:

  • 优雅的关闭 TCP 连接,也就是尽量保证被动关闭的一端收到它自己发出去的 FIN 报文的 ACK 确认报文;

  • 处理延迟的重复报文,这主要是为了避免前后两个使用相同四元组的连接中的前一个连接的报文干扰后一个连接。

四. 数据传输

1. 交互数据流

交互数据,主要是 ACK 报文;

Nagle 算法

TCP 在接受到数据时并不会立即发送 ACK 报文;相反,它推迟发送,等待一般为 200ms 内,是否有数据一起发送的;其目的是为了尽量网络利用率;

在我们的服务器,一般会关闭其 Nagle 算法;通过 TCP_NODELAY 选项来关闭;

2. 成块数据流

滑动窗口

该协议来控制流量方法,该协议允许发送方在停止并等待确认前可以连续发送多个分组。针对接收方设置的所能接受的容量大小;

3. 超时与重传

超时并不是固定值,而是通过一定的算法,计算出超时时间值,

往返时间(RTT)测量

大多数源于伯克利的 TCP 实现在任何时候对每个连接仅测量一次 RTT 值;在发送一个报文段时,如果给定连接的定时器已经被使用了,则该报文段不被计时了;例如:报文 1 在 0.0023 发送出去,会创建一个定时器;接着报文 2 在 1.0042 发送出去,因为报文 1 已经使用了定时器,所以报文 2 就不再被计时;


如果 ACK 到达时数据没有被重传,则被平滑的 RTT 和被平滑的均值偏差将基于这个新测量进行更新,否则将不会更新;

重传超时时间 RTO 计算

采用指数退避,假设第一次超时时间为 6s,我们使用倍数 2,因此下一个超时时间为 12s,再下一次超时时间为 24s,48s,以此类推。


根据 jacobson 算法,公式如下:

  • Err=M-A => A 是被平滑的 RTT,M 是测量到的 RTT;

  • A=A+gErr =>g 表示增量,起到平均作用,值为 0.125;

  • D=D+h*(Math.abs(Err) -D) => D 为被平滑的均值偏差,h 表示偏差的增益,值为 0.25;

  • RTO=A+4D => 刚初始化时公式为 RTO=A+2D,后面采用这个 4 倍;

但上面的公式并不是完全照用的;

  • 初始化时=》A=0,D=3s。所示 RTO=A+2D=0+2*3=6s

  • 第一个报文,M=1.5 =》

A=M+0.5=1.5+0.5=2

D=A/2=2/2=1

RTO=A+4D=2+4*1=6s

  • 第二个报文,M=0.5 =》

Err=M-A=0.5-2=-1.5

A=A+gErr=2+0.125*(-1.5)=1.8125

D=D+h*(Math.abs(Err) -D) = 1+0.25*(1.5-1)=1.125

RTO=A+4D=1.8125+4*1.125=6.3125

4. 限流

下面的四个算法,目前还不是太了解其原理,先摘抄下来,待后续再仔细研究;

慢启动算法

该算法通过观察到新分组进入网络的速率应该与另一端返回确认的速率相同而进行工作。针对发送方增加的另一个窗口,拥塞窗口(congestion window,cwnd);

当与另一个网络的主机建立 TCP 连接时,拥塞窗口被初始化为 1 个报文段(即另一端通告的报文段大小)。每收到一个 ACK,拥塞窗口就增加一个报文段。发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。


对每个连接,TCP 管理 4 个不同的定时器。

  • 重传定时器适用于当希望收到另一端的确认。

  • 坚持定时器使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口。

  • 保活定时器可检测到一个空闲连接的另一端合适崩溃或重启。

  • 2MSL 定时器测量一个连接处于 TIME_WAIT 状态的时间。

拥塞避免算法

是一种处理丢失分组的方法;过程如下

拥塞便面算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口 cwnd 和一个慢启动门限 ssthresh。工作过程如下:

  • 对一个给定的连接,初始化 cwnd 为 1 个报文段,ssthresh 为 65535 个字节

  • TCP 输出例程的输出不能超过 cwnd 和接收方通告窗口的大小。拥塞避免是发送方使用的流量控制,而通告窗口则是接收方进行的流量控制。前者是发送方感受到的网络拥塞的估计,而后者则与接收方在该连接上的可用缓存大小有关。

  • 当拥塞发生时(超时或收到重复确认),ssthresh 被设置为当前窗口大小的一半(cwnd 和接收方通告大小的最小值,但最少为 2 个报文段)。此外,如果是超时引起了拥塞,则 cwnd 被设置为 1 个报文段(这就是慢启动)

  • 当新的数据被对方确认时,就增加 cwnd,但增加的方法依赖于我们是否正在进行慢启动或拥塞避免。如果 cwnd 小于等于 ssthresh,则正在进行慢启动,否则正在进行拥塞避免。慢启动一直持续到我们回到当拥塞发生时所处位置一半的时候才停止(因为我们记录了在步骤 2 中给我们制造麻烦的窗口大小的一半),然后转为执行拥塞避免。

快速重传算法 &快速恢复算法

假如这只是一些报文段的重新排序,则在重新排序的报文段被处理并产生一个新的 ACK 之前,只可能产生 1~2 个重复的 ACK。如果一连串收到 3 或 3 个以上的重复 ACK,就非常可能是一个报文段丢失了。于是我们就重传丢失的数据报文段,而无需等待超时定时器移除。这就是快速重传算法;

接下来执行的不是慢启动算法而是用色避免算法,这就是快速恢复算法;

上面的拥塞避免算法以及快速

5. 坚持定时器

如果一个确认丢失了,则双方就有可能因为等待对方而使连接终止:接收方等待接受数据,而发送方在等待允许它继续发送数据的窗口更新。为防止这种死锁情况的发生,发送使用一个坚持定时器来周期性向接收方查询,以便发现窗口是否已增大。这些报文我们称之为窗口探查


糊涂窗口综合症:接收方可以通告一个小的窗口(而不是一直等到有大的窗口时才通告),而发送方可以通过这个小窗口发送少量的数据(而不是等待其它的数据以便发送一个大的报文段),即,少量的数据通过连接交换,而不是满长度的报文段,TCP 的传输效率可想而知。

如何避免“糊涂窗口综合症”:

  • 接收方:

  • 接收方不通告小窗口,除非增加一个报文段(MMS)或者接收方缓存空间的一半,否则通告为 0。

  • 发送方:

  • (1)可以发送一个满长度的报文段(MMS)

  • (2)可以发送至少接收方通告窗口一半的报文段

  • (3)能够发送手头的所有数据并且不希望接收 ACK,或者该连接禁止了 Nagle 算法时,可以发送任意数据。


坚持定时器工作流程:

(1)发送端收到 0 窗口通告后,就启动坚持定时器,并在定时器溢出的时候向客户端查询窗口是否已经增大。

(2)在定时器未到,就收到非零通告,则关闭该定时器,并发送数据。

(3)若定时器已到,还没有收到非零通告,就发探查报文。

(4)如果探查报文 ACK 的通告窗口为 0,就将坚持定时器的值加倍,TCP 的坚持定时器使用 1,2,4,8,16……64 秒这样的普通指数退避序列来作为每一次的溢出时间,重复 1、2、3 步,如果通告窗口非零,发送数据,关闭定时器。

6.保活定时器

保活定时器是为了应对 TCP 连接双方出现长时间的没有数据传输的情况。如果客户端与服务器建立了 TCP 连接之后,客户端由于某种原因导致主机故障,则服务器就不能收到来自客户端的数据,而服务器不可能一直处于等待状态,保活定时器就是用来解决这个问题的。服务器每收到一次客户端的数据,就重新设置保活定时器,通常为 2 小时,如果 2 小时没有收到客户端的数据,服务端就发送一个探测报文,以后每隔 75 秒发送一次,如果连续发送 10 次探测报文段后仍没有收到客户端的响应,服务器就认为客户端出现了故障,就可以终止这个连接。

五. 网络安全

这里主要讲述的 DDOS 攻击的分类

TCP SYN 攻击

SYN Flood 攻击是当前网络上最为常见的 DDoS 攻击,它利用了 TCP 协议实现上的一个缺陷。通过向网络服务所在端口发送大量的伪造源地址的攻击报文,就可能造成目标服务器中的半开连接队列被占满,从而阻止其他合法用户进行访问。

攻击者向被攻击者发起大量的 SYN 包(第一次握手包),并且伪装源 IP 地址。被攻击者会发送 SYN-ACK( 第二次握手包) 到假造的 IP 地址,因此永不可能收到 ACK (第三次握手包)。这样被攻击者将会等待 ACK( 第三次握手包) 的达到,从而占用内存和 CPU 的负载。通常把没有完全建立起来的连接称为半开连接,大量半开连接的存在将使得正常用户无法和被攻击者建立正常的 TCP 连接,从而无法提供正常的服务。


TCP RST 攻击

 TCP 协议头部有一个标志位称为“RST”位,正常的数据包中该位为 0,一旦该位设置为 1,则接收该数据包的主机将立即断开 TCP 会话。TCP Reset 攻击中,攻击者可以伪造 TCP 连接其中的一方给另一方发送带有 RST 位的包来断开 TCP 连接,但是要确保这个数据包的源 IP 地址、源端口号、目的 IP 地址、目的端口号、序列号等特征符合已有 TCP 连接的特征,这就要求攻击者要能够监视通信双方之间的 TCP 连接。

引用

为什么tcp的TIME_WAIT状态要维持2MSL

TCP/IP 详解 卷 1:协议

发布于: 4 小时前阅读数: 3
用户头像

邱学喆

关注

计算机原理的深度解读,源码分析。 2018.08.26 加入

在IT领域keep Learning。要知其然,也要知其所以然。原理的爱好,源码的阅读。输出我对原理以及源码解读的理解。个人的仓库:https://gitee.com/Michael_Chan

评论

发布
暂无评论
TCP协议认知篇