UDP校验和计算

31

/usr/include/netinet/udp.h 中定义的UDP头结构如下:

struct udphdr
{
  u_int16_t source;
  u_int16_t dest;
  u_int16_t len;
  u_int16_t check;
};

头部的检查字段中存储了什么值?如何验证校验和是否正确?我的意思是校验和是根据哪些数据计算出来的?(是仅仅基于UDP头部还是包括跟随其后的有效负载?)

谢谢。

3个回答

43
UDP校验和覆盖了整个负载,以及头中的其他字段和IP头中的一些字段。为了执行计算(在这个伪头部、UDP头和负载上执行),从IP头构建伪头部。包括伪头的原因是捕获被路由到错误IP地址的数据包。
基本上,在接收端,将头和数据区的所有16位字加在一起(在16位处换行),并检查结果是否等于0xffff。
在发送端,稍微复杂一些。对所有16位值执行一次补码求和,然后取该值的补码(即,反转所有位)来填充校验和字段(有一个额外的条件,即计算出零的校验和将改变为所有位均为1)。
补码求和不仅仅是所有补码值的总和。它有点更复杂。
基本上,您有一个从零开始的运行16位累加器,并将每个16位值添加到其中。当其中一个加法导致进位时,该值被包裹并再次将其值增加1。这实际上将16位加法的进位位添加到值中。
顺便说一句,我个人认为这可能可以通过使用ADC(带进位的加法)指令有效地完成,而不是ADD(令人惊讶的是,加法)或在CPU上可用的任何等效指令。如果没有进位,ADC将只添加进位的零位。在当时进行这些操作时(是的,不幸的是,我已经老了),内存远远超过速度,现在的情况并非如此,因此在代码中节省几个字节可能会使您成为宇宙的半神皇帝。请注意,第二次(或者如果你使用前面提到的那种方法,则是下一个ADC的进位为2的情况)时,您永远不必担心进位问题,因为两个最大的16位值相加会得到(从0x1fffe截断)0xfffe - 对其加一永远不会导致另一个进位。
计算得出反码和之后,将其位反转并插入数据包,这将导致接收端的计算产生0xffff,当然前提是没有传输错误。
值得注意的是,数据负载始终进行填充,以确保存在整数个16位字。如果它被填充了,长度字段将告诉您实际长度。 RFC768是详细说明此内容的规范。

1
所有头部的16位字(其中UDP校验和为零)都会被相加,然后对其进行一次补码操作(即反转所有位),得到的结果将被放入校验和字段中。不过,RFC所说的是“所有头部的16位字(其中UDP校验和为零)的一次补码操作(即反转所有位)都会被相加,然后对其进行一次补码操作,得到的结果将被放入校验和字段中”。 - Joren
2
这不对。"一补数和"并不意味着取每个字的一补数然后相加。它的意思是将这些字相加,当产生进位位时,将1加到运行总和中。请参见http://mathforum.org/library/drmath/view/54379.html。 - Jim Hunziker
1
有趣的一点关于补码校验和,这可能与为什么选择它有很大关系,就是交换要求相加的字的顺序将会交换总和的字节顺序。 - supercat
很高兴知道我不是唯一一个对“补码和校验和”这个术语感到困惑的人。无论我去哪里,这个术语都被随意地使用,但实际上它并不那么简单,是吗? - cib
如果我理解正确,您可以使用常规的无符号二进制加法并丢弃进位来验证校验和。但是在我的大学课程脚本中,不仅用于计算校验和,还使用了补码加法来验证校验和。 - cib
显示剩余3条评论

3
一个易于理解的UDP校验和计算示例是由Gerd Hoffmann完成的。
您可以搜索“net-checksum.c Gerd Hoffmann”或在此处查看该文件:

https://gist.github.com/fxlv/81209bbd150abfeaceb1f85ff076c9f3

你可以使用net_checksum_tcpudp函数,将UDP负载长度、协议、源IP和目的IP以及UDP负载本身传递给它,然后它会自动处理。最后需要调用htons()函数对校验和进行转换即可。

0

你已经链接到了DragonflyBSD,尽管该源文件是派生自OpenBSD。这是官方的OpenBSD git链接:https://github.com/openbsd/src/blob/master/sbin/dhclient/packet.c - Conrad Meyer

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