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