TCP相关知识汇总
前言
之前学习掘金小册深入理解 TCP 协议:从原理到实战,以及其他网络相关知识的笔记,一直忘了整理,今天整理一下,梳理一下脉络。
分层模型
OSI
七层模型和TCP/IP
四层模型?
OSI
七层模型
-
物理层:传输原始的数据比特流、如“0”、“1”信号的电平表示,传输的单位为
bit
; -
数据链路层:对物理层传递上来的比特流进行封装,单位是帧,帧中含有地址、协议、数据以及校验码等信息。通过校验、确认和反馈重发等手段,将不可靠的物理链路改造成对网络层来说无差错的数据链路。同时,链路层还具备流量控制功能,用于协调收发双方的数据传输速率,以防接收方的缓存区溢出。以太网的MTU为1500字节;
-
网络层:数据以网络协议单元(分组)为单位进行传输,主要解决如何使数据分组跨越通信子网从源地址发送到目的地地址的问题,需要进行路由选择。路由选择决定了数据在网络中传输的路径。同时,为了避免网络阻塞,也需要对分组数量进行控制。当分组跨越的子网越多时,还有解决网际互连的问题;
-
传输层:端到端,即主机到主机之间的通信连接,主要作用就是提供端到端的数据透明运输服务。此层要求具备差错控制和流量控制等功能;
-
会话层:进程之间的通信连接,主要组织和同步不同主机上各进程间的通信。会话层主要负责在两个会话层实体之间进行对话连接的建立和拆除。在半双工状态下,会话层提供一种数据权标来控制某一方何时有权发送数据;会话层还会在数据流中插入同步点,这样即使在数据传输过程因网络问题中断后,也不需要再从头开始重新传送,而只需要传输最近一个同步点之后的数据;
-
表现层:提供数据的转换,可以将计算机内部的表现形式转换为网络通信中采用的标准表现形式,这样就可以让采用不同编码方式的计算机在通信中相互理解数据的内容。常见的功能如加解密、编解码服务;
-
应用层:用计算机用户提供接口和服务;
TCP/IP
四层模型
分层的作用
- 各层间相互解耦,减少了依赖;
- 灵活性更好,可拓展性更高;
- 易于测试和维护,可以单独测试某一层
OSI为什么会被TCP/IP取代?
- 制定标准的专家缺少实战经验;
- 制定周期过长,满足该标准的设备无法及时进入市场;
- 模型设计不合理,某些功能在多个层次中重复出现;
TCP/IP 每层中常见协议
三次挥手相关
三次挥手过程
流程如下:
- 第一次握手:主动端给被动端发送一个
SYN
报文,并指明主动端的初始化序列号ISN(C)
; - 第二次握手:被动端收到
SYN
报文后,也会回应一个SYN
报文,并且也制定好自己的ISN
;同时,会将主动端的ISN
+1作为ACK
的值,表明自己已经接收到了主动端的SYN
报文; - 第三次握手:主动端收到
SYN
+ACK
报文后,回应一个ACK
报文,并且将被动端的ISN
+1作为ACK
的值,此时主动端连接已建立; - 被动端收到
ACK
报文后,连接建立,三次握手完成;
三次握手的作用
1.主要是为了确认双方的接收和发送能力正常
-
第一次握手:校验主动端的发送能力是否正常;
-
第二次握手:主动端的发送能力正常、被动端的接收能力正常(被动端回复了
ACK
),开始校验被动的发送能力和主动端的接收能力是否正常; -
第三次握手:主动端的接收能力、被动端的发送能力正常(主动端了回复了
ACK
),此时主动端的连接已建立成功; -
被动端收到了
ACK
之后,确认自己的发送能力正常,连接建立成功
2.指定两端各自的初始化序列号ISN
,以便让对方知道接下来接收数据的时候如何按序列号组装数据,为可靠传输做准备
3.如果是HTTPS
协议的话,三次握手这个过程,还会进行数字证书的验证和加密密钥的生成。
状态的流转
主动端:CLOSED
—>SYN_SENT
—>ESTABLISHED
;
被动端:CLOSED
—>SYN_RCVD
—>ESTABLISHED
;
CLOSED
并不是一个真实的状态,而是一个假想的起点和终点。
ISN相关知识点
初始化序列号ISN
不是从0开始的,通信双方各自生成,一般情况下两端生成的序列号不会相同。生成该ISN
的算法要求ISN
随着时间进行变化,会递增的分配给后续的TCP
连接。
ISN可不可以固定?
先来看下能不能固定?因为TCP连接四元组(源IP
,源端口号,目的IP
,目的端口号)可以唯一确定一个连接。因而,即使所有的连接ISN
都是固定于某一个值的,连接之间也不会相互干扰。但ISN
若是固定了,则会存在几个问题。
- 安全性。如果
ISN
是固定的,当ISN
被攻击者获得的话,攻击者很容易构造一个在对方窗口中的序列号,而源IP
和源端口号也很容易伪造,这样一来的话就可以伪造一个RST
包,将正常的连接强制关闭。因而,采用动态地址的ISN
安全性方面大大提高; - 避免前后连接相互干扰。若是开启了
SO_RESUEADDR
选项,则端口号可以被重用,这样的话,当接收到一个包时,就不知道这个包是属于新连接的还是属于网络中被阻塞的旧包的,这样就会造成数据的混淆。如果是动态增长的ISN
,就可以保证两个连接的ISN
不会相同,不会串包。
SYN段为什么消耗一个序列号,而ACK却不用?
SYN
端长度为0,却要消耗一个序列号,原因在于SYN
段需要对端确认;ACK
端长度为0,不需要消耗序列号,也不用对端确认。
建立链接为什么要三次握手?两次或者四次行不行?
两个方面:
- 防止已失效的连接请求又传输到服务器端。由于网络原因,客户端第一次发送的
SYN
报文被可能滞留在了网络中。在某一个时刻,服务器突然又接收到了这个SYN
报文。如果只是两次握手,那么这个时候,服务器端会以为客户端又要建立连接,因而开始分配资源,如内存和CPU
等,等待着客户端发送数据报文,但此时客服端一脸懵逼,并表示我不想跟你建立连接,于是服务端白白浪费资源在等着客户端发送数据。而如果是三次握手的话,服务端接收到这个SYN
报文后,就会发送SYN
和ACK
报文。当客户端收到这个SYN
+ACK
报文时,根据ack
的值就知道这个连接请求已经过时,此时会发生一个RST
报文,告知服务器端该请求连接已失效,这样服务器端接收到该RST
报文后也不会分配资源去等待客户端的连接。 - 握手的过程中,也包含同步客户端与服务端双方
ISN
步骤。如果只是两次握手,那么只有服务器端知道客户端的ISN
,而客户端不知道服务器端的ISN
。由于TCP
是全双工连接,如果两次握手的话,只有一端到另一端的连接是通畅的。
首先说4次握手,其实为了保证可靠性,这个握手次数可以一直循环下去;但是这没有一个终止就没有意义了。所以3次,保证了各方消息有来有回就足够了。当然这里可能有一种情况是,客户端发送的 ACK
在网络中被丢了。那怎么办?
-
其实大部分时候,我们连接建立完成就会立刻发送数据,所以如果服务端没有收到
ACK
没关系,当收到数据就会认为连接已经建立; -
如果连接建立后不立马传输数据,那么服务端认为连接没有建立成功会周期性重发
SYN&ACK
直到客户端确认成功。
半连接队列
当服务器端绑定、监听某个端口后,系统内核就会为这个端口建立两个队列,SYN
队列(未完成握手的队列)和ACCEP
T队列(已完成握手的队列)。
当客户端第一次发生的SYN
报文到达服务器端时,系统内核会将该信息放入SYN
队列中,同时回应一个SYN
+ACK
报文给客户端。若是服务器端接着收到了来自客户端的ACK
报文后,会将之前存入SYN
队列的连接信息取出,放入ACCEPT
队列中。如下图所示:
SYN-ACK重传次数
当服务器端向客户端回应SYN
+ACK
报文时,就会设置一个计时器等待着来自客户端的应答。若是超过计时器的即时,则会重新再发送一个SYN
+ACK
报文,如此反复,直到重发次数达到配置文件中设定的次数。若是超过了最大重发次数,服务器端仍没有收到来自客户端的应答,则会将该连接信息从SYN
队列中删除。此外,每次重传等待的时间不相同,一般为指数递增,例如,1s、2s、4s...。
三次握手过程中可以携带数据么?
第一次、第二次不可以,第三次可以。
第一次握手如果可以携带数据的话,攻击者可以在SYN
报文中写入大量垃圾数据,这样服务器就会话费很多时间和资源来处理这些垃圾数据。若是攻击者不停的发送SYN
报文,这样就有可能导致服务器端资源耗尽,出现宕机。
而对于第三次连接,此时客户端已经处于ESTABLISHED
状态,也就是说,对于客户端来说,它已经知道服务器端的接收和发送能力正常,此时携带数据就不会出现什么问题。
三次握手过程中,报文中ack
值的变化
最后一次握手,如果携带数据,则seq=x+2
,否则seq=x+1
。
SYN Flood 攻击
SYN Flood
攻击是一种广为人知的DoS
(拒绝服务攻击)方式。其利用TCP
协议的缺陷,通过大量发送伪造的TCP
连接请求,从而使得被攻击方的资源耗尽(CPU
满负荷或者是内存不足)。
想象一个场景:客户端大量伪造不存在的IP
发送SYN
包,服务端应答的SYN
+ACK
包去了一个不存在的IP
地址,由于地址不存在,所以服务端没有接收到ACK
相关信息,这样就会不断重发直至超时,服务端默认重发5次(tcp_synack_retries
),然后等待一段时间后就会丢弃这个未完成的连接。这段时间被称之为SYN Timeout
。而服务端此时将有大量的连接处于SYN_RCVD
状态,服务端会维持一个非常大的半连接队列来保存这些连接信息,这样会消耗大量的CPU
和内存资源。同时,服务端也忙于处理这些攻击请求而无暇顾及那些来自客户端的正常请求,在客户端看来,服务端已失去响应,这种情况就是SYN Flood
攻击。
解决办法
增加SYN
连接数:tcp_max_syn_backlog
修改配置文件piv4.tcp_max_syn_backlog
中的值,将该值增大,不过,真实情况下其实效果不理想,因为总会把资源耗尽。
减少tcp_synack_retries
重试次数
重试次数由 /proc/sys/net/ipv4/tcp_synack_retries
控制,默认情况下是 5 次,当收到SYN+ACK
故意不回 ACK
或者回复的很慢的时候,调小这个值很有必要。另外补充一下,当默认重发5次时,每次重发的时间间隔分别为1s、2s、4s、8s以及16s,发送完最后一次ACK
报文后再等待32s,服务端才会丢弃这个连接。这样算下来,一次恶意的SYN
包,会占用一个服务端连接63s,这个时间也被称之为SYN Timeout
。如果有大量恶意的SYN
包,半连接队列很快就会被占满了。
缩短 SYN Timeout
时间
由于SYN Flood
的攻击效果主要取决于服务端上维护的半连接队列。而攻击导致的连接数=SYN
攻击频率* SYN Timeout
,所以通过缩短 SYN Timeout
,例如20s以下,就可以成倍地降低服务端的负荷。注意,SYN Timeout
不能设置过小,否则会影响正常的客户端访问请求。该方法缺点在于只能应对攻击频率不高的场景。
SYN Cookie机制
给每一个请求连接的IP
地址分配一个Cookie
,如果短时间内连续受到某个IP
的重复SYN
报文,则认为是受到了攻击,以后这个IP
地址来的包都会被丢弃。该方法的缺点在于依赖于真实的IP
。SOCK_RAW
可以修改IP
地址。
SYN Cache
SYN Cache
机制的特点在于,当服务端接收到了SYN
报文时,不再是直接进行资源的分配,是根据这个 SYN
包计算出一个 Cookie
值,作为握手第二步的序列号回复 SYN+ACK
,等对方回应 ACK
包时校验回复的 ACK
值是否合法,如果合法才三次握手成功,分配连接资源。如下图所示:
Cookie
总长度为32位,计算过程如下:
static __u32 secure_tcp_syn_cookie(__be32 saddr, __be32 daddr, __be16 sport,
__be16 dport, __u32 sseq, __u32 data)
{
u32 count = tcp_cookie_time(); // 系统开机经过的分钟数
return (cookie_hash(saddr, daddr, sport, dport, 0, 0) + // 第一次 hmac 哈希
sseq + // 客户端传过来的 SEQ 序列号
(count << COOKIEBITS) + // 系统开机经过的分钟数左移 24 位
((cookie_hash(saddr, daddr, sport, dport, count, 1) + data)
& COOKIEMASK)); // 增加 MSS 值做第二次 hmac 哈希然后取低 24 位
}
其中 COOKIEBITS
等于 24,COOKIEMASK
为 低 24 位的掩码,也即 0x00FFFFFF
,count
为系统的分钟数,sseq
为客户端传过来的 SEQ 序列号。
第一,这里的 MSS
值只能是少数的几种,由数组 msstab
值决定。
第二,因为 syn-cookie 是一个无状态的机制,服务端不保存状态,不能使用其它所有 TCP 选项,除非开启Timestamp
选项。
硬件防火墙
SYN Flood
攻击很容易被硬件防火墙拦截。
LAND攻击
LAND
攻击也是发生在三次握手的过程当中。LAND
攻击报文中的源IP
地址和目标IP
地址都是目标主机的IP
地址。这样的话,当目标主机接收到这个SYN
报文后,就会向该报文的源IP
地址发送一个ACK
报文,并建立起一个TCP
连接控制结构,即TCB
。由于这个ACK
报文中的源IP
地址和目标IP
地址都是目标主机本身,这个ACK
报文也就发给了目标主机本身。这样的话,如果攻击者发送了足够多的SYN
报文,则目标主机的TCB
可能会耗尽,最终不能提供正常的服务。
解决:通过防火墙、路由设备、建立规则,丢弃掉源IP
地址和目标IP
地址一样的SYN
报文、SYN
+ACK
报文以及TCP
连接。
Connection Flood
利用真实的IP
地址向服务器发起大量的连接,并且在建立连接之后很长时间不释放并定时发送垃圾数据使连接可以长时间保持,占用系统资源,造成服务端上残余连接过多,效率降低,无法及时响应其他客户端发出的正常请求。
解决:
- 限制每个源
IP
的连接数; - 对恶意连接的
IP
进行封禁; - 主动清除残余连接;
四次挥手
当三次握手建立连接之后,C/S之间就可以传递数据了。客户端传输完数据之后就要开始准备断开连接了。
四次挥手过程
流程如下:
- 第一次挥手:客户端发出一个
FIN
报文,报文中指定一个序列号u
,接着客户端就会FIN_WAIT1
状态,此时客户端将不再发送数据给服务器端,等待服务器端的确认信息; - 第二次挥手:服务器端收到来自客户端的
FIN
报文,接着会回复一个ACK
报文,指定一个序列号v
,且ack=u+1
,此时服务器端将进入CLOSE-WAIT
状态;此时TCP
连接处于半关闭状态,客户端到服务器端的连接已关闭,但服务器端若是有数据想发给客户端,仍可以继续发送;另一方面,当客户端收到来自服务端的ACK
报文时,将会进入FIN_WAIT2
状态,等待服务端发出连接释放报文FIN
; - 第三次挥手:如果服务器端也想断开连接,则可以像第一次挥手的客户端一样,发出一个连接释放报文
FIN
,指定一个序列号v2
。此时服务端进入LAST-ACK
阶段,等待来自客户端的ACK
报文; - 第四次挥手:客户端收到来自服务端的
FIN
报文后,应答一个ACK
报文,指定序列号为u+1
,且ack=v2+1
。此时客户端进入TIME_WAIT
状态,一般需要等待2个MSL的时间等待服务端接收到ACK
报文后,客户端才会真正进入CLOSED
状态; - 服务端收到来自客户端的
ACK
报文后随之也进入到了CLOSE
状态;
FIN
报文表示发出端和接收端这一方向的连接关闭,不会再有数据的流动。
为什么建立连接只需要三次握手,而断开连接需要四次挥手呢?
- 三次握手建立连接时,服务端再收到来自客户的
SYN
报文时,可以直接发送SYN
+ACK
报文,其中ACK
报文是用来应答客户端的ACK
报文,SYN
报文是用来同步的; - 在断开连接时,由于
TCP
连接是全双工连接,连接的两端都可以发送和接收数据。因而当服务端收到来自客户端的FIN
报文时,只是代表客户端不会再向服务端发送数据,但服务端还是可以继续向客户端发送数据的。因此,这时服务端只会向客户端发送一个ACK
报文,表示收到了FIN
报文。只有当服务端也没有数据发送给客户端时,自己才会发出FIN
报文,断开连接;所以在这个过程不会出现FIN
+ACK
报文一起出现的场景。因而,断开连接需要四次。
MSL
MSL
,Maximum Segment Lifetime
,指的是“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。这是因为TCP
报文段以IP
数据报在网络内传输,而IP
数据报则有限制其生存时间的TTL
字段。
在四次挥手的过程中,当客户端收到来自服务端的FIN
报文时,客户端会进入TIME-WAIT
状态,该状态维持的时间一般为2 * MSL,这样可以让TCP
再次发送最后的ACK
报文,以防之前的ACK
报文丢失。
2MSL
等待的另一个结果就是,在等待的时间内,定义这个TCP
连接的四元组(源IP、源端口、目的IP、目的端口)不能再被重新分配使用,直到2MSL
时间结束。
TIME_WAIT状态意义?
- 保证客户端发送的最后一个
ACK
报文能够达到服务端。数据在网络中传播时,有时候会因为原因被丢失或者是滞留。所以在最后一次挥手时,当客户端发出ACK
报文后,处于LAST-ACK
状态的服务端有可能没有收到这条报文信息,从而重发FIN
报文给客户端。客户端处于TIME_WAIT
状态,可以防止这条TCP
连接所持有的资源被释放或者重新分配,这样就可以接收服务端重新发送的FIN
报文,并重新发送ACK
报文和重置2MSL
计时器。这样,客户端和服务端最后都会顺利进入CLOSED
状态。若是不进入TIME-WAIT
状态,而是直接进入CLOSED
状态,服务端重新发送的FIN
就无法被接收和应答,这样一来服务端就无法正常进入关闭状态; - 防止已失效的连接请求出现在本连接中。客户端发送完最后一个
ACK
报文后,再经过2MSL
,就可以使本连接之持续的时间内产生的所有报文段都从网络中消失,使得下一个新的连接不会出现这种旧的连接请求报文段。
大量的TIME-WAIT会有什么危害?
如果系统中存在大量的TIME-WAIT
状态,则创建新的SOCKET
连接时会收到影响。这是因为在一个TCP
连接中,一个SOCKET
如果关闭的话,它将保持TIME-WAIT
状态大概1-4分钟。如果此时有许多连接快速的打开或者关闭的话,系统中处于TIME-WAIT
状态的SOCKET
将会越来越多。同时对于TCP
连接来说,四元组分别为源IP
和源端口,目的IP
和目的端口。所以由于本地端口数量的限制,同一时间内只有有限个SOCKET
连接可以建立。如果太多的SOCKET
处于TIME-WAIT
状态,由于用于新建连接的本地端口太过缺乏,将会很难建立新的连接。
如何消除大量TCP短连接引起的TIME-WAIT?
-
改用长连接。但代价较大,长连接消耗的系统资源大于短连接,大量的长连接可能会影响机器的性能。
-
修改配置文件
-
修改
ipv4.ip_local_range
,增大可用端口范围,但只是治标不治本; -
修改
tcp_max_rw_buckets
参数,这个参数表示可以同时保持TIME-WAIT
状态的SOCKET
的最大数量,如果超过该值,将会立刻清除处于TIME-WAIT
状态的SOCKET
并打印告警信息。内网低延迟等网络状况好的情况下可以设为0。网络状况不好的情况下设的过低有风险; -
将
ipv4.tcp_tw_resuse
参数由0修改为1,表示允许处于TIME-WAIT
状态的SOCKET
可以被用于新的TCP
连接上; -
将
ipv.tcp_tw_recycle
参数由0修改为1,表示开启TCP
连接处于TIME-WAIT
状态的SOCKET
可以被快速回收; -
将
ipv4.tcp_timestamps
由0设置为1,这个是resuse
和recycle
的依赖参数; -
调整
MSL
的值,因为TIME-WAIT必须要等待2MSL
时间,所以可以通过该参数来减少等待时间;
-
需要注意的是,对于tw的reuse、recycle其实是违反TCP协议规定的,服务器资源允许、负载不大的条件下,尽量不要打开。