5 from scapy.layers.l2 import Ether
6 from scapy.packet import Raw
7 from scapy.layers.inet import IP, IPOption
8 from scapy.contrib.igmpv3 import IGMPv3, IGMPv3gr, IGMPv3mq, IGMPv3mr
10 from framework import tag_fixme_vpp_workers
11 from framework import VppTestCase, VppTestRunner
12 from vpp_igmp import (
21 from vpp_ip_route import find_mroute, VppIpTable
29 @tag_fixme_vpp_workers
30 class TestIgmp(VppTestCase):
35 super(TestIgmp, cls).setUpClass()
38 def tearDownClass(cls):
39 super(TestIgmp, cls).tearDownClass()
42 super(TestIgmp, self).setUp()
44 self.create_pg_interfaces(range(4))
49 self.ip_table = VppIpTable(self, 1)
50 self.ip_table.add_vpp_config()
52 for pg in self.pg_interfaces[2:]:
54 for pg in self.pg_interfaces:
60 for pg in self.pg_interfaces:
61 self.vapi.igmp_clear_interface(pg.sw_if_index)
65 super(TestIgmp, self).tearDown()
67 def send(self, ti, pkts):
69 self.pg_enable_capture(self.pg_interfaces)
72 def test_igmp_flush(self):
73 """IGMP Link Up/down and Flush"""
76 # FIX THIS. Link down.
79 def test_igmp_enable(self):
80 """IGMP enable/disable on an interface
82 check for the addition/removal of the IGMP mroutes"""
84 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
85 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1, IGMP_MODE.HOST)
87 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
88 self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
90 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1, IGMP_MODE.HOST)
91 self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 1, IGMP_MODE.HOST)
93 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32, table_id=1))
94 self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32, table_id=1))
95 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
96 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0, IGMP_MODE.HOST)
97 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0, IGMP_MODE.HOST)
98 self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 0, IGMP_MODE.HOST)
100 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
101 self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
102 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32, table_id=1))
103 self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32, table_id=1))
105 def verify_general_query(self, p):
107 self.assertEqual(len(ip.options), 1)
108 self.assertEqual(ip.options[0].option, 20)
109 self.assertEqual(ip.dst, "224.0.0.1")
110 self.assertEqual(ip.proto, 2)
112 self.assertEqual(igmp.type, 0x11)
113 self.assertEqual(igmp.gaddr, "0.0.0.0")
115 def verify_group_query(self, p, grp, srcs):
117 self.assertEqual(ip.dst, grp)
118 self.assertEqual(ip.proto, 2)
119 self.assertEqual(len(ip.options), 1)
120 self.assertEqual(ip.options[0].option, 20)
121 self.assertEqual(ip.proto, 2)
123 self.assertEqual(igmp.type, 0x11)
124 self.assertEqual(igmp.gaddr, grp)
126 def verify_report(self, rx, records):
128 self.assertEqual(rx[IP].dst, "224.0.0.22")
129 self.assertEqual(len(ip.options), 1)
130 self.assertEqual(ip.options[0].option, 20)
131 self.assertEqual(ip.proto, 2)
133 IGMPv3.igmpv3types[rx[IGMPv3].type], "Version 3 Membership Report"
135 self.assertEqual(rx[IGMPv3mr].numgrp, len(records))
137 received = rx[IGMPv3mr].records
139 for ii in range(len(records)):
142 self.assertEqual(IGMPv3gr.igmpv3grtypes[gr.rtype], r.type)
143 self.assertEqual(gr.numsrc, len(r.sg.saddrs))
144 self.assertEqual(gr.maddr, r.sg.gaddr)
145 self.assertEqual(len(gr.srcaddrs), len(r.sg.saddrs))
147 self.assertEqual(sorted(gr.srcaddrs), sorted(r.sg.saddrs))
149 def add_group(self, itf, sg, n_pkts=2):
150 self.pg_enable_capture(self.pg_interfaces)
153 hs = VppHostState(self, IGMP_FILTER.INCLUDE, itf.sw_if_index, sg)
156 capture = itf.get_capture(n_pkts, timeout=10)
158 # reports are transmitted twice due to default rebostness value=2
159 self.verify_report(capture[0], [IgmpRecord(sg, "Allow New Sources")]),
160 self.verify_report(capture[1], [IgmpRecord(sg, "Allow New Sources")]),
164 def remove_group(self, hs):
165 self.pg_enable_capture(self.pg_interfaces)
167 hs.remove_vpp_config()
169 capture = self.pg0.get_capture(1, timeout=10)
171 self.verify_report(capture[0], [IgmpRecord(hs.sg, "Block Old Sources")])
173 def test_igmp_host(self):
174 """IGMP Host functions"""
177 # Enable interface for host functions
179 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
182 # Add one S,G of state and expect a state-change event report
183 # indicating the addition of the S,G
185 h1 = self.add_group(self.pg0, IgmpSG("239.1.1.1", ["1.1.1.1"]))
187 # search for the corresponding state created in VPP
188 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
189 self.assertEqual(len(dump), 1)
190 self.assertTrue(find_igmp_state(dump, self.pg0, "239.1.1.1", "1.1.1.1"))
193 # Send a general query (to the all router's address)
194 # expect VPP to respond with a membership report.
195 # Pad the query with 0 - some devices in the big wild
196 # internet are prone to this.
199 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
200 / IP(src=self.pg0.remote_ip4, dst="224.0.0.1", tos=0xC0)
201 / IGMPv3(type="Membership Query", mrcode=100)
202 / IGMPv3mq(gaddr="0.0.0.0")
206 self.send(self.pg0, p_g)
208 capture = self.pg0.get_capture(1, timeout=10)
209 self.verify_report(capture[0], [IgmpRecord(h1.sg, "Mode Is Include")])
212 # Group specific query
215 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
217 src=self.pg0.remote_ip4,
222 copy_flag=1, optclass="control", option="router_alert", length=4
226 / IGMPv3(type="Membership Query", mrcode=100)
227 / IGMPv3mq(gaddr="239.1.1.1")
230 self.send(self.pg0, p_gs)
232 capture = self.pg0.get_capture(1, timeout=10)
233 self.verify_report(capture[0], [IgmpRecord(h1.sg, "Mode Is Include")])
236 # A group and source specific query, with the source matching
240 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
242 src=self.pg0.remote_ip4,
247 copy_flag=1, optclass="control", option="router_alert", length=4
251 / IGMPv3(type="Membership Query", mrcode=100)
252 / IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.1"])
255 self.send(self.pg0, p_gs1)
257 capture = self.pg0.get_capture(1, timeout=10)
258 self.verify_report(capture[0], [IgmpRecord(h1.sg, "Mode Is Include")])
261 # A group and source specific query that reports more sources
262 # than the packet actually has.
265 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
267 src=self.pg0.remote_ip4,
272 copy_flag=1, optclass="control", option="router_alert", length=4
276 / IGMPv3(type="Membership Query", mrcode=100)
277 / IGMPv3mq(gaddr="239.1.1.1", numsrc=4, srcaddrs=["1.1.1.1"])
280 self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
283 # A group and source specific query, with the source NOT matching
284 # the source VPP has. There should be no response.
287 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
289 src=self.pg0.remote_ip4,
294 copy_flag=1, optclass="control", option="router_alert", length=4
298 / IGMPv3(type="Membership Query", mrcode=100)
299 / IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.2"])
302 self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
305 # A group and source specific query, with the multiple sources
306 # one of which matches the source VPP has.
307 # The report should contain only the source VPP has.
310 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
312 src=self.pg0.remote_ip4,
317 copy_flag=1, optclass="control", option="router_alert", length=4
321 / IGMPv3(type="Membership Query", mrcode=100)
322 / IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.3"])
325 self.send(self.pg0, p_gs3)
327 capture = self.pg0.get_capture(1, timeout=10)
328 self.verify_report(capture[0], [IgmpRecord(h1.sg, "Mode Is Include")])
331 # Two source and group specific queries in quick succession, the
332 # first does not have VPPs source the second does. then vice-versa
334 self.send(self.pg0, [p_gs2, p_gs1])
335 capture = self.pg0.get_capture(1, timeout=10)
336 self.verify_report(capture[0], [IgmpRecord(h1.sg, "Mode Is Include")])
338 self.send(self.pg0, [p_gs1, p_gs2])
339 capture = self.pg0.get_capture(1, timeout=10)
340 self.verify_report(capture[0], [IgmpRecord(h1.sg, "Mode Is Include")])
343 # remove state, expect the report for the removal
345 self.remove_group(h1)
347 dump = self.vapi.igmp_dump()
348 self.assertFalse(dump)
351 # A group with multiple sources
354 self.pg0, IgmpSG("239.1.1.1", ["1.1.1.1", "1.1.1.2", "1.1.1.3"])
357 # search for the corresponding state created in VPP
358 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
359 self.assertEqual(len(dump), 3)
360 for s in h2.sg.saddrs:
361 self.assertTrue(find_igmp_state(dump, self.pg0, "239.1.1.1", s))
363 # Send a general query (to the all router's address)
364 # expect VPP to respond with a membership report will all sources
366 self.send(self.pg0, p_g)
368 capture = self.pg0.get_capture(1, timeout=10)
369 self.verify_report(capture[0], [IgmpRecord(h2.sg, "Mode Is Include")])
372 # Group and source specific query; some present some not
375 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
377 src=self.pg0.remote_ip4,
382 copy_flag=1, optclass="control", option="router_alert", length=4
386 / IGMPv3(type="Membership Query", mrcode=100)
387 / IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.4"])
390 self.send(self.pg0, p_gs)
392 capture = self.pg0.get_capture(1, timeout=10)
397 IgmpSG("239.1.1.1", ["1.1.1.1", "1.1.1.2"]), "Mode Is Include"
403 # add loads more groups
406 self.pg0, IgmpSG("239.1.1.2", ["2.1.1.1", "2.1.1.2", "2.1.1.3"])
409 self.pg0, IgmpSG("239.1.1.3", ["3.1.1.1", "3.1.1.2", "3.1.1.3"])
412 self.pg0, IgmpSG("239.1.1.4", ["4.1.1.1", "4.1.1.2", "4.1.1.3"])
415 self.pg0, IgmpSG("239.1.1.5", ["5.1.1.1", "5.1.1.2", "5.1.1.3"])
444 # the order the groups come in is not important, so what is
445 # checked for is what VPP is sending today.
447 self.send(self.pg0, p_g)
449 capture = self.pg0.get_capture(1, timeout=10)
454 IgmpRecord(h3.sg, "Mode Is Include"),
455 IgmpRecord(h2.sg, "Mode Is Include"),
456 IgmpRecord(h6.sg, "Mode Is Include"),
457 IgmpRecord(h4.sg, "Mode Is Include"),
458 IgmpRecord(h5.sg, "Mode Is Include"),
459 IgmpRecord(h7.sg, "Mode Is Include"),
464 # modify a group to add and remove some sources
488 self.pg_enable_capture(self.pg_interfaces)
492 capture = self.pg0.get_capture(1, timeout=10)
497 IgmpSG("239.1.1.6", ["6.1.1.17", "6.1.1.18"]), "Allow New Sources"
500 IgmpSG("239.1.1.6", ["6.1.1.3", "6.1.1.4"]), "Block Old Sources"
506 # add an additional groups with many sources so that each group
507 # consumes the link MTU. We should therefore see multiple state
508 # state reports when queried.
510 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [560, 0, 0, 0])
514 src_list.append("10.1.1.%d" % i)
516 h8 = self.add_group(self.pg0, IgmpSG("238.1.1.1", src_list))
517 h9 = self.add_group(self.pg0, IgmpSG("238.1.1.2", src_list))
519 self.send(self.pg0, p_g)
521 capture = self.pg0.get_capture(4, timeout=10)
526 IgmpRecord(h3.sg, "Mode Is Include"),
527 IgmpRecord(h2.sg, "Mode Is Include"),
528 IgmpRecord(h6.sg, "Mode Is Include"),
529 IgmpRecord(h4.sg, "Mode Is Include"),
530 IgmpRecord(h5.sg, "Mode Is Include"),
533 self.verify_report(capture[1], [IgmpRecord(h8.sg, "Mode Is Include")])
534 self.verify_report(capture[2], [IgmpRecord(h7.sg, "Mode Is Include")])
535 self.verify_report(capture[3], [IgmpRecord(h9.sg, "Mode Is Include")])
538 # drop the MTU further (so a 128 sized group won't fit)
540 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [512, 0, 0, 0])
542 self.pg_enable_capture(self.pg_interfaces)
548 self.pg0.sw_if_index,
549 IgmpSG("238.1.1.3", src_list),
553 capture = self.pg0.get_capture(2, timeout=10)
554 # wait for a little bit
558 # remove state, expect the report for the removal
559 # the dump should be empty
561 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [600, 0, 0, 0])
562 self.remove_group(h8)
563 self.remove_group(h9)
564 self.remove_group(h2)
565 self.remove_group(h3)
566 self.remove_group(h4)
567 self.remove_group(h5)
568 self.remove_group(h6)
569 self.remove_group(h7)
570 self.remove_group(h10)
572 self.logger.info(self.vapi.cli("sh igmp config"))
573 self.assertFalse(self.vapi.igmp_dump())
577 # ADD STATE ON MORE INTERFACES
580 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
582 def test_igmp_router(self):
583 """IGMP Router Functions"""
586 # Drop reports when not enabled
589 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
591 src=self.pg0.remote_ip4,
597 copy_flag=1, optclass="control", option="router_alert", length=4
601 / IGMPv3(type="Version 3 Membership Report")
604 rtype="Allow New Sources",
606 srcaddrs=["10.1.1.1", "10.1.1.2"],
610 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
612 src=self.pg0.remote_ip4,
617 copy_flag=1, optclass="control", option="router_alert", length=4
621 / IGMPv3(type="Version 3 Membership Report")
624 rtype="Block Old Sources",
626 srcaddrs=["10.1.1.1", "10.1.1.2"],
630 self.send(self.pg0, p_j)
631 self.assertFalse(self.vapi.igmp_dump())
634 # drop the default timer values so these tests execute in a
635 # reasonable time frame
637 self.vapi.cli("test igmp timers query 1 src 3 leave 1")
640 # enable router functions on the interface
642 self.pg_enable_capture(self.pg_interfaces)
644 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.ROUTER)
645 self.vapi.want_igmp_events(1)
648 # wait for router to send general query
651 capture = self.pg0.get_capture(1, timeout=2)
652 self.verify_general_query(capture[0])
653 self.pg_enable_capture(self.pg_interfaces)
657 # re-send the report. VPP should now hold state for the new group
658 # VPP sends a notification that a new group has been joined
660 self.send(self.pg0, p_j)
663 wait_for_igmp_event(self, 1, self.pg0, "239.1.1.1", "10.1.1.1", 1)
666 wait_for_igmp_event(self, 1, self.pg0, "239.1.1.1", "10.1.1.2", 1)
668 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
669 self.assertEqual(len(dump), 2)
670 self.assertTrue(find_igmp_state(dump, self.pg0, "239.1.1.1", "10.1.1.1"))
671 self.assertTrue(find_igmp_state(dump, self.pg0, "239.1.1.1", "10.1.1.2"))
674 # wait for the per-source timer to expire
675 # the state should be reaped
676 # VPP sends a notification that the group has been left
679 wait_for_igmp_event(self, 4, self.pg0, "239.1.1.1", "10.1.1.1", 0)
682 wait_for_igmp_event(self, 1, self.pg0, "239.1.1.1", "10.1.1.2", 0)
684 self.assertFalse(self.vapi.igmp_dump())
687 # resend the join. wait for two queries and then send a current-state
688 # record to include all sources. this should reset the expiry time
689 # on the sources and thus they will still be present in 2 seconds time.
690 # If the source timer was not refreshed, then the state would have
691 # expired in 3 seconds.
693 self.send(self.pg0, p_j)
695 wait_for_igmp_event(self, 1, self.pg0, "239.1.1.1", "10.1.1.1", 1)
698 wait_for_igmp_event(self, 1, self.pg0, "239.1.1.1", "10.1.1.2", 1)
700 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
701 self.assertEqual(len(dump), 2)
703 capture = self.pg0.get_capture(2, timeout=3)
704 self.verify_general_query(capture[0])
705 self.verify_general_query(capture[1])
708 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
710 src=self.pg0.remote_ip4,
715 copy_flag=1, optclass="control", option="router_alert", length=4
719 / IGMPv3(type="Version 3 Membership Report")
722 rtype="Mode Is Include",
724 srcaddrs=["10.1.1.1", "10.1.1.2"],
728 self.send(self.pg0, p_cs)
731 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
732 self.assertEqual(len(dump), 2)
733 self.assertTrue(find_igmp_state(dump, self.pg0, "239.1.1.1", "10.1.1.1"))
734 self.assertTrue(find_igmp_state(dump, self.pg0, "239.1.1.1", "10.1.1.2"))
737 # wait for the per-source timer to expire
738 # the state should be reaped
741 wait_for_igmp_event(self, 4, self.pg0, "239.1.1.1", "10.1.1.1", 0)
744 wait_for_igmp_event(self, 1, self.pg0, "239.1.1.1", "10.1.1.2", 0)
746 self.assertFalse(self.vapi.igmp_dump())
749 # resend the join, then a leave. Router sends a group+source
750 # specific query containing both sources
752 self.send(self.pg0, p_j)
755 wait_for_igmp_event(self, 1, self.pg0, "239.1.1.1", "10.1.1.1", 1)
758 wait_for_igmp_event(self, 1, self.pg0, "239.1.1.1", "10.1.1.2", 1)
760 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
761 self.assertEqual(len(dump), 2)
763 self.send(self.pg0, p_l)
764 capture = self.pg0.get_capture(1, timeout=3)
765 self.verify_group_query(capture[0], "239.1.1.1", ["10.1.1.1", "10.1.1.2"])
768 # the group specific query drops the timeout to leave (=1) seconds
771 wait_for_igmp_event(self, 2, self.pg0, "239.1.1.1", "10.1.1.1", 0)
774 wait_for_igmp_event(self, 1, self.pg0, "239.1.1.1", "10.1.1.2", 0)
776 self.assertFalse(self.vapi.igmp_dump())
777 self.assertFalse(self.vapi.igmp_dump())
780 # a TO_EX({}) / IN_EX({}) is treated like a (*,G) join
783 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
785 src=self.pg0.remote_ip4,
791 copy_flag=1, optclass="control", option="router_alert", length=4
795 / IGMPv3(type="Version 3 Membership Report")
797 / IGMPv3gr(rtype="Change To Exclude Mode", maddr="239.1.1.2")
800 self.send(self.pg0, p_j)
803 wait_for_igmp_event(self, 1, self.pg0, "239.1.1.2", "0.0.0.0", 1)
807 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
809 src=self.pg0.remote_ip4,
815 copy_flag=1, optclass="control", option="router_alert", length=4
819 / IGMPv3(type="Version 3 Membership Report")
821 / IGMPv3gr(rtype="Mode Is Exclude", maddr="239.1.1.3")
824 self.send(self.pg0, p_j)
827 wait_for_igmp_event(self, 1, self.pg0, "239.1.1.3", "0.0.0.0", 1)
831 # A 'allow sources' for {} should be ignored as it should
835 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
837 src=self.pg0.remote_ip4,
843 copy_flag=1, optclass="control", option="router_alert", length=4
847 / IGMPv3(type="Version 3 Membership Report")
849 / IGMPv3gr(rtype="Allow New Sources", maddr="239.1.1.4")
852 self.send(self.pg0, p_j)
854 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
855 self.assertTrue(find_igmp_state(dump, self.pg0, "239.1.1.2", "0.0.0.0"))
856 self.assertTrue(find_igmp_state(dump, self.pg0, "239.1.1.3", "0.0.0.0"))
857 self.assertFalse(find_igmp_state(dump, self.pg0, "239.1.1.4", "0.0.0.0"))
860 # a TO_IN({}) and IS_IN({}) are treated like a (*,G) leave
862 self.vapi.cli("set logging class igmp level debug")
864 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
866 src=self.pg0.remote_ip4,
872 copy_flag=1, optclass="control", option="router_alert", length=4
876 / IGMPv3(type="Version 3 Membership Report")
878 / IGMPv3gr(rtype="Change To Include Mode", maddr="239.1.1.2")
881 self.send(self.pg0, p_l)
883 wait_for_igmp_event(self, 2, self.pg0, "239.1.1.2", "0.0.0.0", 0)
887 Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
889 src=self.pg0.remote_ip4,
895 copy_flag=1, optclass="control", option="router_alert", length=4
899 / IGMPv3(type="Version 3 Membership Report")
901 / IGMPv3gr(rtype="Mode Is Include", maddr="239.1.1.3")
904 self.send(self.pg0, p_l)
907 wait_for_igmp_event(self, 2, self.pg0, "239.1.1.3", "0.0.0.0", 0)
909 self.assertFalse(self.vapi.igmp_dump(self.pg0.sw_if_index))
912 # disable router config
914 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.ROUTER)
916 def _create_igmpv3_pck(self, itf, rtype, maddr, srcaddrs):
918 Ether(dst=itf.local_mac, src=itf.remote_mac)
926 copy_flag=1, optclass="control", option="router_alert", length=4
930 / IGMPv3(type="Version 3 Membership Report")
932 / IGMPv3gr(rtype=rtype, maddr=maddr, srcaddrs=srcaddrs)
936 def test_igmp_proxy_device(self):
937 """IGMP proxy device"""
938 self.pg2.admin_down()
939 self.pg2.unconfig_ip4()
940 self.pg2.set_table_ip4(0)
941 self.pg2.config_ip4()
944 self.vapi.cli("test igmp timers query 10 src 3 leave 1")
947 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
948 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1, IGMP_MODE.ROUTER)
949 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1, IGMP_MODE.ROUTER)
951 # create IGMP proxy device
952 self.vapi.igmp_proxy_device_add_del(0, self.pg0.sw_if_index, 1)
953 self.vapi.igmp_proxy_device_add_del_interface(0, self.pg1.sw_if_index, 1)
954 self.vapi.igmp_proxy_device_add_del_interface(0, self.pg2.sw_if_index, 1)
956 # send join on pg1. join should be proxied by pg0
957 p_j = self._create_igmpv3_pck(
958 self.pg1, "Allow New Sources", "239.1.1.1", ["10.1.1.1", "10.1.1.2"]
960 self.send(self.pg1, p_j)
962 capture = self.pg0.get_capture(1, timeout=1)
967 IgmpSG("239.1.1.1", ["10.1.1.1", "10.1.1.2"]), "Allow New Sources"
971 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
973 # send join on pg2. join should be proxied by pg0.
974 # the group should contain only 10.1.1.3 as
975 # 10.1.1.1 was already reported
976 p_j = self._create_igmpv3_pck(
977 self.pg2, "Allow New Sources", "239.1.1.1", ["10.1.1.1", "10.1.1.3"]
979 self.send(self.pg2, p_j)
981 capture = self.pg0.get_capture(1, timeout=1)
984 [IgmpRecord(IgmpSG("239.1.1.1", ["10.1.1.3"]), "Allow New Sources")],
986 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
988 # send leave on pg2. leave for 10.1.1.3 should be proxyed
989 # as pg2 was the only interface interested in 10.1.1.3
990 p_l = self._create_igmpv3_pck(
991 self.pg2, "Block Old Sources", "239.1.1.1", ["10.1.1.3"]
993 self.send(self.pg2, p_l)
995 capture = self.pg0.get_capture(1, timeout=2)
998 [IgmpRecord(IgmpSG("239.1.1.1", ["10.1.1.3"]), "Block Old Sources")],
1000 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
1002 # disable igmp on pg1 (also removes interface from proxy device)
1003 # proxy leave for 10.1.1.2. pg2 is still interested in 10.1.1.1
1004 self.pg_enable_capture(self.pg_interfaces)
1005 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0, IGMP_MODE.ROUTER)
1007 capture = self.pg0.get_capture(1, timeout=1)
1010 [IgmpRecord(IgmpSG("239.1.1.1", ["10.1.1.2"]), "Block Old Sources")],
1012 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
1014 # disable IGMP on pg0 and pg1.
1015 # disabling IGMP on pg0 (proxy device upstream interface)
1016 # removes this proxy device
1017 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
1018 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0, IGMP_MODE.ROUTER)
1019 self.assertFalse(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
1022 if __name__ == "__main__":
1023 unittest.main(testRunner=VppTestRunner)