forked from Mirror/frr
Merge pull request #17461 from csiltala/multicast-boundary-acl
pimd: Extend multicast boundary/ACL functionality
This commit is contained in:
commit
c05c2b15e5
|
@ -9,9 +9,7 @@ defined, it can be applied in any direction.
|
||||||
IP Access List
|
IP Access List
|
||||||
==============
|
==============
|
||||||
|
|
||||||
.. clicmd:: access-list NAME [seq (1-4294967295)] permit IPV4-NETWORK
|
.. clicmd:: access-list NAME [seq (1-4294967295)] <permit|deny> <A.B.C.D/M [exact-match]|any>
|
||||||
|
|
||||||
.. clicmd:: access-list NAME [seq (1-4294967295)] deny IPV4-NETWORK
|
|
||||||
|
|
||||||
seq
|
seq
|
||||||
seq `number` can be set either automatically or manually. In the
|
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 permit 10.0.0.0/8
|
||||||
access-list filter seq 13 permit 10.0.0.0/7
|
access-list filter seq 13 permit 10.0.0.0/7
|
||||||
|
|
||||||
|
.. clicmd:: access-list NAME [seq (1-4294967295)] <deny|permit> ip <A.B.C.D A.B.C.D|host A.B.C.D|any> <A.B.C.D A.B.C.D|host A.B.C.D|any>
|
||||||
|
|
||||||
|
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 <ip|ipv6> access-list [json]
|
.. clicmd:: show <ip|ipv6> access-list [json]
|
||||||
|
|
||||||
Display all IPv4 or IPv6 access lists.
|
Display all IPv4 or IPv6 access lists.
|
||||||
|
|
|
@ -6,9 +6,9 @@ PIM
|
||||||
|
|
||||||
PIM -- Protocol Independent Multicast
|
PIM -- Protocol Independent Multicast
|
||||||
|
|
||||||
*pimd* supports pim-sm as well as igmp v2 and v3. pim is
|
*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
|
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
|
do S,G mrouting. Additionally, PIM can be used in the EVPN underlay
|
||||||
network for optimizing forwarding of overlay BUM traffic.
|
network for optimizing forwarding of overlay BUM traffic.
|
||||||
|
|
||||||
.. note::
|
.. 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
|
.. clicmd:: ip multicast boundary oil WORD
|
||||||
|
|
||||||
Set a pim multicast boundary, based upon the WORD prefix-list. If a pim join
|
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
|
or IGMP report is received on this interface and the group is denied by the
|
||||||
prefix-list, PIM will ignore the join or report.
|
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)
|
.. clicmd:: ip igmp last-member-query-count (1-255)
|
||||||
|
|
||||||
Set the IGMP last member query count. The default value is 2. 'no' form of
|
Set the IGMP last member query count. The default value is 2. 'no' form of
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
@ -215,7 +216,6 @@ void pim_if_delete(struct interface *ifp)
|
||||||
if (pim_ifp->bfd_config.profile)
|
if (pim_ifp->bfd_config.profile)
|
||||||
XFREE(MTYPE_TMP, 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);
|
XFREE(MTYPE_PIM_INTERFACE, pim_ifp);
|
||||||
|
|
||||||
ifp->info = NULL;
|
ifp->info = NULL;
|
||||||
|
@ -1258,6 +1258,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);
|
||||||
|
|
|
@ -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;
|
struct prefix_list *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;
|
||||||
|
|
|
@ -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} */
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "if.h"
|
#include "if.h"
|
||||||
#include "lib_errors.h"
|
#include "lib_errors.h"
|
||||||
|
#include "plist.h"
|
||||||
|
#include "plist_int.h"
|
||||||
|
|
||||||
#include "pimd.h"
|
#include "pimd.h"
|
||||||
#include "pim_instance.h"
|
#include "pim_instance.h"
|
||||||
|
@ -507,6 +509,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 +650,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 +1813,14 @@ 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->name
|
||||||
pim_ifp->boundary_oil_plist);
|
: "(not found)"),
|
||||||
|
(pim_ifp->boundary_acl ? pim_ifp->boundary_acl->name
|
||||||
|
: "(not found)"));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1943,11 +1949,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 */
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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__);
|
||||||
|
|
|
@ -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);
|
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,
|
bool msdp_peer_sa_filter(const struct pim_msdp_peer *mp,
|
||||||
const struct pim_msdp_sa *sa)
|
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. */
|
/* Find access list and test it. */
|
||||||
acl = access_list_lookup(AFI_IP, mp->acl_out);
|
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 true;
|
||||||
|
|
||||||
return false;
|
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. */
|
/* Filter incoming SA with configured access list. */
|
||||||
if (mp->acl_in) {
|
if (mp->acl_in) {
|
||||||
acl = access_list_lookup(AFI_IP, 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))
|
if (pim_msdp_log_sa_events(mp->pim))
|
||||||
zlog_info("MSDP peer %pI4 filter SA in (%pI4, %pI4)", &mp->peer,
|
zlog_info("MSDP peer %pI4 filter SA in (%pI4, %pI4)", &mp->peer,
|
||||||
&sg.src, &sg.grp);
|
&sg.src, &sg.grp);
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -2390,7 +2390,6 @@ int lib_interface_pim_address_family_multicast_boundary_oil_modify(
|
||||||
{
|
{
|
||||||
struct interface *ifp;
|
struct interface *ifp;
|
||||||
struct pim_interface *pim_ifp;
|
struct pim_interface *pim_ifp;
|
||||||
const char *plist;
|
|
||||||
const struct lyd_node *if_dnode;
|
const struct lyd_node *if_dnode;
|
||||||
|
|
||||||
switch (args->event) {
|
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_dnode = yang_dnode_get_parent(args->dnode, "interface");
|
||||||
if (!is_pim_interface(if_dnode)) {
|
if (!is_pim_interface(if_dnode)) {
|
||||||
snprintf(args->errmsg, args->errmsg_len,
|
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;
|
return NB_ERR_VALIDATION;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -2408,13 +2412,8 @@ int lib_interface_pim_address_family_multicast_boundary_oil_modify(
|
||||||
case NB_EV_APPLY:
|
case NB_EV_APPLY:
|
||||||
ifp = nb_running_get_entry(args->dnode, NULL, true);
|
ifp = nb_running_get_entry(args->dnode, NULL, true);
|
||||||
pim_ifp = ifp->info;
|
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 =
|
pim_ifp->boundary_oil_plist =
|
||||||
XSTRDUP(MTYPE_PIM_INTERFACE, plist);
|
prefix_list_lookup(AFI_IP, yang_dnode_get_string(args->dnode, NULL));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2444,8 +2443,72 @@ int lib_interface_pim_address_family_multicast_boundary_oil_destroy(
|
||||||
case NB_EV_APPLY:
|
case NB_EV_APPLY:
|
||||||
ifp = nb_running_get_entry(args->dnode, NULL, true);
|
ifp = nb_running_get_entry(args->dnode, NULL, true);
|
||||||
pim_ifp = ifp->info;
|
pim_ifp = ifp->info;
|
||||||
if (pim_ifp->boundary_oil_plist)
|
pim_ifp->boundary_oil_plist = NULL;
|
||||||
XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist);
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,10 @@
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "prefix.h"
|
#include "prefix.h"
|
||||||
#include "plist.h"
|
#include "plist.h"
|
||||||
|
#include "plist_int.h"
|
||||||
|
|
||||||
|
#include "pimd.h"
|
||||||
|
#include "pim_instance.h"
|
||||||
#include "pim_util.h"
|
#include "pim_util.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -126,20 +129,86 @@ int pim_is_group_224_4(struct in_addr group_addr)
|
||||||
return prefix_match(&group_all, &group);
|
return prefix_match(&group_all, &group);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp)
|
static bool pim_cisco_match(const struct filter *filter, const struct in_addr *source,
|
||||||
|
const struct in_addr *group)
|
||||||
{
|
{
|
||||||
struct prefix grp_pfx;
|
const struct filter_cisco *cfilter = &filter->u.cfilter;
|
||||||
struct prefix_list *pl;
|
uint32_t source_addr;
|
||||||
|
uint32_t group_addr;
|
||||||
|
|
||||||
if (!pim_ifp->boundary_oil_plist)
|
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, pim_addr *src)
|
||||||
|
{
|
||||||
|
bool is_filtered = false;
|
||||||
|
#if PIM_IPV == 4
|
||||||
|
struct prefix grp_pfx = {};
|
||||||
|
pim_addr any_src = PIMADDR_ANY;
|
||||||
|
|
||||||
|
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);
|
/* Filter if either group or (S,G) are denied */
|
||||||
return pl ? prefix_list_apply_ext(pl, NULL, &grp_pfx, true) ==
|
if (pim_ifp->boundary_oil_plist) {
|
||||||
PREFIX_DENY
|
is_filtered = prefix_list_apply_ext(pim_ifp->boundary_oil_plist, NULL, &grp_pfx,
|
||||||
: false;
|
true) == PREFIX_DENY;
|
||||||
|
if (is_filtered && PIM_DEBUG_EVENTS) {
|
||||||
|
zlog_debug("Filtering group %pI4 per prefix-list %s", grp,
|
||||||
|
pim_ifp->boundary_oil_plist->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <zebra.h>
|
#include <zebra.h>
|
||||||
|
#include "lib/filter.h"
|
||||||
|
|
||||||
#include "checksum.h"
|
#include "checksum.h"
|
||||||
#include "pimd.h"
|
#include "pimd.h"
|
||||||
|
@ -22,7 +23,9 @@ 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_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);
|
||||||
bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp);
|
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, 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 */
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include "vty.h"
|
#include "vty.h"
|
||||||
#include "vrf.h"
|
#include "vrf.h"
|
||||||
#include "plist.h"
|
#include "plist.h"
|
||||||
|
#include "plist_int.h"
|
||||||
|
#include "filter.h"
|
||||||
|
|
||||||
#include "pimd.h"
|
#include "pimd.h"
|
||||||
#include "pim_vty.h"
|
#include "pim_vty.h"
|
||||||
|
@ -492,7 +494,13 @@ int pim_config_write(struct vty *vty, int writes, struct interface *ifp,
|
||||||
/* boundary */
|
/* boundary */
|
||||||
if (pim_ifp->boundary_oil_plist) {
|
if (pim_ifp->boundary_oil_plist) {
|
||||||
vty_out(vty, " " PIM_AF_NAME " multicast boundary oil %s\n",
|
vty_out(vty, " " PIM_AF_NAME " multicast boundary oil %s\n",
|
||||||
pim_ifp->boundary_oil_plist);
|
pim_ifp->boundary_oil_plist->name);
|
||||||
|
++writes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pim_ifp->boundary_acl) {
|
||||||
|
vty_out(vty, " " PIM_AF_NAME " multicast boundary %s\n",
|
||||||
|
pim_ifp->boundary_acl->name);
|
||||||
++writes;
|
++writes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
39
tests/topotests/pim_boundary_acl/r1/frr.conf
Normal file
39
tests/topotests/pim_boundary_acl/r1/frr.conf
Normal file
|
@ -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
|
19
tests/topotests/pim_boundary_acl/r2/frr.conf
Normal file
19
tests/topotests/pim_boundary_acl/r2/frr.conf
Normal file
|
@ -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
|
||||||
|
!
|
13
tests/topotests/pim_boundary_acl/r3/frr.conf
Normal file
13
tests/topotests/pim_boundary_acl/r3/frr.conf
Normal file
|
@ -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
|
||||||
|
!
|
22
tests/topotests/pim_boundary_acl/rp/frr.conf
Normal file
22
tests/topotests/pim_boundary_acl/rp/frr.conf
Normal file
|
@ -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
|
523
tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py
Normal file
523
tests/topotests/pim_boundary_acl/test_pim_boundary_acl.py
Normal file
|
@ -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))
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue