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