aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Smith <mgsmith@netgate.com>2020-02-11 11:25:32 -0600
committerDave Barach <openvpp@barachs.net>2020-02-13 19:46:30 +0000
commit39e9428b90bc74d1bb15fc17759c8ef6ad712418 (patch)
treede9317a906a7df43bf2140a654d3b7675cab8d86
parentf75defa7676759fa81ae75e7edd492572c6b8fd6 (diff)
vrrp: add plugin providing vrrp support
Type: feature Add a new plugin to support HA using VRRPv3 (RFC 5798). Change-Id: Iaa2c37e6172f8f41e9165f178f44d481f6e247b9 Signed-off-by: Matthew Smith <mgsmith@netgate.com>
-rw-r--r--MAINTAINERS5
-rw-r--r--src/plugins/vrrp/CMakeLists.txt28
-rw-r--r--src/plugins/vrrp/FEATURE.yaml23
-rw-r--r--src/plugins/vrrp/node.c753
-rw-r--r--src/plugins/vrrp/setup.pg20
-rw-r--r--src/plugins/vrrp/test/test_vrrp.py1288
-rw-r--r--src/plugins/vrrp/vrrp.api245
-rw-r--r--src/plugins/vrrp/vrrp.c1240
-rw-r--r--src/plugins/vrrp/vrrp.h373
-rw-r--r--src/plugins/vrrp/vrrp_all_api_h.h11
-rw-r--r--src/plugins/vrrp/vrrp_api.c501
-rw-r--r--src/plugins/vrrp/vrrp_cli.c507
-rw-r--r--src/plugins/vrrp/vrrp_format.c146
-rw-r--r--src/plugins/vrrp/vrrp_msg_enum.h23
-rw-r--r--src/plugins/vrrp/vrrp_packet.c735
-rw-r--r--src/plugins/vrrp/vrrp_packet.h58
-rw-r--r--src/plugins/vrrp/vrrp_periodic.c228
-rw-r--r--src/plugins/vrrp/vrrp_test.c693
-rw-r--r--test/patches/scapy-2.4.3/vrrp.patch35
19 files changed, 6912 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index bb86fd40c90..2c17b0861ea 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -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
index 00000000000..21715d2d954
--- /dev/null
+++ b/src/plugins/vrrp/CMakeLists.txt
@@ -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
index 00000000000..d92e97e03d1
--- /dev/null
+++ b/src/plugins/vrrp/FEATURE.yaml
@@ -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
index 00000000000..b819919e428
--- /dev/null
+++ b/src/plugins/vrrp/node.c
@@ -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
index 00000000000..9275fcc46c6
--- /dev/null
+++ b/src/plugins/vrrp/setup.pg
@@ -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
index 00000000000..cc46a1de5f4
--- /dev/null
+++ b/src/plugins/vrrp/test/test_vrrp.py
@@ -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
index 00000000000..1894d1ccd5d
--- /dev/null
+++ b/src/plugins/vrrp/vrrp.api
@@ -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
index 00000000000..13cdba6f5e7
--- /dev/null
+++ b/src/plugins/vrrp/vrrp.c
@@ -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
index 00000000000..9c636c42802
--- /dev/null
+++ b/src/plugins/vrrp/vrrp.h
@@ -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
index 00000000000..4f45909de70
--- /dev/null
+++ b/src/plugins/vrrp/vrrp_all_api_h.h
@@ -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
index 00000000000..3b9c256b263
--- /dev/null
+++ b/src/plugins/vrrp/vrrp_api.c
@@ -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
index 00000000000..447b4749a28
--- /dev/null
+++ b/src/plugins/vrrp/vrrp_cli.c
@@ -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
index 00000000000..df9bf930b78
--- /dev/null
+++ b/src/plugins/vrrp/vrrp_format.c
@@ -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
index 00000000000..48ae619205a
--- /dev/null
+++ b/src/plugins/vrrp/vrrp_msg_enum.h
@@ -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
index 00000000000..f624b1876c3
--- /dev/null
+++ b/src/plugins/vrrp/vrrp_packet.c
@@ -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
index 00000000000..1cbf62d7c72
--- /dev/null
+++ b/src/plugins/vrrp/vrrp_packet.h
@@ -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
index 00000000000..d37347701fe
--- /dev/null
+++ b/src/plugins/vrrp/vrrp_periodic.c
@@ -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
index 00000000000..89ad712dc73
--- /dev/null
+++ b/src/plugins/vrrp/vrrp_test.c
@@ -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
index 00000000000..39e99d4045e
--- /dev/null
+++ b/test/patches/scapy-2.4.3/vrrp.patch
@@ -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: