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