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

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

四年多前的一个往事

大约在2010年的时候,我排查了一个问题。

问题描写叙述例如以下:

服务端:Linux Kernel 2.6.8/192.168.188.100
client:Windows XP/192.168.40.34
业务流程(简化版):
1.client向服务端发起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.印证:果然server启用了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呢?非常easy。假设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策略以及是否有代理以及其他七层处理情况)
測试client:Windows/192.168.2.100/GW 192.168.2.1
中间链路:公共Internet
可用接入方式:3G/有线拨号
服务端设备:第三方负载均衡设备。防火器等
业务流程:client与服务端建立SSL连接
故障:
client连接3G网卡使用无线链路,业务正常;client使用有线链路,SSL握手不成功,SSL握手过程的Client Certificate传输失败。

分析:

1.通过抓包分析,在有线链路上。发送client证书(长度超过1500)后。会收到一条ICMP need frag消息,说是长度超限,链路MTU为1480,而实际发送的是1500。

通过无线链路,相同收到了这个ICMP need frag。仅仅是报告的MTU不同。无线链路相应的是1400。

2.有线链路,client接受ICMP need frag,又一次发送,仅仅是截掉了20字节的长度,然而抓包发现client会不断重传这个包,始终收不到服务端的ACK。其间。由于client久久不能发送成功数据到服务端,服务端会回复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迅速返回,client不断重试发送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步后面的排查我没有參与。可是终于。我方设备确实没有收到clientSSL握手过程传出的证书,说明白实是中间设备阻止了这个”大包“的传输。至于它究竟是谁,究竟怎么回事,与我们无关了。但对于我个人而言。对其还是比較感兴趣的。

对于该次排错的总结

这是一个典型的网络问题。涉及到IP和TCP,细节不多,但足够典型。其实这个问题与终于的业务逻辑没有关系,可是事实往往是,仅仅有在业务逻辑无法正常时,这类底层的问题才会暴露,这是TCP/IP协议栈的性质所致。

此类问题的排查要点在于,你要用最快的速度把它与高层协议隔离开来,而且不能陷入不论什么细节。
TCP细节:为何不必考虑TCP细节?这类场景既不特殊。又不复杂,假设陷入TCP细节的话。会掩盖或者忽略大量横向的问题,比方你会死盯着TCP的重传机制做仔细研究,或者仔细地研究RTT计算方法,终于也不一定能得到什么结论。换句话说,你一定要相信TCP是正常的。

服务程序细节:这个也是要隔离的。由于server并没有真的開始服务,且故障是100%重现的。因此能够确定这不是什么复杂的问题所导致,真正复杂的问题往往不是100%重现,即便是你挖掘出其重现规律,也够你喝一壶的。

TCP问题和IP问题的相异:它们尽管都是网络协议栈的一员,可是使用方式却大不相同。

实际上TCP提高了使用者的门槛。一般而言,TCP是让程序去使用的,因此你要想TCP跑起来。起码要理解其大致原理,或者说懂socket机制。假设你上网浏览网页。尽管也是用的TCP,它确实跑起来了,可是使用者不是你。而是你的浏览器。

IP就不同,IP的配置者能够是小白,而且随意配置都不会报错。

再往下,布线问题,拓扑问题。差点儿没有什么门槛,可是却更加easy出错。

因此首先要排除的就是这类问题。

防火墙策略或者程序BUG:实际上,第一步就须要询问管理员,是不是防火墙上特殊的策略所致,然而对于无法得到这个消息的时候,你就不能从这儿開始了。接下来,与之平等的是怀疑程序的处理BUG,此时,隔离出原有的业务逻辑细节是重要的,现象是大包无法收到ACK。此时就要忽略掉这个大包的内容以及其上下文,直接发送一个随意大包进行測试。

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

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

时间: 2024-08-04 14:13:46

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

linux用户态和内核态通信之netlink机制【转】

本文转载自:http://blog.csdn.net/zcabcd123/article/details/8272360 这是一篇学习笔记,主要是对<Linux 系统内核空间与用户空间通信的实现与分析>中的源码imp2的分析.其中的源码,可以到以下URL下载: http://www-128.ibm.com/developerworks/cn/Linux/l-netlink/imp2.tar.gz [size=3]参考文档[/size] <linux 系统内核空间与用户空间通信的实现与分析

Linux 用户态与内核态的交互【转载】

