blob: f1c49acba4c54183c2ae5ab51dedd65bf16b5076 [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
Neale Ranns01b0a052019-06-30 09:05:05 +00005from scapy.layers.l2 import Ether, Raw
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)
Neale Ranns01b0a052019-06-30 09:05:05 +0000197 # expect VPP to respond with a membership report.
198 # Pad the query with 0 - some devices in the big wild
199 # internet are prone to this.
Neale Ranns947ea622018-06-07 23:48:20 -0700200 #
201 p_g = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
202 IP(src=self.pg0.remote_ip4, dst='224.0.0.1', tos=0xc0) /
203 IGMPv3(type="Membership Query", mrcode=100) /
Neale Ranns01b0a052019-06-30 09:05:05 +0000204 IGMPv3mq(gaddr="0.0.0.0") /
205 Raw('\x00' * 10))
Neale Ranns947ea622018-06-07 23:48:20 -0700206
207 self.send(self.pg0, p_g)
208
209 capture = self.pg0.get_capture(1, timeout=10)
210 self.verify_report(capture[0],
211 [IgmpRecord(h1.sg, "Mode Is Include")])
212
213 #
214 # Group specific query
215 #
216 p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
217 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
218 options=[IPOption(copy_flag=1, optclass="control",
219 option="router_alert")]) /
220 IGMPv3(type="Membership Query", mrcode=100) /
221 IGMPv3mq(gaddr="239.1.1.1"))
222
223 self.send(self.pg0, p_gs)
224
225 capture = self.pg0.get_capture(1, timeout=10)
226 self.verify_report(capture[0],
227 [IgmpRecord(h1.sg, "Mode Is Include")])
228
229 #
230 # A group and source specific query, with the source matching
231 # the source VPP has
232 #
233 p_gs1 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
234 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
235 options=[IPOption(copy_flag=1, optclass="control",
236 option="router_alert")]) /
237 IGMPv3(type="Membership Query", mrcode=100) /
238 IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.1"]))
239
240 self.send(self.pg0, p_gs1)
241
242 capture = self.pg0.get_capture(1, timeout=10)
243 self.verify_report(capture[0],
244 [IgmpRecord(h1.sg, "Mode Is Include")])
245
246 #
Neale Ranns01b0a052019-06-30 09:05:05 +0000247 # A group and source specific query that reports more sources
248 # than the packet actually has.
249 #
250 p_gs2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
251 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
252 options=[IPOption(copy_flag=1, optclass="control",
253 option="router_alert")]) /
254 IGMPv3(type="Membership Query", mrcode=100) /
255 IGMPv3mq(gaddr="239.1.1.1", numsrc=4, srcaddrs=["1.1.1.1"]))
256
257 self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
258
259 #
Neale Ranns947ea622018-06-07 23:48:20 -0700260 # A group and source specific query, with the source NOT matching
261 # the source VPP has. There should be no response.
262 #
263 p_gs2 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
264 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
265 options=[IPOption(copy_flag=1, optclass="control",
266 option="router_alert")]) /
267 IGMPv3(type="Membership Query", mrcode=100) /
268 IGMPv3mq(gaddr="239.1.1.1", srcaddrs=["1.1.1.2"]))
269
270 self.send_and_assert_no_replies(self.pg0, p_gs2, timeout=10)
271
272 #
273 # A group and source specific query, with the multiple sources
274 # one of which matches the source VPP has.
275 # The report should contain only the source VPP has.
276 #
277 p_gs3 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
278 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
279 options=[IPOption(copy_flag=1, optclass="control",
280 option="router_alert")]) /
281 IGMPv3(type="Membership Query", mrcode=100) /
282 IGMPv3mq(gaddr="239.1.1.1",
283 srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
284
285 self.send(self.pg0, p_gs3)
286
287 capture = self.pg0.get_capture(1, timeout=10)
288 self.verify_report(capture[0],
289 [IgmpRecord(h1.sg, "Mode Is Include")])
290
291 #
Paul Vinciguerra8feeaff2019-03-27 11:25:48 -0700292 # Two source and group specific queries in quick succession, the
Neale Ranns947ea622018-06-07 23:48:20 -0700293 # first does not have VPPs source the second does. then vice-versa
294 #
295 self.send(self.pg0, [p_gs2, p_gs1])
296 capture = self.pg0.get_capture(1, timeout=10)
297 self.verify_report(capture[0],
298 [IgmpRecord(h1.sg, "Mode Is Include")])
299
300 self.send(self.pg0, [p_gs1, p_gs2])
301 capture = self.pg0.get_capture(1, timeout=10)
302 self.verify_report(capture[0],
303 [IgmpRecord(h1.sg, "Mode Is Include")])
304
305 #
306 # remove state, expect the report for the removal
307 #
308 self.remove_group(h1)
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100309
310 dump = self.vapi.igmp_dump()
311 self.assertFalse(dump)
312
Neale Ranns947ea622018-06-07 23:48:20 -0700313 #
314 # A group with multiple sources
315 #
316 h2 = self.add_group(self.pg0,
317 IgmpSG("239.1.1.1",
318 ["1.1.1.1", "1.1.1.2", "1.1.1.3"]))
319
320 # search for the corresponding state created in VPP
321 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
322 self.assertEqual(len(dump), 3)
323 for s in h2.sg.saddrs:
324 self.assertTrue(find_igmp_state(dump, self.pg0,
325 "239.1.1.1", s))
326 #
327 # Send a general query (to the all router's address)
328 # expect VPP to respond with a membership report will all sources
329 #
330 self.send(self.pg0, p_g)
331
332 capture = self.pg0.get_capture(1, timeout=10)
333 self.verify_report(capture[0],
334 [IgmpRecord(h2.sg, "Mode Is Include")])
335
336 #
337 # Group and source specific query; some present some not
338 #
339 p_gs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
340 IP(src=self.pg0.remote_ip4, dst='239.1.1.1', tos=0xc0,
341 options=[IPOption(copy_flag=1, optclass="control",
342 option="router_alert")]) /
343 IGMPv3(type="Membership Query", mrcode=100) /
344 IGMPv3mq(gaddr="239.1.1.1",
345 srcaddrs=["1.1.1.1", "1.1.1.2", "1.1.1.4"]))
346
347 self.send(self.pg0, p_gs)
348
349 capture = self.pg0.get_capture(1, timeout=10)
350 self.verify_report(capture[0],
351 [IgmpRecord(
352 IgmpSG('239.1.1.1', ["1.1.1.1", "1.1.1.2"]),
353 "Mode Is Include")])
354
355 #
356 # add loads more groups
357 #
358 h3 = self.add_group(self.pg0,
359 IgmpSG("239.1.1.2",
360 ["2.1.1.1", "2.1.1.2", "2.1.1.3"]))
361 h4 = self.add_group(self.pg0,
362 IgmpSG("239.1.1.3",
363 ["3.1.1.1", "3.1.1.2", "3.1.1.3"]))
364 h5 = self.add_group(self.pg0,
365 IgmpSG("239.1.1.4",
366 ["4.1.1.1", "4.1.1.2", "4.1.1.3"]))
367 h6 = self.add_group(self.pg0,
368 IgmpSG("239.1.1.5",
369 ["5.1.1.1", "5.1.1.2", "5.1.1.3"]))
370 h7 = self.add_group(self.pg0,
371 IgmpSG("239.1.1.6",
372 ["6.1.1.1", "6.1.1.2",
373 "6.1.1.3", "6.1.1.4",
374 "6.1.1.5", "6.1.1.6",
375 "6.1.1.7", "6.1.1.8",
376 "6.1.1.9", "6.1.1.10",
377 "6.1.1.11", "6.1.1.12",
378 "6.1.1.13", "6.1.1.14",
379 "6.1.1.15", "6.1.1.16"]))
380
381 #
382 # general query.
383 # the order the groups come in is not important, so what is
384 # checked for is what VPP is sending today.
385 #
386 self.send(self.pg0, p_g)
387
388 capture = self.pg0.get_capture(1, timeout=10)
389
390 self.verify_report(capture[0],
391 [IgmpRecord(h3.sg, "Mode Is Include"),
392 IgmpRecord(h2.sg, "Mode Is Include"),
393 IgmpRecord(h6.sg, "Mode Is Include"),
394 IgmpRecord(h4.sg, "Mode Is Include"),
395 IgmpRecord(h5.sg, "Mode Is Include"),
396 IgmpRecord(h7.sg, "Mode Is Include")])
397
398 #
399 # modify a group to add and remove some sources
400 #
401 h7.sg = IgmpSG("239.1.1.6",
402 ["6.1.1.1", "6.1.1.2",
403 "6.1.1.5", "6.1.1.6",
404 "6.1.1.7", "6.1.1.8",
405 "6.1.1.9", "6.1.1.10",
406 "6.1.1.11", "6.1.1.12",
407 "6.1.1.13", "6.1.1.14",
408 "6.1.1.15", "6.1.1.16",
409 "6.1.1.17", "6.1.1.18"])
410
411 self.pg_enable_capture(self.pg_interfaces)
412 self.pg_start()
413 h7.add_vpp_config()
414
415 capture = self.pg0.get_capture(1, timeout=10)
416 self.verify_report(capture[0],
417 [IgmpRecord(IgmpSG("239.1.1.6",
418 ["6.1.1.17", "6.1.1.18"]),
419 "Allow New Sources"),
420 IgmpRecord(IgmpSG("239.1.1.6",
421 ["6.1.1.3", "6.1.1.4"]),
422 "Block Old Sources")])
423
424 #
425 # add an additional groups with many sources so that each group
426 # consumes the link MTU. We should therefore see multiple state
427 # state reports when queried.
428 #
429 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [560, 0, 0, 0])
430
431 src_list = []
432 for i in range(128):
433 src_list.append("10.1.1.%d" % i)
434
435 h8 = self.add_group(self.pg0,
436 IgmpSG("238.1.1.1", src_list))
437 h9 = self.add_group(self.pg0,
438 IgmpSG("238.1.1.2", src_list))
439
440 self.send(self.pg0, p_g)
441
442 capture = self.pg0.get_capture(4, timeout=10)
443
444 self.verify_report(capture[0],
445 [IgmpRecord(h3.sg, "Mode Is Include"),
446 IgmpRecord(h2.sg, "Mode Is Include"),
447 IgmpRecord(h6.sg, "Mode Is Include"),
448 IgmpRecord(h4.sg, "Mode Is Include"),
449 IgmpRecord(h5.sg, "Mode Is Include")])
450 self.verify_report(capture[1],
451 [IgmpRecord(h8.sg, "Mode Is Include")])
452 self.verify_report(capture[2],
453 [IgmpRecord(h7.sg, "Mode Is Include")])
454 self.verify_report(capture[3],
455 [IgmpRecord(h9.sg, "Mode Is Include")])
456
457 #
458 # drop the MTU further (so a 128 sized group won't fit)
459 #
460 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [512, 0, 0, 0])
461
462 self.pg_enable_capture(self.pg_interfaces)
463 self.pg_start()
464
465 h10 = VppHostState(self,
466 IGMP_FILTER.INCLUDE,
467 self.pg0.sw_if_index,
468 IgmpSG("238.1.1.3", src_list))
469 h10.add_vpp_config()
470
471 capture = self.pg0.get_capture(2, timeout=10)
Andrew Yourtchenkocb265c62019-07-25 10:03:51 +0000472 # wait for a little bit
473 self.sleep(1)
Neale Ranns947ea622018-06-07 23:48:20 -0700474
475 #
476 # remove state, expect the report for the removal
477 # the dump should be empty
478 #
479 self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [600, 0, 0, 0])
480 self.remove_group(h8)
481 self.remove_group(h9)
482 self.remove_group(h2)
483 self.remove_group(h3)
484 self.remove_group(h4)
485 self.remove_group(h5)
486 self.remove_group(h6)
487 self.remove_group(h7)
488 self.remove_group(h10)
489
490 self.logger.info(self.vapi.cli("sh igmp config"))
491 self.assertFalse(self.vapi.igmp_dump())
492
493 #
494 # TODO
495 # ADD STATE ON MORE INTERFACES
496 #
497
498 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
499 0,
500 IGMP_MODE.HOST)
501
502 def test_igmp_router(self):
503 """ IGMP Router Functions """
504
505 #
506 # Drop reports when not enabled
507 #
508 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
509 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
510 options=[IPOption(copy_flag=1, optclass="control",
511 option="router_alert")]) /
512 IGMPv3(type="Version 3 Membership Report") /
513 IGMPv3mr(numgrp=1) /
514 IGMPv3gr(rtype="Allow New Sources",
515 maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
516 p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
517 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
518 options=[IPOption(copy_flag=1, optclass="control",
519 option="router_alert")]) /
520 IGMPv3(type="Version 3 Membership Report") /
521 IGMPv3mr(numgrp=1) /
522 IGMPv3gr(rtype="Block Old Sources",
523 maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
524
525 self.send(self.pg0, p_j)
526 self.assertFalse(self.vapi.igmp_dump())
527
528 #
529 # drop the default timer values so these tests execute in a
530 # reasonable time frame
531 #
532 self.vapi.cli("test igmp timers query 1 src 3 leave 1")
533
534 #
535 # enable router functions on the interface
536 #
537 self.pg_enable_capture(self.pg_interfaces)
538 self.pg_start()
539 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
540 1,
541 IGMP_MODE.ROUTER)
542 self.vapi.want_igmp_events(1)
543
544 #
545 # wait for router to send general query
546 #
547 for ii in range(3):
548 capture = self.pg0.get_capture(1, timeout=2)
549 self.verify_general_query(capture[0])
550 self.pg_enable_capture(self.pg_interfaces)
551 self.pg_start()
552
553 #
554 # re-send the report. VPP should now hold state for the new group
555 # VPP sends a notification that a new group has been joined
556 #
557 self.send(self.pg0, p_j)
558
559 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
560 "239.1.1.1", "10.1.1.1", 1))
561 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
562 "239.1.1.1", "10.1.1.2", 1))
563 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
564 self.assertEqual(len(dump), 2)
565 self.assertTrue(find_igmp_state(dump, self.pg0,
566 "239.1.1.1", "10.1.1.1"))
567 self.assertTrue(find_igmp_state(dump, self.pg0,
568 "239.1.1.1", "10.1.1.2"))
569
570 #
571 # wait for the per-source timer to expire
572 # the state should be reaped
573 # VPP sends a notification that the group has been left
574 #
575 self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
576 "239.1.1.1", "10.1.1.1", 0))
577 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
578 "239.1.1.1", "10.1.1.2", 0))
579 self.assertFalse(self.vapi.igmp_dump())
580
581 #
582 # resend the join. wait for two queries and then send a current-state
Paul Vinciguerra8feeaff2019-03-27 11:25:48 -0700583 # record to include all sources. this should reset the expiry time
Neale Ranns947ea622018-06-07 23:48:20 -0700584 # on the sources and thus they will still be present in 2 seconds time.
585 # If the source timer was not refreshed, then the state would have
586 # expired in 3 seconds.
587 #
588 self.send(self.pg0, p_j)
589 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
590 "239.1.1.1", "10.1.1.1", 1))
591 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
592 "239.1.1.1", "10.1.1.2", 1))
593 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
594 self.assertEqual(len(dump), 2)
595
596 capture = self.pg0.get_capture(2, timeout=3)
597 self.verify_general_query(capture[0])
598 self.verify_general_query(capture[1])
599
600 p_cs = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
601 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0,
602 options=[IPOption(copy_flag=1, optclass="control",
603 option="router_alert")]) /
604 IGMPv3(type="Version 3 Membership Report") /
605 IGMPv3mr(numgrp=1) /
606 IGMPv3gr(rtype="Mode Is Include",
607 maddr="239.1.1.1", srcaddrs=["10.1.1.1", "10.1.1.2"]))
608
609 self.send(self.pg0, p_cs)
610
611 self.sleep(2)
612 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
613 self.assertEqual(len(dump), 2)
614 self.assertTrue(find_igmp_state(dump, self.pg0,
615 "239.1.1.1", "10.1.1.1"))
616 self.assertTrue(find_igmp_state(dump, self.pg0,
617 "239.1.1.1", "10.1.1.2"))
618
619 #
620 # wait for the per-source timer to expire
621 # the state should be reaped
622 #
623 self.assertTrue(wait_for_igmp_event(self, 4, self.pg0,
624 "239.1.1.1", "10.1.1.1", 0))
625 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
626 "239.1.1.1", "10.1.1.2", 0))
627 self.assertFalse(self.vapi.igmp_dump())
628
629 #
Paul Vinciguerra8feeaff2019-03-27 11:25:48 -0700630 # resend the join, then a leave. Router sends a group+source
Neale Ranns947ea622018-06-07 23:48:20 -0700631 # specific query containing both sources
632 #
633 self.send(self.pg0, p_j)
634
635 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
636 "239.1.1.1", "10.1.1.1", 1))
637 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
638 "239.1.1.1", "10.1.1.2", 1))
639 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
640 self.assertEqual(len(dump), 2)
641
642 self.send(self.pg0, p_l)
643 capture = self.pg0.get_capture(1, timeout=3)
644 self.verify_group_query(capture[0], "239.1.1.1",
645 ["10.1.1.1", "10.1.1.2"])
646
647 #
648 # the group specific query drops the timeout to leave (=1) seconds
649 #
650 self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
651 "239.1.1.1", "10.1.1.1", 0))
652 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
653 "239.1.1.1", "10.1.1.2", 0))
654 self.assertFalse(self.vapi.igmp_dump())
655 self.assertFalse(self.vapi.igmp_dump())
656
657 #
Neale Ranns0f7af532018-11-06 05:51:58 -0800658 # a TO_EX({}) / IN_EX({}) is treated like a (*,G) join
Neale Rannsc17776e2018-09-26 06:51:39 -0700659 #
660 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
661 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
662 options=[IPOption(copy_flag=1, optclass="control",
663 option="router_alert")]) /
664 IGMPv3(type="Version 3 Membership Report") /
665 IGMPv3mr(numgrp=1) /
Neale Ranns0f7af532018-11-06 05:51:58 -0800666 IGMPv3gr(rtype="Change To Exclude Mode", maddr="239.1.1.2"))
Neale Rannsc17776e2018-09-26 06:51:39 -0700667
668 self.send(self.pg0, p_j)
669
670 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
671 "239.1.1.2", "0.0.0.0", 1))
672
Neale Ranns0f7af532018-11-06 05:51:58 -0800673 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
674 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
675 options=[IPOption(copy_flag=1, optclass="control",
676 option="router_alert")]) /
677 IGMPv3(type="Version 3 Membership Report") /
678 IGMPv3mr(numgrp=1) /
679 IGMPv3gr(rtype="Mode Is Exclude", maddr="239.1.1.3"))
680
681 self.send(self.pg0, p_j)
682
683 self.assertTrue(wait_for_igmp_event(self, 1, self.pg0,
684 "239.1.1.3", "0.0.0.0", 1))
685
686 #
Paul Vinciguerra8feeaff2019-03-27 11:25:48 -0700687 # A 'allow sources' for {} should be ignored as it should
Neale Ranns0f7af532018-11-06 05:51:58 -0800688 # never be sent.
689 #
690 p_j = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
691 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
692 options=[IPOption(copy_flag=1, optclass="control",
693 option="router_alert")]) /
694 IGMPv3(type="Version 3 Membership Report") /
695 IGMPv3mr(numgrp=1) /
696 IGMPv3gr(rtype="Allow New Sources", maddr="239.1.1.4"))
697
698 self.send(self.pg0, p_j)
699
700 dump = self.vapi.igmp_dump(self.pg0.sw_if_index)
701 self.assertTrue(find_igmp_state(dump, self.pg0,
702 "239.1.1.2", "0.0.0.0"))
703 self.assertTrue(find_igmp_state(dump, self.pg0,
704 "239.1.1.3", "0.0.0.0"))
705 self.assertFalse(find_igmp_state(dump, self.pg0,
706 "239.1.1.4", "0.0.0.0"))
707
708 #
709 # a TO_IN({}) and IS_IN({}) are treated like a (*,G) leave
710 #
711 self.vapi.cli("set logging class igmp level debug")
712 p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
713 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
714 options=[IPOption(copy_flag=1, optclass="control",
715 option="router_alert")]) /
716 IGMPv3(type="Version 3 Membership Report") /
717 IGMPv3mr(numgrp=1) /
718 IGMPv3gr(rtype="Change To Include Mode", maddr="239.1.1.2"))
719
720 self.send(self.pg0, p_l)
721 self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
722 "239.1.1.2", "0.0.0.0", 0))
723
724 p_l = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
725 IP(src=self.pg0.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
726 options=[IPOption(copy_flag=1, optclass="control",
727 option="router_alert")]) /
728 IGMPv3(type="Version 3 Membership Report") /
729 IGMPv3mr(numgrp=1) /
730 IGMPv3gr(rtype="Mode Is Include", maddr="239.1.1.3"))
731
732 self.send(self.pg0, p_l)
733
734 self.assertTrue(wait_for_igmp_event(self, 2, self.pg0,
735 "239.1.1.3", "0.0.0.0", 0))
736 self.assertFalse(self.vapi.igmp_dump(self.pg0.sw_if_index))
737
Neale Rannsc17776e2018-09-26 06:51:39 -0700738 #
Neale Ranns947ea622018-06-07 23:48:20 -0700739 # disable router config
740 #
741 self.vapi.igmp_enable_disable(self.pg0.sw_if_index,
742 0,
743 IGMP_MODE.ROUTER)
744
Jakub Grajciar97748ca2018-10-04 11:05:35 +0200745 def _create_igmpv3_pck(self, itf, rtype, maddr, srcaddrs):
746 p = (Ether(dst=itf.local_mac, src=itf.remote_mac) /
747 IP(src=itf.remote_ip4, dst="224.0.0.22", tos=0xc0, ttl=1,
748 options=[IPOption(copy_flag=1, optclass="control",
749 option="router_alert")]) /
750 IGMPv3(type="Version 3 Membership Report") /
751 IGMPv3mr(numgrp=1) /
752 IGMPv3gr(rtype=rtype,
753 maddr=maddr, srcaddrs=srcaddrs))
754 return p
755
756 def test_igmp_proxy_device(self):
757 """ IGMP proxy device """
758 self.pg2.admin_down()
759 self.pg2.unconfig_ip4()
760 self.pg2.set_table_ip4(0)
761 self.pg2.config_ip4()
762 self.pg2.admin_up()
763
764 self.vapi.cli('test igmp timers query 10 src 3 leave 1')
765
766 # enable IGMP
767 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 1, IGMP_MODE.HOST)
768 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 1,
769 IGMP_MODE.ROUTER)
770 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 1,
771 IGMP_MODE.ROUTER)
772
773 # create IGMP proxy device
774 self.vapi.igmp_proxy_device_add_del(0, self.pg0.sw_if_index, 1)
775 self.vapi.igmp_proxy_device_add_del_interface(0,
776 self.pg1.sw_if_index, 1)
777 self.vapi.igmp_proxy_device_add_del_interface(0,
778 self.pg2.sw_if_index, 1)
779
780 # send join on pg1. join should be proxied by pg0
781 p_j = self._create_igmpv3_pck(self.pg1, "Allow New Sources",
782 "239.1.1.1", ["10.1.1.1", "10.1.1.2"])
783 self.send(self.pg1, p_j)
784
785 capture = self.pg0.get_capture(1, timeout=1)
786 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
787 ["10.1.1.1", "10.1.1.2"]), "Allow New Sources")])
788 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
789
790 # send join on pg2. join should be proxied by pg0.
791 # the group should contain only 10.1.1.3 as
792 # 10.1.1.1 was already reported
793 p_j = self._create_igmpv3_pck(self.pg2, "Allow New Sources",
794 "239.1.1.1", ["10.1.1.1", "10.1.1.3"])
795 self.send(self.pg2, p_j)
796
797 capture = self.pg0.get_capture(1, timeout=1)
798 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
799 ["10.1.1.3"]), "Allow New Sources")])
800 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
801
802 # send leave on pg2. leave for 10.1.1.3 should be proxyed
803 # as pg2 was the only interface interested in 10.1.1.3
804 p_l = self._create_igmpv3_pck(self.pg2, "Block Old Sources",
805 "239.1.1.1", ["10.1.1.3"])
806 self.send(self.pg2, p_l)
807
808 capture = self.pg0.get_capture(1, timeout=2)
809 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
810 ["10.1.1.3"]), "Block Old Sources")])
811 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
812
813 # disable igmp on pg1 (also removes interface from proxy device)
814 # proxy leave for 10.1.1.2. pg2 is still interested in 10.1.1.1
815 self.pg_enable_capture(self.pg_interfaces)
816 self.vapi.igmp_enable_disable(self.pg1.sw_if_index, 0,
817 IGMP_MODE.ROUTER)
818
819 capture = self.pg0.get_capture(1, timeout=1)
820 self.verify_report(capture[0], [IgmpRecord(IgmpSG("239.1.1.1",
821 ["10.1.1.2"]), "Block Old Sources")])
822 self.assertTrue(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
823
824 # disable IGMP on pg0 and pg1.
825 # disabling IGMP on pg0 (proxy device upstream interface)
826 # removes this proxy device
827 self.vapi.igmp_enable_disable(self.pg0.sw_if_index, 0, IGMP_MODE.HOST)
828 self.vapi.igmp_enable_disable(self.pg2.sw_if_index, 0,
829 IGMP_MODE.ROUTER)
830 self.assertFalse(find_mroute(self, "239.1.1.1", "0.0.0.0", 32))
831
Jakub Grajciar7b867a82017-12-08 16:28:42 +0100832
833if __name__ == '__main__':
834 unittest.main(testRunner=VppTestRunner)