TCP连接管理、客户端和服务端的状态、第三次握手 一些问题

TCP连接的建立

假设一个客户上的一个进程想和另一台主机上的一个进程建立一条连接。
客户的应用进程首先通知客户TC片,它想建立一个与服务器上某个进程之间的连接。
客户的TCP会用以下方式与服务器中的TCP建立一条TCP连接:

  1. 客户端的TCP首先像服务器端的TCP发送一个特殊的TCP报文段。该报文段中不包含应用层数据。
    报文段首部的标志位: SYN = 1 => 这个报文段被称为SYN报文段
    客户会随机选择一个初始序号 client_isn,被放置到序号字段中。
    该报文段被封装在一个IP数据报中,并发送给服务器,为了避免某些攻击,client_isn到选择有一定的研究。

  2. 一旦TCP SYN报文段的IP数据报到达服务器主机,服务器中数据报中提取出TCP SYN报文段
    为该TCP连接分配TCP缓存和变量,并向客户TCP发送允许连接的报文段(在第三步握手之前分配这些缓存和变量,使得TCP容易收到称为SYN洪泛的拒绝服务攻击)。
    这个允许连接的报文段不包含应用层数据。报文段的首部含有三个重要信息。

    1. SYN = 1
    2. 确认号字段被置为 client_isn + 1
    3. 服务器选择自己的初始序号,将其放置到TCP报文段首部的 序号字段中。

    这个报文段被称为SYNACK报文段

  3. 在收到SYNACK报文段后,客户也要给该连接分配缓存和变量,客户端向服务器发送另外一个报文段。

    1. 最后一个报文段对服务器的允许连接的报文段已经了确认
      TCP报文段首部的确认字段 = server_isn + 1
    2. SYN = 0, 因为连接已经建立了,握手的第三个阶段可以在报文段的数据部分携带客户到服务器的数据。

在完成这三个步骤以后,客户端和服务端主机就可以相互发送包括数据的报文段了。

  1. 在以后的每一个报文段中, SYN bit = 0

TCP连接的关闭

参与一条TCP连接的两个进程中的任何一个都能终止该连接。
当连接结束后,主机中的缓存和变量将被释放。

客户应用进程发出一个关闭命令。

  1. 客户TCP应用进程向服务器发送一个特殊的TCP报文段
    FIN = 1

  2. 服务器接收到该报文段后,就像发送方回送一个 确认报文段

  3. 随后,服务器发送它自己的终止报文段
    FIN = 1

  4. 客户对这个服务器的终止报文段进行确认。

TCP生命周期

在TCP的连接的生命周期中,允许在每台主机的TCP协议在各种TCP状态之间变迁。

客户TCP

  1. 处于CLOSED(关闭)状态。
    客户的应用程序发起一个新的TCP连接,客户TCP发送一个SYN报文段。
  2. 处于SYN_SENT 状态。
    等待来自服务器TCP的对客户所发报文段进行确认且SYN = 1的一个报文段。收到这个报文段后
  3. 处于ESTABLISHED(已建立)状态。
    处于ESTABLISHED状态后,TCP客户就能发送和接收有效荷载数据的TCP报文段了
    假设这个客户程序要关闭该连接,引起客户TCP发送一个带有 FIN = 1的TCP报文段
  4. 处于FIN_WAIT1状态。
    客户TCP等待一个来自服务器的带有确认的TCP报文段,当收到这个报文段后
  5. 处于FIN_WAIT2状态。
    客户等待来自服务器的FIN=1的另一个报文段,当收到这个报文段后
    客户TCP对服务器的报文段进行确认
  6. 处于TIME_WAIT状态。
    假定ACK丢失,TIME_WAIT状态使得TCP客户重传最后的确认报文。
    TIME_WAIT状态中所消耗的时间是与具体实现有关的,典型的为30s,1min,2min。
    经过等待后,连接就正式关闭,客户端所有资源将被释放
  7. 处于CLOASE状态。

服务端TCP状态

  1. CLOSED
    服务器应用程序创建了一个SOCKET, BIND 端口,LISTEN
  2. LISTEN
    服务器正在监听客户发送器SYN报文段的端口

假设该主机 接受了SYN报文段 并且发送了SYN ACK报文段
3. SYN_RCVD状态
接受ACK,不发送, 假设接受到了ACK报文段
4. ESTABLISHED状态
假设是客户端提出关闭
接受到了FIN后, 发送ACK报文段
5. CLOSE_WAIT状态
发送FIN
6. LAST_ACK状态
接收ACK,不发送
7. CLOSED状态

image

