Download | Plain Text | Line Numbers
/*
* ----------[ the problem ]----------
* This small example calculates/verifies the checksum for VRRP3 IPv4 packets
* as described in RFC 5798 section 5.2.8 using a IPV4 pseudo header
* see vrrp3_checksum(...)
*
* Additional it calculates the *different* checksum for VRRP3 IPv4 packets
* JunOS is using. This shows JunOS is doing a simple ip checksum without
* using any pseudo header at all (like in VRRP2).
* see vrrp2_checksum(...)
*
* NOTE: JunOS VRRP3 checksum is correct for IPv6 packets (see other notes).
*
* ----------[ differences in pseudo code ]----------
* Correct VRRP flow:
* if VRRP2:
* sum = RFC1071_checksum(vrrp_data)
* else VRRP3_IPv4:
* sum = RFC1071_checksum(ipv4_pseudoheader + vrrp_data)
* else if VRRP3_IPv6:
* sum = RFC1071_checksum(ipv6_pseudoheader + vrrp_data)
*
* JunOS flow:
* if VRRP2 or VRRP3_IPv4:
* sum = RFC1071_checksum(vrrp_data)
* else if VRRP3_IPv6:
* sum = RFC1071_checksum(ipv6_pseudoheader + vrrp_data)
*
* ----------[ reproduce ]----------
* $ set protocol vrrp version-3
* $ set interfaces irb.1 family inet address 10.1.0.10/24 vrrp-group 2 virtual-address 10.1.0.1
*
* ----------[ other notes ]----------
* Before JunOS Release 12.2, JunOS was doing a checksum without pseudo header
* for IPv6 packets as well. This changed in order to comply to the RFC.
* Additional a configuration knob "protocols vrrp checksum-without-pseudoheader"
* was introduced for interoperability to older JunOS releases.
* see http://www.juniper.net/techpubs/en_US/junos13.3/topics/concept/vrrpv3-junos-support.html
*
* ----------[ proposal ]----------
* - fix the VRRP3 IP4 implementation to comply to the RFC
* - add an additional knob: protocols vrrp checksum-without-ipv4-pseudoheader
*/
#include <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
// from https://www.ietf.org/rfc/rfc1071.txt
// section 4.1
uint16_t ip_checksum(unsigned short *addr, size_t count)
{
/* Compute Internet Checksum for "count" bytes
* beginning at location "addr".
*/
register long sum = 0;
while (count > 1)
{
/* This is the inner loop */
sum += *addr++;
count -= 2;
}
/* Add left-over byte, if any */
if (count > 0)
sum += *(unsigned char *)addr;
/* Fold 32-bit sum to 16 bits */
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
return ~sum;
}
// from RFC 5798 section 5.2.8
// http://tools.ietf.org/html/rfc5798#section-5.2.8
//
// The checksum is the 16-bit one's complement of the one's complement
// sum of the entire VRRP message starting with the version field
// =====> and a "pseudo-header" <====
// as defined in Section 8.1 of [RFC2460]. The next
// header field in the "pseudo-header" should be set to 112 (decimal)
// for VRRP. For computing the checksum, the checksum field is set to
// zero. See RFC1071 for more detail [RFC1071].
uint16_t vrrp3_checksum(uint32_t src_ip, uint32_t dst_ip,
unsigned short *data, size_t data_len)
{
uint8_t proto = 112;
uint16_t sum;
// allocate buffer to prepend ipv4 pseudo header
// see http://en.wikipedia.org/wiki/User_Datagram_Protocol#IPv4_Pseudo_Header
unsigned buf_len = sizeof(unsigned short) * 6 + data_len;
unsigned short *buf = malloc(buf_len);
buf[0] = (src_ip >> 16);
buf[1] = (src_ip & 0xffff);
buf[2] = (dst_ip >> 16);
buf[3] = (dst_ip & 0xffff);
buf[4] = proto;
buf[5] = data_len;
// copy VRRP packet payload to buffer
memcpy(buf + 6, data, data_len);
// calculate checksum
sum = ip_checksum(buf, buf_len);
free(buf);
return sum;
}
// VRRP2 checksum = ip checksum
uint16_t vrrp2_checksum(unsigned short *data, size_t data_len)
{
return ip_checksum(data, data_len);
}
int main()
{
// make sure the checksum implementation is sane
// example from http://en.wikipedia.org/wiki/IPv4_header_checksum
unsigned short packet_test[] = {
0x4500, 0x0073, 0x0000, 0x4000, 0x4011, 0xb861, 0xc0a8, 0x0001, 0xc0a8, 0x00c7
};
assert(ip_checksum(packet_test, sizeof(packet_test)) == 0);
// example VRRP3 IPv4 packet from MikroTik
unsigned short vrrp3_mikrotik[] = {
0x3102, 0x6401, 0x0064, 0x75fb, 0x0a01, 0x0001
};
// example VRRP2 packet from JunOS
unsigned short vrrp2_junos[] = {
0x210a, 0xc801, 0x0001, 0x0cf1, 0x0a01, 0x0001
};
// example VRRP3 IPv4 packet from JunOS
unsigned short vrrp3_junos[] = {
0x3102, 0x6401, 0x0064, 0x6096, 0x0a01, 0x0001
};
// IP adresses of both routers
uint32_t ip_junos = 0x0a01000a; // 10.1.0.10
uint32_t ip_mikrotik = 0x0a01000b; // 10.1.0.11
uint32_t dst_ip = 0xe0000012; // 224.0.0.18
// verify MikroTik checksum is correct according to RFC 5798
assert(vrrp3_checksum(ip_mikrotik, dst_ip, vrrp3_mikrotik,
sizeof(vrrp3_mikrotik)) == 0);
// verify VRRP2 checksum is correct in JunOS
assert(vrrp2_checksum(vrrp2_junos, sizeof(vrrp2_junos)) == 0);
// show JunOS is doing a VRRP2 checksum for VRRP3 IPv4 packets
assert(vrrp2_checksum(vrrp3_junos, sizeof(vrrp3_junos)) == 0);
// show that both checksum methods *are* incompatible
assert(vrrp3_checksum(ip_junos, dst_ip, vrrp3_junos,
sizeof(vrrp3_junos)) != 0);
assert(vrrp2_checksum(vrrp3_mikrotik, sizeof(vrrp3_mikrotik)) != 0);
return 0;
}