From a9bee74ea2ed6e91a7a49d291ad1a8d3c2c1bec0 Mon Sep 17 00:00:00 2001 From: Corey Siltala Date: Mon, 25 Nov 2024 10:43:30 -0600 Subject: [PATCH 1/5] pimd: Move ACL handling to pim_util.c Move the extended access-list handling from pim_msdp_packet.c to pim_util.c to allow use elsewhere in the daemon. Signed-off-by: Corey Siltala --- pimd/pim_msdp_packet.c | 51 ++---------------------------------------- pimd/pim_util.c | 41 +++++++++++++++++++++++++++++++++ pimd/pim_util.h | 3 +++ 3 files changed, 46 insertions(+), 49 deletions(-) diff --git a/pimd/pim_msdp_packet.c b/pimd/pim_msdp_packet.c index f66a941ee312..c858bad1adbb 100644 --- a/pimd/pim_msdp_packet.c +++ b/pimd/pim_msdp_packet.c @@ -367,53 +367,6 @@ static void pim_msdp_pkt_sa_fill_one(struct pim_msdp_sa *sa) stream_put_ipv4(sa->pim->msdp.work_obuf, sa->sg.src.s_addr); } -static bool msdp_cisco_match(const struct filter *filter, - const struct in_addr *source, - const struct in_addr *group) -{ - const struct filter_cisco *cfilter = &filter->u.cfilter; - uint32_t source_addr; - uint32_t group_addr; - - group_addr = group->s_addr & ~cfilter->mask_mask.s_addr; - - if (cfilter->extended) { - source_addr = source->s_addr & ~cfilter->addr_mask.s_addr; - if (group_addr == cfilter->mask.s_addr && - source_addr == cfilter->addr.s_addr) - return true; - } else if (group_addr == cfilter->addr.s_addr) - return true; - - return false; -} - -static enum filter_type msdp_access_list_apply(struct access_list *access, - const struct in_addr *source, - const struct in_addr *group) -{ - struct filter *filter; - struct prefix group_prefix; - - if (access == NULL) - return FILTER_DENY; - - for (filter = access->head; filter; filter = filter->next) { - if (filter->cisco) { - if (msdp_cisco_match(filter, source, group)) - return filter->type; - } else { - group_prefix.family = AF_INET; - group_prefix.prefixlen = IPV4_MAX_BITLEN; - group_prefix.u.prefix4.s_addr = group->s_addr; - if (access_list_apply(access, &group_prefix)) - return filter->type; - } - } - - return FILTER_DENY; -} - bool msdp_peer_sa_filter(const struct pim_msdp_peer *mp, const struct pim_msdp_sa *sa) { @@ -425,7 +378,7 @@ bool msdp_peer_sa_filter(const struct pim_msdp_peer *mp, /* Find access list and test it. */ acl = access_list_lookup(AFI_IP, mp->acl_out); - if (msdp_access_list_apply(acl, &sa->sg.src, &sa->sg.grp) == FILTER_DENY) + if (pim_access_list_apply(acl, &sa->sg.src, &sa->sg.grp) == FILTER_DENY) return true; return false; @@ -641,7 +594,7 @@ static void pim_msdp_pkt_sa_rx_one(struct pim_msdp_peer *mp, struct in_addr rp) /* Filter incoming SA with configured access list. */ if (mp->acl_in) { acl = access_list_lookup(AFI_IP, mp->acl_in); - if (msdp_access_list_apply(acl, &sg.src, &sg.grp) == FILTER_DENY) { + if (pim_access_list_apply(acl, &sg.src, &sg.grp) == FILTER_DENY) { if (pim_msdp_log_sa_events(mp->pim)) zlog_info("MSDP peer %pI4 filter SA in (%pI4, %pI4)", &mp->peer, &sg.src, &sg.grp); diff --git a/pimd/pim_util.c b/pimd/pim_util.c index 657e84ae50aa..49ae6949a2b7 100644 --- a/pimd/pim_util.c +++ b/pimd/pim_util.c @@ -126,6 +126,47 @@ int pim_is_group_224_4(struct in_addr group_addr) return prefix_match(&group_all, &group); } +static bool pim_cisco_match(const struct filter *filter, const struct in_addr *source, + const struct in_addr *group) +{ + const struct filter_cisco *cfilter = &filter->u.cfilter; + uint32_t source_addr; + uint32_t group_addr; + + group_addr = group->s_addr & ~cfilter->mask_mask.s_addr; + + if (cfilter->extended) { + source_addr = source->s_addr & ~cfilter->addr_mask.s_addr; + if (group_addr == cfilter->mask.s_addr && source_addr == cfilter->addr.s_addr) + return true; + } else if (group_addr == cfilter->addr.s_addr) + return true; + + return false; +} + +enum filter_type pim_access_list_apply(struct access_list *access, const struct in_addr *source, + const struct in_addr *group) +{ + struct filter *filter; + struct prefix group_prefix = {}; + + if (access == NULL) + return FILTER_DENY; + + for (filter = access->head; filter; filter = filter->next) { + if (filter->cisco) { + if (pim_cisco_match(filter, source, group)) + return filter->type; + } + } + + group_prefix.family = AF_INET; + group_prefix.prefixlen = IPV4_MAX_BITLEN; + group_prefix.u.prefix4.s_addr = group->s_addr; + return access_list_apply(access, &group_prefix); +} + bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp) { struct prefix grp_pfx; diff --git a/pimd/pim_util.h b/pimd/pim_util.h index c882fe4878a3..cffa93ed2955 100644 --- a/pimd/pim_util.h +++ b/pimd/pim_util.h @@ -10,6 +10,7 @@ #include #include +#include "lib/filter.h" #include "checksum.h" #include "pimd.h" @@ -22,6 +23,8 @@ void pim_pkt_dump(const char *label, const uint8_t *buf, int size); int pim_is_group_224_0_0_0_24(struct in_addr group_addr); int pim_is_group_224_4(struct in_addr group_addr); +enum filter_type pim_access_list_apply(struct access_list *access, const struct in_addr *source, + const struct in_addr *group); bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp); int pim_get_all_mcast_group(struct prefix *prefix); bool pim_addr_is_multicast(pim_addr addr); From 4de4017d64ccaaa5a0f768873bc36aad4a8912a6 Mon Sep 17 00:00:00 2001 From: Corey Siltala Date: Mon, 25 Nov 2024 10:36:54 -0600 Subject: [PATCH 2/5] pimd,yang: Extend multicast boundary functionality Add new interface command ip multicast boundary ACCESSLIST4_NAME. This allows filtering on both source and group using the extended access-list syntax vs. group-only as with the existing "ip multicast boundary oil" command, which uses prefix-lists. If both are configured, the prefix- list is evaluated first. The default behavior for both prefix-lists and access-lists remains "deny", so the prefix-list must have a terminating "permit" statement in order to also evaluate against the access-list. The following example denies groups in range 229.1.1.0/24 and groups in range 232.1.1.0/24 with source 10.0.20.2: ! ip prefix-list pim-oil-plist seq 10 deny 229.1.1.0/24 ip prefix-list pim-oil-plist seq 20 permit any ! access-list pim-acl seq 10 deny ip host 10.0.20.2 232.1.1.0 0.0.0.255 access-list pim-acl seq 20 permit ip any any ! interface r1-eth0 ip address 10.0.20.1/24 ip igmp ip pim ip multicast boundary oil pim-oil-plist ip multicast boundary pim-acl ! Signed-off-by: Corey Siltala --- pimd/pim_cmd.c | 16 +++++++++++ pimd/pim_iface.c | 9 ++++++ pimd/pim_iface.h | 4 ++- pimd/pim_igmp.c | 2 +- pimd/pim_igmpv2.c | 3 ++ pimd/pim_igmpv3.c | 23 ++++++++-------- pimd/pim_join.c | 14 ++++------ pimd/pim_mroute.c | 8 ++++-- pimd/pim_nb.c | 7 +++++ pimd/pim_nb.h | 2 ++ pimd/pim_nb_config.c | 65 ++++++++++++++++++++++++++++++++++++++++++++ pimd/pim_util.c | 43 ++++++++++++++++++++++++----- pimd/pim_util.h | 2 +- pimd/pim_vty.c | 7 +++++ yang/frr-pim.yang | 12 +++++++- 15 files changed, 184 insertions(+), 33 deletions(-) diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c index f4c25ea81ea9..bac9645759e6 100644 --- a/pimd/pim_cmd.c +++ b/pimd/pim_cmd.c @@ -5871,6 +5871,21 @@ DEFUN(interface_no_ip_pim_boundary_oil, return pim_process_no_ip_pim_boundary_oil_cmd(vty); } +DEFPY_YANG(interface_ip_pim_boundary_acl, + interface_ip_pim_boundary_acl_cmd, + "[no] ip multicast boundary ACCESSLIST4_NAME$name", + NO_STR + IP_STR + "Generic multicast configuration options\n" + "Define multicast boundary\n" + "Access-list to filter OIL with by source and group\n") +{ + nb_cli_enqueue_change(vty, "./multicast-boundary-acl", + (!!no ? NB_OP_DESTROY : NB_OP_MODIFY), name); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, FRR_PIM_AF_XPATH_VAL); +} + DEFUN (interface_ip_mroute, interface_ip_mroute_cmd, "ip mroute INTERFACE A.B.C.D [A.B.C.D]", @@ -9018,6 +9033,7 @@ void pim_cmd_init(void) install_element(INTERFACE_NODE, &interface_no_ip_pim_hello_cmd); install_element(INTERFACE_NODE, &interface_ip_pim_boundary_oil_cmd); install_element(INTERFACE_NODE, &interface_no_ip_pim_boundary_oil_cmd); + install_element(INTERFACE_NODE, &interface_ip_pim_boundary_acl_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_query_generate_cmd); // Static mroutes NEB diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c index 19460aa445d6..f92a42dd8a54 100644 --- a/pimd/pim_iface.c +++ b/pimd/pim_iface.c @@ -38,6 +38,7 @@ #include "pim_igmp_join.h" #include "pim_vxlan.h" #include "pim_tib.h" +#include "pim_util.h" #include "pim6_mld.h" @@ -1258,6 +1259,14 @@ static int gm_join_sock(const char *ifname, ifindex_t ifindex, { int join_fd; + if (pim_is_group_filtered(pim_ifp, &group_addr, &source_addr)) { + if (PIM_DEBUG_GM_EVENTS) { + zlog_debug("%s: join failed for (S,G)=(%pPAs,%pPAs) due to multicast boundary filtering", + __func__, &source_addr, &group_addr); + } + return -1; + } + pim_ifp->igmp_ifstat_joins_sent++; join_fd = pim_socket_raw(IPPROTO_GM); diff --git a/pimd/pim_iface.h b/pimd/pim_iface.h index 95bac084d2bb..18e88ffbd5dc 100644 --- a/pimd/pim_iface.h +++ b/pimd/pim_iface.h @@ -133,8 +133,10 @@ struct pim_interface { uint32_t pim_dr_priority; /* config */ int pim_dr_num_nondrpri_neighbors; /* neighbors without dr_pri */ - /* boundary prefix-list */ + /* boundary prefix-list (group) */ char *boundary_oil_plist; + /* boundary access-list (source and group) */ + struct access_list *boundary_acl; /* Turn on Active-Active for this interface */ bool activeactive; diff --git a/pimd/pim_igmp.c b/pimd/pim_igmp.c index 1ba9bc45a20b..12f424248f9f 100644 --- a/pimd/pim_igmp.c +++ b/pimd/pim_igmp.c @@ -666,7 +666,7 @@ static int igmp_v1_recv_report(struct gm_sock *igmp, struct in_addr from, memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); - if (pim_is_group_filtered(ifp->info, &group_addr)) + if (pim_is_group_filtered(ifp->info, &group_addr, NULL)) return -1; /* non-existent group is created as INCLUDE {empty} */ diff --git a/pimd/pim_igmpv2.c b/pimd/pim_igmpv2.c index 944dffdc3389..720a4944fef5 100644 --- a/pimd/pim_igmpv2.c +++ b/pimd/pim_igmpv2.c @@ -134,6 +134,9 @@ int igmp_v2_recv_report(struct gm_sock *igmp, struct in_addr from, ifp->name, group_str); } + if (pim_is_group_filtered(pim_ifp, &group_addr, NULL)) + return -1; + /* * RFC 4604 * section 2.2.1 diff --git a/pimd/pim_igmpv3.c b/pimd/pim_igmpv3.c index 2c5ad4d44b1b..d0ba79378b31 100644 --- a/pimd/pim_igmpv3.c +++ b/pimd/pim_igmpv3.c @@ -507,6 +507,8 @@ static void allow(struct gm_sock *igmp, struct in_addr from, struct in_addr *src_addr; src_addr = sources + i; + if (pim_is_group_filtered(igmp->interface->info, &group_addr, src_addr)) + continue; source = igmp_get_source_by_addr(group, *src_addr, NULL); if (!source) @@ -646,7 +648,7 @@ void igmpv3_report_isex(struct gm_sock *igmp, struct in_addr from, on_trace(__func__, ifp, from, group_addr, num_sources, sources); - if (pim_is_group_filtered(ifp->info, &group_addr)) + if (pim_is_group_filtered(ifp->info, &group_addr, NULL)) return; /* non-existent group is created as INCLUDE {empty} */ @@ -1809,12 +1811,13 @@ static bool igmp_pkt_grp_addr_ok(struct interface *ifp, const char *from_str, pim_ifp = ifp->info; /* determine filtering status for group */ - if (pim_is_group_filtered(pim_ifp, &grp)) { + if (pim_is_group_filtered(pim_ifp, &grp, NULL)) { if (PIM_DEBUG_GM_PACKETS) { - zlog_debug( - "Filtering IGMPv3 group record %pI4 from %s on %s per prefix-list %s", - &grp.s_addr, from_str, ifp->name, - pim_ifp->boundary_oil_plist); + zlog_debug("Filtering IGMPv3 group record %pI4 from %s on %s per prefix-list %s or access-list %s", + &grp.s_addr, from_str, ifp->name, + (pim_ifp->boundary_oil_plist ? pim_ifp->boundary_oil_plist + : "(not found)"), + (pim_ifp->boundary_acl ? pim_ifp->boundary_acl->name : "(not found)")); } return false; } @@ -1943,11 +1946,9 @@ int igmp_v3_recv_report(struct gm_sock *igmp, struct in_addr from, sizeof(struct in_addr)); if (PIM_DEBUG_GM_PACKETS) { - zlog_debug( - " Recv IGMP report v3 from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%pI4", - from_str, ifp->name, i, rec_type, - rec_auxdatalen, rec_num_sources, - &rec_group); + zlog_debug(" Recv IGMP report v3 (type %d) from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%pI4", + rec_type, from_str, ifp->name, i, rec_type, rec_auxdatalen, + rec_num_sources, &rec_group); } /* Scan sources */ diff --git a/pimd/pim_join.c b/pimd/pim_join.c index 2feafabb4dff..7796e8b95169 100644 --- a/pimd/pim_join.c +++ b/pimd/pim_join.c @@ -245,7 +245,7 @@ int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh, uint16_t msg_num_pruned_sources; int source; struct pim_ifchannel *starg_ch = NULL, *sg_ch = NULL; - bool filtered = false; + bool group_filtered = false; memset(&sg, 0, sizeof(sg)); addr_offset = pim_parse_addr_group(&sg, buf, pastend - buf); @@ -275,7 +275,7 @@ int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh, &src_addr, ifp->name); /* boundary check */ - filtered = pim_is_group_filtered(pim_ifp, &sg.grp); + group_filtered = pim_is_group_filtered(pim_ifp, &sg.grp, NULL); /* Scan joined sources */ for (source = 0; source < msg_num_joined_sources; ++source) { @@ -287,8 +287,8 @@ int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh, buf += addr_offset; - /* if we are filtering this group, skip the join */ - if (filtered) + /* if we are filtering this group or (S,G), skip the join */ + if (group_filtered || pim_is_group_filtered(pim_ifp, &sg.grp, &sg.src)) continue; recv_join(ifp, neigh, msg_holdtime, msg_upstream_addr, @@ -312,10 +312,6 @@ int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh, buf += addr_offset; - /* if we are filtering this group, skip the prune */ - if (filtered) - continue; - recv_prune(ifp, neigh, msg_holdtime, msg_upstream_addr, &sg, msg_source_flags); /* @@ -361,7 +357,7 @@ int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh, } } } - if (starg_ch && !filtered) + if (starg_ch && !group_filtered) pim_ifchannel_set_star_g_join_state(starg_ch, 1, 0); starg_ch = NULL; } /* scan groups */ diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c index 9d290c3c6f2b..96eb5f48f523 100644 --- a/pimd/pim_mroute.c +++ b/pimd/pim_mroute.c @@ -35,6 +35,7 @@ #include "pim_sock.h" #include "pim_vxlan.h" #include "pim_msg.h" +#include "pim_util.h" static void mroute_read_on(struct pim_instance *pim); static int pim_upstream_mroute_update(struct channel_oil *c_oil, @@ -271,7 +272,9 @@ int pim_mroute_msg_nocache(int fd, struct interface *ifp, const kernmsg *msg) *oil_incoming_vif(up->channel_oil) >= MAXVIFS) { pim_upstream_mroute_iif_update(up->channel_oil, __func__); } - pim_register_join(up); + + if (!pim_is_group_filtered(pim_ifp, &sg.grp, &sg.src)) + pim_register_join(up); /* if we have receiver, inherit from parent */ pim_upstream_inherited_olist_decide(pim_ifp->pim, up); @@ -632,7 +635,8 @@ int pim_mroute_msg_wrvifwhole(int fd, struct interface *ifp, const char *buf, pim_upstream_keep_alive_timer_start( up, pim_ifp->pim->keep_alive_time); up->channel_oil->cc.pktcnt++; - pim_register_join(up); + if (!pim_is_group_filtered(pim_ifp, &sg.grp, &sg.src)) + pim_register_join(up); pim_upstream_inherited_olist(pim_ifp->pim, up); if (!up->channel_oil->installed) pim_upstream_mroute_add(up->channel_oil, __func__); diff --git a/pimd/pim_nb.c b/pimd/pim_nb.c index 4a5ad87942b2..2b39f2dcb827 100644 --- a/pimd/pim_nb.c +++ b/pimd/pim_nb.c @@ -352,6 +352,13 @@ const struct frr_yang_module_info frr_pim_info = { .destroy = lib_interface_pim_address_family_multicast_boundary_oil_destroy, } }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/multicast-boundary-acl", + .cbs = { + .modify = lib_interface_pim_address_family_multicast_boundary_acl_modify, + .destroy = lib_interface_pim_address_family_multicast_boundary_acl_destroy, + } + }, { .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/mroute", .cbs = { diff --git a/pimd/pim_nb.h b/pimd/pim_nb.h index a9693c65d8e6..f27b86680fb4 100644 --- a/pimd/pim_nb.h +++ b/pimd/pim_nb.h @@ -140,6 +140,8 @@ int lib_interface_pim_address_family_multicast_boundary_oil_modify( struct nb_cb_modify_args *args); int lib_interface_pim_address_family_multicast_boundary_oil_destroy( struct nb_cb_destroy_args *args); +int lib_interface_pim_address_family_multicast_boundary_acl_modify(struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_multicast_boundary_acl_destroy(struct nb_cb_destroy_args *args); int lib_interface_pim_address_family_mroute_create( struct nb_cb_create_args *args); int lib_interface_pim_address_family_mroute_destroy( diff --git a/pimd/pim_nb_config.c b/pimd/pim_nb_config.c index 171614208f09..2533f8c4d491 100644 --- a/pimd/pim_nb_config.c +++ b/pimd/pim_nb_config.c @@ -2452,6 +2452,71 @@ int lib_interface_pim_address_family_multicast_boundary_oil_destroy( return NB_OK; } +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/multicast-boundary-acl + */ +int lib_interface_pim_address_family_multicast_boundary_acl_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + snprintf(args->errmsg, args->errmsg_len, + "%% Enable PIM and/or IGMP on this interface first"); + return NB_ERR_VALIDATION; + } + if (!access_list_lookup(AFI_IP, yang_dnode_get_string(args->dnode, NULL))) { + snprintf(args->errmsg, args->errmsg_len, + "%% Specified access-list not found"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->boundary_acl = + access_list_lookup(AFI_IP, yang_dnode_get_string(args->dnode, NULL)); + break; + } + + return NB_OK; +} + +int lib_interface_pim_address_family_multicast_boundary_acl_destroy(struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + snprintf(args->errmsg, args->errmsg_len, + "%% Enable PIM and/or IGMP on this interface first"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->boundary_acl = NULL; + break; + } + + return NB_OK; +} + /* * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/mroute */ diff --git a/pimd/pim_util.c b/pimd/pim_util.c index 49ae6949a2b7..b6f3be52fc74 100644 --- a/pimd/pim_util.c +++ b/pimd/pim_util.c @@ -10,6 +10,8 @@ #include "prefix.h" #include "plist.h" +#include "pimd.h" +#include "pim_instance.h" #include "pim_util.h" /* @@ -167,20 +169,47 @@ enum filter_type pim_access_list_apply(struct access_list *access, const struct return access_list_apply(access, &group_prefix); } -bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp) +bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp, pim_addr *src) { - struct prefix grp_pfx; - struct prefix_list *pl; + bool is_filtered = false; +#if PIM_IPV == 4 + struct prefix grp_pfx = {}; + struct prefix_list *pl = NULL; + pim_addr any_src = PIMADDR_ANY; - if (!pim_ifp->boundary_oil_plist) + if (!pim_ifp->boundary_oil_plist && !pim_ifp->boundary_acl) return false; pim_addr_to_prefix(&grp_pfx, *grp); pl = prefix_list_lookup(PIM_AFI, pim_ifp->boundary_oil_plist); - return pl ? prefix_list_apply_ext(pl, NULL, &grp_pfx, true) == - PREFIX_DENY - : false; + + /* Filter if either group or (S,G) are denied */ + if (pl) { + is_filtered = prefix_list_apply_ext(pl, NULL, &grp_pfx, true) == PREFIX_DENY; + if (is_filtered && PIM_DEBUG_EVENTS) { + zlog_debug("Filtering group %pI4 per prefix-list %s", grp, + pim_ifp->boundary_oil_plist); + } + } + if (!is_filtered && pim_ifp->boundary_acl) { + /* If src not provided, set to "any" (*)? */ + if (!src) + src = &any_src; + /* S,G filtering using extended access-list syntax */ + is_filtered = pim_access_list_apply(pim_ifp->boundary_acl, src, grp) == FILTER_DENY; + if (is_filtered && PIM_DEBUG_EVENTS) { + if (pim_addr_is_any(*src)) { + zlog_debug("Filtering (S,G)=(*, %pI4) per access-list %s", grp, + pim_ifp->boundary_acl->name); + } else { + zlog_debug("Filtering (S,G)=(%pI4, %pI4) per access-list %s", src, + grp, pim_ifp->boundary_acl->name); + } + } + } +#endif + return is_filtered; } diff --git a/pimd/pim_util.h b/pimd/pim_util.h index cffa93ed2955..dda93110b80d 100644 --- a/pimd/pim_util.h +++ b/pimd/pim_util.h @@ -25,7 +25,7 @@ int pim_is_group_224_0_0_0_24(struct in_addr group_addr); int pim_is_group_224_4(struct in_addr group_addr); enum filter_type pim_access_list_apply(struct access_list *access, const struct in_addr *source, const struct in_addr *group); -bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp); +bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp, pim_addr *src); int pim_get_all_mcast_group(struct prefix *prefix); bool pim_addr_is_multicast(pim_addr addr); #endif /* PIM_UTIL_H */ diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c index ed91d2339b5f..ec87093325f0 100644 --- a/pimd/pim_vty.c +++ b/pimd/pim_vty.c @@ -12,6 +12,7 @@ #include "vty.h" #include "vrf.h" #include "plist.h" +#include "filter.h" #include "pimd.h" #include "pim_vty.h" @@ -496,6 +497,12 @@ int pim_config_write(struct vty *vty, int writes, struct interface *ifp, ++writes; } + if (pim_ifp->boundary_acl) { + vty_out(vty, " " PIM_AF_NAME " multicast boundary %s\n", + pim_ifp->boundary_acl->name); + ++writes; + } + if (pim_ifp->pim_passive_enable) { vty_out(vty, " " PIM_AF_NAME " pim passive\n"); ++writes; diff --git a/yang/frr-pim.yang b/yang/frr-pim.yang index 33602fd29e60..473226653eed 100644 --- a/yang/frr-pim.yang +++ b/yang/frr-pim.yang @@ -78,6 +78,10 @@ module frr-pim { type string; } + typedef access-list-ref { + type string; + } + /* * Groupings */ @@ -507,7 +511,13 @@ module frr-pim { leaf multicast-boundary-oil { type plist-ref; description - "Prefix-List to define multicast boundary"; + "Prefix-List to define multicast boundary by group"; + } + + leaf multicast-boundary-acl { + type access-list-ref; + description + "Access-list to define multicast boundary by source and group"; } list mroute { From 7c2c70dd2b2a826847f66c095206792a0b7e1ff6 Mon Sep 17 00:00:00 2001 From: Corey Siltala Date: Thu, 14 Nov 2024 13:08:28 -0600 Subject: [PATCH 3/5] tests: Add basic multicast boundary test Add simple test to show filtering of IGMP joins using new "ip multicast boundary" filtering with access-lists, include test of existing prefix- list based "ip multicast boundary oil" command. Signed-off-by: Corey Siltala --- tests/topotests/pim_boundary_acl/r1/frr.conf | 39 ++ tests/topotests/pim_boundary_acl/r2/frr.conf | 19 + tests/topotests/pim_boundary_acl/r3/frr.conf | 13 + tests/topotests/pim_boundary_acl/rp/frr.conf | 22 + .../pim_boundary_acl/test_pim_boundary_acl.py | 523 ++++++++++++++++++ 5 files changed, 616 insertions(+) create mode 100644 tests/topotests/pim_boundary_acl/r1/frr.conf create mode 100644 tests/topotests/pim_boundary_acl/r2/frr.conf create mode 100644 tests/topotests/pim_boundary_acl/r3/frr.conf create mode 100644 tests/topotests/pim_boundary_acl/rp/frr.conf create mode 100644 tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py diff --git a/tests/topotests/pim_boundary_acl/r1/frr.conf b/tests/topotests/pim_boundary_acl/r1/frr.conf new file mode 100644 index 000000000000..cc639b304b37 --- /dev/null +++ b/tests/topotests/pim_boundary_acl/r1/frr.conf @@ -0,0 +1,39 @@ +hostname r1 +! +!debug pim events +!debug igmp events +!debug igmp packets +! +ip prefix-list pim-oil-plist seq 10 deny 229.1.1.0/24 +ip prefix-list pim-oil-plist seq 20 permit any +! +access-list pim-acl seq 10 deny ip host 10.0.20.2 232.1.1.0 0.0.0.255 +access-list pim-acl seq 20 permit ip any any +! +interface r1-eth0 + ip address 10.0.20.1/24 + ip igmp + ip pim +! +interface r1-eth1 + ip address 10.0.30.1/24 + ip pim +! +interface r1-eth2 + ip address 10.0.40.1/24 + ip igmp + ip pim +! +interface lo + ip address 10.254.0.1/32 + ip pim +! +router pim + rp 10.254.0.3 + join-prune-interval 5 +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 10.0.30.3 remote-as external + neighbor 10.0.30.3 timers 3 10 + redistribute connected diff --git a/tests/topotests/pim_boundary_acl/r2/frr.conf b/tests/topotests/pim_boundary_acl/r2/frr.conf new file mode 100644 index 000000000000..10ace947b2bc --- /dev/null +++ b/tests/topotests/pim_boundary_acl/r2/frr.conf @@ -0,0 +1,19 @@ +hostname r2 +! +!debug pim events +!debug igmp events +!debug igmp packets +! +ip prefix-list pim-oil-plist seq 10 deny 229.1.1.0/24 +ip prefix-list pim-oil-plist seq 20 permit any +! +access-list pim-acl seq 10 deny ip host 10.0.20.2 232.1.1.0 0.0.0.255 +access-list pim-acl seq 20 permit ip any any +! +interface r2-eth0 + ip address 10.0.20.2/24 + ip pim +! +interface lo + ip address 10.254.0.2/32 +! diff --git a/tests/topotests/pim_boundary_acl/r3/frr.conf b/tests/topotests/pim_boundary_acl/r3/frr.conf new file mode 100644 index 000000000000..972077426643 --- /dev/null +++ b/tests/topotests/pim_boundary_acl/r3/frr.conf @@ -0,0 +1,13 @@ +hostname r3 +! +!debug pim events +!debug igmp events +!debug igmp packets +! +interface r3-eth0 + ip address 10.0.40.4/24 + ip pim +! +interface lo + ip address 10.254.0.4/32 +! diff --git a/tests/topotests/pim_boundary_acl/rp/frr.conf b/tests/topotests/pim_boundary_acl/rp/frr.conf new file mode 100644 index 000000000000..f6eed2391705 --- /dev/null +++ b/tests/topotests/pim_boundary_acl/rp/frr.conf @@ -0,0 +1,22 @@ +hostname rp +! +interface rp-eth0 + ip address 10.0.30.3/24 + ip pim +! +interface lo + ip address 10.254.0.3/32 + ip pim +! +router pim + rp 10.254.0.3 + join-prune-interval 5 + register-accept-list ACCEPT +! +ip prefix-list ACCEPT seq 5 permit 10.0.20.0/24 le 32 +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 10.0.30.1 remote-as external + neighbor 10.0.30.1 timers 3 10 + redistribute connected \ No newline at end of file diff --git a/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py b/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py new file mode 100644 index 000000000000..1488e610c8ce --- /dev/null +++ b/tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py @@ -0,0 +1,523 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_pim_boundary_acl.py +# +# Copyright (c) 2024 Architecture Technology Corporation +# Corey Siltala +# + +""" +test_pim_boundary_acl.py: Test multicast boundary commands (access-lists and prefix-lists) +""" + +import os +import sys +import pytest +import json +from functools import partial + +pytestmark = [pytest.mark.pimd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +ASM_GROUP="229.1.1.1" +SSM_GROUP="232.1.1.1" + +def build_topo(tgen): + "Build function" + + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + tgen.add_router("rp") + + # rp ------ r1 -------- r2 + # \ + # --------- r3 + # r1 -> .1 + # r2 -> .2 + # rp -> .3 + # r3 -> .4 + # loopback network is 10.254.0.X/32 + # + # r1 <- sw1 -> r2 + # r1-eth0 <-> r2-eth0 + # 10.0.20.0/24 + sw = tgen.add_switch("sw1") + sw.add_link(tgen.gears["r1"]) + sw.add_link(tgen.gears["r2"]) + + # r1 <- sw2 -> rp + # r1-eth1 <-> rp-eth0 + # 10.0.30.0/24 + sw = tgen.add_switch("sw2") + sw.add_link(tgen.gears["r1"]) + sw.add_link(tgen.gears["rp"]) + + # r1 <- sw3 -> r3 + # r1-eth2 <-> r3-eth0 + # 10.0.40.0/24 + sw = tgen.add_switch("sw3") + sw.add_link(tgen.gears["r1"]) + sw.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + # For all registered routers, load the zebra configuration file + for rname, router in tgen.routers().items(): + logger.info("Loading router %s" % rname) + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + # tgen.mininet_cli() + + +def teardown_module(): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_pim_rp_setup(): + "Ensure basic routing has come up and the rp has an outgoing interface" + # Ensure rp and r1 establish pim neighbor ship and bgp has come up + # Finally ensure that the rp has an outgoing interface on r1 + tgen = get_topogen() + + r1 = tgen.gears["r1"] + expected = { + "10.254.0.3":[ + { + "outboundInterface":"r1-eth1", + "group":"224.0.0.0/4", + "source":"Static" + } + ] + } + + test_func = partial( + topotest.router_json_cmp, r1, "show ip pim rp-info json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(r1.name) + assert result is None, assertmsg + # tgen.mininet_cli() + + +def test_pim_asm_igmp_join_acl(): + "Test ASM IGMP joins with prefix-list ACLs" + logger.info("Send IGMP joins from r2 to r1 with ACL enabled and disabled") + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + r1 = tgen.gears["r1"] + + # No IGMP sources other than from self for AutoRP Discovery group initially + expected = { + "r1-eth0":{ + "name":"r1-eth0", + "224.0.1.40":"*", + "229.1.1.1":None + }, + "r1-eth2":{ + "name":"r1-eth2", + "224.0.1.40":"*", + "229.1.1.1":None + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip igmp sources json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected no IGMP sources other than for AutoRP Discovery" + + # Send IGMP join from r2, check if r1 has IGMP source + r2.vtysh_cmd(( + """ + configure terminal + interface {} + ip igmp join {} + """ + ).format("r2-eth0", ASM_GROUP)) + expected = { + "r1-eth0":{ + "name":"r1-eth0", + "229.1.1.1":{ + "group":"229.1.1.1", + "sources":[ + { + "source":"*", + "timer":"--:--", + "forwarded":False, + "uptime":"*" + } + ] + } + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip igmp sources json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected IGMP source to be present but is absent" + + # Test inbound boundary on r1 + # Enable multicast boundary on r1, toggle IGMP join on r2 + r2.vtysh_cmd(( + """ + configure terminal + interface r2-eth0 + no ip igmp join {} + """ + ).format(ASM_GROUP)) + r1.vtysh_cmd( + """ + configure terminal + interface r1-eth0 + ip multicast boundary oil pim-oil-plist + """ + ) + r2.vtysh_cmd(( + """ + configure terminal + interface r2-eth0 + ip igmp join {} + """ + ).format(ASM_GROUP)) + expected = { + "r1-eth0":{ + "name":"r1-eth0", + "229.1.1.1":None + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip igmp sources json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected IGMP source to be absent but is present" + + # Test outbound boundary on r2 + # Enable multicast boundary on r2, toggle IGMP join (test outbound) + # Note: json_cmp treats "*" as wildcard but in this case that's actually what the source is + expected = { + "vrf":"default", + "r2-eth0":{ + "name":"r2-eth0", + "groups":[ + { + "source":"*", + "group":"229.1.1.1", + "primaryAddr":"10.0.20.2", + "sockFd":"*", + "upTime":"*" + } + ] + } + } + test_func = partial( + topotest.router_json_cmp, r2, "show ip igmp join json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected IGMP join to be present but is absent" + + r2.vtysh_cmd(( + """ + configure terminal + interface r2-eth0 + no ip igmp join {} + ip multicast boundary oil pim-oil-plist + ip igmp join {} + """ + ).format(ASM_GROUP, ASM_GROUP)) + expected = { + "vrf":"default", + "r2-eth0":None + } + test_func = partial( + topotest.router_json_cmp, r2, "show ip igmp join json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected IGMP join to be absent but is present" + + # Cleanup + r2.vtysh_cmd(( + """ + configure terminal + interface r2-eth0 + no ip igmp join {} + no ip multicast boundary oil pim-oil-plist + """ + ).format(ASM_GROUP)) + + +def test_pim_ssm_igmp_join_acl(): + "Test SSM IGMP joins with extended ACLs" + logger.info("Send IGMP joins from r2 to r1 with ACL enabled and disabled") + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r3 = tgen.gears["r3"] + r2 = tgen.gears["r2"] + r1 = tgen.gears["r1"] + + # No IGMP sources other than from self for AutoRP Discovery group initially + expected = { + "r1-eth0":{ + "name":"r1-eth0", + "224.0.1.40":"*", + "229.1.1.1":None, + "232.1.1.1":None + }, + "r1-eth2":{ + "name":"r1-eth2", + "224.0.1.40":"*", + "229.1.1.1":None, + "232.1.1.1":None + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip igmp sources json", {} + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected no IGMP sources other than from AutoRP Discovery" + + # Send IGMP join from r2, check if r1 has IGMP source + r2.vtysh_cmd(( + """ + configure terminal + interface r2-eth0 + ip igmp join {} 10.0.20.2 + """ + ).format(SSM_GROUP)) + expected = { + "r1-eth0":{ + "name":"r1-eth0", + "232.1.1.1":{ + "group":"232.1.1.1", + "sources":[ + { + "source":"10.0.20.2", + "timer":"*", + "forwarded":False, + "uptime":"*" + } + ] + } + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip igmp sources json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected IGMP source to be present but is absent" + + # Test inbound boundary on r1 + # Enable multicast boundary on r1, toggle IGMP join on r2 + r2.vtysh_cmd(( + """ + configure terminal + interface r2-eth0 + no ip igmp join {} 10.0.20.2 + """ + ).format(SSM_GROUP)) + r1.vtysh_cmd( + """ + configure terminal + interface r1-eth0 + ip multicast boundary pim-acl + """ + ) + r2.vtysh_cmd(( + """ + configure terminal + interface r2-eth0 + ip igmp join {} 10.0.20.2 + """ + ).format(SSM_GROUP)) + expected = { + "r1-eth0":{ + "name":"r1-eth0", + "232.1.1.1":None + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip igmp sources json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected IGMP source to be absent but is present" + + # Add lower, more-specific permit rule to access-list + r2.vtysh_cmd(( + """ + configure terminal + interface r2-eth0 + no ip igmp join {} 10.0.20.2 + """ + ).format(SSM_GROUP)) + r1.vtysh_cmd(( + """ + configure terminal + access-list pim-acl seq 5 permit ip host 10.0.20.2 {} 0.0.0.128 + """ + ).format(SSM_GROUP)) + r2.vtysh_cmd(( + """ + configure terminal + interface r2-eth0 + ip igmp join {} 10.0.20.2 + """ + ).format(SSM_GROUP)) + expected = { + "r1-eth0":{ + "name":"r1-eth0", + "232.1.1.1":{ + "group":"232.1.1.1", + "sources":[ + { + "source":"10.0.20.2", + "timer":"*", + "forwarded":False, + "uptime":"*" + } + ] + } + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip igmp sources json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected IGMP source to be present but is absent" + + # Test outbound boundary on r2 + # Enable multicast boundary on r2, toggle IGMP join (test outbound) + expected = { + "vrf":"default", + "r2-eth0":{ + "name":"r2-eth0", + "groups":[ + { + "source":"10.0.20.2", + "group":"232.1.1.1", + "primaryAddr":"10.0.20.2", + "sockFd":"*", + "upTime":"*" + } + ] + } + } + test_func = partial( + topotest.router_json_cmp, r2, "show ip igmp join json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected IGMP join to be present but is absent" + + # Enable boundary ACL, check join is absent + r2.vtysh_cmd(( + """ + configure terminal + interface r2-eth0 + no ip igmp join {} 10.0.20.2 + ip multicast boundary pim-acl + ip igmp join {} 10.0.20.2 + """ + ).format(SSM_GROUP, SSM_GROUP)) + expected = { + "vrf":"default", + "r2-eth0":None + } + test_func = partial( + topotest.router_json_cmp, r2, "show ip igmp join json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected IGMP join to be absent but is present" + # Check sources on r1 again, should be absent even though we permitted it because r2 is blocking it outbound + expected = { + "r1-eth0":{ + "name":"r1-eth0", + "232.1.1.1":None + }, + "r1-eth2":{ + "name":"r1-eth2", + "232.1.1.1":None + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip igmp sources json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected IGMP source to be absent but is present" + + # Send IGMP join from r3 with different source, should show up on r1 + # Add lower, more-specific permit rule to access-list + r3.vtysh_cmd(( + """ + configure terminal + interface r3-eth0 + ip igmp join {} 10.0.40.4 + """ + ).format(SSM_GROUP)) + expected = { + "r1-eth0":{ + "name":"r1-eth0", + "232.1.1.1":None + }, + "r1-eth2":{ + "name":"r1-eth2", + "232.1.1.1":{ + "group":"232.1.1.1", + "sources":[ + { + "source":"10.0.40.4", + "timer":"*", + "forwarded":False, + "uptime":"*" + } + ] + } + } + } + test_func = partial( + topotest.router_json_cmp, r1, "show ip igmp sources json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assert result is None, "Expected IGMP source to be present but is absent" + + # PIM join + # PIM-DM forwarding + + +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)) From ff5309ca2d0291cd6da776dd83c210fb01aeb948 Mon Sep 17 00:00:00 2001 From: Corey Siltala Date: Thu, 14 Nov 2024 13:08:52 -0600 Subject: [PATCH 4/5] doc: Expand ACL and multicast boundary documentation Add documentation for existing extended access-list functionality and the new "ip multicast boundary" command leveraging that functionality. Signed-off-by: Corey Siltala --- doc/user/filter.rst | 27 +++++++++++++++++++++++--- doc/user/pim.rst | 46 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/doc/user/filter.rst b/doc/user/filter.rst index c1146e50aa2b..be63095166fa 100644 --- a/doc/user/filter.rst +++ b/doc/user/filter.rst @@ -9,9 +9,7 @@ defined, it can be applied in any direction. IP Access List ============== -.. clicmd:: access-list NAME [seq (1-4294967295)] permit IPV4-NETWORK - -.. clicmd:: access-list NAME [seq (1-4294967295)] deny IPV4-NETWORK +.. clicmd:: access-list NAME [seq (1-4294967295)] seq seq `number` can be set either automatically or manually. In the @@ -35,6 +33,29 @@ IP Access List access-list filter permit 10.0.0.0/8 access-list filter seq 13 permit 10.0.0.0/7 +.. clicmd:: access-list NAME [seq (1-4294967295)] ip + + The extended access-list syntax enables filtering on both source and destination + IP addresses (or source and group, if used for multicast boundaries). The + source address is first in order in the command. + + If providing a mask, note that the access-lists use wildcard masks (inverse + matching logic of subnet masks). If specifying ``host``, only the single address + given will be matched. + + A basic example is as follows: + + .. code-block:: frr + + access-list filter seq 5 permit ip host 10.0.20.2 232.1.1.0 0.0.0.128 + access-list filter seq 10 deny ip 10.0.20.0 0.0.0.255 232.1.1.0 0.0.0.255 + access-list filter seq 15 permit ip any any + + .. note :: + + If an access-list is specified but no match is found, the default verdict + is deny. + .. clicmd:: show access-list [json] Display all IPv4 or IPv6 access lists. diff --git a/doc/user/pim.rst b/doc/user/pim.rst index 05418da5a990..ef49b076dff6 100644 --- a/doc/user/pim.rst +++ b/doc/user/pim.rst @@ -6,9 +6,9 @@ PIM PIM -- Protocol Independent Multicast -*pimd* supports pim-sm as well as igmp v2 and v3. pim is -vrf aware and can work within the context of vrf's in order to -do S,G mrouting. Additionally PIM can be used in the EVPN underlay +*pimd* supports PIM-SM as well as IGMP v2 and v3. PIM is +VRF aware and can work within the context of VRFs in order to +do S,G mrouting. Additionally, PIM can be used in the EVPN underlay network for optimizing forwarding of overlay BUM traffic. .. note:: @@ -348,10 +348,46 @@ is in a vrf, enter the interface command with the vrf keyword at the end. .. clicmd:: ip multicast boundary oil WORD - Set a pim multicast boundary, based upon the WORD prefix-list. If a pim join - or IGMP report is received on this interface and the Group is denied by the + Set a PIM multicast boundary, based upon the WORD prefix-list. If a PIM join + or IGMP report is received on this interface and the group is denied by the prefix-list, PIM will ignore the join or report. + .. code-block:: frr + + prefix-list multicast-acl seq 5 permit 232.1.1.1/32 + prefix-list multicast-acl seq 10 deny 232.1.1.0/24 + prefix-list multicast-acl seq 15 permit any + ! + interface r1-eth0 + ip pim + ip igmp + ip multicast boundary oil multicast-acl + exit + +.. clicmd:: ip multicast boundary ACCESS-LIST + + Set a PIM multicast boundary, based upon the ACCESS-LIST. If a PIM join + or IGMP report is received on this interface and the (S,G) tuple is denied by the + access-list, PIM will ignore the join or report. + + To filter on both source and group, the extended access-list syntax must be used. + + If both a prefix-list and access-list are configured for multicast boundaries, + the prefix-list will be evaluated first (and must have a terminating "permit any" + in order to also evaluate against the access-list). + + .. code-block:: frr + + access-list multicast-acl seq 5 permit ip host 10.0.20.2 host 232.1.1.1 + access-list multicast-acl seq 10 deny ip 10.0.20.0 0.0.0.255 232.1.1.0 0.0.0.255 + access-list multicast-acl seq 15 permit ip any any + ! + interface r1-eth0 + ip pim + ip igmp + ip multicast boundary pim-acl + exit + .. clicmd:: ip igmp last-member-query-count (1-255) Set the IGMP last member query count. The default value is 2. 'no' form of From 8465ba1dde06102ae631ad3d53c2e2e53cc6c7b9 Mon Sep 17 00:00:00 2001 From: Corey Siltala Date: Tue, 26 Nov 2024 08:35:42 -0600 Subject: [PATCH 5/5] pimd: Convert boundary_oil_plist to struct prefix_list Rather than storing the prefix-list name and looking it up every time we use it, store a pointer to the prefix-list itself. Signed-off-by: Corey Siltala --- pimd/pim_iface.c | 1 - pimd/pim_iface.h | 2 +- pimd/pim_igmpv3.c | 7 +++++-- pimd/pim_nb_config.c | 18 ++++++++---------- pimd/pim_util.c | 11 +++++------ pimd/pim_vty.c | 3 ++- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c index f92a42dd8a54..bd7164c9b972 100644 --- a/pimd/pim_iface.c +++ b/pimd/pim_iface.c @@ -216,7 +216,6 @@ void pim_if_delete(struct interface *ifp) if (pim_ifp->bfd_config.profile) XFREE(MTYPE_TMP, pim_ifp->bfd_config.profile); - XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist); XFREE(MTYPE_PIM_INTERFACE, pim_ifp); ifp->info = NULL; diff --git a/pimd/pim_iface.h b/pimd/pim_iface.h index 18e88ffbd5dc..90a81a21d026 100644 --- a/pimd/pim_iface.h +++ b/pimd/pim_iface.h @@ -134,7 +134,7 @@ struct pim_interface { int pim_dr_num_nondrpri_neighbors; /* neighbors without dr_pri */ /* boundary prefix-list (group) */ - char *boundary_oil_plist; + struct prefix_list *boundary_oil_plist; /* boundary access-list (source and group) */ struct access_list *boundary_acl; diff --git a/pimd/pim_igmpv3.c b/pimd/pim_igmpv3.c index d0ba79378b31..7348d8130f2c 100644 --- a/pimd/pim_igmpv3.c +++ b/pimd/pim_igmpv3.c @@ -9,6 +9,8 @@ #include "memory.h" #include "if.h" #include "lib_errors.h" +#include "plist.h" +#include "plist_int.h" #include "pimd.h" #include "pim_instance.h" @@ -1815,9 +1817,10 @@ static bool igmp_pkt_grp_addr_ok(struct interface *ifp, const char *from_str, if (PIM_DEBUG_GM_PACKETS) { zlog_debug("Filtering IGMPv3 group record %pI4 from %s on %s per prefix-list %s or access-list %s", &grp.s_addr, from_str, ifp->name, - (pim_ifp->boundary_oil_plist ? pim_ifp->boundary_oil_plist + (pim_ifp->boundary_oil_plist ? pim_ifp->boundary_oil_plist->name : "(not found)"), - (pim_ifp->boundary_acl ? pim_ifp->boundary_acl->name : "(not found)")); + (pim_ifp->boundary_acl ? pim_ifp->boundary_acl->name + : "(not found)")); } return false; } diff --git a/pimd/pim_nb_config.c b/pimd/pim_nb_config.c index 2533f8c4d491..41457ffba2d2 100644 --- a/pimd/pim_nb_config.c +++ b/pimd/pim_nb_config.c @@ -2390,7 +2390,6 @@ int lib_interface_pim_address_family_multicast_boundary_oil_modify( { struct interface *ifp; struct pim_interface *pim_ifp; - const char *plist; const struct lyd_node *if_dnode; switch (args->event) { @@ -2398,7 +2397,12 @@ int lib_interface_pim_address_family_multicast_boundary_oil_modify( if_dnode = yang_dnode_get_parent(args->dnode, "interface"); if (!is_pim_interface(if_dnode)) { snprintf(args->errmsg, args->errmsg_len, - "Pim not enabled on this interface"); + "%% Enable PIM and/or IGMP on this interface first"); + return NB_ERR_VALIDATION; + } + if (!prefix_list_lookup(AFI_IP, yang_dnode_get_string(args->dnode, NULL))) { + snprintf(args->errmsg, args->errmsg_len, + "%% Specified prefix-list not found"); return NB_ERR_VALIDATION; } break; @@ -2408,13 +2412,8 @@ int lib_interface_pim_address_family_multicast_boundary_oil_modify( case NB_EV_APPLY: ifp = nb_running_get_entry(args->dnode, NULL, true); pim_ifp = ifp->info; - plist = yang_dnode_get_string(args->dnode, NULL); - - if (pim_ifp->boundary_oil_plist) - XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist); - pim_ifp->boundary_oil_plist = - XSTRDUP(MTYPE_PIM_INTERFACE, plist); + prefix_list_lookup(AFI_IP, yang_dnode_get_string(args->dnode, NULL)); break; } @@ -2444,8 +2443,7 @@ int lib_interface_pim_address_family_multicast_boundary_oil_destroy( case NB_EV_APPLY: ifp = nb_running_get_entry(args->dnode, NULL, true); pim_ifp = ifp->info; - if (pim_ifp->boundary_oil_plist) - XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist); + pim_ifp->boundary_oil_plist = NULL; break; } diff --git a/pimd/pim_util.c b/pimd/pim_util.c index b6f3be52fc74..40404714e7fb 100644 --- a/pimd/pim_util.c +++ b/pimd/pim_util.c @@ -9,6 +9,7 @@ #include "log.h" #include "prefix.h" #include "plist.h" +#include "plist_int.h" #include "pimd.h" #include "pim_instance.h" @@ -174,7 +175,6 @@ bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp, pim_add bool is_filtered = false; #if PIM_IPV == 4 struct prefix grp_pfx = {}; - struct prefix_list *pl = NULL; pim_addr any_src = PIMADDR_ANY; if (!pim_ifp->boundary_oil_plist && !pim_ifp->boundary_acl) @@ -182,14 +182,13 @@ bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp, pim_add pim_addr_to_prefix(&grp_pfx, *grp); - pl = prefix_list_lookup(PIM_AFI, pim_ifp->boundary_oil_plist); - /* Filter if either group or (S,G) are denied */ - if (pl) { - is_filtered = prefix_list_apply_ext(pl, NULL, &grp_pfx, true) == PREFIX_DENY; + if (pim_ifp->boundary_oil_plist) { + is_filtered = prefix_list_apply_ext(pim_ifp->boundary_oil_plist, NULL, &grp_pfx, + true) == PREFIX_DENY; if (is_filtered && PIM_DEBUG_EVENTS) { zlog_debug("Filtering group %pI4 per prefix-list %s", grp, - pim_ifp->boundary_oil_plist); + pim_ifp->boundary_oil_plist->name); } } if (!is_filtered && pim_ifp->boundary_acl) { diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c index ec87093325f0..4c3762b2edfa 100644 --- a/pimd/pim_vty.c +++ b/pimd/pim_vty.c @@ -12,6 +12,7 @@ #include "vty.h" #include "vrf.h" #include "plist.h" +#include "plist_int.h" #include "filter.h" #include "pimd.h" @@ -493,7 +494,7 @@ int pim_config_write(struct vty *vty, int writes, struct interface *ifp, /* boundary */ if (pim_ifp->boundary_oil_plist) { vty_out(vty, " " PIM_AF_NAME " multicast boundary oil %s\n", - pim_ifp->boundary_oil_plist); + pim_ifp->boundary_oil_plist->name); ++writes; }