mirror_frr/pimd/pim_bsm.c
Jafar Al-Gharaibeh cda66453c1 pimd: explicitly ensure the RP src is BSR
With the recent suppoort of multiple sources of RPs, we can assume non static RPs
are BSR RPs. Just make the check explicit for BSR.

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
2025-01-13 10:18:24 -06:00

2204 lines
57 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* pim_bsm.c: PIM BSM handling routines
*
* Copyright (C) 2018-19 Vmware, Inc.
* Saravanan K
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <lib/network.h>
#include <lib/iana_afi.h>
#include <lib/sockunion.h>
#include <lib/sockopt.h>
#include "if.h"
#include "pimd.h"
#include "pim_iface.h"
#include "pim_instance.h"
#include "pim_neighbor.h"
#include "pim_rpf.h"
#include "pim_hello.h"
#include "pim_pim.h"
#include "pim_nht.h"
#include "pim_bsm.h"
#include "pim_time.h"
#include "pim_zebra.h"
#include "pim_util.h"
#include "pim_sock.h"
/* Functions forward declaration */
static void pim_bs_timer_start(struct bsm_scope *scope, int bs_timeout);
static void pim_g2rp_timer_start(struct bsm_rpinfo *bsrp, int hold_time);
static inline void pim_g2rp_timer_restart(struct bsm_rpinfo *bsrp,
int hold_time);
static void pim_bsm_accept_any(struct bsm_scope *scope);
static void pim_cand_bsr_trigger(struct bsm_scope *scope, bool verbose);
static void pim_cand_bsr_pending(struct bsm_scope *scope);
/* Memory Types */
DEFINE_MTYPE_STATIC(PIMD, PIM_BSGRP_NODE, "PIM BSR advertised grp info");
DEFINE_MTYPE_STATIC(PIMD, PIM_BSRP_INFO, "PIM BSR advertised RP info");
DEFINE_MTYPE(PIMD, PIM_BSM_FRAG, "PIM BSM fragment");
DEFINE_MTYPE_STATIC(PIMD, PIM_BSM_PKT_VAR_MEM, "PIM BSM Packet");
DEFINE_MTYPE_STATIC(PIMD, PIM_CAND_RP_GRP, "PIM Candidate RP group");
static int cand_rp_group_cmp(const struct cand_rp_group *a,
const struct cand_rp_group *b)
{
return prefix_cmp(&a->p, &b->p);
}
DECLARE_RBTREE_UNIQ(cand_rp_groups, struct cand_rp_group, item,
cand_rp_group_cmp);
/* All bsm packets forwarded shall be fit within ip mtu less iphdr(max) */
#define MAX_IP_HDR_LEN 24
/* pim_bsm_write_config - Write the interface pim bsm configuration.*/
void pim_bsm_write_config(struct vty *vty, struct interface *ifp)
{
struct pim_interface *pim_ifp = ifp->info;
if (pim_ifp) {
if (!pim_ifp->bsm_enable)
vty_out(vty, " no " PIM_AF_NAME " pim bsm\n");
if (!pim_ifp->ucast_bsm_accept)
vty_out(vty, " no " PIM_AF_NAME " pim unicast-bsm\n");
}
}
static void pim_bsm_rpinfo_free(struct bsm_rpinfo *bsrp_info)
{
EVENT_OFF(bsrp_info->g2rp_timer);
XFREE(MTYPE_PIM_BSRP_INFO, bsrp_info);
}
static void pim_bsm_rpinfos_free(struct bsm_rpinfos_head *head)
{
struct bsm_rpinfo *bsrp_info;
while ((bsrp_info = bsm_rpinfos_pop(head)))
pim_bsm_rpinfo_free(bsrp_info);
}
static void pim_free_bsgrp_data(struct bsgrp_node *bsgrp_node)
{
pim_bsm_rpinfos_free(bsgrp_node->bsrp_list);
pim_bsm_rpinfos_free(bsgrp_node->partial_bsrp_list);
XFREE(MTYPE_PIM_BSGRP_NODE, bsgrp_node);
}
static void pim_free_bsgrp_node(struct route_table *rt, struct prefix *grp)
{
struct route_node *rn;
rn = route_node_lookup(rt, grp);
if (rn) {
rn->info = NULL;
route_unlock_node(rn);
route_unlock_node(rn);
}
}
static void pim_bsm_frag_free(struct bsm_frag *bsfrag)
{
XFREE(MTYPE_PIM_BSM_FRAG, bsfrag);
}
void pim_bsm_frags_free(struct bsm_scope *scope)
{
struct bsm_frag *bsfrag;
while ((bsfrag = bsm_frags_pop(scope->bsm_frags)))
pim_bsm_frag_free(bsfrag);
}
int pim_bsm_rpinfo_cmp(const struct bsm_rpinfo *node1,
const struct bsm_rpinfo *node2)
{
/* RP election Algo :
* Step-1 : Loweset Rp priority will have higher precedance.
* Step-2 : If priority same then higher hash val will have
* higher precedance.
* Step-3 : If Hash val is same then highest rp address will
* become elected RP.
*/
if (node1->rp_prio < node2->rp_prio)
return -1;
if (node1->rp_prio > node2->rp_prio)
return 1;
if (node1->hash < node2->hash)
return 1;
if (node1->hash > node2->hash)
return -1;
return pim_addr_cmp(node2->rp_address, node1->rp_address);
}
static struct bsgrp_node *pim_bsm_new_bsgrp_node(struct route_table *rt,
struct prefix *grp)
{
struct route_node *rn;
struct bsgrp_node *bsgrp;
rn = route_node_get(rt, grp);
if (!rn) {
zlog_warn("%s: route node creation failed", __func__);
return NULL;
}
bsgrp = XCALLOC(MTYPE_PIM_BSGRP_NODE, sizeof(struct bsgrp_node));
rn->info = bsgrp;
bsm_rpinfos_init(bsgrp->bsrp_list);
bsm_rpinfos_init(bsgrp->partial_bsrp_list);
prefix_copy(&bsgrp->group, grp);
return bsgrp;
}
/* BS timer for NO_INFO, ACCEPT_ANY & ACCEPT_PREFERRED.
* Candidate BSR handling is separate further below
*/
static void pim_on_bs_timer(struct event *t)
{
struct bsm_scope *scope;
scope = EVENT_ARG(t);
EVENT_OFF(scope->bs_timer);
if (PIM_DEBUG_BSM)
zlog_debug("%s: Bootstrap Timer expired for scope: %d",
__func__, scope->sz_id);
assertf(scope->state <= ACCEPT_PREFERRED, "state=%d", scope->state);
pim_nht_bsr_del(scope->pim, scope->current_bsr);
pim_bsm_accept_any(scope);
}
static void pim_bsm_accept_any(struct bsm_scope *scope)
{
struct route_node *rn;
struct bsgrp_node *bsgrp_node;
struct bsm_rpinfo *bsrp;
EVENT_OFF(scope->t_ebsr_regen_bsm);
/* Reset scope zone data */
scope->state = ACCEPT_ANY;
scope->current_bsr = PIMADDR_ANY;
scope->current_bsr_prio = 0;
scope->current_bsr_first_ts = 0;
scope->current_bsr_last_ts = 0;
scope->bsm_frag_tag = 0;
pim_bsm_frags_free(scope);
for (rn = route_top(scope->bsrp_table); rn; rn = route_next(rn)) {
bsgrp_node = (struct bsgrp_node *)rn->info;
if (!bsgrp_node) {
if (PIM_DEBUG_BSM)
zlog_debug("%s: bsgrp_node is null", __func__);
continue;
}
/* Give grace time for rp to continue for another hold time */
bsrp = bsm_rpinfos_first(bsgrp_node->bsrp_list);
if (bsrp)
pim_g2rp_timer_restart(bsrp, bsrp->rp_holdtime);
/* clear pending list */
pim_bsm_rpinfos_free(bsgrp_node->partial_bsrp_list);
bsgrp_node->pend_rp_cnt = 0;
}
/* we're leaving ACCEPT_PREFERRED, which doubles as C-BSR if we're
* configured to be a Candidate BSR. See if we're P-BSR now.
*/
pim_cand_bsr_trigger(scope, false);
}
static void pim_bs_timer_stop(struct bsm_scope *scope)
{
if (PIM_DEBUG_BSM)
zlog_debug("%s : BS timer being stopped of sz: %d", __func__,
scope->sz_id);
EVENT_OFF(scope->bs_timer);
}
static void pim_bs_timer_start(struct bsm_scope *scope, int bs_timeout)
{
if (!scope) {
if (PIM_DEBUG_BSM)
zlog_debug("%s : Invalid scope(NULL).", __func__);
return;
}
EVENT_OFF(scope->bs_timer);
if (PIM_DEBUG_BSM)
zlog_debug(
"%s : starting bs timer for scope %d with timeout %d secs",
__func__, scope->sz_id, bs_timeout);
event_add_timer(router->master, pim_on_bs_timer, scope, bs_timeout,
&scope->bs_timer);
}
static inline void pim_bs_timer_restart(struct bsm_scope *scope, int bs_timeout)
{
pim_bs_timer_start(scope, bs_timeout);
}
static void bsm_unicast_sock_read(struct event *t)
{
struct bsm_scope *scope = EVENT_ARG(t);
pim_sock_read_helper(scope->unicast_sock, scope->pim, false);
event_add_read(router->master, bsm_unicast_sock_read, scope,
scope->unicast_sock, &scope->unicast_read);
}
void pim_bsm_proc_init(struct pim_instance *pim)
{
struct bsm_scope *scope = &pim->global_scope;
memset(scope, 0, sizeof(*scope));
scope->sz_id = PIM_GBL_SZ_ID;
scope->bsrp_table = route_table_init();
scope->accept_nofwd_bsm = true;
scope->state = NO_INFO;
scope->pim = pim;
bsm_frags_init(scope->bsm_frags);
pim_bs_timer_start(scope, PIM_BS_TIME);
scope->cand_rp_interval = PIM_CRP_ADV_INTERVAL;
cand_rp_groups_init(scope->cand_rp_groups);
scope->unicast_sock = pim_socket_raw(IPPROTO_PIM);
set_nonblocking(scope->unicast_sock);
sockopt_reuseaddr(scope->unicast_sock);
if (setsockopt_ifindex(PIM_AF, scope->unicast_sock, 1) == -1)
zlog_warn("%s: Without IP_PKTINFO, src interface can't be determined",
__func__);
pim_socket_ip_hdr(scope->unicast_sock);
frr_with_privs (&pimd_privs) {
vrf_bind(pim->vrf->vrf_id, scope->unicast_sock, NULL);
}
event_add_read(router->master, bsm_unicast_sock_read, scope,
scope->unicast_sock, &scope->unicast_read);
}
void pim_bsm_proc_free(struct pim_instance *pim)
{
struct bsm_scope *scope = &pim->global_scope;
struct route_node *rn;
struct bsgrp_node *bsgrp;
struct cand_rp_group *crpgrp;
EVENT_OFF(scope->unicast_read);
close(scope->unicast_sock);
pim_bs_timer_stop(scope);
pim_bsm_frags_free(scope);
for (rn = route_top(scope->bsrp_table); rn; rn = route_next(rn)) {
bsgrp = rn->info;
if (!bsgrp)
continue;
pim_free_bsgrp_data(bsgrp);
}
while ((crpgrp = cand_rp_groups_pop(scope->cand_rp_groups)))
XFREE(MTYPE_PIM_CAND_RP_GRP, crpgrp);
cand_rp_groups_fini(scope->cand_rp_groups);
route_table_finish(scope->bsrp_table);
}
static bool is_hold_time_elapsed(void *data)
{
struct bsm_rpinfo *bsrp;
bsrp = data;
if (bsrp->elapse_time < bsrp->rp_holdtime)
return false;
else
return true;
}
static void pim_on_g2rp_timer(struct event *t)
{
struct bsm_rpinfo *bsrp;
struct bsm_rpinfo *bsrp_node;
struct bsgrp_node *bsgrp_node;
struct pim_instance *pim;
struct rp_info *rp_info;
struct route_node *rn;
uint16_t elapse;
pim_addr bsrp_addr;
bsrp = EVENT_ARG(t);
EVENT_OFF(bsrp->g2rp_timer);
bsgrp_node = bsrp->bsgrp_node;
pim = bsgrp_node->scope->pim;
bsrp_addr = bsrp->rp_address;
/*
* Update elapse for all bsrp nodes except on the BSR itself.
* The timer is meant to remove any bsr RPs learned from the BSR that
* we don't hear from anymore. on the BSR itself, no need to do this.
*/
if (pim->global_scope.state != BSR_ELECTED) {
/* elapse time is the hold time of expired node */
elapse = bsrp->rp_holdtime;
frr_each_safe (bsm_rpinfos, bsgrp_node->bsrp_list, bsrp_node) {
bsrp_node->elapse_time += elapse;
if (is_hold_time_elapsed(bsrp_node)) {
bsm_rpinfos_del(bsgrp_node->bsrp_list, bsrp_node);
pim_bsm_rpinfo_free(bsrp_node);
}
}
}
/* Get the next elected rp node */
bsrp = bsm_rpinfos_first(bsgrp_node->bsrp_list);
rn = route_node_lookup(pim->rp_table, &bsgrp_node->group);
if (!rn) {
zlog_warn("%s: Route node doesn't exist", __func__);
return;
}
rp_info = (struct rp_info *)rn->info;
if (!rp_info) {
route_unlock_node(rn);
return;
}
if (rp_info->rp_src == RP_SRC_BSR) {
/* If new rp available, change it else delete the existing */
if (bsrp) {
pim_g2rp_timer_start(
bsrp, (bsrp->rp_holdtime - bsrp->elapse_time));
pim_rp_change(pim, bsrp->rp_address, bsgrp_node->group,
RP_SRC_BSR);
} else {
pim_rp_del(pim, bsrp_addr, bsgrp_node->group, NULL,
RP_SRC_BSR);
}
}
if (!bsm_rpinfos_count(bsgrp_node->bsrp_list)
&& !bsm_rpinfos_count(bsgrp_node->partial_bsrp_list)) {
pim_free_bsgrp_node(pim->global_scope.bsrp_table,
&bsgrp_node->group);
pim_free_bsgrp_data(bsgrp_node);
}
}
static void pim_g2rp_timer_start(struct bsm_rpinfo *bsrp, int hold_time)
{
if (!bsrp) {
if (PIM_DEBUG_BSM)
zlog_debug("%s : Invalid brsp(NULL).", __func__);
return;
}
EVENT_OFF(bsrp->g2rp_timer);
if (PIM_DEBUG_BSM)
zlog_debug(
"%s : starting g2rp timer for grp: %pFX - rp: %pPAs with timeout %d secs(Actual Hold time : %d secs)",
__func__, &bsrp->bsgrp_node->group, &bsrp->rp_address,
hold_time, bsrp->rp_holdtime);
event_add_timer(router->master, pim_on_g2rp_timer, bsrp, hold_time,
&bsrp->g2rp_timer);
}
static inline void pim_g2rp_timer_restart(struct bsm_rpinfo *bsrp,
int hold_time)
{
pim_g2rp_timer_start(bsrp, hold_time);
}
static void pim_g2rp_timer_stop(struct bsm_rpinfo *bsrp)
{
if (!bsrp)
return;
if (PIM_DEBUG_BSM)
zlog_debug("%s : stopping g2rp timer for grp: %pFX - rp: %pPAs",
__func__, &bsrp->bsgrp_node->group,
&bsrp->rp_address);
EVENT_OFF(bsrp->g2rp_timer);
}
static bool is_hold_time_zero(void *data)
{
struct bsm_rpinfo *bsrp;
bsrp = data;
if (bsrp->rp_holdtime)
return false;
else
return true;
}
static void pim_instate_pend_list(struct bsgrp_node *bsgrp_node)
{
struct bsm_rpinfo *active;
struct bsm_rpinfo *pend;
struct rp_info *rp_info;
struct route_node *rn;
struct pim_instance *pim;
struct rp_info *rp_all;
struct prefix group_all;
bool had_rp_node = true;
pim = bsgrp_node->scope->pim;
active = bsm_rpinfos_first(bsgrp_node->bsrp_list);
/* Remove nodes with hold time 0 & check if list still has a head */
frr_each_safe (bsm_rpinfos, bsgrp_node->partial_bsrp_list, pend) {
if (is_hold_time_zero(pend)) {
bsm_rpinfos_del(bsgrp_node->partial_bsrp_list, pend);
pim_bsm_rpinfo_free(pend);
}
}
pend = bsm_rpinfos_first(bsgrp_node->partial_bsrp_list);
pim_get_all_mcast_group(&group_all);
rp_all = pim_rp_find_match_group(pim, &group_all);
rn = route_node_lookup(pim->rp_table, &bsgrp_node->group);
if (pend)
pim_g2rp_timer_start(pend, pend->rp_holdtime);
/* if rp node doesn't exist or exist but not configured(rp_all),
* install the rp from head(if exists) of partial list. List is
* is sorted such that head is the elected RP for the group.
*/
if (!rn || (prefix_same(&rp_all->group, &bsgrp_node->group) &&
pim_rpf_addr_is_inaddr_any(&rp_all->rp))) {
if (PIM_DEBUG_BSM)
zlog_debug("%s: Route node doesn't exist", __func__);
if (pend)
pim_rp_new(pim, pend->rp_address, bsgrp_node->group,
NULL, RP_SRC_BSR);
had_rp_node = false;
} else {
rp_info = (struct rp_info *)rn->info;
if (!rp_info) {
route_unlock_node(rn);
if (pend)
pim_rp_new(pim, pend->rp_address,
bsgrp_node->group, NULL, RP_SRC_BSR);
had_rp_node = false;
}
}
/* We didn't have rp node and pending list is empty(unlikely), cleanup*/
if ((!had_rp_node) && (!pend)) {
pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table,
&bsgrp_node->group);
pim_free_bsgrp_data(bsgrp_node);
return;
}
if ((had_rp_node) && (rp_info->rp_src != RP_SRC_STATIC)) {
/* This means we searched and got rp node, needs unlock */
route_unlock_node(rn);
if (active && pend) {
if (pim_addr_cmp(active->rp_address, pend->rp_address))
pim_rp_change(pim, pend->rp_address,
bsgrp_node->group, RP_SRC_BSR);
}
/* Possible when the first BSM has group with 0 rp count */
if ((!active) && (!pend)) {
if (PIM_DEBUG_BSM) {
zlog_debug(
"%s: Both bsrp and partial list are empty",
__func__);
}
pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table,
&bsgrp_node->group);
pim_free_bsgrp_data(bsgrp_node);
return;
}
/* Possible when a group with 0 rp count received in BSM */
if ((active) && (!pend)) {
pim_rp_del(pim, active->rp_address, bsgrp_node->group,
NULL, RP_SRC_BSR);
pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table,
&bsgrp_node->group);
if (PIM_DEBUG_BSM) {
zlog_debug("%s:Pend List is null,del grp node",
__func__);
}
pim_free_bsgrp_data(bsgrp_node);
return;
}
}
if ((had_rp_node) && (rp_info->rp_src == RP_SRC_STATIC)) {
/* We need to unlock rn this case */
route_unlock_node(rn);
/* there is a chance that static rp exist and bsrp cleaned
* so clean bsgrp node if pending list empty
*/
if (!pend) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s: Partial list is empty, static rp exists",
__func__);
pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table,
&bsgrp_node->group);
pim_free_bsgrp_data(bsgrp_node);
return;
}
}
/* swap the list & delete all nodes in partial list (old bsrp_list)
* before swap
* active is head of bsrp list
* pend is head of partial list
* After swap
* active is head of partial list
* pend is head of bsrp list
* So check appriate head after swap and clean the new partial list
*/
bsm_rpinfos_swap_all(bsgrp_node->bsrp_list,
bsgrp_node->partial_bsrp_list);
if (active)
pim_g2rp_timer_stop(active);
pim_bsm_rpinfos_free(bsgrp_node->partial_bsrp_list);
}
static bool is_preferred_bsr(struct pim_instance *pim, pim_addr bsr,
uint32_t bsr_prio)
{
if (bsr_prio > pim->global_scope.current_bsr_prio)
return true;
else if (bsr_prio == pim->global_scope.current_bsr_prio) {
if (pim_addr_cmp(bsr, pim->global_scope.current_bsr) >= 0)
return true;
else
return false;
} else if (!pim_addr_cmp(bsr, pim->global_scope.current_bsr)) {
/* BSR config changed, lower prio now. local BSR check
* is handled separately in pim_bsm_update()
*/
return true;
} else
return false;
}
static void pim_bsm_update(struct pim_instance *pim, pim_addr bsr,
uint32_t bsr_prio)
{
pim->global_scope.current_bsr_prio = bsr_prio;
pim->global_scope.current_bsr_last_ts = pim_time_monotonic_sec();
if (pim->global_scope.bsr_addrsel.run &&
pim->global_scope.cand_bsr_prio > bsr_prio &&
pim->global_scope.state < BSR_PENDING) {
/* current BSR is now less preferred than ourselves */
pim_cand_bsr_pending(&pim->global_scope);
return;
}
if (!pim_addr_cmp(bsr, pim->global_scope.current_bsr))
return;
switch (pim->global_scope.state) {
case BSR_PENDING:
if (PIM_DEBUG_BSM)
zlog_debug("Candidate BSR dropping out of BSR election, better BSR (%u, %pPA)",
bsr_prio, &bsr);
break;
case BSR_ELECTED:
if (PIM_DEBUG_BSM)
zlog_debug("Lost BSR status, better BSR (%u, %pPA)",
bsr_prio, &bsr);
break;
case NO_INFO:
case ACCEPT_ANY:
case ACCEPT_PREFERRED:
break;
}
EVENT_OFF(pim->global_scope.t_ebsr_regen_bsm);
if (pim->global_scope.state == BSR_ELECTED)
pim_crp_db_clear(&pim->global_scope);
else
pim_nht_bsr_del(pim, pim->global_scope.current_bsr);
pim_nht_bsr_add(pim, bsr);
pim->global_scope.current_bsr = bsr;
pim->global_scope.current_bsr_first_ts = pim_time_monotonic_sec();
pim->global_scope.state = ACCEPT_PREFERRED;
pim_cand_rp_trigger(&pim->global_scope);
}
void pim_bsm_clear(struct pim_instance *pim)
{
struct route_node *rn;
struct route_node *rpnode;
struct bsgrp_node *bsgrp;
pim_addr nht_p;
struct prefix g_all;
struct rp_info *rp_all;
struct pim_upstream *up;
struct rp_info *rp_info;
bool upstream_updated = false;
EVENT_OFF(pim->global_scope.t_ebsr_regen_bsm);
if (pim->global_scope.state == BSR_ELECTED)
pim_crp_db_clear(&pim->global_scope);
else
pim_nht_bsr_del(pim, pim->global_scope.current_bsr);
/* Reset scope zone data */
pim->global_scope.accept_nofwd_bsm = false;
pim->global_scope.state = ACCEPT_ANY;
pim->global_scope.current_bsr = PIMADDR_ANY;
pim->global_scope.current_bsr_prio = 0;
pim->global_scope.current_bsr_first_ts = 0;
pim->global_scope.current_bsr_last_ts = 0;
pim->global_scope.bsm_frag_tag = 0;
pim_bsm_frags_free(&pim->global_scope);
pim_bs_timer_stop(&pim->global_scope);
for (rn = route_top(pim->global_scope.bsrp_table); rn;
rn = route_next(rn)) {
bsgrp = rn->info;
if (!bsgrp)
continue;
rpnode = route_node_lookup(pim->rp_table, &bsgrp->group);
if (!rpnode) {
pim_free_bsgrp_node(bsgrp->scope->bsrp_table,
&bsgrp->group);
pim_free_bsgrp_data(bsgrp);
continue;
}
rp_info = (struct rp_info *)rpnode->info;
if ((!rp_info) || (rp_info->rp_src != RP_SRC_BSR)) {
pim_free_bsgrp_node(bsgrp->scope->bsrp_table,
&bsgrp->group);
pim_free_bsgrp_data(bsgrp);
continue;
}
/* Deregister addr with Zebra NHT */
nht_p = rp_info->rp.rpf_addr;
if (PIM_DEBUG_PIM_NHT_RP) {
zlog_debug("%s: Deregister RP addr %pPA with Zebra ",
__func__, &nht_p);
}
pim_nht_delete_tracked(pim, nht_p, NULL, rp_info);
pim_get_all_mcast_group(&g_all);
rp_all = pim_rp_find_match_group(pim, &g_all);
if (rp_all == rp_info) {
rp_all->rp.rpf_addr = PIMADDR_ANY;
rp_all->i_am_rp = 0;
} else {
/* Delete the rp_info from rp-list */
listnode_delete(pim->rp_list, rp_info);
/* Delete the rp node from rp_table */
rpnode->info = NULL;
route_unlock_node(rpnode);
route_unlock_node(rpnode);
XFREE(MTYPE_PIM_RP, rp_info);
}
pim_free_bsgrp_node(bsgrp->scope->bsrp_table, &bsgrp->group);
pim_free_bsgrp_data(bsgrp);
}
pim_rp_refresh_group_to_rp_mapping(pim);
frr_each (rb_pim_upstream, &pim->upstream_head, up) {
/* Find the upstream (*, G) whose upstream address is same as
* the RP
*/
if (!pim_addr_is_any(up->sg.src))
continue;
struct prefix grp;
struct rp_info *trp_info;
pim_addr_to_prefix(&grp, up->sg.grp);
trp_info = pim_rp_find_match_group(pim, &grp);
/* RP not found for the group grp */
if (pim_rpf_addr_is_inaddr_any(&trp_info->rp)) {
pim_upstream_rpf_clear(pim, up);
pim_rp_set_upstream_addr(pim, &up->upstream_addr,
up->sg.src, up->sg.grp);
} else {
/* RP found for the group grp */
pim_upstream_update(pim, up);
upstream_updated = true;
}
}
if (upstream_updated)
pim_zebra_update_all_interfaces(pim);
}
static bool pim_bsm_send_intf(uint8_t *buf, int len, struct interface *ifp,
pim_addr dst_addr)
{
struct pim_interface *pim_ifp;
pim_ifp = ifp->info;
if (!pim_ifp) {
if (PIM_DEBUG_BSM)
zlog_debug("%s: Pim interface not available for %s",
__func__, ifp->name);
return false;
}
if (pim_ifp->pim_sock_fd == -1) {
if (PIM_DEBUG_BSM)
zlog_debug("%s: Pim sock not available for %s",
__func__, ifp->name);
return false;
}
if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address,
dst_addr, buf, len, ifp)) {
zlog_warn("%s: Could not send BSM message on interface: %s",
__func__, ifp->name);
return false;
}
if (!pim_ifp->pim_passive_enable)
pim_ifp->pim_ifstat_bsm_tx++;
pim_ifp->pim->bsm_sent++;
return true;
}
static bool pim_bsm_frag_send(uint8_t *buf, uint32_t len, struct interface *ifp,
uint32_t pim_mtu, pim_addr dst_addr, bool no_fwd)
{
struct pim_interface *pim_ifp = ifp->info;
struct bsmmsg_grpinfo *grpinfo, *curgrp;
uint8_t *firstgrp_ptr;
uint8_t *pkt;
uint8_t *pak_start;
uint32_t parsed_len = 0;
uint32_t this_pkt_rem;
uint32_t copy_byte_count;
uint32_t this_pkt_len;
uint8_t total_rp_cnt;
uint8_t this_rp_cnt;
uint8_t frag_rp_cnt;
uint8_t rp_fit_cnt;
bool pak_pending = false;
/* MTU passed here is PIM MTU (IP MTU less IP Hdr) */
if (pim_mtu < (PIM_MIN_BSM_LEN)) {
zlog_warn(
"%s: mtu(pim mtu: %d) size less than minimum bootstrap len",
__func__, pim_mtu);
if (PIM_DEBUG_BSM)
zlog_debug(
"%s: mtu (pim mtu:%d) less than minimum bootstrap len",
__func__, pim_mtu);
return false;
}
pak_start = XCALLOC(MTYPE_PIM_BSM_PKT_VAR_MEM, pim_mtu);
pkt = pak_start;
/* Fill PIM header later before sending packet to calc checksum */
pkt += PIM_MSG_HEADER_LEN;
buf += PIM_MSG_HEADER_LEN;
/* copy bsm header to new packet at offset of pim hdr */
memcpy(pkt, buf, PIM_BSM_HDR_LEN);
pkt += PIM_BSM_HDR_LEN;
buf += PIM_BSM_HDR_LEN;
parsed_len += (PIM_MSG_HEADER_LEN + PIM_BSM_HDR_LEN);
/* Store the position of first grp ptr, which can be reused for
* next packet to start filling group. old bsm header and pim hdr
* remains. So need not be filled again for next packet onwards.
*/
firstgrp_ptr = pkt;
/* we received mtu excluding IP hdr len as param
* now this_pkt_rem is mtu excluding
* PIM_BSM_HDR_LEN + PIM_MSG_HEADER_LEN
*/
this_pkt_rem = pim_mtu - (PIM_BSM_HDR_LEN + PIM_MSG_HEADER_LEN);
/* For each group till the packet length parsed */
while (parsed_len < len) {
/* pkt ---> fragment's current pointer
* buf ---> input buffer's current pointer
* mtu ---> size of the pim packet - PIM header
* curgrp ---> current group on the fragment
* grpinfo ---> current group on the input buffer
* this_pkt_rem ---> bytes remaing on the current fragment
* rp_fit_cnt ---> num of rp for current grp that
* fits this frag
* total_rp_cnt ---> total rp present for the group in the buf
* frag_rp_cnt ---> no of rp for the group to be fit in
* the frag
* this_rp_cnt ---> how many rp have we parsed
*/
grpinfo = (struct bsmmsg_grpinfo *)buf;
memcpy(pkt, buf, PIM_BSM_GRP_LEN);
curgrp = (struct bsmmsg_grpinfo *)pkt;
parsed_len += PIM_BSM_GRP_LEN;
pkt += PIM_BSM_GRP_LEN;
buf += PIM_BSM_GRP_LEN;
this_pkt_rem -= PIM_BSM_GRP_LEN;
/* initialize rp count and total_rp_cnt before the rp loop */
this_rp_cnt = 0;
total_rp_cnt = grpinfo->frag_rp_count;
/* Loop till all RPs for the group parsed */
while (this_rp_cnt < total_rp_cnt) {
/* All RP from a group processed here.
* group is pointed by grpinfo.
* At this point make sure buf pointing to a RP
* within a group
*/
rp_fit_cnt = this_pkt_rem / PIM_BSM_RP_LEN;
/* calculate how many rp am i going to copy in
* this frag
*/
if (rp_fit_cnt > (total_rp_cnt - this_rp_cnt))
frag_rp_cnt = total_rp_cnt - this_rp_cnt;
else
frag_rp_cnt = rp_fit_cnt;
/* populate the frag rp count for the current grp */
curgrp->frag_rp_count = frag_rp_cnt;
copy_byte_count = frag_rp_cnt * PIM_BSM_RP_LEN;
/* copy all the rp that we are fitting in this
* frag for the grp
*/
memcpy(pkt, buf, copy_byte_count);
this_rp_cnt += frag_rp_cnt;
buf += copy_byte_count;
pkt += copy_byte_count;
parsed_len += copy_byte_count;
this_pkt_rem -= copy_byte_count;
/* Either we couldn't fit all rp for the group or the
* mtu reached
*/
if ((this_rp_cnt < total_rp_cnt)
|| (this_pkt_rem
< (PIM_BSM_GRP_LEN + PIM_BSM_RP_LEN))) {
/* No space to fit in more rp, send this pkt */
this_pkt_len = pim_mtu - this_pkt_rem;
pim_msg_build_header(
pim_ifp->primary_address, dst_addr,
pak_start, this_pkt_len,
PIM_MSG_TYPE_BOOTSTRAP, no_fwd);
pim_bsm_send_intf(pak_start, this_pkt_len, ifp,
dst_addr);
/* Construct next fragment. Reuse old packet */
pkt = firstgrp_ptr;
this_pkt_rem = pim_mtu - (PIM_BSM_HDR_LEN
+ PIM_MSG_HEADER_LEN);
/* If pkt can't accommodate next group + at
* least one rp, we must break out of this inner
* loop and process next RP
*/
if (total_rp_cnt == this_rp_cnt)
break;
/* If some more RPs for the same group pending,
* fill grp hdr
*/
memcpy(pkt, (uint8_t *)grpinfo,
PIM_BSM_GRP_LEN);
curgrp = (struct bsmmsg_grpinfo *)pkt;
pkt += PIM_BSM_GRP_LEN;
this_pkt_rem -= PIM_BSM_GRP_LEN;
pak_pending = false;
} else {
/* We filled something but not yet sent out */
pak_pending = true;
}
} /* while RP count */
} /*while parsed len */
/* Send if we have any unsent packet */
if (pak_pending) {
this_pkt_len = pim_mtu - this_pkt_rem;
pim_msg_build_header(pim_ifp->primary_address, dst_addr,
pak_start, this_pkt_len,
PIM_MSG_TYPE_BOOTSTRAP, no_fwd);
pim_bsm_send_intf(pak_start, (pim_mtu - this_pkt_rem), ifp,
dst_addr);
}
XFREE(MTYPE_PIM_BSM_PKT_VAR_MEM, pak_start);
return true;
}
static void pim_bsm_fwd_whole_sz(struct pim_instance *pim, uint8_t *buf,
uint32_t len, int sz)
{
struct interface *ifp;
struct pim_interface *pim_ifp;
pim_addr dst_addr;
uint32_t pim_mtu;
bool no_fwd = false;
bool ret = false;
/* For now only global scope zone is supported, so send on all
* pim interfaces in the vrf
*/
dst_addr = qpim_all_pim_routers_addr;
FOR_ALL_INTERFACES (pim->vrf, ifp) {
pim_ifp = ifp->info;
if ((!pim_ifp) || (!pim_ifp->bsm_enable))
continue;
/*
* RFC 5059 Sec 3.4:
* When a Bootstrap message is forwarded, it is forwarded out
* of every multicast-capable interface that has PIM neighbors.
*
* So skipping pim interfaces with no neighbors.
*/
if (listcount(pim_ifp->pim_neighbor_list) == 0)
continue;
pim_hello_require(ifp);
pim_mtu = ifp->mtu - MAX_IP_HDR_LEN;
if (pim_mtu < len) {
ret = pim_bsm_frag_send(buf, len, ifp, pim_mtu,
dst_addr, no_fwd);
if (PIM_DEBUG_BSM)
zlog_debug("%s: pim_bsm_frag_send returned %s",
__func__, ret ? "TRUE" : "FALSE");
} else {
pim_msg_build_header(pim_ifp->primary_address, dst_addr,
buf, len, PIM_MSG_TYPE_BOOTSTRAP,
no_fwd);
if (!pim_bsm_send_intf(buf, len, ifp, dst_addr)) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s: pim_bsm_send_intf returned false",
__func__);
}
}
}
}
bool pim_bsm_new_nbr_fwd(struct pim_neighbor *neigh, struct interface *ifp)
{
pim_addr dst_addr;
struct pim_interface *pim_ifp;
struct bsm_scope *scope;
struct bsm_frag *bsfrag;
uint32_t pim_mtu;
bool no_fwd = true;
bool ret = false;
if (PIM_DEBUG_BSM)
zlog_debug("%s: New neighbor %pPA seen on %s", __func__,
&neigh->source_addr, ifp->name);
pim_ifp = ifp->info;
/* DR only forwards BSM packet */
if (!pim_addr_cmp(pim_ifp->pim_dr_addr, pim_ifp->primary_address)) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s: It is not DR, so don't forward BSM packet",
__func__);
}
if (!pim_ifp->bsm_enable) {
if (PIM_DEBUG_BSM)
zlog_debug("%s: BSM proc not enabled on %s", __func__,
ifp->name);
return ret;
}
scope = &pim_ifp->pim->global_scope;
if (!bsm_frags_count(scope->bsm_frags)) {
if (PIM_DEBUG_BSM)
zlog_debug("%s: BSM list for the scope is empty",
__func__);
return ret;
}
if (!pim_ifp->ucast_bsm_accept) {
dst_addr = qpim_all_pim_routers_addr;
if (PIM_DEBUG_BSM)
zlog_debug("%s: Sending BSM mcast to %pPA", __func__,
&neigh->source_addr);
} else {
dst_addr = neigh->source_addr;
if (PIM_DEBUG_BSM)
zlog_debug("%s: Sending BSM ucast to %pPA", __func__,
&neigh->source_addr);
}
pim_mtu = ifp->mtu - MAX_IP_HDR_LEN;
pim_hello_require(ifp);
frr_each (bsm_frags, scope->bsm_frags, bsfrag) {
if (pim_mtu < bsfrag->size) {
ret = pim_bsm_frag_send(bsfrag->data, bsfrag->size, ifp,
pim_mtu, dst_addr, no_fwd);
if (!ret) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s: pim_bsm_frag_send failed",
__func__);
}
} else {
/* Pim header needs to be constructed */
pim_msg_build_header(pim_ifp->primary_address, dst_addr,
bsfrag->data, bsfrag->size,
PIM_MSG_TYPE_BOOTSTRAP, no_fwd);
ret = pim_bsm_send_intf(bsfrag->data, bsfrag->size, ifp,
dst_addr);
if (!ret) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s: pim_bsm_frag_send failed",
__func__);
}
}
}
return ret;
}
struct bsgrp_node *pim_bsm_get_bsgrp_node(struct bsm_scope *scope,
struct prefix *grp)
{
struct route_node *rn;
struct bsgrp_node *bsgrp;
rn = route_node_lookup(scope->bsrp_table, grp);
if (!rn) {
if (PIM_DEBUG_BSM)
zlog_debug("%s: Route node doesn't exist for the group",
__func__);
return NULL;
}
bsgrp = rn->info;
route_unlock_node(rn);
return bsgrp;
}
static uint32_t hash_calc_on_grp_rp(struct prefix group, pim_addr rp,
uint8_t hashmasklen)
{
uint64_t temp;
uint32_t hash;
uint32_t grpaddr;
uint32_t rp_add;
uint32_t mask = 0xffffffff;
/* mask to be made zero if hashmasklen is 0 because mask << 32
* may not give 0. hashmasklen can be 0 to 32.
*/
if (hashmasklen == 0)
mask = 0;
/* in_addr stores ip in big endian, hence network byte order
* convert to uint32 before processing hash
*/
#if PIM_IPV == 4
grpaddr = ntohl(group.u.prefix4.s_addr);
#else
grpaddr = group.u.prefix6.s6_addr32[0] ^ group.u.prefix6.s6_addr32[1] ^
group.u.prefix6.s6_addr32[2] ^ group.u.prefix6.s6_addr32[3];
#endif
/* Avoid shifting by 32 bit on a 32 bit register */
if (hashmasklen)
grpaddr = grpaddr & ((mask << (32 - hashmasklen)));
else
grpaddr = grpaddr & mask;
#if PIM_IPV == 4
rp_add = ntohl(rp.s_addr);
#else
rp_add = rp.s6_addr32[0] ^ rp.s6_addr32[1] ^ rp.s6_addr32[2] ^
rp.s6_addr32[3];
#endif
temp = 1103515245 * ((1103515245 * (uint64_t)grpaddr + 12345) ^ rp_add)
+ 12345;
hash = temp & (0x7fffffff);
return hash;
}
static bool pim_install_bsm_grp_rp(struct pim_instance *pim,
struct bsgrp_node *grpnode,
struct bsmmsg_rpinfo *rp)
{
struct bsm_rpinfo *bsm_rpinfo;
uint8_t hashMask_len = pim->global_scope.hashMasklen;
/*memory allocation for bsm_rpinfo */
bsm_rpinfo = XCALLOC(MTYPE_PIM_BSRP_INFO, sizeof(*bsm_rpinfo));
bsm_rpinfo->rp_prio = rp->rp_pri;
bsm_rpinfo->rp_holdtime = rp->rp_holdtime;
bsm_rpinfo->rp_address = rp->rpaddr.addr;
bsm_rpinfo->elapse_time = 0;
/* Back pointer to the group node. */
bsm_rpinfo->bsgrp_node = grpnode;
/* update hash for this rp node */
bsm_rpinfo->hash = hash_calc_on_grp_rp(grpnode->group, rp->rpaddr.addr,
hashMask_len);
if (bsm_rpinfos_add(grpnode->partial_bsrp_list, bsm_rpinfo) == NULL) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s, bs_rpinfo node added to the partial bs_rplist.",
__func__);
return true;
}
if (PIM_DEBUG_BSM)
zlog_debug("%s: list node not added", __func__);
XFREE(MTYPE_PIM_BSRP_INFO, bsm_rpinfo);
return false;
}
static void pim_update_pending_rp_cnt(struct bsm_scope *sz,
struct bsgrp_node *bsgrp,
uint16_t bsm_frag_tag,
uint32_t total_rp_count)
{
if (bsgrp->pend_rp_cnt) {
/* received bsm is different packet ,
* it is not same fragment.
*/
if (bsm_frag_tag != bsgrp->frag_tag) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s,Received a new BSM ,so clear the pending bs_rpinfo list.",
__func__);
pim_bsm_rpinfos_free(bsgrp->partial_bsrp_list);
bsgrp->pend_rp_cnt = total_rp_count;
}
} else
bsgrp->pend_rp_cnt = total_rp_count;
bsgrp->frag_tag = bsm_frag_tag;
}
/* Parsing BSR packet and adding to partial list of corresponding bsgrp node */
bool pim_bsm_parse_install_g2rp(struct bsm_scope *scope, uint8_t *buf,
int buflen, uint16_t bsm_frag_tag)
{
struct bsmmsg_grpinfo grpinfo;
struct bsmmsg_rpinfo rpinfo;
struct prefix group;
struct bsgrp_node *bsgrp = NULL;
int frag_rp_cnt = 0;
int offset = 0;
int ins_count = 0;
pim_addr grp_addr;
while (buflen > offset) {
if (offset + (int)sizeof(struct bsmmsg_grpinfo) > buflen) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s: buflen received %d is less than the internal data structure of the packet would suggest",
__func__, buflen);
return false;
}
/* Extract Group tlv from BSM */
memcpy(&grpinfo, buf, sizeof(struct bsmmsg_grpinfo));
grp_addr = grpinfo.group.addr;
if (PIM_DEBUG_BSM)
zlog_debug(
"%s, Group %pPAs Rpcount:%d Fragment-Rp-count:%d",
__func__, &grp_addr, grpinfo.rp_count,
grpinfo.frag_rp_count);
buf += sizeof(struct bsmmsg_grpinfo);
offset += sizeof(struct bsmmsg_grpinfo);
group.family = PIM_AF;
if (grpinfo.group.mask > PIM_MAX_BITLEN) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s, prefix length specified: %d is too long",
__func__, grpinfo.group.mask);
return false;
}
pim_addr_to_prefix(&group, grp_addr);
group.prefixlen = grpinfo.group.mask;
/* Get the Group node for the BSM rp table */
bsgrp = pim_bsm_get_bsgrp_node(scope, &group);
if (grpinfo.rp_count == 0) {
struct bsm_rpinfo *old_rpinfo;
/* BSR explicitly no longer has RPs for this group */
if (!bsgrp)
continue;
if (PIM_DEBUG_BSM)
zlog_debug(
"%s, Rp count is zero for group: %pPAs",
__func__, &grp_addr);
old_rpinfo = bsm_rpinfos_first(bsgrp->bsrp_list);
if (old_rpinfo)
pim_rp_del(scope->pim, old_rpinfo->rp_address,
group, NULL, RP_SRC_BSR);
pim_free_bsgrp_node(scope->bsrp_table, &bsgrp->group);
pim_free_bsgrp_data(bsgrp);
continue;
}
if (!bsgrp) {
if (PIM_DEBUG_BSM)
zlog_debug("%s, Create new BSM Group node.",
__func__);
/* create a new node to be added to the tree. */
bsgrp = pim_bsm_new_bsgrp_node(scope->bsrp_table,
&group);
if (!bsgrp) {
zlog_debug(
"%s, Failed to get the BSM group node.",
__func__);
continue;
}
bsgrp->scope = scope;
}
pim_update_pending_rp_cnt(scope, bsgrp, bsm_frag_tag,
grpinfo.rp_count);
frag_rp_cnt = grpinfo.frag_rp_count;
ins_count = 0;
while (frag_rp_cnt--) {
if (offset + (int)sizeof(struct bsmmsg_rpinfo)
> buflen) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s, buflen received: %u is less than the internal data structure of the packet would suggest",
__func__, buflen);
return false;
}
/* Extract RP address tlv from BSM */
memcpy(&rpinfo, buf, sizeof(struct bsmmsg_rpinfo));
rpinfo.rp_holdtime = ntohs(rpinfo.rp_holdtime);
buf += sizeof(struct bsmmsg_rpinfo);
offset += sizeof(struct bsmmsg_rpinfo);
if (PIM_DEBUG_BSM) {
pim_addr rp_addr;
rp_addr = rpinfo.rpaddr.addr;
zlog_debug(
"%s, Rp address - %pPAs; pri:%d hold:%d",
__func__, &rp_addr, rpinfo.rp_pri,
rpinfo.rp_holdtime);
}
/* Call Install api to update grp-rp mappings */
if (pim_install_bsm_grp_rp(scope->pim, bsgrp, &rpinfo))
ins_count++;
}
bsgrp->pend_rp_cnt -= ins_count;
if (!bsgrp->pend_rp_cnt) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s, Recvd all the rps for this group, so bsrp list with penidng rp list.",
__func__);
/* replace the bsrp_list with pending list */
pim_instate_pend_list(bsgrp);
}
}
return true;
}
int pim_bsm_process(struct interface *ifp, pim_sgaddr *sg, uint8_t *buf,
uint32_t buf_size, bool no_fwd)
{
struct bsm_hdr *bshdr;
int sz = PIM_GBL_SZ_ID;
struct bsmmsg_grpinfo *msg_grp;
struct pim_interface *pim_ifp = NULL;
struct bsm_frag *bsfrag;
struct pim_instance *pim;
uint16_t frag_tag;
pim_addr bsr_addr;
bool empty_bsm = false;
/* BSM Packet acceptance validation */
pim_ifp = ifp->info;
if (!pim_ifp) {
if (PIM_DEBUG_BSM)
zlog_debug("%s: multicast not enabled on interface %s",
__func__, ifp->name);
return -1;
}
if (pim_ifp->pim_passive_enable) {
if (PIM_DEBUG_PIM_PACKETS)
zlog_debug(
"skip receiving PIM message on passive interface %s",
ifp->name);
return 0;
}
pim_ifp->pim_ifstat_bsm_rx++;
pim = pim_ifp->pim;
pim->bsm_rcvd++;
/* Drop if bsm processing is disabled on interface */
if (!pim_ifp->bsm_enable) {
zlog_warn("%s: BSM not enabled on interface %s", __func__,
ifp->name);
pim_ifp->pim_ifstat_bsm_cfg_miss++;
pim->bsm_dropped++;
return -1;
}
if (buf_size < (PIM_MSG_HEADER_LEN + sizeof(struct bsm_hdr))) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s: received buffer length of %d which is too small to properly decode",
__func__, buf_size);
return -1;
}
bshdr = (struct bsm_hdr *)(buf + PIM_MSG_HEADER_LEN);
if (bshdr->hm_len > PIM_MAX_BITLEN) {
zlog_warn(
"Bad hashmask length for %s; got %hhu, expected value in range 0-32",
PIM_AF_NAME, bshdr->hm_len);
pim->bsm_dropped++;
return -1;
}
pim->global_scope.hashMasklen = bshdr->hm_len;
frag_tag = ntohs(bshdr->frag_tag);
/* NB: bshdr->bsr_addr.addr is packed/unaligned => memcpy */
memcpy(&bsr_addr, &bshdr->bsr_addr.addr, sizeof(bsr_addr));
/* Identify empty BSM */
if ((buf_size - PIM_BSM_HDR_LEN - PIM_MSG_HEADER_LEN) < PIM_BSM_GRP_LEN)
empty_bsm = true;
if (!empty_bsm) {
msg_grp = (struct bsmmsg_grpinfo *)(buf + PIM_MSG_HEADER_LEN
+ PIM_BSM_HDR_LEN);
/* Currently we don't support scope zoned BSM */
if (msg_grp->group.sz) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s : Administratively scoped range BSM received",
__func__);
pim_ifp->pim_ifstat_bsm_invalid_sz++;
pim->bsm_dropped++;
return -1;
}
}
if (!pim_addr_cmp(sg->grp, qpim_all_pim_routers_addr)) {
/* Multicast BSMs are only accepted if source interface & IP
* match RPF towards the BSR's IP address, or they have
* no-forward set
*/
if (!no_fwd &&
!pim_nht_bsr_rpf_check(pim, bsr_addr, ifp, sg->src)) {
if (PIM_DEBUG_BSM)
zlog_debug(
"BSM check: RPF to BSR %pPAs is not %pPA%%%s",
&bsr_addr, &sg->src, ifp->name);
pim->bsm_dropped++;
return -1;
}
} else if (if_address_is_local(&sg->grp, PIM_AF, pim->vrf->vrf_id)) {
/* Unicast BSM received - if ucast bsm not enabled on
* the interface, drop it
*/
if (!pim_ifp->ucast_bsm_accept) {
if (PIM_DEBUG_BSM)
zlog_debug(
"%s : Unicast BSM not enabled on interface %s",
__func__, ifp->name);
pim_ifp->pim_ifstat_ucast_bsm_cfg_miss++;
pim->bsm_dropped++;
return -1;
}
} else {
if (PIM_DEBUG_BSM)
zlog_debug("%s : Invalid destination address",
__func__);
pim->bsm_dropped++;
return -1;
}
/* when the BSR restarts, it can get its own BSR advertisement thrown
* back at it, and without this we'll go into ACCEPT_PREFERRED with
* ourselves as the BSR when we should be in BSR_ELECTED.
*/
if (if_address_is_local(&bshdr->bsr_addr.addr, PIM_AF,
pim->vrf->vrf_id)) {
if (PIM_DEBUG_BSM)
zlog_debug("%s : Dropping BSM from ourselves", __func__);
pim->bsm_dropped++;
return -1;
}
/* Drop if bsr is not preferred bsr */
if (!is_preferred_bsr(pim, bsr_addr, bshdr->bsr_prio)) {
if (pim->global_scope.state == BSR_PENDING && !no_fwd) {
/* in P-BSR state, non-preferred BSMs are forwarded, but
* content is ignored.
*/
if (PIM_DEBUG_BSM)
zlog_debug("%s : Forwarding non-preferred BSM during Pending-BSR state",
__func__);
pim_bsm_fwd_whole_sz(pim_ifp->pim, buf, buf_size, sz);
return -1;
}
if (PIM_DEBUG_BSM)
zlog_debug("%s : Received a non-preferred BSM",
__func__);
pim->bsm_dropped++;
return -1;
}
if (no_fwd) {
/* only accept no-forward BSM if quick refresh on startup */
if ((pim->global_scope.accept_nofwd_bsm) ||
(frag_tag == pim->global_scope.bsm_frag_tag)) {
pim->global_scope.accept_nofwd_bsm = false;
} else {
if (PIM_DEBUG_BSM)
zlog_debug("%s : nofwd_bsm received on %pPAs when accpt_nofwd_bsm false",
__func__, &bsr_addr);
pim->bsm_dropped++;
pim_ifp->pim_ifstat_ucast_bsm_cfg_miss++;
return -1;
}
}
/* BSM packet is seen, so resetting accept_nofwd_bsm to false */
if (pim->global_scope.accept_nofwd_bsm)
pim->global_scope.accept_nofwd_bsm = false;
if (empty_bsm) {
if (PIM_DEBUG_BSM)
zlog_debug("%s : Empty Pref BSM received", __func__);
}
/* Parse Update bsm rp table and install/uninstall rp if required */
if (!pim_bsm_parse_install_g2rp(
&pim_ifp->pim->global_scope,
(buf + PIM_BSM_HDR_LEN + PIM_MSG_HEADER_LEN),
(buf_size - PIM_BSM_HDR_LEN - PIM_MSG_HEADER_LEN),
frag_tag)) {
zlog_warn("BSM from %pPA failed to parse",
(pim_addr *)&bshdr->bsr_addr.addr);
pim->bsm_dropped++;
return -1;
}
/* Restart the bootstrap timer */
pim_bs_timer_restart(&pim_ifp->pim->global_scope,
PIM_BSR_DEFAULT_TIMEOUT);
/* If new BSM received, clear the old bsm database */
if (pim_ifp->pim->global_scope.bsm_frag_tag != frag_tag) {
if (PIM_DEBUG_BSM) {
zlog_debug("%s: Current frag tag: %d Frag teg rcvd: %d",
__func__,
pim_ifp->pim->global_scope.bsm_frag_tag,
frag_tag);
}
pim_bsm_frags_free(&pim_ifp->pim->global_scope);
pim_ifp->pim->global_scope.bsm_frag_tag = frag_tag;
}
/* update the scope information from bsm */
pim_bsm_update(pim, bsr_addr, bshdr->bsr_prio);
if (!no_fwd) {
pim_bsm_fwd_whole_sz(pim_ifp->pim, buf, buf_size, sz);
bsfrag = XCALLOC(MTYPE_PIM_BSM_FRAG,
sizeof(struct bsm_frag) + buf_size);
bsfrag->size = buf_size;
memcpy(bsfrag->data, buf, buf_size);
bsm_frags_add_tail(pim_ifp->pim->global_scope.bsm_frags,
bsfrag);
}
return 0;
}
static void pim_elec_bsr_timer(struct event *t)
{
struct bsm_scope *scope = EVENT_ARG(t);
struct bsm_frag *frag;
struct bsm_hdr *hdr;
assert(scope->state == BSR_ELECTED);
scope->bsm_frag_tag++;
frag = bsm_frags_first(scope->bsm_frags);
assert(frag);
hdr = (struct bsm_hdr *)(frag->data + PIM_MSG_HEADER_LEN);
hdr->frag_tag = htons(scope->bsm_frag_tag);
unsigned int timer = PIM_BS_TIME;
if (scope->changed_bsm_trigger) {
if (PIM_DEBUG_BSM)
zlog_debug("Sending triggered BSM");
scope->changed_bsm_trigger--;
timer = 5;
} else {
if (PIM_DEBUG_BSM)
zlog_debug("Sending scheduled BSM");
pim_bsm_sent(scope);
}
pim_bsm_fwd_whole_sz(scope->pim, frag->data, frag->size, scope->sz_id);
scope->current_bsr_last_ts = pim_time_monotonic_sec();
event_add_timer(router->master, pim_elec_bsr_timer, scope, timer,
&scope->bs_timer);
}
void pim_bsm_changed(struct bsm_scope *scope)
{
struct event t;
EVENT_OFF(scope->bs_timer);
scope->changed_bsm_trigger = 2;
t.arg = scope;
pim_elec_bsr_timer(&t);
}
static void pim_cand_bsr_pending_expire(struct event *t)
{
struct bsm_scope *scope = EVENT_ARG(t);
assertf(scope->state == BSR_PENDING, "state=%d", scope->state);
if (!pim_addr_is_any(scope->current_bsr)) {
assertf(scope->cand_bsr_prio >= scope->current_bsr_prio,
"cand_bsr %pPA prio %u is less than current_bsr %pPA prio %u",
&scope->bsr_addrsel.run_addr, scope->current_bsr_prio, &scope->current_bsr,
scope->cand_bsr_prio);
if (scope->cand_bsr_prio == scope->current_bsr_prio)
assertf(pim_addr_cmp(scope->bsr_addrsel.run_addr, scope->current_bsr) > 0,
"cand_bsr %pPA < current_bsr %pPA", &scope->bsr_addrsel.run_addr,
&scope->current_bsr);
}
if (PIM_DEBUG_BSM)
zlog_debug("Elected BSR, wait expired without preferable BSMs");
scope->state = BSR_ELECTED;
scope->current_bsr_prio = scope->cand_bsr_prio;
scope->current_bsr = scope->bsr_addrsel.run_addr;
scope->bsm_frag_tag = frr_weak_random();
scope->current_bsr_first_ts = pim_time_monotonic_sec();
pim_cand_rp_trigger(scope);
pim_bsm_generate(scope);
}
#if PIM_IPV == 6
static float bsr_addr_delay(pim_addr best, pim_addr local)
{
unsigned int pos;
uint32_t best_4b, local_4b;
float delay_log;
for (pos = 0; pos < 12; pos++) {
if (best.s6_addr[pos] != local.s6_addr[pos])
break;
}
memcpy(&best_4b, &best.s6_addr[pos], 4);
memcpy(&local_4b, &local.s6_addr[pos], 4);
delay_log = log2(1 + ntohl(best_4b) - ntohl(local_4b));
delay_log += (12 - pos) * 8;
return delay_log / 64.;
}
#endif
static void pim_cand_bsr_pending(struct bsm_scope *scope)
{
unsigned int bs_rand_override;
uint8_t best_prio;
pim_addr best_addr;
float prio_delay, addr_delay;
EVENT_OFF(scope->bs_timer);
EVENT_OFF(scope->t_ebsr_regen_bsm);
scope->state = BSR_PENDING;
best_prio = MAX(scope->cand_bsr_prio, scope->current_bsr_prio);
best_addr = pim_addr_cmp(scope->bsr_addrsel.run_addr,
scope->current_bsr) > 0
? scope->bsr_addrsel.run_addr
: scope->current_bsr;
/* RFC5059 sec.5 */
#if PIM_IPV == 4
if (scope->cand_bsr_prio == best_prio) {
prio_delay = 0.; /* log2(1) = 0 */
addr_delay = log2(1 + ntohl(best_addr.s_addr) -
ntohl(scope->bsr_addrsel.run_addr.s_addr)) /
16.;
} else {
prio_delay = 2. * log2(1 + best_prio - scope->cand_bsr_prio);
addr_delay = 2 - (ntohl(scope->bsr_addrsel.run_addr.s_addr) /
(float)(1 << 31));
}
#else
if (scope->cand_bsr_prio == best_prio) {
prio_delay = 0.; /* log2(1) = 0 */
addr_delay = bsr_addr_delay(best_addr,
scope->bsr_addrsel.run_addr);
} else {
prio_delay = 2. * log2(1 + best_prio - scope->cand_bsr_prio);
addr_delay = 2 -
(ntohl(scope->bsr_addrsel.run_addr.s6_addr32[0]) /
(float)(1 << 31));
}
#endif
bs_rand_override = 5000 + (int)((prio_delay + addr_delay) * 1000.);
if (PIM_DEBUG_BSM)
zlog_debug("Pending-BSR (%u, %pPA), waiting %ums",
scope->cand_bsr_prio, &scope->bsr_addrsel.run_addr,
bs_rand_override);
event_add_timer_msec(router->master, pim_cand_bsr_pending_expire, scope,
bs_rand_override, &scope->bs_timer);
}
static inline pim_addr if_highest_addr(pim_addr cur, struct interface *ifp)
{
struct connected *connected;
frr_each (if_connected, ifp->connected, connected) {
pim_addr conn_addr;
if (connected->address->family != PIM_AF)
continue;
conn_addr = pim_addr_from_prefix(connected->address);
/* highest address */
if (pim_addr_cmp(conn_addr, cur) > 0)
cur = conn_addr;
}
return cur;
}
void cand_addrsel_clear(struct cand_addrsel *asel)
{
asel->run = false;
asel->run_addr = PIMADDR_ANY;
}
/* returns whether address or active changed */
bool cand_addrsel_update(struct cand_addrsel *asel, struct vrf *vrf)
{
bool is_any = false, prev_run = asel->run;
struct interface *ifp = NULL;
pim_addr new_addr = PIMADDR_ANY;
if (!asel->cfg_enable)
goto out_disable;
switch (asel->cfg_mode) {
case CAND_ADDR_EXPLICIT:
new_addr = asel->cfg_addr;
ifp = if_lookup_address_local(&asel->cfg_addr, PIM_AF,
vrf->vrf_id);
break;
case CAND_ADDR_IFACE:
ifp = if_lookup_by_name_vrf(asel->cfg_ifname, vrf);
if (ifp)
new_addr = if_highest_addr(PIMADDR_ANY, ifp);
break;
case CAND_ADDR_ANY:
is_any = true;
/* fallthru */
case CAND_ADDR_LO:
FOR_ALL_INTERFACES (vrf, ifp) {
if (!if_is_up(ifp))
continue;
if (is_any || if_is_loopback(ifp) || if_is_vrf(ifp))
new_addr = if_highest_addr(new_addr, ifp);
}
break;
}
if (ifp && !if_is_up(ifp))
goto out_disable;
if (pim_addr_is_any(new_addr))
goto out_disable;
/* nothing changed re. address (don't care about interface changes) */
if (asel->run && !pim_addr_cmp(asel->run_addr, new_addr))
return !prev_run;
asel->run = true;
asel->run_addr = new_addr;
return true;
out_disable:
asel->run = false;
asel->run_addr = PIMADDR_ANY;
return prev_run;
}
static void pim_cand_bsr_stop(struct bsm_scope *scope, bool verbose)
{
cand_addrsel_clear(&scope->bsr_addrsel);
switch (scope->state) {
case NO_INFO:
case ACCEPT_ANY:
case ACCEPT_PREFERRED:
return;
case BSR_PENDING:
case BSR_ELECTED:
break;
}
if (PIM_DEBUG_BSM)
zlog_debug("Candidate BSR ceasing operation");
EVENT_OFF(scope->t_ebsr_regen_bsm);
EVENT_OFF(scope->bs_timer);
pim_crp_db_clear(scope);
pim_bsm_accept_any(scope);
}
static void pim_cand_bsr_trigger(struct bsm_scope *scope, bool verbose)
{
/* this is called on all state changes even if we aren't configured
* to be C-BSR at all.
*/
if (!scope->bsr_addrsel.run)
return;
if (scope->current_bsr_prio > scope->cand_bsr_prio) {
assert(scope->state == ACCEPT_PREFERRED);
if (!verbose)
return;
if (PIM_DEBUG_BSM)
zlog_debug("Candidate BSR: known better BSR %pPA (higher priority %u > %u)",
&scope->current_bsr, scope->current_bsr_prio,
scope->cand_bsr_prio);
return;
} else if (scope->current_bsr_prio == scope->cand_bsr_prio &&
pim_addr_cmp(scope->current_bsr,
scope->bsr_addrsel.run_addr) > 0) {
assert(scope->state == ACCEPT_PREFERRED);
if (!verbose)
return;
if (PIM_DEBUG_BSM)
zlog_debug("Candidate BSR: known better BSR %pPA (higher address > %pPA)",
&scope->current_bsr,
&scope->bsr_addrsel.run_addr);
return;
}
if (!pim_addr_cmp(scope->current_bsr, scope->bsr_addrsel.run_addr))
return;
pim_cand_bsr_pending(scope);
}
void pim_cand_bsr_apply(struct bsm_scope *scope)
{
if (!cand_addrsel_update(&scope->bsr_addrsel, scope->pim->vrf))
return;
if (!scope->bsr_addrsel.run) {
pim_cand_bsr_stop(scope, true);
return;
}
if (PIM_DEBUG_BSM)
zlog_debug("Candidate BSR: %pPA, priority %u",
&scope->bsr_addrsel.run_addr, scope->cand_bsr_prio);
pim_cand_bsr_trigger(scope, true);
}
static void pim_cand_rp_adv_stop_maybe(struct bsm_scope *scope)
{
/* actual check whether stop should be sent - covers address
* changes as well as run_addr = 0.0.0.0 (C-RP shutdown)
*/
if (pim_addr_is_any(scope->cand_rp_prev_addr) ||
!pim_addr_cmp(scope->cand_rp_prev_addr,
scope->cand_rp_addrsel.run_addr))
return;
switch (scope->state) {
case ACCEPT_PREFERRED:
case BSR_ELECTED:
break;
case NO_INFO:
case ACCEPT_ANY:
case BSR_PENDING:
default:
return;
}
if (PIM_DEBUG_BSM)
zlog_debug("Candidate-RP (-, %pPA) deregistering self to %pPA",
&scope->cand_rp_prev_addr, &scope->current_bsr);
struct cand_rp_msg *msg;
uint8_t buf[PIM_MSG_HEADER_LEN + sizeof(*msg) + sizeof(pim_encoded_group)];
msg = (struct cand_rp_msg *)(&buf[PIM_MSG_HEADER_LEN]);
msg->prefix_cnt = 0;
msg->rp_prio = 255;
msg->rp_holdtime = 0;
msg->rp_addr.family = PIM_IANA_AFI;
msg->rp_addr.reserved = 0;
msg->rp_addr.addr = scope->cand_rp_prev_addr;
pim_msg_build_header(PIMADDR_ANY, scope->current_bsr, buf, sizeof(buf),
PIM_MSG_TYPE_CANDIDATE, false);
if (pim_msg_send(scope->unicast_sock, PIMADDR_ANY, scope->current_bsr,
buf, sizeof(buf), NULL)) {
zlog_warn("failed to send Cand-RP message: %m");
}
scope->cand_rp_prev_addr = PIMADDR_ANY;
}
static void pim_cand_rp_adv(struct event *t)
{
struct bsm_scope *scope = EVENT_ARG(t);
int next_msec;
pim_cand_rp_adv_stop_maybe(scope);
if (!scope->cand_rp_addrsel.run) {
scope->cand_rp_adv_trigger = 0;
return;
}
switch (scope->state) {
case ACCEPT_PREFERRED:
case BSR_ELECTED:
break;
case ACCEPT_ANY:
case BSR_PENDING:
case NO_INFO:
default:
/* state change will retrigger */
scope->cand_rp_adv_trigger = 0;
zlog_warn("Candidate-RP advertisement not sent in state %d",
scope->state);
return;
}
if (PIM_DEBUG_BSM)
zlog_debug("Candidate-RP (%u, %pPA) advertising %zu groups to %pPA",
scope->cand_rp_prio, &scope->cand_rp_addrsel.run_addr,
cand_rp_groups_count(scope->cand_rp_groups),
&scope->current_bsr);
struct cand_rp_group *grp;
struct cand_rp_msg *msg;
uint8_t buf[PIM_MSG_HEADER_LEN + sizeof(*msg) +
sizeof(pim_encoded_group) *
cand_rp_groups_count(scope->cand_rp_groups)];
size_t i = 0;
msg = (struct cand_rp_msg *)(&buf[PIM_MSG_HEADER_LEN]);
msg->prefix_cnt = cand_rp_groups_count(scope->cand_rp_groups);
msg->rp_prio = scope->cand_rp_prio;
msg->rp_holdtime =
htons(MAX(151, (scope->cand_rp_interval * 5 + 1) / 2));
msg->rp_addr.family = PIM_IANA_AFI;
msg->rp_addr.reserved = 0;
msg->rp_addr.addr = scope->cand_rp_addrsel.run_addr;
frr_each (cand_rp_groups, scope->cand_rp_groups, grp) {
memset(&msg->groups[i], 0, sizeof(msg->groups[i]));
msg->groups[i].family = PIM_IANA_AFI;
msg->groups[i].mask = grp->p.prefixlen;
msg->groups[i].addr = grp->p.prefix;
i++;
}
scope->cand_rp_prev_addr = scope->cand_rp_addrsel.run_addr;
pim_msg_build_header(scope->cand_rp_addrsel.run_addr, scope->current_bsr,
buf, sizeof(buf), PIM_MSG_TYPE_CANDIDATE, false);
if (pim_msg_send(scope->unicast_sock, scope->cand_rp_addrsel.run_addr,
scope->current_bsr, buf, sizeof(buf), NULL)) {
zlog_warn("failed to send Cand-RP message: %m");
}
/* -1s...+1s */
next_msec = (frr_weak_random() & 2047) - 1024;
if (scope->cand_rp_adv_trigger) {
scope->cand_rp_adv_trigger--;
next_msec += 2000;
} else
next_msec += scope->cand_rp_interval * 1000;
event_add_timer_msec(router->master, pim_cand_rp_adv, scope, next_msec,
&scope->cand_rp_adv_timer);
}
void pim_cand_rp_trigger(struct bsm_scope *scope)
{
if (scope->cand_rp_adv_trigger && scope->cand_rp_addrsel.run) {
scope->cand_rp_adv_trigger = PIM_CRP_ADV_TRIGCOUNT;
/* already scheduled to send triggered advertisements, don't
* reschedule so burst changes don't result in an advertisement
* burst
*/
return;
}
EVENT_OFF(scope->cand_rp_adv_timer);
if (!scope->cand_rp_addrsel.run)
return;
scope->cand_rp_adv_trigger = PIM_CRP_ADV_TRIGCOUNT;
struct event t;
t.arg = scope;
pim_cand_rp_adv(&t);
}
void pim_cand_rp_apply(struct bsm_scope *scope)
{
if (!cand_addrsel_update(&scope->cand_rp_addrsel, scope->pim->vrf))
return;
if (!scope->cand_rp_addrsel.run) {
if (PIM_DEBUG_BSM)
zlog_debug("Candidate RP ceasing operation");
cand_addrsel_clear(&scope->cand_rp_addrsel);
EVENT_OFF(scope->cand_rp_adv_timer);
pim_cand_rp_adv_stop_maybe(scope);
scope->cand_rp_adv_trigger = 0;
return;
}
if (PIM_DEBUG_BSM)
zlog_debug("Candidate RP: %pPA, priority %u",
&scope->cand_rp_addrsel.run_addr,
scope->cand_rp_prio);
pim_cand_rp_trigger(scope);
}
void pim_cand_rp_grp_add(struct bsm_scope *scope, const prefix_pim *p)
{
struct cand_rp_group *grp, ref;
ref.p = *p;
grp = cand_rp_groups_find(scope->cand_rp_groups, &ref);
if (grp)
return;
grp = XCALLOC(MTYPE_PIM_CAND_RP_GRP, sizeof(*grp));
grp->p = *p;
cand_rp_groups_add(scope->cand_rp_groups, grp);
pim_cand_rp_trigger(scope);
}
void pim_cand_rp_grp_del(struct bsm_scope *scope, const prefix_pim *p)
{
struct cand_rp_group *grp, ref;
ref.p = *p;
grp = cand_rp_groups_find(scope->cand_rp_groups, &ref);
if (!grp)
return;
cand_rp_groups_del(scope->cand_rp_groups, grp);
XFREE(MTYPE_PIM_CAND_RP_GRP, grp);
pim_cand_rp_trigger(scope);
}
static struct event *t_cand_addrs_reapply;
static void pim_cand_addrs_reapply(struct event *t)
{
struct vrf *vrf;
RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) {
struct pim_instance *pi = vrf->info;
if (!pi)
continue;
/* these call cand_addrsel_update() and apply changes */
pim_cand_bsr_apply(&pi->global_scope);
pim_cand_rp_apply(&pi->global_scope);
}
}
void pim_cand_addrs_changed(void)
{
EVENT_OFF(t_cand_addrs_reapply);
event_add_timer_msec(router->master, pim_cand_addrs_reapply, NULL, 1,
&t_cand_addrs_reapply);
}
static void cand_addrsel_config_write(struct vty *vty,
struct cand_addrsel *addrsel)
{
switch (addrsel->cfg_mode) {
case CAND_ADDR_LO:
break;
case CAND_ADDR_ANY:
vty_out(vty, " source any");
break;
case CAND_ADDR_IFACE:
vty_out(vty, " source interface %s", addrsel->cfg_ifname);
break;
case CAND_ADDR_EXPLICIT:
vty_out(vty, " source address %pPA", &addrsel->cfg_addr);
break;
}
}
int pim_cand_config_write(struct pim_instance *pim, struct vty *vty)
{
struct bsm_scope *scope = &pim->global_scope;
int ret = 0;
if (scope->cand_rp_addrsel.cfg_enable) {
vty_out(vty, " bsr candidate-rp");
if (scope->cand_rp_prio != 192)
vty_out(vty, " priority %u", scope->cand_rp_prio);
if (scope->cand_rp_interval != PIM_CRP_ADV_INTERVAL)
vty_out(vty, " interval %u", scope->cand_rp_interval);
cand_addrsel_config_write(vty, &scope->cand_rp_addrsel);
vty_out(vty, "\n");
ret++;
struct cand_rp_group *group;
frr_each (cand_rp_groups, scope->cand_rp_groups, group) {
vty_out(vty, " bsr candidate-rp group %pFX\n",
&group->p);
ret++;
}
}
if (scope->bsr_addrsel.cfg_enable) {
vty_out(vty, " bsr candidate-bsr");
if (scope->cand_bsr_prio != 64)
vty_out(vty, " priority %u", scope->cand_bsr_prio);
cand_addrsel_config_write(vty, &scope->bsr_addrsel);
vty_out(vty, "\n");
ret++;
}
return ret;
}