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