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