mirror of
https://github.com/FRRouting/frr.git
synced 2025-04-30 13:37:17 +02:00
Merge 07423e7d52
into 3dd4d417be
This commit is contained in:
commit
3f3ff7facb
|
@ -410,6 +410,10 @@ is in a vrf, enter the interface command with the vrf keyword at the end.
|
|||
Tell pim to receive IGMP reports and Query on this interface. The default
|
||||
version is v3. This command is useful on a LHR.
|
||||
|
||||
.. clicmd:: ip igmp require-router-alert
|
||||
|
||||
Only accept IGMP reports with the router-alert IP option.
|
||||
|
||||
.. clicmd:: ip igmp join-group A.B.C.D [A.B.C.D]
|
||||
|
||||
Join multicast group or source-group on an interface. This will result in
|
||||
|
|
|
@ -245,6 +245,10 @@ is in a vrf, enter the interface command with the vrf keyword at the end.
|
|||
Tell pim to receive MLD reports and Query on this interface. The default
|
||||
version is v2. This command is useful on a LHR.
|
||||
|
||||
.. clicmd:: ipv6 mld require-router-alert
|
||||
|
||||
Only accept MLD reports with the router-alert IPv6 hop option.
|
||||
|
||||
.. clicmd:: ipv6 mld join X:X::X:X [Y:Y::Y:Y]
|
||||
|
||||
Join multicast group or source-group on an interface.
|
||||
|
|
|
@ -1845,6 +1845,18 @@ ALIAS(interface_ipv6_pim_neighbor_prefix_list,
|
|||
"Restrict allowed PIM neighbors\n"
|
||||
"Use prefix-list to filter neighbors\n")
|
||||
|
||||
DEFPY_YANG(interface_ipv6_mld_require_ra, interface_ipv6_mld_require_ra_cmd,
|
||||
"[no] ipv6 mld require-router-alert",
|
||||
NO_STR
|
||||
IPV6_STR
|
||||
IFACE_MLD_STR
|
||||
"Require IP Router Alert option for MLD packets\n")
|
||||
{
|
||||
nb_cli_enqueue_change(vty, "./require-router-alert", NB_OP_MODIFY, no ? "false" : "true");
|
||||
|
||||
return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, FRR_PIM_AF_XPATH_VAL);
|
||||
}
|
||||
|
||||
DEFPY (show_ipv6_pim_rp,
|
||||
show_ipv6_pim_rp_cmd,
|
||||
"show ipv6 pim [vrf NAME] rp-info [X:X::X:X/M$group] [json$json]",
|
||||
|
@ -3019,6 +3031,7 @@ void pim_cmd_init(void)
|
|||
&interface_no_ipv6_mld_last_member_query_interval_cmd);
|
||||
install_element(INTERFACE_NODE, &interface_ipv6_pim_neighbor_prefix_list_cmd);
|
||||
install_element(INTERFACE_NODE, &interface_no_ipv6_pim_neighbor_prefix_list_cmd);
|
||||
install_element(INTERFACE_NODE, &interface_ipv6_mld_require_ra_cmd);
|
||||
|
||||
install_element(VIEW_NODE, &show_ipv6_pim_rp_cmd);
|
||||
install_element(VIEW_NODE, &show_ipv6_pim_rp_vrf_all_cmd);
|
||||
|
|
|
@ -1817,7 +1817,7 @@ static void gm_t_recv(struct event *t)
|
|||
goto out_free;
|
||||
}
|
||||
|
||||
if (!ip6_check_hopopts_ra(hopopts, hopopt_len, IP6_ALERT_MLD)) {
|
||||
if (pim_ifp->gmp_require_ra && !ip6_check_hopopts_ra(hopopts, hopopt_len, IP6_ALERT_MLD)) {
|
||||
zlog_err(log_pkt_src(
|
||||
"packet without IPv6 Router Alert MLD option"));
|
||||
gm_ifp->stats.rx_drop_ra++;
|
||||
|
|
|
@ -5706,6 +5706,18 @@ DEFPY_YANG(interface_ip_igmp_immediate_leave,
|
|||
return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, FRR_PIM_AF_XPATH_VAL);
|
||||
}
|
||||
|
||||
DEFPY_YANG(interface_ip_igmp_require_ra, interface_ip_igmp_require_ra_cmd,
|
||||
"[no] ip igmp require-router-alert",
|
||||
NO_STR
|
||||
IP_STR
|
||||
IFACE_IGMP_STR
|
||||
"Require IP Router Alert option for IGMP packets\n")
|
||||
{
|
||||
nb_cli_enqueue_change(vty, "./require-router-alert", NB_OP_MODIFY, no ? "false" : "true");
|
||||
|
||||
return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, FRR_PIM_AF_XPATH_VAL);
|
||||
}
|
||||
|
||||
DEFUN (interface_ip_pim_drprio,
|
||||
interface_ip_pim_drprio_cmd,
|
||||
"ip pim drpriority (0-4294967295)",
|
||||
|
@ -9182,6 +9194,7 @@ void pim_cmd_init(void)
|
|||
install_element(INTERFACE_NODE, &interface_ip_igmp_limits_cmd);
|
||||
install_element(INTERFACE_NODE, &no_interface_ip_igmp_limits_cmd);
|
||||
install_element(INTERFACE_NODE, &interface_ip_igmp_immediate_leave_cmd);
|
||||
install_element(INTERFACE_NODE, &interface_ip_igmp_require_ra_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);
|
||||
|
|
|
@ -97,6 +97,7 @@ struct pim_interface {
|
|||
int gm_last_member_query_count; /* IGMP or MLD last member
|
||||
query count
|
||||
*/
|
||||
bool gmp_require_ra; /* drop IGMP without Router Alert */
|
||||
struct list *gm_socket_list; /* list of struct IGMP or MLD sock */
|
||||
struct list *gm_join_list; /* list of struct IGMP or MLD join */
|
||||
struct list *static_group_list; /* list of struct static group */
|
||||
|
|
|
@ -726,19 +726,55 @@ bool pim_igmp_verify_header(struct ip *ip_hdr, size_t len, size_t *hlen)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool ip_check_hopopts_ra(const uint8_t *options, size_t options_len)
|
||||
{
|
||||
if (options_len < 4)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* The values 148 and 4 were translated from the bits sequence
|
||||
* from RFC 2113 Section 2.1. Syntax.
|
||||
*/
|
||||
if (options[0] != 148)
|
||||
return false;
|
||||
if (options[1] != 4)
|
||||
return false;
|
||||
if (options[2] != 0 && options[3] != 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int pim_igmp_packet(struct gm_sock *igmp, char *buf, size_t len)
|
||||
{
|
||||
const struct pim_interface *pim_interface = igmp->interface->info;
|
||||
struct ip *ip_hdr = (struct ip *)buf;
|
||||
size_t ip_hlen; /* ip header length in bytes */
|
||||
char *igmp_msg;
|
||||
int igmp_msg_len;
|
||||
int msg_type;
|
||||
bool router_alert;
|
||||
char from_str[INET_ADDRSTRLEN];
|
||||
char to_str[INET_ADDRSTRLEN];
|
||||
|
||||
if (!pim_igmp_verify_header(ip_hdr, len, &ip_hlen))
|
||||
return -1;
|
||||
|
||||
if (ip_hlen > sizeof(struct ip)) {
|
||||
const uint8_t *ip_options = (const uint8_t *)(ip_hdr + 1);
|
||||
size_t ip_options_len = ip_hlen - sizeof(struct ip);
|
||||
|
||||
router_alert = ip_check_hopopts_ra(ip_options, ip_options_len);
|
||||
} else
|
||||
router_alert = false;
|
||||
|
||||
if (pim_interface->gmp_require_ra && !router_alert) {
|
||||
if (PIM_DEBUG_GM_PACKETS)
|
||||
zlog_debug("discarding IGMP packet from %pI4 on %s due to Router Alert option missing",
|
||||
&ip_hdr->ip_src, igmp->interface->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
igmp_msg = buf + ip_hlen;
|
||||
igmp_msg_len = len - ip_hlen;
|
||||
msg_type = *igmp_msg;
|
||||
|
|
|
@ -756,6 +756,12 @@ const struct frr_yang_module_info frr_gmp_info = {
|
|||
.modify = lib_interface_gmp_immediate_leave_modify,
|
||||
}
|
||||
},
|
||||
{
|
||||
.xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/require-router-alert",
|
||||
.cbs = {
|
||||
.modify = lib_interface_gmp_require_router_alert_modify,
|
||||
}
|
||||
},
|
||||
{
|
||||
.xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/static-group",
|
||||
.cbs = {
|
||||
|
|
|
@ -292,6 +292,7 @@ int lib_interface_gmp_address_family_static_group_destroy(
|
|||
int lib_interface_gm_max_sources_modify(struct nb_cb_modify_args *args);
|
||||
int lib_interface_gm_max_groups_modify(struct nb_cb_modify_args *args);
|
||||
int lib_interface_gmp_immediate_leave_modify(struct nb_cb_modify_args *args);
|
||||
int lib_interface_gmp_require_router_alert_modify(struct nb_cb_modify_args *args);
|
||||
|
||||
/*
|
||||
* Callback registered with routing_nb lib to validate only
|
||||
|
|
|
@ -4561,6 +4561,29 @@ int lib_interface_gmp_immediate_leave_modify(struct nb_cb_modify_args *args)
|
|||
return NB_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/require-router-alert
|
||||
*/
|
||||
int lib_interface_gmp_require_router_alert_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;
|
||||
pim_ifp->gmp_require_ra = yang_dnode_get_bool(args->dnode, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
return NB_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/proxy
|
||||
*/
|
||||
|
|
|
@ -291,6 +291,12 @@ static int gm_config_write(struct vty *vty, int writes,
|
|||
++writes;
|
||||
}
|
||||
|
||||
/* IF ip igmp require-router-alert */
|
||||
if (pim_ifp->gmp_require_ra) {
|
||||
vty_out(vty, " ip igmp require-router-alert\n");
|
||||
++writes;
|
||||
}
|
||||
|
||||
if (pim_ifp->gm_proxy) {
|
||||
vty_out(vty, " ip igmp proxy\n");
|
||||
++writes;
|
||||
|
@ -380,6 +386,12 @@ static int gm_config_write(struct vty *vty, int writes,
|
|||
++writes;
|
||||
}
|
||||
|
||||
/* IF ip igmp require-router-alert */
|
||||
if (pim_ifp->gmp_require_ra) {
|
||||
vty_out(vty, " ipv6 mld require-router-alert\n");
|
||||
++writes;
|
||||
}
|
||||
|
||||
if (pim_ifp->mld_version != MLD_DEFAULT_VERSION)
|
||||
vty_out(vty, " ipv6 mld version %d\n", pim_ifp->mld_version);
|
||||
|
||||
|
|
58
tests/topotests/lib/packet/igmp/igmp.py
Normal file
58
tests/topotests/lib/packet/igmp/igmp.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# igmp.py
|
||||
# Part of NetDEF CI System
|
||||
#
|
||||
# Copyright (c) 2025 by
|
||||
# Network Device Education Foundation, Inc. ("NetDEF")
|
||||
#
|
||||
import struct
|
||||
|
||||
from scapy.all import Packet
|
||||
from scapy.layers.inet import IP, IPOption_Router_Alert
|
||||
from scapy.layers.l2 import Ether
|
||||
from scapy.packet import bind_layers
|
||||
from scapy.sendrecv import sendp
|
||||
|
||||
def calculate_checksum(packet):
|
||||
if len(packet) % 2 == 1:
|
||||
packet += b'\0'
|
||||
s = sum(struct.unpack("!%dH" % (len(packet) // 2), packet))
|
||||
s = (s >> 16) + (s & 0xffff)
|
||||
s += s >> 16
|
||||
return ~s & 0xffff
|
||||
|
||||
class IGMP(Packet):
|
||||
"""
|
||||
Base class for creating and manipulating IGMP packets.
|
||||
|
||||
Methods:
|
||||
__init__(self, version=1, type=0x11, chksum=None, gaddr="0.0.0.0", src_ip="192.168.100.1", *args, **kwargs):
|
||||
Initializes an IGMP packet with the given parameters.
|
||||
send(self, iface, count=1, interval=0):
|
||||
Sends the IGMP packet on the specified interface.
|
||||
enable_router_alert(self):
|
||||
Enables the Router Alert option for the IGMP packet.
|
||||
"""
|
||||
|
||||
def enable_router_alert(self):
|
||||
router_alert = IPOption_Router_Alert()
|
||||
self.options.append(router_alert)
|
||||
|
||||
def post_build(self, p, pay):
|
||||
if self.chksum is None:
|
||||
chksum = calculate_checksum(p)
|
||||
p = p[:2] + struct.pack("!H", chksum) + p[4:]
|
||||
return p + pay
|
||||
|
||||
def send(self, interval=0, count=1, iface="eth0"):
|
||||
bind_layers(IP, IGMP, proto=2)
|
||||
|
||||
if self.options:
|
||||
packet = Ether() / IP(dst=self.gaddr, tos=0xc0, id=0, ttl=1, src=self.src_ip, options=self.options, proto=2, frag=0) / self
|
||||
else:
|
||||
packet = Ether() / IP(dst=self.gaddr, tos=0xc0, id=0, ttl=1, src=self.src_ip, proto=2, frag=0) / self
|
||||
|
||||
sendp(packet, inter=int(interval), iface=iface, count=int(count))
|
73
tests/topotests/lib/packet/igmp/igmp_v1.py
Normal file
73
tests/topotests/lib/packet/igmp/igmp_v1.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# igmp_v1.py
|
||||
# Part of NetDEF CI System
|
||||
#
|
||||
# Copyright (c) 2025 by
|
||||
# Network Device Education Foundation, Inc. ("NetDEF")
|
||||
#
|
||||
|
||||
import argparse
|
||||
|
||||
from igmp import IGMP
|
||||
|
||||
from scapy.all import ByteField, IPField
|
||||
from scapy.fields import XShortField, BitField
|
||||
|
||||
|
||||
class IGMPv1(IGMP) :
|
||||
"""
|
||||
Represents an IGMPv1 packet.
|
||||
|
||||
Attributes:
|
||||
version (int): IGMP version (default is 1).
|
||||
type (int): The type of the IGMP message (default is 0x11).
|
||||
unused (int): The maximum response time (default is 0).
|
||||
chksum (int): Checksum of the packet.
|
||||
gaddr (str): The group address (default is "0.0.0.0").
|
||||
src_ip (str): Source IP address (default is "192.168.100.1").
|
||||
options (list): Additional options for the packet.
|
||||
|
||||
Methods:
|
||||
__init__(self, version=1, type=0x11, unused=0, chksum=None, gaddr="0.0.0.0", src_ip="192.168.100.1", *args, **kwargs):
|
||||
Initializes an IGMPv1 packet with the given parameters.
|
||||
"""
|
||||
|
||||
name = "IGMPv1"
|
||||
fields_desc = [
|
||||
BitField("version", 1, 4),
|
||||
BitField("type", 0x11, 4),
|
||||
ByteField("unused", 0),
|
||||
XShortField("chksum", None),
|
||||
IPField("gaddr", "0.0.0.0")
|
||||
]
|
||||
|
||||
def __init__(self, version=1, type=0x11, unused=0, chksum=None, gaddr="0.0.0.0", src_ip="192.168.100.1", *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.version = version
|
||||
self.type = type
|
||||
self.unused = unused
|
||||
self.chksum = chksum
|
||||
self.gaddr = gaddr
|
||||
self.src_ip = src_ip
|
||||
self.options = []
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Send an IGMPv1 packet")
|
||||
parser.add_argument("--gaddr", type=str, default="224.0.0.1", help="Group address")
|
||||
parser.add_argument("--src_ip", type=str, default="192.168.1.10", help="Source IP address")
|
||||
parser.add_argument("--type", type=lambda x: int(x, 0), default=0x11, help="Type of IGMP message")
|
||||
parser.add_argument("--enable_router_alert", action="store_true", help="Enable Router Alert option")
|
||||
parser.add_argument("--iface", type=str, default="eth0", help="Network interface to send the packet")
|
||||
parser.add_argument("--count", type=int, default=1, help="Number of packets to send")
|
||||
parser.add_argument("--interval", type=int, default=0, help="Interval between packets")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
igmp_packet = IGMPv1(gaddr=args.gaddr, src_ip=args.src_ip, type=args.type)
|
||||
if args.enable_router_alert:
|
||||
igmp_packet.enable_router_alert()
|
||||
igmp_packet.send(iface=args.iface, count=args.count, interval=args.interval)
|
72
tests/topotests/lib/packet/igmp/igmp_v2.py
Normal file
72
tests/topotests/lib/packet/igmp/igmp_v2.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# imgp_v2.py
|
||||
# Part of NetDEF CI System
|
||||
#
|
||||
# Copyright (c) 2025 by
|
||||
# Network Device Education Foundation, Inc. ("NetDEF")
|
||||
#
|
||||
import argparse
|
||||
|
||||
from scapy.all import ByteField, ShortField, IPField
|
||||
from scapy.fields import BitField
|
||||
from igmp import IGMP
|
||||
|
||||
class IGMPv2(IGMP):
|
||||
"""
|
||||
IGMPv2 class for creating and manipulating IGMP version 2 packets.
|
||||
|
||||
Attributes:
|
||||
name (str): Name of the protocol.
|
||||
fields_desc (list): List of fields in the IGMPv2 packet.
|
||||
version (int): IGMP version.
|
||||
type (int): Type of IGMP message.
|
||||
max_resp_time (int): Maximum response time.
|
||||
chksum (int): Checksum of the packet.
|
||||
gaddr (str): Group address.
|
||||
src_ip (str): Source IP address.
|
||||
options (list): Additional options for the packet.
|
||||
|
||||
Methods:
|
||||
__init__(self, version=1, type=0x11, max_resp_time=10, chksum=None, gaddr="0.0.0.0", src_ip="192.168.100.1", *args, **kwargs):
|
||||
Initializes an IGMPv2 packet with the given parameters.
|
||||
"""
|
||||
|
||||
name = "IGMPv2"
|
||||
fields_desc = [
|
||||
BitField("version", 1, 4),
|
||||
BitField("type", 0x11, 4),
|
||||
ByteField("max_resp_time", 10),
|
||||
ShortField("checksum", None),
|
||||
IPField("gaddr", "0.0.0.0")
|
||||
]
|
||||
|
||||
def __init__(self, version=1, type=0x11, max_resp_time=10, chksum=None, gaddr="0.0.0.0", src_ip="192.168.100.1", *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.version = version
|
||||
self.type = type
|
||||
self.max_resp_time = max_resp_time
|
||||
self.chksum = chksum
|
||||
self.gaddr = gaddr
|
||||
self.src_ip = src_ip
|
||||
self.options = []
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Send an IGMPv2 packet")
|
||||
parser.add_argument("--gaddr", type=str, default="224.0.0.1", help="Group address")
|
||||
parser.add_argument("--src_ip", type=str, default="192.168.1.10", help="Source IP address")
|
||||
parser.add_argument("--type", type=lambda x: int(x, 0), default=0x11, help="Type of IGMP message")
|
||||
parser.add_argument("--enable_router_alert", action="store_true", help="Enable Router Alert option")
|
||||
parser.add_argument("--iface", type=str, default="eth0", help="Network interface to send the packet")
|
||||
parser.add_argument("--count", type=int, default=1, help="Number of packets to send")
|
||||
parser.add_argument("--interval", type=int, default=0, help="Interval between packets")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
igmp_packet = IGMPv2(gaddr=args.gaddr, src_ip=args.src_ip, type=args.type)
|
||||
if args.enable_router_alert:
|
||||
igmp_packet.enable_router_alert()
|
||||
igmp_packet.send(iface=args.iface, count=args.count, interval=args.interval)
|
78
tests/topotests/lib/packet/igmp/igmp_v3.py
Normal file
78
tests/topotests/lib/packet/igmp/igmp_v3.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# imgp_v3.py
|
||||
# Part of NetDEF CI System
|
||||
#
|
||||
# Copyright (c) 2025 by
|
||||
# Network Device Education Foundation, Inc. ("NetDEF")
|
||||
#
|
||||
import argparse
|
||||
|
||||
from scapy.all import ByteField, ShortField, IPField
|
||||
from scapy.contrib.igmpv3 import IGMPv3gr
|
||||
from scapy.fields import BitField, PacketListField
|
||||
from scapy.layers.inet6 import ICMPv6MLDMultAddrRec
|
||||
|
||||
from igmp import IGMP
|
||||
|
||||
class IGMPv3(IGMP):
|
||||
name = "IGMPv3"
|
||||
fields_desc = [
|
||||
BitField("type", 0x22, 8),
|
||||
BitField("reserved1", None, 8),
|
||||
ShortField("checksum", None),
|
||||
ShortField("reserved2", None),
|
||||
ShortField("records_number", None),
|
||||
PacketListField("records",
|
||||
[],
|
||||
IGMPv3gr,
|
||||
count_from=lambda p: p.records_number)
|
||||
]
|
||||
|
||||
def __init__(self, version=3, type=0x22, max_resp_time=10,
|
||||
chksum=None, records=[], maddrs=[], rtype=1, gaddr="224.0.0.22",
|
||||
src_ip="192.168.100.1", *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.version = version
|
||||
self.type = type
|
||||
self.max_resp_time = max_resp_time
|
||||
self.chksum = chksum
|
||||
self.src_ip = src_ip
|
||||
self.options = []
|
||||
self.gaddr = gaddr
|
||||
|
||||
num_maddrs = len(maddrs)
|
||||
grouped_sources = [[] for _ in range(num_maddrs)]
|
||||
for index, source in enumerate(records):
|
||||
grouped_sources[index % num_maddrs].append(source)
|
||||
|
||||
for maddr, sources in zip(maddrs, grouped_sources):
|
||||
self.records.append(IGMPv3gr(numsrc=len(sources), srcaddrs=sources, maddr=maddr, rtype=rtype))
|
||||
|
||||
self.records_number = num_maddrs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Send an IGMPv3 packet")
|
||||
parser.add_argument("--gaddr", type=str, default="224.0.0.22", help="Destination IP address")
|
||||
parser.add_argument("--maddr", action='append', default=[], help="Multicast Address Records")
|
||||
parser.add_argument("--src_ip", type=str, default="192.168.1.10", help="Source IP address")
|
||||
parser.add_argument("--type", type=lambda x: int(x, 0), default=0x22, help="Type of IGMP message")
|
||||
parser.add_argument("--rtype", type=int, default=1, help="Record type")
|
||||
parser.add_argument("--enable_router_alert", action="store_true", help="Enable Router Alert option")
|
||||
parser.add_argument("--iface", type=str, default="eth0", help="Network interface to send the packet")
|
||||
parser.add_argument("--count", type=int, default=1, help="Number of packets to send")
|
||||
parser.add_argument("--interval", type=int, default=0, help="Interval between packets")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
igmp_packet = IGMPv3(maddrs=args.maddr,
|
||||
src_ip=args.src_ip,
|
||||
type=args.type,
|
||||
gaddr=args.gaddr,
|
||||
rtype=args.rtype)
|
||||
if args.enable_router_alert:
|
||||
igmp_packet.enable_router_alert()
|
||||
igmp_packet.send(iface=args.iface, count=args.count, interval=args.interval)
|
49
tests/topotests/lib/packet/mld/mld.py
Normal file
49
tests/topotests/lib/packet/mld/mld.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# mld.py
|
||||
# Part of NetDEF CI System
|
||||
#
|
||||
# Copyright (c) 2025 by
|
||||
# Network Device Education Foundation, Inc. ("NetDEF")
|
||||
#
|
||||
|
||||
from scapy.layers.inet6 import IPv6, IPv6ExtHdrHopByHop, _ICMPv6ML, RouterAlert
|
||||
from scapy.layers.l2 import Ether
|
||||
from scapy.packet import bind_layers
|
||||
from scapy.sendrecv import sendp
|
||||
|
||||
class MLD(_ICMPv6ML):
|
||||
"""
|
||||
MLD is a class representing a Multicast Listener Discovery (MLD) packet.
|
||||
|
||||
Attributes:
|
||||
name (str): Name of the packet, default is "MLD".
|
||||
options (list): List of options, default is an empty list.
|
||||
mladdr (str): Multicast address, default is "::".
|
||||
src_ip (str): Source IP address, default is "fe80::1".
|
||||
|
||||
Methods:
|
||||
enable_router_alert(self):
|
||||
Enables the Router Alert option for the MLD packet.
|
||||
|
||||
send(self, interval=0, count=1, iface="eth0"):
|
||||
Sends the MLD packet on the specified network interface.
|
||||
"""
|
||||
|
||||
name = "MLD"
|
||||
|
||||
def enable_router_alert(self):
|
||||
router_alert = RouterAlert(value=0)
|
||||
self.options.append(router_alert)
|
||||
|
||||
def send(self, interval=0, count=1, iface="eth0"):
|
||||
bind_layers(IPv6, MLD, nh=58) # nh=58 for ICMPv6
|
||||
|
||||
if self.options:
|
||||
packet = Ether() / IPv6(dst=self.dst_ip, src=self.src_ip, hlim=1) / IPv6ExtHdrHopByHop(options=self.options) / self
|
||||
else:
|
||||
packet = Ether() / IPv6(dst=self.dst_ip, src=self.src_ip, hlim=1) / self
|
||||
|
||||
sendp(packet, inter=int(interval), iface=iface, count=int(count))
|
91
tests/topotests/lib/packet/mld/mld_v1.py
Normal file
91
tests/topotests/lib/packet/mld/mld_v1.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# mld_v1.py
|
||||
# Part of NetDEF CI System
|
||||
#
|
||||
# Copyright (c) 2025 by
|
||||
# Network Device Education Foundation, Inc. ("NetDEF")
|
||||
#
|
||||
|
||||
import argparse
|
||||
|
||||
from scapy.fields import BitField, XShortField, IP6Field, ByteField, ByteEnumField, ShortField
|
||||
from scapy.layers.inet6 import icmp6types
|
||||
|
||||
from mld import MLD
|
||||
|
||||
class MLDv1(MLD):
|
||||
"""
|
||||
MLDv1 is a class representing an MLD (Multicast Listener Discovery) version 1 packet.
|
||||
|
||||
Attributes:
|
||||
type (int): Type of MLD message, default is 0x11.
|
||||
code (int): Code of MLD message, default is 0.
|
||||
cksum (int): Checksum of the packet, default is None.
|
||||
mrd (int): Maximum response delay, default is 0.
|
||||
reserved (int): Reserved field, default is 0.
|
||||
mladdr (str): Multicast address, default is "::".
|
||||
src_ip (str): Source IP address, default is "fe80::1".
|
||||
dst_ip (str): Destination IP address, default is "fe80::2".
|
||||
options (list): List of options, default is an empty list.
|
||||
|
||||
Methods:
|
||||
__init__(self, type=0x11, code=0, max_response_delay=0, chksum=None, gaddr="ff02::1", src_ip="fe80::1", dst_ip="fe80::2", *args, **kwargs):
|
||||
Initializes an MLDv1 packet with the given parameters.
|
||||
|
||||
send(self, iface="eth0", count=1, interval=0):
|
||||
Sends the MLDv1 packet on the specified network interface.
|
||||
"""
|
||||
|
||||
name = "MLDv1"
|
||||
fields_desc = [
|
||||
ByteEnumField("type", 130, icmp6types),
|
||||
ByteField("code", 0),
|
||||
XShortField("cksum", None),
|
||||
ShortField("mrd", 0),
|
||||
ShortField("reserved", 0),
|
||||
IP6Field("mladdr", "::")
|
||||
]
|
||||
|
||||
def __init__(self, type=0x11, code=0, max_response_delay=0, chksum=None, gaddr="ff02::1", src_ip="fe80::1", dst_ip="ff02::16", *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.type = type
|
||||
self.code = code
|
||||
self.mrd = max_response_delay
|
||||
self.cksum = chksum
|
||||
self.reserved = 0
|
||||
self.mladdr = gaddr
|
||||
self.src_ip = src_ip
|
||||
self.dst_ip = dst_ip
|
||||
self.options = []
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Send an MLDv1 packet")
|
||||
parser.add_argument("--type", type=lambda x: int(x, 0), default=0x83, help="Type of MLD message")
|
||||
parser.add_argument("--code", type=int, default=0, help="Code of MLD message")
|
||||
parser.add_argument("--max_response_delay", type=int, default=0, help="Maximum response delay")
|
||||
parser.add_argument("--chksum", type=int, default=None, help="Checksum of the packet")
|
||||
parser.add_argument("--gaddr", type=str, default="ff02::1", help="Group address")
|
||||
parser.add_argument("--src_ip", type=str, default="fe80::1", help="Source IP address")
|
||||
parser.add_argument("--dst_ip", type=str, default="ff02::16", help="Destination IP address")
|
||||
parser.add_argument("--enable_router_alert", action="store_true", help="Enable Router Alert option")
|
||||
parser.add_argument("--iface", type=str, default="eth0", help="Network interface to send the packet")
|
||||
parser.add_argument("--count", type=int, default=1, help="Number of packets to send")
|
||||
parser.add_argument("--interval", type=int, default=0, help="Interval between packets")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
mld_packet = MLDv1(gaddr=args.gaddr,
|
||||
src_ip=args.src_ip,
|
||||
dst_ip=args.dst_ip,
|
||||
type=args.type,
|
||||
code=args.code,
|
||||
max_response_delay=args.max_response_delay)
|
||||
|
||||
if args.enable_router_alert:
|
||||
mld_packet.enable_router_alert()
|
||||
|
||||
mld_packet.send(iface=args.iface, count=args.count, interval=args.interval)
|
118
tests/topotests/lib/packet/mld/mld_v2.py
Normal file
118
tests/topotests/lib/packet/mld/mld_v2.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
# mld_v2.py
|
||||
# Part of NetDEF CI System
|
||||
#
|
||||
# Copyright (c) 2025 by
|
||||
# Network Device Education Foundation, Inc. ("NetDEF")
|
||||
#
|
||||
|
||||
import argparse
|
||||
|
||||
from scapy.fields import BitField, XShortField, IP6Field, ByteField, ByteEnumField, ShortField, PacketListField
|
||||
from scapy.layers.inet6 import icmp6types, ICMPv6MLDMultAddrRec
|
||||
|
||||
from mld import MLD
|
||||
|
||||
class MLDv2(MLD):
|
||||
"""
|
||||
MLDv2 (Multicast Listener Discovery version 2) class for creating and handling MLDv2 packets.
|
||||
|
||||
Inherits from:
|
||||
MLD: Base class for MLD packets.
|
||||
|
||||
Attributes:
|
||||
name (str): Name of the packet type.
|
||||
fields_desc (list): List of fields in the packet.
|
||||
type (int): Type of MLD message.
|
||||
code (int): Code of MLD message.
|
||||
cksum (int): Checksum of the packet.
|
||||
reserved (int): Reserved field.
|
||||
src_ip (str): Source IP address.
|
||||
dst_ip (str): Destination IP address.
|
||||
options (list): List of options for the packet.
|
||||
records (list): List of multicast address records.
|
||||
records_number (int): Number of multicast address records.
|
||||
|
||||
Record Type values:
|
||||
1: MODE_IS_INCLUDE
|
||||
2: MODE_IS_EXCLUDE
|
||||
3: CHANGE_TO_INCLUDE_MODE
|
||||
4: CHANGE_TO_EXCLUDE_MODE
|
||||
5: ALLOW_NEW_SOURCES
|
||||
6: BLOCK_OLD_SOURCES
|
||||
|
||||
Methods:
|
||||
__init__(self, proto_type=143, code=0, rtype=1, chksum=None, src_ip="fe80::1", dst_ip="ff02::fb", records=[], *args, **kwargs):
|
||||
Initializes an MLDv2 packet with the given parameters.
|
||||
|
||||
enable_router_alert(self):
|
||||
Enables the Router Alert option for the packet.
|
||||
|
||||
send(self, iface="eth0", count=1, interval=0):
|
||||
Sends the MLDv2 packet on the specified network interface.
|
||||
"""
|
||||
name = "MLDv2"
|
||||
fields_desc = [
|
||||
ByteEnumField("type", 143, icmp6types),
|
||||
ByteField("code", 0),
|
||||
XShortField("cksum", None),
|
||||
BitField("reserved", 0, 16),
|
||||
BitField("records_number", 0, 16),
|
||||
PacketListField("records",
|
||||
[],
|
||||
ICMPv6MLDMultAddrRec,
|
||||
count_from=lambda p: p.records_number)
|
||||
]
|
||||
|
||||
def __init__(self, proto_type=143, code=0, rtype=1,
|
||||
chksum=None, src_ip="fe80::1", dst_ip="ff02::16", maddrs=[], *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.type = proto_type
|
||||
self.code = code
|
||||
self.cksum = chksum
|
||||
self.reserved = 0
|
||||
self.src_ip = src_ip
|
||||
self.dst_ip = dst_ip
|
||||
self.options = []
|
||||
|
||||
num_maddrs = len(maddrs)
|
||||
grouped_sources = [[] for _ in range(num_maddrs)]
|
||||
for index, source in enumerate(maddrs):
|
||||
grouped_sources[index % num_maddrs].append(source)
|
||||
|
||||
for maddr, sources in zip(maddrs, grouped_sources):
|
||||
self.records.append(ICMPv6MLDMultAddrRec(dst=maddr, rtype=rtype))
|
||||
|
||||
self.records_number = num_maddrs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Send an MLDv2 packet")
|
||||
parser.add_argument("--type", type=int, default=143, help="Type of MLD message")
|
||||
parser.add_argument("--code", type=int, default=0, help="Code of MLD message")
|
||||
parser.add_argument("--chksum", type=int, default=None, help="Checksum of the packet")
|
||||
parser.add_argument("--src_ip", type=str, default="fe80::1", help="Source IP address")
|
||||
parser.add_argument("--dst_ip", type=str, default="ff02::16", help="Destination IP address")
|
||||
parser.add_argument("--maddr", action='append', default=[], help="Multicast Address Records")
|
||||
parser.add_argument("--rtype", type=int, default=2, help="Record type")
|
||||
parser.add_argument("--enable_router_alert", action="store_true", help="Enable Router Alert option")
|
||||
parser.add_argument("--iface", type=str, default="eth0", help="Network interface to send the packet")
|
||||
parser.add_argument("--count", type=int, default=1, help="Number of packets to send")
|
||||
parser.add_argument("--interval", type=int, default=0, help="Interval between packets")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
packet = MLDv2(maddrs=args.maddr,
|
||||
src_ip=args.src_ip,
|
||||
dst_ip=args.dst_ip,
|
||||
rtype=args.rtype,
|
||||
proto_type=args.type,
|
||||
code=args.code)
|
||||
|
||||
if args.enable_router_alert:
|
||||
packet.enable_router_alert()
|
||||
|
||||
packet.send(iface=args.iface, count=args.count, interval=args.interval)
|
|
@ -174,32 +174,36 @@ def test_pim_convergence():
|
|||
# This neighbor is denied by default
|
||||
expect_pim_peer("r1", "ip", "r1-eth1", "192.168.2.2", missing=True)
|
||||
# Lets configure the prefix list so the above neighbor gets accepted:
|
||||
tgen.gears["r1"].vtysh_cmd("""
|
||||
tgen.gears["r1"].vtysh_cmd(
|
||||
"""
|
||||
configure terminal
|
||||
ip prefix-list pim-eth0-neighbors permit 192.168.2.0/24
|
||||
""")
|
||||
"""
|
||||
)
|
||||
expect_pim_peer("r1", "ip", "r1-eth1", "192.168.2.2", missing=False)
|
||||
|
||||
#
|
||||
# IPv6 part
|
||||
#
|
||||
out = tgen.gears["r1"].vtysh_cmd("show interface r1-eth0 json", True)
|
||||
r1_r2_link_address = out["r1-eth0"]["ipAddresses"][1]["address"].split('/')[0]
|
||||
r1_r2_link_address = out["r1-eth0"]["ipAddresses"][1]["address"].split("/")[0]
|
||||
out = tgen.gears["r1"].vtysh_cmd("show interface r1-eth1 json", True)
|
||||
r1_r3_link_address = out["r1-eth1"]["ipAddresses"][1]["address"].split('/')[0]
|
||||
r1_r3_link_address = out["r1-eth1"]["ipAddresses"][1]["address"].split("/")[0]
|
||||
out = tgen.gears["r2"].vtysh_cmd("show interface r2-eth0 json", True)
|
||||
r2_link_address = out["r2-eth0"]["ipAddresses"][1]["address"].split('/')[0]
|
||||
r2_link_address = out["r2-eth0"]["ipAddresses"][1]["address"].split("/")[0]
|
||||
out = tgen.gears["r3"].vtysh_cmd("show interface r3-eth0 json", True)
|
||||
r3_link_address = out["r3-eth0"]["ipAddresses"][1]["address"].split('/')[0]
|
||||
r3_link_address = out["r3-eth0"]["ipAddresses"][1]["address"].split("/")[0]
|
||||
|
||||
expect_pim_peer("r1", "ipv6", "r1-eth0", r2_link_address)
|
||||
expect_pim_peer("r2", "ipv6", "r2-eth0", r1_r2_link_address)
|
||||
expect_pim_peer("r1", "ipv6", "r1-eth1", r3_link_address, missing=True)
|
||||
|
||||
tgen.gears["r1"].vtysh_cmd(f"""
|
||||
tgen.gears["r1"].vtysh_cmd(
|
||||
f"""
|
||||
configure terminal
|
||||
ipv6 prefix-list pimv6-eth0-neighbors permit {r3_link_address}/64
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
expect_pim_peer("r1", "ipv6", "r1-eth1", r3_link_address, missing=False)
|
||||
|
||||
|
@ -502,6 +506,279 @@ def test_mldv1_immediate_leave():
|
|||
app_helper.stop_host("h3")
|
||||
|
||||
|
||||
def host_send_igmp_packet(host, script, type, source, group, router_alert=True):
|
||||
"Sends packet using specified script from host."
|
||||
command = f"python3 {CWD}/../lib/packet/{script}"
|
||||
command += f" --src_ip={source} --gaddr={group}"
|
||||
command += f" --iface={host}-eth0 --type={type}"
|
||||
if router_alert:
|
||||
command += f" --enable_router_alert"
|
||||
|
||||
tgen = get_topogen()
|
||||
tgen.gears[host].run(command)
|
||||
|
||||
|
||||
def host_send_igmpv3_packet(host, source, group, router_alert=True):
|
||||
"Sends packet using specified script from host."
|
||||
command = f"python3 {CWD}/../lib/packet/igmp/igmp_v3.py"
|
||||
command += f" --src_ip={source} --iface={host}-eth0"
|
||||
command += f" --maddr={group} --rtype=2"
|
||||
if router_alert:
|
||||
command += f" --enable_router_alert"
|
||||
|
||||
tgen = get_topogen()
|
||||
tgen.gears[host].run(command)
|
||||
|
||||
|
||||
def expect_igmp_group(router, interface, group, missing=False):
|
||||
tgen = get_topogen()
|
||||
|
||||
igmp_groups = tgen.gears[router].vtysh_cmd("show ip igmp groups json", isjson=True)
|
||||
try:
|
||||
for entry in igmp_groups[interface]["groups"]:
|
||||
if entry["group"] == group:
|
||||
return True
|
||||
|
||||
return False
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
|
||||
def test_igmp_router_alert():
|
||||
"Test IGMP router alert check feature."
|
||||
|
||||
tgen = get_topogen()
|
||||
if tgen.routers_have_failure():
|
||||
pytest.skip(tgen.errors)
|
||||
|
||||
#
|
||||
# Test that without require-router-alert we learn IGMP groups
|
||||
#
|
||||
source = "192.168.100.100"
|
||||
group = "224.100.10.10"
|
||||
host_send_igmp_packet(
|
||||
"h1", "igmp/igmp_v1.py", 0x12, source, group, router_alert=False
|
||||
)
|
||||
test_func = partial(expect_igmp_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv, "failed to learn group using IGMPv1 without router alert"
|
||||
|
||||
group = "224.100.10.11"
|
||||
host_send_igmp_packet(
|
||||
"h1", "igmp/igmp_v2.py", 0x16, source, group, router_alert=False
|
||||
)
|
||||
test_func = partial(expect_igmp_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv, "failed to learn group using IGMPv2 without router alert"
|
||||
|
||||
group = "224.100.10.12"
|
||||
host_send_igmpv3_packet("h1", source, group, False)
|
||||
test_func = partial(expect_igmp_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv, "failed to learn group using IGMPv3 without router alert"
|
||||
|
||||
#
|
||||
# Test that with require-router-alert we don't learn IGMP groups
|
||||
#
|
||||
tgen.gears["r1"].vtysh_cmd(
|
||||
"""
|
||||
configure terminal
|
||||
interface r1-eth2
|
||||
ip igmp require-router-alert
|
||||
"""
|
||||
)
|
||||
|
||||
source = "192.168.100.100"
|
||||
group = "224.100.10.20"
|
||||
host_send_igmp_packet(
|
||||
"h1", "igmp/igmp_v1.py", 0x12, source, group, router_alert=False
|
||||
)
|
||||
test_func = partial(expect_igmp_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to not learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv == False, "failed to learn group using IGMPv1 without router alert"
|
||||
|
||||
group = "224.100.10.21"
|
||||
host_send_igmp_packet(
|
||||
"h1", "igmp/igmp_v2.py", 0x16, source, group, router_alert=False
|
||||
)
|
||||
test_func = partial(expect_igmp_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to not learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv == False, "failed to learn group using IGMPv2 without router alert"
|
||||
|
||||
group = "224.100.10.22"
|
||||
host_send_igmpv3_packet("h1", source, group, False)
|
||||
test_func = partial(expect_igmp_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to not learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv == False, "failed to learn group using IGMPv3 without router alert"
|
||||
|
||||
#
|
||||
# Test that with require-router-alert we learn IGMP groups
|
||||
#
|
||||
source = "192.168.100.100"
|
||||
group = "224.100.10.30"
|
||||
host_send_igmp_packet(
|
||||
"h1", "igmp/igmp_v1.py", 0x12, source, group, router_alert=True
|
||||
)
|
||||
test_func = partial(expect_igmp_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv, "failed to learn group using IGMPv1 without router alert"
|
||||
|
||||
group = "224.100.10.31"
|
||||
host_send_igmp_packet(
|
||||
"h1", "igmp/igmp_v2.py", 0x16, source, group, router_alert=True
|
||||
)
|
||||
test_func = partial(expect_igmp_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv, "failed to learn group using IGMPv2 without router alert"
|
||||
|
||||
group = "224.100.10.32"
|
||||
host_send_igmpv3_packet("h1", source, group, True)
|
||||
test_func = partial(expect_igmp_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv, "failed to learn group using IGMPv3 without router alert"
|
||||
|
||||
tgen.gears["r1"].vtysh_cmd(
|
||||
"""
|
||||
configure terminal
|
||||
interface r1-eth2
|
||||
no ip igmp require-router-alert
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def host_send_mldv1_packet(host, source, group, router_alert=True):
|
||||
"Sends packet using specified script from host."
|
||||
command = f"python3 {CWD}/../lib/packet/mld/mld_v1.py"
|
||||
command += f" --src_ip={source} --gaddr={group}"
|
||||
command += f" --iface={host}-eth0"
|
||||
if router_alert:
|
||||
command += f" --enable_router_alert"
|
||||
|
||||
tgen = get_topogen()
|
||||
tgen.gears[host].run(command)
|
||||
|
||||
|
||||
def host_send_mldv2_packet(host, source, group, router_alert=True):
|
||||
"Sends packet using specified script from host."
|
||||
command = f"python3 {CWD}/../lib/packet/mld/mld_v2.py"
|
||||
command += f" --src_ip={source} --iface={host}-eth0"
|
||||
command += f" --maddr={group} --rtype=2"
|
||||
if router_alert:
|
||||
command += f" --enable_router_alert"
|
||||
|
||||
tgen = get_topogen()
|
||||
tgen.gears[host].run(command)
|
||||
|
||||
|
||||
def expect_mld_group(router, interface, group, missing=False):
|
||||
tgen = get_topogen()
|
||||
|
||||
igmp_groups = tgen.gears[router].vtysh_cmd("show ipv6 mld groups json", isjson=True)
|
||||
try:
|
||||
for entry in igmp_groups[interface]["groups"]:
|
||||
if entry["group"] == group:
|
||||
return True
|
||||
|
||||
return False
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
|
||||
def test_mld_router_alert():
|
||||
"Test IGMP router alert check feature."
|
||||
tgen = get_topogen()
|
||||
if tgen.routers_have_failure():
|
||||
pytest.skip(tgen.errors)
|
||||
|
||||
addr_out = json.loads(tgen.gears["h1"].run("ip -j addr show dev h1-eth0"))
|
||||
source = None
|
||||
for address in addr_out[0]["addr_info"]:
|
||||
if address["family"] != "inet6":
|
||||
continue
|
||||
if address["scope"] != "link":
|
||||
continue
|
||||
|
||||
source = address["local"]
|
||||
break
|
||||
assert source is not None, "failed to find link-local address"
|
||||
|
||||
#
|
||||
# Test that without require-router-alert we learn MLD groups
|
||||
#
|
||||
group = "ff05::100"
|
||||
host_send_mldv1_packet("h1", source, group, router_alert=False)
|
||||
test_func = partial(expect_mld_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv, "failed to learn group using MLDv1 without router alert"
|
||||
|
||||
group = "ff05::101"
|
||||
host_send_mldv2_packet("h1", source, group, router_alert=False)
|
||||
test_func = partial(expect_mld_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv, "failed to learn group using MLDv2 without router alert"
|
||||
|
||||
#
|
||||
# Test that with require-router-alert we don't learn MLD groups
|
||||
#
|
||||
tgen.gears["r1"].vtysh_cmd(
|
||||
"""
|
||||
configure terminal
|
||||
interface r1-eth2
|
||||
ipv6 mld require-router-alert
|
||||
"""
|
||||
)
|
||||
|
||||
group = "ff05::110"
|
||||
host_send_mldv1_packet("h1", source, group, router_alert=False)
|
||||
test_func = partial(expect_mld_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv == False, "failed to learn group using MLDv1 without router alert"
|
||||
|
||||
group = "ff05::111"
|
||||
host_send_mldv2_packet("h1", source, group, router_alert=False)
|
||||
test_func = partial(expect_mld_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv == False, "failed to learn group using MLDv2 without router alert"
|
||||
|
||||
#
|
||||
# Test that with require-router-alert we learn MLD groups
|
||||
#
|
||||
group = "ff05::120"
|
||||
host_send_mldv1_packet("h1", source, group, router_alert=True)
|
||||
test_func = partial(expect_mld_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv, "failed to learn group using MLDv1 without router alert"
|
||||
|
||||
group = "ff05::121"
|
||||
host_send_mldv2_packet("h1", source, group, router_alert=True)
|
||||
test_func = partial(expect_mld_group, "r1", "r1-eth2", group)
|
||||
logger.info(f"Waiting for r1 to learn {group} in interface r1-eth2")
|
||||
rv, _ = topotest.run_and_expect(test_func, True, count=10, wait=2)
|
||||
assert rv, "failed to learn group using MLDv2 without router alert"
|
||||
|
||||
tgen.gears["r1"].vtysh_cmd(
|
||||
"""
|
||||
configure terminal
|
||||
interface r1-eth2
|
||||
no ipv6 mld require-router-alert
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def test_memory_leak():
|
||||
"Run the memory leak test and report results."
|
||||
tgen = get_topogen()
|
||||
|
|
|
@ -194,6 +194,13 @@ module frr-gmp {
|
|||
Has no effect when IGMPv3/MLDv2 is in use.";
|
||||
}
|
||||
|
||||
leaf require-router-alert {
|
||||
type boolean;
|
||||
default "false";
|
||||
description
|
||||
"Only process IGMP packets with IP Router Alert option set.";
|
||||
}
|
||||
|
||||
list static-group {
|
||||
key "group-addr source-addr";
|
||||
description
|
||||
|
|
Loading…
Reference in a new issue