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