【发布时间】:2013-02-02 21:30:50
【问题描述】:
我正在尝试计算 ICMPv6 消息的校验和(准确地说,是邻居广告)。
RFC 4443 将其描述为“整个 ICMPv6 消息的反码和的 16 位反码”
还有一些关于如何做到这一点的示例代码(虽然我认为它来自 IPv4,但唯一的区别是总和中包含的内容,而不是如何计算它): RFC 1071
我从 Wireshark 获取了一个数据包,并按主机字节顺序输入了短裤。然后我打印正确的校验和,将其归零并计算我的。但它们不匹配。根据 RFC 1071,字节序不应该是问题(结果不仅仅是字节交换,而是完全关闭)。
根据RFC 2460 #8.1我需要在计算中包含“伪头”,只包含Src+Dst地址,长度为32位宽字段和下一个头类型。
调用代码:
uint32_t payloadlen = htonl(32);
struct ip6_hdr *ip6;
struct nd_neighbor_advert *na;
size_t len, offset, tmplen;
uint8_t *tmppacket, icmp = 58;
uint8_t packet[] = {
0x60, 0x00, 0x00, 0x00, 0x00, 0x20, 0x3A, 0xFF, 0x20, 0x01, 0x0D, 0xB8,
0xDD, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x54, 0xFF,
0xFE, 0x00, 0x22, 0x00, 0x88, 0x00, 0x54, 0xB9, 0x60, 0x00, 0x00, 0x00,
0x20, 0x01, 0x0D, 0xB8, 0xDD, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x00, 0x03, 0x54, 0x00, 0x00, 0x13
};
na->nd_na_hdr.icmp6_cksum = 0;
tmplen = 40+sizeof(struct nd_neighbor_advert)+ICMP_OPT_LEN;
tmppacket = malloc(tmplen);
memset(tmppacket, 0, 40);
offset = sizeof(struct in6_addr);
memcpy(tmppacket, &ip6->ip6_src, offset);
memcpy(tmppacket+offset, &ip6->ip6_dst, offset);
memcpy(tmppacket+offset*2, &payloadlen, 4);
memcpy(tmppacket+39, &icmp, 1);
memcpy(tmppacket+40, packet+sizeof(struct ip6_hdr),
sizeof(struct nd_neighbor_advert)+ICMP_OPT_LEN);
na = (struct nd_neighbor_advert *) (tmppacket+40);
na->nd_na_hdr.icmp6_cksum = checksum((uint16_t *) tmppacket, tmplen);
printf("Checksum calc: %hX\n", na->nd_na_hdr.icmp6_cksum);
dump((unsigned char *) tmppacket, tmplen);
校验和函数:
uint16_t checksum (uint16_t *addr, size_t bytes) {
unsigned int i;
uint16_t *p = addr;
uint32_t sum = 0;
/* Sum */
for (i=bytes; i > 1; i -= 2)
sum += *p++;
/* If uneven length */
if (i > 0)
sum += (uint16_t) *((unsigned char *) (p));
/* Carry */
while ((sum & 0xFFFF0000) != 0)
sum = (sum >> 16) + (sum & 0xFFFF);
return ~((uint16_t) sum);
}
这只是快速和肮脏的让它开始工作。 “main”的代码省略了一些东西。校验和函数中的字节序应该不是问题,而且这个数据包的长度是偶数。
结果应该是 B954,但我得到的是 DB32。 转储的输出是:
数据包大小:72
2001 0DB8 DDDD 0000 0000 0000 0000 0100 FE80 0000 0000 0000 0203 54FF FE00 2200 0000 0020 0000 003A 8800 32DB 6000 0000 2001 0DB8 DDDD 0000 0000 0000 0000 0100 0201 0003 5400 0013
感谢到目前为止的提示。如果您知道还有什么问题,我会很感激您的意见。
【问题讨论】:
-
没有必要将数据包本身表示为
uint16_t[],我认为这只会引起混淆。如果您需要添加字节,请执行此操作,即将数据包表示为uint8_t的数组,当然要保留确切的字节顺序。 -
@unwind 是的,我不需要这样做。但在最终程序中,无论如何我都不会手动输入数据包。在校验和中,我不需要添加字节,而是 16 位值。无论我将 uint8_t[] 还是 uint16_t[] 传递给函数,它都不会改变结果。
-
你试过
setsockopt(2)opname = IPV6_CHECKSUM 吗?