diff options
author | Ole Troan <otroan@employees.org> | 2023-08-17 13:36:08 +0200 |
---|---|---|
committer | Ole Troan <otroan@employees.org> | 2023-08-25 09:15:32 +0200 |
commit | 6ee3aa41c395d036c8c79a3681cc5ee6bc6fceb9 (patch) | |
tree | fcbb367a65e0c9fc932f62fcaf2d46ff622ba9dc /src | |
parent | ecb62d2e5d0af14e2de143a729abdf35e132e5d5 (diff) |
npt66: network prefix translation for ipv6
This is the initial commit of a NPTv6 (RFC6296) implementation for VPP.
It's restricted to a single internal to external binding and runs
as an output/input feature on the egress interface.
Type: feature
Change-Id: I0e3497af97f1ebd99377b84dbf599ecea935ca24
Signed-off-by: Ole Troan <otroan@employees.org>
Diffstat (limited to 'src')
-rw-r--r-- | src/plugins/npt66/CMakeLists.txt | 17 | ||||
-rw-r--r-- | src/plugins/npt66/FEATURE.yaml | 16 | ||||
-rw-r--r-- | src/plugins/npt66/npt66.api | 18 | ||||
-rw-r--r-- | src/plugins/npt66/npt66.c | 116 | ||||
-rw-r--r-- | src/plugins/npt66/npt66.h | 28 | ||||
-rw-r--r-- | src/plugins/npt66/npt66_api.c | 71 | ||||
-rw-r--r-- | src/plugins/npt66/npt66_cli.c | 88 | ||||
-rw-r--r-- | src/plugins/npt66/npt66_node.c | 289 |
8 files changed, 643 insertions, 0 deletions
diff --git a/src/plugins/npt66/CMakeLists.txt b/src/plugins/npt66/CMakeLists.txt new file mode 100644 index 00000000000..aee784d96f0 --- /dev/null +++ b/src/plugins/npt66/CMakeLists.txt @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright(c) 2023 Cisco Systems, Inc. + +add_vpp_plugin(npt66 + SOURCES + npt66.c + npt66_api.c + npt66_cli.c + npt66_node.c + + + MULTIARCH_SOURCES + npt66_node.c + + API_FILES + npt66.api +) diff --git a/src/plugins/npt66/FEATURE.yaml b/src/plugins/npt66/FEATURE.yaml new file mode 100644 index 00000000000..8874ae22017 --- /dev/null +++ b/src/plugins/npt66/FEATURE.yaml @@ -0,0 +1,16 @@ +--- +name: NPTv6 +maintainer: Ole Troan <otroan@employees.org> +features: + - NPTv6 + +description: "This plugin implements NPTv6 as described in RFC6296. + It supports arbitrary prefix lengths. And performs an + algorithmic mapping between internal and external IPv6 prefixes. + The mapping is checksum neutral. + The implementation is currently limited to a single statically configured binding + per interface. + A typical IPv6 CE use case, the external prefix would be learnt via DHCP PD + " +state: development +properties: [API, CLI, MULTITHREAD] diff --git a/src/plugins/npt66/npt66.api b/src/plugins/npt66/npt66.api new file mode 100644 index 00000000000..01ce7759799 --- /dev/null +++ b/src/plugins/npt66/npt66.api @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright(c) 2023 Cisco Systems, Inc. + +option version = "0.0.1"; + +import "vnet/interface_types.api"; +import "vnet/ip/ip_types.api"; + +autoendian autoreply define npt66_binding_add_del +{ + u32 client_index; + u32 context; + + bool is_add; + vl_api_interface_index_t sw_if_index; + vl_api_ip6_prefix_t internal; + vl_api_ip6_prefix_t external; +}; diff --git a/src/plugins/npt66/npt66.c b/src/plugins/npt66/npt66.c new file mode 100644 index 00000000000..e3cbbbd1a7b --- /dev/null +++ b/src/plugins/npt66/npt66.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright(c) 2023 Cisco Systems, Inc. + +/* + * npt66.c: NPT66 plugin + * An implementation of Network Prefix Translation for IPv6-to-IPv6 (NPTv6) as + * specified in RFC6296. + */ + +#include <stdio.h> +#include <stdint.h> +#include <inttypes.h> +#include <vlib/vlib.h> +#include <vnet/feature/feature.h> +#include <vppinfra/pool.h> +#include "npt66.h" + +static int +npt66_feature_enable_disable (u32 sw_if_index, bool is_add) +{ + if (vnet_feature_enable_disable ("ip6-unicast", "npt66-input", sw_if_index, + is_add, 0, 0) != 0) + return -1; + if (vnet_feature_enable_disable ("ip6-output", "npt66-output", sw_if_index, + is_add, 0, 0) != 0) + return -1; + return 0; +} + +static void +ipv6_prefix_zero (ip6_address_t *address, int prefix_len) +{ + int byte_index = prefix_len / 8; + int bit_offset = prefix_len % 8; + uint8_t mask = (1 << (8 - bit_offset)) - 1; + if (byte_index < 16) + { + address->as_u8[byte_index] &= mask; + for (int i = byte_index + 1; i < 16; i++) + { + address->as_u8[i] = 0; + } + } +} + +int +npt66_binding_add_del (u32 sw_if_index, ip6_address_t *internal, + int internal_plen, ip6_address_t *external, + int external_plen, bool is_add) +{ + npt66_main_t *nm = &npt66_main; + + if (is_add) + { + + /* Ensure prefix lengths are less than or equal to a /64 */ + if (internal_plen > 64 || external_plen > 64) + return VNET_API_ERROR_INVALID_VALUE; + + /* Create a binding entry */ + npt66_binding_t *b; + pool_get_zero (nm->bindings, b); + b->internal = *internal; + b->internal_plen = internal_plen; + b->external = *external; + b->external_plen = external_plen; + b->sw_if_index = sw_if_index; + + ipv6_prefix_zero (&b->internal, internal_plen); + ipv6_prefix_zero (&b->external, external_plen); + vec_validate_init_empty (nm->interface_by_sw_if_index, sw_if_index, ~0); + nm->interface_by_sw_if_index[sw_if_index] = b - nm->bindings; + + uword delta = 0; + delta = ip_csum_add_even (delta, b->external.as_u64[0]); + delta = ip_csum_add_even (delta, b->external.as_u64[1]); + delta = ip_csum_sub_even (delta, b->internal.as_u64[0]); + delta = ip_csum_sub_even (delta, b->internal.as_u64[1]); + delta = ip_csum_fold (delta); + b->delta = delta; + } + else + { + /* Delete a binding entry */ + npt66_binding_t *b = npt66_interface_by_sw_if_index (sw_if_index); + if (!b) + return VNET_API_ERROR_NO_SUCH_ENTRY; + nm->interface_by_sw_if_index[sw_if_index] = ~0; + pool_put (nm->bindings, b); + } + + /* Enable feature on interface */ + int rv = npt66_feature_enable_disable (sw_if_index, is_add); + + return rv; +} + +/* + * Do a lookup in the interface vector (interface_by_sw_if_index) + * and return pool entry. + */ +npt66_binding_t * +npt66_interface_by_sw_if_index (u32 sw_if_index) +{ + npt66_main_t *nm = &npt66_main; + + if (!nm->interface_by_sw_if_index || + sw_if_index > (vec_len (nm->interface_by_sw_if_index) - 1)) + return 0; + u32 index = nm->interface_by_sw_if_index[sw_if_index]; + if (index == ~0) + return 0; + if (pool_is_free_index (nm->bindings, index)) + return 0; + return pool_elt_at_index (nm->bindings, index); +} diff --git a/src/plugins/npt66/npt66.h b/src/plugins/npt66/npt66.h new file mode 100644 index 00000000000..428dadb1672 --- /dev/null +++ b/src/plugins/npt66/npt66.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright(c) 2023 Cisco Systems, Inc. + +#include <vlib/vlib.h> +#include <vnet/ip/ip6_packet.h> + +typedef struct +{ + u32 sw_if_index; + ip6_address_t internal; + ip6_address_t external; + u8 internal_plen; + u8 external_plen; + uword delta; +} npt66_binding_t; +typedef struct +{ + u32 *interface_by_sw_if_index; + npt66_binding_t *bindings; + u16 msg_id_base; +} npt66_main_t; + +extern npt66_main_t npt66_main; + +int npt66_binding_add_del (u32 sw_if_index, ip6_address_t *internal, + int internal_plen, ip6_address_t *external, + int external_plen, bool is_add); +npt66_binding_t *npt66_interface_by_sw_if_index (u32 sw_if_index); diff --git a/src/plugins/npt66/npt66_api.c b/src/plugins/npt66/npt66_api.c new file mode 100644 index 00000000000..91eb73c1e45 --- /dev/null +++ b/src/plugins/npt66/npt66_api.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright(c) 2023 Cisco Systems, Inc. + +#include <stdbool.h> +#include <npt66/npt66.h> +#include <vnet/vnet.h> +#include <npt66/npt66.api_enum.h> +#include <npt66/npt66.api_types.h> +#include <vlibmemory/api.h> +#include <vnet/ip/ip.h> +#include <vnet/ip/ip_types_api.h> +#include <vpp/app/version.h> + +npt66_main_t npt66_main; + +/* + * This file contains the API handlers for the pnat.api + */ + +#define REPLY_MSG_ID_BASE npt66_main.msg_id_base +#include <vlibapi/api_helper_macros.h> + +static void +vl_api_npt66_binding_add_del_t_handler (vl_api_npt66_binding_add_del_t *mp) +{ + vl_api_npt66_binding_add_del_reply_t *rmp; + int rv; + clib_warning ("Interface index: %d", mp->sw_if_index); + VALIDATE_SW_IF_INDEX_END (mp); + + rv = npt66_binding_add_del ( + mp->sw_if_index, (ip6_address_t *) &mp->internal.address, mp->internal.len, + (ip6_address_t *) &mp->external.address, mp->external.len, mp->is_add); + +bad_sw_if_index: + REPLY_MACRO_END (VL_API_NPT66_BINDING_ADD_DEL_REPLY); +} + +/* API definitions */ +#include <vnet/format_fns.h> +#include <npt66/npt66.api.c> + +/* Set up the API message handling tables */ +clib_error_t * +npt66_plugin_api_hookup (vlib_main_t *vm) +{ + npt66_main_t *nm = &npt66_main; + + nm->msg_id_base = setup_message_id_table (); + return 0; +} + +/* + * Register the plugin and hook up the API + */ +#include <vnet/plugin/plugin.h> +VLIB_PLUGIN_REGISTER () = { + .version = VPP_BUILD_VER, + .description = "NPTv6", +}; + +clib_error_t * +npt66_init (vlib_main_t *vm) +{ + npt66_main_t *nm = &npt66_main; + memset (nm, 0, sizeof (*nm)); + + return npt66_plugin_api_hookup (vm); +} + +VLIB_INIT_FUNCTION (npt66_init); diff --git a/src/plugins/npt66/npt66_cli.c b/src/plugins/npt66/npt66_cli.c new file mode 100644 index 00000000000..268bca264b3 --- /dev/null +++ b/src/plugins/npt66/npt66_cli.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright(c) 2023 Cisco Systems, Inc. + +#include <stdbool.h> +#include <vlib/vlib.h> +#include <vnet/feature/feature.h> +#include <vnet/ip/ip.h> +#include <vppinfra/clib_error.h> +#include "npt66.h" + +static clib_error_t * +set_npt66_binding_command_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t *error = 0; + bool internal_set = false, external_set = false; + bool add = true; + u32 sw_if_index = ~0; + ip6_address_t internal, external; + int internal_plen = 0, external_plen = 0; + + /* Get a line of input. */ + 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, "internal %U/%d", unformat_ip6_address, + &internal, &internal_plen)) + internal_set = true; + else if (unformat (line_input, "external %U/%d", unformat_ip6_address, + &external, &external_plen)) + external_set = true; + else if (unformat (line_input, "interface %U", + unformat_vnet_sw_interface, vnet_get_main (), + &sw_if_index)) + ; + else if (unformat (line_input, "del")) + { + add = false; + } + else + { + error = clib_error_return (0, "unknown input `%U'", + format_unformat_error, line_input); + goto done; + } + } + if (sw_if_index == ~0) + { + error = clib_error_return (0, "interface is required `%U'", + format_unformat_error, line_input); + goto done; + } + if (!internal_set) + { + error = clib_error_return (0, "missing parameter: internal `%U'", + format_unformat_error, line_input); + goto done; + } + if (!external_set) + { + error = clib_error_return (0, "missing parameter: external `%U'", + format_unformat_error, line_input); + goto done; + } + + int rv = npt66_binding_add_del (sw_if_index, &internal, internal_plen, + &external, external_plen, add); + if (rv) + { + error = clib_error_return (0, "Adding binding failed %d", rv); + goto done; + } + +done: + unformat_free (line_input); + + return error; +} + +VLIB_CLI_COMMAND (set_npt66_binding_command, static) = { + .path = "set npt66 binding", + .short_help = "set npt66 binding interface <name> internal <pfx> " + "external <pfx> [del]", + .function = set_npt66_binding_command_fn, +}; diff --git a/src/plugins/npt66/npt66_node.c b/src/plugins/npt66/npt66_node.c new file mode 100644 index 00000000000..95fe8594dbb --- /dev/null +++ b/src/plugins/npt66/npt66_node.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright(c) 2023 Cisco Systems, Inc. + +// This file contains the implementation of the NPT66 node. +// RFC6296: IPv6-to-IPv6 Network Prefix Translation (NPTv6) + +#include <vnet/ip/ip.h> +#include <vnet/ip/ip6.h> +#include <vnet/ip/ip6_packet.h> + +#include <npt66/npt66.h> + +typedef struct +{ + u32 pool_index; + ip6_address_t internal; + ip6_address_t external; +} npt66_trace_t; + +static inline u8 * +format_npt66_trace (u8 *s, va_list *args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + npt66_trace_t *t = va_arg (*args, npt66_trace_t *); + + if (t->pool_index != ~0) + s = format (s, "npt66: index %d internal: %U external: %U\n", + t->pool_index, format_ip6_address, &t->internal, + format_ip6_address, &t->external); + else + s = format (s, "npt66: index %d (binding not found)\n", t->pool_index); + return s; +} + +/* NPT66 next-nodes */ +typedef enum +{ + NPT66_NEXT_DROP, + NPT66_N_NEXT +} npt66_next_t; + +static ip6_address_t +ip6_prefix_copy (ip6_address_t dest, ip6_address_t src, int plen) +{ + int bytes_to_copy = plen / 8; + int residual_bits = plen % 8; + + // Copy full bytes + for (int i = 0; i < bytes_to_copy; i++) + { + dest.as_u8[i] = src.as_u8[i]; + } + + // Handle the residual bits, if any + if (residual_bits) + { + uint8_t mask = 0xFF << (8 - residual_bits); + dest.as_u8[bytes_to_copy] = (dest.as_u8[bytes_to_copy] & ~mask) | + (src.as_u8[bytes_to_copy] & mask); + } + return dest; +} +static int +ip6_prefix_cmp (ip6_address_t a, ip6_address_t b, int plen) +{ + int bytes_to_compare = plen / 8; + int residual_bits = plen % 8; + + // Compare full bytes + for (int i = 0; i < bytes_to_compare; i++) + { + if (a.as_u8[i] != b.as_u8[i]) + { + return 0; // prefixes are not identical + } + } + + // Compare the residual bits, if any + if (residual_bits) + { + uint8_t mask = 0xFF << (8 - residual_bits); + if ((a.as_u8[bytes_to_compare] & mask) != + (b.as_u8[bytes_to_compare] & mask)) + { + return 0; // prefixes are not identical + } + } + return 1; // prefixes are identical +} + +static int +npt66_adjust_checksum (int plen, bool add, ip_csum_t delta, + ip6_address_t *address) +{ + if (plen <= 48) + { + // TODO: Check for 0xFFFF + if (address->as_u16[3] == 0xffff) + return -1; + address->as_u16[3] = add ? ip_csum_add_even (address->as_u16[3], delta) : + ip_csum_sub_even (address->as_u16[3], delta); + } + else + { + /* For prefixes longer than 48 find a 16-bit word in the interface id */ + for (int i = 4; i < 8; i++) + { + if (address->as_u16[i] == 0xffff) + continue; + address->as_u16[i] = add ? + ip_csum_add_even (address->as_u16[i], delta) : + ip_csum_sub_even (address->as_u16[i], delta); + break; + } + } + return 0; +} + +static int +npt66_translate (ip6_header_t *ip, npt66_binding_t *binding, int dir) +{ + int rv = 0; + clib_warning ("npt66_translate: before: %U", format_ip6_header, ip, 40); + if (dir == VLIB_TX) + { + if (!ip6_prefix_cmp (ip->src_address, binding->internal, + binding->internal_plen)) + { + clib_warning ("npt66_translate: src address is not internal"); + goto done; + } + ip->src_address = ip6_prefix_copy (ip->src_address, binding->external, + binding->external_plen); + /* Checksum neutrality */ + rv = npt66_adjust_checksum (binding->internal_plen, false, + binding->delta, &ip->src_address); + } + else + { + if (!ip6_prefix_cmp (ip->dst_address, binding->external, + binding->external_plen)) + { + clib_warning ("npt66_translate: dst address is not external"); + goto done; + } + ip->dst_address = ip6_prefix_copy (ip->dst_address, binding->internal, + binding->internal_plen); + rv = npt66_adjust_checksum (binding->internal_plen, true, binding->delta, + &ip->src_address); + } + clib_warning ("npt66_translate: after: %U", format_ip6_header, ip, 40); +done: + return rv; +} + +/* + * Lookup the packet tuple in the flow cache, given the lookup mask. + * If a binding is found, rewrite the packet according to instructions, + * otherwise follow configured default action (forward, punt or drop) + */ +// TODO: Make use of SVR configurable +static_always_inline uword +npt66_node_inline (vlib_main_t *vm, vlib_node_runtime_t *node, + vlib_frame_t *frame, int dir) +{ + npt66_main_t *nm = &npt66_main; + u32 n_left_from, *from; + u16 nexts[VLIB_FRAME_SIZE] = { 0 }, *next = nexts; + u32 pool_indicies[VLIB_FRAME_SIZE], *pi = pool_indicies; + vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs; + ip6_header_t *ip; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + vlib_get_buffers (vm, from, b, n_left_from); + npt66_binding_t *binding; + + /* Stage 1: build vector of flow hash (based on lookup mask) */ + while (n_left_from > 0) + { + clib_warning ("DIRECTION: %u", dir); + u32 sw_if_index = vnet_buffer (b[0])->sw_if_index[dir]; + u32 iph_offset = + dir == VLIB_TX ? vnet_buffer (b[0])->ip.save_rewrite_length : 0; + ip = (ip6_header_t *) (vlib_buffer_get_current (b[0]) + iph_offset); + binding = npt66_interface_by_sw_if_index (sw_if_index); + ASSERT (binding); + *pi = binding - nm->bindings; + + /* By default pass packet to next node in the feature chain */ + vnet_feature_next_u16 (next, b[0]); + + int rv = npt66_translate (ip, binding, dir); + if (rv < 0) + { + clib_warning ("npt66_translate failed"); + *next = NPT66_NEXT_DROP; + } + + /*next: */ + next += 1; + n_left_from -= 1; + b += 1; + pi += 1; + } + + /* Packet trace */ + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE))) + { + u32 i; + b = bufs; + pi = pool_indicies; + + for (i = 0; i < frame->n_vectors; i++) + { + if (b[0]->flags & VLIB_BUFFER_IS_TRACED) + { + npt66_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t)); + if (*pi != ~0) + { + if (!pool_is_free_index (nm->bindings, *pi)) + { + npt66_binding_t *tr = + pool_elt_at_index (nm->bindings, *pi); + t->internal = tr->internal; + t->external = tr->external; + } + } + t->pool_index = *pi; + + b += 1; + pi += 1; + } + else + break; + } + } + vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors); + + return frame->n_vectors; +} + +VLIB_NODE_FN (npt66_input_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + return npt66_node_inline (vm, node, frame, VLIB_RX); +} +VLIB_NODE_FN (npt66_output_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + return npt66_node_inline (vm, node, frame, VLIB_TX); +} + +VLIB_REGISTER_NODE(npt66_input_node) = { + .name = "npt66-input", + .vector_size = sizeof(u32), + .format_trace = format_npt66_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + // .n_errors = NPT66_N_ERROR, + // .error_counters = npt66_error_counters, + .n_next_nodes = NPT66_N_NEXT, + .next_nodes = + { + [NPT66_NEXT_DROP] = "error-drop", + }, +}; + +VLIB_REGISTER_NODE (npt66_output_node) = { + .name = "npt66-output", + .vector_size = sizeof (u32), + .format_trace = format_npt66_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + // .n_errors = npt66_N_ERROR, + // .error_counters = npt66_error_counters, + .sibling_of = "npt66-input", +}; + +/* Hook up features */ +VNET_FEATURE_INIT (npt66_input, static) = { + .arc_name = "ip6-unicast", + .node_name = "npt66-input", + .runs_after = VNET_FEATURES ("ip4-sv-reassembly-feature"), +}; +VNET_FEATURE_INIT (npt66_output, static) = { + .arc_name = "ip6-output", + .node_name = "npt66-output", + .runs_after = VNET_FEATURES ("ip4-sv-reassembly-output-feature"), +}; |