Merge pull request #16861 from btrent98/igmp-proxy2

Add igmp proxy support
This commit is contained in:
Donald Sharp 2024-09-24 12:32:15 -04:00 committed by GitHub
commit 269d63a5c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 729 additions and 17 deletions

View file

@ -302,6 +302,12 @@ is in a vrf, enter the interface command with the vrf keyword at the end.
Add a static multicast group or source-group on an interface. This will behave
as if there is a receiver on this interface without any IGMP reports.
.. clicmd:: ip igmp proxy
Tell pim to send proxy IGMP reports for joins occuring on all other
interfaces on this interface. Join-groups on other interfaces will
also be proxied. The default version is v3.
.. clicmd:: ip igmp query-interval (1-65535)
Set the IGMP query interval that PIM will use.
@ -475,6 +481,10 @@ cause great confusion.
Display IGMP group retransmission information.
.. clicmd:: show ip igmp [vrf NAME] proxy [json]
Display IGMP proxy join information.
.. clicmd:: show ip igmp [vrf NAME] sources [json]
Display IGMP sources information.

View file

@ -567,7 +567,7 @@ static void igmp_show_interfaces_single(struct pim_instance *pim,
}
static void igmp_show_interface_join(struct pim_instance *pim, struct vty *vty,
bool uj)
bool uj, enum gm_join_type join_type)
{
struct interface *ifp;
time_t now;
@ -612,6 +612,10 @@ static void igmp_show_interface_join(struct pim_instance *pim, struct vty *vty,
char source_str[INET_ADDRSTRLEN];
char uptime[10];
if (ij->join_type != join_type &&
ij->join_type != GM_JOIN_BOTH)
continue;
pim_time_uptime(uptime, sizeof(uptime),
now - ij->sock_creation);
pim_inet4_dump("<grp?>", ij->group_addr, group_str,
@ -1784,7 +1788,7 @@ DEFUN (show_ip_igmp_join,
if (!vrf)
return CMD_WARNING;
igmp_show_interface_join(vrf->info, vty, uj);
igmp_show_interface_join(vrf->info, vty, uj, GM_JOIN_STATIC);
return CMD_SUCCESS;
}
@ -1822,7 +1826,61 @@ DEFUN (show_ip_igmp_join_vrf_all,
first = false;
} else
vty_out(vty, "VRF: %s\n", vrf->name);
igmp_show_interface_join(vrf->info, vty, uj);
igmp_show_interface_join(vrf->info, vty, uj, GM_JOIN_STATIC);
}
if (uj)
vty_out(vty, "}\n");
return CMD_SUCCESS;
}
DEFUN (show_ip_igmp_proxy,
show_ip_igmp_proxy_cmd,
"show ip igmp [vrf NAME] proxy [json]",
SHOW_STR
IP_STR
IGMP_STR
VRF_CMD_HELP_STR
"IGMP proxy join information\n"
JSON_STR)
{
int idx = 2;
bool uj = use_json(argc, argv);
struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj);
if (!vrf)
return CMD_WARNING;
igmp_show_interface_join(vrf->info, vty, uj, GM_JOIN_PROXY);
return CMD_SUCCESS;
}
DEFUN (show_ip_igmp_proxy_vrf_all,
show_ip_igmp_proxy_vrf_all_cmd,
"show ip igmp vrf all proxy [json]",
SHOW_STR
IP_STR
IGMP_STR
VRF_CMD_HELP_STR
"IGMP proxy join information\n"
JSON_STR)
{
bool uj = use_json(argc, argv);
struct vrf *vrf;
bool first = true;
if (uj)
vty_out(vty, "{ ");
RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
if (uj) {
if (!first)
vty_out(vty, ", ");
vty_out(vty, " \"%s\": ", vrf->name);
first = false;
} else
vty_out(vty, "VRF: %s\n", vrf->name);
igmp_show_interface_join(vrf->info, vty, uj, GM_JOIN_PROXY);
}
if (uj)
vty_out(vty, "}\n");
@ -5756,6 +5814,18 @@ DEFUN (interface_no_ip_pim_hello,
return pim_process_no_ip_pim_hello_cmd(vty);
}
DEFPY (interface_ip_igmp_proxy,
interface_ip_igmp_proxy_cmd,
"[no] ip igmp proxy",
NO_STR
IP_STR
IGMP_STR
"Proxy IGMP join/prune operations\n")
{
return pim_process_ip_gmp_proxy_cmd(vty, !no);
}
DEFUN (debug_igmp,
debug_igmp_cmd,
"debug igmp",
@ -8718,6 +8788,7 @@ void pim_cmd_init(void)
&interface_ip_igmp_last_member_query_interval_cmd);
install_element(INTERFACE_NODE,
&interface_no_ip_igmp_last_member_query_interval_cmd);
install_element(INTERFACE_NODE, &interface_ip_igmp_proxy_cmd);
install_element(INTERFACE_NODE, &interface_ip_pim_activeactive_cmd);
install_element(INTERFACE_NODE, &interface_ip_pim_ssm_cmd);
install_element(INTERFACE_NODE, &interface_no_ip_pim_ssm_cmd);
@ -8761,6 +8832,8 @@ void pim_cmd_init(void)
install_element(VIEW_NODE, &show_ip_igmp_join_group_vrf_all_cmd);
install_element(VIEW_NODE, &show_ip_igmp_static_group_cmd);
install_element(VIEW_NODE, &show_ip_igmp_static_group_vrf_all_cmd);
install_element(VIEW_NODE, &show_ip_igmp_proxy_cmd);
install_element(VIEW_NODE, &show_ip_igmp_proxy_vrf_all_cmd);
install_element(VIEW_NODE, &show_ip_igmp_groups_cmd);
install_element(VIEW_NODE, &show_ip_igmp_groups_vrf_all_cmd);
install_element(VIEW_NODE, &show_ip_igmp_groups_retransmissions_cmd);

View file

@ -420,6 +420,17 @@ int pim_process_no_ip_pim_boundary_oil_cmd(struct vty *vty)
FRR_PIM_AF_XPATH_VAL);
}
int pim_process_ip_gmp_proxy_cmd(struct vty *vty, bool enable)
{
if (enable)
nb_cli_enqueue_change(vty, "./proxy", NB_OP_MODIFY, "true");
else
nb_cli_enqueue_change(vty, "./proxy", NB_OP_DESTROY, NULL);
return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH,
FRR_PIM_AF_XPATH_VAL);
}
int pim_process_ip_mroute_cmd(struct vty *vty, const char *interface,
const char *group_str, const char *source_str)
{

View file

@ -47,6 +47,7 @@ int pim_process_no_ip_pim_hello_cmd(struct vty *vty);
int pim_process_ip_pim_activeactive_cmd(struct vty *vty, const char *no);
int pim_process_ip_pim_boundary_oil_cmd(struct vty *vty, const char *oil);
int pim_process_no_ip_pim_boundary_oil_cmd(struct vty *vty);
int pim_process_ip_gmp_proxy_cmd(struct vty *vty, bool enable);
int pim_process_ip_mroute_cmd(struct vty *vty, const char *interface,
const char *group_str, const char *source_str);
int pim_process_no_ip_mroute_cmd(struct vty *vty, const char *interface,

View file

@ -1294,7 +1294,8 @@ static int gm_join_sock(const char *ifname, ifindex_t ifindex,
}
static struct gm_join *gm_join_new(struct interface *ifp, pim_addr group_addr,
pim_addr source_addr)
pim_addr source_addr,
enum gm_join_type join_type)
{
struct pim_interface *pim_ifp;
struct gm_join *ij;
@ -1317,6 +1318,7 @@ static struct gm_join *gm_join_new(struct interface *ifp, pim_addr group_addr,
ij->sock_fd = join_fd;
ij->group_addr = group_addr;
ij->source_addr = source_addr;
ij->join_type = join_type;
ij->sock_creation = pim_time_monotonic_sec();
listnode_add(pim_ifp->gm_join_list, ij);
@ -1353,7 +1355,7 @@ static struct static_group *static_group_new(struct interface *ifp,
}
ferr_r pim_if_gm_join_add(struct interface *ifp, pim_addr group_addr,
pim_addr source_addr)
pim_addr source_addr, enum gm_join_type join_type)
{
struct pim_interface *pim_ifp;
struct gm_join *ij;
@ -1375,10 +1377,13 @@ ferr_r pim_if_gm_join_add(struct interface *ifp, pim_addr group_addr,
* group
*/
if (ij) {
/* turn an existing join into a "both" join */
if (ij->join_type != join_type)
ij->join_type = GM_JOIN_BOTH;
return ferr_ok();
}
if (!gm_join_new(ifp, group_addr, source_addr)) {
if (!gm_join_new(ifp, group_addr, source_addr, join_type)) {
return ferr_cfg_invalid("can't join (%pPA,%pPA) on interface %s",
&source_addr, &group_addr, ifp->name);
}
@ -1394,7 +1399,7 @@ ferr_r pim_if_gm_join_add(struct interface *ifp, pim_addr group_addr,
}
int pim_if_gm_join_del(struct interface *ifp, pim_addr group_addr,
pim_addr source_addr)
pim_addr source_addr, enum gm_join_type join_type)
{
struct pim_interface *pim_ifp;
struct gm_join *ij;
@ -1420,6 +1425,20 @@ int pim_if_gm_join_del(struct interface *ifp, pim_addr group_addr,
return -3;
}
if (ij->join_type != join_type) {
if (ij->join_type != GM_JOIN_BOTH) {
zlog_warn("%s: wrong " GM
" gm_join_type %pPAs source %pPAs on interface %s",
__func__, &group_addr, &source_addr,
ifp->name);
return -4;
}
/* drop back to a single join type from current setting of GM_JOIN_BOTH */
ij->join_type = (join_type == GM_JOIN_STATIC ? GM_JOIN_PROXY
: GM_JOIN_STATIC);
return 0;
}
if (close(ij->sock_fd)) {
zlog_warn(
"%s: failure closing sock_fd=%d for " GM
@ -1456,7 +1475,8 @@ static void pim_if_gm_join_del_all(struct interface *ifp)
return;
for (ALL_LIST_ELEMENTS(pim_ifp->gm_join_list, node, nextnode, ij))
pim_if_gm_join_del(ifp, ij->group_addr, ij->source_addr);
pim_if_gm_join_del(ifp, ij->group_addr, ij->source_addr,
GM_JOIN_STATIC);
}
ferr_r pim_if_static_group_add(struct interface *ifp, pim_addr group_addr,
@ -1562,6 +1582,55 @@ static void pim_if_static_group_del_all(struct interface *ifp)
stgrp->source_addr);
}
void pim_if_gm_proxy_init(struct pim_instance *pim, struct interface *oif)
{
struct interface *ifp;
FOR_ALL_INTERFACES (pim->vrf, ifp) {
struct pim_interface *pim_ifp = ifp->info;
struct listnode *source_node, *group_node;
struct gm_group *group;
struct gm_source *src;
if (!pim_ifp)
continue;
if (ifp == oif) /* skip the source interface */
continue;
for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, group_node,
group)) {
for (ALL_LIST_ELEMENTS_RO(group->group_source_list,
source_node, src)) {
pim_if_gm_join_add(oif, group->group_addr,
src->source_addr,
GM_JOIN_PROXY);
}
}
} /* scan interfaces */
}
void pim_if_gm_proxy_finis(struct pim_instance *pim, struct interface *ifp)
{
struct pim_interface *pim_ifp = ifp->info;
struct listnode *join_node;
struct listnode *next_join_node;
struct gm_join *join;
if (!pim_ifp) {
zlog_warn("%s: multicast not enabled on interface %s", __func__,
ifp->name);
return;
}
for (ALL_LIST_ELEMENTS(pim_ifp->gm_join_list, join_node, next_join_node,
join)) {
if (join)
pim_if_gm_join_del(ifp, join->group_addr,
join->source_addr, GM_JOIN_PROXY);
}
}
/*
RFC 4601

View file

@ -63,6 +63,7 @@ struct pim_interface {
bool pim_passive_enable : 1;
bool gm_enable : 1;
bool gm_proxy : 1; /* proxy IGMP joins/prunes */
ifindex_t mroute_vif_index;
struct pim_instance *pim;
@ -219,9 +220,11 @@ int pim_if_t_override_msec(struct interface *ifp);
pim_addr pim_find_primary_addr(struct interface *ifp);
ferr_r pim_if_gm_join_add(struct interface *ifp, pim_addr group_addr,
pim_addr source_addr);
pim_addr source_addr, enum gm_join_type join_type);
int pim_if_gm_join_del(struct interface *ifp, pim_addr group_addr,
pim_addr source_addr);
pim_addr source_addr, enum gm_join_type join_type);
void pim_if_gm_proxy_init(struct pim_instance *pim, struct interface *oif);
void pim_if_gm_proxy_finis(struct pim_instance *pim, struct interface *ifp);
ferr_r pim_if_static_group_add(struct interface *ifp, pim_addr group_addr,
pim_addr source_addr);

View file

@ -213,15 +213,17 @@ void igmp_source_forward_stop(struct gm_source *source)
IGMP_SOURCE_TEST_FORWARDING(source->source_flags));
}
group = source->source_group;
pim_oif = group->interface->info;
/* Prevent IGMP interface from removing multicast route multiple
times */
if (!IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) {
tib_sg_proxy_join_prune_check(pim_oif->pim, sg,
group->interface, false);
return;
}
group = source->source_group;
pim_oif = group->interface->info;
tib_sg_gm_prune(pim_oif->pim, sg, group->interface,
&source->source_channel_oil);
IGMP_SOURCE_DONT_FORWARDING(source->source_flags);

