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