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