+ # self.pg0.get_capture(nr_packets)
+
+ def test_punt_socket_traffic_multi_ports_multi_sockets(self):
+ """ Punt socket traffic multi ports and multi sockets"""
+
+ punt_l4 = mk_vpp_cfg6()
+
+ # configuration for each UDP port
+ cfgs = dict()
+
+ #
+ # create stream of packets for each port
+ #
+ for port in self.ports:
+ # choose port from port list
+ cfgs[port] = {}
+
+ pkt = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
+ UDP(sport=9876, dport=port) /
+ Raw(b'\xa5' * 100))
+ cfgs[port]['pkts'] = pkt * self.nr_packets
+ cfgs[port]['port'] = port
+ cfgs[port]['vpp'] = copy.deepcopy(set_port(punt_l4, port))
+
+ # configure punt sockets
+ cfgs[port]['sock'] = self.socket_client_create(
+ "%s/socket_%d" % (self.tempdir, port))
+ self.vapi.punt_socket_register(
+ cfgs[port]['vpp'],
+ "%s/socket_%d" % (self.tempdir, port))
+
+ #
+ # send the packets that get punted
+ #
+ for cfg in cfgs.values():
+ self.send_and_assert_no_replies(self.pg0, cfg['pkts'])
+
+ #
+ # test that we got the excepted packets on the expected socket
+ #
+ for cfg in cfgs.values():
+ rx = cfg['sock'].close()
+ self.verify_udp_pkts(rx, len(cfg['pkts']), cfg['port'])
+ self.vapi.punt_socket_deregister(cfg['vpp'])
+
+ def test_punt_socket_traffic_multi_ports_single_socket(self):
+ """ Punt socket traffic multi ports and single socket"""
+
+ pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4
+ af_ip6 = VppEnum.vl_api_address_family_t.ADDRESS_IP6
+ udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP
+ punt_l4 = {
+ 'type': pt_l4,
+ 'punt': {
+ 'l4': {
+ 'af': af_ip6,
+ 'protocol': udp_proto,
+ }
+ }
+ }
+
+ #
+ # create stream of packets with each port
+ #
+ pkts = []
+ for port in self.ports:
+ # choose port from port list
+ pkt = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) /
+ UDP(sport=9876, dport=port) /
+ Raw(b'\xa5' * 100))
+ pkts += pkt * self.nr_packets
+
+ #
+ # no punt socket
+ #
+ punts = self.vapi.punt_socket_dump(type=pt_l4)
+ self.assertEqual(len(punts), 0)
+
+ #
+ # configure a punt socket
+ #
+ self.socket_client_create("%s/socket_multi" % self.tempdir)
+ for p in self.ports:
+ self.vapi.punt_socket_register(set_port(punt_l4, p),
+ "%s/socket_multi" % self.tempdir)
+ punts = self.vapi.punt_socket_dump(type=pt_l4)
+ self.assertEqual(len(punts), len(self.ports))
+
+ #
+ # expect punt socket and no packets on pg0
+ #
+ self.vapi.cli("clear errors")
+ self.vapi.cli("clear trace")
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ # give a chance to punt socket to collect all packets
+ self.sleep(1)
+ self.pg0.get_capture(0)
+ rx = self.socket_client_close()
+
+ for p in self.ports:
+ self.verify_udp_pkts(rx, self.nr_packets, p)
+ self.vapi.punt_socket_deregister(set_port(punt_l4, p))
+ punts = self.vapi.punt_socket_dump(type=pt_l4)
+ self.assertEqual(len(punts), 0)
+
+
+class TestExceptionPuntSocket(TestPuntSocket):
+ """ Punt Socket for Exceptions """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestExceptionPuntSocket, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestExceptionPuntSocket, cls).tearDownClass()
+
+ def setUp(self):
+ super(TestExceptionPuntSocket, self).setUp()
+
+ self.create_pg_interfaces(range(2))
+ for i in self.pg_interfaces:
+ i.config_ip4()
+ i.resolve_arp()
+
+ def tearDown(self):
+ super(TestExceptionPuntSocket, self).tearDown()
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.admin_down()
+
+ def test_registration(self):
+ """ Punt socket registration/deregistration"""
+
+ pt_ex = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_EXCEPTION
+
+ punts = self.vapi.punt_socket_dump(type=pt_ex)
+ self.assertEqual(len(punts), 0)
+
+ #
+ # configure a punt socket
+ #
+ punt_ex = {
+ 'type': pt_ex,
+ 'punt': {
+ 'exception': {}
+ }
+ }
+
+ self.vapi.punt_socket_register(set_reason(punt_ex, 1),
+ "%s/socket_punt_1" % self.tempdir)
+ self.vapi.punt_socket_register(set_reason(punt_ex, 2),
+ "%s/socket_punt_2" % self.tempdir)
+ punts = self.vapi.punt_socket_dump(type=pt_ex)
+ self.assertEqual(len(punts), 2)
+ self.verify_exception(set_reason(punt_ex, 1), punts[0])
+ self.verify_exception(set_reason(punt_ex, 2), punts[1])
+
+ #
+ # deregister a punt socket
+ #
+ self.vapi.punt_socket_deregister(set_reason(punt_ex, 1))
+ punts = self.vapi.punt_socket_dump(type=pt_ex)
+ self.assertEqual(len(punts), 1)
+
+ #
+ # configure a punt socket again
+ #
+ self.vapi.punt_socket_register(set_reason(punt_ex, 1),
+ "%s/socket_punt_1" % self.tempdir)
+ self.vapi.punt_socket_register(set_reason(punt_ex, 3),
+ "%s/socket_punt_3" % self.tempdir)
+ punts = self.vapi.punt_socket_dump(type=pt_ex)
+ self.assertEqual(len(punts), 3)
+
+ self.logger.info(self.vapi.cli("sh punt sock reg exception"))
+
+ #
+ # deregister all punt socket
+ #
+ self.vapi.punt_socket_deregister(set_reason(punt_ex, 1))
+ self.vapi.punt_socket_deregister(set_reason(punt_ex, 2))
+ self.vapi.punt_socket_deregister(set_reason(punt_ex, 3))
+ punts = self.vapi.punt_socket_dump(type=pt_ex)
+ self.assertEqual(len(punts), 0)
+
+ def verify_esp_pkts(self, rxs, n_sent, spi, has_udp):
+ self.assertEqual(len(rxs), n_sent)
+ for rx in rxs:
+ self.assertTrue(rx.haslayer(IP))
+ self.assertTrue(rx.haslayer(ESP))
+ self.assertEqual(rx[ESP].spi, spi)
+ if has_udp:
+ self.assertTrue(rx.haslayer(UDP))
+
+ def test_traffic(self):
+ """ Punt socket traffic """
+
+ port = self.ports[0]
+ pt_ex = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_EXCEPTION
+ punt_ex = {
+ 'type': pt_ex,
+ 'punt': {
+ 'exception': {}
+ }
+ }
+
+ #
+ # we need an IPSec tunnels for this to work otherwise ESP gets dropped
+ # due to unknown IP proto
+ #
+ VppIpsecTunInterface(self, self.pg0, 1000, 1000,
+ (VppEnum.vl_api_ipsec_crypto_alg_t.
+ IPSEC_API_CRYPTO_ALG_AES_CBC_128),
+ b"0123456701234567",
+ b"0123456701234567",
+ (VppEnum.vl_api_ipsec_integ_alg_t.
+ IPSEC_API_INTEG_ALG_SHA1_96),
+ b"0123456701234567",
+ b"0123456701234567").add_vpp_config()
+ VppIpsecTunInterface(self, self.pg1, 1000, 1000,
+ (VppEnum.vl_api_ipsec_crypto_alg_t.
+ IPSEC_API_CRYPTO_ALG_AES_CBC_128),
+ b"0123456701234567",
+ b"0123456701234567",
+ (VppEnum.vl_api_ipsec_integ_alg_t.
+ IPSEC_API_INTEG_ALG_SHA1_96),
+ b"0123456701234567",
+ b"0123456701234567",
+ udp_encap=True).add_vpp_config()
+
+ #
+ # we're dealing with IPSec tunnels punting for no-such-tunnel
+ # adn SPI=0
+ #
+ cfgs = dict()
+ cfgs['ipsec4-no-such-tunnel'] = {'spi': 99,
+ 'udp': False,
+ 'itf': self.pg0}
+ cfgs['ipsec4-spi-o-udp-0'] = {'spi': 0,
+ 'udp': True,
+ 'itf': self.pg1}
+
+ #
+ # find the VPP ID for these punt exception reasin
+ #
+ rs = self.vapi.punt_reason_dump()
+ for key in cfgs:
+ for r in rs:
+ if r.reason.name == key:
+ cfgs[key]['id'] = r.reason.id
+ cfgs[key]['vpp'] = copy.deepcopy(
+ set_reason(punt_ex,
+ cfgs[key]['id']))
+ break
+
+ #
+ # configure punt sockets
+ #
+ for cfg in cfgs.values():
+ cfg['sock'] = self.socket_client_create("%s/socket_%d" %
+ (self.tempdir, cfg['id']))
+ self.vapi.punt_socket_register(
+ cfg['vpp'], "%s/socket_%d" % (self.tempdir, cfg['id']))
+
+ #
+ # create packet streams for 'no-such-tunnel' exception
+ #
+ for cfg in cfgs.values():
+ pkt = (Ether(src=cfg['itf'].remote_mac,
+ dst=cfg['itf'].local_mac) /
+ IP(src=cfg['itf'].remote_ip4,
+ dst=cfg['itf'].local_ip4))
+ if (cfg['udp']):
+ pkt = pkt / UDP(sport=666, dport=4500)
+ pkt = (pkt / ESP(spi=cfg['spi'], seq=3) /
+ Raw(b'\xa5' * 100))
+ cfg['pkts'] = [pkt]
+
+ #
+ # send packets for each SPI we expect to be punted
+ #
+ for cfg in cfgs.values():
+ self.send_and_assert_no_replies(cfg['itf'], cfg['pkts'])
+
+ #
+ # verify the punted packets arrived on the associated socket
+ #
+ for cfg in cfgs.values():
+ rx = cfg['sock'].close()
+ self.verify_esp_pkts(rx, len(cfg['pkts']),
+ cfg['spi'], cfg['udp'])
+
+ #
+ # socket deregister
+ #
+ for cfg in cfgs.values():
+ self.vapi.punt_socket_deregister(cfg['vpp'])
+
+
+class TestIpProtoPuntSocket(TestPuntSocket):
+ """ Punt Socket for IP packets """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestIpProtoPuntSocket, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestIpProtoPuntSocket, cls).tearDownClass()
+
+ def setUp(self):
+ super(TestIpProtoPuntSocket, self).setUp()
+
+ for i in self.pg_interfaces:
+ i.config_ip4()
+ i.resolve_arp()
+
+ def tearDown(self):
+ super(TestIpProtoPuntSocket, self).tearDown()
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.admin_down()
+
+ def test_registration(self):
+ """ Punt socket registration/deregistration"""
+
+ af_ip4 = VppEnum.vl_api_address_family_t.ADDRESS_IP4
+ pt_ip = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_IP_PROTO
+ proto_ospf = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_OSPF
+ proto_eigrp = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_EIGRP
+
+ punts = self.vapi.punt_socket_dump(type=pt_ip)
+ self.assertEqual(len(punts), 0)
+
+ #
+ # configure a punt socket
+ #
+ punt_ospf = {
+ 'type': pt_ip,
+ 'punt': {
+ 'ip_proto': {
+ 'af': af_ip4,
+ 'protocol': proto_ospf
+ }
+ }
+ }
+ punt_eigrp = {
+ 'type': pt_ip,
+ 'punt': {
+ 'ip_proto': {
+ 'af': af_ip4,
+ 'protocol': proto_eigrp
+ }
+ }
+ }
+
+ self.vapi.punt_socket_register(punt_ospf,
+ "%s/socket_punt_1" % self.tempdir)
+ self.vapi.punt_socket_register(punt_eigrp,
+ "%s/socket_punt_2" % self.tempdir)
+ self.logger.info(self.vapi.cli("sh punt sock reg ip"))
+ punts = self.vapi.punt_socket_dump(type=pt_ip)
+ self.assertEqual(len(punts), 2)
+ self.verify_ip_proto(punt_ospf, punts[0])
+ self.verify_ip_proto(punt_eigrp, punts[1])
+
+ #
+ # deregister a punt socket
+ #
+ self.vapi.punt_socket_deregister(punt_ospf)
+ punts = self.vapi.punt_socket_dump(type=pt_ip)
+ self.assertEqual(len(punts), 1)
+
+ #
+ # configure a punt socket again
+ #
+ self.vapi.punt_socket_register(punt_ospf,
+ "%s/socket_punt_3" % self.tempdir)
+ punts = self.vapi.punt_socket_dump(type=pt_ip)
+ self.assertEqual(len(punts), 2)
+
+ self.logger.info(self.vapi.cli("sh punt sock reg exception"))
+
+ #
+ # deregister all punt socket
+ #
+ self.vapi.punt_socket_deregister(punt_eigrp)
+ self.vapi.punt_socket_deregister(punt_ospf)
+ punts = self.vapi.punt_socket_dump(type=pt_ip)
+ self.assertEqual(len(punts), 0)
+
+ def verify_ospf_pkts(self, rxs, n_sent):
+ self.assertEqual(len(rxs), n_sent)
+ for rx in rxs:
+ self.assertTrue(rx.haslayer(OSPF_Hdr))
+
+ def test_traffic(self):
+ """ Punt socket traffic """
+
+ af_ip4 = VppEnum.vl_api_address_family_t.ADDRESS_IP4
+ pt_ip = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_IP_PROTO
+ proto_ospf = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_OSPF
+
+ #
+ # configure a punt socket to capture OSPF packets
+ #
+ punt_ospf = {
+ 'type': pt_ip,
+ 'punt': {
+ 'ip_proto': {
+ 'af': af_ip4,
+ 'protocol': proto_ospf
+ }
+ }
+ }
+
+ #
+ # create packet streams and configure a punt sockets
+ #
+ pkt = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
+ OSPF_Hdr() /
+ OSPFv3_Hello())
+ pkts = pkt * 7
+
+ sock = self.socket_client_create("%s/socket_1" % self.tempdir)
+ self.vapi.punt_socket_register(
+ punt_ospf, "%s/socket_1" % self.tempdir)
+
+ #
+ # send packets for each SPI we expect to be punted
+ #
+ self.send_and_assert_no_replies(self.pg0, pkts)
+
+ #
+ # verify the punted packets arrived on the associated socket
+ #
+ rx = sock.close()
+ self.verify_ospf_pkts(rx, len(pkts))
+ self.vapi.punt_socket_deregister(punt_ospf)
+
+
+class TestPunt(VppTestCase):
+ """ Exception Punt Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestPunt, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestPunt, cls).tearDownClass()
+
+ def setUp(self):
+ super(TestPunt, self).setUp()
+
+ self.create_pg_interfaces(range(4))
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.resolve_arp()
+ i.config_ip6()
+ i.resolve_ndp()
+
+ def tearDown(self):
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.unconfig_ip6()
+ i.admin_down()
+ super(TestPunt, self).tearDown()
+
+ def test_punt(self):
+ """ Exception Path testing """
+
+ #
+ # dump the punt registered reasons
+ # search for a few we know should be there
+ #
+ rs = self.vapi.punt_reason_dump()
+
+ reasons = ["ipsec6-no-such-tunnel",
+ "ipsec4-no-such-tunnel",
+ "ipsec4-spi-o-udp-0"]
+
+ for reason in reasons:
+ found = False
+ for r in rs:
+ if r.reason.name == reason:
+ found = True
+ break
+ self.assertTrue(found)
+
+ #
+ # Using the test CLI we will hook in a exception path to
+ # send ACL deny packets out of pg0 and pg1.
+ # the ACL is src,dst = 1.1.1.1,1.1.1.2
+ #
+ ip_1_1_1_2 = VppIpRoute(self, "1.1.1.2", 32,
+ [VppRoutePath(self.pg3.remote_ip4,
+ self.pg3.sw_if_index)])
+ ip_1_1_1_2.add_vpp_config()
+ ip_1_2 = VppIpRoute(self, "1::2", 128,
+ [VppRoutePath(self.pg3.remote_ip6,
+ self.pg3.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)])
+ ip_1_2.add_vpp_config()
+
+ p4 = (Ether(src=self.pg2.remote_mac,
+ dst=self.pg2.local_mac) /
+ IP(src="1.1.1.1", dst="1.1.1.2") /
+ UDP(sport=1234, dport=1234) /
+ Raw(b'\xa5' * 100))
+ p6 = (Ether(src=self.pg2.remote_mac,
+ dst=self.pg2.local_mac) /
+ IPv6(src="1::1", dst="1::2") /
+ UDP(sport=1234, dport=1234) /
+ Raw(b'\xa5' * 100))
+ self.send_and_expect(self.pg2, p4*1, self.pg3)
+ self.send_and_expect(self.pg2, p6*1, self.pg3)
+
+ #
+ # apply the punting features
+ #
+ self.vapi.cli("test punt pg2")
+
+ #
+ # dump the punt reasons to learn the IDs assigned
+ #
+ rs = self.vapi.punt_reason_dump(reason={'name': "reason-v4"})
+ r4 = rs[0].reason.id
+ rs = self.vapi.punt_reason_dump(reason={'name': "reason-v6"})
+ r6 = rs[0].reason.id
+
+ #
+ # pkts now dropped
+ #
+ self.send_and_assert_no_replies(self.pg2, p4*NUM_PKTS)
+ self.send_and_assert_no_replies(self.pg2, p6*NUM_PKTS)
+
+ #
+ # Check state:
+ # 1 - node error counters
+ # 2 - per-reason counters
+ # 2, 3 are the index of the assigned punt reason
+ #
+ stats = self.statistics.get_err_counter(
+ "/err/punt-dispatch/No registrations")
+ self.assertEqual(stats, 2*NUM_PKTS)
+
+ stats = self.statistics.get_counter("/net/punt")
+ self.assertEqual(stats[0][r4]['packets'], NUM_PKTS)
+ self.assertEqual(stats[0][r6]['packets'], NUM_PKTS)
+
+ #
+ # use the test CLI to test a client that punts exception
+ # packets out of pg0
+ #
+ self.vapi.cli("test punt pg0 %s" % self.pg0.remote_ip4)
+ self.vapi.cli("test punt pg0 %s" % self.pg0.remote_ip6)
+
+ rx4s = self.send_and_expect(self.pg2, p4*NUM_PKTS, self.pg0)
+ rx6s = self.send_and_expect(self.pg2, p6*NUM_PKTS, self.pg0)
+
+ #
+ # check the packets come out IP unmodified but destined to pg0 host
+ #
+ for rx in rx4s:
+ self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
+ self.assertEqual(rx[Ether].src, self.pg0.local_mac)
+ self.assertEqual(p4[IP].dst, rx[IP].dst)
+ self.assertEqual(p4[IP].ttl, rx[IP].ttl)
+ for rx in rx6s:
+ self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
+ self.assertEqual(rx[Ether].src, self.pg0.local_mac)
+ self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
+ self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
+
+ stats = self.statistics.get_counter("/net/punt")
+ self.assertEqual(stats[0][r4]['packets'], 2*NUM_PKTS)
+ self.assertEqual(stats[0][r6]['packets'], 2*NUM_PKTS)
+
+ #
+ # add another registration for the same reason to send packets
+ # out of pg1
+ #
+ self.vapi.cli("test punt pg1 %s" % self.pg1.remote_ip4)
+ self.vapi.cli("test punt pg1 %s" % self.pg1.remote_ip6)
+
+ self.vapi.cli("clear trace")
+ self.pg2.add_stream(p4 * NUM_PKTS)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rxd = self.pg0.get_capture(NUM_PKTS)
+ for rx in rxd:
+ self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
+ self.assertEqual(rx[Ether].src, self.pg0.local_mac)
+ self.assertEqual(p4[IP].dst, rx[IP].dst)
+ self.assertEqual(p4[IP].ttl, rx[IP].ttl)
+ rxd = self.pg1.get_capture(NUM_PKTS)
+ for rx in rxd:
+ self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
+ self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+ self.assertEqual(p4[IP].dst, rx[IP].dst)
+ self.assertEqual(p4[IP].ttl, rx[IP].ttl)
+
+ self.vapi.cli("clear trace")
+ self.pg2.add_stream(p6 * NUM_PKTS)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rxd = self.pg0.get_capture(NUM_PKTS)
+ for rx in rxd:
+ self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
+ self.assertEqual(rx[Ether].src, self.pg0.local_mac)
+ self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
+ self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
+ rxd = self.pg1.get_capture(NUM_PKTS)
+ for rx in rxd:
+ self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
+ self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+ self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
+ self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
+
+ stats = self.statistics.get_counter("/net/punt")
+ self.assertEqual(stats[0][r4]['packets'], 3*NUM_PKTS)
+ self.assertEqual(stats[0][r6]['packets'], 3*NUM_PKTS)
+
+ self.logger.info(self.vapi.cli("show vlib graph punt-dispatch"))
+ self.logger.info(self.vapi.cli("show punt client"))
+ self.logger.info(self.vapi.cli("show punt reason"))
+ self.logger.info(self.vapi.cli("show punt stats"))
+ self.logger.info(self.vapi.cli("show punt db"))