import unittest
-from framework import VppTestCase, VppTestRunner, running_extended_tests
-from vpp_igmp import *
+from scapy.layers.l2 import Ether, Raw
+from scapy.layers.inet import IP, IPOption
+from scapy.contrib.igmpv3 import IGMPv3, IGMPv3gr, IGMPv3mq, IGMPv3mr
-from scapy.layers.l2 import Ether
-from scapy.layers.inet import IP
-from scapy.contrib.igmpv3 import *
-from scapy.contrib.igmp import *
+from framework import VppTestCase, VppTestRunner, running_extended_tests
+from vpp_igmp import find_igmp_state, IGMP_FILTER, IgmpRecord, IGMP_MODE, \
+ IgmpSG, VppHostState, wait_for_igmp_event
from vpp_ip_route import find_mroute, VppIpTable
class TestIgmp(VppTestCase):
""" IGMP Test Case """
+ @classmethod
+ def setUpClass(cls):
+ super(TestIgmp, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestIgmp, cls).tearDownClass()
+
def setUp(self):
super(TestIgmp, self).setUp()
#
# Send a general query (to the all router's address)
- # expect VPP to respond with a membership report
+ # expect VPP to respond with a membership report.
+ # Pad the query with 0 - some devices in the big wild
+ # internet are prone to this.
#
p_g = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst='224.0.0.1', tos=0xc0) /
IGMPv3(type="Membership Query", mrcode=100) /
- IGMPv3mq(gaddr="0.0.0.0"))
+ IGMPv3mq(gaddr="0.0.0.0") /
+ Raw('\x00' * 10))
self.send(self.pg0, p_g)
self.verify_report(capture[0],
[IgmpRecord(h1.sg, "Mode Is Include")])
+ #
+ # A group and source specific query that reports more sources
+ # than the packet actually has.
+ #
+ p_gs2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Membership Query", mrcode=100) /
+ IGMPv3mq(gaddr="239.1.1.1", numsrc=4, srcaddrs=["1.1.1.1"]))
+
+ self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
+
#
# A group and source specific query, with the source NOT matching
# the source VPP has. There should be no response.
[IgmpRecord(h1.sg, "Mode Is Include")])
#
- # Two source and group specific queires in qucik sucession, the
+ # Two source and group specific queries in quick succession, the
# first does not have VPPs source the second does. then vice-versa
#
self.send(self.pg0, [p_gs2, p_gs1])
#
# resend the join. wait for two queries and then send a current-state
- # record to include all sources. this should reset the exiry time
+ # record to include all sources. this should reset the expiry time
# on the sources and thus they will still be present in 2 seconds time.
# If the source timer was not refreshed, then the state would have
# expired in 3 seconds.
self.assertFalse(self.vapi.igmp_dump())
#
- # resend the join, then a leave. Router sends a gruop+source
+ # resend the join, then a leave. Router sends a group+source
# specific query containing both sources
#
self.send(self.pg0, p_j)
self.assertFalse(self.vapi.igmp_dump())
#
- # A (*,G) host report
+ # a TO_EX({}) / IN_EX({}) is treated like a (*,G) join
#
p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
option="router_alert")]) /
IGMPv3(type="Version 3 Membership Report") /
IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype="Allow New Sources", maddr="239.1.1.2"))
+ IGMPv3gr(rtype="Change To Exclude Mode", maddr="239.1.1.2"))
self.send(self.pg0, p_j)
self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
"239.1.1.2", "0.0.0.0", 1))
+ p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Version 3 Membership Report") /
+ IGMPv3mr(numgrp=1) /
+ IGMPv3gr(rtype="Mode Is Exclude", maddr="239.1.1.3"))
+
+ self.send(self.pg0, p_j)
+
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.3", "0.0.0.0", 1))
+
+ #
+ # A 'allow sources' for {} should be ignored as it should
+ # never be sent.
+ #
+ p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Version 3 Membership Report") /
+ IGMPv3mr(numgrp=1) /
+ IGMPv3gr(rtype="Allow New Sources", maddr="239.1.1.4"))
+
+ self.send(self.pg0, p_j)
+
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.2", "0.0.0.0"))
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.3", "0.0.0.0"))
+ self.assertFalse(find_igmp_state(dump, self.pg0,
+ "239.1.1.4", "0.0.0.0"))
+
+ #
+ # a TO_IN({}) and IS_IN({}) are treated like a (*,G) leave
+ #
+ self.vapi.cli("set logging class igmp level debug")
+ p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Version 3 Membership Report") /
+ IGMPv3mr(numgrp=1) /
+ IGMPv3gr(rtype="Change To Include Mode", maddr="239.1.1.2"))
+
+ self.send(self.pg0, p_l)
+ self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
+ "239.1.1.2", "0.0.0.0", 0))
+
+ p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Version 3 Membership Report") /
+ IGMPv3mr(numgrp=1) /
+ IGMPv3gr(rtype="Mode Is Include", maddr="239.1.1.3"))
+
+ self.send(self.pg0, p_l)
+
+ self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
+ "239.1.1.3", "0.0.0.0", 0))
+ self.assertFalse(self.vapi.igmp_dump(self.pg0.sw_if_index))
+
#
# disable router config
#
0,
IGMP_MODE.ROUTER)
+ def _create_igmpv3_pck(self, itf, rtype, maddr, srcaddrs):
+ p = (Ether(dst=itf.local_mac, src=itf.remote_mac) /
+ IP(src=itf.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Version 3 Membership Report") /
+ IGMPv3mr(numgrp=1) /
+ IGMPv3gr(rtype=rtype,
+ maddr=maddr, srcaddrs=srcaddrs))
+ return p
+
+ def test_igmp_proxy_device(self):
+ """ IGMP proxy device """
+ self.pg2.admin_down()
+ self.pg2.unconfig_ip4()
+ self.pg2.set_table_ip4(0)
+ self.pg2.config_ip4()
+ self.pg2.admin_up()
+
+ self.vapi.cli('test igmp timers query 10 src 3 leave 1')
+
+ # enable IGMP
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
+ self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1,
+ IGMP_MODE.ROUTER)
+ self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1,
+ IGMP_MODE.ROUTER)
+
+ # create IGMP proxy device
+ self.vapi.igmp_proxy_device_add_del(0, self.pg0.sw_if_index, 1)
+ self.vapi.igmp_proxy_device_add_del_interface(0,
+ self.pg1.sw_if_index, 1)
+ self.vapi.igmp_proxy_device_add_del_interface(0,
+ self.pg2.sw_if_index, 1)
+
+ # send join on pg1. join should be proxied by pg0
+ p_j = self._create_igmpv3_pck(self.pg1, "Allow New Sources",
+ "239.1.1.1", ["10.1.1.1", "10.1.1.2"])
+ self.send(self.pg1, p_j)
+
+ capture = self.pg0.get_capture(1, timeout=1)
+ self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
+ ["10.1.1.1", "10.1.1.2"]), "Allow New Sources")])
+ self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
+
+ # send join on pg2. join should be proxied by pg0.
+ # the group should contain only 10.1.1.3 as
+ # 10.1.1.1 was already reported
+ p_j = self._create_igmpv3_pck(self.pg2, "Allow New Sources",
+ "239.1.1.1", ["10.1.1.1", "10.1.1.3"])
+ self.send(self.pg2, p_j)
+
+ capture = self.pg0.get_capture(1, timeout=1)
+ self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
+ ["10.1.1.3"]), "Allow New Sources")])
+ self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
+
+ # send leave on pg2. leave for 10.1.1.3 should be proxyed
+ # as pg2 was the only interface interested in 10.1.1.3
+ p_l = self._create_igmpv3_pck(self.pg2, "Block Old Sources",
+ "239.1.1.1", ["10.1.1.3"])
+ self.send(self.pg2, p_l)
+
+ capture = self.pg0.get_capture(1, timeout=2)
+ self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
+ ["10.1.1.3"]), "Block Old Sources")])
+ self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
+
+ # disable igmp on pg1 (also removes interface from proxy device)
+ # proxy leave for 10.1.1.2. pg2 is still interested in 10.1.1.1
+ self.pg_enable_capture(self.pg_interfaces)
+ self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0,
+ IGMP_MODE.ROUTER)
+
+ capture = self.pg0.get_capture(1, timeout=1)
+ self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
+ ["10.1.1.2"]), "Block Old Sources")])
+ self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
+
+ # disable IGMP on pg0 and pg1.
+ # disabling IGMP on pg0 (proxy device upstream interface)
+ # removes this proxy device
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
+ self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0,
+ IGMP_MODE.ROUTER)
+ self.assertFalse(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
+
if __name__ == '__main__':
unittest.main(testRunner=VppTestRunner)