summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorNeale Ranns <nranns@cisco.com>2018-06-07 23:48:20 -0700
committerFlorin Coras <florin.coras@gmail.com>2018-07-09 21:10:53 +0000
commit947ea6222dad1ef04595c34273e9231395aef443 (patch)
tree8990854b2901ff8cc2241b9abfc99b0b4b54d517 /test
parentdd47ecadcf63772a6037a1bb3715772d80e87f51 (diff)
IGMP improvements
- Enable/Disable an interface for IGMP - improve logging - refactor common code - no orphaned timers - IGMP state changes in main thread only - Large groups split over multiple state-change reports - SSM range configuration API. - more tests Change-Id: If5674f1044e7e97274a711f47807c9ba689d7b9a Signed-off-by: Neale Ranns <nranns@cisco.com>
Diffstat (limited to 'test')
-rw-r--r--test/framework.py5
-rw-r--r--test/test_igmp.py732
-rw-r--r--test/vpp_igmp.py75
-rw-r--r--test/vpp_ip_route.py38
-rw-r--r--test/vpp_papi_provider.py35
5 files changed, 642 insertions, 243 deletions
diff --git a/test/framework.py b/test/framework.py
index fdaba2b84d9..4ecb66fe408 100644
--- a/test/framework.py
+++ b/test/framework.py
@@ -837,12 +837,13 @@ class VppTestCase(unittest.TestCase):
"Finished sleep (%s) - slept %ss (wanted %ss)" % (
remark, after - before, timeout))
- def send_and_assert_no_replies(self, intf, pkts, remark=""):
+ def send_and_assert_no_replies(self, intf, pkts, remark="", timeout=None):
self.vapi.cli("clear trace")
intf.add_stream(pkts)
self.pg_enable_capture(self.pg_interfaces)
self.pg_start()
- timeout = 1
+ if not timeout:
+ timeout = 1
for i in self.pg_interfaces:
i.get_capture(0, timeout=timeout)
i.assert_nothing_captured(remark=remark)
diff --git a/test/test_igmp.py b/test/test_igmp.py
index 22b6d2f02e3..cfdd1a8aed3 100644
--- a/test/test_igmp.py
+++ b/test/test_igmp.py
@@ -1,16 +1,20 @@
#!/usr/bin/env python
import unittest
-import socket
from framework import VppTestCase, VppTestRunner, running_extended_tests
from vpp_igmp import *
-from scapy.packet import Raw
from scapy.layers.l2 import Ether
from scapy.layers.inet import IP
from scapy.contrib.igmpv3 import *
from scapy.contrib.igmp import *
+from vpp_ip_route import find_mroute, VppIpTable
+
+
+class IgmpMode:
+ HOST = 1
+ ROUTER = 0
class TestIgmp(VppTestCase):
@@ -19,11 +23,16 @@ class TestIgmp(VppTestCase):
def setUp(self):
super(TestIgmp, self).setUp()
- self.create_pg_interfaces(range(2))
+ self.create_pg_interfaces(range(4))
self.sg_list = []
self.config_list = []
self.ip_addr = []
+ self.ip_table = VppIpTable(self, 1)
+ self.ip_table.add_vpp_config()
+
+ for pg in self.pg_interfaces[2:]:
+ pg.set_table_ip4(1)
for pg in self.pg_interfaces:
pg.admin_up()
pg.config_ip4()
@@ -33,6 +42,7 @@ class TestIgmp(VppTestCase):
for pg in self.pg_interfaces:
self.vapi.igmp_clear_interface(pg.sw_if_index)
pg.unconfig_ip4()
+ pg.set_table_ip4(0)
pg.admin_down()
super(TestIgmp, self).tearDown()
@@ -41,271 +51,589 @@ class TestIgmp(VppTestCase):
self.pg_enable_capture(self.pg_interfaces)
self.pg_start()
- def test_igmp_parse_report(self):
- """ IGMP parse Membership Report """
+ def test_igmp_flush(self):
+ """ IGMP Link Up/down and Flush """
#
- # VPP acts as a router
+ # FIX THIS. Link down.
#
- self.vapi.want_igmp_events(1)
- # hos sends join IGMP 'join'
- p_join = (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=0,
- option="router_alert",
- length=2, value=0)) /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=3, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
+ def test_igmp_enable(self):
+ """ IGMP enable/disable on an interface
- self.send(self.pg0, p_join)
+ check for the addition/removal of the IGMP mroutes """
- # search for the corresponding state created in VPP
- dump = self.vapi.igmp_dump()
- self.assertEqual(len(dump), 1)
- self.assertEqual(dump[0].sw_if_index, self.pg0.sw_if_index)
- self.assertEqual(dump[0].gaddr,
- socket.inet_pton(socket.AF_INET,
- "224.1.1.1"))
- self.assertEqual(dump[0].saddr,
- socket.inet_pton(socket.AF_INET,
- "10.1.1.1"))
+ 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.HOST)
- # VPP sends a notification that a new group has been joined
- ev = self.vapi.wait_for_event(2, "igmp_event")
-
- self.assertEqual(ev.saddr,
- socket.inet_pton(socket.AF_INET,
- "10.1.1.1"))
- self.assertEqual(ev.gaddr,
- socket.inet_pton(socket.AF_INET,
- "224.1.1.1"))
- self.assertEqual(ev.is_join, 1)
-
- # host sends IGMP leave
- p_leave = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22', tos=0xc0) /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=4, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
-
- self.send(self.pg0, p_leave)
-
- # VPP sends a notification that a new group has been left
- ev = self.vapi.wait_for_event(2, "igmp_event")
-
- self.assertEqual(ev.saddr,
- socket.inet_pton(socket.AF_INET,
- "10.1.1.1"))
- self.assertEqual(ev.gaddr,
- socket.inet_pton(socket.AF_INET,
- "224.1.1.1"))
- self.assertEqual(ev.is_join, 0)
-
- # state gone
- dump = self.vapi.igmp_dump()
- self.assertFalse(dump)
+ self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
+ self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
- # resend the join
- self.send(self.pg0, p_join)
- dump = self.vapi.igmp_dump()
- self.assertEqual(len(dump), 1)
- self.assertEqual(dump[0].sw_if_index, self.pg0.sw_if_index)
- self.assertEqual(dump[0].gaddr,
- socket.inet_pton(socket.AF_INET,
- "224.1.1.1"))
- self.assertEqual(dump[0].saddr,
- socket.inet_pton(socket.AF_INET,
- "10.1.1.1"))
-
- # IGMP block
- p_block = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22', tos=0xc0) /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=6, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
-
- self.send(self.pg0, p_block)
+ self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1, IGMP_MODE.HOST)
+ self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 1, IGMP_MODE.HOST)
- dump = self.vapi.igmp_dump()
- self.assertFalse(dump)
+ self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
+ table_id=1))
+ self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
+ table_id=1))
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
+ self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0, IGMP_MODE.HOST)
+ self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0, IGMP_MODE.HOST)
+ self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 0, IGMP_MODE.HOST)
+
+ self.assertFalse(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
+ self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
+ self.assertFalse(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
+ table_id=1))
+ self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
+ table_id=1))
def verify_general_query(self, p):
ip = p[IP]
+ self.assertEqual(len(ip.options), 1)
+ self.assertEqual(ip.options[0].option, 20)
self.assertEqual(ip.dst, "224.0.0.1")
self.assertEqual(ip.proto, 2)
igmp = p[IGMPv3]
self.assertEqual(igmp.type, 0x11)
self.assertEqual(igmp.gaddr, "0.0.0.0")
- def test_igmp_send_query(self):
- """ IGMP send General Query """
+ def verify_group_query(self, p, grp, srcs):
+ ip = p[IP]
+ self.assertEqual(ip.dst, grp)
+ self.assertEqual(ip.proto, 2)
+ self.assertEqual(len(ip.options), 1)
+ self.assertEqual(ip.options[0].option, 20)
+ self.assertEqual(ip.proto, 2)
+ igmp = p[IGMPv3]
+ self.assertEqual(igmp.type, 0x11)
+ self.assertEqual(igmp.gaddr, grp)
+
+ def verify_report(self, rx, records):
+ ip = rx[IP]
+ self.assertEqual(rx[IP].dst, "224.0.0.22")
+ self.assertEqual(len(ip.options), 1)
+ self.assertEqual(ip.options[0].option, 20)
+ self.assertEqual(ip.proto, 2)
+ self.assertEqual(IGMPv3.igmpv3types[rx[IGMPv3].type],
+ "Version 3 Membership Report")
+ self.assertEqual(rx[IGMPv3mr].numgrp, len(records))
+
+ received = rx[IGMPv3mr].records
+
+ for ii in range(len(records)):
+ gr = received[ii]
+ r = records[ii]
+ self.assertEqual(IGMPv3gr.igmpv3grtypes[gr.rtype], r.type)
+ self.assertEqual(gr.numsrc, len(r.sg.saddrs))
+ self.assertEqual(gr.maddr, r.sg.gaddr)
+ self.assertEqual(len(gr.srcaddrs), len(r.sg.saddrs))
+
+ self.assertEqual(sorted(gr.srcaddrs),
+ sorted(r.sg.saddrs))
+
+ def add_group(self, itf, sg, n_pkts=2):
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ hs = VppHostState(self,
+ IGMP_FILTER.INCLUDE,
+ itf.sw_if_index,
+ sg)
+ hs.add_vpp_config()
+
+ capture = itf.get_capture(n_pkts, timeout=10)
+
+ # reports are transmitted twice due to default rebostness value=2
+ self.verify_report(capture[0],
+ [IgmpRecord(sg, "Allow New Sources")]),
+ self.verify_report(capture[1],
+ [IgmpRecord(sg, "Allow New Sources")]),
+
+ return hs
+
+ def remove_group(self, hs):
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ hs.remove_vpp_config()
+
+ capture = self.pg0.get_capture(1, timeout=10)
+
+ self.verify_report(capture[0],
+ [IgmpRecord(hs.sg, "Block Old Sources")])
+
+ def test_igmp_host(self):
+ """ IGMP Host functions """
#
- # VPP acts as a router.
- # Send a membership report so VPP builds state
+ # Enable interface for host functions
#
- p_mr = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22') /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=3, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
+ 1,
+ IGMP_MODE.HOST)
- self.send(self.pg0, p_mr)
- self.logger.info(self.vapi.cli("sh igmp config"))
+ #
+ # Add one S,G of state and expect a state-change event report
+ # indicating the addition of the S,G
+ #
+ h1 = self.add_group(self.pg0, IgmpSG("239.1.1.1", ["1.1.1.1"]))
+
+ # search for the corresponding state created in VPP
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 1)
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", "1.1.1.1"))
#
- # wait for VPP to send out the General Query
+ # Send a general query (to the all router's address)
+ # expect VPP to respond with a membership report
#
- capture = self.pg0.get_capture(1, timeout=61)
+ 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"))
- self.verify_general_query(capture[0])
+ self.send(self.pg0, p_g)
+
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
#
- # the state will expire in 10 more seconds
+ # Group specific query
#
- self.sleep(10)
- self.assertFalse(self.vapi.igmp_dump())
+ p_gs = (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"))
- @unittest.skipUnless(running_extended_tests(), "part of extended tests")
- def test_igmp_src_exp(self):
- """ IGMP per source timer """
+ self.send(self.pg0, p_gs)
+
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
#
- # VPP Acts as a router
+ # A group and source specific query, with the source matching
+ # the source VPP has
#
+ p_gs1 = (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", srcaddrs=["1.1.1.1"]))
- # Host join for (10.1.1.1,224.1.1.1)
- p_mr1 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22') /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=3, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
+ self.send(self.pg0, p_gs1)
- self.send(self.pg0, p_mr1)
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
- # VPP (router) sends General Query
- capture = self.pg0.get_capture(1, timeout=61)
+ #
+ # A group and source specific query, with the source NOT matching
+ # the source VPP has. There should be no response.
+ #
+ 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", srcaddrs=["1.1.1.2"]))
- self.verify_general_query(capture[0])
+ self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
- # host join for same G and another S: (10.1.1.2,224.1.1.1)
- # therefore leaving (10.1.1.1,224.1.1.1)
- p_mr2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22') /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=2, maddr="224.1.1.1", srcaddrs=["10.1.1.2"]))
+ #
+ # A group and source specific query, with the multiple sources
+ # one of which matches the source VPP has.
+ # The report should contain only the source VPP has.
+ #
+ p_gs3 = (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",
+ srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
- self.send(self.pg0, p_mr2)
+ self.send(self.pg0, p_gs3)
- # wait for VPP to send general query
- capture = self.pg0.get_capture(1, timeout=61)
- self.verify_general_query(capture[0])
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
- # host leaves (10.1.1.2,224.1.1.1)
- p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22') /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=2, maddr="224.1.1.1", srcaddrs=["10.1.1.2"]))
+ #
+ # Two source and group specific queires in qucik sucession, the
+ # first does not have VPPs source the second does. then vice-versa
+ #
+ self.send(self.pg0, [p_gs2, p_gs1])
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
- self.send(self.pg0, p_l)
+ self.send(self.pg0, [p_gs1, p_gs2])
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h1.sg, "Mode Is Include")])
- # FIXME BUG
- p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
- IP(src=self.pg0.remote_ip4, dst='224.0.0.22') /
- IGMPv3() /
- IGMPv3mr(numgrp=1) /
- IGMPv3gr(rtype=2, maddr="224.1.1.1", srcaddrs=["10.1.1.1"]))
- self.send(self.pg0, p_l)
+ #
+ # remove state, expect the report for the removal
+ #
+ self.remove_group(h1)
+
+ dump = self.vapi.igmp_dump()
+ self.assertFalse(dump)
#
- # host has left all groups, no state left.
+ # A group with multiple sources
#
- self.sleep(10)
- self.logger.error(self.vapi.cli("sh igmp config"))
- self.assertFalse(self.vapi.igmp_dump())
+ h2 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.1",
+ ["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
+
+ # search for the corresponding state created in VPP
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 3)
+ for s in h2.sg.saddrs:
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", s))
+ #
+ # Send a general query (to the all router's address)
+ # expect VPP to respond with a membership report will all sources
+ #
+ self.send(self.pg0, p_g)
- def test_igmp_query_resp(self):
- """ IGMP General Query response """
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(h2.sg, "Mode Is Include")])
#
- # VPP acting as a host.
- # Add a listener in VPP for (10.1.1.1,244.1.1.1)
+ # Group and source specific query; some present some not
#
- self.config_list.append(
- VppIgmpConfig(
- self, self.pg0.sw_if_index, IgmpSG(
- socket.inet_pton(
- socket.AF_INET, "10.1.1.1"), socket.inet_pton(
- socket.AF_INET, "224.1.1.1"))))
- self.config_list[0].add_vpp_config()
+ p_gs = (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",
+ srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.4"]))
- # verify state exists
- self.assertTrue(self.vapi.igmp_dump(self.pg0.sw_if_index))
+ self.send(self.pg0, p_gs)
+
+ capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(
+ IgmpSG('239.1.1.1', ["1.1.1.1", "1.1.1.2"]),
+ "Mode Is Include")])
#
- # Send a general query (from a router)
+ # add loads more groups
#
- p = (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=0x11, mrcode=100) /
- IGMPv3mq(gaddr="0.0.0.0"))
+ h3 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.2",
+ ["2.1.1.1", "2.1.1.2", "2.1.1.3"]))
+ h4 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.3",
+ ["3.1.1.1", "3.1.1.2", "3.1.1.3"]))
+ h5 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.4",
+ ["4.1.1.1", "4.1.1.2", "4.1.1.3"]))
+ h6 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.5",
+ ["5.1.1.1", "5.1.1.2", "5.1.1.3"]))
+ h7 = self.add_group(self.pg0,
+ IgmpSG("239.1.1.6",
+ ["6.1.1.1", "6.1.1.2",
+ "6.1.1.3", "6.1.1.4",
+ "6.1.1.5", "6.1.1.6",
+ "6.1.1.7", "6.1.1.8",
+ "6.1.1.9", "6.1.1.10",
+ "6.1.1.11", "6.1.1.12",
+ "6.1.1.13", "6.1.1.14",
+ "6.1.1.15", "6.1.1.16"]))
- self.send(self.pg0, p)
+ #
+ # general query.
+ # the order the groups come in is not important, so what is
+ # checked for is what VPP is sending today.
+ #
+ self.send(self.pg0, p_g)
+
+ capture = self.pg0.get_capture(1, timeout=10)
+
+ self.verify_report(capture[0],
+ [IgmpRecord(h3.sg, "Mode Is Include"),
+ IgmpRecord(h2.sg, "Mode Is Include"),
+ IgmpRecord(h6.sg, "Mode Is Include"),
+ IgmpRecord(h4.sg, "Mode Is Include"),
+ IgmpRecord(h5.sg, "Mode Is Include"),
+ IgmpRecord(h7.sg, "Mode Is Include")])
#
- # expect VPP to respond with a membership report for the
- # (10.1.1.1, 224.1.1.1) state
+ # modify a group to add and remove some sources
#
+ h7.sg = IgmpSG("239.1.1.6",
+ ["6.1.1.1", "6.1.1.2",
+ "6.1.1.5", "6.1.1.6",
+ "6.1.1.7", "6.1.1.8",
+ "6.1.1.9", "6.1.1.10",
+ "6.1.1.11", "6.1.1.12",
+ "6.1.1.13", "6.1.1.14",
+ "6.1.1.15", "6.1.1.16",
+ "6.1.1.17", "6.1.1.18"])
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ h7.add_vpp_config()
+
capture = self.pg0.get_capture(1, timeout=10)
+ self.verify_report(capture[0],
+ [IgmpRecord(IgmpSG("239.1.1.6",
+ ["6.1.1.17", "6.1.1.18"]),
+ "Allow New Sources"),
+ IgmpRecord(IgmpSG("239.1.1.6",
+ ["6.1.1.3", "6.1.1.4"]),
+ "Block Old Sources")])
- self.assertEqual(capture[0][IGMPv3].type, 0x22)
- self.assertEqual(capture[0][IGMPv3mr].numgrp, 1)
- self.assertEqual(capture[0][IGMPv3gr].rtype, 1)
- self.assertEqual(capture[0][IGMPv3gr].numsrc, 1)
- self.assertEqual(capture[0][IGMPv3gr].maddr, "224.1.1.1")
- self.assertEqual(len(capture[0][IGMPv3gr].srcaddrs), 1)
- self.assertEqual(capture[0][IGMPv3gr].srcaddrs[0], "10.1.1.1")
+ #
+ # add an additional groups with many sources so that each group
+ # consumes the link MTU. We should therefore see multiple state
+ # state reports when queried.
+ #
+ self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [560, 0, 0, 0])
+
+ src_list = []
+ for i in range(128):
+ src_list.append("10.1.1.%d" % i)
+
+ h8 = self.add_group(self.pg0,
+ IgmpSG("238.1.1.1", src_list))
+ h9 = self.add_group(self.pg0,
+ IgmpSG("238.1.1.2", src_list))
+
+ self.send(self.pg0, p_g)
+
+ capture = self.pg0.get_capture(4, timeout=10)
+
+ self.verify_report(capture[0],
+ [IgmpRecord(h3.sg, "Mode Is Include"),
+ IgmpRecord(h2.sg, "Mode Is Include"),
+ IgmpRecord(h6.sg, "Mode Is Include"),
+ IgmpRecord(h4.sg, "Mode Is Include"),
+ IgmpRecord(h5.sg, "Mode Is Include")])
+ self.verify_report(capture[1],
+ [IgmpRecord(h8.sg, "Mode Is Include")])
+ self.verify_report(capture[2],
+ [IgmpRecord(h7.sg, "Mode Is Include")])
+ self.verify_report(capture[3],
+ [IgmpRecord(h9.sg, "Mode Is Include")])
- def test_igmp_listen(self):
- """ IGMP listen (S,G)s """
+ #
+ # drop the MTU further (so a 128 sized group won't fit)
+ #
+ self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [512, 0, 0, 0])
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ h10 = VppHostState(self,
+ IGMP_FILTER.INCLUDE,
+ self.pg0.sw_if_index,
+ IgmpSG("238.1.1.3", src_list))
+ h10.add_vpp_config()
+
+ capture = self.pg0.get_capture(2, timeout=10)
#
- # VPP acts as a host
- # Add IGMP group state to multiple interfaces and validate its
- # presence
+ # remove state, expect the report for the removal
+ # the dump should be empty
#
- for pg in self.pg_interfaces:
- self.sg_list.append(IgmpSG(socket.inet_pton(
- socket.AF_INET, "10.1.1.%d" % pg._sw_if_index),
- socket.inet_pton(socket.AF_INET, "224.1.1.1")))
+ self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [600, 0, 0, 0])
+ self.remove_group(h8)
+ self.remove_group(h9)
+ self.remove_group(h2)
+ self.remove_group(h3)
+ self.remove_group(h4)
+ self.remove_group(h5)
+ self.remove_group(h6)
+ self.remove_group(h7)
+ self.remove_group(h10)
- for pg in self.pg_interfaces:
- self.config_list.append(
- VppIgmpConfig(
- self,
- pg._sw_if_index,
- self.sg_list))
- self.config_list[-1].add_vpp_config()
-
- for config in self.config_list:
- dump = self.vapi.igmp_dump(config.sw_if_index)
- self.assertTrue(dump)
- self.assertEqual(len(dump), len(config.sg_list))
- for idx, e in enumerate(dump):
- self.assertEqual(e.sw_if_index, config.sw_if_index)
- self.assertEqual(e.saddr, config.sg_list[idx].saddr)
- self.assertEqual(e.gaddr, config.sg_list[idx].gaddr)
-
- for config in self.config_list:
- config.remove_vpp_config()
+ self.logger.info(self.vapi.cli("sh igmp config"))
+ self.assertFalse(self.vapi.igmp_dump())
- dump = self.vapi.igmp_dump()
- self.assertFalse(dump)
+ #
+ # TODO
+ # ADD STATE ON MORE INTERFACES
+ #
+
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
+ 0,
+ IGMP_MODE.HOST)
+
+ def test_igmp_router(self):
+ """ IGMP Router Functions """
+
+ #
+ # Drop reports when not enabled
+ #
+ 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.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
+ 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,
+ options=[IPOption(copy_flag=1, optclass="control",
+ option="router_alert")]) /
+ IGMPv3(type="Version 3 Membership Report") /
+ IGMPv3mr(numgrp=1) /
+ IGMPv3gr(rtype="Block Old Sources",
+ maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
+
+ self.send(self.pg0, p_j)
+ self.assertFalse(self.vapi.igmp_dump())
+
+ #
+ # drop the default timer values so these tests execute in a
+ # reasonable time frame
+ #
+ self.vapi.cli("test igmp timers query 1 src 3 leave 1")
+
+ #
+ # enable router functions on the interface
+ #
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
+ 1,
+ IGMP_MODE.ROUTER)
+ self.vapi.want_igmp_events(1)
+
+ #
+ # wait for router to send general query
+ #
+ for ii in range(3):
+ capture = self.pg0.get_capture(1, timeout=2)
+ self.verify_general_query(capture[0])
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ #
+ # re-send the report. VPP should now hold state for the new group
+ # VPP sends a notification that a new group has been joined
+ #
+ self.send(self.pg0, p_j)
+
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.1", 1))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 1))
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 2)
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", "10.1.1.1"))
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", "10.1.1.2"))
+
+ #
+ # wait for the per-source timer to expire
+ # the state should be reaped
+ # VPP sends a notification that the group has been left
+ #
+ self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
+ "239.1.1.1", "10.1.1.1", 0))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 0))
+ self.assertFalse(self.vapi.igmp_dump())
+
+ #
+ # resend the join. wait for two queries and then send a current-state
+ # record to include all sources. this should reset the exiry 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.send(self.pg0, p_j)
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.1", 1))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 1))
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 2)
+
+ capture = self.pg0.get_capture(2, timeout=3)
+ self.verify_general_query(capture[0])
+ self.verify_general_query(capture[1])
+
+ p_cs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
+ 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.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
+
+ self.send(self.pg0, p_cs)
+
+ self.sleep(2)
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 2)
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", "10.1.1.1"))
+ self.assertTrue(find_igmp_state(dump, self.pg0,
+ "239.1.1.1", "10.1.1.2"))
+
+ #
+ # wait for the per-source timer to expire
+ # the state should be reaped
+ #
+ self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
+ "239.1.1.1", "10.1.1.1", 0))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 0))
+ self.assertFalse(self.vapi.igmp_dump())
+
+ #
+ # resend the join, then a leave. Router sends a gruop+source
+ # specific query containing both sources
+ #
+ self.send(self.pg0, p_j)
+
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.1", 1))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 1))
+ dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
+ self.assertEqual(len(dump), 2)
+
+ self.send(self.pg0, p_l)
+ capture = self.pg0.get_capture(1, timeout=3)
+ self.verify_group_query(capture[0], "239.1.1.1",
+ ["10.1.1.1", "10.1.1.2"])
+
+ #
+ # the group specific query drops the timeout to leave (=1) seconds
+ #
+ self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
+ "239.1.1.1", "10.1.1.1", 0))
+ self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
+ "239.1.1.1", "10.1.1.2", 0))
+ self.assertFalse(self.vapi.igmp_dump())
+ self.assertFalse(self.vapi.igmp_dump())
+
+ #
+ # disable router config
+ #
+ self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
+ 0,
+ IGMP_MODE.ROUTER)
if __name__ == '__main__':
diff --git a/test/vpp_igmp.py b/test/vpp_igmp.py
index d1a308878c5..8f0191644bd 100644
--- a/test/vpp_igmp.py
+++ b/test/vpp_igmp.py
@@ -1,39 +1,80 @@
from vpp_object import VppObject
+import socket
+
+
+class IGMP_MODE:
+ ROUTER = 0
+ HOST = 1
+
+
+class IGMP_FILTER:
+ INCLUDE = 1
+ EXCLUDE = 0
+
+
+def find_igmp_state(states, itf, gaddr, saddr):
+ for s in states:
+ if s.sw_if_index == itf.sw_if_index and \
+ s.gaddr.address == socket.inet_pton(socket.AF_INET, gaddr) and \
+ s.saddr.address == socket.inet_pton(socket.AF_INET, saddr):
+ return True
+ return False
+
+
+def wait_for_igmp_event(test, timeout, itf, gaddr, saddr, ff):
+ ev = test.vapi.wait_for_event(timeout, "igmp_event")
+ if ev.sw_if_index == itf.sw_if_index and \
+ ev.gaddr.address == socket.inet_pton(socket.AF_INET, gaddr) and \
+ ev.saddr.address == socket.inet_pton(socket.AF_INET, saddr) and \
+ ev.filter == ff:
+ return True
+ return False
class IgmpSG():
- def __init__(self, saddr, gaddr):
- self.saddr = saddr
+ def __init__(self, gaddr, saddrs):
self.gaddr = gaddr
+ self.gaddr_p = socket.inet_pton(socket.AF_INET, gaddr)
+ self.saddrs = saddrs
+ self.saddrs_p = []
+ self.saddrs_encoded = []
+ for s in saddrs:
+ ss = socket.inet_pton(socket.AF_INET, s)
+ self.saddrs_p.append(ss)
+ self.saddrs_encoded.append({'address': ss})
+
+class IgmpRecord():
+ def __init__(self, sg, type):
+ self.sg = sg
+ self.type = type
-class VppIgmpConfig(VppObject):
- def __init__(self, test, sw_if_index, sg=None):
+
+class VppHostState(VppObject):
+ def __init__(self, test, filter, sw_if_index, sg):
self._test = test
self.sw_if_index = sw_if_index
- if isinstance(sg, list):
- self.sg_list = sg
- else:
- self.sg_list = []
- self.sg_list.append(sg)
-
- def add_sg(self, sg):
- self.sg.append(sg)
+ self.filter = filter
+ self.sg = sg
def add_vpp_config(self):
- for e in self.sg_list:
- self._test.vapi.igmp_listen(
- 1, self.sw_if_index, e.saddr, e.gaddr)
+ self._test.vapi.igmp_listen(
+ self.filter, self.sw_if_index,
+ self.sg.saddrs_encoded, self.sg.gaddr_p)
def remove_vpp_config(self):
- self._test.vapi.igmp_clear_interface(self.sw_if_index)
+ self._test.vapi.igmp_listen(
+ self.filter,
+ self.sw_if_index,
+ [],
+ self.sg.gaddr_p)
def __str__(self):
return self.object_id()
def object_id(self):
- return "%s:%d" % (self.sg_list, self.sw_if_index)
+ return "%s:%d" % (self.sg, self.sw_if_index)
def query_vpp_config(self):
return self._test.vapi.igmp_dump()
diff --git a/test/vpp_ip_route.py b/test/vpp_ip_route.py
index 17a42fec706..9e5c53184e3 100644
--- a/test/vpp_ip_route.py
+++ b/test/vpp_ip_route.py
@@ -60,6 +60,25 @@ def find_route(test, ip_addr, len, table_id=0, inet=AF_INET):
return False
+def find_mroute(test, grp_addr, src_addr, grp_addr_len,
+ table_id=0, inet=AF_INET):
+ if inet == AF_INET:
+ s = 4
+ routes = test.vapi.ip_mfib_dump()
+ else:
+ s = 16
+ routes = test.vapi.ip6_mfib_dump()
+ gaddr = inet_pton(inet, grp_addr)
+ saddr = inet_pton(inet, src_addr)
+ for e in routes:
+ if gaddr == e.grp_address[:s] \
+ and grp_addr_len == e.address_length \
+ and saddr == e.src_address[:s] \
+ and table_id == e.table_id:
+ return True
+ return False
+
+
class VppIpTable(VppObject):
def __init__(self,
@@ -324,6 +343,8 @@ class VppIpMRoute(VppObject):
self.is_ip6 = is_ip6
self.rpf_id = rpf_id
+ self.grp_addr_p = grp_addr
+ self.src_addr_p = src_addr
if is_ip6:
self.grp_addr = inet_pton(AF_INET6, grp_addr)
self.src_addr = inet_pton(AF_INET6, src_addr)
@@ -406,17 +427,12 @@ class VppIpMRoute(VppObject):
is_ipv6=self.is_ip6)
def query_vpp_config(self):
- if self.is_ip6:
- dump = self._test.vapi.ip6_mfib_dump()
- else:
- dump = self._test.vapi.ip_mfib_dump()
- for e in dump:
- if self.grp_addr == e.grp_address \
- and self.grp_addr_len == e.address_length \
- and self.src_addr == e.src_address \
- and self.table_id == e.table_id:
- return True
- return False
+ return find_mroute(self._test,
+ self.grp_addr_p,
+ self.src_addr_p,
+ self.grp_addr_len,
+ self.table_id,
+ inet=AF_INET6 if self.is_ip6 == 1 else AF_INET)
def __str__(self):
return self.object_id()
diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py
index 2d4b44764f3..5383b07fc4c 100644
--- a/test/vpp_papi_provider.py
+++ b/test/vpp_papi_provider.py
@@ -3474,7 +3474,14 @@ class VppPapiProvider(object):
'input_source': input_source,
'enable': enable})
- def igmp_listen(self, enable, sw_if_index, saddr, gaddr):
+ def igmp_enable_disable(self, sw_if_index, enable, host):
+ """ Enable/disable IGMP on a given interface """
+ return self.api(self.papi.igmp_enable_disable,
+ {'enable': enable,
+ 'mode': host,
+ 'sw_if_index': sw_if_index})
+
+ def igmp_listen(self, filter, sw_if_index, saddrs, gaddr):
""" Listen for new (S,G) on specified interface
:param enable: add/del
@@ -3483,20 +3490,26 @@ class VppPapiProvider(object):
:param gaddr: group ip4 addr
"""
return self.api(self.papi.igmp_listen,
- {'enable': enable,
- 'sw_if_index': sw_if_index,
- 'saddr': saddr,
- 'gaddr': gaddr})
+ {
+ 'group':
+ {
+ 'filter': filter,
+ 'sw_if_index': sw_if_index,
+ 'n_srcs': len(saddrs),
+ 'saddrs': saddrs,
+ 'gaddr':
+ {
+ 'address': gaddr
+ }
+ }
+ })
def igmp_dump(self, sw_if_index=None):
""" Dump all (S,G) interface configurations """
if sw_if_index is None:
- dump_all = 1
- sw_if_index = 0
- else:
- dump_all = 0
- return self.api(self.papi.igmp_dump, {'sw_if_index': sw_if_index,
- 'dump_all': dump_all})
+ sw_if_index = 0xffffffff
+ return self.api(self.papi.igmp_dump,
+ {'sw_if_index': sw_if_index})
def igmp_clear_interface(self, sw_if_index):
""" Remove all (S,G)s from specified interface