IPv6的TSO/GRO/GSO及其Linux实现的不妥

很明确的一件事是,IPv6不允许中间设备对报文分片。具体为什么这么设计,就是为了简单高效。因此,IPv6报头简洁了不少。

但TSO貌似并未违背取消IPv6分片的初衷,硬件把一些都处理的妥妥的,在路由软件层看来,一切好像没有发生过一样。

我先简单解释一下TSO和IP分片的区别:
在这里插入图片描述

我们来看一个简单的实验,用IPv6从服务端拉一个大文件,服务端和客户端的抓包如下:
在这里插入图片描述
在客户端看来,没有IP分片,因此它不需要做分片重组的动作,它实实在在就是收到了一个完整的7140字节的报文,就好像这个报文就是从服务端真实发出的一样,而实际上,服务器显然没有发送过这么大的报文,显然,这是客户端聚合了几个小包的结果。

因此,下面的结论是合理的:

  • 如果转发设备觉得一个IPv6数据包大于出口的MTU,并且如果它的载荷是TCP段的话,启用TSO将其分段而不是直接丢弃并发送ICMP too big,这并无不妥。

IPv6只是规定了中间设备不能分片,但是却没有规定必须保持原样传输啊。

然而,Linux内核是这么实现的吗?严格来讲,Linux对于上面的描述,仅仅实现了一半。

Linux内核做到了:

  • 即便是转发设备,只要使能了LRO/GRO,就会进行收包聚合,将多个小包尽可能聚合成一个大包,并且修改对应的IPv6头。
  • 即便是转发设备,在转发IPv6超过MTU大小的报文时,只要该报文元数据gso_size不大于MTU,就能成功转发,依靠GSO/TSO在发送时对其进行分段发送,重新分割成独立的小报文。

然而,这是不妥的。

gso_size可以认为是聚合前小报文的大小,在我看来,即便它大于出口MTU,也是可以利用TSO/GSO机制将其分段的啊!只是Linux没有实现而已。

具体看这段代码:

int ip6_forward(struct sk_buff *skb)
{
    ...
    if (ip6_pkt_too_big(skb, mtu)) {
        /* Again, force OUTPUT device used as source address */
        skb->dev = dst->dev;
        icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
        __IP6_INC_STATS(net, idev, IPSTATS_MIB_INTOOBIGERRORS);
        __IP6_INC_STATS(net, ip6_dst_idev(dst),
                IPSTATS_MIB_FRAGFAILS);
        kfree_skb(skb);
        return -EMSGSIZE;
    }
    ...
}

当你看ip6_pkt_too_big的代码时,里面会找到这个不妥的逻辑。

这里没有必要抬杠说什么GSO的实现之类就是如此,我的意思是,只要指定一个出口的MTU,无论什么样的TCP包,底层TSO/GSO都会按照该MTU的大小将其分成小段,每一个小段都是一个独立完整的IPv6报文。这个和入口GRO设置的gso_size有个毛线关系啊!

但事实上,Linux内核对此的实现就是这么残!我用systemtap和bpftrace实地探测过的结论,后来我才看的代码。


浙江温州皮鞋湿,下雨进水不会胖。

原文链接: https://blog.csdn.net/dog250/article/details/111399794

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    IPv6的TSO/GRO/GSO及其Linux实现的不妥

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/405807

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午9:26
下一篇 2023年4月26日 上午9:26

相关推荐