TCP是面向连接的(connection-oriented),即收发双方在发送数据之前,必须首先建立一个连接,这样在连接断开之前,就一直使用这个连接传输数据。建立连接包括参数的设置、内存空间的分配,收发双方参数的协商等,这一过程需要经过三次成功的沟通,一般叫做“三次握手” (a three-way handshake)。
用通俗的话来讲,这三次沟通就是:
- 发起方:“你好,请问我可以跟你建立一个连接吗?”(发送请求,等待回复)
- 接收方:“好啊,我准备好了,来吧。”
- 发起方:“好的,谢谢,我现在开始向你发送数据了。”
当然,在具体的实现过程中,还包含许多细节,以下一一阐述。
1. TCP报文段结构
要了解三次握手的过程中发送了什么报文,首先得知道TCP报文段由哪些字段构成,其中哪些字段在这个过程中起了关键作用。
我们重点关注以下几个字段:
- Sequence number:序列号,用来标记一个报文段的序号,报文段首字节的字节流编号
- Acknowledgment number:确认号,只有ACK标志位为1时,确认序号字段才有效
- ACK:用于指示确认号是有效的
- SYN:同步序列编号(Synchronize Sequence Numbers)
2. 三次握手
接下来更为仔细地观察一条TCP连接是如何建立的。
假设运行在一台主机(客户)上的进程想与另一台主机(服务器)上的进程建立一条连接。客户中的TCP会以如下的方式与服务器中的TCP建立一条TCP连接:
- 第一步:客户端的TCP向服务器端的TCP发送一个特殊的TCP报文段,这个报文段不包含应用层数据,且其首部的SYN被置为1,这个特殊的报文段称为SYN报文段。并且,客户机随机选择一个初始序号(client_isn, initial sequence number),并将该值放在序列号字段下。客户端发送SYN报文段,并进入SYN_SENT状态,等待服务器确认。
- 第二步:一旦服务端收到该TCP SYN报文段(从SYN标记位为1可以判断),会为该连接分配TCP缓存(buffers)和变量,并发送一个允许连接的报文段 (connection-granted segment)给客户TCP。这个报文段也不包含应用层数据,并且SYN同样被置为1,此外ACK标记位也为1,确认号为client_isn+1,并且选择一个初始序号server_isn作为序列号字段的值。这个报文段被称为SYNACK报文段。此时服务器进入SYN_RECV状态。
- 第三步:客户端收到SYNACK报文段(通过其中的SYN,ACK,确认号可以判断)之后,也为该连接分配缓存和变量。然后客户机会再次向服务端发送一个确认报文,ACK标记位为1,确认号为server_isn+1,(这次SYN为0,SYN只在前两次握手中置为1),这次的报文段可以携带来自应用层的数据。此时客户端进入ESTABLISHED状态。服务端收到这个报文段后,也进入ESTABLISHED状态,此时连接就算完全建立好了,双方可以相互发送数据。
3. SYN洪泛攻击
在上面的讨论中我们知道,服务器收到一个SYN报文段时,分配并初始化连接变量和缓存,然后发送一个SYNACK进行响应。在收到来自客户端的ACK报文段之前,连接并没有完全建立,我们称它为半开连接 (half-open connection)。如果客户不发送ACK以完成三次握手的第三步,那么服务器会在一定时间内终止该半开连接,并回收分配的资源。
在这样的协议下,很容易被一种叫做SYN洪泛攻击 (SYN flood attack) 的拒绝服务攻击 (Denial of Service (DoS) attack) 侵袭。攻击者向服务器发送大量的TCP SYN报文段,而不完成三次握手的第三步,这样服务器不断为这些半开连接分配资源,导致服务器的连接资源消耗殆尽。
目前有一种防御机制可以抵御这种攻击,称为SYN cookie。
这种机制的思想在于,在收到SYN之后不马上进行分配资源(因为怕了),而是在第三步时判断连接的发起者是否为一个合法用户,如果是,再分配资源并建立连接。
首先,当服务器收到一个SYN时,不马上分配资源,而是按如下方式生成一个初始的序列号:该序列号是 “SYN报文段中的源和目的IP地址与端口号以及一个只有服务器自己知道的秘密数 (secret number) ” 的hash值,也就是说,只有知道这个秘密数,才可能算出这个序列号(这个初始序列号就称为“cookie”)。然后服务器就发送包含这个初始序列号的SYNACK。需要注意的是,服务器此时不维护任何关于该SYN的状态信息,甚至不用记住这个cookie值。所以如果客户没有返回一个ACK,那么对服务器来说就当什么时都没发生,现在SYN洪泛攻击就做不成了。
那么合法用户是怎样完成第三个步骤的呢?其实并没有什么改变,任然按照原来的方式进行,发送一个ACK给服务器。此时需要动点手脚的是服务端,服务端怎么判断这个ACK报文是对之前的SYNACK的确认呢?很简单,因为之前的SYNACK的序列号是根据“SYN报文段中的源和目的IP地址与端口号以及一个只有服务器自己知道的秘密数”算出来的,那么这次如果还是那个用户的话,那么源和目的IP地址与端口号是不会变的,然后秘密数服务端也知道,用原来的hash函数一算,就得出来了该序列号,然后加1,看是不是跟这个ACK报文的确认号相等,如果相等,那说明这个ACK对应之前的SYNACK,是合法的,于是创建一个连接。
4. 为什么是“三次”
首先,为什么是三次握手而不是四次或者更多?这个问题是比较简单的,因为既然三次能够解决的问题,为什么非要用四次来浪费资源?
但其实问题的重点在于,为什么不能只用两次?第三次握手去掉不行吗?
对于应对SYN洪泛攻击的改进版的“三次握手”来说(见上文),第三次握手肯定是必须的,这个显而易见。
那如果不考虑攻击呢?两次握手就能搞定吗?
谢希仁版《计算机网络》对这个问题进行了讨论。总的来说,三次握手是为了防止当已失效的连接请求报文段突然又传到服务端,造成双方的不一致,导致资源的浪费。
“已失效的连接请求报文段”指的是这样的情况,客户端发出一个SYN报文段,由于阻塞或者其他原因在网络中滞留,以至于客户端认为丢包了(其实并没有丢),于是重新发出一个SYN报文段,假设这一次顺利完成了,那么双方建立连接。这看起来似乎没什么问题,但网络中有一个隐患,就是那个还在网络中传输的SYN报文段,如果这个SYN在连接期间被服务端收到了,那服务端只会无视它,这样就万事大吉了,但如果是在连接释放之后被收到呢?此时服务端认为有人向他发出连接请求,于是响应一个SYNACK回去,如果采用两次握手的话,那么服务器认为此时连接已经建立好了。但是当客户端收到这个SYNACK时,如果他并没有发起连接,那么他不会理睬这个SYNACK,就当没事发生过(如果客户端此时正好发起连接,那其实他也不会理睬这个SYNACK,因为确认号不对啊。)。那问题就大了,这时候服务器以为连接好了,向客户端发送数据,而客户端处于CLOSED状态,会丢弃这些包,这样就很浪费了。并且还有一个尴尬的问题,就是这个时候当客户端打算发起连接时,服务端又不理睬了,在这里尬这,他们就别想互发数据了。当然这些问题似乎不是不可解决的,当客户端发现服务端老是向自己发数据,而自己总是丢弃,可能会向服务端发一个RST(报文段的RST标记号为1),强制服务端关闭连接。但资源总归是浪费了一会了。而用三次握手就不会出现这样的问题。
相关阅读:
计算机网络——TCP四次挥手过程详解
TCP连接的三次握手四次挥手——类比异地恋情侣开始交往和分手(通俗易懂)
TCP是面向连接的(connection-oriented),即收发双方在发送数据之前,必须首先建立一个连接,这样在连接断开之前,就一直使用这个连接传输数据。建立连接包括参数的设置、内存空间的分配,收发双方参数的协商等,这一过程需要经过三次成功的沟通,一般叫做“三次握手” (a three-way handshake)。
用通俗的话来讲,这三次沟通就是:
- 发起方:“你好,请问我可以跟你建立一个连接吗?”(发送请求,等待回复)
- 接收方:“好啊,我准备好了,来吧。”
- 发起方:“好的,谢谢,我现在开始向你发送数据了。”
当然,在具体的实现过程中,还包含许多细节,以下一一阐述。
1. TCP报文段结构
要了解三次握手的过程中发送了什么报文,首先得知道TCP报文段由哪些字段构成,其中哪些字段在这个过程中起了关键作用。
我们重点关注以下几个字段:
- Sequence number:序列号,用来标记一个报文段的序号,报文段首字节的字节流编号
- Acknowledgment number:确认号,只有ACK标志位为1时,确认序号字段才有效
- ACK:用于指示确认号是有效的
- SYN:同步序列编号(Synchronize Sequence Numbers)
2. 三次握手
接下来更为仔细地观察一条TCP连接是如何建立的。
假设运行在一台主机(客户)上的进程想与另一台主机(服务器)上的进程建立一条连接。客户中的TCP会以如下的方式与服务器中的TCP建立一条TCP连接:
- 第一步:客户端的TCP向服务器端的TCP发送一个特殊的TCP报文段,这个报文段不包含应用层数据,且其首部的SYN被置为1,这个特殊的报文段称为SYN报文段。并且,客户机随机选择一个初始序号(client_isn, initial sequence number),并将该值放在序列号字段下。客户端发送SYN报文段,并进入SYN_SENT状态,等待服务器确认。
- 第二步:一旦服务端收到该TCP SYN报文段(从SYN标记位为1可以判断),会为该连接分配TCP缓存(buffers)和变量,并发送一个允许连接的报文段 (connection-granted segment)给客户TCP。这个报文段也不包含应用层数据,并且SYN同样被置为1,此外ACK标记位也为1,确认号为client_isn+1,并且选择一个初始序号server_isn作为序列号字段的值。这个报文段被称为SYNACK报文段。此时服务器进入SYN_RECV状态。
- 第三步:客户端收到SYNACK报文段(通过其中的SYN,ACK,确认号可以判断)之后,也为该连接分配缓存和变量。然后客户机会再次向服务端发送一个确认报文,ACK标记位为1,确认号为server_isn+1,(这次SYN为0,SYN只在前两次握手中置为1),这次的报文段可以携带来自应用层的数据。此时客户端进入ESTABLISHED状态。服务端收到这个报文段后,也进入ESTABLISHED状态,此时连接就算完全建立好了,双方可以相互发送数据。
3. SYN洪泛攻击
在上面的讨论中我们知道,服务器收到一个SYN报文段时,分配并初始化连接变量和缓存,然后发送一个SYNACK进行响应。在收到来自客户端的ACK报文段之前,连接并没有完全建立,我们称它为半开连接 (half-open connection)。如果客户不发送ACK以完成三次握手的第三步,那么服务器会在一定时间内终止该半开连接,并回收分配的资源。
在这样的协议下,很容易被一种叫做SYN洪泛攻击 (SYN flood attack) 的拒绝服务攻击 (Denial of Service (DoS) attack) 侵袭。攻击者向服务器发送大量的TCP SYN报文段,而不完成三次握手的第三步,这样服务器不断为这些半开连接分配资源,导致服务器的连接资源消耗殆尽。
目前有一种防御机制可以抵御这种攻击,称为SYN cookie。
这种机制的思想在于,在收到SYN之后不马上进行分配资源(因为怕了),而是在第三步时判断连接的发起者是否为一个合法用户,如果是,再分配资源并建立连接。
首先,当服务器收到一个SYN时,不马上分配资源,而是按如下方式生成一个初始的序列号:该序列号是 “SYN报文段中的源和目的IP地址与端口号以及一个只有服务器自己知道的秘密数 (secret number) ” 的hash值,也就是说,只有知道这个秘密数,才可能算出这个序列号(这个初始序列号就称为“cookie”)。然后服务器就发送包含这个初始序列号的SYNACK。需要注意的是,服务器此时不维护任何关于该SYN的状态信息,甚至不用记住这个cookie值。所以如果客户没有返回一个ACK,那么对服务器来说就当什么时都没发生,现在SYN洪泛攻击就做不成了。
那么合法用户是怎样完成第三个步骤的呢?其实并没有什么改变,任然按照原来的方式进行,发送一个ACK给服务器。此时需要动点手脚的是服务端,服务端怎么判断这个ACK报文是对之前的SYNACK的确认呢?很简单,因为之前的SYNACK的序列号是根据“SYN报文段中的源和目的IP地址与端口号以及一个只有服务器自己知道的秘密数”算出来的,那么这次如果还是那个用户的话,那么源和目的IP地址与端口号是不会变的,然后秘密数服务端也知道,用原来的hash函数一算,就得出来了该序列号,然后加1,看是不是跟这个ACK报文的确认号相等,如果相等,那说明这个ACK对应之前的SYNACK,是合法的,于是创建一个连接。
4. 为什么是“三次”
首先,为什么是三次握手而不是四次或者更多?这个问题是比较简单的,因为既然三次能够解决的问题,为什么非要用四次来浪费资源?
但其实问题的重点在于,为什么不能只用两次?第三次握手去掉不行吗?
对于应对SYN洪泛攻击的改进版的“三次握手”来说(见上文),第三次握手肯定是必须的,这个显而易见。
那如果不考虑攻击呢?两次握手就能搞定吗?
谢希仁版《计算机网络》对这个问题进行了讨论。总的来说,三次握手是为了防止当已失效的连接请求报文段突然又传到服务端,造成双方的不一致,导致资源的浪费。
“已失效的连接请求报文段”指的是这样的情况,客户端发出一个SYN报文段,由于阻塞或者其他原因在网络中滞留,以至于客户端认为丢包了(其实并没有丢),于是重新发出一个SYN报文段,假设这一次顺利完成了,那么双方建立连接。这看起来似乎没什么问题,但网络中有一个隐患,就是那个还在网络中传输的SYN报文段,如果这个SYN在连接期间被服务端收到了,那服务端只会无视它,这样就万事大吉了,但如果是在连接释放之后被收到呢?此时服务端认为有人向他发出连接请求,于是响应一个SYNACK回去,如果采用两次握手的话,那么服务器认为此时连接已经建立好了。但是当客户端收到这个SYNACK时,如果他并没有发起连接,那么他不会理睬这个SYNACK,就当没事发生过(如果客户端此时正好发起连接,那其实他也不会理睬这个SYNACK,因为确认号不对啊。)。那问题就大了,这时候服务器以为连接好了,向客户端发送数据,而客户端处于CLOSED状态,会丢弃这些包,这样就很浪费了。并且还有一个尴尬的问题,就是这个时候当客户端打算发起连接时,服务端又不理睬了,在这里尬这,他们就别想互发数据了。当然这些问题似乎不是不可解决的,当客户端发现服务端老是向自己发数据,而自己总是丢弃,可能会向服务端发一个RST(报文段的RST标记号为1),强制服务端关闭连接。但资源总归是浪费了一会了。而用三次握手就不会出现这样的问题。
相关阅读:
计算机网络——TCP四次挥手过程详解
TCP连接的三次握手四次挥手——类比异地恋情侣开始交往和分手(通俗易懂)