nat: 1:1 policy NAT 18/30718/23
authorOle Troan <ot@cisco.com>
Tue, 12 Jan 2021 20:49:38 +0000 (21:49 +0100)
committerNeale Ranns <neale@graphiant.com>
Fri, 5 Feb 2021 13:27:48 +0000 (13:27 +0000)
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>
17 files changed:
src/plugins/nat/CMakeLists.txt
src/plugins/nat/pnat/.clang-format [new file with mode: 0644]
src/plugins/nat/pnat/FEATURE.yaml [new file with mode: 0644]
src/plugins/nat/pnat/pnat.api [new file with mode: 0644]
src/plugins/nat/pnat/pnat.c [new file with mode: 0644]
src/plugins/nat/pnat/pnat.h [new file with mode: 0644]
src/plugins/nat/pnat/pnat.md [new file with mode: 0644]
src/plugins/nat/pnat/pnat_api.c [new file with mode: 0644]
src/plugins/nat/pnat/pnat_cli.c [new file with mode: 0644]
src/plugins/nat/pnat/pnat_node.c [new file with mode: 0644]
src/plugins/nat/pnat/pnat_node.h [new file with mode: 0644]
src/plugins/nat/pnat/pnat_test.c [new file with mode: 0644]
src/plugins/nat/pnat/pnat_test_stubs.h [new file with mode: 0644]
src/plugins/nat/test/test_pnat.py [new file with mode: 0644]
src/tools/vppapigen/vppapigen.py
src/tools/vppapigen/vppapigen_c.py
src/vnet/ip/ip_checksum.c

index 83c148f..d3f3eac 100644 (file)
@@ -139,3 +139,59 @@ add_vpp_plugin(nat64
 
   LINK_LIBRARIES nat
 )
