TCP——三次握手

 

什么是TCP三次握手?

三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。
进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。
实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号交换TCP窗口大小信息
 
 

TCP三次握手流程和队列

队列说明

TCP三次握手时,Linux内核会维护两个队列:

  1. 半连接队列,被称为 syn 队列,存放SYN_RCVD状态的连接
  2. 全连接队列,被称为 accept 队列,存放ESTABLISHED状态的连接
如果一个服务器要处理大量网络连接,且并发性比较高,那么这两个队列长度就非常重要了。
因为,即使服务器的硬件配置非常高,服务器端程序性能很好,但是这两个队列非常小,那么经常会出现客户端连接不上的现象,
因为,这两个队列一旦满了后,很容易丢包,或者连接被复位。所以,如果服务器并发访问量非常高,那么这两个队列的设置就非常重要了。
 

流程说明

  1. 客户端调用connect()与服务端建立连接。客户端发送SYN请求报文段,将SYN位置为1,Sequence Number为x(x 是随机生成的一个 int 数值),然后客户端进入SYN_SENT状态,等待服务器的确认。
  2. 服务端接收到SYN报文段,
    1. 如果半连接队列未满,将连接信息放到半连接队列中,
      然后对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为 y(y 是随机生存的一个 int 数值);
      服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务端进入SYN_RECV状态(也被称为半连接状态)。
    2. 如果半连接队列满了,那么丢弃当前请求。
  3. 客户端收到SYN ACK报文段。然后将Acknowledgment Number设置为y+1,向服务端发送ACK报文段,客户端进入ESTABLISHED状态。
  4. 服务端接收客户端ACK报文段,
    1. 如果全连接队列未满,就会从半连接队列里面将数据取出来放入全连接队列,等待应用使用,进行ESTABLISHED状态;
    2. 如果全连接队列满了,就会根据 tcpaborton_overflow 配置执行策略。

 

半连接队列查看

服务端处于SYN_RECV状态的TCP连接,这种状态的都在半连接队列,因此可以使用如下命令进行计算:

netstat -natp | grep SYN_RECV | wc -l
233

注:表示半连接状态的TCP连接有233个

 

半连接队列溢出

半连接满了之后的动作是直接忽略(ignore or dropped),此时客户端需要不断的重发SYN进行重试,重试的参数由tcp_syn_retries决定,该参数默认是5。如果超过客户端设置的超时时间,会报连接超时异常。

当半连接队列溢出时,可通过以下命令查询:

netstat -s | egrep "listen|LISTEN"
7102 SYNs to LISTEN sockets ignored  表示半连接队列溢出次数
7102 表示半连接队列溢出的次数,隔几秒查询一次,如果这个数字一直在递增,说明半连接队列出现了溢出的状态

 

配置半连接队列

半连接队列长度由内核参数 tcp_max_syn_backlog(/proc/sys/net/ipv4/tcp_max_syn_backlog)决定,当使用 SYN Cookie 时(就是内核参数 net.ipv4.tcp_syncookies = 1),这个参数无效。

半连接队列的长度肯定小于全连接队列的长度,即:半连接队列长度 = min(backlog内核参数 net.core.somaxconn,内核参数 tcp_max_syn_backlog)

 

半连接的SYN Flood洪水攻击

SYN Flood是当前最流行的DoS(拒绝服务攻击)与DDoS(分布式拒绝服务攻击)的方式之一,

这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,常用假冒的IP或IP号段发来海量的请求连接的第一个握手包(SYN包),被攻击服务器回应第二个握手包(SYN+ACK包),

因为对方是假冒IP,对方永远收不到包且不会回应第三个握手包。导致被攻击服务器保持大量SYN_RECV状态的“半连接”,并且会重试默认5次回应第二个握手包直至超时,

这些伪造的SYN包将长时间占用半连接队列,导致正常的SYN请求因为半连接队列满而被丢弃(让正常的业务请求连接不进来),从而引起网络拥塞甚至系统瘫痪。

检测 SYN 攻击:

当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。

netstat -n -p TCP | grep SYN_RECV

常见的防御 SYN 攻击的方法有如下几种:

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

 

全连接队列查看

服务端可以使用 ss 命令进行查看全连接队列的状态,ss 命令获取数据又分为LISTEN 状态,和非LISTEN 状态。

ss命令参数:

  • -l 显示正在 Listener 的socket
  • -n 不解析服务名称
  • -t 只显示tcp

LISTEN状态:

