udp: fix csum computation when offload disabled
[vpp.git] / test / test_l2bd_multi_instance.py
1 #!/usr/bin/env python3
2 """L2BD Multi-instance Test Case HLD:
3
4 **NOTES:**
5     - higher number of pg-l2 interfaces causes problems => only 15 pg-l2 \
6     interfaces in 5 bridge domains are tested
7     - jumbo packets in configuration with 14 l2-pg interfaces leads to \
8     problems too
9
10 **config 1**
11     - add 15 pg-l2 interfaces
12     - configure one host per pg-l2 interface
13     - configure 5 bridge domains (BD)
14     - add 3 pg-l2 interfaces per BD
15
16 **test 1**
17     - send L2 MAC frames between all pg-l2 interfaces of all BDs
18
19 **verify 1**
20     - check BD data by parsing output of bridge_domain_dump API command
21     - all packets received correctly
22
23 **config 2**
24     - update data of 5 BD
25         - disable learning, forwarding, flooding and uu_flooding for BD1
26         - disable forwarding for BD2
27         - disable flooding for BD3
28         - disable uu_flooding for BD4
29         - disable learning for BD5
30
31 **verify 2**
32     - check BD data by parsing output of bridge_domain_dump API command
33
34 **config 3**
35     - delete 2 BDs
36
37 **test 3**
38     - send L2 MAC frames between all pg-l2 interfaces of all BDs
39     - send L2 MAC frames between all pg-l2 interfaces formerly assigned to \
40     deleted BDs
41
42 **verify 3**
43     - check BD data by parsing output of bridge_domain_dump API command
44     - all packets received correctly on all 3 pg-l2 interfaces assigned to BDs
45     - no packet received on all 3 pg-l2 interfaces of all deleted BDs
46
47 **config 4**
48     - add 2 BDs
49     - add 3 pg-l2 interfaces per BD
50
51 **test 4**
52     - send L2 MAC frames between all pg-l2 interfaces of all BDs
53
54 **verify 4**
55     - check BD data by parsing output of bridge_domain_dump API command
56     - all packets received correctly
57
58 **config 5**
59     - delete 5 BDs
60
61 **verify 5**
62     - check BD data by parsing output of bridge_domain_dump API command
63 """
64
65 import unittest
66 import random
67
68 from scapy.packet import Raw
69 from scapy.layers.l2 import Ether
70 from scapy.layers.inet import IP, UDP
71
72 from framework import VppTestCase
73 from asfframework import VppTestRunner
74 from util import Host, ppp
75
76
77 class TestL2bdMultiInst(VppTestCase):
78     """L2BD Multi-instance Test Case"""
79
80     @classmethod
81     def setUpClass(cls):
82         """
83         Perform standard class setup (defined by class method setUpClass in
84         class VppTestCase) before running the test case, set test case related
85         variables and configure VPP.
86         """
87         super(TestL2bdMultiInst, cls).setUpClass()
88
89         try:
90             # Create pg interfaces
91             n_bd = 5
92             cls.ifs_per_bd = ifs_per_bd = 3
93             n_ifs = n_bd * ifs_per_bd
94             cls.create_pg_interfaces(range(n_ifs))
95
96             # Packet flows mapping pg0 -> pg1, pg2 etc.
97             cls.flows = dict()
98             for b in range(n_bd):
99                 bd_ifs = cls.bd_if_range(b + 1)
100                 for j in bd_ifs:
101                     cls.flows[cls.pg_interfaces[j]] = [
102                         cls.pg_interfaces[x] for x in bd_ifs if x != j
103                     ]
104                     assert len(cls.flows[cls.pg_interfaces[j]]) == ifs_per_bd - 1
105
106             # Mapping between packet-generator index and lists of test hosts
107             cls.hosts_by_pg_idx = dict()
108
109             # Create test host entries
110             cls.create_hosts(5)
111
112             # Packet sizes - jumbo packet (9018 bytes) skipped
113             cls.pg_if_packet_sizes = [64, 512, 1518]
114
115             # Set up all interfaces
116             for i in cls.pg_interfaces:
117                 i.admin_up()
118
119         except Exception:
120             super(TestL2bdMultiInst, cls).tearDownClass()
121             raise
122
123     @classmethod
124     def tearDownClass(cls):
125         super(TestL2bdMultiInst, cls).tearDownClass()
126
127     def setUp(self):
128         """
129         Clear trace and packet infos before running each test.
130         """
131         self.reset_packet_infos()
132         super(TestL2bdMultiInst, self).setUp()
133         # Create list of BDs
134         self.bd_list = []
135
136         # Create dict of BDs
137         self.bd_map = {}
138
139         # Create list of deleted BDs
140         self.bd_deleted_list = []
141
142         # Create list of pg_interfaces in BDs
143         self.pg_in_bd = []
144
145     def tearDown(self):
146         """
147         Show various debug prints after each test.
148         """
149         super(TestL2bdMultiInst, self).tearDown()
150         if not self.vpp_dead:
151             self.logger.info(self.vapi.ppcli("show l2fib verbose"))
152             self.logger.info(self.vapi.ppcli("show bridge-domain"))
153
154     @classmethod
155     def create_hosts(cls, hosts_per_if):
156         """
157         Create required number of host MAC addresses and distribute them
158         among interfaces. Create host IPv4 address for every host MAC
159         address.
160
161         :param int hosts_per_if: Number of hosts per if to create MAC/IPv4
162                                  addresses for.
163         """
164         c = hosts_per_if
165         assert not cls.hosts_by_pg_idx
166         for i in range(len(cls.pg_interfaces)):
167             pg_idx = cls.pg_interfaces[i].sw_if_index
168             cls.hosts_by_pg_idx[pg_idx] = [
169                 Host(
170                     "00:00:00:ff:%02x:%02x" % (pg_idx, j + 1),
171                     "172.17.1%02u.%u" % (pg_idx, j + 1),
172                 )
173                 for j in range(c)
174             ]
175
176     @classmethod
177     def bd_if_range(cls, b):
178         n = cls.ifs_per_bd
179         start = (b - 1) * n
180         return range(start, start + n)
181
182     def create_bd_and_mac_learn(self, count, start=1):
183         """
184         Create required number of bridge domains with MAC learning enabled,
185         put 3 l2-pg interfaces to every bridge domain and send MAC learning
186         packets.
187
188         :param int count: Number of bridge domains to be created.
189         :param int start: Starting number of the bridge domain ID.
190             (Default value = 1)
191         """
192         for b in range(start, start + count):
193             if b == start:
194                 self.vapi.bridge_domain_add_del_v2(
195                     bd_id=b, flood=1, uu_flood=1, forward=1, learn=1, is_add=1
196                 )
197                 bd_id = b
198             else:
199                 ret = self.vapi.bridge_domain_add_del_v2(
200                     bd_id=0xFFFFFFFF, flood=1, uu_flood=1, forward=1, learn=1, is_add=1
201                 )
202                 bd_id = ret.bd_id
203             self.logger.info("Bridge domain ID %d created" % bd_id)
204             if self.bd_list.count(bd_id) == 0:
205                 self.bd_map[b] = bd_id
206                 self.bd_list.append(bd_id)
207             if self.bd_deleted_list.count(bd_id) == 1:
208                 self.bd_deleted_list.remove(bd_id)
209             for j in self.bd_if_range(b):
210                 pg_if = self.pg_interfaces[j]
211                 self.vapi.sw_interface_set_l2_bridge(
212                     rx_sw_if_index=pg_if.sw_if_index, bd_id=bd_id
213                 )
214                 self.logger.info(
215                     "pg-interface %s added to bridge domain ID %d" % (pg_if.name, bd_id)
216                 )
217                 self.pg_in_bd.append(pg_if)
218                 hosts = self.hosts_by_pg_idx[pg_if.sw_if_index]
219                 packets = [
220                     Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac) for host in hosts
221                 ]
222                 pg_if.add_stream(packets)
223         self.logger.info("Sending broadcast eth frames for MAC learning")
224         self.pg_start()
225         self.logger.info(self.vapi.ppcli("show bridge-domain"))
226         self.logger.info(self.vapi.ppcli("show l2fib"))
227
228     def delete_bd(self, count, start=1):
229         """
230         Delete required number of bridge domains.
231
232         :param int count: Number of bridge domains to be created.
233         :param int start: Starting number of the bridge domain ID.
234             (Default value = 1)
235         """
236         for b in range(start, start + count):
237             bd_id = self.bd_map[b]
238             for j in self.bd_if_range(b):
239                 pg_if = self.pg_interfaces[j]
240                 self.vapi.sw_interface_set_l2_bridge(
241                     rx_sw_if_index=pg_if.sw_if_index, bd_id=bd_id, enable=0
242                 )
243                 self.pg_in_bd.remove(pg_if)
244                 self.logger.info(
245                     "pg-interface %s removed from bridge domain ID %d"
246                     % (pg_if.name, bd_id)
247                 )
248             self.vapi.bridge_domain_add_del_v2(bd_id=bd_id, is_add=0)
249             self.bd_map.pop(b)
250             self.bd_list.remove(bd_id)
251             self.bd_deleted_list.append(bd_id)
252             self.logger.info("Bridge domain ID %d deleted" % bd_id)
253
254     def create_stream(self, src_if):
255         """
256         Create input packet stream for defined interface using hosts list.
257
258         :param object src_if: Interface to create packet stream for.
259         :param list packet_sizes: List of required packet sizes.
260         :return: Stream of packets.
261         """
262         packet_sizes = self.pg_if_packet_sizes
263         pkts = []
264         src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
265         for dst_if in self.flows[src_if]:
266             dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
267             for dst_host in dst_hosts:
268                 pkt_info = self.create_packet_info(src_if, dst_if)
269                 payload = self.info_to_payload(pkt_info)
270                 src_host = random.choice(src_hosts)
271                 p = (
272                     Ether(dst=dst_host.mac, src=src_host.mac)
273                     / IP(src=src_host.ip4, dst=dst_host.ip4)
274                     / UDP(sport=1234, dport=1234)
275                     / Raw(payload)
276                 )
277                 pkt_info.data = p.copy()
278                 size = random.choice(packet_sizes)
279                 self.extend_packet(p, size)
280                 pkts.append(p)
281         self.logger.debug(
282             "Input stream created for port %s. Length: %u pkt(s)"
283             % (src_if.name, len(pkts))
284         )
285         return pkts
286
287     def verify_capture(self, dst_if):
288         """
289         Verify captured input packet stream for defined interface.
290
291         :param object dst_if: Interface to verify captured packet stream for.
292         """
293         last_info = dict()
294         for i in self.flows[dst_if]:
295             last_info[i.sw_if_index] = None
296         dst = dst_if.sw_if_index
297         for packet in dst_if.get_capture():
298             try:
299                 ip = packet[IP]
300                 udp = packet[UDP]
301                 info = self.payload_to_info(packet[Raw])
302                 self.assertEqual(info.dst, dst)
303                 self.logger.debug(
304                     "Got packet on port %s: src=%u (id=%u)"
305                     % (dst_if.name, info.src, info.index)
306                 )
307                 last_info[info.src] = self.get_next_packet_info_for_interface2(
308                     info.src, dst, last_info[info.src]
309                 )
310                 pkt_info = last_info[info.src]
311                 self.assertTrue(pkt_info is not None)
312                 self.assertEqual(info.index, pkt_info.index)
313                 # Check standard fields against saved data in pkt
314                 saved = pkt_info.data
315                 self.assertEqual(ip.src, saved[IP].src)
316                 self.assertEqual(ip.dst, saved[IP].dst)
317                 self.assertEqual(udp.sport, saved[UDP].sport)
318                 self.assertEqual(udp.dport, saved[UDP].dport)
319             except:
320                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
321                 raise
322         s = ""
323         remaining = 0
324         for src in self.flows[dst_if]:
325             remaining_packet = self.get_next_packet_info_for_interface2(
326                 src.sw_if_index, dst, last_info[src.sw_if_index]
327             )
328             if remaining_packet is None:
329                 s += "Port %u: Packet expected from source %u didn't arrive\n" % (
330                     dst,
331                     src.sw_if_index,
332                 )
333                 remaining += 1
334             self.assertNotEqual(0, remaining, s)
335
336     def set_bd_flags(self, bd_id, **args):
337         """
338         Enable/disable defined feature(s) of the bridge domain.
339
340         :param int bd_id: Bridge domain ID.
341         :param list args: List of feature/status pairs. Allowed features: \
342         learn, forward, flood, uu_flood and arp_term. Status False means \
343         disable, status True means enable the feature.
344         :raise: ValueError in case of unknown feature in the input.
345         """
346         for flag in args:
347             if flag == "learn":
348                 feature_bitmap = 1 << 0
349             elif flag == "forward":
350                 feature_bitmap = 1 << 1
351             elif flag == "flood":
352                 feature_bitmap = 1 << 2
353             elif flag == "uu_flood":
354                 feature_bitmap = 1 << 3
355             elif flag == "arp_term":
356                 feature_bitmap = 1 << 4
357             else:
358                 raise ValueError("Unknown feature used: %s" % flag)
359             is_set = 1 if args[flag] else 0
360             self.vapi.bridge_flags(bd_id=bd_id, is_set=is_set, flags=feature_bitmap)
361         self.logger.info("Bridge domain ID %d updated" % bd_id)
362
363     def verify_bd(self, bd_id, **args):
364         """
365         Check if the bridge domain is configured and verify expected status
366         of listed features.
367
368         :param int bd_id: Bridge domain ID.
369         :param list args: List of feature/status pairs. Allowed features: \
370         learn, forward, flood, uu_flood and arp_term. Status False means \
371         disable, status True means enable the feature.
372         :return: 1 if bridge domain is configured, otherwise return 0.
373         :raise: ValueError in case of unknown feature in the input.
374         """
375         bd_dump = self.vapi.bridge_domain_dump(bd_id)
376         if len(bd_dump) == 0:
377             self.logger.info("Bridge domain ID %d is not configured" % bd_id)
378             return 0
379         else:
380             bd_dump = bd_dump[0]
381             if len(args) > 0:
382                 for flag in args:
383                     expected_status = 1 if args[flag] else 0
384                     if flag == "learn":
385                         flag_status = bd_dump[6]
386                     elif flag == "forward":
387                         flag_status = bd_dump[5]
388                     elif flag == "flood":
389                         flag_status = bd_dump[3]
390                     elif flag == "uu_flood":
391                         flag_status = bd_dump[4]
392                     elif flag == "arp_term":
393                         flag_status = bd_dump[7]
394                     else:
395                         raise ValueError("Unknown feature used: %s" % flag)
396                     self.assertEqual(expected_status, flag_status)
397             return 1
398
399     def run_verify_test(self):
400         """
401         Create packet streams for all configured l2-pg interfaces, send all \
402         prepared packet streams and verify that:
403             - all packets received correctly on all pg-l2 interfaces assigned
404               to bridge domains
405             - no packet received on all pg-l2 interfaces not assigned to
406               bridge domains
407
408         :raise RuntimeError: if no packet captured on l2-pg interface assigned
409                              to the bridge domain or if any packet is captured
410                              on l2-pg interface not assigned to the bridge
411                              domain.
412         """
413         # Test
414         # Create incoming packet streams for packet-generator interfaces
415         # for pg_if in self.pg_interfaces:
416         assert len(self._packet_count_for_dst_if_idx) == 0
417         for pg_if in self.pg_in_bd:
418             pkts = self.create_stream(pg_if)
419             pg_if.add_stream(pkts)
420
421         # Enable packet capture and start packet sending
422         self.pg_enable_capture(self.pg_in_bd)
423         self.pg_start()
424
425         # Verify
426         # Verify outgoing packet streams per packet-generator interface
427         for pg_if in self.pg_in_bd:
428             self.verify_capture(pg_if)
429
430     def test_l2bd_inst_01(self):
431         """L2BD Multi-instance test 1 - create 5 BDs"""
432         # Config 1
433         # Create 5 BDs, put interfaces to these BDs and send MAC learning
434         # packets
435         self.create_bd_and_mac_learn(5)
436
437         # Verify 1
438         for bd_id in self.bd_list:
439             self.assertEqual(self.verify_bd(bd_id), 1)
440
441         # Test 1
442         # self.vapi.cli("clear trace")
443         self.run_verify_test()
444         self.delete_bd(5)
445
446     def test_l2bd_inst_02(self):
447         """L2BD Multi-instance test 2 - update data of 5 BDs"""
448         # Config 2
449         # Update data of 5 BDs (disable learn, forward, flood, uu-flood)
450         self.create_bd_and_mac_learn(5)
451         self.set_bd_flags(
452             self.bd_list[0], learn=False, forward=False, flood=False, uu_flood=False
453         )
454         self.set_bd_flags(self.bd_list[1], forward=False)
455         self.set_bd_flags(self.bd_list[2], flood=False)
456         self.set_bd_flags(self.bd_list[3], uu_flood=False)
457         self.set_bd_flags(self.bd_list[4], learn=False)
458
459         # Verify 2
460         # Skipping check of uu_flood as it is not returned by
461         # bridge_domain_dump api command
462         self.verify_bd(
463             self.bd_list[0], learn=False, forward=False, flood=False, uu_flood=False
464         )
465         self.verify_bd(
466             self.bd_list[1], learn=True, forward=False, flood=True, uu_flood=True
467         )
468         self.verify_bd(
469             self.bd_list[2], learn=True, forward=True, flood=False, uu_flood=True
470         )
471         self.verify_bd(
472             self.bd_list[3], learn=True, forward=True, flood=True, uu_flood=False
473         )
474         self.verify_bd(
475             self.bd_list[4], learn=False, forward=True, flood=True, uu_flood=True
476         )
477         self.delete_bd(5)
478
479     def test_l2bd_inst_03(self):
480         """L2BD Multi-instance test 3 - delete 2 BDs"""
481         # Config 3
482         # Delete 2 BDs
483         self.create_bd_and_mac_learn(5)
484         self.delete_bd(2)
485
486         # Verify 3
487         for bd_id in self.bd_deleted_list:
488             self.assertEqual(self.verify_bd(bd_id), 0)
489         for bd_id in self.bd_list:
490             self.assertEqual(self.verify_bd(bd_id), 1)
491
492         # Test 3
493         self.run_verify_test()
494         self.delete_bd(3, 3)
495
496     def test_l2bd_inst_04(self):
497         """L2BD Multi-instance test 4 - add 2 BDs"""
498         # Config 4
499         # Create 5 BDs, put interfaces to these BDs and send MAC learning
500         # packets
501         self.create_bd_and_mac_learn(2)
502
503         # Verify 4
504         for bd_id in self.bd_list:
505             self.assertEqual(self.verify_bd(bd_id), 1)
506
507         # Test 4
508         # self.vapi.cli("clear trace")
509         self.run_verify_test()
510         self.delete_bd(2)
511
512     def test_l2bd_inst_05(self):
513         """L2BD Multi-instance test 5 - delete 5 BDs"""
514         # Config 5
515         # Delete 5 BDs
516         self.create_bd_and_mac_learn(5)
517         self.delete_bd(5)
518
519         # Verify 5
520         for bd_id in self.bd_deleted_list:
521             self.assertEqual(self.verify_bd(bd_id), 0)
522         for bd_id in self.bd_list:
523             self.assertEqual(self.verify_bd(bd_id), 1)
524
525
526 if __name__ == "__main__":
527     unittest.main(testRunner=VppTestRunner)