解决eCos系统的lwIP存在的bug,该bug导致TCP重发失效,最终导致TCP拒绝服务 |
您所在的位置:网站首页 › tcp发包和重发 › 解决eCos系统的lwIP存在的bug,该bug导致TCP重发失效,最终导致TCP拒绝服务 |
问题描述
跑 eCos + lwIP 的设备作为 TCP 服务器,在网络不繁忙不丢包的情况下,一切正常,在网络繁忙会出现丢包的情况下,重试几次后 TCP 拒绝服务(对 SYN 包都不会有任何响应, ping 功能可能正常也可能无响应),其它任务正常。能够承受的重试次数和选项 lwIP networking stack >> Protocols >> TCP support >> Sender pbufs (CYGNUM_LWIP_TCP_SND_QUEUELEN) 有关(据我推测,没有实测)。 抓到的数据包如下: 看出来了吗? 客户端没有收到服务器的响应,服务器也没有重发!这不是 TCP 啊!TCP 是会重发的,对方没有收到就会重发,但是这里就是没有重发。 解决办法原因是定时器资源不够,TCP 没有申请到定时器,没法处理任何和超时有关的事物。先给出解决办法,问题分析在后面。有两个办法可以解决这个问题,二选一,第二方法更彻底。 一、在配置工具中增加一个用户定时器将上图所示的 Simultaneous active timeouts by user modules 选项值加1。这样 lwIP 就有足够的定时器可用了,TCP 所需要的定时器也能正确分配到。 二、修改 lwip_net.cdl这个办法更加地彻底,从根本上解决了缺少定时器的问题。 关于超时定时器的配置部分(lwip_net.cdl:1342): cdl_option CYGNUM_LWIP_MEMP_NUM_CORE_SYS_TIMEOUT { display "Simultaneous active timeouts by core modules" flavor data calculated { CYGPKG_LWIP_TCP + CYGFUN_LWIP_IP_REASSEMBLY + CYGPKG_LWIP_ARP + (CYGPKG_LWIP_DHCP * 2) + CYGPKG_LWIP_AUTOIP + CYGPKG_LWIP_IGMP + CYGPKG_LWIP_DNS + CYGPKG_LWIP_PPP } description " The number of simulateously active timeouts used by the lwIP core modules." }从这个选项的脚本可以看出,lwIP 所需要的定时器数量是根据所选择的功能自动算出来的,但是少了 lwip_select 所需要的那个超时定时器。 将该脚本改成如下: cdl_option CYGNUM_LWIP_MEMP_NUM_CORE_SYS_TIMEOUT { display "Simultaneous active timeouts by core modules" flavor data calculated { CYGPKG_LWIP_TCP + CYGFUN_LWIP_IP_REASSEMBLY + CYGPKG_LWIP_ARP + (CYGPKG_LWIP_DHCP * 2) + CYGPKG_LWIP_AUTOIP + CYGPKG_LWIP_IGMP + CYGPKG_LWIP_DNS + CYGPKG_LWIP_PPP + CYGPKG_LWIP_SOCKET_API } description " The number of simulateously active timeouts used by the lwIP core modules." }即在选项的计算式中加入 + CYGPKG_LWIP_SOCKET_API 。lwip_select 是 CYGPKG_LWIP_SOCKET_API 组件提供的一个函数。 修复后抓取的包: lwIP 有个很好的特性,那就是 Traffic statistics,打开这个特性,lwIP 可以统计数据包的收发以及内存的使用情况。在资源足够的情况下,强烈建议打开该选项。在 eCos 中,这个选项是 lwIP networking stack >> Traffic statistics (CYGPKG_LWIP_STATS)。 调试是查找问题的好帮手,一定要留调试接口,一定要掌握调试这么手艺。 将设备连接调试器,连续运行直到故障重现,暂停程序执行,这个时候就可以检查 statistics 了,查看 lwip_tcpip >> current >> src >> core >> stats.c 文件中的 lwip_stats 结构。 检查的结果就是 SYS_TIMEOUT 类型的 LWIP_MEMPOOL 发生了错误,只分配了 6 个,但是实际最多需要 7 个。 给 lwIP 多增加几个定时器资源,再执行就不会出现故障了。 因此可以肯定 TCP 处理超时重发的定时器分配失败了,也就没有 TCP 的超时处理了,也就不会重发了。lwIP 所需要的定时器,大部分都在 tcpip_thread 的开始处申请完了,TCP 的定时器是在有需要的时候调用 tcp_timer_needed 函数申请的。在 eCos 中,lwIP 所需要的定时器个数是自动计算的: CYGNUM_LWIP_MEMP_NUM_CORE_SYS_TIMEOUT = CYGPKG_LWIP_TCP + CYGFUN_LWIP_IP_REASSEMBLY + CYGPKG_LWIP_ARP + (CYGPKG_LWIP_DHCP * 2) + CYGPKG_LWIP_AUTOIP + CYGPKG_LWIP_IGMP + CYGPKG_LWIP_DNS + CYGPKG_LWIP_PPP经检查发现,除了上面引用到的特性会使用定时器外,socket 的 lwip_select 函数也会使用到定时器,因此这里算式少算了一个定时器,问题就出在这。 lwip_select 函数通过调用 sys_sem_wait_timeout 函数间接地使用到了定时器。 我们的程序使用了 lwip_select 函数,而且还带超时,所以触发了这个bug。 缺少 TCP 定时器引起TCP拒绝服务的原因: TCP 发送一个数据包后不会立即释放该数据包的内存资源,因为该数据包可能还需要重发。网络不丢包的情况下,对方能正确地接收数据包,并返回 ACK 包,lwip 在接收到 ACK 包后释放发送包的内存资源。所以在不丢包的情况下,不会有问题。网络丢包的情况下,超时定时器会重发没有被 ACK 的数据包,直到接收到 ACK 或发送超时再释放发送包的内存资源,超时定时器未被开启的情况下,发送的数据包丢失以后,因为没有了超时重发,对方永远都接收不到数据包,lwip 也不会接收到该数据包的ACK而释放资源,同样也不会因为发送超时而释放资源(因为定时器没开启,所有超时机制都失效了),因此这个数据包将永远占据着资源而不释放。发生多次这样的情况以后,内存资源被耗尽,lwip 已经申请不到内存来处理新的数据包了,开始出现拒绝服务。lwip 使用的内存资源有多种,看哪种资源首先被耗尽,可能会导致ping不通的情况。还有更快捷地发现问题的途径:启用断言! lwIP 的断言设计也是很完善的,完全可以使用断言来捕获资源不够的问题,查看定时器申请的代码(sys_timeout函数): timeout = memp_malloc(MEMP_SYS_TIMEOUT); if (timeout == NULL) { LWIP_ASSERT("sys_timeout: timeout != NULL", timeout != NULL); return; }如果启用了断言,当资源分配失败,这里的 LWIP_ASSERT 就被触发了! 但是断言有一点不好,非调试状态下它会引起系统死机或复位,而不仅仅是网络功能失效,对产品的口碑而言,网络功能失效比死机或复位要好一些。 要打开 eCos 中 lwIP 的断言,那么就要打开整个 eCos 的断言。 嗯 …… 下次找问题,先打开断言! |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |