关于网络:如何计算非线性skb/payload的TCP/UDP校验和?

How to calculate TCP/UDP checksum for non-linear skb / payload?

我正在尝试计算正在发送的 skb 上的 TCP/UDP 标头校验和。

基本上,有两个函数应该完成所有工作
(在这个 stackoverflow 问题和这个问题中也提到过):

  • csum_tcpudp_magic
  • csum_partial

例如,对于 TCP:

1
2
3
4
5
6
7
8
  tcph->check = 0;
  tcph->check = csum_tcpudp_magic(iph->saddr,
                                  iph->daddr,
                                  tcp_packet_len,
                                  IPPROTO_TCP,
                                  csum_partial((unsigned char *)tcph,
                                               tcp_packet_len,
                                               0));

这很好用——我能够传输 TCP/UDP 数据包并在接收端使用wireshark 验证校验和。但是,我找不到任何关于碎片化 SKB 的信息。

这两个"魔术"函数会处理 TCP/UDP 负载分片的情况吗?

此外,TCP/UDP 标头可能驻留在 SKB 的非线性部分(也可能是分段的)。

这些函数也会处理这种情况吗?

我确实知道我可以尝试使用更大的数据包,这会导致它们碎片化,但是现在错误处理的代价是内核 oops,我不能让系统崩溃。


经过大量挖掘,我发现了这一点。
csum_tcpudp_magiccsum_partial 不能与碎片化的 skb 一起使用。
如果要使用它们来计算 TCP/UDP 校验和,我需要使所有数据包有效负载驻留在 skb 的线性部分中。

幸运的是,Linux 中有一个实用函数可以做到这一点 - skb_linearize.

所以基本上,流程是:

1
2
3
4
5
6
7
8
9
10
11
12
  if (unlikely(skb_linearize(skb) != 0))
      ... goto drop - no memory...;

  ... do whatever you want with data ...
  tcph->check = 0;
  tcph->check = csum_tcpudp_magic(iph->saddr,
                                  iph->daddr,
                                  tcp_packet_len,
                                  IPPROTO_TCP,
                                  csum_partial((unsigned char *)tcph,
                                               tcp_packet_len,
                                               0));

如果无法进行线性化(性能、内存或其他原因),则需要"手动"计算校验和。


到此代码运行时,数据包已重新组装。