RST

TCP种段RST表示复位,用来关闭异常连接。
会出现 RST的一些情况:

  1. 端口未打开
    假设端口或源IP与SOCKET不匹配
    假设一个主机接受了一个 TCP SYN报文,端口为80,但是这个主机的80端口不接受连接。
    则 该主机向源发送一个特殊重制报文段。
    RST标志为 = 1
    如果这个主机接受的是UDP分组,它的目的端口与进行中的UDP套接字不匹配,该主机发送一个特殊的ICMP数据报。

    采用nmap对某个主机发送一个特殊的TCP SYN报文段, 有3种可能的输出:

    1. 源主机收到一个 TCP SYNACK报文段。意味着目标主机上一个应用程序执使用TCP端口XXX允许,返回”打开“。

    2. 源主机收到一个RST报文段。 意味着该SYN报文段到达了目标主机,但目标主机没有允许一个使用TCP端口XXX的应用程序。
      但知道发向该主机端口XXX的报文段没有被源和目标 主机之间的任何防火墙所阻挡。

    3. 源什么都没有收到。 表示SYN报文段可能被中间的防火墙所阻挡。

  2. 请求超时
    有两个主机,A向B发送一个SYN,B回了一个SYNACK,然后B也发了一个RST。为什么?假设主机A发送SYN后,设置超时时间为100ms,然后接收到 SYN ACK后,时间为110ms,那么主机A认为程序接收超时,然后发送了RST拒绝了进一步发送数据。

  3. RST攻击
    A和B建立了TCP连接,然后 ** C伪造了一个TCP包发给B**, 使得B异常的断开了与A之间的TCP连接,就是RST攻击。
    那么发送什么样的TCP包可以达成目的?

    1. C 伪装成A发过去的包,这个包是RST包,B将会丢弃与A的缓冲区上的所有数据,强制关闭掉连接。
    2. 发过去的是SYN包,B表示A已经G了,处于正常连接的状态又连接新的连接,B主动向A发个RST包,并在自己这端强制关掉连接。
      要伪造 源端口和序列号。
      B作为服务器IP和端口事公开的,那么我们只需要去下手A,IP肯定知道,A的源端口是不清楚的,可能是A随机生成的。
      序列号问题与滑动窗口相对应,伪造的TCP包里需要填写序列号。
      如果序列号不在A之前发送向B的滑动窗口内,B会主动丢弃的。这个可以采用暴力解决。sequence是4B的,取之有限。
      窗口大小按65535来算,我们除一下,然后就可以发65537次,就能落到滑窗内。RST包非常小,加IP头和TCP头才40字节,然后算一下带宽,很快就能搞定。所以来说序列号也不是问题。
      解决: 通过防火墙解决

关于第三次握手

  1. 针对服务端: 防止已失效的请求报文段突然又传送到了服务端而产生连接到误判。
    1. 假设客户端发送了一个SYN报文段A到服务器, 但是这个A在某个网络节点上被阻塞了
    2. 客户端超时重发了一个SYN报文段B到服务器,然后正常连接数据传输完毕,然后释放连接。
    3. SYN报文段A延迟了一段时间又到了服务器,这个报文段本来应该失效了,但是服务端收到后以为客户端又发出了一次连接请求。
      假设这里没有第三次握手,这时只要服务端发送了确认,然后进入ESTABLISHED状态,就建立了新的连接,但是由于客户端没有发出连接的请求,因此不会理会服务端的确认(SYNACK)。也不会向服务端发送数据,但是服务端却认为已经建立了新的连接,并且一直等待客户端发送数据,然后直到保活计时到了设定值,然后判定客户端出现问题,才会关闭这个连接,这样就浪费了很多服务器资源。
      而采用三次握手,服务端非要收到最后的ACK,且SYN=0,才会进入建立连接状态。
  2. 针对客户端:
    假设没有第三次握手,在第二次握手失败的时候,服务器端发出去请求就认为连接成功了,然后开始发送数据,客户端都没有收到第二次握手,也不知道服务端的序列号是多少,然后就将服务端发来的数据都忽略掉了。
  3. 在第三次握手失效的时候:
    服务器不会重传ACK报文,而是直接发送RST报文,进入CLOSED状态,这样做的目的是为了防止SYN洪泛攻击。

关于SYN Flood攻击:

