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