nat: pnat copy and clear byte instructions 55/31255/11
authorAndrew Yourtchenko <ayourtch@gmail.com>
Tue, 23 Feb 2021 08:56:27 +0000 (08:56 +0000)
committerDamjan Marion <dmarion@me.com>
Mon, 15 Mar 2021 17:53:05 +0000 (17:53 +0000)
Type: feature
Signed-off-by: Ole Troan <ot@cisco.com>
Change-Id: I8e48bdcc4c311717e067bb0a4e0b409a2eb8e83d
Signed-off-by: Andrew Yourtchenko <ayourtch@gmail.com>
Signed-off-by: Ole Troan <ot@cisco.com>
13 files changed:
src/plugins/nat/CMakeLists.txt
src/plugins/nat/pnat/pnat.api
src/plugins/nat/pnat/pnat.c
src/plugins/nat/pnat/pnat.h
src/plugins/nat/pnat/pnat_cli.c
src/plugins/nat/pnat/pnat_node.c
src/plugins/nat/pnat/pnat_node.h
src/plugins/nat/pnat/tests/missing_rule.def [new file with mode: 0644]
src/plugins/nat/pnat/tests/packets.def [new file with mode: 0644]
src/plugins/nat/pnat/tests/pnat_test.c [moved from src/plugins/nat/pnat/pnat_test.c with 62% similarity]
src/plugins/nat/pnat/tests/pnat_test_stubs.h [moved from src/plugins/nat/pnat/pnat_test_stubs.h with 90% similarity]
src/plugins/nat/pnat/tests/test_genpackets.py [new file with mode: 0755]
src/plugins/nat/pnat/tests/test_packets.h [new file with mode: 0644]

index 623c92a..2545da6 100644 (file)
@@ -172,16 +172,33 @@ add_vpp_plugin(pnat
 )
 
 # Unit tests
+set (PACKET_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/pnat/tests/test_packets.h)
+set (PACKET_DEFS
+  ${CMAKE_CURRENT_SOURCE_DIR}/pnat/tests/packets.def
+  ${CMAKE_CURRENT_SOURCE_DIR}/pnat/tests/missing_rule.def)
+set (PACKET_TESTGET ${CMAKE_CURRENT_SOURCE_DIR}/pnat/tests/test_genpackets.py)
+
 add_vpp_executable(test_pnat
   SOURCES
-  pnat/pnat_test.c
+  pnat/tests/pnat_test.c
   pnat/pnat_node.c
   pnat/pnat.c
   ../../vnet/ip/ip_checksum.c
+
   LINK_LIBRARIES vppinfra vlib
   NO_INSTALL
 )
 
+add_custom_target(test_pnat-generate
+  COMMAND ${PACKET_TESTGET} ${PACKET_DEFS} > ${PACKET_HEADER}
+  COMMENT "Generating packet test definitions ${PACKET_DEFS}"
+)
+
+add_custom_target(test_pnat-run
+  COMMAND test_pnat
+  DEPENDS test_pnat
+)
+
 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)
index c18c894..fe7bf98 100644 (file)
@@ -13,7 +13,7 @@
  * limitations under the License.
  */
 
-option version = "0.0.1";
+option version = "0.1.1";
 
 import "vnet/interface_types.api";
 import "vnet/ip/ip_types.api";
@@ -24,6 +24,8 @@ enum pnat_mask
     PNAT_DA = 0x2,
     PNAT_SPORT = 0x4,
     PNAT_DPORT = 0x8,
+    PNAT_COPY_BYTE = 0x10,
+    PNAT_CLEAR_BYTE = 0x20,
 };
 
 enum pnat_attachment_point
@@ -33,7 +35,7 @@ enum pnat_attachment_point
     PNAT_ATTACHMENT_POINT_MAX,
 };
 
-typedef pnat_5tuple
+typedef pnat_match_tuple
 {
     vl_api_ip4_address_t src;
     vl_api_ip4_address_t dst;
@@ -43,12 +45,24 @@ typedef pnat_5tuple
     vl_api_pnat_mask_t mask;
 };
 
+typedef pnat_rewrite_tuple
+{
+    vl_api_ip4_address_t src;
+    vl_api_ip4_address_t dst;
+    u16 sport;
+    u16 dport;
+    vl_api_pnat_mask_t mask; // needed?
+    u8 from_offset;
+    u8 to_offset;
+    u8 clear_offset;
+};
+
 autoendian define pnat_binding_add
 {
     u32 client_index;
     u32 context;
-    vl_api_pnat_5tuple_t match;
-    vl_api_pnat_5tuple_t rewrite;
+    vl_api_pnat_match_tuple_t match;
+    vl_api_pnat_rewrite_tuple_t rewrite;
 };
 
 autoendian define pnat_binding_add_reply
@@ -107,8 +121,8 @@ define pnat_bindings_get_reply
 define pnat_bindings_details
 {
   u32 context;
-  vl_api_pnat_5tuple_t match;
-  vl_api_pnat_5tuple_t rewrite;
+  vl_api_pnat_match_tuple_t match;
+  vl_api_pnat_rewrite_tuple_t rewrite;
 };
 
 define pnat_interfaces_get
@@ -147,6 +161,12 @@ counters pnat {
     units "packets";
     description "rewrite failed";
   };
