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 VppTestCase, VppTestRunner, running_extended_tests
11 from vpp_igmp import find_igmp_state, IGMP_FILTER, IgmpRecord, IGMP_MODE, \
12 IgmpSG, VppHostState, wait_for_igmp_event
13 from vpp_ip_route import find_mroute, VppIpTable
21 class TestIgmp(VppTestCase):
22 """ IGMP Test Case """
26 super(TestIgmp, cls).setUpClass()
29 def tearDownClass(cls):
30 super(TestIgmp, cls).tearDownClass()
33 super(TestIgmp, self).setUp()
35 self.create_pg_interfaces(range(4))
40 self.ip_table = VppIpTable(self, 1)
41 self.ip_table.add_vpp_config()
43 for pg in self.pg_interfaces[2:]:
45 for pg in self.pg_interfaces:
51 for pg in self.pg_interfaces:
52 self.vapi.igmp_clear_interface(pg.sw_if_index)
56 super(TestIgmp, self).tearDown()
58 def send(self, ti, pkts):
60 self.pg_enable_capture(self.pg_interfaces)
63 def test_igmp_flush(self):
64 """ IGMP Link Up/down and Flush """
67 # FIX THIS. Link down.
70 def test_igmp_enable(self):
71 """ IGMP enable/disable on an interface
73 check for the addition/removal of the IGMP mroutes """
75 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
76 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1, IGMP_MODE.HOST)
78 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
79 self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
81 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1, IGMP_MODE.HOST)
82 self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 1, IGMP_MODE.HOST)
84 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
86 self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
88 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
89 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0, IGMP_MODE.HOST)
90 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0, IGMP_MODE.HOST)
91 self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 0, IGMP_MODE.HOST)
93 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
94 self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
95 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
97 self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
100 def verify_general_query(self, p):
102 self.assertEqual(len(ip.options), 1)
103 self.assertEqual(ip.options[0].option, 20)
104 self.assertEqual(ip.dst, "224.0.0.1")
105 self.assertEqual(ip.proto, 2)
107 self.assertEqual(igmp.type, 0x11)
108 self.assertEqual(igmp.gaddr, "0.0.0.0")
110 def verify_group_query(self, p, grp, srcs):
112 self.assertEqual(ip.dst, grp)
113 self.assertEqual(ip.proto, 2)
114 self.assertEqual(len(ip.options), 1)
115 self.assertEqual(ip.options[0].option, 20)
116 self.assertEqual(ip.proto, 2)
118 self.assertEqual(igmp.type, 0x11)
119 self.assertEqual(igmp.gaddr, grp)
121 def verify_report(self, rx, records):
123 self.assertEqual(rx[IP].dst, "224.0.0.22")
124 self.assertEqual(len(ip.options), 1)
125 self.assertEqual(ip.options[0].option, 20)
126 self.assertEqual(ip.proto, 2)
127 self.assertEqual(IGMPv3.igmpv3types[rx[IGMPv3].type],
128 "Version 3 Membership Report")
129 self.assertEqual(rx[IGMPv3mr].numgrp, len(records))
131 received = rx[IGMPv3mr].records
133 for ii in range(len(records)):
136 self.assertEqual(IGMPv3gr.igmpv3grtypes[gr.rtype], r.type)
137 self.assertEqual(gr.numsrc, len(r.sg.saddrs))
138 self.assertEqual(gr.maddr, r.sg.gaddr)
139 self.assertEqual(len(gr.srcaddrs), len(r.sg.saddrs))
141 self.assertEqual(sorted(gr.srcaddrs),
144 def add_group(self, itf, sg, n_pkts=2):
145 self.pg_enable_capture(self.pg_interfaces)
148 hs = VppHostState(self,
154 capture = itf.get_capture(n_pkts, timeout=10)
156 # reports are transmitted twice due to default rebostness value=2
157 self.verify_report(capture[0],
158 [IgmpRecord(sg, "Allow New Sources")]),
159 self.verify_report(capture[1],
160 [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],
172 [IgmpRecord(hs.sg, "Block Old Sources")])
174 def test_igmp_host(self):
175 """ IGMP Host functions """
178 # Enable interface for host functions
180 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
185 # Add one S,G of state and expect a state-change event report
186 # indicating the addition of the S,G
188 h1 = self.add_group(self.pg0, IgmpSG("239.1.1.1", ["1.1.1.1"]))
190 # search for the corresponding state created in VPP
191 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
192 self.assertEqual(len(dump), 1)
193 self.assertTrue(find_igmp_state(dump, self.pg0,
194 "239.1.1.1", "1.1.1.1"))
197 # Send a general query (to the all router's address)
198 # expect VPP to respond with a membership report.
199 # Pad the query with 0 - some devices in the big wild
200 # internet are prone to this.
202 p_g = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
203 IP(src=self.pg0.remote_ip4, dst='224.0.0.1', tos=0xc0) /
204 IGMPv3(type="Membership Query", mrcode=100) /
205 IGMPv3mq(gaddr="0.0.0.0") /
208 self.send(self.pg0, p_g)
210 capture = self.pg0.get_capture(1, timeout=10)
211 self.verify_report(capture[0],
212 [IgmpRecord(h1.sg, "Mode Is Include")])
215 # Group specific query
217 p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
218 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
219 options=[IPOption(copy_flag=1, optclass="control",
220 option="router_alert")]) /
221 IGMPv3(type="Membership Query", mrcode=100) /
222 IGMPv3mq(gaddr="239.1.1.1"))
224 self.send(self.pg0, p_gs)
226 capture = self.pg0.get_capture(1, timeout=10)
227 self.verify_report(capture[0],
228 [IgmpRecord(h1.sg, "Mode Is Include")])
231 # A group and source specific query, with the source matching
234 p_gs1 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
235 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
236 options=[IPOption(copy_flag=1, optclass="control",
237 option="router_alert")]) /
238 IGMPv3(type="Membership Query", mrcode=100) /
239 IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.1"]))
241 self.send(self.pg0, p_gs1)
243 capture = self.pg0.get_capture(1, timeout=10)
244 self.verify_report(capture[0],
245 [IgmpRecord(h1.sg, "Mode Is Include")])
248 # A group and source specific query that reports more sources
249 # than the packet actually has.
251 p_gs2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
252 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
253 options=[IPOption(copy_flag=1, optclass="control",
254 option="router_alert")]) /
255 IGMPv3(type="Membership Query", mrcode=100) /
256 IGMPv3mq(gaddr="239.1.1.1", numsrc=4, srcaddrs=["1.1.1.1"]))
258 self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
261 # A group and source specific query, with the source NOT matching
262 # the source VPP has. There should be no response.
264 p_gs2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
265 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
266 options=[IPOption(copy_flag=1, optclass="control",
267 option="router_alert")]) /
268 IGMPv3(type="Membership Query", mrcode=100) /
269 IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.2"]))
271 self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
274 # A group and source specific query, with the multiple sources
275 # one of which matches the source VPP has.
276 # The report should contain only the source VPP has.
278 p_gs3 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
279 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
280 options=[IPOption(copy_flag=1, optclass="control",
281 option="router_alert")]) /
282 IGMPv3(type="Membership Query", mrcode=100) /
283 IGMPv3mq(gaddr="239.1.1.1",
284 srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
286 self.send(self.pg0, p_gs3)
288 capture = self.pg0.get_capture(1, timeout=10)
289 self.verify_report(capture[0],
290 [IgmpRecord(h1.sg, "Mode Is Include")])
293 # Two source and group specific queries in quick succession, the
294 # first does not have VPPs source the second does. then vice-versa
296 self.send(self.pg0, [p_gs2, p_gs1])
297 capture = self.pg0.get_capture(1, timeout=10)
298 self.verify_report(capture[0],
299 [IgmpRecord(h1.sg, "Mode Is Include")])
301 self.send(self.pg0, [p_gs1, p_gs2])
302 capture = self.pg0.get_capture(1, timeout=10)
303 self.verify_report(capture[0],
304 [IgmpRecord(h1.sg, "Mode Is Include")])
307 # remove state, expect the report for the removal
309 self.remove_group(h1)
311 dump = self.vapi.igmp_dump()
312 self.assertFalse(dump)
315 # A group with multiple sources
317 h2 = self.add_group(self.pg0,
319 ["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
321 # search for the corresponding state created in VPP
322 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
323 self.assertEqual(len(dump), 3)
324 for s in h2.sg.saddrs:
325 self.assertTrue(find_igmp_state(dump, self.pg0,
328 # Send a general query (to the all router's address)
329 # expect VPP to respond with a membership report will all sources
331 self.send(self.pg0, p_g)
333 capture = self.pg0.get_capture(1, timeout=10)
334 self.verify_report(capture[0],
335 [IgmpRecord(h2.sg, "Mode Is Include")])
338 # Group and source specific query; some present some not
340 p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
341 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
342 options=[IPOption(copy_flag=1, optclass="control",
343 option="router_alert")]) /
344 IGMPv3(type="Membership Query", mrcode=100) /
345 IGMPv3mq(gaddr="239.1.1.1",
346 srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.4"]))
348 self.send(self.pg0, p_gs)
350 capture = self.pg0.get_capture(1, timeout=10)
351 self.verify_report(capture[0],
353 IgmpSG('239.1.1.1', ["1.1.1.1", "1.1.1.2"]),
357 # add loads more groups
359 h3 = self.add_group(self.pg0,
361 ["2.1.1.1", "2.1.1.2", "2.1.1.3"]))
362 h4 = self.add_group(self.pg0,
364 ["3.1.1.1", "3.1.1.2", "3.1.1.3"]))
365 h5 = self.add_group(self.pg0,
367 ["4.1.1.1", "4.1.1.2", "4.1.1.3"]))
368 h6 = self.add_group(self.pg0,
370 ["5.1.1.1", "5.1.1.2", "5.1.1.3"]))
371 h7 = self.add_group(self.pg0,
373 ["6.1.1.1", "6.1.1.2",
374 "6.1.1.3", "6.1.1.4",
375 "6.1.1.5", "6.1.1.6",
376 "6.1.1.7", "6.1.1.8",
377 "6.1.1.9", "6.1.1.10",
378 "6.1.1.11", "6.1.1.12",
379 "6.1.1.13", "6.1.1.14",
380 "6.1.1.15", "6.1.1.16"]))
384 # the order the groups come in is not important, so what is
385 # checked for is what VPP is sending today.
387 self.send(self.pg0, p_g)
389 capture = self.pg0.get_capture(1, timeout=10)
391 self.verify_report(capture[0],
392 [IgmpRecord(h3.sg, "Mode Is Include"),
393 IgmpRecord(h2.sg, "Mode Is Include"),
394 IgmpRecord(h6.sg, "Mode Is Include"),
395 IgmpRecord(h4.sg, "Mode Is Include"),
396 IgmpRecord(h5.sg, "Mode Is Include"),
397 IgmpRecord(h7.sg, "Mode Is Include")])
400 # modify a group to add and remove some sources
402 h7.sg = IgmpSG("239.1.1.6",
403 ["6.1.1.1", "6.1.1.2",
404 "6.1.1.5", "6.1.1.6",
405 "6.1.1.7", "6.1.1.8",
406 "6.1.1.9", "6.1.1.10",
407 "6.1.1.11", "6.1.1.12",
408 "6.1.1.13", "6.1.1.14",
409 "6.1.1.15", "6.1.1.16",
410 "6.1.1.17", "6.1.1.18"])
412 self.pg_enable_capture(self.pg_interfaces)
416 capture = self.pg0.get_capture(1, timeout=10)
417 self.verify_report(capture[0],
418 [IgmpRecord(IgmpSG("239.1.1.6",
419 ["6.1.1.17", "6.1.1.18"]),
420 "Allow New Sources"),
421 IgmpRecord(IgmpSG("239.1.1.6",
422 ["6.1.1.3", "6.1.1.4"]),
423 "Block Old Sources")])
426 # add an additional groups with many sources so that each group
427 # consumes the link MTU. We should therefore see multiple state
428 # state reports when queried.
430 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [560, 0, 0, 0])
434 src_list.append("10.1.1.%d" % i)
436 h8 = self.add_group(self.pg0,
437 IgmpSG("238.1.1.1", src_list))
438 h9 = self.add_group(self.pg0,
439 IgmpSG("238.1.1.2", src_list))
441 self.send(self.pg0, p_g)
443 capture = self.pg0.get_capture(4, timeout=10)
445 self.verify_report(capture[0],
446 [IgmpRecord(h3.sg, "Mode Is Include"),
447 IgmpRecord(h2.sg, "Mode Is Include"),
448 IgmpRecord(h6.sg, "Mode Is Include"),
449 IgmpRecord(h4.sg, "Mode Is Include"),
450 IgmpRecord(h5.sg, "Mode Is Include")])
451 self.verify_report(capture[1],
452 [IgmpRecord(h8.sg, "Mode Is Include")])
453 self.verify_report(capture[2],
454 [IgmpRecord(h7.sg, "Mode Is Include")])
455 self.verify_report(capture[3],
456 [IgmpRecord(h9.sg, "Mode Is Include")])
459 # drop the MTU further (so a 128 sized group won't fit)
461 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [512, 0, 0, 0])
463 self.pg_enable_capture(self.pg_interfaces)
466 h10 = VppHostState(self,
468 self.pg0.sw_if_index,
469 IgmpSG("238.1.1.3", src_list))
472 capture = self.pg0.get_capture(2, timeout=10)
473 # wait for a little bit
477 # remove state, expect the report for the removal
478 # the dump should be empty
480 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [600, 0, 0, 0])
481 self.remove_group(h8)
482 self.remove_group(h9)
483 self.remove_group(h2)
484 self.remove_group(h3)
485 self.remove_group(h4)
486 self.remove_group(h5)
487 self.remove_group(h6)
488 self.remove_group(h7)
489 self.remove_group(h10)
491 self.logger.info(self.vapi.cli("sh igmp config"))
492 self.assertFalse(self.vapi.igmp_dump())
496 # ADD STATE ON MORE INTERFACES
499 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
503 def test_igmp_router(self):
504 """ IGMP Router Functions """
507 # Drop reports when not enabled
509 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
510 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
511 options=[IPOption(copy_flag=1, optclass="control",
512 option="router_alert")]) /
513 IGMPv3(type="Version 3 Membership Report") /
515 IGMPv3gr(rtype="Allow New Sources",
516 maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
517 p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
518 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
519 options=[IPOption(copy_flag=1, optclass="control",
520 option="router_alert")]) /
521 IGMPv3(type="Version 3 Membership Report") /
523 IGMPv3gr(rtype="Block Old Sources",
524 maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
526 self.send(self.pg0, p_j)
527 self.assertFalse(self.vapi.igmp_dump())
530 # drop the default timer values so these tests execute in a
531 # reasonable time frame
533 self.vapi.cli("test igmp timers query 1 src 3 leave 1")
536 # enable router functions on the interface
538 self.pg_enable_capture(self.pg_interfaces)
540 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
543 self.vapi.want_igmp_events(1)
546 # wait for router to send general query
549 capture = self.pg0.get_capture(1, timeout=2)
550 self.verify_general_query(capture[0])
551 self.pg_enable_capture(self.pg_interfaces)
555 # re-send the report. VPP should now hold state for the new group
556 # VPP sends a notification that a new group has been joined
558 self.send(self.pg0, p_j)
560 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
561 "239.1.1.1", "10.1.1.1", 1))
562 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
563 "239.1.1.1", "10.1.1.2", 1))
564 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
565 self.assertEqual(len(dump), 2)
566 self.assertTrue(find_igmp_state(dump, self.pg0,
567 "239.1.1.1", "10.1.1.1"))
568 self.assertTrue(find_igmp_state(dump, self.pg0,
569 "239.1.1.1", "10.1.1.2"))
572 # wait for the per-source timer to expire
573 # the state should be reaped
574 # VPP sends a notification that the group has been left
576 self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
577 "239.1.1.1", "10.1.1.1", 0))
578 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
579 "239.1.1.1", "10.1.1.2", 0))
580 self.assertFalse(self.vapi.igmp_dump())
583 # resend the join. wait for two queries and then send a current-state
584 # record to include all sources. this should reset the expiry time
585 # on the sources and thus they will still be present in 2 seconds time.
586 # If the source timer was not refreshed, then the state would have
587 # expired in 3 seconds.
589 self.send(self.pg0, p_j)
590 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
591 "239.1.1.1", "10.1.1.1", 1))
592 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
593 "239.1.1.1", "10.1.1.2", 1))
594 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
595 self.assertEqual(len(dump), 2)
597 capture = self.pg0.get_capture(2, timeout=3)
598 self.verify_general_query(capture[0])
599 self.verify_general_query(capture[1])
601 p_cs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
602 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
603 options=[IPOption(copy_flag=1, optclass="control",
604 option="router_alert")]) /
605 IGMPv3(type="Version 3 Membership Report") /
607 IGMPv3gr(rtype="Mode Is Include",
608 maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
610 self.send(self.pg0, p_cs)
613 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
614 self.assertEqual(len(dump), 2)
615 self.assertTrue(find_igmp_state(dump, self.pg0,
616 "239.1.1.1", "10.1.1.1"))
617 self.assertTrue(find_igmp_state(dump, self.pg0,
618 "239.1.1.1", "10.1.1.2"))
621 # wait for the per-source timer to expire
622 # the state should be reaped
624 self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
625 "239.1.1.1", "10.1.1.1", 0))
626 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
627 "239.1.1.1", "10.1.1.2", 0))
628 self.assertFalse(self.vapi.igmp_dump())
631 # resend the join, then a leave. Router sends a group+source
632 # specific query containing both sources
634 self.send(self.pg0, p_j)
636 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
637 "239.1.1.1", "10.1.1.1", 1))
638 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
639 "239.1.1.1", "10.1.1.2", 1))
640 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
641 self.assertEqual(len(dump), 2)
643 self.send(self.pg0, p_l)
644 capture = self.pg0.get_capture(1, timeout=3)
645 self.verify_group_query(capture[0], "239.1.1.1",
646 ["10.1.1.1", "10.1.1.2"])
649 # the group specific query drops the timeout to leave (=1) seconds
651 self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
652 "239.1.1.1", "10.1.1.1", 0))
653 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
654 "239.1.1.1", "10.1.1.2", 0))
655 self.assertFalse(self.vapi.igmp_dump())
656 self.assertFalse(self.vapi.igmp_dump())
659 # a TO_EX({}) / IN_EX({}) is treated like a (*,G) join
661 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
662 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
663 options=[IPOption(copy_flag=1, optclass="control",
664 option="router_alert")]) /
665 IGMPv3(type="Version 3 Membership Report") /
667 IGMPv3gr(rtype="Change To Exclude Mode", maddr="239.1.1.2"))
669 self.send(self.pg0, p_j)
671 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
672 "239.1.1.2", "0.0.0.0", 1))
674 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
675 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
676 options=[IPOption(copy_flag=1, optclass="control",
677 option="router_alert")]) /
678 IGMPv3(type="Version 3 Membership Report") /
680 IGMPv3gr(rtype="Mode Is Exclude", maddr="239.1.1.3"))
682 self.send(self.pg0, p_j)
684 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
685 "239.1.1.3", "0.0.0.0", 1))
688 # A 'allow sources' for {} should be ignored as it should
691 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
692 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
693 options=[IPOption(copy_flag=1, optclass="control",
694 option="router_alert")]) /
695 IGMPv3(type="Version 3 Membership Report") /
697 IGMPv3gr(rtype="Allow New Sources", maddr="239.1.1.4"))
699 self.send(self.pg0, p_j)
701 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
702 self.assertTrue(find_igmp_state(dump, self.pg0,
703 "239.1.1.2", "0.0.0.0"))
704 self.assertTrue(find_igmp_state(dump, self.pg0,
705 "239.1.1.3", "0.0.0.0"))
706 self.assertFalse(find_igmp_state(dump, self.pg0,
707 "239.1.1.4", "0.0.0.0"))
710 # a TO_IN({}) and IS_IN({}) are treated like a (*,G) leave
712 self.vapi.cli("set logging class igmp level debug")
713 p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
714 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
715 options=[IPOption(copy_flag=1, optclass="control",
716 option="router_alert")]) /
717 IGMPv3(type="Version 3 Membership Report") /
719 IGMPv3gr(rtype="Change To Include Mode", maddr="239.1.1.2"))
721 self.send(self.pg0, p_l)
722 self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
723 "239.1.1.2", "0.0.0.0", 0))
725 p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
726 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
727 options=[IPOption(copy_flag=1, optclass="control",
728 option="router_alert")]) /
729 IGMPv3(type="Version 3 Membership Report") /
731 IGMPv3gr(rtype="Mode Is Include", maddr="239.1.1.3"))
733 self.send(self.pg0, p_l)
735 self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
736 "239.1.1.3", "0.0.0.0", 0))
737 self.assertFalse(self.vapi.igmp_dump(self.pg0.sw_if_index))
740 # disable router config
742 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
746 def _create_igmpv3_pck(self, itf, rtype, maddr, srcaddrs):
747 p = (Ether(dst=itf.local_mac, src=itf.remote_mac) /
748 IP(src=itf.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
749 options=[IPOption(copy_flag=1, optclass="control",
750 option="router_alert")]) /
751 IGMPv3(type="Version 3 Membership Report") /
753 IGMPv3gr(rtype=rtype,
754 maddr=maddr, srcaddrs=srcaddrs))
757 def test_igmp_proxy_device(self):
758 """ IGMP proxy device """
759 self.pg2.admin_down()
760 self.pg2.unconfig_ip4()
761 self.pg2.set_table_ip4(0)
762 self.pg2.config_ip4()
765 self.vapi.cli('test igmp timers query 10 src 3 leave 1')
768 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
769 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1,
771 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1,
774 # create IGMP proxy device
775 self.vapi.igmp_proxy_device_add_del(0, self.pg0.sw_if_index, 1)
776 self.vapi.igmp_proxy_device_add_del_interface(0,
777 self.pg1.sw_if_index, 1)
778 self.vapi.igmp_proxy_device_add_del_interface(0,
779 self.pg2.sw_if_index, 1)
781 # send join on pg1. join should be proxied by pg0
782 p_j = self._create_igmpv3_pck(self.pg1, "Allow New Sources",
783 "239.1.1.1", ["10.1.1.1", "10.1.1.2"])
784 self.send(self.pg1, p_j)
786 capture = self.pg0.get_capture(1, timeout=1)
787 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
788 ["10.1.1.1", "10.1.1.2"]), "Allow New Sources")])
789 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
791 # send join on pg2. join should be proxied by pg0.
792 # the group should contain only 10.1.1.3 as
793 # 10.1.1.1 was already reported
794 p_j = self._create_igmpv3_pck(self.pg2, "Allow New Sources",
795 "239.1.1.1", ["10.1.1.1", "10.1.1.3"])
796 self.send(self.pg2, p_j)
798 capture = self.pg0.get_capture(1, timeout=1)
799 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
800 ["10.1.1.3"]), "Allow New Sources")])
801 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
803 # send leave on pg2. leave for 10.1.1.3 should be proxyed
804 # as pg2 was the only interface interested in 10.1.1.3
805 p_l = self._create_igmpv3_pck(self.pg2, "Block Old Sources",
806 "239.1.1.1", ["10.1.1.3"])
807 self.send(self.pg2, p_l)
809 capture = self.pg0.get_capture(1, timeout=2)
810 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
811 ["10.1.1.3"]), "Block Old Sources")])
812 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
814 # disable igmp on pg1 (also removes interface from proxy device)
815 # proxy leave for 10.1.1.2. pg2 is still interested in 10.1.1.1
816 self.pg_enable_capture(self.pg_interfaces)
817 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0,
820 capture = self.pg0.get_capture(1, timeout=1)
821 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
822 ["10.1.1.2"]), "Block Old Sources")])
823 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
825 # disable IGMP on pg0 and pg1.
826 # disabling IGMP on pg0 (proxy device upstream interface)
827 # removes this proxy device
828 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
829 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0,
831 self.assertFalse(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
834 if __name__ == '__main__':
835 unittest.main(testRunner=VppTestRunner)