TCP 的那些事 | 三次握手

TCP在建立连接时,会进行三次握手,流程如图1所示:

TCP 的那些事 | 三次握手

图1 TCP  三次握手流程图

3次握手的主要目的是初始化Sequence Number 的初始值及协商一些通信双方的通信参数。通信的双方要互相通知对方自己的初始化的Sequence Number(缩写为ISN:Inital Sequence Number)。SYN,全称Synchronize Sequence Numbers。也就上图中的 x 和 y。这个号要作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输的问题而乱序(TCP会用这个序号来拼接数据)。

第一步:Client首先向Server发送了SYN,seq为x,

第二步:Server向Client回复了SYN + ACK,seq为y,ACK为x+1,其中x为Client的seq

第三步:Client向Server发送ACK,值为y+1,其中,y为第二步Server的seq

 

其中,第一步及第二步是告知对方各自的起始seq,第三步是链接请求方向对方发送收到其SYN+ACK的确认包ACK。此时,你就会疑惑,如果第三步的ACK丢了怎么办,那么Server没有收到ACK,它并不知道Client是否有收到其SYN+ACK。那么你可能会想,Server收到ACK后,在回一个确认包,如果这样,那么Client在收到这个包后,是否还应该回复一个包?这样就陷入的无穷无尽的交互中。为了打破这种情况,于是将握手次数定为3。

 

通过 Wireshark 对三次握手进行抓包,如下图2所示:

TCP 的那些事 | 三次握手

图2 三次握手 Wireshark 抓包

我们看到发起方的 Seq 为0,接收方的 Seq 也为0,那么是不是说通信双方的初始 Seq 都为0呢?其实不是,这个是 Wireshark 为了方便展示,以握手时的 Seq 为0来简化后续交互的 Seq(相对值),可以在"Edit-->Preference-->Protocols-->TCP"中不勾选"Relative Sequence Numbers"。那么真实的Seq如图3所示:

TCP 的那些事 | 三次握手

图3 三次握手Wireshark抓包--绝对***

那么,SYN的初始值ISN怎么取值呢?

 

ISN是不能硬编码的,不然会出问题的——比如:如果连接建好后始终用1来做ISN,如果client发了30个segment过去,但是网络断了,于是 client重连,又用了1做ISN,但是之前连接的那些包到了,于是就被当成了新连接的包,此时,client的Sequence Number 可能是3,而Server端认为client端的这个号是30了。全乱了。RFC793中说,ISN会和一个假的时钟绑在一起,这个时钟会在每4微秒对ISN做加一操作,直到超过2^32,又从0开始。这样,一个ISN的周期大约是4.55个小时。因为,我们假设我们的TCP Segment在网络上的存活时间不会超过MSL(Maximum Segment Lifetime),所以,只要MSL的值小于4.55小时,那么,我们就不会重用到ISN。

 

关于建连接时SYN超时。试想一下,如果server端接到了clien发的SYN后回了SYN-ACK后client掉线了,server端没有收到client回来的ACK,那么,这个连接处于一个中间状态,即没成功,也没失败。于是,server端如果在一定时间内没有收到的TCP会重发SYN-ACK。在Linux下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻售,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP才会把断开这个连接。

 

关于SYN Flood攻击。一些恶意的人就为此制造了SYN Flood攻击——给服务器发了一个SYN后,就下线了,于是服务器需要默认等63s才会断开连接,这样,攻击者就可以把服务器的syn连接的队列耗尽,让正常的连接请求不能处理。于是,Linux下给了一个叫tcp_syncookies的参数来应对这个事——当SYN队列满了后,TCP会通过源地址端口、目标地址端口和时间戳打造出一个特别的Sequence Number发回去(又叫cookie),如果是攻击者则不会有响应,进而可以释放相关资源,如果是正常连接,则会把这个 SYN Cookie发回来,然后服务端可以通过cookie建连接(即使你不在SYN队列中)。

 

可以通过Wireshark发现SYS Flood。"Analyze --> Expert info --> Chats",如图3所示:

TCP 的那些事 | 三次握手

图3 Wireshark chats分析

或者通过命令:tcp.connection.syn

 

如何确定失败的握手?

表达式1:(tcp.flags.reset==1) && (tcp.seq=1),前提是启用了Relative Sequence Numbers。

表达式2:(tcp.flags.sys==1) && (tcp.analysis.retransmission):过滤出重传的握手请求,使用技巧是两端同时抓包。

 

三次握手中有一种特殊的握手形式:连接的双方几乎同时主动提出建立连接,即双方几乎同时发出了SYN包,但是发生的可能性很小。每一方必须发送一个SYN,且这些SYN必须传递给对方。这需要每一方使用一个对方熟知的端口作为本地端口。这又称为同时打开Simultaneous Open)。

 

例如,主机A中的一个应用程序使用本地端口7777,并与主机B的端口8888执行主动打开。主机B中的应用程序则使用本地端口8888,并与主机A的端口7777执行主动打开。

 

TCP是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接(其他的协议族,最突出的是OSI运输层,在这种情况下将建立两条连接而不是一条连接)。

 

当出现同时打开的情况时,状态变迁如图4所示。两端几乎在同时发送SYN,并进入SYN_SENT状态。当每一端收到SYN时,状态变为SYN_RCVD,同时它们都再发SYN并对收到的SYN进行确认。当双方都收到SYN及相应的ACK时,状态都变迁为ESTABLISHED。

TCP 的那些事 | 三次握手

 

4 Simultaneous Open TCP连接建立过程

一个同时打开的连接需要交换4个报文段,比正常的三次握手多一个。此外,要注意的是我们没有将任何一端称为客户或服务器,因为每一端既是客户又是服务器。

 

余晟在文章《认个错,TCP的"三次握手、四次挥手",不能想当然》中对三次握手做了更近一步的分析,很值得一读。

 

推荐阅读:《认个错,TCP的"三次握手、四次挥手",不能想当然》、TCP没那么难吧? 和 TCP没那么难吧【续】

 

本次的三次握手就介绍到这里,如有错误,还请指正。

 

TCP 的那些事 | 三次握手
扫描二维码,关注“小眼睛的梦呓”公众号,在手机端查看文章

参考资料

1. TCP的那些事儿(上)

2.《Wireshark网络分析的艺术》

3.《认个错,TCP的"三次握手、四次挥手",不能想当然

4. 《TCP/IP 详解 卷1》