建议有些网络基础的人查看此文档,当然了新手仔细看也能看懂,大佬直接跳过新手加油站
首先我们知道传输层TCP协议为了确保可靠连接,会有连接时的三次握手和断开连接时的四次挥手的机制
我们这里不讨论掉三次握手,来看一下正常四次挥手的流程(如下图所示)
注意:实际上client和server端都可以先发出断开连接请求,只不过下图演示的是客户端先发起关闭请求
新手可能看到上面那个图一脸蒙圈,那么我来说明一下,看完说明再理解能更加明了一些(大佬直接跳过)。
好了,简单知道了上述内容就可以看我们四次握手的流程了,分为以下四步
状态:
上方是流程的一个详解,我们这里主要讨论状态,因为这跟后边四次握手的不同情况有关系。
FIN-WAIT-1:表示想主动关闭连接。向对方发送FIN报文后会进入到FIN-WAIT-1状态。
CLOSE-WAIT:表示在等待关闭。当对方发送FIN给自己,自己会回应一个ACK报文,此时进入CLOSE——WAIT状态。在此状态下,是需要考虑自己还有没有数据要发给对方,如果没有就发送FIN报文给对方。
**FIN-WAIT-2:**接收到了对方的ACK确认后就会进入该状态,并等待对方发送FIN报文。
如果接收到了对方同时带FIN,和ACK的报文,就可以直接进入到了TIME-WAIT状态,而无需经过FIN-WAIT-2状态
**LAST-ACK:**被动关闭方发送FIN报文后,等待对方的ACK报文,当收到对方的ack报文后进入到close状态。
TIME-WAIT:表示主动方收到了对方的FIN报文,并发送了ACK报文,在等待2MSL后即可进入到CLOSED状态了。
MSL:(Maximum Segment Lifetime,最大分段生存期),是TCP报文在internet上的最长存活时间,每个TCP实现都需要一个具体的MSL,RFC 1122建议是2分钟。所以2MSL就是4分钟。
**CLOSED:**关闭状态
注意:
这里解释的状态只是正常的挥手中出现的,还有别的状态下方会讨论到。
以上只是通常情况,四次握手会根据实际的不同,发生不同的变化,下面用Wireshark抓包分析一下。
框起来的是四次挥手,至于上边那三个,有基础的应该知道那是三次握手。可以看到 步骤和我们的之前描述的四次挥手的流程一模一样。
注意:
这里我写的三次挥手情况,它只是在表面上看来是三次挥手,实际上的步骤没有改变,只不过把第二次和第三次合并了。下方是我用WireShark抓包的截图,其中中间标红的忽略掉只看绿色部分。
我们会发现,正常的三次挥手到这里怎么变成了四次了,第二次挥手怎么没了?
实际上它还是存在的,就像我上方所说,这种情况是将第二次和第三次合并了,也就是说当客户端发送了关闭连接的请求的时候,服务器因为之前跟你建立连接并且已经把你之前想要的数据传给你了,然后没事了一直等你消息,你一直没动静,早就等的不耐烦了,这时它收到了你的关闭连接请求,好家伙,爷早就不想干了,它快速反应过来我这边数据已经处理完了,于是在回复你消息收到了的同时也告诉你它也想跟你关闭连接。
参考:
注意:
这里重头戏来了,我们之前看了正常TCP断开连接的四次挥手过程。
当然还有看上去是经历三次挥手,实际上跟正常的四次挥手过程类似,不过是将第2,3次合并了,具体流程其实还是可以算是一样的。
特殊情况:
那么还有一种情况,就是假如在相同的时间,比如12点整,这个时候客户端和服务器都想和对方断开连接,同时发给对方一个FIN包,那么两边会出现什么情况?
这时会出现一个新状态→CLOSING:
一种罕见的状态,双方同时都想和对方断开连接,也就是当你发送FIN报文后,没有收到对方的ACK报文反而也收到了对方的FIN报文,那么双方都会进入到CLOSING状态,等待对方的ack报文,如果等待到了对方的ack报文,就直接关闭。
抓包分析:
这里因为要构造TCP连接,所以用了packetdrill,只能在Linux系统中使用,所以人生苦短我用Kali
注意:
脚本不难,但是注意抓包的时候抓的网卡一定要对,我这里抓的是any,我也不知道any是什么,但是看它的线路跟eth0几乎一样,在根据英文,盲猜就是任何网卡的包都抓的意思
脚本:
0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
0.000 bind(3, ..., ...) = 0
0.000 listen(3, 1) = 0
0.100 < S 0:0(0) win 32792 <mss 1460,sackOK,nop,nop,nop,wscale 7>
0.100 > S. 0:0(0) ack 1<...>
0.200 < . 1:1(0) ack 1 win 257
0.200 accept(3, ..., ...) = 4
// 象征性写入一些数据,装的像一点一个正常的TCP连接:握手-传输-挥手
0.250 write(4, ..., 3000) = 3000
0.300 < . 1:1(0) ack 3001 win 257
// 主动断开,发送FIN
0.400 close(4) = 0
// 在未对上述close的FIN进行ACK前,先FIN
0.500 < F. 1:1(0) ack 3001 win 260
// 至此,成功进入同时关闭的CLOSING状态。
// 由于packetdrill不能用dup调用,也好用多线程,为了维持进程不退出,只能等待
10000.000 close(4) = 0
结果如图所示:
kali里面自带wireshark,抓包结果如下,可以看见出现了两个FIN,ACK
这里详解一下下面几个问题:
假设43515端口是客户端,8080端口是服务器
正常当同时出现FIN的时候,双方等待对方的ack并且如果接收到ACK就进入TIME-WAIT状态,等过了时间就关闭了,如果没接收到对方的ACK,就会重传FIN包。图中明显是一方没接收到ACK包,一直在重传,这个不用深究,看了大佬的文章总结出来应该是操作系统内核的BUG,了解一下就好
三次挥手和CLOSING状态的区分
之前我们讲过三次挥手状态是将第二次挥手,和第三次挥手合并了,那么这两种情况好像啊,怎么区分呢?其实很简单,三次挥手虽然简化了但是步骤没省略所有的seq和ack都是正常的。
而CLOSING状态,看好两个FIN包,正常第一个FIN包服务器发送seq 1001给客户端,客户端期望的下次数据是什么?是不是应该是1002,而我们图中第二个FIN包是多少,是不是1001很明显它不是接收到服务器的FIN包后才发回来的,而还停留在上个服务器发的包呢。
seq和ack对应不上问题
看懂了上面区分问题,这个问题就简单了。客户端发的FIN包,ack是1001,但是服务器给的ACK包的seq确实1002这是为什么?,因为双方都知道,奥,我们的FIN包同时发的,那么按照正常挥手情况你的ACK应该是1002啊,不过现在咱俩冲突了,那他很聪明,就直接按照正常情况给你发了seq为1002的包
这个就没啥好说的了,基本上就是两种情况