5 from scapy.layers.l2 import Ether
6 from scapy.layers.inet import IP, IPOption
7 from scapy.contrib.igmpv3 import IGMPv3, IGMPv3gr, IGMPv3mq, IGMPv3mr
9 from framework import VppTestCase, VppTestRunner, running_extended_tests
10 from vpp_igmp import find_igmp_state, IGMP_FILTER, IgmpRecord, IGMP_MODE, \
11 IgmpSG, VppHostState, wait_for_igmp_event
12 from vpp_ip_route import find_mroute, VppIpTable
20 class TestIgmp(VppTestCase):
21 """ IGMP Test Case """
25 super(TestIgmp, cls).setUpClass()
28 def tearDownClass(cls):
29 super(TestIgmp, cls).tearDownClass()
32 super(TestIgmp, self).setUp()
34 self.create_pg_interfaces(range(4))
39 self.ip_table = VppIpTable(self, 1)
40 self.ip_table.add_vpp_config()
42 for pg in self.pg_interfaces[2:]:
44 for pg in self.pg_interfaces:
50 for pg in self.pg_interfaces:
51 self.vapi.igmp_clear_interface(pg.sw_if_index)
55 super(TestIgmp, self).tearDown()
57 def send(self, ti, pkts):
59 self.pg_enable_capture(self.pg_interfaces)
62 def test_igmp_flush(self):
63 """ IGMP Link Up/down and Flush """
66 # FIX THIS. Link down.
69 def test_igmp_enable(self):
70 """ IGMP enable/disable on an interface
72 check for the addition/removal of the IGMP mroutes """
74 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
75 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1, IGMP_MODE.HOST)
77 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
78 self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
80 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1, IGMP_MODE.HOST)
81 self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 1, IGMP_MODE.HOST)
83 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
85 self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
87 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
88 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0, IGMP_MODE.HOST)
89 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0, IGMP_MODE.HOST)
90 self.vapi.igmp_enable_disable(self.pg3.sw_if_index, 0, IGMP_MODE.HOST)
92 self.assertFalse(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
93 self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
94 self.assertFalse(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
96 self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
99 def verify_general_query(self, p):
101 self.assertEqual(len(ip.options), 1)
102 self.assertEqual(ip.options[0].option, 20)
103 self.assertEqual(ip.dst, "224.0.0.1")
104 self.assertEqual(ip.proto, 2)
106 self.assertEqual(igmp.type, 0x11)
107 self.assertEqual(igmp.gaddr, "0.0.0.0")
109 def verify_group_query(self, p, grp, srcs):
111 self.assertEqual(ip.dst, grp)
112 self.assertEqual(ip.proto, 2)
113 self.assertEqual(len(ip.options), 1)
114 self.assertEqual(ip.options[0].option, 20)
115 self.assertEqual(ip.proto, 2)
117 self.assertEqual(igmp.type, 0x11)
118 self.assertEqual(igmp.gaddr, grp)
120 def verify_report(self, rx, records):
122 self.assertEqual(rx[IP].dst, "224.0.0.22")
123 self.assertEqual(len(ip.options), 1)
124 self.assertEqual(ip.options[0].option, 20)
125 self.assertEqual(ip.proto, 2)
126 self.assertEqual(IGMPv3.igmpv3types[rx[IGMPv3].type],
127 "Version 3 Membership Report")
128 self.assertEqual(rx[IGMPv3mr].numgrp, len(records))
130 received = rx[IGMPv3mr].records
132 for ii in range(len(records)):
135 self.assertEqual(IGMPv3gr.igmpv3grtypes[gr.rtype], r.type)
136 self.assertEqual(gr.numsrc, len(r.sg.saddrs))
137 self.assertEqual(gr.maddr, r.sg.gaddr)
138 self.assertEqual(len(gr.srcaddrs), len(r.sg.saddrs))
140 self.assertEqual(sorted(gr.srcaddrs),
143 def add_group(self, itf, sg, n_pkts=2):
144 self.pg_enable_capture(self.pg_interfaces)
147 hs = VppHostState(self,
153 capture = itf.get_capture(n_pkts, timeout=10)
155 # reports are transmitted twice due to default rebostness value=2
156 self.verify_report(capture[0],
157 [IgmpRecord(sg, "Allow New Sources")]),
158 self.verify_report(capture[1],
159 [IgmpRecord(sg, "Allow New Sources")]),
163 def remove_group(self, hs):
164 self.pg_enable_capture(self.pg_interfaces)
166 hs.remove_vpp_config()
168 capture = self.pg0.get_capture(1, timeout=10)
170 self.verify_report(capture[0],
171 [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,
184 # Add one S,G of state and expect a state-change event report
185 # indicating the addition of the S,G
187 h1 = self.add_group(self.pg0, IgmpSG("239.1.1.1", ["1.1.1.1"]))
189 # search for the corresponding state created in VPP
190 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
191 self.assertEqual(len(dump), 1)
192 self.assertTrue(find_igmp_state(dump, self.pg0,
193 "239.1.1.1", "1.1.1.1"))
196 # Send a general query (to the all router's address)
197 # expect VPP to respond with a membership report
199 p_g = (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"))
204 self.send(self.pg0, p_g)
206 capture = self.pg0.get_capture(1, timeout=10)
207 self.verify_report(capture[0],
208 [IgmpRecord(h1.sg, "Mode Is Include")])
211 # Group specific query
213 p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
214 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
215 options=[IPOption(copy_flag=1, optclass="control",
216 option="router_alert")]) /
217 IGMPv3(type="Membership Query", mrcode=100) /
218 IGMPv3mq(gaddr="239.1.1.1"))
220 self.send(self.pg0, p_gs)
222 capture = self.pg0.get_capture(1, timeout=10)
223 self.verify_report(capture[0],
224 [IgmpRecord(h1.sg, "Mode Is Include")])
227 # A group and source specific query, with the source matching
230 p_gs1 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
231 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
232 options=[IPOption(copy_flag=1, optclass="control",
233 option="router_alert")]) /
234 IGMPv3(type="Membership Query", mrcode=100) /
235 IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.1"]))
237 self.send(self.pg0, p_gs1)
239 capture = self.pg0.get_capture(1, timeout=10)
240 self.verify_report(capture[0],
241 [IgmpRecord(h1.sg, "Mode Is Include")])
244 # A group and source specific query, with the source NOT matching
245 # the source VPP has. There should be no response.
247 p_gs2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
248 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
249 options=[IPOption(copy_flag=1, optclass="control",
250 option="router_alert")]) /
251 IGMPv3(type="Membership Query", mrcode=100) /
252 IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.2"]))
254 self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
257 # A group and source specific query, with the multiple sources
258 # one of which matches the source VPP has.
259 # The report should contain only the source VPP has.
261 p_gs3 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
262 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
263 options=[IPOption(copy_flag=1, optclass="control",
264 option="router_alert")]) /
265 IGMPv3(type="Membership Query", mrcode=100) /
266 IGMPv3mq(gaddr="239.1.1.1",
267 srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
269 self.send(self.pg0, p_gs3)
271 capture = self.pg0.get_capture(1, timeout=10)
272 self.verify_report(capture[0],
273 [IgmpRecord(h1.sg, "Mode Is Include")])
276 # Two source and group specific queries in quick succession, the
277 # first does not have VPPs source the second does. then vice-versa
279 self.send(self.pg0, [p_gs2, p_gs1])
280 capture = self.pg0.get_capture(1, timeout=10)
281 self.verify_report(capture[0],
282 [IgmpRecord(h1.sg, "Mode Is Include")])
284 self.send(self.pg0, [p_gs1, p_gs2])
285 capture = self.pg0.get_capture(1, timeout=10)
286 self.verify_report(capture[0],
287 [IgmpRecord(h1.sg, "Mode Is Include")])
290 # remove state, expect the report for the removal
292 self.remove_group(h1)
294 dump = self.vapi.igmp_dump()
295 self.assertFalse(dump)
298 # A group with multiple sources
300 h2 = self.add_group(self.pg0,
302 ["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
304 # search for the corresponding state created in VPP
305 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
306 self.assertEqual(len(dump), 3)
307 for s in h2.sg.saddrs:
308 self.assertTrue(find_igmp_state(dump, self.pg0,
311 # Send a general query (to the all router's address)
312 # expect VPP to respond with a membership report will all sources
314 self.send(self.pg0, p_g)
316 capture = self.pg0.get_capture(1, timeout=10)
317 self.verify_report(capture[0],
318 [IgmpRecord(h2.sg, "Mode Is Include")])
321 # Group and source specific query; some present some not
323 p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
324 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
325 options=[IPOption(copy_flag=1, optclass="control",
326 option="router_alert")]) /
327 IGMPv3(type="Membership Query", mrcode=100) /
328 IGMPv3mq(gaddr="239.1.1.1",
329 srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.4"]))
331 self.send(self.pg0, p_gs)
333 capture = self.pg0.get_capture(1, timeout=10)
334 self.verify_report(capture[0],
336 IgmpSG('239.1.1.1', ["1.1.1.1", "1.1.1.2"]),
340 # add loads more groups
342 h3 = self.add_group(self.pg0,
344 ["2.1.1.1", "2.1.1.2", "2.1.1.3"]))
345 h4 = self.add_group(self.pg0,
347 ["3.1.1.1", "3.1.1.2", "3.1.1.3"]))
348 h5 = self.add_group(self.pg0,
350 ["4.1.1.1", "4.1.1.2", "4.1.1.3"]))
351 h6 = self.add_group(self.pg0,
353 ["5.1.1.1", "5.1.1.2", "5.1.1.3"]))
354 h7 = self.add_group(self.pg0,
356 ["6.1.1.1", "6.1.1.2",
357 "6.1.1.3", "6.1.1.4",
358 "6.1.1.5", "6.1.1.6",
359 "6.1.1.7", "6.1.1.8",
360 "6.1.1.9", "6.1.1.10",
361 "6.1.1.11", "6.1.1.12",
362 "6.1.1.13", "6.1.1.14",
363 "6.1.1.15", "6.1.1.16"]))
367 # the order the groups come in is not important, so what is
368 # checked for is what VPP is sending today.
370 self.send(self.pg0, p_g)
372 capture = self.pg0.get_capture(1, timeout=10)
374 self.verify_report(capture[0],
375 [IgmpRecord(h3.sg, "Mode Is Include"),
376 IgmpRecord(h2.sg, "Mode Is Include"),
377 IgmpRecord(h6.sg, "Mode Is Include"),
378 IgmpRecord(h4.sg, "Mode Is Include"),
379 IgmpRecord(h5.sg, "Mode Is Include"),
380 IgmpRecord(h7.sg, "Mode Is Include")])
383 # modify a group to add and remove some sources
385 h7.sg = IgmpSG("239.1.1.6",
386 ["6.1.1.1", "6.1.1.2",
387 "6.1.1.5", "6.1.1.6",
388 "6.1.1.7", "6.1.1.8",
389 "6.1.1.9", "6.1.1.10",
390 "6.1.1.11", "6.1.1.12",
391 "6.1.1.13", "6.1.1.14",
392 "6.1.1.15", "6.1.1.16",
393 "6.1.1.17", "6.1.1.18"])
395 self.pg_enable_capture(self.pg_interfaces)
399 capture = self.pg0.get_capture(1, timeout=10)
400 self.verify_report(capture[0],
401 [IgmpRecord(IgmpSG("239.1.1.6",
402 ["6.1.1.17", "6.1.1.18"]),
403 "Allow New Sources"),
404 IgmpRecord(IgmpSG("239.1.1.6",
405 ["6.1.1.3", "6.1.1.4"]),
406 "Block Old Sources")])
409 # add an additional groups with many sources so that each group
410 # consumes the link MTU. We should therefore see multiple state
411 # state reports when queried.
413 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [560, 0, 0, 0])
417 src_list.append("10.1.1.%d" % i)
419 h8 = self.add_group(self.pg0,
420 IgmpSG("238.1.1.1", src_list))
421 h9 = self.add_group(self.pg0,
422 IgmpSG("238.1.1.2", src_list))
424 self.send(self.pg0, p_g)
426 capture = self.pg0.get_capture(4, timeout=10)
428 self.verify_report(capture[0],
429 [IgmpRecord(h3.sg, "Mode Is Include"),
430 IgmpRecord(h2.sg, "Mode Is Include"),
431 IgmpRecord(h6.sg, "Mode Is Include"),
432 IgmpRecord(h4.sg, "Mode Is Include"),
433 IgmpRecord(h5.sg, "Mode Is Include")])
434 self.verify_report(capture[1],
435 [IgmpRecord(h8.sg, "Mode Is Include")])
436 self.verify_report(capture[2],
437 [IgmpRecord(h7.sg, "Mode Is Include")])
438 self.verify_report(capture[3],
439 [IgmpRecord(h9.sg, "Mode Is Include")])
442 # drop the MTU further (so a 128 sized group won't fit)
444 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [512, 0, 0, 0])
446 self.pg_enable_capture(self.pg_interfaces)
449 h10 = VppHostState(self,
451 self.pg0.sw_if_index,
452 IgmpSG("238.1.1.3", src_list))
455 capture = self.pg0.get_capture(2, timeout=10)
458 # remove state, expect the report for the removal
459 # the dump should be empty
461 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [600, 0, 0, 0])
462 self.remove_group(h8)
463 self.remove_group(h9)
464 self.remove_group(h2)
465 self.remove_group(h3)
466 self.remove_group(h4)
467 self.remove_group(h5)
468 self.remove_group(h6)
469 self.remove_group(h7)
470 self.remove_group(h10)
472 self.logger.info(self.vapi.cli("sh igmp config"))
473 self.assertFalse(self.vapi.igmp_dump())
477 # ADD STATE ON MORE INTERFACES
480 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
484 def test_igmp_router(self):
485 """ IGMP Router Functions """
488 # Drop reports when not enabled
490 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
491 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
492 options=[IPOption(copy_flag=1, optclass="control",
493 option="router_alert")]) /
494 IGMPv3(type="Version 3 Membership Report") /
496 IGMPv3gr(rtype="Allow New Sources",
497 maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
498 p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
499 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
500 options=[IPOption(copy_flag=1, optclass="control",
501 option="router_alert")]) /
502 IGMPv3(type="Version 3 Membership Report") /
504 IGMPv3gr(rtype="Block Old Sources",
505 maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
507 self.send(self.pg0, p_j)
508 self.assertFalse(self.vapi.igmp_dump())
511 # drop the default timer values so these tests execute in a
512 # reasonable time frame
514 self.vapi.cli("test igmp timers query 1 src 3 leave 1")
517 # enable router functions on the interface
519 self.pg_enable_capture(self.pg_interfaces)
521 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
524 self.vapi.want_igmp_events(1)
527 # wait for router to send general query
530 capture = self.pg0.get_capture(1, timeout=2)
531 self.verify_general_query(capture[0])
532 self.pg_enable_capture(self.pg_interfaces)
536 # re-send the report. VPP should now hold state for the new group
537 # VPP sends a notification that a new group has been joined
539 self.send(self.pg0, p_j)
541 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
542 "239.1.1.1", "10.1.1.1", 1))
543 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
544 "239.1.1.1", "10.1.1.2", 1))
545 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
546 self.assertEqual(len(dump), 2)
547 self.assertTrue(find_igmp_state(dump, self.pg0,
548 "239.1.1.1", "10.1.1.1"))
549 self.assertTrue(find_igmp_state(dump, self.pg0,
550 "239.1.1.1", "10.1.1.2"))
553 # wait for the per-source timer to expire
554 # the state should be reaped
555 # VPP sends a notification that the group has been left
557 self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
558 "239.1.1.1", "10.1.1.1", 0))
559 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
560 "239.1.1.1", "10.1.1.2", 0))
561 self.assertFalse(self.vapi.igmp_dump())
564 # resend the join. wait for two queries and then send a current-state
565 # record to include all sources. this should reset the expiry time
566 # on the sources and thus they will still be present in 2 seconds time.
567 # If the source timer was not refreshed, then the state would have
568 # expired in 3 seconds.
570 self.send(self.pg0, p_j)
571 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
572 "239.1.1.1", "10.1.1.1", 1))
573 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
574 "239.1.1.1", "10.1.1.2", 1))
575 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
576 self.assertEqual(len(dump), 2)
578 capture = self.pg0.get_capture(2, timeout=3)
579 self.verify_general_query(capture[0])
580 self.verify_general_query(capture[1])
582 p_cs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
583 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
584 options=[IPOption(copy_flag=1, optclass="control",
585 option="router_alert")]) /
586 IGMPv3(type="Version 3 Membership Report") /
588 IGMPv3gr(rtype="Mode Is Include",
589 maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
591 self.send(self.pg0, p_cs)
594 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
595 self.assertEqual(len(dump), 2)
596 self.assertTrue(find_igmp_state(dump, self.pg0,
597 "239.1.1.1", "10.1.1.1"))
598 self.assertTrue(find_igmp_state(dump, self.pg0,
599 "239.1.1.1", "10.1.1.2"))
602 # wait for the per-source timer to expire
603 # the state should be reaped
605 self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
606 "239.1.1.1", "10.1.1.1", 0))
607 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
608 "239.1.1.1", "10.1.1.2", 0))
609 self.assertFalse(self.vapi.igmp_dump())
612 # resend the join, then a leave. Router sends a group+source
613 # specific query containing both sources
615 self.send(self.pg0, p_j)
617 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
618 "239.1.1.1", "10.1.1.1", 1))
619 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
620 "239.1.1.1", "10.1.1.2", 1))
621 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
622 self.assertEqual(len(dump), 2)
624 self.send(self.pg0, p_l)
625 capture = self.pg0.get_capture(1, timeout=3)
626 self.verify_group_query(capture[0], "239.1.1.1",
627 ["10.1.1.1", "10.1.1.2"])
630 # the group specific query drops the timeout to leave (=1) seconds
632 self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
633 "239.1.1.1", "10.1.1.1", 0))
634 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
635 "239.1.1.1", "10.1.1.2", 0))
636 self.assertFalse(self.vapi.igmp_dump())
637 self.assertFalse(self.vapi.igmp_dump())
640 # a TO_EX({}) / IN_EX({}) is treated like a (*,G) join
642 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
643 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
644 options=[IPOption(copy_flag=1, optclass="control",
645 option="router_alert")]) /
646 IGMPv3(type="Version 3 Membership Report") /
648 IGMPv3gr(rtype="Change To Exclude Mode", maddr="239.1.1.2"))
650 self.send(self.pg0, p_j)
652 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
653 "239.1.1.2", "0.0.0.0", 1))
655 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
656 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
657 options=[IPOption(copy_flag=1, optclass="control",
658 option="router_alert")]) /
659 IGMPv3(type="Version 3 Membership Report") /
661 IGMPv3gr(rtype="Mode Is Exclude", maddr="239.1.1.3"))
663 self.send(self.pg0, p_j)
665 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
666 "239.1.1.3", "0.0.0.0", 1))
669 # A 'allow sources' for {} should be ignored as it should
672 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
673 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
674 options=[IPOption(copy_flag=1, optclass="control",
675 option="router_alert")]) /
676 IGMPv3(type="Version 3 Membership Report") /
678 IGMPv3gr(rtype="Allow New Sources", maddr="239.1.1.4"))
680 self.send(self.pg0, p_j)
682 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
683 self.assertTrue(find_igmp_state(dump, self.pg0,
684 "239.1.1.2", "0.0.0.0"))
685 self.assertTrue(find_igmp_state(dump, self.pg0,
686 "239.1.1.3", "0.0.0.0"))
687 self.assertFalse(find_igmp_state(dump, self.pg0,
688 "239.1.1.4", "0.0.0.0"))
691 # a TO_IN({}) and IS_IN({}) are treated like a (*,G) leave
693 self.vapi.cli("set logging class igmp level debug")
694 p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
695 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
696 options=[IPOption(copy_flag=1, optclass="control",
697 option="router_alert")]) /
698 IGMPv3(type="Version 3 Membership Report") /
700 IGMPv3gr(rtype="Change To Include Mode", maddr="239.1.1.2"))
702 self.send(self.pg0, p_l)
703 self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
704 "239.1.1.2", "0.0.0.0", 0))
706 p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
707 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
708 options=[IPOption(copy_flag=1, optclass="control",
709 option="router_alert")]) /
710 IGMPv3(type="Version 3 Membership Report") /
712 IGMPv3gr(rtype="Mode Is Include", maddr="239.1.1.3"))
714 self.send(self.pg0, p_l)
716 self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
717 "239.1.1.3", "0.0.0.0", 0))
718 self.assertFalse(self.vapi.igmp_dump(self.pg0.sw_if_index))
721 # disable router config
723 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
727 def _create_igmpv3_pck(self, itf, rtype, maddr, srcaddrs):
728 p = (Ether(dst=itf.local_mac, src=itf.remote_mac) /
729 IP(src=itf.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
730 options=[IPOption(copy_flag=1, optclass="control",
731 option="router_alert")]) /
732 IGMPv3(type="Version 3 Membership Report") /
734 IGMPv3gr(rtype=rtype,
735 maddr=maddr, srcaddrs=srcaddrs))
738 def test_igmp_proxy_device(self):
739 """ IGMP proxy device """
740 self.pg2.admin_down()
741 self.pg2.unconfig_ip4()
742 self.pg2.set_table_ip4(0)
743 self.pg2.config_ip4()
746 self.vapi.cli('test igmp timers query 10 src 3 leave 1')
749 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
750 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1,
752 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1,
755 # create IGMP proxy device
756 self.vapi.igmp_proxy_device_add_del(0, self.pg0.sw_if_index, 1)
757 self.vapi.igmp_proxy_device_add_del_interface(0,
758 self.pg1.sw_if_index, 1)
759 self.vapi.igmp_proxy_device_add_del_interface(0,
760 self.pg2.sw_if_index, 1)
762 # send join on pg1. join should be proxied by pg0
763 p_j = self._create_igmpv3_pck(self.pg1, "Allow New Sources",
764 "239.1.1.1", ["10.1.1.1", "10.1.1.2"])
765 self.send(self.pg1, p_j)
767 capture = self.pg0.get_capture(1, timeout=1)
768 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
769 ["10.1.1.1", "10.1.1.2"]), "Allow New Sources")])
770 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
772 # send join on pg2. join should be proxied by pg0.
773 # the group should contain only 10.1.1.3 as
774 # 10.1.1.1 was already reported
775 p_j = self._create_igmpv3_pck(self.pg2, "Allow New Sources",
776 "239.1.1.1", ["10.1.1.1", "10.1.1.3"])
777 self.send(self.pg2, p_j)
779 capture = self.pg0.get_capture(1, timeout=1)
780 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
781 ["10.1.1.3"]), "Allow New Sources")])
782 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
784 # send leave on pg2. leave for 10.1.1.3 should be proxyed
785 # as pg2 was the only interface interested in 10.1.1.3
786 p_l = self._create_igmpv3_pck(self.pg2, "Block Old Sources",
787 "239.1.1.1", ["10.1.1.3"])
788 self.send(self.pg2, p_l)
790 capture = self.pg0.get_capture(1, timeout=2)
791 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
792 ["10.1.1.3"]), "Block Old Sources")])
793 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
795 # disable igmp on pg1 (also removes interface from proxy device)
796 # proxy leave for 10.1.1.2. pg2 is still interested in 10.1.1.1
797 self.pg_enable_capture(self.pg_interfaces)
798 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0,
801 capture = self.pg0.get_capture(1, timeout=1)
802 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
803 ["10.1.1.2"]), "Block Old Sources")])
804 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
806 # disable IGMP on pg0 and pg1.
807 # disabling IGMP on pg0 (proxy device upstream interface)
808 # removes this proxy device
809 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
810 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0,
812 self.assertFalse(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
815 if __name__ == '__main__':
816 unittest.main(testRunner=VppTestRunner)