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>
This commit is contained in:
@@ -816,6 +816,11 @@ I: bpf_trace_filter
|
||||
M: Mohammed Hawari <mohammed@hawari.fr>
|
||||
F: src/plugins/bpf_trace_filter
|
||||
|
||||
Plugin - NPTv6
|
||||
I: npt66
|
||||
M: Ole Troan <otroan@employees.org>
|
||||
F: src/plugins/npt66
|
||||
|
||||
cJSON
|
||||
I: cjson
|
||||
M: Ole Troan <ot@cisco.com>
|
||||
|
@@ -762,6 +762,8 @@ nodaemon
|
||||
noevaluate
|
||||
nonaddress
|
||||
nosyslog
|
||||
npt
|
||||
npt66
|
||||
ns
|
||||
nsess
|
||||
nsh
|
||||
|
17
src/plugins/npt66/CMakeLists.txt
Normal file
17
src/plugins/npt66/CMakeLists.txt
Normal file
@@ -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
|
||||
)
|
16
src/plugins/npt66/FEATURE.yaml
Normal file
16
src/plugins/npt66/FEATURE.yaml
Normal file
@@ -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]
|
18
src/plugins/npt66/npt66.api
Normal file
18
src/plugins/npt66/npt66.api
Normal file
@@ -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;
|
||||
};
|
116
src/plugins/npt66/npt66.c
Normal file
116
src/plugins/npt66/npt66.c
Normal file
@@ -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);
|
||||
}
|
28
src/plugins/npt66/npt66.h
Normal file
28
src/plugins/npt66/npt66.h
Normal file
@@ -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);
|
71
src/plugins/npt66/npt66_api.c
Normal file
71
src/plugins/npt66/npt66_api.c
Normal file
@@ -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);
|
88
src/plugins/npt66/npt66_cli.c
Normal file
88
src/plugins/npt66/npt66_cli.c
Normal file
@@ -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,
|
||||
};
|
289
src/plugins/npt66/npt66_node.c
Normal file
289
src/plugins/npt66/npt66_node.c
Normal file
@@ -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"),
|
||||
};
|
89
test/test_npt66.py
Normal file
89
test/test_npt66.py
Normal file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
import ipaddress
|
||||
from framework import VppTestCase, VppTestRunner
|
||||
|
||||
from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest
|
||||
from scapy.layers.l2 import Ether
|
||||
from scapy.packet import Raw
|
||||
|
||||
|
||||
class TestNPT66(VppTestCase):
|
||||
"""NPTv6 Test Case"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestNPT66, self).setUp()
|
||||
|
||||
# create 2 pg interfaces
|
||||
self.create_pg_interfaces(range(2))
|
||||
|
||||
for i in self.pg_interfaces:
|
||||
i.admin_up()
|
||||
i.config_ip6()
|
||||
i.resolve_ndp()
|
||||
|
||||
def tearDown(self):
|
||||
for i in self.pg_interfaces:
|
||||
i.unconfig_ip6()
|
||||
i.admin_down()
|
||||
super(TestNPT66, self).tearDown()
|
||||
|
||||
def send_and_verify(self, in2out, internal, external):
|
||||
if in2out:
|
||||
sendif = self.pg0
|
||||
recvif = self.pg1
|
||||
local_mac = self.pg0.local_mac
|
||||
remote_mac = self.pg0.remote_mac
|
||||
src = ipaddress.ip_interface(internal).ip + 1
|
||||
dst = self.pg1.remote_ip6
|
||||
else:
|
||||
sendif = self.pg1
|
||||
recvif = self.pg0
|
||||
local_mac = self.pg1.local_mac
|
||||
remote_mac = self.pg1.remote_mac
|
||||
src = self.pg1.remote_ip6
|
||||
dst = ipaddress.ip_interface(external).ip + 1
|
||||
|
||||
p = (
|
||||
Ether(dst=local_mac, src=remote_mac)
|
||||
/ IPv6(src=src, dst=dst)
|
||||
/ ICMPv6EchoRequest()
|
||||
)
|
||||
rxs = self.send_and_expect(sendif, p, recvif)
|
||||
for rx in rxs:
|
||||
rx.show2()
|
||||
original_cksum = rx[ICMPv6EchoRequest].cksum
|
||||
del rx[ICMPv6EchoRequest].cksum
|
||||
rx = rx.__class__(bytes(rx))
|
||||
self.assertEqual(original_cksum, rx[ICMPv6EchoRequest].cksum)
|
||||
|
||||
def do_test(self, internal, external):
|
||||
self.vapi.npt66_binding_add_del(
|
||||
sw_if_index=self.pg1.sw_if_index,
|
||||
internal=internal,
|
||||
external=external,
|
||||
is_add=True,
|
||||
)
|
||||
self.vapi.cli(f"ip route add {internal} via {self.pg0.remote_ip6}")
|
||||
|
||||
self.send_and_verify(True, internal, external)
|
||||
self.send_and_verify(False, internal, external)
|
||||
|
||||
self.vapi.npt66_binding_add_del(
|
||||
sw_if_index=self.pg1.sw_if_index,
|
||||
internal=internal,
|
||||
external=external,
|
||||
is_add=False,
|
||||
)
|
||||
|
||||
def test_npt66_simple(self):
|
||||
"""Send and receive a packet through NPT66"""
|
||||
|
||||
self.do_test("fc00:1::/48", "2001:db8:1::/48")
|
||||
self.do_test("fc00:1234::/32", "2001:db8:1::/32")
|
||||
self.do_test("fc00:1234::/63", "2001:db8:1::/56")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(testRunner=VppTestRunner)
|
Reference in New Issue
Block a user