Created
September 20, 2022 13:14
-
-
Save sigeryang/a73e353d0fe5e7ef7e8feed600c63cbb to your computer and use it in GitHub Desktop.
VRRP Checksum Test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdbool.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <netinet/in.h> | |
#include <arpa/inet.h> | |
// https://github.com/FRRouting/frr/blob/master/lib/checksum.c | |
#define add_carry(dst, add) \ | |
do { \ | |
typeof(dst) _add = (add); \ | |
dst += _add; \ | |
if (dst < _add) \ | |
dst++; \ | |
} while (0) | |
uint16_t in_cksumv(const struct iovec *iov, size_t iov_len) | |
{ | |
const struct iovec *iov_end; | |
uint32_t sum = 0; | |
union { | |
uint8_t bytes[2]; | |
uint16_t word; | |
} wordbuf; | |
bool have_oddbyte = false; | |
for (iov_end = iov + iov_len; iov < iov_end; iov++) { | |
const uint8_t *ptr, *end; | |
ptr = (const uint8_t *)iov->iov_base; | |
end = ptr + iov->iov_len; | |
if (ptr == end) | |
continue; | |
if (have_oddbyte) { | |
have_oddbyte = false; | |
wordbuf.bytes[1] = *ptr++; | |
add_carry(sum, wordbuf.word); | |
} | |
while (ptr + 8 <= end) { | |
add_carry(sum, *(const uint32_t *)(ptr + 0)); | |
add_carry(sum, *(const uint32_t *)(ptr + 4)); | |
ptr += 8; | |
} | |
while (ptr + 2 <= end) { | |
add_carry(sum, *(const uint16_t *)ptr); | |
ptr += 2; | |
} | |
if (ptr + 1 <= end) { | |
wordbuf.bytes[0] = *ptr++; | |
have_oddbyte = true; | |
} | |
} | |
if (have_oddbyte) { | |
wordbuf.bytes[1] = 0; | |
add_carry(sum, wordbuf.word); | |
} | |
sum = (sum >> 16) + (sum & 0xffff); | |
sum += (sum >> 16); | |
return ~sum; | |
} | |
#define array_size(x) (sizeof(x) / sizeof(x[0])) | |
// https://github.com/FRRouting/frr/blob/master/lib/checksum.h | |
struct ipv4_ph { | |
struct in_addr src; | |
struct in_addr dst; | |
uint8_t rsvd; | |
uint8_t proto; | |
uint16_t len; | |
} __attribute__((packed)); | |
struct ipv6_ph { | |
struct in6_addr src; | |
struct in6_addr dst; | |
uint32_t ulpl; | |
uint8_t zero[3]; | |
uint8_t next_hdr; | |
} __attribute__((packed)); | |
static inline uint16_t in_cksum(const void *data, size_t nbytes) | |
{ | |
struct iovec iov[1]; | |
iov[0].iov_base = (void *)data; | |
iov[0].iov_len = nbytes; | |
return in_cksumv(iov, array_size(iov)); | |
} | |
static inline uint16_t in_cksum_with_ph4(const struct ipv4_ph *ph, | |
const void *data, size_t nbytes) | |
{ | |
struct iovec iov[2]; | |
iov[0].iov_base = (void *)ph; | |
iov[0].iov_len = sizeof(*ph); | |
iov[1].iov_base = (void *)data; | |
iov[1].iov_len = nbytes; | |
return in_cksumv(iov, array_size(iov)); | |
} | |
enum ipaddr_type_t { | |
IPADDR_NONE = AF_UNSPEC, | |
IPADDR_V4 = AF_INET, | |
IPADDR_V6 = AF_INET6, | |
}; | |
struct ipaddr { | |
enum ipaddr_type_t ipa_type; | |
union { | |
uint8_t addr; | |
struct in_addr _v4_addr; | |
struct in6_addr _v6_addr; | |
} ip; | |
#define ipaddr_v4 ip._v4_addr | |
#define ipaddr_v6 ip._v6_addr | |
}; | |
#define IPPROTO_VRRP 112 | |
struct vrrp_hdr { | |
uint8_t vertype; | |
uint8_t vrid; | |
uint8_t priority; | |
uint8_t naddr; | |
union { | |
struct { | |
uint8_t auth_type; | |
uint8_t adver_int; | |
} v2; | |
struct { | |
uint16_t adver_int; | |
} v3; | |
}; | |
uint16_t chksum; | |
} __attribute__((packed)); | |
struct vrrp_pkt { | |
struct vrrp_hdr hdr; | |
union { | |
struct in_addr v4; | |
struct in6_addr v6; | |
} addrs[]; | |
} __attribute__((packed)); | |
#define VRRP_MCASTV4_GROUP_STR "224.0.0.18" | |
#define VRRP_MCASTV6_GROUP_STR "ff02:0:0:0:0:0:0:12" | |
#define DATA_LENGTH(x) (sizeof(x) - 1) | |
#define FRR_VRRP_MESSAGE "\x31\x32\xbe\x01\x00\x64\x86\x83\xc0\xa8\x14\x03" | |
#define FRR_SOURCE "192.168.20.1" | |
#define CISCO_VRRP_MESSAGE "\x31\x32\x64\x01\x00\xc8\x95\x58\xc0\xa8\x14\x03" | |
#define CISCO_SOURCE "192.168.20.4" | |
struct test { | |
const char *name; | |
const char *message; | |
size_t length; | |
const char *source; | |
} tests[] = { | |
{ "FRR", FRR_VRRP_MESSAGE, DATA_LENGTH(FRR_VRRP_MESSAGE), FRR_SOURCE }, | |
{ "CISCO", CISCO_VRRP_MESSAGE, DATA_LENGTH(CISCO_VRRP_MESSAGE), CISCO_SOURCE }, | |
}; | |
int main() { | |
for (int i = 0; i < array_size(tests); i++) { | |
const struct test *t = &tests[i]; | |
struct vrrp_pkt *pkt = malloc(t->length); | |
memcpy(pkt, t->message, t->length); | |
struct in_addr src; | |
inet_aton(t->source, &src); | |
uint16_t real_chksum = pkt->hdr.chksum; | |
pkt->hdr.chksum = 0; | |
// Checksum WITHOUT pseudo-header | |
uint16_t chksum_without_ph = in_cksum(pkt, t->length); | |
// Checksum WITH pseudo-header | |
uint16_t chksum_with_ph; | |
// https://github.com/FRRouting/frr/blob/master/vrrpd/vrrp_packet.c | |
struct ipv4_ph ph = {}; | |
ph.src = src; | |
inet_pton(AF_INET, VRRP_MCASTV4_GROUP_STR, &ph.dst); | |
ph.proto = IPPROTO_VRRP; | |
ph.len = htons(t->length); | |
chksum_with_ph = in_cksum_with_ph4(&ph, pkt, t->length); | |
if (real_chksum == chksum_without_ph) { | |
printf("%s (%s) checksum method: WITHOUT PH\n", t->name, t->source); | |
} else if (real_chksum == chksum_with_ph) { | |
printf("%s (%s) checksum method: WITH PH\n", t->name, t->source); | |
} else { | |
printf("%s (%s) checksum method: N/A\n", t->name, t->source); | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment