misc: Fix python scripts shebang line
[vpp.git] / test / test_l2_fib.py
1 #!/usr/bin/env python3
2 """L2 FIB Test Case HLD:
3
4 **config 1**
5     - add 4 pg-l2 interfaces
6     - configure them into l2bd
7     - configure 100 MAC entries in L2 fib - 25 MACs per interface
8     - L2 MAC learning and unknown unicast flooding disabled in l2bd
9     - configure 100 MAC entries in L2 fib - 25 MACs per interface
10
11 **test 1**
12     - send L2 MAC frames between all 4 pg-l2 interfaces for all of 100 MAC \
13     entries in the FIB
14
15 **verify 1**
16     - all packets received correctly
17
18 **config 2**
19     - delete 12 MAC entries - 3 MACs per interface
20
21 **test 2a**
22     - send L2 MAC frames between all 4 pg-l2 interfaces for non-deleted MAC \
23     entries
24
25 **verify 2a**
26     - all packets received correctly
27
28 **test 2b**
29     - send L2 MAC frames between all 4 pg-l2 interfaces for all of 12 deleted \
30     MAC entries
31
32 **verify 2b**
33     - no packet received on all 4 pg-l2 interfaces
34
35 **config 3**
36     - configure new 100 MAC entries in L2 fib - 25 MACs per interface
37
38 **test 3**
39     - send L2 MAC frames between all 4 pg-l2 interfaces for all of 188 MAC \
40     entries in the FIB
41
42 **verify 3**
43     - all packets received correctly
44
45 **config 4**
46     - delete 160 MAC entries, 40 MACs per interface
47
48 **test 4a**
49     - send L2 MAC frames between all 4 pg-l2 interfaces for all of 28 \
50     non-deleted MAC entries
51
52 **verify 4a**
53     - all packets received correctly
54
55 **test 4b**
56     - try send L2 MAC frames between all 4 pg-l2 interfaces for all of 172 \
57     deleted MAC entries
58
59 **verify 4b**
60     - no packet received on all 4 pg-l2 interfaces
61 """
62
63 import unittest
64 import random
65
66 from scapy.packet import Raw
67 from scapy.layers.l2 import Ether
68 from scapy.layers.inet import IP, UDP
69
70 from framework import VppTestCase, VppTestRunner
71 from util import Host, ppp
72 from vpp_papi import mac_pton
73
74 # from src/vnet/l2/l2_fib.h
75 MAC_EVENT_ACTION_ADD = 0
76 MAC_EVENT_ACTION_DELETE = 1
77 MAC_EVENT_ACTION_MOVE = 2
78
79
80 class TestL2fib(VppTestCase):
81     """ L2 FIB Test Case """
82
83     @classmethod
84     def bd_ifs(cls, bd_id):
85         return range((bd_id - 1) * cls.n_ifs_per_bd,
86                      bd_id * cls.n_ifs_per_bd - 1)
87
88     @classmethod
89     def setUpClass(cls):
90         """
91         Perform standard class setup (defined by class method setUpClass in
92         class VppTestCase) before running the test case, set test case related
93         variables and configure VPP.
94
95         :var int bd_id: Bridge domain ID.
96         """
97         super(TestL2fib, cls).setUpClass()
98
99         try:
100             n_brs = cls.n_brs = range(1, 3)
101             cls.n_ifs_per_bd = 4
102             n_ifs = range(cls.n_ifs_per_bd * len(cls.n_brs))
103             # Create pg interfaces
104             cls.create_pg_interfaces(n_ifs)
105
106             cls.flows = dict()
107             for bd_id in n_brs:
108                 # Packet flows mapping pg0 -> pg1, pg2, pg3 etc.
109                 ifs = cls.bd_ifs(bd_id)
110                 for j in ifs:
111                     cls.flows[cls.pg_interfaces[j]] = [
112                         cls.pg_interfaces[x] for x in ifs if x != j]
113
114             # Packet sizes
115             cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
116
117             for bd_id in n_brs:
118                 # Create BD with MAC learning and unknown unicast flooding
119                 # disabled and put interfaces to this BD
120                 cls.vapi.bridge_domain_add_del(bd_id=bd_id, uu_flood=0,
121                                                learn=0)
122                 ifs = [cls.pg_interfaces[i] for i in cls.bd_ifs(bd_id)]
123                 for pg_if in ifs:
124                     cls.vapi.sw_interface_set_l2_bridge(
125                         rx_sw_if_index=pg_if.sw_if_index, bd_id=bd_id)
126
127             # Set up all interfaces
128             for i in cls.pg_interfaces:
129                 i.admin_up()
130         except Exception:
131             super(TestL2fib, cls).tearDownClass()
132             raise
133
134     @classmethod
135     def tearDownClass(cls):
136         super(TestL2fib, cls).tearDownClass()
137
138     def setUp(self):
139         super(TestL2fib, self).setUp()
140         self.reset_packet_infos()
141
142     def tearDown(self):
143         """
144         Show various debug prints after each test.
145         """
146         super(TestL2fib, self).tearDown()
147         if not self.vpp_dead:
148             for bd_id in self.n_brs:
149                 self.logger.info(self.vapi.ppcli("show bridge-domain %s detail"
150                                                  % bd_id))
151
152     def show_commands_at_teardown(self):
153         self.logger.info(self.vapi.ppcli("show l2fib verbose"))
154
155     def create_hosts(self, n_hosts_per_if, subnet):
156         """
157         Create required number of host MAC addresses and distribute them among
158         interfaces. Create host IPv4 address for every host MAC address.
159
160         :param int n_hosts_per_if: Number of per interface hosts to
161         create MAC/IPv4 addresses for.
162         """
163
164         hosts = dict()
165         for pg_if in self.pg_interfaces:
166             swif = pg_if.sw_if_index
167
168             def mac(j): return "00:00:%02x:ff:%02x:%02x" % (subnet, swif, j)
169
170             def ip(j): return "172.%02u.1%02x.%u" % (subnet, swif, j)
171
172             def h(j): return Host(mac(j), ip(j))
173             hosts[swif] = [h(j) for j in range(n_hosts_per_if)]
174         return hosts
175
176     def split_hosts(self, hosts, n):
177         splits = dict()
178         for pg_if in self.pg_interfaces:
179             swif = pg_if.sw_if_index
180             splits[swif] = hosts[swif][:n]
181             hosts[swif] = hosts[swif][n:]
182         return splits
183
184     def learn_hosts(self, bd_id, hosts):
185         """
186         Create and send per interface L2 MAC broadcast packet stream to
187         let the bridge domain learn these MAC addresses.
188
189         :param int bd_id: BD to teach
190         :param dict hosts: dict of hosts per interface
191         """
192         self.vapi.bridge_flags(bd_id=bd_id, is_set=1, flags=1)
193         ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
194         for pg_if in ifs:
195             swif = pg_if.sw_if_index
196             packets = [Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac)
197                        for host in hosts[swif]]
198             pg_if.add_stream(packets)
199         self.logger.info("Sending broadcast eth frames for MAC learning")
200         self.pg_start()
201
202     def config_l2_fib_entries(self, bd_id, hosts):
203         """
204         Config required number of L2 FIB entries.
205
206         :param int bd_id: BD's id
207         :param int count: Number of L2 FIB entries to be created.
208         :param int start: Starting index of the host list. (Default value = 0)
209         """
210         ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
211         for pg_if in ifs:
212             swif = pg_if.sw_if_index
213             for host in hosts[swif]:
214                 self.vapi.l2fib_add_del(
215                     mac_pton(host.mac), bd_id, swif, static_mac=1)
216
217     def delete_l2_fib_entry(self, bd_id, hosts):
218         """
219         Delete required number of L2 FIB entries.
220
221         :param int count: Number of L2 FIB entries to be created.
222         """
223         ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
224         for pg_if in ifs:
225             swif = pg_if.sw_if_index
226             for host in hosts[swif]:
227                 self.vapi.l2fib_add_del(
228                     mac_pton(host.mac), bd_id, swif, is_add=0)
229
230     def flush_int(self, swif, learned_hosts):
231         """
232         Flush swif L2 FIB entries.
233
234         :param int swif: sw if index.
235         """
236         flushed = dict()
237         self.vapi.l2fib_flush_int(swif)
238         flushed[swif] = learned_hosts[swif]
239         learned_hosts[swif] = []
240         return flushed
241
242     def flush_bd(self, bd_id, learned_hosts):
243         """
244         Flush bd_id L2 FIB entries.
245
246         :param int bd_id: Bridge Domain id.
247         """
248         self.vapi.l2fib_flush_bd(bd_id)
249         flushed = dict()
250         ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
251         for pg_if in ifs:
252             swif = pg_if.sw_if_index
253             flushed[swif] = learned_hosts[swif]
254             learned_hosts[swif] = []
255         return flushed
256
257     def flush_all(self):
258         """
259         Flush All L2 FIB entries.
260         """
261         self.vapi.l2fib_flush_all()
262
263     def create_stream(self, src_if, packet_sizes, if_src_hosts, if_dst_hosts):
264         """
265         Create input packet stream for defined interface using hosts or
266         deleted_hosts list.
267
268         :param object src_if: Interface to create packet stream for.
269         :param list packet_sizes: List of required packet sizes.
270         :param boolean deleted: Set to True if deleted_hosts list required.
271         :return: Stream of packets.
272         """
273         src_hosts = if_src_hosts[src_if.sw_if_index]
274         if not src_hosts:
275             return []
276         pkts = []
277         for dst_if in self.flows[src_if]:
278             dst_swif = dst_if.sw_if_index
279             if dst_swif not in if_dst_hosts:
280                 continue
281             dst_hosts = if_dst_hosts[dst_swif]
282             for dst_host in dst_hosts:
283                 src_host = random.choice(src_hosts)
284                 pkt_info = self.create_packet_info(src_if, dst_if)
285                 payload = self.info_to_payload(pkt_info)
286                 p = (Ether(dst=dst_host.mac, src=src_host.mac) /
287                      IP(src=src_host.ip4, dst=dst_host.ip4) /
288                      UDP(sport=1234, dport=1234) /
289                      Raw(payload))
290                 pkt_info.data = p.copy()
291                 size = random.choice(packet_sizes)
292                 self.extend_packet(p, size)
293                 pkts.append(p)
294         return pkts
295
296     def verify_capture(self, pg_if, capture):
297         """
298         Verify captured input packet stream for defined interface.
299
300         :param object pg_if: Interface to verify captured packet stream for.
301         :param list capture: Captured packet stream.
302         """
303         last_info = dict()
304         for i in self.pg_interfaces:
305             last_info[i.sw_if_index] = None
306         dst_sw_if_index = pg_if.sw_if_index
307         for packet in capture:
308             payload_info = self.payload_to_info(packet[Raw])
309             try:
310                 ip = packet[IP]
311                 udp = packet[UDP]
312                 packet_index = payload_info.index
313                 self.assertEqual(payload_info.dst, dst_sw_if_index)
314                 self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
315                                   (pg_if.name, payload_info.src, packet_index))
316                 next_info = self.get_next_packet_info_for_interface2(
317                     payload_info.src, dst_sw_if_index,
318                     last_info[payload_info.src])
319                 last_info[payload_info.src] = next_info
320                 self.assertTrue(next_info is not None)
321                 self.assertEqual(packet_index, next_info.index)
322                 saved_packet = next_info.data
323                 # Check standard fields
324                 self.assertEqual(ip.src, saved_packet[IP].src)
325                 self.assertEqual(ip.dst, saved_packet[IP].dst)
326                 self.assertEqual(udp.sport, saved_packet[UDP].sport)
327                 self.assertEqual(udp.dport, saved_packet[UDP].dport)
328             except:
329                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
330                 raise
331         for i in self.pg_interfaces:
332             remaining_packet = self.get_next_packet_info_for_interface2(
333                 i, dst_sw_if_index, last_info[i.sw_if_index])
334             self.assertTrue(
335                 remaining_packet is None,
336                 "Port %u: Packet expected from source %u didn't arrive" %
337                 (dst_sw_if_index, i.sw_if_index))
338
339     def run_verify_test(self, bd_id, src_hosts, dst_hosts):
340         # Test
341         # Create incoming packet streams for packet-generator interfaces
342         self.reset_packet_infos()
343         ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
344         for i in ifs:
345             pkts = self.create_stream(
346                 i, self.pg_if_packet_sizes,
347                 if_src_hosts=src_hosts,
348                 if_dst_hosts=dst_hosts)
349             if pkts:
350                 i.add_stream(pkts)
351
352         self.vapi.bridge_flags(bd_id=bd_id, is_set=0, flags=1)
353         # Enable packet capture and start packet sending
354         self.pg_enable_capture(ifs)
355         self.pg_start()
356
357         # Verify
358         # Verify outgoing packet streams per packet-generator interface
359         for i in ifs:
360             if not dst_hosts[i.sw_if_index]:
361                 continue
362             capture = i.get_capture()
363             self.logger.info("Verifying capture on interface %s" % i.name)
364             self.verify_capture(i, capture)
365
366     def run_verify_negat_test(self, bd_id, src_hosts, dst_hosts):
367         # Test
368         # Create incoming packet streams for packet-generator interfaces for
369         # deleted MAC addresses
370         self.reset_packet_infos()
371         ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
372         for i in ifs:
373             pkts = self.create_stream(
374                 i, self.pg_if_packet_sizes,
375                 if_src_hosts=src_hosts,
376                 if_dst_hosts=dst_hosts)
377             if pkts:
378                 i.add_stream(pkts)
379
380         self.vapi.bridge_flags(bd_id=bd_id, is_set=0, flags=1)
381         # Enable packet capture and start packet sending
382         self.pg_enable_capture(ifs)
383         self.pg_start()
384
385         # Verify
386         # Verify outgoing packet streams per packet-generator interface
387         timeout = 1
388         for i in ifs:
389             i.get_capture(0, timeout=timeout)
390             i.assert_nothing_captured(remark="outgoing interface")
391             timeout = 0.1
392
393     def test_l2_fib_program100(self):
394         """ L2 FIB - program 100 MACs
395         """
396         bd_id = 1
397         hosts = self.create_hosts(100, subnet=17)
398         self.config_l2_fib_entries(bd_id, hosts)
399         self.run_verify_test(bd_id, hosts, hosts)
400
401     def test_l2_fib_program100_delete12(self):
402         """ L2 FIB - program 100, delete 12 MACs
403         """
404         bd_id = 1
405         hosts = self.create_hosts(100, subnet=17)
406         self.config_l2_fib_entries(bd_id, hosts)
407         del_hosts = self.split_hosts(hosts, 12)
408         self.delete_l2_fib_entry(bd_id, del_hosts)
409
410         self.run_verify_test(bd_id, hosts, hosts)
411         self.run_verify_negat_test(bd_id, hosts, del_hosts)
412
413     def test_l2_fib_program100_add100(self):
414         """ L2 FIB - program 100, add 100 MACs
415         """
416         bd_id = 1
417         hosts = self.create_hosts(100, subnet=17)
418         self.config_l2_fib_entries(bd_id, hosts)
419         hosts2 = self.create_hosts(100, subnet=22)
420         self.config_l2_fib_entries(bd_id, hosts2)
421         self.run_verify_test(bd_id, hosts, hosts2)
422
423     def test_l2_fib_program10_learn10(self):
424         """ L2 FIB - program 10 MACs, learn 10
425         """
426         hosts = self.create_hosts(20, subnet=35)
427         lhosts = self.split_hosts(hosts, 10)
428
429         bd1 = 1
430         bd2 = 2
431         self.learn_hosts(bd1, lhosts)
432         self.learn_hosts(bd2, lhosts)
433         self.config_l2_fib_entries(bd1, hosts)
434         self.config_l2_fib_entries(bd2, hosts)
435         self.run_verify_test(bd1, lhosts, hosts)
436         self.run_verify_test(bd2, lhosts, hosts)
437
438     def test_l2_fib_flush_int(self):
439         """ L2 FIB - flush interface
440         """
441         hosts = self.create_hosts(20, subnet=36)
442         lhosts = self.split_hosts(hosts, 10)
443
444         bd1 = 1
445         self.learn_hosts(bd1, lhosts)
446         self.config_l2_fib_entries(bd1, hosts)
447         self.run_verify_test(bd1, lhosts, hosts)
448         flushed = self.flush_int(self.pg_interfaces[0].sw_if_index, lhosts)
449         self.run_verify_test(bd1, hosts, lhosts)
450         self.run_verify_negat_test(bd1, hosts, flushed)
451
452     def test_l2_fib_flush_bd(self):
453         """ L2 FIB - flush BD
454         """
455         hosts = self.create_hosts(20, subnet=37)
456         lhosts = self.split_hosts(hosts, 10)
457
458         bd1 = 1
459         self.learn_hosts(bd1, lhosts)
460         self.config_l2_fib_entries(bd1, hosts)
461         self.run_verify_test(bd1, lhosts, hosts)
462         flushed = self.flush_bd(bd1, lhosts)
463         self.run_verify_negat_test(bd1, hosts, flushed)
464
465     def test_l2_fib_flush_all(self):
466         """ L2 FIB - flush all
467         """
468         hosts = self.create_hosts(20, subnet=38)
469         lhosts = self.split_hosts(hosts, 10)
470
471         bd1 = 1
472         bd2 = 2
473         self.learn_hosts(bd1, lhosts)
474         self.learn_hosts(bd2, lhosts)
475         self.config_l2_fib_entries(bd1, hosts)
476         self.config_l2_fib_entries(bd2, hosts)
477         self.run_verify_test(bd1, hosts, lhosts)
478         self.run_verify_test(bd2, hosts, lhosts)
479
480         self.flush_all()
481
482         self.run_verify_negat_test(bd1, hosts, lhosts)
483         self.run_verify_negat_test(bd2, hosts, lhosts)
484
485     def test_l2_fib_mac_learn_evs(self):
486         """ L2 FIB - mac learning events
487         """
488         bd1 = 1
489         hosts = self.create_hosts(10, subnet=39)
490
491         self.vapi.want_l2_macs_events()
492         self.learn_hosts(bd1, hosts)
493
494         self.sleep(1)
495         self.logger.info(self.vapi.ppcli("show l2fib"))
496         evs = self.vapi.collect_events()
497         learned_macs = {
498             e.mac[i].mac_addr for e in evs for i in range(e.n_macs)
499             if e.mac[i].action == MAC_EVENT_ACTION_ADD}
500         macs = {h.bin_mac for swif in self.bd_ifs(bd1)
501                 for h in hosts[self.pg_interfaces[swif].sw_if_index]}
502         self.vapi.want_l2_macs_events(enable_disable=0)
503         self.assertEqual(len(learned_macs ^ macs), 0)
504
505     def test_l2_fib_macs_learn_max(self):
506         """ L2 FIB - mac learning max macs in event
507         """
508         bd1 = 1
509         hosts = self.create_hosts(10, subnet=40)
510
511         ev_macs = 1
512         self.vapi.want_l2_macs_events(max_macs_in_event=ev_macs)
513         self.learn_hosts(bd1, hosts)
514
515         self.sleep(1)
516         self.logger.info(self.vapi.ppcli("show l2fib"))
517         evs = self.vapi.collect_events()
518         self.vapi.want_l2_macs_events(enable_disable=0)
519
520         self.assertGreater(len(evs), 0)
521         learned_macs = {
522             e.mac[i].mac_addr for e in evs for i in range(e.n_macs)
523             if e.mac[i].action == MAC_EVENT_ACTION_ADD}
524         macs = {h.bin_mac for swif in self.bd_ifs(bd1)
525                 for h in hosts[self.pg_interfaces[swif].sw_if_index]}
526
527         for e in evs:
528             self.assertLess(len(e), ev_macs * 10)
529         self.assertEqual(len(learned_macs ^ macs), 0)
530
531
532 if __name__ == '__main__':
533     unittest.main(testRunner=VppTestRunner)