ss -lnt |grep 8086
State      Recv-Q Send-Q   Local Address:Port         Peer Address:Port 
LISTEN     0      1024     ::ffff:10.0.101.22:8086    :::* 
  • Recv-Q 完成三次握手并等待服务端 accept() 的 TCP 全连接总数,
  • Send-Q 全连接队列大小

非LISTEN状态:

ss -nt |grep 8086
[root@localhost ~]# ss -nt |grep 8086
State      Recv-Q Send-Q   Local Address:Port        Peer Address:Port
CLOSE-WAIT 34     0        ::ffff:10.0.101.22:8086   ::ffff:10.0.101.102:10628
ESTAB      1295   0        ::ffff:10.0.101.22:8086   ::ffff:10.0.101.102:12833 
  • Recv-Q 接收缓冲区数据大小(已收到但未被应用进程读取的字节数)
  • Send-Q 发送缓冲区数据大小(已发送但未收到确认的字节数)

 

全连接队列溢出

当有大量请求进入,如果TCP全连接队列过小的话就会出现全连接队列溢出,当出现全连接队列溢出现象的时候,后续的请求就会被丢弃,就会出现服务请求数量上不去的现象。

前面提到在TCP三次握手的最后一步,当全连接队列已满就会根据tcpabortonoverflow策略进行处理。Linux 可通过 /proc/sys/net/ipv4/tcp_abort_on_overflow 进行配置。

1、当tcpabortonoverflow=0服务端accept 队列满了,客户端发来ack,服务端直接丢弃该ACK,此时服务端处于【synrcvd】的状态,客户端处于【established】的状态。

      在该状态下会有一个定时器重传服务端 SYN/ACK 给客户端,(不超过 tcp_synack_retries 指定的次数,Linux下默认5),

      超过后,服务器不在重传,后续也不会有任何动作。如果此时客户端发送数据过来,服务端会返回RST。

2、当tcpaborton_overflow=1,服务端accept队列满了,客户端发来ack,服务端直接返回RST通知client,表示废掉这个握手过程和这个连接,client会报connection reset by peer。

3、当全连接队列溢出时,可通过以下命令查询:

netstat -s | egrep "listen|LISTEN"
7102 times the listen queue of a socket overflowed 全连接队列溢出的次数
7102 times表示全连接队列溢出的次数,隔几秒查询一次,如果这个数字一直在递增,说明全连接队列出现了溢出的状态

 

配置全连接队列

全连接队列大小取决于backlog 和somaxconn 的最小值,也就是 min(backlog,somaxconn)

  1. somaxconn 是Linux内核参数,默认128,可通过/proc/sys/net/core/somaxconn进行配置
  2. backlog是 listen(int sockfd,int backlog)函数中的参数backlog

backlog参数配置

  • Nginx 配置 server{ listen 8080 default_server backlog=512}
  • Redis 配置 redis.conf文件 tcp-backlog 511参数
  • Netty 配置 option(ChannelOption.SO_BACKLOG, 1024)
  • Tomcat 配置,默认值是100。独立Tomcat配置了 server.xml,其实 connector 中 acceptCount;Spring Boot内置Tomcat得配置server.tomcat.accept-count

 

相关参数

  • tcp_max_syn_backlog
  • net.core.somaxconn
  • backlog(应用层传入)
  • tcp_syncookies
  • tcp_synack_retries
  • tcp_abort_on_overflow
  • tcp_syn_retries

 

为什么要三次握手?

1、确认双方都能明确自己和对方的收、发能力是正常的

  • 第一次握手:客户端发送网络包,服务端收到了。 这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
  • 第二次握手:服务端发包,客户端收到了。 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
  • 第三次握手:客户端发包,服务端收到了。 这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。

2、指定自己的初始化序列号(ISN),为后面的可靠传送做准备

  • 当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。ISN可以看作是一个32比特的计数器,每4ms加1 。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。
  • 三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。
3、首要原因是为了防止旧的重复连接初始化造成混乱
 
4、为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误
      举例如下:
      已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server
      本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接
      假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求因此不会理睬server的确认,也不会向server发送数据
      但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了
      采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。

 

三次握手抓包分析

下图为三次握手的流程图:

下面通过我们 wireshark 抓包工具来分析三次握手:

 

第一次握手

建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;(x 是随机生成的一个 int 数值)然后,客户端进入SYN_SEND状态,等待服务器的确认;

 

第二次握手

服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);
同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为 y (y 是随机生存的一个 int 数值);
服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;

 

第三次握手

客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,

这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。

 

 

引用:

posted on 2021-04-21 16:43  曹伟雄  阅读(609)  评论(0编辑  收藏  举报

导航