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报文后,就会发送SYNACK报文。当客户端收到这个SYN+ACK报文时,根据ack的值就知道这个连接请求已经过时,此时会发生一个RST报文,告知服务器端该请求连接已失效,这样服务器端接收到该RST报文后也不会分配资源去等待客户端的连接。
  • 握手的过程中,也包含同步客户端与服务端双方ISN步骤。如果只是两次握手,那么只有服务器端知道客户端的ISN,而客户端不知道服务器端的ISN。由于TCP是全双工连接,如果两次握手的话,只有一端到另一端的连接是通畅的。

  首先说4次握手,其实为了保证可靠性,这个握手次数可以一直循环下去;但是这没有一个终止就没有意义了。所以3次,保证了各方消息有来有回就足够了。当然这里可能有一种情况是,客户端发送的 ACK 在网络中被丢了。那怎么办?

  • 其实大部分时候,我们连接建立完成就会立刻发送数据,所以如果服务端没有收到 ACK 没关系,当收到数据就会认为连接已经建立;

  • 如果连接建立后不立马传输数据,那么服务端认为连接没有建立成功会周期性重发 SYN&ACK 直到客户端确认成功。

半连接队列

  当服务器端绑定、监听某个端口后,系统内核就会为这个端口建立两个队列,SYN队列(未完成握手的队列)ACCEPT队列(已完成握手的队列)

  当客户端第一次发生的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地址来的包都会被丢弃。该方法的缺点在于依赖于真实的IPSOCK_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 位的掩码,也即 0x00FFFFFFcount 为系统的分钟数,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

  MSLMaximum 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,这个是resuserecycle的依赖参数;

    • 调整MSL的值,因为TIME-WAIT必须要等待2MSL时间,所以可以通过该参数来减少等待时间;

  需要注意的是,对于tw的reuse、recycle其实是违反TCP协议规定的,服务器资源允许、负载不大的条件下,尽量不要打开

客户端与服务端状态流转图

Reference

深入理解 TCP 协议:从原理到实战

posted @ 2020-07-28 00:10  Reecelin  阅读(425)  评论(0编辑  收藏  举报