4547b38a45e87538b0d9d616743c92cde49833b5
[vpp.git] / test / test_ip6_vrf_multi_instance.py
1 #!/usr/bin/env python
2 """IP6 VRF Multi-instance Test Case HLD:
3
4 **NOTES:**
5     - higher number of pg-ip6 interfaces causes problems => only 15 pg-ip6 \
6     interfaces in 5 VRFs are tested
7     - jumbo packets in configuration with 15 pg-ip6 interfaces leads to \
8     problems too
9
10 **config 1**
11     - add 15 pg-ip6 interfaces
12     - configure 5 hosts per pg-ip6 interface
13     - configure 4 VRFs
14     - add 3 pg-ip6 interfaces per VRF
15
16 **test 1**
17     - send IP6 packets between all pg-ip6 interfaces in all VRF groups
18
19 **verify 1**
20     - check VRF data by parsing output of ip6_fib_dump API command
21     - all packets received correctly in case of pg-ip6 interfaces in the same
22     VRF
23     - no packet received in case of pg-ip6 interfaces not in VRF
24     - no packet received in case of pg-ip6 interfaces in different VRFs
25
26 **config 2**
27     - reset 2 VRFs
28
29 **test 2**
30     - send IP6 packets between all pg-ip6 interfaces in all VRF groups
31
32 **verify 2**
33     - check VRF data by parsing output of ip6_fib_dump API command
34     - all packets received correctly in case of pg-ip6 interfaces in the same
35     VRF
36     - no packet received in case of pg-ip6 interfaces not in VRF
37     - no packet received in case of pg-ip6 interfaces in different VRFs
38
39 **config 3**
40     - add 1 of reset VRFs and 1 new VRF
41
42 **test 3**
43     - send IP6 packets between all pg-ip6 interfaces in all VRF groups
44
45 **verify 3**
46     - check VRF data by parsing output of ip6_fib_dump API command
47     - all packets received correctly in case of pg-ip6 interfaces in the same
48     VRF
49     - no packet received in case of pg-ip6 interfaces not in VRF
50     - no packet received in case of pg-ip6 interfaces in different VRFs
51
52 **config 4**
53     - reset all VRFs (i.e. no VRF except VRF=0 created)
54
55 **test 4**
56     - send IP6 packets between all pg-ip6 interfaces in all VRF groups
57
58 **verify 4**
59     - check VRF data by parsing output of ip6_fib_dump API command
60     - all packets received correctly in case of pg-ip6 interfaces in the same
61     VRF
62     - no packet received in case of pg-ip6 interfaces not in VRF
63     - no packet received in case of pg-ip6 interfaces in different VRFs
64 """
65
66 import unittest
67 import random
68 import socket
69
70 from scapy.packet import Raw
71 from scapy.layers.l2 import Ether
72 from scapy.layers.inet6 import UDP, IPv6, ICMPv6ND_NS, ICMPv6ND_RA, \
73     RouterAlert, IPv6ExtHdrHopByHop
74 from scapy.utils6 import in6_ismaddr, in6_isllsnmaddr, in6_getAddrType
75 from scapy.pton_ntop import inet_ntop
76
77 from framework import VppTestCase, VppTestRunner
78 from util import ppp
79 from vrf import VRFState
80
81
82 def is_ipv6_misc_ext(p):
83     """ Is packet one of uninteresting IPv6 broadcasts (extended to filter out
84     ICMPv6 Neighbor Discovery - Neighbor Advertisement packets too)? """
85     if p.haslayer(ICMPv6ND_RA):
86         if in6_ismaddr(p[IPv6].dst):
87             return True
88     if p.haslayer(ICMPv6ND_NS):
89         if in6_isllsnmaddr(p[IPv6].dst):
90             return True
91     if p.haslayer(IPv6ExtHdrHopByHop):
92         for o in p[IPv6ExtHdrHopByHop].options:
93             if isinstance(o, RouterAlert):
94                 return True
95     return False
96
97
98 class TestIP6VrfMultiInst(VppTestCase):
99     """ IP6 VRF  Multi-instance Test Case """
100
101     @classmethod
102     def setUpClass(cls):
103         """
104         Perform standard class setup (defined by class method setUpClass in
105         class VppTestCase) before running the test case, set test case related
106         variables and configure VPP.
107         """
108         super(TestIP6VrfMultiInst, cls).setUpClass()
109
110         # Test variables
111         cls.hosts_per_pg = 5
112         cls.nr_of_vrfs = 5
113         cls.pg_ifs_per_vrf = 3
114
115         try:
116             # Create pg interfaces
117             cls.create_pg_interfaces(
118                 range(cls.nr_of_vrfs * cls.pg_ifs_per_vrf))
119
120             # Packet flows mapping pg0 -> pg1, pg2 etc.
121             cls.flows = dict()
122             for i in range(len(cls.pg_interfaces)):
123                 multiplicand = i / cls.pg_ifs_per_vrf
124                 pg_list = [
125                     cls.pg_interfaces[multiplicand * cls.pg_ifs_per_vrf + j]
126                     for j in range(cls.pg_ifs_per_vrf)
127                     if (multiplicand * cls.pg_ifs_per_vrf + j) != i]
128                 cls.flows[cls.pg_interfaces[i]] = pg_list
129
130             # Packet sizes - jumbo packet (9018 bytes) skipped
131             cls.pg_if_packet_sizes = [64, 512, 1518]
132
133             # Set up all interfaces
134             for pg_if in cls.pg_interfaces:
135                 pg_if.admin_up()
136                 pg_if.generate_remote_hosts(cls.hosts_per_pg)
137
138             # Create list of VRFs
139             cls.vrf_list = list()
140
141             # Create list of reset VRFs
142             cls.vrf_reset_list = list()
143
144             # Create list of pg_interfaces in VRFs
145             cls.pg_in_vrf = list()
146
147             # Create list of pg_interfaces not in VRFs
148             cls.pg_not_in_vrf = [pg_if for pg_if in cls.pg_interfaces]
149
150             # Create mapping of pg_interfaces to VRF IDs
151             cls.pg_if_by_vrf_id = dict()
152             for i in range(cls.nr_of_vrfs):
153                 vrf_id = i + 1
154                 pg_list = [
155                     cls.pg_interfaces[i * cls.pg_ifs_per_vrf + j]
156                     for j in range(cls.pg_ifs_per_vrf)]
157                 cls.pg_if_by_vrf_id[vrf_id] = pg_list
158
159         except Exception:
160             super(TestIP6VrfMultiInst, cls).tearDownClass()
161             raise
162
163     def setUp(self):
164         """
165         Clear trace and packet infos before running each test.
166         """
167         super(TestIP6VrfMultiInst, self).setUp()
168         self.reset_packet_infos()
169
170     def tearDown(self):
171         """
172         Show various debug prints after each test.
173         """
174         super(TestIP6VrfMultiInst, self).tearDown()
175         if not self.vpp_dead:
176             self.logger.info(self.vapi.ppcli("show ip6 fib"))
177             self.logger.info(self.vapi.ppcli("show ip6 neighbors"))
178
179     def create_vrf_and_assign_interfaces(self, count, start=1):
180         """
181         Create required number of FIB tables / VRFs, put 3 pg-ip6 interfaces
182         to every FIB table / VRF.
183
184         :param int count: Number of FIB tables / VRFs to be created.
185         :param int start: Starting number of the FIB table / VRF ID. \
186         (Default value = 1)
187         """
188         for i in range(count):
189             vrf_id = i + start
190             pg_if = self.pg_if_by_vrf_id[vrf_id][0]
191             dest_addr = pg_if.local_ip6n
192             dest_addr_len = 64
193             self.vapi.ip_table_add_del(vrf_id, is_add=1, is_ipv6=1)
194             self.vapi.ip_add_del_route(
195                 dest_addr, dest_addr_len, pg_if.local_ip6n, is_ipv6=1,
196                 table_id=vrf_id, is_multipath=1)
197             self.logger.info("IPv6 VRF ID %d created" % vrf_id)
198             if vrf_id not in self.vrf_list:
199                 self.vrf_list.append(vrf_id)
200             if vrf_id in self.vrf_reset_list:
201                 self.vrf_reset_list.remove(vrf_id)
202             for j in range(self.pg_ifs_per_vrf):
203                 pg_if = self.pg_if_by_vrf_id[vrf_id][j]
204                 pg_if.set_table_ip6(vrf_id)
205                 self.logger.info("pg-interface %s added to IPv6 VRF ID %d"
206                                  % (pg_if.name, vrf_id))
207                 if pg_if not in self.pg_in_vrf:
208                     self.pg_in_vrf.append(pg_if)
209                 if pg_if in self.pg_not_in_vrf:
210                     self.pg_not_in_vrf.remove(pg_if)
211                 pg_if.config_ip6()
212                 pg_if.disable_ipv6_ra()
213                 pg_if.configure_ipv6_neighbors()
214         self.logger.debug(self.vapi.ppcli("show ip6 fib"))
215         self.logger.debug(self.vapi.ppcli("show ip6 neighbors"))
216
217     def reset_vrf_and_remove_from_vrf_list(self, vrf_id):
218         """
219         Reset required FIB table / VRF and remove it from VRF list.
220
221         :param int vrf_id: The FIB table / VRF ID to be reset.
222         """
223         # self.vapi.reset_vrf(vrf_id, is_ipv6=1)
224         self.vapi.reset_fib(vrf_id, is_ipv6=1)
225         if vrf_id in self.vrf_list:
226             self.vrf_list.remove(vrf_id)
227         if vrf_id not in self.vrf_reset_list:
228             self.vrf_reset_list.append(vrf_id)
229         for j in range(self.pg_ifs_per_vrf):
230             pg_if = self.pg_if_by_vrf_id[vrf_id][j]
231             pg_if.unconfig_ip6()
232             if pg_if in self.pg_in_vrf:
233                 self.pg_in_vrf.remove(pg_if)
234             if pg_if not in self.pg_not_in_vrf:
235                 self.pg_not_in_vrf.append(pg_if)
236         self.logger.info("IPv6 VRF ID %d reset finished" % vrf_id)
237         self.logger.debug(self.vapi.ppcli("show ip6 fib"))
238         self.logger.debug(self.vapi.ppcli("show ip6 neighbors"))
239         self.vapi.ip_table_add_del(vrf_id, is_add=0, is_ipv6=1)
240
241     def create_stream(self, src_if, packet_sizes):
242         """
243         Create input packet stream for defined interface using hosts list.
244
245         :param object src_if: Interface to create packet stream for.
246         :param list packet_sizes: List of required packet sizes.
247         :return: Stream of packets.
248         """
249         pkts = []
250         src_hosts = src_if.remote_hosts
251         for dst_if in self.flows[src_if]:
252             for dst_host in dst_if.remote_hosts:
253                 src_host = random.choice(src_hosts)
254                 pkt_info = self.create_packet_info(src_if, dst_if)
255                 payload = self.info_to_payload(pkt_info)
256                 p = (Ether(dst=src_if.local_mac, src=src_host.mac) /
257                      IPv6(src=src_host.ip6, dst=dst_host.ip6) /
258                      UDP(sport=1234, dport=1234) /
259                      Raw(payload))
260                 pkt_info.data = p.copy()
261                 size = random.choice(packet_sizes)
262                 self.extend_packet(p, size)
263                 pkts.append(p)
264         self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
265                           % (src_if.name, len(pkts)))
266         return pkts
267
268     def create_stream_crosswise_vrf(self, src_if, vrf_id, packet_sizes):
269         """
270         Create input packet stream for negative test for leaking across
271         different VRFs for defined interface using hosts list.
272
273         :param object src_if: Interface to create packet stream for.
274         :param int vrf_id: The FIB table / VRF ID where src_if is assigned.
275         :param list packet_sizes: List of required packet sizes.
276         :return: Stream of packets.
277         """
278         pkts = []
279         src_hosts = src_if.remote_hosts
280         vrf_lst = list(self.vrf_list)
281         vrf_lst.remove(vrf_id)
282         for vrf in vrf_lst:
283             for dst_if in self.pg_if_by_vrf_id[vrf]:
284                 for dst_host in dst_if.remote_hosts:
285                     src_host = random.choice(src_hosts)
286                     pkt_info = self.create_packet_info(src_if, dst_if)
287                     payload = self.info_to_payload(pkt_info)
288                     p = (Ether(dst=src_if.local_mac, src=src_host.mac) /
289                          IPv6(src=src_host.ip6, dst=dst_host.ip6) /
290                          UDP(sport=1234, dport=1234) /
291                          Raw(payload))
292                     pkt_info.data = p.copy()
293                     size = random.choice(packet_sizes)
294                     self.extend_packet(p, size)
295                     pkts.append(p)
296         self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
297                           % (src_if.name, len(pkts)))
298         return pkts
299
300     def verify_capture(self, pg_if, capture):
301         """
302         Verify captured input packet stream for defined interface.
303
304         :param object pg_if: Interface to verify captured packet stream for.
305         :param list capture: Captured packet stream.
306         """
307         last_info = dict()
308         for i in self.pg_interfaces:
309             last_info[i.sw_if_index] = None
310         dst_sw_if_index = pg_if.sw_if_index
311         for packet in capture:
312             try:
313                 ip = packet[IPv6]
314                 udp = packet[UDP]
315                 payload_info = self.payload_to_info(str(packet[Raw]))
316                 packet_index = payload_info.index
317                 self.assertEqual(payload_info.dst, dst_sw_if_index)
318                 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
319                                   (pg_if.name, payload_info.src, packet_index))
320                 next_info = self.get_next_packet_info_for_interface2(
321                     payload_info.src, dst_sw_if_index,
322                     last_info[payload_info.src])
323                 last_info[payload_info.src] = next_info
324                 self.assertIsNotNone(next_info)
325                 self.assertEqual(packet_index, next_info.index)
326                 saved_packet = next_info.data
327                 # Check standard fields
328                 self.assertEqual(ip.src, saved_packet[IPv6].src)
329                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
330                 self.assertEqual(udp.sport, saved_packet[UDP].sport)
331                 self.assertEqual(udp.dport, saved_packet[UDP].dport)
332             except:
333                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
334                 raise
335         for i in self.pg_interfaces:
336             remaining_packet = self.get_next_packet_info_for_interface2(
337                 i, dst_sw_if_index, last_info[i.sw_if_index])
338             self.assertIsNone(
339                 remaining_packet,
340                 "Port %u: Packet expected from source %u didn't arrive" %
341                 (dst_sw_if_index, i.sw_if_index))
342
343     def verify_vrf(self, vrf_id):
344         """
345         Check if the FIB table / VRF ID is configured.
346
347         :param int vrf_id: The FIB table / VRF ID to be verified.
348         :return: 1 if the FIB table / VRF ID is configured, otherwise return 0.
349         """
350         ip6_fib_dump = self.vapi.ip6_fib_dump()
351         vrf_exist = False
352         vrf_count = 0
353         for ip6_fib_details in ip6_fib_dump:
354             if ip6_fib_details.table_id == vrf_id:
355                 if not vrf_exist:
356                     vrf_exist = True
357                 addr = inet_ntop(socket.AF_INET6, ip6_fib_details.address)
358                 found = False
359                 for pg_if in self.pg_if_by_vrf_id[vrf_id]:
360                     if found:
361                         break
362                     for host in pg_if.remote_hosts:
363                         if str(addr) == str(host.ip6):
364                             vrf_count += 1
365                             found = True
366                             break
367         if not vrf_exist and vrf_count == 0:
368             self.logger.info("IPv6 VRF ID %d is not configured" % vrf_id)
369             return VRFState.not_configured
370         elif vrf_exist and vrf_count == 0:
371             self.logger.info("IPv6 VRF ID %d has been reset" % vrf_id)
372             return VRFState.reset
373         else:
374             self.logger.info("IPv6 VRF ID %d is configured" % vrf_id)
375             return VRFState.configured
376
377     def run_verify_test(self):
378         """
379         Create packet streams for all configured pg interfaces, send all \
380         prepared packet streams and verify that:
381             - all packets received correctly on all pg-ip6 interfaces assigned
382               to VRFs
383             - no packet received on all pg-ip6 interfaces not assigned to VRFs
384
385         :raise RuntimeError: If no packet captured on pg-ip6 interface assigned
386             to VRF or if any packet is captured on pg-ip6 interface not
387             assigned to VRF.
388         """
389         # Test
390         # Create incoming packet streams for packet-generator interfaces
391         for pg_if in self.pg_interfaces:
392             pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
393             pg_if.add_stream(pkts)
394
395         # Enable packet capture and start packet sending
396         self.pg_enable_capture(self.pg_interfaces)
397         self.pg_start()
398
399         # Verify
400         # Verify outgoing packet streams per packet-generator interface
401         for pg_if in self.pg_interfaces:
402             if pg_if in self.pg_in_vrf:
403                 capture = pg_if.get_capture(remark="interface is in VRF")
404                 self.verify_capture(pg_if, capture)
405             elif pg_if in self.pg_not_in_vrf:
406                 pg_if.assert_nothing_captured(remark="interface is not in VRF",
407                                               filter_out_fn=is_ipv6_misc_ext)
408                 self.logger.debug("No capture for interface %s" % pg_if.name)
409             else:
410                 raise Exception("Unknown interface: %s" % pg_if.name)
411
412     def run_crosswise_vrf_test(self):
413         """
414         Create packet streams for every pg-ip6 interface in VRF towards all
415         pg-ip6 interfaces in other VRFs, send all prepared packet streams and \
416         verify that:
417              - no packet received on all configured pg-ip6 interfaces
418
419         :raise RuntimeError: If any packet is captured on any pg-ip6 interface.
420         """
421         # Test
422         # Create incoming packet streams for packet-generator interfaces
423         for vrf_id in self.vrf_list:
424             for pg_if in self.pg_if_by_vrf_id[vrf_id]:
425                 pkts = self.create_stream_crosswise_vrf(
426                     pg_if, vrf_id, self.pg_if_packet_sizes)
427                 pg_if.add_stream(pkts)
428
429         # Enable packet capture and start packet sending
430         self.pg_enable_capture(self.pg_interfaces)
431         self.pg_start()
432
433         # Verify
434         # Verify outgoing packet streams per packet-generator interface
435         for pg_if in self.pg_interfaces:
436             pg_if.assert_nothing_captured(remark="interface is in other VRF",
437                                           filter_out_fn=is_ipv6_misc_ext)
438             self.logger.debug("No capture for interface %s" % pg_if.name)
439
440     def test_ip6_vrf_01(self):
441         """ IP6 VRF  Multi-instance test 1 - create 4 VRFs
442         """
443         # Config 1
444         # Create 4 VRFs
445         self.create_vrf_and_assign_interfaces(4)
446
447         # Verify 1
448         for vrf_id in self.vrf_list:
449             self.assert_equal(self.verify_vrf(vrf_id),
450                               VRFState.configured, VRFState)
451
452         # Test 1
453         self.run_verify_test()
454         self.run_crosswise_vrf_test()
455
456     def test_ip6_vrf_02(self):
457         """ IP6 VRF  Multi-instance test 2 - reset 2 VRFs
458         """
459         # Config 2
460         # Delete 2 VRFs
461         self.reset_vrf_and_remove_from_vrf_list(1)
462         self.reset_vrf_and_remove_from_vrf_list(2)
463
464         # Verify 2
465         for vrf_id in self.vrf_reset_list:
466             self.assert_equal(self.verify_vrf(vrf_id),
467                               VRFState.reset, VRFState)
468         for vrf_id in self.vrf_list:
469             self.assert_equal(self.verify_vrf(vrf_id),
470                               VRFState.configured, VRFState)
471
472         # Test 2
473         self.run_verify_test()
474         self.run_crosswise_vrf_test()
475
476         # Reset routes learned from ICMPv6 Neighbor Discovery
477         for vrf_id in self.vrf_reset_list:
478             self.reset_vrf_and_remove_from_vrf_list(vrf_id)
479
480     def test_ip6_vrf_03(self):
481         """ IP6 VRF  Multi-instance 3 - add 2 VRFs
482         """
483         # Config 3
484         # Add 1 of reset VRFs and 1 new VRF
485         self.create_vrf_and_assign_interfaces(1)
486         self.create_vrf_and_assign_interfaces(1, start=5)
487
488         # Verify 3
489         for vrf_id in self.vrf_reset_list:
490             self.assert_equal(self.verify_vrf(vrf_id),
491                               VRFState.reset, VRFState)
492         for vrf_id in self.vrf_list:
493             self.assert_equal(self.verify_vrf(vrf_id),
494                               VRFState.configured, VRFState)
495
496         # Test 3
497         self.run_verify_test()
498         self.run_crosswise_vrf_test()
499
500         # Reset routes learned from ICMPv6 Neighbor Discovery
501         for vrf_id in self.vrf_reset_list:
502             self.reset_vrf_and_remove_from_vrf_list(vrf_id)
503
504     def test_ip6_vrf_04(self):
505         """ IP6 VRF  Multi-instance test 4 - reset 4 VRFs
506         """
507         # Config 4
508         # Reset all VRFs (i.e. no VRF except VRF=0 configured)
509         for i in range(len(self.vrf_list)):
510             self.reset_vrf_and_remove_from_vrf_list(self.vrf_list[0])
511
512         # Verify 4
513         for vrf_id in self.vrf_reset_list:
514             self.assert_equal(self.verify_vrf(vrf_id),
515                               VRFState.reset, VRFState)
516         vrf_list_length = len(self.vrf_list)
517         self.assertEqual(
518             vrf_list_length, 0,
519             "List of configured VRFs is not empty: %s != 0" % vrf_list_length)
520
521         # Test 4
522         self.run_verify_test()
523         self.run_crosswise_vrf_test()
524
525
526 if __name__ == '__main__':
527     unittest.main(testRunner=VppTestRunner)