1#!/usr/bin/env python3
2
3import unittest
4import random
5
6from scapy.packet import Raw
7from scapy.layers.l2 import Ether, Dot1Q
8from scapy.layers.inet import IP, UDP
9
10from framework import VppTestCase, VppTestRunner
11from util import Host, ppp
12from vpp_sub_interface import VppDot1QSubint, VppDot1ADSubint
13
14
15class TestL2bd(VppTestCase):
16    """ L2BD Test Case """
17
18    @classmethod
19    def setUpClass(cls):
20        """
21        Perform standard class setup (defined by class method setUpClass in
22        class VppTestCase) before running the test case, set test case related
23        variables and configure VPP.
24
25        :var int bd_id: Bridge domain ID.
26        :var int mac_entries_count: Number of MAC entries for bridge-domain to
27            learn.
28        :var int dot1q_tag: VLAN tag for dot1q sub-interface.
29        :var int dot1ad_sub_id: SubID of dot1ad sub-interface.
30        :var int dot1ad_outer_tag: VLAN S-tag for dot1ad sub-interface.
31        :var int dot1ad_inner_tag: VLAN C-tag for dot1ad sub-interface.
32        :var int sl_pkts_per_burst: Number of packets in burst for single-loop
33            test.
34        :var int dl_pkts_per_burst: Number of packets in burst for dual-loop
35            test.
36        """
37        super(TestL2bd, cls).setUpClass()
38
39        # Test variables
40        cls.bd_id = 1
41        cls.mac_entries_count = 100
42        # cls.dot1q_sub_id = 100
43        cls.dot1q_tag = 100
44        cls.dot1ad_sub_id = 20
45        cls.dot1ad_outer_tag = 200
46        cls.dot1ad_inner_tag = 300
47        cls.sl_pkts_per_burst = 2
48        cls.dl_pkts_per_burst = 257
49
50        try:
51            # create 3 pg interfaces
52            cls.create_pg_interfaces(range(3))
53
54            # create 2 sub-interfaces for pg1 and pg2
55            cls.sub_interfaces = [
56                VppDot1QSubint(cls, cls.pg1, cls.dot1q_tag),
57                VppDot1ADSubint(cls, cls.pg2, cls.dot1ad_sub_id,
58                                cls.dot1ad_outer_tag, cls.dot1ad_inner_tag)]
59
60            # packet flows mapping pg0 -> pg1, pg2, etc.
61            cls.flows = dict()
62            cls.flows[cls.pg0] = [cls.pg1, cls.pg2]
63            cls.flows[cls.pg1] = [cls.pg0, cls.pg2]
64            cls.flows[cls.pg2] = [cls.pg0, cls.pg1]
65
66            # packet sizes
67            cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
68            cls.sub_if_packet_sizes = [64, 512, 1518 + 4, 9018 + 4]
69
70            cls.interfaces = list(cls.pg_interfaces)
71            cls.interfaces.extend(cls.sub_interfaces)
72
73            # Create BD with MAC learning enabled and put interfaces and
74            #  sub-interfaces to this BD
75            for pg_if in cls.pg_interfaces:
76                sw_if_index = pg_if.sub_if.sw_if_index \
77                    if hasattr(pg_if, 'sub_if') else pg_if.sw_if_index
78                cls.vapi.sw_interface_set_l2_bridge(rx_sw_if_index=sw_if_index,
79                                                    bd_id=cls.bd_id)
80
81            # setup all interfaces
82            for i in cls.interfaces:
83                i.admin_up()
84
85            # mapping between packet-generator index and lists of test hosts
86            cls.hosts_by_pg_idx = dict()
87
88            # create test host entries and inject packets to learn MAC entries
89            # in the bridge-domain
90            cls.create_hosts_and_learn(cls.mac_entries_count)
91            cls.logger.info(cls.vapi.ppcli("show l2fib"))
92
93        except Exception:
94            super(TestL2bd, cls).tearDownClass()
95            raise
96
97    @classmethod
98    def tearDownClass(cls):
99        super(TestL2bd, cls).tearDownClass()
100
101    def setUp(self):
102        """
103        Clear trace and packet infos before running each test.
104        """
105        super(TestL2bd, self).setUp()
106        self.reset_packet_infos()
107
108    def tearDown(self):
109        """
110        Show various debug prints after each test.
111        """
112        super(TestL2bd, self).tearDown()
113        if not self.vpp_dead:
114            self.logger.info(self.vapi.ppcli("show l2fib verbose"))
115            self.logger.info(self.vapi.ppcli("show bridge-domain %s detail" %
116                                             self.bd_id))
117
118    @classmethod
119    def create_hosts_and_learn(cls, count):
120        """
121        Create required number of host MAC addresses and distribute them among
122        interfaces. Create host IPv4 address for every host MAC address. Create
123        L2 MAC packet stream with host MAC addresses per interface to let
124        the bridge domain learn these MAC addresses.
125
126        :param count: Integer number of hosts to create MAC/IPv4 addresses for.
127        """
128        n_int = len(cls.pg_interfaces)
129        macs_per_if = count // n_int
130        i = -1
131        for pg_if in cls.pg_interfaces:
132            i += 1
133            start_nr = macs_per_if * i
134            end_nr = count if i == (n_int - 1) else macs_per_if * (i + 1)
135            cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
136            hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
137            packets = []
138            for j in range(start_nr, end_nr):
139                host = Host(
140                    "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
141                    "172.17.1%02x.%u" % (pg_if.sw_if_index, j))
142                packet = (Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac))
143                hosts.append(host)
144                if hasattr(pg_if, 'sub_if'):
145                    packet = pg_if.sub_if.add_dot1_layer(packet)
146                packets.append(packet)
147            pg_if.add_stream(packets)
148        cls.logger.info("Sending broadcast eth frames for MAC learning")
149        cls.pg_start()
150
151    def create_stream(self, src_if, packet_sizes, packets_per_burst):
152        """
153        Create input packet stream for defined interface.
154
155        :param object src_if: Interface to create packet stream for.
156        :param list packet_sizes: List of required packet sizes.
157        :param int packets_per_burst: Number of packets in burst.
158        :return: Stream of packets.
159        """
160        pkts = []
161        for i in range(0, packets_per_burst):
162            dst_if = self.flows[src_if][i % 2]
163            dst_host = random.choice(self.hosts_by_pg_idx[dst_if.sw_if_index])
164            src_host = random.choice(self.hosts_by_pg_idx[src_if.sw_if_index])
165            pkt_info = self.create_packet_info(src_if, dst_if)
166            payload = self.info_to_payload(pkt_info)
167            p = (Ether(dst=dst_host.mac, src=src_host.mac) /
168                 IP(src=src_host.ip4, dst=dst_host.ip4) /
169                 UDP(sport=1234, dport=1234) /
170                 Raw(payload))
171            pkt_info.data = p.copy()
172            if hasattr(src_if, 'sub_if'):
173                p = src_if.sub_if.add_dot1_layer(p)
174            size = random.choice(packet_sizes)
175            self.extend_packet(p, size)
176            pkts.append(p)
177        return pkts
178
179    def verify_capture(self, pg_if, capture):
180        """
181        Verify captured input packet stream for defined interface.
182
183        :param object pg_if: Interface to verify captured packet stream for.
184        :param list capture: Captured packet stream.
185        """
186        last_info = dict()
187        for i in self.pg_interfaces:
188            last_info[i.sw_if_index] = None
189        dst_sw_if_index = pg_if.sw_if_index
190        for packet in capture:
191            payload_info = self.payload_to_info(packet[Raw])
192            src_sw_if_index = payload_info.src
193            src_if = None
194            for ifc in self.pg_interfaces:
195                if ifc != pg_if:
196                    if ifc.sw_if_index == src_sw_if_index:
197                        src_if = ifc
198                        break
199            if hasattr(src_if, 'sub_if'):
200                # Check VLAN tags and Ethernet header
201                packet = src_if.sub_if.remove_dot1_layer(packet)
202            self.assertTrue(Dot1Q not in packet)
203            try:
204                ip = packet[IP]
205                udp = packet[UDP]
206                packet_index = payload_info.index
207                self.assertEqual(payload_info.dst, dst_sw_if_index)
208                self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
209                                  (pg_if.name, payload_info.src, packet_index))
210                next_info = self.get_next_packet_info_for_interface2(
211                    payload_info.src, dst_sw_if_index,
212                    last_info[payload_info.src])
213                last_info[payload_info.src] = next_info
214                self.assertTrue(next_info is not None)
215                self.assertEqual(packet_index, next_info.index)
216                saved_packet = next_info.data
217                # Check standard fields
218                self.assertEqual(ip.src, saved_packet[IP].src)
219                self.assertEqual(ip.dst, saved_packet[IP].dst)
220                self.assertEqual(udp.sport, saved_packet[UDP].sport)
221                self.assertEqual(udp.dport, saved_packet[UDP].dport)
222            except:
223                self.logger.error(ppp("Unexpected or invalid packet:", packet))
224                raise
225        for i in self.pg_interfaces:
226            remaining_packet = self.get_next_packet_info_for_interface2(
227                i, dst_sw_if_index, last_info[i.sw_if_index])
228            self.assertTrue(
229                remaining_packet is None,
230                "Port %u: Packet expected from source %u didn't arrive" %
231                (dst_sw_if_index, i.sw_if_index))
232
233    def run_l2bd_test(self, pkts_per_burst):
234        """ L2BD MAC learning test """
235
236        # Create incoming packet streams for packet-generator interfaces
237        for i in self.pg_interfaces:
238            packet_sizes = self.sub_if_packet_sizes if hasattr(i, 'sub_if') \
239                else self.pg_if_packet_sizes
240            pkts = self.create_stream(i, packet_sizes, pkts_per_burst)
241            i.add_stream(pkts)
242
243        # Enable packet capture and start packet sending
244        self.pg_enable_capture(self.pg_interfaces)
245        self.pg_start()
246
247        # Verify outgoing packet streams per packet-generator interface
248        for i in self.pg_interfaces:
249            capture = i.get_capture()
250            self.logger.info("Verifying capture on interface %s" % i.name)
251            self.verify_capture(i, capture)
252
253    def test_l2bd_sl(self):
254        """ L2BD MAC learning single-loop test
255
256        Test scenario:
257            1.config
258                MAC learning enabled
259                learn 100 MAC entries
260                3 interfaces: untagged, dot1q, dot1ad (dot1q used instead of
261                dot1ad in the first version)
262
263            2.sending l2 eth pkts between 3 interface
264                64B, 512B, 1518B, 9200B (ether_size)
265                burst of 2 pkts per interface
266        """
267
268        self.run_l2bd_test(self.sl_pkts_per_burst)
269
270    def test_l2bd_dl(self):
271        """ L2BD MAC learning dual-loop test
272
273         Test scenario:
274            1.config
275                MAC learning enabled
276                learn 100 MAC entries
277                3 interfaces: untagged, dot1q, dot1ad (dot1q used instead of
278                dot1ad in the first version)
279
280            2.sending l2 eth pkts between 3 interface
281                64B, 512B, 1518B, 9200B (ether_size)
282                burst of 257 pkts per interface
283        """
284
285        self.run_l2bd_test(self.dl_pkts_per_burst)
286
287
288if __name__ == '__main__':
289    unittest.main(testRunner=VppTestRunner)
290