vrrp: add plugin providing vrrp support 81/24981/7
authorMatthew Smith <mgsmith@netgate.com>
Tue, 11 Feb 2020 17:25:32 +0000 (11:25 -0600)
committerDave Barach <openvpp@barachs.net>
Thu, 13 Feb 2020 19:46:30 +0000 (19:46 +0000)
Type: feature

Add a new plugin to support HA using VRRPv3 (RFC 5798).

Change-Id: Iaa2c37e6172f8f41e9165f178f44d481f6e247b9
Signed-off-by: Matthew Smith <mgsmith@netgate.com>
19 files changed:
MAINTAINERS
src/plugins/vrrp/CMakeLists.txt [new file with mode: 0644]
src/plugins/vrrp/FEATURE.yaml [new file with mode: 0644]
src/plugins/vrrp/node.c [new file with mode: 0644]
src/plugins/vrrp/setup.pg [new file with mode: 0644]
src/plugins/vrrp/test/test_vrrp.py [new file with mode: 0644]
src/plugins/vrrp/vrrp.api [new file with mode: 0644]
src/plugins/vrrp/vrrp.c [new file with mode: 0644]
src/plugins/vrrp/vrrp.h [new file with mode: 0644]
src/plugins/vrrp/vrrp_all_api_h.h [new file with mode: 0644]
src/plugins/vrrp/vrrp_api.c [new file with mode: 0644]
src/plugins/vrrp/vrrp_cli.c [new file with mode: 0644]
src/plugins/vrrp/vrrp_format.c [new file with mode: 0644]
src/plugins/vrrp/vrrp_msg_enum.h [new file with mode: 0644]
src/plugins/vrrp/vrrp_packet.c [new file with mode: 0644]
src/plugins/vrrp/vrrp_packet.h [new file with mode: 0644]
src/plugins/vrrp/vrrp_periodic.c [new file with mode: 0644]
src/plugins/vrrp/vrrp_test.c [new file with mode: 0644]
test/patches/scapy-2.4.3/vrrp.patch [new file with mode: 0644]

index bb86fd4..2c17b08 100644 (file)
@@ -649,6 +649,11 @@ Awkward chained buffer geometry tool
 I:     oddbuf
 F:     src/plugins/oddbuf
 
+Plugin - VRRP
+I:     vrrp
+M:     Matthew Smith <mgsmith@netgate.com>
+F:     src/plugins/vrrp
+
 VPP Config Tooling
 I:     vpp_config
 M:     John DeNisco <jdenisco@cisco.com>
