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