tests: python3 use byte strings in raw()
[vpp.git] / src / plugins / igmp / test / test_igmp.py
1 #!/usr/bin/env python3
2
3 import unittest
4
5 from scapy.layers.l2 import Ether, Raw
6 from scapy.layers.inet import IP, IPOption
7 from scapy.contrib.igmpv3 import IGMPv3, IGMPv3gr, IGMPv3mq, IGMPv3mr
8
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
13
14
15 class IgmpMode:
16     HOST = 1
17     ROUTER = 0
18
19
20 class TestIgmp(VppTestCase):
21     """ IGMP Test Case """
22
23     @classmethod
24     def setUpClass(cls):
25         super(TestIgmp, cls).setUpClass()
26
27     @classmethod
28     def tearDownClass(cls):
29         super(TestIgmp, cls).tearDownClass()
30
31     def setUp(self):
32         super(TestIgmp, self).setUp()
33
34         self.create_pg_interfaces(range(4))
35         self.sg_list = []
36         self.config_list = []
37
38         self.ip_addr = []
39         self.ip_table = VppIpTable(self, 1)
40         self.ip_table.add_vpp_config()
41
42         for pg in self.pg_interfaces[2:]:
43             pg.set_table_ip4(1)
44         for pg in self.pg_interfaces:
45             pg.admin_up()
46             pg.config_ip4()
47             pg.resolve_arp()
48
49     def tearDown(self):
50         for pg in self.pg_interfaces:
51             self.vapi.igmp_clear_interface(pg.sw_if_index)
52             pg.unconfig_ip4()
53             pg.set_table_ip4(0)
54             pg.admin_down()
55         super(TestIgmp, self).tearDown()
56
57     def send(self, ti, pkts):
58         ti.add_stream(pkts)
59         self.pg_enable_capture(self.pg_interfaces)
60         self.pg_start()
61
62     def test_igmp_flush(self):
63         """ IGMP Link Up/down and Flush """
64
65         #
66         # FIX THIS. Link down.
67         #
68
69     def test_igmp_enable(self):
70         """ IGMP enable/disable on an interface
71
72         check for the addition/removal of the IGMP mroutes """
73
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)
76
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))
79
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)
82
83         self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
84                                     table_id=1))
85         self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
86                                     table_id=1))
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)
91
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,
95                                      table_id=1))
96         self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
97                                      table_id=1))
98
99     def verify_general_query(self, p):
100         ip = p[IP]
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)
105         igmp = p[IGMPv3]
106         self.assertEqual(igmp.type, 0x11)
107         self.assertEqual(igmp.gaddr, "0.0.0.0")
108
109     def verify_group_query(self, p, grp, srcs):
110         ip = p[IP]
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)
116         igmp = p[IGMPv3]
117         self.assertEqual(igmp.type, 0x11)
118         self.assertEqual(igmp.gaddr, grp)
119
120     def verify_report(self, rx, records):
121         ip = rx[IP]
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))
129
130         received = rx[IGMPv3mr].records
131
132         for ii in range(len(records)):
133             gr = received[ii]
134             r = records[ii]
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))
139
140             self.assertEqual(sorted(gr.srcaddrs),
141                              sorted(r.sg.saddrs))
142
143     def add_group(self, itf, sg, n_pkts=2):
144         self.pg_enable_capture(self.pg_interfaces)
145         self.pg_start()
146
147         hs = VppHostState(self,
148                           IGMP_FILTER.INCLUDE,
149                           itf.sw_if_index,
150                           sg)
151         hs.add_vpp_config()
152
153         capture = itf.get_capture(n_pkts, timeout=10)
154
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")]),
160
161         return hs
162
163     def remove_group(self, hs):
164         self.pg_enable_capture(self.pg_interfaces)
165         self.pg_start()
166         hs.remove_vpp_config()
167
168         capture = self.pg0.get_capture(1, timeout=10)
169
170         self.verify_report(capture[0],
171                            [IgmpRecord(hs.sg, "Block Old Sources")])
172
173     def test_igmp_host(self):
174         """ IGMP Host functions """
175
176         #
177         # Enable interface for host functions
178         #
179         self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
180                                       1,
181                                       IGMP_MODE.HOST)
182
183         #
184         # Add one S,G of state and expect a state-change event report
185         # indicating the addition of the S,G
186         #
187         h1 = self.add_group(self.pg0, IgmpSG("239.1.1.1", ["1.1.1.1"]))
188
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"))
194
195         #
196         # Send a general query (to the all router's address)
197         # expect VPP to respond with a membership report.
198         # Pad the query with 0 - some devices in the big wild
199         # internet are prone to this.
200         #
201         p_g = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
202                IP(src=self.pg0.remote_ip4, dst='224.0.0.1', tos=0xc0) /
203                IGMPv3(type="Membership Query", mrcode=100) /
204                IGMPv3mq(gaddr="0.0.0.0") /
205                Raw(b'\x00' * 10))
206
207         self.send(self.pg0, p_g)
208
209         capture = self.pg0.get_capture(1, timeout=10)
210         self.verify_report(capture[0],
211                            [IgmpRecord(h1.sg, "Mode Is Include")])
212
213         #
214         # Group specific query
215         #
216         p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
217                 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
218                    options=[IPOption(copy_flag=1, optclass="control",
219                                      option="router_alert")]) /
220                 IGMPv3(type="Membership Query", mrcode=100) /
221                 IGMPv3mq(gaddr="239.1.1.1"))
222
223         self.send(self.pg0, p_gs)
224
225         capture = self.pg0.get_capture(1, timeout=10)
226         self.verify_report(capture[0],
227                            [IgmpRecord(h1.sg, "Mode Is Include")])
228
229         #
230         # A group and source specific query, with the source matching
231         # the source VPP has
232         #
233         p_gs1 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
234                  IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
235                     options=[IPOption(copy_flag=1, optclass="control",
236                                       option="router_alert")]) /
237                  IGMPv3(type="Membership Query", mrcode=100) /
238                  IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.1"]))
239
240         self.send(self.pg0, p_gs1)
241
242         capture = self.pg0.get_capture(1, timeout=10)
243         self.verify_report(capture[0],
244                            [IgmpRecord(h1.sg, "Mode Is Include")])
245
246         #
247         # A group and source specific query that reports more sources
248         # than the packet actually has.
249         #
250         p_gs2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
251                  IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
252                     options=[IPOption(copy_flag=1, optclass="control",
253                                       option="router_alert")]) /
254                  IGMPv3(type="Membership Query", mrcode=100) /
255                  IGMPv3mq(gaddr="239.1.1.1", numsrc=4, srcaddrs=["1.1.1.1"]))
256
257         self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
258
259         #
260         # A group and source specific query, with the source NOT matching
261         # the source VPP has. There should be no response.
262         #
263         p_gs2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
264                  IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
265                     options=[IPOption(copy_flag=1, optclass="control",
266                                       option="router_alert")]) /
267                  IGMPv3(type="Membership Query", mrcode=100) /
268                  IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.2"]))
269
270         self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
271
272         #
273         # A group and source specific query, with the multiple sources
274         # one of which matches the source VPP has.
275         # The report should contain only the source VPP has.
276         #
277         p_gs3 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
278                  IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
279                     options=[IPOption(copy_flag=1, optclass="control",
280                                       option="router_alert")]) /
281                  IGMPv3(type="Membership Query", mrcode=100) /
282                  IGMPv3mq(gaddr="239.1.1.1",
283                           srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
284
285         self.send(self.pg0, p_gs3)
286
287         capture = self.pg0.get_capture(1, timeout=10)
288         self.verify_report(capture[0],
289                            [IgmpRecord(h1.sg, "Mode Is Include")])
290
291         #
292         # Two source and group specific queries in quick succession, the
293         # first does not have VPPs source the second does. then vice-versa
294         #
295         self.send(self.pg0, [p_gs2, p_gs1])
296         capture = self.pg0.get_capture(1, timeout=10)
297         self.verify_report(capture[0],
298                            [IgmpRecord(h1.sg, "Mode Is Include")])
299
300         self.send(self.pg0, [p_gs1, p_gs2])
301         capture = self.pg0.get_capture(1, timeout=10)
302         self.verify_report(capture[0],
303                            [IgmpRecord(h1.sg, "Mode Is Include")])
304
305         #
306         # remove state, expect the report for the removal
307         #
308         self.remove_group(h1)
309
310         dump = self.vapi.igmp_dump()
311         self.assertFalse(dump)
312
313         #
314         # A group with multiple sources
315         #
316         h2 = self.add_group(self.pg0,
317                             IgmpSG("239.1.1.1",
318                                    ["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
319
320         # search for the corresponding state created in VPP
321         dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
322         self.assertEqual(len(dump), 3)
323         for s in h2.sg.saddrs:
324             self.assertTrue(find_igmp_state(dump, self.pg0,
325                                             "239.1.1.1", s))
326         #
327         # Send a general query (to the all router's address)
328         # expect VPP to respond with a membership report will all sources
329         #
330         self.send(self.pg0, p_g)
331
332         capture = self.pg0.get_capture(1, timeout=10)
333         self.verify_report(capture[0],
334                            [IgmpRecord(h2.sg, "Mode Is Include")])
335
336         #
337         # Group and source specific query; some present some not
338         #
339         p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
340                 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
341                    options=[IPOption(copy_flag=1, optclass="control",
342                                      option="router_alert")]) /
343                 IGMPv3(type="Membership Query", mrcode=100) /
344                 IGMPv3mq(gaddr="239.1.1.1",
345                          srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.4"]))
346
347         self.send(self.pg0, p_gs)
348
349         capture = self.pg0.get_capture(1, timeout=10)
350         self.verify_report(capture[0],
351                            [IgmpRecord(
352                                IgmpSG('239.1.1.1', ["1.1.1.1", "1.1.1.2"]),
353                                "Mode Is Include")])
354
355         #
356         # add loads more groups
357         #
358         h3 = self.add_group(self.pg0,
359                             IgmpSG("239.1.1.2",
360                                    ["2.1.1.1", "2.1.1.2", "2.1.1.3"]))
361         h4 = self.add_group(self.pg0,
362                             IgmpSG("239.1.1.3",
363                                    ["3.1.1.1", "3.1.1.2", "3.1.1.3"]))
364         h5 = self.add_group(self.pg0,
365                             IgmpSG("239.1.1.4",
366                                    ["4.1.1.1", "4.1.1.2", "4.1.1.3"]))
367         h6 = self.add_group(self.pg0,
368                             IgmpSG("239.1.1.5",
369                                    ["5.1.1.1", "5.1.1.2", "5.1.1.3"]))
370         h7 = self.add_group(self.pg0,
371                             IgmpSG("239.1.1.6",
372                                    ["6.1.1.1", "6.1.1.2",
373                                     "6.1.1.3", "6.1.1.4",
374                                     "6.1.1.5", "6.1.1.6",
375                                     "6.1.1.7", "6.1.1.8",
376                                     "6.1.1.9", "6.1.1.10",
377                                     "6.1.1.11", "6.1.1.12",
378                                     "6.1.1.13", "6.1.1.14",
379                                     "6.1.1.15", "6.1.1.16"]))
380
381         #
382         # general query.
383         # the order the groups come in is not important, so what is
384         # checked for is what VPP is sending today.
385         #
386         self.send(self.pg0, p_g)
387
388         capture = self.pg0.get_capture(1, timeout=10)
389
390         self.verify_report(capture[0],
391                            [IgmpRecord(h3.sg, "Mode Is Include"),
392                             IgmpRecord(h2.sg, "Mode Is Include"),
393                             IgmpRecord(h6.sg, "Mode Is Include"),
394                             IgmpRecord(h4.sg, "Mode Is Include"),
395                             IgmpRecord(h5.sg, "Mode Is Include"),
396                             IgmpRecord(h7.sg, "Mode Is Include")])
397
398         #
399         # modify a group to add and remove some sources
400         #
401         h7.sg = IgmpSG("239.1.1.6",
402                        ["6.1.1.1", "6.1.1.2",
403                         "6.1.1.5", "6.1.1.6",
404                         "6.1.1.7", "6.1.1.8",
405                         "6.1.1.9", "6.1.1.10",
406                         "6.1.1.11", "6.1.1.12",
407                         "6.1.1.13", "6.1.1.14",
408                         "6.1.1.15", "6.1.1.16",
409                         "6.1.1.17", "6.1.1.18"])
410
411         self.pg_enable_capture(self.pg_interfaces)
412         self.pg_start()
413         h7.add_vpp_config()
414
415         capture = self.pg0.get_capture(1, timeout=10)
416         self.verify_report(capture[0],
417                            [IgmpRecord(IgmpSG("239.1.1.6",
418                                               ["6.1.1.17", "6.1.1.18"]),
419                                        "Allow New Sources"),
420                             IgmpRecord(IgmpSG("239.1.1.6",
421                                               ["6.1.1.3", "6.1.1.4"]),
422                                        "Block Old Sources")])
423
424         #
425         # add an additional groups with many sources so that each group
426         # consumes the link MTU. We should therefore see multiple state
427         # state reports when queried.
428         #
429         self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [560, 0, 0, 0])
430
431         src_list = []
432         for i in range(128):
433             src_list.append("10.1.1.%d" % i)
434
435         h8 = self.add_group(self.pg0,
436                             IgmpSG("238.1.1.1", src_list))
437         h9 = self.add_group(self.pg0,
438                             IgmpSG("238.1.1.2", src_list))
439
440         self.send(self.pg0, p_g)
441
442         capture = self.pg0.get_capture(4, timeout=10)
443
444         self.verify_report(capture[0],
445                            [IgmpRecord(h3.sg, "Mode Is Include"),
446                             IgmpRecord(h2.sg, "Mode Is Include"),
447                             IgmpRecord(h6.sg, "Mode Is Include"),
448                             IgmpRecord(h4.sg, "Mode Is Include"),
449                             IgmpRecord(h5.sg, "Mode Is Include")])
450         self.verify_report(capture[1],
451                            [IgmpRecord(h8.sg, "Mode Is Include")])
452         self.verify_report(capture[2],
453                            [IgmpRecord(h7.sg, "Mode Is Include")])
454         self.verify_report(capture[3],
455                            [IgmpRecord(h9.sg, "Mode Is Include")])
456
457         #
458         # drop the MTU further (so a 128 sized group won't fit)
459         #
460         self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [512, 0, 0, 0])
461
462         self.pg_enable_capture(self.pg_interfaces)
463         self.pg_start()
464
465         h10 = VppHostState(self,
466                            IGMP_FILTER.INCLUDE,
467                            self.pg0.sw_if_index,
468                            IgmpSG("238.1.1.3", src_list))
469         h10.add_vpp_config()
470
471         capture = self.pg0.get_capture(2, timeout=10)
472         # wait for a little bit
473         self.sleep(1)
474
475         #
476         # remove state, expect the report for the removal
477         # the dump should be empty
478         #
479         self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [600, 0, 0, 0])
480         self.remove_group(h8)
481         self.remove_group(h9)
482         self.remove_group(h2)
483         self.remove_group(h3)
484         self.remove_group(h4)
485         self.remove_group(h5)
486         self.remove_group(h6)
487         self.remove_group(h7)
488         self.remove_group(h10)
489
490         self.logger.info(self.vapi.cli("sh igmp config"))
491         self.assertFalse(self.vapi.igmp_dump())
492
493         #
494         # TODO
495         #  ADD STATE ON MORE INTERFACES
496         #
497
498         self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
499                                       0,
500                                       IGMP_MODE.HOST)
501
502     def test_igmp_router(self):
503         """ IGMP Router Functions """
504
505         #
506         # Drop reports when not enabled
507         #
508         p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
509                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
510                   options=[IPOption(copy_flag=1, optclass="control",
511                                     option="router_alert")]) /
512                IGMPv3(type="Version 3 Membership Report") /
513                IGMPv3mr(numgrp=1) /
514                IGMPv3gr(rtype="Allow New Sources",
515                         maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
516         p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
517                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
518                   options=[IPOption(copy_flag=1, optclass="control",
519                                     option="router_alert")]) /
520                IGMPv3(type="Version 3 Membership Report") /
521                IGMPv3mr(numgrp=1) /
522                IGMPv3gr(rtype="Block Old Sources",
523                         maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
524
525         self.send(self.pg0, p_j)
526         self.assertFalse(self.vapi.igmp_dump())
527
528         #
529         # drop the default timer values so these tests execute in a
530         # reasonable time frame
531         #
532         self.vapi.cli("test igmp timers query 1 src 3 leave 1")
533
534         #
535         # enable router functions on the interface
536         #
537         self.pg_enable_capture(self.pg_interfaces)
538         self.pg_start()
539         self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
540                                       1,
541                                       IGMP_MODE.ROUTER)
542         self.vapi.want_igmp_events(1)
543
544         #
545         # wait for router to send general query
546         #
547         for ii in range(3):
548             capture = self.pg0.get_capture(1, timeout=2)
549             self.verify_general_query(capture[0])
550             self.pg_enable_capture(self.pg_interfaces)
551             self.pg_start()
552
553         #
554         # re-send the report. VPP should now hold state for the new group
555         # VPP sends a notification that a new group has been joined
556         #
557         self.send(self.pg0, p_j)
558
559         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
560                                             "239.1.1.1", "10.1.1.1", 1))
561         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
562                                             "239.1.1.1", "10.1.1.2", 1))
563         dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
564         self.assertEqual(len(dump), 2)
565         self.assertTrue(find_igmp_state(dump, self.pg0,
566                                         "239.1.1.1", "10.1.1.1"))
567         self.assertTrue(find_igmp_state(dump, self.pg0,
568                                         "239.1.1.1", "10.1.1.2"))
569
570         #
571         # wait for the per-source timer to expire
572         # the state should be reaped
573         # VPP sends a notification that the group has been left
574         #
575         self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
576                                             "239.1.1.1", "10.1.1.1", 0))
577         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
578                                             "239.1.1.1", "10.1.1.2", 0))
579         self.assertFalse(self.vapi.igmp_dump())
580
581         #
582         # resend the join. wait for two queries and then send a current-state
583         # record to include all sources. this should reset the expiry time
584         # on the sources and thus they will still be present in 2 seconds time.
585         # If the source timer was not refreshed, then the state would have
586         # expired in 3 seconds.
587         #
588         self.send(self.pg0, p_j)
589         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
590                                             "239.1.1.1", "10.1.1.1", 1))
591         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
592                                             "239.1.1.1", "10.1.1.2", 1))
593         dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
594         self.assertEqual(len(dump), 2)
595
596         capture = self.pg0.get_capture(2, timeout=3)
597         self.verify_general_query(capture[0])
598         self.verify_general_query(capture[1])
599
600         p_cs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
601                 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
602                    options=[IPOption(copy_flag=1, optclass="control",
603                                      option="router_alert")]) /
604                 IGMPv3(type="Version 3 Membership Report") /
605                 IGMPv3mr(numgrp=1) /
606                 IGMPv3gr(rtype="Mode Is Include",
607                          maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
608
609         self.send(self.pg0, p_cs)
610
611         self.sleep(2)
612         dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
613         self.assertEqual(len(dump), 2)
614         self.assertTrue(find_igmp_state(dump, self.pg0,
615                                         "239.1.1.1", "10.1.1.1"))
616         self.assertTrue(find_igmp_state(dump, self.pg0,
617                                         "239.1.1.1", "10.1.1.2"))
618
619         #
620         # wait for the per-source timer to expire
621         # the state should be reaped
622         #
623         self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
624                                             "239.1.1.1", "10.1.1.1", 0))
625         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
626                                             "239.1.1.1", "10.1.1.2", 0))
627         self.assertFalse(self.vapi.igmp_dump())
628
629         #
630         # resend the join, then a leave. Router sends a group+source
631         # specific query containing both sources
632         #
633         self.send(self.pg0, p_j)
634
635         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
636                                             "239.1.1.1", "10.1.1.1", 1))
637         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
638                                             "239.1.1.1", "10.1.1.2", 1))
639         dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
640         self.assertEqual(len(dump), 2)
641
642         self.send(self.pg0, p_l)
643         capture = self.pg0.get_capture(1, timeout=3)
644         self.verify_group_query(capture[0], "239.1.1.1",
645                                 ["10.1.1.1", "10.1.1.2"])
646
647         #
648         # the group specific query drops the timeout to leave (=1) seconds
649         #
650         self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
651                                             "239.1.1.1", "10.1.1.1", 0))
652         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
653                                             "239.1.1.1", "10.1.1.2", 0))
654         self.assertFalse(self.vapi.igmp_dump())
655         self.assertFalse(self.vapi.igmp_dump())
656
657         #
658         # a TO_EX({}) / IN_EX({}) is treated like a (*,G) join
659         #
660         p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
661                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
662                   options=[IPOption(copy_flag=1, optclass="control",
663                                     option="router_alert")]) /
664                IGMPv3(type="Version 3 Membership Report") /
665                IGMPv3mr(numgrp=1) /
666                IGMPv3gr(rtype="Change To Exclude Mode", maddr="239.1.1.2"))
667
668         self.send(self.pg0, p_j)
669
670         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
671                                             "239.1.1.2", "0.0.0.0", 1))
672
673         p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
674                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
675                   options=[IPOption(copy_flag=1, optclass="control",
676                                     option="router_alert")]) /
677                IGMPv3(type="Version 3 Membership Report") /
678                IGMPv3mr(numgrp=1) /
679                IGMPv3gr(rtype="Mode Is Exclude", maddr="239.1.1.3"))
680
681         self.send(self.pg0, p_j)
682
683         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
684                                             "239.1.1.3", "0.0.0.0", 1))
685
686         #
687         # A 'allow sources' for {} should be ignored as it should
688         # never be sent.
689         #
690         p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
691                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
692                   options=[IPOption(copy_flag=1, optclass="control",
693                                     option="router_alert")]) /
694                IGMPv3(type="Version 3 Membership Report") /
695                IGMPv3mr(numgrp=1) /
696                IGMPv3gr(rtype="Allow New Sources", maddr="239.1.1.4"))
697
698         self.send(self.pg0, p_j)
699
700         dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
701         self.assertTrue(find_igmp_state(dump, self.pg0,
702                                         "239.1.1.2", "0.0.0.0"))
703         self.assertTrue(find_igmp_state(dump, self.pg0,
704                                         "239.1.1.3", "0.0.0.0"))
705         self.assertFalse(find_igmp_state(dump, self.pg0,
706                                          "239.1.1.4", "0.0.0.0"))
707
708         #
709         # a TO_IN({}) and IS_IN({}) are treated like a (*,G) leave
710         #
711         self.vapi.cli("set logging class igmp level debug")
712         p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
713                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
714                   options=[IPOption(copy_flag=1, optclass="control",
715                                     option="router_alert")]) /
716                IGMPv3(type="Version 3 Membership Report") /
717                IGMPv3mr(numgrp=1) /
718                IGMPv3gr(rtype="Change To Include Mode", maddr="239.1.1.2"))
719
720         self.send(self.pg0, p_l)
721         self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
722                                             "239.1.1.2", "0.0.0.0", 0))
723
724         p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
725                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
726                   options=[IPOption(copy_flag=1, optclass="control",
727                                     option="router_alert")]) /
728                IGMPv3(type="Version 3 Membership Report") /
729                IGMPv3mr(numgrp=1) /
730                IGMPv3gr(rtype="Mode Is Include", maddr="239.1.1.3"))
731
732         self.send(self.pg0, p_l)
733
734         self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
735                                             "239.1.1.3", "0.0.0.0", 0))
736         self.assertFalse(self.vapi.igmp_dump(self.pg0.sw_if_index))
737
738         #
739         # disable router config
740         #
741         self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
742                                       0,
743                                       IGMP_MODE.ROUTER)
744
745     def _create_igmpv3_pck(self, itf, rtype, maddr, srcaddrs):
746         p = (Ether(dst=itf.local_mac, src=itf.remote_mac) /
747              IP(src=itf.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
748                 options=[IPOption(copy_flag=1, optclass="control",
749                                   option="router_alert")]) /
750              IGMPv3(type="Version 3 Membership Report") /
751              IGMPv3mr(numgrp=1) /
752              IGMPv3gr(rtype=rtype,
753                       maddr=maddr, srcaddrs=srcaddrs))
754         return p
755
756     def test_igmp_proxy_device(self):
757         """ IGMP proxy device """
758         self.pg2.admin_down()
759         self.pg2.unconfig_ip4()
760         self.pg2.set_table_ip4(0)
761         self.pg2.config_ip4()
762         self.pg2.admin_up()
763
764         self.vapi.cli('test igmp timers query 10 src 3 leave 1')
765
766         # enable IGMP
767         self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
768         self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1,
769                                       IGMP_MODE.ROUTER)
770         self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1,
771                                       IGMP_MODE.ROUTER)
772
773         # create IGMP proxy device
774         self.vapi.igmp_proxy_device_add_del(0, self.pg0.sw_if_index, 1)
775         self.vapi.igmp_proxy_device_add_del_interface(0,
776                                                       self.pg1.sw_if_index, 1)
777         self.vapi.igmp_proxy_device_add_del_interface(0,
778                                                       self.pg2.sw_if_index, 1)
779
780         # send join on pg1. join should be proxied by pg0
781         p_j = self._create_igmpv3_pck(self.pg1, "Allow New Sources",
782                                       "239.1.1.1", ["10.1.1.1", "10.1.1.2"])
783         self.send(self.pg1, p_j)
784
785         capture = self.pg0.get_capture(1, timeout=1)
786         self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
787                            ["10.1.1.1", "10.1.1.2"]), "Allow New Sources")])
788         self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
789
790         # send join on pg2. join should be proxied by pg0.
791         # the group should contain only 10.1.1.3 as
792         # 10.1.1.1 was already reported
793         p_j = self._create_igmpv3_pck(self.pg2, "Allow New Sources",
794                                       "239.1.1.1", ["10.1.1.1", "10.1.1.3"])
795         self.send(self.pg2, p_j)
796
797         capture = self.pg0.get_capture(1, timeout=1)
798         self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
799                            ["10.1.1.3"]), "Allow New Sources")])
800         self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
801
802         # send leave on pg2. leave for 10.1.1.3 should be proxyed
803         # as pg2 was the only interface interested in 10.1.1.3
804         p_l = self._create_igmpv3_pck(self.pg2, "Block Old Sources",
805                                       "239.1.1.1", ["10.1.1.3"])
806         self.send(self.pg2, p_l)
807
808         capture = self.pg0.get_capture(1, timeout=2)
809         self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
810                            ["10.1.1.3"]), "Block Old Sources")])
811         self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
812
813         # disable igmp on pg1 (also removes interface from proxy device)
814         # proxy leave for 10.1.1.2. pg2 is still interested in 10.1.1.1
815         self.pg_enable_capture(self.pg_interfaces)
816         self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0,
817                                       IGMP_MODE.ROUTER)
818
819         capture = self.pg0.get_capture(1, timeout=1)
820         self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
821                            ["10.1.1.2"]), "Block Old Sources")])
822         self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
823
824         # disable IGMP on pg0 and pg1.
825         #   disabling IGMP on pg0 (proxy device upstream interface)
826         #   removes this proxy device
827         self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
828         self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0,
829                                       IGMP_MODE.ROUTER)
830         self.assertFalse(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
831
832
833 if __name__ == '__main__':
834     unittest.main(testRunner=VppTestRunner)