攻击者伪造地址对服务器发起SYN请求,服务器回应SYN+ACK,然后服务器会尝试5次TCP_SYN_RETIRE。消耗了服务器的内存和带宽。
	1. 无效连接监视释放
			这种方法不停的监视系统中的半开连接和不活动连接,当到达阈值时,拆除这些连接,释放系统资源。
	2. 延缓TCB分配方法
			SYN Flood利用了SYN数据报一道,系统立即分配了TCB(线程控制块)资源,从而占用了系统归档资源,因此有2个技术来解决这一问题。
			1. SYN Cache技术:
				收到SYN后先不分配TCB,回应一个 SYN ACK,然后在一个专用的HASH表中保存这种半开连接,直到收到正确的ACK再去分配TCB .
			2. SYN Cookie技术:
				完全不使用任何存储资源,用特殊的算法生成sequnce number, 考虑了对方的IP、端口、己方的IP、端口的固定信息,以及对方不知道的一些信息,比如MSS、时间等,收到对方的ACK报文后,重新计算,看看是否和对方报文的确认号 - 1相同,从而决定是否分配TCB资源
	3. SYN Proxy防火墙
		对试图穿越的SYN请求进行验证后再放行。

keepalive如何工作?TCP连接会自动断开吗? 客户端出现故障怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
若在一个给定连接上,两小时之内无任何活动,服务器便向客户端发送一个探测段。(我们将在下面的例子中看到探测段的样子。)客户端主机必须是下列四种状态之一:

  1. 客户端主机依旧活跃(up)运行,并且从服务器可到达。从客户端TCP的正常响应,服务器知道对方仍然活跃。服务器的TCP为接下来的两小时复位存活定时器,如果在这两个小时到期之前,连接上发生应用程序的通信,则定时器重新为往下的两小时复位,并且接着交换数据。
  2. 客户端已经崩溃,或者已经关闭(down),或者正在重启过程中。在这两种情况下,它的TCP都不会响应。服务器没有收到对其发出探测的响应,并且在75秒之后超时。服务器将总共发送10个这样的探测,每个探测75秒。如果没有收到一个响应,它就认为客户端主机已经关闭并终止连接。
  3. 客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个 RST复位,从而引起服务器对连接的终止。
  4. 客户端主机活跃运行,但从服务器不可到达。这与状态2类似,因为TCP无法区别它们两个。它所能表明的仅是未收到对其探测的回复。

 服务器不必担心客户端主机被关闭然后重启的情况(这里指的是操作员执行的正常关闭,而不是主机的崩溃)。当系统被操作员关闭时,所有的应用程序进程(也就是客户端进程)都将被终止,客户端TCP会在连接上发送一个FIN。收到这个FIN后,服务器TCP向服务器进程报告一个文件结束,以允许服务器检测这种状态。

如果客户端没有收到服务端的第二次回应客户端会怎么做?
超时重传机制,在SYN-SEND阶段没有收到回应,会重发第一次握手,但这个重传的时间,和重传的设置是可以手动设置的。
为什么有TIME_WAIT状态?

  1. 为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。
  2. 他还可以防止已失效的报文段。客户端在发送最后一个ACK之后,再经过经过2MSL(报文段最大存货时间的两倍),就可以使本链接持续时间内所产生的所有报文段都从网络中消失。从保证在关闭连接后不会有还在网络中滞留的报文段去骚扰服务器

为什么连接的时候需要3次握手,断开的时候需要4次挥手
TCP建立连接要进行3次握手,而断开连接要进行4次,这是由于TCP的半关闭造成的,因为TCP连接是全双工的(数据可在两个方向上同时传递)
所以进行关闭时每个方向上都要单独进行关闭,这个单方向的关闭就叫半关闭。关闭的方法是一方完成它的数据传输后,就发送一个FIN来向另一方通告将要终止这个方向的连接,收到这个FIN表示这个方向上再没有数据流动,但是收到FIN的那一端仍然可以发送数据。但在发生这个过程以后,服务端可能需要继续发送数据(这个阶段是close_wait),在服务端也确保需要关闭的时候,服务端再发送FIN,尝试去关闭。

TCP四次挥手,最后一次ack没有收到?

  1. 当第四步的A发送的确认报文,B收到时,A会等待2MSL的时间后,连接彻底关闭。(因为B收到了,所以2MSL时间内B不会重发第三步的释放报文)
  2. 当第四步的A发送的确认报文,B没有收到时,B会继续发送第三步的释放报文,A收到后会继续发送第四步的确认报文(此时会重新启动2MSL计时器,重新等待2MSL时间),若在接下来的2MSL的时间内未收到B发送的第三步的释放报文,则意味着B已经收到了A的ack确认报文,连接彻底关闭

MSL 是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

