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