blob: f99bdb25c8c93ec6f37a34d77e769f49edc4053f [file] [log] [blame]
Renato Botelho do Coutoead1e532019-10-31 13:31:07 -05001#!/usr/bin/env python3
Jakub Grajciar7b867a82017-12-08 16:28:42 +01002
3import unittest
Jakub Grajciar7b867a82017-12-08 16:28:42 +01004
snaramre5d4b8912019-12-13 23:39:35 +00005from scapy.layers.l2 import Ether
6from scapy.packet import Raw
Paul Vinciguerraa279d9c2019-02-28 09:00:09 -08007from scapy.layers.inet import IP, IPOption
8from scapy.contrib.igmpv3 import IGMPv3, IGMPv3gr, IGMPv3mq, IGMPv3mr
9
Andrew Yourtchenko8dc0d482021-01-29 13:17:19 +000010from framework import tag_fixme_vpp_workers
Klement Sekerab23ffd72021-05-31 16:08:53 +020011from framework import VppTestCase, VppTestRunner
Paul Vinciguerraa279d9c2019-02-28 09:00:09 -080012from vpp_igmp import find_igmp_state, IGMP_FILTER, IgmpRecord, IGMP_MODE, \
13 IgmpSG, VppHostState, wait_for_igmp_event
Neale Ranns947ea622018-06-07 23:48:20 -070014from vpp_ip_route import find_mroute, VppIpTable
15
16
17class IgmpMode:
18 HOST = 1
19 ROUTER = 0
Jakub Grajciar7b867a82017-12-08 16:28:42 +010020
21
Andrew Yourtchenko8dc0d482021-01-29 13:17:19 +000022@tag_fixme_vpp_workers
Jakub Grajciar7b867a82017-12-08 16:28:42 +010023class TestIgmp(VppTestCase):
24 """ IGMP Test Case """
25
Paul Vinciguerra7f9b7f92019-03-12 19:23:27 -070026 @classmethod
27 def setUpClass(cls):
28 super(TestIgmp, cls).setUpClass()
29
30 @classmethod
31 def tearDownClass(cls):
32 super(TestIgmp, cls).tearDownClass()
33
Jakub Grajciar7b867a82017-12-08 16:28:42 +010034 def setUp(self):
35 super(TestIgmp, self).setUp()
36
Neale Ranns947ea622018-06-07 23:48:20 -070037 self.create_pg_interfaces(range(4))
Jakub Grajciar7b867a82017-12-08 16:28:42 +010038 self.sg_list = []
39 self.config_list = []
40
41 self.ip_addr = []
Neale Ranns947ea622018-06-07 23:48:20 -070042 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +010047 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()
Neale Ranns947ea622018-06-07 23:48:20 -070056 pg.set_table_ip4(0)
Jakub Grajciar7b867a82017-12-08 16:28:42 +010057 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
Neale Ranns947ea622018-06-07 23:48:20 -070065 def test_igmp_flush(self):
66 """ IGMP Link Up/down and Flush """
Jakub Grajciar7b867a82017-12-08 16:28:42 +010067
68 #
Neale Ranns947ea622018-06-07 23:48:20 -070069 # FIX THIS. Link down.
Jakub Grajciar7b867a82017-12-08 16:28:42 +010070 #
Jakub Grajciar7b867a82017-12-08 16:28:42 +010071
Neale Ranns947ea622018-06-07 23:48:20 -070072 def test_igmp_enable(self):
73 """ IGMP enable/disable on an interface
Jakub Grajciar7b867a82017-12-08 16:28:42 +010074
Neale Ranns947ea622018-06-07 23:48:20 -070075 check for the addition/removal of the IGMP mroutes """
Jakub Grajciar7b867a82017-12-08 16:28:42 +010076
Neale Ranns947ea622018-06-07 23:48:20 -070077 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +010079
Neale Ranns947ea622018-06-07 23:48:20 -070080 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))
Jakub Grajciar7b867a82017-12-08 16:28:42 +010082
Neale Ranns947ea622018-06-07 23:48:20 -070083 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +010085
Neale Ranns947ea622018-06-07 23:48:20 -070086 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +010094
Neale Ranns03c254e2020-03-17 14:25:10 +000095 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32))
Neale Ranns947ea622018-06-07 23:48:20 -070096 self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32))
Neale Ranns03c254e2020-03-17 14:25:10 +000097 self.assertTrue(find_mroute(self, "224.0.0.1", "0.0.0.0", 32,
98 table_id=1))
Neale Ranns947ea622018-06-07 23:48:20 -070099 self.assertFalse(find_mroute(self, "224.0.0.22", "0.0.0.0", 32,
100 table_id=1))
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100101
102 def verify_general_query(self, p):
103 ip = p[IP]
Neale Ranns947ea622018-06-07 23:48:20 -0700104 self.assertEqual(len(ip.options), 1)
105 self.assertEqual(ip.options[0].option, 20)
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100106 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
Neale Ranns947ea622018-06-07 23:48:20 -0700112 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100122
Neale Ranns947ea622018-06-07 23:48:20 -0700123 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))
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100132
Neale Ranns947ea622018-06-07 23:48:20 -0700133 received = rx[IGMPv3mr].records
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100134
Neale Ranns947ea622018-06-07 23:48:20 -0700135 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))
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100142
Neale Ranns947ea622018-06-07 23:48:20 -0700143 self.assertEqual(sorted(gr.srcaddrs),
144 sorted(r.sg.saddrs))
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100145
Neale Ranns947ea622018-06-07 23:48:20 -0700146 def add_group(self, itf, sg, n_pkts=2):
147 self.pg_enable_capture(self.pg_interfaces)
148 self.pg_start()
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100149
Neale Ranns947ea622018-06-07 23:48:20 -0700150 hs = VppHostState(self,
151 IGMP_FILTER.INCLUDE,
152 itf.sw_if_index,
153 sg)
154 hs.add_vpp_config()
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100155
Neale Ranns947ea622018-06-07 23:48:20 -0700156 capture = itf.get_capture(n_pkts, timeout=10)
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100157
Neale Ranns947ea622018-06-07 23:48:20 -0700158 # 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")]),
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100163
Neale Ranns947ea622018-06-07 23:48:20 -0700164 return hs
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100165
Neale Ranns947ea622018-06-07 23:48:20 -0700166 def remove_group(self, hs):
167 self.pg_enable_capture(self.pg_interfaces)
168 self.pg_start()
169 hs.remove_vpp_config()
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100170
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100171 capture = self.pg0.get_capture(1, timeout=10)
172
Neale Ranns947ea622018-06-07 23:48:20 -0700173 self.verify_report(capture[0],
174 [IgmpRecord(hs.sg, "Block Old Sources")])
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100175
Neale Ranns947ea622018-06-07 23:48:20 -0700176 def test_igmp_host(self):
177 """ IGMP Host functions """
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100178
179 #
Neale Ranns947ea622018-06-07 23:48:20 -0700180 # Enable interface for host functions
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100181 #
Neale Ranns947ea622018-06-07 23:48:20 -0700182 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
183 1,
184 IGMP_MODE.HOST)
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100185
Neale Ranns947ea622018-06-07 23:48:20 -0700186 #
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"]))
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100191
Neale Ranns947ea622018-06-07 23:48:20 -0700192 # 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"))
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100197
Neale Ranns947ea622018-06-07 23:48:20 -0700198 #
199 # Send a general query (to the all router's address)
Neale Ranns01b0a052019-06-30 09:05:05 +0000200 # 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.
Neale Ranns947ea622018-06-07 23:48:20 -0700203 #
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) /
Neale Ranns01b0a052019-06-30 09:05:05 +0000207 IGMPv3mq(gaddr="0.0.0.0") /
Ole Troan770a0de2019-11-07 13:52:21 +0100208 Raw(b'\x00' * 10))
Neale Ranns947ea622018-06-07 23:48:20 -0700209
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 #
Neale Ranns01b0a052019-06-30 09:05:05 +0000250 # 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 #
Neale Ranns947ea622018-06-07 23:48:20 -0700263 # 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 #
Paul Vinciguerra8feeaff2019-03-27 11:25:48 -0700295 # Two source and group specific queries in quick succession, the
Neale Ranns947ea622018-06-07 23:48:20 -0700296 # 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100312
313 dump = self.vapi.igmp_dump()
314 self.assertFalse(dump)
315
Neale Ranns947ea622018-06-07 23:48:20 -0700316 #
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)
Andrew Yourtchenkocb265c62019-07-25 10:03:51 +0000475 # wait for a little bit
476 self.sleep(1)
Neale Ranns947ea622018-06-07 23:48:20 -0700477
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
Paul Vinciguerra8feeaff2019-03-27 11:25:48 -0700586 # record to include all sources. this should reset the expiry time
Neale Ranns947ea622018-06-07 23:48:20 -0700587 # 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 #
Paul Vinciguerra8feeaff2019-03-27 11:25:48 -0700633 # resend the join, then a leave. Router sends a group+source
Neale Ranns947ea622018-06-07 23:48:20 -0700634 # 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 #
Neale Ranns0f7af532018-11-06 05:51:58 -0800661 # a TO_EX({}) / IN_EX({}) is treated like a (*,G) join
Neale Rannsc17776e2018-09-26 06:51:39 -0700662 #
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) /
Neale Ranns0f7af532018-11-06 05:51:58 -0800669 IGMPv3gr(rtype="Change To Exclude Mode", maddr="239.1.1.2"))
Neale Rannsc17776e2018-09-26 06:51:39 -0700670
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
Neale Ranns0f7af532018-11-06 05:51:58 -0800676 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 #
Paul Vinciguerra8feeaff2019-03-27 11:25:48 -0700690 # A 'allow sources' for {} should be ignored as it should
Neale Ranns0f7af532018-11-06 05:51:58 -0800691 # 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
Neale Rannsc17776e2018-09-26 06:51:39 -0700741 #
Neale Ranns947ea622018-06-07 23:48:20 -0700742 # disable router config
743 #
744 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
745 0,
746 IGMP_MODE.ROUTER)
747
Jakub Grajciar97748ca2018-10-04 11:05:35 +0200748 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
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100835
836if __name__ == '__main__':
837 unittest.main(testRunner=VppTestRunner)