1*c2e0b604SKristof Provost# 2*c2e0b604SKristof Provost# SPDX-License-Identifier: BSD-2-Clause 3*c2e0b604SKristof Provost# 4*c2e0b604SKristof Provost# Copyright (c) 2023 Rubicon Communications, LLC (Netgate) 5*c2e0b604SKristof Provost# 6*c2e0b604SKristof Provost# Redistribution and use in source and binary forms, with or without 7*c2e0b604SKristof Provost# modification, are permitted provided that the following conditions 8*c2e0b604SKristof Provost# are met: 9*c2e0b604SKristof Provost# 1. Redistributions of source code must retain the above copyright 10*c2e0b604SKristof Provost# notice, this list of conditions and the following disclaimer. 11*c2e0b604SKristof Provost# 2. Redistributions in binary form must reproduce the above copyright 12*c2e0b604SKristof Provost# notice, this list of conditions and the following disclaimer in the 13*c2e0b604SKristof Provost# documentation and/or other materials provided with the distribution. 14*c2e0b604SKristof Provost# 15*c2e0b604SKristof Provost# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16*c2e0b604SKristof Provost# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17*c2e0b604SKristof Provost# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18*c2e0b604SKristof Provost# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19*c2e0b604SKristof Provost# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20*c2e0b604SKristof Provost# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21*c2e0b604SKristof Provost# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22*c2e0b604SKristof Provost# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23*c2e0b604SKristof Provost# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24*c2e0b604SKristof Provost# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25*c2e0b604SKristof Provost# SUCH DAMAGE. 26*c2e0b604SKristof Provost# 27*c2e0b604SKristof Provostimport pytest 28*c2e0b604SKristof Provostfrom atf_python.sys.net.tools import ToolsHelper 29*c2e0b604SKristof Provostfrom atf_python.sys.net.vnet import VnetTestTemplate 30*c2e0b604SKristof Provostimport os 31*c2e0b604SKristof Provostimport socket 32*c2e0b604SKristof Provostimport struct 33*c2e0b604SKristof Provostimport sys 34*c2e0b604SKristof Provostimport logging 35*c2e0b604SKristof Provostlogging.getLogger("scapy").setLevel(logging.CRITICAL) 36*c2e0b604SKristof Provostcurdir = os.path.dirname(os.path.realpath(__file__)) 37*c2e0b604SKristof Provostnetpfil_common = curdir + "/../netpfil/common" 38*c2e0b604SKristof Provostsys.path.append(netpfil_common) 39*c2e0b604SKristof Provostfrom sniffer import Sniffer 40*c2e0b604SKristof Provost 41*c2e0b604SKristof Provostsc = None 42*c2e0b604SKristof Provostsp = None 43*c2e0b604SKristof Provost 44*c2e0b604SKristof Provostdef check_igmpv3(args, pkt): 45*c2e0b604SKristof Provost igmp = pkt.getlayer(sc.igmpv3.IGMPv3) 46*c2e0b604SKristof Provost if igmp is None: 47*c2e0b604SKristof Provost return False 48*c2e0b604SKristof Provost 49*c2e0b604SKristof Provost igmpmr = pkt.getlayer(sc.igmpv3.IGMPv3mr) 50*c2e0b604SKristof Provost if igmpmr is None: 51*c2e0b604SKristof Provost return False 52*c2e0b604SKristof Provost 53*c2e0b604SKristof Provost for r in igmpmr.records: 54*c2e0b604SKristof Provost if r.maddr != args["group"]: 55*c2e0b604SKristof Provost return False 56*c2e0b604SKristof Provost if args["type"] == "join": 57*c2e0b604SKristof Provost if r.rtype != 4: 58*c2e0b604SKristof Provost return False 59*c2e0b604SKristof Provost elif args["type"] == "leave": 60*c2e0b604SKristof Provost if r.rtype != 3: 61*c2e0b604SKristof Provost return False 62*c2e0b604SKristof Provost r.show() 63*c2e0b604SKristof Provost 64*c2e0b604SKristof Provost return True 65*c2e0b604SKristof Provost 66*c2e0b604SKristof Provostclass TestIGMP(VnetTestTemplate): 67*c2e0b604SKristof Provost REQUIRED_MODULES = [] 68*c2e0b604SKristof Provost TOPOLOGY = { 69*c2e0b604SKristof Provost "vnet1": { "ifaces": [ "if1" ] }, 70*c2e0b604SKristof Provost "if1": { "prefixes4": [ ("192.0.2.1/24", "192.0.2.2/24" ) ] }, 71*c2e0b604SKristof Provost } 72*c2e0b604SKristof Provost 73*c2e0b604SKristof Provost def setup_method(self, method): 74*c2e0b604SKristof Provost global sc 75*c2e0b604SKristof Provost if sc is None: 76*c2e0b604SKristof Provost import scapy.contrib as _sc 77*c2e0b604SKristof Provost import scapy.contrib.igmp 78*c2e0b604SKristof Provost import scapy.contrib.igmpv3 79*c2e0b604SKristof Provost import scapy.all as _sp 80*c2e0b604SKristof Provost sc = _sc 81*c2e0b604SKristof Provost sp = _sp 82*c2e0b604SKristof Provost super().setup_method(method) 83*c2e0b604SKristof Provost 84*c2e0b604SKristof Provost def test_igmp3_join_leave(self): 85*c2e0b604SKristof Provost "Test that we send the expected join/leave IGMPv2 messages" 86*c2e0b604SKristof Provost 87*c2e0b604SKristof Provost if1 = self.vnet.iface_alias_map["if1"] 88*c2e0b604SKristof Provost 89*c2e0b604SKristof Provost # Start a background sniff 90*c2e0b604SKristof Provost expected_pkt = { "type": "join", "group": "230.0.0.1" } 91*c2e0b604SKristof Provost sniffer = Sniffer(expected_pkt, check_igmpv3, if1.name, timeout=10) 92*c2e0b604SKristof Provost 93*c2e0b604SKristof Provost # Now join a multicast group, and see if we're getting the igmp packet we expect 94*c2e0b604SKristof Provost s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) 95*c2e0b604SKristof Provost mreq = struct.pack("4sl", socket.inet_aton('230.0.0.1'), socket.INADDR_ANY) 96*c2e0b604SKristof Provost s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) 97*c2e0b604SKristof Provost 98*c2e0b604SKristof Provost # Wait for the sniffer to see the join packet 99*c2e0b604SKristof Provost sniffer.join() 100*c2e0b604SKristof Provost assert(sniffer.correctPackets > 0) 101*c2e0b604SKristof Provost 102*c2e0b604SKristof Provost # Now leave, check for the packet 103*c2e0b604SKristof Provost expected_pkt = { "type": "leave", "group": "230.0.0.1" } 104*c2e0b604SKristof Provost sniffer = Sniffer(expected_pkt, check_igmpv3, if1.name) 105*c2e0b604SKristof Provost 106*c2e0b604SKristof Provost s.close() 107*c2e0b604SKristof Provost sniffer.join() 108*c2e0b604SKristof Provost assert(sniffer.correctPackets > 0) 109