如何计算TCP校验和

7

我正在编写一个内核模块,使用Netfilter钩子修改TCP头信息,并在发送之前重新计算校验和。
我还在接收端编辑头部,因此也需要在那里重新计算校验和。

在网上搜索时,我发现有些人说我可以简单地将其设置为0,它就会自动计算。但显然这种方法行不通。
我也找到了这个函数

tcp_v4_send_check(struct sock *sk, struct sk_buff *skb);

虽然没有人解释这个如何使用,以及我是否可以在接收/发送时实际使用它。
我的尝试是将校验和设置为0,然后调用此函数,传递我拥有的skb和skb->sk,但仍然没有效果。
因此,请问有什么直接的方法来计算TCP数据报的校验和?

你到底把什么设置为了0,你期望在哪里找到校验和?看代码,你应该将tcp_hdr(skb)->check设置为0,然后调用函数,此后新的校验和应该在其中。 - BjoernD
这正是我所做的,但当我加载模块时,所有TCP连接都无法工作。 - Fingolfin
你可能应该在问题中添加 C 标签。顺便看一下 http://www.winpcap.org/pipermail/winpcap-users/2007-July/001984.html。 - Jite
有一整个RFC专门讲述这个问题,并提供了C代码。去查一下吧。 - user207421
@EJP:感谢RFC :) 或许我的问题有些令人困惑;五年前我并不想自己编写代码,我想使用内核所使用的任何东西(因为我为什么要自己编写呢?),但是在获取问题中引用的函数发送正确的校验和方面遇到了困难。 - Fingolfin
3个回答

3
重新计算校验和时,最好计算增量校验和——根据你修改的字段修改现有的校验和,而不是读取整个数据包。这必须在更改数据包时完成,当你知道旧值和新值时。
基本思路是 `tcp->check +=(new_val - old_val)`。由于以下原因,它比这更复杂:
1. `old_val` 和 `new_val` 需要是对齐在 2 字节上的 16 位值(例如更改端口号)。 2. 校验和使用补码算术,因此需要进行进位反馈。这基本上意味着,如果 `tcp->check + new_val - old_val` 是负数,则需要从结果中减去 1。

2
这是正确的做法。它更快,且数据损坏的风险更小。重新计算校验和可能会掩盖已经发生在数据中的任何损坏。 - kasperd

1

@ugoren的回答不够精确。根据RFC1624 https://www.rfc-editor.org/rfc/rfc1624,有时会产生-0(0xFFFF),这是不允许的。

正确计算校验和的方法应该是:new_tcp_check = ~(~old_tcp_check + ~old_val + new_val)


@ugoren的答案是正确的,但只使用您的公式似乎更简单。 - Daniel
难道说 old_checksum + old_val + ~new_val 不更简单吗? - rassa45

1
这是一个结合了netfilter API和TCP校验和的示例(而不是IP):

http://www.linuxvirtualserver.org/software/tcpsp/index.html

请查看名为tcpsp_core.c的文件。
    th->check = 0;
    th->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
                                  datalen, iph->protocol,
                                  csum_partial((char *)th, datalen, 0));
    skb->ip_summed = CHECKSUM_UNNECESSARY;

(注意,首先分配零,计算校验和,然后指示不需要IP校验和)。

取决于您加载的netfilter模块(有很多!),它们将在不同的层工作,例如,下面显示了在IP层工作的iptable(图像):

http://ars.sciencedirect.com/content/image/1-s2.0-S1389128608004040-gr3.jpg


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接