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