udp: add udp decapsulation

Possibility to register a port via CLI or API to decap incoming UDP
packets:
 - For CLI, a user needs to specify the inner protocol (only MPLS
   supported for now)
 - For API, the protocol is specified by index
Added unittests

Type: feature
Change-Id: Ifedd86d8db2e355b7618472554fd67d77a13a4aa
Signed-off-by: Arthur de Kerhor <arthurdekerhor@gmail.com>
This commit is contained in:
Arthur de Kerhor
2021-05-20 11:48:00 +02:00
committed by Florin Coras
parent b740fdc8ff
commit 8a6f5d394c
7 changed files with 372 additions and 14 deletions

View File

@ -660,6 +660,7 @@ list(APPEND VNET_SOURCES
udp/udp_pg.c udp/udp_pg.c
udp/udp_encap_node.c udp/udp_encap_node.c
udp/udp_encap.c udp/udp_encap.c
udp/udp_decap.c
udp/udp_api.c udp/udp_api.c
) )

View File

@ -2328,10 +2328,12 @@ fib_path_contribute_urpf (fib_node_index_t path_index,
case FIB_PATH_TYPE_DVR: case FIB_PATH_TYPE_DVR:
fib_urpf_list_append(urpf, path->dvr.fp_interface); fib_urpf_list_append(urpf, path->dvr.fp_interface);
break; break;
case FIB_PATH_TYPE_UDP_ENCAP:
fib_urpf_list_append(urpf, path->udp_encap.fp_udp_encap_id);
break;
case FIB_PATH_TYPE_DEAG: case FIB_PATH_TYPE_DEAG:
case FIB_PATH_TYPE_RECEIVE: case FIB_PATH_TYPE_RECEIVE:
case FIB_PATH_TYPE_INTF_RX: case FIB_PATH_TYPE_INTF_RX:
case FIB_PATH_TYPE_UDP_ENCAP:
case FIB_PATH_TYPE_BIER_FMASK: case FIB_PATH_TYPE_BIER_FMASK:
case FIB_PATH_TYPE_BIER_TABLE: case FIB_PATH_TYPE_BIER_TABLE:
case FIB_PATH_TYPE_BIER_IMP: case FIB_PATH_TYPE_BIER_IMP:

View File

@ -45,6 +45,26 @@ typedef udp_encap
u32 id; u32 id;
}; };
enum udp_decap_next_proto
{
UDP_API_DECAP_PROTO_IP4,
UDP_API_DECAP_PROTO_IP6,
UDP_API_DECAP_PROTO_MPLS,
};
/**
* @brief UDP Decap object
* @param is_ip4 - IPv4 if non-zero, else IPv6
* @param port - port to listen on for the decap
* @param next_proto - the protocol of the inner header
*/
typedef udp_decap
{
u8 is_ip4;
u16 port;
vl_api_udp_decap_next_proto_t next_proto;
};
/** /**
* @brief Add UDP encap * @brief Add UDP encap
* @param client_index - opaque cookie to identify the sender * @param client_index - opaque cookie to identify the sender
@ -103,6 +123,21 @@ define udp_encap_details
vl_api_udp_encap_t udp_encap; vl_api_udp_encap_t udp_encap;
}; };
/**
* @brief Add/Del UDP decap
* @param client_index - opaque cookie to identify the sender
* @param context - sender context, to match reply w/ request
* @param is_add - add decap if non-zero, else delete
* @param udp_decap - UDP decap description
*/
autoreply define udp_decap_add_del
{
u32 client_index;
u32 context;
bool is_add;
vl_api_udp_decap_t udp_decap;
};
/* /*
* Local Variables: * Local Variables:
* eval: (c-set-style "gnu") * eval: (c-set-style "gnu")

View File

@ -16,6 +16,7 @@
#include <vnet/vnet.h> #include <vnet/vnet.h>
#include <vlibmemory/api.h> #include <vlibmemory/api.h>
#include <vnet/udp/udp_local.h>
#include <vnet/udp/udp_encap.h> #include <vnet/udp/udp_encap.h>
#include <vnet/fib/fib_table.h> #include <vnet/fib/fib_table.h>
#include <vnet/ip/ip_types_api.h> #include <vnet/ip/ip_types_api.h>
@ -38,11 +39,11 @@
#include <vlibapi/api_helper_macros.h> #include <vlibapi/api_helper_macros.h>
#define foreach_udp_api_msg \ #define foreach_udp_api_msg \
_ (UDP_ENCAP_DEL, udp_encap_del) \ _ (UDP_ENCAP_DEL, udp_encap_del) \
_ (UDP_ENCAP_ADD, udp_encap_add) \ _ (UDP_ENCAP_ADD, udp_encap_add) \
_(UDP_ENCAP_DUMP, udp_encap_dump) _ (UDP_ENCAP_DUMP, udp_encap_dump) \
_ (UDP_DECAP_ADD_DEL, udp_decap_add_del)
static void static void
send_udp_encap_details (const udp_encap_t * ue, vl_api_registration_t * reg, send_udp_encap_details (const udp_encap_t * ue, vl_api_registration_t * reg,
@ -92,8 +93,7 @@ send_udp_encap_details (const udp_encap_t * ue, vl_api_registration_t * reg,
} }
static void static void
vl_api_udp_encap_dump_t_handler (vl_api_udp_encap_dump_t * mp, vl_api_udp_encap_dump_t_handler (vl_api_udp_encap_dump_t *mp)
vlib_main_t * vm)
{ {
vl_api_registration_t *reg; vl_api_registration_t *reg;
udp_encap_t *ue; udp_encap_t *ue;
@ -111,7 +111,7 @@ vl_api_udp_encap_dump_t_handler (vl_api_udp_encap_dump_t * mp,
} }
static void static void
vl_api_udp_encap_add_t_handler (vl_api_udp_encap_add_t * mp, vlib_main_t * vm) vl_api_udp_encap_add_t_handler (vl_api_udp_encap_add_t *mp)
{ {
vl_api_udp_encap_add_reply_t *rmp; vl_api_udp_encap_add_reply_t *rmp;
ip46_address_t src_ip, dst_ip; ip46_address_t src_ip, dst_ip;
@ -152,7 +152,7 @@ done:
} }
static void static void
vl_api_udp_encap_del_t_handler (vl_api_udp_encap_del_t * mp, vlib_main_t * vm) vl_api_udp_encap_del_t_handler (vl_api_udp_encap_del_t *mp)
{ {
vl_api_udp_encap_del_reply_t *rmp; vl_api_udp_encap_del_reply_t *rmp;
int rv = 0; int rv = 0;
@ -162,6 +162,45 @@ vl_api_udp_encap_del_t_handler (vl_api_udp_encap_del_t * mp, vlib_main_t * vm)
REPLY_MACRO (VL_API_UDP_ENCAP_DEL_REPLY); REPLY_MACRO (VL_API_UDP_ENCAP_DEL_REPLY);
} }
u32
udp_api_decap_proto_to_index (vlib_main_t *vm,
vl_api_udp_decap_next_proto_t iproto)
{
switch (iproto)
{
case UDP_API_DECAP_PROTO_IP4:
return vlib_get_node_by_name (vm, (u8 *) "ip4-input")->index;
case UDP_API_DECAP_PROTO_IP6:
return vlib_get_node_by_name (vm, (u8 *) "ip6-input")->index;
case UDP_API_DECAP_PROTO_MPLS:
return vlib_get_node_by_name (vm, (u8 *) "mpls-input")->index;
}
return ~0;
}
static void
vl_api_udp_decap_add_del_t_handler (vl_api_udp_decap_add_del_t *mp)
{
vl_api_udp_decap_add_del_reply_t *rmp;
vlib_main_t *vm = vlib_get_main ();
int rv = 0;
if (mp->is_add)
{
u32 node_index =
udp_api_decap_proto_to_index (vm, ntohl (mp->udp_decap.next_proto));
if (node_index == ~0)
rv = VNET_API_ERROR_INVALID_PROTOCOL;
else
udp_register_dst_port (vm, ntohs (mp->udp_decap.port), node_index,
mp->udp_decap.is_ip4);
}
else
udp_unregister_dst_port (vm, ntohs (mp->udp_decap.port),
mp->udp_decap.is_ip4);
REPLY_MACRO (VL_API_UDP_DECAP_ADD_DEL_REPLY);
}
#define vl_msg_name_crc_list #define vl_msg_name_crc_list
#include <vnet/udp/udp.api.h> #include <vnet/udp/udp.api.h>
#undef vl_msg_name_crc_list #undef vl_msg_name_crc_list

106
src/vnet/udp/udp_decap.c Normal file
View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2021 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <vnet/udp/udp.h>
uword
unformat_next_node (unformat_input_t *input, va_list *args)
{
vlib_main_t *vm = va_arg (*args, vlib_main_t *);
u32 *node_index = va_arg (*args, u32 *);
if (unformat (input, "mpls"))
*node_index = vlib_get_node_by_name (vm, (u8 *) "mpls-input")->index;
else if (unformat (input, "ip4"))
*node_index = vlib_get_node_by_name (vm, (u8 *) "ip4-input")->index;
else if (unformat (input, "ip6"))
*node_index = vlib_get_node_by_name (vm, (u8 *) "ip6-input")->index;
else
return 0;
return 1;
}
static clib_error_t *
udp_decap_cli (vlib_main_t *vm, unformat_input_t *input,
vlib_cli_command_t *cmd_arg)
{
unformat_input_t _line_input, *line_input = &_line_input;
clib_error_t *error = NULL;
u8 is_add = 1, is_ip4 = 1;
int i = 0;
u16 port = 0;
u32 node_index = ~0;
if (!unformat_user (input, unformat_line_input, line_input))
return 0;
while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
{
if (unformat (line_input, "add"))
is_add = 1;
else if (unformat (line_input, "del"))
is_add = 0;
else if (unformat (line_input, "ipv4"))
is_ip4 = 1;
else if (unformat (line_input, "ipv6"))
is_ip4 = 0;
else if (unformat (line_input, "%d", &i))
port = i;
else if (unformat (line_input, "next-proto %U", unformat_next_node, vm,
&node_index))
;
else
{
error = clib_error_return (0, "parse error: '%U'",
format_unformat_error, line_input);
goto done;
}
}
if (port == 0)
{
error = clib_error_return (0, "missing port");
goto done;
}
if (is_add && node_index == ~0)
{
error = clib_error_return (0, "missing protocol");
goto done;
}
if (is_add)
udp_register_dst_port (vm, port, node_index, is_ip4);
else
udp_unregister_dst_port (vm, port, is_ip4);
done:
unformat_free (line_input);
return error;
}
/*?
* Register a port to decapsulate incoming UDP encapsulated packets.
*
* @cliexpar
* @clistart
* udp decap add ipv4 1234 next-proto mpls
* @cliend
* @cliexcmd{udp decap [add|del] [ipv4|ipv6] <dst-port> next-proto
<inner-protocol>}
?*/
VLIB_CLI_COMMAND (udp_decap_add_command, static) = {
.path = "udp decap",
.short_help =
"udp decap [add|del] [ipv4|ipv6] <dst-port> next-proto <inner-protocol>",
.function = udp_decap_cli,
};