+
+add_vpp_plugin(pnat
+  SOURCES
+  pnat/pnat.c
+  pnat/pnat_cli.c
+  pnat/pnat_api.c
+  pnat/pnat_node.c
+
+  API_FILES
+  pnat/pnat.api
+)
+
+# Unit tests
+add_vpp_executable(test_pnat
+  SOURCES
+  pnat/pnat_test.c
+  pnat/pnat_node.c
+  pnat/pnat.c
+  ../../vnet/ip/ip_checksum.c
+  LINK_LIBRARIES vppinfra vlib
+  NO_INSTALL
+)
+
+if("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.13" AND "${CMAKE_C_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
+  set(TARGET_NAME test_pnat)
+  set(COV_SOURCES ${CMAKE_SOURCE_DIR}/plugins/nat/pnat/pnat.c ${CMAKE_SOURCE_DIR}/plugins/nat/pnat/pnat_node.h ${CMAKE_SOURCE_DIR}/plugins/nat/pnat/pnat_node.c)
+
+  message("Building with llvm Code Coverage Tools ${TARGET_NAME}")
+  target_compile_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping)
+  target_link_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate -fcoverage-mapping)
+  target_compile_options(${TARGET_NAME} PRIVATE -fsanitize=address)
+  target_link_options(${TARGET_NAME} PRIVATE -fsanitize=address)
+
+  # llvm-cov
+  add_custom_target(${TARGET_NAME}-ccov-preprocessing
+    COMMAND LLVM_PROFILE_FILE=${TARGET_NAME}.profraw $<TARGET_FILE:${TARGET_NAME}>
+    COMMAND llvm-profdata merge -sparse ${TARGET_NAME}.profraw -o ${TARGET_NAME}.profdata
+    DEPENDS ${TARGET_NAME})
+
+  add_custom_target(${TARGET_NAME}-ccov-show
+    COMMAND llvm-cov show $<TARGET_FILE:${TARGET_NAME}> -instr-profile=${TARGET_NAME}.profdata -show-line-counts-or-regions ${COV_SOURCES}
+    DEPENDS ${TARGET_NAME}-ccov-preprocessing)
+
+  add_custom_target(${TARGET_NAME}-ccov-report
+    COMMAND llvm-cov report -show-functions $<TARGET_FILE:${TARGET_NAME}> -instr-profile=${TARGET_NAME}.profdata ${COV_SOURCES}
+    DEPENDS ${TARGET_NAME}-ccov-preprocessing)
+
+  add_custom_target(${TARGET_NAME}-ccov
+    COMMAND llvm-cov show $<TARGET_FILE:${TARGET_NAME}> -instr-profile=${TARGET_NAME}.profdata -show-line-counts-or-regions -output-dir=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET_NAME}-llvm-cov -format="html" ${COV_SOURCES}
+    DEPENDS ${TARGET_NAME}-ccov-preprocessing)
+
+  add_custom_command(TARGET ${TARGET_NAME}-ccov POST_BUILD
+    COMMAND ;
+    COMMENT "Open ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET_NAME}-llvm-cov/index.html in your browser to view the coverage report."
+)
+endif()
diff --git a/src/plugins/nat/pnat/.clang-format b/src/plugins/nat/pnat/.clang-format
new file mode 100644 (file)
index 0000000..6f25dc7
--- /dev/null
@@ -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 (file)
index 0000000..56f553a
--- /dev/null
@@ -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 (file)
index 0000000..c18c894
--- /dev/null
@@ -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 (file)
index 0000000..335d275
--- /dev/null
@@ -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 (file)
index 0000000..c5869ce
--- /dev/null
@@ -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 (file)
index 0000000..a7c3376
--- /dev/null
@@ -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 (file)
index 0000000..dad658c
--- /dev/null
@@ -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 (file)
index 0000000..2d38901
--- /dev/null
@@ -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 (file)
index 0000000..0209b49
--- /dev/null
@@ -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 (file)
index 0000000..3f22355
--- /dev/null
@@ -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 (file)
index 0000000..762b4bd
--- /dev/null
@@ -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 (file)
index 0000000..2801398
--- /dev/null
@@ -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
diff --git a/src/plugins/nat/test/test_pnat.py b/src/plugins/nat/test/test_pnat.py
new file mode 100644 (file)
index 0000000..5e52fa9
--- /dev/null
@@ -0,0 +1,203 @@
+#!/usr/bin/env python3
+"""Policy 1:1 NAT functional tests"""
+
+import unittest
+from scapy.layers.inet import Ether, IP, UDP, ICMP
+from framework import VppTestCase, VppTestRunner
+from vpp_papi import VppEnum
+
+
+class TestPNAT(VppTestCase):
+    """ PNAT Test Case """
+    maxDiff = None
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestPNAT, cls).setUpClass()
+        cls.create_pg_interfaces(range(2))
+        cls.interfaces = list(cls.pg_interfaces)
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestPNAT, cls).tearDownClass()
+
+    def setUp(self):
+        super(TestPNAT, self).setUp()
+        for i in self.interfaces:
+            i.admin_up()
+            i.config_ip4()
+            i.resolve_arp()
+
+    def tearDown(self):
+        super(TestPNAT, self).tearDown()
+        if not self.vpp_dead:
+            for i in self.pg_interfaces:
+                i.unconfig_ip4()
+                i.admin_down()
+
+    def validate(self, rx, expected):
+        self.assertEqual(rx, expected.__class__(expected))
+
+    def validate_bytes(self, rx, expected):
+        self.assertEqual(rx, expected)
+
+    def ping_check(self):
+        """ Verify non matching traffic works. """
+        p_ether = Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac)
+
+        icmpecho = (IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
+                    ICMP())
+        reply = (IP(src=self.pg0.local_ip4, dst=self.pg0.remote_ip4) /
+                 ICMP(type='echo-reply'))
+        rx = self.send_and_expect(self.pg0, p_ether/icmpecho * 1, self.pg0)
+        for p in rx:
+            reply[IP].id = p[IP].id
+            self.validate(p[1], reply)
+
+    def test_pnat(self):
+        """ PNAT test """
+
+        PNAT_IP4_INPUT = VppEnum.vl_api_pnat_attachment_point_t.PNAT_IP4_INPUT
+        PNAT_IP4_OUTPUT = \
+            VppEnum.vl_api_pnat_attachment_point_t.PNAT_IP4_OUTPUT
+
+        tests = [
+            {
+                'input': PNAT_IP4_INPUT,
+                'sw_if_index': self.pg0.sw_if_index,
+                'match': {'mask': 0xa, 'dst': '10.10.10.10', 'proto': 17,
+                          'dport': 6871},
+                'rewrite': {'mask': 0x2, 'dst': self.pg1.remote_ip4},
+                'send': (IP(src=self.pg0.remote_ip4, dst='10.10.10.10') /
+                         UDP(dport=6871)),
+                'reply': (IP(src=self.pg0.remote_ip4,
+                             dst=self.pg1.remote_ip4) /
+                          UDP(dport=6871))
+            },
+            {
+                'input': PNAT_IP4_OUTPUT,
+                'sw_if_index': self.pg1.sw_if_index,
+                'match': {'mask': 0x9, 'src': self.pg0.remote_ip4, 'proto': 17,
+                          'dport': 6871},
+                'rewrite': {'mask': 0x1, 'src': '11.11.11.11'},
+                'send': (IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                         UDP(dport=6871)),
+                'reply': (IP(src='11.11.11.11', dst=self.pg1.remote_ip4) /
+                          UDP(dport=6871))
+            },
+            {
+                'input': PNAT_IP4_INPUT,
+                'sw_if_index': self.pg0.sw_if_index,
+                'match': {'mask': 0xa, 'dst': '10.10.10.10', 'proto': 17,
+                          'dport': 6871},
+                'rewrite': {'mask': 0xa, 'dst': self.pg1.remote_ip4,
+                            'dport': 5555},
+                'send': (IP(src=self.pg0.remote_ip4, dst='10.10.10.10') /
+                         UDP(dport=6871)),
+                'reply': (IP(src=self.pg0.remote_ip4,
+                             dst=self.pg1.remote_ip4) /
+                          UDP(dport=5555))
+            },
+            {
+                'input': PNAT_IP4_INPUT,
+                'sw_if_index': self.pg0.sw_if_index,
+                'match': {'mask': 0xa, 'dst': self.pg1.remote_ip4, 'proto': 17,
+                          'dport': 6871},
+                'rewrite': {'mask': 0x8, 'dport': 5555},
+                'send': (IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                         UDP(dport=6871, chksum=0)),
+                'reply': (IP(src=self.pg0.remote_ip4,
+                             dst=self.pg1.remote_ip4) /
+                          UDP(dport=5555, chksum=0))
+            },
+            {
+                'input': PNAT_IP4_INPUT,
+                'sw_if_index': self.pg0.sw_if_index,
+                'match': {'mask': 0x2, 'dst': self.pg1.remote_ip4, 'proto': 1},
+                'rewrite': {'mask': 0x1, 'src': '8.8.8.8'},
+                'send': (IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                         ICMP()),
+                'reply': IP(src='8.8.8.8', dst=self.pg1.remote_ip4)/ICMP(),
+            },
+        ]
+
+        p_ether = Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac)
+        for t in tests:
+            rv = self.vapi.pnat_binding_add(match=t['match'],
+                                            rewrite=t['rewrite'])
+            self.vapi.pnat_binding_attach(sw_if_index=t['sw_if_index'],
+                                          attachment=t['input'],
+                                          binding_index=rv.binding_index)
+
+            reply = t['reply']
+            reply[IP].ttl -= 1
+            rx = self.send_and_expect(self.pg0, p_ether/t['send']*1, self.pg1)
+            for p in rx:
+                # p.show2()
+                self.validate(p[1], reply)
+
+            self.ping_check()
+
+            self.vapi.pnat_binding_detach(sw_if_index=t['sw_if_index'],
+                                          attachment=t['input'],
+                                          binding_index=rv.binding_index)
+            self.vapi.pnat_binding_del(binding_index=rv.binding_index)
+
+    def test_pnat_show(self):
+        """ PNAT show tests """
+
+        PNAT_IP4_INPUT = VppEnum.vl_api_pnat_attachment_point_t.PNAT_IP4_INPUT
+        PNAT_IP4_OUTPUT = \
+            VppEnum.vl_api_pnat_attachment_point_t.PNAT_IP4_OUTPUT
+
+        tests = [
+            {
+                'input': PNAT_IP4_INPUT,
+                'sw_if_index': self.pg0.sw_if_index,
+                'match': {'mask': 0xa, 'dst': '10.10.10.10', 'proto': 17,
+                          'dport': 6871},
+                'rewrite': {'mask': 0x2, 'dst': self.pg1.remote_ip4},
+                'send': (IP(src=self.pg0.remote_ip4, dst='10.10.10.10') /
+                         UDP(dport=6871)),
+                'reply': (IP(src=self.pg0.remote_ip4,
+                             dst=self.pg1.remote_ip4) /
+                          UDP(dport=6871))
+            },
+            {
+                'input': PNAT_IP4_OUTPUT,
+                'sw_if_index': self.pg1.sw_if_index,
+                'match': {'mask': 0x9, 'src': self.pg0.remote_ip4, 'proto': 17,
+                          'dport': 6871},
+                'rewrite': {'mask': 0x1, 'src': '11.11.11.11'},
+                'send': (IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+                         UDP(dport=6871)),
+                'reply': (IP(src='11.11.11.11', dst=self.pg1.remote_ip4) /
+                          UDP(dport=6871))
+            },
+        ]
+        binding_index = []
+        for t in tests:
+            rv = self.vapi.pnat_binding_add(match=t['match'],
+                                            rewrite=t['rewrite'])
+            binding_index.append(rv.binding_index)
+            self.vapi.pnat_binding_attach(sw_if_index=t['sw_if_index'],
+                                          attachment=t['input'],
+                                          binding_index=rv.binding_index)
+
+        rv, l = self.vapi.pnat_bindings_get()
+        self.assertEqual(len(l), len(tests))
+
+        rv, l = self.vapi.pnat_interfaces_get()
+        self.assertEqual(len(l), 2)
+
+        self.logger.info(self.vapi.cli("show pnat translations"))
+        self.logger.info(self.vapi.cli("show pnat interfaces"))
+
+        for i, t in enumerate(tests):
+            self.vapi.pnat_binding_detach(sw_if_index=t['sw_if_index'],
+                                          attachment=t['input'],
+                                          binding_index=binding_index[i])
+            self.vapi.pnat_binding_del(binding_index=binding_index[i])
+
+if __name__ == '__main__':
+    unittest.main(testRunner=VppTestRunner)
index 7c59696..33510b8 100755 (executable)
@@ -635,7 +635,7 @@ class VPPAPIParser(object):
         '''counter_elements : counter_element
                             | counter_elements counter_element'''
         if len(p) == 2:
