2017-12-27 1138 views
0

我一直在尝试为IPv6实现我自己的简化TCP/IP协议栈,目前我的目标是能够回答ICMPv6回应请求。
我使用以下结构,用于存储在稍后的计算所需要的数据:C中ICMPv6校验和计算返回错误结果

typedef uint16_t n_uint16_t;  //network byte order 
typedef uint32_t n_uint32_t; 

n_uint16_t htons(uint16_t n); 
n_uint32_t htonl(uint32_t n); 

struct ipv6hdr { 
    n_uint32_t  vtcfl;   //version, traffic class, flow label 

    n_uint16_t  payload_len; 
    unsigned char nexthdr; 
    unsigned char hop_limit; 

    unsigned char saddr[IP6_ALEN]; //IP6_ALEN = 16 
    unsigned char daddr[IP6_ALEN]; 
}; 

struct icmp6hdr { 
    unsigned char type; 
    unsigned char code; 
    n_uint16_t  cksum; 

    union { 
     n_uint32_t  un_data32[1]; /* type-specific field */ 
     n_uint16_t  un_data16[2]; /* type-specific field */ 
     unsigned char un_data8[4]; /* type-specific field */ 
    } dataun; 
}; 

(也有定义的类型,用于处理endianity)

我使用以下函数来计算的ICMPv6校验和。前两个是相应地创建ICMPv6数据包和IPv6伪头字段的缓冲区。然后我计算缓冲区的16位字段之和的余数。

n_uint16_t icmpv6_chksum(struct ipv6hdr *ip6, struct icmp6hdr *icmp) { 
    unsigned char buf[65535]; 
    unsigned char *ptr = &(buf[0]); 
    int chksumlen = 0; 

    //ICMPv6 type 
    memcpy(ptr, &icmp->type, sizeof(icmp->type)); 
    ptr += sizeof(icmp->type); 
    chksumlen += sizeof(icmp->type); 

    //ICMPv6 code 
    memcpy(ptr, &icmp->code, sizeof(icmp->code)); 
    ptr += sizeof(icmp->code); 
    chksumlen += sizeof(icmp->code); 

    //ICMPv6 payload 
    memcpy(ptr, &icmp->dataun.un_data32, sizeof(icmp->dataun.un_data32)); 
    ptr += sizeof(icmp->dataun.un_data32); 
    chksumlen += sizeof(icmp->dataun); 

    return pseudoheaderchksum(buf, ptr, ip6, chksumlen); 
} 

n_uint16_t pseudoheaderchksum(unsigned char *buf, unsigned char *ptr, struct ipv6hdr *ip6, int chksumlen) { 
    //source address 
    memcpy(ptr, &ip6->saddr, sizeof(ip6->saddr)); 
    ptr += sizeof(ip6->saddr); 
    chksumlen += sizeof(ip6->saddr); 

    //dest address 
    memcpy(ptr, &ip6->daddr, sizeof(ip6->daddr)); 
    ptr += sizeof(ip6->daddr); 
    chksumlen += sizeof(ip6->daddr); 

    //upper layer length 
    n_uint32_t upprlen = 0; 
    upprlen += sizeof(ip6->payload_len); 
    memcpy(ptr, &upprlen, sizeof(upprlen)); 
    ptr += 4; 
    chksumlen += 4; 

    //3 bytes of zeros, then next header byte 
    *ptr = 0; 
    ptr++; 
    *ptr = 0; 
    ptr++; 
    *ptr = 0; 
    ptr++; 
    chksumlen += 3; 
    memcpy(ptr, &ip6->nexthdr, sizeof(ip6->nexthdr)); 
    ptr += sizeof(ip6->nexthdr); 
    chksumlen += sizeof(ip6->nexthdr); 
    return chksum((uint16_t *) buf, chksumlen); 
} 

//counting internet checksum 
n_uint16_t chksum(uint16_t *buf, int len) { 
    int count = len; 
    n_uint32_t sum = 0; 
    n_uint16_t res = 0; 
    while (count > 1) { 
     sum += (*(buf)); 
     buf++; 
     count -= 2; 
    } 
    //if number of bytes was odd 
    if (count > 0) { 
     sum += *(unsigned char *) buf; 
    } 
    while (sum >> 16) { 
     sum = sum + (sum >> 16); 
    } 
    res = (n_uint16_t) sum; 
    return ~res; 
} 

