aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/nat/pnat
diff options
context:
space:
mode:
authorOle Troan <ot@cisco.com>2021-01-12 21:49:38 +0100
committerNeale Ranns <neale@graphiant.com>2021-02-05 13:27:48 +0000
commit18327be5d458f9f73c12d76e677ee5a068ec6b10 (patch)
treebac6dbc08280e5bd6d5749ea56c862e6cdc38434 /src/plugins/nat/pnat
parent490b92738f3cc1c8d534abd6dee8dba942cb652d (diff)
nat: 1:1 policy NAT
A NAT sub-plugin doing statically configured match/rewrite on IP4 input or output. It's stateless (no connection tracking). Currently it supports rewriting of SA, DA and TCP/UDP ports. It should be simple to add new rewrites if required. API: pnat_binding_add, pnat_binding_del, pnat_bindings_get, pnat_interfaces_get CLI: set pnat translation interface <name> match <5-tuple> rewrite <5-tuple> {in|out} [del] show pnat translations show pnat interfaces Trying a new C based unit testing scheme. Where the graph node is tested in isolation. See pnat/pnat_test.c. Also added new cmake targets to generate coverage directly. E.g.: make test_pnat-ccov-report File '/vpp/sdnat/src/plugins/nat/pnat/pnat.c': Name Regions Miss Cover Lines Miss Cover ------------------------------------------------------------------------------------ pnat_interface_by_sw_if_index 39 8 79.49% 13 0 100.00% pnat_instructions_from_mask 9 0 100.00% 13 0 100.00% pnat_binding_add 64 8 87.50% 31 2 93.55% pnat_flow_lookup 4 4 0.00% 10 10 0.00% pnat_binding_attach 104 75 27.88% 33 6 81.82% pnat_binding_detach 30 5 83.33% 23 2 91.30% pnat_binding_del 97 33 65.98% 17 3 82.35% pnat.c:pnat_calc_key_from_5tuple 9 1 88.89% 14 1 92.86% pnat.c:pnat_interface_check_mask 10 2 80.00% 11 2 81.82% pnat.c:pnat_enable 5 0 100.00% 11 0 100.00% pnat.c:pnat_enable_interface 107 26 75.70% 60 15 75.00% pnat.c:pnat_disable_interface 91 30 67.03% 32 7 78.12% pnat.c:pnat_disable 7 2 71.43% 13 7 46.15% ------------------------------------------------------------------------------------ TOTAL 576 194 66.32% 281 55 80.43% File '/vpp/sdnat/src/plugins/nat/pnat/pnat_node.h': Name Regions Miss Cover Lines Miss Cover ------------------------------------------------------------------------------------ pnat_test.c:pnat_node_inline 67 11 83.58% 115 1 99.13% pnat_test.c:pnat_calc_key 9 2 77.78% 14 2 85.71% pnat_test.c:pnat_rewrite_ip4 55 11 80.00% 60 12 80.00% pnat_test.c:format_pnat_trace 1 1 0.00% 12 12 0.00% pnat_node.c:pnat_node_inline 63 63 0.00% 115 115 0.00% pnat_node.c:pnat_calc_key 9 9 0.00% 14 14 0.00% pnat_node.c:pnat_rewrite_ip4 55 55 0.00% 60 60 0.00% pnat_node.c:format_pnat_trace 5 5 0.00% 12 12 0.00% ------------------------------------------------------------------------------------ TOTAL 264 157 40.53% 402 228 43.28% Type: feature Change-Id: I9c897f833603054a8303e7369ebff6512517c9e0 Signed-off-by: Ole Troan <ot@cisco.com>
Diffstat (limited to 'src/plugins/nat/pnat')
-rw-r--r--src/plugins/nat/pnat/.clang-format3
-rw-r--r--src/plugins/nat/pnat/FEATURE.yaml12
-rw-r--r--src/plugins/nat/pnat/pnat.api155
-rw-r--r--src/plugins/nat/pnat/pnat.c372
-rw-r--r--src/plugins/nat/pnat/pnat.h121
-rw-r--r--src/plugins/nat/pnat/pnat.md37
-rw-r--r--src/plugins/nat/pnat/pnat_api.c207
-rw-r--r--src/plugins/nat/pnat/pnat_cli.c274
-rw-r--r--src/plugins/nat/pnat/pnat_node.c83
-rw-r--r--src/plugins/nat/pnat/pnat_node.h196
-rw-r--r--src/plugins/nat/pnat/pnat_test.c504
-rw-r--r--src/plugins/nat/pnat/pnat_test_stubs.h214
12 files changed, 2178 insertions, 0 deletions
diff --git a/src/plugins/nat/pnat/.clang-format b/src/plugins/nat/pnat/.clang-format
new file mode 100644
index 00000000000..6f25dc76efc
--- /dev/null
+++ b/src/plugins/nat/pnat/.clang-format
@@ -0,0 +1,3 @@
+SortIncludes: false
+BasedOnStyle: LLVM
+IndentWidth: 4
diff --git a/src/plugins/nat/pnat/FEATURE.yaml b/src/plugins/nat/pnat/FEATURE.yaml
new file mode 100644
index 00000000000..56f553a6081
--- /dev/null
+++ b/src/plugins/nat/pnat/FEATURE.yaml
@@ -0,0 +1,12 @@
+---
+name: Policy 1:1 NAT
+maintainer: Ole Troan <ot@cisco.com>
+description: "Match packet against rule and translate according to given instructions.
+ Rules are kept in a flow cache bihash. Instructions in a pool of translation entries.
+
+ For a given interface/direction all rules must use the same lookup mask. E.g. SA + SP.
+
+ A dynamic NAT would punt to slow path on a miss in the flow cache, in this case the miss behaviour is configurable.
+ Default behaviour is pass packet along unchanged."
+state: experimental
+properties: [API, CLI, MULTITHREAD]
diff --git a/src/plugins/nat/pnat/pnat.api b/src/plugins/nat/pnat/pnat.api
new file mode 100644
index 00000000000..c18c89445e9
--- /dev/null
+++ b/src/plugins/nat/pnat/pnat.api
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+
+option version = "0.0.1";
+
+import "vnet/interface_types.api";
+import "vnet/ip/ip_types.api";
+
+enum pnat_mask
+{
+ PNAT_SA = 0x1,
+ PNAT_DA = 0x2,
+ PNAT_SPORT = 0x4,
+ PNAT_DPORT = 0x8,
+};
+
+enum pnat_attachment_point
+{
+ PNAT_IP4_INPUT,
+ PNAT_IP4_OUTPUT,
+ PNAT_ATTACHMENT_POINT_MAX,
+};
+
+typedef pnat_5tuple
+{
+ vl_api_ip4_address_t src;
+ vl_api_ip4_address_t dst;
+ vl_api_ip_proto_t proto;
+ u16 sport;
+ u16 dport;
+ vl_api_pnat_mask_t mask;
+};
+
+autoendian define pnat_binding_add
+{
+ u32 client_index;
+ u32 context;
+ vl_api_pnat_5tuple_t match;
+ vl_api_pnat_5tuple_t rewrite;
+};
+
+autoendian define pnat_binding_add_reply
+{
+ u32 context;
+ i32 retval;
+ u32 binding_index;
+};
+
+autoendian autoreply define pnat_binding_del
+{
+ u32 client_index;
+ u32 context;
+ u32 binding_index;
+};
+
+autoendian autoreply define pnat_binding_attach
+{
+ u32 client_index;
+ u32 context;
+ vl_api_interface_index_t sw_if_index;
+ vl_api_pnat_attachment_point_t attachment;
+ u32 binding_index;
+};
+
+autoendian autoreply define pnat_binding_detach
+{
+ u32 client_index;
+ u32 context;
+ vl_api_interface_index_t sw_if_index;
+ vl_api_pnat_attachment_point_t attachment;
+ u32 binding_index;
+};
+
+service {
+ rpc pnat_bindings_get returns pnat_bindings_get_reply
+ stream pnat_bindings_details;
+ rpc pnat_interfaces_get returns pnat_interfaces_get_reply
+ stream pnat_interfaces_details;
+};
+
+define pnat_bindings_get
+{
+ u32 client_index;
+ u32 context;
+ u32 cursor;
+};
+
+define pnat_bindings_get_reply
+{
+ u32 context;
+ i32 retval;
+ u32 cursor;
+};
+
+define pnat_bindings_details
+{
+ u32 context;
+ vl_api_pnat_5tuple_t match;
+ vl_api_pnat_5tuple_t rewrite;
+};
+
+define pnat_interfaces_get
+{
+ u32 client_index;
+ u32 context;
+ u32 cursor;
+};
+
+define pnat_interfaces_get_reply
+{
+ u32 context;
+ i32 retval;
+ u32 cursor;
+};
+
+define pnat_interfaces_details
+{
+ u32 context;
+ vl_api_interface_index_t sw_if_index;
+ bool enabled[2]; /* PNAT_ATTACHMENT_POINT_MAX */
+ vl_api_pnat_mask_t lookup_mask[2]; /* PNAT_ATTACHMENT_POINT_MAX */
+};
+
+counters pnat {
+ none {
+ severity info;
+ type counter64;
+ units "packets";
+ description "successfully rewritten";
+ };
+
+ rewrite {
+ severity error;
+ type counter64;
+ units "packets";
+ description "rewrite failed";
+ };
+};
+
+paths {
+ "/err/pnat-input" "pnat";
+ "/err/pnat-output" "pnat";
+};
diff --git a/src/plugins/nat/pnat/pnat.c b/src/plugins/nat/pnat/pnat.c
new file mode 100644
index 00000000000..335d2750ce7
--- /dev/null
+++ b/src/plugins/nat/pnat/pnat.c
@@ -0,0 +1,372 @@
+/*
+ * 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 "pnat.h"
+#include <arpa/inet.h>
+#include <stdbool.h>
+#include <vlib/vlib.h>
+#include <vnet/feature/feature.h>
+#include <vnet/fib/fib_table.h>
+#include <vnet/ip/format.h>
+#include <vnet/ip/ip4.h>
+#include <vnet/ip/ip4_packet.h>
+#include <vnet/ip/reass/ip4_sv_reass.h>
+#include <vppinfra/clib_error.h>
+
+/*
+ * This is the main control plane part of the PNAT (Policy 1:1 NAT) feature.
+ */
+
+pnat_main_t pnat_main;
+
+/*
+ * Do a lookup in the interface vector (interface_by_sw_if_index)
+ * and return pool entry.
+ */
+pnat_interface_t *pnat_interface_by_sw_if_index(u32 sw_if_index) {
+ pnat_main_t *pm = &pnat_main;
+
+ if (!pm->interface_by_sw_if_index ||
+ sw_if_index > (vec_len(pm->interface_by_sw_if_index) - 1))
+ return 0;
+ u32 index = pm->interface_by_sw_if_index[sw_if_index];
+ if (index == ~0)
+ return 0;
+ if (pool_is_free_index(pm->interfaces, index))
+ return 0;
+ return pool_elt_at_index(pm->interfaces, index);
+}
+
+static pnat_mask_fast_t pnat_mask2fast(pnat_mask_t lookup_mask) {
+ pnat_mask_fast_t m = {0};
+
+ if (lookup_mask & PNAT_SA)
+ m.as_u64[0] = 0xffffffff00000000;
+ if (lookup_mask & PNAT_DA)
+ m.as_u64[0] |= 0x00000000ffffffff;
+ m.as_u64[1] = 0xffffffff00000000;
+ if (lookup_mask & PNAT_SPORT)
+ m.as_u64[1] |= 0x00000000ffff0000;
+ if (lookup_mask & PNAT_DPORT)
+ m.as_u64[1] |= 0x000000000000ffff;
+ return m;
+}
+
+/*
+ * Create new PNAT interface object and register the pnat feature in the
+ * corresponding feature chain.
+ * Also enable shallow virtual reassembly, to ensure that we have
+ * L4 ports available for all packets we receive.
+ */
+static clib_error_t *pnat_enable_interface(u32 sw_if_index,
+ pnat_attachment_point_t attachment,
+ pnat_mask_t mask) {
+ pnat_main_t *pm = &pnat_main;
+ pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
+
+ if (!interface) {
+ pool_get_zero(pm->interfaces, interface);
+ interface->sw_if_index = sw_if_index;
+ vec_validate_init_empty(pm->interface_by_sw_if_index, sw_if_index, ~0);
+ pm->interface_by_sw_if_index[sw_if_index] = interface - pm->interfaces;
+ }
+
+ char *nodename;
+ char *arcname;
+ bool input = false;
+ switch (attachment) {
+ case PNAT_IP4_INPUT:
+ nodename = "pnat-input";
+ arcname = "ip4-unicast";
+ input = true;
+ break;
+
+ case PNAT_IP4_OUTPUT:
+ nodename = "pnat-output";
+ arcname = "ip4-output";
+ break;
+ default:
+ return clib_error_return(0, "Unknown attachment point %u %u",
+ sw_if_index, attachment);
+ }
+
+ if (!interface->enabled[attachment]) {
+ if (vnet_feature_enable_disable(arcname, nodename, sw_if_index, 1, 0,
+ 0) != 0)
+ return clib_error_return(0, "PNAT feature enable failed on %u",
+ sw_if_index);
+
+ if (input) {
+ /* TODO: Make shallow virtual reassembly configurable */
+ ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 1);
+ } else {
+ ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index, 1);
+ }
+
+ interface->lookup_mask[attachment] = mask;
+ interface->lookup_mask_fast[attachment] = pnat_mask2fast(mask);
+ interface->enabled[attachment] = true;
+
+ } else {
+ pnat_mask_t current_mask = interface->lookup_mask[attachment];
+ if (current_mask != mask) {
+ return clib_error_return(0,
+ "PNAT lookup mask must be consistent per "
+ "interface/direction %u",
+ sw_if_index);
+ }
+ }
+
+ interface->refcount++;
+
+ return 0;
+}
+
+/*
+ * Delete interface object when no rules reference the interface.
+ */
+static int pnat_disable_interface(u32 sw_if_index,
+ pnat_attachment_point_t attachment) {
+ pnat_main_t *pm = &pnat_main;
+ pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
+
+ if (!interface)
+ return 0;
+ if (interface->refcount == 0)
+ return 0;
+
+ if (interface->enabled[attachment] && attachment == PNAT_IP4_INPUT) {
+ ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 0);
+ if (vnet_feature_enable_disable("ip4-unicast", "pnat-input",
+ sw_if_index, 0, 0, 0) != 0)
+ return -1;
+ }
+ if (interface->enabled[attachment] && attachment == PNAT_IP4_OUTPUT) {
+ ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index, 0);
+ if (vnet_feature_enable_disable("ip4-output", "pnat-output",
+ sw_if_index, 0, 0, 0) != 0)
+ return -1;
+ }
+
+ interface->lookup_mask[attachment] = 0;
+ interface->enabled[attachment] = false;
+
+ interface->refcount--;
+ if (interface->refcount == 0) {
+ pm->interface_by_sw_if_index[sw_if_index] = ~0;
+ pool_put(pm->interfaces, interface);
+ }
+ return 0;
+}
+
+/*
+ * From a 5-tuple (with mask) calculate the key used in the flow cache lookup.
+ */
+static inline void pnat_calc_key_from_5tuple(u32 sw_if_index,
+ pnat_attachment_point_t attachment,
+ pnat_5tuple_t *match,
+ clib_bihash_kv_16_8_t *kv) {
+ pnat_mask_fast_t mask = pnat_mask2fast(match->mask);
+ ip4_address_t src, dst;
+ clib_memcpy(&src, &match->src, 4);
+ clib_memcpy(&dst, &match->dst, 4);
+ pnat_calc_key(sw_if_index, attachment, src, dst, match->proto,
+ htons(match->sport), htons(match->dport), mask, kv);
+}
+
+/*
+ * Map between the 5-tuple mask and the instruction set of the rewrite node.
+ */
+pnat_instructions_t pnat_instructions_from_mask(pnat_mask_t m) {
+ pnat_instructions_t i = 0;
+
+ if (m & PNAT_SA)
+ i |= PNAT_INSTR_SOURCE_ADDRESS;
+ if (m & PNAT_DA)
+ i |= PNAT_INSTR_DESTINATION_ADDRESS;
+ if (m & PNAT_SPORT)
+ i |= PNAT_INSTR_SOURCE_PORT;
+ if (m & PNAT_DPORT)
+ i |= PNAT_INSTR_DESTINATION_PORT;
+ return i;
+}
+
+/*
+ * "Init" the PNAT datastructures. Called upon first creation of a PNAT rule.
+ * TODO: Make number of buckets configurable.
+ */
+static void pnat_enable(void) {
+ pnat_main_t *pm = &pnat_main;
+ if (pm->enabled)
+ return;
+
+ /* Create new flow cache table */
+ clib_bihash_init_16_8(&pm->flowhash, "PNAT flow hash",
+ PNAT_FLOW_HASH_BUCKETS, 0);
+
+ pm->enabled = true;
+}
+static void pnat_disable(void) {
+ pnat_main_t *pm = &pnat_main;
+
+ if (!pm->enabled)
+ return;
+ if (pool_elts(pm->translations))
+ return;
+
+ /* Delete flow cache table */
+ clib_bihash_free_16_8(&pm->flowhash);
+
+ pm->enabled = false;
+}
+
+/*
+ * Ensure that a new rule lookup mask matches what's installed on interface
+ */
+static int pnat_interface_check_mask(u32 sw_if_index,
+ pnat_attachment_point_t attachment,
+ pnat_mask_t mask) {
+ pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
+ if (!interface)
+ return 0;
+ if (!interface->enabled[attachment])
+ return 0;
+ if (interface->lookup_mask[attachment] != mask)
+ return -1;
+
+ return 0;
+}
+
+int pnat_binding_add(pnat_5tuple_t *match, pnat_5tuple_t *rewrite, u32 *index) {
+ pnat_main_t *pm = &pnat_main;
+
+ *index = -1;
+
+ /* If we aren't matching or rewriting, why are we here? */
+ if (match->mask == 0 || rewrite->mask == 0)
+ return -1;
+
+ /* Check if protocol is set if ports are set */
+ if ((match->dport || match->sport) &&
+ (match->proto != IP_API_PROTO_UDP && match->proto != IP_API_PROTO_TCP))
+ return -2;
+
+ /* Create pool entry */
+ pnat_translation_t *t;
+ pool_get_zero(pm->translations, t);
+ memcpy(&t->post_da, &rewrite->dst, 4);
+ memcpy(&t->post_sa, &rewrite->src, 4);
+ t->post_sp = rewrite->sport;
+ t->post_dp = rewrite->dport;
+ t->instructions = pnat_instructions_from_mask(rewrite->mask);
+
+ /* These are only used for show commands and trace */
+ t->match = *match;
+
+ /* Rewrite of protocol is not supported, ignore. */
+ t->rewrite = *rewrite;
+ t->rewrite.proto = 0;
+
+ *index = t - pm->translations;
+
+ return 0;
+}
+u32 pnat_flow_lookup(u32 sw_if_index, pnat_attachment_point_t attachment,
+ pnat_5tuple_t *match) {
+ pnat_main_t *pm = &pnat_main;
+ clib_bihash_kv_16_8_t kv, value;
+ pnat_calc_key_from_5tuple(sw_if_index, attachment, match, &kv);
+ if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) {
+ return value.value;
+ }
+ return ~0;
+}
+
+int pnat_binding_attach(u32 sw_if_index, pnat_attachment_point_t attachment,
+ u32 binding_index) {
+ pnat_main_t *pm = &pnat_main;
+
+ if (!pm->translations ||
+ pool_is_free_index(pm->translations, binding_index))
+ return -1;
+
+ pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index);
+
+ if (pnat_interface_check_mask(sw_if_index, attachment, t->match.mask) != 0)
+ return -2;
+
+ pnat_enable();
+
+ /* Verify non-duplicate */
+ clib_bihash_kv_16_8_t kv, value;
+ pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv);
+ if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) {
+ return -3;
+ }
+
+ /* Create flow cache */
+ kv.value = binding_index;
+ if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 1)) {
+ pool_put(pm->translations, t);
+ return -4;
+ }
+
+ /* Register interface */
+ pnat_enable_interface(sw_if_index, attachment, t->match.mask);
+
+ return 0;
+}
+
+int pnat_binding_detach(u32 sw_if_index, pnat_attachment_point_t attachment,
+ u32 binding_index) {
+ pnat_main_t *pm = &pnat_main;
+
+ if (!pm->translations ||
+ pool_is_free_index(pm->translations, binding_index))
+ return -1;
+
+ pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index);
+
+ /* Verify non-duplicate */
+ clib_bihash_kv_16_8_t kv;
+ pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv);
+ if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 0)) {
+ return -2;
+ }
+
+ /* Deregister interface */
+ pnat_disable_interface(sw_if_index, attachment);
+
+ pnat_disable();
+
+ return 0;
+}
+
+/*
+ * Delete a translation using the index returned from pnat_add_translation.
+ */
+int pnat_binding_del(u32 index) {
+ pnat_main_t *pm = &pnat_main;
+
+ if (pool_is_free_index(pm->translations, index)) {
+ clib_warning("Binding delete: translation does not exist: %d", index);
+ return -1;
+ }
+
+ pnat_translation_t *t = pool_elt_at_index(pm->translations, index);
+ pool_put(pm->translations, t);
+
+ return 0;
+}
diff --git a/src/plugins/nat/pnat/pnat.h b/src/plugins/nat/pnat/pnat.h
new file mode 100644
index 00000000000..c5869ce3ca6
--- /dev/null
+++ b/src/plugins/nat/pnat/pnat.h
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+#ifndef included_pnat_h
+#define included_pnat_h
+
+#include <stdbool.h>
+#include <vnet/ip/ip4_packet.h>
+#include <vppinfra/bihash_16_8.h>
+
+#define PNAT_FLOW_HASH_BUCKETS 256
+
+/* Definitions from pnat.api */
+#include <pnat/pnat.api_types.h>
+typedef vl_api_pnat_5tuple_t pnat_5tuple_t;
+typedef vl_api_pnat_mask_t pnat_mask_t;
+typedef vl_api_pnat_attachment_point_t pnat_attachment_point_t;
+
+/* Rewrite instructions */
+typedef enum {
+ PNAT_INSTR_NONE = 1 << 0,
+ PNAT_INSTR_SOURCE_ADDRESS = 1 << 1,
+ PNAT_INSTR_SOURCE_PORT = 1 << 2,
+ PNAT_INSTR_DESTINATION_ADDRESS = 1 << 3,
+ PNAT_INSTR_DESTINATION_PORT = 1 << 4,
+} pnat_instructions_t;
+
+typedef struct {
+ u64 as_u64[2];
+} pnat_mask_fast_t;
+
+/* Session cache entries */
+typedef struct {
+ /* What to translate to */
+ pnat_instructions_t instructions;
+
+ /* Stored in network byte order */
+ ip4_address_t post_sa;
+ ip4_address_t post_da;
+ u16 post_sp;
+ u16 post_dp;
+
+ /* Used for trace/show commands */
+ pnat_5tuple_t match;
+ pnat_5tuple_t rewrite;
+} pnat_translation_t;
+
+/* Interface object */
+typedef struct {
+ u32 sw_if_index;
+ pnat_mask_t lookup_mask[PNAT_ATTACHMENT_POINT_MAX];
+ pnat_mask_fast_t lookup_mask_fast[PNAT_ATTACHMENT_POINT_MAX];
+
+ /* Feature chain enabled on interface */
+ bool enabled[PNAT_ATTACHMENT_POINT_MAX];
+
+ u32 refcount;
+} pnat_interface_t;
+
+/* Globals */
+typedef struct {
+ bool enabled;
+
+ clib_bihash_16_8_t flowhash; /* Bi-directional */
+
+ /* Interface pool */
+ pnat_interface_t *interfaces;
+ u32 *interface_by_sw_if_index;
+
+ /* Translations pool */
+ pnat_translation_t *translations;
+
+ u16 msg_id_base;
+} pnat_main_t;
+extern pnat_main_t pnat_main;
+
+pnat_interface_t *pnat_interface_by_sw_if_index(u32 sw_if_index);
+
+/* Packet trace information */
+typedef struct {
+ u32 pool_index;
+ pnat_5tuple_t match;
+ pnat_5tuple_t rewrite;
+} pnat_trace_t;
+
+int pnat_binding_add(pnat_5tuple_t *match, pnat_5tuple_t *rewrite,
+ u32 *binding_index);
+int pnat_binding_del(u32 binding_index);
+int pnat_binding_attach(u32 sw_if_index, pnat_attachment_point_t attachment,
+ u32 binding_index);
+int pnat_binding_detach(u32 sw_if_index, pnat_attachment_point_t attachment,
+ u32 binding_index);
+u32 pnat_flow_lookup(u32 sw_if_index, pnat_attachment_point_t attachment,
+ pnat_5tuple_t *match);
+
+static inline void
+pnat_calc_key(u32 sw_if_index, pnat_attachment_point_t attachment,
+ ip4_address_t src, ip4_address_t dst, u8 protocol, u16 sport,
+ u16 dport, pnat_mask_fast_t mask, clib_bihash_kv_16_8_t *kv) {
+ kv->key[0] = kv->key[1] = 0;
+ kv->key[0] = (u64)src.as_u32 << 32 | dst.as_u32;
+ kv->key[0] &= mask.as_u64[0];
+ kv->key[1] |=
+ (u64)protocol << 56 | (u64)sw_if_index << 36 | (u64)attachment << 32;
+ kv->key[1] |= sport << 16 | dport;
+ kv->key[1] &= mask.as_u64[1];
+}
+
+#endif
diff --git a/src/plugins/nat/pnat/pnat.md b/src/plugins/nat/pnat/pnat.md
new file mode 100644
index 00000000000..a7c33766b90
--- /dev/null
+++ b/src/plugins/nat/pnat/pnat.md
@@ -0,0 +1,37 @@
+# PNAT: 1:1 match and rewrite programmable NAT
+
+PNAT is a stateless statically configured, match and rewrite plugin.
+It uses a set of match and rewrite rules that are applied on the IP
+input and output feature paths. A PNAT rule is unidirectional.
+
+The match is done using up to a 6-tuple; IP source and destination address,
+IP protocol, transport layer source and destination ports, and FIB table / interface index.
+
+While multiple match/rewrite rules can be applied to an interface (per direction), the match
+pattern must be the same across all rules on that interface/direction.
+
+If required in the future, matching could be done using the general classifier, allowing matching
+on any protocol field, as well having an ordered set of match patterns.
+
+If the packet does not match, it will by default be passed to the next graph node in the feature chain.
+If desired a different miss behaviour could be implemented, e.g. similarly to dynamic NAT, the packet punted to a slow path.
+
+## Rewrite instructions
+
+``` c
+typedef enum {
+ PNAT_INSTR_NONE = 1 << 0,
+ PNAT_INSTR_SOURCE_ADDRESS = 1 << 1,
+ PNAT_INSTR_SOURCE_PORT = 1 << 2,
+ PNAT_INSTR_DESTINATION_ADDRESS = 1 << 3,
+ PNAT_INSTR_DESTINATION_PORT = 1 << 4,
+} pnat_instructions_t;
+```
+
+These are the supported rewrite instructions.
+The IP checksum and the TCP/UDP checksum are incrementally updated as required.
+
+There are only a few "sanity checks" on the rewrites. For example, the rewrite in the outbound direction
+is applied on the ip-output feature chain. If one were to rewrite the IP destination address, the routing
+decision and determination of the next-hop has already been done, and the packet would still be forwarded
+to the original next-hop.
diff --git a/src/plugins/nat/pnat/pnat_api.c b/src/plugins/nat/pnat/pnat_api.c
new file mode 100644
index 00000000000..dad658c3e9e
--- /dev/null
+++ b/src/plugins/nat/pnat/pnat_api.c
@@ -0,0 +1,207 @@
+/*
+ * 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 "pnat.h"
+#include <pnat/pnat.api_enum.h>
+#include <pnat/pnat.api_types.h>
+#include <vlibmemory/api.h>
+#include <vnet/fib/fib_table.h>
+#include <vnet/ip/ip.h>
+#include <vnet/ip/ip_types_api.h>
+#include <vnet/ip/reass/ip4_sv_reass.h>
+#include <vnet/ip/reass/ip6_full_reass.h>
+#include <vnet/ip/reass/ip6_sv_reass.h>
+
+/*
+ * This file contains the API handlers for the pnat.api
+ */
+
+#define REPLY_MSG_ID_BASE pm->msg_id_base
+#include <vlibapi/api_helper_macros.h>
+
+static void vl_api_pnat_binding_add_t_handler(vl_api_pnat_binding_add_t *mp) {
+ pnat_main_t *pm = &pnat_main;
+ vl_api_pnat_binding_add_reply_t *rmp;
+ u32 binding_index;
+ int rv = pnat_binding_add(&mp->match, &mp->rewrite, &binding_index);
+ REPLY_MACRO2_END(VL_API_PNAT_BINDING_ADD_REPLY,
+ ({ rmp->binding_index = binding_index; }));
+}
+
+static void
+vl_api_pnat_binding_attach_t_handler(vl_api_pnat_binding_attach_t *mp) {
+ pnat_main_t *pm = &pnat_main;
+ vl_api_pnat_binding_attach_reply_t *rmp;
+ int rv;
+
+ /* Ensure that the interface exists */
+ if (!vnet_sw_if_index_is_api_valid(mp->sw_if_index)) {
+ rv = VNET_API_ERROR_INVALID_SW_IF_INDEX;
+ goto bad_sw_if_index;
+ }
+
+ rv =
+ pnat_binding_attach(mp->sw_if_index, mp->attachment, mp->binding_index);
+
+bad_sw_if_index:
+ REPLY_MACRO_END(VL_API_PNAT_BINDING_ATTACH_REPLY);
+}
+
+static void
+vl_api_pnat_binding_detach_t_handler(vl_api_pnat_binding_detach_t *mp) {
+ pnat_main_t *pm = &pnat_main;
+ vl_api_pnat_binding_detach_reply_t *rmp;
+ int rv;
+
+ /* Ensure that the interface exists */
+ if (!vnet_sw_if_index_is_api_valid(mp->sw_if_index)) {
+ rv = VNET_API_ERROR_INVALID_SW_IF_INDEX;
+ goto bad_sw_if_index;
+ }
+
+ rv =
+ pnat_binding_detach(mp->sw_if_index, mp->attachment, mp->binding_index);
+
+bad_sw_if_index:
+ REPLY_MACRO_END(VL_API_PNAT_BINDING_DETACH_REPLY);
+}
+
+static void vl_api_pnat_binding_del_t_handler(vl_api_pnat_binding_del_t *mp) {
+ pnat_main_t *pm = &pnat_main;
+ vl_api_pnat_binding_del_reply_t *rmp;
+ int rv = pnat_binding_del(mp->binding_index);
+ REPLY_MACRO_END(VL_API_PNAT_BINDING_DEL_REPLY);
+}
+
+/*
+ * Workaround for a bug in vppapigen that doesn't register the endian handler
+ * for _details messages. When that's fixed it should be possible to use
+ * REPLY_MACRO_DETAILS4_END and not have to care about endian-ness in the
+ * handler itself.
+ */
+#define vl_endianfun
+#include <pnat/pnat.api.h>
+#undef vl_endianfun
+static void send_bindings_details(u32 index, vl_api_registration_t *rp,
+ u32 context) {
+ pnat_main_t *pm = &pnat_main;
+ vl_api_pnat_bindings_details_t *rmp;
+ pnat_translation_t *t = pool_elt_at_index(pm->translations, index);
+
+ /* Make sure every field is initiated (or don't skip the clib_memset()) */
+
+ REPLY_MACRO_DETAILS4(VL_API_PNAT_BINDINGS_DETAILS, rp, context, ({
+ rmp->match = t->match;
+ rmp->rewrite = t->rewrite;
+
+ /* Endian hack until apigen registers _details
+ * endian functions */
+ vl_api_pnat_bindings_details_t_endian(rmp);
+ rmp->_vl_msg_id = htons(rmp->_vl_msg_id);
+ rmp->context = htonl(rmp->context);
+ }));
+}
+
+static void vl_api_pnat_bindings_get_t_handler(vl_api_pnat_bindings_get_t *mp) {
+ pnat_main_t *pm = &pnat_main;
+ vl_api_pnat_bindings_get_reply_t *rmp;
+
+ i32 rv = 0;
+
+ if (pool_elts(pm->translations) == 0) {
+ REPLY_MACRO(VL_API_PNAT_BINDINGS_GET_REPLY);
+ return;
+ }
+
+ /*
+ * "cursor" comes from the get call, and allows client to continue a dump
+ */
+ REPLY_AND_DETAILS_MACRO(VL_API_PNAT_BINDINGS_GET_REPLY, pm->translations, ({
+ send_bindings_details(cursor, rp, mp->context);
+ }));
+}
+
+static void send_interfaces_details(u32 index, vl_api_registration_t *rp,
+ u32 context) {
+ pnat_main_t *pm = &pnat_main;
+ vl_api_pnat_interfaces_details_t *rmp;
+ pnat_interface_t *i = pool_elt_at_index(pm->interfaces, index);
+
+ /* Make sure every field is initiated (or don't skip the clib_memset()) */
+
+ REPLY_MACRO_DETAILS4(
+ VL_API_PNAT_INTERFACES_DETAILS, rp, context, ({
+ rmp->sw_if_index = i->sw_if_index;
+ clib_memcpy(rmp->enabled, i->enabled, PNAT_ATTACHMENT_POINT_MAX);
+ clib_memcpy(rmp->lookup_mask, i->lookup_mask,
+ sizeof(i->lookup_mask) * PNAT_ATTACHMENT_POINT_MAX);
+
+ /* Endian hack until apigen registers _details
+ * endian functions */
+ vl_api_pnat_interfaces_details_t_endian(rmp);
+ rmp->_vl_msg_id = htons(rmp->_vl_msg_id);
+ rmp->context = htonl(rmp->context);
+ }));
+}
+
+static void
+vl_api_pnat_interfaces_get_t_handler(vl_api_pnat_interfaces_get_t *mp) {
+ pnat_main_t *pm = &pnat_main;
+ vl_api_pnat_interfaces_get_reply_t *rmp;
+
+ i32 rv = 0;
+
+ if (pool_elts(pm->interfaces) == 0) {
+ REPLY_MACRO(VL_API_PNAT_INTERFACES_GET_REPLY);
+ return;
+ }
+
+ /*
+ * "cursor" comes from the get call, and allows client to continue a dump
+ */
+ REPLY_AND_DETAILS_MACRO(
+ VL_API_PNAT_INTERFACES_GET_REPLY, pm->interfaces,
+ ({ send_interfaces_details(cursor, rp, mp->context); }));
+}
+
+/* API definitions */
+#include <vnet/format_fns.h>
+#include <pnat/pnat.api.c>
+
+/* Set up the API message handling tables */
+clib_error_t *pnat_plugin_api_hookup(vlib_main_t *vm) {
+ pnat_main_t *pm = &pnat_main;
+
+ pm->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 = "0.0.1",
+ .description = "Policy 1:1 NAT",
+};
+
+clib_error_t *pnat_init(vlib_main_t *vm) {
+ pnat_main_t *pm = &pnat_main;
+ memset(pm, 0, sizeof(*pm));
+
+ return pnat_plugin_api_hookup(vm);
+}
+
+VLIB_INIT_FUNCTION(pnat_init);
diff --git a/src/plugins/nat/pnat/pnat_cli.c b/src/plugins/nat/pnat/pnat_cli.c
new file mode 100644
index 00000000000..2d389013c08
--- /dev/null
+++ b/src/plugins/nat/pnat/pnat_cli.c
@@ -0,0 +1,274 @@
+/*
+ * 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 <stdbool.h>
+#include <vlib/vlib.h>
+#include <vnet/feature/feature.h>
+#include <vnet/ip/ip.h>
+#include <vnet/ip/ip4.h>
+#include <vnet/ip/ip4_packet.h>
+#include <vppinfra/clib_error.h>
+#include <vppinfra/pool.h>
+#include "pnat.h"
+
+/*
+ * This file contains the handlers for the (unsupported) VPP debug CLI.
+ */
+u8 *format_pnat_5tuple(u8 *s, va_list *args) {
+ pnat_5tuple_t *t = va_arg(*args, pnat_5tuple_t *);
+ s = format(s, "{");
+ if (t->mask & PNAT_SA)
+ s = format(s, "%U", format_ip4_address, &t->src);
+ else
+ s = format(s, "*");
+ if (t->mask & PNAT_SPORT)
+ s = format(s, ":%u,", t->sport);
+ else
+ s = format(s, ":*,");
+ if (t->proto > 0)
+ s = format(s, "%U,", format_ip_protocol, t->proto);
+ else
+ s = format(s, "*,");
+ if (t->mask & PNAT_DA)
+ s = format(s, "%U", format_ip4_address, &t->dst);
+ else
+ s = format(s, "*");
+ if (t->mask & PNAT_DPORT)
+ s = format(s, ":%u", t->dport);
+ else
+ s = format(s, ":*");
+ s = format(s, "}");
+ return s;
+}
+
+u8 *format_pnat_translation(u8 *s, va_list *args) {
+ u32 index = va_arg(*args, u32);
+ pnat_translation_t *t = va_arg(*args, pnat_translation_t *);
+ s = format(s, "[%d] match: %U rewrite: %U", index, format_pnat_5tuple,
+ &t->match, format_pnat_5tuple, &t->rewrite);
+ return s;
+}
+
+static u8 *format_pnat_mask(u8 *s, va_list *args) {
+ pnat_mask_t t = va_arg(*args, pnat_mask_t);
+ if (t & PNAT_SA)
+ s = format(s, "SA ");
+ if (t & PNAT_SPORT)
+ s = format(s, "SP ");
+ if (t & PNAT_DA)
+ s = format(s, "DA ");
+ if (t & PNAT_DPORT)
+ s = format(s, "DP");
+ return s;
+}
+
+static u8 *format_pnat_interface(u8 *s, va_list *args) {
+ pnat_interface_t *interface = va_arg(*args, pnat_interface_t *);
+ s = format(s, "sw_if_index: %d", interface->sw_if_index);
+ if (interface->enabled[PNAT_IP4_INPUT]) {
+ s = format(s, " input mask: %U", format_pnat_mask,
+ interface->lookup_mask[PNAT_IP4_INPUT]);
+ }
+ if (interface->enabled[PNAT_IP4_OUTPUT]) {
+ s = format(s, " output mask: %U", format_pnat_mask,
+ interface->lookup_mask[PNAT_IP4_OUTPUT]);
+ }
+ return s;
+}
+
+uword unformat_pnat_5tuple(unformat_input_t *input, va_list *args) {
+ pnat_5tuple_t *t = va_arg(*args, pnat_5tuple_t *);
+ u32 dport, sport;
+ while (1) {
+ if (unformat(input, "src %U", unformat_ip4_address, &t->src))
+ t->mask |= PNAT_SA;
+ else if (unformat(input, "dst %U", unformat_ip4_address, &t->dst))
+ t->mask |= PNAT_DA;
+ else if (unformat(input, "sport %d", &sport)) {
+ if (sport < 0 || sport > 65535)
+ return 0;
+ t->mask |= PNAT_SPORT;
+ t->sport = sport;
+ } else if (unformat(input, "dport %d", &dport)) {
+ if (dport < 0 || dport > 65535)
+ return 0;
+ t->mask |= PNAT_DPORT;
+ t->dport = dport;
+ } else if (unformat(input, "proto %U", unformat_ip_protocol, &t->proto))
+ ;
+ else
+ break;
+ }
+ return 1;
+}
+
+static clib_error_t *set_pnat_translation_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 in = false, out = false;
+ bool match_set = false, rewrite_set = false;
+ bool add = true;
+ u32 sw_if_index = ~0;
+ pnat_5tuple_t match = {0};
+ pnat_5tuple_t rewrite = {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, "match %U", unformat_pnat_5tuple, &match))
+ match_set = true;
+ else if (unformat(line_input, "rewrite %U", unformat_pnat_5tuple,
+ &rewrite))
+ rewrite_set = true;
+ else if (unformat(line_input, "interface %U",
+ unformat_vnet_sw_interface, vnet_get_main(),
+ &sw_if_index))
+ ;
+ else if (unformat(line_input, "in")) {
+ in = true;
+ } else if (unformat(line_input, "out")) {
+ out = true;
+ } 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 ((in && out) || (!in && !out)) {
+ error = clib_error_return(0, "in or out is required `%U'",
+ format_unformat_error, line_input);
+ goto done;
+ }
+ if (!match_set) {
+ error = clib_error_return(0, "missing parameter: match `%U'",
+ format_unformat_error, line_input);
+ goto done;
+ }
+ if (!rewrite_set) {
+ error = clib_error_return(0, "missing parameter: rewrite `%U'",
+ format_unformat_error, line_input);
+ goto done;
+ }
+
+ if ((match.dport || match.sport) &&
+ (match.proto != 17 && match.proto != 6)) {
+ error = clib_error_return(0, "missing protocol (TCP|UDP): match `%U'",
+ format_unformat_error, line_input);
+ goto done;
+ }
+ pnat_attachment_point_t attachment = in ? PNAT_IP4_INPUT : PNAT_IP4_OUTPUT;
+
+ if (add) {
+ u32 binding_index;
+ int rv = pnat_binding_add(&match, &rewrite, &binding_index);
+ if (rv) {
+ error = clib_error_return(0, "Adding binding failed %d", rv);
+ goto done;
+ }
+ rv = pnat_binding_attach(sw_if_index, attachment, binding_index);
+ if (rv) {
+ pnat_binding_del(binding_index);
+ error = clib_error_return(
+ 0, "Attaching binding to interface failed %d", rv);
+ goto done;
+ }
+ } else {
+ /* Lookup binding and lookup interface if both exists proceed with
+ * delete */
+ u32 binding_index = pnat_flow_lookup(sw_if_index, attachment, &match);
+ if (binding_index == ~0) {
+ error = clib_error_return(0, "Binding does not exist");
+ goto done;
+ }
+ pnat_attachment_point_t attachment =
+ in ? PNAT_IP4_INPUT : PNAT_IP4_OUTPUT;
+ int rv = pnat_binding_detach(sw_if_index, attachment, binding_index);
+ if (rv) {
+ error = clib_error_return(0, "Detaching binding failed %d %d",
+ binding_index, rv);
+ goto done;
+ }
+ rv = pnat_binding_del(binding_index);
+ if (rv) {
+ error = clib_error_return(0, "Deleting translation failed %d %d",
+ binding_index, rv);
+ goto done;
+ }
+ }
+
+done:
+ unformat_free(line_input);
+
+ return error;
+}
+
+VLIB_CLI_COMMAND(set_pnat_translation_command, static) = {
+ .path = "set pnat translation",
+ .short_help = "set pnat translation interface <name> match <5-tuple> "
+ "rewrite <5-tuple> {in|out} [del]",
+ .function = set_pnat_translation_command_fn,
+};
+
+static clib_error_t *
+show_pnat_translations_command_fn(vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd) {
+ pnat_main_t *pm = &pnat_main;
+ pnat_translation_t *s;
+ clib_error_t *error = 0;
+
+ /* Get a line of input. */
+ pool_foreach(s, pm->translations) {
+ vlib_cli_output(vm, "%U", format_pnat_translation, s - pm->translations,
+ s);
+ }
+ return error;
+}
+
+VLIB_CLI_COMMAND(show_pnat_translations_command, static) = {
+ .path = "show pnat translations",
+ .short_help = "show pnat translations",
+ .function = show_pnat_translations_command_fn,
+};
+
+static clib_error_t *show_pnat_interfaces_command_fn(vlib_main_t *vm,
+ unformat_input_t *input,
+ vlib_cli_command_t *cmd) {
+ pnat_main_t *pm = &pnat_main;
+ pnat_interface_t *interface;
+ clib_error_t *error = 0;
+
+ /* Get a line of input. */
+ pool_foreach(interface, pm->interfaces) {
+ vlib_cli_output(vm, "%U", format_pnat_interface, interface);
+ }
+ return error;
+}
+
+VLIB_CLI_COMMAND(show_pnat_interfaces_command, static) = {
+ .path = "show pnat interfaces",
+ .short_help = "show pnat interfaces",
+ .function = show_pnat_interfaces_command_fn,
+};
diff --git a/src/plugins/nat/pnat/pnat_node.c b/src/plugins/nat/pnat/pnat_node.c
new file mode 100644
index 00000000000..0209b49b0aa
--- /dev/null
+++ b/src/plugins/nat/pnat/pnat_node.c
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+/*
+ * Policy NAT.
+ * Match packet against rule in a hash and translate according to given
+ * instructions. Rules are kept in a flow-cache bihash. Instructions in a pool
+ * of translation entries.
+ *
+ * All rules for a given interface/direction must use the same lookup pattern.
+ * E.g. SA+SP.
+ *
+ * A dynamic NAT would punt to slow path on a miss in the flow cache, in this
+ * case the miss behaviour is configurable. Default behaviour is pass packet
+ * along unchanged.
+ *
+ * The data structures are shared and assuming that updates to the tables are
+ * rare. Data-structures are protected depending on the API/CLI barriers.
+ */
+
+#include <stdbool.h>
+#include <vlib/vlib.h>
+#include <pnat/pnat.api_enum.h> /* For error counters */
+#include "pnat_node.h" /* Graph nodes */
+
+VLIB_NODE_FN(pnat_input_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) {
+ return pnat_node_inline(vm, node, frame, PNAT_IP4_INPUT, VLIB_RX);
+}
+VLIB_NODE_FN(pnat_output_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) {
+ return pnat_node_inline(vm, node, frame, PNAT_IP4_OUTPUT, VLIB_TX);
+}
+
+VLIB_REGISTER_NODE(pnat_input_node) = {
+ .name = "pnat-input",
+ .vector_size = sizeof(u32),
+ .format_trace = format_pnat_trace,
+ .type = VLIB_NODE_TYPE_INTERNAL,
+ .n_errors = PNAT_N_ERROR,
+ .error_counters = pnat_error_counters,
+ .n_next_nodes = PNAT_N_NEXT,
+ .next_nodes =
+ {
+ [PNAT_NEXT_DROP] = "error-drop",
+ },
+};
+
+VLIB_REGISTER_NODE(pnat_output_node) = {
+ .name = "pnat-output",
+ .vector_size = sizeof(u32),
+ .format_trace = format_pnat_trace,
+ .type = VLIB_NODE_TYPE_INTERNAL,
+ .n_errors = PNAT_N_ERROR,
+ .error_counters = pnat_error_counters,
+ .sibling_of = "pnat-input",
+};
+
+/* Hook up features */
+VNET_FEATURE_INIT(pnat_input, static) = {
+ .arc_name = "ip4-unicast",
+ .node_name = "pnat-input",
+ .runs_after = VNET_FEATURES("acl-plugin-in-ip4-fa",
+ "ip4-sv-reassembly-feature"),
+};
+VNET_FEATURE_INIT(pnat_output, static) = {
+ .arc_name = "ip4-output",
+ .node_name = "pnat-output",
+ .runs_after = VNET_FEATURES("acl-plugin-out-ip4-fa",
+ "ip4-sv-reassembly-output-feature"),
+};
diff --git a/src/plugins/nat/pnat/pnat_node.h b/src/plugins/nat/pnat/pnat_node.h
new file mode 100644
index 00000000000..3f2235509fa
--- /dev/null
+++ b/src/plugins/nat/pnat/pnat_node.h
@@ -0,0 +1,196 @@
+/*
+ * 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.
+ */
+
+#ifndef included_pnat_node_h
+#define included_pnat_node_h
+
+#include "pnat.h"
+#include <pnat/pnat.api_enum.h>
+#include <vnet/feature/feature.h>
+#include <vnet/udp/udp_packet.h>
+#include <vnet/ip/format.h>
+
+/* PNAT next-nodes */
+typedef enum { PNAT_NEXT_DROP, PNAT_N_NEXT } pnat_next_t;
+
+// u8 *format_pnat_key(u8 *s, va_list *args);
+u8 *format_pnat_5tuple(u8 *s, va_list *args);
+static inline u8 *format_pnat_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 *);
+ pnat_trace_t *t = va_arg(*args, pnat_trace_t *);
+
+ s = format(s, "pnat: index %d\n", t->pool_index);
+ if (t->pool_index != ~0) {
+ s = format(s, " match: %U\n", format_pnat_5tuple, &t->match);
+ s = format(s, " rewrite: %U", format_pnat_5tuple, &t->rewrite);
+ }
+ return s;
+}
+
+/*
+ * Given a packet and rewrite instructions from a translation modify packet.
+ */
+static u32 pnat_rewrite_ip4(u32 pool_index, ip4_header_t *ip) {
+ pnat_main_t *pm = &pnat_main;
+ if (pool_is_free_index(pm->translations, pool_index))
+ return PNAT_ERROR_REWRITE;
+ pnat_translation_t *t = pool_elt_at_index(pm->translations, pool_index);
+
+ ip_csum_t csumd = 0;
+
+ if (t->instructions & PNAT_INSTR_DESTINATION_ADDRESS) {
+ csumd = ip_csum_sub_even(csumd, ip->dst_address.as_u32);
+ csumd = ip_csum_add_even(csumd, t->post_da.as_u32);
+ ip->dst_address = t->post_da;
+ }
+ if (t->instructions & PNAT_INSTR_SOURCE_ADDRESS) {
+ csumd = ip_csum_sub_even(csumd, ip->src_address.as_u32);
+ csumd = ip_csum_add_even(csumd, t->post_sa.as_u32);
+ ip->src_address = t->post_sa;
+ }
+
+ ip_csum_t csum = ip->checksum;
+ csum = ip_csum_sub_even(csum, csumd);
+ ip->checksum = ip_csum_fold(csum);
+ ASSERT(ip->checksum == ip4_header_checksum(ip));
+
+ /* L4 ports */
+ if (ip->protocol == IP_PROTOCOL_TCP) {
+ tcp_header_t *tcp = ip4_next_header(ip);
+ ip_csum_t l4csum = tcp->checksum;
+ if (t->instructions & PNAT_INSTR_DESTINATION_PORT) {
+ l4csum = ip_csum_sub_even(l4csum, tcp->dst_port);
+ l4csum = ip_csum_add_even(l4csum, clib_net_to_host_u16(t->post_dp));
+ tcp->dst_port = clib_net_to_host_u16(t->post_dp);
+ }
+ if (t->instructions & PNAT_INSTR_SOURCE_PORT) {
+ l4csum = ip_csum_sub_even(l4csum, tcp->src_port);
+ l4csum = ip_csum_add_even(l4csum, clib_net_to_host_u16(t->post_sp));
+ tcp->src_port = clib_net_to_host_u16(t->post_sp);
+ }
+ l4csum = ip_csum_sub_even(l4csum, csumd);
+ tcp->checksum = ip_csum_fold(l4csum);
+ } else if (ip->protocol == IP_PROTOCOL_UDP) {
+ udp_header_t *udp = ip4_next_header(ip);
+ ip_csum_t l4csum = udp->checksum;
+ if (t->instructions & PNAT_INSTR_DESTINATION_PORT) {
+ l4csum = ip_csum_sub_even(l4csum, udp->dst_port);
+ l4csum = ip_csum_add_even(l4csum, clib_net_to_host_u16(t->post_dp));
+ udp->dst_port = clib_net_to_host_u16(t->post_dp);
+ }
+ if (t->instructions & PNAT_INSTR_SOURCE_PORT) {
+ l4csum = ip_csum_sub_even(l4csum, udp->src_port);
+ l4csum = ip_csum_add_even(l4csum, clib_net_to_host_u16(t->post_sp));
+ udp->src_port = clib_net_to_host_u16(t->post_sp);
+ }
+ if (udp->checksum) {
+ l4csum = ip_csum_sub_even(l4csum, csumd);
+ udp->checksum = ip_csum_fold(l4csum);
+ }
+ }
+ return PNAT_ERROR_NONE;
+}
+
+/*
+ * 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)
+ */
+static_always_inline uword pnat_node_inline(vlib_main_t *vm,
+ vlib_node_runtime_t *node,
+ vlib_frame_t *frame,
+ pnat_attachment_point_t attachment,
+ int dir) {
+ pnat_main_t *pm = &pnat_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;
+ clib_bihash_kv_16_8_t kv, value;
+ ip4_header_t *ip0;
+
+ from = vlib_frame_vector_args(frame);
+ n_left_from = frame->n_vectors;
+ vlib_get_buffers(vm, from, b, n_left_from);
+ pnat_interface_t *interface;
+
+ /* Stage 1: build vector of flow hash (based on lookup mask) */
+ while (n_left_from > 0) {
+ u32 sw_if_index0 = vnet_buffer(b[0])->sw_if_index[dir];
+ u16 sport0 = vnet_buffer(b[0])->ip.reass.l4_src_port;
+ u16 dport0 = vnet_buffer(b[0])->ip.reass.l4_dst_port;
+ u32 iph_offset = vnet_buffer(b[0])->ip.reass.save_rewrite_length;
+ ip0 = (ip4_header_t *)(vlib_buffer_get_current(b[0]) + iph_offset);
+ interface = pnat_interface_by_sw_if_index(sw_if_index0);
+ ASSERT(interface);
+ pnat_mask_fast_t mask = interface->lookup_mask_fast[attachment];
+ pnat_calc_key(sw_if_index0, attachment, ip0->src_address,
+ ip0->dst_address, ip0->protocol, sport0, dport0, mask,
+ &kv);
+ /* By default pass packet to next node in the feature chain */
+ vnet_feature_next_u16(next, b[0]);
+
+ if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) {
+ /* Cache hit */
+ *pi = value.value;
+ u32 iph_offset = vnet_buffer(b[0])->ip.reass.save_rewrite_length;
+ ip0 = (ip4_header_t *)(vlib_buffer_get_current(b[0]) + iph_offset);
+ u32 errno0 = pnat_rewrite_ip4(value.value, ip0);
+ if (PREDICT_FALSE(errno0)) {
+ next[0] = PNAT_NEXT_DROP;
+ b[0]->error = node->errors[errno0];
+ }
+ } else {
+ /* Cache miss */
+ *pi = ~0;
+ }
+ next += 1;
+
+ /*next: */
+ 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) {
+ pnat_trace_t *t = vlib_add_trace(vm, node, b[0], sizeof(*t));
+ if (*pi != ~0) {
+ if (!pool_is_free_index(pm->translations, *pi)) {
+ pnat_translation_t *tr =
+ pool_elt_at_index(pm->translations, *pi);
+ t->match = tr->match;
+ t->rewrite = tr->rewrite;
+ }
+ }
+ 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;
+}
+#endif
diff --git a/src/plugins/nat/pnat/pnat_test.c b/src/plugins/nat/pnat/pnat_test.c
new file mode 100644
index 00000000000..762b4bdb3f0
--- /dev/null
+++ b/src/plugins/nat/pnat/pnat_test.c
@@ -0,0 +1,504 @@
+/*
+ * 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 <stdbool.h>
+#include <assert.h>
+#include <vlib/vlib.h>
+#include <vnet/feature/feature.h>
+#include <vppinfra/clib_error.h>
+#include <vnet/ip/ip4_packet.h>
+#include <vnet/udp/udp.h>
+#include <vppinfra/bihash_16_8.h>
+#include <vppinfra/bihash_template.c>
+#include <vnet/fib/ip4_fib.h>
+#include "pnat.h"
+#include <pnat/pnat.api_enum.h> /* For error counters */
+#include <arpa/inet.h>
+#include "pnat_test_stubs.h"
+
+/*
+** Buffer management in test setup
+** Allocate buffers return vector of buffer indicies.
+**
+** Setup frame with buffers when calling function.
+** Global vector of all buffers with their indicies?
+** Convert buffer index to pointer?
+*/
+struct buffers {
+ u8 data[2048];
+};
+struct buffers buffers[256];
+struct buffers expected[256];
+u32 *buffers_vector = 0;
+
+static u32 *buffer_init(u32 *vector, int count) {
+ int i;
+ for (i = 0; i < count; i++) {
+ vec_add1(vector, i);
+ }
+ return vector;
+}
+#define PNAT_TEST_DEBUG 0
+
+u32 *results_bi = 0; /* global vector of result buffers */
+u16 *results_next = 0;
+vlib_node_runtime_t *node;
+
+#define log_info(M, ...) \
+ fprintf(stderr, "\033[32;1m[OK] " M "\033[0m\n", ##__VA_ARGS__)
+#define log_error(M, ...) \
+ fprintf(stderr, "\033[31;1m[ERROR] (%s:%d:) " M "\033[0m\n", __FILE__, \
+ __LINE__, ##__VA_ARGS__)
+#define test_assert(A, M, ...) \
+ if (!(A)) { \
+ log_error(M, ##__VA_ARGS__); \
+ assert(A); \
+ } else { \
+ log_info(M, ##__VA_ARGS__); \
+ }
+
+/*
+ * Always return the frame of generated packets
+ */
+#define vlib_frame_vector_args test_vlib_frame_vector_args
+void *test_vlib_frame_vector_args(vlib_frame_t *f) { return buffers_vector; }
+
+/* Synthetic value for vnet_feature_next */
+#define NEXT_PASSTHROUGH 4242
+
+#define vnet_feature_next_u16 test_vnet_feature_next_u16
+void vnet_feature_next_u16(u16 *next0, vlib_buffer_t *b0) {
+ *next0 = NEXT_PASSTHROUGH;
+}
+
+/* Gather output packets */
+#define vlib_buffer_enqueue_to_next test_vlib_buffer_enqueue_to_next
+void test_vlib_buffer_enqueue_to_next(vlib_main_t *vm,
+ vlib_node_runtime_t *node, u32 *buffers,
+ u16 *nexts, uword count) {
+ vec_add(results_next, nexts, count);
+ vec_add(results_bi, buffers, count);
+}
+
+pnat_trace_t trace = {0};
+#define vlib_add_trace test_vlib_add_trace
+void *test_vlib_add_trace(vlib_main_t *vm, vlib_node_runtime_t *r,
+ vlib_buffer_t *b, u32 n_data_bytes) {
+ return &trace;
+}
+
+#define vlib_get_buffers test_vlib_get_buffers
+void test_vlib_get_buffers(vlib_main_t *vm, u32 *bi, vlib_buffer_t **b,
+ int count) {
+ int i;
+ for (i = 0; i < count; i++) {
+ b[i] = (vlib_buffer_t *)&buffers[bi[i]];
+ }
+}
+
+vlib_buffer_t *test_vlib_get_buffer(u32 bi) {
+ return (vlib_buffer_t *)&buffers[bi];
+}
+
+/* Must be included here to allow the above functions to override */
+#include "pnat_node.h"
+
+/*** TESTS ***/
+
+typedef struct {
+ char *src;
+ char *dst;
+ u8 proto;
+ u16 sport;
+ u16 dport;
+} test_5tuple_t;
+
+typedef struct {
+ char *name;
+ test_5tuple_t send;
+ test_5tuple_t expect;
+ u32 expect_next_index;
+} test_t;
+
+test_t tests[] = {
+ {
+ .name = "da rewritten",
+ .send = {"1.1.1.1", "2.2.2.2", 17, 80, 6871},
+ .expect = {"1.1.1.1", "1.2.3.4", 17, 80, 6871},
+ .expect_next_index = NEXT_PASSTHROUGH,
+ },
+ {
+ .name = "unchanged",
+ .send = {"1.1.1.1", "2.2.2.2", 17, 80, 8080},
+ .expect = {"1.1.1.1", "2.2.2.2", 17, 80, 8080},
+ .expect_next_index = NEXT_PASSTHROUGH,
+ },
+ {
+ .name = "tcp da",
+ .send = {"1.1.1.1", "2.2.2.2", 6, 80, 6871},
+ .expect = {"1.1.1.1", "1.2.3.4", 6, 80, 6871},
+ .expect_next_index = NEXT_PASSTHROUGH,
+ },
+ {
+ .name = "tcp da ports",
+ .send = {"1.1.1.1", "2.2.2.2", 6, 80, 6872},
+ .expect = {"1.1.1.1", "1.2.3.4", 6, 53, 8000},
+ .expect_next_index = NEXT_PASSTHROUGH,
+ },
+};
+
+/* Rules */
+typedef struct {
+ test_5tuple_t match;
+ test_5tuple_t rewrite;
+ bool in;
+ u32 index;
+} rule_t;
+
+rule_t rules[] = {
+ {
+ .match = {.dst = "2.2.2.2", .proto = 17, .dport = 6871},
+ .rewrite = {.dst = "1.2.3.4"},
+ .in = true,
+ },
+ {
+ .match = {.dst = "2.2.2.2", .proto = 6, .dport = 6871},
+ .rewrite = {.dst = "1.2.3.4"},
+ .in = true,
+ },
+ {
+ .match = {.dst = "2.2.2.2", .proto = 6, .dport = 6872},
+ .rewrite = {.dst = "1.2.3.4", .sport = 53, .dport = 8000},
+ .in = true,
+ },
+ {
+ .match = {.dst = "2.2.2.2", .proto = 6, .dport = 6873},
+ .rewrite = {.dst = "1.2.3.4", .sport = 53, .dport = 8000},
+ .in = true,
+ },
+};
+
+static int fill_packets(vlib_main_t *vm, vlib_buffer_t *b,
+ test_5tuple_t *test) {
+ b->flags |= VLIB_BUFFER_IS_TRACED;
+
+ ip4_header_t *ip = (ip4_header_t *)vlib_buffer_get_current(b);
+ memset(ip, 0, sizeof(*ip));
+ ip->ip_version_and_header_length = 0x45;
+ ip->ttl = 64;
+ inet_pton(AF_INET, test->src, &ip->src_address.as_u32);
+ inet_pton(AF_INET, test->dst, &ip->dst_address.as_u32);
+ ip->protocol = test->proto;
+
+ if (test->proto == IP_PROTOCOL_UDP) {
+ udp_header_t *udp = ip4_next_header(ip);
+ memset(udp, 0, sizeof(*udp));
+ udp->dst_port = htons(test->dport);
+ udp->src_port = htons(test->sport);
+ udp->length = htons(8);
+ vnet_buffer(b)->ip.reass.l4_src_port = udp->src_port;
+ vnet_buffer(b)->ip.reass.l4_dst_port = udp->dst_port;
+ b->current_length = 28;
+ ip->length = htons(b->current_length);
+ ip->checksum = ip4_header_checksum(ip);
+ udp->checksum = ip4_tcp_udp_compute_checksum(vm, b, ip);
+ } else if (test->proto == IP_PROTOCOL_TCP) {
+ tcp_header_t *tcp = ip4_next_header(ip);
+ memset(tcp, 0, sizeof(*tcp));
+ tcp->dst_port = htons(test->dport);
+ tcp->src_port = htons(test->sport);
+ vnet_buffer(b)->ip.reass.l4_src_port = tcp->src_port;
+ vnet_buffer(b)->ip.reass.l4_dst_port = tcp->dst_port;
+ b->current_length = sizeof(ip4_header_t) + sizeof(tcp_header_t);
+ ip->length = htons(b->current_length);
+ ip->checksum = ip4_header_checksum(ip);
+ tcp->checksum = ip4_tcp_udp_compute_checksum(vm, b, ip);
+ } else {
+ b->current_length = sizeof(ip4_header_t);
+ ip->length = htons(b->current_length);
+ ip->checksum = ip4_header_checksum(ip);
+ vnet_buffer(b)->ip.reass.l4_src_port = 0;
+ vnet_buffer(b)->ip.reass.l4_dst_port = 0;
+ }
+
+ return 0;
+}
+
+static void ruleto5tuple(test_5tuple_t *r, pnat_5tuple_t *t) {
+ if (r->src) {
+ inet_pton(AF_INET, r->src, &t->src);
+ t->mask |= PNAT_SA;
+ }
+ if (r->dst) {
+ inet_pton(AF_INET, r->dst, &t->dst);
+ t->mask |= PNAT_DA;
+ }
+ if (r->dport) {
+ t->dport = r->dport;
+ t->mask |= PNAT_DPORT;
+ }
+ if (r->sport) {
+ t->sport = r->sport;
+ t->mask |= PNAT_SPORT;
+ }
+ t->proto = r->proto;
+}
+
+static void add_translation(rule_t *r) {
+ pnat_5tuple_t match = {0};
+ pnat_5tuple_t rewrite = {0};
+
+ ruleto5tuple(&r->match, &match);
+ ruleto5tuple(&r->rewrite, &rewrite);
+
+ int rv = pnat_binding_add(&match, &rewrite, &r->index);
+ assert(rv == 0);
+
+ rv = pnat_binding_attach(0, PNAT_IP4_INPUT, r->index);
+ assert(rv == 0);
+}
+
+static void del_translation(rule_t *r) {
+ int rv = pnat_binding_detach(0, PNAT_IP4_INPUT, r->index);
+ assert(rv == 0);
+
+ rv = pnat_binding_del(r->index);
+ assert(rv == 0);
+}
+
+static void validate_packet(vlib_main_t *vm, char *name, u32 bi,
+ vlib_buffer_t *expected_b) {
+ vlib_buffer_t *b = test_vlib_get_buffer(bi);
+ assert(b);
+
+ ip4_header_t *ip = (ip4_header_t *)vlib_buffer_get_current(b);
+ ip4_header_t *expected_ip =
+ (ip4_header_t *)vlib_buffer_get_current(expected_b);
+
+#if PNAT_TEST_DEBUG
+ clib_warning("Received packet: %U", format_ip4_header, ip, 20);
+ clib_warning("Expected packet: %U", format_ip4_header, expected_ip, 20);
+ tcp_header_t *tcp = ip4_next_header(ip);
+ clib_warning("IP: %U TCP: %U", format_ip4_header, ip, sizeof(*ip),
+ format_tcp_header, tcp, sizeof(*tcp));
+ tcp = ip4_next_header(expected_ip);
+ clib_warning("IP: %U TCP: %U", format_ip4_header, expected_ip, sizeof(*ip),
+ format_tcp_header, tcp, sizeof(*tcp));
+#endif
+
+ u32 flags = ip4_tcp_udp_validate_checksum(vm, b);
+ assert((flags & VNET_BUFFER_F_L4_CHECKSUM_CORRECT) != 0);
+ flags = ip4_tcp_udp_validate_checksum(vm, expected_b);
+ assert((flags & VNET_BUFFER_F_L4_CHECKSUM_CORRECT) != 0);
+ assert(b->current_length == expected_b->current_length);
+
+ test_assert(memcmp(ip, expected_ip, b->current_length) == 0, "%s", name);
+}
+
+extern vlib_node_registration_t pnat_input_node;
+
+static void test_table(test_t *t, int no_tests) {
+ // walk through table of tests
+ int i;
+ vlib_main_t *vm = &vlib_global_main;
+
+ /* Generate packet data */
+ for (i = 0; i < no_tests; i++) {
+ // create input buffer(s)
+ fill_packets(vm, (vlib_buffer_t *)&buffers[i], &t[i].send);
+ fill_packets(vm, (vlib_buffer_t *)&expected[i], &t[i].expect);
+ }
+
+ /* send packets through graph node */
+ vlib_frame_t frame = {.n_vectors = no_tests};
+ node->flags |= VLIB_NODE_FLAG_TRACE;
+
+ pnat_node_inline(vm, node, &frame, PNAT_IP4_INPUT, VLIB_RX);
+
+ /* verify tests */
+ for (i = 0; i < no_tests; i++) {
+ assert(t[i].expect_next_index == results_next[i]);
+ validate_packet(vm, t[i].name, results_bi[i],
+ (vlib_buffer_t *)&expected[i]);
+ }
+ vec_free(results_next);
+ vec_free(results_bi);
+}
+
+static void test_performance(void) {
+ pnat_main_t *pm = &pnat_main;
+ int i;
+ vlib_main_t *vm = &vlib_global_main;
+
+ for (i = 0; i < sizeof(rules) / sizeof(rules[0]); i++) {
+ add_translation(&rules[i]);
+ }
+ assert(pool_elts(pm->translations) == sizeof(rules) / sizeof(rules[0]));
+
+ int no_tests = sizeof(tests) / sizeof(tests[0]);
+ /* Generate packet data */
+ for (i = 0; i < VLIB_FRAME_SIZE; i++) {
+ // create input buffer(s)
+ fill_packets(vm, (vlib_buffer_t *)&buffers[i],
+ &tests[i % no_tests].send);
+ // fill_packets(vm, (vlib_buffer_t *)&expected[i], &tests[i %
+ // no_tests].expect);
+ }
+
+ /* send packets through graph node */
+ vlib_frame_t frame = {.n_vectors = VLIB_FRAME_SIZE};
+ node->flags &= ~VLIB_NODE_FLAG_TRACE;
+
+ int j;
+ for (j = 0; j < 10000; j++) {
+ pnat_node_inline(vm, node, &frame, PNAT_IP4_INPUT, VLIB_RX);
+
+#if 0
+ for (i = 0; i < VLIB_FRAME_SIZE; i++) {
+ assert(tests[i % no_tests].expect_next_index == results_next[i]);
+ validate_packet(vm, tests[i % no_tests].name, results_bi[i], (vlib_buffer_t *)&expected[i]);
+ }
+#endif
+ vec_free(results_next);
+ vec_free(results_bi);
+ }
+
+ for (i = 0; i < sizeof(rules) / sizeof(rules[0]); i++) {
+ del_translation(&rules[i]);
+ }
+ assert(pool_elts(pm->translations) == 0);
+ assert(pool_elts(pm->interfaces) == 0);
+}
+
+static void test_packets(void) {
+ pnat_main_t *pm = &pnat_main;
+ int i;
+ for (i = 0; i < sizeof(rules) / sizeof(rules[0]); i++) {
+ add_translation(&rules[i]);
+ }
+ assert(pool_elts(pm->translations) == sizeof(rules) / sizeof(rules[0]));
+
+ test_table(tests, sizeof(tests) / sizeof(tests[0]));
+
+ for (i = 0; i < sizeof(rules) / sizeof(rules[0]); i++) {
+ del_translation(&rules[i]);
+ }
+ assert(pool_elts(pm->translations) == 0);
+ assert(pool_elts(pm->interfaces) == 0);
+}
+static void test_attach(void) {
+ pnat_attachment_point_t attachment = PNAT_IP4_INPUT;
+ u32 binding_index = 0;
+ u32 sw_if_index = 0;
+ int rv = pnat_binding_attach(sw_if_index, attachment, binding_index);
+ test_assert(rv == -1, "binding_attach - nothing to attach");
+
+ rv = pnat_binding_detach(sw_if_index, attachment, 1234);
+ test_assert(rv == -1, "binding_detach - nothing to detach");
+
+ pnat_5tuple_t match = {.mask = PNAT_SA};
+ pnat_5tuple_t rewrite = {.mask = PNAT_SA};
+ rv = pnat_binding_add(&match, &rewrite, &binding_index);
+ assert(rv == 0);
+
+ rv = pnat_binding_attach(sw_if_index, attachment, binding_index);
+ test_assert(rv == 0, "binding_attach - rule");
+
+ rv = pnat_binding_detach(sw_if_index, attachment, binding_index);
+ test_assert(rv == 0, "binding_detach - rule");
+
+ rv = pnat_binding_del(binding_index);
+ assert(rv == 0);
+}
+
+static void test_del_before_detach(void) {
+ pnat_attachment_point_t attachment = PNAT_IP4_INPUT;
+ u32 binding_index = 0;
+ u32 sw_if_index = 0;
+
+ /* Ensure 5-tuple here will not duplicate with other tests cause this will
+ * not be removed from flow cache */
+ rule_t rule = {
+ .match = {.dst = "123.123.123.123", .proto = 17, .dport = 6871},
+ .rewrite = {.dst = "1.2.3.4"},
+ .in = true,
+ };
+
+ add_translation(&rule);
+
+ int rv = pnat_binding_del(binding_index);
+ assert(rv == 0);
+
+ test_t test = {
+ .name = "hit missing rule",
+ .send = {"1.1.1.1", "123.123.123.123", 17, 80, 6871},
+ .expect = {"1.1.1.1", "123.123.123.123", 17, 80, 6871},
+ .expect_next_index = PNAT_NEXT_DROP,
+ };
+
+ test_table(&test, 1);
+
+ /* For now if you have deleted before detach, can't find key */
+ rv = pnat_binding_detach(sw_if_index, attachment, binding_index);
+ test_assert(rv == -1, "binding_detach - failure");
+
+ /* Re-add the rule and try again */
+ pnat_5tuple_t match = {0};
+ pnat_5tuple_t rewrite = {0};
+ ruleto5tuple(&rule.match, &match);
+ ruleto5tuple(&rule.rewrite, &rewrite);
+ rv = pnat_binding_add(&match, &rewrite, &binding_index);
+ assert(rv == 0);
+ rv = pnat_binding_detach(sw_if_index, attachment, binding_index);
+ test_assert(rv == 0, "binding_detach - pass");
+ rv = pnat_binding_del(binding_index);
+ assert(rv == 0);
+}
+
+static void test_api(void) {
+ test_attach();
+ test_del_before_detach();
+}
+
+/*
+ * Unit testing:
+ * 1) Table of packets and expected outcomes. Run through
+ * 2) Performance tests. Measure instructions, cache behaviour etc.
+ */
+clib_error_t *ip_checksum_init(vlib_main_t *vm);
+
+int main(int argc, char **argv) {
+
+ clib_mem_init(0, 3ULL << 30);
+
+ vlib_main_t *vm = &vlib_global_main;
+
+ buffers_vector = buffer_init(buffers_vector, 256);
+
+ assert(vlib_node_main_init(vm) == 0);
+
+ ip_checksum_init(vm);
+
+ u32 node_index = vlib_register_node(vm, &pnat_input_node);
+ node = vlib_node_get_runtime(vm, node_index);
+ assert(node);
+
+ /* Test API */
+ test_api();
+
+ test_packets();
+
+ test_performance();
+}
diff --git a/src/plugins/nat/pnat/pnat_test_stubs.h b/src/plugins/nat/pnat/pnat_test_stubs.h
new file mode 100644
index 00000000000..2801398407c
--- /dev/null
+++ b/src/plugins/nat/pnat/pnat_test_stubs.h
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ */
+
+#ifndef included_pnat_test_stubs_h
+#define included_pnat_test_stubs_h
+
+void os_panic(void) {}
+void os_exit(int code) {}
+u32 ip4_fib_table_get_index_for_sw_if_index(u32 sw_if_index) { return 0; }
+#include <vpp/stats/stat_segment.h>
+clib_error_t *stat_segment_register_gauge(u8 *names,
+ stat_segment_update_fn update_fn,
+ u32 index) {
+ return 0;
+};
+#include <vnet/feature/feature.h>
+vnet_feature_main_t feature_main;
+void classify_get_trace_chain(void){};
+
+/* Format an IP4 address. */
+u8 *format_ip4_address(u8 *s, va_list *args) {
+ u8 *a = va_arg(*args, u8 *);
+ return format(s, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]);
+}
+
+u8 *format_pnat_5tuple(u8 *s, va_list *args) { return 0; }
+
+vl_counter_t pnat_error_counters[10];
+
+int ip4_sv_reass_enable_disable_with_refcnt(u32 sw_if_index, int is_enable) {
+ return 0;
+}
+int ip4_sv_reass_output_enable_disable_with_refcnt(u32 sw_if_index,
+ int is_enable) {
+ return 0;
+}
+int vnet_feature_enable_disable(const char *arc_name, const char *node_name,
+ u32 sw_if_index, int enable_disable,
+ void *feature_config,
+ u32 n_feature_config_bytes) {
+ return 0;
+}
+vnet_main_t *vnet_get_main(void) { return 0; }
+
+static struct {
+ vec_header_t h;
+ vlib_main_t *vm;
+} __attribute__((packed)) __bootstrap_vlib_main_vector
+ __attribute__((aligned(CLIB_CACHE_LINE_BYTES))) = {
+ .h.len = 1,
+ .vm = &vlib_global_main,
+};
+
+vlib_main_t **vlib_mains = &__bootstrap_vlib_main_vector.vm;
+
+/* Compute TCP/UDP/ICMP4 checksum in software. */
+u16 ip4_tcp_udp_compute_checksum(vlib_main_t *vm, vlib_buffer_t *p0,
+ ip4_header_t *ip0) {
+ ip_csum_t sum0;
+ u32 ip_header_length, payload_length_host_byte_order;
+
+ /* Initialize checksum with ip header. */
+ ip_header_length = ip4_header_bytes(ip0);
+ payload_length_host_byte_order =
+ clib_net_to_host_u16(ip0->length) - ip_header_length;
+ sum0 = clib_host_to_net_u32(payload_length_host_byte_order +
+ (ip0->protocol << 16));
+
+ if (BITS(uword) == 32) {
+ sum0 = ip_csum_with_carry(sum0,
+ clib_mem_unaligned(&ip0->src_address, u32));
+ sum0 = ip_csum_with_carry(sum0,
+ clib_mem_unaligned(&ip0->dst_address, u32));
+ } else
+ sum0 = ip_csum_with_carry(sum0,
+ clib_mem_unaligned(&ip0->src_address, u64));
+ return ip_calculate_l4_checksum(vm, p0, sum0,
+ payload_length_host_byte_order, (u8 *)ip0,
+ ip_header_length, NULL);
+}
+
+u32 ip4_tcp_udp_validate_checksum(vlib_main_t *vm, vlib_buffer_t *p0) {
+ ip4_header_t *ip0 = vlib_buffer_get_current(p0);
+ udp_header_t *udp0;
+ u16 sum16;
+
+ ASSERT(ip0->protocol == IP_PROTOCOL_TCP ||
+ ip0->protocol == IP_PROTOCOL_UDP);
+
+ udp0 = (void *)(ip0 + 1);
+ if (ip0->protocol == IP_PROTOCOL_UDP && udp0->checksum == 0) {
+ p0->flags |= (VNET_BUFFER_F_L4_CHECKSUM_COMPUTED |
+ VNET_BUFFER_F_L4_CHECKSUM_CORRECT);
+ return p0->flags;
+ }
+
+ sum16 = ip4_tcp_udp_compute_checksum(vm, p0, ip0);
+
+ p0->flags |= (VNET_BUFFER_F_L4_CHECKSUM_COMPUTED |
+ ((sum16 == 0) << VNET_BUFFER_F_LOG2_L4_CHECKSUM_CORRECT));
+
+ return p0->flags;
+}
+u8 *format_tcp_header(u8 *s, va_list *args) {
+ tcp_header_t *tcp = va_arg(*args, tcp_header_t *);
+ u32 max_header_bytes = va_arg(*args, u32);
+ u32 header_bytes;
+ u32 indent;
+
+ /* Nothing to do. */
+ if (max_header_bytes < sizeof(tcp[0]))
+ return format(s, "TCP header truncated");
+
+ indent = format_get_indent(s);
+ indent += 2;
+ header_bytes = tcp_header_bytes(tcp);
+
+ s = format(s, "TCP: %d -> %d", clib_net_to_host_u16(tcp->src),
+ clib_net_to_host_u16(tcp->dst));
+
+ s = format(s, "\n%Useq. 0x%08x ack 0x%08x", format_white_space, indent,
+ clib_net_to_host_u32(tcp->seq_number),
+ clib_net_to_host_u32(tcp->ack_number));
+
+ s = format(s, "\n%Utcp header: %d bytes", format_white_space, indent,
+ tcp->flags, header_bytes);
+
+ s = format(s, "\n%Uwindow %d, checksum 0x%04x", format_white_space, indent,
+ clib_net_to_host_u16(tcp->window),
+ clib_net_to_host_u16(tcp->checksum));
+ return s;
+}
+
+/* Format an IP4 header. */
+u8 *format_ip4_header(u8 *s, va_list *args) {
+ ip4_header_t *ip = va_arg(*args, ip4_header_t *);
+ u32 max_header_bytes = va_arg(*args, u32);
+ u32 ip_version, header_bytes;
+ u32 indent;
+
+ /* Nothing to do. */
+ if (max_header_bytes < sizeof(ip[0]))
+ return format(s, "IP header truncated");
+
+ indent = format_get_indent(s);
+ indent += 2;
+
+ ip_version = (ip->ip_version_and_header_length >> 4);
+ header_bytes = (ip->ip_version_and_header_length & 0xf) * sizeof(u32);
+
+ s = format(s, "%d: %U -> %U", ip->protocol, format_ip4_address,
+ ip->src_address.data, format_ip4_address, ip->dst_address.data);
+
+ /* Show IP version and header length only with unexpected values. */
+ if (ip_version != 4 || header_bytes != sizeof(ip4_header_t))
+ s = format(s, "\n%Uversion %d, header length %d", format_white_space,
+ indent, ip_version, header_bytes);
+
+ s = format(s, "\n%Utos 0x%02x, ttl %d, length %d, checksum 0x%04x",
+ format_white_space, indent, ip->tos, ip->ttl,
+ clib_net_to_host_u16(ip->length),
+ clib_net_to_host_u16(ip->checksum));
+
+ /* Check and report invalid checksums. */
+ {
+ if (!ip4_header_checksum_is_valid(ip))
+ s = format(s, " (should be 0x%04x)",
+ clib_net_to_host_u16(ip4_header_checksum(ip)));
+ }
+
+ {
+ u32 f = clib_net_to_host_u16(ip->flags_and_fragment_offset);
+ u32 o;
+
+ s = format(s, "\n%Ufragment id 0x%04x", format_white_space, indent,
+ clib_net_to_host_u16(ip->fragment_id));
+
+ /* Fragment offset. */
+ o = 8 * (f & 0x1fff);
+ f ^= f & 0x1fff;
+ if (o != 0)
+ s = format(s, " offset %d", o);
+
+ if (f != 0) {
+ s = format(s, ", flags ");
+#define _(l) \
+ if (f & IP4_HEADER_FLAG_##l) \
+ s = format(s, #l);
+ _(MORE_FRAGMENTS);
+ _(DONT_FRAGMENT);
+ _(CONGESTION);
+#undef _
+ }
+ /* Fragment packet but not the first. */
+ if (o != 0)
+ return s;
+ }
+
+ return s;
+}
+
+#endif