移动 > 腾讯 > qq

SYN 报文什么时候情况下会被丢弃?

66人参与 2024-08-06 qq

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【doug lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说spring不支持多线程事务,你给我抽他!】

终结b站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解mesi协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

之前有个读者在秋招面试的时候,被问了这么一个问题:syn 报文什么时候情况下会被丢弃?

好家伙,现在面试都问那么细节了吗?

不过话说回来,这个问题跟工作上也是有关系的,因为我就在工作中碰到这么奇怪的时候,客户端向服务端发起了连接,但是连接并没有建立起来,通过抓包分析发现,服务端是收到 syn 报文了,但是并没有回复 syn+ack(tcp 第二次握手),说明 syn 报文被服务端忽略了,然后客户端就一直在超时重传 syn 报文,直到达到最大的重传次数。

接下来,我就给出我遇到过 syn 报文被丢弃的两种场景:

# 坑爹的 tcp_tw_recycle

tcp 四次挥手过程中,主动断开连接方会有一个 time_wait 的状态,这个状态会持续 2 msl 后才会转变为 closed 状态。

在 linux 操作系统下,time_wait 状态的持续时间是 60 秒,这意味着这 60 秒内,客户端一直会占用着这个端口。要知道,端口资源也是有限的,一般可以开启的端口为 32768~61000 ,也可以通过如下参数设置指定范围:

     net.ipv4.ip_local_port_range

如果客户端(发起连接方)的 time_wait 状态过多 ,占满了所有端口资源,那么就无法对「目的 ip+ 目的 port」都一样的服务器发起连接了,但是被使用的端口,还是可以继续对另外一个服务器发起连接的。

因此,客户端(发起连接方)都是和「目的 ip+ 目的 port 」都一样的服务器建立连接的话,当客户端的 time_wait 状态连接过多的话,就会受端口资源限制,如果占满了所有端口资源,那么就无法再跟「目的 ip+ 目的 port」都一样的服务器建立连接了。

不过,即使是在这种场景下,只要连接的是不同的服务器,端口是可以重复使用的,所以客户端还是可以向其他服务器发起连接的,这是因为内核在定位一个连接的时候,是通过四元组(源ip、源端口、目的ip、目的端口)信息来定位的,并不会因为客户端的端口一样,而导致连接冲突。

但是 time_wait 状态也不是摆设作用,它的作用有两个:

不过,linux 操作系统提供了两个可以系统参数来快速回收处于 time_wait 状态的连接,这两个参数都是默认关闭的:

要使得这两个选项生效,有一个前提条件,就是要打开 tcp 时间戳,即 net.ipv4.tcp_timestamps=1(默认即为 1))。

tcp_tw_recycle 在使用了 nat 的网络下是不安全的!

对于服务器来说,如果同时开启了recycle 和 timestamps 选项,则会开启一种称之为「 per-host 的 paws 机制」。

tcp_timestamps 选项开启之后, paws 机制会自动开启,它的作用是防止 tcp 包中的序列号发生绕回。

正常来说每个 tcp 包都会有自己唯一的 seq,出现 tcp 数据包重传的时候会复用 seq 号,这样接收方能通过 seq 号来判断数据包的唯一性,也能在重复收到某个数据包的时候判断数据是不是重传的。 但是 tcp 这个 seq 号是有限的,一共 32 bit,seq 开始是递增,溢出之后从 0 开始再次依次递增 。

所以当 seq 号出现溢出后单纯通过 seq 号无法标识数据包的唯一性,某个数据包延迟或因重发而延迟时可能导致连接传递的数据被破坏,比如:

上图 a 数据包出现了重传,并在 seq 号耗尽再次从 a 递增时,第一次发的 a 数据包延迟到达了 server,这种情况下如果没有别的机制来保证,server 会认为延迟到达的 a 数据包是正确的而接收,反而是将正常的第三次发的 seq 为 a 的数据包丢弃,造成数据传输错误。

paws 就是为了避免这个问题而产生的,在开启 tcp_timestamps 选项情况下,一台机器发的所有 tcp 包都会带上发送时的时间戳,paws 要求连接双方维护最近一次收到的数据包的时间戳(recent tsval),每收到一个新数据包都会读取数据包中的时间戳值跟 recent tsval 值做比较, 如果发现收到的数据包中时间戳不是递增的,则表示该数据包是过期的,就会直接丢弃这个数据包 。

对于上面图中的例子有了 paws 机制就能做到在收到 delay 到达的 a 号数据包时,识别出它是个过期的数据包而将其丢掉。

前面我提到,开启了 recycle 和 timestamps 选项,就会开启一种叫 per-host 的 paws 机制。 per-host 是对「对端 ip 做 paws 检查」 ,而非对「ip + 端口」四元组做 paws 检查。

