misc: Fix python scripts shebang line
[vpp.git] / test / test_l2bd.py
1 #!/usr/bin/env python3
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, ppp
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(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
288 if __name__ == '__main__':
289     unittest.main(testRunner=VppTestRunner)