vrrp: add stats support and update API

Add simple counter statistics to VRRP, based on a subset of those
defined in RFC8347.

Add an update API that allows in-place modification of an existing
instance. The method returns a vrrp_index which can be used both for
retrieving statistics and to modify non-key parameters. Also add a
delete method which will take that vrrp_index as parameter.

Type: improvement
Signed-off-by: Emanuele Di Pascale <lele84@gmail.com>
Change-Id: I2cd11467b4dbd9dfdb5aa748783144b4883dba57
This commit is contained in:
Emanuele Di Pascale
2022-03-29 12:29:23 +02:00
parent 2518dca440
commit 7539e4b552
9 changed files with 763 additions and 18 deletions
+10
View File
@@ -130,6 +130,7 @@ vrrp_input_process_master (vrrp_vr_t *vr, vrrp_input_process_args_t *args)
{
clib_warning ("Received shutdown message from a peer on VR %U",
format_vrrp_vr_key, vr);
vrrp_incr_stat_counter (VRRP_STAT_COUNTER_PRIO0_RCVD, vr->stat_index);
vrrp_adv_send (vr, 0);
vrrp_vr_timer_set (vr, VRRP_VR_TIMER_ADV);
return;
@@ -167,6 +168,7 @@ vrrp_input_process_backup (vrrp_vr_t *vr, vrrp_input_process_args_t *args)
{
clib_warning ("Master for VR %U is shutting down", format_vrrp_vr_key,
vr);
vrrp_incr_stat_counter (VRRP_STAT_COUNTER_PRIO0_RCVD, vr->stat_index);
vrt->master_down_int = vrt->skew;
vrrp_vr_timer_set (vr, VRRP_VR_TIMER_MASTER_DOWN);
return;
@@ -201,6 +203,8 @@ vrrp_input_process (vrrp_input_process_args_t * args)
return;
}
vrrp_incr_stat_counter (VRRP_STAT_COUNTER_ADV_RCVD, vr->stat_index);
switch (vr->runtime.state)
{
case VRRP_VR_STATE_INIT:
@@ -607,6 +611,7 @@ vrrp_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
if (*ttl0 != 255)
{
error0 = VRRP_ERROR_BAD_TTL;
vrrp_incr_err_counter (VRRP_ERR_COUNTER_TTL);
goto trace;
}
@@ -614,6 +619,7 @@ vrrp_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
if ((vrrp0->vrrp_version_and_type >> 4) != 3)
{
error0 = VRRP_ERROR_NOT_VERSION_3;
vrrp_incr_err_counter (VRRP_ERR_COUNTER_VERSION);
goto trace;
}
@@ -622,6 +628,7 @@ vrrp_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
((u32) vrrp0->n_addrs) * addr_len)
{
error0 = VRRP_ERROR_INCOMPLETE_PKT;
vrrp_incr_err_counter (VRRP_ERR_COUNTER_PKT_LEN);
goto trace;
}
@@ -629,6 +636,7 @@ vrrp_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
if (rx_csum0 != vrrp_adv_csum (ip0, vrrp0, is_ipv6, payload_len0))
{
error0 = VRRP_ERROR_BAD_CHECKSUM;
vrrp_incr_err_counter (VRRP_ERR_COUNTER_CHKSUM);
goto trace;
}
@@ -638,6 +646,7 @@ vrrp_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
vrrp0->vr_id, is_ipv6)))
{
error0 = VRRP_ERROR_UNKNOWN_VR;
vrrp_incr_err_counter (VRRP_ERR_COUNTER_VRID);
goto trace;
}
@@ -646,6 +655,7 @@ vrrp_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
if (vrrp0->n_addrs != vec_len (vr0->config.vr_addrs))
{
error0 = VRRP_ERROR_ADDR_MISMATCH;
vrrp_incr_err_counter (VRRP_ERR_COUNTER_ADDR_LIST);
goto trace;
}
+50 -1
View File
@@ -5,7 +5,7 @@
*
*/
option version = "1.0.1";
option version = "1.1.1";
import "vnet/interface_types.api";
import "vnet/ip/ip_types.api";
@@ -60,6 +60,55 @@ autoreply define vrrp_vr_add_del {
vl_api_address_t addrs[n_addrs];
};
/** @brief Replace an existing VRRP virtual router in-place or create a new one
@param client_index - opaque cookie to identify the sender
@param context - sender context, to match reply w/ request
@param vrrp_index - an existing VRRP entry to replace, or 0xffffffff to crate a new one
@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
*/
define vrrp_vr_update {
u32 client_index;
u32 context;
u32 vrrp_index;
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 Reply to a VRRP add/replace
* @param context - returned sender context, to match reply w/ request
* @param vrrp_index - index of the updated or newly created VRRP instance
* @param retval 0 - no error
*/
define vrrp_vr_update_reply {
u32 context;
i32 retval;
u32 vrrp_index;
};
/**
* @brief Delete an existing VRRP instance
* @param client_index - opaque cookie to identify the sender
* @param context - returned sender context, to match reply w/ request
* @param vrrp_index - index of the VRRP instance to delete
*/
autoreply define vrrp_vr_del {
u32 client_index;
u32 context;
u32 vrrp_index;
};
/** \brief VRRP: dump virtual router data
@param client_index - opaque cookie to identify the sender
@param context - sender context, to match reply w/ request
+260 -14
View File
File diff suppressed because it is too large Load Diff
+42 -1
View File
@@ -108,6 +108,7 @@ typedef struct vrrp_vr
vrrp_vr_config_t config;
vrrp_vr_runtime_t runtime;
vrrp_vr_tracking_t tracking;
u32 stat_index;
} vrrp_vr_t;
/* Timers */
@@ -185,9 +186,46 @@ extern vlib_node_registration_t vrrp_periodic_node;
#define VRRP_EVENT_VR_STOP 2
#define VRRP_EVENT_PERIODIC_ENABLE_DISABLE 3
/* global error counter types */
#define foreach_vrrp_err_counter \
_ (CHKSUM, 0) \
_ (VERSION, 1) \
_ (VRID, 2) \
_ (TTL, 3) \
_ (ADDR_LIST, 4) \
_ (PKT_LEN, 5)
typedef enum vrrp_err_counter_
{
#define _(sym, val) VRRP_ERR_COUNTER_##sym = val,
foreach_vrrp_err_counter
#undef _
} vrrp_err_counter_t;
#define VRRP_ERR_COUNTER_MAX 6
/* per-instance stats */
#define foreach_vrrp_stat_counter \
_ (MASTER_TRANS, 0) \
_ (ADV_SENT, 1) \
_ (ADV_RCVD, 2) \
_ (PRIO0_SENT, 3) \
_ (PRIO0_RCVD, 4)
typedef enum vrrp_stat_counter_
{
#define _(sym, val) VRRP_STAT_COUNTER_##sym = val,
foreach_vrrp_stat_counter
#undef _
} vrrp_stat_counter_t;
#define VRRP_STAT_COUNTER_MAX 5
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_add_del (u8 is_add, vrrp_vr_config_t *conf, index_t *ret_index);
int vrrp_vr_update (index_t *vrrp_index, vrrp_vr_config_t *vr_conf);
int vrrp_vr_del (index_t vrrp_index);
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);
@@ -209,6 +247,9 @@ int vrrp_vr_tracking_ifs_add_del (vrrp_vr_t * vr,
u8 is_add);
void vrrp_vr_event (vrrp_vr_t * vr, vrrp_vr_state_t new_state);
// stats
void vrrp_incr_err_counter (vrrp_err_counter_t err_type);
void vrrp_incr_stat_counter (vrrp_stat_counter_t stat_type, u32 stat_index);
always_inline void
vrrp_vr_skew_compute (vrrp_vr_t * vr)
+104 -1
View File
@@ -24,6 +24,109 @@
#include <vlibapi/api_helper_macros.h>
/* API message handlers */
static void
vl_api_vrrp_vr_update_t_handler (vl_api_vrrp_vr_update_t *mp)
{
vl_api_vrrp_vr_update_reply_t *rmp;
vrrp_vr_config_t vr_conf;
u32 api_flags;
u32 vrrp_index = INDEX_INVALID;
ip46_address_t *addrs = 0;
int rv;
VALIDATE_SW_IF_INDEX (mp);
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;
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
{
vrrp_index = ntohl (mp->vrrp_index);
rv = vrrp_vr_update (&vrrp_index, &vr_conf);
}
vec_free (addrs);
BAD_SW_IF_INDEX_LABEL;
// clang-format off
REPLY_MACRO2 (VL_API_VRRP_VR_UPDATE_REPLY,
({
rmp->vrrp_index = htonl (vrrp_index);
}));
// clang-format on
}
static void
vl_api_vrrp_vr_del_t_handler (vl_api_vrrp_vr_del_t *mp)
{
vl_api_vrrp_vr_del_reply_t *rmp;
int rv;
rv = vrrp_vr_del (ntohl (mp->vrrp_index));
REPLY_MACRO (VL_API_VRRP_VR_DEL_REPLY);
}
static void
vl_api_vrrp_vr_add_del_t_handler (vl_api_vrrp_vr_add_del_t * mp)
{
@@ -103,7 +206,7 @@ vl_api_vrrp_vr_add_del_t_handler (vl_api_vrrp_vr_add_del_t * mp)
rv = VNET_API_ERROR_INVALID_VALUE;
}
else
rv = vrrp_vr_add_del (mp->is_add, &vr_conf);
rv = vrrp_vr_add_del (mp->is_add, &vr_conf, NULL);
vec_free (addrs);
+1 -1
View File
@@ -102,7 +102,7 @@ vrrp_vr_add_del_command_fn (vlib_main_t * vm,
vr_conf.adv_interval = (u16) interval;
vr_conf.vr_addrs = addrs;
rv = vrrp_vr_add_del (is_add, &vr_conf);
rv = vrrp_vr_add_del (is_add, &vr_conf, NULL);
switch (rv)
{
+6
View File
@@ -354,6 +354,12 @@ vrrp_adv_send (vrrp_vr_t * vr, int shutdown)
vlib_put_frame_to_node (vm, node_index, to_frame);
vrrp_incr_stat_counter (VRRP_STAT_COUNTER_ADV_SENT, vr->stat_index);
if (shutdown)
{
vrrp_incr_stat_counter (VRRP_STAT_COUNTER_PRIO0_SENT, vr->stat_index);
}
vec_free (bi);
return 0;
+170
View File
@@ -34,6 +34,176 @@ vrrp_test_main_t vrrp_test_main;
#define __plugin_msg_base vrrp_test_main.msg_id_base
#include <vlibapi/vat_helper_macros.h>
static int
api_vrrp_vr_update (vat_main_t *vam)
{
unformat_input_t *i = vam->input;
u32 sw_if_index = ~0;
u32 vr_id, priority, interval, vrrp_index;
u8 is_ipv6, no_preempt, accept_mode, vr_unicast;
u8 n_addrs4, n_addrs6;
vl_api_vrrp_vr_update_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;
vrrp_index = INDEX_INVALID;
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, "vrrp_index %u", &vrrp_index))
;
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
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_UPDATE, mp, vec_len (ip_addrs) * sizeof (*api_addr));
mp->vrrp_index = htonl (vrrp_index);
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 void
vl_api_vrrp_vr_update_reply_t_handler (vl_api_vrrp_vr_update_reply_t *mp)
{
}
static int
api_vrrp_vr_del (vat_main_t *vam)
{
unformat_input_t *i = vam->input;
vl_api_vrrp_vr_del_t *mp;
u32 vrrp_index = INDEX_INVALID;
int ret;
while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
{
if (unformat (i, "vrrp_index %u", &vrrp_index))
;
else
break;
}
/* Construct the API message */
M (VRRP_VR_DEL, mp);
mp->vrrp_index = htonl (vrrp_index);
/* send it... */
S (mp);
/* Wait for a reply... */
W (ret);
return ret;
}
static int
api_vrrp_vr_add_del (vat_main_t * vam)
{
+120
View File
@@ -37,6 +37,8 @@ VRRP_VR_STATE_BACKUP = 1
VRRP_VR_STATE_MASTER = 2
VRRP_VR_STATE_INTF_DOWN = 3
VRRP_INDEX_INVALID = 0xffffffff
def is_non_arp(p):
""" Want to filter out advertisements, igmp, etc"""
@@ -95,6 +97,7 @@ class VppVRRPVirtualRouter(VppObject):
self._adv_dest_ip = "224.0.0.18"
self._vips = ([intf.local_ip4] if vips is None else vips)
self._tracked_ifs = []
self._vrrp_index = VRRP_INDEX_INVALID
def add_vpp_config(self):
self._test.vapi.vrrp_vr_add_del(is_add=1,
@@ -106,6 +109,20 @@ class VppVRRPVirtualRouter(VppObject):
n_addrs=len(self._vips),
addrs=self._vips)
def update_vpp_config(self):
r = self._test.vapi.vrrp_vr_update(vrrp_index=self._vrrp_index,
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)
self._vrrp_index = r.vrrp_index
def delete_vpp_config(self):
self._test.vapi.vrrp_vr_del(vrrp_index=self._vrrp_index)
def query_vpp_config(self):
vrs = self._test.vapi.vrrp_vr_dump(sw_if_index=self._intf.sw_if_index)
for vr in vrs:
@@ -341,6 +358,56 @@ class TestVRRP4(VppTestCase):
vr.remove_vpp_config()
self._vrs = []
# Same as above but with the update API, and add a change
# of parameters to test that too
def test_vrrp4_master_adv_update(self):
""" IPv4 Master VR adv + Update to Backup """
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.update_vpp_config()
vr.start_stop(is_start=1)
self.logger.info(self.vapi.cli("show vrrp vr"))
# Update VR with lower prio and larger interval
# we need to keep old VR for the adv checks
upd_vr = VppVRRPVirtualRouter(self, self.pg0, 100,
prio=100, intvl=2*intvl,
flags=self._default_flags,
vips=[self.pg0.remote_ip4])
upd_vr._vrrp_index = vr._vrrp_index
upd_vr.update_vpp_config()
start_time = time.time()
self.logger.info(self.vapi.cli("show vrrp vr"))
upd_vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
self._vrs = [upd_vr]
pkts = self.pg0.get_capture(5)
# 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)
# Init -> Backup: An IGMP join should be sent
self.verify_vrrp4_igmp(pkts[4])
# send higher prio advertisements, should not receive any
end_time = start_time + 2 * upd_vr.master_down_seconds()
src_ip = self.pg0.remote_ip4
pkts = [upd_vr.vrrp_adv_packet(prio=110, src_ip=src_ip)]
while time.time() < end_time:
self.send_and_assert_no_replies(self.pg0, pkts, timeout=intvl*0.01)
self.logger.info(self.vapi.cli("show trace"))
upd_vr.start_stop(is_start=0)
self.logger.info(self.vapi.cli("show vrrp vr"))
# 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):
@@ -875,6 +942,59 @@ class TestVRRP6(VppTestCase):
vr.remove_vpp_config()
self._vrs = []
# Same as above but with the update API, and add a change
# of parameters to test that too
def test_vrrp6_master_adv_update(self):
""" IPv6 Master VR adv + Update to Backup """
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.update_vpp_config()
vr.start_stop(is_start=1)
self.logger.info(self.vapi.cli("show vrrp vr"))
# Update VR with lower prio and larger interval
# we need to keep old VR for the adv checks
upd_vr = VppVRRPVirtualRouter(self, self.pg0, 100,
prio=100, intvl=2*intvl,
flags=self._default_flags,
vips=[self.pg0.remote_ip6])
upd_vr._vrrp_index = vr._vrrp_index
upd_vr.update_vpp_config()
start_time = time.time()
self.logger.info(self.vapi.cli("show vrrp vr"))
upd_vr.assert_state_equals(VRRP_VR_STATE_BACKUP)
self._vrs = [upd_vr]
pkts = self.pg0.get_capture(5, 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)
# Init -> Backup: A multicast listener report should be sent
# not actually verified in the test below, where I took this from
# send higher prio advertisements, should not see VPP send any
src_ip = self.pg0.remote_ip6_ll
pkts = [upd_vr.vrrp_adv_packet(prio=110, src_ip=src_ip)]
self.logger.info(self.vapi.cli("show vlib graph"))
end_time = start_time + 2 * upd_vr.master_down_seconds()
while time.time() < end_time:
self.send_and_assert_no_replies(
self.pg0, pkts, timeout=0.01*upd_vr._intvl)
self.logger.info(self.vapi.cli("show trace"))
vr.start_stop(is_start=0)
self.logger.info(self.vapi.cli("show vrrp vr"))
# 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):