提起那件事有两个原因,其一是当时没有记录下来整个过程,可是后续的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之间。