TIME_WART状态过多
在高并发短连接的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态。如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。
解决办法:
编辑/etc/sysctl.conf
设置1. 开启syn cookies,当SYN等待队队列溢出时,启动cookies来处理防范少量SYN攻击
2. 开启重用,允许处于time-wait状态的socket能狗重新用于新的TCP连接
3. 开启快速回收
4. 设置tcp_fin_timeout
CLOSE_WAIT过多

  1. 程序问题: 忘记写关闭close了,不会发出FIN包
  2. 响应太慢或者超时设置过小:如果连接双方不和谐,一方不耐烦直接 timeout,另一方却还在忙于耗时逻辑,就会导致 close 被延后。响应太慢是首要问题,不过换个角度看,也可能是 timeout 设置过小。
  3. BACKLOG 太大:此处的 backlog 不是 syn backlog,而是 accept 的 backlog,如果 backlog 太大的话,设想突然遭遇大访问量的话,即便响应速度不慢,也可能出现来不及消费的情况,导致多余的请求还在队列里就被对方关闭了。

TCP服务端收到syn但是不回复syn ack问题分析
可能是服务端开启了.tcp_tw_recycle,不建议开启这个,开启reuse就好了。
当开启了tcp_tw_recycle选项后,当连接进入TIME_WAIT状态后,会记录对应远端主机最后到达分节的时间戳。如果同样的主机有新的分节到达,且时间戳小于之前记录的时间戳,即视为无效,相应的数据包会被丢弃(rfc1323)
shell> netstat -s | grep timestamp
... packets rejects in established connections because of timestamp
如果服务器身处NAT环境,安全起见,通常要禁止tcp_tw_recycle,至于TIME_WAIT连接过多的问题,可以通过激活tcp_tw_reuse来缓解(只对客户端有作用)。

客户端发送了SYN,但是收不到SYNACK报文段
客户端会重发SYN,重试的次数由tcp_syn_retries参数控制,默认是6次。

net.ipv4.tcp_syn_retries = 6

第一次重试发送在1s,然后超时时间翻倍,2、4、8、16、32 一共6次重试,最后一次重试会等64s。 一共127s。
假如在内网通讯时,可以适当调低重试次数,尽快把错误暴露给应用程序。

客户端发送SYN,服务端接收了SYN,发送了SYN ACK,但是收不到ACK
服务端处于SYN_RCVD状态,处于这个状态下,服务器必须建立一个SYN半连接队列来维护未完成握手信息,当这个队列溢出后,服务器将无法建立新连接。
当SYN半连接队列已满的时候,只能丢弃连接吗? 并不是这样,可以开启syncookies,序号由当前的状态计算出,放在序号字段,然后当客户端返回ACK报文时,取出值进行验证,如果合法,就认为连接建立成功。
linux开启syncookies?
调整tcp_syncookies参数,0: 表示关闭该功能,2:无条件开启该功能,1:仅当SYN半连接队列放不下时,再启用。
由于syncookies仅用于应对syn泛洪攻击,这种方式建立的连接,许多TCP都无法使用,所以设置tcp_syncookies值为1.
tcp_synack_retries 的默认重试次数是5 次,与客户端重发 SYN 类似,它的重试会经历 1、2、4、8、16 秒,最后一次重试后等待 32 秒,若仍然没有收到 ACK,才会关闭连接,故共需要等待 63 秒。

服务器收到 ACK 后连接建立成功,此时,内核会把连接从 SYN 半连接队列中移出,再移入 accept 队列,等待进程调用 accept 函数时把连接取出来。如果进程不能及时地调用 accept 函数,就会造成 accept 队列溢出,最终导致建立好的 TCP 连接被丢弃。
实际上,丢弃连接只是 Linux 的默认行为,我们还可以选择向客户端发送 RST 复位报文,告诉客户端连接已经建立失败。打开这一功能需要将 tcp_abort_on_overflow 参数设置为 1。

参考: https://www.cnblogs.com/peterleee/p/11414413.html
参考:https://blog.csdn.net/u014520797/article/details/118371896?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2aggregatepagefirst_rank_ecpm_v1~rank_v31_ecpm-1-118371896.pc_agg_new_rank&utm_term=%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AAack%E4%B8%A2%E5%A4%B1&spm=1000.2123.3001.4430
参考:https://blog.csdn.net/qiao_qing/article/details/117380137?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_title~default-4.pc_relevant_paycolumn_v3&spm=1001.2101.3001.4242.3&utm_relevant_index=7
参考: https://yuanrengu.com/2020/77eef79f.html

posted @   CrazyShanShan  阅读(402)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
点击右上角即可分享
微信分享提示