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