summaryrefslogtreecommitdiffstats
path: root/pimd/pim_bsm.c
diff options
context:
space:
mode:
Diffstat (limited to 'pimd/pim_bsm.c')
-rw-r--r--pimd/pim_bsm.c881
1 files changed, 815 insertions, 66 deletions
diff --git a/pimd/pim_bsm.c b/pimd/pim_bsm.c
index df916194..a44e4e08 100644
--- a/pimd/pim_bsm.c
+++ b/pimd/pim_bsm.c
@@ -10,6 +10,17 @@
#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"
@@ -23,18 +34,32 @@
#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_STATIC(PIMD, PIM_BSM_FRAG, "PIM BSM fragment");
+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
@@ -90,7 +115,7 @@ static void pim_bsm_frag_free(struct bsm_frag *bsfrag)
XFREE(MTYPE_PIM_BSM_FRAG, bsfrag);
}
-static void pim_bsm_frags_free(struct bsm_scope *scope)
+void pim_bsm_frags_free(struct bsm_scope *scope)
{
struct bsm_frag *bsfrag;
@@ -140,12 +165,12 @@ static struct bsgrp_node *pim_bsm_new_bsgrp_node(struct route_table *rt,
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 route_node *rn;
struct bsm_scope *scope;
- struct bsgrp_node *bsgrp_node;
- struct bsm_rpinfo *bsrp;
scope = EVENT_ARG(t);
EVENT_OFF(scope->bs_timer);
@@ -154,7 +179,20 @@ static void pim_on_bs_timer(struct event *t)
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;
@@ -181,6 +219,11 @@ static void pim_on_bs_timer(struct event *t)
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)
@@ -212,36 +255,77 @@ 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)
{
- memset(&pim->global_scope, 0, sizeof(struct bsm_scope));
-
- pim->global_scope.sz_id = PIM_GBL_SZ_ID;
- pim->global_scope.bsrp_table = route_table_init();
- pim->global_scope.accept_nofwd_bsm = true;
- pim->global_scope.state = NO_INFO;
- pim->global_scope.pim = pim;
- bsm_frags_init(pim->global_scope.bsm_frags);
- pim_bs_timer_start(&pim->global_scope, PIM_BS_TIME);
+ 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;
- pim_bs_timer_stop(&pim->global_scope);
- pim_bsm_frags_free(&pim->global_scope);
+ EVENT_OFF(scope->unicast_read);
+ close(scope->unicast_sock);
- for (rn = route_top(pim->global_scope.bsrp_table); rn;
- rn = route_next(rn)) {
+ 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);
}
- route_table_finish(pim->global_scope.bsrp_table);
+ 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)
@@ -512,9 +596,6 @@ static void pim_instate_pend_list(struct bsgrp_node *bsgrp_node)
static bool is_preferred_bsr(struct pim_instance *pim, pim_addr bsr,
uint32_t bsr_prio)
{
- if (!pim_addr_cmp(bsr, pim->global_scope.current_bsr))
- return true;
-
if (bsr_prio > pim->global_scope.current_bsr_prio)
return true;
@@ -523,6 +604,11 @@ static bool is_preferred_bsr(struct pim_instance *pim, pim_addr bsr,
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;
}
@@ -530,17 +616,52 @@ static bool is_preferred_bsr(struct pim_instance *pim, pim_addr bsr,
static void pim_bsm_update(struct pim_instance *pim, pim_addr bsr,
uint32_t bsr_prio)
{
- if (pim_addr_cmp(bsr, pim->global_scope.current_bsr)) {
- 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->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)
@@ -555,7 +676,12 @@ void pim_bsm_clear(struct pim_instance *pim)
struct rp_info *rp_info;
bool upstream_updated = false;
- pim_nht_bsr_del(pim, pim->global_scope.current_bsr);
+ 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;
@@ -1116,8 +1242,8 @@ static void pim_update_pending_rp_cnt(struct bsm_scope *sz,
}
/* Parsing BSR packet and adding to partial list of corresponding bsgrp node */
-static bool pim_bsm_parse_install_g2rp(struct bsm_scope *scope, uint8_t *buf,
- int buflen, uint16_t bsm_frag_tag)
+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;
@@ -1338,35 +1464,6 @@ int pim_bsm_process(struct interface *ifp, pim_sgaddr *sg, uint8_t *buf,
}
}
- /* Drop if bsr is not preferred bsr */
- if (!is_preferred_bsr(pim, bsr_addr, bshdr->bsr_prio)) {
- 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 (!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
@@ -1403,6 +1500,57 @@ int pim_bsm_process(struct interface *ifp, pim_sgaddr *sg, uint8_t *buf,
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__);
@@ -1413,9 +1561,8 @@ int pim_bsm_process(struct interface *ifp, pim_sgaddr *sg, uint8_t *buf,
(buf + PIM_BSM_HDR_LEN + PIM_MSG_HEADER_LEN),
(buf_size - PIM_BSM_HDR_LEN - PIM_MSG_HEADER_LEN),
frag_tag)) {
- if (PIM_DEBUG_BSM) {
- zlog_debug("%s, Parsing BSM failed.", __func__);
- }
+ zlog_warn("BSM from %pPA failed to parse",
+ (pim_addr *)&bshdr->bsr_addr.addr);
pim->bsm_dropped++;
return -1;
}
@@ -1451,3 +1598,605 @@ int pim_bsm_process(struct interface *ifp, pim_sgaddr *sg, uint8_t *buf,
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;
+}
+
+static void cand_addrsel_clear(struct cand_addrsel *asel)
+{
+ asel->run = false;
+ asel->run_addr = PIMADDR_ANY;
+}
+
+/* returns whether address or active changed */
+static 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;
+}