精华内容
下载资源
问答
  • fifo半空半满信号解释

    2019-11-19 10:36:17
  • 继续接收新数据,此为触发半满中断的情况,在半满中断回调函数中的操作是将head_ptr到tail_ptr之间的数据写入fifo(图中2号区域),然后移动head_ptr到tail_ptr的位置,模型如下: head_ptr = 上次tail_ptr的位置;...

    写在前面

    串口在各种项目中可谓是太常用了,它也是搞嵌入式必须弄懂的一个通信协议,最近维护了很久的一个项目,设备内另一模块程序更新后出现了不稳定的情况,现象就是某个功能有时候正常有时候不正常,经排查是通信接口上出现了丢包导致的,通信的接口正是用的串口,然后经过多次优化,解决了问题,以此记录一下优化过程。

    软硬件环境

    软件:MDK5、stm32 HAL库

    硬件:项目上主控芯片为stm32f407zet6(调试时使用的stm32f103c8t6),整板外设只用了5个串口,2个硬件定时器。

    库函数接口

    首先看一下用到的库函数接口,不重要的忽略:

    __HAL_DMA_GET_COUNTER获取DMA剩余未接收数据
    HAL_UART_Transmit串口阻塞方式发送函数
    HAL_UART_Transmit_IT串口中断方式发送函数
    HAL_UART_Receive_IT串口中断方式接收数据
    HAL_UART_Transmit_DMA串口DMA方式发送函数
    HAL_UART_Receive_DMA串口DMA方式接收函数
    HAL_UART_TxCpltCallback串口发送完成回调函数
    HAL_UART_RxCpltCallback串口接收完成回调函数
    HAL_UART_RxHalfCpltCallback串口接收过半回调函数

    初始实现方式

    由于项目中是自定义帧格式,而且每个帧长很短,不超过16字节,所以最开始串口接收使用的是DMA单字节接收,当检测接收到一个完整帧时,将收到的帧写入fifo,然后发送一个信号量,被阻塞的任务得到信号量后从fifo读取帧并作相应操作,大致流程如下:
    流程图
    这样的实现方式比较简单,在数据速率比较恒定的情况下是没有问题的,但最近与之通信的模块程序更新后,出现了偶尔突发数据量会很大的情况,这样就可能会丢失数据。

    第一次优化

    知道了问题所在后,进行第一次优化,经过分析有以下方案可以选用:

    1. DMA(或中断)一次接收多字节
    2. DMA加空闲中断

    因为最终选用的第二种方式,所以说说为什么第一种方式不行,原因有以下几点:

    • 对于中断方式来说一次接收多字节并未解决频繁中断的问题,还是会一个字节产生一次中断
    • 单纯的DMA(或中断)必须要接收到指定数量的数据才能完全读走数据,否则数据会一直被缓存无法读取
    • 通信数据帧是不定长的

    空闲中断会在收到一个字节后指定时间内未收到下一个字节时产生,这样的话就可以在产生空闲中断时将收到的数据读走,而不会一直被缓存着,实现代码如下(在使能DMA接收的前提下):

    /* 初始化时使能空闲中断 */
    __HAL_UART_ENABLE_IT(&UartHandle, UART_IT_IDLE);
    
    /* 串口中断处理函数中增加对空闲中断的处理 */
    void USART_IRQHandler(void)
    {
        uint32_t tmp = 0;
    
        if(__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_IDLE))
        {
            /* 清空闲中断标志位 */
            __HAL_UART_CLEAR_IDLEFLAG(&UartHandle);
            /* 停止DMA接收 */
            HAL_UART_DMAStop(&UartHandle);
            /* 得到已接收数据长度 */
            tmp = UartHandle.RxXferSize - __HAL_DMA_GET_COUNTER(UartHandle.hdmarx);
    
            if(0 != tmp)
            {
                /* 存入数据到fifo */
            }
            /* 再次开启DMA接收 */
            HAL_UART_Receive_DMA(&UartHandle, UartHandle.pRxBuffPtr, UartHandle.RxXferSize);
        }
    
        HAL_UART_IRQHandler(&UartHandle);
    }
    

    理论上来说这样的话只要接收缓冲足够大,写入fifo的操作只会发生在产生空闲中断时,应该会大大缓解丢包的情况,但实际测试效果却不明显,并没有很好的处理突发数据的接收。
    分析原因应该是由于DMA接收是不受控的,在处理空闲中断时短暂关闭了DMA的接收,而就是在这个关闭的过程中如果有新数据到来,则只能丢弃(并且会丢弃前面已接收的一部分数据),从而产生丢包。

    第二次优化

    既然知道了是因为短暂关闭DMA导致的,那就一步到位,想办法不关闭DMA就能解决了,stm32的DMA除了满中断还有个半满中断,也就是接收数据过半时产生中断,那就可以在接收数据过半时将已收到的前半数据写入fifo,然后产生满中断时将后半数据写入fifo,此时DMA会自动将写指针移动到接收缓存的头部继续接收,循环这个过程,就不必关闭DMA,大致流程如下:
    流程图
    按照如上的流程进行优化后,的确没发现丢包的情况了,这种和stm32f4支持的多缓冲原理类似,但是多缓冲的话在stm32f103c8t6上面没有这个功能,我是在stm32f103c8t6上面验证的,所以没有使用多缓冲。
    就在我以为这样就结束了时,又发现了新的问题,前面说过DMA要收到指定数量的数据时才会产生中断,那这里还漏了一种情况,那就是如果发送方发送过来的帧长不足以让DMA产生中断,那数据就会被缓存,直到满足条件才能读取,这样的话肯定不行,所以就得空闲中断上场了。

    最后的修改

    现在只需要将空闲中断加入上面那个流程,就能够应对各种情况了,因为是不定长帧,所以空闲中断会在接收的任意期间产生,我用一个全局变量head_ptr来保存缓冲区的读起始偏移tail_ptr来保存缓冲区的读结束偏移(注意tail_ptr是虚拟的,它的值有三种情况,后面会讲到),这样实现一个类循环fifo结构。

    1. 初始状态,head_ptrtail_ptr都指向缓冲区首部,收到一帧数据后,没有新数据到来,触发空闲中断,在空闲中断回调函数中要做的操作就是读走这部分数据(图中1号区域head_ptrtail_ptr之间的数据),并且将head_ptr移动到tail_ptr的位置,模型如下:(只要产生空闲中断,都适用此流程)
      流程1
    head_ptr = 上次tail_ptr的位置;
    /* huart->RxXferSize为接收缓存的总大小,__HAL_DMA_GET_COUNTER获取的是还未接收的数据大小 */
    tail_ptr = huart->RxXferSize - __HAL_DMA_GET_COUNTER(huart->hdmarx);
    
    1. 继续接收新数据,此为触发半满中断的情况,在半满中断回调函数中的操作是将head_ptrtail_ptr之间的数据写入fifo(图中2号区域),然后移动head_ptrtail_ptr的位置,模型如下:
      流程2
    head_ptr = 上次tail_ptr的位置;
    /* huart->RxXferSize为接收缓存的总大小,(huart->RxXferSize & 1)奇数时为1,偶数时为0 */
    tail_ptr = (huart->RxXferSize >> 1) + (huart->RxXferSize & 1);
    
    1. 继续接收数据直到产生满中断,在满中断回调函数中的操作也是将head_ptrtail_ptr之间的数据写入fifo(图中3号区域),然后移动head_ptrtail_ptr的位置,模型如下:
      流程3
    head_ptr = 上次tail_ptr的位置;
    /* huart->RxXferSize为接收缓存的总大小 */
    tail_ptr = huart->RxXferSize;
    

    至此各种情况就都考虑完整了,一个相对可靠的串口接收程序就实现了。

    收发数据模型

    此文源码我放在了我的码云仓库上,有需要的可以自行下载(https://gitee.com/wei513723/stm32-stable-uart-transmit-receive),源码中可以通过宏进行选择使用中断接收DMA接收DMA加空闲中断接收三种方式,使用的程序收发数据模型如下:
    中断收模型
    DMA收模型
    DMA加空闲中断模型
    发送模型

    结尾

    关于源码中这几个宏的配置须知:

    /*是否使能DMA接收*/
    #define UART_USE_DMA_RX 1
    /*是否使能DMA发送*/
    #define UART_USE_DMA_TX 1
    
    #if UART_USE_DMA_RX
        /*是否使能空闲中断*/
        #define UART_USE_IDLE_IT 1
    #endif
    
    /*配置接收缓冲区的大小*/
    #define UART_BUF_SIZE 64
    

    推荐:DMA+空闲中断方式

    • 中断方式:缺点是中断频繁,每收到一个字节都会产生一次中断;必须接收到指定长度数据才能读走数据;适合定长数据帧;用不了DMA才推荐此种方式
    • DMA方式:缺点是必须接收到指定长度数据才能读走数据;适合定长数据帧
    • DMA加空闲中断:最优解

    接收缓冲区大小根据自己需求而定,波特率越高,接收缓冲区大小相对的也应更大,接收fifo和发送fifo的大小也就越大。

    欢迎扫码关注我的微信公众号

    展开全文
  • 关于FIFO之半满信号

    千次阅读 2015-01-12 16:22:30
    这两个星期一直在寻找关于半满信号的定义,今天总算找到一份美国专利,也不知道是什么时候的事了: RAM based FIFO memory half-full detection apparatus and method Document Type and Number: United States ...
    这两个星期一直在寻找关于半满信号的定义,今天总算找到一份美国专利,也不知道是什么时候的事了:
    RAM based FIFO memory half-full detection apparatus and method
    Document Type and Number: United States Patent 5490257
    Link to this Page: httpwww.freepatentsonline.com5490257.html
    Abstract: A method for detecting a half-full condition of a first-in, first-out memory array. The method of the
    invention includes the steps of a) moving a write pointer through the array to write data to alternating rows of
    the memory array; b) moving a read pointer through the array to read data from the alternating rows of the memory
    array in first-in, first-out order; and c) providing a half-full indication when the read pointer and the write
    pointer point to adjacent rows in the memory array. This method eliminates the need to route lines across the array
    to detect a half-full condition, thereby reducing die and power requirements and offering an increase in speed.
    大致意思是分别移出读地址和写地址来做判断,
    对于同步的FIFO,只需判断非空满判断位的那一位,如12bit的信号,12位为空满判断用的,那么11位,可以用做半满判决.
    而对于在异步的FIFO中,应该可以根据读写地址来判断半满信号,verilog程序就比较简单了,采用assign语句吧,免得和读时钟有联系
    展开全文
  • 每日英语,每天进步一点点:前言网上许多博客针对增大 TCP 连接队列和全连接队列的方式如下:增大 TCP 连接队列方式是增大 tcp_max_syn_backlog;增大 TCP 全...
    每日英语,每天进步一点点:

    前言

    网上许多博客针对增大 TCP 半连接队列和全连接队列的方式如下:

    • 增大 TCP 半连接队列方式是增大 tcp_max_syn_backlog;

    • 增大 TCP 全连接队列方式是增大 listen() 函数中的 backlog;

    这里先跟大家说下,上面的方式都是不准确的。

    “你怎么知道不准确?”

    很简单呀,因为我做了实验和看了 TCP 协议栈的内核源码,发现要增大这两个队列长度,不是简简单单增大某一个参数就可以的。

    接下来,就会以实战 + 源码分析,带大家解密 TCP 半连接队列和全连接队列。

    “源码分析,那不是劝退吗?我们搞 Java 的看不懂呀”

    放心,本文的源码分析不会涉及很深的知识,因为都被我删减了,你只需要会条件判断语句 if、左移右移操作符、加减法等基本语法,就可以看懂。

    另外,不仅有源码分析,还会介绍 Linux 排查半连接队列和全连接队列的命令。

    “哦?似乎很有看头,那我姑且看一下吧!”

    行,没有被劝退的小伙伴,值得鼓励,下面这图是本文的提纲:

    本文提纲

    正文

    什么是 TCP 半连接队列和全连接队列?

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

    • 半连接队列,也称 SYN 队列;

    • 全连接队列,也称 accepet 队列;

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

    半连接队列与全连接队列

    不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或返回 RST 包。


    实战 - TCP 全连接队列溢出

    如何知道应用程序的 TCP 全连接队列大小?

    在服务端可以使用 ss 命令,来查看 TCP 全连接队列的情况:

    但需要注意的是 ss 命令获取的 Recv-Q/Send-Q 在「LISTEN 状态」和「非 LISTEN 状态」所表达的含义是不同的。从下面的内核代码可以看出区别:

    在「LISTEN 状态」时,Recv-Q/Send-Q 表示的含义如下:


    • Recv-Q:当前全连接队列的大小,也就是当前已完成三次握手并等待服务端 accept() 的 TCP 连接个数;

    • Send-Q:当前全连接最大队列长度,上面的输出结果说明监听 8088 端口的 TCP 服务进程,最大全连接长度为 128;

    在「非 LISTEN 状态」时,Recv-Q/Send-Q 表示的含义如下:


    • Recv-Q:已收到但未被应用进程读取的字节数;

    • Send-Q:已发送但未收到确认的字节数;

    如何模拟 TCP 全连接队列溢出的场景?

    测试环境

    实验环境:

    • 客户端和服务端都是 CentOs 6.5 ,Linux 内核版本 2.6.32

    • 服务端 IP 192.168.3.200,客户端 IP 192.168.3.100

    • 服务端是 Nginx 服务,端口为 8088

    这里先介绍下 wrk 工具,它是一款简单的 HTTP 压测工具,它能够在单机多核 CPU 的条件下,使用系统自带的高性能 I/O 机制,通过多线程和事件模式,对目标机器产生大量的负载。

    本次模拟实验就使用 wrk 工具来压力测试服务端,发起大量的请求,一起看看服务端 TCP 全连接队列满了会发生什么?有什么观察指标?

    客户端执行 wrk 命令对服务端发起压力测试,并发 3 万个连接:

    在服务端可以使用 ss 命令,来查看当前 TCP 全连接队列的情况:

    其间共执行了两次 ss 命令,从上面的输出结果,可以发现当前 TCP 全连接队列上升到了 129 大小,超过了最大 TCP 全连接队列。

    当超过了 TCP 最大全连接队列,服务端则会丢掉后续进来的 TCP 连接,丢掉的 TCP 连接的个数会被统计起来,我们可以使用 netstat -s 命令来查看:

    上面看到的 41150 times ,表示全连接队列溢出的次数,注意这个是累计值。可以隔几秒钟执行下,如果这个数字一直在增加的话肯定全连接队列偶尔满了。

    从上面的模拟结果,可以得知,当服务端并发处理大量请求时,如果 TCP 全连接队列过小,就容易溢出。发生 TCP 全连接队溢出的时候,后续的请求就会被丢弃,这样就会出现服务端请求数量上不去的现象。

    全连接队列溢出

    全连接队列满了,就只会丢弃连接吗?

    实际上,丢弃连接只是 Linux 的默认行为,我们还可以选择向客户端发送 RST 复位报文,告诉客户端连接已经建立失败。

    tcp_abort_on_overflow 共有两个值分别是 0 和 1,其分别表示:

    • 0 :表示如果全连接队列满了,那么 server 扔掉 client  发过来的 ack ;

    • 1 :表示如果全连接队列满了,那么 server 发送一个 reset 包给 client,表示废掉这个握手过程和这个连接;

    如果要想知道客户端连接不上服务端,是不是服务端 TCP 全连接队列满的原因,那么可以把 tcp_abort_on_overflow 设置为 1,这时如果在客户端异常中可以看到很多 connection reset by peer 的错误,那么就可以证明是由于服务端 TCP 全连接队列溢出的问题。

    通常情况下,应当把 tcp_abort_on_overflow 设置为 0,因为这样更有利于应对突发流量。

    举个例子,当 TCP 全连接队列满导致服务器丢掉了 ACK,与此同时,客户端的连接状态却是 ESTABLISHED,进程就在建立好的连接上发送请求。只要服务器没有为请求回复 ACK,请求就会被多次重发。如果服务器上的进程只是短暂的繁忙造成 accept 队列满,那么当 TCP 全连接队列有空位时,再次接收到的请求报文由于含有 ACK,仍然会触发服务器端成功建立连接。

    所以,tcp_abort_on_overflow 设为 0 可以提高连接建立的成功率,只有你非常肯定 TCP 全连接队列会长期溢出时,才能设置为 1 以尽快通知客户端。

    如何增大 TCP 全连接队列呢?

    是的,当发现 TCP 全连接队列发生溢出的时候,我们就需要增大该队列的大小,以便可以应对客户端大量的请求。

    TCP 全连接队列足最大值取决于 somaxconn 和 backlog 之间的最小值,也就是 min(somaxconn, backlog)。从下面的 Linux 内核代码可以得知:


    • somaxconn 是 Linux 内核的参数,默认值是 128,可以通过 /proc/sys/net/core/somaxconn 来设置其值;

    • backloglisten(int sockfd, int backlog) 函数中的 backlog 大小,Nginx 默认值是 511,可以通过修改配置文件设置其长度;

    前面模拟测试中,我的测试环境:

    • somaxconn 是默认值 128;

    • Nginx 的 backlog 是默认值 511

    所以测试环境的 TCP 全连接队列最大值为 min(128, 511),也就是 128,可以执行 ss 命令查看:

    现在我们重新压测,把 TCP 全连接队列搞大,把 somaxconn 设置成 5000:

    接着把 Nginx 的 backlog 也同样设置成 5000:

    最后要重启 Nginx 服务,因为只有重新调用 listen() 函数, TCP 全连接队列才会重新初始化。

    重启完后 Nginx 服务后,服务端执行 ss 命令,查看 TCP 全连接队列大小:

    从执行结果,可以发现 TCP 全连接最大值为 5000。

    增大 TCP 全连接队列后,继续压测

    客户端同样以 3 万个连接并发发送请求给服务端:

    服务端执行 ss 命令,查看 TCP 全连接队列使用情况:

    从上面的执行结果,可以发现全连接队列使用增长的很快,但是一直都没有超过最大值,所以就不会溢出,那么 netstat -s 就不会有 TCP 全连接队列溢出个数的显示:

    说明 TCP 全连接队列最大值从 128 增大到 5000 后,服务端抗住了 3 万连接并发请求,也没有发生全连接队列溢出的现象了。

    如果持续不断地有连接因为 TCP 全连接队列溢出被丢弃,就应该调大 backlog 以及 somaxconn 参数。


    实战 - TCP 半连接队列溢出

    如何查看 TCP 半连接队列长度?

    很遗憾,TCP 半连接队列长度的长度,没有像全连接队列那样可以用 ss 命令查看。

    但是我们可以抓住 TCP 半连接的特点,就是服务端处于 SYN_RECV 状态的 TCP 连接,就是在 TCP 半连接队列。

    于是,我们可以使用如下命令计算当前 TCP 半连接队列长度:


    如何模拟 TCP 半连接队列溢出场景?

    模拟 TCP 半连接溢出场景不难,实际上就是对服务端一直发送 TCP SYN 包,但是不回第三次握手 ACK,这样就会使得服务端有大量的处于 SYN_RECV 状态的 TCP 连接。

    这其实也就是所谓的 SYN 洪泛、SYN 攻击、DDos 攻击。

    测试环境

    实验环境:

    • 客户端和服务端都是 CentOs 6.5 ,Linux 内核版本 2.6.32

    • 服务端 IP 192.168.3.200,客户端 IP 192.168.3.100

    • 服务端是 Nginx 服务,端口为 8088

    注意:本次模拟实验是没有开启 tcp_syncookies,关于 tcp_syncookies 的作用,后续会说明。

    本次实验使用 hping3 工具模拟 SYN 攻击:

    当服务端受到 SYN 攻击后,连接服务端 ssh 就会断开了,无法再连上。只能在服务端主机上执行查看当前 TCP 半连接队列大小:

    同时,还可以通过 netstat -s 观察半连接队列溢出的情况:

    上面输出的数值是累计值,表示共有多少个 TCP 连接因为半连接队列溢出而被丢弃。隔几秒执行几次,如果有上升的趋势,说明当前存在半连接队列溢出的现象

    大部分人都说 tcp_max_syn_backlog 是指定半连接队列的大小,是真的吗?

    很遗憾,半连接队列的大小并不单单只跟 tcp_max_syn_backlog 有关系。

    上面模拟 SYN 攻击场景时,服务端的 tcp_max_syn_backlog 的默认值如下:

    但是在测试的时候发现,服务端最多只有 256 个半连接队列,而不是 512,所以半连接队列的最大长度不一定由 tcp_max_syn_backlog 值决定的

    接下来,走进 Linux 内核的源码,来分析 TCP 半连接队列的最大值是如何决定的。

    TCP 第一次握手(收到 SYN 包)的 Linux 内核代码如下,其中缩减了大量的代码,只需要重点关注 TCP 半连接队列溢出的处理逻辑:

    从源码中,我可以得出共有三个条件因队列长度的关系而被丢弃的:


    1. 如果半连接队列满了,并且没有开启 tcp_syncookies,则会丢弃;

    2. 若全连接队列满了,且没有重传 SYN+ACK 包的连接请求多于 1 个,则会丢弃;

    3. 如果没有开启 tcp_syncookies,并且 max_syn_backlog 减去 当前半连接队列长度小于 (max_syn_backlog >> 2),则会丢弃;

    关于 tcp_syncookies 的设置,后面在详细说明,可以先给大家说一下,开启 tcp_syncookies 是缓解 SYN 攻击其中一个手段。

    接下来,我们继续跟一下检测半连接队列是否满的函数 inet_csk_reqsk_queue_is_full 和 检测全连接队列是否满的函数 sk_acceptq_is_full :

    从上面源码,可以得知:

    • 连接队列的最大值是 sk_max_ack_backlog 变量,sk_max_ack_backlog 实际上是在 listen() 源码里指定的,也就是 min(somaxconn, backlog)

    • 连接队列的最大值是 max_qlen_log 变量,max_qlen_log 是在哪指定的呢?现在暂时还不知道,我们继续跟进;

    我们继续跟进代码,看一下是哪里初始化了半连接队列的最大值 max_qlen_log:

    从上面的代码中,我们可以算出 max_qlen_log 是 8,于是代入到 检测半连接队列是否满的函数 reqsk_queue_is_full :

    也就是 qlen >> 8 什么时候为 1 就代表半连接队列满了。这计算这不难,很明显是当 qlen 为 256 时,256 >> 8 = 1

    至此,总算知道为什么上面模拟测试 SYN 攻击的时候,服务端处于 SYN_RECV 连接最大只有 256 个。

    可见,半连接队列最大值不是单单由 max_syn_backlog 决定,还跟 somaxconn 和 backlog 有关系。

    在 Linux 2.6.32 内核版本,它们之间的关系,总体可以概况为:


    1. 当 max_syn_backlog > min(somaxconn, backlog) 时, 半连接队列最大值 max_qlen_log = min(somaxconn, backlog) * 2;

    2. 当 max_syn_backlog < min(somaxconn, backlog) 时, 半连接队列最大值 max_qlen_log = max_syn_backlog * 2;

    半连接队列最大值 max_qlen_log 就表示服务端处于 SYN_REVC 状态的最大个数吗?

    依然很遗憾,并不是。

    max_qlen_log 是理论半连接队列最大值,并不一定代表服务端处于 SYN_REVC 状态的最大个数。

    在前面我们在分析 TCP 第一次握手(收到 SYN 包)时会被丢弃的三种条件:

    1. 如果半连接队列满了,并且没有开启 tcp_syncookies,则会丢弃;

    2. 若全连接队列满了,且没有重传 SYN+ACK 包的连接请求多于 1 个,则会丢弃;

    3. 如果没有开启 tcp_syncookies,并且 max_syn_backlog 减去 当前半连接队列长度小于 (max_syn_backlog >> 2),则会丢弃;

    假设条件 1 当前半连接队列的长度 「没有超过」理论的半连接队列最大值  max_qlen_log,那么如果条件 3 成立,则依然会丢弃 SYN 包,也就会使得服务端处于 SYN_REVC 状态的最大个数不会是理论值 max_qlen_log。

    似乎很难理解,我们继续接着做实验,实验见真知。

    服务端环境如下:

    配置完后,服务端要重启 Nginx,因为全连接队列最大和半连接队列最大值是在 listen() 函数初始化。

    根据前面的源码分析,我们可以计算出半连接队列 max_qlen_log 的最大值为 256:

    客户端执行 hping3 发起 SYN 攻击:

    服务端执行如下命令,查看处于 SYN_RECV 状态的最大个数:

    可以发现,服务端处于 SYN_RECV 状态的最大个数并不是 max_qlen_log 变量的值。

    这就是前面所说的原因:如果当前半连接队列的长度 「没有超过」理论半连接队列最大值  max_qlen_log,那么如果条件 3 成立,则依然会丢弃 SYN 包,也就会使得服务端处于 SYN_REVC 状态的最大个数不会是理论值 max_qlen_log。

    我们来分析一波条件 3 :

    从上面的分析,可以得知如果触发「当前半连接队列长度 > 192」条件,TCP 第一次握手的 SYN 包是会被丢弃的。

    在前面我们测试的结果,服务端处于 SYN_RECV 状态的最大个数是 193,正好是触发了条件 3,所以处于 SYN_RECV 状态的个数还没到「理论半连接队列最大值 256」,就已经把 SYN 包丢弃了。

    所以,服务端处于 SYN_RECV 状态的最大个数分为如下两种情况:

    1. 如果「当前半连接队列」没超过「理论半连接队列最大值」,但是超过 max_syn_backlog - (max_syn_backlog >> 2),那么处于 SYN_RECV 状态的最大个数就是 max_syn_backlog - (max_syn_backlog >> 2);

    2. 如果「当前半连接队列」超过「理论半连接队列最大值」,那么处于 SYN_RECV 状态的最大个数就是「理论半连接队列最大值」;

    每个 Linux 内核版本「理论」半连接最大值计算方式会不同。

    在上面我们是针对 Linux 2.6.32 版本分析的「理论」半连接最大值的算法,可能每个版本有些不同。

    比如在 Linux 5.0.0 的时候,「理论」半连接最大值就是全连接队列最大值,但依然还是有队列溢出的三个条件:


    如果 SYN 半连接队列已满,只能丢弃连接吗?

    并不是这样,开启 syncookies 功能就可以在不使用 SYN 半连接队列的情况下成功建立连接,在前面我们源码分析也可以看到这点,当开启了  syncookies 功能就不会丢弃连接。

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

    开启 syncookies 功能

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

    • 0 值,表示关闭该功能;

    • 1 值,表示仅当 SYN 半连接队列放不下时,再启用它;

    • 2 值,表示无条件开启功能;

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


    如何防御 SYN 攻击?

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

    • 增大半连接队列;

    • 开启 tcp_syncookies 功能

    • 减少 SYN+ACK 重传次数

    方式一:增大半连接队列

    在前面源码和实验中,得知要想增大半连接队列,我们得知不能只单纯增大 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_REVC 状态的 TCP 连接,处于这个状态的 TCP 会重传 SYN+ACK ,当重传超过次数达到上限后,就会断开连接。

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


    巨人的肩膀

    [1] 系统性能调优必知必会.陶辉.极客时间.

    [2] https://blog.cloudflare.com/syn-packet-handling-in-the-wild

    ---

    彩蛋:扫描下方二维码关注后回复 000 获取 HTTP、操作系统、计算机网络等相关学习资料。

    
    
    展开全文
  • 行业分类-机械工程-无保持架球轴承自动填球装置及其填球方法.zip
  • 绝对干货满满。 本书参考资料书籍: 《JavaScript权威指南(第6)》 《JavaScript高级程序设计》 《你不知道的JavaScript(上、中卷)》 《Effective JavaScript:编写高质量JavaScript代码的68个有效方法》
  • 在进行client不断的对server端进行connect的过程中...Linux内核协议栈为一个tcp连接管理使用两个队列,一个是链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求),一个是全连接队列(accpetd队列)(用来保存处
  • TCP三次握手详解-深入浅出(有图实例演示)

    万次阅读 多人点赞 2018-08-08 21:13:48
    3.1.2 连接队列(syn queue)已 服务器不会将该连接的状态变为SYN_RCVD,且将该连接丢弃(SYN flood攻击就是利用这个原理, 对于SYN foold攻击,应对方法之一是使syncookies生效,将其值置1即可,路径/proc/sys/...
  • 较为详细的MUSIC算法原理及MATLAB实现

    万次阅读 多人点赞 2019-09-11 10:58:54
    正定的,它有D个正特征值和 M − D M-D M − D 个零特征值。 再考虑有噪声存在的情况 R x = A R s A H + σ 2 I R_x=AR_sA^H+\sigma^2I R x ​ = A R s ​ A H + σ 2 I 由于 σ 2 > 0 \sigma^2>0 σ 2 > ...
  • 补仓:在下跌过程中买入仓:资金的全部买入。 仓:资金的一半买入。 清仓:股票的全部卖出。 空仓:不持有任何股票。 减仓:卖出一部分。守仓:就是持有不卖出。 倒仓:日内T+0交易。 清仓、就是卖光手...
  • 文章目录可逆表示正定可逆实对称表示正定实对称表示正定正定表示正定正定表示正定正定与主子式一些重要的xiao结论列秩表示正定行秩表示正定上三角表示正定 可逆表示正定 实对称A为正定⇔\Leftrightarrow...
  • 文章目录可逆表示正定证明可逆实对称表示正定证明实对称表示正定正定表示正定证明正定表示正定正定与主子式证明一些重要的xiao结论列秩表示正定证明行秩表示正定证明上三角表示正定证明 可逆表示正定 实...
  • 第三步的时候server收到client的ack,如果这时全连接队列没,那么从连接队列拿出相关信息放入到全连接队列中,否则按tcp_abort_on_overflow指示的执行。  这时如果全连接队列了并且tcp_abort_on_overflow是0...
  • C#基础教程-c#实例教程,适合初学者

    万次阅读 多人点赞 2016-08-22 11:13:24
    C#基础教程-c#实例教程,适合初学者。 第一章 C#语言基础 本章介绍C#语言的基础知识,希望具有C语言的读者能够基本掌握C#语言,并以此为基础,能够进一步学习用C#语言编写window应用程序和Web应用程序。...
  • 简述TCP的三次握手和四次挥手过程

    万次阅读 多人点赞 2018-05-11 10:28:40
    并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将产时间占用未连接队列,导致正常的SYN请求因为队列而被丢弃,...
  • 半啤酒 javascript中神经网络如何学习预测的示例取决于根据创建的用户决策,啤酒杯是半满还是半空
  • BaseNavViewController *navVc = [[BaseNavViewController alloc] initWithRootViewController:loginVc]; //加上下面这句=0的代码,登录页就顶到最上面去了 navVc.modalPresentationStyle = 0;...
  • TCP的连接队列和全连接队列

    千次阅读 2019-03-26 10:26:12
    攻击就是针对连接队列的,攻击方不停地建连接,但是建连接的时候只做第一步,第二步中攻击方收到server的syn+ack后故意扔掉什么也不做,server需要一个超时时间把这个连接断开,会隔时重发ack+syn,尝试几次后还没...
  • Linux日志磁盘了,怎么办?

    千次阅读 2020-04-10 09:11:16
    Linux日志磁盘了,怎么办? Linux 如何使用echo指令向文件写入内容 echo "Raspberry" > test.txt echo "Intel Galileo" >> test.log 找到那些日志文件比较大? 搜索当前目录下,超过800M...
  • Vue根节点div不占屏幕

    千次阅读 2019-08-22 17:23:06
    /*设置占屏幕*/ { background-color : red ; width : 100% ; height : 100px ; margin : 0 ; } .home_title { width : 100% ; height : 60px ; background-color : #0593d3 ;...
  • 问题背景: 收到一台环境ubuntu,df 查看如下: root@ubuntu-110:/mnt# df -h Filesystem Size Used Avail Use% Mounted on udev 63G 0 63G 0% /dev tmpfs 13G 12M 13G 1% /run /de...
  • 根据干湿地区的特点,介绍并分析了流域综合产流模型,将它与蓄产流模型在模型的建立上进行了分析和比较;并把这个模型应用到干湿的文峪河流域,使之与蓄产流模型的计算结果进行比较,结果表明:流域综合产流...
  • TCP连接队列和全连接队列 1 简单的TCP建连流程 先来张图,如下: (1)client端使用connect()向server端发起连接请求(发送syn包),此时client端的TCP的状态为SYN_SENT (2)server端在收到SYN包后,将TCP相关信息...
  • 格雷码下的 fifo空判断

    千次阅读 2019-07-19 20:46:02
    使用gray码进行对比,如何判断“空”与“”  使用gray码解决了一个问题(降低亚稳态),但同时也带来另一个问题,即在格雷码域如何判断空与。  对于“空”的判断依然依据二者完全相等(包括MSB);  而对于...
  • 《单片机原理及应用》复习提纲

    万次阅读 多人点赞 2015-12-14 10:42:02
    在进行加或减运算时,如果操作结果的低字节向高字节产生进位或借位时,将由硬件置“1”,否则清“0”。   OV(PSW.2)   ——溢出标志: 在有符号数加减运算中,若有异常结果,OV硬件置1,否则硬件...
  • 本Demo自定义了一个基于VideoView的视频播放组合控件,包括控制条样式的自定义,屏和全屏播放功能的添加,全屏播放隐藏状态栏和虚拟键,屏播放展示状态栏和虚拟键
  • 电脑窗口全屏切换快捷键

    万次阅读 2019-03-02 18:27:49
    对于经常打开多个应用的时候非常给力 window系统 win + ←:最大化窗口到左侧的屏幕上 win + →:最大化窗口到右侧的屏幕上 win+ ↑:最大化窗口 win+ ↓:最小化窗口 重复相同操作还原之前的...ctrl + w...
  • FIFO阈值如何设置?将阈值与FIFO深度的关系?

    千次阅读 多人点赞 2018-06-03 12:32:08
    FIFO阈值如何设置1、 什么是FIFO?FIFO(first in firstout)是一种用寄存器reg或者RAM实现的存储结构,常用于存储数据通道中的...2、 什么是FIFO阈值FIFO阈值包含将阈值afull_cnt和将空阈值aempty_cnt,当FIF...
  • DMA HAL_DMA_Start_IT...将传输完成中断屏蔽,如下: if(hdma-&gt;XferHalfCpltCallback != NULL) { // hdma-&gt;Instance-&gt;CR |= DMA_IT_HT; // } 当然也可以修改XferHalfCpltCallback 的值,但...
  • 作为营销人,出于职业习惯,对新鲜事物总有一种探索欲。以下结合自己所见所闻以及查找到的资料做...陈翔六点是抖音技巧搞笑段子运营中较为成功的一个,除了这种团队精心打造的剧情。还有个人脱口秀、街上、地铁撩...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 121,607
精华内容 48,642
关键字:

半版满版