532ed87f-0b67-42ac-8e16-b2953de0b53b.png

状态解析

(1) CLOSED 状态时初始状态。

(2) LISTEN:被动打开,服务器端的 状态变为LISTEN(监听)。被动打开的概念:连接的一端的应用程序通知操作系统,希望建立一个传入的连接。这时候操作系统为连接的这一端建立一个连 接。与之对应的是主动连接:应用程序通过主动打开请求来告诉操作系统建立一个连接。

(3)SYN_RECVD:服务器端收到SYN后,状态为SYN;发送SYN ACK;

(4) SYN_SENTY:应用程序发送SYN后,状态为SYN_SENT;

(5)ESTABLISHED:SYNRECVD收到ACK后,状态为ESTABLISHED; SYN_SENT在收到SYN ACK,发送ACK,状态为ESTABLISHED;

(6)CLOSE_WAIT:服务器端在收到FIN后,发送ACK,状态为CLOSE_WAIT;如果此时服务器端还有数据需要发送,那么就发送,直到数据发送完毕;此时,服务器端发送FIN,状态变为LAST_ACK;

(7)FIN_WAIT_1:应用程序端发送FIN,准备断开TCP连接;状态从ESTABLISHED——>FIN_WAIT_1;

(8)FIN_WAIT_2:应用程序端只收到服务器端得ACK信号,并没有收到FIN信号;说明服务器端还有数据传输,那么此时为半连接;

(9)TIME_WAIT:有两种方式进入 该状态:1、FIN_WAIT_1进入:此时应用程序端口收到FIN+ACK(而不是像FIN_WAIT_2那样只收到ACK,说明数据已经发送完毕)并 向服务器端口发送ACK;2、FIN_WAIT_2进入:此时应用程序端口收到了FIN,然后向服务器端发送ACK;TIME_WAIT是为了实现TCP 全双工连接的可靠性关闭,用来重发可能丢失的ACK报文;需要持续2个MSL(最大报文生存时间):假设应用程序端口在进入TIME_WAIT后,2个 MSL时间内并没有收到FIN,说明应用程序最后发出的ACK已经收到了;否则,会在2个MSL内在此收到ACK报文;

三次握手

三次握手的目的

  • 连接服务器指定端口,建立 TCP 连接;

  • 同步连接双方的序列号和确认号;

  • 交换 TCP 窗口大小信息。

在 socket 编程中,客户端执行 connect() 时。将触发三次握手。

  • 第一次握手(SYN=1, seq=x):
    客户端发送一个 TCP 的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。
    发送完毕后,客户端进入 SYN_SEND 状态。

  • 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):
    服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。 发送完毕后,服务器端进入 SYN_RCVD 状态。

  • 第三次握手(ACK=1,ACKnum=y+1)
    客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1
    发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。

四次挥手

TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作

  • 第一次挥手(FIN=1,seq=x)
    假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。
    发送完毕后,客户端进入 FIN_WAIT_1 状态。

  • 第二次挥手(ACK=1,ACKnum=x+1)
    服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。
    发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。

  • 第三次挥手(FIN=1,seq=y)
    服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。
    发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。

  • 第四次挥手(ACK=1,ACKnum=y+1)
    客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。
    服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
    客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。

状态与Linux套接字函数

Listen()函数对应的状态

对应三次握手过程,且此过程由用户调用listen()后,内核自动完成。

接收一个连接时,Linux socket需要完成以下步骤:

  1. A socket is created with socket(2).

  2. The socket is bound to a local address using bind(2), so that other sockets may be connect(2)ed to it.

  3. A  willingness  to  accept  incoming connections and a queue limit for incoming connections are specified with listen().

  4. Connections are accepted with accept(2).

在第三步listen()阶段,Linux内核为每一个监听的tcp端口维持两个队列:

  • 未完成队列:接收到一个SYN建立连接请求,处于SYN_RCVD状态

  • 已完成队列:已完成TCP三次握手过程,处于ESTABLISHED状态

当有一个SYN到来请求建立连接时,就在未完成队列中新建一项。当三次握手过程完成后,就将套接口从未完成队列移动到已完成队列。已完成队列的大小可以通过int listen(int sockfd, int backlog)函数中的backlog定义,在Ubuntu 16.04中使用如下命令查看backlog的定义:

$ man 2 listen
...
       int listen(int sockfd, int backlog);
...
  The backlog specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection  requests. The
       maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog (256). When syn cookies are enabled there is no logical maximum length and this setting is ignored. If a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt  at  connection succeeds.

