misc: Fix python scripts shebang line
[vpp.git] / test / test_punt.py
1 #!/usr/bin/env python3
2 import binascii
3 import random
4 import socket
5 import os
6 import threading
7 import struct
8 import copy
9 from struct import unpack, unpack_from
10
11 try:
12     import unittest2 as unittest
13 except ImportError:
14     import unittest
15
16 from util import ppp, ppc
17 from re import compile
18 import scapy.compat
19 from scapy.packet import Raw
20 from scapy.layers.l2 import Ether
21 from scapy.layers.inet import IP, UDP, ICMP
22 from scapy.layers.ipsec import ESP
23 import scapy.layers.inet6 as inet6
24 from scapy.layers.inet6 import IPv6, ICMPv6DestUnreach
25 from scapy.contrib.ospf import OSPF_Hdr, OSPFv3_Hello
26 from framework import VppTestCase, VppTestRunner
27
28 from vpp_ip import DpoProto
29 from vpp_ip_route import VppIpRoute, VppRoutePath
30 from vpp_papi import VppEnum
31 from vpp_ipsec_tun_interface import VppIpsecTunInterface
32
33 NUM_PKTS = 67
34
35
36 class serverSocketThread(threading.Thread):
37     """ Socket server thread"""
38
39     def __init__(self, threadID, sockName):
40         threading.Thread.__init__(self)
41         self.threadID = threadID
42         self.sockName = sockName
43         self.sock = None
44         self.rx_pkts = []
45
46     def rx_packets(self):
47         # Wait for some packets on socket
48         while True:
49             data = self.sock.recv(65536)
50
51             # punt socket metadata
52             # packet_desc = data[0:8]
53
54             # Ethernet
55             self.rx_pkts.append(Ether(data[8:]))
56
57     def run(self):
58         self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
59         try:
60             os.unlink(self.sockName)
61         except:
62             pass
63         self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536)
64         self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536)
65         self.sock.bind(self.sockName)
66
67         self.rx_packets()
68
69     def close(self):
70         self.sock.close()
71         return self.rx_pkts
72
73
74 class TestPuntSocket(VppTestCase):
75     """ Punt Socket """
76
77     ports = [1111, 2222, 3333, 4444]
78     sock_servers = list()
79     # FIXME: nr_packets > 3 results in failure
80     # nr_packets = 3 makes the test unstable
81     nr_packets = 2
82
83     @classmethod
84     def setUpClass(cls):
85         super(TestPuntSocket, cls).setUpClass()
86
87     @classmethod
88     def tearDownClass(cls):
89         super(TestPuntSocket, cls).tearDownClass()
90
91     @classmethod
92     def setUpConstants(cls):
93         cls.extra_vpp_punt_config = [
94             "punt", "{", "socket", cls.tempdir+"/socket_punt", "}"]
95         super(TestPuntSocket, cls).setUpConstants()
96
97     def setUp(self):
98         super(TestPuntSocket, self).setUp()
99         random.seed()
100
101         self.create_pg_interfaces(range(2))
102         for i in self.pg_interfaces:
103             i.admin_up()
104
105     def tearDown(self):
106         del self.sock_servers[:]
107         super(TestPuntSocket, self).tearDown()
108
109     def socket_client_create(self, sock_name, id=None):
110         thread = serverSocketThread(id, sock_name)
111         self.sock_servers.append(thread)
112         thread.start()
113         return thread
114
115     def socket_client_close(self):
116         rx_pkts = []
117         for thread in self.sock_servers:
118             rx_pkts += thread.close()
119         return rx_pkts
120
121     def verify_port(self, pr, vpr):
122         self.assertEqual(vpr.punt.type, pr['type'])
123         self.assertEqual(vpr.punt.punt.l4.port,
124                          pr['punt']['l4']['port'])
125         self.assertEqual(vpr.punt.punt.l4.protocol,
126                          pr['punt']['l4']['protocol'])
127         self.assertEqual(vpr.punt.punt.l4.af,
128                          pr['punt']['l4']['af'])
129
130     def verify_exception(self, pr, vpr):
131         self.assertEqual(vpr.punt.type, pr['type'])
132         self.assertEqual(vpr.punt.punt.exception.id,
133                          pr['punt']['exception']['id'])
134
135     def verify_ip_proto(self, pr, vpr):
136         self.assertEqual(vpr.punt.type, pr['type'])
137         self.assertEqual(vpr.punt.punt.ip_proto.af,
138                          pr['punt']['ip_proto']['af'])
139         self.assertEqual(vpr.punt.punt.ip_proto.protocol,
140                          pr['punt']['ip_proto']['protocol'])
141
142     def verify_udp_pkts(self, rxs, n_rx, port):
143         n_match = 0
144         for rx in rxs:
145             self.assertTrue(rx.haslayer(UDP))
146             if rx[UDP].dport == port:
147                 n_match += 1
148         self.assertEqual(n_match, n_rx)
149
150
151 def set_port(pr, port):
152     pr['punt']['l4']['port'] = port
153     return pr
154
155
156 def set_reason(pr, reason):
157     pr['punt']['exception']['id'] = reason
158     return pr
159
160
161 def mk_vpp_cfg4():
162     pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
163     af_ip4 = VppEnum.vl_api_address_family_t.ADDRESS_IP4
164     udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
165     punt_l4 = {
166         'type': pt_l4,
167         'punt': {
168             'l4': {
169                 'af': af_ip4,
170                 'protocol': udp_proto
171             }
172         }
173     }
174     return punt_l4
175
176
177 def mk_vpp_cfg6():
178     pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
179     af_ip6 = VppEnum.vl_api_address_family_t.ADDRESS_IP6
180     udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
181     punt_l4 = {
182         'type': pt_l4,
183         'punt': {
184             'l4': {
185                 'af': af_ip6,
186                 'protocol': udp_proto
187             }
188         }
189     }
190     return punt_l4
191
192
193 class TestIP4PuntSocket(TestPuntSocket):
194     """ Punt Socket for IPv4 UDP """
195
196     @classmethod
197     def setUpClass(cls):
198         super(TestIP4PuntSocket, cls).setUpClass()
199
200     @classmethod
201     def tearDownClass(cls):
202         super(TestIP4PuntSocket, cls).tearDownClass()
203
204     def setUp(self):
205         super(TestIP4PuntSocket, self).setUp()
206
207         for i in self.pg_interfaces:
208             i.config_ip4()
209             i.resolve_arp()
210
211     def tearDown(self):
212         super(TestIP4PuntSocket, self).tearDown()
213         for i in self.pg_interfaces:
214             i.unconfig_ip4()
215             i.admin_down()
216
217     def test_punt_socket_dump(self):
218         """ Punt socket registration/deregistration"""
219
220         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
221         af_ip4 = VppEnum.vl_api_address_family_t.ADDRESS_IP4
222         udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
223
224         punts = self.vapi.punt_socket_dump(type=pt_l4)
225         self.assertEqual(len(punts), 0)
226
227         #
228         # configure a punt socket
229         #
230         punt_l4 = mk_vpp_cfg4()
231
232         self.vapi.punt_socket_register(set_port(punt_l4, 1111),
233                                        "%s/socket_punt_1111" % self.tempdir)
234         self.vapi.punt_socket_register(set_port(punt_l4, 2222),
235                                        "%s/socket_punt_2222" % self.tempdir)
236         punts = self.vapi.punt_socket_dump(type=pt_l4)
237         self.assertEqual(len(punts), 2)
238         self.verify_port(set_port(punt_l4, 1111), punts[0])
239         self.verify_port(set_port(punt_l4, 2222), punts[1])
240
241         #
242         # deregister a punt socket
243         #
244         self.vapi.punt_socket_deregister(set_port(punt_l4, 1111))
245         punts = self.vapi.punt_socket_dump(type=pt_l4)
246         self.assertEqual(len(punts), 1)
247
248         #
249         # configure a punt socket again
250         #
251         self.vapi.punt_socket_register(set_port(punt_l4, 1111),
252                                        "%s/socket_punt_1111" % self.tempdir)
253         self.vapi.punt_socket_register(set_port(punt_l4, 3333),
254                                        "%s/socket_punt_3333" % self.tempdir)
255         punts = self.vapi.punt_socket_dump(type=pt_l4)
256         self.assertEqual(len(punts), 3)
257
258         self.logger.info(self.vapi.cli("sh punt sock reg"))
259
260         #
261         # deregister all punt socket
262         #
263         self.vapi.punt_socket_deregister(set_port(punt_l4, 1111))
264         self.vapi.punt_socket_deregister(set_port(punt_l4, 2222))
265         self.vapi.punt_socket_deregister(set_port(punt_l4, 3333))
266         punts = self.vapi.punt_socket_dump(type=pt_l4)
267         self.assertEqual(len(punts), 0)
268
269     def test_punt_socket_traffic_single_port_single_socket(self):
270         """ Punt socket traffic single port single socket"""
271
272         port = self.ports[0]
273         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
274         punt_l4 = set_port(mk_vpp_cfg4(), port)
275
276         p = (Ether(src=self.pg0.remote_mac,
277                    dst=self.pg0.local_mac) /
278              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
279              UDP(sport=9876, dport=port) /
280              Raw(b'\xa5' * 100))
281
282         pkts = p * self.nr_packets
283
284         punts = self.vapi.punt_socket_dump(type=pt_l4)
285         self.assertEqual(len(punts), 0)
286
287         #
288         # expect ICMP - port unreachable for all packets
289         #
290         rx = self.send_and_expect(self.pg0, pkts, self.pg0)
291
292         for p in rx:
293             self.assertEqual(int(p[IP].proto), 1)   # ICMP
294             self.assertEqual(int(p[ICMP].code), 3)  # unreachable
295
296         #
297         # configure a punt socket
298         #
299         self.socket_client_create("%s/socket_%d" % (self.tempdir, port))
300         self.vapi.punt_socket_register(punt_l4, "%s/socket_%d" %
301                                        (self.tempdir, port))
302         punts = self.vapi.punt_socket_dump(type=pt_l4)
303         self.assertEqual(len(punts), 1)
304
305         #
306         # expect punt socket and no packets on pg0
307         #
308         self.send_and_assert_no_replies(self.pg0, pkts)
309         rx = self.socket_client_close()
310         self.verify_udp_pkts(rx, len(pkts), port)
311
312         #
313         # remove punt socket. expect ICMP - port unreachable for all packets
314         #
315         self.vapi.punt_socket_deregister(punt_l4)
316         punts = self.vapi.punt_socket_dump(type=pt_l4)
317         self.assertEqual(len(punts), 0)
318
319         rx = self.send_and_expect(self.pg0, pkts, self.pg0)
320         for p in rx:
321             self.assertEqual(int(p[IP].proto), 1)   # ICMP
322             self.assertEqual(int(p[ICMP].code), 3)  # unreachable
323
324     def test_punt_socket_traffic_multi_ports_multi_sockets(self):
325         """ Punt socket traffic multi ports and multi sockets"""
326
327         punt_l4 = mk_vpp_cfg4()
328
329         # configuration for each UDP port
330         cfgs = dict()
331
332         #
333         # create stream of packets for each port
334         #
335         for port in self.ports:
336             # choose port from port list
337             cfgs[port] = {}
338
339             pkt = (Ether(src=self.pg0.remote_mac,
340                          dst=self.pg0.local_mac) /
341                    IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
342                    UDP(sport=9876, dport=port) /
343                    Raw(b'\xa5' * 100))
344             cfgs[port]['pkts'] = pkt * self.nr_packets
345             cfgs[port]['port'] = port
346             cfgs[port]['vpp'] = copy.deepcopy(set_port(punt_l4, port))
347
348             # configure punt sockets
349             cfgs[port]['sock'] = self.socket_client_create(
350                 "%s/socket_%d" % (self.tempdir, port))
351             self.vapi.punt_socket_register(
352                 cfgs[port]['vpp'],
353                 "%s/socket_%d" % (self.tempdir, port))
354
355         #
356         # send the packets that get punted
357         #
358         for cfg in cfgs.values():
359             self.send_and_assert_no_replies(self.pg0, cfg['pkts'])
360
361         #
362         # test that we got the excepted packets on the expected socket
363         #
364         for cfg in cfgs.values():
365             rx = cfg['sock'].close()
366             self.verify_udp_pkts(rx, len(cfg['pkts']), cfg['port'])
367             self.vapi.punt_socket_deregister(cfg['vpp'])
368
369     def test_punt_socket_traffic_multi_ports_single_socket(self):
370         """ Punt socket traffic multi ports and single socket"""
371
372         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
373         punt_l4 = mk_vpp_cfg4()
374
375         #
376         # create stream of packets with each port
377         #
378         pkts = []
379         for port in self.ports:
380             # choose port from port list
381             pkt = (Ether(src=self.pg0.remote_mac,
382                          dst=self.pg0.local_mac) /
383                    IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
384                    UDP(sport=9876, dport=port) /
385                    Raw(b'\xa5' * 100))
386             pkts += pkt * self.nr_packets
387
388         #
389         # configure a punt socket
390         #
391         self.socket_client_create("%s/socket_multi" % self.tempdir)
392         for p in self.ports:
393             self.vapi.punt_socket_register(set_port(punt_l4, p),
394                                            "%s/socket_multi" % self.tempdir)
395         punts = self.vapi.punt_socket_dump(type=pt_l4)
396         self.assertEqual(len(punts), len(self.ports))
397
398         #
399         # expect punt socket and no packets on pg0
400         #
401         self.send_and_assert_no_replies(self.pg0, pkts)
402         self.logger.info(self.vapi.cli("show trace"))
403         rx = self.socket_client_close()
404
405         for p in self.ports:
406             self.verify_udp_pkts(rx, self.nr_packets, p)
407             self.vapi.punt_socket_deregister(set_port(punt_l4, p))
408         punts = self.vapi.punt_socket_dump(type=pt_l4)
409         self.assertEqual(len(punts), 0)
410
411
412 class TestIP6PuntSocket(TestPuntSocket):
413     """ Punt Socket for IPv6 UDP """
414
415     @classmethod
416     def setUpClass(cls):
417         super(TestIP6PuntSocket, cls).setUpClass()
418
419     @classmethod
420     def tearDownClass(cls):
421         super(TestIP6PuntSocket, cls).tearDownClass()
422
423     def setUp(self):
424         super(TestIP6PuntSocket, self).setUp()
425
426         for i in self.pg_interfaces:
427             i.config_ip6()
428             i.resolve_ndp()
429
430     def tearDown(self):
431         super(TestIP6PuntSocket, self).tearDown()
432         for i in self.pg_interfaces:
433             i.unconfig_ip6()
434             i.admin_down()
435
436     def test_punt_socket_dump(self):
437         """ Punt socket registration """
438
439         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
440         af_ip6 = VppEnum.vl_api_address_family_t.ADDRESS_IP6
441         udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
442         #
443         # configure a punt socket
444         #
445         punt_l4 = {
446             'type': pt_l4,
447             'punt': {
448                 'l4': {
449                     'af': af_ip6,
450                     'protocol': udp_proto
451                 }
452             }
453         }
454
455         punts = self.vapi.punt_socket_dump(type=pt_l4)
456         self.assertEqual(len(punts), 0)
457
458         #
459         # configure a punt socket
460         #
461         self.vapi.punt_socket_register(set_port(punt_l4, 1111),
462                                        "%s/socket_1111" % self.tempdir)
463         self.vapi.punt_socket_register(set_port(punt_l4, 2222),
464                                        "%s/socket_2222" % self.tempdir)
465         punts = self.vapi.punt_socket_dump(type=pt_l4)
466         self.assertEqual(len(punts), 2)
467         self.verify_port(set_port(punt_l4, 1111), punts[0])
468         self.verify_port(set_port(punt_l4, 2222), punts[1])
469
470         #
471         # deregister a punt socket
472         #
473         self.vapi.punt_socket_deregister(set_port(punt_l4, 1111))
474         punts = self.vapi.punt_socket_dump(type=pt_l4)
475         self.assertEqual(len(punts), 1)
476
477         #
478         # configure a punt socket again
479         #
480         self.vapi.punt_socket_register(set_port(punt_l4, 1111),
481                                        "%s/socket_1111" % self.tempdir)
482         punts = self.vapi.punt_socket_dump(type=pt_l4)
483         self.assertEqual(len(punts), 2)
484
485         #
486         # deregister all punt socket
487         #
488         self.vapi.punt_socket_deregister(set_port(punt_l4, 1111))
489         self.vapi.punt_socket_deregister(set_port(punt_l4, 2222))
490         self.vapi.punt_socket_deregister(set_port(punt_l4, 3333))
491         punts = self.vapi.punt_socket_dump(type=pt_l4)
492         self.assertEqual(len(punts), 0)
493
494     def test_punt_socket_traffic_single_port_single_socket(self):
495         """ Punt socket traffic single port single socket"""
496
497         port = self.ports[0]
498         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
499         af_ip6 = VppEnum.vl_api_address_family_t.ADDRESS_IP6
500         udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
501         punt_l4 = {
502             'type': pt_l4,
503             'punt': {
504                 'l4': {
505                     'af': af_ip6,
506                     'protocol': udp_proto,
507                     'port': port,
508                 }
509             }
510         }
511
512         p = (Ether(src=self.pg0.remote_mac,
513                    dst=self.pg0.local_mac) /
514              IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
515              inet6.UDP(sport=9876, dport=port) /
516              Raw(b'\xa5' * 100))
517
518         pkts = p * self.nr_packets
519
520         punts = self.vapi.punt_socket_dump(type=pt_l4)
521         self.assertEqual(len(punts), 0)
522
523         #
524         # expect ICMPv6 - destination unreachable for all packets
525         #
526         self.vapi.cli("clear trace")
527         self.pg0.add_stream(pkts)
528         self.pg_enable_capture(self.pg_interfaces)
529         self.pg_start()
530         # FIXME - when punt socket deregister is implemented
531         # rx = self.pg0.get_capture(self.nr_packets)
532         # for p in rx:
533         #     self.assertEqual(int(p[IPv6].nh), 58)                # ICMPv6
534         #     self.assertEqual(int(p[ICMPv6DestUnreach].code),4)  # unreachable
535
536         #
537         # configure a punt socket
538         #
539         self.socket_client_create("%s/socket_%d" % (self.tempdir, port))
540         self.vapi.punt_socket_register(punt_l4, "%s/socket_%d" %
541                                        (self.tempdir, port))
542         punts = self.vapi.punt_socket_dump(type=pt_l4)
543         self.assertEqual(len(punts), 1)
544
545         #
546         # expect punt socket and no packets on pg0
547         #
548         self.vapi.cli("clear errors")
549         self.vapi.cli("clear trace")
550         self.pg0.add_stream(pkts)
551         self.pg_enable_capture(self.pg_interfaces)
552         self.pg_start()
553         self.pg0.get_capture(0)
554         self.logger.info(self.vapi.cli("show trace"))
555         rx = self.socket_client_close()
556         self.verify_udp_pkts(rx, len(pkts), port)
557
558         #
559         # remove punt socket. expect ICMP - dest. unreachable for all packets
560         #
561         self.vapi.punt_socket_deregister(punt_l4)
562         punts = self.vapi.punt_socket_dump(type=pt_l4)
563         self.assertEqual(len(punts), 0)
564         self.pg0.add_stream(pkts)
565         self.pg_enable_capture(self.pg_interfaces)
566         self.pg_start()
567         # FIXME - when punt socket deregister is implemented
568         # self.pg0.get_capture(nr_packets)
569
570     def test_punt_socket_traffic_multi_ports_multi_sockets(self):
571         """ Punt socket traffic multi ports and multi sockets"""
572
573         punt_l4 = mk_vpp_cfg6()
574
575         # configuration for each UDP port
576         cfgs = dict()
577
578         #
579         # create stream of packets for each port
580         #
581         for port in self.ports:
582             # choose port from port list
583             cfgs[port] = {}
584
585             pkt = (Ether(src=self.pg0.remote_mac,
586                          dst=self.pg0.local_mac) /
587                    IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
588                    UDP(sport=9876, dport=port) /
589                    Raw(b'\xa5' * 100))
590             cfgs[port]['pkts'] = pkt * self.nr_packets
591             cfgs[port]['port'] = port
592             cfgs[port]['vpp'] = copy.deepcopy(set_port(punt_l4, port))
593
594             # configure punt sockets
595             cfgs[port]['sock'] = self.socket_client_create(
596                 "%s/socket_%d" % (self.tempdir, port))
597             self.vapi.punt_socket_register(
598                 cfgs[port]['vpp'],
599                 "%s/socket_%d" % (self.tempdir, port))
600
601         #
602         # send the packets that get punted
603         #
604         for cfg in cfgs.values():
605             self.send_and_assert_no_replies(self.pg0, cfg['pkts'])
606
607         #
608         # test that we got the excepted packets on the expected socket
609         #
610         for cfg in cfgs.values():
611             rx = cfg['sock'].close()
612             self.verify_udp_pkts(rx, len(cfg['pkts']), cfg['port'])
613             self.vapi.punt_socket_deregister(cfg['vpp'])
614
615     def test_punt_socket_traffic_multi_ports_single_socket(self):
616         """ Punt socket traffic multi ports and single socket"""
617
618         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
619         af_ip6 = VppEnum.vl_api_address_family_t.ADDRESS_IP6
620         udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
621         punt_l4 = {
622             'type': pt_l4,
623             'punt': {
624                 'l4': {
625                     'af': af_ip6,
626                     'protocol': udp_proto,
627                 }
628             }
629         }
630
631         #
632         # create stream of packets with each port
633         #
634         pkts = []
635         for port in self.ports:
636             # choose port from port list
637             pkt = (Ether(src=self.pg0.remote_mac,
638                          dst=self.pg0.local_mac) /
639                    IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
640                    UDP(sport=9876, dport=port) /
641                    Raw(b'\xa5' * 100))
642             pkts += pkt * self.nr_packets
643
644         #
645         # no punt socket
646         #
647         punts = self.vapi.punt_socket_dump(type=pt_l4)
648         self.assertEqual(len(punts), 0)
649
650         #
651         # configure a punt socket
652         #
653         self.socket_client_create("%s/socket_multi" % self.tempdir)
654         for p in self.ports:
655             self.vapi.punt_socket_register(set_port(punt_l4, p),
656                                            "%s/socket_multi" % self.tempdir)
657         punts = self.vapi.punt_socket_dump(type=pt_l4)
658         self.assertEqual(len(punts), len(self.ports))
659
660         #
661         # expect punt socket and no packets on pg0
662         #
663         self.vapi.cli("clear errors")
664         self.vapi.cli("clear trace")
665         self.pg0.add_stream(pkts)
666         self.pg_enable_capture(self.pg_interfaces)
667         self.pg_start()
668         # give a chance to punt socket to collect all packets
669         self.sleep(1)
670         self.pg0.get_capture(0)
671         rx = self.socket_client_close()
672
673         for p in self.ports:
674             self.verify_udp_pkts(rx, self.nr_packets, p)
675             self.vapi.punt_socket_deregister(set_port(punt_l4, p))
676         punts = self.vapi.punt_socket_dump(type=pt_l4)
677         self.assertEqual(len(punts), 0)
678
679
680 class TestExceptionPuntSocket(TestPuntSocket):
681     """ Punt Socket for Exceptions """
682
683     @classmethod
684     def setUpClass(cls):
685         super(TestExceptionPuntSocket, cls).setUpClass()
686
687     @classmethod
688     def tearDownClass(cls):
689         super(TestExceptionPuntSocket, cls).tearDownClass()
690
691     def setUp(self):
692         super(TestExceptionPuntSocket, self).setUp()
693
694         for i in self.pg_interfaces:
695             i.config_ip4()
696             i.resolve_arp()
697
698     def tearDown(self):
699         super(TestExceptionPuntSocket, self).tearDown()
700         for i in self.pg_interfaces:
701             i.unconfig_ip4()
702             i.admin_down()
703
704     def test_registration(self):
705         """ Punt socket registration/deregistration"""
706
707         pt_ex = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_EXCEPTION
708
709         punts = self.vapi.punt_socket_dump(type=pt_ex)
710         self.assertEqual(len(punts), 0)
711
712         #
713         # configure a punt socket
714         #
715         punt_ex = {
716             'type': pt_ex,
717             'punt': {
718                 'exception': {}
719             }
720         }
721
722         self.vapi.punt_socket_register(set_reason(punt_ex, 1),
723                                        "%s/socket_punt_1" % self.tempdir)
724         self.vapi.punt_socket_register(set_reason(punt_ex, 2),
725                                        "%s/socket_punt_2" % self.tempdir)
726         punts = self.vapi.punt_socket_dump(type=pt_ex)
727         self.assertEqual(len(punts), 2)
728         self.verify_exception(set_reason(punt_ex, 1), punts[0])
729         self.verify_exception(set_reason(punt_ex, 2), punts[1])
730
731         #
732         # deregister a punt socket
733         #
734         self.vapi.punt_socket_deregister(set_reason(punt_ex, 1))
735         punts = self.vapi.punt_socket_dump(type=pt_ex)
736         self.assertEqual(len(punts), 1)
737
738         #
739         # configure a punt socket again
740         #
741         self.vapi.punt_socket_register(set_reason(punt_ex, 1),
742                                        "%s/socket_punt_1" % self.tempdir)
743         self.vapi.punt_socket_register(set_reason(punt_ex, 3),
744                                        "%s/socket_punt_3" % self.tempdir)
745         punts = self.vapi.punt_socket_dump(type=pt_ex)
746         self.assertEqual(len(punts), 3)
747
748         self.logger.info(self.vapi.cli("sh punt sock reg exception"))
749
750         #
751         # deregister all punt socket
752         #
753         self.vapi.punt_socket_deregister(set_reason(punt_ex, 1))
754         self.vapi.punt_socket_deregister(set_reason(punt_ex, 2))
755         self.vapi.punt_socket_deregister(set_reason(punt_ex, 3))
756         punts = self.vapi.punt_socket_dump(type=pt_ex)
757         self.assertEqual(len(punts), 0)
758
759     def verify_esp_pkts(self, rxs, n_sent, spi, has_udp):
760         self.assertEqual(len(rxs), n_sent)
761         for rx in rxs:
762             self.assertTrue(rx.haslayer(IP))
763             self.assertTrue(rx.haslayer(ESP))
764             self.assertEqual(rx[ESP].spi, spi)
765             if has_udp:
766                 self.assertTrue(rx.haslayer(UDP))
767
768     def test_traffic(self):
769         """ Punt socket traffic """
770
771         port = self.ports[0]
772         pt_ex = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_EXCEPTION
773         punt_ex = {
774             'type': pt_ex,
775             'punt': {
776                 'exception': {}
777             }
778         }
779
780         #
781         # we need an IPSec tunnels for this to work otherwise ESP gets dropped
782         # due to unknown IP proto
783         #
784         VppIpsecTunInterface(self, self.pg0, 1000, 1000,
785                              (VppEnum.vl_api_ipsec_crypto_alg_t.
786                               IPSEC_API_CRYPTO_ALG_AES_CBC_128),
787                              b"0123456701234567",
788                              b"0123456701234567",
789                              (VppEnum.vl_api_ipsec_integ_alg_t.
790                               IPSEC_API_INTEG_ALG_SHA1_96),
791                              b"0123456701234567",
792                              b"0123456701234567").add_vpp_config()
793         VppIpsecTunInterface(self, self.pg0, 1001, 1001,
794                              (VppEnum.vl_api_ipsec_crypto_alg_t.
795                               IPSEC_API_CRYPTO_ALG_AES_CBC_128),
796                              b"0123456701234567",
797                              b"0123456701234567",
798                              (VppEnum.vl_api_ipsec_integ_alg_t.
799                               IPSEC_API_INTEG_ALG_SHA1_96),
800                              b"0123456701234567",
801                              b"0123456701234567",
802                              udp_encap=True).add_vpp_config()
803
804         #
805         # we're dealing with IPSec tunnels punting for no-such-tunnel
806         # adn SPI=0
807         #
808         cfgs = dict()
809         cfgs['ipsec4-no-such-tunnel'] = {'spi': 99, 'udp': False}
810         cfgs['ipsec4-spi-o-udp-0'] = {'spi': 0, 'udp': True}
811
812         #
813         # find the VPP ID for these punt exception reasin
814         #
815         rs = self.vapi.punt_reason_dump()
816         for key in cfgs:
817             for r in rs:
818                 if r.reason.name == key:
819                     cfgs[key]['id'] = r.reason.id
820                     cfgs[key]['vpp'] = copy.deepcopy(
821                         set_reason(punt_ex,
822                                    cfgs[key]['id']))
823                     break
824
825         #
826         # configure punt sockets
827         #
828         for cfg in cfgs.values():
829             cfg['sock'] = self.socket_client_create("%s/socket_%d" %
830                                                     (self.tempdir, cfg['id']))
831             self.vapi.punt_socket_register(
832                 cfg['vpp'], "%s/socket_%d" % (self.tempdir, cfg['id']))
833
834         #
835         # create packet streams for 'no-such-tunnel' exception
836         #
837         for cfg in cfgs.values():
838             pkt = (Ether(src=self.pg0.remote_mac,
839                          dst=self.pg0.local_mac) /
840                    IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4))
841             if (cfg['udp']):
842                 pkt = pkt / UDP(sport=666, dport=4500)
843             pkt = (pkt / ESP(spi=cfg['spi'], seq=3) /
844                    Raw(b'\xa5' * 100))
845             cfg['pkts'] = [pkt]
846
847         #
848         # send packets for each SPI we expect to be punted
849         #
850         for cfg in cfgs.values():
851             self.send_and_assert_no_replies(self.pg0, cfg['pkts'])
852
853         #
854         # verify the punted packets arrived on the associated socket
855         #
856         for cfg in cfgs.values():
857             rx = cfg['sock'].close()
858             self.verify_esp_pkts(rx, len(cfg['pkts']),
859                                  cfg['spi'], cfg['udp'])
860
861         #
862         # socket deregister
863         #
864         for cfg in cfgs.values():
865             self.vapi.punt_socket_deregister(cfg['vpp'])
866
867
868 class TestIpProtoPuntSocket(TestPuntSocket):
869     """ Punt Socket for IP packets """
870
871     @classmethod
872     def setUpClass(cls):
873         super(TestIpProtoPuntSocket, cls).setUpClass()
874
875     @classmethod
876     def tearDownClass(cls):
877         super(TestIpProtoPuntSocket, cls).tearDownClass()
878
879     def setUp(self):
880         super(TestIpProtoPuntSocket, self).setUp()
881
882         for i in self.pg_interfaces:
883             i.config_ip4()
884             i.resolve_arp()
885
886     def tearDown(self):
887         super(TestIpProtoPuntSocket, self).tearDown()
888         for i in self.pg_interfaces:
889             i.unconfig_ip4()
890             i.admin_down()
891
892     def test_registration(self):
893         """ Punt socket registration/deregistration"""
894
895         af_ip4 = VppEnum.vl_api_address_family_t.ADDRESS_IP4
896         pt_ip = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_IP_PROTO
897         proto_ospf = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_OSPF
898         proto_eigrp = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_EIGRP
899
900         punts = self.vapi.punt_socket_dump(type=pt_ip)
901         self.assertEqual(len(punts), 0)
902
903         #
904         # configure a punt socket
905         #
906         punt_ospf = {
907             'type': pt_ip,
908             'punt': {
909                 'ip_proto': {
910                     'af': af_ip4,
911                     'protocol': proto_ospf
912                 }
913             }
914         }
915         punt_eigrp = {
916             'type': pt_ip,
917             'punt': {
918                 'ip_proto': {
919                     'af': af_ip4,
920                     'protocol': proto_eigrp
921                 }
922             }
923         }
924
925         self.vapi.punt_socket_register(punt_ospf,
926                                        "%s/socket_punt_1" % self.tempdir)
927         self.vapi.punt_socket_register(punt_eigrp,
928                                        "%s/socket_punt_2" % self.tempdir)
929         self.logger.info(self.vapi.cli("sh punt sock reg ip"))
930         punts = self.vapi.punt_socket_dump(type=pt_ip)
931         self.assertEqual(len(punts), 2)
932         self.verify_ip_proto(punt_ospf, punts[0])
933         self.verify_ip_proto(punt_eigrp, punts[1])
934
935         #
936         # deregister a punt socket
937         #
938         self.vapi.punt_socket_deregister(punt_ospf)
939         punts = self.vapi.punt_socket_dump(type=pt_ip)
940         self.assertEqual(len(punts), 1)
941
942         #
943         # configure a punt socket again
944         #
945         self.vapi.punt_socket_register(punt_ospf,
946                                        "%s/socket_punt_3" % self.tempdir)
947         punts = self.vapi.punt_socket_dump(type=pt_ip)
948         self.assertEqual(len(punts), 2)
949
950         self.logger.info(self.vapi.cli("sh punt sock reg exception"))
951
952         #
953         # deregister all punt socket
954         #
955         self.vapi.punt_socket_deregister(punt_eigrp)
956         self.vapi.punt_socket_deregister(punt_ospf)
957         punts = self.vapi.punt_socket_dump(type=pt_ip)
958         self.assertEqual(len(punts), 0)
959
960     def verify_ospf_pkts(self, rxs, n_sent):
961         self.assertEqual(len(rxs), n_sent)
962         for rx in rxs:
963             self.assertTrue(rx.haslayer(OSPF_Hdr))
964
965     def test_traffic(self):
966         """ Punt socket traffic """
967
968         af_ip4 = VppEnum.vl_api_address_family_t.ADDRESS_IP4
969         pt_ip = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_IP_PROTO
970         proto_ospf = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_OSPF
971
972         #
973         # configure a punt socket to capture OSPF packets
974         #
975         punt_ospf = {
976             'type': pt_ip,
977             'punt': {
978                 'ip_proto': {
979                     'af': af_ip4,
980                     'protocol': proto_ospf
981                 }
982             }
983         }
984
985         #
986         # create packet streams and configure a punt sockets
987         #
988         pkt = (Ether(src=self.pg0.remote_mac,
989                      dst=self.pg0.local_mac) /
990                IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
991                OSPF_Hdr() /
992                OSPFv3_Hello())
993         pkts = pkt * 7
994
995         sock = self.socket_client_create("%s/socket_1" % self.tempdir)
996         self.vapi.punt_socket_register(
997             punt_ospf, "%s/socket_1" % self.tempdir)
998
999         #
1000         # send packets for each SPI we expect to be punted
1001         #
1002         self.send_and_assert_no_replies(self.pg0, pkts)
1003
1004         #
1005         # verify the punted packets arrived on the associated socket
1006         #
1007         rx = sock.close()
1008         self.verify_ospf_pkts(rx, len(pkts))
1009         self.vapi.punt_socket_deregister(punt_ospf)
1010
1011
1012 class TestPunt(VppTestCase):
1013     """ Exception Punt Test Case """
1014
1015     @classmethod
1016     def setUpClass(cls):
1017         super(TestPunt, cls).setUpClass()
1018
1019     @classmethod
1020     def tearDownClass(cls):
1021         super(TestPunt, cls).tearDownClass()
1022
1023     def setUp(self):
1024         super(TestPunt, self).setUp()
1025
1026         self.create_pg_interfaces(range(4))
1027
1028         for i in self.pg_interfaces:
1029             i.admin_up()
1030             i.config_ip4()
1031             i.resolve_arp()
1032             i.config_ip6()
1033             i.resolve_ndp()
1034
1035     def tearDown(self):
1036         for i in self.pg_interfaces:
1037             i.unconfig_ip4()
1038             i.unconfig_ip6()
1039             i.ip6_disable()
1040             i.admin_down()
1041         super(TestPunt, self).tearDown()
1042
1043     def test_punt(self):
1044         """ Exception Path testing """
1045
1046         #
1047         # dump the punt registered reasons
1048         #  search for a few we know should be there
1049         #
1050         rs = self.vapi.punt_reason_dump()
1051
1052         reasons = ["ipsec6-no-such-tunnel",
1053                    "ipsec4-no-such-tunnel",
1054                    "ipsec4-spi-o-udp-0"]
1055
1056         for reason in reasons:
1057             found = False
1058             for r in rs:
1059                 if r.reason.name == reason:
1060                     found = True
1061                     break
1062             self.assertTrue(found)
1063
1064         #
1065         # Using the test CLI we will hook in a exception path to
1066         # send ACL deny packets out of pg0 and pg1.
1067         # the ACL is src,dst = 1.1.1.1,1.1.1.2
1068         #
1069         ip_1_1_1_2 = VppIpRoute(self, "1.1.1.2", 32,
1070                                 [VppRoutePath(self.pg3.remote_ip4,
1071                                               self.pg3.sw_if_index)])
1072         ip_1_1_1_2.add_vpp_config()
1073         ip_1_2 = VppIpRoute(self, "1::2", 128,
1074                             [VppRoutePath(self.pg3.remote_ip6,
1075                                           self.pg3.sw_if_index,
1076                                           proto=DpoProto.DPO_PROTO_IP6)])
1077         ip_1_2.add_vpp_config()
1078
1079         p4 = (Ether(src=self.pg2.remote_mac,
1080                     dst=self.pg2.local_mac) /
1081               IP(src="1.1.1.1", dst="1.1.1.2") /
1082               UDP(sport=1234, dport=1234) /
1083               Raw(b'\xa5' * 100))
1084         p6 = (Ether(src=self.pg2.remote_mac,
1085                     dst=self.pg2.local_mac) /
1086               IPv6(src="1::1", dst="1::2") /
1087               UDP(sport=1234, dport=1234) /
1088               Raw(b'\xa5' * 100))
1089         self.send_and_expect(self.pg2, p4*1, self.pg3)
1090         self.send_and_expect(self.pg2, p6*1, self.pg3)
1091
1092         #
1093         # apply the punting features
1094         #
1095         self.vapi.cli("test punt pg2")
1096
1097         #
1098         # dump the punt reasons to learn the IDs assigned
1099         #
1100         rs = self.vapi.punt_reason_dump(reason={'name': "reason-v4"})
1101         r4 = rs[0].reason.id
1102         rs = self.vapi.punt_reason_dump(reason={'name': "reason-v6"})
1103         r6 = rs[0].reason.id
1104
1105         #
1106         # pkts now dropped
1107         #
1108         self.send_and_assert_no_replies(self.pg2, p4*NUM_PKTS)
1109         self.send_and_assert_no_replies(self.pg2, p6*NUM_PKTS)
1110
1111         #
1112         # Check state:
1113         #  1 - node error counters
1114         #  2 - per-reason counters
1115         #    2, 3 are the index of the assigned punt reason
1116         #
1117         stats = self.statistics.get_err_counter(
1118             "/err/punt-dispatch/No registrations")
1119         self.assertEqual(stats, 2*NUM_PKTS)
1120
1121         stats = self.statistics.get_counter("/net/punt")
1122         self.assertEqual(stats[0][r4]['packets'], NUM_PKTS)
1123         self.assertEqual(stats[0][r6]['packets'], NUM_PKTS)
1124
1125         #
1126         # use the test CLI to test a client that punts exception
1127         # packets out of pg0
1128         #
1129         self.vapi.cli("test punt pg0 %s" % self.pg0.remote_ip4)
1130         self.vapi.cli("test punt pg0 %s" % self.pg0.remote_ip6)
1131
1132         rx4s = self.send_and_expect(self.pg2, p4*NUM_PKTS, self.pg0)
1133         rx6s = self.send_and_expect(self.pg2, p6*NUM_PKTS, self.pg0)
1134
1135         #
1136         # check the packets come out IP unmodified but destined to pg0 host
1137         #
1138         for rx in rx4s:
1139             self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
1140             self.assertEqual(rx[Ether].src, self.pg0.local_mac)
1141             self.assertEqual(p4[IP].dst, rx[IP].dst)
1142             self.assertEqual(p4[IP].ttl, rx[IP].ttl)
1143         for rx in rx6s:
1144             self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
1145             self.assertEqual(rx[Ether].src, self.pg0.local_mac)
1146             self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
1147             self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
1148
1149         stats = self.statistics.get_counter("/net/punt")
1150         self.assertEqual(stats[0][r4]['packets'], 2*NUM_PKTS)
1151         self.assertEqual(stats[0][r6]['packets'], 2*NUM_PKTS)
1152
1153         #
1154         # add another registration for the same reason to send packets
1155         # out of pg1
1156         #
1157         self.vapi.cli("test punt pg1 %s" % self.pg1.remote_ip4)
1158         self.vapi.cli("test punt pg1 %s" % self.pg1.remote_ip6)
1159
1160         self.vapi.cli("clear trace")
1161         self.pg2.add_stream(p4 * NUM_PKTS)
1162         self.pg_enable_capture(self.pg_interfaces)
1163         self.pg_start()
1164
1165         rxd = self.pg0.get_capture(NUM_PKTS)
1166         for rx in rxd:
1167             self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
1168             self.assertEqual(rx[Ether].src, self.pg0.local_mac)
1169             self.assertEqual(p4[IP].dst, rx[IP].dst)
1170             self.assertEqual(p4[IP].ttl, rx[IP].ttl)
1171         rxd = self.pg1.get_capture(NUM_PKTS)
1172         for rx in rxd:
1173             self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
1174             self.assertEqual(rx[Ether].src, self.pg1.local_mac)
1175             self.assertEqual(p4[IP].dst, rx[IP].dst)
1176             self.assertEqual(p4[IP].ttl, rx[IP].ttl)
1177
1178         self.vapi.cli("clear trace")
1179         self.pg2.add_stream(p6 * NUM_PKTS)
1180         self.pg_enable_capture(self.pg_interfaces)
1181         self.pg_start()
1182
1183         rxd = self.pg0.get_capture(NUM_PKTS)
1184         for rx in rxd:
1185             self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
1186             self.assertEqual(rx[Ether].src, self.pg0.local_mac)
1187             self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
1188             self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
1189         rxd = self.pg1.get_capture(NUM_PKTS)
1190         for rx in rxd:
1191             self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
1192             self.assertEqual(rx[Ether].src, self.pg1.local_mac)
1193             self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
1194             self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
1195
1196         stats = self.statistics.get_counter("/net/punt")
1197         self.assertEqual(stats[0][r4]['packets'], 3*NUM_PKTS)
1198         self.assertEqual(stats[0][r6]['packets'], 3*NUM_PKTS)
1199
1200         self.logger.info(self.vapi.cli("show vlib graph punt-dispatch"))
1201         self.logger.info(self.vapi.cli("show punt client"))
1202         self.logger.info(self.vapi.cli("show punt reason"))
1203         self.logger.info(self.vapi.cli("show punt stats"))
1204         self.logger.info(self.vapi.cli("show punt db"))
1205
1206
1207 if __name__ == '__main__':
1208     unittest.main(testRunner=VppTestRunner)