Punt: socket register for exception dispatched/punted packets based on reason
[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 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 import six
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     nr_packets = 3
80
81     @classmethod
82     def setUpClass(cls):
83         super(TestPuntSocket, cls).setUpClass()
84
85     @classmethod
86     def tearDownClass(cls):
87         super(TestPuntSocket, cls).tearDownClass()
88
89     @classmethod
90     def setUpConstants(cls):
91         cls.extra_vpp_punt_config = [
92             "punt", "{", "socket", cls.tempdir+"/socket_punt", "}"]
93         super(TestPuntSocket, cls).setUpConstants()
94
95     def setUp(self):
96         super(TestPuntSocket, self).setUp()
97         random.seed()
98
99         self.create_pg_interfaces(range(2))
100         for i in self.pg_interfaces:
101             i.admin_up()
102
103     def tearDown(self):
104         del self.sock_servers[:]
105         super(TestPuntSocket, self).tearDown()
106
107     def socket_client_create(self, sock_name, id=None):
108         thread = serverSocketThread(id, sock_name)
109         self.sock_servers.append(thread)
110         thread.start()
111         return thread
112
113     def socket_client_close(self):
114         rx_pkts = []
115         for thread in self.sock_servers:
116             rx_pkts += thread.close()
117         return rx_pkts
118
119     def verify_port(self, pr, vpr):
120         self.assertEqual(vpr.punt.type, pr['type'])
121         self.assertEqual(vpr.punt.punt.l4.port,
122                          pr['punt']['l4']['port'])
123         self.assertEqual(vpr.punt.punt.l4.protocol,
124                          pr['punt']['l4']['protocol'])
125         self.assertEqual(vpr.punt.punt.l4.af,
126                          pr['punt']['l4']['af'])
127
128     def verify_exception(self, pr, vpr):
129         self.assertEqual(vpr.punt.type, pr['type'])
130         self.assertEqual(vpr.punt.punt.exception.id,
131                          pr['punt']['exception']['id'])
132
133     def verify_udp_pkts(self, rxs, n_rx, port):
134         n_match = 0
135         for rx in rxs:
136             self.assertTrue(rx.haslayer(UDP))
137             if rx[UDP].dport == port:
138                 n_match += 1
139         self.assertEqual(n_match, n_rx)
140
141
142 def set_port(pr, port):
143     pr['punt']['l4']['port'] = port
144     return pr
145
146
147 def set_reason(pr, reason):
148     pr['punt']['exception']['id'] = reason
149     return pr
150
151
152 def mk_vpp_cfg4():
153     pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
154     af_ip4 = VppEnum.vl_api_address_family_t.ADDRESS_IP4
155     udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
156     punt_l4 = {
157         'type': pt_l4,
158         'punt': {
159             'l4': {
160                 'af': af_ip4,
161                 'protocol': udp_proto
162             }
163         }
164     }
165     return punt_l4
166
167
168 def mk_vpp_cfg6():
169     pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
170     af_ip6 = VppEnum.vl_api_address_family_t.ADDRESS_IP6
171     udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
172     punt_l4 = {
173         'type': pt_l4,
174         'punt': {
175             'l4': {
176                 'af': af_ip6,
177                 'protocol': udp_proto
178             }
179         }
180     }
181     return punt_l4
182
183
184 class TestIP4PuntSocket(TestPuntSocket):
185     """ Punt Socket for IPv4 """
186
187     @classmethod
188     def setUpClass(cls):
189         super(TestIP4PuntSocket, cls).setUpClass()
190
191     @classmethod
192     def tearDownClass(cls):
193         super(TestIP4PuntSocket, cls).tearDownClass()
194
195     def setUp(self):
196         super(TestIP4PuntSocket, self).setUp()
197
198         for i in self.pg_interfaces:
199             i.config_ip4()
200             i.resolve_arp()
201
202     def tearDown(self):
203         super(TestIP4PuntSocket, self).tearDown()
204         for i in self.pg_interfaces:
205             i.unconfig_ip4()
206             i.admin_down()
207
208     def test_punt_socket_dump(self):
209         """ Punt socket registration/deregistration"""
210
211         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
212         af_ip4 = VppEnum.vl_api_address_family_t.ADDRESS_IP4
213         udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
214
215         punts = self.vapi.punt_socket_dump(type=pt_l4)
216         self.assertEqual(len(punts), 0)
217
218         #
219         # configure a punt socket
220         #
221         punt_l4 = mk_vpp_cfg4()
222
223         self.vapi.punt_socket_register(set_port(punt_l4, 1111),
224                                        b"%s/socket_punt_1111" %
225                                        six.ensure_binary(self.tempdir))
226         self.vapi.punt_socket_register(set_port(punt_l4, 2222),
227                                        b"%s/socket_punt_2222" %
228                                        six.ensure_binary(self.tempdir))
229         punts = self.vapi.punt_socket_dump(type=pt_l4)
230         self.assertEqual(len(punts), 2)
231         self.verify_port(set_port(punt_l4, 1111), punts[0])
232         self.verify_port(set_port(punt_l4, 2222), punts[1])
233
234         #
235         # deregister a punt socket
236         #
237         self.vapi.punt_socket_deregister(set_port(punt_l4, 1111))
238         punts = self.vapi.punt_socket_dump(type=pt_l4)
239         self.assertEqual(len(punts), 1)
240
241         #
242         # configure a punt socket again
243         #
244         self.vapi.punt_socket_register(set_port(punt_l4, 1111),
245                                        b"%s/socket_punt_1111" %
246                                        six.ensure_binary(self.tempdir))
247         self.vapi.punt_socket_register(set_port(punt_l4, 3333),
248                                        b"%s/socket_punt_3333" %
249                                        six.ensure_binary(self.tempdir))
250         punts = self.vapi.punt_socket_dump(type=pt_l4)
251         self.assertEqual(len(punts), 3)
252
253         self.logger.info(self.vapi.cli("sh punt sock reg"))
254
255         #
256         # deregister all punt socket
257         #
258         self.vapi.punt_socket_deregister(set_port(punt_l4, 1111))
259         self.vapi.punt_socket_deregister(set_port(punt_l4, 2222))
260         self.vapi.punt_socket_deregister(set_port(punt_l4, 3333))
261         punts = self.vapi.punt_socket_dump(type=pt_l4)
262         self.assertEqual(len(punts), 0)
263
264     def test_punt_socket_traffic_single_port_single_socket(self):
265         """ Punt socket traffic single port single socket"""
266
267         port = self.ports[0]
268         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
269         punt_l4 = set_port(mk_vpp_cfg4(), port)
270
271         p = (Ether(src=self.pg0.remote_mac,
272                    dst=self.pg0.local_mac) /
273              IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
274              UDP(sport=9876, dport=port) /
275              Raw('\xa5' * 100))
276
277         pkts = p * self.nr_packets
278
279         punts = self.vapi.punt_socket_dump(type=pt_l4)
280         self.assertEqual(len(punts), 0)
281
282         #
283         # expect ICMP - port unreachable for all packets
284         #
285         rx = self.send_and_expect(self.pg0, pkts, self.pg0)
286
287         for p in rx:
288             self.assertEqual(int(p[IP].proto), 1)   # ICMP
289             self.assertEqual(int(p[ICMP].code), 3)  # unreachable
290
291         #
292         # configure a punt socket
293         #
294         self.socket_client_create(b"%s/socket_%d" % (
295             six.ensure_binary(self.tempdir), port))
296         self.vapi.punt_socket_register(punt_l4, b"%s/socket_%d" % (
297             six.ensure_binary(self.tempdir), port))
298         punts = self.vapi.punt_socket_dump(type=pt_l4)
299         self.assertEqual(len(punts), 1)
300
301         #
302         # expect punt socket and no packets on pg0
303         #
304         self.send_and_assert_no_replies(self.pg0, pkts)
305         rx = self.socket_client_close()
306         self.verify_udp_pkts(rx, len(pkts), port)
307
308         #
309         # remove punt socket. expect ICMP - port unreachable for all packets
310         #
311         self.vapi.punt_socket_deregister(punt_l4)
312         punts = self.vapi.punt_socket_dump(type=pt_l4)
313         self.assertEqual(len(punts), 0)
314
315         rx = self.send_and_expect(self.pg0, pkts, self.pg0)
316         for p in rx:
317             self.assertEqual(int(p[IP].proto), 1)   # ICMP
318             self.assertEqual(int(p[ICMP].code), 3)  # unreachable
319
320     def test_punt_socket_traffic_multi_ports_multi_sockets(self):
321         """ Punt socket traffic multi ports and multi sockets"""
322
323         punt_l4 = mk_vpp_cfg4()
324
325         # configuration for each UDP port
326         cfgs = dict()
327
328         #
329         # create stream of packets for each port
330         #
331         for port in self.ports:
332             # choose port from port list
333             cfgs[port] = {}
334
335             pkt = (Ether(src=self.pg0.remote_mac,
336                          dst=self.pg0.local_mac) /
337                    IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
338                    UDP(sport=9876, dport=port) /
339                    Raw('\xa5' * 100))
340             cfgs[port]['pkts'] = pkt * self.nr_packets
341             cfgs[port]['port'] = port
342             cfgs[port]['vpp'] = copy.deepcopy(set_port(punt_l4, port))
343
344             # configure punt sockets
345             cfgs[port]['sock'] = self.socket_client_create(
346                 b"%s/socket_%d" % (six.ensure_binary(self.tempdir), port))
347             self.vapi.punt_socket_register(
348                 cfgs[port]['vpp'],
349                 b"%s/socket_%d" % (six.ensure_binary(self.tempdir),
350                                    port))
351
352         #
353         # send the packets that get punted
354         #
355         for cfg in cfgs.values():
356             self.send_and_assert_no_replies(self.pg0, cfg['pkts'])
357
358         #
359         # test that we got the excepted packets on the expected socket
360         #
361         for cfg in cfgs.values():
362             rx = cfg['sock'].close()
363             self.verify_udp_pkts(rx, len(cfg['pkts']), cfg['port'])
364             self.vapi.punt_socket_deregister(cfg['vpp'])
365
366     def test_punt_socket_traffic_multi_ports_single_socket(self):
367         """ Punt socket traffic multi ports and single socket"""
368
369         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
370         punt_l4 = mk_vpp_cfg4()
371
372         #
373         # create stream of packets with each port
374         #
375         pkts = []
376         for port in self.ports:
377             # choose port from port list
378             pkt = (Ether(src=self.pg0.remote_mac,
379                          dst=self.pg0.local_mac) /
380                    IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
381                    UDP(sport=9876, dport=port) /
382                    Raw('\xa5' * 100))
383             pkts += pkt * self.nr_packets
384
385         #
386         # configure a punt socket
387         #
388         self.socket_client_create(b"%s/socket_multi" %
389                                   six.ensure_binary(self.tempdir))
390         for p in self.ports:
391             self.vapi.punt_socket_register(set_port(punt_l4, p),
392                                            b"%s/socket_multi" %
393                                            six.ensure_binary(self.tempdir))
394         punts = self.vapi.punt_socket_dump(type=pt_l4)
395         self.assertEqual(len(punts), len(self.ports))
396
397         #
398         # expect punt socket and no packets on pg0
399         #
400         self.send_and_assert_no_replies(self.pg0, pkts)
401         self.logger.info(self.vapi.cli("show trace"))
402         rx = self.socket_client_close()
403
404         for p in self.ports:
405             self.verify_udp_pkts(rx, self.nr_packets, p)
406             self.vapi.punt_socket_deregister(set_port(punt_l4, p))
407         punts = self.vapi.punt_socket_dump(type=pt_l4)
408         self.assertEqual(len(punts), 0)
409
410
411 class TestIP6PuntSocket(TestPuntSocket):
412     """ Punt Socket for IPv6"""
413
414     @classmethod
415     def setUpClass(cls):
416         super(TestIP6PuntSocket, cls).setUpClass()
417
418     @classmethod
419     def tearDownClass(cls):
420         super(TestIP6PuntSocket, cls).tearDownClass()
421
422     def setUp(self):
423         super(TestIP6PuntSocket, self).setUp()
424
425         for i in self.pg_interfaces:
426             i.config_ip6()
427             i.resolve_ndp()
428
429     def tearDown(self):
430         super(TestIP6PuntSocket, self).tearDown()
431         for i in self.pg_interfaces:
432             i.unconfig_ip6()
433             i.admin_down()
434
435     def test_punt_socket_dump(self):
436         """ Punt socket registration """
437
438         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
439         af_ip6 = VppEnum.vl_api_address_family_t.ADDRESS_IP6
440         udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
441         #
442         # configure a punt socket
443         #
444         punt_l4 = {
445             'type': pt_l4,
446             'punt': {
447                 'l4': {
448                     'af': af_ip6,
449                     'protocol': udp_proto
450                 }
451             }
452         }
453
454         punts = self.vapi.punt_socket_dump(type=pt_l4)
455         self.assertEqual(len(punts), 0)
456
457         #
458         # configure a punt socket
459         #
460         self.vapi.punt_socket_register(set_port(punt_l4, 1111),
461                                        b"%s/socket_1111" %
462                                        six.ensure_binary(self.tempdir))
463         self.vapi.punt_socket_register(set_port(punt_l4, 2222),
464                                        b"%s/socket_2222" %
465                                        six.ensure_binary(self.tempdir))
466         punts = self.vapi.punt_socket_dump(type=pt_l4)
467         self.assertEqual(len(punts), 2)
468         self.verify_port(set_port(punt_l4, 1111), punts[0])
469         self.verify_port(set_port(punt_l4, 2222), punts[1])
470
471         #
472         # deregister a punt socket
473         #
474         self.vapi.punt_socket_deregister(set_port(punt_l4, 1111))
475         punts = self.vapi.punt_socket_dump(type=pt_l4)
476         self.assertEqual(len(punts), 1)
477
478         #
479         # configure a punt socket again
480         #
481         self.vapi.punt_socket_register(set_port(punt_l4, 1111),
482                                        b"%s/socket_1111" %
483                                        six.ensure_binary(self.tempdir))
484         punts = self.vapi.punt_socket_dump(type=pt_l4)
485         self.assertEqual(len(punts), 2)
486
487         #
488         # deregister all punt socket
489         #
490         self.vapi.punt_socket_deregister(set_port(punt_l4, 1111))
491         self.vapi.punt_socket_deregister(set_port(punt_l4, 2222))
492         self.vapi.punt_socket_deregister(set_port(punt_l4, 3333))
493         punts = self.vapi.punt_socket_dump(type=pt_l4)
494         self.assertEqual(len(punts), 0)
495
496     def test_punt_socket_traffic_single_port_single_socket(self):
497         """ Punt socket traffic single port single socket"""
498
499         port = self.ports[0]
500         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
501         af_ip6 = VppEnum.vl_api_address_family_t.ADDRESS_IP6
502         udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
503         punt_l4 = {
504             'type': pt_l4,
505             'punt': {
506                 'l4': {
507                     'af': af_ip6,
508                     'protocol': udp_proto,
509                     'port': port,
510                 }
511             }
512         }
513
514         p = (Ether(src=self.pg0.remote_mac,
515                    dst=self.pg0.local_mac) /
516              IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
517              inet6.UDP(sport=9876, dport=port) /
518              Raw('\xa5' * 100))
519
520         pkts = p * self.nr_packets
521
522         punts = self.vapi.punt_socket_dump(type=pt_l4)
523         self.assertEqual(len(punts), 0)
524
525         #
526         # expect ICMPv6 - destination unreachable for all packets
527         #
528         self.vapi.cli("clear trace")
529         self.pg0.add_stream(pkts)
530         self.pg_enable_capture(self.pg_interfaces)
531         self.pg_start()
532         # FIXME - when punt socket deregister is implemented
533         # rx = self.pg0.get_capture(self.nr_packets)
534         # for p in rx:
535         #     self.assertEqual(int(p[IPv6].nh), 58)                # ICMPv6
536         #     self.assertEqual(int(p[ICMPv6DestUnreach].code),4)  # unreachable
537
538         #
539         # configure a punt socket
540         #
541         self.socket_client_create(b"%s/socket_%d" % (
542             six.ensure_binary(self.tempdir), port))
543         self.vapi.punt_socket_register(punt_l4, b"%s/socket_%d" % (
544             six.ensure_binary(self.tempdir), port))
545         punts = self.vapi.punt_socket_dump(type=pt_l4)
546         self.assertEqual(len(punts), 1)
547
548         #
549         # expect punt socket and no packets on pg0
550         #
551         self.vapi.cli("clear errors")
552         self.vapi.cli("clear trace")
553         self.pg0.add_stream(pkts)
554         self.pg_enable_capture(self.pg_interfaces)
555         self.pg_start()
556         self.pg0.get_capture(0)
557         self.logger.info(self.vapi.cli("show trace"))
558         rx = self.socket_client_close()
559         self.verify_udp_pkts(rx, len(pkts), port)
560
561         #
562         # remove punt socket. expect ICMP - dest. unreachable for all packets
563         #
564         self.vapi.punt_socket_deregister(punt_l4)
565         punts = self.vapi.punt_socket_dump(type=pt_l4)
566         self.assertEqual(len(punts), 0)
567         self.pg0.add_stream(pkts)
568         self.pg_enable_capture(self.pg_interfaces)
569         self.pg_start()
570         # FIXME - when punt socket deregister is implemented
571         # self.pg0.get_capture(nr_packets)
572
573     def test_punt_socket_traffic_multi_ports_multi_sockets(self):
574         """ Punt socket traffic multi ports and multi sockets"""
575
576         punt_l4 = mk_vpp_cfg6()
577
578         # configuration for each UDP port
579         cfgs = dict()
580
581         #
582         # create stream of packets for each port
583         #
584         for port in self.ports:
585             # choose port from port list
586             cfgs[port] = {}
587
588             pkt = (Ether(src=self.pg0.remote_mac,
589                          dst=self.pg0.local_mac) /
590                    IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
591                    UDP(sport=9876, dport=port) /
592                    Raw('\xa5' * 100))
593             cfgs[port]['pkts'] = pkt * self.nr_packets
594             cfgs[port]['port'] = port
595             cfgs[port]['vpp'] = copy.deepcopy(set_port(punt_l4, port))
596
597             # configure punt sockets
598             cfgs[port]['sock'] = self.socket_client_create(
599                 b"%s/socket_%d" % (six.ensure_binary(self.tempdir), port))
600             self.vapi.punt_socket_register(
601                 cfgs[port]['vpp'],
602                 b"%s/socket_%d" % (six.ensure_binary(self.tempdir),
603                                    port))
604
605         #
606         # send the packets that get punted
607         #
608         for cfg in cfgs.values():
609             self.send_and_assert_no_replies(self.pg0, cfg['pkts'])
610
611         #
612         # test that we got the excepted packets on the expected socket
613         #
614         for cfg in cfgs.values():
615             rx = cfg['sock'].close()
616             self.verify_udp_pkts(rx, len(cfg['pkts']), cfg['port'])
617             self.vapi.punt_socket_deregister(cfg['vpp'])
618
619     def test_punt_socket_traffic_multi_ports_single_socket(self):
620         """ Punt socket traffic multi ports and single socket"""
621
622         pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
623         af_ip6 = VppEnum.vl_api_address_family_t.ADDRESS_IP6
624         udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
625         punt_l4 = {
626             'type': pt_l4,
627             'punt': {
628                 'l4': {
629                     'af': af_ip6,
630                     'protocol': udp_proto,
631                 }
632             }
633         }
634
635         #
636         # create stream of packets with each port
637         #
638         pkts = []
639         for port in self.ports:
640             # choose port from port list
641             pkt = (Ether(src=self.pg0.remote_mac,
642                          dst=self.pg0.local_mac) /
643                    IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
644                    UDP(sport=9876, dport=port) /
645                    Raw('\xa5' * 100))
646             pkts += pkt * self.nr_packets
647
648         #
649         # no punt socket
650         #
651         punts = self.vapi.punt_socket_dump(type=pt_l4)
652         self.assertEqual(len(punts), 0)
653
654         #
655         # configure a punt socket
656         #
657         self.socket_client_create(b"%s/socket_multi" %
658                                   six.ensure_binary(self.tempdir))
659         for p in self.ports:
660             self.vapi.punt_socket_register(set_port(punt_l4, p),
661                                            b"%s/socket_multi" %
662                                            six.ensure_binary(self.tempdir))
663         punts = self.vapi.punt_socket_dump(type=pt_l4)
664         self.assertEqual(len(punts), len(self.ports))
665
666         #
667         # expect punt socket and no packets on pg0
668         #
669         self.vapi.cli("clear errors")
670         self.vapi.cli("clear trace")
671         self.pg0.add_stream(pkts)
672         self.pg_enable_capture(self.pg_interfaces)
673         self.pg_start()
674         self.pg0.get_capture(0)
675         rx = self.socket_client_close()
676
677         for p in self.ports:
678             self.verify_udp_pkts(rx, self.nr_packets, p)
679             self.vapi.punt_socket_deregister(set_port(punt_l4, p))
680         punts = self.vapi.punt_socket_dump(type=pt_l4)
681         self.assertEqual(len(punts), 0)
682
683
684 class TestExceptionPuntSocket(TestPuntSocket):
685     """ Punt Socket for Exceptions """
686
687     @classmethod
688     def setUpClass(cls):
689         super(TestExceptionPuntSocket, cls).setUpClass()
690
691     @classmethod
692     def tearDownClass(cls):
693         super(TestExceptionPuntSocket, cls).tearDownClass()
694
695     def setUp(self):
696         super(TestExceptionPuntSocket, self).setUp()
697
698         for i in self.pg_interfaces:
699             i.config_ip4()
700             i.resolve_arp()
701
702     def tearDown(self):
703         super(TestExceptionPuntSocket, self).tearDown()
704         for i in self.pg_interfaces:
705             i.unconfig_ip4()
706             i.admin_down()
707
708     def test_registration(self):
709         """ Punt socket registration/deregistration"""
710
711         pt_ex = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_EXCEPTION
712
713         punts = self.vapi.punt_socket_dump(type=pt_ex)
714         self.assertEqual(len(punts), 0)
715
716         #
717         # configure a punt socket
718         #
719         punt_ex = {
720             'type': pt_ex,
721             'punt': {
722                 'exception': {}
723             }
724         }
725
726         self.vapi.punt_socket_register(set_reason(punt_ex, 1),
727                                        b"%s/socket_punt_1" %
728                                        six.ensure_binary(self.tempdir))
729         self.vapi.punt_socket_register(set_reason(punt_ex, 2),
730                                        b"%s/socket_punt_2" %
731                                        six.ensure_binary(self.tempdir))
732         punts = self.vapi.punt_socket_dump(type=pt_ex)
733         self.assertEqual(len(punts), 2)
734         self.verify_exception(set_reason(punt_ex, 1), punts[0])
735         self.verify_exception(set_reason(punt_ex, 2), punts[1])
736
737         #
738         # deregister a punt socket
739         #
740         self.vapi.punt_socket_deregister(set_reason(punt_ex, 1))
741         punts = self.vapi.punt_socket_dump(type=pt_ex)
742         self.assertEqual(len(punts), 1)
743
744         #
745         # configure a punt socket again
746         #
747         self.vapi.punt_socket_register(set_reason(punt_ex, 1),
748                                        b"%s/socket_punt_1" %
749                                        six.ensure_binary(self.tempdir))
750         self.vapi.punt_socket_register(set_reason(punt_ex, 3),
751                                        b"%s/socket_punt_3" %
752                                        six.ensure_binary(self.tempdir))
753         punts = self.vapi.punt_socket_dump(type=pt_ex)
754         self.assertEqual(len(punts), 3)
755
756         self.logger.info(self.vapi.cli("sh punt sock reg exception"))
757
758         #
759         # deregister all punt socket
760         #
761         self.vapi.punt_socket_deregister(set_reason(punt_ex, 1))
762         self.vapi.punt_socket_deregister(set_reason(punt_ex, 2))
763         self.vapi.punt_socket_deregister(set_reason(punt_ex, 3))
764         punts = self.vapi.punt_socket_dump(type=pt_ex)
765         self.assertEqual(len(punts), 0)
766
767     def verify_esp_pkts(self, rxs, n_sent, spi):
768         self.assertEqual(len(rxs), n_sent)
769         for rx in rxs:
770             self.assertTrue(rx.haslayer(ESP))
771             self.assertEqual(rx[ESP].spi, spi)
772
773     def test_traffic(self):
774         """ Punt socket traffic """
775
776         port = self.ports[0]
777         pt_ex = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_EXCEPTION
778         punt_ex = {
779             'type': pt_ex,
780             'punt': {
781                 'exception': {}
782             }
783         }
784
785         #
786         # we need an IPSec tunnel for this to work otherwise ESP gets dropped
787         # due to unknown IP proto
788         #
789         VppIpsecTunInterface(self, self.pg0, 1000, 1000,
790                              (VppEnum.vl_api_ipsec_crypto_alg_t.
791                               IPSEC_API_CRYPTO_ALG_AES_CBC_128),
792                              "0123456701234567",
793                              "0123456701234567",
794                              (VppEnum.vl_api_ipsec_integ_alg_t.
795                               IPSEC_API_INTEG_ALG_SHA1_96),
796                              "0123456701234567",
797                              "0123456701234567").add_vpp_config()
798
799         #
800         # we're dealing with IPSec tunnels punting for no-such-tunnel
801         # adn SPI=0
802         #
803         cfgs = dict()
804         cfgs['ipsec4-no-such-tunnel'] = {'spi': 99}
805         cfgs['ipsec4-spi-0'] = {'spi': 0}
806
807         #
808         # find the VPP ID for these punt exception reasin
809         #
810         rs = self.vapi.punt_reason_dump()
811         for key in cfgs:
812             for r in rs:
813                 if r.reason.name == key:
814                     cfgs[key]['id'] = r.reason.id
815                     cfgs[key]['vpp'] = copy.deepcopy(
816                         set_reason(punt_ex,
817                                    cfgs[key]['id']))
818                     break
819
820         #
821         # create packet streams and configure a punt sockets
822         #
823         for cfg in cfgs.values():
824             pkt = (Ether(src=self.pg0.remote_mac,
825                          dst=self.pg0.local_mac) /
826                    IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
827                    ESP(spi=cfg['spi'], seq=3) /
828                    Raw('\xa5' * 100))
829             cfg['pkts'] = pkt * self.nr_packets
830
831             cfg['sock'] = self.socket_client_create(b"%s/socket_%d" % (
832                 six.ensure_binary(self.tempdir), cfg['id']))
833             self.vapi.punt_socket_register(
834                 cfg['vpp'],
835                 b"%s/socket_%d" % (six.ensure_binary(self.tempdir),
836                                    cfg['id']))
837
838         #
839         # send packets for each SPI we expect to be punted
840         #
841         for cfg in cfgs.values():
842             self.send_and_assert_no_replies(self.pg0, cfg['pkts'])
843
844         #
845         # verify the punted packets arrived on the associated socket
846         #
847         for cfg in cfgs.values():
848             rx = cfg['sock'].close()
849             self.verify_esp_pkts(rx, len(cfg['pkts']), cfg['spi'])
850             self.vapi.punt_socket_deregister(cfg['vpp'])
851
852
853 class TestPunt(VppTestCase):
854     """ Punt Test Case """
855
856     @classmethod
857     def setUpClass(cls):
858         super(TestPunt, cls).setUpClass()
859
860     @classmethod
861     def tearDownClass(cls):
862         super(TestPunt, cls).tearDownClass()
863
864     def setUp(self):
865         super(TestPunt, self).setUp()
866
867         self.create_pg_interfaces(range(4))
868
869         for i in self.pg_interfaces:
870             i.admin_up()
871             i.config_ip4()
872             i.resolve_arp()
873             i.config_ip6()
874             i.resolve_ndp()
875
876     def tearDown(self):
877         for i in self.pg_interfaces:
878             i.unconfig_ip4()
879             i.unconfig_ip6()
880             i.ip6_disable()
881             i.admin_down()
882         super(TestPunt, self).tearDown()
883
884     def test_punt(self):
885         """ Exception Path testing """
886
887         #
888         # Using the test CLI we will hook in a exception path to
889         # send ACL deny packets out of pg0 and pg1.
890         # the ACL is src,dst = 1.1.1.1,1.1.1.2
891         #
892         ip_1_1_1_2 = VppIpRoute(self, "1.1.1.2", 32,
893                                 [VppRoutePath(self.pg3.remote_ip4,
894                                               self.pg3.sw_if_index)])
895         ip_1_1_1_2.add_vpp_config()
896         ip_1_2 = VppIpRoute(self, "1::2", 128,
897                             [VppRoutePath(self.pg3.remote_ip6,
898                                           self.pg3.sw_if_index,
899                                           proto=DpoProto.DPO_PROTO_IP6)],
900                             is_ip6=1)
901         ip_1_2.add_vpp_config()
902
903         p4 = (Ether(src=self.pg2.remote_mac,
904                     dst=self.pg2.local_mac) /
905               IP(src="1.1.1.1", dst="1.1.1.2") /
906               UDP(sport=1234, dport=1234) /
907               Raw('\xa5' * 100))
908         p6 = (Ether(src=self.pg2.remote_mac,
909                     dst=self.pg2.local_mac) /
910               IPv6(src="1::1", dst="1::2") /
911               UDP(sport=1234, dport=1234) /
912               Raw('\xa5' * 100))
913         self.send_and_expect(self.pg2, p4*1, self.pg3)
914         self.send_and_expect(self.pg2, p6*1, self.pg3)
915
916         #
917         # apply the punting features
918         #
919         self.vapi.cli("test punt pg2")
920
921         #
922         # pkts now dropped
923         #
924         self.send_and_assert_no_replies(self.pg2, p4*NUM_PKTS)
925         self.send_and_assert_no_replies(self.pg2, p6*NUM_PKTS)
926
927         #
928         # Check state:
929         #  1 - node error counters
930         #  2 - per-reason counters
931         #    2, 3 are the index of the assigned punt reason
932         #
933         stats = self.statistics.get_err_counter(
934             "/err/punt-dispatch/No registrations")
935         self.assertEqual(stats, 2*NUM_PKTS)
936
937         stats = self.statistics.get_counter("/net/punt")
938         self.assertEqual(stats[0][7]['packets'], NUM_PKTS)
939         self.assertEqual(stats[0][8]['packets'], NUM_PKTS)
940
941         #
942         # use the test CLI to test a client that punts exception
943         # packets out of pg0
944         #
945         self.vapi.cli("test punt pg0 %s" % self.pg0.remote_ip4)
946         self.vapi.cli("test punt pg0 %s" % self.pg0.remote_ip6)
947
948         rx4s = self.send_and_expect(self.pg2, p4*NUM_PKTS, self.pg0)
949         rx6s = self.send_and_expect(self.pg2, p6*NUM_PKTS, self.pg0)
950
951         #
952         # check the packets come out IP unmodified but destined to pg0 host
953         #
954         for rx in rx4s:
955             self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
956             self.assertEqual(rx[Ether].src, self.pg0.local_mac)
957             self.assertEqual(p4[IP].dst, rx[IP].dst)
958             self.assertEqual(p4[IP].ttl, rx[IP].ttl)
959         for rx in rx6s:
960             self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
961             self.assertEqual(rx[Ether].src, self.pg0.local_mac)
962             self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
963             self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
964
965         stats = self.statistics.get_counter("/net/punt")
966         self.assertEqual(stats[0][7]['packets'], 2*NUM_PKTS)
967         self.assertEqual(stats[0][8]['packets'], 2*NUM_PKTS)
968
969         #
970         # add another registration for the same reason to send packets
971         # out of pg1
972         #
973         self.vapi.cli("test punt pg1 %s" % self.pg1.remote_ip4)
974         self.vapi.cli("test punt pg1 %s" % self.pg1.remote_ip6)
975
976         self.vapi.cli("clear trace")
977         self.pg2.add_stream(p4 * NUM_PKTS)
978         self.pg_enable_capture(self.pg_interfaces)
979         self.pg_start()
980
981         rxd = self.pg0.get_capture(NUM_PKTS)
982         for rx in rxd:
983             self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
984             self.assertEqual(rx[Ether].src, self.pg0.local_mac)
985             self.assertEqual(p4[IP].dst, rx[IP].dst)
986             self.assertEqual(p4[IP].ttl, rx[IP].ttl)
987         rxd = self.pg1.get_capture(NUM_PKTS)
988         for rx in rxd:
989             self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
990             self.assertEqual(rx[Ether].src, self.pg1.local_mac)
991             self.assertEqual(p4[IP].dst, rx[IP].dst)
992             self.assertEqual(p4[IP].ttl, rx[IP].ttl)
993
994         self.vapi.cli("clear trace")
995         self.pg2.add_stream(p6 * NUM_PKTS)
996         self.pg_enable_capture(self.pg_interfaces)
997         self.pg_start()
998
999         rxd = self.pg0.get_capture(NUM_PKTS)
1000         for rx in rxd:
1001             self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
1002             self.assertEqual(rx[Ether].src, self.pg0.local_mac)
1003             self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
1004             self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
1005         rxd = self.pg1.get_capture(NUM_PKTS)
1006         for rx in rxd:
1007             self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
1008             self.assertEqual(rx[Ether].src, self.pg1.local_mac)
1009             self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
1010             self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
1011
1012         stats = self.statistics.get_counter("/net/punt")
1013         self.assertEqual(stats[0][7]['packets'], 3*NUM_PKTS)
1014         self.assertEqual(stats[0][8]['packets'], 3*NUM_PKTS)
1015
1016         self.logger.info(self.vapi.cli("show vlib graph punt-dispatch"))
1017         self.logger.info(self.vapi.cli("show punt client"))
1018         self.logger.info(self.vapi.cli("show punt reason"))
1019         self.logger.info(self.vapi.cli("show punt stats"))
1020         self.logger.info(self.vapi.cli("show punt db"))
1021
1022         #
1023         # dump the punt registered reasons
1024         #  search for a few we know should be there
1025         #
1026         rs = self.vapi.punt_reason_dump()
1027
1028         reasons = ["ipsec6-no-such-tunnel",
1029                    "ipsec4-no-such-tunnel",
1030                    "ipsec6-spi-0",
1031                    "ipsec4-spi-0"]
1032
1033         for reason in reasons:
1034             found = False
1035             for r in rs:
1036                 if r.reason.name == reason:
1037                     found = True
1038                     break
1039             self.assertTrue(found)
1040
1041
1042 if __name__ == '__main__':
1043     unittest.main(testRunner=VppTestRunner)