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