dhcp ip: DSCP settings for transmitted DHCP packets
[vpp.git] / test / test_igmp.py
1 #!/usr/bin/env python
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('\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
473         #
474         # remove state, expect the report for the removal
475         # the dump should be empty
476         #
477         self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [600, 0, 0, 0])
478         self.remove_group(h8)
479         self.remove_group(h9)
480         self.remove_group(h2)
481         self.remove_group(h3)
482         self.remove_group(h4)
483         self.remove_group(h5)
484         self.remove_group(h6)
485         self.remove_group(h7)
486         self.remove_group(h10)
487
488         self.logger.info(self.vapi.cli("sh igmp config"))
489         self.assertFalse(self.vapi.igmp_dump())
490
491         #
492         # TODO
493         #  ADD STATE ON MORE INTERFACES
494         #
495
496         self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
497                                       0,
498                                       IGMP_MODE.HOST)
499
500     def test_igmp_router(self):
501         """ IGMP Router Functions """
502
503         #
504         # Drop reports when not enabled
505         #
506         p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
507                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
508                   options=[IPOption(copy_flag=1, optclass="control",
509                                     option="router_alert")]) /
510                IGMPv3(type="Version 3 Membership Report") /
511                IGMPv3mr(numgrp=1) /
512                IGMPv3gr(rtype="Allow New Sources",
513                         maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
514         p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
515                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
516                   options=[IPOption(copy_flag=1, optclass="control",
517                                     option="router_alert")]) /
518                IGMPv3(type="Version 3 Membership Report") /
519                IGMPv3mr(numgrp=1) /
520                IGMPv3gr(rtype="Block Old Sources",
521                         maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
522
523         self.send(self.pg0, p_j)
524         self.assertFalse(self.vapi.igmp_dump())
525
526         #
527         # drop the default timer values so these tests execute in a
528         # reasonable time frame
529         #
530         self.vapi.cli("test igmp timers query 1 src 3 leave 1")
531
532         #
533         # enable router functions on the interface
534         #
535         self.pg_enable_capture(self.pg_interfaces)
536         self.pg_start()
537         self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
538                                       1,
539                                       IGMP_MODE.ROUTER)
540         self.vapi.want_igmp_events(1)
541
542         #
543         # wait for router to send general query
544         #
545         for ii in range(3):
546             capture = self.pg0.get_capture(1, timeout=2)
547             self.verify_general_query(capture[0])
548             self.pg_enable_capture(self.pg_interfaces)
549             self.pg_start()
550
551         #
552         # re-send the report. VPP should now hold state for the new group
553         # VPP sends a notification that a new group has been joined
554         #
555         self.send(self.pg0, p_j)
556
557         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
558                                             "239.1.1.1", "10.1.1.1", 1))
559         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
560                                             "239.1.1.1", "10.1.1.2", 1))
561         dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
562         self.assertEqual(len(dump), 2)
563         self.assertTrue(find_igmp_state(dump, self.pg0,
564                                         "239.1.1.1", "10.1.1.1"))
565         self.assertTrue(find_igmp_state(dump, self.pg0,
566                                         "239.1.1.1", "10.1.1.2"))
567
568         #
569         # wait for the per-source timer to expire
570         # the state should be reaped
571         # VPP sends a notification that the group has been left
572         #
573         self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
574                                             "239.1.1.1", "10.1.1.1", 0))
575         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
576                                             "239.1.1.1", "10.1.1.2", 0))
577         self.assertFalse(self.vapi.igmp_dump())
578
579         #
580         # resend the join. wait for two queries and then send a current-state
581         # record to include all sources. this should reset the expiry time
582         # on the sources and thus they will still be present in 2 seconds time.
583         # If the source timer was not refreshed, then the state would have
584         # expired in 3 seconds.
585         #
586         self.send(self.pg0, p_j)
587         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
588                                             "239.1.1.1", "10.1.1.1", 1))
589         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
590                                             "239.1.1.1", "10.1.1.2", 1))
591         dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
592         self.assertEqual(len(dump), 2)
593
594         capture = self.pg0.get_capture(2, timeout=3)
595         self.verify_general_query(capture[0])
596         self.verify_general_query(capture[1])
597
598         p_cs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
599                 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
600                    options=[IPOption(copy_flag=1, optclass="control",
601                                      option="router_alert")]) /
602                 IGMPv3(type="Version 3 Membership Report") /
603                 IGMPv3mr(numgrp=1) /
604                 IGMPv3gr(rtype="Mode Is Include",
605                          maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
606
607         self.send(self.pg0, p_cs)
608
609         self.sleep(2)
610         dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
611         self.assertEqual(len(dump), 2)
612         self.assertTrue(find_igmp_state(dump, self.pg0,
613                                         "239.1.1.1", "10.1.1.1"))
614         self.assertTrue(find_igmp_state(dump, self.pg0,
615                                         "239.1.1.1", "10.1.1.2"))
616
617         #
618         # wait for the per-source timer to expire
619         # the state should be reaped
620         #
621         self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
622                                             "239.1.1.1", "10.1.1.1", 0))
623         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
624                                             "239.1.1.1", "10.1.1.2", 0))
625         self.assertFalse(self.vapi.igmp_dump())
626
627         #
628         # resend the join, then a leave. Router sends a group+source
629         # specific query containing both sources
630         #
631         self.send(self.pg0, p_j)
632
633         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
634                                             "239.1.1.1", "10.1.1.1", 1))
635         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
636                                             "239.1.1.1", "10.1.1.2", 1))
637         dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
638         self.assertEqual(len(dump), 2)
639
640         self.send(self.pg0, p_l)
641         capture = self.pg0.get_capture(1, timeout=3)
642         self.verify_group_query(capture[0], "239.1.1.1",
643                                 ["10.1.1.1", "10.1.1.2"])
644
645         #
646         # the group specific query drops the timeout to leave (=1) seconds
647         #
648         self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
649                                             "239.1.1.1", "10.1.1.1", 0))
650         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
651                                             "239.1.1.1", "10.1.1.2", 0))
652         self.assertFalse(self.vapi.igmp_dump())
653         self.assertFalse(self.vapi.igmp_dump())
654
655         #
656         # a TO_EX({}) / IN_EX({}) is treated like a (*,G) join
657         #
658         p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
659                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
660                   options=[IPOption(copy_flag=1, optclass="control",
661                                     option="router_alert")]) /
662                IGMPv3(type="Version 3 Membership Report") /
663                IGMPv3mr(numgrp=1) /
664                IGMPv3gr(rtype="Change To Exclude Mode", maddr="239.1.1.2"))
665
666         self.send(self.pg0, p_j)
667
668         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
669                                             "239.1.1.2", "0.0.0.0", 1))
670
671         p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
672                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
673                   options=[IPOption(copy_flag=1, optclass="control",
674                                     option="router_alert")]) /
675                IGMPv3(type="Version 3 Membership Report") /
676                IGMPv3mr(numgrp=1) /
677                IGMPv3gr(rtype="Mode Is Exclude", maddr="239.1.1.3"))
678
679         self.send(self.pg0, p_j)
680
681         self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
682                                             "239.1.1.3", "0.0.0.0", 1))
683
684         #
685         # A 'allow sources' for {} should be ignored as it should
686         # never be sent.
687         #
688         p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
689                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
690                   options=[IPOption(copy_flag=1, optclass="control",
691                                     option="router_alert")]) /
692                IGMPv3(type="Version 3 Membership Report") /
693                IGMPv3mr(numgrp=1) /
694                IGMPv3gr(rtype="Allow New Sources", maddr="239.1.1.4"))
695
696         self.send(self.pg0, p_j)
697
698         dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
699         self.assertTrue(find_igmp_state(dump, self.pg0,
700                                         "239.1.1.2", "0.0.0.0"))
701         self.assertTrue(find_igmp_state(dump, self.pg0,
702                                         "239.1.1.3", "0.0.0.0"))
703         self.assertFalse(find_igmp_state(dump, self.pg0,
704                                          "239.1.1.4", "0.0.0.0"))
705
706         #
707         # a TO_IN({}) and IS_IN({}) are treated like a (*,G) leave
708         #
709         self.vapi.cli("set logging class igmp level debug")
710         p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
711                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
712                   options=[IPOption(copy_flag=1, optclass="control",
713                                     option="router_alert")]) /
714                IGMPv3(type="Version 3 Membership Report") /
715                IGMPv3mr(numgrp=1) /
716                IGMPv3gr(rtype="Change To Include Mode", maddr="239.1.1.2"))
717
718         self.send(self.pg0, p_l)
719         self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
720                                             "239.1.1.2", "0.0.0.0", 0))
721
722         p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
723                IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
724                   options=[IPOption(copy_flag=1, optclass="control",
725                                     option="router_alert")]) /
726                IGMPv3(type="Version 3 Membership Report") /
727                IGMPv3mr(numgrp=1) /
728                IGMPv3gr(rtype="Mode Is Include", maddr="239.1.1.3"))
729
730         self.send(self.pg0, p_l)
731
732         self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
733                                             "239.1.1.3", "0.0.0.0", 0))
734         self.assertFalse(self.vapi.igmp_dump(self.pg0.sw_if_index))
735
736         #
737         # disable router config
738         #
739         self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
740                                       0,
741                                       IGMP_MODE.ROUTER)
742
743     def _create_igmpv3_pck(self, itf, rtype, maddr, srcaddrs):
744         p = (Ether(dst=itf.local_mac, src=itf.remote_mac) /
745              IP(src=itf.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
746                 options=[IPOption(copy_flag=1, optclass="control",
747                                   option="router_alert")]) /
748              IGMPv3(type="Version 3 Membership Report") /
749              IGMPv3mr(numgrp=1) /
750              IGMPv3gr(rtype=rtype,
751                       maddr=maddr, srcaddrs=srcaddrs))
752         return p
753
754     def test_igmp_proxy_device(self):
755         """ IGMP proxy device """
756         self.pg2.admin_down()
757         self.pg2.unconfig_ip4()
758         self.pg2.set_table_ip4(0)
759         self.pg2.config_ip4()
760         self.pg2.admin_up()
761
762         self.vapi.cli('test igmp timers query 10 src 3 leave 1')
763
764         # enable IGMP
765         self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
766         self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1,
767                                       IGMP_MODE.ROUTER)
768         self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1,
769                                       IGMP_MODE.ROUTER)
770
771         # create IGMP proxy device
772         self.vapi.igmp_proxy_device_add_del(0, self.pg0.sw_if_index, 1)
773         self.vapi.igmp_proxy_device_add_del_interface(0,
774                                                       self.pg1.sw_if_index, 1)
775         self.vapi.igmp_proxy_device_add_del_interface(0,
776                                                       self.pg2.sw_if_index, 1)
777
778         # send join on pg1. join should be proxied by pg0
779         p_j = self._create_igmpv3_pck(self.pg1, "Allow New Sources",
780                                       "239.1.1.1", ["10.1.1.1", "10.1.1.2"])
781         self.send(self.pg1, p_j)
782
783         capture = self.pg0.get_capture(1, timeout=1)
784         self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
785                            ["10.1.1.1", "10.1.1.2"]), "Allow New Sources")])
786         self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
787
788         # send join on pg2. join should be proxied by pg0.
789         # the group should contain only 10.1.1.3 as
790         # 10.1.1.1 was already reported
791         p_j = self._create_igmpv3_pck(self.pg2, "Allow New Sources",
792                                       "239.1.1.1", ["10.1.1.1", "10.1.1.3"])
793         self.send(self.pg2, p_j)
794
795         capture = self.pg0.get_capture(1, timeout=1)
796         self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
797                            ["10.1.1.3"]), "Allow New Sources")])
798         self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
799
800         # send leave on pg2. leave for 10.1.1.3 should be proxyed
801         # as pg2 was the only interface interested in 10.1.1.3
802         p_l = self._create_igmpv3_pck(self.pg2, "Block Old Sources",
803                                       "239.1.1.1", ["10.1.1.3"])
804         self.send(self.pg2, p_l)
805
806         capture = self.pg0.get_capture(1, timeout=2)
807         self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
808                            ["10.1.1.3"]), "Block Old Sources")])
809         self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
810
811         # disable igmp on pg1 (also removes interface from proxy device)
812         # proxy leave for 10.1.1.2. pg2 is still interested in 10.1.1.1
813         self.pg_enable_capture(self.pg_interfaces)
814         self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0,
815                                       IGMP_MODE.ROUTER)
816
817         capture = self.pg0.get_capture(1, timeout=1)
818         self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
819                            ["10.1.1.2"]), "Block Old Sources")])
820         self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
821
822         # disable IGMP on pg0 and pg1.
823         #   disabling IGMP on pg0 (proxy device upstream interface)
824         #   removes this proxy device
825         self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
826         self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0,
827                                       IGMP_MODE.ROUTER)
828         self.assertFalse(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
829
830
831 if __name__ == '__main__':
832     unittest.main(testRunner=VppTestRunner)