blob: c1452c9b83fbe5c2dc44c90b13446a3cfc8f5f95 [file] [log] [blame]
Jakub Grajciar7b867a82017-12-08 16:28:42 +01001#!/usr/bin/env python
2
3import unittest
Jakub Grajciar7b867a82017-12-08 16:28:42 +01004
5from framework import VppTestCase, VppTestRunner, running_extended_tests
6from vpp_igmp import *
7
Jakub Grajciar7b867a82017-12-08 16:28:42 +01008from scapy.layers.l2 import Ether
9from scapy.layers.inet import IP
10from scapy.contrib.igmpv3 import *
11from scapy.contrib.igmp import *
Neale Ranns947ea622018-06-07 23:48:20 -070012from vpp_ip_route import find_mroute, VppIpTable
13
14
15class IgmpMode:
16 HOST = 1
17 ROUTER = 0
Jakub Grajciar7b867a82017-12-08 16:28:42 +010018
19
Jakub Grajciar7b867a82017-12-08 16:28:42 +010020class TestIgmp(VppTestCase):
21 """ IGMP Test Case """
22
23 def setUp(self):
24 super(TestIgmp, self).setUp()
25
Neale Ranns947ea622018-06-07 23:48:20 -070026 self.create_pg_interfaces(range(4))
Jakub Grajciar7b867a82017-12-08 16:28:42 +010027 self.sg_list = []
28 self.config_list = []
29
30 self.ip_addr = []
Neale Ranns947ea622018-06-07 23:48:20 -070031 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +010036 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()
Neale Ranns947ea622018-06-07 23:48:20 -070045 pg.set_table_ip4(0)
Jakub Grajciar7b867a82017-12-08 16:28:42 +010046 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
Neale Ranns947ea622018-06-07 23:48:20 -070054 def test_igmp_flush(self):
55 """ IGMP Link Up/down and Flush """
Jakub Grajciar7b867a82017-12-08 16:28:42 +010056
57 #
Neale Ranns947ea622018-06-07 23:48:20 -070058 # FIX THIS. Link down.
Jakub Grajciar7b867a82017-12-08 16:28:42 +010059 #
Jakub Grajciar7b867a82017-12-08 16:28:42 +010060
Neale Ranns947ea622018-06-07 23:48:20 -070061 def test_igmp_enable(self):
62 """ IGMP enable/disable on an interface
Jakub Grajciar7b867a82017-12-08 16:28:42 +010063
Neale Ranns947ea622018-06-07 23:48:20 -070064 check for the addition/removal of the IGMP mroutes """
Jakub Grajciar7b867a82017-12-08 16:28:42 +010065
Neale Ranns947ea622018-06-07 23:48:20 -070066 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +010068
Neale Ranns947ea622018-06-07 23:48:20 -070069 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))
Jakub Grajciar7b867a82017-12-08 16:28:42 +010071
Neale Ranns947ea622018-06-07 23:48:20 -070072 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +010074
Neale Ranns947ea622018-06-07 23:48:20 -070075 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +010083
Neale Ranns947ea622018-06-07 23:48:20 -070084 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))
Jakub Grajciar7b867a82017-12-08 16:28:42 +010090
91 def verify_general_query(self, p):
92 ip = p[IP]
Neale Ranns947ea622018-06-07 23:48:20 -070093 self.assertEqual(len(ip.options), 1)
94 self.assertEqual(ip.options[0].option, 20)
Jakub Grajciar7b867a82017-12-08 16:28:42 +010095 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
Neale Ranns947ea622018-06-07 23:48:20 -0700101 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100111
Neale Ranns947ea622018-06-07 23:48:20 -0700112 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))
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100121
Neale Ranns947ea622018-06-07 23:48:20 -0700122 received = rx[IGMPv3mr].records
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100123
Neale Ranns947ea622018-06-07 23:48:20 -0700124 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))
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100131
Neale Ranns947ea622018-06-07 23:48:20 -0700132 self.assertEqual(sorted(gr.srcaddrs),
133 sorted(r.sg.saddrs))
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100134
Neale Ranns947ea622018-06-07 23:48:20 -0700135 def add_group(self, itf, sg, n_pkts=2):
136 self.pg_enable_capture(self.pg_interfaces)
137 self.pg_start()
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100138
Neale Ranns947ea622018-06-07 23:48:20 -0700139 hs = VppHostState(self,
140 IGMP_FILTER.INCLUDE,
141 itf.sw_if_index,
142 sg)
143 hs.add_vpp_config()
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100144
Neale Ranns947ea622018-06-07 23:48:20 -0700145 capture = itf.get_capture(n_pkts, timeout=10)
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100146
Neale Ranns947ea622018-06-07 23:48:20 -0700147 # 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")]),
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100152
Neale Ranns947ea622018-06-07 23:48:20 -0700153 return hs
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100154
Neale Ranns947ea622018-06-07 23:48:20 -0700155 def remove_group(self, hs):
156 self.pg_enable_capture(self.pg_interfaces)
157 self.pg_start()
158 hs.remove_vpp_config()
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100159
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100160 capture = self.pg0.get_capture(1, timeout=10)
161
Neale Ranns947ea622018-06-07 23:48:20 -0700162 self.verify_report(capture[0],
163 [IgmpRecord(hs.sg, "Block Old Sources")])
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100164
Neale Ranns947ea622018-06-07 23:48:20 -0700165 def test_igmp_host(self):
166 """ IGMP Host functions """
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100167
168 #
Neale Ranns947ea622018-06-07 23:48:20 -0700169 # Enable interface for host functions
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100170 #
Neale Ranns947ea622018-06-07 23:48:20 -0700171 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
172 1,
173 IGMP_MODE.HOST)
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100174
Neale Ranns947ea622018-06-07 23:48:20 -0700175 #
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"]))
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100180
Neale Ranns947ea622018-06-07 23:48:20 -0700181 # 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"))
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100186
Neale Ranns947ea622018-06-07 23:48:20 -0700187 #
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 queires in qucik sucession, 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)
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100285
286 dump = self.vapi.igmp_dump()
287 self.assertFalse(dump)
288
Neale Ranns947ea622018-06-07 23:48:20 -0700289 #
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 exiry 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 gruop+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 #
Neale Ranns0f7af532018-11-06 05:51:58 -0800632 # a TO_EX({}) / IN_EX({}) is treated like a (*,G) join
Neale Rannsc17776e2018-09-26 06:51:39 -0700633 #
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) /
Neale Ranns0f7af532018-11-06 05:51:58 -0800640 IGMPv3gr(rtype="Change To Exclude Mode", maddr="239.1.1.2"))
Neale Rannsc17776e2018-09-26 06:51:39 -0700641
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
Neale Ranns0f7af532018-11-06 05:51:58 -0800647 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 sourcees' 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
Neale Rannsc17776e2018-09-26 06:51:39 -0700712 #
Neale Ranns947ea622018-06-07 23:48:20 -0700713 # disable router config
714 #
715 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
716 0,
717 IGMP_MODE.ROUTER)
718
Jakub Grajciar97748ca2018-10-04 11:05:35 +0200719 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
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100806
807if __name__ == '__main__':
808 unittest.main(testRunner=VppTestRunner)