IP-MAC,ND:wildcard events,fix sending multiple events
[vpp.git] / test / test_l2bd_arp_term.py
1 #!/usr/bin/env python
2 """ L2BD ARP term Test """
3
4 import unittest
5 import random
6 import copy
7
8 from socket import AF_INET, AF_INET6
9
10 from scapy.packet import Raw
11 from scapy.layers.l2 import Ether, ARP
12 from scapy.layers.inet import IP
13 from scapy.utils import inet_pton, inet_ntop
14 from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ptop, in6_islladdr, \
15     in6_mactoifaceid, in6_ismaddr
16 from scapy.layers.inet6 import IPv6, UDP, ICMPv6ND_NS, ICMPv6ND_RS, \
17     ICMPv6ND_RA, ICMPv6NDOptSrcLLAddr, getmacbyip6, ICMPv6MRD_Solicitation, \
18     ICMPv6NDOptMTU, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptPrefixInfo, \
19     ICMPv6ND_NA, ICMPv6NDOptDstLLAddr, ICMPv6DestUnreach, icmp6types
20
21 from framework import VppTestCase, VppTestRunner
22 from util import Host, ppp, mactobinary
23
24
25 class TestL2bdArpTerm(VppTestCase):
26     """ L2BD arp termination Test Case """
27
28     @classmethod
29     def setUpClass(cls):
30         """
31         Perform standard class setup (defined by class method setUpClass in
32         class VppTestCase) before running the test case, set test case related
33         variables and configure VPP.
34         """
35         super(TestL2bdArpTerm, cls).setUpClass()
36
37         try:
38             # Create pg interfaces
39             n_bd = 1
40             cls.ifs_per_bd = ifs_per_bd = 3
41             n_ifs = n_bd * ifs_per_bd
42             cls.create_pg_interfaces(range(n_ifs))
43
44             # Set up all interfaces
45             for i in cls.pg_interfaces:
46                 i.admin_up()
47
48             cls.hosts = set()
49
50         except Exception:
51             super(TestL2bdArpTerm, cls).tearDownClass()
52             raise
53
54     def setUp(self):
55         """
56         Clear trace and packet infos before running each test.
57         """
58         self.reset_packet_infos()
59         super(TestL2bdArpTerm, self).setUp()
60
61     def tearDown(self):
62         """
63         Show various debug prints after each test.
64         """
65         super(TestL2bdArpTerm, self).tearDown()
66         if not self.vpp_dead:
67             self.logger.info(self.vapi.ppcli("show l2fib verbose"))
68             self.logger.info(self.vapi.ppcli("show bridge-domain 1 detail"))
69
70     def add_del_arp_term_hosts(self, entries, bd_id=1, is_add=1, is_ipv6=0):
71         for e in entries:
72             ip = e.ip4n if is_ipv6 == 0 else e.ip6n
73             self.vapi.bd_ip_mac_add_del(bd_id=bd_id,
74                                         mac=e.bin_mac,
75                                         ip=ip,
76                                         is_ipv6=is_ipv6,
77                                         is_add=is_add)
78
79     @classmethod
80     def mac_list(cls, b6_range):
81         return ["00:00:ca:fe:00:%02x" % b6 for b6 in b6_range]
82
83     @classmethod
84     def ip4_host(cls, subnet, host, mac):
85         return Host(mac=mac,
86                     ip4="172.17.1%02u.%u" % (subnet, host))
87
88     @classmethod
89     def ip4_hosts(cls, subnet, start, mac_list):
90         return {cls.ip4_host(subnet, start + j, mac_list[j])
91                 for j in range(len(mac_list))}
92
93     @classmethod
94     def ip6_host(cls, subnet, host, mac):
95         return Host(mac=mac,
96                     ip6="fd01:%x::%x" % (subnet, host))
97
98     @classmethod
99     def ip6_hosts(cls, subnet, start, mac_list):
100         return {cls.ip6_host(subnet, start + j, mac_list[j])
101                 for j in range(len(mac_list))}
102
103     @classmethod
104     def bd_swifs(cls, b):
105         n = cls.ifs_per_bd
106         start = (b - 1) * n
107         return [cls.pg_interfaces[j] for j in range(start, start + n)]
108
109     def bd_add_del(self, bd_id=1, is_add=1):
110         if is_add:
111             self.vapi.bridge_domain_add_del(bd_id=bd_id, is_add=is_add)
112         for swif in self.bd_swifs(bd_id):
113             swif_idx = swif.sw_if_index
114             self.vapi.sw_interface_set_l2_bridge(
115                 swif_idx, bd_id=bd_id, enable=is_add)
116         if not is_add:
117             self.vapi.bridge_domain_add_del(bd_id=bd_id, is_add=is_add)
118
119     @classmethod
120     def arp_req(cls, src_host, host):
121         return (Ether(dst="ff:ff:ff:ff:ff:ff", src=src_host.mac) /
122                 ARP(op="who-has",
123                     hwsrc=src_host.bin_mac,
124                     pdst=host.ip4,
125                     psrc=src_host.ip4))
126
127     @classmethod
128     def arp_reqs(cls, src_host, entries):
129         return [cls.arp_req(src_host, e) for e in entries]
130
131     @classmethod
132     def garp_req(cls, host):
133         return cls.arp_req(host, host)
134
135     @classmethod
136     def garp_reqs(cls, entries):
137         return [cls.garp_req(e) for e in entries]
138
139     def arp_resp_host(self, src_host, arp_resp):
140         ether = arp_resp[Ether]
141         self.assertEqual(ether.dst, src_host.mac)
142
143         arp = arp_resp[ARP]
144         self.assertEqual(arp.hwtype, 1)
145         self.assertEqual(arp.ptype, 0x800)
146         self.assertEqual(arp.hwlen, 6)
147         self.assertEqual(arp.plen, 4)
148         arp_opts = {"who-has": 1, "is-at": 2}
149         self.assertEqual(arp.op, arp_opts["is-at"])
150         self.assertEqual(arp.hwdst, src_host.mac)
151         self.assertEqual(arp.pdst, src_host.ip4)
152         return Host(mac=arp.hwsrc, ip4=arp.psrc)
153
154     def arp_resp_hosts(self, src_host, pkts):
155         return {self.arp_resp_host(src_host, p) for p in pkts}
156
157     def inttoip4(self, ip):
158         o1 = int(ip / 16777216) % 256
159         o2 = int(ip / 65536) % 256
160         o3 = int(ip / 256) % 256
161         o4 = int(ip) % 256
162         return '%(o1)s.%(o2)s.%(o3)s.%(o4)s' % locals()
163
164     def arp_event_host(self, e):
165         return Host(mac=':'.join(['%02x' % ord(char) for char in e.new_mac]),
166                     ip4=self.inttoip4(e.address))
167
168     def arp_event_hosts(self, evs):
169         return {self.arp_event_host(e) for e in evs}
170
171     def nd_event_host(self, e):
172         return Host(mac=':'.join(['%02x' % ord(char) for char in e.new_mac]),
173                     ip6=inet_ntop(AF_INET6, e.address))
174
175     def nd_event_hosts(self, evs):
176         return {self.nd_event_host(e) for e in evs}
177
178     @classmethod
179     def ns_req(cls, src_host, host):
180         nsma = in6_getnsma(inet_pton(AF_INET6, "fd10::ffff"))
181         d = inet_ntop(AF_INET6, nsma)
182         return (Ether(dst="ff:ff:ff:ff:ff:ff", src=src_host.mac) /
183                 IPv6(dst=d, src=src_host.ip6) /
184                 ICMPv6ND_NS(tgt=host.ip6) /
185                 ICMPv6NDOptSrcLLAddr(lladdr=src_host.mac))
186
187     @classmethod
188     def ns_reqs_dst(cls, entries, dst_host):
189         return [cls.ns_req(e, dst_host) for e in entries]
190
191     @classmethod
192     def ns_reqs_src(cls, src_host, entries):
193         return [cls.ns_req(src_host, e) for e in entries]
194
195     def na_resp_host(self, src_host, rx):
196         self.assertEqual(rx[Ether].dst, src_host.mac)
197         self.assertEqual(in6_ptop(rx[IPv6].dst),
198                          in6_ptop(src_host.ip6))
199
200         self.assertTrue(rx.haslayer(ICMPv6ND_NA))
201         self.assertTrue(rx.haslayer(ICMPv6NDOptDstLLAddr))
202
203         na = rx[ICMPv6ND_NA]
204         return Host(mac=na.lladdr, ip6=na.tgt)
205
206     def na_resp_hosts(self, src_host, pkts):
207         return {self.na_resp_host(src_host, p) for p in pkts}
208
209     def set_bd_flags(self, bd_id, **args):
210         """
211         Enable/disable defined feature(s) of the bridge domain.
212
213         :param int bd_id: Bridge domain ID.
214         :param list args: List of feature/status pairs. Allowed features: \
215         learn, forward, flood, uu_flood and arp_term. Status False means \
216         disable, status True means enable the feature.
217         :raise: ValueError in case of unknown feature in the input.
218         """
219         for flag in args:
220             if flag == "learn":
221                 feature_bitmap = 1 << 0
222             elif flag == "forward":
223                 feature_bitmap = 1 << 1
224             elif flag == "flood":
225                 feature_bitmap = 1 << 2
226             elif flag == "uu_flood":
227                 feature_bitmap = 1 << 3
228             elif flag == "arp_term":
229                 feature_bitmap = 1 << 4
230             else:
231                 raise ValueError("Unknown feature used: %s" % flag)
232             is_set = 1 if args[flag] else 0
233             self.vapi.bridge_flags(bd_id, is_set, feature_bitmap)
234         self.logger.info("Bridge domain ID %d updated" % bd_id)
235
236     def verify_arp(self, src_host, req_hosts, resp_hosts, bd_id=1):
237         reqs = self.arp_reqs(src_host, req_hosts)
238
239         for swif in self.bd_swifs(bd_id):
240             swif.add_stream(reqs)
241
242         self.pg_enable_capture(self.pg_interfaces)
243         self.pg_start()
244
245         for swif in self.bd_swifs(bd_id):
246             resp_pkts = swif.get_capture(len(resp_hosts))
247             resps = self.arp_resp_hosts(src_host, resp_pkts)
248             self.assertEqual(len(resps ^ resp_hosts), 0)
249
250     def verify_nd(self, src_host, req_hosts, resp_hosts, bd_id=1):
251         reqs = self.ns_reqs_src(src_host, req_hosts)
252
253         for swif in self.bd_swifs(bd_id):
254             swif.add_stream(reqs)
255
256         self.pg_enable_capture(self.pg_interfaces)
257         self.pg_start()
258
259         for swif in self.bd_swifs(bd_id):
260             resp_pkts = swif.get_capture(len(resp_hosts))
261             resps = self.na_resp_hosts(src_host, resp_pkts)
262             self.assertEqual(len(resps ^ resp_hosts), 0)
263
264     def test_l2bd_arp_term_01(self):
265         """ L2BD arp term - add 5 hosts, verify arp responses
266         """
267         src_host = self.ip4_host(50, 50, "00:00:11:22:33:44")
268         self.bd_add_del(1, is_add=1)
269         self.set_bd_flags(1, arp_term=True, flood=False,
270                           uu_flood=False, learn=False)
271         macs = self.mac_list(range(1, 5))
272         hosts = self.ip4_hosts(4, 1, macs)
273         self.add_del_arp_term_hosts(hosts, is_add=1)
274         self.verify_arp(src_host, hosts, hosts)
275         type(self).hosts = hosts
276
277     def test_l2bd_arp_term_02(self):
278         """ L2BD arp term - delete 3 hosts, verify arp responses
279         """
280         src_host = self.ip4_host(50, 50, "00:00:11:22:33:44")
281         macs = self.mac_list(range(1, 3))
282         deleted = self.ip4_hosts(4, 1, macs)
283         self.add_del_arp_term_hosts(deleted, is_add=0)
284         remaining = self.hosts - deleted
285         self.verify_arp(src_host, self.hosts, remaining)
286         type(self).hosts = remaining
287         self.bd_add_del(1, is_add=0)
288
289     def test_l2bd_arp_term_03(self):
290         """ L2BD arp term - recreate BD1, readd 3 hosts, verify arp responses
291         """
292         src_host = self.ip4_host(50, 50, "00:00:11:22:33:44")
293         self.bd_add_del(1, is_add=1)
294         self.set_bd_flags(1, arp_term=True, flood=False,
295                           uu_flood=False, learn=False)
296         macs = self.mac_list(range(1, 3))
297         readded = self.ip4_hosts(4, 1, macs)
298         self.add_del_arp_term_hosts(readded, is_add=1)
299         self.verify_arp(src_host, self.hosts | readded, readded)
300         type(self).hosts = readded
301
302     def test_l2bd_arp_term_04(self):
303         """ L2BD arp term - 2 IP4 addrs per host
304         """
305         src_host = self.ip4_host(50, 50, "00:00:11:22:33:44")
306         macs = self.mac_list(range(1, 3))
307         sub5_hosts = self.ip4_hosts(5, 1, macs)
308         self.add_del_arp_term_hosts(sub5_hosts, is_add=1)
309         hosts = self.hosts | sub5_hosts
310         self.verify_arp(src_host, hosts, hosts)
311         type(self).hosts = hosts
312         self.bd_add_del(1, is_add=0)
313
314     def test_l2bd_arp_term_05(self):
315         """ L2BD arp term - create and update 10 IP4-mac pairs
316         """
317         src_host = self.ip4_host(50, 50, "00:00:11:22:33:44")
318         self.bd_add_del(1, is_add=1)
319         self.set_bd_flags(1, arp_term=True, flood=False,
320                           uu_flood=False, learn=False)
321         macs1 = self.mac_list(range(10, 20))
322         hosts1 = self.ip4_hosts(5, 1, macs1)
323         self.add_del_arp_term_hosts(hosts1, is_add=1)
324         self.verify_arp(src_host, hosts1, hosts1)
325         macs2 = self.mac_list(range(20, 30))
326         hosts2 = self.ip4_hosts(5, 1, macs2)
327         self.add_del_arp_term_hosts(hosts2, is_add=1)
328         self.verify_arp(src_host, hosts1, hosts2)
329         self.bd_add_del(1, is_add=0)
330
331     def test_l2bd_arp_term_06(self):
332         """ L2BD arp/ND term - hosts with both ip4/ip6
333         """
334         src_host4 = self.ip4_host(50, 50, "00:00:11:22:33:44")
335         src_host6 = self.ip6_host(50, 50, "00:00:11:22:33:44")
336         self.bd_add_del(1, is_add=1)
337         # enable flood to make sure requests are not flooded
338         self.set_bd_flags(1, arp_term=True, flood=True,
339                           uu_flood=False, learn=False)
340         macs = self.mac_list(range(10, 20))
341         hosts6 = self.ip6_hosts(5, 1, macs)
342         hosts4 = self.ip4_hosts(5, 1, macs)
343         self.add_del_arp_term_hosts(hosts4, is_add=1)
344         self.add_del_arp_term_hosts(hosts6, is_add=1, is_ipv6=1)
345         self.verify_arp(src_host4, hosts4, hosts4)
346         self.verify_nd(src_host6, hosts6, hosts6)
347         self.bd_add_del(1, is_add=0)
348
349     def test_l2bd_arp_term_07(self):
350         """ L2BD ND term - Add and Del hosts, verify ND replies
351         """
352         src_host6 = self.ip6_host(50, 50, "00:00:11:22:33:44")
353         self.bd_add_del(1, is_add=1)
354         self.set_bd_flags(1, arp_term=True, flood=False,
355                           uu_flood=False, learn=False)
356         macs = self.mac_list(range(10, 20))
357         hosts6 = self.ip6_hosts(5, 1, macs)
358         self.add_del_arp_term_hosts(hosts6, is_add=1, is_ipv6=1)
359         self.verify_nd(src_host6, hosts6, hosts6)
360         del_macs = self.mac_list(range(10, 15))
361         deleted = self.ip6_hosts(5, 1, del_macs)
362         self.add_del_arp_term_hosts(deleted, is_add=0, is_ipv6=1)
363         self.verify_nd(src_host6, hosts6, hosts6 - deleted)
364         self.bd_add_del(1, is_add=0)
365
366     def test_l2bd_arp_term_08(self):
367         """ L2BD ND term - Add and update IP+mac, verify ND replies
368         """
369         src_host = self.ip6_host(50, 50, "00:00:11:22:33:44")
370         self.bd_add_del(1, is_add=1)
371         self.set_bd_flags(1, arp_term=True, flood=False,
372                           uu_flood=False, learn=False)
373         macs1 = self.mac_list(range(10, 20))
374         hosts = self.ip6_hosts(5, 1, macs1)
375         self.add_del_arp_term_hosts(hosts, is_add=1, is_ipv6=1)
376         self.verify_nd(src_host, hosts, hosts)
377         macs2 = self.mac_list(range(20, 30))
378         updated = self.ip6_hosts(5, 1, macs2)
379         self.add_del_arp_term_hosts(updated, is_add=1, is_ipv6=1)
380         self.verify_nd(src_host, hosts, updated)
381         self.bd_add_del(1, is_add=0)
382
383     def test_l2bd_arp_term_09(self):
384         """ L2BD arp term - send garps, verify arp event reports
385         """
386         self.vapi.want_ip4_arp_events()
387         self.bd_add_del(1, is_add=1)
388         self.set_bd_flags(1, arp_term=True, flood=False,
389                           uu_flood=False, learn=False)
390         macs = self.mac_list(range(90, 95))
391         hosts = self.ip4_hosts(5, 1, macs)
392
393         garps = self.garp_reqs(hosts)
394         self.bd_swifs(1)[0].add_stream(garps)
395
396         self.pg_enable_capture(self.pg_interfaces)
397         self.pg_start()
398         evs = [self.vapi.wait_for_event(1, "ip4_arp_event")
399                for i in range(len(hosts))]
400         ev_hosts = self.arp_event_hosts(evs)
401         self.assertEqual(len(ev_hosts ^ hosts), 0)
402
403     def test_l2bd_arp_term_10(self):
404         """ L2BD arp term - send duplicate garps, verify suppression
405         """
406         macs = self.mac_list(range(70, 71))
407         hosts = self.ip4_hosts(6, 1, macs)
408
409         """ send the packet 5 times expect one event
410         """
411         garps = self.garp_reqs(hosts) * 5
412         self.bd_swifs(1)[0].add_stream(garps)
413
414         self.pg_enable_capture(self.pg_interfaces)
415         self.pg_start()
416         evs = [self.vapi.wait_for_event(1, "ip4_arp_event")
417                for i in range(len(hosts))]
418         ev_hosts = self.arp_event_hosts(evs)
419         self.assertEqual(len(ev_hosts ^ hosts), 0)
420
421     def test_l2bd_arp_term_11(self):
422         """ L2BD arp term - disable ip4 arp events,send garps, verify no events
423         """
424         self.vapi.want_ip4_arp_events(enable_disable=0)
425         macs = self.mac_list(range(90, 95))
426         hosts = self.ip4_hosts(5, 1, macs)
427
428         garps = self.garp_reqs(hosts)
429         self.bd_swifs(1)[0].add_stream(garps)
430
431         self.pg_enable_capture(self.pg_interfaces)
432         self.pg_start()
433         self.sleep(1)
434         self.assertEqual(len(self.vapi.collect_events()), 0)
435         self.bd_add_del(1, is_add=0)
436
437     def test_l2bd_arp_term_12(self):
438         """ L2BD ND term - send NS packets verify reports
439         """
440         self.vapi.want_ip6_nd_events(address=inet_pton(AF_INET6, "::0"))
441         dst_host = self.ip6_host(50, 50, "00:00:11:22:33:44")
442         self.bd_add_del(1, is_add=1)
443         self.set_bd_flags(1, arp_term=True, flood=False,
444                           uu_flood=False, learn=False)
445         macs = self.mac_list(range(10, 15))
446         hosts = self.ip6_hosts(5, 1, macs)
447         reqs = self.ns_reqs_dst(hosts, dst_host)
448         self.bd_swifs(1)[0].add_stream(reqs)
449
450         self.pg_enable_capture(self.pg_interfaces)
451         self.pg_start()
452         evs = [self.vapi.wait_for_event(2, "ip6_nd_event")
453                for i in range(len(hosts))]
454         ev_hosts = self.nd_event_hosts(evs)
455         self.assertEqual(len(ev_hosts ^ hosts), 0)
456
457     def test_l2bd_arp_term_13(self):
458         """ L2BD ND term - send duplicate ns, verify suppression
459         """
460         dst_host = self.ip6_host(50, 50, "00:00:11:22:33:44")
461         macs = self.mac_list(range(10, 11))
462         hosts = self.ip6_hosts(5, 1, macs)
463         reqs = self.ns_reqs_dst(hosts, dst_host) * 5
464         self.bd_swifs(1)[0].add_stream(reqs)
465
466         self.pg_enable_capture(self.pg_interfaces)
467         self.pg_start()
468         evs = [self.vapi.wait_for_event(2, "ip6_nd_event")
469                for i in range(len(hosts))]
470         ev_hosts = self.nd_event_hosts(evs)
471         self.assertEqual(len(ev_hosts ^ hosts), 0)
472
473     def test_l2bd_arp_term_14(self):
474         """ L2BD ND term - disable ip4 arp events,send ns, verify no events
475         """
476         self.vapi.want_ip6_nd_events(enable_disable=0,
477                                      address=inet_pton(AF_INET6, "::0"))
478         dst_host = self.ip6_host(50, 50, "00:00:11:22:33:44")
479         macs = self.mac_list(range(10, 15))
480         hosts = self.ip6_hosts(5, 1, macs)
481         reqs = self.ns_reqs_dst(hosts, dst_host)
482         self.bd_swifs(1)[0].add_stream(reqs)
483
484         self.pg_enable_capture(self.pg_interfaces)
485         self.pg_start()
486         self.sleep(1)
487         self.assertEqual(len(self.vapi.collect_events()), 0)
488         self.bd_add_del(1, is_add=0)
489
490
491 if __name__ == '__main__':
492     unittest.main(testRunner=VppTestRunner)