diff --git a/doc/user/pim.rst b/doc/user/pim.rst index 5701560bd68c..372fdd73ae62 100644 --- a/doc/user/pim.rst +++ b/doc/user/pim.rst @@ -83,6 +83,41 @@ PIM Routers cannot see data flowing in better than 30 second chunks. This command is vrf aware, to configure for a vrf, specify the vrf in the router pim block. +.. clicmd:: bsr candidate-bsr [priority (0-255)] [source [address A.B.C.D] | [interface INTERFACE] | [loopback] | [any]] + + Configure the router to advertise itself as a candidate PIM-SM BSR. The candidate + with the highest priority becomes the BSR for the domain (high wins). When priority is the + same for more than one candidate BSR, the candidate with the highest IP address + becomes the BSR of the domain. The address can be configured explicitly + via ``address``, or be selecting an interface name using ``interface``. + If ``any`` is configured the highest address from any interface will be selected. + By default, the highest loopback address is selected, which can also be + configured via ``loopback`` + +.. clicmd:: bsr candidate-rp [interval] + + Configure the router to advertise itself as a candidate PIM-SM RP at the + specified ``interval`` in seconds. + + +.. clicmd:: bsr candidate-rp group A.B.C.D/M + + Configure the multicast group prefix that this candidate RP advertises itself for. + This command can be repeated for all desired groups that need to be added to the + candidate RP advertisement. + +.. clicmd:: bsr candidate-rp [priority (0-255)] [source [address A.B.C.D] | [interface INTERFACE] | [loopback] | [any]] + + Configure the router to advertise itself as a candidate PIM-SM RP. ``interval`` + can be used to configure the interval in seconds to send these advertisements. + The candidate with the lowest priority becomes the RP for the domain (low wins). + When priority is the same for more than one candidate RP, the candidate with + the highest IP address becomes the BSR of the domain. The address can be + configured explicitly via ``address``, or be selecting an interface name + using ``interface``. If ``any`` is configured the highest address from any + interface will be selected.By default, the highest loopback address is + selected, which can also be configured via ``loopback``. + .. clicmd:: register-accept-list PLIST When pim receives a register packet the source of the packet will be compared @@ -611,11 +646,28 @@ cause great confusion. Display PIM MLAG (multi-chassis link aggregation) session status and control message statistics. -.. clicmd:: show ip pim bsr +.. clicmd:: show ip pim bsr [vrf NAME] [json] Display current bsr, its uptime and last received bsm age. -.. clicmd:: show ip pim bsrp-info [vrf NAME] [json] +.. clicmd:: show ip pim bsr candidate-bsr [vrf NAME] [json] + + Display information about the candidate BSR state on this router. + +.. clicmd:: show ip pim bsr candidate-rp [vrf NAME] [json] + + Display information about the candidate RP state on this router. + +.. clicmd:: show ip pim bsr candidate-rp-database [vrf NAME] [json] + + Display the current list of candidate RPs received by this router. + +.. clicmd:: show ip pim bsr groups [vrf NAME] [json] + + Display the current list of multicast group mapping received by + this router from candidate RPs. + +.. clicmd:: show ip pim bsr rp-info [vrf NAME] [json] Display group-to-rp mappings received from E-BSR. diff --git a/doc/user/pimv6.rst b/doc/user/pimv6.rst index edf7a82015da..e4e28d71ab1e 100644 --- a/doc/user/pimv6.rst +++ b/doc/user/pimv6.rst @@ -80,6 +80,29 @@ PIMv6 Router cannot see data flowing in better than 30 second chunks. This command is vrf aware, to configure for a vrf, specify the vrf in the router pim6 block. +.. clicmd:: bsr candidate-bsr [priority (0-255)] [source [address X:X::X:X] | [interface INTERFACE] | [loopback] | [any]] + + Configure the router to advertise itself as a candidate PIM-SM BSR. The candidate + with the highest priority becomes the BSR for the domain (high wins). When priority is the + same for more than one candidate BSR, the candidate with the highest IP address + becomes the BSR of the domain. The address can be configured explicitly + via ``address``, or be selecting an interface name using ``interface``. + If ``any`` is configured the highest address from any interface will be selected. + By default, the highest loopback address is selected, which can also be + configured via ``loopback`` + +.. clicmd:: bsr candidate-rp [interval (1-4294967295) ] [priority (0-255)] [source [address X:X::X:X] | [interface INTERFACE] | [loopback] | [any]] + + Configure the router to advertise itself as a candidate PIM-SM RP. ``interval`` + can be used to configure the interval in seconds to send these advertisements. + The candidate with the lowest priority becomes the RP for the domain (low wins). + When priority is the same for more than one candidate RP, the candidate with + the highest IP address becomes the BSR of the domain. The address can be + configured explicitly via ``address``, or be selecting an interface name + using ``interface``. If ``any`` is configured the highest address from any + interface will be selected.By default, the highest loopback address is + selected, which can also be configured via ``loopback``. + .. clicmd:: spt-switchover infinity-and-beyond [prefix-list PLIST] On the last hop router if it is desired to not switch over to the SPT tree @@ -391,11 +414,28 @@ General multicast routing state Display total number of S,G mroutes and number of S,G mroutes installed into the kernel for all vrfs. -.. clicmd:: show ipv6 pim bsr +.. clicmd:: show ipv6 pim bsr [vrf NAME] [json] Display current bsr, its uptime and last received bsm age. -.. clicmd:: show ipv6 pim bsrp-info [vrf NAME] [json] +.. clicmd:: show ipv6 pim bsr candidate-bsr [vrf NAME] [json] + + Display information about the candidate BSR state on this router. + +.. clicmd:: show ipv6 pim bsr candidate-rp [vrf NAME] [json] + + Display information about the candidate RP state on this router. + +.. clicmd:: show ipv6 pim bsr candidate-rp-database [vrf NAME] [json] + + Display the current list of candidate RPs received by this router. + +.. clicmd:: show ipv6 pim bsr groups [vrf NAME] [json] + + Display the current list of multicast group mapping received by + this router from candidate RPs. + +.. clicmd:: show ipv6 pim bsr rp-info [vrf NAME] [json] Display group-to-rp mappings received from E-BSR. diff --git a/pimd/pim6_cmd.c b/pimd/pim6_cmd.c index f7a4e0e48112..f1ebdb554c0b 100644 --- a/pimd/pim6_cmd.c +++ b/pimd/pim6_cmd.c @@ -1259,6 +1259,62 @@ DEFPY (no_ipv6_pim_ucast_bsm, return pim_process_no_unicast_bsm_cmd(vty); } +DEFPY (pim6_bsr_candidate_bsr, + pim6_bsr_candidate_bsr_cmd, + "[no] bsr candidate-bsr [{priority (0-255)|source
}]", + NO_STR + BSR_STR + "Make this router a Candidate BSR\n" + "BSR Priority (higher wins)\n" + "BSR Priority (higher wins)\n" + "Specify IP address for BSR operation\n" + "Local address to use\n" + "Local address to use\n" + "Interface to pick address from\n" + "Interface to pick address from\n" + "Pick highest loopback address (default)\n" + "Pick highest address from any interface\n") +{ + return pim_process_bsr_candidate_cmd(vty, FRR_PIM_CAND_BSR_XPATH, no, + false, any, ifname, address_str, + priority_str, NULL); +} + +DEFPY (pim6_bsr_candidate_rp, + pim6_bsr_candidate_rp_cmd, + "[no] bsr candidate-rp [{priority (0-255)|interval (1-4294967295)|source
}]", + NO_STR + "Bootstrap Router configuration\n" + "Make this router a Candidate RP\n" + "RP Priority (lower wins)\n" + "RP Priority (lower wins)\n" + "Advertisement interval (seconds)\n" + "Advertisement interval (seconds)\n" + "Specify IP address for RP operation\n" + "Local address to use\n" + "Local address to use\n" + "Interface to pick address from\n" + "Interface to pick address from\n" + "Pick highest loopback address (default)\n" + "Pick highest address from any interface\n") +{ + return pim_process_bsr_candidate_cmd(vty, FRR_PIM_CAND_RP_XPATH, no, + true, any, ifname, address_str, + priority_str, interval_str); +} + +DEFPY (pim6_bsr_candidate_rp_group, + pim6_bsr_candidate_rp_group_cmd, + "[no] bsr candidate-rp group X:X::X:X/M", + NO_STR + "Bootstrap Router configuration\n" + "Make this router a Candidate RP\n" + "Configure groups to become candidate RP for\n" + "Multicast group prefix\n") +{ + return pim_process_bsr_crp_grp_cmd(vty, group_str, no); +} + DEFPY (pim6_ssmpingd, pim6_ssmpingd_cmd, "ssmpingd [X:X::X:X]$source", @@ -1719,6 +1775,90 @@ DEFPY (show_ipv6_pim_secondary, return pim_show_secondary_helper(vrf, vty); } +DEFPY (show_ipv6_pim_bsr_cand_bsr, + show_ipv6_pim_bsr_cand_bsr_cmd, + "show ipv6 pim bsr candidate-bsr [vrf NAME$vrfname] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + BSR_STR + "Current PIM router candidate BSR state\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, !!json); + + if (!vrf || !vrf->info) + return CMD_WARNING; + + return pim_show_bsr_cand_bsr(vrf, vty, !!json); +} + +DEFPY (show_ipv6_pim_bsr_cand_rp, + show_ipv6_pim_bsr_cand_rp_cmd, + "show ipv6 pim bsr candidate-rp [vrf VRF_NAME] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + BSR_STR + "Current PIM router candidate RP state\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + struct vrf *vrf = pim_cmd_lookup(vty, vrf_name); + + if (!vrf || !vrf->info) + return CMD_WARNING; + + return pim_show_bsr_cand_rp(vrf, vty, !!json); +} + +DEFPY (show_ipv6_pim_bsr_rpdb, + show_ipv6_pim_bsr_rpdb_cmd, + "show ipv6 pim bsr candidate-rp-database [vrf VRF_NAME] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + BSR_STR + "Candidate RPs database on this router (if it is the BSR)\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + struct vrf *vrf = pim_cmd_lookup(vty, vrf_name); + + if (!vrf || !vrf->info) + return CMD_WARNING; + + struct pim_instance *pim = vrf->info; + struct bsm_scope *scope = &pim->global_scope; + + return pim_crp_db_show(vty, scope, !!json); +} + +DEFPY (show_ipv6_pim_bsr_groups, + show_ipv6_pim_bsr_groups_cmd, + "show ipv6 pim bsr groups [vrf VRF_NAME] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + "boot-strap router information\n" + "Candidate RP groups\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + struct vrf *vrf = pim_cmd_lookup(vty, vrf_name); + + if (!vrf || !vrf->info) + return CMD_WARNING; + + struct pim_instance *pim = vrf->info; + struct bsm_scope *scope = &pim->global_scope; + + return pim_crp_groups_show(vty, scope, !!json); +} + + DEFPY (show_ipv6_pim_statistics, show_ipv6_pim_statistics_cmd, "show ipv6 pim [vrf NAME] statistics [interface WORD$word] [json$json]", @@ -2650,6 +2790,9 @@ void pim_cmd_init(void) install_element(PIM6_NODE, &no_pim6_rp_prefix_list_cmd); install_element(PIM6_NODE, &pim6_ssmpingd_cmd); install_element(PIM6_NODE, &no_pim6_ssmpingd_cmd); + install_element(PIM6_NODE, &pim6_bsr_candidate_rp_cmd); + install_element(PIM6_NODE, &pim6_bsr_candidate_rp_group_cmd); + install_element(PIM6_NODE, &pim6_bsr_candidate_bsr_cmd); install_element(CONFIG_NODE, &ipv6_mld_group_watermark_cmd); install_element(VRF_NODE, &ipv6_mld_group_watermark_cmd); @@ -2705,6 +2848,10 @@ void pim_cmd_init(void) install_element(VIEW_NODE, &show_ipv6_pim_rpf_cmd); install_element(VIEW_NODE, &show_ipv6_pim_rpf_vrf_all_cmd); install_element(VIEW_NODE, &show_ipv6_pim_secondary_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_bsr_cand_bsr_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_bsr_cand_rp_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_bsr_rpdb_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_bsr_groups_cmd); install_element(VIEW_NODE, &show_ipv6_pim_statistics_cmd); install_element(VIEW_NODE, &show_ipv6_pim_upstream_cmd); install_element(VIEW_NODE, &show_ipv6_pim_upstream_vrf_all_cmd); diff --git a/pimd/pim6_main.c b/pimd/pim6_main.c index 24443404eb5e..07b70ae2b3df 100644 --- a/pimd/pim6_main.c +++ b/pimd/pim6_main.c @@ -103,6 +103,7 @@ static const struct frr_yang_module_info *const pim6d_yang_modules[] = { &frr_routing_info, &frr_pim_info, &frr_pim_rp_info, + &frr_pim_candidate_info, &frr_gmp_info, }; diff --git a/pimd/pim_bsm.c b/pimd/pim_bsm.c index 2d451718a983..115aec893335 100644 --- a/pimd/pim_bsm.c +++ b/pimd/pim_bsm.c @@ -10,6 +10,17 @@ #include "config.h" #endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #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; } @@ -1452,7 +1599,594 @@ int pim_bsm_process(struct interface *ifp, pim_sgaddr *sg, uint8_t *buf, return 0; } -void pim_crp_nht_update(struct pim_instance *pim, struct pim_nexthop_cache *pnc) +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); + assertf(pim_addr_is_any(scope->current_bsr), "current_bsr=%pPA", + &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) { - /* stub for Candidate-RP */ + 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; } diff --git a/pimd/pim_bsm.h b/pimd/pim_bsm.h index fb09e3b1cc36..1eacc1be5770 100644 --- a/pimd/pim_bsm.h +++ b/pimd/pim_bsm.h @@ -21,6 +21,13 @@ #define PIM_BS_TIME 60 /* RFC 5059 - Sec 5 */ #define PIM_BSR_DEFAULT_TIMEOUT 130 /* RFC 5059 - Sec 5 */ +/* number of times to include rp-count = 0 ranges */ +#define PIM_BSR_DEAD_COUNT 3 + +#define PIM_CRP_ADV_TRIGCOUNT 3 +#define PIM_CRP_ADV_INTERVAL 60 +#define PIM_CRP_HOLDTIME 150 + /* These structures are only encoded IPv4 specific */ #define PIM_BSM_HDR_LEN sizeof(struct bsm_hdr) #define PIM_BSM_GRP_LEN sizeof(struct bsmmsg_grpinfo) @@ -33,19 +40,61 @@ * ============== */ -/* Non candidate BSR states */ -enum ncbsr_state { +/* BSR states + * + * Candidate BSR starts at BSR_PENDING, moves to AP or E depending on + * loss/win. Will never go into AA (because in that case it'd become BSR + * itself.) + * + * Non-Candidate BSR starts at NO_INFO, moves to AP & AA depending on + * a BSR being available or not. + */ +enum bsr_state { NO_INFO = 0, ACCEPT_ANY, - ACCEPT_PREFERRED + ACCEPT_PREFERRED, /* = same as C-BSR if candidate */ + BSR_PENDING, + BSR_ELECTED, +}; + +enum cand_addr { + CAND_ADDR_LO = 0, + CAND_ADDR_ANY, + CAND_ADDR_IFACE, + CAND_ADDR_EXPLICIT, }; +/* used separately for Cand-RP and Cand-BSR */ +struct cand_addrsel { + bool cfg_enable; + enum cand_addr cfg_mode : 8; + + /* only valid for mode==CAND_ADDR_IFACE */ + char cfg_ifname[IFNAMSIZ]; + /* only valid for mode==CAND_ADDR_EXPLICIT */ + pim_addr cfg_addr; + + /* running state updated based on above on zebra events */ + pim_addr run_addr; + bool run; +}; + + PREDECL_DLIST(bsm_frags); +PREDECL_RBTREE_UNIQ(cand_rp_groups); + +/* n*m "table" accessed both by-RP and by-group */ +PREDECL_RBTREE_UNIQ(bsr_crp_rps); +PREDECL_RBTREE_UNIQ(bsr_crp_groups); + +PREDECL_RBTREE_UNIQ(bsr_crp_rp_groups); +PREDECL_RBTREE_UNIQ(bsr_crp_group_rps); /* BSM scope - bsm processing is per scope */ struct bsm_scope { int sz_id; /* scope zone id */ - enum ncbsr_state state; /* non candidate BSR state */ + enum bsr_state state; /* BSR state */ + bool accept_nofwd_bsm; /* no fwd bsm accepted for scope */ pim_addr current_bsr; /* current elected BSR for the sz */ uint32_t current_bsr_prio; /* current BSR priority */ @@ -60,6 +109,93 @@ struct bsm_scope { struct route_table *bsrp_table; /* group2rp mapping rcvd from BSR */ struct event *bs_timer; /* Boot strap timer */ + + /* Candidate BSR config */ + struct cand_addrsel bsr_addrsel; + uint8_t cand_bsr_prio; + + /* Candidate BSR state */ + uint8_t current_cand_bsr_prio; + /* if nothing changed from Cand-RP data we received, less work... */ + bool elec_rp_data_changed; + + /* data that the E-BSR keeps - not to be confused with Candidate-RP + * stuff below. These two here are the info about all the Cand-RPs + * that we as a BSR received information for in Cand-RP-adv packets. + */ + struct bsr_crp_rps_head ebsr_rps[1]; + struct bsr_crp_groups_head ebsr_groups[1]; + + /* set if we have any group ranges where we're currently advertising + * rp-count = 0 (includes both ranges without any RPs as well as + * ranges with only NHT-unreachable RPs) + */ + bool ebsr_have_dead_pending; + unsigned int changed_bsm_trigger; + + struct event *t_ebsr_regen_bsm; + + /* Candidate RP config */ + struct cand_addrsel cand_rp_addrsel; + uint8_t cand_rp_prio; + unsigned int cand_rp_interval; /* default: PIM_CRP_ADV_INTERVAL=60 */ + /* holdtime is not configurable, always 2.5 * interval. */ + struct cand_rp_groups_head cand_rp_groups[1]; + + /* Candidate RP state */ + int unicast_sock; + struct event *unicast_read; + struct event *cand_rp_adv_timer; + unsigned int cand_rp_adv_trigger; /* # trigg. C-RP-Adv left to send */ + + /* for sending holdtime=0 zap */ + pim_addr cand_rp_prev_addr; +}; + +struct cand_rp_group { + struct cand_rp_groups_item item; + + prefix_pim p; +}; + +struct bsr_crp_group { + struct bsr_crp_groups_item item; + + prefix_pim range; + struct bsr_crp_group_rps_head rps[1]; + + size_t n_selected; + bool deleted_selected : 1; + + /* number of times we've advertised this range with rp-count = 0 */ + unsigned int dead_count; +}; + +struct bsr_crp_rp { + struct bsr_crp_rps_item item; + + pim_addr addr; + struct bsr_crp_rp_groups_head groups[1]; + + struct bsm_scope *scope; + struct event *t_hold; + time_t seen_first; + time_t seen_last; + + uint16_t holdtime; + uint8_t prio; + bool nht_ok; +}; + +/* "n * m" RP<->Group tie-in */ +struct bsr_crp_item { + struct bsr_crp_rp_groups_item r_g_item; + struct bsr_crp_group_rps_item g_r_item; + + struct bsr_crp_group *group; + struct bsr_crp_rp *rp; + + bool selected : 1; }; /* BSM packet (= fragment) - this is stored as list in bsm_frags inside scope @@ -200,6 +336,14 @@ struct bsmmsg_rpinfo { uint8_t reserved; } __attribute__((packed)); +struct cand_rp_msg { + uint8_t prefix_cnt; + uint8_t rp_prio; + uint16_t rp_holdtime; + pim_encoded_unicast rp_addr; + pim_encoded_group groups[0]; +} __attribute__((packed)); + /* API */ void pim_bsm_proc_init(struct pim_instance *pim); void pim_bsm_proc_free(struct pim_instance *pim); @@ -210,4 +354,39 @@ int pim_bsm_process(struct interface *ifp, pim_sgaddr *sg, uint8_t *buf, bool pim_bsm_new_nbr_fwd(struct pim_neighbor *neigh, struct interface *ifp); struct bsgrp_node *pim_bsm_get_bsgrp_node(struct bsm_scope *scope, struct prefix *grp); + +void pim_bsm_generate(struct bsm_scope *scope); +void pim_bsm_changed(struct bsm_scope *scope); +void pim_bsm_sent(struct bsm_scope *scope); +void pim_bsm_frags_free(struct bsm_scope *scope); + +bool pim_bsm_parse_install_g2rp(struct bsm_scope *scope, uint8_t *buf, + int buflen, uint16_t bsm_frag_tag); + +void pim_cand_bsr_apply(struct bsm_scope *scope); +void pim_cand_rp_apply(struct bsm_scope *scope); +void pim_cand_rp_trigger(struct bsm_scope *scope); +void pim_cand_rp_grp_add(struct bsm_scope *scope, const prefix_pim *p); +void pim_cand_rp_grp_del(struct bsm_scope *scope, const prefix_pim *p); + +void pim_cand_addrs_changed(void); + +int pim_crp_process(struct interface *ifp, pim_sgaddr *src_dst, uint8_t *buf, + uint32_t buf_size); + +struct pim_nexthop_cache; +void pim_crp_nht_update(struct pim_instance *pim, struct pim_nexthop_cache *pnc); + +void pim_crp_db_clear(struct bsm_scope *scope); +int pim_crp_db_show(struct vty *vty, struct bsm_scope *scope, bool json); +int pim_crp_groups_show(struct vty *vty, struct bsm_scope *scope, bool json); + +int pim_cand_config_write(struct pim_instance *pim, struct vty *vty); + +DECLARE_MTYPE(PIM_BSM_FRAG); + +DECLARE_MTYPE(PIM_BSM_FRAG); + +DECLARE_MTYPE(PIM_BSM_FRAG); + #endif diff --git a/pimd/pim_bsr_rpdb.c b/pimd/pim_bsr_rpdb.c new file mode 100644 index 000000000000..3ec9f99cd198 --- /dev/null +++ b/pimd/pim_bsr_rpdb.c @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* PIM RP database for BSR operation + * Copyright (C) 2021 David Lamparter for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "if.h" +#include "pimd.h" +#include "pim_iface.h" +#include "pim_instance.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" + +/* safety limits to prevent DoS/memory exhaustion attacks against the BSR + * + * The BSR is more susceptible than other PIM protocol operation because + * Candidate-RP messages are unicast to the BSR without any 2-way interaction + * and can thus be spoofed blindly(!) from anywhere in the internet. + * + * Everything else is on-link, multicast, or requires an adjacency - much + * harder to mess with. + */ + +/* total number of RPs we keep information for */ +static size_t bsr_max_rps = 1024; + +DEFINE_MTYPE_STATIC(PIMD, PIM_BSR_CRP, "PIM BSR C-RP"); +DEFINE_MTYPE_STATIC(PIMD, PIM_BSR_GROUP, "PIM BSR range"); +DEFINE_MTYPE_STATIC(PIMD, PIM_BSR_ITEM, "PIM BSR C-RP range item"); + +static int rp_cmp(const struct bsr_crp_rp *a, const struct bsr_crp_rp *b) +{ + return pim_addr_cmp(a->addr, b->addr); +} + +DECLARE_RBTREE_UNIQ(bsr_crp_rps, struct bsr_crp_rp, item, rp_cmp); + +static int group_cmp(const struct bsr_crp_group *a, + const struct bsr_crp_group *b) +{ + return prefix_cmp(&a->range, &b->range); +} + +DECLARE_RBTREE_UNIQ(bsr_crp_groups, struct bsr_crp_group, item, group_cmp); + +static int r_g_cmp(const struct bsr_crp_item *a, const struct bsr_crp_item *b) +{ + return prefix_cmp(&a->group->range, &b->group->range); +} + +DECLARE_RBTREE_UNIQ(bsr_crp_rp_groups, struct bsr_crp_item, r_g_item, r_g_cmp); + +static int g_r_cmp(const struct bsr_crp_item *a, const struct bsr_crp_item *b) +{ + const struct bsr_crp_rp *rp_a = a->rp, *rp_b = b->rp; + + /* NHT-failed RPs last */ + if (rp_a->nht_ok > rp_b->nht_ok) + return -1; + if (rp_a->nht_ok < rp_b->nht_ok) + return 1; + + /* This function determines BSR policy in what subset of the received + * RP candidates to advertise. The BSR is free to make its choices + * any way it deems useful + */ + + /* lower numeric values are better */ + if (rp_a->prio < rp_b->prio) + return -1; + if (rp_a->prio > rp_b->prio) + return 1; + + /* prefer older RP for less churn */ + if (rp_a->seen_first < rp_b->seen_first) + return -1; + if (rp_a->seen_first > rp_b->seen_first) + return 1; + + return pim_addr_cmp(rp_a->addr, rp_b->addr); +} + +DECLARE_RBTREE_UNIQ(bsr_crp_group_rps, struct bsr_crp_item, g_r_item, g_r_cmp); + +void pim_bsm_generate(struct bsm_scope *scope) +{ + struct bsm_frag *frag; + struct bsm_hdr *hdr; + bool have_dead = false; + + assertf(scope->state == BSR_ELECTED, "state=%d", scope->state); + + pim_bsm_frags_free(scope); + + struct bsr_crp_group *group; + struct bsr_crp_item *item; + struct bsr_crp_rp *rp; + size_t n_groups = 0, n_rps = 0; + + frr_each (bsr_crp_groups, scope->ebsr_groups, group) { + if (group->n_selected == 0) { + if (group->dead_count >= PIM_BSR_DEAD_COUNT) + continue; + + have_dead = true; + } else + group->dead_count = 0; + + n_groups++; + n_rps += group->n_selected; + } + + if (PIM_DEBUG_BSM) + zlog_debug("Generating BSM (%zu ranges, %zu RPs)", n_groups, n_rps); + + size_t datalen = PIM_MSG_HEADER_LEN + sizeof(*hdr) + + n_groups * sizeof(struct bsmmsg_grpinfo) + + n_rps * sizeof(struct bsmmsg_rpinfo); + + frag = XCALLOC(MTYPE_PIM_BSM_FRAG, sizeof(*frag) + datalen); + + uint8_t *pos = frag->data + PIM_MSG_HEADER_LEN; + uint8_t *end = frag->data + datalen; + + hdr = (struct bsm_hdr *)pos; + pos += sizeof(*hdr); + assert(pos <= end); + + /* TODO: make BSR hashmasklen configurable */ +#if PIM_IPV == 6 + hdr->hm_len = 126; +#else + hdr->hm_len = 30; +#endif + hdr->bsr_prio = scope->current_bsr_prio; + hdr->bsr_addr.family = PIM_IANA_AFI; + hdr->bsr_addr.reserved = 0; + hdr->bsr_addr.addr = scope->bsr_addrsel.run_addr; + + frr_each (bsr_crp_groups, scope->ebsr_groups, group) { + if (group->n_selected == 0 && + group->dead_count >= PIM_BSR_DEAD_COUNT) + continue; + + struct bsmmsg_grpinfo *gi = (struct bsmmsg_grpinfo *)pos; + + pos += sizeof(*gi); + assert(pos <= end); + + gi->group.family = PIM_MSG_ADDRESS_FAMILY; + gi->group.mask = group->range.prefixlen; + gi->group.addr = group->range.prefix; + + size_t n_added = 0; + + frr_each (bsr_crp_group_rps, group->rps, item) { + if (!item->selected) + break; + + struct bsmmsg_rpinfo *ri = (struct bsmmsg_rpinfo *)pos; + + pos += sizeof(*ri); + assert(pos <= end); + + rp = item->rp; + ri->rpaddr.family = PIM_MSG_ADDRESS_FAMILY; + ri->rpaddr.addr = rp->addr; + ri->rp_holdtime = htons(rp->holdtime); + ri->rp_pri = rp->prio; + + n_added++; + } + + gi->rp_count = group->n_selected; + gi->frag_rp_count = n_added; + assert(n_added == group->n_selected); + } + + assertf(pos == end, "end-pos=%td", end - pos); + frag->size = datalen; + + bsm_frags_add_head(scope->bsm_frags, frag); + + scope->ebsr_have_dead_pending = have_dead; + + /* + * The BSR itself doesn't receive (no loopback) the BSM msgs advertising + * the rps. Install the rps directly for the local BSR node. + */ + pim_bsm_parse_install_g2rp(scope, ((uint8_t *) hdr) + PIM_BSM_HDR_LEN, + datalen - PIM_BSM_HDR_LEN - PIM_MSG_HEADER_LEN, scope->bsm_frag_tag); + + pim_bsm_changed(scope); +} + +static void pim_bsm_generate_timer(struct event *t) +{ + struct bsm_scope *scope = EVENT_ARG(t); + + pim_bsm_generate(scope); +} + +static void pim_bsm_generate_sched(struct bsm_scope *scope) +{ + assertf(scope->state == BSR_ELECTED, "state=%d", scope->state); + + if (scope->t_ebsr_regen_bsm) + return; + + event_add_timer(router->master, pim_bsm_generate_timer, scope, 1, + &scope->t_ebsr_regen_bsm); +} + +void pim_bsm_sent(struct bsm_scope *scope) +{ + struct bsr_crp_group *group; + bool have_dead = false, changed = false; + + if (!scope->ebsr_have_dead_pending) + return; + + frr_each_safe (bsr_crp_groups, scope->ebsr_groups, group) { + if (group->n_selected != 0) + continue; + + if (group->dead_count < PIM_BSR_DEAD_COUNT) { + group->dead_count++; + have_dead = true; + continue; + } + + changed = true; + + if (bsr_crp_group_rps_count(group->rps)) + /* have RPs, but none selected */ + continue; + + /* no reason to keep this range anymore */ + bsr_crp_groups_del(scope->ebsr_groups, group); + bsr_crp_group_rps_fini(group->rps); + XFREE(MTYPE_PIM_BSR_GROUP, group); + continue; + } + + scope->ebsr_have_dead_pending = have_dead; + if (changed) + pim_bsm_generate_sched(scope); +} + +static void bsr_crp_reselect(struct bsm_scope *scope, + struct bsr_crp_group *group) +{ + bool changed = false; + struct bsr_crp_item *item; + size_t n_selected = 0; + + frr_each (bsr_crp_group_rps, group->rps, item) { + bool select = false; + + /* hardcode best 2 RPs for now */ + if (item->rp->nht_ok && n_selected < 2) { + select = true; + n_selected++; + } + + if (item->selected != select) { + changed = true; + item->selected = select; + } + } + + changed |= group->deleted_selected; + group->deleted_selected = false; + group->n_selected = n_selected; + + if (changed) + pim_bsm_generate_sched(scope); + + scope->elec_rp_data_changed |= changed; +} + +/* changing rp->nht_ok or rp->prio affects the sort order in group->rp + * lists, so need a delete & re-add if either changes + */ +static void pim_crp_nht_prio_change(struct bsr_crp_rp *rp, bool nht_ok, + uint8_t prio) +{ + struct bsr_crp_item *item; + + frr_each (bsr_crp_rp_groups, rp->groups, item) + bsr_crp_group_rps_del(item->group->rps, item); + + rp->prio = prio; + rp->nht_ok = nht_ok; + + frr_each (bsr_crp_rp_groups, rp->groups, item) { + bsr_crp_group_rps_add(item->group->rps, item); + bsr_crp_reselect(rp->scope, item->group); + } +} + +static struct bsr_crp_group *group_get(struct bsm_scope *scope, + prefix_pim *range) +{ + struct bsr_crp_group *group, ref; + + ref.range = *range; + group = bsr_crp_groups_find(scope->ebsr_groups, &ref); + if (!group) { + group = XCALLOC(MTYPE_PIM_BSR_GROUP, sizeof(*group)); + group->range = *range; + bsr_crp_group_rps_init(group->rps); + bsr_crp_groups_add(scope->ebsr_groups, group); + } + return group; +} + +static void pim_crp_update(struct bsr_crp_rp *rp, struct cand_rp_msg *msg, + size_t ngroups) +{ + struct bsr_crp_rp_groups_head oldgroups[1]; + struct bsr_crp_item *item, itemref; + struct bsr_crp_group *group, groupref; + + //struct bsm_scope *scope = rp->scope; + + bsr_crp_rp_groups_init(oldgroups); + bsr_crp_rp_groups_swap_all(rp->groups, oldgroups); + + itemref.rp = rp; + itemref.group = &groupref; + + assert(msg || ngroups == 0); + + for (size_t i = 0; i < ngroups; i++) { + if (msg->groups[i].family != PIM_MSG_ADDRESS_FAMILY) + continue; + if (msg->groups[i].bidir) + continue; + + prefix_pim pfx; + + pfx.family = PIM_AF; + pfx.prefixlen = msg->groups[i].mask; + pfx.prefix = msg->groups[i].addr; + +#if PIM_IPV == 4 + if (pfx.prefixlen < 4) + continue; + if (!IPV4_CLASS_DE(ntohl(pfx.prefix.s_addr))) + continue; +#endif + + apply_mask(&pfx); + + groupref.range = pfx; + item = bsr_crp_rp_groups_find(oldgroups, &itemref); + + if (item) { + bsr_crp_rp_groups_del(oldgroups, item); + bsr_crp_rp_groups_add(rp->groups, item); + continue; + } + + group = group_get(rp->scope, &pfx); + + item = XCALLOC(MTYPE_PIM_BSR_ITEM, sizeof(*item)); + item->rp = rp; + item->group = group; + + bsr_crp_group_rps_add(group->rps, item); + bsr_crp_rp_groups_add(rp->groups, item); + + bsr_crp_reselect(rp->scope, group); + } + + while ((item = bsr_crp_rp_groups_pop(oldgroups))) { + group = item->group; + if (item->selected) + group->deleted_selected = true; + + bsr_crp_group_rps_del(group->rps, item); + XFREE(MTYPE_PIM_BSR_ITEM, item); + + bsr_crp_reselect(rp->scope, group); + } + bsr_crp_rp_groups_fini(oldgroups); + + if (msg && msg->rp_prio != rp->prio) + pim_crp_nht_prio_change(rp, rp->nht_ok, msg->rp_prio); +} + +void pim_crp_nht_update(struct pim_instance *pim, struct pim_nexthop_cache *pnc) +{ + struct bsm_scope *scope = &pim->global_scope; + struct bsr_crp_rp *rp, ref; + bool ok; + + ref.addr = pnc->rpf.rpf_addr; + rp = bsr_crp_rps_find(scope->ebsr_rps, &ref); + assertf(rp, "addr=%pPA", &ref.addr); + + ok = CHECK_FLAG(pnc->flags, PIM_NEXTHOP_VALID); + if (ok == rp->nht_ok) + return; + + if (PIM_DEBUG_BSM) + zlog_debug("Candidate-RP %pPA NHT %s", &rp->addr, ok ? "UP" : "DOWN"); + pim_crp_nht_prio_change(rp, ok, rp->prio); +} + +static void pim_crp_free(struct pim_instance *pim, struct bsr_crp_rp *rp) +{ + EVENT_OFF(rp->t_hold); + pim_nht_candrp_del(pim, rp->addr); + bsr_crp_rp_groups_fini(rp->groups); + + XFREE(MTYPE_PIM_BSR_CRP, rp); +} + +static void pim_crp_expire(struct event *t) +{ + struct bsr_crp_rp *rp = EVENT_ARG(t); + struct pim_instance *pim = rp->scope->pim; + + if (PIM_DEBUG_BSM) + zlog_debug("Candidate-RP %pPA holdtime expired", &rp->addr); + + pim_crp_update(rp, NULL, 0); + + bsr_crp_rps_del(rp->scope->ebsr_rps, rp); + pim_crp_free(pim, rp); +} + +int pim_crp_process(struct interface *ifp, pim_sgaddr *src_dst, uint8_t *buf, + uint32_t buf_size) +{ + struct pim_interface *pim_ifp = NULL; + struct pim_instance *pim; + struct bsm_scope *scope; + + 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; + } + + //pim_ifp->pim_ifstat_bsm_rx++; + pim = pim_ifp->pim; + //pim->bsm_rcvd++; + + 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 cand_rp_msg))) { + if (PIM_DEBUG_BSM) + zlog_debug("%s: received buffer length of %d which is too small to properly decode", + __func__, buf_size); + return -1; + } + + scope = &pim->global_scope; + + if (scope->state < BSR_PENDING) { + if (PIM_DEBUG_BSM) + zlog_debug("received Candidate-RP message from %pPA while not BSR", + &src_dst->src); + return -1; + } + + size_t remain = buf_size; + struct cand_rp_msg *crp_hdr; + + buf += PIM_MSG_HEADER_LEN; + remain -= PIM_MSG_HEADER_LEN; + + crp_hdr = (struct cand_rp_msg *)buf; + buf += sizeof(*crp_hdr); + remain -= sizeof(*crp_hdr); + + size_t ngroups = crp_hdr->prefix_cnt; + + if (remain < ngroups * sizeof(struct pim_encoded_group_ipv4)) { + if (PIM_DEBUG_BSM) + zlog_debug("truncated Candidate-RP advertisement for RP %pPA from %pPA (too short for %zu groups)", + (pim_addr *)&crp_hdr->rp_addr.addr, + &src_dst->src, ngroups); + return -1; + } + + if (PIM_DEBUG_BSM) + zlog_debug("Candidate-RP: %pPA, prio=%u (from %pPA, %zu groups)", + (pim_addr *)&crp_hdr->rp_addr.addr, crp_hdr->rp_prio, + &src_dst->src, ngroups); + + + struct bsr_crp_rp *rp, ref; + + ref.addr = crp_hdr->rp_addr.addr; + rp = bsr_crp_rps_find(scope->ebsr_rps, &ref); + + if (!rp) { + if (bsr_crp_rps_count(scope->ebsr_rps) >= bsr_max_rps) { + zlog_err("BSR: number of tracked Candidate RPs (%zu) exceeds DoS-protection limit (%zu), dropping advertisement for RP %pPA (packet source %pPA)", + bsr_crp_rps_count(scope->ebsr_rps), + bsr_max_rps, (pim_addr *)&crp_hdr->rp_addr.addr, + &src_dst->src); + return -1; + } + + if (PIM_DEBUG_BSM) + zlog_debug("new Candidate-RP: %pPA (from %pPA)", + (pim_addr *)&crp_hdr->rp_addr.addr, + &src_dst->src); + + rp = XCALLOC(MTYPE_PIM_BSR_CRP, sizeof(*rp)); + rp->scope = scope; + rp->addr = crp_hdr->rp_addr.addr; + rp->prio = 255; + bsr_crp_rp_groups_init(rp->groups); + rp->seen_first = monotime(NULL); + + bsr_crp_rps_add(scope->ebsr_rps, rp); + rp->nht_ok = pim_nht_candrp_add(pim, rp->addr); + } + + rp->seen_last = monotime(NULL); + rp->holdtime = ntohs(crp_hdr->rp_holdtime); + + EVENT_OFF(rp->t_hold); + event_add_timer(router->master, pim_crp_expire, rp, + ntohs(crp_hdr->rp_holdtime), &rp->t_hold); + + pim_crp_update(rp, crp_hdr, ngroups); + return 0; +} + +void pim_crp_db_clear(struct bsm_scope *scope) +{ + struct bsr_crp_rp *rp; + struct bsr_crp_group *group; + struct bsr_crp_item *item; + + while ((rp = bsr_crp_rps_pop(scope->ebsr_rps))) { + while ((item = bsr_crp_rp_groups_pop(rp->groups))) { + group = item->group; + + if (item->selected) + group->deleted_selected = true; + + bsr_crp_group_rps_del(group->rps, item); + XFREE(MTYPE_PIM_BSR_ITEM, item); + } + pim_crp_free(scope->pim, rp); + } + + while ((group = bsr_crp_groups_pop(scope->ebsr_groups))) { + assertf(!bsr_crp_group_rps_count(group->rps), + "range=%pFX rp_count=%zu", &group->range, + bsr_crp_group_rps_count(group->rps)); + + bsr_crp_group_rps_fini(group->rps); + XFREE(MTYPE_PIM_BSR_GROUP, group); + } +} + +int pim_crp_db_show(struct vty *vty, struct bsm_scope *scope, bool json) +{ + struct bsr_crp_rp *rp; + struct bsr_crp_item *item; + + vty_out(vty, "RP/Group NHT Prio Uptime Hold\n"); + + frr_each (bsr_crp_rps, scope->ebsr_rps, rp) { + vty_out(vty, "%-15pPA %4s %4u %8ld %4lu\n", &rp->addr, + rp->nht_ok ? "UP" : "DOWN", rp->prio, + (long)(monotime(NULL) - rp->seen_first), + event_timer_remain_second(rp->t_hold)); + + frr_each (bsr_crp_rp_groups, rp->groups, item) + vty_out(vty, "%c %-18pFX\n", item->selected ? '>' : ' ', + &item->group->range); + } + + return CMD_SUCCESS; +} + +int pim_crp_groups_show(struct vty *vty, struct bsm_scope *scope, bool json) +{ + struct bsr_crp_group *group; + struct bsr_crp_item *item; + + if (scope->ebsr_have_dead_pending) + vty_out(vty, "have_dead_pending\n"); + + frr_each (bsr_crp_groups, scope->ebsr_groups, group) { + vty_out(vty, "%c %pFX", group->n_selected ? '^' : '!', + &group->range); + if (group->n_selected == 0) + vty_out(vty, " (dead %u)", group->dead_count); + + vty_out(vty, "\n"); + + frr_each (bsr_crp_group_rps, group->rps, item) + vty_out(vty, "%c %pPA\n", item->selected ? '>' : ' ', + &item->rp->addr); + } + + return CMD_SUCCESS; +} diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c index 633c46966ead..d71b17448741 100644 --- a/pimd/pim_cmd.c +++ b/pimd/pim_cmd.c @@ -66,27 +66,6 @@ static struct cmd_node debug_node = { .config_write = pim_debug_config_write, }; -static struct vrf *pim_cmd_lookup_vrf(struct vty *vty, struct cmd_token *argv[], - const int argc, int *idx, bool uj) -{ - struct vrf *vrf; - - if (argv_find(argv, argc, "NAME", idx)) - vrf = vrf_lookup_by_name(argv[*idx]->arg); - else - vrf = vrf_lookup_by_id(VRF_DEFAULT); - - if (!vrf) { - if (uj) - vty_json_empty(vty, NULL); - else - vty_out(vty, "Specified VRF: %s does not exist\n", - argv[*idx]->arg); - } - - return vrf; -} - static void pim_show_assert_helper(struct vty *vty, struct pim_interface *pim_ifp, struct pim_ifchannel *ch, time_t now) @@ -2864,7 +2843,7 @@ DEFPY (show_ip_pim_bsm_db, return pim_show_bsm_db_helper(vrf, vty, !!json); } -DEFPY (show_ip_pim_bsrp, +DEFPY_HIDDEN (show_ip_pim_bsrp, show_ip_pim_bsrp_cmd, "show ip pim bsrp-info [vrf NAME] [json$json]", SHOW_STR @@ -2877,6 +2856,109 @@ DEFPY (show_ip_pim_bsrp, return pim_show_group_rp_mappings_info_helper(vrf, vty, !!json); } +DEFPY (show_ip_pim_bsr_rpinfo, + show_ip_pim_bsr_rpinfo_cmd, + "show ip pim bsr rp-info [vrf NAME] [json$json]", + SHOW_STR + IP_STR + PIM_STR + BSR_STR + "PIM cached group-rp mappings information received from BSR\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + return pim_show_group_rp_mappings_info_helper(vrf, vty, !!json); +} + +DEFPY (show_ip_pim_bsr_cand_bsr, + show_ip_pim_bsr_cand_bsr_cmd, + "show ip pim bsr candidate-bsr [vrf NAME$vrfname] [json$json]", + SHOW_STR + IP_STR + PIM_STR + BSR_STR + "Current PIM router candidate BSR state\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, !!json); + + if (!vrf || !vrf->info) + return CMD_WARNING; + + return pim_show_bsr_cand_bsr(vrf, vty, !!json); +} + + +DEFPY (show_ip_pim_bsr_cand_rp, + show_ip_pim_bsr_cand_rp_cmd, + "show ip pim bsr candidate-rp [vrf NAME$vrfname] [json$json]", + SHOW_STR + IP_STR + PIM_STR + BSR_STR + "Current PIM router candidate RP state\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, !!json); + + + if (!vrf || !vrf->info) + return CMD_WARNING; + + + return pim_show_bsr_cand_rp(vrf, vty, !!json); +} + +DEFPY (show_ip_pim_bsr_rpdb, + show_ip_pim_bsr_rpdb_cmd, + "show ip pim bsr candidate-rp-database [vrf NAME$vrfname] [json$json]", + SHOW_STR + IP_STR + PIM_STR + BSR_STR + "Candidate RPs database on this router (if it is the BSR)\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf || !vrf->info) + return CMD_WARNING; + + struct pim_instance *pim = vrf->info; + struct bsm_scope *scope = &pim->global_scope; + + return pim_crp_db_show(vty, scope, !!json); +} + +DEFPY (show_ip_pim_bsr_groups, + show_ip_pim_bsr_groups_cmd, + "show ip pim bsr groups [vrf NAME$vrfname] [json$json]", + SHOW_STR + IP_STR + PIM_STR + "boot-strap router information\n" + "Candidate RP groups\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf || !vrf->info) + return CMD_WARNING; + + struct pim_instance *pim = vrf->info; + struct bsm_scope *scope = &pim->global_scope; + + return pim_crp_groups_show(vty, scope, !!json); +} + DEFPY (show_ip_pim_statistics, show_ip_pim_statistics_cmd, "show ip pim [vrf NAME] statistics [interface WORD$word] [json$json]", @@ -4376,6 +4458,62 @@ DEFPY_ATTR(no_ip_pim_rp_prefix_list, return ret; } +DEFPY (pim_bsr_candidate_bsr, + pim_bsr_candidate_bsr_cmd, + "[no] bsr candidate-bsr [{priority (0-255)|source
}]", + NO_STR + BSR_STR + "Make this router a Candidate BSR\n" + "BSR Priority (higher wins)\n" + "BSR Priority (higher wins)\n" + "Specify IP address for BSR operation\n" + "Local address to use\n" + "Local address to use\n" + "Interface to pick address from\n" + "Interface to pick address from\n" + "Pick highest loopback address (default)\n" + "Pick highest address from any interface\n") +{ + return pim_process_bsr_candidate_cmd(vty, FRR_PIM_CAND_BSR_XPATH, no, + false, any, ifname, address_str, + priority_str, NULL); +} + +DEFPY (pim_bsr_candidate_rp, + pim_bsr_candidate_rp_cmd, + "[no] bsr candidate-rp [{priority (0-255)|interval (1-4294967295)|source
}]", + NO_STR + BSR_STR + "Make this router a Candidate RP\n" + "RP Priority (lower wins)\n" + "RP Priority (lower wins)\n" + "Advertisement interval (seconds)\n" + "Advertisement interval (seconds)\n" + "Specify IP address for RP operation\n" + "Local address to use\n" + "Local address to use\n" + "Interface to pick address from\n" + "Interface to pick address from\n" + "Pick highest loopback address (default)\n" + "Pick highest address from any interface\n") +{ + return pim_process_bsr_candidate_cmd(vty, FRR_PIM_CAND_RP_XPATH, no, + true, any, ifname, address_str, + priority_str, interval_str); +} + +DEFPY (pim_bsr_candidate_rp_group, + pim_bsr_candidate_rp_group_cmd, + "[no] bsr candidate-rp group A.B.C.D/M", + NO_STR + BSR_STR + "Make this router a Candidate RP\n" + "Configure groups to become candidate RP for (At least one group must be configured)\n" + "Multicast group prefix\n") +{ + return pim_process_bsr_crp_grp_cmd(vty, group_str, no); +} + DEFPY (pim_ssm_prefix_list, pim_ssm_prefix_list_cmd, "ssm prefix-list PREFIXLIST4_NAME$plist", @@ -8550,6 +8688,10 @@ void pim_cmd_init(void) install_element(PIM_NODE, &no_pim_msdp_mesh_group_source_cmd); install_element(PIM_NODE, &no_pim_msdp_mesh_group_cmd); + install_element(PIM_NODE, &pim_bsr_candidate_rp_cmd); + install_element(PIM_NODE, &pim_bsr_candidate_rp_group_cmd); + install_element(PIM_NODE, &pim_bsr_candidate_bsr_cmd); + install_element(INTERFACE_NODE, &interface_ip_igmp_cmd); install_element(INTERFACE_NODE, &interface_no_ip_igmp_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_join_cmd); @@ -8670,6 +8812,11 @@ void pim_cmd_init(void) install_element(VIEW_NODE, &show_ip_pim_nexthop_lookup_cmd); install_element(VIEW_NODE, &show_ip_pim_bsrp_cmd); install_element(VIEW_NODE, &show_ip_pim_bsm_db_cmd); + install_element(VIEW_NODE, &show_ip_pim_bsr_rpinfo_cmd); + install_element(VIEW_NODE, &show_ip_pim_bsr_cand_bsr_cmd); + install_element(VIEW_NODE, &show_ip_pim_bsr_cand_rp_cmd); + install_element(VIEW_NODE, &show_ip_pim_bsr_rpdb_cmd); + install_element(VIEW_NODE, &show_ip_pim_bsr_groups_cmd); install_element(VIEW_NODE, &show_ip_pim_statistics_cmd); install_element(VIEW_NODE, &show_ip_msdp_peer_detail_cmd); install_element(VIEW_NODE, &show_ip_msdp_peer_detail_vrf_all_cmd); diff --git a/pimd/pim_cmd_common.c b/pimd/pim_cmd_common.c index a87f5b698184..567cb5b46a5b 100644 --- a/pimd/pim_cmd_common.c +++ b/pimd/pim_cmd_common.c @@ -3389,6 +3389,55 @@ int pim_process_no_unicast_bsm_cmd(struct vty *vty) FRR_PIM_AF_XPATH_VAL); } +/* helper for bsr/rp candidate commands*/ +int pim_process_bsr_candidate_cmd(struct vty *vty, const char *cand_str, + bool no, bool is_rp, bool any, + const char *ifname, const char *addr, + const char *prio, const char *interval) +{ + if (no) + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + else { + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + + if (any) + nb_cli_enqueue_change(vty, "./if-any", NB_OP_CREATE, + NULL); + else if (ifname) + nb_cli_enqueue_change(vty, "./interface", NB_OP_CREATE, + ifname); + else if (addr) + nb_cli_enqueue_change(vty, "./address", NB_OP_CREATE, + addr); + else + nb_cli_enqueue_change(vty, "./if-loopback", + NB_OP_CREATE, NULL); + + if (prio) + nb_cli_enqueue_change(vty, + (is_rp ? "./rp-priority" + : "./bsr-priority"), + NB_OP_MODIFY, prio); + + /* only valid for rp candidate case*/ + if (is_rp && interval) + nb_cli_enqueue_change(vty, "./advertisement-interval", + NB_OP_MODIFY, interval); + } + + return nb_cli_apply_changes(vty, "%s", cand_str); +} + +int pim_process_bsr_crp_grp_cmd(struct vty *vty, const char *grp, bool no) +{ + if (no) + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, grp); + else + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, grp); + + return nb_cli_apply_changes(vty, "%s/group-list", FRR_PIM_CAND_RP_XPATH); +} + static void show_scan_oil_stats(struct pim_instance *pim, struct vty *vty, time_t now) { @@ -4158,6 +4207,27 @@ struct vrf *pim_cmd_lookup(struct vty *vty, const char *name) return vrf; } +struct vrf *pim_cmd_lookup_vrf(struct vty *vty, struct cmd_token *argv[], + const int argc, int *idx, bool uj) +{ + struct vrf *vrf; + + if (argv_find(argv, argc, "NAME", idx)) + vrf = vrf_lookup_by_name(argv[*idx]->arg); + else + vrf = vrf_lookup_by_id(VRF_DEFAULT); + + if (!vrf) { + if (uj) + vty_json_empty(vty, NULL); + else + vty_out(vty, "Specified VRF: %s does not exist\n", + argv[*idx]->arg); + } + + return vrf; +} + void clear_mroute(struct pim_instance *pim) { struct pim_upstream *up; @@ -5188,6 +5258,12 @@ void pim_show_bsr(struct pim_instance *pim, struct vty *vty, bool uj) case ACCEPT_PREFERRED: strlcpy(bsr_state, "ACCEPT_PREFERRED", sizeof(bsr_state)); break; + case BSR_PENDING: + strlcpy(bsr_state, "BSR_PENDING", sizeof(bsr_state)); + break; + case BSR_ELECTED: + strlcpy(bsr_state, "BSR_ELECTED", sizeof(bsr_state)); + break; default: strlcpy(bsr_state, "", sizeof(bsr_state)); } @@ -5207,7 +5283,7 @@ void pim_show_bsr(struct pim_instance *pim, struct vty *vty, bool uj) } else { - vty_out(vty, "PIMv2 Bootstrap information\n"); + vty_out(vty, "PIMv2 Bootstrap Router information\n"); vty_out(vty, "Current preferred BSR address: %pPA\n", &pim->global_scope.current_bsr); vty_out(vty, @@ -5416,6 +5492,101 @@ int pim_show_group_rp_mappings_info_helper(const char *vrf, struct vty *vty, return CMD_SUCCESS; } +int pim_show_bsr_cand_rp(const struct vrf *vrf, struct vty *vty, bool uj) +{ + struct pim_instance *pim; + struct bsm_scope *scope; + json_object *jsondata = NULL; + + if (!vrf || !vrf->info) + return CMD_WARNING; + + pim = (struct pim_instance *)vrf->info; + scope = &pim->global_scope; + + if (!scope->cand_rp_addrsel.run) { + if (!!uj) + vty_out(vty, "{}\n"); + else + vty_out(vty, + "This router is not currently operating as Candidate RP\n"); + return CMD_SUCCESS; + } + + if (!!uj) { + jsondata = json_object_new_object(); + json_object_string_addf(jsondata, "address", "%pPA", + &scope->cand_rp_addrsel.run_addr); + json_object_int_add(jsondata, "priority", scope->cand_rp_prio); + json_object_int_add(jsondata, "nextAdvertisementMsec", + event_timer_remain_msec( + scope->cand_rp_adv_timer)); + + vty_json(vty, jsondata); + return CMD_SUCCESS; + } + + vty_out(vty, "Candidate-RP\nAddress: %pPA\nPriority: %u\n\n", + &scope->cand_rp_addrsel.run_addr, scope->cand_rp_prio); + vty_out(vty, "Next adv.: %lu msec\n", + event_timer_remain_msec(scope->cand_rp_adv_timer)); + + + return CMD_SUCCESS; +} + +int pim_show_bsr_cand_bsr(const struct vrf *vrf, struct vty *vty, bool uj) +{ + struct pim_instance *pim; + struct bsm_scope *scope; + json_object *jsondata = NULL; + + if (!vrf || !vrf->info) + return CMD_WARNING; + + pim = (struct pim_instance *)vrf->info; + scope = &pim->global_scope; + + if (!scope->bsr_addrsel.cfg_enable) { + if (!!uj) + vty_out(vty, "{}\n"); + else + vty_out(vty, + "This router is not currently operating as Candidate BSR\n"); + return CMD_SUCCESS; + } + + if (uj) { + char buf[INET_ADDRSTRLEN]; + + jsondata = json_object_new_object(); + inet_ntop(AF_INET, &scope->bsr_addrsel.run_addr, buf, + sizeof(buf)); + json_object_string_add(jsondata, "address", buf); + json_object_int_add(jsondata, "priority", scope->cand_bsr_prio); + json_object_boolean_add(jsondata, "elected", + pim->global_scope.state == BSR_ELECTED); + + vty_out(vty, "%s\n", + json_object_to_json_string_ext(jsondata, + JSON_C_TO_STRING_PRETTY)); + json_object_free(jsondata); + return CMD_SUCCESS; + } + + vty_out(vty, + "Candidate-BSR\nAddress: %pPA\nPriority: %u\nElected: %s\n", + &scope->bsr_addrsel.run_addr, scope->cand_bsr_prio, + (pim->global_scope.state == BSR_ELECTED) ? " Yes" : " No"); + + if (!pim_addr_cmp(scope->bsr_addrsel.run_addr, PIMADDR_ANY)) + vty_out(vty, + "\nThis router is not currently operating as Candidate BSR\n" + "Configure a BSR address to enable this feature\n\n"); + + return CMD_SUCCESS; +} + /* Display the bsm database details */ static void pim_show_bsm_db(struct pim_instance *pim, struct vty *vty, bool uj) { diff --git a/pimd/pim_cmd_common.h b/pimd/pim_cmd_common.h index da2e44be585b..339074b204aa 100644 --- a/pimd/pim_cmd_common.h +++ b/pimd/pim_cmd_common.h @@ -7,6 +7,8 @@ #ifndef PIM_CMD_COMMON_H #define PIM_CMD_COMMON_H +#define BSR_STR "Bootstrap Router configuration\n" + struct pim_upstream; struct pim_instance; @@ -53,6 +55,13 @@ int pim_process_bsm_cmd(struct vty *vty); int pim_process_no_bsm_cmd(struct vty *vty); int pim_process_unicast_bsm_cmd(struct vty *vty); int pim_process_no_unicast_bsm_cmd(struct vty *vty); + +int pim_process_bsr_candidate_cmd(struct vty *vty, const char *cand_str, + bool no, bool is_rp, bool any, + const char *ifname, const char *addr, + const char *prio, const char *interval); +int pim_process_bsr_crp_grp_cmd(struct vty *vty, const char *grp, bool no); + void json_object_pim_upstream_add(json_object *json, struct pim_upstream *up); void pim_show_rpf(struct pim_instance *pim, struct vty *vty, json_object *json); void pim_show_neighbors_secondary(struct pim_instance *pim, struct vty *vty); @@ -131,6 +140,8 @@ void show_mroute_summary(struct pim_instance *pim, struct vty *vty, json_object *json); int clear_ip_mroute_count_command(struct vty *vty, const char *name); struct vrf *pim_cmd_lookup(struct vty *vty, const char *name); +struct vrf *pim_cmd_lookup_vrf(struct vty *vty, struct cmd_token *argv[], + const int argc, int *idx, bool uj); void clear_mroute(struct pim_instance *pim); void clear_pim_statistics(struct pim_instance *pim); int clear_pim_interface_traffic(const char *vrf, struct vty *vty); @@ -182,6 +193,8 @@ int pim_show_interface_traffic_helper(const char *vrf, const char *if_name, void clear_pim_interfaces(struct pim_instance *pim); void pim_show_bsr(struct pim_instance *pim, struct vty *vty, bool uj); int pim_show_bsr_helper(const char *vrf, struct vty *vty, bool uj); +int pim_show_bsr_cand_bsr(const struct vrf *vrf, struct vty *vty, bool uj); +int pim_show_bsr_cand_rp(const struct vrf *vrf, struct vty *vty, bool uj); int pim_router_config_write(struct vty *vty); /* diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c index 45a2435ae550..125d35ac46d5 100644 --- a/pimd/pim_iface.c +++ b/pimd/pim_iface.c @@ -1844,6 +1844,8 @@ static int pim_ifp_up(struct interface *ifp) } } } + + pim_cand_addrs_changed(); return 0; } @@ -1880,6 +1882,7 @@ static int pim_ifp_down(struct interface *ifp) pim_ifstat_reset(ifp); } + pim_cand_addrs_changed(); return 0; } diff --git a/pimd/pim_instance.c b/pimd/pim_instance.c index a9eec9a9d25b..9a697c9209fa 100644 --- a/pimd/pim_instance.c +++ b/pimd/pim_instance.c @@ -253,6 +253,7 @@ void pim_vrf_terminate(void) if (!pim) continue; + pim_crp_db_clear(&pim->global_scope); pim_ssmpingd_destroy(pim); pim_instance_terminate(pim); diff --git a/pimd/pim_main.c b/pimd/pim_main.c index 8f2ce0bed335..f88aca719eda 100644 --- a/pimd/pim_main.c +++ b/pimd/pim_main.c @@ -68,6 +68,7 @@ static const struct frr_yang_module_info *const pimd_yang_modules[] = { &frr_routing_info, &frr_pim_info, &frr_pim_rp_info, + &frr_pim_candidate_info, &frr_gmp_info, }; diff --git a/pimd/pim_msg.h b/pimd/pim_msg.h index 56923b7ec18f..1f916af88196 100644 --- a/pimd/pim_msg.h +++ b/pimd/pim_msg.h @@ -148,6 +148,7 @@ struct pim_encoded_source_ipv6 { typedef struct pim_encoded_ipv4_unicast pim_encoded_unicast; typedef struct pim_encoded_group_ipv4 pim_encoded_group; typedef struct pim_encoded_source_ipv4 pim_encoded_source; +#define PIM_MSG_ADDRESS_FAMILY PIM_MSG_ADDRESS_FAMILY_IPV4 typedef struct ip ipv_hdr; #define IPV_SRC(ip_hdr) ((ip_hdr))->ip_src #define IPV_DST(ip_hdr) ((ip_hdr))->ip_dst @@ -156,6 +157,7 @@ typedef struct ip ipv_hdr; typedef struct pim_encoded_ipv6_unicast pim_encoded_unicast; typedef struct pim_encoded_group_ipv6 pim_encoded_group; typedef struct pim_encoded_source_ipv6 pim_encoded_source; +#define PIM_MSG_ADDRESS_FAMILY PIM_MSG_ADDRESS_FAMILY_IPV6 typedef struct ip6_hdr ipv_hdr; #define IPV_SRC(ip_hdr) ((ip_hdr))->ip6_src #define IPV_DST(ip_hdr) ((ip_hdr))->ip6_dst diff --git a/pimd/pim_nb.c b/pimd/pim_nb.c index c154c18afa8f..0e8aa48f9dde 100644 --- a/pimd/pim_nb.c +++ b/pimd/pim_nb.c @@ -385,6 +385,112 @@ const struct frr_yang_module_info frr_pim_rp_info = { } }; +const struct frr_yang_module_info frr_pim_candidate_info = { + .name = "frr-pim-candidate", + .nodes = { + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-bsr", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-bsr/bsr-priority", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_priority_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-bsr/address", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-bsr/interface", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-bsr/if-loopback", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-bsr/if-any", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_destroy, + } + }, + + /* Candidate-RP */ + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-rp", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-rp/rp-priority", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_priority_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-rp/advertisement-interval", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_adv_interval_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-rp/group-list", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_group_list_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_group_list_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-rp/address", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-rp/interface", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-rp/if-loopback", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-candidate:candidate-rp/if-any", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; + /* clang-format off */ const struct frr_yang_module_info frr_gmp_info = { .name = "frr-gmp", diff --git a/pimd/pim_nb.h b/pimd/pim_nb.h index fc4c11cea94d..55883ad29ad6 100644 --- a/pimd/pim_nb.h +++ b/pimd/pim_nb.h @@ -9,6 +9,7 @@ extern const struct frr_yang_module_info frr_pim_info; extern const struct frr_yang_module_info frr_pim_rp_info; +extern const struct frr_yang_module_info frr_pim_candidate_info; extern const struct frr_yang_module_info frr_gmp_info; /* frr-pim prototypes*/ @@ -159,6 +160,40 @@ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_prefix_list_destroy( struct nb_cb_destroy_args *args); +/* frr-cand-bsr */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_priority_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_destroy( + struct nb_cb_destroy_args *args); + +/* frr-candidate */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_priority_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_adv_interval_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_group_list_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_group_list_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_destroy( + struct nb_cb_destroy_args *args); + /* frr-gmp prototypes*/ int lib_interface_gmp_address_family_create( struct nb_cb_create_args *args); @@ -204,6 +239,9 @@ int routing_control_plane_protocols_name_validate( #define FRR_PIM_AF_XPATH_VAL "frr-routing:ipv6" #endif +#define FRR_PIM_CAND_RP_XPATH "./frr-pim-candidate:candidate-rp" +#define FRR_PIM_CAND_BSR_XPATH "./frr-pim-candidate:candidate-bsr" + #define FRR_PIM_VRF_XPATH \ "/frr-routing:routing/control-plane-protocols/" \ "control-plane-protocol[type='%s'][name='%s'][vrf='%s']/" \ diff --git a/pimd/pim_nb_config.c b/pimd/pim_nb_config.c index 0a167164c4b5..0c7481c27a8a 100644 --- a/pimd/pim_nb_config.c +++ b/pimd/pim_nb_config.c @@ -2679,6 +2679,391 @@ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp return NB_OK; } +static void yang_addrsel(struct cand_addrsel *addrsel, + const struct lyd_node *node) +{ + memset(addrsel->cfg_ifname, 0, sizeof(addrsel->cfg_ifname)); + addrsel->cfg_addr = PIMADDR_ANY; + + if (yang_dnode_exists(node, "if-any")) { + addrsel->cfg_mode = CAND_ADDR_ANY; + } else if (yang_dnode_exists(node, "address")) { + addrsel->cfg_mode = CAND_ADDR_EXPLICIT; + yang_dnode_get_pimaddr(&addrsel->cfg_addr, node, "address"); + } else if (yang_dnode_exists(node, "interface")) { + addrsel->cfg_mode = CAND_ADDR_IFACE; + strlcpy(addrsel->cfg_ifname, + yang_dnode_get_string(node, "interface"), + sizeof(addrsel->cfg_ifname)); + } else if (yang_dnode_exists(node, "if-loopback")) { + addrsel->cfg_mode = CAND_ADDR_LO; + } +} + +static int candidate_bsr_addrsel(struct bsm_scope *scope, + const struct lyd_node *cand_bsr_node) +{ + yang_addrsel(&scope->bsr_addrsel, cand_bsr_node); + pim_cand_bsr_apply(scope); + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_create( + struct nb_cb_create_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + scope->bsr_addrsel.cfg_enable = true; + scope->cand_bsr_prio = yang_dnode_get_uint8(args->dnode, + "bsr-priority"); + + candidate_bsr_addrsel(scope, args->dnode); + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_destroy( + struct nb_cb_destroy_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + scope->bsr_addrsel.cfg_enable = false; + + pim_cand_bsr_apply(scope); + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_priority_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + scope->cand_bsr_prio = yang_dnode_get_uint8(args->dnode, NULL); + + /* FIXME: force prio update */ + candidate_bsr_addrsel(scope, args->dnode); + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_create( + struct nb_cb_create_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + const struct lyd_node *cand_bsr_node; + + cand_bsr_node = yang_dnode_get_parent(args->dnode, "candidate-bsr"); + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + return candidate_bsr_addrsel(scope, cand_bsr_node); + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + const struct lyd_node *cand_bsr_node; + + cand_bsr_node = yang_dnode_get_parent(args->dnode, "candidate-bsr"); + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + return candidate_bsr_addrsel(scope, cand_bsr_node); + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_addrsel_destroy( + struct nb_cb_destroy_args *args) +{ + /* nothing to do here, we'll get a CREATE for something else */ + return NB_OK; +} + +static int candidate_rp_addrsel(struct bsm_scope *scope, + const struct lyd_node *cand_rp_node) +{ + yang_addrsel(&scope->cand_rp_addrsel, cand_rp_node); + pim_cand_rp_apply(scope); + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_create( + struct nb_cb_create_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + scope->cand_rp_addrsel.cfg_enable = true; + scope->cand_rp_prio = yang_dnode_get_uint8(args->dnode, + "rp-priority"); + scope->cand_rp_interval = + yang_dnode_get_uint32(args->dnode, + "advertisement-interval"); + + candidate_rp_addrsel(scope, args->dnode); + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_destroy( + struct nb_cb_destroy_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + scope->cand_rp_addrsel.cfg_enable = false; + + pim_cand_rp_apply(scope); + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_priority_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + scope->cand_rp_prio = yang_dnode_get_uint8(args->dnode, NULL); + + pim_cand_rp_trigger(scope); + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_adv_interval_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + scope->cand_rp_interval = yang_dnode_get_uint32(args->dnode, + NULL); + + pim_cand_rp_trigger(scope); + break; + } + + return NB_OK; +} + +#if PIM_IPV == 4 +#define yang_dnode_get_pim_p yang_dnode_get_ipv4p +#else +#define yang_dnode_get_pim_p yang_dnode_get_ipv6p +#endif + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_group_list_create( + struct nb_cb_create_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + prefix_pim p; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + yang_dnode_get_pim_p(&p, args->dnode, "."); + pim_cand_rp_grp_add(scope, &p); + break; + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_group_list_destroy( + struct nb_cb_destroy_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + prefix_pim p; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + yang_dnode_get_pim_p(&p, args->dnode, "."); + pim_cand_rp_grp_del(scope, &p); + break; + } + return NB_OK; +} + +static int candidate_rp_addrsel_common(enum nb_event event, + const struct lyd_node *dnode) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct bsm_scope *scope; + + dnode = lyd_parent(dnode); + + switch (event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(dnode, NULL, true); + pim = vrf->info; + scope = &pim->global_scope; + + candidate_rp_addrsel(scope, dnode); + break; + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_create( + struct nb_cb_create_args *args) +{ + return candidate_rp_addrsel_common(args->event, args->dnode); +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_modify( + struct nb_cb_modify_args *args) +{ + return candidate_rp_addrsel_common(args->event, args->dnode); +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_rp_addrsel_destroy( + struct nb_cb_destroy_args *args) +{ + /* nothing to do here - we'll get a create or modify event too */ + return NB_OK; +} + /* * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family */ diff --git a/pimd/pim_nht.c b/pimd/pim_nht.c index 57dcff3b4710..030b933e0948 100644 --- a/pimd/pim_nht.c +++ b/pimd/pim_nht.c @@ -343,7 +343,8 @@ bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr, if (!nbr) continue; - return znh->ifindex == src_ifp->ifindex; + return znh->ifindex == src_ifp->ifindex && + (!pim_addr_cmp(znh->nexthop_addr, src_ip)); } return false; } @@ -404,13 +405,12 @@ bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr, return true; /* MRIB (IGP) may be pointing at a router where PIM is down */ - nbr = pim_neighbor_find(ifp, nhaddr, true); - if (!nbr) continue; - return nh->ifindex == src_ifp->ifindex; + return nh->ifindex == src_ifp->ifindex && + (!pim_addr_cmp(nhaddr, src_ip)); } return false; } diff --git a/pimd/pim_pim.c b/pimd/pim_pim.c index 6a7e8924f2f6..a41bbacea799 100644 --- a/pimd/pim_pim.c +++ b/pimd/pim_pim.c @@ -13,7 +13,6 @@ #include "network.h" #include "pimd.h" -#include "pim_instance.h" #include "pim_pim.h" #include "pim_time.h" #include "pim_iface.h" @@ -139,7 +138,7 @@ static bool pim_pkt_dst_addr_ok(enum pim_msg_type type, pim_addr addr) } int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len, - pim_sgaddr sg) + pim_sgaddr sg, bool is_mcast) { struct iovec iov[2], *iovp = iov; #if PIM_IPV == 4 @@ -274,6 +273,22 @@ int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len, return -1; } + if (!is_mcast) { + if (header->type == PIM_MSG_TYPE_CANDIDATE) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("%s %s: Candidate RP PIM message from %pPA on %s", + __FILE__, __func__, &sg.src, + ifp->name); + + return pim_crp_process(ifp, &sg, pim_msg, pim_msg_len); + } + + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "ignoring link traffic on BSR unicast socket"); + return -1; + } + switch (header->type) { case PIM_MSG_TYPE_HELLO: return pim_hello_recv(ifp, sg.src, pim_msg + PIM_MSG_HEADER_LEN, @@ -322,6 +337,13 @@ int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len, return pim_bsm_process(ifp, &sg, pim_msg, pim_msg_len, no_fwd); break; + case PIM_MSG_TYPE_CANDIDATE: + /* return pim_crp_process(ifp, &sg, pim_msg, pim_msg_len); */ + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "ignoring Candidate-RP packet on multicast socket"); + return 0; + default: if (PIM_DEBUG_PIM_PACKETS) { zlog_debug( @@ -332,13 +354,9 @@ int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len, } } -static void pim_sock_read_on(struct interface *ifp); - -static void pim_sock_read(struct event *t) +int pim_sock_read_helper(int fd, struct pim_instance *pim, bool is_mcast) { - struct interface *ifp, *orig_ifp; - struct pim_interface *pim_ifp; - int fd; + struct interface *ifp = NULL; struct sockaddr_storage from; struct sockaddr_storage to; socklen_t fromlen = sizeof(from); @@ -346,16 +364,9 @@ static void pim_sock_read(struct event *t) uint8_t buf[PIM_PIM_BUFSIZE_READ]; int len; ifindex_t ifindex = -1; - int result = -1; /* defaults to bad */ - static long long count = 0; - int cont = 1; - - orig_ifp = ifp = EVENT_ARG(t); - fd = EVENT_FD(t); - - pim_ifp = ifp->info; + int i; - while (cont) { + for (i = 0; i < router->packet_process; i++) { pim_sgaddr sg; len = pim_socket_recvfromto(fd, buf, sizeof(buf), &from, @@ -369,7 +380,7 @@ static void pim_sock_read(struct event *t) if (PIM_DEBUG_PIM_PACKETS) zlog_debug("Received errno: %d %s", errno, safe_strerror(errno)); - goto done; + return -1; } /* @@ -378,14 +389,21 @@ static void pim_sock_read(struct event *t) * the right ifindex, so just use it. We know * it's the right interface because we bind to it */ - ifp = if_lookup_by_index(ifindex, pim_ifp->pim->vrf->vrf_id); - if (!ifp || !ifp->info) { + if (pim != NULL) + ifp = if_lookup_by_index(ifindex, pim->vrf->vrf_id); + + /* + * unicast BSM pkts (C-RP) may arrive on non pim interfaces + * mcast pkts are only expected in pim interfaces + */ + if (!ifp || (is_mcast && !ifp->info)) { if (PIM_DEBUG_PIM_PACKETS) - zlog_debug( - "%s: Received incoming pim packet on interface(%s:%d) not yet configured for pim", - __func__, ifp ? ifp->name : "Unknown", - ifindex); - goto done; + zlog_debug("%s: Received incoming pim packet on interface(%s:%d)%s", + __func__, + ifp ? ifp->name : "Unknown", ifindex, + is_mcast ? " not yet configured for pim" + : ""); + return -1; } #if PIM_IPV == 4 sg.src = ((struct sockaddr_in *)&from)->sin_addr; @@ -395,27 +413,34 @@ static void pim_sock_read(struct event *t) sg.grp = ((struct sockaddr_in6 *)&to)->sin6_addr; #endif - int fail = pim_pim_packet(ifp, buf, len, sg); + int fail = pim_pim_packet(ifp, buf, len, sg, is_mcast); if (fail) { if (PIM_DEBUG_PIM_PACKETS) zlog_debug("%s: pim_pim_packet() return=%d", __func__, fail); - goto done; + return -1; } - - count++; - if (count % router->packet_process == 0) - cont = 0; } + return 0; +} + +static void pim_sock_read_on(struct interface *ifp); - result = 0; /* good */ +static void pim_sock_read(struct event *t) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + int fd; + + ifp = EVENT_ARG(t); + fd = EVENT_FD(t); -done: - pim_sock_read_on(orig_ifp); + pim_ifp = ifp->info; - if (result) { + if (pim_sock_read_helper(fd, pim_ifp->pim, true) == 0) ++pim_ifp->pim_ifstat_hello_recvfail; - } + + pim_sock_read_on(ifp); } static void pim_sock_read_on(struct interface *ifp) diff --git a/pimd/pim_pim.h b/pimd/pim_pim.h index 35e693013ac1..39b27bceda37 100644 --- a/pimd/pim_pim.h +++ b/pimd/pim_pim.h @@ -10,6 +10,7 @@ #include #include "if.h" +#include "pim_instance.h" #define PIM_PIM_BUFSIZE_READ (20000) #define PIM_PIM_BUFSIZE_WRITE (20000) @@ -42,10 +43,12 @@ void pim_hello_restart_now(struct interface *ifp); void pim_hello_restart_triggered(struct interface *ifp); int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len, - pim_sgaddr sg); + pim_sgaddr sg, bool is_mcast); int pim_msg_send(int fd, pim_addr src, pim_addr dst, uint8_t *pim_msg, int pim_msg_size, struct interface *ifp); int pim_hello_send(struct interface *ifp, uint16_t holdtime); + +int pim_sock_read_helper(int fd, struct pim_instance *pim, bool is_mcast); #endif /* PIM_PIM_H */ diff --git a/pimd/pim_sock.h b/pimd/pim_sock.h index 04ab86474413..1cf01b31d681 100644 --- a/pimd/pim_sock.h +++ b/pimd/pim_sock.h @@ -26,6 +26,7 @@ struct pim_instance; int pim_socket_bind(int fd, struct interface *ifp); void pim_socket_ip_hdr(int fd); +int pim_setsockopt_packetinfo(int fd); int pim_socket_raw(int protocol); int pim_socket_mcast(int protocol, pim_addr ifaddr, struct interface *ifp, uint8_t loop); diff --git a/pimd/pim_tlv.c b/pimd/pim_tlv.c index c463fa227c4c..dd1bf2c05927 100644 --- a/pimd/pim_tlv.c +++ b/pimd/pim_tlv.c @@ -19,12 +19,6 @@ #include "pim_iface.h" #include "pim_addr.h" -#if PIM_IPV == 4 -#define PIM_MSG_ADDRESS_FAMILY PIM_MSG_ADDRESS_FAMILY_IPV4 -#else -#define PIM_MSG_ADDRESS_FAMILY PIM_MSG_ADDRESS_FAMILY_IPV6 -#endif - uint8_t *pim_tlv_append_uint16(uint8_t *buf, const uint8_t *buf_pastend, uint16_t option_type, uint16_t option_value) { diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c index 9cf4bb3e874a..e5324dd8733f 100644 --- a/pimd/pim_vty.c +++ b/pimd/pim_vty.c @@ -182,6 +182,7 @@ int pim_global_config_write_worker(struct pim_instance *pim, struct vty *vty) } writes += pim_rp_config_write(pim, vty); + writes += pim_cand_config_write(pim, vty); if (pim->vrf->vrf_id == VRF_DEFAULT) { if (router->register_suppress_time diff --git a/pimd/pim_zebra.c b/pimd/pim_zebra.c index e25eafc28e78..ce4d85a2c848 100644 --- a/pimd/pim_zebra.c +++ b/pimd/pim_zebra.c @@ -157,6 +157,8 @@ static int pim_zebra_if_address_add(ZAPI_CALLBACK_ARGS) pim_if_addr_add_all(ifp); } } + + pim_cand_addrs_changed(); return 0; } @@ -205,6 +207,8 @@ static int pim_zebra_if_address_del(ZAPI_CALLBACK_ARGS) } connected_free(&c); + + pim_cand_addrs_changed(); return 0; } diff --git a/pimd/subdir.am b/pimd/subdir.am index 1e787a3525c6..48f1e3b7243d 100644 --- a/pimd/subdir.am +++ b/pimd/subdir.am @@ -17,6 +17,7 @@ pim_common = \ pimd/pim_assert.c \ pimd/pim_bfd.c \ pimd/pim_bsm.c \ + pimd/pim_bsr_rpdb.c \ pimd/pim_cmd_common.c \ pimd/pim_errors.c \ pimd/pim_hello.c \ @@ -76,6 +77,7 @@ pimd_pimd_SOURCES = \ nodist_pimd_pimd_SOURCES = \ yang/frr-pim.yang.c \ yang/frr-pim-rp.yang.c \ + yang/frr-pim-candidate.yang.c \ yang/frr-gmp.yang.c \ # end @@ -89,6 +91,7 @@ pimd_pim6d_SOURCES = \ nodist_pimd_pim6d_SOURCES = \ yang/frr-pim.yang.c \ yang/frr-pim-rp.yang.c \ + yang/frr-pim-candidate.yang.c \ yang/frr-gmp.yang.c \ # end @@ -160,12 +163,12 @@ clippy_scan += \ # end pimd_pimd_CFLAGS = $(AM_CFLAGS) -DPIM_IPV=4 -pimd_pimd_LDADD = lib/libfrr.la $(LIBCAP) +pimd_pimd_LDADD = lib/libfrr.la $(LIBCAP) -lm if PIM6D sbin_PROGRAMS += pimd/pim6d pimd_pim6d_CFLAGS = $(AM_CFLAGS) -DPIM_IPV=6 -pimd_pim6d_LDADD = lib/libfrr.la $(LIBCAP) +pimd_pim6d_LDADD = lib/libfrr.la $(LIBCAP) -lm endif pimd_test_igmpv3_join_CFLAGS = $(AM_CFLAGS) -DPIM_IPV=4 diff --git a/tests/topotests/lib/pim.py b/tests/topotests/lib/pim.py index 71e36b62295f..eb3723be42f8 100644 --- a/tests/topotests/lib/pim.py +++ b/tests/topotests/lib/pim.py @@ -1607,7 +1607,7 @@ def verify_pim_rp_info( if type(group_addresses) is not list: group_addresses = [group_addresses] - if type(oif) is not list: + if oif is not None and type(oif) is not list: oif = [oif] for grp in group_addresses: diff --git a/tests/topotests/pim_cand_rp_bsr/__init__.py b/tests/topotests/pim_cand_rp_bsr/__init__.py new file mode 100755 index 000000000000..e69de29bb2d1 diff --git a/tests/topotests/pim_cand_rp_bsr/r1/frr.conf b/tests/topotests/pim_cand_rp_bsr/r1/frr.conf new file mode 100644 index 000000000000..d0aa3d529fb2 --- /dev/null +++ b/tests/topotests/pim_cand_rp_bsr/r1/frr.conf @@ -0,0 +1,25 @@ +! +hostname r1 +password zebra +log file /tmp/r1-frr.log +! +!debug pim packet +!debug pim bsm +! +ip route 0.0.0.0/0 10.0.0.4 +! +interface r1-eth0 + ip address 10.0.0.1/24 + ip igmp + ip pim +! +interface r1-eth1 + ip address 10.0.1.1/24 + ip igmp + ip pim +! +router pim + bsr candidate-bsr priority 200 source address 10.0.0.1 +! +ip forwarding +! diff --git a/tests/topotests/pim_cand_rp_bsr/r2/frr.conf b/tests/topotests/pim_cand_rp_bsr/r2/frr.conf new file mode 100644 index 000000000000..741c839f1923 --- /dev/null +++ b/tests/topotests/pim_cand_rp_bsr/r2/frr.conf @@ -0,0 +1,22 @@ +! +hostname r2 +password zebra +log file /tmp/r2-frr.log +! +ip route 0.0.0.0/0 10.0.0.4 +! +interface r2-eth0 + ip address 10.0.0.2/24 + ip igmp + ip pim +! +interface r2-eth1 + ip address 10.0.2.2/24 + ip igmp + ip pim +! +router pim + bsr candidate-bsr priority 100 source address 10.0.0.2 +! +ip forwarding +! diff --git a/tests/topotests/pim_cand_rp_bsr/r3/frr.conf b/tests/topotests/pim_cand_rp_bsr/r3/frr.conf new file mode 100644 index 000000000000..bd5c8ce93ff9 --- /dev/null +++ b/tests/topotests/pim_cand_rp_bsr/r3/frr.conf @@ -0,0 +1,32 @@ +! +hostname r3 +password zebra +log file /tmp/r3-frr.log +! +!debug pim packet +!debug pim bsm +! +ip route 0.0.0.0/0 10.0.3.4 +ip route 10.0.6.0/24 10.0.3.6 +! +interface r3-eth0 + ip address 10.0.1.3/24 + ip igmp + ip pim +! +interface r3-eth1 + ip address 10.0.3.3/24 + ip igmp + ip pim +! +interface r3-eth2 + ip address 10.0.4.3/24 + ip igmp + ip pim +! +router pim + bsr candidate-rp group 239.0.0.0/16 + bsr candidate-rp priority 10 source address 10.0.3.3 +! +ip forwarding +! diff --git a/tests/topotests/pim_cand_rp_bsr/r4/frr.conf b/tests/topotests/pim_cand_rp_bsr/r4/frr.conf new file mode 100644 index 000000000000..825b227728d1 --- /dev/null +++ b/tests/topotests/pim_cand_rp_bsr/r4/frr.conf @@ -0,0 +1,37 @@ +! +hostname r4 +password zebra +log file /tmp/r4-frr.log +! +ip route 10.0.1.0/24 10.0.0.1 +ip route 10.0.4.0/24 10.0.3.3 +ip route 10.0.6.0/24 10.0.3.6 +! +interface r4-eth0 + ip address 10.0.2.4/24 + ip igmp + ip pim +! +interface r4-eth1 + ip address 10.0.3.4/24 + ip igmp + ip pim +! +interface r4-eth2 + ip address 10.0.5.4/24 + ip igmp + ip pim +! +interface r4-eth3 + ip address 10.0.0.4/24 + ip igmp + ip pim +! +router pim + bsr candidate-rp group 239.0.0.0/24 + bsr candidate-rp group 239.0.0.0/16 + bsr candidate-rp group 239.0.0.0/8 + bsr candidate-rp priority 20 source address 10.0.3.4 +! +ip forwarding +! diff --git a/tests/topotests/pim_cand_rp_bsr/r5/frr.conf b/tests/topotests/pim_cand_rp_bsr/r5/frr.conf new file mode 100644 index 000000000000..c934717d0820 --- /dev/null +++ b/tests/topotests/pim_cand_rp_bsr/r5/frr.conf @@ -0,0 +1,17 @@ +! +hostname r5 +password zebra +log file /tmp/r5-frr.log +! +ip route 0.0.0.0/0 10.0.4.3 +! +interface r5-eth0 + ip address 10.0.4.5/24 + ip igmp + ip pim +! +interface r5-eth1 + ip address 10.0.6.5/24 +! +ip forwarding +! diff --git a/tests/topotests/pim_cand_rp_bsr/r6/frr.conf b/tests/topotests/pim_cand_rp_bsr/r6/frr.conf new file mode 100644 index 000000000000..fd9d1eb5c422 --- /dev/null +++ b/tests/topotests/pim_cand_rp_bsr/r6/frr.conf @@ -0,0 +1,22 @@ +! +hostname r6 +password zebra +log file /tmp/r6-frr.log +! +ip route 0.0.0.0/0 10.0.6.6 +! +interface r6-eth0 + ip address 10.0.5.6/24 + ip igmp + ip pim +! +interface r6-eth1 + ip address 10.0.6.6/24 +! +interface r6-eth2 + ip address 10.0.3.6/24 + ip igmp + ip pim +! +ip forwarding +! diff --git a/tests/topotests/pim_cand_rp_bsr/test_pim_cand_rp_bsr.py b/tests/topotests/pim_cand_rp_bsr/test_pim_cand_rp_bsr.py new file mode 100644 index 000000000000..ce7bc9dc56a1 --- /dev/null +++ b/tests/topotests/pim_cand_rp_bsr/test_pim_cand_rp_bsr.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_pim_cand_rp_bsr.py +# +# Copyright (c) 2024 ATCorp +# Jafar Al-Gharaibeh +# + +import os +import sys +import pytest +import json +from functools import partial + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger +from lib.pim import verify_pim_rp_info +from lib.common_config import step, write_test_header, retry + +from time import sleep + +""" +test_pim_cand_rp_bsr.py: Test candidate RP/BSR functionality +""" + +TOPOLOGY = """ + Candidate RP/BSR functionality + + +---+---+ +---+---+ + | C-BSR | 10.0.0.0/24 | C-BSR | + + R1 + <--------+---------> + R2 | + |elected| .1 | .2 | | + +---+---+ | +---+---+ + .1 | | 10.0.2.0/24 | .2 + | 10.0.1.0/24 | | + .3 | +-----| .4 | .4 + +---+---+ |---->+---+---+ + | C-RP | 10.0.3.0/24 | C-RP | + + R3 + <--------+---------> + R4 | + | prio | .3 | .4 | | + +---+---+ | +---+---+ + .3 | | | .4 + |10.0.4.0/24 | 10.0.5.0/24| + .5 | | .6 | .6 + +---+---+ +---------->+---+---+ + | | | | + + R5 + <------------------> + R6 | + | | .5 .6 | | + +---+---+ 10.0.6.0/24 +---+---+ +""" + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# Required to instantiate the topology builder class. +pytestmark = [pytest.mark.pimd] + + +def build_topo(tgen): + "Build function" + + # Create 6 routers + for rn in range(1, 7): + tgen.add_router("r{}".format(rn)) + + # Create 7 switches and connect routers + sw1 = tgen.add_switch("s1") + sw1.add_link(tgen.gears["r1"]) + sw1.add_link(tgen.gears["r2"]) + + sw = tgen.add_switch("s2") + sw.add_link(tgen.gears["r1"]) + sw.add_link(tgen.gears["r3"]) + + sw = tgen.add_switch("s3") + sw.add_link(tgen.gears["r2"]) + sw.add_link(tgen.gears["r4"]) + + sw3 = tgen.add_switch("s4") + sw3.add_link(tgen.gears["r3"]) + sw3.add_link(tgen.gears["r4"]) + + sw = tgen.add_switch("s5") + sw.add_link(tgen.gears["r3"]) + sw.add_link(tgen.gears["r5"]) + + sw = tgen.add_switch("s6") + sw.add_link(tgen.gears["r4"]) + sw.add_link(tgen.gears["r6"]) + + sw = tgen.add_switch("s7") + sw.add_link(tgen.gears["r5"]) + sw.add_link(tgen.gears["r6"]) + + # make the diagnoal connections + sw1.add_link(tgen.gears["r4"]) + sw3.add_link(tgen.gears["r6"]) + +def setup_module(mod): + logger.info("PIM Candidate RP/BSR:\n {}".format(TOPOLOGY)) + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + logger.info("Loading router %s" % rname) + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + # Initialize all routers. + tgen.start_router() + for router in router_list.values(): + if router.has_version("<", "4.0"): + tgen.set_error("unsupported version") + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + +def test_pim_bsr_election_r1(request): + "Test PIM BSR Election" + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + r2 = tgen.gears["r2"] + # r1 should be the BSR winner because it has higher priority + expected = { + "bsr":"10.0.0.1", + "priority":200, + "state":"ACCEPT_PREFERRED", + } + + test_func = partial( + topotest.router_json_cmp, r2, "show ip pim bsr json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + + assertmsg = "r2: r1 was not elected, bsr election mismatch" + assert result is None, assertmsg + +def test_pim_bsr_cand_bsr_r1(request): + "Test PIM BSR candidate BSR" + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + r2 = tgen.gears["r2"] + + # r2 is a candidate bsr with low priority: elected = False + expected = { + "address": "10.0.0.2", + "priority": 100, + "elected": False + } + test_func = partial( + topotest.router_json_cmp, r2, "show ip pim bsr candidate-bsr json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + + assertmsg = "r2: candidate bsr mismatch " + assert result is None, assertmsg + +def test_pim_bsr_cand_rp(request): + "Test PIM BSR candidate RP" + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + r3 = tgen.gears["r3"] + + # r3 is a candidate rp + expected = { + "address":"10.0.3.3", + "priority":10 + } + test_func = partial( + topotest.router_json_cmp, r3, "show ip pim bsr candidate-rp json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + + assertmsg = "r3: bsr candidate rp mismatch" + assert result is None, assertmsg + + +def test_pim_bsr_rp_info(request): + "Test RP info state" + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + # At this point, all nodes, including r5 should have synced the RP state + step("Verify rp-info on r5 from BSR") + result = verify_pim_rp_info(tgen, None, "r5", "239.0.0.0/16", None, "10.0.3.3", + "BSR", False, "ipv4", True, retry_timeout = 90) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + result = verify_pim_rp_info(tgen, None, "r5", "239.0.0.0/8", None, "10.0.3.4", + "BSR", False, "ipv4", True, retry_timeout = 30) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + result = verify_pim_rp_info(tgen, None, "r5", "239.0.0.0/24", None, "10.0.3.4", + "BSR", False, "ipv4", True, retry_timeout = 30) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify rp-info on the BSR node itself r1") + result = verify_pim_rp_info(tgen, None, "r1", "239.0.0.0/16", None, "10.0.3.3", + "BSR", False, "ipv4", True, retry_timeout = 10) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + result = verify_pim_rp_info(tgen, None, "r1", "239.0.0.0/8", None, "10.0.3.4", + "BSR", False, "ipv4", True, retry_timeout = 10) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + result = verify_pim_rp_info(tgen, None, "r1", "239.0.0.0/24", None, "10.0.3.4", + "BSR", False, "ipv4", True, retry_timeout = 10) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + +def test_pim_bsr_election_fallback_r2(request): + "Test PIM BSR Election Backup" + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + step("Take r1 out from BSR candidates") + r1 = tgen.gears["r1"] + r1.vtysh_cmd( + """ + configure + router pim + no bsr candidate-bsr priority 200 source address 10.0.0.1 + """) + + step("Verify r1 is no longer a BSR candidate") + expected = {} + + test_func = partial( + topotest.router_json_cmp, r1, "show ip pim bsr candidate-bsr json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=1) + + assertmsg = "r1: failed to remove bsr candidate configuration" + assert result is None, assertmsg + + r2 = tgen.gears["r2"] + # We should fall back to r2 as the BSR + expected = { + "bsr":"10.0.0.2", + "priority":100, + "state":"BSR_ELECTED", + } + + step("Verify that we fallback to r2 as the new BSR") + + test_func = partial( + topotest.router_json_cmp, r2, "show ip pim bsr json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=180, wait=1) + + assertmsg = "r2: failed to fallback to r2 as a BSR" + assert result is None, assertmsg + + +def test_pim_bsr_rp_info_fallback(request): + "Test RP info state on r5" + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + step("Take r3 out from RP candidates for group 239.0.0.0/16") + r3 = tgen.gears["r3"] + r3.vtysh_cmd( + """ + configure + router pim + no bsr candidate-rp group 239.0.0.0/16 + """) + + step("Verify falling back to r4 as the new RP for 239.0.0.0/16") + + result = verify_pim_rp_info(tgen, None, "r5", "239.0.0.0/16", None, "10.0.3.4", + "BSR", False, "ipv4", True, retry_timeout = 30) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/yang/frr-pim-candidate.yang b/yang/frr-pim-candidate.yang new file mode 100644 index 000000000000..09d0a0635368 --- /dev/null +++ b/yang/frr-pim-candidate.yang @@ -0,0 +1,174 @@ +module frr-pim-candidate { + yang-version "1.1"; + namespace "http://frrouting.org/yang/pim-candidate"; + + prefix frr-pim-candidate; + + import frr-interface { + prefix frr-interface; + } + + import ietf-inet-types { + prefix "inet"; + } + + import frr-routing { + prefix "frr-rt"; + } + + import frr-pim { + prefix "frr-pim"; + } + + import frr-route-types { + prefix frr-route-types; + } + + organization + "FRRouting"; + + contact + "FRR Users List: + FRR Development List: "; + + description + "The module defines a collection of YANG definitions common for + all PIM (Protocol Independent Multicast) Candidate RP & BSR + (Rendezvous Point & Bootstrap Router) operation. + + Copyright 2020 FRRouting + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."; + + revision 2021-05-04 { + description + "Initial revision."; + reference + "TBD"; + } + + /* + * Groupings + */ + grouping candidate-bsr-container { + description + "Grouping of Candidate BSR settings."; + + container candidate-bsr { + presence + "Enable router to be a Candidate BSR."; + + description + "Candidate BSR settings"; + + leaf bsr-priority { + type uint8; + default "64"; + description + "BSR priority for this router, higher values win."; + } + + choice source-address-or-interface { + description "IP address to use for BSR operation"; + default if-loopback; + leaf address { + type inet:ip-address; + } + leaf interface { + type frr-interface:interface-ref; + } + leaf if-loopback { + type empty; + } + leaf if-any { + type empty; + } + } + } // candidate-bsr + } // candidate-bsr-container + + grouping candidate-rp-container { + description + "Grouping of Candidate RP settings."; + + container candidate-rp { + presence + "Enable router to be a Candidate RP."; + + description + "Candidate RP settings"; + + leaf rp-priority { + type uint8; + default "192"; + description + "RP priority for this router, lower values win."; + } + + leaf advertisement-interval { + type uint32 { + range 1..4294967295; + } + default "60"; + description + "RP advertisement interval (seconds). Holdtime is 2.5 times this."; + } + + leaf-list group-list { + type frr-route-types:ip-multicast-group-prefix; + description + "List of multicast group address."; + } + + choice source-address-or-interface { + description "IP address to use for RP operation"; + default if-loopback; + leaf address { + type inet:ip-address; + } + leaf interface { + type frr-interface:interface-ref; + } + leaf if-loopback { + type empty; + } + leaf if-any { + type empty; + } + } + } + } + + /* + * Configuration data nodes + */ + augment "/frr-rt:routing/frr-rt:control-plane-protocols/" + + "frr-rt:control-plane-protocol/frr-pim:pim/" + + "frr-pim:address-family" { + description "PIM Candidate RP augmentation."; + + uses candidate-bsr-container; + uses candidate-rp-container; + } +} diff --git a/yang/subdir.am b/yang/subdir.am index 71aa04087858..786bd0bca654 100644 --- a/yang/subdir.am +++ b/yang/subdir.am @@ -80,6 +80,7 @@ if PIMD dist_yangmodels_DATA += yang/frr-gmp.yang dist_yangmodels_DATA += yang/frr-pim.yang dist_yangmodels_DATA += yang/frr-pim-rp.yang +dist_yangmodels_DATA += yang/frr-pim-candidate.yang endif if BGPD