Add single-loop test variant to L2BD and L2XC tests
[vpp.git] / test / test_l2bd.py
1 #!/usr/bin/env python
2
3 import unittest
4 import random
5
6 from scapy.packet import Raw
7 from scapy.layers.l2 import Ether, Dot1Q
8 from scapy.layers.inet import IP, UDP
9
10 from framework import VppTestCase, VppTestRunner
11 from util import Host
12 from vpp_sub_interface import VppDot1QSubint, VppDot1ADSubint
13
14
15 class 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(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     def setUp(self):
98         """
99         Clear trace and packet infos before running each test.
100         """
101         super(TestL2bd, self).setUp()
102         self.packet_infos = {}
103
104     def tearDown(self):
105         """
106         Show various debug prints after each test.
107         """
108         super(TestL2bd, self).tearDown()
109         if not self.vpp_dead:
110             self.logger.info(self.vapi.ppcli("show l2fib verbose"))
111             self.logger.info(self.vapi.ppcli("show bridge-domain %s detail" %
112                                            self.bd_id))
113
114     @classmethod
115     def create_hosts_and_learn(cls, count):
116         """
117         Create required number of host MAC addresses and distribute them among
118         interfaces. Create host IPv4 address for every host MAC address. Create
119         L2 MAC packet stream with host MAC addresses per interface to let
120         the bridge domain learn these MAC addresses.
121
122         :param count: Integer number of hosts to create MAC/IPv4 addresses for.
123         """
124         n_int = len(cls.pg_interfaces)
125         macs_per_if = count / n_int
126         i = -1
127         for pg_if in cls.pg_interfaces:
128             i += 1
129             start_nr = macs_per_if * i
130             end_nr = count if i == (n_int - 1) else macs_per_if * (i + 1)
131             cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
132             hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
133             packets = []
134             for j in range(start_nr, end_nr):
135                 host = Host(
136                     "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
137                     "172.17.1%02x.%u" % (pg_if.sw_if_index, j))
138                 packet = (Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac))
139                 hosts.append(host)
140                 if hasattr(pg_if, 'sub_if'):
141                     packet = pg_if.sub_if.add_dot1_layer(packet)
142                 packets.append(packet)
143             pg_if.add_stream(packets)
144         cls.logger.info("Sending broadcast eth frames for MAC learning")
145         cls.pg_start()
146
147     def create_stream(self, src_if, packet_sizes, packets_per_burst):
148         """
149         Create input packet stream for defined interface.
150
151         :param object src_if: Interface to create packet stream for.
152         :param list packet_sizes: List of required packet sizes.
153         :param int packets_per_burst: Number of packets in burst.
154         :return: Stream of packets.
155         """
156         pkts = []
157         for i in range(0, packets_per_burst):
158             dst_if = self.flows[src_if][i % 2]
159             dst_host = random.choice(self.hosts_by_pg_idx[dst_if.sw_if_index])
160             src_host = random.choice(self.hosts_by_pg_idx[src_if.sw_if_index])
161             pkt_info = self.create_packet_info(
162                 src_if.sw_if_index, dst_if.sw_if_index)
163             payload = self.info_to_payload(pkt_info)
164             p = (Ether(dst=dst_host.mac, src=src_host.mac) /
165                  IP(src=src_host.ip4, dst=dst_host.ip4) /
166                  UDP(sport=1234, dport=1234) /
167                  Raw(payload))
168             pkt_info.data = p.copy()
169             if hasattr(src_if, 'sub_if'):
170                 p = src_if.sub_if.add_dot1_layer(p)
171             size = random.choice(packet_sizes)
172             self.extend_packet(p, size)
173             pkts.append(p)
174         return pkts
175
176     def verify_capture(self, pg_if, capture):
177         """
178         Verify captured input packet stream for defined interface.
179
180         :param object pg_if: Interface to verify captured packet stream for.
181         :param list capture: Captured packet stream.
182         """
183         last_info = dict()
184         for i in self.pg_interfaces:
185             last_info[i.sw_if_index] = None
186         dst_sw_if_index = pg_if.sw_if_index
187         for packet in capture:
188             payload_info = self.payload_to_info(str(packet[Raw]))
189             src_sw_if_index = payload_info.src
190             src_if = None
191             for ifc in self.pg_interfaces:
192                 if ifc != pg_if:
193                     if ifc.sw_if_index == src_sw_if_index:
194                         src_if = ifc
195                         break
196             if hasattr(src_if, 'sub_if'):
197                 # Check VLAN tags and Ethernet header
198                 packet = src_if.sub_if.remove_dot1_layer(packet)
199             self.assertTrue(Dot1Q not in packet)
200             try:
201                 ip = packet[IP]
202                 udp = packet[UDP]
203                 packet_index = payload_info.index
204                 self.assertEqual(payload_info.dst, dst_sw_if_index)
205                 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
206                                   (pg_if.name, payload_info.src, packet_index))
207                 next_info = self.get_next_packet_info_for_interface2(
208                     payload_info.src, dst_sw_if_index,
209                     last_info[payload_info.src])
210                 last_info[payload_info.src] = next_info
211                 self.assertTrue(next_info is not None)
212                 self.assertEqual(packet_index, next_info.index)
213                 saved_packet = next_info.data
214                 # Check standard fields
215                 self.assertEqual(ip.src, saved_packet[IP].src)
216                 self.assertEqual(ip.dst, saved_packet[IP].dst)
217                 self.assertEqual(udp.sport, saved_packet[UDP].sport)
218                 self.assertEqual(udp.dport, saved_packet[UDP].dport)
219             except:
220                 self.logger.error("Unexpected or invalid packet:")
221                 self.logger.error(packet.show())
222                 raise
223         for i in self.pg_interfaces:
224             remaining_packet = self.get_next_packet_info_for_interface2(
225                 i, dst_sw_if_index, last_info[i.sw_if_index])
226             self.assertTrue(
227                 remaining_packet is None,
228                 "Port %u: Packet expected from source %u didn't arrive" %
229                 (dst_sw_if_index, i.sw_if_index))
230
231     def run_l2bd_test(self, pkts_per_burst):
232         """ L2BD MAC learning test """
233
234         # Create incoming packet streams for packet-generator interfaces
235         for i in self.pg_interfaces:
236             packet_sizes = self.sub_if_packet_sizes if hasattr(i, 'sub_if') \
237                 else self.pg_if_packet_sizes
238             pkts = self.create_stream(i, packet_sizes, pkts_per_burst)
239             i.add_stream(pkts)
240
241         # Enable packet capture and start packet sending
242         self.pg_enable_capture(self.pg_interfaces)
243         self.pg_start()
244
245         # Verify outgoing packet streams per packet-generator interface
246         for i in self.pg_interfaces:
247             capture = i.get_capture()
248             self.logger.info("Verifying capture on interface %s" % i.name)
249             self.verify_capture(i, capture)
250
251     def test_l2bd_sl(self):
252         """ L2BD MAC learning single-loop test
253
254         Test scenario:
255             1.config
256                 MAC learning enabled
257                 learn 100 MAC enries
258                 3 interfaces: untagged, dot1q, dot1ad (dot1q used instead of
259                 dot1ad in the first version)
260
261             2.sending l2 eth pkts between 3 interface
262                 64B, 512B, 1518B, 9200B (ether_size)
263                 burst of 2 pkts per interface
264         """
265
266         self.run_l2bd_test(self.sl_pkts_per_burst)
267
268     def test_l2bd_dl(self):
269         """ L2BD MAC learning dual-loop test
270
271          Test scenario:
272             1.config
273                 MAC learning enabled
274                 learn 100 MAC enries
275                 3 interfaces: untagged, dot1q, dot1ad (dot1q used instead of
276                 dot1ad in the first version)
277
278             2.sending l2 eth pkts between 3 interface
279                 64B, 512B, 1518B, 9200B (ether_size)
280                 burst of 257 pkts per interface
281         """
282
283         self.run_l2bd_test(self.dl_pkts_per_burst)
284
285
286 if __name__ == '__main__':
287     unittest.main(testRunner=VppTestRunner)