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 <csiltala@atcorp.com>
This commit is contained in:
Corey Siltala 2024-11-25 10:36:54 -06:00
parent a9bee74ea2
commit 4de4017d64
15 changed files with 184 additions and 33 deletions

View file

@ -5871,6 +5871,21 @@ DEFUN(interface_no_ip_pim_boundary_oil,
return pim_process_no_ip_pim_boundary_oil_cmd(vty); 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, DEFUN (interface_ip_mroute,
interface_ip_mroute_cmd, interface_ip_mroute_cmd,
"ip mroute INTERFACE A.B.C.D [A.B.C.D]", "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_no_ip_pim_hello_cmd);
install_element(INTERFACE_NODE, &interface_ip_pim_boundary_oil_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_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); install_element(INTERFACE_NODE, &interface_ip_igmp_query_generate_cmd);
// Static mroutes NEB // Static mroutes NEB

View file

@ -38,6 +38,7 @@
#include "pim_igmp_join.h" #include "pim_igmp_join.h"
#include "pim_vxlan.h" #include "pim_vxlan.h"
#include "pim_tib.h" #include "pim_tib.h"
#include "pim_util.h"
#include "pim6_mld.h" #include "pim6_mld.h"
@ -1258,6 +1259,14 @@ static int gm_join_sock(const char *ifname, ifindex_t ifindex,
{ {
int join_fd; 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++; pim_ifp->igmp_ifstat_joins_sent++;
join_fd = pim_socket_raw(IPPROTO_GM); join_fd = pim_socket_raw(IPPROTO_GM);

View file

@ -133,8 +133,10 @@ struct pim_interface {
uint32_t pim_dr_priority; /* config */ uint32_t pim_dr_priority; /* config */
int pim_dr_num_nondrpri_neighbors; /* neighbors without dr_pri */ int pim_dr_num_nondrpri_neighbors; /* neighbors without dr_pri */
/* boundary prefix-list */ /* boundary prefix-list (group) */
char *boundary_oil_plist; char *boundary_oil_plist;
/* boundary access-list (source and group) */
struct access_list *boundary_acl;
/* Turn on Active-Active for this interface */ /* Turn on Active-Active for this interface */
bool activeactive; bool activeactive;

View file

@ -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)); 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; return -1;
/* non-existent group is created as INCLUDE {empty} */ /* non-existent group is created as INCLUDE {empty} */

View file

@ -134,6 +134,9 @@ int igmp_v2_recv_report(struct gm_sock *igmp, struct in_addr from,
ifp->name, group_str); ifp->name, group_str);
} }
if (pim_is_group_filtered(pim_ifp, &group_addr, NULL))
return -1;
/* /*
* RFC 4604 * RFC 4604
* section 2.2.1 * section 2.2.1

View file

@ -507,6 +507,8 @@ static void allow(struct gm_sock *igmp, struct in_addr from,
struct in_addr *src_addr; struct in_addr *src_addr;
src_addr = sources + i; 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); source = igmp_get_source_by_addr(group, *src_addr, NULL);
if (!source) 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); 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; return;
/* non-existent group is created as INCLUDE {empty} */ /* 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; pim_ifp = ifp->info;
/* determine filtering status for group */ /* 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) { if (PIM_DEBUG_GM_PACKETS) {
zlog_debug( zlog_debug("Filtering IGMPv3 group record %pI4 from %s on %s per prefix-list %s or access-list %s",
"Filtering IGMPv3 group record %pI4 from %s on %s per prefix-list %s", &grp.s_addr, from_str, ifp->name,
&grp.s_addr, from_str, ifp->name, (pim_ifp->boundary_oil_plist ? 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; return false;
} }
@ -1943,11 +1946,9 @@ int igmp_v3_recv_report(struct gm_sock *igmp, struct in_addr from,
sizeof(struct in_addr)); sizeof(struct in_addr));
if (PIM_DEBUG_GM_PACKETS) { if (PIM_DEBUG_GM_PACKETS) {
zlog_debug( zlog_debug(" Recv IGMP report v3 (type %d) from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%pI4",
" Recv IGMP report v3 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,
from_str, ifp->name, i, rec_type, rec_num_sources, &rec_group);
rec_auxdatalen, rec_num_sources,
&rec_group);
} }
/* Scan sources */ /* Scan sources */

View file

@ -245,7 +245,7 @@ int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh,
uint16_t msg_num_pruned_sources; uint16_t msg_num_pruned_sources;
int source; int source;
struct pim_ifchannel *starg_ch = NULL, *sg_ch = NULL; struct pim_ifchannel *starg_ch = NULL, *sg_ch = NULL;
bool filtered = false; bool group_filtered = false;
memset(&sg, 0, sizeof(sg)); memset(&sg, 0, sizeof(sg));
addr_offset = pim_parse_addr_group(&sg, buf, pastend - buf); 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); &src_addr, ifp->name);
/* boundary check */ /* 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 */ /* Scan joined sources */
for (source = 0; source < msg_num_joined_sources; ++source) { 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; buf += addr_offset;
/* if we are filtering this group, skip the join */ /* if we are filtering this group or (S,G), skip the join */
if (filtered) if (group_filtered || pim_is_group_filtered(pim_ifp, &sg.grp, &sg.src))
continue; continue;
recv_join(ifp, neigh, msg_holdtime, msg_upstream_addr, 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; buf += addr_offset;
/* if we are filtering this group, skip the prune */
if (filtered)
continue;
recv_prune(ifp, neigh, msg_holdtime, msg_upstream_addr, recv_prune(ifp, neigh, msg_holdtime, msg_upstream_addr,
&sg, msg_source_flags); &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); pim_ifchannel_set_star_g_join_state(starg_ch, 1, 0);
starg_ch = NULL; starg_ch = NULL;
} /* scan groups */ } /* scan groups */

