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