View File

@ -4,12 +4,15 @@ from framework import tag_fixme_vpp_workers
from framework import VppTestCase, VppTestRunner from framework import VppTestCase, VppTestRunner
from vpp_udp_encap import find_udp_encap, VppUdpEncap from vpp_udp_encap import find_udp_encap, VppUdpEncap
from vpp_udp_decap import VppUdpDecap
from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpTable, VppMplsLabel, \ from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpTable, VppMplsLabel, \
FibPathType VppMplsTable, VppMplsRoute, FibPathType, FibPathProto
from vpp_neighbor import VppNeighbor
from vpp_papi import VppEnum
from scapy.packet import Raw from scapy.packet import Raw
from scapy.layers.l2 import Ether from scapy.layers.l2 import Ether
from scapy.layers.inet import IP, UDP from scapy.layers.inet import IP, UDP, ICMP
from scapy.layers.inet6 import IPv6 from scapy.layers.inet6 import IPv6
from scapy.contrib.mpls import MPLS from scapy.contrib.mpls import MPLS
@ -87,9 +90,12 @@ class TestUdpEncap(VppTestCase):
else: else:
self.assertEqual(rx[IP].ttl, tx[IP].ttl) self.assertEqual(rx[IP].ttl, tx[IP].ttl)
def validate_inner6(self, rx, tx): def validate_inner6(self, rx, tx, hlim=None):
self.assertEqual(rx.src, tx[IPv6].src) self.assertEqual(rx.src, tx[IPv6].src)
self.assertEqual(rx.dst, tx[IPv6].dst) self.assertEqual(rx.dst, tx[IPv6].dst)
if hlim:
self.assertEqual(rx.hlim, hlim)
else:
self.assertEqual(rx.hlim, tx[IPv6].hlim) self.assertEqual(rx.hlim, tx[IPv6].hlim)
def test_udp_encap(self): def test_udp_encap(self):
@ -248,6 +254,140 @@ class TestUdpEncap(VppTestCase):
self.validate_inner4(p, p_4omo4, ttl=63) self.validate_inner4(p, p_4omo4, ttl=63)
self.assertEqual(udp_encap_1.get_stats()['packets'], 2*NUM_PKTS) self.assertEqual(udp_encap_1.get_stats()['packets'], 2*NUM_PKTS)
def test_udp_decap(self):
""" UDP Decap test
"""
#
# construct a UDP decap object for each type of protocol
#
# IPv4
udp_api_proto = VppEnum.vl_api_udp_decap_next_proto_t
next_proto = udp_api_proto.UDP_API_DECAP_PROTO_IP4
udp_decap_0 = VppUdpDecap(self, 1, 220, next_proto)
# IPv6
next_proto = udp_api_proto.UDP_API_DECAP_PROTO_IP6
udp_decap_1 = VppUdpDecap(self, 0, 221, next_proto)
# MPLS
next_proto = udp_api_proto.UDP_API_DECAP_PROTO_MPLS
udp_decap_2 = VppUdpDecap(self, 1, 222, next_proto)
udp_decap_0.add_vpp_config()
udp_decap_1.add_vpp_config()
udp_decap_2.add_vpp_config()
#
# Routes via the corresponding pg after the UDP decap
#
route_4 = VppIpRoute(
self, "1.1.1.1", 32,
[VppRoutePath("0.0.0.0", self.pg0.sw_if_index)],
table_id=0)
route_6 = VppIpRoute(
self, "2001::1", 128,
[VppRoutePath("::", self.pg1.sw_if_index)],
table_id=1)
route_mo4 = VppIpRoute(
self, "3.3.3.3", 32,
[VppRoutePath("0.0.0.0", self.pg2.sw_if_index)],
table_id=2)
route_4.add_vpp_config()
route_6.add_vpp_config()
route_mo4.add_vpp_config()
#
# Adding neighbors to route the packets
#
n_4 = VppNeighbor(self,
self.pg0.sw_if_index,
"00:11:22:33:44:55",
"1.1.1.1")
n_6 = VppNeighbor(self,
self.pg1.sw_if_index,
"11:22:33:44:55:66",
"2001::1")
n_mo4 = VppNeighbor(self,
self.pg2.sw_if_index,
"22:33:44:55:66:77",
"3.3.3.3")
n_4.add_vpp_config()
n_6.add_vpp_config()
n_mo4.add_vpp_config()
#
# MPLS decapsulation config
#
mpls_table = VppMplsTable(self, 0)
mpls_table.add_vpp_config()
mpls_route = VppMplsRoute(
self, 77, 1,
[VppRoutePath("0.0.0.0",
0xFFFFFFFF,
nh_table_id=2,
proto=FibPathProto.FIB_PATH_NH_PROTO_IP4)])
mpls_route.add_vpp_config()
#
# UDP over ipv4 decap
#
p_4 = (Ether(src=self.pg0.remote_mac,
dst=self.pg0.local_mac) /
IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
UDP(sport=1111, dport=220) /
IP(src="2.2.2.2", dst="1.1.1.1") /
UDP(sport=1234, dport=4321) /
Raw(b'\xa5' * 100))
rx = self.send_and_expect(self.pg0, p_4*NUM_PKTS, self.pg0)
p_4 = IP(p_4["UDP"].payload)
for p in rx:
p = IP(p["Ether"].payload)
self.validate_inner4(p, p_4, ttl=63)
#
# UDP over ipv6 decap
#
p_6 = (Ether(src=self.pg1.remote_mac,
dst=self.pg1.local_mac) /
IPv6(src=self.pg1.remote_ip6, dst=self.pg1.local_ip6) /
UDP(sport=2222, dport=221) /
IPv6(src="2001::100", dst="2001::1") /
UDP(sport=1234, dport=4321) /
Raw(b'\xa5' * 100))
rx = self.send_and_expect(self.pg1, p_6*NUM_PKTS, self.pg1)
p_6 = IPv6(p_6["UDP"].payload)
p = IPv6(rx[0]["Ether"].payload)
for p in rx:
p = IPv6(p["Ether"].payload)
self.validate_inner6(p, p_6, hlim=63)
#
# UDP over mpls decap
#
p_mo4 = (Ether(src=self.pg2.remote_mac,
dst=self.pg2.local_mac) /
IP(src=self.pg2.remote_ip4, dst=self.pg2.local_ip4) /
UDP(sport=3333, dport=222) /
MPLS(label=77, ttl=1) /
IP(src="4.4.4.4", dst="3.3.3.3") /
UDP(sport=1234, dport=4321) /
Raw(b'\xa5' * 100))
self.pg2.enable_mpls()
rx = self.send_and_expect(self.pg2, p_mo4*NUM_PKTS, self.pg2)
self.pg2.disable_mpls()
p_mo4 = IP(MPLS(p_mo4["UDP"].payload).payload)
for p in rx:
p = IP(p["Ether"].payload)
self.validate_inner4(p, p_mo4, ttl=63)
@tag_fixme_vpp_workers @tag_fixme_vpp_workers
class TestUDP(VppTestCase): class TestUDP(VppTestCase):

35
test/vpp_udp_decap.py Normal file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""
UDP decap objects
"""
from vpp_object import VppObject
from socket import inet_pton, inet_ntop, AF_INET, AF_INET6
class VppUdpDecap(VppObject):
def __init__(self,
test,
is_ip4,
dst_port,
next_proto):
self._test = test
self.active = False
self.udp_decap = {
'is_ip4': is_ip4,
'port': dst_port,
'next_proto': next_proto
}
def add_vpp_config(self):
self._test.vapi.udp_decap_add_del(True, self.udp_decap)
self._test.registry.register(self, self._test.logger)
self.active = True
def query_vpp_config(self):
return self.active
def remove_vpp_config(self):
self._test.vapi.udp_decap_add_del(False, self.udp_decap)
self.active = False