从Linux 2.6.8内核的一个TSO/NAT bug引出的网络问题排查观点(附一个skb的优化点)

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介:

梦中没有错与对,梦中没有恨和悔...最好闭上你的嘴,这样才算可爱...我不会说:这不公道,我不能接受。我会用朴素的文字记录点点滴滴,早上4点多起来,一气呵成近期的收获与评价,愤怒与忏悔。


四年多前的一个往事

大约在2010年的时候,我排查了一个问题。问题描述如下:


服务端:Linux Kernel 2.6.8/192.168.188.100
客户端:Windows XP/192.168.40.34
业务流程(简化版):
1.客户端向服务端发起SSL连接
2.传输数据

现象:SSL握手的时候,服务端发送Certificate特别慢。


分析:
具体思路,也就是当时怎么想到的,我已经忘了,但是记住一个结论,那就是纠出了Linux 2.6.8的NAT模块的一个bug。
在抓取了好多数据包后,我发现本机总是发给自己一个ICMP need frag的报错信息,发现服务端的Certificate太大,超过了本机出网卡的MTU,以下的一步步的思路,最终纠出了bug:

1.证实服务端程序设置了DF标志。这是显然的,因为只有DF标志的数据包才会触发ICMP need frag信息。


2.疑问:在TCP往IP发送数据的时候,会检测MTU,进而确定MSS,明知道MSS的值,怎么还会发送超限的包呢?计算错误可能性不大,毕竟Linux也是准工业级的了。


3.疑问解答:幸亏我当时还真知道一些名词,于是想到了TCP Segment Offload这个技术。

                     TCP Segment Offload简称TSO,它是针对TCP的硬件分段技术,并不是针对IP分片的,这二者区别应该明白,所以这与IP头的DF标志无关。对于IP分片,只 有第一个分片才会有完整的高层信息(如   果头长可以包括在一个IP分片中的话),而对于TSO导致的IP数据包,每一个IP数据包都会有标准的TCP头,网卡硬件自行计算每一个分段头部的校验 值,序列号等头部字段且自动封装IP头。它旨在提高TCP的性能。


4.印证:果然服务器启用了TSO


5. 疑问:一个大于MTU的IP报文发送到了IP层,且它是的数据一个TCP段,这说明TCP已经知道自己所在的机器有TSO的功能,否则对于本机始发的数据 包,TCP会严格按照MSS封装,它不会封装一个大包,然后让IP去分片的,这是由于对于本机始发而言,TCP MSS对MTU是可以感知到的。对于转发而言,就不是这样了,然而,对于这里的情况,明显是本机始发,TCP是知道TSO的存在的。


6.猜测:既然TCP拥有对TSO的存在感知,然而在IP发送的时候,却又丢失了这种记忆,从TCP发往IP的入口,到IP分片决定的终点,中间一定发生了什么严重的事,迫使TCP丢失了TSO的记忆。


7.质疑:这种故障情况是我在公司模拟的,通过报告人员的信息,我了解到并不是所有的情况都会这样。事实上,我一直不太承认是Linux协议栈本身的问题,不然早就被Fix了,我一直怀疑是外部模块或者一些外部行为比如抓包导致的。


8.可用的信息:到此为止,我还有一个信息,那就是只要加载NAT模块(事实上这是分析出来的,报告人员是不知道所谓的NAT模块的,只知道NAT规则)就会有这个现象,于是目标很明确,死盯NAT模块。


9.开始debug:由于Linux Netfilter NAT模块比较简单,根本不需要高端的可以touch到内存级的工具,只需要printk即可,但是在哪里print是个问题。


10.出错点:在调用ip_fragment(就是该函数里面发送了ICMP need frag)之前,有一个判断(省略了不相关的):

if (skb->len > dst_pmtu(skb->dst) && !skb_shinfo(skb)->tso_size) {
    return ip_fragment(skb, ip_finish_output);
}

前一个判断显然为真,如果要想调用ip_fragment的话,后一个判断一定要是假,实际上,如果开启了TSO,就不该调用ip_fragment的。


11.查找tso_size字段:事情很明显了,一定是哪个地方将tso_size设置成了0!而且一定在NAT模块中(98%以上的可能性吧...),于是在NAT模块中查找设置tso_size的地方。


12. 跟踪ip_nat_fn:这是NAT的入口,进入这个入口的时候,tso_size不是0,可是调用了skb_checksum_help之后 tso_size就是0了,问题一定在这个函数中,注意,调用这个help有一个前提,那就是硬件已经计算了校验和。在这个help函数中,有一个 skb_copy的操作,正是在这个copy之后,tso_size变成了0,于是进一步看skb_copy,最终定位 到,copy_skb_header的最后,并没有将原始skb的tso_size复制到新的skb中,这就是问题所在!


13. 触发条件:什么时候会调用skb_copy呢?很简单,如果skb不完全属于当前的执行流的情况下,按照写时拷贝的原则,需要复制一份。故障现象就是慢, 而数据为本机始发,且为TCP。我们知道,TCP在没有ACK之前,skb是不能被删除的,因此当前的skb肯定只是一个副本,因此就需要拷贝一份了。


14. 影响:如此底层的一个函数。搜索代码,影响巨大,各种慢!对于那次的慢,其慢的流程为:socket发送DF数据--感知TSO--丢失TSO-- ICMP need frag--TCP裁成小段继续发送...如果禁止了lo的ICMP,那么更慢,因为TCP会触发超时重传,而不是ICMP的建议裁减,并且重传是不会成 功的,直到用户程序感知,自行减小发送长度。

为什么旧事重提

提起那件事有两个原因,其一是当时没有记录下来整个过程, 可是后续的patch却一直在用,最终我自己都快不知其所以然了,其二,是通过那次的分析,按照现在的理解,就可以发现Linux协议栈的一个优化点,即 TCP情况下,由于保留了数据skb队列直到ack,那么后续向下的所有skb处理流程都至少要经过一次skb_copy,这种复制操作难道就不能避开 吗?如果加载了某些Netfilter钩子,需要对skb进行写操作,这种串行化行为会严重影响Linux网络协议栈的处理效率,这是Netfilter 的通病之一。

附:skb操作的优化点

1.如果把数据和元数据彻底分开是不是更好呢?
2.进一步将写操作的粒度细分

   有些写操作是针对每一个数据包的,这些不得不复制,但是能否局部复制,然后采取分散聚集IO进行拼接呢?尽量采用指针操作而不是复制数据本身,这正是借鉴 了UNIX fork模型以及虚拟地址空间的COW。如果把skb的空间进行细粒度划分,那么就可以做到,需要COW哪部分就只有那部分,不会导致全局复制。

前几天的一个TCP问题排查过程

现象与过程

早就习惯了那种惊心动魄的三规制度(规定的时间,规定的地点,和规定的人一起解决问题),反而不习惯了按部就班了。事情是这样的。

       周末的时候,中午,正在跟朋友一起聊天吃饭,收到了公司的短信,说是有一个可能与TCP/IP有关的故障,需要定位,我没有随即回复,因为这种事情往往需 要大量的信息,而这些信息一般短信传来的时候早就经过了N手,所以为了不做无用功,等有关人员打电话给我再说吧。

       ...


 (以下描述有所简化)
我方服务端:Linux/IP不确定(处在内网,不知道NAT策略以及是否有代理以及其它七层处理情况)
测试客户端:Windows/192.168.2.100/GW 192.168.2.1
中间链路:公共Internet
可用接入方式:3G/有线拨号
服务端设备:第三方负载均衡设备。防火器等
业务流程:客户端与服务端建立SSL连接
故障:
客户端连接3G网卡使用无线链路,业务正常;客户端使用有线链路,SSL握手不成功,SSL握手过程的Client Certificate传输失败。


分析:

1. 通过抓包分析,在有线链路上,发送客户端证书(长度超过1500)后,会收到一条ICMP need frag消息,说是长度超限,链路MTU为1480,而实际发送的是1500。通过无线链路,同样收到了这个ICMP need frag,只是报告的MTU不同,无线链路对应的是1400。


2.有线链路,客户端接受ICMP need frag,重新发送,只是截掉了20字节的长度,然而抓包发现客户端会不断重传这个包,始终收不到服务端的ACK,其间,由于客户端久久不能发送成功数据到服务端,服务端会回复Dup ACK,以示催促。


3.猜想:起初,我以为是时间戳的原因,由于两端没有开启TCP时间戳,所以在RTT以及重传间隔估算方面会有误差,但是这不能解释100%失败的情形,如果是由于时间戳计算的原因,那不会100%失败,因为计算结果受波动权值影响会比较大。


4.对比无线链路,和有线链路的唯一区别就是ICMP报告的MTU不同。


5.中途总结:
5.1. 此时,我并没有把思路往运营商链路上引导,因为我始终认为那不会有问题,同样,我也不认为是SSL的问题,因为错误总是在发送大包后呈现,事实上,接受了 ICMP need frag后,之前发的那个超限包已经被丢弃,重新发送的是一个小一点的包,对于TCP另一端来讲,这是完全正常的。
5.2.根本无需查看服务日志,因为还没有到达那个层次。抓包结果很明确,就是大包传不过去,其实已经按照MTU发现的值传输了,还是过不去,而无线链路能过去。因此应该不是MTU的问题。

5.3.除了运营商链路,MTU,服务端处理之外,还会是哪的问题呢?事实上,程序的bug也不是不可能的,或者说是一些不为人知的动作,不管怎样,需要隔离问题。


6. 猜测是中间某台设备没法处理大包,这个和MTU没有关系,可能就是它处理不了或者根本上不想处理大包,多大呢?反正1480的包处理不了,减去IP 头,TCP头,剩余的是1440的纯数据。于是写一个简单的TCP client程序,在TCP握手完成后马上发送(为了防止由于不是Client Hello而主动断开,因此必须马上发,只是为了观察针对大包的TCP ACK情况,此时与服务无关)长度1440的数据,验证!


7. 果然没有ACK迅速返回,客户端不断重试发送1440的包(之后10秒到20秒,会有ACK到来,但不是每次都会到来,这明显是不正常的)。为了证明这种 方式的合理性,发送无线链路上MTU限制的数据大小,即1400-20-20=1360的数据,ACK秒回。因此猜测中间设备的数据包处理的长度临界点在 1360和1440之间。


8.经过不断的测试,二分法查询临界点,找到了1380是可处理长度临界点。发送1380 的纯数据是正常的,发送1381的纯数据就不正常了。抓包的目标地址是12.23.45.67,简称MA,现在不确定的是MA是什么,是我方的设备,还是 它方的设备,如果是我方的设备,排错继续,如果不是,排错终止。总之,1380这个临界点是一个疑点,常规来讲是不正常的,但也不能排除有这么限制的正常 理由。无线链路没有问题是因为无线链路的MTU比较小,最大纯数据长度1360小与临界值1380。


9.补充测试,模拟问题机器,将其本机的MTU改为1380+20+20=1420,传输也是正常的,然而改为1421,就不行了。(注意,只有本机的MTU修改才有效,因为只有TCP数据始发设备,MSS才与MTU关联)


.....


1x.第9步后面的排查我没有参与,但是最终,我方设备确实没有收到客户端SSL握手过程传出的证书,说明确实是中间设备阻止了这个”大包“的传输,至于它到底是谁,到底怎么回事,与我们无关了,但对于我个人而言,对其还是比较感兴趣的。

对于该次排错的总结

这 是一个典型的网络问题,涉及到IP和TCP,细节不多,但足够典型。其实这个问题与最终的业务逻辑没有关系,但是事实往往是,只有在业务逻辑无法正常时, 这类底层的问题才会暴露,这是TCP/IP协议栈的性质所致。此类问题的排查要点在于,你要用最快的速度把它与高层协议隔离开来,并且不能陷入任何细节。
TCP细节:为何不必考虑TCP细节?这类场景既不特殊,又不复杂,如果陷入TCP细节的话,会掩盖或者忽略大量横向的问题,比如你会死盯着TCP的重传机制做细致研究,或者细致地研究RTT计算方法,最终也不一定能得到什么结论。换句话说,你一定要相信TCP是正常的。
服务程序细节:这个也是要隔离的。因为服务器并没有真的开始服务,且故障是100%重现的,因此可以确定这不是什么复杂的问题所导致,真正复杂的问题往往不是100%重现,即便是你挖掘出其重现规律,也够你喝一壶的。
TCP 问题和IP问题的相异:它们虽然都是网络协议栈的一员,但是使用方式却大不相同。实际上TCP提高了使用者的门槛,一般而言,TCP是让程序去使用的,因 此你要想TCP跑起来,起码要理解其大致原理,或者说懂socket机制,如果你上网浏览网页,虽然也是用的TCP,它确实跑起来了,但是使用者不是你, 而是你的浏览器。IP就不同,IP的配置者可以是小白,并且随意配置都不会报错。再往下,布线问题,拓扑问题,几乎没有什么门槛,但是却更加容易出错。因 此首先要排除的就是这类问题。
防火墙策略或者程序BUG:实际上,第一步就需要询问管理员,是不是防火墙上特殊的策略所致,然而对于无法得到这个 消息的时候,你就不能从这儿开始了。接下来,与之平等的是怀疑程序的处理BUG,此时,隔离出原有的业务逻辑细节是重要的,现象是大包无法收到ACK,此 时就要忽略掉这个大包的内容以及其上下文,直接发送一个任意大包进行测试。

       因此,这类问题的排查是一个逐步隔离的过程,相对四年前的那次NAT bug的排查,这个故障在技术上要更容易些,所有的复杂性和时间的耽搁全部在人员协调交流上,人员之间信息的误传或者漏传也是一个难点,四年前的那个 NAT bug,是一个技术上更加深入的问题,涉及到了内核协议栈代码级别,同时在此之前,我还要找到这个点,然而它的容易点在于,这个问题只涉及到我一个人,而 且也是100%重现。

天与地,贵在没有记忆,一切伤痕总是会被冲刷,一切荣耀,总是会了无痕迹......



 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1671875

相关文章
|
7天前
|
缓存 监控 关系型数据库
《Linux 简易速速上手小册》第10章: 性能监控与优化(2024 最新版)
《Linux 简易速速上手小册》第10章: 性能监控与优化(2024 最新版)
12 0
|
17天前
|
安全 Linux 虚拟化
网络名称空间在Linux虚拟化技术中的位置
网络名称空间(Network Namespaces)是Linux内核特性之一,提供了隔离网络环境的能力,使得每个网络名称空间都拥有独立的网络设备、IP地址、路由表、端口号范围以及iptables规则等。这一特性在Linux虚拟化技术中占据了核心位置🌟,它不仅为构建轻量级虚拟化解决方案(如容器📦)提供了基础支持,也在传统的虚拟机技术中发挥作用,实现资源隔离和网络虚拟化。
网络名称空间在Linux虚拟化技术中的位置
|
17天前
|
网络协议 安全 Linux
Linux网络名称空间之独立网络资源管理
Linux网络名称空间是一种强大的虚拟化技术🛠️,它允许用户创建隔离的网络环境🌐,每个环境拥有独立的网络资源和配置。这项技术对于云计算☁️、容器化应用📦和网络安全🔒等领域至关重要。本文将详细介绍在Linux网络名称空间中可以拥有的独立网络资源,并指出应用开发人员在使用时应注意的重点。
|
17天前
|
安全 网络协议 Linux
Linux网络名称空间概述
Linux网络名称空间是操作系统级别的一种虚拟化技术🔄,它允许创建隔离的网络环境🌐,使得每个环境拥有自己独立的网络资源,如IP地址📍、路由表🗺️、防火墙规则🔥等。这种技术是Linux内核功能的一部分,为不同的用户空间进程提供了一种创建和使用独立网络协议栈的方式。本文旨在全方面、多维度解释Linux网络名称空间的概念、必要性和作用。
Linux网络名称空间概述
|
15天前
|
存储 算法 Linux
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
39 6
|
5天前
|
机器学习/深度学习 缓存 监控
linux查看CPU、内存、网络、磁盘IO命令
`Linux`系统中,使用`top`命令查看CPU状态,要查看CPU详细信息,可利用`cat /proc/cpuinfo`相关命令。`free`命令用于查看内存使用情况。网络相关命令包括`ifconfig`(查看网卡状态)、`ifdown/ifup`(禁用/启用网卡)、`netstat`(列出网络连接,如`-tuln`组合)以及`nslookup`、`ping`、`telnet`、`traceroute`等。磁盘IO方面,`iostat`(如`-k -p ALL`)显示磁盘IO统计,`iotop`(如`-o -d 1`)则用于查看磁盘IO瓶颈。
|
3天前
|
网络协议 Linux Shell
【linux网络(一)】初识网络, 理解四层网络模型
【linux网络(一)】初识网络, 理解四层网络模型
|
3天前
|
安全 Ubuntu Linux
Linux 网络操作命令Telnet
Linux 网络操作命令Telnet
16 0
Linux 网络操作命令Telnet
|
3天前
|
Ubuntu Linux
Linux(22) Linux设置网络优先级顺序
Linux(22) Linux设置网络优先级顺序
6 0
|
4天前
|
Ubuntu 网络协议 Linux
Linux(20) Ubuntu 20.04 网络接口自动切换路由配置
Linux(20) Ubuntu 20.04 网络接口自动切换路由配置
27 0