-            p[0] = p[1]
+            p[0] = [p[1]]
         else:
             if type(p[1]) is dict:
                 p[0] = [p[1], p[2]]
index 0ded2af..0798e9a 100644 (file)
@@ -73,9 +73,7 @@ class ToJSON():
         write = self.stream.write
         write('#endif\n')
 
-    def get_json_func(self, t):
-        '''Given the type, returns the function to use to create a
-        cJSON object'''
+    def get_base_type(self, t):
         vt_type = None
         try:
             vt = self.types_hash[t]
@@ -83,6 +81,12 @@ class ToJSON():
                 vt_type = vt.alias['type']
         except KeyError:
             vt = t
+        return vt, vt_type
+
+    def get_json_func(self, t):
+        '''Given the type, returns the function to use to create a
+        cJSON object'''
+        vt, vt_type = self.get_base_type(t)
 
         if t in self.is_number or vt_type in self.is_number:
             return 'cJSON_AddNumberToObject', '', False
@@ -101,6 +105,9 @@ class ToJSON():
             return 'cJSON_CreateNumber', ''
         if t == 'bool':
             return 'cJSON_CreateBool', ''
+        vt, vt_type = self.get_base_type(t)
+        if vt.type == 'Enum' or vt.type == 'EnumFlag':
+            return '{t}_tojson'.format(t=t), ''
         return '{t}_tojson'.format(t=t), '&'
 
     def print_string(self, o):
@@ -1039,7 +1046,7 @@ def endianfun_array(o):
 '''
 
     output = ''
-    if o.fieldtype == 'u8' or o.fieldtype == 'string':
+    if o.fieldtype == 'u8' or o.fieldtype == 'string' or o.fieldtype == 'bool':
         output += '    /* a->{n} = a->{n} (no-op) */\n'.format(n=o.fieldname)
     else:
         lfield = 'a->' + o.lengthfield if o.lengthfield else o.length
index d586649..1ac7248 100644 (file)
@@ -123,8 +123,8 @@ do {                                                                        \
 
 ip_csum_t (*vnet_incremental_checksum_fp) (ip_csum_t, void *, uword);
 
-static clib_error_t *
-ip_checksum_init (vlib_main_t * vm)
+clib_error_t *
+ip_checksum_init (vlib_main_t *vm)
 {
   vnet_incremental_checksum_fp = _ip_incremental_checksum;
   return 0;