不幸的是,我测试了现有的数据包,捕获的ICMPv6的校验和比我的计算结果不同。我做错了什么?

PS。我正在使用libpcap来捕获和/或发送原始的以太网数据包。


编辑:什么功能做

icmpv6_chksum更详细的说明 - 获得ICMPv6报文加在它的封装的IPv6数据包的报头的结构。为后面的计算创建一个空缓冲区,并复制ICMPv6类型,ICMPv6代码,ICMPv6消息(基本上是整个ICMPv6数据包,除校验和字段外,在计算过程中无论如何都为零)的值。
将缓冲区,其第一个空位指针和IPv6头部传递到伪首标校验块

pseudoheaderchecksum - 获取缓冲区,其第一个空位置指针和IPv6头部。复制IPv6源地址,IPv6目标地址,数据长度(在这种情况下,它是我们的ICMPv6数据包长度),然后是3个零字节和IPv6下一个标头值(= ICMPv6标头)。
该功能将添加到所谓的“IPv6伪首部”的缓冲器,其由前述的字段组成,如在例如RFC 2460.
现在填充的缓冲区被传递到chksum函数 - 一个应该为缓冲区统计Internet Checksum的函数。

chksum - 获取缓冲区及其以字节为单位的长度。它将缓冲区的16位碎片汇总在一起(如果需要,最后添加一个奇数字节)。溢出被添加到16位和。最后,函数返回计算结果的补码(二进制反转)。
在这个函数中,我试图计算Internet Checksum,如RFC 1071中所述。查看示例实现,它应该(虽然我不是100%确定,算法描述通常是模糊的)正确。


编辑2
好吧,我注意到,我并没有包括ICMPv6的消息体(即跟随标题的内容),所以现在

n_uint16_t icmpv6_chksum(struct ipv6hdr *ip6, struct icmp6hdr *icmp, unsigned char* data, int len) { 
    unsigned char buf[65535]; 
    unsigned char *ptr = &(buf[0]); 
    int chksumlen = 0; 

    //ICMPv6 type 
    memcpy(ptr, &icmp->type, sizeof(icmp->type)); 
    ptr += sizeof(icmp->type); 
    chksumlen += sizeof(icmp->type); 

    //ICMPv6 code 
    memcpy(ptr, &icmp->code, sizeof(icmp->code)); 
    ptr += sizeof(icmp->code); 
    chksumlen += sizeof(icmp->code); 

    //ICMPv6 rest of header 
    memcpy(ptr, &icmp->dataun.un_data32, sizeof(icmp->dataun.un_data32)); 
    ptr += sizeof(icmp->dataun.un_data32); 
    chksumlen += sizeof(icmp->dataun); 

    unsigned char *tmp = data; 
    for(int i=0; i<len; i++){ 
     memcpy(ptr, &tmp, sizeof(unsigned char)); 
     ptr += sizeof(unsigned char); 
     tmp += sizeof(unsigned char); 
     chksumlen += sizeof(unsigned char); 
    } 
    return pseudoheader_chksum(buf, ptr, ip6, chksumlen); 
} 

哪里unsigned char *data的是,一切都来了在ICMPv6头后(不包括以太网FCS)。
不幸的是,校验和计数仍然无法正常工作。

+0

请添加此校验和算法的完整和完整的描述!我没有看到任何'htonl()'和 - 朋友的电话,为什么? –

+0

这些结构可能需要一些'包装'杂志? –

+0

您在校验和中不包含IPv6数据包标头。将伪头添加到ICMP数据报,然后计算伪头和数据报的校验和。 –

回答

0

我怀疑你正在失去跟踪数字应该在主机顺序,以及他们应该在网络顺序。仅仅在n_uint16_tuint16_t之间来回转换不会做任何事情。您需要使用htons() and ntohs()和其他相关功能。