一、三次握手
1)基本流程
注意:只有在一次完整的 roundtrip 之后 Client 才能开始发送数据;Server 则需要收到 ACK x+1, y+1 消息到达后才能开始发送数据。
2)TCP Fast open
三次握手开销比较大,特别是对于小数据量的请求来说,每次建立连接都要等一个roundtrip,因此协议提供了一种快速开始传数据的优化机制.
打开此配置时,可以在第一条 SYN 消息里带上数据,不用等一次完整的 roundtrip。
- 限制:
SYN消息中可带的数据大小有限制- 只有特定的几种 HTTP 请求可使用此方式发送
- 只能用于重复连接之前曾经连过的 Server 的情况下
- 配置:
- Server: Linux kernel v4.1+
- Client: iOS 9+/OSX 10.11+ 或他相应的 Linux 版本
- 应用程序打开相关的 socket flag
- 性能提升:
- HTTP网络延迟降低 15%
- 页面加载时间平均降低 10%
- 某些高延迟情况下有 40% 的降低
3)握手三次的必要性
为什么需要三次握手才建立连接呢?两次会有什么问题呢?为了回答这一问题,下面假设两次握手即可建立连接,考虑以下场景:
Client先给Server发了一个SYN包(编号1),因为网络路由等问题一直没到达服务器,Client侧超时后又重新给Server发了一个新的SYN包(编号2),编号2顺利到达Server并握手成功建立了连接。
但随后编号1的SYN包经过长途跋涉也到达Server,按照假设只需要两次握手就能建立连接,那么这个编号1的SYN也促使Server为其建立一个新的连接(并分配资源),但这时从Client的角度来说,编号1的包已经超时,这个连接应该是无效的。这会造成双方状态不一致以及Server端资源浪费。
4)Wireshark抓包
下图是用WireShark抓的一组HTTP请求的包,HTTP是基于TCP的,所以可以看到最开始会有TCP的三次握手:
用Wireshark可以分析每个包内各个字段的具体内容,不在此详细展开。
5)与握手消息相关的洪泛攻击
洪泛攻击通常是指攻击者通过给目标服务器发送大量消息导致目标服务器资源耗尽而无法继续提供正常服务的攻击方式,针对三次握手中涉及的三种不同种类的消息,攻击者可以有三种洪泛攻击的方式:
SYN flood
攻击者给目标服务器发送大量的SYN消息,服务器会发送SYN/ACK并等待攻击者的ACK消息,这时服务器上会为此连接占用一定资源。
如果攻击者通过篡改包中的源IP或在Client端过滤相应包等方式使目标服务器一直收不到ACK消息,这时服务器上会保持大量的半连接,这些半连接占用的资源会通过一个超时机制来释放,如果攻击者发送的包数量巨大,服务器来不及回收资源,则会出现资源耗尽从而无法响应新的正常请求。
ACK flood
因为TCP是可靠连接,每个消息需要被确认,因此服务器需要对每个ACK类型的消息做处理以决定每个数据包发送的状态和后续处理流程。如果对服务器发送大量ACK消息(不限于握手中的ACK,也可以是普通数据包确认),服务器就需要分配大量的资源来对它们做响应。从而有可能耗尽计算资源使其无法响应正常请求。
SYN/ACK flood
SYN/ACK包一般是服务端向客户端发送的,但攻击者也可以发送大量无效的SYN/ACK消息到服务器以干扰服务器的正常运行流程,耗费服务器的资源。
二、确认和重传
TCP是面向连接的、有序的、可靠的传输层协议,其可靠性主要靠确认和重传机制来保证。
1)确认(ACK)
接收方用ACK来向发送方确认包已成功到达,但并不是发送方的每个包接收方都会发送单独的ACK来确认。接收方发ACK 1234确认的其实是序号在1234之前的所有包都已收到,所以在抓包的时候观察到ACK数量小于发送的包数是正常的。
除了常规的ACK机制外,还有以下两种特殊机制的ACK:
延迟确认
如果打开此选项,接收方在收到对方的包以后会等待一段时间(200ms)再回复,将这段时间内所有要发的小包集合到一起统一发送,这样可以省掉部分固定开销的流量,但缺点是损失了时间。特别是在遇到网络拥塞时,如果多个包丢失,每个丢失的包都需要等接收方的一个新ack才知道丢失了,这时对方如果开启了延迟确认,接收方在收到包后200ms才会发ack,这会让重传等待时间变长。
SACK
开启了这种机制后,接收方在ack包中除了指明下一个预期的包序号外,还指明已收到了那些包,让发送方能立即重传中间丢失的包,而不用等一个一个的ACK(不开SACK的话需要上一个包重发完,发送方收到后的ACK里再指明下一个需要重传的包,每个重传包都要等一个RTT的时间)。通信双方可以在三次握手的时候协商是否开启SACK。
2)重传(Retransmission)
对于已发送但没有被后续ACK确认的包,发送方要负责在合适的时机做重传,主要有以下两种触发机制:
超时重传
发送方在发出包后会等待一段时间,如果没有收到ACK,就会重新发送,超时时间一般较长(默认5s),也会随着实际的rtt时间动态调整。
快速重传
A往B发送多个数据包,其中有一部分包在网络上丢失了,B会在每一个ack回包中指明丢失的最小序号,A在连续收到3个ack里都指定同一序号时,就知道此包已丢失,就会立即重传此包。
三、拥塞控制
1) 流控(Flow control)
为了防止发送方短时间内集中发送过多数据使接收方来不及处理而导致丢包和带宽浪费,通信双方需要分别告知对方自己接收窗口的大小(rwnd):
- 连接刚建立时,两端的
rwnd都使用系统默认值 - 每个
ACK消息都会带上当前最新的rwnd,使对方可以据此动态调整发送的数据量 - 如果接收方
rwnd = 0,则发送方不能再继续发送新数据(发送方法会被阻塞或失败),直到rwnd恢复
Window Scaling
TCP协议只为rwnd预留了16位,使rwnd最大为2^16=65536字节,要想设置超过此值的的窗口大小,需要 Window Scaling。
通信双方在三次握手过程中需要协商一个左移位数(shift),rwnd 的实际大小为ACK消息中带的16位的值左移 shift 以后得到的值。支持 Window Scaling 以后,rwnd 最大支持1G字节。
在Wireshark抓的包中可以看到移位以后的window值(),但如果wireshark没有抓到三次握手的包,只抓到了后面通信的包,则他不知道Windows scaling的值,也就没法计算出准确的window值了。
Window Scaling 在主流操作系统上已默认开启,但通信信道上的中间结点、路由器、防火墙等可能会禁用此选项。
$> sysctl net.ipv4.tcp_window_scaling
$> sysctl -w net.ipv4.tcp_window_scaling=1
2) 拥塞控制(Congestion control)
接收窗口能避免发送方不要发送超过接收方缓存大小的数据,但不能保证中间链路能承受相应的数据量(链路负载和可用带宽是实时变化的,超过可能会导致丢包),而中间链路的实时可用带宽只能靠实际传输包来验证,因此发送方一开始需要比较保守地发包,在确认链路可以承受以后再逐步增加发送量。如果发生了丢包,则需要减小发送的数据量,避免导致更严重的拥塞。
-
目的:防止发送太多数据导致通信链路来不及处理导致网络拥塞
-
机制:
慢启动(Slow-Start)
发送方为每个 tcp 连接维护一个拥塞窗口大小(cwnd),其默认的初始值比较小,随后在每收到一个 ACK时cwnd += 1,这样比如这次发送了1个包,下次就可以发送2个,再下次就能发4个,这样指数增长。
cwnd 不需要通知对端,为提高连接刚建立时的传输效率,可将 cwnd 默认初始值提高到10个 network segments。
协议规定传输中未被 ACK 的数据量(在途字节数)不能超过为 MIN(cwnd, rwnd)。
Slow-Start Restart
在这种机制下,TCP连接在空闲一段时间后系统会将其
cwnd重设为默认值(比较小)。为防止此机制影响连接的传输性能,可采用下列命令关闭此机制:$> sysctl net.ipv4.tcp_slow_start_after_idle $> sysctl -w net.ipv4.tcp_slow_start_after_idle=0
拥塞避免(Congestion avoidance)
网络条件好的情况下,cwnd会一直倍增,直到其超出接收方系统配置的拥塞阈值窗口大小(ssthres),或出现丢包。此时会触发拥塞避免机制来调整拥塞窗口(cwnd)大小
常见算法:
- TCP Tahoe and Reno
- TCP Vegas
- TCP New Reno
- TCP BIC
- TCP CUBIC (Linux上默认)
- Compound TCP (Windows上默认)
Proportional Rate Reduction for TCP(RFC 6937)
- 目的:加快丢包后的恢复速度
- 性能:在有丢包的连接上平均延时降低3-10%
- Linux 3.2+ 内核上默认使用此拥塞避免算法
四、队头阻塞
TCP协议下每个数据包都有唯一的序号,协议能够保证这些包的可靠、按顺序传输。但如果多个包中某个序号比较靠前的在传输过程中发生了丢包,其它序号靠后的包即使已顺利到达,也需要在接收方的缓冲区中等待,直到队头包重传成功才能将报文组装并返回给应用层。
- 队头阻塞发生在
TCP协议层,应用层无法感知,应用层只能感知到总体的传输延迟。 - 队头阻塞的负面影响在音、视频应用这种不要求所有包都到达即可正常运行的场景下尤其明显。
五、四次挥手
上图是四次挥手的主要流程。
主动关闭方(A)先向对方发送FIN消息,因为TCP是有序的,所以FIN将晚于在此之前A发给B的所有数据报文,B收到FIN以后将会回复一个ACK,并通知应用进程此连接需要关闭,这时B的系统缓冲区内可能还有数据在继续向A发送。应用进程收到关闭通知后触发被动关闭流程,这时B也会向A发送一个FIN消息,然后等待A的ACK,收到ACK后即可关闭连接。A则进入TIME-WAIT状态,A这时候还不能关闭而是需要等2MSL是因为B可能没收到它的最后一个ACK从而一直等待从而导致资源无法释放。
MSL
MSL即最长报文寿命,一般1MSL = 2分钟。一般一个TCP连接占用一个端口,在等待计时器期间不会释放端口。其主要作用为:
-
确保第四次挥手时,A发送的ACK可以达到B,如果B在2MSL内没收到,B会重发FIN
-
确保整个连接的所有报文均已过期(TCP实现必须选择一个MSL,是任意报文段被丢弃前,在网络存活的最长时间,所以2MSL足够所有报文过期了)