但是如果客户端网络环境是用了 nat 网关,那么客户端环境的每一台机器通过 nat 网关后,都会是相同的 ip 地址,在服务端看来,就好像只是在跟一个客户端打交道一样,无法区分出来。

per-host paws 机制利用tcp option里的 timestamp 字段的增长来判断串扰数据,而 timestamp 是根据客户端各自的 cpu tick 得出的值。

当客户端 a 通过 nat 网关和服务器建立 tcp 连接,然后服务器主动关闭并且快速回收 time-wait 状态的连接后, 客户端 b 也通过 nat 网关和服务器建立 tcp 连接,注意客户端 a 和 客户端 b 因为经过相同的 nat 网关,所以是用相同的 ip 地址与服务端建立 tcp 连接,如果客户端 b 的 timestamp 比 客户端 a 的 timestamp 小,那么由于服务端的 per-host 的 paws 机制的作用,服务端就会丢弃客户端主机 b 发来的 syn 包 。

因此,tcp_tw_recycle 在使用了 nat 的网络下是存在问题的,如果它是对 tcp 四元组做 paws 检查,而不是对「相同的 ip 做 paws 检查」,那么就不会存在这个问题了。

网上很多博客都说开启 tcp_tw_recycle 参数来优化 tcp,我信你个鬼,糟老头坏的很!

tcp_tw_recycle 在 linux 4.12 版本后,直接取消了这一参数。

# accpet 队列满了

在 tcp 三次握手的时候,linux 内核会维护两个队列,分别是:

服务端收到客户端发起的 syn 请求后, 内核会把该连接存储到半连接队列 ,并向客户端响应 syn+ack,接着客户端会返回 ack,服务端收到第三次握手的 ack 后, 内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来。

# 半连接队列满了

当服务器造成syn攻击,就有可能导致 tcp 半连接队列满了,这时后面来的 syn 包都会被丢弃 。

但是, 如果开启了syncookies 功能,即使半连接队列满了,也不会丢弃syn 包 。

syncookies 是这么做的:服务器根据当前状态计算出一个值,放在己方发出的 syn+ack 报文中发出,当客户端返回 ack 报文时,取出该值验证,如果合法,就认为连接建立成功,如下图所示。

syncookies 参数主要有以下三个值:

那么在应对 syn 攻击时,只需要设置为 1 即可:

这里给出几种防御 syn 攻击的方法:

方式一:增大半连接队列

要想增大半连接队列,我们得知不能只单纯增大 tcp_max_syn_backlog 的值,还需一同增大 somaxconn 和 backlog,也就是增大全连接队列 。否则,只单纯增大 tcp_max_syn_backlog 是无效的。

增大 tcp_max_syn_backlog 和 somaxconn 的方法是修改 linux 内核参数:

增大 backlog 的方式,每个 web 服务都不同,比如 nginx 增大 backlog 的方法如下:

最后,改变了如上这些参数后,要重启 nginx 服务,因为半连接队列和全连接队列都是在 listen() 初始化的。

方式二:开启 tcp_syncookies 功能

开启 tcp_syncookies 功能的方式也很简单,修改 linux 内核参数:

方式三:减少 syn+ack 重传次数

当服务端受到 syn 攻击时,就会有大量处于 syn_recv 状态的 tcp 连接,处于这个状态的 tcp 会重传 syn+ack ,当重传超过次数达到上限后,就会断开连接。

那么针对 syn 攻击的场景,我们可以减少 syn+ack 的重传次数,以加快处于 syn_recv 状态的 tcp 连接断开。

# 全连接队列满了

在服务端并发处理大量请求时,如果 tcp accpet 队列过小,或者应用程序调用 accept() 不及时,就会造成 accpet 队列满了 ,这时后续的连接就会被丢弃,这样就会出现服务端请求数量上不去的现象。

我们可以通过 ss 命令来看 accpet 队列大小,在「listen 状态」时,recv-q/send-q 表示的含义如下:

如果 recv-q 的大小超过 send-q,就说明发生了 accpet 队列满的情况。

要解决这个问题,我们可以:

 

(0)
打赏 微信扫一扫 微信扫一扫

您想发表意见!!点此发布评论

推荐阅读

AI大模型时代下,如何识别AI写作、AI绘画、AI文案?

08-04

AI绘画动漫转真人详细教程

08-04

腾讯 QQ 截图工具升级:支持自动打码、长截图、水印等,桌面端已上线

08-04

6 旬老人 5 个 QQ 好友 4 个是骗子,警方:应提醒长辈防止电诈

08-04

2023年(2024届)计算机保研——中科院计算所、人大高瓴、清华贵系、复旦大数据等

08-02

淘客qq群怎么引流?如何赚取佣金?

10-03

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论