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