2011-09-17 5 views
5

IPv6 라우터 알림 패킷을받는 Linux 사용자 공간 프로그램에서 작업하고 있습니다. RFC4861의 일환으로 ICMPv6 체크섬을 확인해야합니다. 내 연구를 기반으로, IPv6 의사 헤더와 패킷 내용의 칭찬 체크섬을 계산하면 일반적으로 IP 체크섬을 참조하는 것이 가장 일반적입니다. 결과는 0xffff 여야합니다. 하지만 0x3fff의 체크섬을 계속 가져옵니다.ICMPv6 체크섬의 유효성을 검사하려면 어떻게합니까? (왜 0x3fff의 체크섬을 계속 사용합니까?)

체크섬 구현에 문제가 있습니까? 리눅스 커널은 패킷을 사용자 공간에 전달하기 전에 ICMPv6 체크섬을 확인합니까? 거기에 좋은 ICMPv6 패킷 테스트를위한 좋은 레퍼런스 소스가 무엇입니까?

uint16_t 
checksum(const struct in6_addr *src, const struct in6_addr *dst, const void *data, size_t len) { 
    uint32_t checksum = 0; 
    union { 
     uint32_t dword; 
     uint16_t word[2]; 
     uint8_t byte[4]; 
    } temp; 

    // IPv6 Pseudo header source address, destination address, length, zeros, next header 
    checksum += src->s6_addr16[0]; 
    checksum += src->s6_addr16[1]; 
    checksum += src->s6_addr16[2]; 
    checksum += src->s6_addr16[3]; 
    checksum += src->s6_addr16[4]; 
    checksum += src->s6_addr16[5]; 
    checksum += src->s6_addr16[6]; 
    checksum += src->s6_addr16[7]; 

    checksum += dst->s6_addr16[0]; 
    checksum += dst->s6_addr16[1]; 
    checksum += dst->s6_addr16[2]; 
    checksum += dst->s6_addr16[3]; 
    checksum += dst->s6_addr16[4]; 
    checksum += dst->s6_addr16[5]; 
    checksum += dst->s6_addr16[6]; 
    checksum += dst->s6_addr16[7]; 

    temp.dword = htonl(len); 
    checksum += temp.word[0]; 
    checksum += temp.word[1]; 

    temp.byte[0] = 0; 
    temp.byte[1] = 0; 
    temp.byte[2] = 0; 
    temp.byte[3] = 58; // ICMPv6 
    checksum += temp.word[0]; 
    checksum += temp.word[1]; 

    while (len > 1) { 
     checksum += *((const uint16_t *)data); 
     data = (const uint16_t *)data + 1; 
     len -= 2; 
    } 

    if (len > 0) 
     checksum += *((const uint8_t *)data); 

    printf("Checksum %x\n", checksum); 

    while (checksum >> 16 != 0) 
     checksum = (checksum & 0xffff) + (checksum >> 16); 

    checksum = ~checksum; 

    return (uint16_t)checksum; 
} 
+0

큰 또는 작은 엔디안 컴퓨터에서 실행되고 있습니까? – Alnitak

+0

리틀 엔디안 (x86_64)이지만 내가 체크섬을 확인한 내용은 엔디안과 독립적이어야합니다. – dlundquist

+0

+1 "내 연구를 기반으로" – Flexo

답변

1

while 루프가 잔인합니다. 시체는 한 번만 생깁니다.

while (checksum >> 16 != 0) 
    checksum = (checksum & 0xffff) + (checksum >> 16); 

checksum = ~checksum; 

return (uint16_t)checksum; 

대신

checksum += checksum >> 16; 

return (uint16_t)~checksum; 

이 필요하지 않습니다. len은 항상 16 비트입니다.

temp.dword = htonl(len); 
checksum += temp.word[0]; 
checksum += temp.word[1]; 

이것은 필요하지 않습니다. 상수는 그래서 그냥 알고리즘을 사용하면 정수의 엔디안과 마지막 바이트 홀수 바이트를 처리하는 방식을 제외하고, 바로 그렇지 않으면 일반적으로 보이는 58

temp.byte[0] = 0; 
temp.byte[1] = 0; 
temp.byte[2] = 0; 
temp.byte[3] = 58; // ICMPv6 
checksum += temp.word[0]; 
checksum += temp.word[1]; 

를 추가, 항상 00 00 00 58입니다. 내가 프로토콜을 읽었을 때, 바이트는 빅 엔디 언 순서로 합산되어야한다. 즉, 바이트 0xAB 0xCD는 16 비트 0xABCD로 해석되어야한다. 코드는 컴퓨터의 주문에 따라 다릅니다.

정수가 만들어지는 순서는 체크섬에 올바르게 추가되는 캐리 수에 영향을줍니다. 그러나 코드가 대상 시스템과 일치하면 마지막 홀수 바이트가 잘못됩니다. 0xAB을 쓰면 0x00AB가 아닌 0xAB00이됩니다.

+0

모든 코드 제안에 동의했는데 이것이 네 번째 시도 였고 가능한 한 RFC 1071 4.1 예제를 따르려고했습니다. 마지막 바이트가 잘못되었다고 생각했지만 패킷이 정확히 64 바이트 길이였습니다. – dlundquist

1

리틀 엔디안 컴퓨터에서 실행중인 경우 체크섬 누적 중에 바이트 스왑이 많이 필요하다고 생각합니다.

조금 엔디안 시스템에서 예를 들어 2001:을 시작하는 일반적인 IPv6 주소의 s6_addr16[0] 요소는 0x0120, 그리고 0x2001 포함됩니다. 이렇게하면 캐리 비트가 잘못된 위치에 놓이게됩니다.

거기에 htonl()을 사용하고 있기 때문에 길이 코드가 표시되지만, 0x00 0x00 0x00 0x58 및 그 이후의 메시지 누적 논리는 유효하지 않습니다. 내 생각에 비트 왼쪽에 어떤 것도 높은 바이트로 끝나야하며 코드에서 발생하는 낮은 바이트가 아니라고 생각합니다.

또한 0x0000을 의사 헤더 체크섬 바이트로 사용하면 체크섬을 생성 할 때 수행해야 할 작업입니다. 의 유효성을 검사하려면 체크섬에 IPv6 RA에서 수신 한 실제 체크섬 바이트를 사용하고 0xffff을 최종 값으로 사용해야합니다.

+0

주소가 고정되어있는 IPv6 의사 헤더의 데이터에서 체크섬이 완료되었음을 이해합니다. 그물 작업의 최종성 및 호스트에 의해 영향을받지 않습니다. 길이 필드 만 호스트의 endinness로 변환됩니다. 또한 세 라우터에서 라우터 광고를 받고 있는데이 체크섬 알고리즘은받은 모든 광고에 대해 동일한 체크섬을 계산합니다. – dlundquist

1

나는 나의 버그를 발견했다 : 나는 256 바이트의 입력 버퍼를 가지고 있으며, recvmsg()msg_ioviov_len 엘리먼트가 수신 된 데이터의 길이를 반환하도록 수정되었다고 가정했다. 내 라우터 광고의 길이가 64 바이트로 일정하기 때문에이 길이의 차이로 인해 체크섬에 일정한 오류가 발생했습니다.체크섬을 확인하기 위해 바이트 순서를 변경할 필요가 없었습니다 (홀수 길이의 경우 마지막 바이트를 처리하는 데 홀수 길이의 ICMPv6 패킷이 없었음에도 불구하고)

또한 체크섬의 마지막 NOT 위의 코드를 사용하면 체크섬이 유효하면 0을 반환합니다

관련 문제