blob: 2e0db430fa76f445483801ace37db52d752f5058 [file] [log] [blame]
Neale Ranns32e1c012016-11-22 17:07:28 +00001#!/usr/bin/env python
2
3import unittest
4
5from framework import VppTestCase, VppTestRunner
6from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
7from vpp_ip_route import IpMRoute, MRoutePath, MFibSignal
8
9from scapy.packet import Raw
10from scapy.layers.l2 import Ether
11from scapy.layers.inet import IP, UDP, getmacbyip
12from scapy.layers.inet6 import IPv6, getmacbyip6
13from util import ppp
14
15
16class MRouteItfFlags:
17 MFIB_ITF_FLAG_NONE = 0
18 MFIB_ITF_FLAG_NEGATE_SIGNAL = 1
19 MFIB_ITF_FLAG_ACCEPT = 2
20 MFIB_ITF_FLAG_FORWARD = 4
21 MFIB_ITF_FLAG_SIGNAL_PRESENT = 8
22 MFIB_ITF_FLAG_INTERNAL_COPY = 16
23
24
25class MRouteEntryFlags:
26 MFIB_ENTRY_FLAG_NONE = 0
27 MFIB_ENTRY_FLAG_SIGNAL = 1
28 MFIB_ENTRY_FLAG_DROP = 2
29 MFIB_ENTRY_FLAG_CONNECTED = 4
30 MFIB_ENTRY_FLAG_INHERIT_ACCEPT = 8
31
Neale Ranns9bea8fb2017-02-03 04:34:01 -080032#
33# The number of packets sent is set to 90 so that when we replicate more than 3
34# times, which we do for some entries, we will generate more than 256 packets
Neale Rannsaaa396a2017-02-05 09:12:02 -080035# to the next node in the VLIB graph. Thus we are testing the code's
36# correctness handling this over-flow
Neale Ranns9bea8fb2017-02-03 04:34:01 -080037#
38N_PKTS_IN_STREAM = 90
39
Neale Ranns32e1c012016-11-22 17:07:28 +000040
41class TestIPMcast(VppTestCase):
42 """ IP Multicast Test Case """
43
44 def setUp(self):
45 super(TestIPMcast, self).setUp()
46
47 # create 4 pg interfaces
48 self.create_pg_interfaces(range(4))
49
50 # setup interfaces
51 for i in self.pg_interfaces:
52 i.admin_up()
53 i.config_ip4()
54 i.config_ip6()
55 i.resolve_arp()
56 i.resolve_ndp()
57
58 def create_stream_ip4(self, src_if, src_ip, dst_ip):
59 pkts = []
Neale Ranns9bea8fb2017-02-03 04:34:01 -080060 for i in range(0, N_PKTS_IN_STREAM):
Neale Ranns32e1c012016-11-22 17:07:28 +000061 info = self.create_packet_info(src_if, src_if)
62 payload = self.info_to_payload(info)
63 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
64 IP(src=src_ip, dst=dst_ip) /
65 UDP(sport=1234, dport=1234) /
66 Raw(payload))
67 info.data = p.copy()
68 pkts.append(p)
69 return pkts
70
71 def create_stream_ip6(self, src_if, src_ip, dst_ip):
72 pkts = []
Neale Ranns9bea8fb2017-02-03 04:34:01 -080073 for i in range(0, N_PKTS_IN_STREAM):
Neale Ranns32e1c012016-11-22 17:07:28 +000074 info = self.create_packet_info(src_if, src_if)
75 payload = self.info_to_payload(info)
76 p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
77 IPv6(src=src_ip, dst=dst_ip) /
78 UDP(sport=1234, dport=1234) /
79 Raw(payload))
80 info.data = p.copy()
81 pkts.append(p)
82 return pkts
83
84 def verify_filter(self, capture, sent):
85 if not len(capture) == len(sent):
86 # filter out any IPv6 RAs from the captur
87 for p in capture:
88 if (p.haslayer(IPv6)):
89 capture.remove(p)
90 return capture
91
92 def verify_capture_ip4(self, src_if, sent):
Neale Ranns9bea8fb2017-02-03 04:34:01 -080093 rxd = self.pg1.get_capture(N_PKTS_IN_STREAM)
Neale Ranns32e1c012016-11-22 17:07:28 +000094
95 try:
96 capture = self.verify_filter(rxd, sent)
97
98 self.assertEqual(len(capture), len(sent))
99
100 for i in range(len(capture)):
101 tx = sent[i]
102 rx = capture[i]
103
104 # the rx'd packet has the MPLS label popped
105 eth = rx[Ether]
106 self.assertEqual(eth.type, 0x800)
107
108 tx_ip = tx[IP]
109 rx_ip = rx[IP]
110
111 # check the MAC address on the RX'd packet is correctly formed
112 self.assertEqual(eth.dst, getmacbyip(rx_ip.dst))
113
114 self.assertEqual(rx_ip.src, tx_ip.src)
115 self.assertEqual(rx_ip.dst, tx_ip.dst)
116 # IP processing post pop has decremented the TTL
117 self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
118
119 except:
120 raise
121
122 def verify_capture_ip6(self, src_if, sent):
Neale Ranns9bea8fb2017-02-03 04:34:01 -0800123 capture = self.pg1.get_capture(N_PKTS_IN_STREAM)
Neale Ranns32e1c012016-11-22 17:07:28 +0000124
125 self.assertEqual(len(capture), len(sent))
126
127 for i in range(len(capture)):
128 tx = sent[i]
129 rx = capture[i]
130
131 # the rx'd packet has the MPLS label popped
132 eth = rx[Ether]
133 self.assertEqual(eth.type, 0x86DD)
134
135 tx_ip = tx[IPv6]
136 rx_ip = rx[IPv6]
137
138 # check the MAC address on the RX'd packet is correctly formed
139 self.assertEqual(eth.dst, getmacbyip6(rx_ip.dst))
140
141 self.assertEqual(rx_ip.src, tx_ip.src)
142 self.assertEqual(rx_ip.dst, tx_ip.dst)
143 # IP processing post pop has decremented the TTL
144 self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
145
146 def test_ip_mcast(self):
147 """ IP Multicast Replication """
148
149 #
150 # a stream that matches the default route. gets dropped.
151 #
152 self.vapi.cli("clear trace")
153 tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
154 self.pg0.add_stream(tx)
155
156 self.pg_enable_capture(self.pg_interfaces)
157 self.pg_start()
158
159 self.pg0.assert_nothing_captured(
160 remark="IP multicast packets forwarded on default route")
161
162 #
163 # A (*,G).
164 # one accepting interface, pg0, 3 forwarding interfaces
165 #
166 route_232_1_1_1 = IpMRoute(
167 self,
168 "0.0.0.0",
169 "232.1.1.1", 32,
170 MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
171 [MRoutePath(self.pg0.sw_if_index,
172 MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
173 MRoutePath(self.pg1.sw_if_index,
174 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
175 MRoutePath(self.pg2.sw_if_index,
176 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
177 MRoutePath(self.pg3.sw_if_index,
178 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
179 route_232_1_1_1.add_vpp_config()
180
181 #
182 # An (S,G).
183 # one accepting interface, pg0, 2 forwarding interfaces
184 #
185 route_1_1_1_1_232_1_1_1 = IpMRoute(
186 self,
187 "1.1.1.1",
188 "232.1.1.1", 64,
189 MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
190 [MRoutePath(self.pg0.sw_if_index,
191 MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
192 MRoutePath(self.pg1.sw_if_index,
193 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
194 MRoutePath(self.pg2.sw_if_index,
195 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
196 route_1_1_1_1_232_1_1_1.add_vpp_config()
197
198 #
199 # An (*,G/m).
200 # one accepting interface, pg0, 1 forwarding interfaces
201 #
202 route_232 = IpMRoute(
203 self,
204 "0.0.0.0",
205 "232.0.0.0", 8,
206 MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
207 [MRoutePath(self.pg0.sw_if_index,
208 MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
209 MRoutePath(self.pg1.sw_if_index,
210 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
211 route_232.add_vpp_config()
212
213 #
214 # a stream that matches the route for (1.1.1.1,232.1.1.1)
215 #
216 self.vapi.cli("clear trace")
217 tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
218 self.pg0.add_stream(tx)
219
220 self.pg_enable_capture(self.pg_interfaces)
221 self.pg_start()
222
223 # We expect replications on Pg1, 2,
224 self.verify_capture_ip4(self.pg1, tx)
225 self.verify_capture_ip4(self.pg2, tx)
226
227 # no replications on Pg0
228 self.pg0.assert_nothing_captured(
229 remark="IP multicast packets forwarded on PG0")
230 self.pg3.assert_nothing_captured(
231 remark="IP multicast packets forwarded on PG3")
232
233 #
234 # a stream that matches the route for (*,232.0.0.0/8)
235 # Send packets with the 9th bit set so we test the correct clearing
236 # of that bit in the mac rewrite
237 #
238 self.vapi.cli("clear trace")
239 tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.255.255.255")
240 self.pg0.add_stream(tx)
241
242 self.pg_enable_capture(self.pg_interfaces)
243 self.pg_start()
244
245 # We expect replications on Pg1 only
246 self.verify_capture_ip4(self.pg1, tx)
247
248 # no replications on Pg0, Pg2 not Pg3
249 self.pg0.assert_nothing_captured(
250 remark="IP multicast packets forwarded on PG0")
251 self.pg2.assert_nothing_captured(
252 remark="IP multicast packets forwarded on PG2")
253 self.pg3.assert_nothing_captured(
254 remark="IP multicast packets forwarded on PG3")
255
256 #
257 # a stream that matches the route for (*,232.1.1.1)
258 #
259 self.vapi.cli("clear trace")
260 tx = self.create_stream_ip4(self.pg0, "1.1.1.2", "232.1.1.1")
261 self.pg0.add_stream(tx)
262
263 self.pg_enable_capture(self.pg_interfaces)
264 self.pg_start()
265
266 # We expect replications on Pg1, 2, 3.
267 self.verify_capture_ip4(self.pg1, tx)
268 self.verify_capture_ip4(self.pg2, tx)
269 self.verify_capture_ip4(self.pg3, tx)
270
271 # no replications on Pg0
272 self.pg0.assert_nothing_captured(
273 remark="IP multicast packets forwarded on PG0")
274
275 route_232_1_1_1.remove_vpp_config()
276 route_1_1_1_1_232_1_1_1.remove_vpp_config()
277 route_232.remove_vpp_config()
278
279 def test_ip6_mcast(self):
280 """ IPv6 Multicast Replication """
281
282 #
283 # a stream that matches the default route. gets dropped.
284 #
285 self.vapi.cli("clear trace")
286 tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
287 self.pg0.add_stream(tx)
288
289 self.pg_enable_capture(self.pg_interfaces)
290 self.pg_start()
291
292 self.pg0.assert_nothing_captured(
293 remark="IPv6 multicast packets forwarded on default route")
294
295 #
296 # A (*,G).
297 # one accepting interface, pg0, 3 forwarding interfaces
298 #
299 route_ff01_1 = IpMRoute(
300 self,
301 "::",
302 "ff01::1", 128,
303 MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
304 [MRoutePath(self.pg0.sw_if_index,
305 MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
306 MRoutePath(self.pg1.sw_if_index,
307 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
308 MRoutePath(self.pg2.sw_if_index,
309 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
310 MRoutePath(self.pg3.sw_if_index,
311 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
312 is_ip6=1)
313 route_ff01_1.add_vpp_config()
314
315 #
316 # An (S,G).
317 # one accepting interface, pg0, 2 forwarding interfaces
318 #
319 route_2001_ff01_1 = IpMRoute(
320 self,
321 "2001::1",
322 "ff01::1", 256,
323 MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
324 [MRoutePath(self.pg0.sw_if_index,
325 MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
326 MRoutePath(self.pg1.sw_if_index,
327 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
328 MRoutePath(self.pg2.sw_if_index,
329 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
330 is_ip6=1)
331 route_2001_ff01_1.add_vpp_config()
332
333 #
334 # An (*,G/m).
335 # one accepting interface, pg0, 1 forwarding interface
336 #
337 route_ff01 = IpMRoute(
338 self,
339 "::",
340 "ff01::", 16,
341 MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
342 [MRoutePath(self.pg0.sw_if_index,
343 MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
344 MRoutePath(self.pg1.sw_if_index,
345 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
346 is_ip6=1)
347 route_ff01.add_vpp_config()
348
349 #
350 # a stream that matches the route for (*, ff01::/16)
351 #
352 self.vapi.cli("clear trace")
353 tx = self.create_stream_ip6(self.pg0, "2002::1", "ff01:2::255")
354 self.pg0.add_stream(tx)
355
356 self.pg_enable_capture(self.pg_interfaces)
357 self.pg_start()
358
359 # We expect replications on Pg1
360 self.verify_capture_ip6(self.pg1, tx)
361
362 # no replications on Pg0, Pg3
363 self.pg0.assert_nothing_captured(
364 remark="IP multicast packets forwarded on PG0")
365 self.pg2.assert_nothing_captured(
366 remark="IP multicast packets forwarded on PG2")
367 self.pg3.assert_nothing_captured(
368 remark="IP multicast packets forwarded on PG3")
369
370 #
371 # a stream that matches the route for (*,ff01::1)
372 #
373 self.vapi.cli("clear trace")
374 tx = self.create_stream_ip6(self.pg0, "2002::2", "ff01::1")
375 self.pg0.add_stream(tx)
376
377 self.pg_enable_capture(self.pg_interfaces)
378 self.pg_start()
379
380 # We expect replications on Pg1, 2, 3.
381 self.verify_capture_ip6(self.pg1, tx)
382 self.verify_capture_ip6(self.pg2, tx)
383 self.verify_capture_ip6(self.pg3, tx)
384
385 # no replications on Pg0
386 self.pg0.assert_nothing_captured(
387 remark="IPv6 multicast packets forwarded on PG0")
388
389 #
390 # a stream that matches the route for (2001::1, ff00::1)
391 #
392 self.vapi.cli("clear trace")
393 tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
394 self.pg0.add_stream(tx)
395
396 self.pg_enable_capture(self.pg_interfaces)
397 self.pg_start()
398
399 # We expect replications on Pg1, 2,
400 self.verify_capture_ip6(self.pg1, tx)
401 self.verify_capture_ip6(self.pg2, tx)
402
403 # no replications on Pg0, Pg3
404 self.pg0.assert_nothing_captured(
405 remark="IP multicast packets forwarded on PG0")
406 self.pg3.assert_nothing_captured(
407 remark="IP multicast packets forwarded on PG3")
408
409 route_ff01.remove_vpp_config()
410 route_ff01_1.remove_vpp_config()
411 route_2001_ff01_1.remove_vpp_config()
412
413 def _mcast_connected_send_stream(self, dst_ip):
414 self.vapi.cli("clear trace")
415 tx = self.create_stream_ip4(self.pg0,
416 self.pg0.remote_ip4,
417 dst_ip)
418 self.pg0.add_stream(tx)
419
420 self.pg_enable_capture(self.pg_interfaces)
421 self.pg_start()
422
423 # We expect replications on Pg1.
424 self.verify_capture_ip4(self.pg1, tx)
425
426 return tx
427
428 def test_ip_mcast_connected(self):
429 """ IP Multicast Connected Source check """
430
431 #
432 # A (*,G).
433 # one accepting interface, pg0, 1 forwarding interfaces
434 #
435 route_232_1_1_1 = IpMRoute(
436 self,
437 "0.0.0.0",
438 "232.1.1.1", 32,
439 MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
440 [MRoutePath(self.pg0.sw_if_index,
441 MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
442 MRoutePath(self.pg1.sw_if_index,
443 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
444
445 route_232_1_1_1.add_vpp_config()
446 route_232_1_1_1.update_entry_flags(
447 MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED)
448
449 #
450 # Now the (*,G) is present, send from connected source
451 #
452 tx = self._mcast_connected_send_stream("232.1.1.1")
453
454 #
455 # Constrct a representation of the signal we expect on pg0
456 #
457 signal_232_1_1_1_itf_0 = MFibSignal(self,
458 route_232_1_1_1,
459 self.pg0.sw_if_index,
460 tx[0])
461
462 #
463 # read the only expected signal
464 #
465 signals = self.vapi.mfib_signal_dump()
466
467 self.assertEqual(1, len(signals))
468
469 signal_232_1_1_1_itf_0.compare(signals[0])
470
471 #
472 # reading the signal allows for the generation of another
473 # so send more packets and expect the next signal
474 #
475 tx = self._mcast_connected_send_stream("232.1.1.1")
476
477 signals = self.vapi.mfib_signal_dump()
478 self.assertEqual(1, len(signals))
479 signal_232_1_1_1_itf_0.compare(signals[0])
480
481 #
482 # A Second entry with connected check
483 # one accepting interface, pg0, 1 forwarding interfaces
484 #
485 route_232_1_1_2 = IpMRoute(
486 self,
487 "0.0.0.0",
488 "232.1.1.2", 32,
489 MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
490 [MRoutePath(self.pg0.sw_if_index,
491 MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
492 MRoutePath(self.pg1.sw_if_index,
493 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
494
495 route_232_1_1_2.add_vpp_config()
496 route_232_1_1_2.update_entry_flags(
497 MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED)
498
499 #
500 # Send traffic to both entries. One read should net us two signals
501 #
502 signal_232_1_1_2_itf_0 = MFibSignal(self,
503 route_232_1_1_2,
504 self.pg0.sw_if_index,
505 tx[0])
506 tx = self._mcast_connected_send_stream("232.1.1.1")
507 tx2 = self._mcast_connected_send_stream("232.1.1.2")
508
509 #
510 # read the only expected signal
511 #
512 signals = self.vapi.mfib_signal_dump()
513
514 self.assertEqual(2, len(signals))
515
516 signal_232_1_1_1_itf_0.compare(signals[1])
517 signal_232_1_1_2_itf_0.compare(signals[0])
518
519 route_232_1_1_1.remove_vpp_config()
520 route_232_1_1_2.remove_vpp_config()
521
522 def test_ip_mcast_signal(self):
523 """ IP Multicast Signal """
524
525 #
526 # A (*,G).
527 # one accepting interface, pg0, 1 forwarding interfaces
528 #
529 route_232_1_1_1 = IpMRoute(
530 self,
531 "0.0.0.0",
532 "232.1.1.1", 32,
533 MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
534 [MRoutePath(self.pg0.sw_if_index,
535 MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
536 MRoutePath(self.pg1.sw_if_index,
537 MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
538
539 route_232_1_1_1.add_vpp_config()
540 route_232_1_1_1.update_entry_flags(
541 MRouteEntryFlags.MFIB_ENTRY_FLAG_SIGNAL)
542
543 #
544 # Now the (*,G) is present, send from connected source
545 #
546 tx = self._mcast_connected_send_stream("232.1.1.1")
547
548 #
549 # Constrct a representation of the signal we expect on pg0
550 #
551 signal_232_1_1_1_itf_0 = MFibSignal(self,
552 route_232_1_1_1,
553 self.pg0.sw_if_index,
554 tx[0])
555
556 #
557 # read the only expected signal
558 #
559 signals = self.vapi.mfib_signal_dump()
560
561 self.assertEqual(1, len(signals))
562
563 signal_232_1_1_1_itf_0.compare(signals[0])
564
565 #
566 # reading the signal allows for the generation of another
567 # so send more packets and expect the next signal
568 #
569 tx = self._mcast_connected_send_stream("232.1.1.1")
570
571 signals = self.vapi.mfib_signal_dump()
572 self.assertEqual(1, len(signals))
573 signal_232_1_1_1_itf_0.compare(signals[0])
574
575 #
576 # Set the negate-signal on the accepting interval - the signals
577 # should stop
578 #
579 route_232_1_1_1.update_path_flags(
580 self.pg0.sw_if_index,
581 (MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT |
582 MRouteItfFlags.MFIB_ITF_FLAG_NEGATE_SIGNAL))
583
584 tx = self._mcast_connected_send_stream("232.1.1.1")
585
586 signals = self.vapi.mfib_signal_dump()
587 self.assertEqual(0, len(signals))
588
589 #
590 # Clear the SIGNAL flag on the entry and the signals should
591 # come back since the interface is still NEGATE-SIGNAL
592 #
593 route_232_1_1_1.update_entry_flags(
594 MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE)
595
596 tx = self._mcast_connected_send_stream("232.1.1.1")
597
598 signals = self.vapi.mfib_signal_dump()
599 self.assertEqual(1, len(signals))
600 signal_232_1_1_1_itf_0.compare(signals[0])
601
602 #
603 # Lastly remove the NEGATE-SIGNAL from the interface and the
604 # signals should stop
605 #
606 route_232_1_1_1.update_path_flags(self.pg0.sw_if_index,
607 MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT)
608
609 tx = self._mcast_connected_send_stream("232.1.1.1")
610 signals = self.vapi.mfib_signal_dump()
611 self.assertEqual(0, len(signals))
612
613 #
614 # Cleanup
615 #
616 route_232_1_1_1.remove_vpp_config()
617
618
619if __name__ == '__main__':
620 unittest.main(testRunner=VppTestRunner)