aboutsummaryrefslogtreecommitdiffstats
path: root/test/test_vxlan.py
blob: 6bdcb258bcd37c5ee37ed43bccb27260f2563aee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#!/usr/bin/env python

import socket
from util import ip4n_range
import unittest
from framework import VppTestCase, VppTestRunner
from template_bd import BridgeDomain

from scapy.layers.l2 import Ether
from scapy.layers.inet import IP, UDP
from scapy.layers.vxlan import VXLAN
from scapy.utils import atol


class TestVxlan(BridgeDomain, VppTestCase):
    """ VXLAN Test Case """

    def __init__(self, *args):
        BridgeDomain.__init__(self)
        VppTestCase.__init__(self, *args)

    def encapsulate(self, pkt, vni):
        """
        Encapsulate the original payload frame by adding VXLAN header with its
        UDP, IP and Ethernet fields
        """
        return (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
                IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
                UDP(sport=self.dport, dport=self.dport, chksum=0) /
                VXLAN(vni=vni, flags=self.flags) /
                pkt)

    def encap_mcast(self, pkt, src_ip, src_mac, vni):
        """
        Encapsulate the original payload frame by adding VXLAN header with its
        UDP, IP and Ethernet fields
        """
        return (Ether(src=src_mac, dst=self.mcast_mac) /
                IP(src=src_ip, dst=self.mcast_ip4) /
                UDP(sport=self.dport, dport=self.dport, chksum=0) /
                VXLAN(vni=vni, flags=self.flags) /
                pkt)

    def decapsulate(self, pkt):
        """
        Decapsulate the original payload frame by removing VXLAN header
        """
        # check if is set I flag
        self.assertEqual(pkt[VXLAN].flags, int('0x8', 16))
        return pkt[VXLAN].payload

    # Method for checking VXLAN encapsulation.
    #
    def check_encapsulation(self, pkt, vni, local_only=False, mcast_pkt=False):
        # TODO: add error messages
        # Verify source MAC is VPP_MAC and destination MAC is MY_MAC resolved
        #  by VPP using ARP.
        self.assertEqual(pkt[Ether].src, self.pg0.local_mac)
        if not local_only:
            if not mcast_pkt:
                self.assertEqual(pkt[Ether].dst, self.pg0.remote_mac)
            else:
                self.assertEqual(pkt[Ether].dst, type(self).mcast_mac)
        # Verify VXLAN tunnel source IP is VPP_IP and destination IP is MY_IP.
        self.assertEqual(pkt[IP].src, self.pg0.local_ip4)
        if not local_only:
            if not mcast_pkt:
                self.assertEqual(pkt[IP].dst, self.pg0.remote_ip4)
            else:
                self.assertEqual(pkt[IP].dst, type(self).mcast_ip4)
        # Verify UDP destination port is VXLAN 4789, source UDP port could be
        #  arbitrary.
        self.assertEqual(pkt[UDP].dport, type(self).dport)
        # TODO: checksum check
        # Verify VNI
        self.assertEqual(pkt[VXLAN].vni, vni)

    @classmethod
    def create_vxlan_flood_test_bd(cls, vni, n_ucast_tunnels):
        # Create 10 ucast vxlan tunnels under bd
        ip_range_start = 10
        ip_range_end = ip_range_start + n_ucast_tunnels
        next_hop_address = cls.pg0.remote_ip4n
        for dest_ip4n in ip4n_range(next_hop_address, ip_range_start,
                                    ip_range_end):
            # add host route so dest_ip4n will not be resolved
            cls.vapi.ip_add_del_route(dest_ip4n, 32, next_hop_address)
            r = cls.vapi.vxlan_add_del_tunnel(
                src_addr=cls.pg0.local_ip4n,
                dst_addr=dest_ip4n,
                vni=vni)
            cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index, bd_id=vni)

    @classmethod
    def add_del_shared_mcast_dst_load(cls, is_add):
        """
        add or del tunnels sharing the same mcast dst
        to test vxlan ref_count mechanism
        """
        n_shared_dst_tunnels = 2000
        vni_start = 10000
        vni_end = vni_start + n_shared_dst_tunnels
        for vni in range(vni_start, vni_end):
            r = cls.vapi.vxlan_add_del_tunnel(
                src_addr=cls.pg0.local_ip4n,
                dst_addr=cls.mcast_ip4n,
                mcast_sw_if_index=1,
                vni=vni,
                is_add=is_add)
            if r.sw_if_index == 0xffffffff:
                raise "bad sw_if_index"

    @classmethod
    def add_shared_mcast_dst_load(cls):
        cls.add_del_shared_mcast_dst_load(is_add=1)

    @classmethod
    def del_shared_mcast_dst_load(cls):
        cls.add_del_shared_mcast_dst_load(is_add=0)

    @classmethod
    def add_del_mcast_tunnels_load(cls, is_add):
        """
        add or del tunnels to test vxlan stability
        """
        n_distinct_dst_tunnels = 200
        ip_range_start = 10
        ip_range_end = ip_range_start + n_distinct_dst_tunnels
        for dest_ip4n in ip4n_range(cls.mcast_ip4n, ip_range_start,
                                    ip_range_end):
            vni = bytearray(dest_ip4n)[3]
            cls.vapi.vxlan_add_del_tunnel(
                src_addr=cls.pg0.local_ip4n,
                dst_addr=dest_ip4n,
                mcast_sw_if_index=1,
                vni=vni,
                is_add=is_add)

    @classmethod
    def add_mcast_tunnels_load(cls):
        cls.add_del_mcast_tunnels_load(is_add=1)

    @classmethod
    def del_mcast_tunnels_load(cls):
        cls.add_del_mcast_tunnels_load(is_add=0)

    # Class method to start the VXLAN test case.
    #  Overrides setUpClass method in VppTestCase class.
    #  Python try..except statement is used to ensure that the tear down of
    #  the class will be executed even if exception is raised.
    #  @param cls The class pointer.
    @classmethod
    def setUpClass(cls):
        super(TestVxlan, cls).setUpClass()

        try:
            cls.dport = 4789
            cls.flags = 0x8

            # Create 2 pg interfaces.
            cls.create_pg_interfaces(range(4))
            for pg in cls.pg_interfaces:
                pg.admin_up()

            # Configure IPv4 addresses on VPP pg0.
            cls.pg0.config_ip4()

            # Resolve MAC address for VPP's IP address on pg0.
            cls.pg0.resolve_arp()

            # Our Multicast address
            cls.mcast_ip4 = '239.1.1.1'
            cls.mcast_ip4n = socket.inet_pton(socket.AF_INET, cls.mcast_ip4)
            iplong = atol(cls.mcast_ip4)
            cls.mcast_mac = "01:00:5e:%02x:%02x:%02x" % (
                (iplong >> 16) & 0x7F, (iplong >> 8) & 0xFF, iplong & 0xFF)

            # Create VXLAN VTEP on VPP pg0, and put vxlan_tunnel0 and pg1
            #  into BD.
            cls.single_tunnel_bd = 1
            r = cls.vapi.vxlan_add_del_tunnel(
                src_addr=cls.pg0.local_ip4n,
                dst_addr=cls.pg0.remote_ip4n,
                vni=cls.single_tunnel_bd)
            cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index,
                                                bd_id=cls.single_tunnel_bd)
            cls.vapi.sw_interface_set_l2_bridge(cls.pg1.sw_if_index,
                                                bd_id=cls.single_tunnel_bd)

            # Setup vni 2 to test multicast flooding
            cls.n_ucast_tunnels = 10
            cls.mcast_flood_bd = 2
            cls.create_vxlan_flood_test_bd(cls.mcast_flood_bd,
                                           cls.n_ucast_tunnels)
            r = cls.vapi.vxlan_add_del_tunnel(
                src_addr=cls.pg0.local_ip4n,
                dst_addr=cls.mcast_ip4n,
                mcast_sw_if_index=1,
                vni=cls.mcast_flood_bd)
            cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index,
                                                bd_id=cls.mcast_flood_bd)
            cls.vapi.sw_interface_set_l2_bridge(cls.pg2.sw_if_index,
                                                bd_id=cls.mcast_flood_bd)

            # Add and delete mcast tunnels to check stability
            cls.add_shared_mcast_dst_load()
            cls.add_mcast_tunnels_load()
            cls.del_shared_mcast_dst_load()
            cls.del_mcast_tunnels_load()

            # Setup vni 3 to test unicast flooding
            cls.ucast_flood_bd = 3
            cls.create_vxlan_flood_test_bd(cls.ucast_flood_bd,
                                           cls.n_ucast_tunnels)
            cls.vapi.sw_interface_set_l2_bridge(cls.pg3.sw_if_index,
                                                bd_id=cls.ucast_flood_bd)
        except Exception:
            super(TestVxlan, cls).tearDownClass()
            raise

    # Method to define VPP actions before tear down of the test case.
    #  Overrides tearDown method in VppTestCase class.
    #  @param self The object pointer.
    def tearDown(self):
        super(TestVxlan, self).tearDown()
        if not self.vpp_dead:
            self.logger.info(self.vapi.cli("show bridge-domain 1 detail"))
            self.logger.info(self.vapi.cli("show bridge-domain 2 detail"))
            self.logger.info(self.vapi.cli("show bridge-domain 3 detail"))
            self.logger.info(self.vapi.cli("show vxlan tunnel"))


if __name__ == '__main__':
    unittest.main(testRunner=VppTestRunner)
ass="bp">self.has_ip4_config = False self.ip4_table_id = 0 self._local_ip6 = VppIpPrefix("fd01:%x::1" % self.sw_if_index, 64) self.has_ip6_config = False self.ip6_table_id = 0 self._local_addr = {socket.AF_INET: self.local_ip4, socket.AF_INET6: self.local_ip6} self._remote_addr = {socket.AF_INET: self.remote_ip4, socket.AF_INET6: self.remote_ip6} r = self.test.vapi.sw_interface_dump(sw_if_index=self.sw_if_index) for intf in r: if intf.sw_if_index == self.sw_if_index: self._name = intf.interface_name.split(b'\0', 1)[0].decode('utf8') self._local_mac = bytes(intf.l2_address) self._dump = intf break else: raise Exception( "Could not find interface with sw_if_index %d " "in interface dump %s" % (self.sw_if_index, moves.reprlib.repr(r))) self._local_ip6_ll = VppIpAddress(mk_ll_addr(self.local_mac)) self._remote_ip6_ll = mk_ll_addr(self.remote_mac) def config_ip4(self): """Configure IPv4 address on the VPP interface.""" self.test.vapi.sw_interface_add_del_address( sw_if_index=self.sw_if_index, prefix=self._local_ip4.encode()) self.has_ip4_config = True def unconfig_ip4(self): """Remove IPv4 address on the VPP interface.""" try: if self.has_ip4_config: self.test.vapi.sw_interface_add_del_address( sw_if_index=self.sw_if_index, prefix=self._local_ip4.encode(), is_add=0) except AttributeError: self.has_ip4_config = False self.has_ip4_config = False def configure_ipv4_neighbors(self): """For every remote host assign neighbor's MAC to IPv4 addresses. :param vrf_id: The FIB table / VRF ID. (Default value = 0) """ for host in self._remote_hosts: self.test.vapi.ip_neighbor_add_del(self.sw_if_index, host.mac, host.ip4) def config_ip6(self): """Configure IPv6 address on the VPP interface.""" self.test.vapi.sw_interface_add_del_address( sw_if_index=self.sw_if_index, prefix=self._local_ip6.encode()) self.has_ip6_config = True def unconfig_ip6(self): """Remove IPv6 address on the VPP interface.""" try: if self.has_ip6_config: self.test.vapi.sw_interface_add_del_address( sw_if_index=self.sw_if_index, prefix=self._local_ip6.encode(), is_add=0) except AttributeError: self.has_ip6_config = False self.has_ip6_config = False def configure_ipv6_neighbors(self): """For every remote host assign neighbor's MAC to IPv6 addresses. :param vrf_id: The FIB table / VRF ID. (Default value = 0) """ for host in self._remote_hosts: self.test.vapi.ip_neighbor_add_del(self.sw_if_index, host.mac, host.ip6) def unconfig(self): """Unconfigure IPv6 and IPv4 address on the VPP interface.""" self.unconfig_ip4() self.unconfig_ip6() def set_table_ip4(self, table_id): """Set the interface in a IPv4 Table. .. note:: Must be called before configuring IP4 addresses. """ self.ip4_table_id = table_id self.test.vapi.sw_interface_set_table( self.sw_if_index, 0, self.ip4_table_id) def set_table_ip6(self, table_id): """Set the interface in a IPv6 Table. .. note:: Must be called before configuring IP6 addresses. """ self.ip6_table_id = table_id self.test.vapi.sw_interface_set_table( self.sw_if_index, 1, self.ip6_table_id) def disable_ipv6_ra(self): """Configure IPv6 RA suppress on the VPP interface.""" self.test.vapi.sw_interface_ip6nd_ra_config( sw_if_index=self.sw_if_index, suppress=1) def ip6_ra_config(self, no=0, suppress=0, send_unicast=0): """Configure IPv6 RA suppress on the VPP interface.""" self.test.vapi.sw_interface_ip6nd_ra_config( sw_if_index=self.sw_if_index, is_no=no, suppress=suppress, send_unicast=send_unicast) def ip6_ra_prefix(self, prefix, is_no=0, off_link=0, no_autoconfig=0, use_default=0): """Configure IPv6 RA suppress on the VPP interface. prefix can be a string in the format of '<address>/<length_in_bits>' or ipaddress.ipnetwork object (if strict.)""" self.test.vapi.sw_interface_ip6nd_ra_prefix( sw_if_index=self.sw_if_index, prefix=prefix, use_default=use_default, off_link=off_link, no_autoconfig=no_autoconfig, is_no=is_no) def admin_up(self): """Put interface ADMIN-UP.""" self.test.vapi.sw_interface_set_flags( self.sw_if_index, flags=VppEnum.vl_api_if_status_flags_t.IF_STATUS_API_FLAG_ADMIN_UP) def admin_down(self): """Put interface ADMIN-down.""" self.test.vapi.sw_interface_set_flags(self.sw_if_index, flags=0) def link_up(self): """Put interface link-state-UP.""" self.test.vapi.cli("test interface link-state %s up" % self.name) def link_down(self): """Put interface link-state-down.""" self.test.vapi.cli("test interface link-state %s down" % self.name) def ip6_enable(self): """IPv6 Enable interface""" self.test.vapi.sw_interface_ip6_enable_disable(self.sw_if_index, enable=1) def ip6_disable(self): """Put interface ADMIN-DOWN.""" self.test.vapi.sw_interface_ip6_enable_disable(self.sw_if_index, enable=0) def add_sub_if(self, sub_if): """Register a sub-interface with this interface. :param sub_if: sub-interface """ if not hasattr(self, 'sub_if'): self.sub_if = sub_if else: if isinstance(self.sub_if, list): self.sub_if.append(sub_if) else: self.sub_if = sub_if def enable_mpls(self): """Enable MPLS on the VPP interface.""" self.test.vapi.sw_interface_set_mpls_enable(self.sw_if_index) def disable_mpls(self): """Enable MPLS on the VPP interface.""" self.test.vapi.sw_interface_set_mpls_enable(self.sw_if_index, 0) def is_ip4_entry_in_fib_dump(self, dump): for i in dump: n = IPv4Network(text_type("%s/%d" % (self.local_ip4, self.local_ip4_prefix_len))) if i.route.prefix == n and \ i.route.table_id == self.ip4_table_id: return True return False def set_unnumbered(self, ip_sw_if_index): """ Set the interface to unnumbered via ip_sw_if_index """ self.test.vapi.sw_interface_set_unnumbered(ip_sw_if_index, self.sw_if_index) def unset_unnumbered(self, ip_sw_if_index): """ Unset the interface to unnumbered via ip_sw_if_index """ self.test.vapi.sw_interface_set_unnumbered(ip_sw_if_index, self.sw_if_index, is_add=0) def set_proxy_arp(self, enable=1): """ Set the interface to enable/disable Proxy ARP """ self.test.vapi.proxy_arp_intfc_enable_disable( self.sw_if_index, enable) def query_vpp_config(self): dump = self.test.vapi.sw_interface_dump(sw_if_index=self.sw_if_index) return self.is_interface_config_in_dump(dump) def get_interface_config_from_dump(self, dump): for i in dump: if i.interface_name.rstrip(' \t\r\n\0') == self.name and \ i.sw_if_index == self.sw_if_index: return i else: return None def is_interface_config_in_dump(self, dump): return self.get_interface_config_from_dump(dump) is not None def assert_interface_state(self, admin_up_down, link_up_down, expect_event=False): if expect_event: event = self.test.vapi.wait_for_event(timeout=1, name='sw_interface_event') self.test.assert_equal(event.sw_if_index, self.sw_if_index, "sw_if_index") self.test.assert_equal((event.flags & 1), admin_up_down, "admin state") self.test.assert_equal((event.flags & 2), link_up_down, "link state") dump = self.test.vapi.sw_interface_dump() if_state = self.get_interface_config_from_dump(dump) self.test.assert_equal((if_state.flags & 1), admin_up_down, "admin state") self.test.assert_equal((if_state.flags & 2), link_up_down, "link state") def __str__(self): return self.name def get_rx_stats(self): c = self.test.statistics.get_counter("^/if/rx$") return c[0][self.sw_if_index] def get_tx_stats(self): c = self.test.statistics.get_counter("^/if/tx$") return c[0][self.sw_if_index]