View file

@ -35,6 +35,7 @@
#include "pim_sock.h" #include "pim_sock.h"
#include "pim_vxlan.h" #include "pim_vxlan.h"
#include "pim_msg.h" #include "pim_msg.h"
#include "pim_util.h"
static void mroute_read_on(struct pim_instance *pim); static void mroute_read_on(struct pim_instance *pim);
static int pim_upstream_mroute_update(struct channel_oil *c_oil, 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) { *oil_incoming_vif(up->channel_oil) >= MAXVIFS) {
pim_upstream_mroute_iif_update(up->channel_oil, __func__); 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 */ /* if we have receiver, inherit from parent */
pim_upstream_inherited_olist_decide(pim_ifp->pim, up); 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( pim_upstream_keep_alive_timer_start(
up, pim_ifp->pim->keep_alive_time); up, pim_ifp->pim->keep_alive_time);
up->channel_oil->cc.pktcnt++; 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); pim_upstream_inherited_olist(pim_ifp->pim, up);
if (!up->channel_oil->installed) if (!up->channel_oil->installed)
pim_upstream_mroute_add(up->channel_oil, __func__); pim_upstream_mroute_add(up->channel_oil, __func__);

View file

@ -352,6 +352,13 @@ const struct frr_yang_module_info frr_pim_info = {
.destroy = lib_interface_pim_address_family_multicast_boundary_oil_destroy, .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", .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/mroute",
.cbs = { .cbs = {

View file

@ -140,6 +140,8 @@ int lib_interface_pim_address_family_multicast_boundary_oil_modify(
struct nb_cb_modify_args *args); struct nb_cb_modify_args *args);
int lib_interface_pim_address_family_multicast_boundary_oil_destroy( int lib_interface_pim_address_family_multicast_boundary_oil_destroy(
struct nb_cb_destroy_args *args); 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( int lib_interface_pim_address_family_mroute_create(
struct nb_cb_create_args *args); struct nb_cb_create_args *args);
int lib_interface_pim_address_family_mroute_destroy( int lib_interface_pim_address_family_mroute_destroy(

View file

@ -2452,6 +2452,71 @@ int lib_interface_pim_address_family_multicast_boundary_oil_destroy(
return NB_OK; 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 * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/mroute
*/ */

View file

@ -10,6 +10,8 @@
#include "prefix.h" #include "prefix.h"
#include "plist.h" #include "plist.h"
#include "pimd.h"
#include "pim_instance.h"
#include "pim_util.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); 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; bool is_filtered = false;
struct prefix_list *pl; #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; return false;
pim_addr_to_prefix(&grp_pfx, *grp); pim_addr_to_prefix(&grp_pfx, *grp);
pl = prefix_list_lookup(PIM_AFI, pim_ifp->boundary_oil_plist); pl = prefix_list_lookup(PIM_AFI, pim_ifp->boundary_oil_plist);
return pl ? prefix_list_apply_ext(pl, NULL, &grp_pfx, true) ==
PREFIX_DENY /* Filter if either group or (S,G) are denied */
: false; 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;
} }

View file

@ -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); 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, enum filter_type pim_access_list_apply(struct access_list *access, const struct in_addr *source,
const struct in_addr *group); 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); int pim_get_all_mcast_group(struct prefix *prefix);
bool pim_addr_is_multicast(pim_addr addr); bool pim_addr_is_multicast(pim_addr addr);
#endif /* PIM_UTIL_H */ #endif /* PIM_UTIL_H */

View file

@ -12,6 +12,7 @@
#include "vty.h" #include "vty.h"
#include "vrf.h" #include "vrf.h"
#include "plist.h" #include "plist.h"
#include "filter.h"
#include "pimd.h" #include "pimd.h"
#include "pim_vty.h" #include "pim_vty.h"
@ -496,6 +497,12 @@ int pim_config_write(struct vty *vty, int writes, struct interface *ifp,
++writes; ++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) { if (pim_ifp->pim_passive_enable) {
vty_out(vty, " " PIM_AF_NAME " pim passive\n"); vty_out(vty, " " PIM_AF_NAME " pim passive\n");
++writes; ++writes;

View file

@ -78,6 +78,10 @@ module frr-pim {
type string; type string;
} }
typedef access-list-ref {
type string;
}
/* /*
* Groupings * Groupings
*/ */
@ -507,7 +511,13 @@ module frr-pim {
leaf multicast-boundary-oil { leaf multicast-boundary-oil {
type plist-ref; type plist-ref;
description 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 { list mroute {