一、TCP/IP三次握手原理图:
Windows客户机访问Linux的Apache服务,tcpdump抓包截图
Windows客户端(浏览器):192.168.11.145
Linux服务器(Apache):192.168.11.243
1.1、下面我们详细解释TCP/IP建立连接时的三次握手(三次发送报文的过程)及客户端与服务器端在每次握手时显示的连接状态,配有tcpdump抓包来解释?
服务器:192.168.11.244
客户端(浏览器):192.168.11.243
提示:客户端与服务器端的连接状态可以使用netstat -an|more命令来查看
(1)客户端(192.168.11.243)发起访问请求,第一次握手,发送【SYN】Seq=x
当客户端调用connect函数发起连接时,首先发SYN给服务端,然后自己进入SYN_SENT状态,并等待服务端发送ACK+SYN。
此时进入 SYN_SENT状态:表示客户端已经发送了SYN报文,等待服务器端回应,这段时间一直处于这种状态。
10122 0.280825 192.168.11.243 192.168.11.244 TCP 74 22649→80 [SYN] Seq=0 Win=5840 Len=0 MSS=1460 SACK_PERM=1 TSval=3301310567 TSecr=0 WS=128
(2)服务器端(192.168.11.244)响应请求,第二次握手,发送【SYN,ACK】Seq=y Ack=x+1
服务端收到这个报文后,进入SYN_RECV状态,然后发送ACK+SYN给客户端,ACK用于对客户端SYN的回应,SYN用于询问客户端是否准备好进行数据传输。
此时进入SYN_RECV状态:表示服务端收到客户端发送ACK+SYN报文。
10123 0.280828 192.168.11.244 192.168.11.243 TCP 74 80→22649 [SYN, ACK] Seq=0 Ack=1 Win=5792 Len=0 MSS=1460 SACK_PERM=1 TSval=2610666359 TSecr=3301310567 WS=128
(3)客户端(192.168.11.243)响应,第三次握手,发送【ACK】Seq=x+1 Ack=y+1
客户端收到ACK后进入ESTABLISHED状态,然后发送一个带ACK标志的TCP报文作为服务器端SYN的回应告诉服务器端准备好数据传输,发送SYN通知服务器端开始传输。
服务器端收到最后一个SYN+ACK后,进入到ESTABLISHED状态。
ESTABLISHED:表示连接已经建立成功了。
10124 0.280886 192.168.11.243 192.168.11.244 TCP 66 22649→80 [ACK] Seq=1 Ack=1 Win=5888 Len=0 TSval=3301310567 TSecr=2610666359
1.2、我们用文字再次描述一遍客户端访问Web服务器,连接建立(ESTABLISHED)的过程
1)Client
Client发送一个带SYN标志的TCP报文(报文1)到服务器端,表示希望建立一个TCP连接。
此时程序上,Client端调用socket函数调用时,相当于Client端产生了一个处于Closed状态的套接字。
Client端又调用connect函数调用,系统为Client随机分配一个端口,连同传入connect中的参数(Server的IP和端口),这就形成了一个连接四元组,connect调用让Client端的socket处于SYN_SENT状态。
当Server返回确认,并发送SYN,Client返回确认及SYN后,套接字处于ESTABLISHED阶段,此时双方的连接已经可以进行读写操作。
2)Server
Server发送一个带ACK标志和SYN标志的TCP报文(报文2)给客户端,ACK用于对报文1的回应,SYN用于询问客户端是否准备好进行数据传输。
此时在程序上,Server端调用socket函数调用时,相当于Server端产生了一个处于Closed状态的监听套接字。
Server端调用bind操作,将监听套接字与指定的地址和端口关联,然后又调用listen函数,系统会为其分配未完成队列和完成队列,此时的监听套接字可以接受Client的连接,监听套接字状态处于LISTEN状态。
客户端发送一个带ACK标志的TCP报文(报文3),作为报文2的回应。
当Server端调用accept操作时,会从完成队列中取出一个已经完成的client连接,同时在server这端会产生一个会话套接字,用于和client端套接字的通信,这个会话套接字的状态是ESTABLISH。
二、连接关闭(CLOSE)的过程,四次握手
与连接建立分为server/client不同,连接关闭并没有绝对的server/client之分,连接关闭分为主动关闭和被动关闭。 Server和client都可以担任这两个角色中的任意一个。如client可以关闭它与server的连接,同样的server一样也可以关闭一些长时间无读写事件发生的连接。既然这么说了,下面就会分成两个部分:server主动关闭,client主动关闭。
2.1 本例是客户端浏览器访问Apache服务器的80端口,Server返回页面给Client,然后Server主动关闭,Client被动关闭,开始二者都处于ESTABLISHED状态,下面配有tcpdump抓包来分析:
服务器(Apache):172.21.50.1
客户端(浏览器):192.168.144.129
![](http://static.oschina.net/uploads/space/2015/0126/115332_WRBm_86462.jpg)
(1)服务器端(172.21.50.1)主动发起关闭请求,第一次握手开始,调用close函数发送【FIN,ACK】,Seq=x(492)Ack=y(360)到客户端
7 0.014071 172.21.50.1 192.168.144.129 TCP 54 80→54658 [FIN, ACK] Seq=492 Ack=360 Win=15744 Len=0
此时服务器端处于FIN_WAIT1状态。
(2)客户端(192.168.144.129)收到这个【FIN,ACK】,发送回应的【ACK】开始第二次握手,Seq=y(360)Ack=x+1(493)给服务器。
8 0.017852 192.168.144.129 172.21.50.1 TCP 60 54658→80 [ACK] Seq=360 Ack=493 Win=65208 Len=0
服务器端收到这个【ACK】后进入FIN_WAIT2状态。
(3)客户端(192.168.144.129)也没有数据要传输了,开始关闭与客户端的连接,第三次握手开始,也需要调用close函数发送一个【FIN,ACK】,Seq=y(360)Ack=x+1(493)给服务器。
9 0.018789 192.168.144.129 172.21.50.1 TCP 60 54658→80 [FIN, ACK] Seq=360 Ack=493 Win=65208 Len=0
此时客户端的状态为LAST_ACK。
(4)服务器端(172.21.50.1)收到来自客户端的【FIN,ACK】后,服务器端的套接字处于TIME_WAIT状态,第四次握手,它会向客户端再发送一个【ACK】确认,Seq=x+1(493)Ack=y+1(361)。
10 0.018801 172.21.50.1 192.168.144.129 TCP 54 80→54658 [ACK] Seq=493 Ack=361 Win=15744 Len=0
当客户端收到ACK后就处于CLOSED状态。
2.2 本例是客户端浏览器访问Apache服务器的443端口,Server返回页面给Client,然后Server主动关闭,Client被动关闭,开始二者都处于ESTABLISHED状态,下面配有tcpdump抓包来分析:
(这个有点不同,有5次握手)
服务器(Apache):172.21.50.1
客户端(浏览器):192.168.144.129
![](http://static.oschina.net/uploads/space/2015/0126/110815_gaMJ_86462.jpg)
(1)服务器端(172.21.50.1)主动发起关闭请求,第一次握手开始,调用close函数发送【FIN,ACK】,Seq=x(3766)Ack=y(823)到客户端
32 0.419117 172.21.50.1 192.168.144.129 TCP 54 443→52666 [FIN, ACK] Seq=3766 Ack=823 Win=17920 Len=0
此时服务器端处于FIN_WAIT1状态。
(2)客户端(192.168.144.129)收到这个【FIN,ACK】,发送回应的【ACK】开始第二次握手,Seq=y(823)Ack=x(3766)给服务器。
33 0.422318 192.168.144.129 172.21.50.1 TCP 60 52666→443 [ACK] Seq=823 Ack=3766 Win=64852 Len=0
服务器端收到这个【ACK】后进入FIN_WAIT2状态。
(3)客户端(192.168.144.129)再发送一个回应的【ACK】,开始第三次握手,Seq=y(823)Ack=x+1(3767)给服务器。
34 0.422405 192.168.144.129 172.21.50.1 TCP 60 52666→443 [ACK] Seq=823 Ack=3767 Win=64852 Len=0
此时客户端处于CLOSE_WAIT状态。
(4)客户端(192.168.144.129)也没有数据要传输了,开始关闭与客户端的连接,第四次握手开始,也需要调用close函数发送一个【FIN,ACK】,Seq=y(823)Ack=x+1(3767)给服务器。
35 0.423136 192.168.144.129 172.21.50.1 TCP 60 52666→443 [FIN, ACK] Seq=823 Ack=3767 Win=64852 Len=0
此时客户端的状态为LAST_ACK。
(5)服务器端(172.21.50.1)收到来自客户端的【FIN,ACK】后,服务器端的套接字处于TIME_WAIT状态,第五次握手,它会向客户端再发送一个【ACK】确认,Seq=x+1(3767)Ack=y+1(824)。
36 0.423147 172.21.50.1 192.168.144.129 TCP 54 443→52666 [ACK] Seq=3767 Ack=824 Win=17920 Len=0
当客户端收到ACK后就处于CLOSED状态。
由于在连接关闭后,还不能确定所有连接关闭前的包都被客户端接受到了(包的接受是没有先后顺序的),因此有了TIME_WAIT状态。在这个状态中,服务器仍然在等待客户机发送的【ACK】包。这个状态将保持2*MSL的时间,这里的MSL指的是一个TCP包在网络中存在的最长时间,一般情况下 2*MSL=240秒。
3.2.2 Client主动关闭,Server被动关闭:
原理与上面相同,略。
3.3 介绍TIME_WAIT这个状态,分别按照client/server两个部分讲述
首先说一下TCP/IP详解中描述的关于TIME_WAIT的描述及其存在的必要性:
主动关闭的socket当收到对端的FIN操作后,该socket就会处于TIME_WAIT状态,处于TIME_WAIT状态的socket会存活2MSL(Max Segment Lifetime),
之所以存活这么长时间是有理由的:
一方面是可靠的实现TCP全双工连接的终止,也就是当最后的ACK丢失后,被动关闭端会重发FIN,因此主动关闭端需要维持状态信息,以允许它重新发送最终的ACK。
另一方面TCP在2MSL等待期间,定义这个连接(4元组)不能再使用,任何迟到的报文都会丢弃。设想如果没有2MSL的限制,恰好新到的连接正好满足原先的4元组,这时候连接就可能接收到网络上的延迟报文就可能干扰最新建立的连接。重复的分节在网络中消逝。
3.4 状态为TIME_WAIT是不是所有执行主动关闭的socket都会进入TIME_WAIT状态呢?
有没有什么情况使主动关闭的socket直接进入CLOSED状态呢?
主动关闭的一方在发送最后一个 ack 后就会进入 TIME_WAIT 状态 停留2MSL(max segment lifetime)时间,这个是TCP/IP必不可少的,也就是“解决”不了的。也就是TCP/IP设计者本来是这么设计的。
主要有两个原因:
1、防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)
2、可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发 fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以 主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。
特别提示的是:为什么TIME_WAIT状态还需要等待2MSL才能回到CLOSED状态?或者为什么TCP要引入TIME_WAIT状态?
《TCP/IP详解》中如此解释:当TCP执行一个主动关闭,并发回最后一个ACK后,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL,这样可以让TCP再次发送最后的ACK以防止这个ACK丢失(另一端超时重发最后的FIN)。
附注:MSL(Maximum Segment Lifetime)即最大生存时间,RFC 793中指出MSL为2分钟,但是实现中的常用值为30秒、1分钟或者2分钟。
3.5 终止TCP/IP连接(四次握手)
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。原则是主动关闭的一方(如已传输完所有数据等原因)发送一个FIN报文来表示终止这个方向的连接,收到一个FIN意味着这个方向不再有数据流动,但另一个方向仍能继续发送数据,直到另一个方向也发送FIN报文。
四次挥手的具体过程如下:
客户端发送一个FIN报文(报文4)给服务器,表示我将关闭客户端到服务器端这个方向的连接。
服务器收到报文4后,发送一个ACK报文(报文5)给客户端,序号为报文4的序号加1。
服务器发送一个FIN报文(报文6)给客户端,表示自己也将关闭服务器端到客户端这个方向的连接。
客户端收到报文6后,发回一个ACK报文(报文7)给服务器,序号为报文6的序号加1。
至此,一个TCP连接就关闭了。(4次挥手不是关闭TCP连接的唯一办法,见5、TCP/IP答疑解惑)
TCP三次握手,四次挥手的时序图:
四、TCP/IP答疑解惑
4.1 为什么在TCP协议里,建立连接是三次握手,而关闭连接却是四次握手呢?
因为当处于LISTEN 状态的服务器端SOCKET当收到SYN报文(客户端希望新建一个TCP连接)后,它可以把ACK(应答作用)和SYN(同步作用)放在同一个报文里来发 送给客户端。但在关闭TCP连接时,当收到对方的FIN报文时,对方仅仅表示对方没有数据发送给你了,但未必你的所有数据都已经全部发送给了对方,所以你 大可不必马上关闭SOCKET(发送一个FIN报文),等你发送完剩余的数据给对方之后,再发送FIN报文给对方来表示你同意现在关闭连接了,所以通常情 况下,这里的ACK报文和FIN报文都是分开发送的。
4.2 为什么TIME_WAIT 状态还需要等2*MSL秒之后才能返回到CLOSED 状态呢?
因为虽然双方都同意关闭连接了,而且握手的4个报文也都发送完毕,按理可以直接回到CLOSED 状态(就好比从SYN_SENT 状态到ESTABLISH 状态那样),但是我们必须假想网络是不可靠的,你无法保证你(客户端)最后发送的ACK报文一定会被对方收到,就是说对方处于LAST_ACK 状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT 状态的作用就是用来重发可能丢失的ACK报文。
4.3 关闭TCP连接一定需要4次挥手吗?
不一定,4次挥手关闭TCP连接是最安全的做法。但在有些时候,我们不喜欢TIME_WAIT 状态(如当MSL数值设置过大导致服务器端有太多TIME_WAIT状态的TCP连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时 我们可以通过设置SOCKET变量的SO_LINGER标志来避免SOCKET在close()之后进入TIME_WAIT状态,这时将通过发送RST强 制终止TCP连接(取代正常的TCP四次握手的终止方式)。但这并不是一个很好的主意,TIME_WAIT 对于我们来说往往是有利的。
五、SYN攻击
SYN攻击原理:
SYN Flood利用TCP协议缺陷,发送了大量伪造的TCP连接请求,使得被攻击方资源耗尽,无法及时回应或处理正常的服务请求。一个正常的TCP连接需要三次握手,首先客户端发送一个包含SYN标志的数据包,其后服务器返回一个SYN/ACK的应答包,表示客户端的请求被接受,最后客户端再返回一个确认包ACK,这样才完成TCP连接。在服务器端发送应答包后,如果客户端不发出确认,服务器会等待到超时,期间这些半连接状态都保存在一个空间有限的缓存队列中;如果大量的SYN包发到服务器端后没有应答,就会使服务器端的TCP资源迅速耗尽,导致正常的连接不能进入,甚至会导致服务器的系统崩溃。
tcp 0 0 10.11.11.11:23 236.219.139.207:49162 SYN_RECV -
上面是在LINUX系统中看到的,很多连接处于SYN_RECV状态(在WINDOWS系统中是SYN_RECEIVED状态),源IP地址都是随机的,表明这是一种带有IP欺骗的SYN攻击。