Tests Cleanup: Fix missing calls to setUpClass/tearDownClass.
[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     @classmethod
164     def tearDownClass(cls):
165         super(TestIP6VrfMultiInst, cls).tearDownClass()
166
167     def setUp(self):
168         """
169         Clear trace and packet infos before running each test.
170         """
171         super(TestIP6VrfMultiInst, self).setUp()
172         self.reset_packet_infos()
173
174     def tearDown(self):
175         """
176         Show various debug prints after each test.
177         """
178         super(TestIP6VrfMultiInst, self).tearDown()
179         if not self.vpp_dead:
180             self.logger.info(self.vapi.ppcli("show ip6 fib"))
181             self.logger.info(self.vapi.ppcli("show ip6 neighbors"))
182
183     def create_vrf_and_assign_interfaces(self, count, start=1):
184         """
185         Create required number of FIB tables / VRFs, put 3 pg-ip6 interfaces
186         to every FIB table / VRF.
187
188         :param int count: Number of FIB tables / VRFs to be created.
189         :param int start: Starting number of the FIB table / VRF ID. \
190         (Default value = 1)
191         """
192         for i in range(count):
193             vrf_id = i + start
194             pg_if = self.pg_if_by_vrf_id[vrf_id][0]
195             dest_addr = pg_if.local_ip6n
196             dest_addr_len = 64
197             self.vapi.ip_table_add_del(is_ipv6=1, is_add=1, table_id=vrf_id)
198             self.logger.info("IPv6 VRF ID %d created" % vrf_id)
199             if vrf_id not in self.vrf_list:
200                 self.vrf_list.append(vrf_id)
201             if vrf_id in self.vrf_reset_list:
202                 self.vrf_reset_list.remove(vrf_id)
203             for j in range(self.pg_ifs_per_vrf):
204                 pg_if = self.pg_if_by_vrf_id[vrf_id][j]
205                 pg_if.set_table_ip6(vrf_id)
206                 self.logger.info("pg-interface %s added to IPv6 VRF ID %d"
207                                  % (pg_if.name, vrf_id))
208                 if pg_if not in self.pg_in_vrf:
209                     self.pg_in_vrf.append(pg_if)
210                 if pg_if in self.pg_not_in_vrf:
211                     self.pg_not_in_vrf.remove(pg_if)
212                 pg_if.config_ip6()
213                 pg_if.disable_ipv6_ra()
214                 pg_if.configure_ipv6_neighbors()
215         self.logger.debug(self.vapi.ppcli("show ip6 fib"))
216         self.logger.debug(self.vapi.ppcli("show ip6 neighbors"))
217
218     def reset_vrf_and_remove_from_vrf_list(self, vrf_id):
219         """
220         Reset required FIB table / VRF and remove it from VRF list.
221
222         :param int vrf_id: The FIB table / VRF ID to be reset.
223         """
224         # self.vapi.reset_vrf(vrf_id, is_ipv6=1)
225         self.vapi.reset_fib(vrf_id, is_ipv6=1)
226         if vrf_id in self.vrf_list:
227             self.vrf_list.remove(vrf_id)
228         if vrf_id not in self.vrf_reset_list:
229             self.vrf_reset_list.append(vrf_id)
230         for j in range(self.pg_ifs_per_vrf):
231             pg_if = self.pg_if_by_vrf_id[vrf_id][j]
232             pg_if.unconfig_ip6()
233             if pg_if in self.pg_in_vrf:
234                 self.pg_in_vrf.remove(pg_if)
235             if pg_if not in self.pg_not_in_vrf:
236                 self.pg_not_in_vrf.append(pg_if)
237         self.logger.info("IPv6 VRF ID %d reset finished" % vrf_id)
238         self.logger.debug(self.vapi.ppcli("show ip6 fib"))
239         self.logger.debug(self.vapi.ppcli("show ip6 neighbors"))
240         self.vapi.ip_table_add_del(is_ipv6=1, is_add=0, table_id=vrf_id)
241
242     def create_stream(self, src_if, packet_sizes):
243         """
244         Create input packet stream for defined interface using hosts list.
245
246         :param object src_if: Interface to create packet stream for.
247         :param list packet_sizes: List of required packet sizes.
248         :return: Stream of packets.
249         """
250         pkts = []
251         src_hosts = src_if.remote_hosts
252         for dst_if in self.flows[src_if]:
253             for dst_host in dst_if.remote_hosts:
254                 src_host = random.choice(src_hosts)
255                 pkt_info = self.create_packet_info(src_if, dst_if)
256                 payload = self.info_to_payload(pkt_info)
257                 p = (Ether(dst=src_if.local_mac, src=src_host.mac) /
258                      IPv6(src=src_host.ip6, dst=dst_host.ip6) /
259                      UDP(sport=1234, dport=1234) /
260                      Raw(payload))
261                 pkt_info.data = p.copy()
262                 size = random.choice(packet_sizes)
263                 self.extend_packet(p, size)
264                 pkts.append(p)
265         self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
266                           % (src_if.name, len(pkts)))
267         return pkts
268
269     def create_stream_crosswise_vrf(self, src_if, vrf_id, packet_sizes):
270         """
271         Create input packet stream for negative test for leaking across
272         different VRFs for defined interface using hosts list.
273
274         :param object src_if: Interface to create packet stream for.
275         :param int vrf_id: The FIB table / VRF ID where src_if is assigned.
276         :param list packet_sizes: List of required packet sizes.
277         :return: Stream of packets.
278         """
279         pkts = []
280         src_hosts = src_if.remote_hosts
281         vrf_lst = list(self.vrf_list)
282         vrf_lst.remove(vrf_id)
283         for vrf in vrf_lst:
284             for dst_if in self.pg_if_by_vrf_id[vrf]:
285                 for dst_host in dst_if.remote_hosts:
286                     src_host = random.choice(src_hosts)
287                     pkt_info = self.create_packet_info(src_if, dst_if)
288                     payload = self.info_to_payload(pkt_info)
289                     p = (Ether(dst=src_if.local_mac, src=src_host.mac) /
290                          IPv6(src=src_host.ip6, dst=dst_host.ip6) /
291                          UDP(sport=1234, dport=1234) /
292                          Raw(payload))
293                     pkt_info.data = p.copy()
294                     size = random.choice(packet_sizes)
295                     self.extend_packet(p, size)
296                     pkts.append(p)
297         self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
298                           % (src_if.name, len(pkts)))
299         return pkts
300
301     def verify_capture(self, pg_if, capture):
302         """
303         Verify captured input packet stream for defined interface.
304
305         :param object pg_if: Interface to verify captured packet stream for.
306         :param list capture: Captured packet stream.
307         """
308         last_info = dict()
309         for i in self.pg_interfaces:
310             last_info[i.sw_if_index] = None
311         dst_sw_if_index = pg_if.sw_if_index
312         for packet in capture:
313             try:
314                 ip = packet[IPv6]
315                 udp = packet[UDP]
316                 payload_info = self.payload_to_info(packet[Raw])
317                 packet_index = payload_info.index
318                 self.assertEqual(payload_info.dst, dst_sw_if_index)
319                 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
320                                   (pg_if.name, payload_info.src, packet_index))
321                 next_info = self.get_next_packet_info_for_interface2(
322                     payload_info.src, dst_sw_if_index,
323                     last_info[payload_info.src])
324                 last_info[payload_info.src] = next_info
325                 self.assertIsNotNone(next_info)
326                 self.assertEqual(packet_index, next_info.index)
327                 saved_packet = next_info.data
328                 # Check standard fields
329                 self.assertEqual(ip.src, saved_packet[IPv6].src)
330                 self.assertEqual(ip.dst, saved_packet[IPv6].dst)
331                 self.assertEqual(udp.sport, saved_packet[UDP].sport)
332                 self.assertEqual(udp.dport, saved_packet[UDP].dport)
333             except:
334                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
335                 raise
336         for i in self.pg_interfaces:
337             remaining_packet = self.get_next_packet_info_for_interface2(
338                 i, dst_sw_if_index, last_info[i.sw_if_index])
339             self.assertIsNone(
340                 remaining_packet,
341                 "Port %u: Packet expected from source %u didn't arrive" %
342                 (dst_sw_if_index, i.sw_if_index))
343
344     def verify_vrf(self, vrf_id):
345         """
346         Check if the FIB table / VRF ID is configured.
347
348         :param int vrf_id: The FIB table / VRF ID to be verified.
349         :return: 1 if the FIB table / VRF ID is configured, otherwise return 0.
350         """
351         ip6_fib_dump = self.vapi.ip6_fib_dump()
352         vrf_exist = False
353         vrf_count = 0
354         for ip6_fib_details in ip6_fib_dump:
355             if ip6_fib_details.table_id == vrf_id:
356                 if not vrf_exist:
357                     vrf_exist = True
358                 addr = inet_ntop(socket.AF_INET6, ip6_fib_details.address)
359                 found = False
360                 for pg_if in self.pg_if_by_vrf_id[vrf_id]:
361                     if found:
362                         break
363                     for host in pg_if.remote_hosts:
364                         if str(addr) == str(host.ip6):
365                             vrf_count += 1
366                             found = True
367                             break
368         if not vrf_exist and vrf_count == 0:
369             self.logger.info("IPv6 VRF ID %d is not configured" % vrf_id)
370             return VRFState.not_configured
371         elif vrf_exist and vrf_count == 0:
372             self.logger.info("IPv6 VRF ID %d has been reset" % vrf_id)
373             return VRFState.reset
374         else:
375             self.logger.info("IPv6 VRF ID %d is configured" % vrf_id)
376             return VRFState.configured
377
378     def run_verify_test(self):
379         """
380         Create packet streams for all configured pg interfaces, send all \
381         prepared packet streams and verify that:
382             - all packets received correctly on all pg-ip6 interfaces assigned
383               to VRFs
384             - no packet received on all pg-ip6 interfaces not assigned to VRFs
385
386         :raise RuntimeError: If no packet captured on pg-ip6 interface assigned
387             to VRF or if any packet is captured on pg-ip6 interface not
388             assigned to VRF.
389         """
390         # Test
391         # Create incoming packet streams for packet-generator interfaces
392         for pg_if in self.pg_interfaces:
393             pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
394             pg_if.add_stream(pkts)
395
396         # Enable packet capture and start packet sending
397         self.pg_enable_capture(self.pg_interfaces)
398         self.pg_start()
399
400         # Verify
401         # Verify outgoing packet streams per packet-generator interface
402         for pg_if in self.pg_interfaces:
403             if pg_if in self.pg_in_vrf:
404                 capture = pg_if.get_capture(remark="interface is in VRF")
405                 self.verify_capture(pg_if, capture)
406             elif pg_if in self.pg_not_in_vrf:
407                 pg_if.assert_nothing_captured(remark="interface is not in VRF",
408                                               filter_out_fn=is_ipv6_misc_ext)
409                 self.logger.debug("No capture for interface %s" % pg_if.name)
410             else:
411                 raise Exception("Unknown interface: %s" % pg_if.name)
412
413     def run_crosswise_vrf_test(self):
414         """
415         Create packet streams for every pg-ip6 interface in VRF towards all
416         pg-ip6 interfaces in other VRFs, send all prepared packet streams and \
417         verify that:
418              - no packet received on all configured pg-ip6 interfaces
419
420         :raise RuntimeError: If any packet is captured on any pg-ip6 interface.
421         """
422         # Test
423         # Create incoming packet streams for packet-generator interfaces
424         for vrf_id in self.vrf_list:
425             for pg_if in self.pg_if_by_vrf_id[vrf_id]:
426                 pkts = self.create_stream_crosswise_vrf(
427                     pg_if, vrf_id, self.pg_if_packet_sizes)
428                 pg_if.add_stream(pkts)
429
430         # Enable packet capture and start packet sending
431         self.pg_enable_capture(self.pg_interfaces)
432         self.pg_start()
433
434         # Verify
435         # Verify outgoing packet streams per packet-generator interface
436         for pg_if in self.pg_interfaces:
437             pg_if.assert_nothing_captured(remark="interface is in other VRF",
438                                           filter_out_fn=is_ipv6_misc_ext)
439             self.logger.debug("No capture for interface %s" % pg_if.name)
440
441     def test_ip6_vrf_01(self):
442         """ IP6 VRF  Multi-instance test 1 - create 4 VRFs
443         """
444         # Config 1
445         # Create 4 VRFs
446         self.create_vrf_and_assign_interfaces(4)
447
448         # Verify 1
449         for vrf_id in self.vrf_list:
450             self.assert_equal(self.verify_vrf(vrf_id),
451                               VRFState.configured, VRFState)
452
453         # Test 1
454         self.run_verify_test()
455         self.run_crosswise_vrf_test()
456
457     def test_ip6_vrf_02(self):
458         """ IP6 VRF  Multi-instance test 2 - reset 2 VRFs
459         """
460         # Config 2
461         # Delete 2 VRFs
462         self.reset_vrf_and_remove_from_vrf_list(1)
463         self.reset_vrf_and_remove_from_vrf_list(2)
464
465         # Verify 2
466         for vrf_id in self.vrf_reset_list:
467             self.assert_equal(self.verify_vrf(vrf_id),
468                               VRFState.reset, VRFState)
469         for vrf_id in self.vrf_list:
470             self.assert_equal(self.verify_vrf(vrf_id),
471                               VRFState.configured, VRFState)
472
473         # Test 2
474         self.run_verify_test()
475         self.run_crosswise_vrf_test()
476
477         # Reset routes learned from ICMPv6 Neighbor Discovery
478         for vrf_id in self.vrf_reset_list:
479             self.reset_vrf_and_remove_from_vrf_list(vrf_id)
480
481     def test_ip6_vrf_03(self):
482         """ IP6 VRF  Multi-instance 3 - add 2 VRFs
483         """
484         # Config 3
485         # Add 1 of reset VRFs and 1 new VRF
486         self.create_vrf_and_assign_interfaces(1)
487         self.create_vrf_and_assign_interfaces(1, start=5)
488
489         # Verify 3
490         for vrf_id in self.vrf_reset_list:
491             self.assert_equal(self.verify_vrf(vrf_id),
492                               VRFState.reset, VRFState)
493         for vrf_id in self.vrf_list:
494             self.assert_equal(self.verify_vrf(vrf_id),
495                               VRFState.configured, VRFState)
496
497         # Test 3
498         self.run_verify_test()
499         self.run_crosswise_vrf_test()
500
501         # Reset routes learned from ICMPv6 Neighbor Discovery
502         for vrf_id in self.vrf_reset_list:
503             self.reset_vrf_and_remove_from_vrf_list(vrf_id)
504
505     def test_ip6_vrf_04(self):
506         """ IP6 VRF  Multi-instance test 4 - reset 4 VRFs
507         """
508         # Config 4
509         # Reset all VRFs (i.e. no VRF except VRF=0 configured)
510         for i in range(len(self.vrf_list)):
511             self.reset_vrf_and_remove_from_vrf_list(self.vrf_list[0])
512
513         # Verify 4
514         for vrf_id in self.vrf_reset_list:
515             self.assert_equal(self.verify_vrf(vrf_id),
516                               VRFState.reset, VRFState)
517         vrf_list_length = len(self.vrf_list)
518         self.assertEqual(
519             vrf_list_length, 0,
520             "List of configured VRFs is not empty: %s != 0" % vrf_list_length)
521
522         # Test 4
523         self.run_verify_test()
524         self.run_crosswise_vrf_test()
525
526
527 if __name__ == '__main__':
528     unittest.main(testRunner=VppTestRunner)