blob: fb0659605c71bb53ea14415c7351772993d9aab2 [file] [log] [blame]
/*
* vrrp.c - vrrp plugin action functions
*
* Copyright 2019-2020 Rubicon Communications, LLC (Netgate)
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include <vnet/vnet.h>
#include <vnet/plugin/plugin.h>
#include <vnet/mfib/mfib_entry.h>
#include <vnet/mfib/mfib_table.h>
#include <vnet/adj/adj.h>
#include <vnet/adj/adj_mcast.h>
#include <vnet/fib/fib_table.h>
#include <vnet/ip/igmp_packet.h>
#include <vnet/ip/ip6_link.h>
#include <vrrp/vrrp.h>
#include <vrrp/vrrp_packet.h>
#include <vpp/app/version.h>
vrrp_main_t vrrp_main;
static const mac_address_t ipv4_vmac = {
.bytes = {0x00, 0x00, 0x5e, 0x00, 0x01, 0x00}
};
static const mac_address_t ipv6_vmac = {
.bytes = {0x00, 0x00, 0x5e, 0x00, 0x02, 0x00}
};
vlib_simple_counter_main_t vrrp_errs[] = {
/* Total number of VRRP packets received with invalid checksum */
{
.name = "CHKSUM_ERRS",
.stat_segment_name = "/net/vrrp/chksum-errs",
},
/* Total number of VRRP packets received with unknown or unsupported version
*/
{
.name = "VERSION_ERRS",
.stat_segment_name = "/net/vrrp/version-errs",
},
/* Total number of VRRP packets received with invalid VRID */
{
.name = "VRID_ERRS",
.stat_segment_name = "/net/vrrp/vrid-errs",
},
/* Total number of VRRP packets received with TTL/Hop limit != 255 */
{
.name = "TTL_ERRS",
.stat_segment_name = "/net/vrrp/ttl-errs",
},
/* Number of packets received with an address list not matching the locally
configured one */
{
.name = "ADDR_LIST_ERRS",
.stat_segment_name = "/net/vrrp/addr-list-errs",
},
/* Number of packets received with a length less than the VRRP header */
{
.name = "PACKET_LEN_ERRS",
.stat_segment_name = "/net/vrrp/packet-len-errs",
},
};
void
vrrp_incr_err_counter (vrrp_err_counter_t err_type)
{
if (err_type >= VRRP_ERR_COUNTER_MAX)
{
clib_warning ("Attempt to increse error counter of unknown type %u",
err_type);
return;
}
vlib_increment_simple_counter (&vrrp_errs[err_type],
vlib_get_main ()->thread_index, 0, 1);
}
// per-VRRP statistics
/* Number of times a VRRP instance has transitioned to master */
vlib_simple_counter_main_t vrrp_stats[] = {
{
.name = "MASTER_TRANS",
.stat_segment_name = "/net/vrrp/master-trans",
},
/* Number of VRRP advertisements sent by a VRRP instance */
{
.name = "ADV_SENT",
.stat_segment_name = "/net/vrrp/adv-sent",
},
/* Number of VRRP advertisements received by a VRRP instance */
{
.name = "ADV_RCVD",
.stat_segment_name = "/net/vrrp/adv-rcvd",
},
/* Number of VRRP priority-0 packets sent by a VRRP instance */
{
.name = "PRIO0_SENT",
.stat_segment_name = "/net/vrrp/prio0-sent",
},
/* Number of VRRP priority-0 packets received by a VRRP instance */
{
.name = "PRIO0_RCVD",
.stat_segment_name = "/net/vrrp/prio0-rcvd",
},
};
void
vrrp_incr_stat_counter (vrrp_stat_counter_t stat_type, u32 stat_index)
{
if (stat_type >= VRRP_STAT_COUNTER_MAX)
{
clib_warning ("Attempt to increse stat counter of unknown type %u",
stat_type);
return;
}
vlib_increment_simple_counter (
&vrrp_stats[stat_type], vlib_get_main ()->thread_index, stat_index, 1);
}
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;
const char *mc_arc_name = 0, *mc_node_name = 0;
u8 is_ipv6 = vrrp_vr_is_ipv6 (vr);
u32 *vr_index;
int n_master_accept = 0;
int n_started = 0;
if (is_ipv6)
{
arc_name = "ip6-local";
node_name = "vrrp6-nd-input";
mc_arc_name = "ip6-multicast";
mc_node_name = "vrrp6-accept-owner-input";
}
else
{
arc_name = "arp";
node_name = "vrrp4-arp-input";
mc_arc_name = "ip4-multicast";
mc_node_name = "vrrp4-accept-owner-input";
}
intf = vrrp_intf_get (vr->config.sw_if_index);
/* Check other VRs on this intf to see if features need to be toggled */
vec_foreach (vr_index, intf->vr_indices[is_ipv6])
{
vrrp_vr_t *intf_vr = vrrp_vr_lookup_index (*vr_index);
if (intf_vr == vr)
continue;
if (intf_vr->runtime.state == VRRP_VR_STATE_INIT)
continue;
n_started++;
if ((intf_vr->runtime.state == VRRP_VR_STATE_MASTER) &&
vrrp_vr_accept_mode_enabled (intf_vr))
n_master_accept++;
}
/* If entering/leaving init state, start/stop ARP or ND feature if no other
* VRs are active on the interface.
*/
if (((vr->runtime.state == VRRP_VR_STATE_INIT) ||
(new_state == VRRP_VR_STATE_INIT)) && (n_started == 0))
vnet_feature_enable_disable (arc_name, node_name,
vr->config.sw_if_index,
(new_state != VRRP_VR_STATE_INIT), NULL, 0);
/* Special housekeeping when entering/leaving master mode */
if ((vr->runtime.state == VRRP_VR_STATE_MASTER) ||
(new_state == VRRP_VR_STATE_MASTER))
{
/* Maintain count of master state VRs on interface */
if (new_state == VRRP_VR_STATE_MASTER)
intf->n_master_vrs[is_ipv6]++;
else if (intf->n_master_vrs[is_ipv6] > 0)
intf->n_master_vrs[is_ipv6]--;
/* If accept mode is enabled and no other master on intf has accept
* mode enabled, enable/disable feature node to avoid spurious drops by
* spoofing check.
*/
if (vrrp_vr_accept_mode_enabled (vr) && !n_master_accept)
vnet_feature_enable_disable (mc_arc_name, mc_node_name,
vr->config.sw_if_index,
(new_state == VRRP_VR_STATE_MASTER),
NULL, 0);
}
}
/* 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;
/* 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)
{
vrrp_incr_stat_counter (VRRP_STAT_COUNTER_MASTER_TRANS, vr->stat_index);
/* 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_input_process_args_t *args = data;
if (args)
vr->runtime.master_adv_int = args->max_adv_int;
}
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 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);
vrrp_vr_event (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, i;
u32 n_vrs_in_fib = 0;
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);
vec_foreach_index (i, vrm->vrrp_intfs)
{
if (mfib_table_get_index_for_sw_if_index (proto, i) != fib_index)
continue;
n_vrs_in_fib += vrrp_intf_num_vrs (i, is_ipv6);
}
if (enable)
{
/* ensure that the local mcast route exists */
mfib_table_entry_path_update (fib_index, vrrp_prefix, MFIB_SOURCE_API,
MFIB_ENTRY_FLAG_NONE, &for_us);
mfib_table_entry_path_update (fib_index, vrrp_prefix, MFIB_SOURCE_API,
MFIB_ENTRY_FLAG_NONE, &via_itf);
intf->mcast_adj_index[! !is_ipv6] =
adj_mcast_add_or_lock (proto, link_type, sw_if_index);
}
else
{
/* Remove mcast local routes if this is the last VR being deleted */
if (n_vrs_in_fib == 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, index_t vrrp_index)
{
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 && vrrp_index != vr_index)
return VNET_API_ERROR_ADDRESS_IN_USE;
}
return 0;
}
static int
vrrp_vr_valid_addrs (vrrp_vr_config_t *vr_conf, index_t vrrp_index)
{
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, vrrp_index);
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, addr - vr->config.vr_addrs);
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, addr - vr->config.vr_addrs);
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);
}
}
int
vrrp_vr_update (index_t *vrrp_index, vrrp_vr_config_t *vr_conf)
{
index_t index = *vrrp_index;
vrrp_main_t *vrm = &vrrp_main;
vrrp_vr_t *vr = NULL;
vrrp_vr_key_t key = { 0 };
uint8_t must_restart = 0;
int ret = 0;
/* no valid index -> create and return allocated index */
if (index == INDEX_INVALID)
{
return vrrp_vr_add_del (1, vr_conf, vrrp_index);
}
/* update: lookup vrrp instance */
if (pool_is_free_index (vrm->vrs, index))
return (VNET_API_ERROR_NO_SUCH_ENTRY);
/* fetch existing VR */
vr = pool_elt_at_index (vrm->vrs, index);
/* populate key */
key.vr_id = vr->config.vr_id;
key.is_ipv6 = !!(vr->config.flags & VRRP_VR_IPV6);
;
key.sw_if_index = vr->config.sw_if_index;
/* Do not allow changes to the keys of the VRRP instance */
if (vr_conf->vr_id != key.vr_id || vr_conf->sw_if_index != key.sw_if_index ||
!!(vr_conf->flags & VRRP_VR_IPV6) != key.is_ipv6)
{
clib_warning ("Attempt to change VR ID, IP version or interface index "
"for VRRP instance with index %u",
index);
return VNET_API_ERROR_INVALID_ARGUMENT;
}
/* 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, index)) < 0)
return ret;
/* stop it if needed */
must_restart = (vr->runtime.state != VRRP_VR_STATE_INIT);
if (must_restart)
vrrp_vr_start_stop (0, &key);
/* overwrite new config */
vr->config.priority = vr_conf->priority;
vr->config.adv_interval = vr_conf->adv_interval;
vr->config.flags = vr_conf->flags;
/* check if any address has changed */
ip46_address_t *vr_addr, *conf_addr;
uint8_t found;
vec_foreach (vr_addr, vr->config.vr_addrs)
{
found = 0;
vec_foreach (conf_addr, vr_conf->vr_addrs)
{
if (ip46_address_is_equal (vr_addr, conf_addr))
{
found = 1;
break;
}
}
if (!found)
{
vrrp_vr_addr_add_del (vr, 0, vr_addr);
}
}
vec_foreach (conf_addr, vr_conf->vr_addrs)
{
found = 0;
vec_foreach (vr_addr, vr->config.vr_addrs)
{
if (ip46_address_is_equal (vr_addr, conf_addr))
{
found = 1;
break;
}
}
if (!found)
{
vrrp_vr_addr_add_del (vr, 1, conf_addr);
}
}
/* restart it if needed */
if (must_restart)
vrrp_vr_start_stop (1, &key);
return 0;
}
static void
vrrp_vr_del_common (vrrp_vr_t *vr, vrrp_vr_key_t *key)
{
vrrp_main_t *vrm = &vrrp_main;
ip46_address_t *vr_addrs_del_copy;
vrrp_vr_timer_cancel (vr);
vrrp_vr_tracking_ifs_add_del (vr, vr->tracking.interfaces, 0);
vr_addrs_del_copy = vec_dup (vr->config.vr_addrs);
vrrp_vr_addrs_add_del (vr, 0, vr_addrs_del_copy);
mhash_unset (&vrm->vr_index_by_key, key, 0);
vec_free (vr_addrs_del_copy);
vec_free (vr->config.peer_addrs);
vec_free (vr->config.vr_addrs);
vec_free (vr->tracking.interfaces);
pool_put (vrm->vrs, vr);
}
int
vrrp_vr_del (index_t vrrp_index)
{
vrrp_main_t *vrm = &vrrp_main;
vrrp_vr_key_t key;
vrrp_vr_t *vr = 0;
if (pool_is_free_index (vrm->vrs, vrrp_index))
{
return (VNET_API_ERROR_NO_SUCH_ENTRY);
}
else
{
vr = pool_elt_at_index (vrm->vrs, vrrp_index);
key.sw_if_index = vr->config.sw_if_index;
key.vr_id = vr->config.vr_id;
key.is_ipv6 = vrrp_vr_is_ipv6 (vr);
vrrp_vr_del_common (vr, &key);
return 0;
}
}
/* Action function shared between message handler and debug CLI */
int
vrrp_vr_add_del (u8 is_add, vrrp_vr_config_t *vr_conf, index_t *ret_index)
{
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, INDEX_INVALID)) < 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;
/* recall pool index for stats */
vr->stat_index = vr_index;
/* and return it if we were asked to */
if (ret_index != NULL)
{
*ret_index = vr_index;
}
/* allocate & reset stats */
for (int i = 0; i < VRRP_STAT_COUNTER_MAX; i++)
{
vlib_validate_simple_counter (&vrrp_stats[i], vr_index);
vlib_zero_simple_counter (&vrrp_stats[i], vr_index);
}
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_del_common (vr, &key);
}
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);
/* allocate & reset error counters */
for (int i = 0; i < VRRP_ERR_COUNTER_MAX; i++)
{
vlib_validate_simple_counter (&vrrp_errs[i], 0);
vlib_zero_simple_counter (&vrrp_errs[i], 0);
}
return error;
}
VLIB_INIT_FUNCTION (vrrp_init);
VLIB_PLUGIN_REGISTER () =
{
.version = VPP_BUILD_VER,
.description = "VRRP v3 (RFC 5798)",
};
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "gnu")
* End:
*/