深入理解TCP握手

Three-way Handshake

所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。

三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行 connect() 时。将触发三次握手。

  1. 第一次握手(SYN=1,seq=x):客户端发送一个 TCP 的 SYN 标志位1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。发送完毕后,客户端进入 SYN_SEND 状态。

  2. 第二次握手(SYN=1,ACK=1, seq=y, ACKnum=x+1):服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。发送完毕后,服务器端进入 SYN_RCVD 状态。

  3. 第三次握手(ACK=1,ACKnum=y+1):客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。

SYN:同步序列编号(Synchronize Sequence Numbers),是TCP/IP建立连接时使用的握手信号。在客户机和服务器之间建立正常的TCP网络连接时,客户机首先发出一个SYN消息,服务器使用SYN+ACK应答表示接收到了这个消息,最后客户机再以ACK消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。
SYN攻击:利用TCP协议缺陷,发送了大量伪造的TCP连接请求,使得被攻击方资源耗尽,无法及时回应或处理正常的服务请求。一个正常的TCP连接需要三次握手,首先客户端发送一个包含SYN标志的数据包,其后服务器返回一个SYN/ACK的应答包,表示客户端的请求被接受,最后客户端再返回一个确认包ACK,这样才完成TCP连接。在服务器端发送应答包后,如果客户端不发出确认,服务器会等待到超时,期间这些半连接状态都保存在一个空间有限的缓存队列中;如果大量的SYN包发到服务器端后没有应答,就会使服务器端的TCP资源迅速耗尽,导致正常的连接不能进入,甚至会导致服务器的系统崩溃。


Four-way handshake

断开 TCP 连接需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。

  1. 第一次挥手(FIN=1,seq=x):假设客户端想要关闭连接,客户端发送一个 FIN 标志位为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。发送完毕后,客户端进入 FIN_WAIT_1 状态。

  2. 第二次挥手(ACK=1,ACKnum=x+1):服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。

  3. 第三次挥手(FIN=1,seq=y):服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。

  4. 第四次挥手(ACK=1,ACKnum=y+1):客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT 状态,等待可能出现的要求重传的 ACK 包。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。


SYN攻击

SYN 攻击是一种典型的 DoS/DDoS 攻击。

在三次握手过程中,服务器发送 SYN-ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 SYN_RCVD 状态。当收到 ACK 后,服务器才能转入 ESTABLISHED 状态。

SYN 攻击指的是,攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。

如何检测SYN攻击?

检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

如何防御 SYN 攻击?

SYN攻击不能完全被阻止,除非将TCP协议重新设计。我们所做的是尽可能的减轻SYN攻击的危害,常见的防御 SYN 攻击的方法有如下几种:

  • 缩短超时(SYN Timeout)时间
  • 增加最大半连接数
  • 过滤网关防护
  • SYN cookies技术

Attention

TCP 的三次握手最重要的就是协商传输数据用的序列号。那这个序列号究竟有些什么用呢?

这个序号能够帮助后续两端进行确认数据包是否收到,解决顺序、丢包问题;另外我们还可以看到有一个 win 字段,这是双方交流的窗口大小,这在每次传输数据过程中也会携带。主要是告诉对方,我窗口是这么大,别发多了或者别发太少。

TCP的特点是:

  • 顺序问题,依靠序号
  • 丢包问题,依靠序号
  • 流量控制,依靠滑动窗口
  • 拥塞控制,依靠拥塞窗口+滑动窗口
  • 连接维护,三次握手/四次挥手

顺序与丢包问题

由于数据在传输前我们已经有序号了,这里注意一下这个序号是随机的,重复的概率极低,避免了程序发生乱入的可能性。

由于我们每个数据包有序号,虽然发送与到达可能不是顺序的,但是TCP层收到数据后,可以根据序号进行重新排列;另外在这个排列过程中,发现有了1,2,3,5,6这几个包,一检查就知道4要么延时未到达,要么丢包了,等待重传。

这里需要重要说明的一点是。为了提升效率,TCP其实并不是收到一个包就发一个ack。那是如何ACK的呢?还是以上面为例,TCP收到了1,2,3,5,6这几个包,它可能会发送一个 ack(seq=3)的确认包,这样次一次确认了3个包。但是它不会发送 5,6的ack。因为4没有收到啊!一旦4延时到达或者重发到达,就会发送一个 ack(seq=6),又一次确认了3个包。

流量控制与拥塞控制

举例子说明,A给B发送数据,通过握手后,A知道B一次可以收 1000Byte 的数据(B有这么大的处理能力),那么这个时候滑动窗口就可以设置成 1000Byte。那是不是最后真的可以一次发这么多数据给B呢?还不是,这时候得问问拥塞窗口,老兄,现在网络情况怎么样?一次运 1000Byte 的数据有压力吗?拥塞窗口一通计算说不行,现在是高峰期,最多只能有 600Byte 的货上路。最终这次传数据的时候就是 600Byte。

可以关注抓包数据的 win 值,一直在动态调整。

每次能够发送多少数据,有这么一个公式:LastByteSend - LastByteAcked <= min{cwnd,rwnd}

LastByteSend 是最后一个发送的字节的序号;LastByteAcked 最后一个被确认的字节的序号。

这两个相减得到的是本次能够发送的数据,这个数据一定小于或等于 cwnd 与 rwnd 中最小的一个值。


Status Machine

Client

Server


是什么限制了你的连接

以Nginx为服务端进行举例说明,当客户端调用 connect() 方式时,会向服务端发起三次握手操作,当连接建立成功,在服务器端会生成一个 socket 对象,称为接收数据的 socket。服务向客户端发起的 connect() 也会生成一个 socket 对象,该对象叫发送数据的 socket。

确定一个 socket 所需的元素有:源IP、源Port、目标IP、目标Port和协议。

这里之所针对收发数据分别建立不同的 socket 对象,主要是从效率和管理上考虑。

连接上限是多少?

在服务器监听端,IP 和 Port 都是固定的,那么只有客户端的 IP 和 Port 是可变化的,假设使用的是 IPv4,那么理论上的连接数应该是 2^32(IP) * 2^16(Port) = 2^48。但实际上是服务器端是不能建立这么多连接的,因为每一个 socket 都需要消耗内存,以及每一个进程的文件描述符是有上限的,这些都限制了最终的连接数。

提高连接数的手段

  1. 多进程
  2. 多线程
  3. IO多路复用
  4. 协程

Reference