这个 backlog 的大小会受到系统全局设定的限制,系统全局参数 /proc/sys/net/core/somaxconn 决定了每个 listen() backlog 的上限值(现有系统此值一般为128)。如果应用程序调用 listen() 所设定的 backlog 值超出 somaxconn,backlog 会被截短为somaxconn.

如何查看 listen() backlog?

在较高版本的内核中(测试环境 2.6.32-573.el6.x86_64),可以通过 ss -ln 来查看当前各 socket 的 backlog 状态。
"Recv-Q" 表示已经占用的 backlog.
"Send-Q" 表示 backlog 的总大小。

close()函数调用对应的状态

client为主动关闭,server为被动关闭。

  1. client 通过调用close():向server发起连接关闭请求,TCP flag 中FIN位置1,填充seq=m,此时client处于FIN_WAIT_1状态。

  2. server收到关闭连接请求后,回应client已接收到关闭请求,TCP flag中ACK位置1,填充ACK seq = m + 1,此时server处于CLOSE_WAIT状态。client收到ACK后进入FIN_WAIT_2状态。

  3. server检查是否有未传完的数据,如果有,server将继续传送未传输完毕的数据直到传完为止。如果没有未传完的数据,则继续执行步骤4。

  4. server调用close()来关闭连接:向client发送关闭连接的请求,TCP flag 中FIN位置1,填充seq=n,此时server处于LAST_ACK状态。(此时4次挥手只差一个ACK就结束,所以叫LAST _ACK)

  5. client收到FIN之后,会送一个ACK,TCP flag 中ACK位置1,填充seq=n + 1,之后client处于TIME_WAIT状态,server收到最后一个ACK,进入CLOSED状态。

  6. client将处于TIME_WAIT状态,直到超时,等待时间为2MSL(Max Segment Lifetime)。

到此为止,TCP连接拆除完毕。 这里举例双方都是调用的close()来关闭连接,可以用于关闭连接的还有shutdown(),区别在于,close()会等待未传输完的的数据,shutdown()会暴力一点而不去检查

TIME_WAIT状态只会出现在主动关闭的一方,即主动发起第一个FIN的一方。有时我们编写TCP c/s程序,client端我们会直接使用ctl+c来关闭程序,其实只是终止了程序运行,TCP的四次挥手依然会发生,由协议栈去做

注:MSL(最大分段生存期)指明TCP报文在Internet上最长生存时间,每个具体的TCP实现都必须选择一个确定的MSL值.RFC 1122建议是2分钟,但BSD传统实现采用了30秒.TIME_WAIT 状态最大保持时间是2 * MSL,也就是1-4分钟.

TIME_WAIT状态存在的原因

TIME_WAIT状态存在的原因有两个:

  • 防止一个连接中延迟的数据段会被后序的连接错误的解析。当一个连接处于2MSL状态的时候,任何到达的数据段都将会被丢弃。第一个主动关闭的连接是由终端2主动发起的。如果终端2没有保持在TIME_WAIT状态足够长的时间以确保先前的连接中所有的数据段(每个数据段都有自己相应的序列号)都已经不可用了的话,可能会错误的成为第二个连接的一部分。

  • 为了实现TCP全双工连接的终止可靠性。如果来自终端2的最后一个ACK被丢弃,那么终端1将会重新发送最后的FIN,如果这时候终端2的连接状态已经转变到了CLOSED,那么唯一的响应将会是发送一个RST告诉它重发FIN是不被期望的,这样的结果会导致尽管所有的数据都已经正确的传输,但是终端1还是会接收到一个错误消息。

难点解析

为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?

因为每个方向都需要一个FIN和ACK:

  • 在建立连接时:服务器可以把SYN和ACK放在一个包中发送。

  • 但是在断开连接时:如果一端收到FIN包,但此时仍有数据未发送完,此时就需要先向对端回复FIN包的ACK。等到将剩下的数据都发送完之后,再向对端发送FIN,断开这个方向的连接。因此很多时候FIN和ACK需要在两个数据包中发送,因此需要四次握手

SYN攻击

  • 什么是 SYN 攻击(SYN Flood)?
    在三次握手过程中,服务器发送 SYN-ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 SYN_RCVD 状态。当收到 ACK 后,服务器才能转入 ESTABLISHED 状态.
    SYN 攻击指的是,攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。
    SYN 攻击是一种典型的 DoS/DDoS 攻击。

  • 如何检测 SYN 攻击?
    检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

  • 如何防御 SYN 攻击?
    SYN攻击不能完全被阻止,除非将TCP协议重新设计。我们所做的是尽可能的减轻SYN攻击的危害,常见的防御 SYN 攻击的方法有如下几种:

  • 缩短超时(SYN Timeout)时间

  • 增加最大半连接数

  • 过滤网关防护

  • SYN cookies技术

参考文献