开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情
TCP连接建立
上一节我们知道TCP协议是面向连接的,因此在我们使用TCP协议进行通信前,我们需要建立连接。TCP连接的建立就是大名鼎鼎的三次握手过程。本篇文章将通过实际的抓包去对三次握手,以及后续的通信过程进行分析。
三次握手
TCP的三次握手的过程如下图
SYN和ACK就是上一节讲到的标志位,seq为报文序号,ack为报文确认序号。
整个三次握手分为了五个阶段:
CLOSED:表示client处于关闭状态
LISTEN:表示server正在监听某个端口
SYN-SENT:表示client发送了SYN报文,等待server的回应
SYN-RCVD:表示接收到了client的SYN报文
ESTABLISHED:表示连接已建立
握手报文丢失
上面是正常的三次握手,那么如果这三次握手有一次丢失了会发生什么呢?
第一次握手丢失
如果第一次握手丢失,也就是client发送了SYN报文,并且进入了SYN-SENT状态,但是由于这个SYN报文丢失,迟迟没有收到server发送的SYN+ACK,那么就会触发超时重传。
超时的时间和重发次数不同的操作系统可能会不同,以比较常见的1s和五次重传举例的话就是第一次超时重传是1s后,第二次则是2RTO也就是2s,第三次是4s,第四次是8s,第五次则是达到了16s,在等待32s后如果仍然没有接收到server的回复报文,将直接进入CLOSED状态,也就是说每次重传是前一次重传时间的两倍。
第二次握手丢失
第二次握手丢失,就是server接收到了client发送的SYN报文后,进入了SYN-RCVD状态,发送SYN+ACK报文,但是报文丢失。
从client的视角去看,就会发现这和第一次握手丢失是一样的,同样都是没有接收server的回复报文,因此client会和第一次握手丢失一样超时重传SYN报文。
从server的视角去看,就是没有接收到client的ACK报文,因此会超时重传SYN+ACK报文,也就是说第二次握手丢失,server和client都会重传报文。
第三次握手丢失
第三次握手丢失,就是client发送ACK报文后进入ESTABLISHED状态,但是这个ACK报文丢失了。
需要注意的是ACK报文丢失不会重传ACK报文,因为client已经进入连接建立状态了。但是从server的视角去看和第二次握手丢失是一样的,也没有收到client的ACK报文,因此server会进行超时重传。
为什么不是两次握手?
需要三次握手最直接的原因是需要确定双方收发信息的能力,只有至少三次握手server和client才能确定双方都有收发信息的能力,但这不是主要的原因。
主要的原因是为了避免历史连接的建立。如果一个client发送建立连接的请求,但是被网络延迟了。那么client会进行超时重传,在传输完数据后关闭连接。在关闭连接后之前被延迟的建立连接请求到达server,那么server会回复ACK+SYN报文,但是client并没有要连接服务器的意愿,因此不会理睬该信息,也不会向server发送数据。
那么server就一直等待client发送消息,导致大量的server资源被浪费。
抓包实战
如图为WireShark抓包,端口64427为client端口,80为server端口。
可以发现三次握手的报文长度都是0,第一次Seq为0这并不是实际的序号,是wireshark简化了初始化的序号,方便观察。
可以看到第一次握手序号为0,没有确认序号。
第二次握手序号也为0,需要注意的是实际上这两个序号是不同的,我们可以假设这两个序号分别为x和y,那么第一次握手序号为x+0,第二次握手发送的序号为y+0,确认序号为x+1,也就是期望下一次接收到的序号为x+1.
第三次握手序号为x+1,确认序号为y+1。
在建立连接后,可以看到server和client都是在发ACK报文,两个HTTP之间的TCP报文可以看出来时server端在给client发送消息,因为64427发送的报文序号停留在563并且长度为0,也就是一直在发送确认报文,80的报文序号则是一直在改变。
第一次server发送的序号为y+1,长度为1388,然后就可以发现client发送报文的确认序号为y+1389,第二次server发送的序号和client发送的确认序号一致,为y+1389。后续发送的报文也是一样的规律。
SYN攻击
TCP连接需要经过三次握手,如果攻击者伪造不同的IP地址给server发送SYN报文,那么server会接收到后发送SYN+ACK报文,并且进入SYN-RCVD状态,但是攻击者不让client发送回复报文,导致server的半连接队列被占满,无法正常工作。这就是SYN攻击。
正常的三次握手过程是,server接收到SYN报文后会创建半连接对象,并保存到半连接队列中,接着发送SYN+ACK报文等待client的回复。在接收到ACK报文后从半连接队列中取出对象,并创建新的对象放入全连接队列中,最后通过accept接口从全连接对象取出连接对象。
而如果发生了SYN攻击就会导致半连接队列被占满,后续接收到的SYN报文都会被丢弃。
为了减弱SYN攻击的影响,有三种方式去优化
1.增大半连接队列大小:但是如果攻击者频繁地发送SYN报文,那么就算是增大也不会解决问题。
2.减少SYN+ACK的重传次数:如果server没有接收到ACK报文的话会重传SYN+ACK报文,一般重传次数为5次,我们可以减少重传的次数让半连接队列尽快地抛弃无用的半连接对象。
3.开启syncookies:tcp_syncookies是网络自带的一个选项,当半连接队列满了之后,不会抛弃SYN报文,而是计算出一个cookie值,将该值作为SYN+ACK报文的序号。在收到服务端的应答报文后对该报文的确认序号进行检查,如果合法则放入全连接队列中。
总结
本节介绍了TCP三次握手建立连接的过程,分析了client和server处于的不同状态,同时也分析了三次握手每一次如果发生报文丢失会出现什么情况,并且用wireshark进行了抓包。最后还分析了SYN攻击,希望你能对TCP握手有更加深刻的印象。
感谢观看!