TCP三次握手是浏览器和服务器建立连接的方式,目的是为了使二者能够建立连接,便于后续的数据交互传输。
第一次握手:浏览器向服务器发起建立连接的请求
第二次握手:服务器告诉浏览器,我同意你的连接请求,同时我也向你发起建立连接的请求
第三次握手:浏览器也告诉服务器,我同意建立连接。
至此,双方都知道对方同意建立连接,并准备好了进行数据传输,也知道对方知道自己的情况。接下来就可以传输数据了
一次握手:客户端
发送带有 SYN
标志的连接请求数据包
给服务端
二次握手:服务端
发送带有 SYN+ACK
标志的连接请求和应答数据包
给客户端
三次握手:客户端
发送带有 ACK
标志的应答数据包
给服务端(可以携带数据了)
0、初始状态:
服务端监听某个端口,处于 LISTEN
状态。
1、客户端发送TCP连接请求
客户端会随机一个初始序列号seq=x
(client_isn),
设置SYN=1 ,表示这是SYN握手报文。然后就可以把这个 SYN 报文发送给服务端了,表示向服务端发起连接,之后客户端处于 同步已发送
状态。
2、服务端发送针对TCP连接请求的确认
服务端收到客户端的 SYN 报文后,也随机一个初始序列号(server_isn
)(seq=y)
设置ack=x+1, 表示收到了客户端的x之前的数据,希望客户端下次发送的数据从x+1开始。
设置 SYN=1 和 ACK=1。表示这是一个SYN握手和ACK确认应答报文。
最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 同步已接收
状态。
3、客户端发送确认的确认
客户端收到服务端报文后,还要向服务端回应最后一个应答报文
,
将ACK置为 1 ,表示这是一个应答报文
ack=y+1 ,表示收到了服务器的y之前的数据,希望服务器下次发送的数据从y+1开始。
最后把报文发送给服务端,这次报文可以携带数据,之后客户端处于 连接已建立
状态。
服务器收到客户端的应答报文后,也进入连接已建立
状态
三次握手才可以阻止历史重复连接
(主要原因)
三次握手才可以同步双方的初始序列号
三次握手才可以避免重复建立连接
上面123点,两次握手都不能做到。而四次握手可以做到但开销大,可优化至三次握手
原因一:避免历史连接
网络环境是错综复杂的,往往并不是如我们期望的一样:先发送的数据包,就先到达目标主机。可能会由于网络拥堵等乱七八糟的原因,会使得后发送的数据包先到达目标主机
那么这种情况下 TCP 三次握手是如何避免的呢?
三次握手可以保证客户端应对历史连接问题,
但上面情况如果是两次握手的话,只要收到了响应就说明握手成功,客户端不会检测该响应ACK是否正确,这就发生了错误。并且当此前滞留的SYN报文姗姗来迟到达服务器时,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
如果采用的是三次握手,就算是失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
原因二:同步双方初始序列号
TCP 协议的通信双方, 都必须维护一个「序列号」
, 序列号是可靠传输的一个关键因素,它的作用:
接收方可以去除重复的数据;
接收方可以根据数据包的序列号按序接收;
可以标识发送出去的数据包中, 哪些是已经被对方收到的;
可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」
的 SYN
报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
两次握手只保证了客户端的初始序列号被服务端成功接收,没办法保证服务端的初始序列号被客户端确认接收。
四次握手当然能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」,即四次握手没有必要,优化成了三次握手。
原因三:避免重复建立连接
如果只有「两次握手」,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以每收到一个 SYN 就会建立一个新的连接,服务器就会建立多个冗余的无效链接
,造成不必要的资源浪费。
是什么?
SYN攻击属于DoS
攻击(Denial of Service 拒绝服务)的一种。
SYN攻击大量发送伪造源IP
的第一次握手SYN包,服务器每接收到一个SYN包就会为这个连接信息分配核心内存并放入半连接队列
,当攻击的SYN包超过半连接队列的最大值时,正常的客户发送SYN数据包请求连接就会被服务器丢弃。导致正常的连接请求无法成功。
严重者引起网络堵塞甚至系统瘫痪。
正常流程:
当服务端接收到客户端的 SYN 报文时,会将其加入到内核的「 SYN 队列」
;
接着发送 SYN + ACK 给客户端,等待客户端回应 ACK 报文;
服务端接收到 ACK 报文后,从「 SYN 队列」移入
到「 Accept 队列」;
应用从「 Accept 队列」
取出连接。
如何防范?
先确定是SYN洪泛攻击,然后可以采用一些简单的措施,比如:
1、限制ip连接次数:比如限制同一IP一分钟内新建立的连接数仅为10
3、增大半连接状态的连接数容量
但是增大内存资源占用,不推荐。
4、延迟分配连接资源
当服务器收到SYN握手报文时,不马上分配TCP连接资源。而是计算一个 随机值随 第二次握手的SYN+ACK 包传给客户端,当客户端返回第三次握手的 ACK 后,服务器验证随机值的正确性,确认无误才会进入 TCP 的连接状态
,才会分配资源。
如果是恶意攻击者发送的大量SYN报文,只要服务器不为其分配资源,也不至于遭到严重破坏了。
1、TCP 第一次握手的 SYN 丢包了,会发生了什么?
重传
SYN 数据包,重传次数超过阈值后放弃
2、TCP 第二次握手的 SYN、ACK 丢包了,会发生什么?
客户端 SYN 包没有收到ACK,所以会超时重传
服务端 SYN包也没有收到ACK, 也会超时重传。
3、TCP 第三次握手的 ACK 包丢了,会发生什么?
服务端会一直重传
SYN、ACK 包,重传次数超过阈值后放弃
而客户端
根据是否开启保活机制分为两种情况:
开启了保活机制的话,会经过 2 小时 11 分 15 秒
发现一个「死亡」连接,于是客户端就会断开连接。
没有开启的话,则会一直重传该数据包,直到重传次数超过阈值后就会断开 TCP 连接。
为了网络安全
如果不是随机产生初始序列号,黑客将会以很容易的方式获取到你与其他主机之间通信的初始化序列号
,并且伪造序列号进行攻击,这已经成为一种很常见的网络攻击手段。
因为SYN 段需要对方的确认,所以需要占用一个序列号确保这个确认不会出现歧义。如果不占序列号的话,怎么知道这个确认是对数据包的确认还是对syn报文的确认呢?
第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
第二次握手:Client 确认了自己发送、接收正常,对方发送、接收正常;
第三次握手:Server 确认了自己发送正常,对方接收正常
我们知道TCP 连接是由「四元组」唯一确认的。
然后这个场景中,客户端的IP、服务端IP、目的端口并没有变化
所以这个问题关键在于:本次连接的源端口是否和上一次连接的源端口相同。
所以分两种情况:
1、不相同
此时服务端会认为是新的连接要建立,于是就会通过三次握手来建立新的连接。
那旧连接里的服务端会怎么样呢?
如果服务端发送了数据包给客户端,由于客户端的连接已经被关闭了,此时客户的内核就会回RST
报文,服务端收到后就会释放连接。
如果服务端一直没有发送数据包给客户端,在超过一段时间后, TCP保活机制
就会启动,检测到客户端没有存活后,接着服务端就会释放掉该连接。
2、相同
处于 establish 状态的服务端会回复一个携带了对上次报文的确认号和序列号,这个 ACK 被称之为 Challenge ACK。
接着,客户端收到这个 Challenge ACK,发现序列号并不是自己期望收到的,于是就会回 RST
报文,服务端收到后,就会释放掉该连接。
结论:伪造一个能关闭 TCP 连接的 RST 报文
这个合法的 RST 报文必须同时满足「四元组相同」和「序列号正好落在对方的滑动窗口内」
这两个条件。
怎么伪造?
直接伪造符合预期的序列号是比较困难,因为如果一个正在传输数据的 TCP 连接,滑动窗口时刻都在变化,因此很难伪造一个刚好落在对方滑动窗口内的序列号的 RST 报文。
办法还是有的,我们可以伪造一个四元组相同的 SYN
报文,来拿到“合法”的序列号!
怎么拿到?
如果处于 establish 状态的服务端,收到四元组相同的 SYN 报文后,会回复一个 Challenge ACK,这个 ACK 报文里的「确认号」,正好是服务端下一次想要接收的序列号,说白了,就是可以通过这一步拿到服务端下一次预期接收的序列号。
然后用这个确认号作为 RST 报文的序列号,发送给服务端,此时服务端会认为这个 RST 报文里的序列号是合法的,于是就会释放连接!