From 39e9428b90bc74d1bb15fc17759c8ef6ad712418 Mon Sep 17 00:00:00 2001 From: Matthew Smith Date: Tue, 11 Feb 2020 11:25:32 -0600 Subject: [PATCH] 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 --- MAINTAINERS | 5 + src/plugins/vrrp/CMakeLists.txt | 28 + src/plugins/vrrp/FEATURE.yaml | 23 + src/plugins/vrrp/node.c | 753 ++++++++++++++++++++ src/plugins/vrrp/setup.pg | 20 + src/plugins/vrrp/test/test_vrrp.py | 1288 +++++++++++++++++++++++++++++++++++ src/plugins/vrrp/vrrp.api | 245 +++++++ src/plugins/vrrp/vrrp.c | 1240 +++++++++++++++++++++++++++++++++ src/plugins/vrrp/vrrp.h | 373 ++++++++++ src/plugins/vrrp/vrrp_all_api_h.h | 11 + src/plugins/vrrp/vrrp_api.c | 501 ++++++++++++++ src/plugins/vrrp/vrrp_cli.c | 507 ++++++++++++++ src/plugins/vrrp/vrrp_format.c | 146 ++++ src/plugins/vrrp/vrrp_msg_enum.h | 23 + src/plugins/vrrp/vrrp_packet.c | 735 ++++++++++++++++++++ src/plugins/vrrp/vrrp_packet.h | 58 ++ src/plugins/vrrp/vrrp_periodic.c | 228 +++++++ src/plugins/vrrp/vrrp_test.c | 693 +++++++++++++++++++ test/patches/scapy-2.4.3/vrrp.patch | 35 + 19 files changed, 6912 insertions(+) create mode 100644 src/plugins/vrrp/CMakeLists.txt create mode 100644 src/plugins/vrrp/FEATURE.yaml create mode 100644 src/plugins/vrrp/node.c create mode 100644 src/plugins/vrrp/setup.pg create mode 100644 src/plugins/vrrp/test/test_vrrp.py create mode 100644 src/plugins/vrrp/vrrp.api create mode 100644 src/plugins/vrrp/vrrp.c create mode 100644 src/plugins/vrrp/vrrp.h create mode 100644 src/plugins/vrrp/vrrp_all_api_h.h create mode 100644 src/plugins/vrrp/vrrp_api.c create mode 100644 src/plugins/vrrp/vrrp_cli.c create mode 100644 src/plugins/vrrp/vrrp_format.c create mode 100644 src/plugins/vrrp/vrrp_msg_enum.h create mode 100644 src/plugins/vrrp/vrrp_packet.c create mode 100644 src/plugins/vrrp/vrrp_packet.h create mode 100644 src/plugins/vrrp/vrrp_periodic.c create mode 100644 src/plugins/vrrp/vrrp_test.c create mode 100644 test/patches/scapy-2.4.3/vrrp.patch 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 +F: src/plugins/vrrp + VPP Config Tooling I: vpp_config M: John DeNisco 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 +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +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 +#include +#include + +#include +#include + +/* 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 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 +#include +#include + +#include +#include +#include + +/* define message IDs */ +#include +#include +#include + +#define REPLY_MSG_ID_BASE vmp->msg_id_base +#include + +/* 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 +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 +#include +#include + +#include +#include +#include + + +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 [vr_id ] [ipv6] [priority ] [interval ] [no_preempt] [accept_mode] [unicast] [ ...]", + .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 [vr_id ] [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 [(|sw_if_index )]", + .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) (|sw_if_index ) vr_id [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 (|sw_if_index ) vr_id [ipv6] [ ...]", + .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) (|sw_if_index ) vr_id [ipv6] track-index priority [ track-index priority ...]", + .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 +#include +#include +#include + +#include +#include + +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 + +#define vl_msg_id(n,h) n, +typedef enum { +#include + /* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +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 + +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 +#include +#include +#include + +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 +#include +#include +#include + +#include + +uword unformat_sw_if_index (unformat_input_t * input, va_list * args); + +/* Declare message IDs */ +#include +#include +#include +#include + + +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 + +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 +/* + * 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: -- 2.16.6