Linux 用户态与内核态的交互  在 Linux 2.4 版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用 netlink 套接字实现的,例如iprote2网络管理工具,它与内核的交互就全部使用了netlink,著名的内核包过滤框架Netfilter在与用户空间的通 读,也在最新版本中改变为netlink,无疑,它将是Linux用户态与内核态交流的主要方法之一.它的通信依据是一个对应于进程的标识,一般定为该进 程的 ID.当通信的一端处于中断过程时,该标识为 0.当使用 net

Linux入门之CentOS7内核编译三部曲(1)

Linux入门之CentOS7内核编译三部曲(1) 我们知道,一个Linux系统的主要组成是由liunx内核核心和一些支持模块组合而成的.但是在某些场合中,需要某项功能,而当前内核的核心或者模块不支持此功能,那么就需要对内核进行一个升级或者重新编译内核添加相应的功能,以此提供了对此功能的支持. 编译前的准备 认识kernel 所为kernel,就是一种操作系统的核心,当然也是一个文件,而这种核心提供了对一些硬件的支持,一般来说其中包含了一些对常见硬件核心驱动的核心代码.启动系统时会通过加载MBR

Linux入门之CentOS7内核编译三部曲(2)

Linux入门之CentOS7内核编译三部曲(2) 在linux系统中,编译完了内核,得到了所需的功能,并不能表示就已经能完美完成我们的所需.因为内核只是做一个核心的硬件及核心代码的支持,而注意的模块支持任然是对应版本的内核模块文件.这些文件决定着平时能否使用哪些功能.这次不会介绍如果添加给上次编译的新内核模块功能,主要介绍模块的功能和作用以及其加载和卸载. 查看当前系统以及加载的内核模块命令:lsmod  modinfo lsmod 命令 lsmod - program to show the

Linux tcp被动打开内核源码分析

[我是从2个角度来看,其实所谓2个角度,是发现我分析源码时,分析重复了,写了2个分析报告,所以现在都贴出来.] [如果你是想看看,了解一下内核tcp被动打开时如何实现的话,我觉得还是看看概念就可以了,因为即使看了源码,过一个个礼拜你就忘了,如果是你正在修改协议栈,为不知道流程而发愁,那么希望你能看看源码以及注释,希望你给你帮助.] 概念: tcp被动打开,前提是你listen,这个被动打开的前提.你listen过后,其实创建了一个监听套接字,专门负责监听,不会负责传输数据. 当第一个syn包到达

查看Linux系统版本和内核信息

Linux 查看Linux系统版本信息 1. 查看内核版本 1) 方法一:登录到linux执行cat /proc/version [[email protected]_32bit_ip12 ~]$ cat /proc/version  Linux version 2.6.18-194.el5 ([email protected]) (gcc version 4.1.2 20080704 (Red Hat 4.1.2-48)) #1 SMP Fri Apr 2 14:58:35 EDT 2010 

一张图深度解析Linux共享内存的内核实现

一张图深度解析Linux共享内存的内核实现 Sailor_forever  sailing_9806#163.com http://blog.csdn.net/sailor_8318/article/details/39484747 (本原创文章发表于Sailor_forever 的个人blog,未经本人许可,不得用于商业用途.任何个人.媒体.其他网站不得私自抄袭:网络媒体转载请注明出处,增加原文链接,否则属于侵权行为.如有任何问题,请留言或者发邮件给sailing_9806#163.com)

八、Linux精简系统和内核管理裁剪(一)

一.内核 1.什么是内核 内核其实就是操作系统,是驱动计算机硬件,实现人机操作,并提供其他服务器功能的底层系统.linux操作系统也称为"内核",指的是同一件事情. linux的内核是以模块化的方式工作的,主要工作任务包括存储管理.cpu管理.进程管理.文件系统管理.设备驱动管理.网络管理.系统调用.安全管理等. 2.内核的组成部分包括vmlinuz.initrd(linux5)或者initramfs(linux6)./lib/modules(模块,子核心) 二.内核设计 微内核 就是

八、Linux精简系统和内核管理裁剪(二)

八.内核服务管理 内核切换 chroot  initramfs(内核所在的目录) [[email protected] initramfs]# cd .. [[email protected] ~]# chroot initramfs/ bash-4.1# 自定义启动服务 1.编辑启动脚本文件,可以模仿/etc/rc.d/init.d/目录下的脚本文件. 2.cp启动脚本到/etc/rc.d/init.d/目录下. 3.配置启动项,chkconfig -add scriptname 手工编写启动