tests: changes for scapy 2.4.3 migration
[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
6 from scapy.packet import Raw
7 from scapy.layers.inet import IP, IPOption
8 from scapy.contrib.igmpv3 import IGMPv3, IGMPv3gr, IGMPv3mq, IGMPv3mr
9
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
14
15
16 class IgmpMode:
17     HOST = 1
18     ROUTER = 0
19
20
21 class TestIgmp(VppTestCase):
22     """ IGMP Test Case """
23
24     @classmethod
25     def setUpClass(cls):
26         super(TestIgmp, cls).setUpClass()
27
28     @classmethod
29     def tearDownClass(cls):
30         super(TestIgmp, cls).tearDownClass()
31
32     def setUp(self):
33         super(TestIgmp, self).setUp()
34
35         self.create_pg_interfaces(range(4))
36         self.sg_list = []
37         self.config_list = []
38
39         self.ip_addr = []
40         self.ip_table = VppIpTable(self, 1)
41         self.ip_table.add_vpp_config()
42
43         for pg in self.pg_interfaces[2:]:
44             pg.set_table_ip4(1)
45         for pg in self.pg_interfaces:
46             pg.admin_up()
47             pg.config_ip4()
48             pg.resolve_arp()
49
50     def tearDown(self):
51         for pg in self.pg_interfaces:
52             self.vapi.igmp_clear_interface(pg.sw_if_index)
53             pg.unconfig_ip4()
54             pg.set_table_ip4(0)
55             pg.admin_down()
56         super(TestIgmp, self).tearDown()
57
58     def send(self, ti, pkts):
59         ti.add_stream(pkts)
60         self.pg_enable_capture(self.pg_interfaces)
61         self.pg_start()
62
63     def test_igmp_flush(self):
64         """ IGMP Link Up/down and Flush """
65
66         #
67         # FIX THIS. Link down.
68         #
69
70     def test_igmp_enable(self):
71         """ IGMP enable/disable on an interface
72
73         check for the addition/removal of the IGMP mroutes """
74
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)
77
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))
80
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)
83
84         self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
85                                     table_id=1))
86         self.assertTrue(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
87                                     table_id=1))
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)
92
93         self.assertFalse(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.assertFalse(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
96                                      table_id=1))
97         self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
98                                      table_id=1))
99
100     def verify_general_query(self, p):
101         ip = p[IP]
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)
106         igmp = p[IGMPv3]
107         self.assertEqual(igmp.type, 0x11)
108         self.assertEqual(igmp.gaddr, "0.0.0.0")
109
110     def verify_group_query(self, p, grp, srcs):
111         ip = p[IP]
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)
117         igmp = p[IGMPv3]
118         self.assertEqual(igmp.type, 0x11)
119         self.assertEqual(igmp.gaddr, grp)
120
121     def verify_report(self, rx, records):
122         ip = rx[IP]
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))
130
131         received = rx[IGMPv3mr].records
132
133         for ii in range(len(records)):
134             gr = received[ii]
135             r = records[ii]
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))
140
141             self.assertEqual(sorted(gr.srcaddrs),
142                              sorted(r.sg.saddrs))
143
144     def add_group(self, itf, sg, n_pkts=2):
145         self.pg_enable_capture(self.pg_interfaces)
146         self.pg_start()
147
148         hs = VppHostState(self,
149                           IGMP_FILTER.INCLUDE,
150                           itf.sw_if_index,
151                           sg)
152         hs.add_vpp_config()
153
154         capture = itf.get_capture(n_pkts, timeout=10)
155
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")]),
161
162         return hs
163
164     def remove_group(self, hs):
165         self.pg_enable_capture(self.pg_interfaces)
166         self.pg_start()
167         hs.remove_vpp_config()
168
169         capture = self.pg0.get_capture(1, timeout=10)
170
171         self.verify_report(capture[0],
172                            [IgmpRecord(hs.sg, "Block Old Sources")])
173
174     def test_igmp_host(self):
175         """ IGMP Host functions """
176
177         #
178         # Enable interface for host functions
179         #
180         self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
181                                       1,
182                                       IGMP_MODE.HOST)
183
184         #
185         # Add one S,G of state and expect a state-change event report
186         # indicating the addition of the S,G
187         #
188         h1 = self.add_group(self.pg0, IgmpSG("239.1.1.1", ["1.1.1.1"]))
189
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"))
195
196         #
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.
201         #
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") /
206                Raw(b'\x00' * 10))
207
208         self.send(self.pg0, p_g)
209
210         capture = self.pg0.get_capture(1, timeout=10)
211         self.verify_report(capture[0],
212                            [IgmpRecord(h1.sg, "Mode Is Include")])
213
214         #
215         # Group specific query
216         #
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"))
223
224         self.send(self.pg0, p_gs)
225
226         capture = self.pg0.get_capture(1, timeout=10)
227         self.verify_report(capture[0],
228                            [IgmpRecord(h1.sg, "Mode Is Include")])
229
230         #
231         # A group and source specific query, with the source matching
232         # the source VPP has
233         #
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"]))
240
241         self.send(self.pg0, p_gs1)
242
243         capture = self.pg0.get_capture(1, timeout=10)
244         self.verify_report(capture[0],
245                            [IgmpRecord(h1.sg, "Mode Is Include")])
246
247         #
248         # A group and source specific query that reports more sources
249         # than the packet actually has.
250         #
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"]))
257
258         self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
259
260         #
261         # A group and source specific query, with the source NOT matching
262         # the source VPP has. There should be no response.
263         #
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"]))
270
271         self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
272
273         #
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.
277         #
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"]))
285
286         self.send(self.pg0, p_gs3)
287
288         capture = self.pg0.get_capture(1, timeout=10)
289         self.verify_report(capture[0],
290                            [IgmpRecord(h1.sg, "Mode Is Include")])
291
292         #
293         # Two source and group specific queries in quick succession, the
294         # first does not have VPPs source the second does. then vice-versa
295         #
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")])
300
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")])
305
306         #
307         # remove state, expect the report for the removal
308         #
309         self.remove_group(h1)
310
311         dump = self.vapi.igmp_dump()
312         self.assertFalse(dump)
313
314         #
315         # A group with multiple sources
316         #
317         h2 = self.add_group(self.pg0,
318                             IgmpSG("239.1.1.1",
319                                    ["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
320
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,
326                                             "239.1.1.1", s))
327         #
328         # Send a general query (to the all router's address)
329         # expect VPP to respond with a membership report will all sources
330         #
331         self.send(self.pg0, p_g)
332
333         capture = self.pg0.get_capture(1, timeout=10)
334         self.verify_report(capture[0],
335                            [IgmpRecord(h2.sg, "Mode Is Include")])
336
337         #
338         # Group and source specific query; some present some not
339         #
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"]))
347
348         self.send(self.pg0, p_gs)
349
350         capture = self.pg0.get_capture(1, timeout=10)
351         self.verify_report(capture[0],
352                            [IgmpRecord(
353                                IgmpSG('239.1.1.1', ["1.1.1.1", "1.1.1.2"]),
354                                "Mode Is Include")])
355
356         #
357         # add loads more groups
358         #
359         h3 = self.add_group(self.pg0,
360                             IgmpSG("239.1.1.2",
361                                    ["2.1.1.1", "2.1.1.2", "2.1.1.3"]))
362         h4 = self.add_group(self.pg0,
363                             IgmpSG("239.1.1.3",
364                                    ["3.1.1.1", "3.1.1.2", "3.1.1.3"]))
365         h5 = self.add_group(self.pg0,
366                             IgmpSG("239.1.1.4",
367                                    ["4.1.1.1", "4.1.1.2", "4.1.1.3"]))
368         h6 = self.add_group(self.pg0,
369                             IgmpSG("239.1.1.5",
370                                    ["5.1.1.1", "5.1.1.2", "5.1.1.3"]))
371         h7 = self.add_group(self.pg0,
372                             IgmpSG("239.1.1.6",
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"]))
381
382         #
383         # general query.
384         # the order the groups come in is not important, so what is
385         # checked for is what VPP is sending today.
386         #
387         self.send(self.pg0, p_g)
388
389         capture = self.pg0.get_capture(1, timeout=10)
390
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")])
398
399         #
400         # modify a group to add and remove some sources
401         #
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"])
411
412         self.pg_enable_capture(self.pg_interfaces)
413         self.pg_start()
414         h7.add_vpp_config()
415
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")])
424
425         #
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.
429         #
430         self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [560, 0, 0, 0])
431
432         src_list = []
433         for i in range(128):
434             src_list.append("10.1.1.%d" % i)
435
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))
440
441         self.send(self.pg0, p_g)
442
443         capture = self.pg0.get_capture(4, timeout=10)
444
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")])
457
458         #
459         # drop the MTU further (so a 128 sized group won't fit)
460         #
461         self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [512, 0, 0, 0])
462
463         self.pg_enable_capture(self.pg_interfaces)
464         self.pg_start()
465
466         h10 = VppHostState(self,
467                            IGMP_FILTER.INCLUDE,
468                            self.pg0.sw_if_index,
469                            IgmpSG("238.1.1.3", src_list))
470         h10.add_vpp_config()
471
472         capture = self.pg0.get_capture(2, timeout=10)
473         # wait for a little bit
474         self.sleep(1)
475
476         #
477         # remove state, expect the report for the removal
478         # the dump should be empty
479         #
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)
490
491         self.logger.info(self.vapi.cli("sh igmp config"))
492         self.assertFalse(self.vapi.igmp_dump())
493
494         #
495         # TODO
496         #  ADD STATE ON MORE INTERFACES
497         #
498
499         self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
500                                       0,
501                                       IGMP_MODE.HOST)
502
503     def test_igmp_router(self):
504         """ IGMP Router Functions """
505
506         #
507         # Drop reports when not enabled
508         #
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") /
514                IGMPv3mr(numgrp=1) /
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") /
522                IGMPv3mr(numgrp=1) /
523                IGMPv3gr(rtype="Block Old Sources",
524                         maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
525
526         self.send(self.pg0, p_j)
527         self.assertFalse(self.vapi.igmp_dump())
528
529         #
530         # drop the default timer values so these tests execute in a
531         # reasonable time frame
532         #
533         self.vapi.cli("test igmp timers query 1 src 3 leave 1")
534
535         #
536         # enable router functions on the interface
537         #
538         self.pg_enable_capture(self.pg_interfaces)
539         self.pg_start()
540         self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
541                                       1,
542                                       IGMP_MODE.ROUTER)
543         self.vapi.want_igmp_events(1)
544
545         #
546         # wait for router to send general query
547         #
548         for ii in range(3):
549             capture = self.pg0.get_capture(1, timeout=2)
550             self.verify_general_query(capture[0])
551             self.pg_enable_capture(self.pg_interfaces)
552             self.pg_start()
553
554         #
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
557         #
558         self.send(self.pg0, p_j)
559
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"))
570
571         #
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
575         #
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())
581
582         #
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.
588         #
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)
596
597         capture = self.pg0.get_capture(2, timeout=3)
598         self.verify_general_query(capture[0])
599         self.verify_general_query(capture[1])
600
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") /
606                 IGMPv3mr(numgrp=1) /
607                 IGMPv3gr(rtype="Mode Is Include",
608                          maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
609
610         self.send(self.pg0, p_cs)
611
612         self.sleep(2)
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"))
619
620         #
621         # wait for the per-source timer to expire
622         # the state should be reaped
623         #
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())
629
630         #
631         # resend the join, then a leave. Router sends a group+source
632         # specific query containing both sources
633         #
634         self.send(self.pg0, p_j)
635
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)
642
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"])
647
648         #
649         # the group specific query drops the timeout to leave (=1) seconds
650         #
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())
657
658         #
659         # a TO_EX({}) / IN_EX({}) is treated like a (*,G) join
660         #
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") /
666                IGMPv3mr(numgrp=1) /
667                IGMPv3gr(rtype="Change To Exclude Mode", maddr="239.1.1.2"))
668
669         self.send(self.pg0, p_j)
670
671         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
672                                             "239.1.1.2", "0.0.0.0", 1))
673
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") /
679                IGMPv3mr(numgrp=1) /
680                IGMPv3gr(rtype="Mode Is Exclude", maddr="239.1.1.3"))
681
682         self.send(self.pg0, p_j)
683
684         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
685                                             "239.1.1.3", "0.0.0.0", 1))
686
687         #
688         # A 'allow sources' for {} should be ignored as it should
689         # never be sent.
690         #
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") /
696                IGMPv3mr(numgrp=1) /
697                IGMPv3gr(rtype="Allow New Sources", maddr="239.1.1.4"))
698
699         self.send(self.pg0, p_j)
700
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"))
708
709         #
710         # a TO_IN({}) and IS_IN({}) are treated like a (*,G) leave
711         #
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") /
718                IGMPv3mr(numgrp=1) /
719                IGMPv3gr(rtype="Change To Include Mode", maddr="239.1.1.2"))
720
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))
724
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") /
730                IGMPv3mr(numgrp=1) /
731                IGMPv3gr(rtype="Mode Is Include", maddr="239.1.1.3"))
732
733         self.send(self.pg0, p_l)
734
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))
738
739         #
740         # disable router config
741         #
742         self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
743                                       0,
744                                       IGMP_MODE.ROUTER)
745
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") /
752              IGMPv3mr(numgrp=1) /
753              IGMPv3gr(rtype=rtype,
754                       maddr=maddr, srcaddrs=srcaddrs))
755         return p
756
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()
763         self.pg2.admin_up()
764
765         self.vapi.cli('test igmp timers query 10 src 3 leave 1')
766
767         # enable IGMP
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,
770                                       IGMP_MODE.ROUTER)
771         self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1,
772                                       IGMP_MODE.ROUTER)
773
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)
780
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)
785
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))
790
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)
797
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))
802
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)
808
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))
813
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,
818                                       IGMP_MODE.ROUTER)
819
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))
824
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,
830                                       IGMP_MODE.ROUTER)
831         self.assertFalse(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
832
833
834 if __name__ == '__main__':
835     unittest.main(testRunner=VppTestRunner)