+  tooshort {
+    severity info;
+    type counter64;
+    units "packets";
+    description "packet too short for rewrite";
+  };
 };
 
 paths {
index a47521f..7f02ec4 100644 (file)
@@ -178,7 +178,7 @@ static int pnat_disable_interface(u32 sw_if_index,
  */
 static inline void pnat_calc_key_from_5tuple(u32 sw_if_index,
                                              pnat_attachment_point_t attachment,
-                                             pnat_5tuple_t *match,
+                                             pnat_match_tuple_t *match,
                                              clib_bihash_kv_16_8_t *kv) {
     pnat_mask_fast_t mask = pnat_mask2fast(match->mask);
     ip4_address_t src, dst;
@@ -202,6 +202,10 @@ pnat_instructions_t pnat_instructions_from_mask(pnat_mask_t m) {
         i |= PNAT_INSTR_SOURCE_PORT;
     if (m & PNAT_DPORT)
         i |= PNAT_INSTR_DESTINATION_PORT;
+    if (m & PNAT_COPY_BYTE)
+        i |= PNAT_INSTR_COPY_BYTE;
+    if (m & PNAT_CLEAR_BYTE)
+        i |= PNAT_INSTR_CLEAR_BYTE;
     return i;
 }
 
@@ -251,7 +255,15 @@ static int pnat_interface_check_mask(u32 sw_if_index,
     return 0;
 }
 
-int pnat_binding_add(pnat_5tuple_t *match, pnat_5tuple_t *rewrite, u32 *index) {
+/*
+ * Add a binding to the binding table.
+ * Returns 0 on success.
+ *  -1: Invalid mask
+ *  -2: Only matches ports for UDP or TCP
+ *
+ */
+int pnat_binding_add(pnat_match_tuple_t *match, pnat_rewrite_tuple_t *rewrite,
+                     u32 *index) {
     pnat_main_t *pm = &pnat_main;
 
     *index = -1;
@@ -272,21 +284,25 @@ int pnat_binding_add(pnat_5tuple_t *match, pnat_5tuple_t *rewrite, u32 *index) {
     memcpy(&t->post_sa, &rewrite->src, 4);
     t->post_sp = rewrite->sport;
     t->post_dp = rewrite->dport;
+    t->from_offset = rewrite->from_offset;
+    t->to_offset = rewrite->to_offset;
+    t->clear_offset = rewrite->clear_offset;
     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;
 }
+
+/*
+ * Looks a match flow in the flow cache, returns the index  in the binding table
+ */
 u32 pnat_flow_lookup(u32 sw_if_index, pnat_attachment_point_t attachment,
-                     pnat_5tuple_t *match) {
+                     pnat_match_tuple_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);
@@ -296,6 +312,14 @@ u32 pnat_flow_lookup(u32 sw_if_index, pnat_attachment_point_t attachment,
     return ~0;
 }
 
+/*
+ * Attach a binding to an interface / direction.
+ * Returns 0 on success.
+ * -1: Binding does not exist
+ * -2: Interface mask does not match
+ * -3: Existing match entry in flow table
+ * -4: Adding flow table entry failed
+ */
 int pnat_binding_attach(u32 sw_if_index, pnat_attachment_point_t attachment,
                         u32 binding_index) {
     pnat_main_t *pm = &pnat_main;
index af64fce..7ad87c8 100644 (file)
@@ -24,7 +24,8 @@
 
 /* Definitions from pnat.api */
 #include <pnat/pnat.api_types.h>
-typedef vl_api_pnat_5tuple_t pnat_5tuple_t;
+typedef vl_api_pnat_match_tuple_t pnat_match_tuple_t;
+typedef vl_api_pnat_rewrite_tuple_t pnat_rewrite_tuple_t;
 typedef vl_api_pnat_mask_t pnat_mask_t;
 typedef vl_api_pnat_attachment_point_t pnat_attachment_point_t;
 
@@ -35,6 +36,8 @@ typedef enum {
     PNAT_INSTR_SOURCE_PORT = 1 << 2,
     PNAT_INSTR_DESTINATION_ADDRESS = 1 << 3,
     PNAT_INSTR_DESTINATION_PORT = 1 << 4,
+    PNAT_INSTR_COPY_BYTE = 1 << 5,
+    PNAT_INSTR_CLEAR_BYTE = 1 << 6,
 } pnat_instructions_t;
 
 typedef struct {
@@ -52,9 +55,15 @@ typedef struct {
     u16 post_sp;
     u16 post_dp;
 
+    /* Byte copy inside of packet */
+    u8 from_offset;
+    u8 to_offset;
+
+    u8 clear_offset; /* Clear byte */
+
     /* Used for trace/show commands */
-    pnat_5tuple_t match;
-    pnat_5tuple_t rewrite;
+    pnat_match_tuple_t match;
+    pnat_rewrite_tuple_t rewrite;
 } pnat_translation_t;
 
 /* Interface object */
@@ -91,11 +100,11 @@ 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_match_tuple_t match;
+    pnat_rewrite_tuple_t rewrite;
 } pnat_trace_t;
 
-int pnat_binding_add(pnat_5tuple_t *match, pnat_5tuple_t *rewrite,
+int pnat_binding_add(pnat_match_tuple_t *match, pnat_rewrite_tuple_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,
@@ -103,7 +112,7 @@ int pnat_binding_attach(u32 sw_if_index, pnat_attachment_point_t attachment,
 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);
+                     pnat_match_tuple_t *match);
 
 static inline void
 pnat_calc_key(u32 sw_if_index, pnat_attachment_point_t attachment,
index 84dfd8d..082f077 100644 (file)
@@ -26,8 +26,8 @@
 /*
  * 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 *);
+u8 *format_pnat_match_tuple(u8 *s, va_list *args) {
+    pnat_match_tuple_t *t = va_arg(*args, pnat_match_tuple_t *);
     s = format(s, "{");
     if (t->mask & PNAT_SA)
         s = format(s, "%U", format_ip4_address, &t->src);
@@ -52,12 +52,38 @@ u8 *format_pnat_5tuple(u8 *s, va_list *args) {
     s = format(s, "}");
     return s;
 }
+u8 *format_pnat_rewrite_tuple(u8 *s, va_list *args) {
+    pnat_rewrite_tuple_t *t = va_arg(*args, pnat_rewrite_tuple_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->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, ":*");
+    if (t->mask & PNAT_COPY_BYTE)
+        s = format(s, " copy byte@[%d->%d]", t->from_offset, t->to_offset);
+    if (t->mask & PNAT_CLEAR_BYTE)
+        s = format(s, " clear byte@[%d]", t->clear_offset);
+    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);
+    s = format(s, "[%d] match: %U rewrite: %U", index, format_pnat_match_tuple,
+               &t->match, format_pnat_rewrite_tuple, &t->rewrite);
     return s;
 }
 
@@ -88,8 +114,8 @@ static u8 *format_pnat_interface(u8 *s, va_list *args) {
     return s;
 }
 
-uword unformat_pnat_5tuple(unformat_input_t *input, va_list *args) {
-    pnat_5tuple_t *t = va_arg(*args, pnat_5tuple_t *);
+uword unformat_pnat_match_tuple(unformat_input_t *input, va_list *args) {
+    pnat_match_tuple_t *t = va_arg(*args, pnat_match_tuple_t *);
     u32 dport, sport;
     while (1) {
         if (unformat(input, "src %U", unformat_ip4_address, &t->src))
@@ -114,6 +140,45 @@ uword unformat_pnat_5tuple(unformat_input_t *input, va_list *args) {
     return 1;
 }
 
+uword unformat_pnat_rewrite_tuple(unformat_input_t *input, va_list *args) {
+    pnat_rewrite_tuple_t *t = va_arg(*args, pnat_rewrite_tuple_t *);
+    u32 dport, sport;
+    u32 to_offset, from_offset, clear_offset;
+
+    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, "copy-byte-at-offset %d %d", &from_offset,
+                            &to_offset)) {
+            if (from_offset == to_offset || to_offset > 255 ||
+                from_offset > 255)
+                return 0;
+            t->mask |= PNAT_COPY_BYTE;
+            t->from_offset = from_offset;
+            t->to_offset = to_offset;
+        } else if (unformat(input, "clear-byte-at-offset %d", &clear_offset)) {
+            if (clear_offset > 255)
+                return 0;
+            t->mask |= PNAT_CLEAR_BYTE;
+            t->clear_offset = clear_offset;
+        } 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) {
@@ -123,17 +188,17 @@ static clib_error_t *set_pnat_translation_command_fn(vlib_main_t *vm,
     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};
+    pnat_match_tuple_t match = {0};
+    pnat_rewrite_tuple_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))
+        if (unformat(line_input, "match %U", unformat_pnat_match_tuple, &match))
             match_set = true;
-        else if (unformat(line_input, "rewrite %U", unformat_pnat_5tuple,
+        else if (unformat(line_input, "rewrite %U", unformat_pnat_rewrite_tuple,
                           &rewrite))
             rewrite_set = true;
         else if (unformat(line_input, "interface %U",
@@ -228,7 +293,7 @@ done:
 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]",
+                  "rewrite <tuple> {in|out} [del]",
     .function = set_pnat_translation_command_fn,
 };
 
index ec505cf..3277e6a 100644 (file)
@@ -74,12 +74,10 @@ VLIB_REGISTER_NODE(pnat_output_node) = {
 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"),
+    .runs_after = VNET_FEATURES("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"),
+    .runs_after = VNET_FEATURES("ip4-sv-reassembly-output-feature"),
 };
index 89acab0..595189c 100644 (file)
@@ -25,8 +25,8 @@
 /* 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);
+u8 *format_pnat_match_tuple(u8 *s, va_list *args);
+u8 *format_pnat_rewrite_tuple(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 *);
@@ -34,8 +34,10 @@ static inline u8 *format_pnat_trace(u8 *s, va_list *args) {
 
     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);
+        s = format(s, "        match: %U\n", format_pnat_match_tuple,
+                   &t->match);
+        s = format(s, "        rewrite: %U", format_pnat_rewrite_tuple,
+                   &t->rewrite);
     }
     return s;
 }
@@ -43,6 +45,7 @@ static inline u8 *format_pnat_trace(u8 *s, va_list *args) {
 /*
  * Given a packet and rewrite instructions from a translation modify packet.
  */
+// TODO: Generalize to write with mask
 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))
@@ -65,10 +68,22 @@ static u32 pnat_rewrite_ip4(u32 pool_index, ip4_header_t *ip) {
     ip_csum_t csum = ip->checksum;
     csum = ip_csum_sub_even(csum, csumd);
     ip->checksum = ip_csum_fold(csum);
+    if (ip->checksum == 0xffff)
+        ip->checksum = 0;
     ASSERT(ip->checksum == ip4_header_checksum(ip));
 
+    u16 plen = clib_net_to_host_u16(ip->length);
+
+    /* Nothing more to do if this is a fragment. */
+    if (ip4_is_fragment(ip))
+        return PNAT_ERROR_NONE;
+
     /* L4 ports */
     if (ip->protocol == IP_PROTOCOL_TCP) {
+        /* Assume IP4 header is 20 bytes */
+        if (plen < sizeof(ip4_header_t) + sizeof(tcp_header_t))
+            return PNAT_ERROR_TOOSHORT;
+
         tcp_header_t *tcp = ip4_next_header(ip);
         ip_csum_t l4csum = tcp->checksum;
         if (t->instructions & PNAT_INSTR_DESTINATION_PORT) {
@@ -84,6 +99,8 @@ static u32 pnat_rewrite_ip4(u32 pool_index, ip4_header_t *ip) {
         l4csum = ip_csum_sub_even(l4csum, csumd);
         tcp->checksum = ip_csum_fold(l4csum);
     } else if (ip->protocol == IP_PROTOCOL_UDP) {
+        if (plen < sizeof(ip4_header_t) + sizeof(udp_header_t))
+            return PNAT_ERROR_TOOSHORT;
         udp_header_t *udp = ip4_next_header(ip);
         ip_csum_t l4csum = udp->checksum;
         if (t->instructions & PNAT_INSTR_DESTINATION_PORT) {
@@ -101,6 +118,25 @@ static u32 pnat_rewrite_ip4(u32 pool_index, ip4_header_t *ip) {
             udp->checksum = ip_csum_fold(l4csum);
         }
     }
+    if (t->instructions & PNAT_INSTR_COPY_BYTE) {
+        /* Copy byte from somewhere in packet to elsewhere */
+
+        if (t->to_offset >= plen || t->from_offset > plen) {
+            return PNAT_ERROR_TOOSHORT;
+        }
+        u8 *p = (u8 *)ip;
+        p[t->to_offset] = p[t->from_offset];
+        ip->checksum = ip4_header_checksum(ip);
+        // TODO: L4 checksum
+    }
+    if (t->instructions & PNAT_INSTR_CLEAR_BYTE) {
+        /* Clear byte at offset */
+        u8 *p = (u8 *)ip;
+        p[t->clear_offset] = 0;
+        ip->checksum = ip4_header_checksum(ip);
+        // TODO: L4 checksum
+    }
+
     return PNAT_ERROR_NONE;
 }
 
@@ -109,6 +145,7 @@ static u32 pnat_rewrite_ip4(u32 pool_index, ip4_header_t *ip) {
  * If a binding is found, rewrite the packet according to instructions,
  * otherwise follow configured default action (forward, punt or drop)
  */
+// TODO: Make use of SVR configurable
 static_always_inline uword pnat_node_inline(vlib_main_t *vm,
                                             vlib_node_runtime_t *node,
                                             vlib_frame_t *frame,
@@ -156,9 +193,9 @@ static_always_inline uword pnat_node_inline(vlib_main_t *vm,
             /* Cache miss */
             *pi = ~0;
         }
-        next += 1;
 
         /*next: */
+        next += 1;
         n_left_from -= 1;
         b += 1;
         pi += 1;
diff --git a/src/plugins/nat/pnat/tests/missing_rule.def b/src/plugins/nat/pnat/tests/missing_rule.def
new file mode 100644 (file)
index 0000000..6213549
--- /dev/null
@@ -0,0 +1,8 @@
+[
+    (
+        "hit missing rule",
+        IP(src='1.1.1.1', dst='123.123.123.123')/UDP(chksum=0, sport=80, dport=6871),
+        IP(src='1.1.1.1', dst='123.123.123.123')/UDP(chksum=0, sport=80, dport=6871),
+        0
+    ),
+]
diff --git a/src/plugins/nat/pnat/tests/packets.def b/src/plugins/nat/pnat/tests/packets.def
new file mode 100644 (file)
index 0000000..b848664
--- /dev/null
@@ -0,0 +1,69 @@
+[
+    (
+        "da rewritten",
+        IP(src='1.1.1.1', dst='2.2.2.2')/UDP(sport=80, dport=6871),
+        IP(src='1.1.1.1', dst='1.2.3.4')/UDP(sport=80, dport=6871),
+        4242
+    ),
+    (
+        "unchanged",
+        IP(src='1.1.1.1', dst='2.2.2.2')/UDP(sport=80, dport=8080),
+        IP(src='1.1.1.1', dst='2.2.2.2')/UDP(sport=80, dport=8080),
+        4242
+    ),
+    (
+        "tcp da",
+        IP(src='1.1.1.1', dst='2.2.2.2')/TCP(sport=80, dport=6871),
+        IP(src='1.1.1.1', dst='1.2.3.4')/TCP(sport=80, dport=6871),
+        4242
+    ),
+    (
+        "tcp da ports",
+        IP(src='1.1.1.1', dst='2.2.2.2')/TCP(sport=80, dport=6872),
+        IP(src='1.1.1.1', dst='1.2.3.4')/TCP(sport=53, dport=8000),
+        4242
+    ),
+    (
+        "tcp da ports -- fragmented packet",
+        IP(src='1.1.1.1', dst='2.2.2.2', flags='MF', frag=100)/Raw('X' * 40),
+        IP(src='1.1.1.1', dst='2.2.2.2', flags='MF', frag=100)/Raw('X' * 40),
+        4242
+    ),
+    (
+        "short packet",
+        IP(src='1.1.1.1', dst='2.2.2.2'),
+        IP(src='1.1.1.1', dst='2.2.2.2'),
+        4242
+    ),
+    (
+        "copy byte",
+        IP(src='1.1.1.234', dst='2.2.2.2')/UDP(chksum=0, sport=80, dport=6874),
+        IP(src='1.1.1.234', dst='2.2.2.234')/UDP(chksum=0, sport=80, dport=6874),
+        4242
+    ),
+    (
+        "copy byte into Geneve reserved field",
+        IP(src='1.1.1.1', dst='2.2.2.2')/UDP(chksum=0, sport=80, dport=6877)/GENEVE(vni=4040),
+        IP(src='1.1.1.1', dst='1.2.3.4')/UDP(chksum=0, sport=80, dport=6877)/Raw(b'\x00\x00\x00\x00\x00\x0F\xC8\x01'),
+        4242
+    ),
+    (
+        "copy byte overrun",
+        IP(src='1.1.1.234', dst='2.2.2.2')/UDP(chksum=0, sport=80, dport=6875),
+        IP(src='1.1.1.234', dst='2.2.2.2')/UDP(chksum=0, sport=80, dport=6875),
+        0
+    ),
+    (
+        "clear byte",
+        IP(src='1.1.1.234', dst='2.2.2.2')/UDP(chksum=0, sport=80, dport=6876),
+        IP(src='1.1.1.234', dst='2.2.2.2')/UDP(chksum=0, sport=80, dport=220),
+        4242
+    ),
+    (
+        "packet with options",
+        IP(src='1.1.1.1', dst='2.2.2.2', options=[IPOption('\x07')])/TCP(sport=80, dport=6872),
+        IP(src='1.1.1.1', dst='1.2.3.4', options=[IPOption('\x07')])/TCP(sport=53, dport=8000),
+        4242
+    ),
+
+]
similarity index 62%
rename from src/plugins/nat/pnat/pnat_test.c
rename to src/plugins/nat/pnat/tests/pnat_test.c
index 762b4bd..ab55e7e 100644 (file)
@@ -23,7 +23,7 @@
 #include <vppinfra/bihash_16_8.h>
 #include <vppinfra/bihash_template.c>
 #include <vnet/fib/ip4_fib.h>
-#include "pnat.h"
+#include "../pnat.h"
 #include <pnat/pnat.api_enum.h> /* For error counters */
 #include <arpa/inet.h>
 #include "pnat_test_stubs.h"
@@ -50,10 +50,10 @@ static u32 *buffer_init(u32 *vector, int count) {
     }
     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, ...)                                                       \
@@ -61,13 +61,18 @@ vlib_node_runtime_t *node;
 #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, ...)                                                 \
+#define test_assert_log(A, M, ...)                                             \
     if (!(A)) {                                                                \
         log_error(M, ##__VA_ARGS__);                                           \
         assert(A);                                                             \
     } else {                                                                   \
         log_info(M, ##__VA_ARGS__);                                            \
     }
+#define test_assert(A, M, ...)                                                 \
+    if (!(A)) {                                                                \
+        log_error(M, ##__VA_ARGS__);                                           \
+        assert(A);                                                             \
+    }
 
 /*
  * Always return the frame of generated packets
@@ -113,53 +118,32 @@ vlib_buffer_t *test_vlib_get_buffer(u32 bi) {
 }
 
 /* Must be included here to allow the above functions to override */
-#include "pnat_node.h"
+#include "../pnat_node.h"
 
 /*** TESTS ***/
 
+typedef struct {
+    char *name;
+    int nsend;
+    char *send;
+    int nexpect;
+    char *expect;
+    u32 expect_next_index;
+} test_t;
+#include "test_packets.h"
+
+/* Rules */
 typedef struct {
     char *src;
     char *dst;
     u8 proto;
     u16 sport;
     u16 dport;
+    u8 from_offset;
+    u8 to_offset;
+    u8 clear_offset;
 } 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;
@@ -188,55 +172,55 @@ rule_t rules[] = {
         .rewrite = {.dst = "1.2.3.4", .sport = 53, .dport = 8000},
         .in = true,
     },
+    {
+        .match = {.dst = "2.2.2.2", .proto = 17, .dport = 6874},
+        .rewrite = {.from_offset = 15, .to_offset = 19},
+        .in = true,
+    },
+    {
+        .match = {.dst = "2.2.2.2", .proto = 17, .dport = 6875},
+        .rewrite = {.from_offset = 15, .to_offset = 50},
+        .in = true,
+    },
+    {
+        .match = {.dst = "2.2.2.2", .proto = 17, .dport = 6877},
+        .rewrite = {.dst = "1.2.3.4", .from_offset = 15, .to_offset = 35},
+        .in = true,
+    },
+    {
+        .match = {.dst = "2.2.2.2", .proto = 17, .dport = 6876},
+        .rewrite = {.clear_offset = 22},
+        .in = true,
+    },
 };
 
-static int fill_packets(vlib_main_t *vm, vlib_buffer_t *b,
-                        test_5tuple_t *test) {
+static int fill_packets(vlib_main_t *vm, vlib_buffer_t *b, int n, char *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) {
+
+    memcpy(ip, test, n);
+
+    /* Do the work of SVR */
+    vnet_buffer(b)->ip.reass.l4_src_port = 0;
+    vnet_buffer(b)->ip.reass.l4_dst_port = 0;
+    b->current_length = n;
+
+    if (ip4_is_fragment(ip))
+        return 0;
+    if (ip->protocol == 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) {
+    } else if (ip->protocol == 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) {
+static void ruletomatch(test_5tuple_t *r, pnat_match_tuple_t *t) {
     if (r->src) {
         inet_pton(AF_INET, r->src, &t->src);
         t->mask |= PNAT_SA;
@@ -256,12 +240,40 @@ static void ruleto5tuple(test_5tuple_t *r, pnat_5tuple_t *t) {
     t->proto = r->proto;
 }
 
+static void ruletorewrite(test_5tuple_t *r, pnat_rewrite_tuple_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;
+    }
+    if (r->to_offset || r->from_offset) {
+        t->to_offset = r->to_offset;
+        t->from_offset = r->from_offset;
+        t->mask |= PNAT_COPY_BYTE;
+    }
+    if (r->clear_offset) {
+        t->clear_offset = r->clear_offset;
+        t->mask |= PNAT_CLEAR_BYTE;
+    }
+}
+
 static void add_translation(rule_t *r) {
-    pnat_5tuple_t match = {0};
-    pnat_5tuple_t rewrite = {0};
+    pnat_match_tuple_t match = {0};
+    pnat_rewrite_tuple_t rewrite = {0};
 
-    ruleto5tuple(&r->match, &match);
-    ruleto5tuple(&r->rewrite, &rewrite);
+    ruletomatch(&r->match, &match);
+    ruletorewrite(&r->rewrite, &rewrite);
 
     int rv = pnat_binding_add(&match, &rewrite, &r->index);
     assert(rv == 0);
@@ -287,24 +299,47 @@ static void validate_packet(vlib_main_t *vm, char *name, u32 bi,
     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);
+    if (ip->protocol == IP_PROTOCOL_UDP || ip->protocol == IP_PROTOCOL_TCP) {
+        u32 flags = ip4_tcp_udp_validate_checksum(vm, b);
+        test_assert((flags & VNET_BUFFER_F_L4_CHECKSUM_CORRECT) != 0, "%s",
+                    name);
+        flags = ip4_tcp_udp_validate_checksum(vm, expected_b);
+        test_assert((flags & VNET_BUFFER_F_L4_CHECKSUM_CORRECT) != 0, "%s",
+                    name);
+    }
+    test_assert(b->current_length == expected_b->current_length, "%s %d vs %d",
+                name, b->current_length, expected_b->current_length);
+
+    if (memcmp(ip, expected_ip, b->current_length) != 0) {
+        if (ip->protocol == IP_PROTOCOL_UDP) {
+            udp_header_t *udp = ip4_next_header(ip);
+            clib_warning("Received: IP: %U UDP: %U", format_ip4_header, ip,
+                         sizeof(*ip), format_udp_header, udp, sizeof(*udp));
+            udp = ip4_next_header(expected_ip);
+            clib_warning("%U", format_hexdump, ip, b->current_length);
+            clib_warning("Expected: IP: %U UDP: %U", format_ip4_header,
+                         expected_ip, sizeof(*ip), format_udp_header, udp,
+                         sizeof(*udp));
+            clib_warning("%U", format_hexdump, expected_ip,
+                         expected_b->current_length);
+        } else if (ip->protocol == IP_PROTOCOL_TCP) {
+            tcp_header_t *tcp = ip4_next_header(ip);
+            clib_warning("Received IP: %U TCP: %U", format_ip4_header, ip,
+                         sizeof(*ip), format_tcp_header, tcp, sizeof(*tcp));
+            tcp = ip4_next_header(expected_ip);
+            clib_warning("Expected IP: %U TCP: %U", format_ip4_header,
+                         expected_ip, sizeof(*ip), format_tcp_header, tcp,
+                         sizeof(*tcp));
+        } else {
+            clib_warning("Received: IP: %U", format_ip4_header, ip,
+                         sizeof(*ip));
+            clib_warning("Expected: IP: %U", format_ip4_header, expected_ip,
+                         sizeof(*ip));
+        }
+        test_assert_log(0, "%s", name);
+    } else {
+        test_assert_log(1, "%s", name);
+    }
 }
 
 extern vlib_node_registration_t pnat_input_node;
@@ -317,8 +352,9 @@ static void test_table(test_t *t, int no_tests) {
     /* 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);
+        fill_packets(vm, (vlib_buffer_t *)&buffers[i], t[i].nsend, t[i].send);
+        fill_packets(vm, (vlib_buffer_t *)&expected[i], t[i].nexpect,
+                     t[i].expect);
     }
 
     /* send packets through graph node */
@@ -329,7 +365,7 @@ static void test_table(test_t *t, int no_tests) {
 
     /* verify tests */
     for (i = 0; i < no_tests; i++) {
-        assert(t[i].expect_next_index == results_next[i]);
+        test_assert(t[i].expect_next_index == results_next[i], "%s", t[i].name);
         validate_packet(vm, t[i].name, results_bi[i],
                         (vlib_buffer_t *)&expected[i]);
     }
@@ -337,7 +373,7 @@ static void test_table(test_t *t, int no_tests) {
     vec_free(results_bi);
 }
 
-static void test_performance(void) {
+void test_performance(void) {
     pnat_main_t *pm = &pnat_main;
     int i;
     vlib_main_t *vm = &vlib_global_main;
@@ -347,12 +383,13 @@ static void test_performance(void) {
     }
     assert(pool_elts(pm->translations) == sizeof(rules) / sizeof(rules[0]));
 
-    int no_tests = sizeof(tests) / sizeof(tests[0]);
+    int no_tests = sizeof(tests_packets) / sizeof(tests_packets[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);
+                     tests_packets[i % no_tests].nsend,
+                     tests_packets[i % no_tests].send);
         // fill_packets(vm, (vlib_buffer_t *)&expected[i], &tests[i %
         // no_tests].expect);
     }
@@ -382,7 +419,7 @@ static void test_performance(void) {
     assert(pool_elts(pm->interfaces) == 0);
 }
 
-static void test_packets(void) {
+void test_packets(void) {
     pnat_main_t *pm = &pnat_main;
     int i;
     for (i = 0; i < sizeof(rules) / sizeof(rules[0]); i++) {
@@ -390,7 +427,7 @@ static void test_packets(void) {
     }
     assert(pool_elts(pm->translations) == sizeof(rules) / sizeof(rules[0]));
 
-    test_table(tests, sizeof(tests) / sizeof(tests[0]));
+    test_table(tests_packets, sizeof(tests_packets) / sizeof(tests_packets[0]));
 
     for (i = 0; i < sizeof(rules) / sizeof(rules[0]); i++) {
         del_translation(&rules[i]);
@@ -398,6 +435,7 @@ static void test_packets(void) {
     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;
@@ -408,8 +446,8 @@ static void test_attach(void) {
     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};
+    pnat_match_tuple_t match = {.mask = PNAT_SA};
+    pnat_rewrite_tuple_t rewrite = {.mask = PNAT_SA};
     rv = pnat_binding_add(&match, &rewrite, &binding_index);
     assert(rv == 0);
 
@@ -441,24 +479,17 @@ static void test_del_before_detach(void) {
     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);
+    test_table(&tests_missing_rule[0], 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);
+    pnat_match_tuple_t match = {0};
+    pnat_rewrite_tuple_t rewrite = {0};
+    ruletomatch(&rule.match, &match);
+    ruletorewrite(&rule.rewrite, &rewrite);
     rv = pnat_binding_add(&match, &rewrite, &binding_index);
     assert(rv == 0);
     rv = pnat_binding_detach(sw_if_index, attachment, binding_index);
@@ -467,11 +498,56 @@ static void test_del_before_detach(void) {
     assert(rv == 0);
 }
 
-static void test_api(void) {
+void test_api(void) {
     test_attach();
     test_del_before_detach();
 }
 
+void test_checksum(void) {
+    int i;
+    vlib_main_t *vm = &vlib_global_main;
+    pnat_main_t *pm = &pnat_main;
+
+    test_t test = {
+        .name = "checksum",
+        .nsend = 28,
+        .send =
+            (char[]){0x45, 0x00, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+                     0x74, 0xcb, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02,
+                     0x00, 0x50, 0x1a, 0xd7, 0x00, 0x08, 0xde, 0xb1},
+    };
+
+    for (i = 0; i < sizeof(rules) / sizeof(rules[0]); i++) {
+        add_translation(&rules[i]);
+    }
+    assert(pool_elts(pm->translations) == sizeof(rules) / sizeof(rules[0]));
+
+    /* send packets through graph node */
+    vlib_frame_t frame = {.n_vectors = 1};
+    node->flags |= VLIB_NODE_FLAG_TRACE;
+
+    ip4_header_t *ip =
+        (ip4_header_t *)vlib_buffer_get_current((vlib_buffer_t *)&buffers[0]);
+
+    for (i = 0; i < 65535; i++) {
+
+        /* Get a buffer. Loop through 64K variations of it to check checksum */
+        memset(&buffers[0], 0, 2048);
+        fill_packets(vm, (vlib_buffer_t *)&buffers[0], test.nsend, test.send);
+
+        ip->src_address.as_u32 = i;
+        ip->checksum = 0;
+        ip->checksum = ip4_header_checksum(ip);
+        pnat_node_inline(vm, node, &frame, PNAT_IP4_INPUT, VLIB_RX);
+    }
+
+    test_assert_log(1, "%s", test.name);
+
+    for (i = 0; i < sizeof(rules) / sizeof(rules[0]); i++) {
+        del_translation(&rules[i]);
+    }
+}
+
 /*
  * Unit testing:
  * 1) Table of packets and expected outcomes. Run through
@@ -497,8 +573,15 @@ int main(int argc, char **argv) {
 
     /* Test API */
     test_api();
-
     test_packets();
-
+    test_checksum();
     test_performance();
 }
+
+/*
+ * NEW TESTS:
+ * - Chained buffers. Only do rewrite in first buffer
+ * - No interface. Can that really happen?
+ * - IP length shorter than buffer.
+ * - IP length longer than buffer.
+ */
similarity index 90%
rename from src/plugins/nat/pnat/pnat_test_stubs.h
rename to src/plugins/nat/pnat/tests/pnat_test_stubs.h
index 2801398..62aa050 100644 (file)
@@ -35,7 +35,8 @@ u8 *format_ip4_address(u8 *s, va_list *args) {
     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; }
+u8 *format_pnat_match_tuple(u8 *s, va_list *args) { return 0; }
+u8 *format_pnat_rewrite_tuple(u8 *s, va_list *args) { return 0; }
 
 vl_counter_t pnat_error_counters[10];
 
@@ -142,6 +143,28 @@ u8 *format_tcp_header(u8 *s, va_list *args) {
                clib_net_to_host_u16(tcp->checksum));
     return s;
 }
+/* Format UDP header. */
+u8 *format_udp_header(u8 *s, va_list *args) {
+    udp_header_t *udp = va_arg(*args, udp_header_t *);
+    u32 max_header_bytes = va_arg(*args, u32);
+    u32 indent;
+
+    /* Nothing to do. */
+    if (max_header_bytes < sizeof(udp[0]))
+        return format(s, "UDP header truncated");
+
+    indent = format_get_indent(s);
+    indent += 2;
+
+    s = format(s, "UDP: %d -> %d", clib_net_to_host_u16(udp->src_port),
+               clib_net_to_host_u16(udp->dst_port));
+
+    s = format(s, "\n%Ulength %d, checksum 0x%04x", format_white_space, indent,
+               clib_net_to_host_u16(udp->length),
+               clib_net_to_host_u16(udp->checksum));
+
+    return s;
+}
 
 /* Format an IP4 header. */
 u8 *format_ip4_header(u8 *s, va_list *args) {
diff --git a/src/plugins/nat/pnat/tests/test_genpackets.py b/src/plugins/nat/pnat/tests/test_genpackets.py
new file mode 100755 (executable)
index 0000000..9d32d3e
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+from importlib.machinery import SourceFileLoader
+from scapy.all import *
+from scapy.contrib.geneve import GENEVE
+
+def hexstring(p):
+    s = bytes(p.__class__(p))
+    return ",".join("0x{:02x}".format(c) for c in s)
+
+def output_test(filename, tests):
+    (name, ext) = os.path.basename(filename).split(".")
+    print('/* DO NOT EDIT: automatically generated by test_genpackets.py */')
+    print('/* clang-format off */')
+    print('test_t tests_{}[] = {{'.format(name))
+
+    for t in tests:
+        print('  {')
+        print('    .name = "{}",'.format(t[0]))
+        print('    .nsend = {},'.format(len(t[1])))
+        print('    .send = (char []){{{}}},'.format(hexstring(t[1])))
+        print('    .nexpect = {},'.format(len(t[2])))
+        print('    .expect = (char []){{{}}},'.format(hexstring(t[2])))
+        print('    .expect_next_index = {}'.format(t[3]))
+        print('  },')
+    print('};')
+    print('/* clang-format on */')
+
+# Read tests from file
+for filename in sys.argv[1:]:
+    with open(filename) as f:
+        content = f.read().replace('\n', '')
+
+    tests = eval(content)
+    output_test(filename, tests)
+
diff --git a/src/plugins/nat/pnat/tests/test_packets.h b/src/plugins/nat/pnat/tests/test_packets.h
new file mode 100644 (file)
index 0000000..b010450
--- /dev/null
@@ -0,0 +1,106 @@
+/* DO NOT EDIT: automatically generated by test_genpackets.py */
+/* clang-format off */
+test_t tests_packets[] = {
+  {
+    .name = "da rewritten",
+    .nsend = 28,
+    .send = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x74,0xcb,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x00,0x50,0x1a,0xd7,0x00,0x08,0xde,0xb1},
+    .nexpect = 28,
+    .expect = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x74,0xc9,0x01,0x01,0x01,0x01,0x01,0x02,0x03,0x04,0x00,0x50,0x1a,0xd7,0x00,0x08,0xde,0xaf},
+    .expect_next_index = 4242
+  },
+  {
+    .name = "unchanged",
+    .nsend = 28,
+    .send = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x74,0xcb,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x00,0x50,0x1f,0x90,0x00,0x08,0xd9,0xf8},
+    .nexpect = 28,
+    .expect = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x74,0xcb,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x00,0x50,0x1f,0x90,0x00,0x08,0xd9,0xf8},
+    .expect_next_index = 4242
+  },
+  {
+    .name = "tcp da",
+    .nsend = 40,
+    .send = (char []){0x45,0x00,0x00,0x28,0x00,0x01,0x00,0x00,0x40,0x06,0x74,0xca,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x00,0x50,0x1a,0xd7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x02,0x20,0x00,0x6e,0xb6,0x00,0x00},
+    .nexpect = 40,
+    .expect = (char []){0x45,0x00,0x00,0x28,0x00,0x01,0x00,0x00,0x40,0x06,0x74,0xc8,0x01,0x01,0x01,0x01,0x01,0x02,0x03,0x04,0x00,0x50,0x1a,0xd7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x02,0x20,0x00,0x6e,0xb4,0x00,0x00},
+    .expect_next_index = 4242
+  },
+  {
+    .name = "tcp da ports",
+    .nsend = 40,
+    .send = (char []){0x45,0x00,0x00,0x28,0x00,0x01,0x00,0x00,0x40,0x06,0x74,0xca,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x00,0x50,0x1a,0xd8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x02,0x20,0x00,0x6e,0xb5,0x00,0x00},
+    .nexpect = 40,
+    .expect = (char []){0x45,0x00,0x00,0x28,0x00,0x01,0x00,0x00,0x40,0x06,0x74,0xc8,0x01,0x01,0x01,0x01,0x01,0x02,0x03,0x04,0x00,0x35,0x1f,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x02,0x20,0x00,0x6a,0x66,0x00,0x00},
+    .expect_next_index = 4242
+  },
+  {
+    .name = "tcp da ports -- fragmented packet",
+    .nsend = 60,
+    .send = (char []){0x45,0x00,0x00,0x3c,0x00,0x01,0x20,0x64,0x40,0x00,0x54,0x58,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58},
+    .nexpect = 60,
+    .expect = (char []){0x45,0x00,0x00,0x3c,0x00,0x01,0x20,0x64,0x40,0x00,0x54,0x58,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58},
+    .expect_next_index = 4242
+  },
+  {
+    .name = "short packet",
+    .nsend = 20,
+    .send = (char []){0x45,0x00,0x00,0x14,0x00,0x01,0x00,0x00,0x40,0x00,0x74,0xe4,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02},
+    .nexpect = 20,
+    .expect = (char []){0x45,0x00,0x00,0x14,0x00,0x01,0x00,0x00,0x40,0x00,0x74,0xe4,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02},
+    .expect_next_index = 4242
+  },
+  {
+    .name = "copy byte",
+    .nsend = 28,
+    .send = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x73,0xe2,0x01,0x01,0x01,0xea,0x02,0x02,0x02,0x02,0x00,0x50,0x1a,0xda,0x00,0x08,0x00,0x00},
+    .nexpect = 28,
+    .expect = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x72,0xfa,0x01,0x01,0x01,0xea,0x02,0x02,0x02,0xea,0x00,0x50,0x1a,0xda,0x00,0x08,0x00,0x00},
+    .expect_next_index = 4242
+  },
+  {
+    .name = "copy byte into Geneve reserved field",
+    .nsend = 36,
+    .send = (char []){0x45,0x00,0x00,0x24,0x00,0x01,0x00,0x00,0x40,0x11,0x74,0xc3,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x00,0x50,0x1a,0xdd,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xc8,0x00},
+    .nexpect = 36,
+    .expect = (char []){0x45,0x00,0x00,0x24,0x00,0x01,0x00,0x00,0x40,0x11,0x74,0xc1,0x01,0x01,0x01,0x01,0x01,0x02,0x03,0x04,0x00,0x50,0x1a,0xdd,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xc8,0x01},
+    .expect_next_index = 4242
+  },
+  {
+    .name = "copy byte overrun",
+    .nsend = 28,
+    .send = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x73,0xe2,0x01,0x01,0x01,0xea,0x02,0x02,0x02,0x02,0x00,0x50,0x1a,0xdb,0x00,0x08,0x00,0x00},
+    .nexpect = 28,
+    .expect = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x73,0xe2,0x01,0x01,0x01,0xea,0x02,0x02,0x02,0x02,0x00,0x50,0x1a,0xdb,0x00,0x08,0x00,0x00},
+    .expect_next_index = 0
+  },
+  {
+    .name = "clear byte",
+    .nsend = 28,
+    .send = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x73,0xe2,0x01,0x01,0x01,0xea,0x02,0x02,0x02,0x02,0x00,0x50,0x1a,0xdc,0x00,0x08,0x00,0x00},
+    .nexpect = 28,
+    .expect = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x73,0xe2,0x01,0x01,0x01,0xea,0x02,0x02,0x02,0x02,0x00,0x50,0x00,0xdc,0x00,0x08,0x00,0x00},
+    .expect_next_index = 4242
+  },
+  {
+    .name = "packet with options",
+    .nsend = 44,
+    .send = (char []){0x46,0x00,0x00,0x2c,0x00,0x01,0x00,0x00,0x40,0x06,0x6c,0xc6,0x01,0x01,0x01,0x01,0x02,0x02,0x02,0x02,0x07,0x00,0x00,0x00,0x00,0x50,0x1a,0xd8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x02,0x20,0x00,0x6e,0xb5,0x00,0x00},
+    .nexpect = 44,
+    .expect = (char []){0x46,0x00,0x00,0x2c,0x00,0x01,0x00,0x00,0x40,0x06,0x6c,0xc4,0x01,0x01,0x01,0x01,0x01,0x02,0x03,0x04,0x07,0x00,0x00,0x00,0x00,0x35,0x1f,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x02,0x20,0x00,0x6a,0x66,0x00,0x00},
+    .expect_next_index = 4242
+  },
+};
+/* clang-format on */
+/* DO NOT EDIT: automatically generated by test_genpackets.py */
+/* clang-format off */
+test_t tests_missing_rule[] = {
+  {
+    .name = "hit missing rule",
+    .nsend = 28,
+    .send = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x81,0xd8,0x01,0x01,0x01,0x01,0x7b,0x7b,0x7b,0x7b,0x00,0x50,0x1a,0xd7,0x00,0x08,0x00,0x00},
+    .nexpect = 28,
+    .expect = (char []){0x45,0x00,0x00,0x1c,0x00,0x01,0x00,0x00,0x40,0x11,0x81,0xd8,0x01,0x01,0x01,0x01,0x7b,0x7b,0x7b,0x7b,0x00,0x50,0x1a,0xd7,0x00,0x08,0x00,0x00},
+    .expect_next_index = 0
+  },
+};
+/* clang-format on */