diff --git a/src/plugins/vrrp/CMakeLists.txt b/src/plugins/vrrp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..21715d2
--- /dev/null
@@ -0,0 +1,28 @@
+#
+# Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+add_vpp_plugin(vrrp
+  SOURCES
+  vrrp.c 
+  vrrp_api.c
+  vrrp_cli.c
+  vrrp_format.c
+  node.c 
+  vrrp_packet.c
+  vrrp_periodic.c
+
+  MULTIARCH_SOURCES
+  node.c 
+
+  API_FILES
+  vrrp.api
+
+  INSTALL_HEADERS
+  vrrp.h
+
+  API_TEST_SOURCES
+  vrrp_test.c
+)
diff --git a/src/plugins/vrrp/FEATURE.yaml b/src/plugins/vrrp/FEATURE.yaml
new file mode 100644 (file)
index 0000000..d92e97e
--- /dev/null
@@ -0,0 +1,23 @@
+---
+name: Virtual Router Redundancy Protocol
+maintainer: Matthew Smith <mgsmith@netgate.com>
+features:
+  - VRRPv3 (RFC 5798) for IPv4 and IPv6:
+      - Signaling/advertisements and election of a master
+      - Replies to ARP, NS requests for virtual router addresses
+  - VRRP virtual MAC address support:
+      - DPDK interfaces with PMD support for multiple MAC addresses via the
+        rte_eth_dev_mac_addr_add(), rte_eth_dev_mac_addr_del()
+      - Other interfaces which are set in promiscuous mode may work
+  - Support interface types for VRRP virtual routers:
+      - Hardware interfaces
+      - VLAN subinterfaces
+      - Bond interfaces
+  - Additional features not specified in RFC 5798:
+      - Allows sending advertisements to unicast peers instead of multicast
+      - Allows a virtual router's priority to be adjusted based on the state
+        of an upstream interface. Mentioned as a configuration option to
+        "track interfaces or networks" in RFC 8347.
+description: "Virtual Router Redundancy Protocol implementation (VRRPv3)"
+state: production
+properties: [API, CLI, STATS, MULTITHREAD]
diff --git a/src/plugins/vrrp/node.c b/src/plugins/vrrp/node.c
new file mode 100644 (file)
index 0000000..b819919
--- /dev/null
@@ -0,0 +1,753 @@
+/*
+ * node.c - vrrp packet handling node definitions
+ *
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+#include <vlib/vlib.h>
+#include <vlibmemory/api.h>
+#include <vnet/vnet.h>
+#include <vnet/ip/ip4_packet.h>
+#include <vnet/ip/ip6_link.h>
+#include <vnet/ethernet/arp_packet.h>
+#include <vnet/pg/pg.h>
+#include <vppinfra/error.h>
+#include <vrrp/vrrp.h>
+#include <vrrp/vrrp_packet.h>
+
+typedef struct
+{
+  u32 sw_if_index;
+  u8 is_ipv6;
+  vrrp_header_t vrrp;
+  u8 addrs[256];               /* print up to 64 IPv4 or 16 IPv6 addresses */
+} vrrp_trace_t;
+
+/* packet trace format function */
+static u8 *
+format_vrrp_trace (u8 * s, va_list * args)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
+  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+  vrrp_trace_t *t = va_arg (*args, vrrp_trace_t *);
+  int i;
+
+  s = format (s, "VRRP: sw_if_index %d IPv%d\n",
+             t->sw_if_index, (t->is_ipv6) ? 6 : 4);
+  s = format (s, "    %U\n", format_vrrp_packet_hdr, &t->vrrp);
+  s = format (s, "    addresses: ");
+
+  for (i = 0; i < t->vrrp.n_addrs; i++)
+    {
+      if (t->is_ipv6)
+       s = format (s, "%U ", format_ip6_address,
+                   (ip6_address_t *) (t->addrs + i * 16));
+      else
+       s = format (s, "%U ", format_ip4_address,
+                   (ip4_address_t *) (t->addrs + i * 4));
+    }
+
+  return s;
+}
+
+extern vlib_node_registration_t vrrp4_input_node;
+extern vlib_node_registration_t vrrp6_input_node;
+extern vlib_node_registration_t vrrp4_arp_input_node;
+extern vlib_node_registration_t vrrp6_nd_input_node;
+
+#define foreach_vrrp_error                                       \
+_(RECEIVED, "VRRP packets processed")                            \
+_(BAD_TTL, "VRRP advertisement TTL is not 255")                          \
+_(NOT_VERSION_3, "VRRP version is not 3")                        \
+_(INCOMPLETE_PKT, "VRRP packet has wrong size")                          \
+_(BAD_CHECKSUM, "VRRP checksum is invalid")                      \
+_(UNKNOWN_VR, "VRRP message does not match known VRs")           \
+_(ADDR_MISMATCH, "VR addrs do not match configuration")
+
+typedef enum
+{
+#define _(sym,str) VRRP_ERROR_##sym,
+  foreach_vrrp_error
+#undef _
+    VRRP_N_ERROR,
+} vrrp_error_t;
+
+static char *vrrp_error_strings[] = {
+#define _(sym,string) string,
+  foreach_vrrp_error
+#undef _
+};
+
+typedef enum
+{
+  VRRP_INPUT_NEXT_DROP,
+  VRRP_INPUT_N_NEXT,
+} vrrp_next_t;
+
+typedef struct vrrp_input_process_args
+{
+  u32 vr_index;
+  vrrp_header_t *pkt;
+} vrrp_input_process_args_t;
+
+/* Given a VR and a pointer to the VRRP header of an incoming packet,
+ * compare the local src address to the peers. Return < 0 if the local
+ * address < the peer address, 0 if they're equal, > 0 if
+ * the local address > the peer address
+ */
+static int
+vrrp_vr_addr_cmp (vrrp_vr_t * vr, vrrp_header_t * pkt)
+{
+  vrrp_vr_config_t *vrc = &vr->config;
+  void *peer_addr, *local_addr;
+  ip46_address_t addr;
+  int addr_size;
+
+  clib_memset (&addr, 0, sizeof (addr));
+
+  if (vrrp_vr_is_ipv6 (vr))
+    {
+      peer_addr = &(((ip6_header_t *) pkt) - 1)->src_address;
+      local_addr = &addr.ip6;
+      addr_size = 16;
+      ip6_address_copy (local_addr,
+                       ip6_get_link_local_address (vrc->sw_if_index));
+    }
+  else
+    {
+      peer_addr = &(((ip4_header_t *) pkt) - 1)->src_address;
+      local_addr = &addr.ip4;
+      addr_size = 4;
+      ip4_src_address_for_packet (&ip4_main.lookup_main,
+                                 vrc->sw_if_index, local_addr);
+    }
+
+  return memcmp (local_addr, peer_addr, addr_size);
+}
+
+static void
+vrrp_input_process_master (vrrp_vr_t * vr, vrrp_header_t * pkt)
+{
+  /* received priority 0, another VR is shutting down. send an adv and
+   * remain in the master state
+   */
+  if (pkt->priority == 0)
+    {
+      clib_warning ("Received shutdown message from a peer on VR %U",
+                   format_vrrp_vr_key, vr);
+      vrrp_adv_send (vr, 0);
+      vrrp_vr_timer_set (vr, VRRP_VR_TIMER_ADV);
+      return;
+    }
+
+  /* if either:
+   * - received priority > adjusted priority, or
+   * - received priority == adjusted priority and peer addr > local addr
+   * allow the local VR to be preempted by the peer
+   */
+  if ((pkt->priority > vrrp_vr_priority (vr)) ||
+      ((pkt->priority == vrrp_vr_priority (vr)) &&
+       (vrrp_vr_addr_cmp (vr, pkt) < 0)))
+    {
+      vrrp_vr_transition (vr, VRRP_VR_STATE_BACKUP, pkt);
+
+      return;
+    }
+
+  /* if we made it this far, eiher received prority < adjusted priority or
+   * received == adjusted and local addr > peer addr. Ignore.
+   */
+  return;
+}
+
+/* RFC 5798 section 6.4.2 */
+static void
+vrrp_input_process_backup (vrrp_vr_t * vr, vrrp_header_t * pkt)
+{
+  vrrp_vr_config_t *vrc = &vr->config;
+  vrrp_vr_runtime_t *vrt = &vr->runtime;
+
+  /* master shutting down, ready for election */
+  if (pkt->priority == 0)
+    {
+      clib_warning ("Master for VR %U is shutting down", format_vrrp_vr_key,
+                   vr);
+      vrt->master_down_int = vrt->skew;
+      vrrp_vr_timer_set (vr, VRRP_VR_TIMER_MASTER_DOWN);
+      return;
+    }
+
+  /* no preempt set or adv from a higher priority router, update timers */
+  if (!(vrc->flags & VRRP_VR_PREEMPT) ||
+      (pkt->priority >= vrrp_vr_priority (vr)))
+    {
+      vrt->master_adv_int = clib_net_to_host_u16 (pkt->rsvd_and_max_adv_int);
+      vrt->master_adv_int &= ((u16) 0x0fff);   /* ignore rsvd bits */
+
+      vrrp_vr_skew_compute (vr);
+      vrrp_vr_master_down_compute (vr);
+      vrrp_vr_timer_set (vr, VRRP_VR_TIMER_MASTER_DOWN);
+      return;
+    }
+
+  /* preempt set or our priority > received, continue to wait on master down */
+  return;
+}
+
+always_inline void
+vrrp_input_process (vrrp_input_process_args_t * args)
+{
+  vrrp_vr_t *vr;
+
+  vr = vrrp_vr_lookup_index (args->vr_index);
+
+  if (!vr)
+    {
+      clib_warning ("Error retrieving VR with index %u", args->vr_index);
+      return;
+    }
+
+  switch (vr->runtime.state)
+    {
+    case VRRP_VR_STATE_INIT:
+      return;
+    case VRRP_VR_STATE_BACKUP:
+      /* this is usually the only state an advertisement should be received */
+      vrrp_input_process_backup (vr, args->pkt);
+      break;
+    case VRRP_VR_STATE_MASTER:
+      /* might be getting preempted. or have a misbehaving peer */
+      clib_warning ("Received advertisement for master VR %U",
+                   format_vrrp_vr_key, vr);
+      vrrp_input_process_master (vr, args->pkt);
+      break;
+    default:
+      clib_warning ("Received advertisement for VR %U in unknown state %d",
+                   format_vrrp_vr_key, vr, vr->runtime.state);
+      break;
+    }
+
+  return;
+}
+
+typedef struct
+{
+  ip46_address_t ip;
+  u32 vr_index;
+  u8 vr_id;
+  u8 is_ipv6;
+} vrrp_arp_nd_trace_t;
+
+
+static u8 *
+format_vrrp_arp_nd_input_trace (u8 * s, va_list * va)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *);
+  CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *);
+  vrrp_arp_nd_trace_t *t = va_arg (*va, vrrp_arp_nd_trace_t *);
+
+  s = format (s, "address %U",
+             (t->is_ipv6) ? format_ip6_address : format_ip4_address,
+             (t->is_ipv6) ? (void *) &t->ip.ip6 : (void *) &t->ip.ip4);
+
+  if (t->vr_index != ~0)
+    s = format (s, ": vr_index %u vr_id %u", t->vr_index, t->vr_id);
+
+  return s;
+}
+
+typedef enum
+{
+  VRRP_ARP_INPUT_NEXT_DROP,
+  VRRP_ARP_INPUT_NEXT_REPLY_TX,
+  VRRP_ARP_N_NEXT,
+} vrrp_arp_next_t;
+
+typedef enum
+{
+  VRRP_ND_INPUT_NEXT_DROP,
+  VRRP_ND_INPUT_NEXT_REPLY_TX,
+  VRRP_ND_N_NEXT,
+} vrrp_nd_next_t;
+
+static_always_inline void
+vrrp_arp_nd_next (vlib_buffer_t * b, u32 * next_index, u32 * vr_index,
+                 u8 is_ipv6)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  vlib_main_t *vm = vlib_get_main ();
+  ethernet_header_t *eth, *eth_new;
+  void *lookup_addr = 0;
+  vrrp_vr_t *vr;
+  u32 sw_if_index;
+  vnet_link_t link_type;
+  u8 *rewrite, rewrite_len;
+  int bogus_length;
+  /* ND vars */
+  ip6_header_t *ip6 = 0;
+  icmp6_neighbor_solicitation_or_advertisement_header_t *sol_adv = 0;
+  icmp6_neighbor_discovery_ethernet_link_layer_address_option_t *lladdr = 0;
+  /* ARP vars */
+  ethernet_arp_header_t *arp;
+  ip4_address_t ip4_addr;
+
+  if (is_ipv6)
+    {
+      ip6 = vlib_buffer_get_current (b);
+
+      /* we only care about about ICMP6 neighbor solicitiations */
+      if (ip6->protocol != IP_PROTOCOL_ICMP6)
+       return;
+
+      sol_adv = ip6_next_header (ip6);
+      lladdr = (void *) (sol_adv + 1);
+
+      /* skip anything other than neighbor solicitations */
+      if (sol_adv->icmp.type != ICMP6_neighbor_solicitation)
+       return;
+
+      lookup_addr = &sol_adv->target_address;
+      link_type = VNET_LINK_IP6;
+    }
+  else
+    {
+      arp = vlib_buffer_get_current (b);
+
+      /* skip non-request packets */
+      if (arp->opcode != clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_request))
+       return;
+
+      lookup_addr = &arp->ip4_over_ethernet[1].ip4;
+      link_type = VNET_LINK_ARP;
+    }
+
+  sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
+
+  /* Don't bother with a hash lookup if no VRs configured on this interface */
+  if (!vrrp_intf_num_vrs (sw_if_index, is_ipv6))
+    return;
+
+  /* skip requests that are not for VRRP addresses */
+  *vr_index = vrrp_vr_lookup_address (sw_if_index, is_ipv6, lookup_addr);
+  if (*vr_index == ~0)
+    return;
+
+  /* only reply if the VR is in the master state */
+  vr = vrrp_vr_lookup_index (*vr_index);
+  if (!vr || vr->runtime.state != VRRP_VR_STATE_MASTER)
+    return;
+
+  eth = ethernet_buffer_get_header (b);
+  rewrite = ethernet_build_rewrite (vnm, sw_if_index, link_type,
+                                   eth->src_address);
+  rewrite_len = vec_len (rewrite);
+  if (rewrite_len == 0)
+    return;
+
+  /* send the reply out the incoming interface */
+  *next_index = VRRP_ARP_INPUT_NEXT_REPLY_TX;
+  vnet_buffer (b)->sw_if_index[VLIB_TX] = sw_if_index;
+
+  /* the outbound ethernet & vlan headers may have a different length than
+   * the received header, so get a pointer to the new start of the packet
+   * and write the header there.
+   */
+  vlib_buffer_advance (b, -rewrite_len);
+  eth_new = vlib_buffer_get_current (b);
+  clib_memcpy_fast (eth_new, rewrite, rewrite_len);
+  vec_free (rewrite);
+
+  if (is_ipv6)
+    {
+      if (ip6_address_is_unspecified (&ip6->src_address))
+       ip6_set_reserved_multicast_address (&ip6->dst_address,
+                                           IP6_MULTICAST_SCOPE_link_local,
+                                           IP6_MULTICAST_GROUP_ID_all_hosts);
+      else
+       ip6->dst_address = ip6->src_address;
+
+      ip6->src_address = sol_adv->target_address;
+      ip6->hop_limit = 255;
+      sol_adv->icmp.type = ICMP6_neighbor_advertisement;
+      sol_adv->icmp.checksum = 0;
+      sol_adv->advertisement_flags =
+       clib_host_to_net_u32 (ICMP6_NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER
+                             | ICMP6_NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
+                             | ICMP6_NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE);
+
+      clib_memcpy (lladdr->ethernet_address, vr->runtime.mac.bytes,
+                  sizeof (mac_address_t));
+      lladdr->header.type =
+       ICMP6_NEIGHBOR_DISCOVERY_OPTION_target_link_layer_address;
+
+      sol_adv->icmp.checksum =
+       ip6_tcp_udp_icmp_compute_checksum (vm, b, ip6, &bogus_length);
+
+    }
+  else
+    {
+      ip4_addr = arp->ip4_over_ethernet[1].ip4;
+
+      arp->opcode = clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply);
+      arp->ip4_over_ethernet[1] = arp->ip4_over_ethernet[0];
+
+      arp->ip4_over_ethernet[0].mac = vr->runtime.mac;
+      arp->ip4_over_ethernet[0].ip4 = ip4_addr;
+    }
+}
+
+static_always_inline uword
+vrrp_arp_nd_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
+                         vlib_frame_t * frame, u8 is_ipv6)
+{
+  u32 n_left_from, *from, next_index, *to_next;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  next_index = node->cached_next_index;
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+
+         vlib_buffer_t *b0;
+         u32 bi0;
+         u32 next0;
+         u32 vr_index = ~0;
+
+         bi0 = from[0];
+         to_next[0] = bi0;
+         from += 1;
+         to_next += 1;
+         n_left_from -= 1;
+         n_left_to_next -= 1;
+
+         b0 = vlib_get_buffer (vm, bi0);
+
+         vnet_feature_next (&next0, b0);
+         vrrp_arp_nd_next (b0, &next0, &vr_index, is_ipv6);
+
+         if (b0->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             vrrp_arp_nd_trace_t *t =
+               vlib_add_trace (vm, node, b0, sizeof (*t));
+             vrrp_vr_t *vr;
+
+             if (is_ipv6)
+               {
+                 ip6_header_t *ip0;
+                 icmp6_neighbor_solicitation_or_advertisement_header_t
+                   * sol_adv0;
+
+                 ip0 = vlib_buffer_get_current (b0);
+                 sol_adv0 = ip6_next_header (ip0);
+                 t->ip.ip6 = sol_adv0->target_address;
+               }
+             else
+               {
+                 ethernet_arp_header_t *arp0;
+
+                 arp0 = vlib_buffer_get_current (b0);
+                 t->ip.ip4 = arp0->ip4_over_ethernet[0].ip4;
+               }
+
+             vr = vrrp_vr_lookup_index (vr_index);
+             if (vr)
+               t->vr_id = vr->config.vr_id;
+
+             t->vr_index = vr_index;
+             t->is_ipv6 = is_ipv6;
+           }
+
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
+                                          n_left_to_next, bi0, next0);
+       }
+
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  return frame->n_vectors;
+}
+
+VLIB_NODE_FN (vrrp4_arp_input_node) (vlib_main_t * vm,
+                                    vlib_node_runtime_t * node,
+                                    vlib_frame_t * frame)
+{
+  return vrrp_arp_nd_input_inline (vm, node, frame, 0 /* is_ipv6 */ );
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (vrrp4_arp_input_node) =
+{
+  .name = "vrrp4-arp-input",
+  .vector_size = sizeof (u32),
+  .format_trace = format_vrrp_arp_nd_input_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN(vrrp_error_strings),
+  .error_strings = vrrp_error_strings,
+
+  .n_next_nodes = VRRP_ARP_N_NEXT,
+
+  .next_nodes = {
+        [VRRP_ARP_INPUT_NEXT_DROP] = "error-drop",
+        [VRRP_ARP_INPUT_NEXT_REPLY_TX] = "interface-output",
+  },
+};
+
+VNET_FEATURE_INIT (vrrp4_arp_feat_node, static) =
+{
+  .arc_name = "arp",
+  .node_name = "vrrp4-arp-input",
+  .runs_before = VNET_FEATURES ("arp-reply"),
+};
+
+VLIB_NODE_FN (vrrp6_nd_input_node) (vlib_main_t * vm,
+                                    vlib_node_runtime_t * node,
+                                    vlib_frame_t * frame)
+{
+  return vrrp_arp_nd_input_inline (vm, node, frame, 1 /* is_ipv6 */);
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (vrrp6_nd_input_node) =
+{
+  .name = "vrrp6-nd-input",
+  .vector_size = sizeof (u32),
+  .format_trace = format_vrrp_arp_nd_input_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN(vrrp_error_strings),
+  .error_strings = vrrp_error_strings,
+
+  .n_next_nodes = VRRP_ND_N_NEXT,
+
+  .next_nodes = {
+        [VRRP_ND_INPUT_NEXT_DROP] = "error-drop",
+        [VRRP_ND_INPUT_NEXT_REPLY_TX] = "interface-output",
+  },
+};
+
+VNET_FEATURE_INIT (vrrp6_nd_feat_node, static) =
+{
+  .arc_name = "ip6-local",
+  .node_name = "vrrp6-nd-input",
+  .runs_before = VNET_FEATURES ("ip6-local-end-of-arc"),
+};
+
+static_always_inline uword
+vrrp_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
+                   vlib_frame_t * frame, u8 is_ipv6)
+{
+  u32 n_left_from, *from;
+  vrrp_main_t *vmp = &vrrp_main;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+
+  while (n_left_from > 0)
+    {
+      u32 bi0;
+      vlib_buffer_t *b0;
+      u32 next0, error0;
+      void *ip0;
+      vrrp_header_t *vrrp0;
+      vrrp_vr_t *vr0;
+      vrrp_input_process_args_t args0;
+      u8 *ttl0;
+      u16 rx_csum0;
+      u16 payload_len0;
+      int addr_len;
+
+      bi0 = from[0];
+      b0 = vlib_get_buffer (vm, bi0);
+
+      ip0 = vlib_buffer_get_current (b0);
+
+      if (is_ipv6)
+       {
+         ip6_header_t *ip6 = ip0;
+
+         vrrp0 = (vrrp_header_t *) (ip6 + 1);
+         ttl0 = &ip6->hop_limit;
+         addr_len = 16;
+         payload_len0 = clib_net_to_host_u16 (ip6->payload_length);
+         vlib_buffer_advance (b0, sizeof (*ip6));
+       }
+      else
+       {
+         ip4_header_t *ip4 = ip0;
+
+         vrrp0 = (vrrp_header_t *) (ip4 + 1);
+         ttl0 = &ip4->ttl;
+         addr_len = 4;
+         payload_len0 = clib_net_to_host_u16 (ip4->length) - sizeof(*ip4);
+         vlib_buffer_advance (b0, sizeof (*ip4));
+       }
+
+      next0 = VRRP_INPUT_NEXT_DROP;
+
+      error0 = VRRP_ERROR_RECEIVED;
+
+      /* Validation from RFC 5798 sec 7.1 */
+
+      /* checksum set to 0 for calculation, save original value */
+      rx_csum0 = vrrp0->checksum;
+      vrrp0->checksum = 0;
+
+      /* Mandatory - TTL/hop limit must be 255 */
+      if (*ttl0 != 255)
+       {
+         error0 = VRRP_ERROR_BAD_TTL;
+         goto trace;
+       }
+
+      /* Mandatory - VRRP version must be 3 */
+      if ((vrrp0->vrrp_version_and_type >> 4) != 3)
+       {
+         error0 = VRRP_ERROR_NOT_VERSION_3;
+         goto trace;
+       }
+
+      /* Mandatory - packet must be complete */
+      if (b0->current_length < sizeof (*vrrp0) + vrrp0->n_addrs * addr_len)
+       {
+         error0 = VRRP_ERROR_INCOMPLETE_PKT;
+         goto trace;
+       }
+
+      /* Mandatory - checksum must be correct */
+      if (rx_csum0 != vrrp_adv_csum (ip0, vrrp0, is_ipv6, payload_len0))
+       {
+         error0 = VRRP_ERROR_BAD_CHECKSUM;
+         goto trace;
+       }
+
+      /* Mandatory - VR must be configured on the interface adv received on */
+      if (!(vr0 =
+             vrrp_vr_lookup (vnet_buffer(b0)->sw_if_index[VLIB_RX],
+                             vrrp0->vr_id, is_ipv6)))
+       {
+         error0 = VRRP_ERROR_UNKNOWN_VR;
+         goto trace;
+       }
+
+      /* Optional - count of addresses should match configuration */
+      /* Could also check that addresses match, but likely to be O(n^2) */
+      if (vrrp0->n_addrs != vec_len (vr0->config.vr_addrs))
+       {
+         error0 = VRRP_ERROR_ADDR_MISMATCH;
+         goto trace;
+       }
+
+      /* signal main thread to process contents of packet */
+      args0.vr_index = vr0 - vmp->vrs;
+      args0.pkt = vrrp0;
+
+      vl_api_rpc_call_main_thread (vrrp_input_process, (u8 *) &args0,
+                                  sizeof (args0));
+
+    trace:
+      vrrp0->checksum = rx_csum0; /* restore csum for correct trace output */
+      b0->error = node->errors[error0];
+
+      if (b0->flags & VLIB_BUFFER_IS_TRACED)
+       {
+         vrrp_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t));
+
+         t->sw_if_index = vnet_buffer(b0)->sw_if_index[VLIB_RX];
+         t->is_ipv6 = is_ipv6;
+         clib_memcpy_fast (&t->vrrp, vrrp0, sizeof (*vrrp0));
+         clib_memcpy_fast (t->addrs, (void *) (vrrp0 + 1),
+                           vrrp0->n_addrs * (is_ipv6 ? 16 : 4));
+       }
+
+      /* always drop, never forward or reply here */
+      vlib_set_next_frame_buffer (vm, node, next0, bi0);
+
+      from += 1;
+      n_left_from -= 1;
+    }
+
+  return frame->n_vectors;
+}
+
+VLIB_NODE_FN (vrrp4_input_node) (vlib_main_t * vm, vlib_node_runtime_t * node,
+                               vlib_frame_t * frame)
+{
+  return vrrp_input_inline (vm, node, frame, 0);
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (vrrp4_input_node) =
+{
+  .name = "vrrp4-input",
+  .vector_size = sizeof (u32),
+  .format_trace = format_vrrp_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN(vrrp_error_strings),
+  .error_strings = vrrp_error_strings,
+
+  .n_next_nodes = VRRP_INPUT_N_NEXT,
+
+  .next_nodes = {
+        [VRRP_INPUT_NEXT_DROP] = "error-drop",
+  },
+};
+
+VLIB_NODE_FN (vrrp6_input_node) (vlib_main_t * vm, vlib_node_runtime_t * node,
+                               vlib_frame_t * frame)
+{
+  return vrrp_input_inline (vm, node, frame, 1);
+}
+
+VLIB_REGISTER_NODE (vrrp6_input_node) =
+{
+  .name = "vrrp6-input",
+  .vector_size = sizeof (u32),
+  .format_trace = format_vrrp_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN(vrrp_error_strings),
+  .error_strings = vrrp_error_strings,
+
+  .n_next_nodes = VRRP_INPUT_N_NEXT,
+
+  .next_nodes = {
+        [VRRP_INPUT_NEXT_DROP] = "error-drop",
+  },
+};
+
+static clib_error_t *
+vrrp_input_init (vlib_main_t *vm)
+{
+  clib_error_t *error;
+
+  if ((error = vlib_call_init_function (vm, vrrp_init)))
+    return error;
+
+  ip4_register_protocol (IP_PROTOCOL_VRRP, vrrp4_input_node.index);
+  ip6_register_protocol (IP_PROTOCOL_VRRP, vrrp6_input_node.index);
+
+  return 0;
+}
+
+VLIB_INIT_FUNCTION (vrrp_input_init);
+
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/vrrp/setup.pg b/src/plugins/vrrp/setup.pg
new file mode 100644 (file)
index 0000000..9275fcc
--- /dev/null
@@ -0,0 +1,20 @@
+
+comment { simple debug CLI setup script w/ packet generator test vector }
+set term page off
+loop create
+set int ip address loop0 192.168.1.1/24
+set int state loop0 up
+
+comment { Packet generator script. Src MAC 00:de:ad:be:ef:01 }
+comment { Dst mac 01:ba:db:ab:be:01 ethtype 0800 }
+packet-generator new {
+    name simple
+    limit 1
+    size 128-128
+    interface loop0
+    node vrrp
+    data {
+        hex 0x00deadbeef0001badbabbe010800 
+        incrementing 30
+    }
+}
diff --git a/src/plugins/vrrp/test/test_vrrp.py b/src/plugins/vrrp/test/test_vrrp.py
new file mode 100644 (file)
index 0000000..cc46a1d
--- /dev/null
@@ -0,0 +1,1288 @@
+#!/usr/bin/env python3
+
+#
+# Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+import unittest
+import time
+from socket import inet_pton, inet_ntop, AF_INET6
+
+from vpp_object import VppObject
+from vpp_papi import VppEnum
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, ARP
+from scapy.layers.inet import IP, ICMP, icmptypes
+from scapy.layers.inet6 import IPv6, ipv6nh, IPv6ExtHdrHopByHop, \
+    ICMPv6MLReport2, ICMPv6ND_NA, ICMPv6ND_NS, ICMPv6NDOptDstLLAddr, \
+    ICMPv6NDOptSrcLLAddr, ICMPv6EchoRequest, ICMPv6EchoReply
+from scapy.contrib.igmpv3 import IGMPv3, IGMPv3mr, IGMPv3gr
+from scapy.layers.vrrp import IPPROTO_VRRP, VRRPv3
+from scapy.utils6 import in6_getnsma, in6_getnsmac
+from framework import VppTestCase, VppTestRunner
+from util import ip6_normalize
+
+VRRP_VR_FLAG_PREEMPT = 1
+VRRP_VR_FLAG_ACCEPT = 2
+VRRP_VR_FLAG_UNICAST = 4
+VRRP_VR_FLAG_IPV6 = 8
+
+VRRP_VR_STATE_INIT = 0
+VRRP_VR_STATE_BACKUP = 1
+VRRP_VR_STATE_MASTER = 2
+VRRP_VR_STATE_INTF_DOWN = 3
+
+
+def is_non_arp(p):
+    """ Want to filter out advertisements, igmp, etc"""
+    if p.haslayer(ARP):
+        return False
+
+    return True
+
+
+def is_not_adv(p):
+    """ Filter out everything but advertisements. E.g. multicast RD/ND """
+    if p.haslayer(VRRPv3):
+        return False
+
+    return True
+
+
+def is_not_echo_reply(p):
+    """ filter out advertisements and other while waiting for echo reply """
+    if p.haslayer(IP) and p.haslayer(ICMP):
+        if icmptypes[p[ICMP].type] == "echo-reply":
+            return False
+    elif p.haslayer(IPv6) and p.haslayer(ICMPv6EchoReply):
+        return False
+
+    return True
+
+
+class VppVRRPVirtualRouter(VppObject):
+
+    def __init__(self,
+                 test,
+                 intf,
+                 vr_id,
+                 prio=100,
+                 intvl=100,
+                 flags=VRRP_VR_FLAG_PREEMPT,
+                 vips=None):
+        self._test = test
+        self._intf = intf
+        self._sw_if_index = self._intf.sw_if_index
+        self._vr_id = vr_id
+        self._prio = prio
+        self._intvl = intvl
+        self._flags = flags
+        if (flags & VRRP_VR_FLAG_IPV6):
+            self._is_ipv6 = 1
+            self._adv_dest_mac = "33:33:00:00:00:12"
+            self._virtual_mac = "00:00:5e:00:02:%02x" % vr_id
+            self._adv_dest_ip = "ff02::12"
+            self._vips = ([intf.local_ip6] if vips is None else vips)
+        else:
+            self._is_ipv6 = 0
+            self._adv_dest_mac = "01:00:5e:00:00:12"
+            self._virtual_mac = "00:00:5e:00:01:%02x" % vr_id
+            self._adv_dest_ip = "224.0.0.18"
+            self._vips = ([intf.local_ip4] if vips is None else vips)
+        self._tracked_ifs = []
+
+    def add_vpp_config(self):
+        self._test.vapi.vrrp_vr_add_del(is_add=1,
+                                        sw_if_index=self._intf.sw_if_index,
+                                        vr_id=self._vr_id,
+                                        priority=self._prio,
+                                        interval=self._intvl,
+                                        flags=self._flags,
+                                        n_addrs=len(self._vips),
+                                        addrs=self._vips)
+
+    def query_vpp_config(self):
+        vrs = self._test.vapi.vrrp_vr_dump(sw_if_index=self._intf.sw_if_index)
+        for vr in vrs:
+            if vr.config.vr_id != self._vr_id:
+                continue
+
+            is_ipv6 = (1 if (vr.config.flags & VRRP_VR_FLAG_IPV6) else 0)
+            if is_ipv6 != self._is_ipv6:
+                continue
+
+            return vr
+
+        return None
+
+    def remove_vpp_config(self):
+        self._test.vapi.vrrp_vr_add_del(is_add=0,
+                                        sw_if_index=self._intf.sw_if_index,
+                                        vr_id=self._vr_id,
+                                        priority=self._prio,
+                                        interval=self._intvl,
+                                        flags=self._flags,
+                                        n_addrs=len(self._vips),
+                                        addrs=self._vips)
+
+    def start_stop(self, is_start):
+        self._test.vapi.vrrp_vr_start_stop(is_start=is_start,
+                                           sw_if_index=self._intf.sw_if_index,
+                                           vr_id=self._vr_id,
+                                           is_ipv6=self._is_ipv6)
+        self._start_time = (time.time() if is_start else None)
+
+    def add_del_tracked_interface(self, is_add, sw_if_index, prio):
+        args = {
+            'sw_if_index': self._intf.sw_if_index,
+            'is_ipv6': self._is_ipv6,
+            'vr_id': self._vr_id,
+            'is_add': is_add,
+            'n_ifs': 1,
+            'ifs': [{'sw_if_index': sw_if_index, 'priority': prio}]
+        }
+        self._test.vapi.vrrp_vr_track_if_add_del(**args)
+        self._tracked_ifs.append(args['ifs'][0])
+
+    def set_unicast_peers(self, addrs):
+        args = {
+            'sw_if_index': self._intf.sw_if_index,
+            'is_ipv6': self._is_ipv6,
+            'vr_id': self._vr_id,
+            'n_addrs': len(addrs),
+            'addrs': addrs
+        }
+        self._test.vapi.vrrp_vr_set_peers(**args)
+        self._unicast_peers = addrs
+
+    def vrrp_adv_packet(self, prio=None, src_ip=None):
+        dst_ip = self._adv_dest_ip
+        if prio is None:
+            prio = self._prio
+        eth = Ether(dst=self._adv_dest_mac, src=self._virtual_mac)
+        vrrp = VRRPv3(vrid=self._vr_id, priority=prio,
+                      ipcount=len(self._vips), adv=self._intvl)
+        if self._is_ipv6:
+            src_ip = (self._intf.local_ip6_ll if src_ip is None else src_ip)
+            ip = IPv6(src=src_ip, dst=dst_ip, nh=IPPROTO_VRRP, hlim=255)
+            vrrp.addrlist = self._vips
+        else:
+            src_ip = (self._intf.local_ip4 if src_ip is None else src_ip)
+            ip = IP(src=src_ip, dst=dst_ip, proto=IPPROTO_VRRP, ttl=255, id=0)
+            vrrp.addrlist = self._vips
+
+        # Fill in default values & checksums
+        pkt = Ether(raw(eth / ip / vrrp))
+        return pkt
+
+    def start_time(self):
+        return self._start_time
+
+    def virtual_mac(self):
+        return self._virtual_mac
+
+    def virtual_ips(self):
+        return self._vips
+
+    def adv_dest_mac(self):
+        return self._adv_dest_mac
+
+    def adv_dest_ip(self):
+        return self._adv_dest_ip
+
+    def priority(self):
+        return self._prio
+
+    def vr_id(self):
+        return self._vr_id
+
+    def adv_interval(self):
+        return self._intvl
+
+    def interface(self):
+        return self._intf
+
+    def assert_state_equals(self, state):
+        vr_details = self.query_vpp_config()
+        self._test.assertEqual(vr_details.runtime.state, state)
+
+    def master_down_seconds(self):
+        vr_details = self.query_vpp_config()
+        return (vr_details.runtime.master_down_int * 0.01)
+
+
+class TestVRRP4(VppTestCase):
+    """ IPv4 VRRP Test Case """
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestVRRP4, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestVRRP4, cls).tearDownClass()
+
+    def setUp(self):
+        super(TestVRRP4, self).setUp()
+
+        self.create_pg_interfaces(range(2))
+
+        for i in self.pg_interfaces:
+            i.admin_up()
+            i.config_ip4()
+            i.generate_remote_hosts(5)
+            i.configure_ipv4_neighbors()
+
+        self._vrs = []
+        self._default_flags = VRRP_VR_FLAG_PREEMPT
+        self._default_adv = 100
+
+    def tearDown(self):
+        for vr in self._vrs:
+            try:
+                vr_api = vr.query_vpp_config()
+                if vr_api.runtime.state != VRRP_VR_STATE_INIT:
+                    vr.start_stop(is_start=0)
+                vr.remove_vpp_config()
+            except:
+                self.logger.error("Error cleaning up")
+
+        for i in self.pg_interfaces:
+            i.admin_down()
+            i.unconfig_ip4()
+            i.unconfig_ip6()
+
+        self._vrs = []
+
+        super(TestVRRP4, self).tearDown()
+
+    def verify_vrrp4_igmp(self, pkt):
+        ip = pkt[IP]
+        self.assertEqual(ip.dst, "224.0.0.22")
+        self.assertEqual(ip.proto, 2)
+
+        igmp = pkt[IGMPv3]
+        self.assertEqual(IGMPv3.igmpv3types[igmp.type],
+                         "Version 3 Membership Report")
+
+        igmpmr = pkt[IGMPv3mr]
+        self.assertEqual(igmpmr.numgrp, 1)
+        self.assertEqual(igmpmr.records[0].maddr, "224.0.0.18")
+
+    def verify_vrrp4_garp(self, pkt, vip, vmac):
+        arp = pkt[ARP]
+
+        # ARP "who-has" op == 1
+        self.assertEqual(arp.op, 1)
+        self.assertEqual(arp.pdst, arp.psrc)
+        self.assertEqual(arp.pdst, vip)
+        self.assertEqual(arp.hwsrc, vmac)
+
+    def verify_vrrp4_adv(self, rx_pkt, vr, prio=None):
+        vips = vr.virtual_ips()
+        eth = rx_pkt[Ether]
+        ip = rx_pkt[IP]
+        vrrp = rx_pkt[VRRPv3]
+
+        pkt = vr.vrrp_adv_packet(prio=prio)
+
+        # Source MAC is virtual MAC, destination is multicast MAC
+        self.assertEqual(eth.src, vr.virtual_mac())
+        self.assertEqual(eth.dst, vr.adv_dest_mac())
+
+        self.assertEqual(ip.dst, "224.0.0.18")
+        self.assertEqual(ip.ttl, 255)
+        self.assertEqual(ip.proto, IPPROTO_VRRP)
+
+        self.assertEqual(vrrp.version, 3)
+        self.assertEqual(vrrp.type, 1)
+        self.assertEqual(vrrp.vrid, vr.vr_id())
+        if prio is None:
+            prio = vr.priority()
+        self.assertEqual(vrrp.priority, prio)
+        self.assertEqual(vrrp.ipcount, len(vips))
+        self.assertEqual(vrrp.adv, vr.adv_interval())
+        self.assertListEqual(vrrp.addrlist, vips)
+
+    # VR with priority 255 owns the virtual address and should
+    # become master and start advertising immediately.
+    def test_vrrp4_master_adv(self):
+        """ IPv4 Master VR advertises """
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        prio = 255
+        intvl = self._default_adv
+        vr = VppVRRPVirtualRouter(self, self.pg0, 100,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags)
+
+        vr.add_vpp_config()
+        vr.start_stop(is_start=1)
+        self.logger.info(self.vapi.cli("show vrrp vr"))
+        vr.start_stop(is_start=0)
+        self.logger.info(self.vapi.cli("show vrrp vr"))
+
+        pkts = self.pg0.get_capture(4)
+
+        # Init -> Master: IGMP Join, VRRP adv, gratuitous ARP are sent
+        self.verify_vrrp4_igmp(pkts[0])
+        self.verify_vrrp4_adv(pkts[1], vr, prio=prio)
+        self.verify_vrrp4_garp(pkts[2], vr.virtual_ips()[0], vr.virtual_mac())
+        # Master -> Init: Adv with priority 0 sent to force an election
+        self.verify_vrrp4_adv(pkts[3], vr, prio=0)
+
+        vr.remove_vpp_config()
+        self._vrs = []
+
+    # VR with priority < 255 enters backup state and does not advertise as
+    # long as it receives higher priority advertisements
+    def test_vrrp4_backup_noadv(self):
+        """ IPv4 Backup VR does not advertise """
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        intvl_s = intvl * 0.01
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[self.pg0.remote_ip4])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        vr.start_stop(is_start=1)
+
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+        # watch for advertisements for 2x the master down preemption timeout
+        end_time = vr.start_time() + 2 * vr.master_down_seconds()
+
+        # Init -> Backup: An IGMP join should be sent
+        pkts = self.pg0.get_capture(1)
+        self.verify_vrrp4_igmp(pkts[0])
+
+        # send higher prio advertisements, should not receive any
+        src_ip = self.pg0.remote_ip4
+        pkts = [vr.vrrp_adv_packet(prio=prio+10, src_ip=src_ip)]
+        while time.time() < end_time:
+            self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
+            self.logger.info(self.vapi.cli("show trace"))
+
+        vr.start_stop(is_start=0)
+        self.logger.info(self.vapi.cli("show vrrp vr"))
+        vr.remove_vpp_config()
+        self._vrs = []
+
+    def test_vrrp4_master_arp(self):
+        """ IPv4 Master VR replies to ARP """
+        self.pg_start()
+
+        # VR virtual IP is the default, which is the pg local IP
+        vr_id = 100
+        prio = 255
+        intvl = self._default_adv
+        vr = VppVRRPVirtualRouter(self, self.pg0, 100,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags)
+        self._vrs.append(vr)
+
+        vr.add_vpp_config()
+
+        # before the VR is up, ARP should resolve to interface MAC
+        self.pg0.resolve_arp()
+        self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
+
+        # start the VR, ARP should now resolve to virtual MAC
+        vr.start_stop(is_start=1)
+        self.pg0.resolve_arp()
+        self.assertEqual(self.pg0.local_mac, vr.virtual_mac())
+
+        # stop the VR, ARP should resolve to interface MAC again
+        vr.start_stop(is_start=0)
+        self.pg0.resolve_arp()
+        self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
+
+        vr.remove_vpp_config()
+        self._vrs = []
+
+    def test_vrrp4_backup_noarp(self):
+        """ IPv4 Backup VR ignores ARP """
+        # We need an address for a virtual IP that is not the IP that
+        # ARP requests will originate from
+
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        vip = self.pg0.remote_hosts[1].ip4
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        arp_req = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+                   ARP(op=ARP.who_has, pdst=vip,
+                   psrc=self.pg0.remote_ip4, hwsrc=self.pg0.remote_mac))
+
+        # Before the VR is started make sure no reply to request for VIP
+        self.pg_start()
+        self.pg_enable_capture(self.pg_interfaces)
+        self.send_and_assert_no_replies(self.pg0, [arp_req], timeout=1)
+
+        # VR should start in backup state and still should not reply to ARP
+        # send a higher priority adv to make sure it does not become master
+        adv = vr.vrrp_adv_packet(prio=prio+10, src_ip=self.pg0.remote_ip4)
+        vr.start_stop(is_start=1)
+        self.send_and_assert_no_replies(self.pg0, [adv, arp_req], timeout=1)
+
+        vr.start_stop(is_start=0)
+        vr.remove_vpp_config()
+        self._vrs = []
+
+    def test_vrrp4_election(self):
+        """ IPv4 Backup VR becomes master if no advertisements received """
+
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        intvl_s = intvl * 0.01
+        vip = self.pg0.remote_ip4
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        self.pg_start()
+        vr.start_stop(is_start=1)
+
+        # VR should be in backup state after starting
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+        end_time = vr.start_time() + vr.master_down_seconds()
+
+        # should not receive adverts until timer expires & state transition
+        self.pg_enable_capture(self.pg_interfaces)
+        while (time.time() + intvl_s) < end_time:
+            time.sleep(intvl_s)
+            self.pg0.assert_nothing_captured(filter_out_fn=is_not_adv)
+
+        # VR should be in master state, should send an adv
+        self.pg0.enable_capture()
+        self.pg0.wait_for_packet(intvl_s, is_not_adv)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+    def test_vrrp4_backup_preempts(self):
+        """ IPv4 Backup VR preempts lower priority master """
+
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        intvl_s = intvl * 0.01
+        vip = self.pg0.remote_ip4
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        self.pg_start()
+        vr.start_stop(is_start=1)
+
+        # VR should be in backup state after starting
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+        end_time = vr.start_time() + vr.master_down_seconds()
+
+        # send lower prio advertisements until timer expires
+        src_ip = self.pg0.remote_ip4
+        pkts = [vr.vrrp_adv_packet(prio=prio-10, src_ip=src_ip)]
+        while time.time() + intvl_s < end_time:
+            self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
+            self.logger.info(self.vapi.cli("show trace"))
+
+        # when timer expires, VR should take over as master
+        self.pg0.enable_capture()
+        self.pg0.wait_for_packet(timeout=intvl_s, filter_out_fn=is_not_adv)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+    def test_vrrp4_master_preempted(self):
+        """ IPv4 Master VR preempted by higher priority backup """
+
+        # A prio 255 VR cannot be preempted so the prio has to be lower and
+        # we have to wait for it to take over
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        vip = self.pg0.remote_ip4
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        # start VR
+        vr.start_stop(is_start=1)
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+
+        # wait for VR to take over as master
+        end_time = vr.start_time() + vr.master_down_seconds()
+        sleep_s = end_time - time.time()
+        time.sleep(sleep_s)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+        # Build advertisement packet and send it
+        pkts = [vr.vrrp_adv_packet(prio=255, src_ip=self.pg0.remote_ip4)]
+        self.pg_send(self.pg0, pkts)
+
+        # VR should be in backup state again
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+
+    def test_vrrp4_accept_mode_disabled(self):
+        """ IPv4 Master VR does not reply for VIP w/ accept mode off """
+
+        # accept mode only matters when prio < 255, so it will have to
+        # come up as a backup and take over as master after the timeout
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        vip = self.pg0.remote_hosts[4].ip4
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        # start VR
+        vr.start_stop(is_start=1)
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+
+        # wait for VR to take over as master
+        end_time = vr.start_time() + vr.master_down_seconds()
+        sleep_s = end_time - time.time()
+        time.sleep(sleep_s)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+        # send an ICMP echo to the VR virtual IP address
+        echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
+                IP(dst=vip, src=self.pg0.remote_ip4) /
+                ICMP(seq=1, id=self.pg0.sw_if_index, type='echo-request'))
+        self.pg_send(self.pg0, [echo])
+
+        # wait for an echo reply. none should be received
+        time.sleep(1)
+        self.pg0.assert_nothing_captured(filter_out_fn=is_not_echo_reply)
+
+    def test_vrrp4_accept_mode_enabled(self):
+        """ IPv4 Master VR replies for VIP w/ accept mode on """
+
+        # A prio 255 VR cannot be preempted so the prio has to be lower and
+        # we have to wait for it to take over
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        vip = self.pg0.remote_hosts[4].ip4
+        flags = (VRRP_VR_FLAG_PREEMPT | VRRP_VR_FLAG_ACCEPT)
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        # start VR
+        vr.start_stop(is_start=1)
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+
+        # wait for VR to take over as master
+        end_time = vr.start_time() + vr.master_down_seconds()
+        sleep_s = end_time - time.time()
+        time.sleep(sleep_s)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+        # send an ICMP echo to the VR virtual IP address
+        echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
+                IP(dst=vip, src=self.pg0.remote_ip4) /
+                ICMP(seq=1, id=self.pg0.sw_if_index, type='echo-request'))
+        self.pg_send(self.pg0, [echo])
+
+        # wait for an echo reply.
+        time.sleep(1)
+        rx_pkts = self.pg0.get_capture(expected_count=1, timeout=1,
+                                       filter_out_fn=is_not_echo_reply)
+
+        self.assertEqual(rx_pkts[0][IP].src, vip)
+        self.assertEqual(rx_pkts[0][IP].dst, self.pg0.remote_ip4)
+        self.assertEqual(icmptypes[rx_pkts[0][ICMP].type], "echo-reply")
+        self.assertEqual(rx_pkts[0][ICMP].seq, 1)
+        self.assertEqual(rx_pkts[0][ICMP].id, self.pg0.sw_if_index)
+
+    def test_vrrp4_intf_tracking(self):
+        """ IPv4 Master VR adjusts priority based on tracked interface """
+
+        vr_id = 100
+        prio = 255
+        intvl = self._default_adv
+        intvl_s = intvl * 0.01
+        vip = self.pg0.local_ip4
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        # add pg1 as a tracked interface and start the VR
+        adjustment = 50
+        adjusted_prio = prio - adjustment
+        vr.add_del_tracked_interface(is_add=1,
+                                     sw_if_index=self.pg1.sw_if_index,
+                                     prio=adjustment)
+        vr.start_stop(is_start=1)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+        adv_configured = vr.vrrp_adv_packet(prio=prio)
+        adv_adjusted = vr.vrrp_adv_packet(prio=adjusted_prio)
+
+        # tracked intf is up ->  advertised priority == configured priority
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+        self.assertEqual(rx, adv_configured)
+
+        # take down pg1, verify priority is now being adjusted
+        self.pg1.admin_down()
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+        self.assertEqual(rx, adv_adjusted)
+
+        # bring up pg1, verify priority now matches configured value
+        self.pg1.admin_up()
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+        self.assertEqual(rx, adv_configured)
+
+        # remove IP address from pg1, verify priority now being adjusted
+        self.pg1.unconfig_ip4()
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+        self.assertEqual(rx, adv_adjusted)
+
+        # add IP address to pg1, verify priority now matches configured value
+        self.pg1.config_ip4()
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+        self.assertEqual(rx, adv_configured)
+
+    def test_vrrp4_master_adv_unicast(self):
+        """ IPv4 Master VR advertises (unicast) """
+
+        vr_id = 100
+        prio = 255
+        intvl = self._default_adv
+        intvl_s = intvl * 0.01
+        vip = self.pg0.local_ip4
+        flags = (self._default_flags | VRRP_VR_FLAG_UNICAST)
+        unicast_peer = self.pg0.remote_hosts[4]
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+        vr.set_unicast_peers([unicast_peer.ip4])
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        # Start VR, transition to master
+        vr.start_stop(is_start=1)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+
+        self.assertTrue(rx.haslayer(Ether))
+        self.assertTrue(rx.haslayer(IP))
+        self.assertTrue(rx.haslayer(VRRPv3))
+        self.assertEqual(rx[Ether].src, self.pg0.local_mac)
+        self.assertEqual(rx[Ether].dst, unicast_peer.mac)
+        self.assertEqual(rx[IP].src, self.pg0.local_ip4)
+        self.assertEqual(rx[IP].dst, unicast_peer.ip4)
+        self.assertEqual(rx[VRRPv3].vrid, vr_id)
+        self.assertEqual(rx[VRRPv3].priority, prio)
+        self.assertEqual(rx[VRRPv3].ipcount, 1)
+        self.assertEqual(rx[VRRPv3].addrlist, [vip])
+
+
+class TestVRRP6(VppTestCase):
+    """ IPv6 VRRP Test Case """
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestVRRP6, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestVRRP6, cls).tearDownClass()
+
+    def setUp(self):
+        super(TestVRRP6, self).setUp()
+
+        self.create_pg_interfaces(range(2))
+
+        for i in self.pg_interfaces:
+            i.admin_up()
+            i.config_ip6()
+            i.generate_remote_hosts(5)
+            i.configure_ipv6_neighbors()
+
+        self._vrs = []
+        self._default_flags = (VRRP_VR_FLAG_IPV6 | VRRP_VR_FLAG_PREEMPT)
+        self._default_adv = 100
+
+    def tearDown(self):
+        for vr in self._vrs:
+            try:
+                vr_api = vr.query_vpp_config()
+                if vr_api.runtime.state != VRRP_VR_STATE_INIT:
+                    vr.start_stop(is_start=0)
+                vr.remove_vpp_config()
+            except:
+                self.logger.error("Error cleaning up")
+
+        for i in self.pg_interfaces:
+            i.admin_down()
+            i.unconfig_ip4()
+            i.unconfig_ip6()
+
+        self._vrs = []
+
+        super(TestVRRP6, self).tearDown()
+
+    def verify_vrrp6_mlr(self, pkt, vr):
+        ip6 = pkt[IPv6]
+        self.assertEqual(ip6.dst, "ff02::16")
+        self.assertEqual(ipv6nh[ip6.nh], "Hop-by-Hop Option Header")
+
+        hbh = pkt[IPv6ExtHdrHopByHop]
+        self.assertEqual(ipv6nh[hbh.nh], "ICMPv6")
+
+        self.assertTrue(pkt.haslayer(ICMPv6MLReport2))
+        mlr = pkt[ICMPv6MLReport2]
+        # should contain mc addr records for:
+        # - VRRPv3 multicast addr
+        # - solicited node mc addr record for each VR virtual IPv6 address
+        vips = vr.virtual_ips()
+        self.assertEqual(mlr.records_number, len(vips) + 1)
+        self.assertEqual(mlr.records[0].dst, vr.adv_dest_ip())
+
+    def verify_vrrp6_adv(self, rx_pkt, vr, prio=None):
+        self.assertTrue(rx_pkt.haslayer(Ether))
+        self.assertTrue(rx_pkt.haslayer(IPv6))
+        self.assertTrue(rx_pkt.haslayer(VRRPv3))
+
+        # generate a packet for this VR and compare it to the one received
+        pkt = vr.vrrp_adv_packet(prio=prio)
+        self.assertTrue(rx_pkt.haslayer(Ether))
+        self.assertTrue(rx_pkt.haslayer(IPv6))
+        self.assertTrue(rx_pkt.haslayer(VRRPv3))
+
+        self.assertEqual(pkt, rx_pkt)
+
+    def verify_vrrp6_gna(self, pkt, vr):
+        self.assertTrue(pkt.haslayer(Ether))
+        self.assertTrue(pkt.haslayer(IPv6))
+        self.assertTrue(pkt.haslayer(ICMPv6ND_NA))
+        self.assertTrue(pkt.haslayer(ICMPv6NDOptDstLLAddr))
+
+        self.assertEqual(pkt[Ether].dst, "33:33:00:00:00:01")
+
+        self.assertEqual(pkt[IPv6].dst, "ff02::1")
+        # convert addrs to packed format since string versions could differ
+        src_addr = inet_pton(AF_INET6, pkt[IPv6].src)
+        vr_ll_addr = inet_pton(AF_INET6, vr.interface().local_ip6_ll)
+        self.assertEqual(src_addr, vr_ll_addr)
+
+        self.assertTrue(pkt[ICMPv6ND_NA].tgt in vr.virtual_ips())
+        self.assertEqual(pkt[ICMPv6NDOptDstLLAddr].lladdr, vr.virtual_mac())
+
+    # VR with priority 255 owns the virtual address and should
+    # become master and start advertising immediately.
+    def test_vrrp6_master_adv(self):
+        """ IPv6 Master VR advertises """
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        prio = 255
+        intvl = self._default_adv
+        vr = VppVRRPVirtualRouter(self, self.pg0, 100,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags)
+        self._vrs.append(vr)
+
+        vr.add_vpp_config()
+        self.logger.info(self.vapi.cli("show vrrp vr"))
+        vr.start_stop(is_start=1)
+        self.logger.info(self.vapi.cli("show vrrp vr"))
+        vr.start_stop(is_start=0)
+        self.logger.info(self.vapi.cli("show vrrp vr"))
+
+        pkts = self.pg0.get_capture(4, filter_out_fn=None)
+
+        # Init -> Master: Multicast group Join, VRRP adv, gratuitous NAs sent
+        self.verify_vrrp6_mlr(pkts[0], vr)
+        self.verify_vrrp6_adv(pkts[1], vr, prio=prio)
+        self.verify_vrrp6_gna(pkts[2], vr)
+        # Master -> Init: Adv with priority 0 sent to force an election
+        self.verify_vrrp6_adv(pkts[3], vr, prio=0)
+
+        vr.remove_vpp_config()
+        self._vrs = []
+
+    # VR with priority < 255 enters backup state and does not advertise as
+    # long as it receives higher priority advertisements
+    def test_vrrp6_backup_noadv(self):
+        """ IPv6 Backup VR does not advertise """
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        intvl_s = intvl * 0.01
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[self.pg0.remote_ip6])
+        vr.add_vpp_config()
+        self._vrs.append(vr)
+
+        vr.start_stop(is_start=1)
+
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+        # watch for advertisements for 2x the master down preemption timeout
+        end_time = vr.start_time() + 2 * vr.master_down_seconds()
+
+        # Init -> Backup: A multicast listener report should be sent
+        pkts = self.pg0.get_capture(1, filter_out_fn=None)
+
+        # send higher prio advertisements, should not see VPP send any
+        src_ip = self.pg0.remote_ip6_ll
+        num_advs = 5
+        pkts = [vr.vrrp_adv_packet(prio=prio+10, src_ip=src_ip)]
+        self.logger.info(self.vapi.cli("show vlib graph"))
+        while time.time() < end_time:
+            self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
+            self.logger.info(self.vapi.cli("show trace"))
+            num_advs -= 1
+
+        vr.start_stop(is_start=0)
+        self.logger.info(self.vapi.cli("show vrrp vr"))
+        vr.remove_vpp_config()
+        self._vrs = []
+
+    def test_vrrp6_master_nd(self):
+        """ IPv6 Master VR replies to NDP """
+        self.pg_start()
+
+        # VR virtual IP is the default, which is the pg local IP
+        vr_id = 100
+        prio = 255
+        intvl = self._default_adv
+        vr = VppVRRPVirtualRouter(self, self.pg0, 100,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags)
+        vr.add_vpp_config()
+        self._vrs.append(vr)
+
+        # before the VR is up, NDP should resolve to interface MAC
+        self.pg0.resolve_ndp()
+        self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
+
+        # start the VR, NDP should now resolve to virtual MAC
+        vr.start_stop(is_start=1)
+        self.pg0.resolve_ndp()
+        self.assertEqual(self.pg0.local_mac, vr.virtual_mac())
+
+        # stop the VR, ARP should resolve to interface MAC again
+        vr.start_stop(is_start=0)
+        self.pg0.resolve_ndp()
+        self.assertNotEqual(self.pg0.local_mac, vr.virtual_mac())
+
+        vr.remove_vpp_config()
+        self._vrs = []
+
+    def test_vrrp6_backup_nond(self):
+        """ IPv6 Backup VR ignores NDP """
+        # We need an address for a virtual IP that is not the IP that
+        # ARP requests will originate from
+
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        intvl_s = intvl * 0.01
+        vip = self.pg0.remote_hosts[1].ip6
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        vr.add_vpp_config()
+        self._vrs.append(vr)
+
+        nsma = in6_getnsma(inet_pton(socket.AF_INET6, vip))
+        dmac = in6_getnsmac(nsma)
+        dst_ip = inet_ntop(socket.AF_INET6, nsma)
+
+        ndp_req = (Ether(dst=dmac, src=self.pg0.remote_mac) /
+                   IPv6(dst=dst_ip, src=self.pg0.remote_ip6) /
+                   ICMPv6ND_NS(tgt=vip) /
+                   ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
+
+        # Before the VR is started make sure no reply to request for VIP
+        self.send_and_assert_no_replies(self.pg0, [ndp_req], timeout=1)
+
+        # VR should start in backup state and still should not reply to NDP
+        # send a higher priority adv to make sure it does not become master
+        adv = vr.vrrp_adv_packet(prio=prio+10, src_ip=self.pg0.remote_ip6)
+        pkts = [adv, ndp_req]
+        vr.start_stop(is_start=1)
+        self.send_and_assert_no_replies(self.pg0, pkts,  timeout=intvl_s)
+
+        vr.start_stop(is_start=0)
+
+    def test_vrrp6_election(self):
+        """ IPv6 Backup VR becomes master if no advertisements received """
+
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        intvl_s = intvl * 0.01
+        vip = self.pg0.remote_ip6
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        self.pg_start()
+        vr.start_stop(is_start=1)
+
+        # VR should be in backup state after starting
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+        end_time = vr.start_time() + vr.master_down_seconds()
+
+        # no advertisements should arrive until timer expires
+        self.pg0.enable_capture()
+        while (time.time() + intvl_s) < end_time:
+            time.sleep(intvl_s)
+            self.pg0.assert_nothing_captured(filter_out_fn=is_not_adv)
+
+        # VR should be in master state after timer expires
+        self.pg0.enable_capture()
+        self.pg0.wait_for_packet(intvl_s, is_not_adv)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+    def test_vrrp6_backup_preempts(self):
+        """ IPv6 Backup VR preempts lower priority master """
+
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        intvl_s = intvl * 0.01
+        vip = self.pg0.remote_ip6
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        self.pg_start()
+        vr.start_stop(is_start=1)
+
+        # VR should be in backup state after starting
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+        end_time = vr.start_time() + vr.master_down_seconds()
+
+        # send lower prio advertisements until timer expires
+        src_ip = self.pg0.remote_ip6
+        pkts = [vr.vrrp_adv_packet(prio=prio-10, src_ip=src_ip)]
+        while (time.time() + intvl_s) < end_time:
+            self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl_s)
+            self.logger.info(self.vapi.cli("show trace"))
+
+        # when timer expires, VR should take over as master
+        self.pg0.enable_capture()
+        self.pg0.wait_for_packet(timeout=intvl_s, filter_out_fn=is_not_adv)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+    def test_vrrp6_master_preempted(self):
+        """ IPv6 Master VR preempted by higher priority backup """
+
+        # A prio 255 VR cannot be preempted so the prio has to be lower and
+        # we have to wait for it to take over
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        vip = self.pg0.remote_ip6
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        # start VR
+        vr.start_stop(is_start=1)
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+
+        # wait for VR to take over as master
+        end_time = vr.start_time() + vr.master_down_seconds()
+        sleep_s = end_time - time.time()
+        time.sleep(sleep_s)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+        # Build advertisement packet and send it
+        pkts = [vr.vrrp_adv_packet(prio=255, src_ip=self.pg0.remote_ip6)]
+        self.pg_send(self.pg0, pkts)
+
+        # VR should be in backup state again
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+
+    def test_vrrp6_accept_mode_disabled(self):
+        """ IPv6 Master VR does not reply for VIP w/ accept mode off """
+
+        # accept mode only matters when prio < 255, so it will have to
+        # come up as a backup and take over as master after the timeout
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        vip = self.pg0.remote_hosts[4].ip6
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        # start VR
+        vr.start_stop(is_start=1)
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+
+        # wait for VR to take over as master
+        end_time = vr.start_time() + vr.master_down_seconds()
+        sleep_s = end_time - time.time()
+        time.sleep(sleep_s)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+        # send an ICMPv6 echo to the VR virtual IP address
+        echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
+                IPv6(dst=vip, src=self.pg0.remote_ip6) /
+                ICMPv6EchoRequest(seq=1, id=self.pg0.sw_if_index))
+        self.pg_send(self.pg0, [echo])
+
+        # wait for an echo reply. none should be received
+        time.sleep(1)
+        self.pg0.assert_nothing_captured(filter_out_fn=is_not_echo_reply)
+
+    def test_vrrp6_accept_mode_enabled(self):
+        """ IPv6 Master VR replies for VIP w/ accept mode on """
+
+        # A prio 255 VR cannot be preempted so the prio has to be lower and
+        # we have to wait for it to take over
+        vr_id = 100
+        prio = 100
+        intvl = self._default_adv
+        vip = self.pg0.remote_hosts[4].ip6
+        flags = (self._default_flags | VRRP_VR_FLAG_ACCEPT)
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        # start VR
+        vr.start_stop(is_start=1)
+        vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
+
+        # wait for VR to take over as master
+        end_time = vr.start_time() + vr.master_down_seconds()
+        sleep_s = end_time - time.time()
+        time.sleep(sleep_s)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+        # send an ICMP echo to the VR virtual IP address
+        echo = (Ether(dst=vr.virtual_mac(), src=self.pg0.remote_mac) /
+                IPv6(dst=vip, src=self.pg0.remote_ip6) /
+                ICMPv6EchoRequest(seq=1, id=self.pg0.sw_if_index))
+        self.pg_send(self.pg0, [echo])
+
+        # wait for an echo reply.
+        time.sleep(1)
+        rx_pkts = self.pg0.get_capture(expected_count=1, timeout=1,
+                                       filter_out_fn=is_not_echo_reply)
+
+        self.assertEqual(rx_pkts[0][IPv6].src, vip)
+        self.assertEqual(rx_pkts[0][IPv6].dst, self.pg0.remote_ip6)
+        self.assertEqual(rx_pkts[0][ICMPv6EchoReply].seq, 1)
+        self.assertEqual(rx_pkts[0][ICMPv6EchoReply].id, self.pg0.sw_if_index)
+
+    def test_vrrp6_intf_tracking(self):
+        """ IPv6 Master VR adjusts priority based on tracked interface """
+
+        vr_id = 100
+        prio = 255
+        intvl = self._default_adv
+        intvl_s = intvl * 0.01
+        vip = self.pg0.local_ip6
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=self._default_flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        # add pg1 as a tracked interface and start the VR
+        adjustment = 50
+        adjusted_prio = prio - adjustment
+        vr.add_del_tracked_interface(is_add=1,
+                                     sw_if_index=self.pg1.sw_if_index,
+                                     prio=adjustment)
+        vr.start_stop(is_start=1)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+        adv_configured = vr.vrrp_adv_packet(prio=prio)
+        adv_adjusted = vr.vrrp_adv_packet(prio=adjusted_prio)
+
+        # tracked intf is up ->  advertised priority == configured priority
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+        self.assertEqual(rx, adv_configured)
+
+        # take down pg1, verify priority is now being adjusted
+        self.pg1.admin_down()
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+        self.assertEqual(rx, adv_adjusted)
+
+        # bring up pg1, verify priority now matches configured value
+        self.pg1.admin_up()
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+        self.assertEqual(rx, adv_configured)
+
+        # remove IP address from pg1, verify priority now being adjusted
+        self.pg1.unconfig_ip6()
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+        self.assertEqual(rx, adv_adjusted)
+
+        # add IP address to pg1, verify priority now matches configured value
+        self.pg1.config_ip6()
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+        self.assertEqual(rx, adv_configured)
+
+    def test_vrrp6_master_adv_unicast(self):
+        """ IPv6 Master VR advertises (unicast) """
+
+        vr_id = 100
+        prio = 255
+        intvl = self._default_adv
+        intvl_s = intvl * 0.01
+        vip = self.pg0.local_ip6
+        flags = (self._default_flags | VRRP_VR_FLAG_UNICAST)
+        unicast_peer = self.pg0.remote_hosts[4]
+        vr = VppVRRPVirtualRouter(self, self.pg0, vr_id,
+                                  prio=prio, intvl=intvl,
+                                  flags=flags,
+                                  vips=[vip])
+        self._vrs.append(vr)
+        vr.add_vpp_config()
+        vr.set_unicast_peers([unicast_peer.ip6])
+
+        # After adding the VR, it should be in the init state
+        vr.assert_state_equals(VRRP_VR_STATE_INIT)
+
+        # Start VR, transition to master
+        vr.start_stop(is_start=1)
+        vr.assert_state_equals(VRRP_VR_STATE_MASTER)
+
+        self.pg0.enable_capture()
+        rx = self.pg0.wait_for_packet(timeout=intvl_s,
+                                      filter_out_fn=is_not_adv)
+
+        self.assertTrue(rx.haslayer(Ether))
+        self.assertTrue(rx.haslayer(IPv6))
+        self.assertTrue(rx.haslayer(VRRPv3))
+        self.assertEqual(rx[Ether].src, self.pg0.local_mac)
+        self.assertEqual(rx[Ether].dst, unicast_peer.mac)
+        self.assertEqual(ip6_normalize(rx[IPv6].src),
+                         ip6_normalize(self.pg0.local_ip6_ll))
+        self.assertEqual(ip6_normalize(rx[IPv6].dst),
+                         ip6_normalize(unicast_peer.ip6))
+        self.assertEqual(rx[VRRPv3].vrid, vr_id)
+        self.assertEqual(rx[VRRPv3].priority, prio)
+        self.assertEqual(rx[VRRPv3].ipcount, 1)
+        self.assertEqual(rx[VRRPv3].addrlist, [vip])
+
+
+if __name__ == '__main__':
+    unittest.main(testRunner=VppTestRunner)
diff --git a/src/plugins/vrrp/vrrp.api b/src/plugins/vrrp/vrrp.api
new file mode 100644 (file)
index 0000000..1894d1c
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+option version = "1.0.0";
+
+import "vnet/interface_types.api";
+import "vnet/ip/ip_types.api";
+import "vnet/ethernet/ethernet_types.api";
+
+typedef vrrp_vr_key
+{
+  vl_api_interface_index_t sw_if_index;
+  u8 vr_id;
+  u8 is_ipv6;
+};
+
+enum vrrp_vr_flags
+{
+  VRRP_API_VR_PREEMPT = 0x1,
+  VRRP_API_VR_ACCEPT = 0x2,
+  VRRP_API_VR_UNICAST = 0x4,
+  VRRP_API_VR_IPV6 = 0x8,
+};
+
+typedef vrrp_vr_conf
+{
+  vl_api_interface_index_t sw_if_index;
+  u8 vr_id;
+  u8 priority;
+  u16 interval;
+  vl_api_vrrp_vr_flags_t flags;
+};
+
+/** \brief VRRP: Add or delete a VRRP virtual router
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param is_add - 0 if deleting, != 0 if adding
+    @param sw_if_index - interface backed up by this vr
+    @param vr_id - the VR ID advertised by this vr
+    @param priority - the priority advertised for this vr
+    @param interval - interval between advertisements in centiseconds
+    @param flags - bit flags for booleans - preempt, accept, unicast, ipv6
+    @param n_addrs - number of addresses being backed up by this vr
+    @param addrs - the addresses backed up by this vr
+*/
+autoreply define vrrp_vr_add_del {
+  u32 client_index;
+  u32 context;
+  u8 is_add;
+  vl_api_interface_index_t sw_if_index;
+  u8 vr_id;
+  u8 priority;
+  u16 interval;
+  vl_api_vrrp_vr_flags_t flags;
+  u8 n_addrs;
+  vl_api_address_t addrs[n_addrs];
+};
+
+/** \brief VRRP: dump virtual router data
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param sw_if_index - interface to use as filter (0,~0 == "all")
+*/
+define vrrp_vr_dump {
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+};
+
+enum vrrp_vr_state
+{
+  VRRP_API_VR_STATE_INIT = 0,
+  VRRP_API_VR_STATE_BACKUP,
+  VRRP_API_VR_STATE_MASTER,
+  VRRP_API_VR_STATE_INTF_DOWN,
+};
+
+typedef vrrp_vr_tracking
+{
+  u32 interfaces_dec;
+  u8 priority;
+};
+
+typedef vrrp_vr_runtime
+{
+  vl_api_vrrp_vr_state_t state;
+  u16 master_adv_int;
+  u16 skew;
+  u16 master_down_int;
+  vl_api_mac_address_t mac;
+  vl_api_vrrp_vr_tracking_t tracking;
+};
+
+/** \brief VRRP: VR dump response
+    @param context - sender context which was passed in the request
+    @param conf - configuration parameters for the VR
+    @param runtime - runtime state for the VR
+*/
+define vrrp_vr_details {
+  u32 context;
+  vl_api_vrrp_vr_conf_t config;
+  vl_api_vrrp_vr_runtime_t runtime;
+  u8 n_addrs;
+  vl_api_address_t addrs[n_addrs];
+};
+
+/** \brief VRRP: start or shutdown the VRRP protocol for a virtual router
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param sw_if_index - interface ID that VR is backing up
+    @param vr_id - VR ID
+    @param is_ipv6 - 1 for IPv6, 0 for IPv4
+    @param is_start - 1 to start VRRP proto on this VR, 0 to shutdown
+*/
+autoreply define vrrp_vr_start_stop {
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+  u8 vr_id;
+  u8 is_ipv6;
+  u8 is_start;
+};
+
+/** \brief VRRP: set unicast peers for a VR
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param sw_if_index - interface ID that VR is backing up
+    @param vr_id - VR ID
+    @param is_ipv6 - 1 for IPv6, 0 for IPv4
+    @param n_addrs - number of peer addresses
+    @param addrs - peer addresses 
+*/
+autoreply define vrrp_vr_set_peers {
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+  u8 vr_id;
+  u8 is_ipv6;
+  u8 n_addrs;
+  vl_api_address_t addrs[n_addrs];
+};
+
+/** \brief VRRP: dump virtual router peer address data
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param sw_if_index - interface (0,~0 == "all" -> ignore is_ipv6 & vr_id))
+    @param is_ipv6 - 0 -> IPv4, 1 -> IPv6
+    @param vr_id - ID of VR to dump
+*/
+define vrrp_vr_peer_dump {
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+  u8 is_ipv6;
+  u8 vr_id;
+};
+
+/** \brief VRRP: VR peer dump response
+    @param context - sender context which was passed in the request
+    @param sw_if_index - interface index
+    @param is_ipv6 - 0 -> IPv4, 1 -> IPv6
+    @param vr_id - ID of VR
+    @param n_peer_addrs - number of peer addresses
+    @param peer_addrs - peer addresses 
+*/
+autoreply define vrrp_vr_peer_details {
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+  u8 vr_id;
+  u8 is_ipv6;
+  u8 n_peer_addrs;
+  vl_api_address_t peer_addrs[n_peer_addrs];
+};
+
+/** \brief VR interface tracking
+    @param sw_if_index - the interface index to track (not the VR sw_if_index)
+    @param priority - the adjustment to VR priority if intf is down
+*/
+typedef vrrp_vr_track_if
+{
+  vl_api_interface_index_t sw_if_index;
+  u8 priority;
+};
+
+/** \brief VRRP: Add/delete VR priority tracking of interface status
+    @param context - sender context which was passed in the request
+    @param sw_if_index - interface index
+    @param is_ipv6 - 0 -> IPv4, 1 -> IPv6
+    @param vr_id - ID of VR
+    @param is_add - 0 -> delete, 1 -> add
+    @param n_ifs - number of interface tracking records
+    @param ifs - array of interface tracking records
+*/
+autoreply define vrrp_vr_track_if_add_del
+{
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+  u8 is_ipv6;
+  u8 vr_id;
+  u8 is_add;
+  u8 n_ifs;
+  vl_api_vrrp_vr_track_if_t ifs[n_ifs];
+};
+
+/** \brief VRRP: dump virtual router interface tracking data
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param sw_if_index - interface
+    @param is_ipv6 - 0 -> IPv4, 1 -> IPv6
+    @param vr_id - ID of VR to dump
+    @param dump_all - dump all VR interface tracking, ignore other fields
+*/
+define vrrp_vr_track_if_dump {
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+  u8 is_ipv6;
+  u8 vr_id;
+  u8 dump_all;
+};
+
+/** \brief VRRP: VR interface tracking dump response
+    @param context - sender context which was passed in the request
+    @param sw_if_index - interface index
+    @param is_ipv6 - 0 -> IPv4, 1 -> IPv6
+    @param vr_id - ID of VR
+    @param n_ifs - number of tracked interfaces
+    @param ifs - array of tracked interface data
+*/
+autoreply define vrrp_vr_track_if_details {
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+  u8 vr_id;
+  u8 is_ipv6;
+  u8 n_ifs;
+  vl_api_vrrp_vr_track_if_t ifs[n_ifs];
+};
+
diff --git a/src/plugins/vrrp/vrrp.c b/src/plugins/vrrp/vrrp.c
new file mode 100644 (file)
index 0000000..13cdba6
--- /dev/null
@@ -0,0 +1,1240 @@
+/*
+ * vrrp.c - vrrp plugin action functions
+ *
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#include <vnet/vnet.h>
+#include <vnet/plugin/plugin.h>
+#include <vnet/mfib/mfib_entry.h>
+#include <vnet/mfib/mfib_table.h>
+#include <vnet/adj/adj.h>
+#include <vnet/adj/adj_mcast.h>
+#include <vnet/fib/fib_table.h>
+#include <vnet/ip/igmp_packet.h>
+#include <vnet/ip/ip6_link.h>
+
+#include <vrrp/vrrp.h>
+#include <vrrp/vrrp_packet.h>
+
+#include <vpp/app/version.h>
+
+vrrp_main_t vrrp_main;
+
+static const mac_address_t ipv4_vmac = {
+  .bytes = {0x00, 0x00, 0x5e, 0x00, 0x01, 0x00}
+};
+
+static const mac_address_t ipv6_vmac = {
+  .bytes = {0x00, 0x00, 0x5e, 0x00, 0x02, 0x00}
+};
+
+typedef struct
+{
+  vrrp_vr_key_t key;
+  u32 count;
+} vrrp_hwif_vr_count_t;
+
+typedef enum
+{
+  VRRP_IF_UPDATE_IP,
+  VRRP_IF_UPDATE_HW_LINK,
+  VRRP_IF_UPDATE_SW_ADMIN,
+} vrrp_intf_update_type_t;
+
+typedef struct
+{
+  vrrp_intf_update_type_t type;
+  u32 sw_if_index;
+  u32 hw_if_index;
+  int intf_up;
+} vrrp_intf_update_t;
+
+static int vrrp_intf_is_up (u32 sw_if_index, u8 is_ipv6,
+                           vrrp_intf_update_t * pending);
+
+static walk_rc_t
+vrrp_hwif_master_count_walk (vnet_main_t * vnm, u32 sw_if_index, void *arg)
+{
+  vrrp_hwif_vr_count_t *vr_count = arg;
+  vrrp_vr_t *vr;
+
+  vr = vrrp_vr_lookup (sw_if_index, vr_count->key.vr_id,
+                      vr_count->key.is_ipv6);
+
+  if (vr && (vr->runtime.state == VRRP_VR_STATE_MASTER))
+    vr_count->count++;
+
+  return WALK_CONTINUE;
+}
+
+/*
+ * Get a count of VRs in master state on a given hardware interface with
+ * the provided VR ID and AF.
+ */
+static u32
+vrrp_vr_hwif_master_vrs_by_vrid (u32 hw_if_index, u8 vr_id, u8 is_ipv6)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  vrrp_hwif_vr_count_t vr_count;
+
+  clib_memset (&vr_count, 0, sizeof (vr_count));
+
+  vr_count.key.vr_id = vr_id;
+  vr_count.key.is_ipv6 = is_ipv6;
+
+  vnet_hw_interface_walk_sw (vnm, hw_if_index,
+                            vrrp_hwif_master_count_walk, &vr_count);
+
+  return vr_count.count;
+}
+
+/*
+ * Add or delete the VR virtual MAC address on the hardware interface
+ * when a VR enters or leaves the master state.
+ *
+ * Multiple subinterfaces may host the same VR ID. We should only add or
+ * delete the virtual MAC if this is the first VR being enabled on the
+ * hardware interface or the last one being disabled, respectively.
+ */
+void
+vrrp_vr_transition_vmac (vrrp_vr_t * vr, vrrp_vr_state_t new_state)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  clib_error_t *error = 0;
+  vnet_hw_interface_t *hw;
+  u8 enable = (new_state == VRRP_VR_STATE_MASTER);
+  u32 n_master_vrs;
+
+  hw = vnet_get_sup_hw_interface (vnm, vr->config.sw_if_index);
+  n_master_vrs =
+    vrrp_vr_hwif_master_vrs_by_vrid (hw->hw_if_index, vr->config.vr_id,
+                                    vrrp_vr_is_ipv6 (vr));
+
+  /* enable only if current master vrs is 0, disable only if 0 or 1 */
+  if ((enable && !n_master_vrs) || (!enable && (n_master_vrs < 2)))
+    {
+      clib_warning ("%s virtual MAC address %U on hardware interface %u",
+                   (enable) ? "Adding" : "Deleting",
+                   format_ethernet_address, vr->runtime.mac.bytes,
+                   hw->hw_if_index);
+
+      error = vnet_hw_interface_add_del_mac_address
+       (vnm, hw->hw_if_index, vr->runtime.mac.bytes, enable);
+    }
+
+  if (error)
+    clib_error_report (error);
+}
+
+/*
+ * Manage VR interface data on transition to/from master:
+ *  - enable or disable ARP/ND input feature if appropriate
+ *  - update count of VRs in master state
+ */
+static void
+vrrp_vr_transition_intf (vrrp_vr_t * vr, vrrp_vr_state_t new_state)
+{
+  vrrp_intf_t *intf;
+  const char *arc_name = 0, *node_name = 0;
+  u8 is_ipv6 = vrrp_vr_is_ipv6 (vr);
+
+  /* only need to do something if entering or leaving master state */
+  if ((vr->runtime.state != VRRP_VR_STATE_MASTER) &&
+      (new_state != VRRP_VR_STATE_MASTER))
+    return;
+
+  if (is_ipv6)
+    {
+      arc_name = "ip6-local";
+      node_name = "vrrp6-nd-input";
+    }
+  else
+    {
+      arc_name = "arp";
+      node_name = "vrrp4-arp-input";
+    }
+
+  intf = vrrp_intf_get (vr->config.sw_if_index);
+  if (new_state == VRRP_VR_STATE_MASTER)
+    {
+      intf->n_master_vrs[is_ipv6]++;
+      if (intf->n_master_vrs[is_ipv6] == 1)
+       vnet_feature_enable_disable (arc_name, node_name,
+                                    vr->config.sw_if_index, 1, NULL, 0);
+    }
+  else
+    {
+      if (intf->n_master_vrs[is_ipv6] == 1)
+       vnet_feature_enable_disable (arc_name, node_name,
+                                    vr->config.sw_if_index, 0, NULL, 0);
+      /* If the count were already 0, leave it at 0 */
+      if (intf->n_master_vrs[is_ipv6])
+       intf->n_master_vrs[is_ipv6]--;
+    }
+}
+
+/* If accept mode enabled, add/remove VR addresses from interface */
+static void
+vrrp_vr_transition_addrs (vrrp_vr_t * vr, vrrp_vr_state_t new_state)
+{
+  vlib_main_t *vm = vlib_get_main ();
+  u8 is_del;
+  ip46_address_t *vr_addr;
+
+  if (!vrrp_vr_accept_mode_enabled (vr))
+    return;
+
+  /* owner always has VR addresses configured, should never remove them */
+  if (vrrp_vr_is_owner (vr))
+    return;
+
+  if (vrrp_vr_is_unicast (vr))
+    return;
+
+  /* only need to do something if entering or leaving master state */
+  if ((vr->runtime.state != VRRP_VR_STATE_MASTER) &&
+      (new_state != VRRP_VR_STATE_MASTER))
+    return;
+
+  is_del = (new_state != VRRP_VR_STATE_MASTER);
+
+  clib_warning ("%s VR addresses on sw_if_index %u",
+               (is_del) ? "Deleting" : "Adding", vr->config.sw_if_index);
+
+  vec_foreach (vr_addr, vr->config.vr_addrs)
+  {
+    ip_interface_address_t *ia = NULL;
+
+    /* We need to know the address length to use, find it from another
+     * address on the interface. Or use a default (/24, /64).
+     */
+    if (!vrrp_vr_is_ipv6 (vr))
+      {
+       ip4_main_t *im = &ip4_main;
+       ip4_address_t *intf4;
+
+       intf4 =
+         ip4_interface_address_matching_destination
+         (im, &vr_addr->ip4, vr->config.sw_if_index, &ia);
+
+       ip4_add_del_interface_address (vm, vr->config.sw_if_index,
+                                      &vr_addr->ip4,
+                                      (intf4 ? ia->address_length : 24),
+                                      is_del);
+      }
+    else
+      {
+       ip6_main_t *im = &ip6_main;
+       ip6_address_t *intf6;
+
+       intf6 =
+         ip6_interface_address_matching_destination
+         (im, &vr_addr->ip6, vr->config.sw_if_index, &ia);
+
+       ip6_add_del_interface_address (vm, vr->config.sw_if_index,
+                                      &vr_addr->ip6,
+                                      (intf6 ? ia->address_length : 64),
+                                      is_del);
+      }
+  }
+}
+
+void
+vrrp_vr_transition (vrrp_vr_t * vr, vrrp_vr_state_t new_state, void *data)
+{
+
+  clib_warning ("VR %U transitioning to %U", format_vrrp_vr_key, vr,
+               format_vrrp_vr_state, new_state);
+
+  /* Don't do anything if transitioning to the state VR is already in.
+   * This should never happen, just covering our bases.
+   */
+  if (new_state == vr->runtime.state)
+    return;
+
+  if (new_state == VRRP_VR_STATE_MASTER)
+    {
+      /* RFC 5798 sec 6.4.1 (105) - startup event for VR with priority 255
+       *          sec 6.4.2 (365) - master down timer fires on backup VR
+       */
+
+      vrrp_vr_multicast_group_join (vr);
+      vrrp_adv_send (vr, 0);
+      vrrp_garp_or_na_send (vr);
+
+      vrrp_vr_timer_set (vr, VRRP_VR_TIMER_ADV);
+    }
+  else if (new_state == VRRP_VR_STATE_BACKUP)
+    {
+      /* RFC 5798 sec 6.4.1 (150) - startup event for VR with priority < 255
+       *          sec 6.4.3 (735) - master preempted by higher priority VR
+       */
+
+      vrrp_vr_multicast_group_join (vr);
+
+      if (vr->runtime.state == VRRP_VR_STATE_MASTER)
+       {
+         vrrp_header_t *pkt = data;
+         vr->runtime.master_adv_int = vrrp_adv_int_from_packet (pkt);
+
+       }
+      else                     /* INIT, INTF_DOWN */
+       vr->runtime.master_adv_int = vr->config.adv_interval;
+
+      vrrp_vr_skew_compute (vr);
+      vrrp_vr_master_down_compute (vr);
+      vrrp_vr_timer_set (vr, VRRP_VR_TIMER_MASTER_DOWN);
+
+    }
+  else if (new_state == VRRP_VR_STATE_INIT)
+    {
+      /* RFC 5798 sec 6.4.2 (345) - shutdown event for backup VR
+       *          sec 6.4.3 (655) - shutdown event for master VR
+       */
+
+      vrrp_vr_timer_cancel (vr);
+      if (vr->runtime.state == VRRP_VR_STATE_MASTER)
+       vrrp_adv_send (vr, 1);
+    }
+  else if (new_state == VRRP_VR_STATE_INTF_DOWN)
+    /* State is not specified by RFC. This is to avoid attempting to
+     * send packets on an interface that's down and to avoid having a
+     * VR believe it is already the master when an interface is brought up
+     */
+    vrrp_vr_timer_cancel (vr);
+
+  /* add/delete virtual IP addrs if accept_mode is true */
+  vrrp_vr_transition_addrs (vr, new_state);
+
+  /* enable/disable arp/ND input features if necessary */
+  vrrp_vr_transition_intf (vr, new_state);
+
+  /* add/delete virtual MAC address on NIC if necessary */
+  vrrp_vr_transition_vmac (vr, new_state);
+
+  vr->runtime.state = new_state;
+}
+
+#define VRRP4_MCAST_ADDR_AS_U8 { 224, 0, 0, 18 }
+#define VRRP6_MCAST_ADDR_AS_U8 \
+{ 0xff, 0x2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12 }
+
+static const mfib_prefix_t all_vrrp4_routers = {
+  .fp_proto = FIB_PROTOCOL_IP4,
+  .fp_len = 32,
+  .fp_grp_addr = {
+                 .ip4 = {
+                         .as_u8 = VRRP4_MCAST_ADDR_AS_U8,
+                         },
+                 },
+};
+
+static const mfib_prefix_t all_vrrp6_routers = {
+  .fp_proto = FIB_PROTOCOL_IP6,
+  .fp_len = 128,
+  .fp_grp_addr = {
+                 .ip6 = {
+                         .as_u8 = VRRP6_MCAST_ADDR_AS_U8,
+                         },
+                 },
+};
+
+static int
+vrrp_intf_enable_disable_mcast (u8 enable, u32 sw_if_index, u8 is_ipv6)
+{
+  vrrp_main_t *vrm = &vrrp_main;
+  vrrp_intf_t *intf;
+  u32 fib_index;
+  const mfib_prefix_t *vrrp_prefix;
+  fib_protocol_t proto;
+  vnet_link_t link_type;
+  fib_route_path_t for_us = {
+    .frp_sw_if_index = 0xffffffff,
+    .frp_weight = 1,
+    .frp_flags = FIB_ROUTE_PATH_LOCAL,
+    .frp_mitf_flags = MFIB_ITF_FLAG_FORWARD,
+  };
+  fib_route_path_t via_itf = {
+    .frp_sw_if_index = sw_if_index,
+    .frp_weight = 1,
+    .frp_mitf_flags = MFIB_ITF_FLAG_ACCEPT,
+  };
+
+  intf = vrrp_intf_get (sw_if_index);
+
+  if (is_ipv6)
+    {
+      proto = FIB_PROTOCOL_IP6;
+      link_type = VNET_LINK_IP6;
+      vrrp_prefix = &all_vrrp6_routers;
+    }
+  else
+    {
+      proto = FIB_PROTOCOL_IP4;
+      link_type = VNET_LINK_IP4;
+      vrrp_prefix = &all_vrrp4_routers;
+    }
+
+  for_us.frp_proto = fib_proto_to_dpo (proto);
+  via_itf.frp_proto = fib_proto_to_dpo (proto);
+  fib_index = mfib_table_get_index_for_sw_if_index (proto, sw_if_index);
+
+  if (enable)
+    {
+      if (pool_elts (vrm->vrs) == 1)
+       mfib_table_entry_path_update (fib_index, vrrp_prefix, MFIB_SOURCE_API,
+                                     &for_us);
+
+      mfib_table_entry_path_update (fib_index, vrrp_prefix, MFIB_SOURCE_API,
+                                   &via_itf);
+      intf->mcast_adj_index[! !is_ipv6] =
+       adj_mcast_add_or_lock (proto, link_type, sw_if_index);
+    }
+  else
+    {
+      if (pool_elts (vrm->vrs) == 0)
+       mfib_table_entry_path_remove (fib_index, vrrp_prefix, MFIB_SOURCE_API,
+                                     &for_us);
+
+      mfib_table_entry_path_remove (fib_index, vrrp_prefix, MFIB_SOURCE_API,
+                                   &via_itf);
+    }
+
+  return 0;
+}
+
+static int
+vrrp_intf_vr_add_del (u8 is_add, u32 sw_if_index, u32 vr_index, u8 is_ipv6)
+{
+  vrrp_intf_t *vr_intf;
+
+  vr_intf = vrrp_intf_get (sw_if_index);
+  if (!vr_intf)
+    return -1;
+
+  if (is_add)
+    {
+      if (!vec_len (vr_intf->vr_indices[is_ipv6]))
+       vrrp_intf_enable_disable_mcast (1, sw_if_index, is_ipv6);
+
+      vec_add1 (vr_intf->vr_indices[is_ipv6], vr_index);
+    }
+  else
+    {
+      u32 per_intf_index =
+       vec_search (vr_intf->vr_indices[is_ipv6], vr_index);
+
+      if (per_intf_index != ~0)
+       vec_del1 (vr_intf->vr_indices[is_ipv6], per_intf_index);
+
+      /* no more VRs on this interface, disable multicast */
+      if (!vec_len (vr_intf->vr_indices[is_ipv6]))
+       vrrp_intf_enable_disable_mcast (0, sw_if_index, is_ipv6);
+    }
+
+  return 0;
+}
+
+/* RFC 5798 section 8.3.2 says to take care not to configure more than
+ * one VRRP router as the "IPvX address owner" of a VRID. Make sure that
+ * all of the addresses configured for this VR are configured on the
+ * interface.
+ */
+static int
+vrrp_vr_valid_addrs_owner (vrrp_vr_config_t * vr_conf)
+{
+  ip46_address_t *addr;
+  u8 is_ipv6 = (vr_conf->flags & VRRP_VR_IPV6) != 0;
+
+  vec_foreach (addr, vr_conf->vr_addrs)
+  {
+    if (!ip_interface_has_address (vr_conf->sw_if_index, addr, !is_ipv6))
+      return VNET_API_ERROR_ADDRESS_NOT_FOUND_FOR_INTERFACE;
+  }
+
+  return 0;
+}
+
+static int
+vrrp_vr_valid_addrs_unused (vrrp_vr_config_t * vr_conf)
+{
+  ip46_address_t *vr_addr;
+  u8 is_ipv6 = (vr_conf->flags & VRRP_VR_IPV6) != 0;
+
+  vec_foreach (vr_addr, vr_conf->vr_addrs)
+  {
+    u32 vr_index;
+    void *addr;
+
+    addr = (is_ipv6) ? (void *) &vr_addr->ip6 : (void *) &vr_addr->ip4;
+    vr_index = vrrp_vr_lookup_address (vr_conf->sw_if_index, is_ipv6, addr);
+    if (vr_index != ~0)
+      return VNET_API_ERROR_ADDRESS_IN_USE;
+  }
+
+  return 0;
+}
+
+static int
+vrrp_vr_valid_addrs (vrrp_vr_config_t * vr_conf)
+{
+  int ret = 0;
+
+  /* If the VR owns the addresses, make sure they are configured */
+  if (vr_conf->priority == 255 &&
+      (ret = vrrp_vr_valid_addrs_owner (vr_conf)) < 0)
+    return ret;
+
+  /* make sure no other VR has already configured any of the VR addresses */
+  ret = vrrp_vr_valid_addrs_unused (vr_conf);
+
+  return ret;
+}
+
+int
+vrrp_vr_addr_add_del (vrrp_vr_t * vr, u8 is_add, ip46_address_t * vr_addr)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  u32 vr_index;
+  vrrp4_arp_key_t key4;
+  vrrp6_nd_key_t key6;
+  ip46_address_t *addr;
+
+  if (!vr || !vr_addr)
+    return VNET_API_ERROR_INVALID_ARGUMENT;
+
+  vr_index = vr - vmp->vrs;
+
+  if (vrrp_vr_is_ipv6 (vr))
+    {
+      key6.sw_if_index = vr->config.sw_if_index;
+      key6.addr = vr_addr->ip6;
+      if (is_add)
+       {
+         hash_set_mem_alloc (&vmp->vrrp6_nd_lookup, &key6, vr_index);
+         vec_add1 (vr->config.vr_addrs, vr_addr[0]);
+       }
+      else
+       {
+         hash_unset_mem_free (&vmp->vrrp6_nd_lookup, &key6);
+         vec_foreach (addr, vr->config.vr_addrs)
+         {
+           if (!ip46_address_cmp (addr, vr_addr))
+             {
+               vec_del1 (vr->config.vr_addrs, vr->config.vr_addrs - addr);
+               break;
+             }
+         }
+       }
+    }
+  else
+    {
+      key4.sw_if_index = vr->config.sw_if_index;
+      key4.addr = vr_addr->ip4;
+      if (is_add)
+       {
+         hash_set (vmp->vrrp4_arp_lookup, key4.as_u64, vr_index);
+         vec_add1 (vr->config.vr_addrs, vr_addr[0]);
+       }
+      else
+       {
+         hash_unset (vmp->vrrp4_arp_lookup, key4.as_u64);
+         vec_foreach (addr, vr->config.vr_addrs)
+         {
+           if (!ip46_address_cmp (addr, vr_addr))
+             {
+               vec_del1 (vr->config.vr_addrs, vr->config.vr_addrs - addr);
+               break;
+             }
+         }
+       }
+    }
+
+  return 0;
+}
+
+static void
+vrrp_vr_addrs_add_del (vrrp_vr_t * vr, u8 is_add, ip46_address_t * vr_addrs)
+{
+  ip46_address_t *vr_addr;
+
+  vec_foreach (vr_addr, vr_addrs)
+  {
+    vrrp_vr_addr_add_del (vr, is_add, vr_addr);
+  }
+}
+
+/* Action function shared between message handler and debug CLI */
+int
+vrrp_vr_add_del (u8 is_add, vrrp_vr_config_t * vr_conf)
+{
+  vrrp_main_t *vrm = &vrrp_main;
+  vnet_main_t *vnm = vnet_get_main ();
+  vrrp_vr_key_t key;
+  uword *p;
+  u32 vr_index;
+  vrrp_vr_t *vr = 0;
+  int ret;
+
+  if (vr_conf->sw_if_index == ~0 ||
+      !vnet_sw_interface_is_valid (vnm, vr_conf->sw_if_index))
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  clib_memset (&key, 0, sizeof (key));
+
+  key.sw_if_index = vr_conf->sw_if_index;
+  key.vr_id = vr_conf->vr_id;
+  key.is_ipv6 = ((vr_conf->flags & VRRP_VR_IPV6) != 0);
+
+  p = mhash_get (&vrm->vr_index_by_key, &key);
+
+  if (is_add)
+    {
+      /* does a VR matching this key already exist ? */
+      if (p)
+       {
+         clib_warning ("VR %u for IPv%d already exists on sw_if_index %u",
+                       key.vr_id, (key.is_ipv6) ? 6 : 4, key.sw_if_index);
+         return VNET_API_ERROR_ENTRY_ALREADY_EXISTS;
+       }
+
+      /* were IPvX addresses included ? */
+      if (!vec_len (vr_conf->vr_addrs))
+       {
+         clib_warning ("Conf of VR %u for IPv%d on sw_if_index %u "
+                       " does not contain IP addresses",
+                       key.vr_id, (key.is_ipv6) ? 6 : 4, key.sw_if_index);
+         return VNET_API_ERROR_INVALID_SRC_ADDRESS;
+       }
+
+      /* Make sure the addresses are ok to use */
+      if ((ret = vrrp_vr_valid_addrs (vr_conf)) < 0)
+       return ret;
+
+      pool_get_zero (vrm->vrs, vr);
+      vr_index = vr - vrm->vrs;
+
+      clib_memcpy (&vr->config, vr_conf, sizeof (vrrp_vr_config_t));
+
+      vr->config.vr_addrs = 0; /* allocate our own memory */
+      vrrp_vr_addrs_add_del (vr, is_add, vr_conf->vr_addrs);
+
+      vr->runtime.state = VRRP_VR_STATE_INIT;
+      vr->runtime.timer_index = ~0;
+
+      /* set virtual MAC based on IP version and VR ID */
+      vr->runtime.mac = (key.is_ipv6) ? ipv6_vmac : ipv4_vmac;
+      vr->runtime.mac.bytes[5] = vr_conf->vr_id;
+
+      mhash_set (&vrm->vr_index_by_key, &key, vr_index, 0);
+    }
+  else
+    {
+      if (!p)
+       {
+         clib_warning ("No VR %u for IPv%d exists on sw_if_index %u",
+                       key.vr_id, (key.is_ipv6) ? 6 : 4, key.sw_if_index);
+         return VNET_API_ERROR_NO_SUCH_ENTRY;
+       }
+
+      vr_index = p[0];
+      vr = pool_elt_at_index (vrm->vrs, vr_index);
+
+      vrrp_vr_tracking_ifs_add_del (vr, vr->tracking.interfaces, is_add);
+      vrrp_vr_addrs_add_del (vr, is_add, vr->config.vr_addrs);
+      mhash_unset (&vrm->vr_index_by_key, &key, 0);
+      vec_free (vr->config.vr_addrs);
+      vec_free (vr->tracking.interfaces);
+      pool_put (vrm->vrs, vr);
+    }
+
+  vrrp_intf_vr_add_del (is_add, vr_conf->sw_if_index, vr_index, key.is_ipv6);
+
+  return 0;
+}
+
+int
+vrrp_vr_start_stop (u8 is_start, vrrp_vr_key_t * vr_key)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  uword *p;
+  vrrp_vr_t *vr = 0;
+
+  p = mhash_get (&vmp->vr_index_by_key, vr_key);
+  if (!p)
+    return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+  vr = pool_elt_at_index (vmp->vrs, p[0]);
+
+  /* return success if already in the desired state */
+  switch (vr->runtime.state)
+    {
+    case VRRP_VR_STATE_INIT:
+      if (!is_start)
+       {
+         clib_warning ("Attempting to stop already stopped VR (%U)",
+                       format_vrrp_vr_key, vr);
+         return 0;
+       }
+      break;
+    default:
+      if (is_start)
+       {
+         clib_warning ("Attempting to start already started VR (%U)",
+                       format_vrrp_vr_key, vr);
+         return 0;
+       }
+      break;
+    }
+
+  if (is_start)
+    {
+      if (vrrp_vr_is_unicast (vr) && vec_len (vr->config.peer_addrs) == 0)
+       {
+         clib_warning ("Cannot start unicast VR without peers");
+         return VNET_API_ERROR_INIT_FAILED;
+       }
+
+      vmp->n_vrs_started++;
+
+      if (!vrrp_intf_is_up (vr->config.sw_if_index, vrrp_vr_is_ipv6 (vr),
+                           NULL))
+       {
+         clib_warning ("VRRP VR started on down interface (%U)",
+                       format_vrrp_vr_key, vr);
+         vrrp_vr_transition (vr, VRRP_VR_STATE_INTF_DOWN, NULL);
+       }
+      else if (vrrp_vr_is_owner (vr))
+       vrrp_vr_transition (vr, VRRP_VR_STATE_MASTER, NULL);
+      else
+       vrrp_vr_transition (vr, VRRP_VR_STATE_BACKUP, NULL);
+    }
+  else
+    {
+      vmp->n_vrs_started--;
+
+      vrrp_vr_transition (vr, VRRP_VR_STATE_INIT, NULL);
+    }
+
+  clib_warning ("%d VRs configured, %d VRs running",
+               pool_elts (vmp->vrs), vmp->n_vrs_started);
+
+  return 0;
+}
+
+static int
+vrrp_vr_set_peers_validate (vrrp_vr_t * vr, ip46_address_t * peers)
+{
+  if (!vrrp_vr_is_unicast (vr))
+    {
+      clib_warning ("Peers can only be set on a unicast VR");
+      return VNET_API_ERROR_INVALID_ARGUMENT;
+    }
+
+  if (vr->runtime.state != VRRP_VR_STATE_INIT)
+    {
+      clib_warning ("Cannot set peers on a running VR");
+      return VNET_API_ERROR_RSRC_IN_USE;
+    }
+
+  if (vec_len (peers) == 0)
+    {
+      clib_warning ("No peer addresses provided");
+      return VNET_API_ERROR_INVALID_DST_ADDRESS;
+    }
+
+  return 0;
+}
+
+int
+vrrp_vr_set_peers (vrrp_vr_key_t * vr_key, ip46_address_t * peers)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  uword *p;
+  vrrp_vr_t *vr = 0;
+  int ret = 0;
+
+  p = mhash_get (&vmp->vr_index_by_key, vr_key);
+  if (!p)
+    return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+  vr = pool_elt_at_index (vmp->vrs, p[0]);
+
+  ret = vrrp_vr_set_peers_validate (vr, peers);
+  if (ret < 0)
+    return ret;
+
+  if (vr->config.peer_addrs)
+    vec_free (vr->config.peer_addrs);
+
+  vr->config.peer_addrs = vec_dup (peers);
+
+  return 0;
+}
+
+/* Manage reference on the interface to the VRs which track that interface */
+static void
+vrrp_intf_tracking_vr_add_del (u32 sw_if_index, vrrp_vr_t * vr, u8 is_add)
+{
+  vrrp_intf_t *intf;
+  u32 vr_index;
+  u8 is_ipv6 = vrrp_vr_is_ipv6 (vr);
+  int i;
+
+  intf = vrrp_intf_get (sw_if_index);
+  vr_index = vrrp_vr_index (vr);
+
+  /* Try to find the VR index in the list of tracking VRs */
+  vec_foreach_index (i, intf->tracking_vrs[is_ipv6])
+  {
+    if (vec_elt (intf->tracking_vrs[is_ipv6], i) != vr_index)
+      continue;
+
+    /* Current index matches VR index */
+    if (!is_add)
+      vec_delete (intf->tracking_vrs[is_ipv6], 1, i);
+
+    /* If deleting, the job is done. If adding, it's already here */
+    return;
+  }
+
+  /* vr index was not found. */
+  if (is_add)
+    vec_add1 (intf->tracking_vrs[is_ipv6], vr_index);
+}
+
+/* Check if sw intf admin state is up or in the process of coming up */
+static int
+vrrp_intf_sw_admin_up (u32 sw_if_index, vrrp_intf_update_t * pending)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  int admin_up;
+
+  if (pending && (pending->type == VRRP_IF_UPDATE_SW_ADMIN))
+    admin_up = pending->intf_up;
+  else
+    admin_up = vnet_sw_interface_is_admin_up (vnm, sw_if_index);
+
+  return admin_up;
+}
+
+/* Check if hw intf link state is up or int the process of coming up */
+static int
+vrrp_intf_hw_link_up (u32 sw_if_index, vrrp_intf_update_t * pending)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  vnet_sw_interface_t *sup_sw;
+  int link_up;
+
+  sup_sw = vnet_get_sup_sw_interface (vnm, sw_if_index);
+
+  if (pending && (pending->type == VRRP_IF_UPDATE_HW_LINK) &&
+      (pending->hw_if_index == sup_sw->hw_if_index))
+    link_up = pending->intf_up;
+  else
+    link_up = vnet_hw_interface_is_link_up (vnm, sup_sw->hw_if_index);
+
+  return link_up;
+}
+
+/* Check if interface has ability to send IP packets. */
+static int
+vrrp_intf_ip_up (u32 sw_if_index, u8 is_ipv6, vrrp_intf_update_t * pending)
+{
+  int ip_up;
+
+  if (pending && pending->type == VRRP_IF_UPDATE_IP)
+    ip_up = pending->intf_up;
+  else
+    /* Either a unicast address has to be explicitly assigned, or
+     * for IPv6 only, a link local assigned and multicast/ND enabled
+     */
+    ip_up =
+      ((ip_interface_get_first_ip (sw_if_index, !is_ipv6) != 0) ||
+       (is_ipv6 && ip6_link_is_enabled (sw_if_index)));
+
+  return ip_up;
+}
+
+static int
+vrrp_intf_is_up (u32 sw_if_index, u8 is_ipv6, vrrp_intf_update_t * pending)
+{
+  int admin_up, link_up, ip_up;
+
+  admin_up = vrrp_intf_sw_admin_up (sw_if_index, pending);
+  link_up = vrrp_intf_hw_link_up (sw_if_index, pending);
+  ip_up = vrrp_intf_ip_up (sw_if_index, is_ipv6, pending);
+
+  return (admin_up && link_up && ip_up);
+}
+
+/* Examine the state of interfaces tracked by a VR and compute the priority
+ * adjustment that should be applied to the VR. If this is being called
+ * by the hw_link_up_down callback, the pending new flags on the sup hw
+ * interface have not been updated yet, so accept those as an optional
+ * argument.
+ */
+void
+vrrp_vr_tracking_ifs_compute (vrrp_vr_t * vr, vrrp_intf_update_t * pending)
+{
+  vrrp_vr_tracking_if_t *intf;
+  u32 total_priority = 0;
+
+  vec_foreach (intf, vr->tracking.interfaces)
+  {
+    if (vrrp_intf_is_up (intf->sw_if_index, vrrp_vr_is_ipv6 (vr), pending))
+      continue;
+
+    total_priority += intf->priority;
+  }
+
+  if (total_priority != vr->tracking.interfaces_dec)
+    {
+      clib_warning ("VR %U interface track adjustment change from %u to %u",
+                   format_vrrp_vr_key, vr, vr->tracking.interfaces_dec,
+                   total_priority);
+      vr->tracking.interfaces_dec = total_priority;
+    }
+}
+
+/* Manage tracked interfaces on a VR */
+int
+vrrp_vr_tracking_if_add_del (vrrp_vr_t * vr, u32 sw_if_index, u8 prio,
+                            u8 is_add)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  vrrp_vr_tracking_if_t *track_intf;
+
+  /* VR can't track non-existent interface */
+  if (!vnet_sw_interface_is_valid (vnm, sw_if_index))
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  /* VR can't track it's own interface */
+  if (sw_if_index == vr->config.sw_if_index)
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX_2;
+
+  /* update intf vector of tracking VRs */
+  vrrp_intf_tracking_vr_add_del (sw_if_index, vr, is_add);
+
+  /* update VR vector of tracked interfaces */
+  vec_foreach (track_intf, vr->tracking.interfaces)
+  {
+    if (track_intf->sw_if_index != sw_if_index)
+      continue;
+
+    /* found it */
+    if (!is_add)
+      vec_delete
+       (vr->tracking.interfaces, 1, track_intf - vr->tracking.interfaces);
+
+    return 0;
+  }
+
+  if (is_add)
+    {
+      vec_add2 (vr->tracking.interfaces, track_intf, 1);
+
+      track_intf->sw_if_index = sw_if_index;
+      track_intf->priority = prio;
+    }
+
+  return 0;
+}
+
+int
+vrrp_vr_tracking_ifs_add_del (vrrp_vr_t * vr,
+                             vrrp_vr_tracking_if_t * track_ifs, u8 is_add)
+{
+  vrrp_vr_tracking_if_t *track_if, *ifs_copy;
+  int rv = 0;
+
+  /* if deleting & track_ifs points to the VR list of tracked intfs, the
+   * vector could be modified as we iterate it. make a copy instead */
+  ifs_copy = vec_dup (track_ifs);
+
+  /* add each tracked interface in the vector */
+  vec_foreach (track_if, ifs_copy)
+  {
+    rv = vrrp_vr_tracking_if_add_del (vr, track_if->sw_if_index,
+                                     track_if->priority, (is_add != 0));
+
+    /* if operation failed, undo the previous changes */
+    if (rv)
+      {
+       vrrp_vr_tracking_if_t *rb_if;
+
+       for (rb_if = track_if - 1; rb_if >= track_ifs; rb_if -= 1)
+         vrrp_vr_tracking_if_add_del (vr, rb_if->sw_if_index,
+                                      rb_if->priority, !(is_add != 0));
+       break;
+      }
+  }
+
+  vec_free (ifs_copy);
+
+  vrrp_vr_tracking_ifs_compute (vr, 0);
+
+  return rv;
+}
+
+/* Compute priority to advertise on all VRs which track the given interface
+ * and address family. The flags on an HW interface are not updated until
+ * after link up/down callbacks are invoked, so if this function is called
+ * by the link up/down callback, the flags about to be set will be passed
+ * via the 'pending' argument. Otherwise, pending will be NULL.
+ */
+static void
+vrrp_intf_tracking_vrs_compute (u32 sw_if_index,
+                               vrrp_intf_update_t * pending, u8 is_ipv6)
+{
+  u32 *vr_index;
+  vrrp_vr_t *vr;
+  vrrp_intf_t *intf = vrrp_intf_get (sw_if_index);
+
+  vec_foreach (vr_index, intf->tracking_vrs[is_ipv6])
+  {
+    vr = vrrp_vr_lookup_index (*vr_index);
+    if (vr)
+      vrrp_vr_tracking_ifs_compute (vr, pending);
+  }
+}
+
+/* Interface being brought up/down is a quasi-{startup/shutdown} event.
+ * Execute an appropriate state transition for all VRs on the interface.
+ * This function may be invoked by:
+ *    sw interface admin up/down event
+ *    hw interface link up/down event
+ */
+clib_error_t *
+vrrp_sw_interface_up_down (vrrp_intf_update_t * pending)
+{
+  vrrp_intf_t *intf;
+  int i;
+  u32 *vr_index;
+  vrrp_vr_t *vr;
+
+  intf = vrrp_intf_get (pending->sw_if_index);
+  if (!intf)
+    return 0;
+
+  /* adjust state of VR's configured on this interface */
+  for (i = 0; i < 2; i++)
+    {
+      int is_up;
+
+      if (!intf->vr_indices[i])
+       continue;
+
+      is_up = vrrp_intf_is_up (pending->sw_if_index, i, pending);
+
+      vec_foreach (vr_index, intf->vr_indices[i])
+      {
+       vrrp_vr_state_t vr_state;
+
+       vr = vrrp_vr_lookup_index (*vr_index);
+       if (!vr)
+         continue;
+
+       if (vr->runtime.state == VRRP_VR_STATE_INIT)
+         continue;             /* VR not started yet, no transition */
+
+       if (!is_up)
+         vr_state = VRRP_VR_STATE_INTF_DOWN;
+       else
+         {
+           if (vr->runtime.state != VRRP_VR_STATE_INTF_DOWN)
+             continue;         /* shouldn't happen */
+
+           vr_state = (vrrp_vr_is_owner (vr)) ?
+             VRRP_VR_STATE_MASTER : VRRP_VR_STATE_BACKUP;
+         }
+
+       vrrp_vr_transition (vr, vr_state, NULL);
+      }
+    }
+
+  /* compute adjustments on any VR's tracking this interface */
+  vrrp_intf_tracking_vrs_compute (pending->sw_if_index, pending,
+                                 0 /* is_ipv6 */ );
+  vrrp_intf_tracking_vrs_compute (pending->sw_if_index, pending,
+                                 1 /* is_ipv6 */ );
+
+  return 0;
+}
+
+/* Process change in admin status on an interface */
+clib_error_t *
+vrrp_sw_interface_admin_up_down (vnet_main_t * vnm, u32 sw_if_index,
+                                u32 flags)
+{
+  vrrp_intf_update_t pending = {
+    .type = VRRP_IF_UPDATE_SW_ADMIN,
+    .sw_if_index = sw_if_index,
+    .intf_up = ((flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) != 0),
+  };
+
+  return vrrp_sw_interface_up_down (&pending);
+}
+
+VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (vrrp_sw_interface_admin_up_down);
+
+static walk_rc_t
+vrrp_hw_interface_link_up_down_walk (vnet_main_t * vnm,
+                                    u32 sw_if_index, void *arg)
+{
+  vrrp_intf_update_t *pending = arg;
+
+  pending->sw_if_index = sw_if_index;
+  vrrp_sw_interface_up_down (pending);
+
+  return WALK_CONTINUE;
+}
+
+static clib_error_t *
+vrrp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
+{
+  vrrp_intf_update_t pending = {
+    .type = VRRP_IF_UPDATE_HW_LINK,
+    .hw_if_index = hw_if_index,
+    .intf_up = ((flags & VNET_HW_INTERFACE_FLAG_LINK_UP) != 0),
+  };
+
+  /* walk the sw interface and sub interfaces to adjust interface tracking */
+  vnet_hw_interface_walk_sw (vnm, hw_if_index,
+                            vrrp_hw_interface_link_up_down_walk, &pending);
+
+  return 0;
+}
+
+VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (vrrp_hw_interface_link_up_down);
+
+static void
+vrrp_ip4_add_del_interface_addr (ip4_main_t * im,
+                                uword opaque,
+                                u32 sw_if_index,
+                                ip4_address_t * address,
+                                u32 address_length,
+                                u32 if_address_index, u32 is_del)
+{
+  vrrp_intf_tracking_vrs_compute (sw_if_index, NULL, 0 /* is_ipv6 */ );
+}
+
+static ip6_link_delegate_id_t vrrp_ip6_delegate_id;
+
+static u8 *
+format_vrrp_ip6_link (u8 * s, va_list * args)
+{
+  index_t indi = va_arg (*args, index_t);
+  u32 indent = va_arg (*args, u32);
+  vrrp_intf_t *intf;
+  u32 *vr_index;
+
+  intf = vrrp_intf_get ((u32) indi);
+
+  s = format (s, "%UVRRP VRs monitoring this link:\n",
+             format_white_space, indent);
+
+  vec_foreach (vr_index, intf->tracking_vrs[1])
+  {
+    vrrp_vr_t *vr = vrrp_vr_lookup_index (*vr_index);
+
+    s = format (s, "%U%U\n", format_white_space, indent + 2,
+               format_vrrp_vr_key, vr);
+  }
+
+  return s;
+}
+
+static void
+vrrp_intf_ip6_enable_disable (u32 sw_if_index, int enable)
+{
+  vrrp_intf_update_t pending = {
+    .type = VRRP_IF_UPDATE_IP,
+    .sw_if_index = sw_if_index,
+    .intf_up = enable,
+  };
+
+  vrrp_intf_tracking_vrs_compute (sw_if_index, &pending, 1 /* is_ipv6 */ );
+}
+
+static void
+vrrp_intf_ip6_enable (u32 sw_if_index)
+{
+  vrrp_intf_ip6_enable_disable (sw_if_index, 1 /* enable */ );
+  ip6_link_delegate_update (sw_if_index, vrrp_ip6_delegate_id, sw_if_index);
+}
+
+static void
+vrrp_intf_ip6_disable (index_t indi)
+{
+  vrrp_intf_ip6_enable_disable (indi, 0 /* enable */ );
+}
+
+const static ip6_link_delegate_vft_t vrrp_ip6_delegate_vft = {
+  .ildv_enable = vrrp_intf_ip6_enable,
+  .ildv_disable = vrrp_intf_ip6_disable,
+  .ildv_format = format_vrrp_ip6_link,
+};
+
+static clib_error_t *
+vrrp_init (vlib_main_t * vm)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  clib_error_t *error = 0;
+  ip4_main_t *im4 = &ip4_main;
+  ip4_add_del_interface_address_callback_t cb4;
+  vlib_node_t *intf_output_node;
+
+  clib_memset (vmp, 0, sizeof (*vmp));
+
+  if ((error = vlib_call_init_function (vm, ip4_lookup_init)) ||
+      (error = vlib_call_init_function (vm, ip6_lookup_init)))
+    return error;
+
+  vmp->vlib_main = vm;
+  vmp->vnet_main = vnet_get_main ();
+
+  intf_output_node = vlib_get_node_by_name (vm, (u8 *) "interface-output");
+  vmp->intf_output_node_idx = intf_output_node->index;
+
+  error = vrrp_plugin_api_hookup (vm);
+
+  if (error)
+    return error;
+
+  mhash_init (&vmp->vr_index_by_key, sizeof (u32), sizeof (vrrp_vr_key_t));
+  vmp->vrrp4_arp_lookup = hash_create (0, sizeof (uword));
+  vmp->vrrp6_nd_lookup = hash_create_mem (0, sizeof (vrrp6_nd_key_t),
+                                         sizeof (uword));
+
+  cb4.function = vrrp_ip4_add_del_interface_addr;
+  cb4.function_opaque = 0;
+  vec_add1 (im4->add_del_interface_address_callbacks, cb4);
+
+  vrrp_ip6_delegate_id = ip6_link_delegate_register (&vrrp_ip6_delegate_vft);
+
+  return error;
+}
+
+VLIB_INIT_FUNCTION (vrrp_init);
+
+
+/* *INDENT-OFF* */
+VLIB_PLUGIN_REGISTER () =
+{
+  .version = VPP_BUILD_VER,
+  .description = "VRRP v3 (RFC 5798)",
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/vrrp/vrrp.h b/src/plugins/vrrp/vrrp.h
new file mode 100644 (file)
index 0000000..9c636c4
--- /dev/null
@@ -0,0 +1,373 @@
+
+/*
+ * vrrp.h - vrrp plug-in header file
+ *
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+#ifndef __included_vrrp_h__
+#define __included_vrrp_h__
+
+#include <vnet/vnet.h>
+#include <vnet/ip/ip.h>
+#include <vnet/ethernet/ethernet.h>
+
+#include <vppinfra/hash.h>
+#include <vppinfra/error.h>
+
+/* VRRP configuration */
+typedef enum vrrp_vr_flags
+{
+  VRRP_VR_PREEMPT = 0x1,
+  VRRP_VR_ACCEPT = 0x2,
+  VRRP_VR_UNICAST = 0x4,
+  VRRP_VR_IPV6 = 0x8,
+} vrrp_vr_flags_t;
+
+typedef struct vrrp_vr_key
+{
+  u32 sw_if_index;
+  u8 vr_id;
+  u8 is_ipv6;
+} vrrp_vr_key_t;
+
+/* *INDENT-OFF* */
+typedef CLIB_PACKED
+(struct vrrp4_arp_key {
+  union {
+    struct {
+      u32 sw_if_index;
+      ip4_address_t addr;
+    };
+    u64 as_u64;
+  };
+}) vrrp4_arp_key_t;
+/* *INDENT-ON* */
+
+/* *INDENT-OFF* */
+typedef CLIB_PACKED
+(struct vrrp6_nd_key {
+  u32 sw_if_index;
+  ip6_address_t addr;
+}) vrrp6_nd_key_t;
+/* *INDENT-ON* */
+
+typedef struct vrrp_vr_tracking_if
+{
+  u32 sw_if_index;
+  u8 priority;
+} vrrp_vr_tracking_if_t;
+
+typedef struct vrrp_vr_tracking
+{
+  vrrp_vr_tracking_if_t *interfaces;
+  u32 interfaces_dec;
+} vrrp_vr_tracking_t;
+
+typedef struct vrrp_vr_config
+{
+  u32 sw_if_index;
+  u8 vr_id;
+  u8 priority;
+  u16 adv_interval;
+  vrrp_vr_flags_t flags;
+  ip46_address_t *vr_addrs;
+  ip46_address_t *peer_addrs;
+} vrrp_vr_config_t;
+
+#define foreach_vrrp_vr_state          \
+_(0, INIT, "Initialize")               \
+_(1, BACKUP, "Backup")                 \
+_(2, MASTER, "Master")                 \
+_(3, INTF_DOWN, "Interface Down")
+
+/* VRRP runtime data */
+typedef enum vrrp_vr_state
+{
+#define _(v,f,n) VRRP_VR_STATE_##f = v,
+  foreach_vrrp_vr_state
+#undef _
+} vrrp_vr_state_t;
+
+typedef struct vrrp_vr_runtime
+{
+  vrrp_vr_state_t state;
+  u16 master_adv_int;
+  u16 skew;
+  u16 master_down_int;
+  mac_address_t mac;
+  f64 last_sent;
+  u32 timer_index;
+} vrrp_vr_runtime_t;
+
+/* Per-VR data */
+typedef struct vrrp_vr
+{
+  vrrp_vr_config_t config;
+  vrrp_vr_runtime_t runtime;
+  vrrp_vr_tracking_t tracking;
+} vrrp_vr_t;
+
+/* Timers */
+typedef enum vrrp_vr_timer_type
+{
+  VRRP_VR_TIMER_ADV,
+  VRRP_VR_TIMER_MASTER_DOWN,
+} vrrp_vr_timer_type_t;
+
+typedef struct vrrp_vr_timer
+{
+  u32 vr_index;
+  f64 expire_time;             /* monotonic, relative to vlib_time_now() */
+  vrrp_vr_timer_type_t type;
+} vrrp_vr_timer_t;
+
+typedef struct
+{
+  /* vectors of vr indices which are configured on this interface
+   * 0 -> ipv4, 1 -> ipv6 */
+  u32 *vr_indices[2];
+
+  /* vector of VR indices which track the state of this interface
+   * 0 -> ipv4, 1*/
+  u32 *tracking_vrs[2];
+
+  /* multicast adjacency indices. 0 -> ipv4, 1 -> ipv6 */
+  adj_index_t mcast_adj_index[2];
+
+  /* number of VRs in master state on sw intf. 0 -> ipv4, 1 -> ipv6 */
+  u8 n_master_vrs[2];
+
+} vrrp_intf_t;
+
+typedef struct
+{
+  /* API message ID base */
+  u16 msg_id_base;
+
+  /* pool of VRs */
+  vrrp_vr_t *vrs;
+
+  /* pool of timers and ordered vector of pool indices */
+  vrrp_vr_timer_t *vr_timers;
+  u32 *pending_timers;
+
+  /* number of running VRs - don't register for VRRP proto if not running */
+  u16 n_vrs_started;
+
+  /* hash mapping a VR key to a pool entry */
+  mhash_t vr_index_by_key;
+
+  /* hashes mapping sw_if_index and address to a vr index */
+  uword *vrrp4_arp_lookup;
+  uword *vrrp6_nd_lookup;
+
+  /* vector of interface data indexed by sw_if_index */
+  vrrp_intf_t *vrrp_intfs;
+
+  /* convenience */
+  vlib_main_t *vlib_main;
+  vnet_main_t *vnet_main;
+  ethernet_main_t *ethernet_main;
+
+  u32 intf_output_node_idx;
+} vrrp_main_t;
+
+extern vrrp_main_t vrrp_main;
+
+extern vlib_node_registration_t vrrp_node;
+extern vlib_node_registration_t vrrp_periodic_node;
+
+/* Periodic function events */
+#define VRRP_EVENT_VR_TIMER_UPDATE 1
+#define VRRP_EVENT_VR_STOP 2
+#define VRRP_EVENT_PERIODIC_ENABLE_DISABLE 3
+
+clib_error_t *vrrp_plugin_api_hookup (vlib_main_t * vm);
+
+int vrrp_vr_add_del (u8 is_add, vrrp_vr_config_t * conf);
+int vrrp_vr_start_stop (u8 is_start, vrrp_vr_key_t * vr_key);
+extern u8 *format_vrrp_vr (u8 * s, va_list * args);
+extern u8 *format_vrrp_vr_key (u8 * s, va_list * args);
+extern u8 *format_vrrp_vr_state (u8 * s, va_list * args);
+extern u8 *format_vrrp_packet_hdr (u8 * s, va_list * args);
+void vrrp_vr_timer_set (vrrp_vr_t * vr, vrrp_vr_timer_type_t type);
+void vrrp_vr_timer_cancel (vrrp_vr_t * vr);
+void vrrp_vr_transition (vrrp_vr_t * vr, vrrp_vr_state_t new_state,
+                        void *data);
+int vrrp_vr_set_peers (vrrp_vr_key_t * key, ip46_address_t * peers);
+int vrrp_vr_multicast_group_join (vrrp_vr_t * vr);
+int vrrp_adv_send (vrrp_vr_t * vr, int shutdown);
+int vrrp_garp_or_na_send (vrrp_vr_t * vr);
+u16 vrrp_adv_csum (void *l3_hdr, void *payload, u8 is_ipv6, u16 len);
+int vrrp_vr_tracking_if_add_del (vrrp_vr_t * vr, u32 sw_if_index,
+                                u8 priority, u8 is_add);
+int vrrp_vr_tracking_ifs_add_del (vrrp_vr_t * vr,
+                                 vrrp_vr_tracking_if_t * track_ifs,
+                                 u8 is_add);
+
+
+always_inline void
+vrrp_vr_skew_compute (vrrp_vr_t * vr)
+{
+  vrrp_vr_config_t *vrc = &vr->config;
+  vrrp_vr_runtime_t *vrt = &vr->runtime;
+
+  vrt->skew = (((256 - vrc->priority) * vrt->master_adv_int) / 256);
+}
+
+always_inline void
+vrrp_vr_master_down_compute (vrrp_vr_t * vr)
+{
+  vrrp_vr_runtime_t *vrt = &vr->runtime;
+
+  vrt->master_down_int = (3 * vrt->master_adv_int) + vrt->skew;
+}
+
+always_inline vrrp_vr_t *
+vrrp_vr_lookup (u32 sw_if_index, u8 vr_id, u8 is_ipv6)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vrrp_vr_key_t key = {
+    .sw_if_index = sw_if_index,
+    .vr_id = vr_id,
+    .is_ipv6 = (is_ipv6 != 0),
+  };
+  uword *p;
+
+  p = mhash_get (&vmp->vr_index_by_key, &key);
+  if (p)
+    return pool_elt_at_index (vmp->vrs, p[0]);
+
+  return 0;
+}
+
+always_inline vrrp_vr_t *
+vrrp_vr_lookup_index (u32 vr_index)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+
+  if (pool_is_free_index (vmp->vrs, vr_index))
+    return 0;
+
+  return pool_elt_at_index (vmp->vrs, vr_index);
+}
+
+always_inline u32
+vrrp_vr_lookup_address (u32 sw_if_index, u8 is_ipv6, void *addr)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  uword *p;
+  vrrp4_arp_key_t key4;
+  vrrp6_nd_key_t key6;
+
+  if (is_ipv6)
+    {
+      key6.sw_if_index = sw_if_index;
+      key6.addr = ((ip6_address_t *) addr)[0];
+      p = hash_get_mem (vmp->vrrp6_nd_lookup, &key6);
+    }
+  else
+    {
+      key4.sw_if_index = sw_if_index;
+      key4.addr = ((ip4_address_t *) addr)[0];
+      p = hash_get (vmp->vrrp4_arp_lookup, key4.as_u64);
+    }
+
+  if (p)
+    return p[0];
+
+  return ~0;
+}
+
+always_inline vrrp_intf_t *
+vrrp_intf_get (u32 sw_if_index)
+{
+  vrrp_main_t *vrm = &vrrp_main;
+
+  if (sw_if_index == ~0)
+    return NULL;
+
+  vec_validate (vrm->vrrp_intfs, sw_if_index);
+  return vec_elt_at_index (vrm->vrrp_intfs, sw_if_index);
+}
+
+always_inline int
+vrrp_intf_num_vrs (u32 sw_if_index, u8 is_ipv6)
+{
+  vrrp_intf_t *intf = vrrp_intf_get (sw_if_index);
+
+  if (intf)
+    return vec_len (intf->vr_indices[is_ipv6]);
+
+  return 0;
+}
+
+always_inline u8
+vrrp_vr_is_ipv6 (vrrp_vr_t * vr)
+{
+  return ((vr->config.flags & VRRP_VR_IPV6) != 0);
+}
+
+always_inline u8
+vrrp_vr_is_unicast (vrrp_vr_t * vr)
+{
+  return ((vr->config.flags & VRRP_VR_UNICAST) != 0);
+}
+
+always_inline u8
+vrrp_vr_is_owner (vrrp_vr_t * vr)
+{
+  return (vr->config.priority == 255);
+}
+
+always_inline u8
+vrrp_vr_n_vr_addrs (vrrp_vr_t * vr)
+{
+  return vec_len (vr->config.vr_addrs);
+}
+
+always_inline u8
+vrrp_vr_n_peer_addrs (vrrp_vr_t * vr)
+{
+  return vec_len (vr->config.peer_addrs);
+}
+
+always_inline u8
+vrrp_vr_accept_mode_enabled (vrrp_vr_t * vr)
+{
+  return ((vr->config.flags & VRRP_VR_ACCEPT) != 0);
+}
+
+always_inline u32
+vrrp_vr_index (vrrp_vr_t * vr)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+
+  return vr - vmp->vrs;
+}
+
+always_inline u8
+vrrp_vr_priority (vrrp_vr_t * vr)
+{
+  u8 rv;
+
+  if (vr->tracking.interfaces_dec < (u32) vr->config.priority)
+    rv = vr->config.priority - vr->tracking.interfaces_dec;
+  else
+    rv = 1;
+
+  return rv;
+}
+
+#endif /* __included_vrrp_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/vrrp/vrrp_all_api_h.h b/src/plugins/vrrp/vrrp_all_api_h.h
new file mode 100644 (file)
index 0000000..4f45909
--- /dev/null
@@ -0,0 +1,11 @@
+
+/*
+ * vrrp_all_api_h.h - vrrp plug-in api #include file
+ *
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+/* Include the generated file, see BUILT_SOURCES in Makefile.am */
+#include <vrrp/vrrp.api.h>
diff --git a/src/plugins/vrrp/vrrp_api.c b/src/plugins/vrrp/vrrp_api.c
new file mode 100644 (file)
index 0000000..3b9c256
--- /dev/null
@@ -0,0 +1,501 @@
+/*
+ * vrrp.c - vpp vrrp plug-in
+ *
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#include <vnet/vnet.h>
+#include <vnet/plugin/plugin.h>
+#include <vrrp/vrrp.h>
+
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#include <vpp/app/version.h>
+
+/* define message IDs */
+#include <vnet/format_fns.h>
+#include <vrrp/vrrp.api_enum.h>
+#include <vrrp/vrrp.api_types.h>
+
+#define REPLY_MSG_ID_BASE vmp->msg_id_base
+#include <vlibapi/api_helper_macros.h>
+
+/* API message handlers */
+static void
+vl_api_vrrp_vr_add_del_t_handler (vl_api_vrrp_vr_add_del_t * mp)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vl_api_vrrp_vr_add_del_reply_t *rmp;
+  vrrp_vr_config_t vr_conf;
+  u32 api_flags;
+  ip46_address_t *addrs = 0;
+  int rv;
+
+  api_flags = htonl (mp->flags);
+
+  clib_memset (&vr_conf, 0, sizeof (vr_conf));
+
+  vr_conf.sw_if_index = ntohl (mp->sw_if_index);
+  vr_conf.vr_id = mp->vr_id;
+  vr_conf.priority = mp->priority;
+  vr_conf.adv_interval = ntohs (mp->interval);
+
+  if (api_flags & VRRP_API_VR_PREEMPT)
+    vr_conf.flags |= VRRP_VR_PREEMPT;
+
+  if (api_flags & VRRP_API_VR_ACCEPT)
+    vr_conf.flags |= VRRP_VR_ACCEPT;
+
+  if (api_flags & VRRP_API_VR_UNICAST)
+    vr_conf.flags |= VRRP_VR_UNICAST;
+
+  if (api_flags & VRRP_API_VR_IPV6)
+    vr_conf.flags |= VRRP_VR_IPV6;
+
+  if (mp->is_add)
+    {
+      int i;
+
+      for (i = 0; i < mp->n_addrs; i++)
+       {
+         ip46_address_t *addr;
+         void *src, *dst;
+         int len;
+
+         vec_add2 (addrs, addr, 1);
+
+         if (ntohl (mp->addrs[i].af) == ADDRESS_IP4)
+           {
+             src = &mp->addrs[i].un.ip4;
+             dst = &addr->ip4;
+             len = sizeof (addr->ip4);
+           }
+         else
+           {
+             src = &mp->addrs[i].un.ip6;
+             dst = &addr->ip6;
+             len = sizeof (addr->ip6);
+           }
+
+         clib_memcpy (dst, src, len);
+       }
+
+      vr_conf.vr_addrs = addrs;
+    }
+
+  if (vr_conf.priority == 0)
+    {
+      clib_warning ("VR priority must be > 0");
+      rv = VNET_API_ERROR_INVALID_VALUE;
+    }
+  else if (vr_conf.adv_interval == 0)
+    {
+      clib_warning ("VR advertisement interval must be > 0");
+      rv = VNET_API_ERROR_INVALID_VALUE;
+    }
+  else if (vr_conf.vr_id == 0)
+    {
+      clib_warning ("VR ID must be > 0");
+      rv = VNET_API_ERROR_INVALID_VALUE;
+    }
+  else
+    rv = vrrp_vr_add_del (mp->is_add, &vr_conf);
+
+  vec_free (addrs);
+
+  REPLY_MACRO (VL_API_VRRP_VR_ADD_DEL_REPLY);
+}
+
+static void
+send_vrrp_vr_details (vrrp_vr_t * vr, vl_api_registration_t * reg,
+                     u32 context)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vl_api_vrrp_vr_details_t *mp;
+  int n_addrs, msg_size;
+  ip46_address_t *addr;
+  vl_api_address_t *api_addr;
+  u32 api_flags = 0;
+
+  n_addrs = vec_len (vr->config.vr_addrs);
+  msg_size = sizeof (*mp) + n_addrs * sizeof (*api_addr);
+  mp = vl_msg_api_alloc (msg_size);
+  if (!mp)
+    return;
+  clib_memset (mp, 0, msg_size);
+  mp->_vl_msg_id = htons (VL_API_VRRP_VR_DETAILS + vmp->msg_id_base);
+  mp->context = context;
+
+  /* config */
+  mp->config.sw_if_index = htonl (vr->config.sw_if_index);
+  mp->config.vr_id = vr->config.vr_id;
+  mp->config.priority = vr->config.priority;
+  mp->config.interval = htons (vr->config.adv_interval);
+
+  if (vr->config.flags & VRRP_VR_PREEMPT)
+    api_flags |= VRRP_API_VR_PREEMPT;
+  if (vr->config.flags & VRRP_VR_ACCEPT)
+    api_flags |= VRRP_API_VR_ACCEPT;
+  if (vrrp_vr_is_unicast (vr))
+    api_flags |= VRRP_API_VR_UNICAST;
+  if (vrrp_vr_is_ipv6 (vr))
+    api_flags |= VRRP_API_VR_IPV6;
+
+  mp->config.flags = htonl (api_flags);
+
+  /* runtime */
+  switch (vr->runtime.state)
+    {
+    case VRRP_VR_STATE_INIT:
+      mp->runtime.state = htonl (VRRP_API_VR_STATE_INIT);
+      break;
+    case VRRP_VR_STATE_BACKUP:
+      mp->runtime.state = htonl (VRRP_API_VR_STATE_BACKUP);
+      break;
+    case VRRP_VR_STATE_MASTER:
+      mp->runtime.state = htonl (VRRP_API_VR_STATE_MASTER);
+      break;
+    case VRRP_VR_STATE_INTF_DOWN:
+      mp->runtime.state = htonl (VRRP_API_VR_STATE_INTF_DOWN);
+      break;
+    default:
+      break;
+    }
+
+  mp->runtime.master_adv_int = htons (vr->runtime.master_adv_int);
+  mp->runtime.skew = htons (vr->runtime.skew);
+  mp->runtime.master_down_int = htons (vr->runtime.master_down_int);
+  clib_memcpy (&mp->runtime.mac, &vr->runtime.mac, sizeof (vr->runtime.mac));
+
+  mp->runtime.tracking.interfaces_dec = htonl (vr->tracking.interfaces_dec);
+  mp->runtime.tracking.priority = vrrp_vr_priority (vr);
+
+  /* addrs */
+  mp->n_addrs = vec_len (vr->config.vr_addrs);
+  api_addr = mp->addrs;
+  vec_foreach (addr, vr->config.vr_addrs)
+  {
+    void *src, *dst;
+    size_t len;
+
+    if (vrrp_vr_is_ipv6 (vr))
+      {
+       api_addr->af = htonl (ADDRESS_IP6);
+       dst = &api_addr->un.ip6;
+       src = &addr->ip6;
+       len = sizeof (addr->ip6);
+      }
+    else
+      {
+       api_addr->af = htonl (ADDRESS_IP4);
+       dst = &api_addr->un.ip4;
+       src = &addr->ip4;
+       len = sizeof (addr->ip4);
+      }
+    clib_memcpy (dst, src, len);
+    api_addr++;
+  }
+
+  vl_api_send_msg (reg, (u8 *) mp);
+}
+
+static void
+vl_api_vrrp_vr_dump_t_handler (vl_api_vrrp_vr_dump_t * mp)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vl_api_registration_t *reg;
+  vrrp_vr_t *vr;
+  u32 sw_if_index;
+
+  reg = vl_api_client_index_to_registration (mp->client_index);
+
+  sw_if_index = htonl (mp->sw_if_index);
+
+  /* *INDENT-OFF* */
+  pool_foreach (vr, vmp->vrs, ({
+
+    if (sw_if_index && (sw_if_index != ~0) &&
+       (sw_if_index != vr->config.sw_if_index))
+      continue;
+
+    send_vrrp_vr_details (vr, reg, mp->context);
+  }));
+  /* *INDENT-ON* */
+}
+
+static void
+vl_api_vrrp_vr_start_stop_t_handler (vl_api_vrrp_vr_start_stop_t * mp)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vl_api_vrrp_vr_start_stop_reply_t *rmp;
+  vrrp_vr_key_t vr_key;
+  int rv;
+
+  clib_memset (&vr_key, 0, sizeof (vr_key));
+
+  vr_key.sw_if_index = ntohl (mp->sw_if_index);
+  vr_key.vr_id = mp->vr_id;
+  vr_key.is_ipv6 = (mp->is_ipv6 != 0);
+
+  rv = vrrp_vr_start_stop ((mp->is_start != 0), &vr_key);
+
+  REPLY_MACRO (VL_API_VRRP_VR_START_STOP_REPLY);
+}
+
+static void
+vl_api_vrrp_vr_set_peers_t_handler (vl_api_vrrp_vr_set_peers_t * mp)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vl_api_vrrp_vr_set_peers_reply_t *rmp;
+  vrrp_vr_key_t vr_key;
+  ip46_address_t *peer_addrs = 0;
+  int i;
+  int rv;
+
+  clib_memset (&vr_key, 0, sizeof (vr_key));
+
+  vr_key.sw_if_index = ntohl (mp->sw_if_index);
+  vr_key.vr_id = mp->vr_id;
+  vr_key.is_ipv6 = (mp->is_ipv6 != 0);
+
+  for (i = 0; i < mp->n_addrs; i++)
+    {
+      ip46_address_t *peer;
+
+      vec_add2 (peer_addrs, peer, 1);
+
+      if (mp->is_ipv6)
+       clib_memcpy (&peer->ip6, mp->addrs[i].un.ip6, 16);
+      else
+       clib_memcpy (&peer->ip4, mp->addrs[i].un.ip4, 4);
+    }
+
+  rv = vrrp_vr_set_peers (&vr_key, peer_addrs);
+
+  vec_free (peer_addrs);
+  REPLY_MACRO (VL_API_VRRP_VR_SET_PEERS_REPLY);
+}
+
+static void
+send_vrrp_vr_peer_details (vrrp_vr_t * vr, vl_api_registration_t * reg,
+                          u32 context)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vl_api_vrrp_vr_peer_details_t *mp;
+  int n_addrs, msg_size;
+  ip46_address_t *addr;
+  vl_api_address_t *api_addr;
+
+  n_addrs = vec_len (vr->config.peer_addrs);
+  msg_size = sizeof (*mp) + n_addrs * sizeof (*api_addr);
+  mp = vl_msg_api_alloc (msg_size);
+  if (!mp)
+    return;
+  clib_memset (mp, 0, msg_size);
+  mp->_vl_msg_id = htons (VL_API_VRRP_VR_PEER_DETAILS + vmp->msg_id_base);
+  mp->context = context;
+
+  mp->sw_if_index = htonl (vr->config.sw_if_index);
+  mp->vr_id = vr->config.vr_id;
+  mp->is_ipv6 = vrrp_vr_is_ipv6 (vr);
+
+  /* addrs */
+  mp->n_peer_addrs = n_addrs;
+  api_addr = mp->peer_addrs;
+  vec_foreach (addr, vr->config.peer_addrs)
+  {
+    void *src, *dst;
+    size_t len;
+
+    if (vrrp_vr_is_ipv6 (vr))
+      {
+       api_addr->af = htonl (ADDRESS_IP6);
+       dst = &api_addr->un.ip6;
+       src = &addr->ip6;
+       len = sizeof (addr->ip6);
+      }
+    else
+      {
+       api_addr->af = htonl (ADDRESS_IP4);
+       dst = &api_addr->un.ip4;
+       src = &addr->ip4;
+       len = sizeof (addr->ip4);
+      }
+    clib_memcpy (dst, src, len);
+    api_addr++;
+  }
+
+  vl_api_send_msg (reg, (u8 *) mp);
+}
+
+static void
+vl_api_vrrp_vr_peer_dump_t_handler (vl_api_vrrp_vr_peer_dump_t * mp)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vl_api_registration_t *reg;
+  vrrp_vr_t *vr;
+  vrrp_vr_key_t vr_key;
+
+  reg = vl_api_client_index_to_registration (mp->client_index);
+
+  vr_key.sw_if_index = ntohl (mp->sw_if_index);
+
+  if (vr_key.sw_if_index && (vr_key.sw_if_index != ~0))
+    {
+      uword *p;
+      u32 vr_index = ~0;
+
+      vr_key.vr_id = mp->vr_id;
+      vr_key.is_ipv6 = mp->is_ipv6;
+
+      p = mhash_get (&vmp->vr_index_by_key, &vr_key);
+      if (!p)
+       return;
+
+      vr_index = p[0];
+      vr = pool_elt_at_index (vmp->vrs, vr_index);
+      send_vrrp_vr_peer_details (vr, reg, mp->context);
+
+      return;
+    }
+
+  /* *INDENT-OFF* */
+  pool_foreach (vr, vmp->vrs, ({
+
+    if (!vec_len (vr->config.peer_addrs))
+      continue;
+
+    send_vrrp_vr_details (vr, reg, mp->context);
+
+  }));
+  /* *INDENT-ON* */
+}
+
+static void
+  vl_api_vrrp_vr_track_if_add_del_t_handler
+  (vl_api_vrrp_vr_track_if_add_del_t * mp)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vl_api_vrrp_vr_track_if_add_del_reply_t *rmp;
+  vrrp_vr_t *vr;
+  vrrp_vr_tracking_if_t *track_if, *track_ifs = 0;
+  int rv = 0, i;
+
+  /* lookup VR and return error if it does not exist */
+  vr =
+    vrrp_vr_lookup (ntohl (mp->sw_if_index), mp->vr_id, (mp->is_ipv6 != 0));
+  if (!vr)
+    {
+      rv = VNET_API_ERROR_INVALID_VALUE;
+      goto done;
+    }
+
+  for (i = 0; i < mp->n_ifs; i++)
+    {
+      vl_api_vrrp_vr_track_if_t *api_track_if = &mp->ifs[i];
+
+      vec_add2 (track_ifs, track_if, 1);
+      track_if->sw_if_index = ntohl (api_track_if->sw_if_index);
+      track_if->priority = api_track_if->priority;
+    }
+
+  rv = vrrp_vr_tracking_ifs_add_del (vr, track_ifs, mp->is_add != 0);
+
+done:
+  vec_free (track_ifs);
+  REPLY_MACRO (VL_API_VRRP_VR_TRACK_IF_ADD_DEL_REPLY);
+}
+
+static void
+send_vrrp_vr_track_if_details (vrrp_vr_t * vr, vl_api_registration_t * reg,
+                              u32 context)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vl_api_vrrp_vr_track_if_details_t *mp;
+  int n_ifs, msg_size;
+  vl_api_vrrp_vr_track_if_t *api_track_if;
+  vrrp_vr_tracking_if_t *track_if;
+
+  if (!vr)
+    return;
+
+  n_ifs = vec_len (vr->tracking.interfaces);
+  msg_size = sizeof (*mp) + n_ifs * sizeof (*api_track_if);
+  mp = vl_msg_api_alloc (msg_size);
+  if (!mp)
+    return;
+  clib_memset (mp, 0, msg_size);
+  mp->_vl_msg_id = htons (VL_API_VRRP_VR_TRACK_IF_DETAILS + vmp->msg_id_base);
+  mp->context = context;
+
+  mp->sw_if_index = htonl (vr->config.sw_if_index);
+  mp->vr_id = vr->config.vr_id;
+  mp->is_ipv6 = vrrp_vr_is_ipv6 (vr);
+
+  /* tracked interfaces */
+  mp->n_ifs = n_ifs;
+  api_track_if = mp->ifs;
+  vec_foreach (track_if, vr->tracking.interfaces)
+  {
+    api_track_if->sw_if_index = htonl (track_if->sw_if_index);
+    api_track_if->priority = track_if->priority;
+    api_track_if += 1;
+  }
+
+  vl_api_send_msg (reg, (u8 *) mp);
+}
+
+static void
+vl_api_vrrp_vr_track_if_dump_t_handler (vl_api_vrrp_vr_track_if_dump_t * mp)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vl_api_registration_t *reg;
+  vrrp_vr_t *vr;
+
+  reg = vl_api_client_index_to_registration (mp->client_index);
+
+  if (!mp->dump_all)
+    {
+      vr = vrrp_vr_lookup (ntohl (mp->sw_if_index), mp->vr_id, mp->is_ipv6);
+      send_vrrp_vr_track_if_details (vr, reg, mp->context);
+
+      return;
+    }
+
+  /* *INDENT-OFF* */
+  pool_foreach (vr, vmp->vrs, ({
+
+    if (!vec_len (vr->tracking.interfaces))
+      continue;
+
+    send_vrrp_vr_track_if_details (vr, reg, mp->context);
+
+  }));
+  /* *INDENT-ON* */
+}
+
+/* Set up the API message handling tables */
+#include <vrrp/vrrp.api.c>
+clib_error_t *
+vrrp_plugin_api_hookup (vlib_main_t * vm)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+
+  /* Ask for a correctly-sized block of API message decode slots */
+  vmp->msg_id_base = setup_message_id_table ();
+
+  return 0;
+}
+
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/vrrp/vrrp_cli.c b/src/plugins/vrrp/vrrp_cli.c
new file mode 100644 (file)
index 0000000..447b474
--- /dev/null
@@ -0,0 +1,507 @@
+/*
+ * vrrp_cli.c - vrrp plugin debug CLI commands
+ *
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#include <vnet/vnet.h>
+#include <vnet/plugin/plugin.h>
+#include <vrrp/vrrp.h>
+
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#include <vpp/app/version.h>
+
+
+static clib_error_t *
+vrrp_vr_add_del_command_fn (vlib_main_t * vm,
+                           unformat_input_t * input,
+                           vlib_cli_command_t * cmd, u8 is_add)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vrrp_vr_config_t vr_conf;
+  u32 sw_if_index, vr_id, priority, interval;
+  ip46_address_t addr, *addrs;
+  u8 n_addrs4, n_addrs6;
+  clib_error_t *ret = 0;
+  int rv;
+
+  clib_memset (&vr_conf, 0, sizeof (vr_conf));
+
+  /* RFC 5798 - preempt enabled by default */
+  vr_conf.flags = VRRP_VR_PREEMPT;
+
+  addrs = 0;
+  n_addrs4 = n_addrs6 = 0;
+
+  /* defaults */
+  sw_if_index = ~0;
+  vr_id = 0;
+  priority = 100;
+  interval = 100;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      clib_memset (&addr, 0, sizeof (addr));
+
+      if (unformat (input, "%U", unformat_vnet_sw_interface, vmp->vnet_main,
+                   &sw_if_index))
+       ;
+      else if (unformat (input, "vr_id %u", &vr_id))
+       ;
+      else if (unformat (input, "ipv6"))
+       vr_conf.flags |= VRRP_VR_IPV6;
+      else if (unformat (input, "priority %u", &priority))
+       ;
+      else if (unformat (input, "interval %u", &interval))
+       ;
+      else if (unformat (input, "no_preempt"))
+       vr_conf.flags &= ~VRRP_VR_PREEMPT;
+      else if (unformat (input, "accept_mode"))
+       vr_conf.flags |= VRRP_VR_ACCEPT;
+      else if (unformat (input, "unicast"))
+       vr_conf.flags |= VRRP_VR_UNICAST;
+      else if (unformat (input, "%U", unformat_ip4_address, &addr.ip4))
+       {
+         n_addrs4++;
+         vec_add1 (addrs, addr);
+       }
+      else if (unformat (input, "%U", unformat_ip6_address, &addr.ip6))
+       {
+         n_addrs6++;
+         vec_add1 (addrs, addr);
+       }
+      else
+       break;
+    }
+
+  if (sw_if_index == ~0)
+    ret = clib_error_return (0, "Please specify an interface...");
+  else if (!vr_id || vr_id > 0xff)
+    ret = clib_error_return (0, "VR ID must be between 1 and 255...");
+
+  if (is_add)
+    {
+      if (!priority || priority > 0xff)
+       ret = clib_error_return (0, "priority must be between 1 and 255...");
+      else if (interval > 0xffff)
+       ret = clib_error_return (0, "interval must be <= 65535...");
+      else if (n_addrs4 && (n_addrs6 || vr_conf.flags & VRRP_VR_IPV6))
+       ret = clib_error_return (0, "Mismatched address families");
+    }
+
+  if (ret)                     /* data validation failed */
+    goto done;
+
+  vr_conf.sw_if_index = sw_if_index;
+  vr_conf.vr_id = (u8) vr_id;
+  vr_conf.priority = (u8) priority;
+  vr_conf.adv_interval = (u16) interval;
+  vr_conf.vr_addrs = addrs;
+
+  rv = vrrp_vr_add_del (is_add, &vr_conf);
+
+  switch (rv)
+    {
+    case 0:
+      break;
+
+      /* adding */
+    case VNET_API_ERROR_ENTRY_ALREADY_EXISTS:
+      ret = clib_error_return (0, "Failed to add VR that already exists");
+      goto done;
+      break;
+
+    case VNET_API_ERROR_INVALID_SRC_ADDRESS:
+      ret = clib_error_return (0, "Failed to add VR with no IP addresses");
+      goto done;
+      break;
+
+    case VNET_API_ERROR_ADDRESS_NOT_FOUND_FOR_INTERFACE:
+      ret = clib_error_return (0, "Failed to add VR with priority 255 - "
+                              "VR IP addresses not configured on interface");
+      goto done;
+      break;
+
+      /* deleting */
+    case VNET_API_ERROR_NO_SUCH_ENTRY:
+      ret = clib_error_return (0, "Failed to delete VR which does not exist");
+      goto done;
+      break;
+
+    default:
+      ret = clib_error_return (0, "vrrp_vr_add_del returned %d", rv);
+      goto done;
+      break;
+    }
+
+done:
+  vec_free (addrs);
+
+  return ret;
+}
+
+static clib_error_t *
+vrrp_vr_add_command_fn (vlib_main_t * vm, unformat_input_t * input,
+                       vlib_cli_command_t * cmd)
+{
+  return vrrp_vr_add_del_command_fn (vm, input, cmd, 1 /* is_add */ );
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (vrrp_vr_add_command, static) =
+{
+  .path = "vrrp vr add",
+  .short_help =
+  "vrrp vr add <interface> [vr_id <n>] [ipv6] [priority <value>] [interval <value>] [no_preempt] [accept_mode] [unicast] [<ip_addr> ...]",
+  .function = vrrp_vr_add_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+vrrp_vr_del_command_fn (vlib_main_t * vm, unformat_input_t * input,
+                       vlib_cli_command_t * cmd)
+{
+  return vrrp_vr_add_del_command_fn (vm, input, cmd, 0 /* is_add */ );
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (vrrp_vr_del_command, static) =
+{
+  .path = "vrrp vr del",
+  .short_help = "vrrp vr del <interface> [vr_id <n>] [ipv6]",
+  .function = vrrp_vr_del_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+vrrp_show_vr_command_fn (vlib_main_t * vm,
+                        unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vrrp_vr_t *vr;
+  u32 sw_if_index = ~0;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "%U", unformat_vnet_sw_interface, vmp->vnet_main,
+                   &sw_if_index))
+       ;
+      else if (unformat (input, "sw_if_index %u", &sw_if_index))
+       ;
+      else
+       break;
+    }
+
+  pool_foreach (vr, vmp->vrs, (
+                               {
+
+                               if (sw_if_index && (sw_if_index != ~0) &&
+                                   (sw_if_index != vr->config.sw_if_index))
+                               continue;
+                               vlib_cli_output (vm, "%U", format_vrrp_vr,
+                                                vr);}
+               ));
+
+  return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (vrrp_show_vr_command, static) =
+{
+  .path = "show vrrp vr",
+  .short_help =
+  "show vrrp vr [(<intf_name>|sw_if_index <n>)]",
+  .function = vrrp_show_vr_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+vrrp_proto_start_stop_command_fn (vlib_main_t * vm,
+                                 unformat_input_t * input,
+                                 vlib_cli_command_t * cmd)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vrrp_vr_key_t vr_key;
+  u32 sw_if_index;
+  u32 vr_id;
+  u8 is_ipv6, is_start, is_stop;
+  int rv;
+
+  clib_memset (&vr_key, 0, sizeof (vr_key));
+
+  /* defaults */
+  sw_if_index = ~0;
+  vr_id = 0;
+  is_ipv6 = is_start = is_stop = 0;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "%U", unformat_vnet_sw_interface, vmp->vnet_main,
+                   &sw_if_index))
+       ;
+      else if (unformat (input, "vr_id %u", &vr_id))
+       ;
+      else if (unformat (input, "ipv6"))
+       is_ipv6 = 1;
+      else if (unformat (input, "start"))
+       is_start = 1;
+      else if (unformat (input, "stop"))
+       is_stop = 1;
+      else
+       return clib_error_return (0, "unknown input `%U'",
+                                 format_unformat_error, input);
+    }
+
+  if (is_start == is_stop)
+    return clib_error_return (0, "One of start or stop must be specified");
+  else if (sw_if_index == ~0)
+    return clib_error_return (0, "Please specify an interface...");
+  else if (!vr_id)
+    return clib_error_return (0, "Invalid VR ID...");
+
+  vr_key.sw_if_index = sw_if_index;
+  vr_key.vr_id = vr_id;
+  vr_key.is_ipv6 = (is_ipv6 != 0);
+
+  rv = vrrp_vr_start_stop (is_start, &vr_key);
+
+  switch (rv)
+    {
+    case 0:
+      break;
+    case VNET_API_ERROR_INIT_FAILED:
+      return clib_error_return (0, "Cannot start unicast VR without peers");
+      break;
+    default:
+      return clib_error_return (0, "vrrp_vr_start_stop returned %d", rv);
+      break;
+    }
+
+  return 0;
+}
+
+static clib_error_t *
+vrrp_peers_command_fn (vlib_main_t * vm, unformat_input_t * input,
+                      vlib_cli_command_t * cmd)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vrrp_vr_key_t vr_key;
+  u32 sw_if_index;
+  u32 vr_id;
+  u8 is_ipv6;
+  int rv;
+  ip46_address_t addr, *addrs;
+  u8 n_addrs4, n_addrs6;
+  clib_error_t *ret = 0;
+
+  clib_memset (&vr_key, 0, sizeof (vr_key));
+
+  /* defaults */
+  addrs = 0;
+  n_addrs4 = n_addrs6 = 0;
+  sw_if_index = ~0;
+  vr_id = 0;
+  is_ipv6 = 0;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "%U", unformat_vnet_sw_interface, vmp->vnet_main,
+                   &sw_if_index))
+       ;
+      else if (unformat (input, "vr_id %u", &vr_id))
+       ;
+      else if (unformat (input, "ipv6"))
+       is_ipv6 = 1;
+      else if (unformat (input, "%U", unformat_ip4_address, &addr.ip4))
+       {
+         n_addrs4++;
+         vec_add1 (addrs, addr);
+       }
+      else if (unformat (input, "%U", unformat_ip6_address, &addr.ip6))
+       {
+         n_addrs6++;
+         vec_add1 (addrs, addr);
+       }
+      else
+       {
+         ret = clib_error_return (0, "unknown input `%U'",
+                                  format_unformat_error, input);
+         goto done;
+       }
+    }
+
+  if (sw_if_index == ~0)
+    ret = clib_error_return (0, "Please specify an interface...");
+  else if (!vr_id)
+    ret = clib_error_return (0, "Invalid VR ID...");
+  else if (n_addrs4 && (n_addrs6 || is_ipv6))
+    ret = clib_error_return (0, "Mismatched address families");
+
+  if (ret)                     /* data validation failed */
+    goto done;
+
+  vr_key.sw_if_index = sw_if_index;
+  vr_key.vr_id = vr_id;
+  vr_key.is_ipv6 = (is_ipv6 != 0);
+
+  rv = vrrp_vr_set_peers (&vr_key, addrs);
+
+  switch (rv)
+    {
+    case 0:
+      break;
+    case VNET_API_ERROR_INVALID_ARGUMENT:
+      ret = clib_error_return (0, "Peers can only be set on a unicast VR");
+      break;
+    case VNET_API_ERROR_RSRC_IN_USE:
+      ret = clib_error_return (0, "Cannot set peers on a running VR");
+      break;
+    case VNET_API_ERROR_INVALID_DST_ADDRESS:
+      ret = clib_error_return (0, "No peer addresses provided");
+      break;
+    default:
+      ret = clib_error_return (0, "vrrp_vr_set_peers returned %d", rv);
+      break;
+    }
+
+done:
+  vec_free (addrs);
+
+  return ret;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (vrrp_proto_start_stop_command, static) =
+{
+  .path = "vrrp proto",
+  .short_help =
+  "vrrp proto (start|stop) (<intf_name>|sw_if_index <n>) vr_id <n> [ipv6]",
+  .function = vrrp_proto_start_stop_command_fn,
+};
+/* *INDENT-ON* */
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (vrrp_peers_command, static) =
+{
+  .path = "vrrp peers",
+  .short_help =
+  "vrrp peers (<intf_name>|sw_if_index <n>) vr_id <n> [ipv6] <peer1_addr> [<peer2_addr> ...]",
+  .function = vrrp_peers_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+vrrp_vr_track_if_command_fn (vlib_main_t * vm,
+                            unformat_input_t * input,
+                            vlib_cli_command_t * cmd)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  vrrp_main_t *vmp = &vrrp_main;
+  u32 sw_if_index, track_if_index, vr_id, priority;
+  u8 is_ipv6 = 0;
+  clib_error_t *ret = 0;
+  vrrp_vr_tracking_if_t *track_intfs = 0, *track_intf;
+  vrrp_vr_t *vr;
+  u8 is_add, is_del;
+  int rv;
+
+  /* defaults */
+  sw_if_index = ~0;
+  vr_id = 0;
+  is_add = is_del = 0;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "%U", unformat_vnet_sw_interface, vmp->vnet_main,
+                   &sw_if_index))
+       ;
+      else if (unformat (input, "add"))
+       is_add = 1;
+      else if (unformat (input, "del"))
+       is_del = 1;
+      else if (unformat (input, "vr_id %u", &vr_id))
+       ;
+      else if (unformat (input, "ipv6"))
+       is_ipv6 = 1;
+      else if (unformat (input, "track-index %u priority %u", &track_if_index,
+                        &priority))
+       {
+         vec_add2 (track_intfs, track_intf, 1);;
+         track_intf->sw_if_index = track_if_index;
+         track_intf->priority = priority;
+       }
+      else
+       break;
+    }
+
+  if (sw_if_index == ~0)
+    ret = clib_error_return (0, "Please specify an interface");
+  else if (!vr_id || vr_id > 0xff)
+    ret = clib_error_return (0, "VR ID must be between 1 and 255");
+  else if (is_add == is_del)
+    ret = clib_error_return (0, "One of add,delete must be specified");
+
+  if (ret)
+    goto done;
+
+  vr = vrrp_vr_lookup (sw_if_index, vr_id, is_ipv6);
+  if (!vr)
+    {
+      ret = clib_error_return (0, "VR not found");
+      goto done;
+    }
+
+  vec_foreach (track_intf, track_intfs)
+  {
+    if (!vnet_sw_interface_is_valid (vnm, track_intf->sw_if_index))
+      {
+       ret = clib_error_return (0, "tracked intf sw_if_index %u invalid",
+                                track_intf->sw_if_index);
+       goto done;
+      }
+    if (!track_intf->priority)
+      {
+       ret = clib_error_return (0, "tracked intf priority must be > 0");
+       goto done;
+      }
+    if (track_intf->priority >= vr->config.priority)
+      {
+       ret = clib_error_return (0, "tracked intf priority must be less "
+                                "than VR priority (%u)",
+                                vr->config.priority);
+       goto done;
+      }
+  }
+
+  rv = vrrp_vr_tracking_ifs_add_del (vr, track_intfs, is_add);
+  if (rv)
+    ret = clib_error_return (0, "vrrp_vr_tracking_ifs_add_del returned %d",
+                            rv);
+
+done:
+  vec_free (track_intfs);
+
+  return ret;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (vrrp_vr_track_if_command, static) =
+{
+  .path = "vrrp vr track-if",
+  .short_help =
+  "vrrp vr track-if (add|del) (<intf_name>|sw_if_index <n>) vr_id <n> [ipv6] track-index <n> priority <n> [ track-index <n> priority <n> ...]",
+  .function = vrrp_vr_track_if_command_fn,
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/vrrp/vrrp_format.c b/src/plugins/vrrp/vrrp_format.c
new file mode 100644 (file)
index 0000000..df9bf93
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#include <vnet/vnet.h>
+#include <vnet/api_errno.h>
+#include <vnet/ip/ip.h>
+#include <vnet/interface.h>
+
+#include <plugins/vrrp/vrrp.h>
+#include <plugins/vrrp/vrrp_packet.h>
+
+u8 *
+format_vrrp_vr_flags (u8 * s, va_list * args)
+{
+  vrrp_vr_flags_t flags = va_arg (*args, vrrp_vr_flags_t);
+
+  s = format (s, "preempt %s accept %s unicast %s",
+             (flags & VRRP_VR_PREEMPT) ? "yes" : "no",
+             (flags & VRRP_VR_ACCEPT) ? "yes" : "no",
+             (flags & VRRP_VR_UNICAST) ? "yes" : "no");
+
+  return s;
+}
+
+u8 *
+format_vrrp_vr_addrs (u8 * s, va_list * args)
+{
+  int is_ipv6 = va_arg (*args, int);
+  ip46_address_t *addrs = va_arg (*args, ip46_address_t *);
+  ip46_address_t *addr;
+
+  vec_foreach (addr, addrs)
+  {
+    s = format (s, "%U ",
+               (is_ipv6) ? format_ip6_address : format_ip4_address,
+               (is_ipv6) ? (u8 *) & addr->ip6 : (u8 *) & addr->ip4);
+  }
+
+  return s;
+}
+
+u8 *
+format_vrrp_vr_state (u8 * s, va_list * args)
+{
+  vrrp_vr_state_t state = va_arg (*args, vrrp_vr_state_t);
+
+  switch (state)
+    {
+#define _(v,f,n) case VRRP_VR_STATE_##f: s = format (s, n); break;
+      foreach_vrrp_vr_state
+#undef _
+    default:
+      s = format (s, "Unknown");
+      break;
+    }
+
+  return s;
+}
+
+u8 *
+format_vrrp_vr_key (u8 * s, va_list * args)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vrrp_vr_t *vr = va_arg (*args, vrrp_vr_t *);
+  vrrp_vr_config_t *vrc = &vr->config;
+
+  s = format (s, "[%d] sw_if_index %u VR ID %u IPv%d",
+             vr - vmp->vrs, vrc->sw_if_index,
+             vrc->vr_id, (vrc->flags & VRRP_VR_IPV6) ? 6 : 4);
+
+  return s;
+}
+
+u8 *
+format_vrrp_vr_track_ifs (u8 * s, va_list * args)
+{
+  vrrp_vr_tracking_if_t *track_ifs = va_arg (*args, vrrp_vr_tracking_if_t *);
+  vrrp_vr_tracking_if_t *track_if;
+
+  vec_foreach (track_if, track_ifs)
+    s = format (s, "sw_if_index %u priority %u ",
+               track_if->sw_if_index, track_if->priority);
+
+  return s;
+}
+
+u8 *
+format_vrrp_vr (u8 * s, va_list * args)
+{
+  vrrp_vr_t *vr = va_arg (*args, vrrp_vr_t *);
+
+  s = format (s, "%U\n", format_vrrp_vr_key, vr);
+
+  s = format (s, "   state %U flags: %U\n",
+             format_vrrp_vr_state, vr->runtime.state,
+             format_vrrp_vr_flags, vr->config.flags);
+  s = format (s, "   priority: configured %u adjusted %u\n",
+             vr->config.priority, vrrp_vr_priority (vr));
+  s = format (s, "   timers: adv interval %u "
+             "master adv %u skew %u master down %u\n",
+             vr->config.adv_interval, vr->runtime.master_adv_int,
+             vr->runtime.skew, vr->runtime.master_down_int);
+
+  s = format (s, "   virtual MAC %U\n", format_ethernet_address,
+             &vr->runtime.mac);
+
+  s = format (s, "   addresses %U\n", format_vrrp_vr_addrs,
+             (vr->config.flags & VRRP_VR_IPV6) != 0, vr->config.vr_addrs);
+
+  s = format (s, "   peer addresses %U\n", format_vrrp_vr_addrs,
+             (vr->config.flags & VRRP_VR_IPV6) != 0, vr->config.peer_addrs);
+
+  s = format (s, "   tracked interfaces %U\n", format_vrrp_vr_track_ifs,
+             vr->tracking.interfaces);
+
+  return s;
+}
+
+u8 *
+format_vrrp_packet_hdr (u8 * s, va_list * args)
+{
+  vrrp_header_t *pkt = va_arg (*args, vrrp_header_t *);
+  u32 version = pkt->vrrp_version_and_type >> 4;
+
+  s = format (s, "ver %u, type %u, VRID %u, prio %u, "
+             "n_addrs %u, interval %u%ss, csum 0x%x",
+             version, pkt->vrrp_version_and_type & 0xf,
+             pkt->vr_id, pkt->priority, pkt->n_addrs,
+             clib_net_to_host_u16 (pkt->rsvd_and_max_adv_int),
+             (version == 3) ? "c" : "", pkt->checksum);
+
+  return s;
+}
+
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/vrrp/vrrp_msg_enum.h b/src/plugins/vrrp/vrrp_msg_enum.h
new file mode 100644 (file)
index 0000000..48ae619
--- /dev/null
@@ -0,0 +1,23 @@
+
+/*
+ * vrrp_msg_enum.h - vrrp plug-in message enumeration
+ *
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+#ifndef included_vrrp_msg_enum_h
+#define included_vrrp_msg_enum_h
+
+#include <vppinfra/byte_order.h>
+
+#define vl_msg_id(n,h) n,
+typedef enum {
+#include <vrrp/vrrp_all_api_h.h>
+    /* We'll want to know how many messages IDs we need... */
+    VL_MSG_FIRST_AVAILABLE,
+} vl_msg_id_t;
+#undef vl_msg_id
+
+#endif /* included_vrrp_msg_enum_h */
diff --git a/src/plugins/vrrp/vrrp_packet.c b/src/plugins/vrrp/vrrp_packet.c
new file mode 100644 (file)
index 0000000..f624b18
--- /dev/null
@@ -0,0 +1,735 @@
+/*
+ * vrrp.c - vrrp plugin action functions
+ *
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#include <vnet/vnet.h>
+#include <vnet/plugin/plugin.h>
+#include <vnet/mfib/mfib_entry.h>
+#include <vnet/mfib/mfib_table.h>
+#include <vnet/adj/adj.h>
+#include <vnet/adj/adj_mcast.h>
+#include <vnet/fib/fib_table.h>
+#include <vnet/ip/igmp_packet.h>
+#include <vnet/ip/ip6_link.h>
+#include <vnet/ethernet/arp_packet.h>
+
+#include <vrrp/vrrp.h>
+#include <vrrp/vrrp_packet.h>
+
+#include <vpp/app/version.h>
+
+static const u8 vrrp4_dst_mac[6] = { 0x1, 0x0, 0x5e, 0x0, 0x0, 0x12 };
+static const u8 vrrp6_dst_mac[6] = { 0x33, 0x33, 0x0, 0x0, 0x0, 0x12 };
+static const u8 vrrp_src_mac_prefix[4] = { 0x0, 0x0, 0x5e, 0x0 };
+
+static int
+vrrp_adv_l2_build_multicast (vrrp_vr_t * vr, vlib_buffer_t * b)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  vnet_link_t link_type;
+  ethernet_header_t *eth;
+  int n_bytes = 0;
+  const void *dst_mac;
+  u8 mac_byte_ipver;
+  u8 *rewrite;
+
+  eth = vlib_buffer_get_current (b);
+
+  if (vrrp_vr_is_ipv6 (vr))
+    {
+      dst_mac = vrrp6_dst_mac;
+      link_type = VNET_LINK_IP6;
+      mac_byte_ipver = 0x2;
+    }
+  else
+    {
+      dst_mac = vrrp4_dst_mac;
+      link_type = VNET_LINK_IP4;
+      mac_byte_ipver = 0x1;
+    }
+
+  rewrite = ethernet_build_rewrite (vnm, vr->config.sw_if_index, link_type,
+                                   dst_mac);
+  clib_memcpy (eth, rewrite, vec_len (rewrite));
+
+  /* change the source mac from the HW addr to the VRRP virtual MAC */
+  clib_memcpy
+    (eth->src_address, vrrp_src_mac_prefix, sizeof (vrrp_src_mac_prefix));
+  eth->src_address[4] = mac_byte_ipver;
+  eth->src_address[5] = vr->config.vr_id;
+
+  n_bytes += vec_len (rewrite);
+
+  vlib_buffer_chain_increase_length (b, b, n_bytes);
+  vlib_buffer_advance (b, n_bytes);
+
+  vec_free (rewrite);
+
+  return n_bytes;
+}
+
+#define VRRP4_MCAST_ADDR_AS_U8 { 224, 0, 0, 18 }
+#define VRRP6_MCAST_ADDR_AS_U8 \
+{ 0xff, 0x2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12 }
+
+static const ip46_address_t vrrp4_mcast_addr = {
+  .ip4 = {.as_u8 = VRRP4_MCAST_ADDR_AS_U8,},
+};
+
+static const ip46_address_t vrrp6_mcast_addr = {
+  .ip6 = {.as_u8 = VRRP6_MCAST_ADDR_AS_U8,},
+};
+
+/* size of static parts of header + (# addrs * addr length) */
+always_inline u16
+vrrp_adv_payload_len (vrrp_vr_t * vr)
+{
+  u16 addr_len = vrrp_vr_is_ipv6 (vr) ? 16 : 4;
+
+  return sizeof (vrrp_header_t) + (vec_len (vr->config.vr_addrs) * addr_len);
+}
+
+static int
+vrrp_adv_l3_build (vrrp_vr_t * vr, vlib_buffer_t * b,
+                  const ip46_address_t * dst)
+{
+  if (!vrrp_vr_is_ipv6 (vr))   /* IPv4 */
+    {
+      ip4_header_t *ip4 = vlib_buffer_get_current (b);
+
+      clib_memset (ip4, 0, sizeof (*ip4));
+      ip4->ip_version_and_header_length = 0x45;
+      ip4->ttl = 255;
+      ip4->protocol = IP_PROTOCOL_VRRP;
+      clib_memcpy (&ip4->dst_address, &dst->ip4, sizeof (dst->ip4));
+      ip4_src_address_for_packet (&ip4_main.lookup_main,
+                                 vr->config.sw_if_index, &ip4->src_address);
+      ip4->length = clib_host_to_net_u16 (sizeof (*ip4) +
+                                         vrrp_adv_payload_len (vr));
+      ip4->checksum = ip4_header_checksum (ip4);
+
+      vlib_buffer_chain_increase_length (b, b, sizeof (*ip4));
+      vlib_buffer_advance (b, sizeof (*ip4));
+
+      return sizeof (*ip4);
+    }
+  else
+    {
+      ip6_header_t *ip6 = vlib_buffer_get_current (b);
+
+      clib_memset (ip6, 0, sizeof (*ip6));
+      ip6->ip_version_traffic_class_and_flow_label = 0x00000060;
+      ip6->hop_limit = 255;
+      ip6->protocol = IP_PROTOCOL_VRRP;
+      clib_memcpy (&ip6->dst_address, &dst->ip6, sizeof (dst->ip6));
+      ip6_address_copy (&ip6->src_address,
+                       ip6_get_link_local_address (vr->config.sw_if_index));
+      ip6->payload_length = clib_host_to_net_u16 (vrrp_adv_payload_len (vr));
+
+      vlib_buffer_chain_increase_length (b, b, sizeof (*ip6));
+      vlib_buffer_advance (b, sizeof (*ip6));
+
+      return sizeof (*ip6);
+    }
+}
+
+
+u16
+vrrp_adv_csum (void *l3_hdr, void *payload, u8 is_ipv6, u16 len)
+{
+  ip_csum_t csum = 0;
+  u8 proto = IP_PROTOCOL_VRRP;
+  int addr_len;
+  int word_size = sizeof (uword);
+  void *src_addr;
+  int i;
+
+  if (is_ipv6)
+    {
+      addr_len = 16;
+      src_addr = &(((ip6_header_t *) l3_hdr)->src_address);
+    }
+  else
+    {
+      addr_len = 4;
+      src_addr = &(((ip4_header_t *) l3_hdr)->src_address);
+    }
+
+  for (i = 0; i < (2 * addr_len); i += word_size)
+    {
+      if (word_size == sizeof (u64))
+       csum =
+         ip_csum_with_carry (csum, clib_mem_unaligned (src_addr + i, u64));
+      else
+       csum =
+         ip_csum_with_carry (csum, clib_mem_unaligned (src_addr + i, u32));
+    }
+
+  csum = ip_csum_with_carry (csum,
+                            clib_host_to_net_u32 (len + (proto << 16)));
+
+  /* now do the payload */
+  csum = ip_incremental_checksum (csum, payload, len);
+
+  csum = ~ip_csum_fold (csum);
+
+  return (u16) csum;
+}
+
+static int
+vrrp_adv_payload_build (vrrp_vr_t * vr, vlib_buffer_t * b, int shutdown)
+{
+  vrrp_header_t *vrrp = vlib_buffer_get_current (b);
+  void *l3_hdr;
+  ip46_address_t *vr_addr;
+  void *hdr_addr;
+  u8 is_ipv6;
+  u8 n_addrs;
+  int len;
+
+  n_addrs = vec_len (vr->config.vr_addrs);
+  is_ipv6 = vrrp_vr_is_ipv6 (vr);
+
+  if (is_ipv6)
+    {
+      ip6_header_t *ip6;
+
+      len = sizeof (*vrrp) + n_addrs * sizeof (ip6_address_t);;
+      l3_hdr = vlib_buffer_get_current (b) - sizeof (ip6_header_t);
+      ip6 = l3_hdr;
+      ip6->payload_length = clib_host_to_net_u16 (len);
+    }
+  else
+    {
+      len = sizeof (*vrrp) + n_addrs * sizeof (ip4_address_t);
+      l3_hdr = vlib_buffer_get_current (b) - sizeof (ip4_header_t);
+    }
+
+  vrrp->vrrp_version_and_type = 0x31;
+  vrrp->vr_id = vr->config.vr_id;
+  vrrp->priority = (shutdown) ? 0 : vrrp_vr_priority (vr);
+  vrrp->n_addrs = vec_len (vr->config.vr_addrs);
+  vrrp->rsvd_and_max_adv_int = clib_host_to_net_u16 (vr->config.adv_interval);
+  vrrp->checksum = 0;
+
+  hdr_addr = (void *) (vrrp + 1);
+
+  vec_foreach (vr_addr, vr->config.vr_addrs)
+  {
+    if (is_ipv6)
+      {
+       clib_memcpy (hdr_addr, &vr_addr->ip6, 16);
+       hdr_addr += 16;
+      }
+    else
+      {
+       clib_memcpy (hdr_addr, &vr_addr->ip4, 4);
+       hdr_addr += 4;
+      }
+  }
+
+  vlib_buffer_chain_increase_length (b, b, vrrp_adv_payload_len (vr));
+
+  vrrp->checksum =
+    vrrp_adv_csum (l3_hdr, vrrp, is_ipv6, vrrp_adv_payload_len (vr));
+
+  return len;
+}
+
+static_always_inline u32
+vrrp_adv_next_node (vrrp_vr_t * vr)
+{
+  if (vrrp_vr_is_unicast (vr))
+    {
+      if (vrrp_vr_is_ipv6 (vr))
+       return ip6_lookup_node.index;
+      else
+       return ip4_lookup_node.index;
+    }
+  else
+    {
+      vrrp_main_t *vmp = &vrrp_main;
+
+      return vmp->intf_output_node_idx;
+    }
+}
+
+static_always_inline const ip46_address_t *
+vrrp_adv_mcast_addr (vrrp_vr_t * vr)
+{
+  if (vrrp_vr_is_ipv6 (vr))
+    return &vrrp6_mcast_addr;
+
+  return &vrrp4_mcast_addr;
+}
+
+int
+vrrp_adv_send (vrrp_vr_t * vr, int shutdown)
+{
+  vlib_main_t *vm = vlib_get_main ();
+  vlib_frame_t *to_frame;
+  int i, n_buffers = 1;
+  u32 node_index, *to_next, *bi = 0;
+  u8 is_unicast = vrrp_vr_is_unicast (vr);
+
+  node_index = vrrp_adv_next_node (vr);
+
+  if (is_unicast)
+    n_buffers = vec_len (vr->config.peer_addrs);
+
+  vec_validate (bi, n_buffers - 1);
+  if (vlib_buffer_alloc (vm, bi, n_buffers) != n_buffers)
+    {
+      clib_warning ("Buffer allocation failed for %U", format_vrrp_vr_key,
+                   vr);
+      vec_free (bi);
+      return -1;
+    }
+
+  to_frame = vlib_get_frame_to_node (vm, node_index);
+  to_next = vlib_frame_vector_args (to_frame);
+
+  for (i = 0; i < n_buffers; i++)
+    {
+      vlib_buffer_t *b;
+      u32 bi0;
+      const ip46_address_t *dst = vrrp_adv_mcast_addr (vr);
+
+      bi0 = vec_elt (bi, i);
+      b = vlib_get_buffer (vm, bi0);
+
+      VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b);
+      b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
+      vnet_buffer (b)->sw_if_index[VLIB_RX] = 0;
+      vnet_buffer (b)->sw_if_index[VLIB_TX] = vr->config.sw_if_index;
+
+      if (is_unicast)
+       {
+         dst = vec_elt_at_index (vr->config.peer_addrs, i);
+         vnet_buffer (b)->sw_if_index[VLIB_TX] = ~0;
+       }
+      else
+       vrrp_adv_l2_build_multicast (vr, b);
+
+      vrrp_adv_l3_build (vr, b, dst);
+      vrrp_adv_payload_build (vr, b, shutdown);
+
+      vlib_buffer_reset (b);
+
+      to_next[i] = bi0;
+    }
+
+  to_frame->n_vectors = n_buffers;
+
+  vlib_put_frame_to_node (vm, node_index, to_frame);
+
+  vec_free (bi);
+
+  return 0;
+}
+
+static void
+vrrp6_na_pkt_build (vrrp_vr_t * vr, vlib_buffer_t * b, ip6_address_t * addr6)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  vlib_main_t *vm = vlib_get_main ();
+  ethernet_header_t *eth;
+  ip6_header_t *ip6;
+  icmp6_neighbor_solicitation_or_advertisement_header_t *na;
+  icmp6_neighbor_discovery_ethernet_link_layer_address_option_t *ll_opt;
+  int payload_length, bogus_length;
+  int rewrite_bytes = 0;
+  u8 *rewrite;
+  u8 dst_mac[6];
+
+  /* L2 headers */
+  eth = vlib_buffer_get_current (b);
+
+  ip6_multicast_ethernet_address (dst_mac, IP6_MULTICAST_GROUP_ID_all_hosts);
+  rewrite =
+    ethernet_build_rewrite (vnm, vr->config.sw_if_index, VNET_LINK_IP6,
+                           dst_mac);
+  rewrite_bytes += vec_len (rewrite);
+  clib_memcpy (eth, rewrite, vec_len (rewrite));
+  vec_free (rewrite);
+
+  b->current_length += rewrite_bytes;
+  vlib_buffer_advance (b, rewrite_bytes);
+
+  /* IPv6 */
+  ip6 = vlib_buffer_get_current (b);
+
+  b->current_length += sizeof (*ip6);
+  clib_memset (ip6, 0, sizeof (*ip6));
+
+  ip6->ip_version_traffic_class_and_flow_label = 0x00000060;
+  ip6->protocol = IP_PROTOCOL_ICMP6;
+  ip6->hop_limit = 255;
+  ip6_set_reserved_multicast_address (&ip6->dst_address,
+                                     IP6_MULTICAST_SCOPE_link_local,
+                                     IP6_MULTICAST_GROUP_ID_all_hosts);
+  ip6_address_copy (&ip6->src_address,
+                   ip6_get_link_local_address (vr->config.sw_if_index));
+
+
+  /* ICMPv6 */
+  na = (icmp6_neighbor_solicitation_or_advertisement_header_t *) (ip6 + 1);
+  ll_opt =
+    (icmp6_neighbor_discovery_ethernet_link_layer_address_option_t *) (na +
+                                                                      1);
+
+  payload_length = sizeof (*na) + sizeof (*ll_opt);
+  b->current_length += payload_length;
+  clib_memset (na, 0, payload_length);
+
+  na->icmp.type = ICMP6_neighbor_advertisement;        /* icmp code, csum are 0 */
+  na->target_address = *addr6;
+  na->advertisement_flags = clib_host_to_net_u32
+    (ICMP6_NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE
+     | ICMP6_NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER);
+
+  ll_opt->header.type =
+    ICMP6_NEIGHBOR_DISCOVERY_OPTION_target_link_layer_address;
+  ll_opt->header.n_data_u64s = 1;
+  clib_memcpy (ll_opt->ethernet_address, vr->runtime.mac.bytes,
+              sizeof (vr->runtime.mac));
+
+  ip6->payload_length = clib_host_to_net_u16 (payload_length);
+  na->icmp.checksum =
+    ip6_tcp_udp_icmp_compute_checksum (vm, b, ip6, &bogus_length);
+}
+
+const mac_address_t broadcast_mac = {
+  .bytes = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff,},
+};
+
+static void
+vrrp4_garp_pkt_build (vrrp_vr_t * vr, vlib_buffer_t * b, ip4_address_t * ip4)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  ethernet_header_t *eth;
+  ethernet_arp_header_t *arp;
+  int rewrite_bytes;
+  u8 *rewrite;
+
+  eth = vlib_buffer_get_current (b);
+
+  rewrite =
+    ethernet_build_rewrite (vnm, vr->config.sw_if_index, VNET_LINK_ARP,
+                           broadcast_mac.bytes);
+  rewrite_bytes = vec_len (rewrite);
+  clib_memcpy (eth, rewrite, rewrite_bytes);
+  vec_free (rewrite);
+
+  b->current_length += rewrite_bytes;
+  vlib_buffer_advance (b, rewrite_bytes);
+
+  arp = vlib_buffer_get_current (b);
+  b->current_length += sizeof (*arp);
+
+  clib_memset (arp, 0, sizeof (*arp));
+
+  arp->l2_type = clib_host_to_net_u16 (ETHERNET_ARP_HARDWARE_TYPE_ethernet);
+  arp->l3_type = clib_host_to_net_u16 (ETHERNET_TYPE_IP4);
+  arp->n_l2_address_bytes = 6;
+  arp->n_l3_address_bytes = 4;
+  arp->opcode = clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_request);
+  arp->ip4_over_ethernet[0].mac = vr->runtime.mac;
+  arp->ip4_over_ethernet[0].ip4 = *ip4;
+  arp->ip4_over_ethernet[1].mac = broadcast_mac;
+  arp->ip4_over_ethernet[1].ip4 = *ip4;
+}
+
+int
+vrrp_garp_or_na_send (vrrp_vr_t * vr)
+{
+  vlib_main_t *vm = vlib_get_main ();
+  vrrp_main_t *vmp = &vrrp_main;
+  vlib_frame_t *to_frame;
+  u32 *bi = 0;
+  u32 n_buffers;
+  u32 *to_next;
+  int i;
+
+  if (vec_len (vr->config.peer_addrs))
+    return 0;                  /* unicast is used in routed environments - don't garp */
+
+  n_buffers = vec_len (vr->config.vr_addrs);
+  if (!n_buffers)
+    {
+      clib_warning ("Unable to send gratuitous ARP for VR %U - no addresses",
+                   format_vrrp_vr_key, vr);
+      return -1;
+    }
+
+  /* need to send a packet for each VR address */
+  vec_validate (bi, n_buffers - 1);
+
+  if (vlib_buffer_alloc (vm, bi, n_buffers) != n_buffers)
+    {
+      clib_warning ("Buffer allocation failed for %U", format_vrrp_vr_key,
+                   vr);
+      vec_free (bi);
+      return -1;
+    }
+
+  to_frame = vlib_get_frame_to_node (vm, vmp->intf_output_node_idx);
+  to_frame->n_vectors = 0;
+  to_next = vlib_frame_vector_args (to_frame);
+
+  for (i = 0; i < n_buffers; i++)
+    {
+      vlib_buffer_t *b;
+      ip46_address_t *addr;
+
+      addr = vec_elt_at_index (vr->config.vr_addrs, i);
+      b = vlib_get_buffer (vm, bi[i]);
+
+      VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b);
+      b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
+      vnet_buffer (b)->sw_if_index[VLIB_RX] = 0;
+      vnet_buffer (b)->sw_if_index[VLIB_TX] = vr->config.sw_if_index;
+
+      if (vrrp_vr_is_ipv6 (vr))
+       vrrp6_na_pkt_build (vr, b, &addr->ip6);
+      else
+       vrrp4_garp_pkt_build (vr, b, &addr->ip4);
+
+      vlib_buffer_reset (b);
+
+      to_next[i] = bi[i];
+      to_frame->n_vectors++;
+    }
+
+  vlib_put_frame_to_node (vm, vmp->intf_output_node_idx, to_frame);
+
+  return 0;
+}
+
+#define IGMP4_MCAST_ADDR_AS_U8 { 224, 0, 0, 22 }
+
+static const ip4_header_t igmp_ip4_mcast = {
+  .ip_version_and_header_length = 0x46,        /* there's options! */
+  .ttl = 1,
+  .protocol = IP_PROTOCOL_IGMP,
+  .tos = 0xc0,
+  .dst_address = {.as_u8 = IGMP4_MCAST_ADDR_AS_U8,},
+};
+
+static void
+vrrp_igmp_pkt_build (vrrp_vr_t * vr, vlib_buffer_t * b)
+{
+  ip4_header_t *ip4;
+  u8 *ip4_options;
+  igmp_membership_report_v3_t *report;
+  igmp_membership_group_v3_t *group;
+
+  ip4 = vlib_buffer_get_current (b);
+  clib_memcpy (ip4, &igmp_ip4_mcast, sizeof (*ip4));
+  ip4_src_address_for_packet (&ip4_main.lookup_main, vr->config.sw_if_index,
+                             &ip4->src_address);
+
+  vlib_buffer_chain_increase_length (b, b, sizeof (*ip4));
+  vlib_buffer_advance (b, sizeof (*ip4));
+
+  ip4_options = (u8 *) (ip4 + 1);
+  ip4_options[0] = 0x94;       /* 10010100 == the router alert option */
+  ip4_options[1] = 0x04;       /* length == 4 bytes */
+  ip4_options[2] = 0x0;                /* value == Router shall examine packet */
+  ip4_options[3] = 0x0;                /* reserved */
+
+  vlib_buffer_chain_increase_length (b, b, 4);
+  vlib_buffer_advance (b, 4);
+
+  report = vlib_buffer_get_current (b);
+
+  report->header.type = IGMP_TYPE_membership_report_v3;
+  report->header.code = 0;
+  report->header.checksum = 0;
+  report->unused = 0;
+  report->n_groups = clib_host_to_net_u16 (1);
+
+  vlib_buffer_chain_increase_length (b, b, sizeof (*report));
+  vlib_buffer_advance (b, sizeof (*report));
+
+  group = vlib_buffer_get_current (b);
+  group->type = IGMP_MEMBERSHIP_GROUP_change_to_exclude;
+  group->n_aux_u32s = 0;
+  group->n_src_addresses = 0;
+  group->group_address.as_u32 = clib_host_to_net_u32 (0xe0000012);
+
+  vlib_buffer_chain_increase_length (b, b, sizeof (*group));
+  vlib_buffer_advance (b, sizeof (*group));
+
+  ip4->length = clib_host_to_net_u16 (b->current_data);
+  ip4->checksum = ip4_header_checksum (ip4);
+
+  int payload_len = vlib_buffer_get_current (b) - ((void *) report);
+  report->header.checksum =
+    ~ip_csum_fold (ip_incremental_checksum (0, report, payload_len));
+
+  vlib_buffer_reset (b);
+}
+
+/* multicast listener report packet format for ethernet. */
+typedef CLIB_PACKED (struct
+                    {
+                    ip6_hop_by_hop_ext_t ext_hdr;
+                    ip6_router_alert_option_t alert;
+                    ip6_padN_option_t pad;
+                    icmp46_header_t icmp;
+                    u16 rsvd;
+                    u16 num_addr_records;
+                    icmp6_multicast_address_record_t records[0];
+                    }) icmp6_multicast_listener_report_header_t;
+
+static void
+vrrp_icmp6_mlr_pkt_build (vrrp_vr_t * vr, vlib_buffer_t * b)
+{
+  vlib_main_t *vm = vlib_get_main ();
+  ip6_header_t *ip6;
+  icmp6_multicast_listener_report_header_t *rh;
+  icmp6_multicast_address_record_t *rr;
+  ip46_address_t *vr_addr;
+  int bogus_length, n_addrs;
+  u16 payload_length;
+
+  n_addrs = vec_len (vr->config.vr_addrs) + 1;
+  payload_length = sizeof (*rh) + (n_addrs * sizeof (*rr));
+  b->current_length = sizeof (*ip6) + payload_length;
+  b->error = ICMP6_ERROR_NONE;
+
+  ip6 = vlib_buffer_get_current (b);
+  rh = (icmp6_multicast_listener_report_header_t *) (ip6 + 1);
+  rr = (icmp6_multicast_address_record_t *) (rh + 1);
+
+  /* IP header */
+  clib_memset (ip6, 0, b->current_length);
+  ip6->ip_version_traffic_class_and_flow_label =
+    clib_host_to_net_u32 (0x60000000);
+  ip6->hop_limit = 1;
+  ip6->protocol = IP_PROTOCOL_IP6_HOP_BY_HOP_OPTIONS;
+  ip6_set_reserved_multicast_address (&ip6->dst_address,
+                                     IP6_MULTICAST_SCOPE_link_local,
+                                     IP6_MULTICAST_GROUP_ID_mldv2_routers);
+  ip6_address_copy (&ip6->src_address,
+                   ip6_get_link_local_address (vr->config.sw_if_index));
+
+  clib_memset (rh, 0, sizeof (*rh));
+
+  /* v6 hop by hop extension header */
+  rh->ext_hdr.next_hdr = IP_PROTOCOL_ICMP6;
+  rh->ext_hdr.n_data_u64s = 0;
+
+  rh->alert.type = IP6_MLDP_ALERT_TYPE;
+  rh->alert.len = 2;
+  rh->alert.value = 0;
+
+  rh->pad.type = 1;
+  rh->pad.len = 0;
+
+  /* icmp6 header */
+  rh->icmp.type = ICMP6_multicast_listener_report_v2;
+  rh->icmp.checksum = 0;
+
+  rh->rsvd = 0;
+  rh->num_addr_records = clib_host_to_net_u16 (n_addrs);
+
+  /* group addresses */
+
+  /* All VRRP routers group */
+  rr->type = 4;
+  rr->aux_data_len_u32s = 0;
+  rr->num_sources = 0;
+  clib_memcpy
+    (&rr->mcast_addr, &vrrp6_mcast_addr.ip6, sizeof (ip6_address_t));
+
+  /* solicited node multicast addresses for VR addrs */
+  vec_foreach (vr_addr, vr->config.vr_addrs)
+  {
+    u32 id;
+
+    rr++;
+    rr->type = 4;
+    rr->aux_data_len_u32s = 0;
+    rr->num_sources = 0;
+
+    id = clib_net_to_host_u32 (vr_addr->ip6.as_u32[3]) & 0x00ffffff;
+    ip6_set_solicited_node_multicast_address (&rr->mcast_addr, id);
+  }
+
+  ip6->payload_length = clib_host_to_net_u16 (payload_length);
+  rh->icmp.checksum = ip6_tcp_udp_icmp_compute_checksum (vm, b, ip6,
+                                                        &bogus_length);
+}
+
+int
+vrrp_vr_multicast_group_join (vrrp_vr_t * vr)
+{
+  vlib_main_t *vm = vlib_get_main ();
+  vlib_buffer_t *b;
+  vlib_frame_t *f;
+  vnet_main_t *vnm = vnet_get_main ();
+  vrrp_intf_t *intf;
+  u32 bi = 0, *to_next;
+  int n_buffers = 1;
+  u8 is_ipv6;
+  u32 node_index;
+
+  if (!vnet_sw_interface_is_up (vnm, vr->config.sw_if_index))
+    return 0;
+
+  if (vlib_buffer_alloc (vm, &bi, n_buffers) != n_buffers)
+    {
+      clib_warning ("Buffer allocation failed for %U", format_vrrp_vr_key,
+                   vr);
+      return -1;
+    }
+
+  is_ipv6 = vrrp_vr_is_ipv6 (vr);
+
+  b = vlib_get_buffer (vm, bi);
+
+  VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b);
+  b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
+
+  vnet_buffer (b)->sw_if_index[VLIB_RX] = 0;
+  vnet_buffer (b)->sw_if_index[VLIB_TX] = vr->config.sw_if_index;
+
+  intf = vrrp_intf_get (vr->config.sw_if_index);
+  vnet_buffer (b)->ip.adj_index[VLIB_TX] = intf->mcast_adj_index[is_ipv6];
+
+  if (is_ipv6)
+    {
+      vrrp_icmp6_mlr_pkt_build (vr, b);
+      node_index = ip6_rewrite_mcast_node.index;
+    }
+  else
+    {
+      vrrp_igmp_pkt_build (vr, b);
+      node_index = ip4_rewrite_mcast_node.index;
+    }
+
+  f = vlib_get_frame_to_node (vm, node_index);
+  to_next = vlib_frame_vector_args (f);
+  to_next[0] = bi;
+  f->n_vectors = 1;
+
+  vlib_put_frame_to_node (vm, node_index, f);
+
+  return f->n_vectors;
+}
+
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/vrrp/vrrp_packet.h b/src/plugins/vrrp/vrrp_packet.h
new file mode 100644 (file)
index 0000000..1cbf62d
--- /dev/null
@@ -0,0 +1,58 @@
+
+/*
+ * vrrp_packet.h - vrrp protocol/packet definitions
+ *
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+#ifndef __included_vrrp_packet_h__
+#define __included_vrrp_packet_h__
+
+#include <vnet/vnet.h>
+
+typedef CLIB_PACKED (struct
+                    {
+                    /* 4 bits for version (always 2 or 3), 4 bits for type (always 1) */
+                    u8 vrrp_version_and_type;
+                    /* VR ID */
+                    u8 vr_id;
+                    /* priority of sender on this VR. value of 0 means a master is abdicating */
+                    u8 priority;
+                    /* count of addresses being backed up by the VR */
+                    u8 n_addrs;
+                    /* max advertisement interval - first 4 bits are reserved and must be 0 */
+                    u16 rsvd_and_max_adv_int;
+                    /* checksum */
+                    u16 checksum;
+                    }) vrrp_header_t;
+
+typedef CLIB_PACKED (struct
+                    {
+                    ip4_header_t ip4; vrrp_header_t vrrp;
+                    }) ip4_and_vrrp_header_t;
+
+typedef CLIB_PACKED (struct
+                    {
+                    ip6_header_t ip6; vrrp_header_t vrrp;
+                    }) ip6_and_vrrp_header_t;
+
+/* the high 4 bits of the advertisement interval are "reserved" and
+ * should be ignored on reception. swap byte order and mask out those bits.
+ */
+always_inline u16
+vrrp_adv_int_from_packet (vrrp_header_t * pkt)
+{
+  return clib_net_to_host_u16 (pkt->rsvd_and_max_adv_int) & ((u16) 0x0fff);
+}
+
+#endif /* __included_vrrp_packet_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/vrrp/vrrp_periodic.c b/src/plugins/vrrp/vrrp_periodic.c
new file mode 100644 (file)
index 0000000..d373477
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * vrrp_periodic.c - vrrp plug-in periodic function
+ *
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+#include <vlib/vlib.h>
+#include <vppinfra/error.h>
+#include <vrrp/vrrp.h>
+#include <vrrp/vrrp_packet.h>
+
+static int
+vrrp_vr_timer_compare (const void *v1, const void *v2)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  const u32 *idx1, *idx2;
+  vrrp_vr_timer_t *timer1, *timer2;
+
+  idx1 = v1;
+  idx2 = v2;
+
+  timer1 = pool_elt_at_index (vmp->vr_timers, *idx1);
+  timer2 = pool_elt_at_index (vmp->vr_timers, *idx2);
+
+  /* don't check equality, they are unlikely to be exactly equal and
+   * if it occurs, it won't matter what order they were in.
+   * sort the list in reverse so we can pick the next timer off the end */
+  if (timer1->expire_time > timer2->expire_time)
+    return -1;
+  else
+    return 1;
+}
+
+static u32
+vrrp_vr_timer_get_next (void)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  int n_timers;
+
+  n_timers = vec_len (vmp->pending_timers);
+
+  if (!n_timers)
+    return ~0;
+
+  return vec_elt (vmp->pending_timers, n_timers - 1);
+}
+
+/* cancel an existing timer. This could happen because:
+ * - adv timer expired on master. another adv should be scheduled.
+ * - a shutdown event is received
+ * - a master is preempted by a higher priority master
+ * - adv received on backup. master down timer should be rescheduled.
+ */
+void
+vrrp_vr_timer_cancel (vrrp_vr_t * vr)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  u32 *t;
+
+  /* don't search for a timer that was already canceled or never set */
+  if (vr->runtime.timer_index == ~0)
+    return;
+
+  /* timers stored in descending order, start at the end of the list */
+  /* vec_foreach_backwards does not deal with 0 pointers, check first */
+  if (vmp->pending_timers)
+    vec_foreach_backwards (t, vmp->pending_timers)
+    {
+      if (*t == vr->runtime.timer_index)
+       {
+         vec_delete (vmp->pending_timers, 1, t - vmp->pending_timers);
+         break;
+       }
+    }
+
+  if (!pool_is_free_index (vmp->vr_timers, vr->runtime.timer_index))
+    pool_put_index (vmp->vr_timers, vr->runtime.timer_index);
+
+  vr->runtime.timer_index = ~0;
+
+  vlib_process_signal_event (vmp->vlib_main, vrrp_periodic_node.index,
+                            VRRP_EVENT_VR_TIMER_UPDATE, 0);
+}
+
+void
+vrrp_vr_timer_set (vrrp_vr_t * vr, vrrp_vr_timer_type_t type)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vlib_main_t *vm = vlib_get_main ();
+  vrrp_vr_timer_t *timer;
+  f64 now;
+
+  /* Each VR should be waiting on at most 1 timer at any given time.
+   * If there is already a timer set for this VR, cancel it.
+   */
+  if (vr->runtime.timer_index != ~0)
+    vrrp_vr_timer_cancel (vr);
+
+  pool_get (vmp->vr_timers, timer);
+  vr->runtime.timer_index = timer - vmp->vr_timers;
+
+  timer->vr_index = vr - vmp->vrs;
+  timer->type = type;
+
+  now = vlib_time_now (vm);
+
+  /* RFC 5798 specifies that timers are in centiseconds, so x / 100.0 */
+  switch (type)
+    {
+    case VRRP_VR_TIMER_ADV:
+      timer->expire_time = now + (vr->config.adv_interval / 100.0);
+      break;
+    case VRRP_VR_TIMER_MASTER_DOWN:
+      timer->expire_time = now + (vr->runtime.master_down_int / 100.0);
+      break;
+    default:
+      /* should never reach here */
+      clib_warning ("Unrecognized VRRP timer type (%d)", type);
+      return;
+    }
+
+  vec_add1 (vmp->pending_timers, vr->runtime.timer_index);
+
+  vec_sort_with_function (vmp->pending_timers, vrrp_vr_timer_compare);
+
+  vlib_process_signal_event (vmp->vlib_main, vrrp_periodic_node.index,
+                            VRRP_EVENT_VR_TIMER_UPDATE, 0);
+}
+
+void
+vrrp_vr_timer_timeout (u32 timer_index)
+{
+  vrrp_main_t *vmp = &vrrp_main;
+  vrrp_vr_timer_t *timer;
+  vrrp_vr_t *vr;
+
+  if (pool_is_free_index (vmp->vr_timers, timer_index))
+    {
+      clib_warning ("Timeout on free timer index %u", timer_index);
+      return;
+    }
+
+  timer = pool_elt_at_index (vmp->vr_timers, timer_index);
+  vr = pool_elt_at_index (vmp->vrs, timer->vr_index);
+
+  switch (timer->type)
+    {
+    case VRRP_VR_TIMER_ADV:
+      vrrp_adv_send (vr, 0);
+      vrrp_vr_timer_set (vr, VRRP_VR_TIMER_ADV);
+      break;
+    case VRRP_VR_TIMER_MASTER_DOWN:
+      vrrp_vr_transition (vr, VRRP_VR_STATE_MASTER, NULL);
+      break;
+    default:
+      clib_warning ("Unrecognized timer type %d", timer->type);
+      return;
+    }
+
+}
+
+static uword
+vrrp_periodic_process (vlib_main_t * vm,
+                      vlib_node_runtime_t * rt, vlib_frame_t * f)
+{
+  vrrp_main_t *pm = &vrrp_main;
+  f64 now;
+  f64 timeout = 10.0;
+  uword *event_data = 0;
+  uword event_type;
+  u32 next_timer = ~0;
+  vrrp_vr_timer_t *timer;
+
+  while (1)
+    {
+      now = vlib_time_now (vm);
+
+      if (next_timer == ~0)
+       {
+         vlib_process_wait_for_event (vm);
+       }
+      else
+       {
+         timer = pool_elt_at_index (pm->vr_timers, next_timer);
+         timeout = timer->expire_time - now;
+
+         vlib_process_wait_for_event_or_clock (vm, timeout);
+       }
+
+      event_type = vlib_process_get_events (vm, (uword **) & event_data);
+
+      switch (event_type)
+       {
+         /* Handle VRRP_EVENT_VR_TIMER_UPDATE */
+       case VRRP_EVENT_VR_TIMER_UPDATE:
+         next_timer = vrrp_vr_timer_get_next ();
+         break;
+
+         /* Handle periodic timeouts */
+       case ~0:
+         vrrp_vr_timer_timeout (next_timer);
+         next_timer = vrrp_vr_timer_get_next ();
+         break;
+       }
+      vec_reset_length (event_data);
+    }
+  return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (vrrp_periodic_node) =
+{
+  .function = vrrp_periodic_process,
+  .type = VLIB_NODE_TYPE_PROCESS,
+  .name = "vrrp-periodic-process",
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/vrrp/vrrp_test.c b/src/plugins/vrrp/vrrp_test.c
new file mode 100644 (file)
index 0000000..89ad712
--- /dev/null
@@ -0,0 +1,693 @@
+/*
+ * vrrp.c - VRRP vpp-api-test plug-in
+ *
+ * Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+#include <vat/vat.h>
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#include <vppinfra/error.h>
+
+#include <vnet/ip/ip.h>
+
+uword unformat_sw_if_index (unformat_input_t * input, va_list * args);
+
+/* Declare message IDs */
+#include <vnet/format_fns.h>
+#include <vrrp/vrrp.api_enum.h>
+#include <vrrp/vrrp.api_types.h>
+#include <vpp/api/vpe.api_types.h>
+
+
+typedef struct
+{
+  /* API message ID base */
+  u16 msg_id_base;
+  u32 ping_id;
+  vat_main_t *vat_main;
+} vrrp_test_main_t;
+
+vrrp_test_main_t vrrp_test_main;
+
+#define __plugin_msg_base vrrp_test_main.msg_id_base
+#include <vlibapi/vat_helper_macros.h>
+
+static int
+api_vrrp_vr_add_del (vat_main_t * vam)
+{
+  unformat_input_t *i = vam->input;
+  u32 sw_if_index = ~0;
+  u32 vr_id, priority, interval;
+  u8 is_ipv6, no_preempt, accept_mode, vr_unicast, is_add, is_del;
+  u8 n_addrs4, n_addrs6;
+  vl_api_vrrp_vr_add_del_t *mp;
+  vl_api_address_t *api_addr;
+  ip46_address_t *ip_addr, *ip_addrs = 0;
+  ip46_address_t addr;
+  int ret = 0;
+
+  interval = priority = 100;
+  n_addrs4 = n_addrs6 = 0;
+  vr_id = is_ipv6 = no_preempt = accept_mode = vr_unicast = 0;
+  is_add = is_del = 0;
+
+  clib_memset (&addr, 0, sizeof (addr));
+
+  /* Parse args required to build the message */
+  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
+       ;
+      else if (unformat (i, "sw_if_index %u", &sw_if_index))
+       ;
+      else if (unformat (i, "vr_id %u", &vr_id))
+       ;
+      else if (unformat (i, "ipv6"))
+       is_ipv6 = 1;
+      else if (unformat (i, "priority %u", &priority))
+       ;
+      else if (unformat (i, "interval %u", &interval))
+       ;
+      else if (unformat (i, "no_preempt"))
+       no_preempt = 1;
+      else if (unformat (i, "accept_mode"))
+       accept_mode = 1;
+      else if (unformat (i, "unicast"))
+       vr_unicast = 1;
+      else if (unformat (i, "%U", unformat_ip4_address, &addr.ip4))
+       {
+         vec_add1 (ip_addrs, addr);
+         n_addrs4++;
+         clib_memset (&addr, 0, sizeof (addr));
+       }
+      else if (unformat (i, "%U", unformat_ip6_address, &addr.ip6))
+       {
+         vec_add1 (ip_addrs, addr);
+         n_addrs6++;
+         clib_memset (&addr, 0, sizeof (addr));
+       }
+      else if (unformat (i, "add"))
+       is_add = 1;
+      else if (unformat (i, "del"))
+       is_del = 1;
+      else
+       break;
+    }
+
+  if (is_add == is_del)
+    {
+      errmsg ("One of add or del must be specified\n");
+      ret = -99;
+    }
+  else if (sw_if_index == ~0)
+    {
+      errmsg ("Interface not set\n");
+      ret = -99;
+    }
+  else if (n_addrs4 && (n_addrs6 || is_ipv6))
+    {
+      errmsg ("Address family mismatch\n");
+      ret = -99;
+    }
+
+  if (ret)
+    goto done;
+
+  /* Construct the API message */
+  M2 (VRRP_VR_ADD_DEL, mp, vec_len (ip_addrs) * sizeof (*api_addr));
+
+  mp->is_add = is_add;
+  mp->sw_if_index = ntohl (sw_if_index);
+  mp->vr_id = vr_id;
+  mp->priority = priority;
+  mp->interval = htons (interval);
+  mp->flags = VRRP_API_VR_PREEMPT;     /* preempt by default */
+
+  if (no_preempt)
+    mp->flags &= ~VRRP_API_VR_PREEMPT;
+
+  if (accept_mode)
+    mp->flags |= VRRP_API_VR_ACCEPT;
+
+  if (vr_unicast)
+    mp->flags |= VRRP_API_VR_UNICAST;
+
+  if (is_ipv6)
+    mp->flags |= VRRP_API_VR_IPV6;
+
+  mp->flags = htonl (mp->flags);
+
+  mp->n_addrs = n_addrs4 + n_addrs6;
+  api_addr = mp->addrs;
+
+  vec_foreach (ip_addr, ip_addrs)
+  {
+    void *src, *dst;
+    int len;
+
+    if (is_ipv6)
+      {
+       api_addr->af = ADDRESS_IP6;
+       src = &ip_addr->ip6;
+       dst = &api_addr->un.ip6;
+       len = sizeof (api_addr->un.ip6);
+      }
+    else
+      {
+       api_addr->af = ADDRESS_IP4;
+       src = &ip_addr->ip4;
+       dst = &api_addr->un.ip4;
+       len = sizeof (api_addr->un.ip4);
+      }
+    clib_memcpy (dst, src, len);
+    api_addr++;
+  }
+
+  /* send it... */
+  S (mp);
+
+  /* Wait for a reply... */
+  W (ret);
+
+done:
+  vec_free (ip_addrs);
+
+  return ret;
+}
+
+static int
+api_vrrp_vr_dump (vat_main_t * vam)
+{
+  vrrp_test_main_t *vtm = &vrrp_test_main;
+  unformat_input_t *i = vam->input;
+  vl_api_vrrp_vr_dump_t *mp;
+  vl_api_control_ping_t *mp_ping;
+  u32 sw_if_index = ~0;
+  int ret;
+
+  if (vam->json_output)
+    {
+      clib_warning ("JSON output not supported for vrrp_vr_dump");
+      return -99;
+    }
+
+  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
+       ;
+      else if (unformat (i, "sw_if_index %u", &sw_if_index))
+       ;
+      else
+       break;
+    }
+
+  M (VRRP_VR_DUMP, mp);
+
+  mp->sw_if_index = htonl (sw_if_index);
+
+  S (mp);
+
+  mp_ping = vl_msg_api_alloc_as_if_client (sizeof (*mp_ping));
+  mp_ping->_vl_msg_id = htons (vtm->ping_id);
+  mp_ping->client_index = vam->my_client_index;
+
+  vam->result_ready = 0;
+  S (mp_ping);
+
+  W (ret);
+  return ret;
+}
+
+static void
+vl_api_vrrp_vr_details_t_handler (vl_api_vrrp_vr_details_t * mp)
+{
+  vat_main_t *vam = vrrp_test_main.vat_main;
+  u32 api_flags = ntohl (mp->config.flags);
+  int i;
+  char *states[] = {
+    "VRRP_API_VR_STATE_INIT",
+    "VRRP_API_VR_STATE_BACKUP",
+    "VRRP_API_VR_STATE_MASTER",
+  };
+
+  fformat (vam->ofp, "sw_if_index %u vr_id %u IPv%d: "
+          "priority %u interval %u preempt %s accept %s unicast %s "
+          "state %s master_adv_interval %u skew %u master_down_interval %u "
+          "mac %U ",
+          ntohl (mp->config.sw_if_index), mp->config.vr_id,
+          (mp->config.flags & VRRP_API_VR_IPV6) ? 6 : 4,
+          mp->config.priority, htons (mp->config.interval),
+          (api_flags & VRRP_API_VR_PREEMPT) ? "yes" : "no",
+          (api_flags & VRRP_API_VR_ACCEPT) ? "yes" : "no",
+          (api_flags & VRRP_API_VR_UNICAST) ? "yes" : "no",
+          states[ntohl (mp->runtime.state)],
+          ntohs (mp->runtime.master_adv_int), ntohs (mp->runtime.skew),
+          ntohs (mp->runtime.master_down_int),
+          format_ethernet_address, &mp->runtime.mac);
+
+  fformat (vam->ofp, "addresses: ");
+
+  for (i = 0; i < mp->n_addrs; i++)
+    {
+      vl_api_address_t *addr = mp->addrs + i;
+
+      fformat (vam->ofp, "%U ",
+              (addr->af) ? format_ip6_address : format_ip4_address,
+              (u8 *) & addr->un);
+    }
+
+  fformat (vam->ofp, "\n");
+}
+
+static int
+api_vrrp_vr_start_stop (vat_main_t * vam)
+{
+  unformat_input_t *i = vam->input;
+  vl_api_vrrp_vr_start_stop_t *mp;
+  u32 sw_if_index = ~0, vr_id;
+  u8 is_ipv6, is_start, is_stop;
+  int ret;
+
+  vr_id = is_ipv6 = is_start = is_stop = 0;
+
+  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
+       ;
+      else if (unformat (i, "sw_if_index %u", &sw_if_index))
+       ;
+      else if (unformat (i, "vr_id %u", &vr_id))
+       ;
+      else if (unformat (i, "ipv6"))
+       is_ipv6 = 1;
+      else if (unformat (i, "start"))
+       is_start = 1;
+      else if (unformat (i, "stop"))
+       is_stop = 1;
+      else
+       break;
+    }
+
+  if (is_start == is_stop)
+    {
+      errmsg ("One of add or del must be specified\n");
+      return -99;
+    }
+  else if (sw_if_index == ~0)
+    {
+      errmsg ("Interface not set\n");
+      return -99;
+    }
+  else if (!vr_id)
+    {
+      errmsg ("VR ID must be between 1 and 255");
+      return -99;
+    }
+
+  M (VRRP_VR_START_STOP, mp);
+
+  mp->sw_if_index = htonl (sw_if_index);
+  mp->vr_id = vr_id;
+  mp->is_ipv6 = (is_ipv6 != 0);
+  mp->is_start = (is_start != 0);
+
+  S (mp);
+
+  W (ret);
+  return ret;
+}
+
+static int
+api_vrrp_vr_track_if_add_del (vat_main_t * vam)
+{
+  unformat_input_t *i = vam->input;
+  vl_api_vrrp_vr_track_if_add_del_t *mp;
+  vl_api_vrrp_vr_track_if_t *track_ifs = 0, *track_if;
+  u32 sw_if_index = ~0, track_sw_if_index = ~0, vr_id, priority;
+  u8 is_ipv6, is_add, is_del;
+  int ret;
+
+  is_ipv6 = is_add = is_del = 0;
+  vr_id = priority = 0;
+
+  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
+       ;
+      else if (unformat (i, "sw_if_index %u", &sw_if_index))
+       ;
+      else if (unformat (i, "vr_id %u", &vr_id))
+       ;
+      else if (unformat (i, "ipv6"))
+       is_ipv6 = 1;
+      else if (unformat (i, "track-index %u priority %u", &track_sw_if_index,
+                        &priority))
+       {
+         vec_add2 (track_ifs, track_if, 1);
+         track_if->sw_if_index = ntohl (track_sw_if_index);
+         track_if->priority = priority;
+       }
+      else if (unformat (i, "add"))
+       is_add = 1;
+      else if (unformat (i, "del"))
+       is_del = 1;
+      else
+       break;
+    }
+
+  if (is_add == is_del)
+    {
+      errmsg ("One of add or del must be specified\n");
+      ret = -99;
+    }
+  else if (sw_if_index == ~0)
+    {
+      errmsg ("VR interface not specified\n");
+      return -99;
+    }
+  else if (!vr_id)
+    {
+      errmsg ("Invalid VR ID - must be between 1 and 255");
+      return -99;
+    }
+  else if (vec_len (track_ifs) == 0)
+    {
+      errmsg ("No tracked interfaces specified for VR\n");
+      return -99;
+    }
+
+  vec_foreach (track_if, track_ifs)
+  {
+    if (!track_if->priority)
+      {
+       errmsg ("Priority must be nonzero");
+       vec_free (track_ifs);
+       return -99;
+      }
+  }
+
+
+  M2 (VRRP_VR_TRACK_IF_ADD_DEL, mp, vec_len (track_ifs) * sizeof (*track_if));
+
+  mp->sw_if_index = htonl (sw_if_index);
+  mp->vr_id = vr_id;
+  mp->is_ipv6 = (is_ipv6 != 0);
+  mp->is_add = is_add;
+  mp->n_ifs = vec_len (track_ifs);
+  clib_memcpy (mp->ifs, track_ifs, mp->n_ifs * sizeof (*track_if));
+
+  S (mp);
+
+  W (ret);
+  return ret;
+}
+
+static int
+api_vrrp_vr_track_if_dump (vat_main_t * vam)
+{
+  vrrp_test_main_t *vtm = &vrrp_test_main;
+  unformat_input_t *i = vam->input;
+  vl_api_vrrp_vr_track_if_dump_t *mp;
+  vl_api_control_ping_t *mp_ping;
+  u32 sw_if_index = ~0, vr_id = 0;
+  u8 is_ipv6 = 0, dump_all = 0;
+  int ret;
+
+  if (vam->json_output)
+    {
+      clib_warning ("JSON output not supported for vrrp_vr_track_if_dump");
+      return -99;
+    }
+
+  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
+       ;
+      else if (unformat (i, "sw_if_index %u", &sw_if_index))
+       ;
+      else if (unformat (i, "vr_id %u", &vr_id))
+       ;
+      else if (unformat (i, "ipv6"))
+       is_ipv6 = 1;
+      else
+       break;
+    }
+
+  /* If no arguments were provided, dump all VRs */
+  if ((sw_if_index == ~0) && !vr_id && !is_ipv6)
+    dump_all = 1;
+
+  /* If any arguments provided, sw_if_index and vr_id must be valid */
+  else if (sw_if_index == ~0)
+    {
+      errmsg ("VR interface not specified\n");
+      return -99;
+    }
+  else if (!vr_id)
+    {
+      errmsg ("Invalid VR ID - must be between 1 and 255");
+      return -99;
+    }
+
+  M (VRRP_VR_TRACK_IF_DUMP, mp);
+
+  mp->dump_all = dump_all;
+  if (!dump_all)
+    {
+      mp->sw_if_index = htonl (sw_if_index);
+      mp->vr_id = vr_id;
+      mp->is_ipv6 = is_ipv6;
+    }
+
+  S (mp);
+
+  mp_ping = vl_msg_api_alloc_as_if_client (sizeof (*mp_ping));
+  mp_ping->_vl_msg_id = htons (vtm->ping_id);
+  mp_ping->client_index = vam->my_client_index;
+
+  vam->result_ready = 0;
+  S (mp_ping);
+
+  W (ret);
+  return ret;
+}
+
+static void
+  vl_api_vrrp_vr_track_if_details_t_handler
+  (vl_api_vrrp_vr_track_if_details_t * mp)
+{
+  vat_main_t *vam = vrrp_test_main.vat_main;
+  int i;
+
+  for (i = 0; i < mp->n_ifs; i++)
+    {
+      fformat (vam->ofp, "VR sw_if_index %u vr_id %u IPv%d - "
+              "track sw_if_index %u priority %u\n",
+              ntohl (mp->sw_if_index), mp->vr_id, (mp->is_ipv6) ? 6 : 4,
+              ntohl (mp->ifs[i].sw_if_index), mp->ifs[i].priority);
+    }
+
+  fformat (vam->ofp, "\n");
+}
+
+static int
+api_vrrp_vr_set_peers (vat_main_t * vam)
+{
+  unformat_input_t *i = vam->input;
+  u32 sw_if_index = ~0;
+  u32 vr_id;
+  u8 is_ipv6;
+  u8 n_addrs4, n_addrs6;
+  vl_api_vrrp_vr_set_peers_t *mp;
+  vl_api_address_t *api_addr;
+  ip46_address_t *ip_addr, *ip_addrs = 0;
+  ip46_address_t addr;
+  int ret = 0;
+
+  n_addrs4 = n_addrs6 = 0;
+  vr_id = is_ipv6 = 0;
+
+  clib_memset (&addr, 0, sizeof (addr));
+
+  /* Parse args required to build the message */
+  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
+       ;
+      else if (unformat (i, "sw_if_index %u", &sw_if_index))
+       ;
+      else if (unformat (i, "vr_id %u", &vr_id))
+       ;
+      else if (unformat (i, "ipv6"))
+       is_ipv6 = 1;
+      else if (unformat (i, "%U", unformat_ip4_address, &addr.ip4))
+       {
+         vec_add1 (ip_addrs, addr);
+         n_addrs4++;
+         clib_memset (&addr, 0, sizeof (addr));
+       }
+      else if (unformat (i, "%U", unformat_ip6_address, &addr.ip6))
+       {
+         vec_add1 (ip_addrs, addr);
+         n_addrs6++;
+         clib_memset (&addr, 0, sizeof (addr));
+       }
+      else
+       break;
+    }
+
+  if (sw_if_index == ~0)
+    {
+      errmsg ("Interface not set\n");
+      ret = -99;
+    }
+  else if (n_addrs4 && (n_addrs6 || is_ipv6))
+    {
+      errmsg ("Address family mismatch\n");
+      ret = -99;
+    }
+
+  if (ret)
+    goto done;
+
+  /* Construct the API message */
+  M2 (VRRP_VR_SET_PEERS, mp, vec_len (ip_addrs) * sizeof (*api_addr));
+
+  mp->sw_if_index = ntohl (sw_if_index);
+  mp->vr_id = vr_id;
+  mp->is_ipv6 = (is_ipv6 != 0);
+
+  mp->n_addrs = n_addrs4 + n_addrs6;
+  api_addr = mp->addrs;
+
+  vec_foreach (ip_addr, ip_addrs)
+  {
+    void *src, *dst;
+    int len;
+
+    if (is_ipv6)
+      {
+       api_addr->af = ADDRESS_IP6;
+       src = &ip_addr->ip6;
+       dst = &api_addr->un.ip6;
+       len = sizeof (api_addr->un.ip6);
+      }
+    else
+      {
+       api_addr->af = ADDRESS_IP4;
+       src = &ip_addr->ip4;
+       dst = &api_addr->un.ip4;
+       len = sizeof (api_addr->un.ip4);
+      }
+    clib_memcpy (dst, src, len);
+    api_addr++;
+  }
+
+  /* send it... */
+  S (mp);
+
+  /* Wait for a reply... */
+  W (ret);
+
+done:
+  vec_free (ip_addrs);
+
+  return ret;
+}
+
+static int
+api_vrrp_vr_peer_dump (vat_main_t * vam)
+{
+  vrrp_test_main_t *vtm = &vrrp_test_main;
+  unformat_input_t *i = vam->input;
+  vl_api_vrrp_vr_peer_dump_t *mp;
+  vl_api_control_ping_t *mp_ping;
+  u32 sw_if_index = ~0, vr_id = 0;
+  u8 is_ipv6 = 0;
+  int ret;
+
+  if (vam->json_output)
+    {
+      clib_warning ("JSON output not supported for vrrp_vr_track_if_dump");
+      return -99;
+    }
+
+  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
+       ;
+      else if (unformat (i, "sw_if_index %u", &sw_if_index))
+       ;
+      else if (unformat (i, "vr_id %u", &vr_id))
+       ;
+      else if (unformat (i, "ipv6"))
+       is_ipv6 = 1;
+      else
+       break;
+    }
+
+  /* sw_if_index and vr_id must be valid */
+  if (sw_if_index == ~0)
+    {
+      errmsg ("VR interface not specified\n");
+      return -99;
+    }
+  else if (!vr_id)
+    {
+      errmsg ("Invalid VR ID - must be between 1 and 255");
+      return -99;
+    }
+
+  M (VRRP_VR_PEER_DUMP, mp);
+
+  mp->sw_if_index = htonl (sw_if_index);
+  mp->vr_id = vr_id;
+  mp->is_ipv6 = is_ipv6;
+
+  S (mp);
+
+  mp_ping = vl_msg_api_alloc_as_if_client (sizeof (*mp_ping));
+  mp_ping->_vl_msg_id = htons (vtm->ping_id);
+  mp_ping->client_index = vam->my_client_index;
+
+  vam->result_ready = 0;
+  S (mp_ping);
+
+  W (ret);
+  return ret;
+}
+
+static void
+vl_api_vrrp_vr_peer_details_t_handler (vl_api_vrrp_vr_peer_details_t * mp)
+{
+  vat_main_t *vam = vrrp_test_main.vat_main;
+  int i;
+
+  fformat (vam->ofp, "sw_if_index %u vr_id %u IPv%d ",
+          ntohl (mp->sw_if_index), mp->vr_id, (mp->is_ipv6) ? 6 : 4);
+
+  fformat (vam->ofp, "peer addresses: ");
+
+  for (i = 0; i < mp->n_peer_addrs; i++)
+    {
+      vl_api_address_t *addr = mp->peer_addrs + i;
+
+      fformat (vam->ofp, "%U ",
+              (addr->af) ? format_ip6_address : format_ip4_address,
+              (u8 *) & addr->un);
+    }
+
+  fformat (vam->ofp, "\n");
+}
+
+#include <vrrp/vrrp.api_test.c>
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/test/patches/scapy-2.4.3/vrrp.patch b/test/patches/scapy-2.4.3/vrrp.patch
new file mode 100644 (file)
index 0000000..39e99d4
--- /dev/null
@@ -0,0 +1,35 @@
+diff --git a/scapy/layers/vrrp.py b/scapy/layers/vrrp.py
+index 1b583653..3d6a5923 100644
+--- a/scapy/layers/vrrp.py
++++ b/scapy/layers/vrrp.py
+@@ -10,7 +10,7 @@ VRRP (Virtual Router Redundancy Protocol).
+ from scapy.packet import Packet, bind_layers
+ from scapy.fields import BitField, ByteField, FieldLenField, FieldListField, \
+-    IPField, IntField, XShortField
++    IPField, IP6Field, IntField, MultipleTypeField, StrField, XShortField
+ from scapy.compat import chb, orb
+ from scapy.layers.inet import IP, in4_chksum, checksum
+ from scapy.layers.inet6 import IPv6, in6_chksum
+@@ -62,9 +62,18 @@ class VRRPv3(Packet):
+         BitField("res", 0, 4),
+         BitField("adv", 100, 12),
+         XShortField("chksum", None),
+-        # FIXME: addrlist should also allow IPv6 addresses :/
+-        FieldListField("addrlist", [], IPField("", "0.0.0.0"),
+-                       count_from=lambda pkt: pkt.ipcount)]
++        MultipleTypeField(
++            [
++                (FieldListField("addrlist", [], IPField("", "0.0.0.0"),
++                                count_from=lambda pkt: pkt.ipcount),
++                 lambda p: isinstance(p.underlayer, IP)),
++                (FieldListField("addrlist", [], IP6Field("", "::"),
++                                count_from=lambda pkt: pkt.ipcount),
++                 lambda p: isinstance(p.underlayer, IPv6)),
++            ],
++            StrField("addrlist", "")
++        )
++    ]
+     def post_build(self, p, pay):
+         if self.chksum is None: