Punt Infra 48/15148/15
authorNeale Ranns <nranns@cisco.com>
Fri, 28 Sep 2018 15:16:14 +0000 (15:16 +0000)
committerDamjan Marion <dmarion@me.com>
Thu, 28 Mar 2019 20:51:32 +0000 (20:51 +0000)
A punt/exception path that provides:
 1) clients that use the infra
 2) clients can create punt reasons
 3) clients can register to recieve packets that are punted
    for a given reason to be sent to the desired node.
 4) nodes which punt packets fill in the {reason,protocol} of the
    buffere (in the meta-data) and send to the new node "punt-dispatch"
 5) punt-dispatch sends packets to the registered nodes or drops

Change-Id: Ia4f144337f1387cbe585b4f375d0842aefffcde5
Signed-off-by: Neale Ranns <nranns@cisco.com>
14 files changed:
src/plugins/gbp/gbp_vxlan.c
src/plugins/unittest/CMakeLists.txt
src/plugins/unittest/punt_test.c [new file with mode: 0644]
src/vlib/CMakeLists.txt
src/vlib/buffer.h
src/vlib/punt.c [new file with mode: 0644]
src/vlib/punt.h [new file with mode: 0644]
src/vlib/punt_node.c [new file with mode: 0644]
src/vlib/vlib.h
src/vnet/ip/punt.c
src/vnet/vxlan-gbp/decap.c
src/vnet/vxlan-gbp/vxlan_gbp.c
src/vnet/vxlan-gbp/vxlan_gbp.h
test/test_punt.py

index 286345e..49dae7e 100644 (file)
@@ -22,6 +22,7 @@
 #include <vnet/vxlan-gbp/vxlan_gbp.h>
 #include <vlibmemory/api.h>
 #include <vnet/fib/fib_table.h>
+#include <vlib/punt.h>
 
 /**
  * A reference to a VXLAN-GBP tunnel created as a child/dependent tunnel
@@ -66,6 +67,10 @@ index_t *gbp_vxlan_tunnel_db;
  */
 index_t *vxlan_tunnel_ref_db;
 
+/**
+ * handle registered with the ;unt infra
+ */
+static vlib_punt_hdl_t punt_hdl;
 
 static char *gbp_vxlan_tunnel_layer_strings[] = {
 #define _(n,s) [GBP_VXLAN_TUN_##n] = s,
@@ -672,28 +677,23 @@ VLIB_CLI_COMMAND (gbp_vxlan_show_node, static) = {
 static clib_error_t *
 gbp_vxlan_init (vlib_main_t * vm)
 {
-  u32 slot4;
-  vlib_node_t *node = vlib_get_node_by_name (vm, (u8 *) "gbp-vxlan4");
-
-  /*
-   * insert ourselves into the VXLAN-GBP arc to collect the no-tunnel
-   * packets.
-   */
-  slot4 = vlib_node_add_next_with_slot (vm,
-                                       vxlan4_gbp_input_node.index,
-                                       node->index,
-                                       VXLAN_GBP_INPUT_NEXT_NO_TUNNEL);
-  ASSERT (slot4 == VXLAN_GBP_INPUT_NEXT_NO_TUNNEL);
-
-  /* slot6 = vlib_node_add_next_with_slot (vm, */
-  /*                                    vxlan6_gbp_input_node.index, */
-  /*                                    gbp_vxlan6_input_node.index, */
-  /*                                    VXLAN_GBP_INPUT_NEXT_NO_TUNNEL); */
-  /* ASSERT (slot6 == VXLAN_GBP_INPUT_NEXT_NO_TUNNEL); */
+  vxlan_gbp_main_t *vxm = &vxlan_gbp_main;
+  clib_error_t *error;
 
   gt_logger = vlib_log_register_class ("gbp", "tun");
 
-  return (NULL);
+  if ((error = vlib_call_init_function (vm, punt_init)))
+    return error;
+  if ((error = vlib_call_init_function (vm, vxlan_gbp_init)))
+    return error;
+
+  punt_hdl = vlib_punt_client_register ("gbp-vxlan");
+
+  vlib_punt_register (punt_hdl,
+                     vxm->punt_no_such_tunnel[FIB_PROTOCOL_IP4],
+                     "gbp-vxlan4");
+
+  return (error);
 }
 
 VLIB_INIT_FUNCTION (gbp_vxlan_init);
index 74bcef3..0af0dea 100644 (file)
@@ -24,6 +24,7 @@ add_vpp_plugin(unittest
   ipsec_test.c
   interface_test.c
   mfib_test.c
+  punt_test.c
   session_test.c
   string_test.c
   tcp_test.c
diff --git a/src/plugins/unittest/punt_test.c b/src/plugins/unittest/punt_test.c
new file mode 100644 (file)
index 0000000..3806dae
--- /dev/null
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2018 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 <vnet/vnet.h>
+#include <vnet/adj/rewrite.h>
+#include <vnet/ethernet/ethernet.h>
+#include <vnet/adj/adj.h>
+#include <vnet/ip/ip.h>
+
+typedef enum punt_next_t_
+{
+  PUNT_NEXT_DROP,
+  PUNT_N_NEXT,
+} punt_next_t;
+
+typedef struct punt_trace_t_
+{
+  vlib_punt_reason_t pt_reason;
+} punt_trace_t;
+
+/**
+ * Per-thread clone vectors
+ */
+u32 **punt_clones;
+
+#define SW_IF_INDEX_PG0 1
+#define SW_IF_INDEX_PG1 2
+
+index_t *adjs[FIB_PROTOCOL_IP_MAX];
+
+static vlib_punt_reason_t punt_reason_v4, punt_reason_v6;
+static vlib_punt_hdl_t punt_hdl;
+
+static u8 *
+format_punt_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 *);
+  punt_trace_t *t = va_arg (*args, punt_trace_t *);
+
+  s = format (s, "punt: %U", format_vlib_punt_reason, t->pt_reason);
+
+  return s;
+}
+
+always_inline uword
+punt_test_fwd (vlib_main_t * vm,
+              vlib_node_runtime_t * node,
+              vlib_frame_t * frame, fib_protocol_t fproto, u32 sw_if_index)
+{
+  u32 n_left_from, *from, *to_next, next_index;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  next_index = node->cached_next_index;
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+         ip_adjacency_t *adj0;
+         vlib_buffer_t *b0;
+         void *ip0;
+         index_t ai0;
+         u32 bi0;
+
+         bi0 = to_next[0] = from[0];
+         from += 1;
+         to_next += 1;
+         n_left_to_next -= 1;
+         n_left_from -= 1;
+
+         b0 = vlib_get_buffer (vm, bi0);
+         vnet_buffer (b0)->sw_if_index[VLIB_TX] = sw_if_index;
+         ai0 = adjs[fproto][sw_if_index];
+
+         adj0 = adj_get (ai0);
+         ip0 = vlib_buffer_get_current (b0);
+
+         vlib_buffer_advance (b0, -adj0->rewrite_header.data_bytes);
+         vnet_rewrite_one_header (adj0[0], ip0, sizeof (ethernet_header_t));
+
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+                                          to_next, n_left_to_next, bi0, 0);
+       }
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  return frame->n_vectors;
+}
+
+always_inline uword
+punt_test_pg0_ip4 (vlib_main_t * vm,
+                  vlib_node_runtime_t * node, vlib_frame_t * frame)
+{
+  return (punt_test_fwd (vm, node, frame, FIB_PROTOCOL_IP4, SW_IF_INDEX_PG0));
+}
+
+always_inline uword
+punt_test_pg1_ip4 (vlib_main_t * vm,
+                  vlib_node_runtime_t * node, vlib_frame_t * frame)
+{
+  return (punt_test_fwd (vm, node, frame, FIB_PROTOCOL_IP4, SW_IF_INDEX_PG1));
+}
+
+always_inline uword
+punt_test_pg0_ip6 (vlib_main_t * vm,
+                  vlib_node_runtime_t * node, vlib_frame_t * frame)
+{
+  return (punt_test_fwd (vm, node, frame, FIB_PROTOCOL_IP6, SW_IF_INDEX_PG0));
+}
+
+always_inline uword
+punt_test_pg1_ip6 (vlib_main_t * vm,
+                  vlib_node_runtime_t * node, vlib_frame_t * frame)
+{
+  return (punt_test_fwd (vm, node, frame, FIB_PROTOCOL_IP6, SW_IF_INDEX_PG1));
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (punt_test_pg0_ip4_node) = {
+  .function = punt_test_pg0_ip4,
+  .name = "punt-test-pg0-ip4",
+  .vector_size = sizeof (u32),
+  .format_trace = format_punt_trace,
+};
+VLIB_REGISTER_NODE (punt_test_pg1_ip4_node) = {
+  .function = punt_test_pg1_ip4,
+  .name = "punt-test-pg1-ip4",
+  .vector_size = sizeof (u32),
+  .format_trace = format_punt_trace,
+};
+VLIB_REGISTER_NODE (punt_test_pg0_ip6_node) = {
+  .function = punt_test_pg0_ip6,
+  .name = "punt-test-pg0-ip6",
+  .vector_size = sizeof (u32),
+  .format_trace = format_punt_trace,
+};
+VLIB_REGISTER_NODE (punt_test_pg1_ip6_node) = {
+  .function = punt_test_pg1_ip6,
+  .name = "punt-test-pg1-ip6",
+  .vector_size = sizeof (u32),
+  .format_trace = format_punt_trace,
+};
+/* *INDENT-ON* */
+
+typedef struct punt_feat_trace_t_
+{
+  vlib_punt_reason_t pt_reason;
+} punt_feat_trace_t;
+
+always_inline uword
+punt_test_feat_inline (vlib_main_t * vm,
+                      vlib_node_runtime_t * node,
+                      vlib_frame_t * frame, u8 is_ip4)
+{
+  u32 n_left_from, *from, *to_next, next_index;
+
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  next_index = node->cached_next_index;
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+         vlib_buffer_t *b0;
+         u32 bi0, next0;
+
+         bi0 = to_next[0] = from[0];
+         from += 1;
+         to_next += 1;
+         n_left_to_next -= 1;
+         n_left_from -= 1;
+         next0 = 0;
+
+         b0 = vlib_get_buffer (vm, bi0);
+
+         if (is_ip4)
+           b0->punt_reason = punt_reason_v4;
+         else
+           b0->punt_reason = punt_reason_v6;
+
+         if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
+           {
+             punt_feat_trace_t *t;
+
+             b0 = vlib_get_buffer (vm, bi0);
+
+             t = vlib_add_trace (vm, node, b0, sizeof (*t));
+             t->pt_reason = b0->punt_reason;
+           }
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+                                          to_next, n_left_to_next,
+                                          bi0, next0);
+       }
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  return frame->n_vectors;
+}
+
+static u8 *
+format_punt_feat_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 *);
+  punt_feat_trace_t *t = va_arg (*args, punt_feat_trace_t *);
+
+  s = format (s, "reason: %U", format_vlib_punt_reason, t->pt_reason);
+
+  return s;
+}
+
+always_inline uword
+punt_test_feat_ip4 (vlib_main_t * vm,
+                   vlib_node_runtime_t * node, vlib_frame_t * frame)
+{
+  return (punt_test_feat_inline (vm, node, frame, 1));
+}
+
+always_inline uword
+punt_test_feat_ip6 (vlib_main_t * vm,
+                   vlib_node_runtime_t * node, vlib_frame_t * frame)
+{
+  return (punt_test_feat_inline (vm, node, frame, 0));
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (punt_test_feat_ip6_node) = {
+  .function = punt_test_feat_ip6,
+  .name = "punt-test-feat-ip6",
+  .vector_size = sizeof (u32),
+  .format_trace = format_punt_feat_trace,
+  .n_next_nodes = 1,
+  .next_nodes = {
+    [0] = "punt-dispatch"
+  }
+};
+VLIB_REGISTER_NODE (punt_test_feat_ip4_node) = {
+  .function = punt_test_feat_ip4,
+  .name = "punt-test-feat-ip4",
+  .vector_size = sizeof (u32),
+  .format_trace = format_punt_feat_trace,
+  .n_next_nodes = 1,
+  .next_nodes = {
+    [0] = "punt-dispatch"
+  }
+};
+VNET_FEATURE_INIT (punt_test_feat_ip6_feature, static) =
+{
+  .arc_name = "ip6-unicast",
+  .node_name = "punt-test-feat-ip6",
+};
+VNET_FEATURE_INIT (punt_test_feat_ip4_feature, static) =
+{
+  .arc_name = "ip4-unicast",
+  .node_name = "punt-test-feat-ip4",
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+punt_test (vlib_main_t * vm,
+          unformat_input_t * input, vlib_cli_command_t * cmd_arg)
+{
+  ip46_address_t ip46 = ip46_address_initializer;
+  fib_protocol_t fproto;
+  vnet_main_t *vnm;
+  u32 sw_if_index;
+  int rc;
+
+  vnm = vnet_get_main ();
+  fproto = FIB_PROTOCOL_IP4;
+
+  if (unformat (input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index))
+    {
+      vlib_node_t *from;
+
+      if (unformat (input, "%U", unformat_ip4_address, &ip46.ip4))
+       {
+         fproto = FIB_PROTOCOL_IP4;
+       }
+      else if (unformat (input, "%U", unformat_ip6_address, &ip46.ip6))
+       {
+         fproto = FIB_PROTOCOL_IP6;
+       }
+      else if (unformat (input, "clear"))
+       {
+         vnet_feature_enable_disable ("ip4-unicast",
+                                      "punt-test-feat-ip4",
+                                      sw_if_index, 0, NULL, 0);
+         vnet_feature_enable_disable ("ip6-unicast",
+                                      "punt-test-feat-ip6",
+                                      sw_if_index, 0, NULL, 0);
+         return NULL;
+       }
+      else
+       {
+         /*
+          * allocate a client and a reason
+          */
+         punt_hdl = vlib_punt_client_register ("test");
+
+         rc =
+           vlib_punt_reason_alloc (punt_hdl, "reason-v4", &punt_reason_v4);
+         rc |=
+           vlib_punt_reason_alloc (punt_hdl, "reason-v6", &punt_reason_v6);
+         ASSERT (!rc);
+
+         vnet_feature_enable_disable ("ip4-unicast",
+                                      "punt-test-feat-ip4",
+                                      sw_if_index, 1, NULL, 0);
+         vnet_feature_enable_disable ("ip6-unicast",
+                                      "punt-test-feat-ip6",
+                                      sw_if_index, 1, NULL, 0);
+         return NULL;
+       }
+
+      if (SW_IF_INDEX_PG0 == sw_if_index)
+       {
+         if (FIB_PROTOCOL_IP4 == fproto)
+           {
+             /*
+              * register the node that will forward the punted packet
+              */
+             vlib_punt_register (punt_hdl, punt_reason_v4,
+                                 "punt-test-pg0-ip4");
+             from = vlib_get_node_by_name (vm, (u8 *) "punt-test-pg0-ip4");
+           }
+         else
+           {
+             vlib_punt_register (punt_hdl, punt_reason_v6,
+                                 "punt-test-pg0-ip6");
+             from = vlib_get_node_by_name (vm, (u8 *) "punt-test-pg0-ip6");
+           }
+       }
+      else
+       {
+         if (FIB_PROTOCOL_IP4 == fproto)
+           {
+             vlib_punt_register (punt_hdl, punt_reason_v4,
+                                 "punt-test-pg1-ip4");
+             from = vlib_get_node_by_name (vm, (u8 *) "punt-test-pg1-ip4");
+           }
+         else
+           {
+             vlib_punt_register (punt_hdl, punt_reason_v6,
+                                 "punt-test-pg1-ip6");
+             from = vlib_get_node_by_name (vm, (u8 *) "punt-test-pg1-ip6");
+           }
+       }
+
+      vlib_node_add_next (vm, from->index,
+                         vnet_tx_node_index_for_sw_interface
+                         (vnm, sw_if_index));
+
+      vec_validate (adjs[fproto], sw_if_index);
+
+      adjs[fproto][sw_if_index] = adj_nbr_find (fproto,
+                                               fib_proto_to_link (fproto),
+                                               &ip46, sw_if_index);
+    }
+
+  return (NULL);
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (test_fib_command, static) =
+{
+  .path = "test punt",
+  .short_help = "punt unit tests - DO NOT RUN ON A LIVE SYSTEM",
+  .function = punt_test,
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
index ff64bae..acd6635 100644 (file)
@@ -63,6 +63,8 @@ add_vpp_library(vlib
   node_format.c
   pci/pci.c
   physmem.c
+  punt.c
+  punt_node.c
   threads.c
   threads_cli.c
   trace.c
@@ -77,6 +79,7 @@ add_vpp_library(vlib
 
   MULTIARCH_SOURCES
   drop.c
+  punt_node.c
 
   INSTALL_HEADERS
   buffer_funcs.h
@@ -102,6 +105,7 @@ add_vpp_library(vlib
   pci/pci.h
   physmem_funcs.h
   physmem.h
+  punt.h
   threads.h
   trace_funcs.h
   trace.h
index 514e73b..0fd57f5 100644 (file)
@@ -139,8 +139,15 @@ typedef union
       * VLIB_BUFFER_NEXT_PRESENT flag is set. */
     u32 next_buffer;
 
-    /** Used by feature subgraph arcs to visit enabled feature nodes */
-    u32 current_config_index;
+    /** The following fields can be in a union because once a packet enters
+     * the punt path, it is no longer on a feature arc */
+    union
+    {
+      /** Used by feature subgraph arcs to visit enabled feature nodes */
+      u32 current_config_index;
+      /* the reason the packet once punted */
+      u32 punt_reason;
+    };
 
     /** Opaque data used by sub-graphs for their own purposes. */
     u32 opaque[10];
diff --git a/src/vlib/punt.c b/src/vlib/punt.c
new file mode 100644 (file)
index 0000000..09f30f4
--- /dev/null
@@ -0,0 +1,629 @@
+/*
+ * Copyright (c) 2018 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 <vlib/punt.h>
+
+/**
+ * The last allocated punt reason
+ */
+static vlib_punt_reason_t punt_reason_last;
+
+/**
+ * Counters per punt-reason
+ */
+vlib_combined_counter_main_t punt_counters = {
+  .name = "punt",
+  .stat_segment_name = "/net/punt",
+};
+
+/**
+ * A punt reason
+ */
+typedef struct punt_reason_data_t_
+{
+  /**
+   * The reason name
+   */
+  u8 *pd_name;
+
+  /**
+   * The allocated reason value
+   */
+  vlib_punt_reason_t pd_reason;
+
+  /**
+   * Clients/owners that have registered this reason
+   */
+  u32 *pd_owners;
+} punt_reason_data_t;
+
+/**
+ * data for each punt reason
+ */
+static punt_reason_data_t *punt_reason_data;
+
+typedef enum punt_format_flags_t_
+{
+  PUNT_FORMAT_FLAG_NONE = 0,
+  PUNT_FORMAT_FLAG_DETAIL = (1 << 0),
+} punt_format_flags_t;
+
+/**
+ * A registration, by a client, to direct punted traffic to a given node
+ */
+typedef struct punt_reg_t_
+{
+  /**
+   * Reason the packets were punted
+   */
+  vlib_punt_reason_t pr_reason;
+
+  /**
+   * number of clients that have made this registration
+   */
+  u16 pr_locks;
+
+  /**
+   * The edge from the punt dispatch node to the requested node
+   */
+  u16 pr_edge;
+
+  /**
+   * node-index to send punted packets to
+   */
+  u32 pr_node_index;
+} punt_reg_t;
+
+/**
+ * Pool of registrations
+ */
+static punt_reg_t *punt_reg_pool;
+
+/**
+ * A DB of all the register nodes against punt reason and node index
+ */
+static uword *punt_reg_db;
+
+/**
+ * A DB used in the DP per-reason to dispatch packets to the requested nodes.
+ * this is a vector of edges per-reason
+ */
+u16 **punt_dp_db;
+
+/**
+ * A client using the punt serivce and its registrations
+ */
+typedef struct punt_client_t_
+{
+  /**
+   * The name of the client
+   */
+  u8 *pc_name;
+
+  /**
+   * The registrations is has made
+   */
+  u32 *pc_regs;
+} punt_client_t;
+
+/**
+ * Pool of clients
+ */
+static punt_client_t *punt_client_pool;
+
+/**
+ * DB of clients key'd by their name
+ */
+static uword *punt_client_db;
+
+u8 *
+format_vlib_punt_reason (u8 * s, va_list * args)
+{
+  vlib_punt_reason_t pr = va_arg (*args, int);
+
+  return (format (s, "[%d] %v", pr, punt_reason_data[pr].pd_name));
+}
+
+vlib_punt_hdl_t
+vlib_punt_client_register (const char *who)
+{
+  u8 *pc_name;
+  uword *p;
+  u32 pci;
+
+  pc_name = format (NULL, "%s", who);
+  p = hash_get_mem (punt_client_db, pc_name);
+
+  if (NULL == p)
+    {
+      punt_client_t *pc;
+
+      pool_get (punt_client_pool, pc);
+      pci = pc - punt_client_pool;
+
+      pc->pc_name = pc_name;
+
+      hash_set_mem (punt_client_db, pc->pc_name, pci);
+    }
+  else
+    {
+      pci = p[0];
+      vec_free (pc_name);
+    }
+
+  return (pci);
+}
+
+static int
+punt_validate_client (vlib_punt_hdl_t client)
+{
+  return (!pool_is_free_index (punt_client_pool, client));
+}
+
+static u64
+punt_reg_mk_key (vlib_punt_reason_t reason, u32 node_index)
+{
+  return (((u64) node_index) << 32 | reason);
+}
+
+static u32
+punt_reg_find (vlib_punt_reason_t reason, u32 node_index)
+{
+  uword *p;
+
+  p = hash_get (punt_reg_db, punt_reg_mk_key (reason, node_index));
+
+  if (p)
+    return p[0];
+
+  return ~0;
+}
+
+static void
+punt_reg_add (const punt_reg_t * pr)
+{
+  hash_set (punt_reg_db, punt_reg_mk_key (pr->pr_reason,
+                                         pr->pr_node_index),
+           pr - punt_reg_pool);
+}
+
+static void
+punt_reg_remove (const punt_reg_t * pr)
+{
+  hash_unset (punt_reg_db, punt_reg_mk_key (pr->pr_reason,
+                                           pr->pr_node_index));
+}
+
+/**
+ * reconstruct the DP per-reason DB
+ */
+static void
+punt_reg_mk_dp (vlib_punt_reason_t reason)
+{
+  u32 pri, *prip, *pris;
+  const punt_reg_t *pr;
+  u16 *edges, *old;
+  u64 key;
+
+  pris = NULL;
+  edges = NULL;
+  vec_validate (punt_dp_db, reason);
+
+  old = punt_dp_db[reason];
+
+  /* *INDENT-OFF* */
+  hash_foreach (key, pri, punt_reg_db,
+    ({
+      vec_add1(pris, pri);
+    }));
+  /* *INDENT-ON* */
+
+  /*
+   * A check for an empty vector is done in the DP, so the a zero
+   * length vector here is ok
+   */
+  vec_foreach (prip, pris)
+  {
+    pr = pool_elt_at_index (punt_reg_pool, *prip);
+
+    if (pr->pr_reason == reason)
+      vec_add1 (edges, pr->pr_edge);
+  }
+
+  /* atomic update of the DP */
+  punt_dp_db[reason] = edges;
+
+  vec_free (old);
+}
+
+int
+vlib_punt_register (vlib_punt_hdl_t client, vlib_punt_reason_t reason,
+                   const char *node_name)
+{
+  vlib_node_t *punt_to, *punt_from;
+  punt_client_t *pc;
+  vlib_main_t *vm;
+  punt_reg_t *pr;
+  u32 pri;
+
+  if (reason >= punt_reason_last)
+    return -1;
+  if (!punt_validate_client (client))
+    return -2;
+
+  vm = vlib_get_main ();
+  pc = pool_elt_at_index (punt_client_pool, client);
+  punt_to = vlib_get_node_by_name (vm, (u8 *) node_name);
+  punt_from = vlib_get_node_by_name (vm, (u8 *) "punt-dispatch");
+
+  /*
+   * find a global matching registration
+   */
+  pri = punt_reg_find (reason, punt_to->index);
+
+  if (~0 != pri)
+    {
+      u32 pos;
+
+      pos = vec_search (pc->pc_regs, pri);
+
+      if (~0 != pos)
+       {
+         /* duplicate registration for this client */
+         return -1;
+       }
+
+      pr = pool_elt_at_index (punt_reg_pool, pri);
+    }
+  else
+    {
+      pool_get (punt_reg_pool, pr);
+
+      pr->pr_reason = reason;
+      pr->pr_node_index = punt_to->index;
+      pr->pr_edge = vlib_node_add_next (vm,
+                                       punt_from->index, pr->pr_node_index);
+
+      pri = pr - punt_reg_pool;
+
+      punt_reg_add (pr);
+    }
+
+  /*
+   * add this reg to the list the client has made
+   */
+  pr->pr_locks++;
+  vec_add1 (pc->pc_regs, pri);
+
+  punt_reg_mk_dp (reason);
+
+  return 0;
+}
+
+int
+vlib_punt_unregister (vlib_punt_hdl_t client,
+                     vlib_punt_reason_t reason, const char *node_name)
+{
+  vlib_node_t *punt_to;
+  punt_client_t *pc;
+  vlib_main_t *vm;
+  punt_reg_t *pr;
+  u32 pri;
+
+  if (reason >= punt_reason_last)
+    return -1;
+
+  vm = vlib_get_main ();
+  pc = pool_elt_at_index (punt_client_pool, client);
+  punt_to = vlib_get_node_by_name (vm, (u8 *) node_name);
+
+  /*
+   * construct a registration and check if it's one this client already has
+   */
+  pri = punt_reg_find (reason, punt_to->index);
+
+  if (~0 != pri)
+    {
+      u32 pos;
+
+      pos = vec_search (pc->pc_regs, pri);
+
+      if (~0 == pos)
+       {
+         /* not a registration for this client */
+         return -1;
+       }
+      vec_del1 (pc->pc_regs, pos);
+
+      pr = pool_elt_at_index (punt_reg_pool, pri);
+
+      pr->pr_locks--;
+
+      if (0 == pr->pr_locks)
+       {
+         punt_reg_remove (pr);
+         pool_put (punt_reg_pool, pr);
+       }
+    }
+
+  /*
+   * rebuild the DP data-base
+   */
+  punt_reg_mk_dp (reason);
+
+  return (0);
+}
+
+int
+vlib_punt_reason_alloc (vlib_punt_hdl_t client,
+                       const char *reason_name, vlib_punt_reason_t * reason)
+{
+  vlib_punt_reason_t new;
+
+  if (!punt_validate_client (client))
+    return -2;
+
+  new = punt_reason_last++;
+  vec_validate (punt_reason_data, new);
+  punt_reason_data[new].pd_name = format (NULL, "%s", reason_name);
+  punt_reason_data[new].pd_reason = new;
+  vec_add1 (punt_reason_data[new].pd_owners, client);
+
+  vlib_validate_combined_counter (&punt_counters, new);
+  vlib_zero_combined_counter (&punt_counters, new);
+
+  *reason = new;
+
+  /* build the DP data-base */
+  punt_reg_mk_dp (*reason);
+
+  return (0);
+}
+
+/* Parse node name -> node index. */
+uword
+unformat_punt_client (unformat_input_t * input, va_list * args)
+{
+  u32 *result = va_arg (*args, u32 *);
+
+  return unformat_user (input, unformat_hash_vec_string,
+                       punt_client_db, result);
+}
+
+u8 *
+format_punt_reg (u8 * s, va_list * args)
+{
+  u32 pri = va_arg (*args, u32);
+  punt_reg_t *pr;
+
+  pr = pool_elt_at_index (punt_reg_pool, pri);
+
+  s = format (s, "%U -> %U",
+             format_vlib_punt_reason, pr->pr_reason,
+             format_vlib_node_name, vlib_get_main (), pr->pr_node_index);
+
+  return (s);
+}
+
+u8 *
+format_punt_reason_data (u8 * s, va_list * args)
+{
+  punt_reason_data_t *pd = va_arg (*args, punt_reason_data_t *);
+  punt_client_t *pc;
+  u32 *pci;
+
+  s = format (s, "[%d] %v from:[", pd->pd_reason, pd->pd_name);
+  vec_foreach (pci, pd->pd_owners)
+  {
+    pc = pool_elt_at_index (punt_client_pool, *pci);
+    s = format (s, "%v ", pc->pc_name);
+  }
+  s = format (s, "]");
+
+  return (s);
+}
+
+u8 *
+format_punt_client (u8 * s, va_list * args)
+{
+  u32 pci = va_arg (*args, u32);
+  punt_format_flags_t flags = va_arg (*args, punt_format_flags_t);
+  punt_client_t *pc;
+
+  pc = pool_elt_at_index (punt_client_pool, pci);
+
+  s = format (s, "%v", pc->pc_name);
+
+  if (flags & PUNT_FORMAT_FLAG_DETAIL)
+    {
+      punt_reason_data_t *pd;
+      u32 *pri;
+
+      s = format (s, "\n registrations:");
+      vec_foreach (pri, pc->pc_regs)
+      {
+       s = format (s, "\n  [%U]", format_punt_reg, *pri);
+      }
+
+      s = format (s, "\n reasons:");
+
+      vec_foreach (pd, punt_reason_data)
+      {
+       u32 *tmp;
+
+       vec_foreach (tmp, pd->pd_owners)
+       {
+         if (*tmp == pci)
+           s = format (s, "\n  %U", format_punt_reason_data, pd);
+       }
+      }
+    }
+  return (s);
+}
+
+static clib_error_t *
+punt_client_show (vlib_main_t * vm,
+                 unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  u32 pci = ~0;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "%U", unformat_punt_client, &pci))
+       ;
+      else
+       break;
+    }
+
+  if (~0 != pci)
+    {
+      vlib_cli_output (vm, "%U", format_punt_client, pci,
+                      PUNT_FORMAT_FLAG_DETAIL);
+    }
+  else
+    {
+      u8 *name;
+
+      /* *INDENT-OFF* */
+      hash_foreach(name, pci, punt_client_db,
+        ({
+          vlib_cli_output (vm, "%U", format_punt_client, pci,
+                           PUNT_FORMAT_FLAG_NONE);
+        }));
+      /* *INDENT-ON* */
+    }
+
+  return (NULL);
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (punt_client_show_command, static) =
+{
+  .path = "show punt client",
+  .short_help = "show client[s] registered with the punt infra",
+  .function = punt_client_show,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+punt_reason_show (vlib_main_t * vm,
+                 unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  const punt_reason_data_t *pd;
+
+  vec_foreach (pd, punt_reason_data)
+  {
+    vlib_cli_output (vm, "%U", format_punt_reason_data, pd);
+  }
+
+  return (NULL);
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (punt_reason_show_command, static) =
+{
+  .path = "show punt reasons",
+  .short_help = "show all punt reasons",
+  .function = punt_reason_show,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+punt_db_show (vlib_main_t * vm,
+             unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  u32 pri, ii, jj;
+  u64 key;
+
+  /* *INDENT-OFF* */
+  hash_foreach (key, pri, punt_reg_db,
+    ({
+      vlib_cli_output (vm, " %U", format_punt_reg, pri);
+    }));
+  /* *INDENT-ON* */
+
+  vlib_cli_output (vm, "\nDerived data-plane data-base:");
+  vlib_cli_output (vm,
+                  "  (for each punt-reason the edge[s] from punt-dispatch)");
+
+  vec_foreach_index (ii, punt_dp_db)
+  {
+    u8 *s = NULL;
+    vlib_cli_output (vm, " %U", format_vlib_punt_reason, ii);
+
+    vec_foreach_index (jj, punt_dp_db[ii])
+    {
+      s = format (s, "%d ", punt_dp_db[ii][jj]);
+    }
+    vlib_cli_output (vm, "   [%v]", s);
+    vec_free (s);
+  }
+
+  return (NULL);
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (punt_db_show_command, static) =
+{
+  .path = "show punt db",
+  .short_help = "show the punt DB",
+  .function = punt_db_show,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+punt_stats_show (vlib_main_t * vm,
+                unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  vlib_combined_counter_main_t *cm = &punt_counters;
+  vlib_counter_t c;
+  u32 ii;
+
+  for (ii = 0; ii < vlib_combined_counter_n_counters (cm); ii++)
+    {
+      vlib_get_combined_counter (cm, ii, &c);
+      vlib_cli_output (vm, "%U packets:%lld bytes:%lld",
+                      format_vlib_punt_reason, ii, c.packets, c.bytes);
+    }
+
+  return (NULL);
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (punt_stats_show_command, static) =
+{
+  .path = "show punt stats",
+  .short_help = "show the punt stats",
+  .function = punt_stats_show,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+punt_init (vlib_main_t * vm)
+{
+  punt_client_db = hash_create_vec (0, sizeof (u8), sizeof (u32));
+
+  return (NULL);
+}
+
+VLIB_INIT_FUNCTION (punt_init);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vlib/punt.h b/src/vlib/punt.h
new file mode 100644 (file)
index 0000000..adb25f9
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2018 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 __PUNT_H__
+#define __PUNT_H__
+
+#include <vlib/vlib.h>
+
+/**
+ * The 'syatem' defined punt reasons.
+ * Only add to this list reasons defined and used within the vlib subsystem.
+ * To define new reasons in e.g. plgins, use punt_reason_alloc()
+ */
+typedef enum vlib_punt_reason_t_
+{
+  PUNT_N_REASONS,
+} vlib_punt_reason_t;
+
+
+/**
+ * @brief Format a punt reason
+ */
+extern u8 *format_vlib_punt_reason (u8 * s, va_list * args);
+
+/**
+ * Typedef for a client handle
+ */
+typedef int vlib_punt_hdl_t;
+
+/**
+ * @brief Register a new clinet
+ *
+ * @param who - The name of the client
+ *
+ * @retrun the handle the punt infra allocated for this client that must
+ *         be used when the client wishes to use the infra
+ */
+vlib_punt_hdl_t vlib_punt_client_register (const char *who);
+
+/**
+ * Allocate a new punt reason
+ */
+extern int vlib_punt_reason_alloc (vlib_punt_hdl_t client,
+                                  const char *reason_name,
+                                  vlib_punt_reason_t * reason);
+
+/**
+ * @brief Register a node to receive particular punted buffers
+ *
+ * @paran client - The registered client registering for the packets
+ * @param reason - The reason the packet was punted
+ * @param node   - The node to which the punted packets will be sent
+ */
+extern int vlib_punt_register (vlib_punt_hdl_t client,
+                              vlib_punt_reason_t reason, const char *node);
+extern int vlib_punt_unregister (vlib_punt_hdl_t client,
+                                vlib_punt_reason_t pr, const char *node);
+
+/**
+ * FOR USE IN THE DP ONLY
+ *
+ * Arc[s] to follow for each reason
+ */
+extern u16 **punt_dp_db;
+
+/**
+ * FOR USE IN THE DP ONLY
+ *
+ * Per-reason counters
+ */
+extern vlib_combined_counter_main_t punt_counters;
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vlib/punt_node.c b/src/vlib/punt_node.c
new file mode 100644 (file)
index 0000000..c87d2b6
--- /dev/null
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2018 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 <vlib/punt.h>
+
+#define foreach_punt_error                     \
+  _(DISPATCHED, "dispatched")                  \
+  _(NO_REASON, "No such punt reason")          \
+  _(NO_REG, "No registrations")                \
+  _(REP_FAIL, "Replication Faliure")
+
+typedef enum punt_error_t_
+{
+#define _(v,s) PUNT_ERROR_##v,
+  foreach_punt_error
+#undef _
+    PUNT_N_ERRORS,
+} punt_error_t;
+
+static char *punt_error_strings[] = {
+#define _(v,s) [PUNT_ERROR_##v] = s,
+  foreach_punt_error
+#undef _
+};
+
+typedef enum punt_next_t_
+{
+  PUNT_NEXT_DROP,
+  PUNT_N_NEXT,
+} punt_next_t;
+
+typedef struct punt_trace_t_
+{
+  vlib_punt_reason_t pt_reason;
+} punt_trace_t;
+
+/**
+ * Per-thread clone vectors
+ */
+#ifndef CLIB_MARCH_VARIANT
+u32 **punt_clones;
+#else
+extern u32 **punt_clones;
+#endif
+
+static u8 *
+format_punt_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 *);
+  punt_trace_t *t = va_arg (*args, punt_trace_t *);
+
+  s = format (s, "reason: %U", format_vlib_punt_reason, t->pt_reason);
+
+  return s;
+}
+
+always_inline u32
+punt_replicate (vlib_main_t * vm,
+               vlib_node_runtime_t * node,
+               u32 thread_index,
+               vlib_buffer_t * b0,
+               u32 bi0,
+               vlib_punt_reason_t pr0,
+               u32 * next_index,
+               u32 * n_left_to_next, u32 ** to_next, u32 * pkts_rep_fail)
+{
+  /* multiple clients => replicate a copy to each */
+  u16 n_clones0, n_cloned0, clone0;
+  u32 ci0, next0;
+
+  n_clones0 = vec_len (punt_dp_db[pr0]);
+  vec_validate (punt_clones[thread_index], n_clones0);
+
+  n_cloned0 = vlib_buffer_clone (vm, bi0,
+                                punt_clones[thread_index],
+                                n_clones0, 2 * CLIB_CACHE_LINE_BYTES);
+
+  if (PREDICT_FALSE (n_cloned0 != n_clones0))
+    {
+      b0->error = node->errors[PUNT_ERROR_REP_FAIL];
+      *pkts_rep_fail += 1;
+    }
+
+  for (clone0 = 1; clone0 < n_cloned0; clone0++)
+    {
+      ci0 = punt_clones[thread_index][clone0];
+
+      *to_next[0] = ci0;
+      *to_next += 1;
+      *n_left_to_next -= 1;
+
+      next0 = punt_dp_db[pr0][clone0];
+
+      if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
+       {
+         vlib_buffer_t *c0;
+         punt_trace_t *t;
+
+         c0 = vlib_get_buffer (vm, ci0);
+
+         if (c0 != b0)
+           vlib_buffer_copy_trace_flag (vm, b0, ci0);
+
+         t = vlib_add_trace (vm, node, c0, sizeof (*t));
+         t->pt_reason = pr0;
+       }
+
+      vlib_validate_buffer_enqueue_x1 (vm, node, *next_index,
+                                      *to_next, *n_left_to_next, ci0, next0);
+
+      /* replications here always go to different next-nodes
+       * so there's no need to check if the to_next frame
+       * is full */
+    }
+
+  /* The original buffer is the first clone */
+  next0 = punt_dp_db[pr0][0];
+  *to_next[0] = bi0;
+  return next0;
+}
+
+always_inline u32
+punt_dispatch_one (vlib_main_t * vm,
+                  vlib_node_runtime_t * node,
+                  vlib_combined_counter_main_t * cm,
+                  u32 thread_index,
+                  u32 bi0,
+                  u32 * next_index,
+                  u32 * n_left_to_next, u32 ** to_next, u32 * pkts_errors)
+{
+  vlib_punt_reason_t pr0;
+  vlib_buffer_t *b0;
+  u32 next0;
+
+  b0 = vlib_get_buffer (vm, bi0);
+  pr0 = b0->punt_reason;
+
+  if (PREDICT_FALSE (pr0 >= vec_len (punt_dp_db)))
+    {
+      b0->error = node->errors[PUNT_ERROR_NO_REASON];
+      next0 = PUNT_NEXT_DROP;
+      pkts_errors[PUNT_ERROR_NO_REASON] += 1;
+    }
+  else
+    {
+      vlib_increment_combined_counter
+       (cm, thread_index, pr0, 1, vlib_buffer_length_in_chain (vm, b0));
+
+      if (PREDICT_TRUE (1 == vec_len (punt_dp_db[pr0])))
+       {
+         /*
+          * one registered client => give it the packet
+          * This is the most likely outcome.
+          */
+         next0 = punt_dp_db[pr0][0];
+         pkts_errors[PUNT_ERROR_DISPATCHED] += 1;
+       }
+      else if (0 == vec_len (punt_dp_db[pr0]))
+       {
+         /* no registered clients => drop */
+         next0 = PUNT_NEXT_DROP;
+         pkts_errors[PUNT_ERROR_NO_REG] += 1;
+       }
+      else
+       {
+         /*
+          * multiple registered clients => replicate
+          */
+         pkts_errors[PUNT_ERROR_DISPATCHED] += 1;
+
+         next0 = punt_replicate (vm, node, thread_index, b0, bi0, pr0,
+                                 next_index, n_left_to_next, to_next,
+                                 &pkts_errors[PUNT_ERROR_REP_FAIL]);
+       }
+    }
+
+  if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
+    {
+      punt_trace_t *t;
+
+      t = vlib_add_trace (vm, node, b0, sizeof (*t));
+      t->pt_reason = pr0;
+    }
+
+  return (next0);
+}
+
+VLIB_NODE_FN (punt_dispatch_node) (vlib_main_t * vm,
+                                  vlib_node_runtime_t * node,
+                                  vlib_frame_t * frame)
+{
+  u32 n_left_from, *from, *to_next, next_index, thread_index;
+  vlib_combined_counter_main_t *cm;
+  u32 pkt_errors[PUNT_N_ERRORS] = { 0 };
+
+  cm = &punt_counters;
+  from = vlib_frame_vector_args (frame);
+  n_left_from = frame->n_vectors;
+  next_index = node->cached_next_index;
+  thread_index = vlib_get_thread_index ();
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+      while (n_left_from > 4 && n_left_to_next > 2)
+       {
+         punt_next_t next0, next1;
+         u32 bi0, bi1;
+
+         {
+           vlib_buffer_t *b2, *b3;
+
+           b2 = vlib_get_buffer (vm, from[2]);
+           b3 = vlib_get_buffer (vm, from[3]);
+
+           vlib_prefetch_buffer_header (b2, LOAD);
+           vlib_prefetch_buffer_header (b3, LOAD);
+         }
+
+         bi0 = to_next[0] = from[0];
+         bi1 = to_next[1] = from[1];
+         from += 2;
+         n_left_from -= 2;
+
+         next0 = punt_dispatch_one (vm, node, cm, thread_index, bi0,
+                                    &next_index, &n_left_to_next,
+                                    &to_next, pkt_errors);
+         next1 = punt_dispatch_one (vm, node, cm, thread_index, bi1,
+                                    &next_index, &n_left_to_next,
+                                    &to_next, pkt_errors);
+
+         to_next += 2;
+         n_left_to_next -= 2;
+
+         vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
+                                          to_next, n_left_to_next,
+                                          bi0, bi1, next0, next1);
+       }
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+         punt_next_t next0;
+         u32 bi0;
+
+         bi0 = to_next[0] = from[0];
+         from += 1;
+         n_left_from -= 1;
+
+         next0 = punt_dispatch_one (vm, node, cm, thread_index, bi0,
+                                    &next_index, &n_left_to_next,
+                                    &to_next, pkt_errors);
+
+         to_next += 1;
+         n_left_to_next -= 1;
+
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+                                          to_next, n_left_to_next,
+                                          bi0, next0);
+       }
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  vlib_node_increment_counter (vm, node->node_index,
+                              PUNT_ERROR_DISPATCHED,
+                              pkt_errors[PUNT_ERROR_DISPATCHED]);
+  vlib_node_increment_counter (vm, node->node_index,
+                              PUNT_ERROR_NO_REASON,
+                              pkt_errors[PUNT_ERROR_NO_REASON]);
+  vlib_node_increment_counter (vm, node->node_index,
+                              PUNT_ERROR_NO_REG,
+                              pkt_errors[PUNT_ERROR_NO_REG]);
+  vlib_node_increment_counter (vm, node->node_index,
+                              PUNT_ERROR_REP_FAIL,
+                              pkt_errors[PUNT_ERROR_REP_FAIL]);
+
+  return frame->n_vectors;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (punt_dispatch_node) = {
+  .name = "punt-dispatch",
+  .vector_size = sizeof (u32),
+  .format_trace = format_punt_trace,
+  .n_errors = PUNT_N_ERRORS,
+  .error_strings = punt_error_strings,
+  .n_next_nodes = PUNT_N_NEXT,
+  .next_nodes = {
+    [PUNT_NEXT_DROP] = "drop",
+  },
+};
+
+/* *INDENT-ON* */
+
+#ifndef CLIB_MARCH_VARIANT
+clib_error_t *
+punt_node_init (vlib_main_t * vm)
+{
+  vec_validate (punt_clones, vlib_num_workers ());
+
+  return NULL;
+}
+
+VLIB_INIT_FUNCTION (punt_node_init);
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
index 2149c47..b5fe47b 100644 (file)
@@ -58,6 +58,7 @@ typedef u32 vlib_log_class_t;
 #include <vlib/error.h>
 #include <vlib/init.h>
 #include <vlib/node.h>
+#include <vlib/punt.h>
 #include <vlib/trace.h>
 #include <vlib/log.h>
 
index 831ece2..743df1f 100644 (file)
@@ -1059,7 +1059,7 @@ VLIB_CLI_COMMAND (show_punt_socket_registration_command, static) =
 /* *INDENT-ON* */
 
 clib_error_t *
-punt_init (vlib_main_t * vm)
+ip_punt_init (vlib_main_t * vm)
 {
   punt_main_t *pm = &punt_main;
 
@@ -1077,7 +1077,7 @@ punt_init (vlib_main_t * vm)
   return 0;
 }
 
-VLIB_INIT_FUNCTION (punt_init);
+VLIB_INIT_FUNCTION (ip_punt_init);
 #endif /* CLIB_MARCH_VARIANT */
 
 static clib_error_t *
index 7dc8d25..c520e25 100644 (file)
@@ -16,7 +16,7 @@
  */
 
 #include <vlib/vlib.h>
-#include <vnet/pg/pg.h>
+
 #include <vnet/vxlan-gbp/vxlan_gbp.h>
 
 typedef struct
@@ -309,7 +309,13 @@ vxlan_gbp_input (vlib_main_t * vm,
              else
                {
                  error0 = VXLAN_GBP_ERROR_NO_SUCH_TUNNEL;
-                 next0 = VXLAN_GBP_INPUT_NEXT_NO_TUNNEL;
+                 next0 = VXLAN_GBP_INPUT_NEXT_PUNT;
+                 if (is_ip4)
+                   b0->punt_reason =
+                     vxm->punt_no_such_tunnel[FIB_PROTOCOL_IP4];
+                 else
+                   b0->punt_reason =
+                     vxm->punt_no_such_tunnel[FIB_PROTOCOL_IP6];
                }
              b0->error = node->errors[error0];
            }
@@ -342,7 +348,13 @@ vxlan_gbp_input (vlib_main_t * vm,
              else
                {
                  error1 = VXLAN_GBP_ERROR_NO_SUCH_TUNNEL;
-                 next1 = VXLAN_GBP_INPUT_NEXT_NO_TUNNEL;
+                 next1 = VXLAN_GBP_INPUT_NEXT_PUNT;
+                 if (is_ip4)
+                   b1->punt_reason =
+                     vxm->punt_no_such_tunnel[FIB_PROTOCOL_IP4];
+                 else
+                   b1->punt_reason =
+                     vxm->punt_no_such_tunnel[FIB_PROTOCOL_IP6];
                }
              b1->error = node->errors[error1];
            }
@@ -444,7 +456,13 @@ vxlan_gbp_input (vlib_main_t * vm,
              else
                {
                  error0 = VXLAN_GBP_ERROR_NO_SUCH_TUNNEL;
-                 next0 = VXLAN_GBP_INPUT_NEXT_NO_TUNNEL;
+                 next0 = VXLAN_GBP_INPUT_NEXT_PUNT;
+                 if (is_ip4)
+                   b0->punt_reason =
+                     vxm->punt_no_such_tunnel[FIB_PROTOCOL_IP4];
+                 else
+                   b0->punt_reason =
+                     vxm->punt_no_such_tunnel[FIB_PROTOCOL_IP6];
                }
              b0->error = node->errors[error0];
            }
index db10f54..e39c26f 100644 (file)
@@ -28,7 +28,7 @@
  *
  * VXLAN GBP provides the features of vxlan and carry group policy id.
  */
-
+static vlib_punt_hdl_t punt_hdl;
 
 vxlan_gbp_main_t vxlan_gbp_main;
 
@@ -1144,10 +1144,14 @@ clib_error_t *
 vxlan_gbp_init (vlib_main_t * vm)
 {
   vxlan_gbp_main_t *vxm = &vxlan_gbp_main;
+  clib_error_t *error;
 
   vxm->vnet_main = vnet_get_main ();
   vxm->vlib_main = vm;
 
+  if ((error = vlib_call_init_function (vm, punt_init)))
+    return (error);
+
   /* initialize the ip6 hash */
   clib_bihash_init_16_8 (&vxm->vxlan4_gbp_tunnel_by_key, "vxlan4-gbp",
                         VXLAN_GBP_HASH_NUM_BUCKETS,
@@ -1162,7 +1166,16 @@ vxlan_gbp_init (vlib_main_t * vm)
 
   fib_node_register_type (FIB_NODE_TYPE_VXLAN_GBP_TUNNEL, &vxlan_gbp_vft);
 
-  return 0;
+  punt_hdl = vlib_punt_client_register ("vxlan-gbp");
+
+  vlib_punt_reason_alloc (punt_hdl,
+                         "VXLAN-GBP-no-such-v4-tunnel",
+                         &vxm->punt_no_such_tunnel[FIB_PROTOCOL_IP4]);
+  vlib_punt_reason_alloc (punt_hdl,
+                         "VXLAN-GBP-no-such-v6-tunnel",
+                         &vxm->punt_no_such_tunnel[FIB_PROTOCOL_IP6]);
+
+  return (error);
 }
 
 VLIB_INIT_FUNCTION (vxlan_gbp_init);
index 6dc6225..6580f38 100644 (file)
@@ -132,7 +132,7 @@ typedef struct
 
 #define foreach_vxlan_gbp_input_next         \
   _(DROP, "error-drop")                      \
-  _(NO_TUNNEL, "error-punt")                 \
+  _(PUNT, "punt-dispatch")                   \
   _(L2_INPUT, "l2-input")                    \
   _(IP4_INPUT, "ip4-input")                  \
   _(IP6_INPUT, "ip6-input")
@@ -189,6 +189,11 @@ typedef struct
 
   /* Record used instances */
   uword *instance_used;
+
+  /**
+   * Punt reasons for no such tunnel
+   */
+  vlib_punt_reason_t punt_no_such_tunnel[FIB_PROTOCOL_IP_MAX];
 } vxlan_gbp_main_t;
 
 extern vxlan_gbp_main_t vxlan_gbp_main;
index 74dc923..b190101 100644 (file)
@@ -23,6 +23,9 @@ from scapy.layers.inet6 import IPv6, ICMPv6DestUnreach
 import six
 from framework import VppTestCase, VppTestRunner
 
+from vpp_ip import DpoProto
+from vpp_ip_route import VppIpRoute, VppRoutePath
+
 
 # Format MAC Address
 def get_mac_addr(bytes_addr):
@@ -671,5 +674,178 @@ class TestIP6PuntSocket(TestPuntSocket):
         punts = self.vapi.punt_socket_dump(is_ip6=1)
         self.assertEqual(len(punts), 0)
 
+
+class TestPunt(VppTestCase):
+    """ Punt Test Case """
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestPunt, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestPunt, cls).tearDownClass()
+
+    def setUp(self):
+        super(TestPunt, self).setUp()
+
+        self.create_pg_interfaces(range(4))
+
+        for i in self.pg_interfaces:
+            i.admin_up()
+            i.config_ip4()
+            i.resolve_arp()
+            i.config_ip6()
+            i.resolve_ndp()
+
+    def tearDown(self):
+        for i in self.pg_interfaces:
+            i.unconfig_ip4()
+            i.unconfig_ip6()
+            i.ip6_disable()
+            i.admin_down()
+        super(TestPunt, self).tearDown()
+
+    def test_punt(self):
+        """ Excpetion Path testing """
+
+        #
+        # Using the test CLI we will hook in a exception path to
+        # send ACL deny packets out of pg0 and pg1.
+        # the ACL is src,dst = 1.1.1.1,1.1.1.2
+        #
+        ip_1_1_1_2 = VppIpRoute(self, "1.1.1.2", 32,
+                                [VppRoutePath(self.pg3.remote_ip4,
+                                              self.pg3.sw_if_index)])
+        ip_1_1_1_2.add_vpp_config()
+        ip_1_2 = VppIpRoute(self, "1::2", 128,
+                            [VppRoutePath(self.pg3.remote_ip6,
+                                          self.pg3.sw_if_index,
+                                          proto=DpoProto.DPO_PROTO_IP6)],
+                            is_ip6=1)
+        ip_1_2.add_vpp_config()
+
+        p4 = (Ether(src=self.pg2.remote_mac,
+                    dst=self.pg2.local_mac) /
+              IP(src="1.1.1.1", dst="1.1.1.2") /
+              UDP(sport=1234, dport=1234) /
+              Raw('\xa5' * 100))
+        p6 = (Ether(src=self.pg2.remote_mac,
+                    dst=self.pg2.local_mac) /
+              IPv6(src="1::1", dst="1::2") /
+              UDP(sport=1234, dport=1234) /
+              Raw('\xa5' * 100))
+        self.send_and_expect(self.pg2, p4*1, self.pg3)
+        self.send_and_expect(self.pg2, p6*1, self.pg3)
+
+        #
+        # apply the punting features
+        #
+        self.vapi.cli("test punt pg2")
+
+        #
+        # pkts now dropped
+        #
+        self.send_and_assert_no_replies(self.pg2, p4*65)
+        self.send_and_assert_no_replies(self.pg2, p6*65)
+
+        #
+        # Check state:
+        #  1 - node error counters
+        #  2 - per-reason counters
+        #    2, 3 are the index of the assigned punt reason
+        #
+        stats = self.statistics.get_counter(
+            "/err/punt-dispatch/No registrations")
+        self.assertEqual(stats, 130)
+
+        stats = self.statistics.get_counter("/net/punt")
+        self.assertEqual(stats[0][2]['packets'], 65)
+        self.assertEqual(stats[0][3]['packets'], 65)
+
+        #
+        # use the test CLI to test a client that punts exception
+        # packets out of pg0
+        #
+        self.vapi.cli("test punt pg0 %s" % self.pg0.remote_ip4)
+        self.vapi.cli("test punt pg0 %s" % self.pg0.remote_ip6)
+
+        rx4s = self.send_and_expect(self.pg2, p4*65, self.pg0)
+        rx6s = self.send_and_expect(self.pg2, p6*65, self.pg0)
+
+        #
+        # check the packets come out IP unmodified but destined to pg0 host
+        #
+        for rx in rx4s:
+            self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
+            self.assertEqual(rx[Ether].src, self.pg0.local_mac)
+            self.assertEqual(p4[IP].dst, rx[IP].dst)
+            self.assertEqual(p4[IP].ttl, rx[IP].ttl)
+        for rx in rx6s:
+            self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
+            self.assertEqual(rx[Ether].src, self.pg0.local_mac)
+            self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
+            self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
+
+        stats = self.statistics.get_counter("/net/punt")
+        self.assertEqual(stats[0][2]['packets'], 2*65)
+        self.assertEqual(stats[0][3]['packets'], 2*65)
+
+        #
+        # add another registration for the same reason to send packets
+        # out of pg1
+        #
+        self.vapi.cli("test punt pg1 %s" % self.pg1.remote_ip4)
+        self.vapi.cli("test punt pg1 %s" % self.pg1.remote_ip6)
+
+        self.vapi.cli("clear trace")
+        self.pg2.add_stream(p4 * 65)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rxd = self.pg0.get_capture(65)
+        for rx in rxd:
+            self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
+            self.assertEqual(rx[Ether].src, self.pg0.local_mac)
+            self.assertEqual(p4[IP].dst, rx[IP].dst)
+            self.assertEqual(p4[IP].ttl, rx[IP].ttl)
+        rxd = self.pg1.get_capture(65)
+        for rx in rxd:
+            self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
+            self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+            self.assertEqual(p4[IP].dst, rx[IP].dst)
+            self.assertEqual(p4[IP].ttl, rx[IP].ttl)
+
+        self.vapi.cli("clear trace")
+        self.pg2.add_stream(p6 * 65)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rxd = self.pg0.get_capture(65)
+        for rx in rxd:
+            self.assertEqual(rx[Ether].dst, self.pg0.remote_mac)
+            self.assertEqual(rx[Ether].src, self.pg0.local_mac)
+            self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
+            self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
+        rxd = self.pg1.get_capture(65)
+        for rx in rxd:
+            self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
+            self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+            self.assertEqual(p6[IPv6].dst, rx[IPv6].dst)
+            self.assertEqual(p6[IPv6].hlim, rx[IPv6].hlim)
+
+        stats = self.statistics.get_counter("/net/punt")
+        self.assertEqual(stats[0][2]['packets'], 3*65)
+        self.assertEqual(stats[0][3]['packets'], 3*65)
+
+        self.logger.info(self.vapi.cli("show vlib graph punt-dispatch"))
+        self.logger.info(self.vapi.cli("show punt client"))
+        self.logger.info(self.vapi.cli("show punt reason"))
+        self.logger.info(self.vapi.cli("show punt stats"))
+        self.logger.info(self.vapi.cli("show punt db"))
+
+        self.vapi.cli("test punt clear")
+
+
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)