From 6ee3aa41c395d036c8c79a3681cc5ee6bc6fceb9 Mon Sep 17 00:00:00 2001 From: Ole Troan Date: Thu, 17 Aug 2023 13:36:08 +0200 Subject: [PATCH] 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 --- MAINTAINERS | 5 + docs/spelling_wordlist.txt | 2 + src/plugins/npt66/CMakeLists.txt | 17 +++ src/plugins/npt66/FEATURE.yaml | 16 +++ src/plugins/npt66/npt66.api | 18 +++ src/plugins/npt66/npt66.c | 116 ++++++++++++++++ src/plugins/npt66/npt66.h | 28 ++++ src/plugins/npt66/npt66_api.c | 71 ++++++++++ src/plugins/npt66/npt66_cli.c | 88 ++++++++++++ src/plugins/npt66/npt66_node.c | 289 +++++++++++++++++++++++++++++++++++++++ test/test_npt66.py | 89 ++++++++++++ 11 files changed, 739 insertions(+) create mode 100644 src/plugins/npt66/CMakeLists.txt create mode 100644 src/plugins/npt66/FEATURE.yaml create mode 100644 src/plugins/npt66/npt66.api create mode 100644 src/plugins/npt66/npt66.c create mode 100644 src/plugins/npt66/npt66.h create mode 100644 src/plugins/npt66/npt66_api.c create mode 100644 src/plugins/npt66/npt66_cli.c create mode 100644 src/plugins/npt66/npt66_node.c create mode 100644 test/test_npt66.py diff --git a/MAINTAINERS b/MAINTAINERS index 84894a30553..6e9b535028f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -816,6 +816,11 @@ I: bpf_trace_filter M: Mohammed Hawari F: src/plugins/bpf_trace_filter +Plugin - NPTv6 +I: npt66 +M: Ole Troan +F: src/plugins/npt66 + cJSON I: cjson M: Ole Troan diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 178a230f54c..1a0669cb4e7 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -762,6 +762,8 @@ nodaemon noevaluate nonaddress nosyslog +npt +npt66 ns nsess nsh 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 +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 +#include +#include +#include +#include +#include +#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 +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 + +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 +#include + +/* 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 +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 +#include +#include +#include +#include +#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 internal " + "external [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 +#include +#include + +#include + +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"), +}; diff --git a/test/test_npt66.py b/test/test_npt66.py new file mode 100644 index 00000000000..5173c62d44f --- /dev/null +++ b/test/test_npt66.py @@ -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) -- 2.16.6