前言
本文向大家介绍了都比较熟悉的 TCP 三次握手和四次挥手。
因为都是满满的干货,就直奔主题了。同时为了助于大家理解,在文章开头部分附上了一些相关概念。
什么是 TCP
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由 IETF 的 RFC 793 定义。
TCP 旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠 TCP 提供可靠的通信服务。TCP 假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP 应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。
所谓协议(protocol),其实就是一个群体之间的规定的规则。
-
面向连接 :并不是真的有东西连接着两端,毕竟无论是面向连接还是无连接,都有网线连接着呢(不包括无线网)。我们可以这么理解面向连接,就是连接双方都维护一个 变量,这个变量维护现在数据传输的状态,例如传输了哪些数据、下一次需要传输哪些数据等等。这个所谓的变量保证双方是一直 ”连着“ 的。
-
可靠的 :准确的来说,TCP 一点都不可靠。一个抽象的协议,不可能左右介质来保证可靠性。但凡是经过某种介质的通信行为均不可能是绝对可靠的!TCP 说的可靠是基于它的 确认和重传机制、数据排序、流量控制、拥塞控制,在通讯时常见的丢包、乱序的情况下依然可以保障被一次接受,中间不丢数据。而且,可靠性是在 TCP 最开始被设计出来的时候被提出的。TCP/IP 本来是一个美国局域网的标准,用于局域网内主机之间的通信协议,由于物理链路安全可控,没有第三方的人为破坏,所以 TCP checknum 可以一定程度上检测到,由于信号突变而产生的数据错误。再加上 TCP 对传输的数据有确认机制,所以在这个大前提下,我们一直称 TCP 是一种可靠的传输机制。但现在的 TCP 暴露于开放的互联网世界,在传输过程中要经过很多设备,只要有人为篡改数据,但通过精心数据调整,checksum 却和没有篡改之前是一样的。所以要正确看待 TCP 的可靠性。具体可参考一下 tcp 的可靠性到底指的是什么? 中车小胖的回答。
TCP 数据包的大小
以太网数据包(packet)的大小是固定的,最初是 1518 字节,后来增加到 1522 字节。其中,1500 字节是负载(payload),22 字节是头信息(head)。
IP 数据包在以太网数据包的负载里面,它也有自己的头信息,最少需要 20 字节,所以 IP 数据包的负载最多为 1480 字节。
TCP 数据包在 IP 数据包的负载里面,它的头信息最少也需要 20 字节,因此 TCP 数据包的最大负载是 1480 - 20 = 1640 字节。由于 IP 和 TCP 协议往往有额外的头信息,所以 TCP 负载实际为 1400 字节左右。
因此,一条 1500 字节的信息需要两个 TCP 数据包。HTTP/2 协议的一大改进,就是压缩 HTTP 协议的头信息,使得一个 HTTP 请求可以放在一个 TCP 数据包里面,而不是分成多个,这样就提高了速度。
注:该部分内容来源于阮一峰老师的 TCP 协议简介。
网络模型
OSI 和 TCP/IP 的对应关系和协议
注:Markdown 表格无法将几个单元格合成一个单元格,因此,TCP/IP 部分用一个单元格同事表示下面的空的单元格内容(PS:如果有会弄的,希望教教我)
| OSI 七层网络模型 | TCP/IP 四层概念模型 | 对应网络协议 |
|---|---|---|
| 应用层(Application) | 应用层 | HTTP、TFTP、FTP、NFS、WAIS、SMTP |
| 表示层(Presentation) | Telnet、Rlogin、SNMP、Gopher | |
| 会话层(Session) | SMTP、DNS | |
| 传输层(Transport) | 传输层 | TCP、UDP |
| 网络层(Network) | 网络层 | IP、ICMP、ARP、RARP、AKP、UUCP |
| 数据链路层(Data Link) | 数据链路层 | FDDI、Ethernet、Arpanet、PDN、SLIP、PPP |
| 物理层(Physical) | IEEE 802.1A、IEEE 802.2 到 IEEE 802.11 |
OSI 模型各层的基本作用
| OSI 层级 | 作用 |
|---|---|
| 应用层 | 为应用程序提供网络服务 |
| 表示层 | 数据格式化、加密、解密 |
| 会话层 | 建立、维护、管理会话连接 |
| 传输层 | 建立、维护、管理端到端连接 |
| 网络层 | IP 寻址和路由选择 |
| 数据链路层 | 控制网络层和物理层之间通信 |
| 物理层 | 比特流传输 |
TCP 数据段格式
源端口和目的端口
源端口和目的端口分别代表呼叫方和被呼叫方的 TCP 端口号,各占 16 位。一个端口与其主机的 IP 地址就可以完整的标识一个端点了,也就是构成了所谓的套接字(Socket)。(16 位代表端口号为 2^16 -1 个即 0 - 65535)
序列号(Sequence Number)
序列号指 TCP 数据段中的 “数据” 部分(不包含 “数据段头” 部分)的第一个字节的编号,占 32 位。在一个 TCP 连接中,传送的数据字节流中的每一个数据字节都要按顺序进行编号,在 “数据段头” 中标识的只是每个数据段的第一个数据字节的编号。整个要传送的字节流的起始序号必须在连接建立时设置。例如,一个数据段的 “序号” 字段值是 101,而该数字段中共有 100 字节,表名本数据段的最后一个字节的编号是 200。这样一来,下一个数据段的 “序列号” 字段值应该是 201,而不是 102,这点要特别注意。
确认号(Acknowledgment Number)
确认号指期望接收到对方下一个数据段中 “数据” 部分的第一个字节序号,占 32 位。注意,”确认号“ 不是代表已经正确接收到的最后一个字节的序号。例如,主机 B 已收到主机 A 发来的一个数据段,其序号值是 101,而该数据段的长度是 100 字节。这表明主机 B 已收到主机 A 前 200 个字节,下一个期望要收到的数据段的第一个字节的序号应该是 201,于是主机 B 在给主机 A 发送确认数据段时要把 “确认号” 设置为 201。
“序列号” 和 “确认号” 两个字段共同用于 TCP 服务中的差错控制,确保 TCP 数据传输的可靠性。
数据偏移
数据偏移指数据段中的 “数据” 部分起始处具体 TCP 数据段起始处的字节偏移量,占 4 位。其实这里的 “数据偏移” 也是在确定 TCP 数据段头部的长度,因为 “数据” 部分是紧挨着数据段头的。因为 TCP 数据段头有不确定的 “可选项” 字段,所以数据偏移字段是非常必要的。但要注意的是,数据偏移量是以 32 位(即 4 字节)为单位来计算的,而不是以单个字节来计算的。因为 4 个比特位可以表示的最大数为 15,所以数据偏移量最大为 60 字节,这也是 TCP 数据段头部分的最大长度。
保留(Reserved)
这是为将来应用而保留的 6 个比特位,目前应全设置为 0。
URG
Urgent Pointer(紧急指针)控制位,指出当前数据段中是否有紧急数据,占 1 位,置 1 时表示有紧急数据。紧急数据会优先安排传送,而不会按照原来的排队顺序进行发送。仅当本字段置为 1,后面的 “紧急指针” 字段才有意义。
ACK
Acknowledgement(确认)控制位,指示 TCP 数据段中的 “确认号” 字段是否有效,占 1 位。仅当 ACK 位置 1 时才表示 “确认号” 字段有效,否则表示 “确认号” 字段无效,应用层实体在读取数据时可以不管 “确认号” 字段。
PSH
Push(推)控制位,指示是否需要立即把收到的该数据段提交给应用进程,占 1 位。当 PSH 置为 1 时要求接收端尽快把该数据段提交给应用进程,而置 0 时没有这个要求,可以先缓存起来。
RST
Reset(重置)控制位,用于重置、释放一个已经混乱的传输连接,然后重建新的传输连接,占 1 位。当 RST 位置 1 时,释放当前传输连接,然后可以重新建立新的传输连接。
SYN
Synchronization(同步)控制位,用来在传输连接建立时同步传输连接序号,占 1 位。当 SYN 位置为 1 时,表示这是一个连接请求或连接确认报文。当 SYN=1,而 ACK=0 时,表名这是一个连接请求数据段。如果对方同意建立连接,则对方会返回一个 SYN=1、ACK=1 的确认。
FIN
Final(最后)控制位,用于释放一个传输连接,占 1 位。当 FIN 位置为 1 时,表示数据已全部传输完成,发送端没有数据要传输了,要求释放当前连接,但是接收端仍然可以继续接收还没有接收完的数据。在正常传输时,该位置为 0。
窗口大小
指示发送此 TCP 数据段的主机用来存储传入数据段的窗口大小,也即发送者当前还可以接收的最大字节数,占 16 位。TCP 的 “窗口大小” 字段是使用可变大小的滑动窗口协议进行流量控制。“窗口大小” 字段的值告诉接收本数据段的主机,从本数据段中所设置的 “确认号” 值算起,本端目前允许对端发送的字节数,是作为让对方设置其发送窗口大小的依据。假设本次所发送的数据段的 “确认号” 字段值 501,而 “窗口大小” 字段值 100,则从 501 算起,本端还可以接收 100 字节(字节序号是 501-600)。
检查和(Checksum)
检查和是指对 “数据段头”、“数据”、“伪头部” 这三部分进行校验,占 16 位。“伪头部” 包括源主机和目的主机的 32 位 IP 地址、TCP 协议号、以及 TCP 数据段长度。
紧急指针(Urgent Pointer)
仅当前面的 URG 控制位置为 1 时才有意义,它指出本数据段中为紧急数据的字节数,占 16 位。“紧急指针” 字段指明了紧急数据的末尾在数据段中的位置。当所有紧急数据处理完后,TCP 就会告诉应用程序恢复到正常操作。要注意的一点是,即使当前窗口大小 为 0,也是可以发送紧急数据的,因为紧急数据无需缓存。
可选项(Option)
“可选项” 字段是可选的,且长度可变,最长可达 40 字节。当没有使用该字段时,TCP 头部的长度是 20 字节。它可以包括窗口缩放选项(Window Scale Option,WSopt)、MSS(最大数据段大小)选项、SACK(选择性确认)选项、时间戳(Timestamp)选项等。
数据(Data)
这是由应用层的应用进程提交的数据,作为 TCP 数据段的 “数据”(有效载荷)部分。
三次握手和四次挥手
三次握手
TCP 建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个 TCP 报文段。
三次握手也称 三报文握手,三报文握手是谢希仁的《计算机网络》首次采用的译名。
在 RFC 973(TCP 标准的文档)中使用的名称是 three way handshake,但这个名称很难译为准确的中文。例如,以前本教材曾采用 “三次握手” 这个广为流行的译名,其实这是在 一次握手 过程中交换了三个报文,而并不是进行了三次握手(这有点像两个人见面进行一次握手时,他们的手上下摇晃了三次,但这并非进行了三次握手)。最近再次重新阅读了 RFC 973 文档,发现有这样的表述:“three way (three message) handshake”。可见采用 “三报文握手” 这样的译名,在意思的表达上应当是比较准确的。请注意,handshake 使用的是单数而不是复数,表名只是 一次握手。
《计算机网络》
在 TCP 连接建立过程中要解决以下三个问题:
- 要使每一方能够确知对方的存在。
- 要允许双方协商一些参数(如最大窗口值、是否使用窗口扩大选项和时间戳选项以及服务质量等)。
- 能够对运输实体资源(如缓存大小、连接表中的项目等)进行分配。
连接建立过程
下面我们来看一下 三报文握手 建立 TCP 连接的过程。
假定主机 A 运行的是 TCP 客户程序,而 B 运行 TCP 服务器程序。最初两端的 TCP 进程都处于 CLOSED(关闭)状态。图中在主机下面的方框分别是 TCP 进程所处的状态。请注意,在本例中,A 主动打开连接,而 B 被动打开连接。
第一次(SYN=1,seq=x)
- 服务器进程进入 LISTEN 状态:一开始,B 的 TCP 服务器进程先创建 传输控制块 TCB(存储了每一个连接中的一些重要信息,如:TCP 连接表,指向发送和接收缓存的指针,指向重传队列的指针,当前的发送和接收序号,等等),准备接受客户进程的连接请求。然后服务器进程就处于 LISTEN(收听)状态,等待客户的连接请求。如有,即作出响应。
- 客户进程进入 SYN-SENT 状态:A 的 TCP 客户进程也是首先创建 传输控制模块 TCB。然后,在打算建立 TCP 连接时,向 B 发出连接请求报文段,这时首部中的同步位
SYN = 1,同时选择一个初始序号seq = x。TCP 规定,SYN 报文段(即SYN = 1的报文段)不能携带数据,但要 消耗掉一个序号。这时 TCP 客户进程进入 SYN-SENT(同步已发送)状态。
第二次(SYN=1,ACK=1,ack=x+1,seq=y)
- 服务器进程进入 SYN-RCVD 状态:B 收到连接请求报文段后,如同意建立连接,则向 A 发送确认。在确认报文段中应把 SYN 位和 ACK 位都置 1,确认号是
ack = x + 1,同时也为自己选择一个初始序号seq = y。请注意,这个报文段也不能携带数据,但同样 要消耗掉一个序号。这时 TCP 服务器进程进入 SYN-RCVD(同步收到)状态。
第三次(ACK=1,seq=x+1,ack=y+1)
- 客户进程进入 ESTABLISHED 阶段:TCP 客户进程收到 B 的确认后,还要向 B 给出确认。确认报文段的 ACK 置 1,确认号
ack = y + 1,而自己的序号seq = x + 1。TCP 的标准规定,ACK 报文段可以携带数据。但 如果不携带数据则不消耗序号,在这种情况下,下一个数据报文段的需要仍是seq = x + 1。这是 TCP 连接已经建立,A 进入 ESTABLISHED(已建立连接)状态。 - 服务器进程进入 ESTABLISHED 状态:当 B 收到 A 的确认后,也进入 ESTABLISHED 状态。
为什么需要三次握手
这个问题的本质是,信道不可靠,但是通信双发需要就某个问题达成一致。而要解决这个问题,无论你在消息中包含什么信息,三次通信是理论上的最小值。所以三次握手不是 TCP 本身的要求,而是为了满足 "在不可靠信道上可靠地传输信息" 这一需求所导致的,请注意这里的本质需求,信道不可靠, 数据传输要可靠。而三次握手能达到这个目的。具体的我们来看一下。
上面给出的连接建立过程叫做 三报文握手。注意,上图中 B 发送给 A 的报文段,也可以拆成两个报文段。可以先发送一个确认报文段(ACK = 1,ack = x + 1),然后再发送一同步报文段(SYN = 1,seq = y)。这样的过程就变成了 四报文握手,但效果是一样的。
因此,至多需要三次报文握手即可确认连接。
那么为什么 A 最后还要发送一次确认呢?这主要是为了防止已失效的连接请求报文段突然又传送到了 B,因而产生错误。
所谓 “已失效的连接请求报文段” 是这样产生的。考虑一种正常情况,A 发出连接请求,但因连接请求保本丢失而未收到确认。于是 A 再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。A 共发送了两个连接请求报文段,其中第一个丢失,第二个到达了 B,没有 “已失效的连接请求报文段”。
现假定出现一种异常情况,即 A 发出的第一个连接请求报文段并没有丢失,而是在某些网络节点长时间滞留了,以致延误到连接释放以后的某个时间才到达 B。本来这事一个早已失效的报文段。但 B 收到此失效的连接请求报文段后,就误认为是 A 又发出一次新的连接请求。于是就向 A 发出确认报文段,同意建立连接。假定不采用报文握手,那么只要 B 发出确认,新的连接就建立了。
由于现在 A 并没有发出建立连接的请求,因此不会理睬 B 的确认,也不会向 B 发送数据。但 B 却以为新的运输连接已经建立了,并一直等待 A 发来的数据。B 的许多资源就这样白白浪费了。
采用三报文握手的方法,可以防止上述现象的发生。例如在刚才的异常情况下,A 不会向 B 的确认发出确认。B 由于收不到确认,就知道 A 并没有要求建立连接。
总结成一句话,就是为了防止已失效的连接请求报文突然又传给服务器,导致服务器一直等待,白白浪费资源。
四次挥手
连接释放过程
数据传输结束后,通信的双方都可释放连接。此时 A 和 B 都处于 ESTABLISHED 状态。现假定 A 主动释放连接。连接释放过程如下:
第一次(FIN=1,seq=u)
- 客户进程进入 FIN-WAIT-1 状态:A 的应用进程向 TCP 发出连接释放报文段,并停止再发送数据,主动关闭 TCP 连接。A 把连接释放报文段首部的终止控制位 FIN 置 1,其序号
seq = u,它等于前面已传送过的数据的最后一个字节的序号加 1。这时 A 进入 FIN-WAIT-1(终止等待 1)状态,等待 B 的确认。请注意,TCP 规定,FIN 报文段即使不携带数据,它也消耗掉一个序号。
第二次(ACK=1,seq=v,ack=u+1)
-
服务端进程进入 CLOSE-WAIT 状态,连接进入 half-close 状态:B 收到连接释放报文段后即发出确认,确认号是
ack = u + 1,而这个报文段自己的序号是 v,等于 B 前面已传送过的数据的最后一个字节的序号加 1。然后 B 就进入 CLOSE-WAIT(关闭等待)状态。TCP 服务器进程这时应通知高层应用进程,因而从 A 到 B 这个方向的连接就释放了,这时的 TCP 连接处于 半关闭(half-close) 状态,即 A 已经没有数据要发送了,但 B 若发送数据,A 仍要接收。也就是说,从 B 到 A 这个方向的连接并未关闭,这个状态可能会持续一段时间。 -
客户线程进入 FIN-WAIT-2 状态:A 收到来自 B 的确认后,就进入 FIN-WAIT-2(终止等待 2)状态,等待 B 发出的连接释放报文段。
第三次(FIN=1,ACK=1,seq=w,ack=u+1)
- 服务端进程进入 LAST-ACK 状态:若 B 已经没有要向 A 发送的数据,其应用进程就通知 TCP 释放连接。这时 B 发出的连接释放报文段必须使
FIN = 1。现假定 B 的序号为 w(在半关闭状态 B 可能又发送了一些数据)。B 还必须重复上次已发送的确认号ack = u + 1。这时 B 就进入了 LAST-ACK(最后确认)状态,等待 A 的确认。
第四次(ACK=1,seq=u+1,ack=w+1)
- 客户线程进入 TIME-WAIT 状态: A 在收到 B 的连接释放报文段后,必须对此发出确认。在确认报文段把 ACK 置 1,确认号
ack = w + 1,而自己的序号是seq = u + 1(根据 TCP 标准,前面发送过的 FIN 报文段要消耗一个序号)。然后进入到 TIME-WAIT(时间等待)状态。请注意,现在 TCP 连接还没有释放掉。必须经过 时间等待计时器(TIME-WAIT timer) 设置的时间 2MSL 后,A 才进入到 CLOSED 状态。时间 MSL 叫做 最长报文段寿命(Maximum Segment Lifetime),RFC 793 建议设为 2 分钟。但这完全是从工程上来考虑的,对于现在的网络,MSL = 2 分钟可能太长了一些。因此 TCP 允许不同的时间可根据具体情况使用更小的 MSL 值。因此,从 A 进入到 TIME-WAIT 状态后,要经过 4 分钟才能进入到 CLOSED 状态,才能开始建立下一个新的连接。当 A 撤销响应的传输控制快 TCB 后,就结束了这次的 TCP 连接。
为什么需要四次挥手
既然握手需要三次报文传输,那么为什么挥手需要四次呢?
我们前面说了,TCP 协议是一种面向连接的、可靠的、基于字节流的协议。TCP 是全双工模式,这就意味着,当客户端 A 发出 FIN 报文段时,只是表示客户端 A 没有数据要发送了,但是这个时候,客户端 A 还是可以接收服务器 B 发送来的数据。当服务器 B 返回 ACK 报文段时,表示它已经知道客户端 A 没有数据要发送了,但是服务器 B 还是可以发送数据到客户端 A 的。这是两次报文传输(挥手)。反过来而言,当服务端也想断开连接时,也需要发送 FIN 报文段,并等待客户端的 ACK 响应。同样需要两次报文传输(挥手)。所以总共需要四次报文传输(四次挥手),通信双方才能都知道对方要断开连接,并发送确认信号通知对方。
总结如下:通信双方发送 FIN 报文段 被对方收到,并接收对方的 ACK 响应,总共会产生 4 次报文传输,即四次挥手。
等待 2MSL 的意义
为什么 A 在 TIME-WAIT 状态必须等待 2MSL 的时间呢?主要有两个理由。
- 为了保证 A 发送的最后一个 ACK 报文段能够到达 B。这个 ACK 报文段有可能丢失,因而使处在 LAST-ACK 状态的 B 收不到对已发送的 FIN + ACK 报文段的确认。B 会超时重传这个 FIN + ACK 报文段,而 A 就能在 2MSL 时间内收到这个重传的 FIN + ACK 报文段。接着 A 重传一次确认,重新启动 2MSL 计时器。最后,A 和 B 都正常进入到 CLOSED 状态。如果 A 在 TIME-WAIT 状态不等待一段时间,而是在发送完 ACK 报文段后立即释放连接,那么就无法收到 B 重传的 FIN + ACK 报文段,因而也不会再发送一次确认报文段。这样,B 就无法按照正常步骤进入 CLOSED 状态。
- 防止三次握手中提到的 “已失效的连接请求报文段” 出现在本连接中。A 在发送完最后一个 ACK 报文段后,再经过时间 2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中小时。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。
B 只要收到了 A 发出的确认,就进入 CLOSED 状态。同样,B 在撤销相应的传输控制块 TCB 后,就结束了这次的 TCP 连接。我们注意到,B 结束 TCP 连接的时间要比 A 早一些。
附加知识点
保活计时器
除了上面提到的时间等待计时器外,TCP 还设有一个 保活计时器(keepalive timer)。摄像有这样的情况:客户已主动与服务器建立了 TCP 连接。但后来客户端的主机突然故障。显然,服务器以后就不能再3收到客户发来的数据。因此,应当有措施使服务器不要再白白等待下去。这就是使用保活计时器。服务器每收到一次客户的数据,就重新设置保活计时器,时间的设置通常是两小时。若两小时没收到客户的数据,服务器就发送一个探测报文段,以后则每隔 75 秒钟发送一次。若一连发送 10 个探测报文段后仍无客户的响应,服务器就认为客户端出了故障,接着就关闭这个连接。
局限
首先 TCP KeepAlive 监测的方式是发送一个 探测包,会给网络带来额外的流量,另外 TCP KeepAlive 只能在内核层级监测连接的存活与否,而连接的存活不一定代表服务可用。例如当一个服务器 CPU 进程服务器占用达到 100%,已经卡死不能响应请求了,此时 TCP KeepAlive 依然会认为连接是存活的。因此 TCP KeepAlive 对于应用层程序的价值是相对较小的。需要做连接保活的应用层程序,例如 QQ,往往会在应用层实现自己的心跳功能。
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 技术
问答环节
什么是半连接队列?
服务器在第一次收到客户端的 SYN 后,就会处于 SYN_REVD 状态,此时双方还没有完全建立连接,服务器会把这种状态下请求连接放入一个队列里,我们把这种队列称之为 半连接队列。
当然,有了半连接队列,就一定会有全连接队列。就是已经完成三次握手,建立起连接的请求连接会放入到一个队列中,这个队列就是 全连接队列。如果队列满了就有可能会出现丢包现象。
什么是 ISN?它是固定的吗?
ISN(Initial Sequence Number)初始序列号,就是我们一端建立连接时发送它的 SYN 包时,为连接选择的初始序列号,即我们在图上看到的 seq。
ISN 肯定不是固定的,它是岁时间变化而变化的。
三次握手的其中一个重要功能是客户端和服务器端交换 ISN,以便让对方知道接下来接收数据的时候如何按照序列号组装数据。如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。
三次握手可以携带数据吗?
从上面的内容中,我们可以看到,三次握手是可以携带数据的,但是 第一次、第二次握手不能携带数据,第三次握手是可以携带数据的。
其实这也很容易理解,假设第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那么它每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不用理服务器的接收、发送能力是否正常,然后疯狂、重复的发送 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。
也就是说,如果第一次握手允许放数据,就会让服务器更容易受到攻击。而对于第三次来说,客户端已经处于 ESTABLISHED 状态,对于客户端来说,它已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也是可以的。
四次挥手可以携带数据吗?
这个问题我的理解是没有必要携带数据,因为我们已经要挥手断开连接了,就意味着我们不想要传递数据了,所以挥手时不携带数据。
而且我们可以把四次挥手简单的理解 “终止——确认——终止——确认” 的过程,终止的过程肯定不会携带数据,第一次确认的话,被动方到主动方这条通信还连接着,没必要把数据放在确认信号中,增加了难度,第二次确认的话,就断开连接了,更没必要携带数据。
《计算机网络》中提到一句话,在上文中我们也可以看到,「请注意,TCP 规定,FIN 报文段即使不携带数据,它也消耗掉一个序号」,说明发送 FIN 报文段的时候不携带数据。
三次握手的各个状态是什么?有什么含义?
我们以客户端和服务端为例,客户端首先发送建立连接请求。
- 服务端 LISTEN(监听):表示服务端等待客户端建立连接请求。
- 客户端 SYN-SENT(同步已发送):表示客户端向服务端发送建立连接请求,并等待确认报文。
- 服务端 SYN-RCVD(同步收到):表示服务端收到客户端的建立连接请求,并回复确认报文。
- 客户端 ESTABLISHED(已建立连接):表示客户端收到服务端发送的确认信号,连接已经建立,并发送确认报文。
- 服务端 ESTABLISHED(已建立连接):表示服务端收到客户端发送的确认报文,连接建立。
四次挥手的各个状态是什么?有什么含义?
我们以客户端和服务端为例,客户端首先发送释放连接请求。
- 服务端 ESTABLISHED(已建立连接):表示连接建立中。
- 客户端 FIN-WAIT-1(终止等待 1):表示客户端向服务端发送连接释放请求,并等待服务端响应。
- 服务端 CLOSE-WAIT(关闭等待):表示服务器收到了连接释放报文段并发出确认报文,此时 TCP 连接处于半关闭状态。
- 客户端 FIN-WAIT-2(终止等待 2):表示客户端收到了服务端的确认报文,并等待服务端发送连接释放报文段。
- 服务端 LAST-ACK(最后确认):表示服务端想断开连接,并发送连接释放报文段,等待客户端发送确认报文段。
- 客户端 TIME-WAIT(时间等待):表示客户端收到了服务端发送的连接释放报文段,并发送了确认报文,进入等待阶段,确保服务端收到确认报文。
第三次握手失败了,怎么处理?
- 服务端:服务端发送了 SYN + ACK 报文后就会启动一个定时器,等待客户端返回的 ACK 报文,如果第三次握手失败的话,客户端给服务端返回了 ACK 报文,服务端并不能收到这个 ACK 报文。那么服务端就会启动超时重传机制,超过规定时间后重新发送 SYN + ACK,重传次数根据
/proc/sys/net/ipv4/tcp_synack_retries赖志定,默认是 5 次。如果重传指定次数到了后,仍然未收到 ACK 应答,那么一段时间后,服务端自动关闭这个连接。 - 客户端:客户端在接收到 SYN + ACK 包之后,它的 TCP 状态就是 ESTABLISHED。客户端向服务端发送数据,服务端以 RST 包响应,客户端得知服务端出错,并断开连接。
参考文档
- TCP 协议
- TCP 协议简介
- 通俗大白话来理解TCP协议的三次握手和四次分手
- 面试官,不要再问我三次握手和四次挥手
- 《深入理解计算机网络》—— 王达
- 《计算机网络》—— 谢希仁