diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c index f4c25ea81e..bac9645759 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 19460aa445..f92a42dd8a 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 95bac084d2..18e88ffbd5 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 1ba9bc45a2..12f424248f 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 944dffdc33..720a4944fe 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 2c5ad4d44b..d0ba79378b 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 2feafabb4d..7796e8b951 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 9d290c3c6f..96eb5f48f5 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 4a5ad87942..2b39f2dcb8 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 a9693c65d8..f27b86680f 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 171614208f..2533f8c4d4 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 49ae6949a2..b6f3be52fc 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 cffa93ed29..dda93110b8 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 ed91d2339b..ec87093325 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 33602fd29e..473226653e 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 {