2017-12-27 6 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를 처리도 정의 타입)

I에서는 ICMPv6을 체크섬을 계산하기위한 함수는 다음의 사용
는 나중에 계산에 필요한 데이터를 저장하기위한 구조를 다음을 사용. 처음 두 개는 그에 따라 ICMPv6 패킷 및 IPv6 의사 헤더 필드에서 버퍼를 생성합니다. 그런 다음 버퍼의 16 비트 필드 합계의 1의 보수를 계산합니다.

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의 체크섬이 계산 결과와 다릅니다. 내가 뭘 잘못 했니?

추신. 원시 이더넷 패킷을 캡처 및/또는 보내려면 libpcap을 사용하고 있습니다.


편집 : 기능

icmpv6_chksum 수행하는 작업에 대한 자세한 설명은 - 된 ICMPv6 패킷 플러스는 캡슐화 된 것있는 IPv6 패킷의 헤더의 구조를 가져옵니다. 나중에 계산할 빈 버퍼를 만들고 ICMPv6 유형, ICMPv6 코드, ICMPv6 메시지 (기본적으로 ICMPv6 패킷 전체, 체크섬 필드 제외, 계산 중에는 0이됩니다)의 값을 복사합니다.
버퍼, 첫 번째 빈 위치 포인터 및 IPv6 헤더를 pseudoheaderchecksum에 전달합니다.

pseudoheaderchecksum - 버퍼, 첫 번째 빈 위치 포인터 및 IPv6 헤더를 가져옵니다. IPv6 소스 주소, IPv6 대상 주소, 데이터 길이 (이 경우 ICMPv6 패킷 길이), 3 제로 바이트 및 IPv6 다음 헤더 값 (= ICMPv6 헤더)을 복사합니다.
이 기능은 소위 "IPv6 가상 헤더"라고 불리는 버퍼에 추가됩니다. RFC 2460.
이제 채워진 버퍼가 chksum 함수로 전달됩니다.이 함수는 버퍼에 대한 인터넷 체크섬 수를 계산합니다.

chksum - 버퍼와 그 길이를 바이트 단위로 가져옵니다. 버퍼의 16 비트 단편을 합계합니다 (필요에 따라 마지막으로 홀수 바이트를 추가합니다). 오버플로가 16 비트 합계에 추가됩니다. 마지막으로 함수는 계산 결과의 1의 보수 (2 진 반전)를 반환합니다.
RFC 1071에 설명 된대로이 함수에서 인터넷 체크섬을 계산하려고합니다. 모범 구현을 살펴보면 (비록 내가 100 % 확신 할 수는 없지만 알고리즘 설명이 종종 모호합니다) 올바른 것이되어야합니다.


2
좋아 편집, 나는 이제 된 ICMPv6 메시지 본문 (헤더를 따르고 있습니다 내용을) 포함되지 않은 것으로 나타났습니다 unsigned char *data 온 모든 것이

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); 
} 

ICMPv6 헤더 이후 (이더넷 FCS 제외).
체크섬 계산이 여전히 제대로 작동하지 않습니다.

+0

이 체크섬 알고리즘에 대한 정확하고 완전한 설명을 추가하십시오! 나는'htonl()'과 친구들 호출이 보이지 않는다. 왜? –

+0

이러한 구조체에는 '압축 된'pragma가 필요할 수 있습니다. –

+0

체크섬에 IPv6 패킷 헤더를 포함하지 마십시오. 의사 헤더를 ICMP 데이터 그램 앞에 추가 한 다음 의사 헤더와 데이터 그램을 통해 체크섬을 계산하십시오. –

답변

0

숫자가 호스트 순서 일 때와 네트워크 순서에 있어야 할 때를 잃어 가고 있다고 생각합니다. 단지 n_uint16_tuint16_t 사이에서 앞뒤로 주조하는 것은 아무 것도하지 않습니다. htons() and ntohs() 및 기타 관련 기능을 사용해야합니다.

관련 문제