TCP三次握手、四次挥手详解
1.TCP报文格式
TCP(Transmission Control Protocol) 传输控制协议。TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接。
我们需要知道TCP在网络OSI的七层模型中的第四层(Transport层),IP在第三层(Network层),第二层(Data Link层),在第二层上的数据,我们叫Frame,在第三层上的数据叫Packet,第四层的数据叫Segment。
TCP传输控制协议是面向连接的可靠的传输层协议,在进行数据传输之前,需要在传输数据的两端(客户端和服务器端)创建一个连接,这个连接由一对插口地址唯一标识,即是在IP报文首部的源IP地址、目的IP地址,以及TCP数据报首部的源端口地址和目的端口地址。
上图中有几个字段需要重点介绍下:
(1)Sequence Number
序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。是包的序号,用来解决网络包乱序(reordering)问题。
(2)Acknowledgement Number
确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
(3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:
a. URG(urgent紧急)
:紧急指针(urgent pointer)有效。
b.ACK
(acknowledgement 确认):确认序号有效。
c.PSH
(push传送):接收方应该尽快将这个报文交给应用层。
d.RST
(reset重置):重置连接。
e.SYN
(synchronous建立联机):发起一个新连接。
f.FIN
(finish结束):释放一个连接。
需要注意的是:
(A)不要将确认序号Ack与标志位中的ACK搞混了。
(B)确认方Ack=发起方Seq+1,两端配对。
2.TCP三次握手
TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接。
所谓三次握手(Three-Way Handshake
)即建立TCP连接,是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。
使用wireshark抓包工具分析如下:
可以得出下图:
解释如下:
第一次握手:Client将标志位SYN置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack (number )=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
第三次握手:Client收到确认后,检查ack是否为x+1,标志位ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给Server,Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
3.TCP四次挥手
所谓四次挥手(Four-Way Wavehand
)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。
连接双方在完成数据传输之后就需要断开连接。由于TCP连接是属于全双工的,即连接双方可以在一条TCP连接上互相传输数据,因此在断开时存在一个半关闭状态,即有有一方失去发送数据的能力,却还能接收数据。因此,断开连接需要分为四次
使用wireshark抓包工具分析如下:
流程如下:
由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
第一次挥手:Client将标志位FIN和ACK置为1并且发送发送一个FIN和ACK,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号Ack为收到Seq+1(与SYN相同,一个FIN占用一个序号),序号Seq=1,Server进入CLOSE_WAIT状态。
第三次挥手:Server发送一个FIN,标志位FIN和ACK置为1,用来关闭Server到Client的数据传送,Seq=y,Ack=上次的Seq+1,Server进入LAST_ACK状态。
第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号Ack为收到序号Seq+1,Server进入CLOSED状态,完成四次挥手。
4.为什么建立连接需要三次握手?
TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接。确保数据能够完整传输。
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发
送。
假设如下异常情况:
客户端向服务器发送了第一条请求报文,但是该报文并未在网络中被丢弃,而是长时间阻滞在某处,而客户端收不到服务器确认,以为该报文丢失,于是重新发送该报文,这次的报文成功到达服务器,如果不使用三次握手,则服务器只需对该报文发出确认,就建立了一个连接。而在这个连接建立,并释放后,第一次发送的,阻滞在网络中的报文到达了服务器,服务器以为是客户端又重新发送了一个连接请求(实际上在客户端那里,该连接早已失效),就又向客户端发送一个确认,但客户端认为他没有发送该请求报文,因此不理睬服务器发送的确认,而服务器以为又建立了一个新的连接,于是一直等待A发来数据,造成了服务器资源的浪费,并且会产生安全隐患。因此,若使用三次握手机制,服务器发送了该确认后,收不到客户端的确认,也就知道并没有建立连接,因此不会将资源浪费在这种没有意义的等待上。
5.为什么断开连接需要四次挥手?
当被动方收到主动方的FIN报文通知时,它仅仅表示主动方没有数据再发送给被动方了。
但未必被动方所有的数据都完整的发送给了主动方,所以被动方不会马上关闭SOCKET,它可能还需要发送一些数据给主动方后,再发送FIN报文给主动方,告诉主动方同意关闭连接,所以这里的ACK报文和FIN报文多数情况下都是分开发送的。
6.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND
状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK
状态下的SOCKET
可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT
状态的作用就是用来重发可能丢失的ACK报文。
7.SYN攻击原理
在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect
),此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。
SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。
SYN攻击时一种典型的DDOS攻击,检测SYN攻击的方式非常简单,即当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了,使用如下命令可以可以查看SYN_RECV
状态:
# netstat -nap | grep SYN_RECV
关于建连接时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队列中)。请注意,请先千万别用tcp_syncookies
来处理正常的大负载的连接的情况。因为,synccookies
是妥协版的TCP协议,并不严谨。对于正常的请求,你应该调整三个TCP参数可供你选择,第一个是:tcp_synack_retries
可以用他来减少重试次数;第二个是:tcp_max_syn_backlog
,可以增大SYN连接数;第三个是:tcp_abort_on_overflow
处理不过来干脆就直接拒绝连接了。