View file

@ -51,10 +51,13 @@
output |= *((ptr) + 1); \
} while (0)
enum gm_join_type { GM_JOIN_STATIC = 0, GM_JOIN_PROXY = 1, GM_JOIN_BOTH = 2 };
struct gm_join {
pim_addr group_addr;
pim_addr source_addr;
int sock_fd;
enum gm_join_type join_type;
time_t sock_creation;
};

View file

@ -553,7 +553,13 @@ const struct frr_yang_module_info frr_gmp_info = {
.destroy = lib_interface_gmp_address_family_join_group_destroy,
}
},
{
{
.xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/proxy",
.cbs = {
.modify = lib_interface_gmp_address_family_proxy_modify,
}
},
{
.xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/static-group",
.cbs = {
.create = lib_interface_gmp_address_family_static_group_create,

View file

@ -221,6 +221,7 @@ int lib_interface_gmp_address_family_join_group_create(
struct nb_cb_create_args *args);
int lib_interface_gmp_address_family_join_group_destroy(
struct nb_cb_destroy_args *args);
int lib_interface_gmp_address_family_proxy_modify(struct nb_cb_modify_args *args);
int lib_interface_gmp_address_family_static_group_create(
struct nb_cb_create_args *args);
int lib_interface_gmp_address_family_static_group_destroy(

View file

@ -26,6 +26,7 @@
#include "lib_errors.h"
#include "pim_util.h"
#include "pim6_mld.h"
#include "pim_igmp.h"
#if PIM_IPV == 6
#define pim6_msdp_err(funcname, argtype) \
@ -3381,6 +3382,33 @@ int lib_interface_gmp_address_family_robustness_variable_modify(
return NB_OK;
}
/*
* XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/proxy
*/
int lib_interface_gmp_address_family_proxy_modify(struct nb_cb_modify_args *args)
{
struct interface *ifp;
struct pim_interface *pim_ifp;
switch (args->event) {
case NB_EV_VALIDATE:
case NB_EV_PREPARE:
case NB_EV_ABORT:
break;
case NB_EV_APPLY:
ifp = nb_running_get_entry(args->dnode, NULL, true);
pim_ifp = ifp->info;
if (pim_ifp)
pim_ifp->gm_proxy = yang_dnode_get_bool(args->dnode,
NULL);
if (pim_ifp->gm_proxy)
pim_if_gm_proxy_init(pim_ifp->pim, ifp);
else
pim_if_gm_proxy_finis(pim_ifp->pim, ifp);
}
return NB_OK;
}
/*
* XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/join-group
*/
@ -3432,7 +3460,8 @@ int lib_interface_gmp_address_family_join_group_create(
"./source-addr");
yang_dnode_get_pimaddr(&group_addr, args->dnode,
"./group-addr");
result = pim_if_gm_join_add(ifp, group_addr, source_addr);
result = pim_if_gm_join_add(ifp, group_addr, source_addr,
GM_JOIN_STATIC);
if (result) {
snprintf(args->errmsg, args->errmsg_len,
"Failure joining " GM " group");
@ -3461,7 +3490,8 @@ int lib_interface_gmp_address_family_join_group_destroy(
"./source-addr");
yang_dnode_get_pimaddr(&group_addr, args->dnode,
"./group-addr");
result = pim_if_gm_join_del(ifp, group_addr, source_addr);
result = pim_if_gm_join_del(ifp, group_addr, source_addr,
GM_JOIN_STATIC);
if (result) {
snprintf(args->errmsg, args->errmsg_len,

View file

@ -78,6 +78,31 @@ tib_sg_oil_setup(struct pim_instance *pim, pim_sgaddr sg, struct interface *oif)
return pim_channel_oil_add(pim, &sg, __func__);
}
void tib_sg_proxy_join_prune_check(struct pim_instance *pim, pim_sgaddr sg,
struct interface *oif, bool join)
{
struct interface *ifp;
FOR_ALL_INTERFACES (pim->vrf, ifp) {
struct pim_interface *pim_ifp = ifp->info;
if (!pim_ifp)
continue;
if (ifp == oif) /* skip the source interface */
continue;
if (pim_ifp->gm_enable && pim_ifp->gm_proxy) {
if (join)
pim_if_gm_join_add(ifp, sg.grp, sg.src,
GM_JOIN_PROXY);
else
pim_if_gm_join_del(ifp, sg.grp, sg.src,
GM_JOIN_PROXY);
}
} /* scan interfaces */
}
bool tib_sg_gm_join(struct pim_instance *pim, pim_sgaddr sg,
struct interface *oif, struct channel_oil **oilp)
{
@ -95,6 +120,8 @@ bool tib_sg_gm_join(struct pim_instance *pim, pim_sgaddr sg,
if (!*oilp)
return false;
tib_sg_proxy_join_prune_check(pim, sg, oif, true);
if (PIM_I_am_DR(pim_oif) || PIM_I_am_DualActive(pim_oif)) {
int result;
@ -137,6 +164,8 @@ void tib_sg_gm_prune(struct pim_instance *pim, pim_sgaddr sg,
{
int result;
tib_sg_proxy_join_prune_check(pim, sg, oif, false);
/*
It appears that in certain circumstances that
igmp_source_forward_stop is called when IGMP forwarding

View file

@ -16,5 +16,8 @@ extern bool tib_sg_gm_join(struct pim_instance *pim, pim_sgaddr sg,
struct interface *oif, struct channel_oil **oilp);
extern void tib_sg_gm_prune(struct pim_instance *pim, pim_sgaddr sg,
struct interface *oif, struct channel_oil **oilp);
extern void tib_sg_proxy_join_prune_check(struct pim_instance *pim,
pim_sgaddr sg, struct interface *oif,
bool join);
#endif /* _FRR_PIM_GLUE_H */

View file

@ -270,6 +270,11 @@ static int gm_config_write(struct vty *vty, int writes,
++writes;
}
if (pim_ifp->gm_proxy) {
vty_out(vty, " ip igmp proxy\n");
++writes;
}
/* ip igmp version */
if (pim_ifp->igmp_version != IGMP_DEFAULT_VERSION) {
vty_out(vty, " ip igmp version %d\n", pim_ifp->igmp_version);

View file

@ -4260,6 +4260,7 @@ def verify_local_igmp_groups(tgen, dut, interface, group_addresses):
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
return True
@retry(retry_timeout=62)
def verify_static_groups(tgen, dut, interface, group_addresses):
"""
@ -4293,7 +4294,9 @@ def verify_static_groups(tgen, dut, interface, group_addresses):
rnode = tgen.routers()[dut]
logger.info("[DUT: %s]: Verifying static groups received:", dut)
show_static_group_json = run_frr_cmd(rnode, "show ip igmp static-group json", isjson=True)
show_static_group_json = run_frr_cmd(
rnode, "show ip igmp static-group json", isjson=True
)
if type(group_addresses) is not list:
group_addresses = [group_addresses]
@ -4330,6 +4333,71 @@ def verify_static_groups(tgen, dut, interface, group_addresses):
logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name))
return True
@retry(retry_timeout=62)
def verify_local_igmp_proxy_groups(
tgen, dut, group_addresses_present, group_addresses_not_present
):
"""
Verify igmp proxy groups are as expected by running
"show ip igmp static-group json" command
Parameters
----------
* `tgen`: topogen object
* `dut`: device under test
* `group_addresses_present`: IGMP group addresses which should
currently be proxied
* `group_addresses_not_present`: IGMP group addresses which should
not currently be proxied
Usage
-----
dut = "r1"
group_addresses_present = "225.1.1.1"
group_addresses_not_present = "225.2.2.2"
result = verify_igmp_proxy_groups(tgen, dut, group_p, group_np)
Returns
-------
errormsg(str) or True
"""
if dut not in tgen.routers():
errormsg = "[DUT %s]: Device not found!"
return errormsg
rnode = tgen.routers()[dut]
logger.info("[DUT: %s]: Verifying local IGMP proxy groups:", dut)
out = rnode.vtysh_cmd("show ip igmp proxy json", isjson=True)
groups = [g["group"] if "group" in g else None for g in out["r1-eth1"]["groups"]]
if type(group_addresses_present) is not list:
group_addresses_present = [group_addresses_present]
if type(group_addresses_not_present) is not list:
group_addresses_not_present = [group_addresses_not_present]
for test_addr in group_addresses_present:
if not test_addr in groups:
errormsg = (
"[DUT %s]: Verifying local IGMP proxy joins FAILED!! "
" Expected but not found: %s " % (dut, test_addr)
)
return errormsg
for test_addr in group_addresses_not_present:
if test_addr in groups:
errormsg = (
"[DUT %s]: Verifying local IGMP proxy join removed FAILED!! "
" Unexpected but found: %s " % (dut, test_addr)
)
return errormsg
return True
def verify_pim_interface_traffic(tgen, input_dict, return_stats=True, addr_type="ipv4"):
"""
Verify ip pim interface traffic by running

View file

@ -0,0 +1,29 @@
hostname r1
!
interface r1-eth0
ip address 10.0.20.1/24
ip igmp
ip pim
ip igmp join 225.1.1.1
ip igmp join 225.2.2.2
!
interface r1-eth1
ip address 10.0.30.1/24
ip pim
ip igmp
ip igmp proxy
!
interface r1-eth2
ip address 10.0.40.1/24
ip igmp
ip pim
ip igmp join 225.3.3.3
ip igmp join 225.4.4.4
!
interface lo
ip address 10.254.0.1/32
ip pim
!
router pim
rp 10.254.0.3
join-prune-interval 5

View file

@ -0,0 +1,19 @@
hostname r2
!
interface r2-eth0
ip address 10.0.20.2/24
ip igmp
ip pim
ip igmp proxy
!
interface r2-eth1
ip address 10.0.80.1/24
ip igmp
ip pim passive
!
interface lo
ip address 10.254.0.2/32
!
router pim
rp 10.254.0.3
join-prune-interval 5

View file

@ -0,0 +1,8 @@
hostname r3
!
interface r3-eth0
ip address 10.0.40.4/24
!
interface lo
ip address 10.254.0.4/32
!

View file

@ -0,0 +1,16 @@
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
join-prune-interval 5
rp 10.254.0.3
register-accept-list ACCEPT
ip prefix-list ACCEPT seq 5 permit 10.0.20.0/24 le 32

View file

@ -0,0 +1,319 @@
#!/usr/bin/env python
# SPDX-License-Identifier: ISC
#
# test_pim_igmp_proxy.py
#
# Copyright (c) 2024 ATCorp
# Barry A. Trent
#
"""
Following tests are covered to test pim igmp proxy:
1. TC:1 Verify correct joins were read from the config and proxied
2. TC:2 Verify joins from another interface are proxied
3. TC:3 Verify correct proxy disable on 'no ip igmp proxy'
4. TC:4 Verify that proper proxy joins are set up on run-time enable
5. TC:5 Verify igmp drops/timeouts from another interface cause
proxy join removal
"""
import os
import sys
import pytest
import json
import time
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
from lib.pim import verify_local_igmp_proxy_groups
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"])
# 10.0.40.0/24
sw = tgen.add_switch("sw3")
sw.add_link(tgen.gears["r1"])
sw.add_link(tgen.gears["r3"])
# Dummy interface for static joins
tgen.gears["r2"].run("ip link add r2-eth1 type dummy")
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():
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_igmp_proxy_config():
"Ensure correct joins were read from the config and proxied"
logger.info("Verify initial igmp proxy setup from config file")
tgen = get_topogen()
r1 = tgen.gears["r1"]
expected = {
"vrf": "default",
"r1-eth1": {
"name": "r1-eth1",
"groups": [
{
"source": "*",
"group": "225.4.4.4",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.3.3.3",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.2.2.2",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.1.1.1",
"primaryAddr": "10.0.30.1",
},
],
},
}
test_func = partial(
topotest.router_json_cmp, r1, "show ip igmp proxy 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_igmp_proxy_learn():
"Ensure joins learned from a neighbor are propagated"
logger.info("Verify joins can be learned")
tgen = get_topogen()
r1 = tgen.gears["r1"]
r2 = tgen.gears["r2"]
r2.vtysh_cmd(
"conf\nint r2-eth0\nip igmp join 225.5.5.5\nip igmp join 225.6.6.6\nexit\nexit"
)
r2.vtysh_cmd(
"conf\nint r2-eth1\nip igmp join 225.7.7.7\nip igmp join 225.8.8.8\nexit\nexit"
)
expected = {
"vrf": "default",
"r1-eth1": {
"name": "r1-eth1",
"groups": [
{
"source": "*",
"group": "225.5.5.5",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.6.6.6",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.7.7.7",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.8.8.8",
"primaryAddr": "10.0.30.1",
},
],
},
}
test_func = partial(
topotest.router_json_cmp, r1, "show ip igmp proxy 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_no_igmp_proxy():
"Check for correct proxy disable"
logger.info("Verify no ip igmp proxy")
tgen = get_topogen()
r1 = tgen.gears["r1"]
r1.vtysh_cmd("conf\nint r1-eth1\nno ip igmp proxy\nexit\nexit")
expected = {"vrf": "default"}
test_func = partial(
topotest.router_json_cmp, r1, "show ip igmp proxy 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_igmp_proxy_restart():
"Check that all proxy joins are captured at run-time enable"
logger.info("Verify runtime ip igmp proxy")
tgen = get_topogen()
r1 = tgen.gears["r1"]
r1.vtysh_cmd("conf\nint r1-eth1\nip igmp proxy\nexit\nexit")
expected = {
"vrf": "default",
"r1-eth1": {
"name": "r1-eth1",
"groups": [
{
"source": "*",
"group": "225.8.8.8",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.7.7.7",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.6.6.6",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.5.5.5",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.4.4.4",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.3.3.3",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.2.2.2",
"primaryAddr": "10.0.30.1",
},
{
"source": "*",
"group": "225.1.1.1",
"primaryAddr": "10.0.30.1",
},
],
},
}
test_func = partial(
topotest.router_json_cmp, r1, "show ip igmp proxy 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_igmp_proxy_leave():
"Ensure drops/timeouts learned from a neighbor are propagated"
logger.info("Verify joins can be dropped")
tgen = get_topogen()
r1 = tgen.gears["r1"]
r2 = tgen.gears["r2"]
r1.vtysh_cmd("conf\nint r1-eth0\nno ip igmp join 225.1.1.1\nexit\nexit")
r2.vtysh_cmd("conf\nint r2-eth0\nno ip igmp join 225.6.6.6\nexit\nexit")
r2.vtysh_cmd("conf\nint r2-eth1\nno ip igmp join 225.8.8.8\nexit\nexit")
joined_addresses = ["225.2.2.2", "225.3.3.3", "225.4.4.4", "225.5.5.5", "225.7.7.7"]
deleted_addresses = ["225.1.1.1", "225.6.6.6", "225.8.8.8"]
result = verify_local_igmp_proxy_groups(
tgen, "r1", joined_addresses, deleted_addresses
)
assert result is True, "Error: {}".format(result)
# tgen.mininet_cli()
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))

View file

@ -146,6 +146,13 @@ module frr-gmp {
"Querier's Robustness Variable allows tuning for the
expected packet loss on a network.";
}
leaf proxy {
type boolean;
default "false";
description
"Enable IGMP proxy on the interface.";
}
list join-group {
key "group-addr source-addr";