After lots of digging I figured this out.
csum_tcpudp_magic
and csum_partial
can't work with fragmented skb.
If you want to use them to calculate TCP/UDP checksums, I need to make all the packet payload to reside in the linear part of the skb.
Luckily, there's a utility function in Linux that does just that - skb_linearize
.
So basically, the flow is:
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));
If linearization is not possible (performance, memory, or whatever reason), then you need to calculate the checksum 'manually'.