标签:
梦中没有错与对,梦中没有恨和悔...最好闭上你的嘴,这样才算可爱...我不会说:这不公道,我不能接受。我会用朴素的文字记录点点滴滴,早上4点多起来,一气呵成近期的收获与评价,愤怒与忏悔。
大约在2010年的时候,我排查了一个问题。问题描述如下:
现象:SSL握手的时候,服务端发送Certificate特别慢。
1.证实服务端程序设置了DF标志。这是显然的,因为只有DF标志的数据包才会触发ICMP need frag信息。
2.疑问:在TCP往IP发送数据的时候,会检测MTU,进而确定MSS,明知道MSS的值,怎么还会发送超限的包呢?计算错误可能性不大,毕竟Linux也是准工业级的了。
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是个问题。
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肯定只是一个副本,因此就需要拷贝一份了。
早就习惯了那种惊心动魄的三规制度(规定的时间,规定的地点,和规定的人一起解决问题),反而不习惯了按部就班了。事情是这样的。
周末的时候,中午,正在跟朋友一起聊天吃饭,收到了公司的短信,说是有一个可能与TCP/IP有关的故障,需要定位,我没有随即回复,因为这种事情往往需要大量的信息,而这些信息一般短信传来的时候早就经过了N手,所以为了不做无用功,等有关人员打电话给我再说吧。...
分析:
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.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关联)
.....
版权声明:本文为博主原创文章,未经博主允许不得转载。
从Linux 2.6.8内核的一个TSO/NAT bug引出的网络问题排查观点(附一个skb的优化点)
标签:
原文地址:http://blog.csdn.net/dog250/article/details/46798461