Make the loss / delay sim available as an output feature 71/18671/9
authorDave Barach <dave@barachs.net>
Wed, 3 Apr 2019 15:20:06 +0000 (11:20 -0400)
committerFlorin Coras <florin.coras@gmail.com>
Thu, 11 Apr 2019 01:19:56 +0000 (01:19 +0000)
Add binary api and debug cli support.

Rewrite for speed: enqueue vlib_buffer_t's to the wheel, instead of
memcpy'ing data. Quad-loop the output feature / x-connect (interior)
node. Prefetch wheel entries in the input node.

Save packet-generator-based unit-test setup in extras/nsim.

Simple config example:

set nsim delay 20 ms bandwidth 1 gbit packet-size 1024
nsim output-feature enable-disable GigabitEthernet3/0/0

Change-Id: I852a32d4eb596e7e2aa1d9b30bf3b53525e39fd1
Signed-off-by: Dave Barach <dave@barachs.net>c
extras/nsim/setup.nsim [new file with mode: 0644]
src/plugins/nsim/node.c
src/plugins/nsim/nsim.api
src/plugins/nsim/nsim.c
src/plugins/nsim/nsim.h
src/plugins/nsim/nsim_input.c
src/plugins/nsim/nsim_test.c

diff --git a/extras/nsim/setup.nsim b/extras/nsim/setup.nsim
new file mode 100644 (file)
index 0000000..999b92a
--- /dev/null
@@ -0,0 +1,25 @@
+set term pag off
+
+loop cre
+
+set int ip address loop0 192.168.2.1/24
+set int state loop0 up
+set nsim delay 20 ms bandwidth 1 gbit packet-size 128
+comment { add drop-fraction 0.8 or some such}
+nsim output-feature enable-disable loop0
+
+packet-generator new {
+    name icmp
+    limit 0
+    size 128-128
+    interface local0
+    node ethernet-input
+    data {
+        IP4: 0001.dead.beef -> 0002.dead.beef
+        ICMP: 192.168.1.2 -> 192.168.2.2
+        incrementing 30
+    }
+}
+
+set int ip address pg0 192.168.1.1/24
+set ip arp loop0 192.168.2.2 0003.dead.beef
index 25112ab..559147b 100644 (file)
@@ -80,20 +80,24 @@ typedef enum
 
 always_inline uword
 nsim_inline (vlib_main_t * vm,
-            vlib_node_runtime_t * node, vlib_frame_t * frame, int is_trace)
+            vlib_node_runtime_t * node, vlib_frame_t * frame, int is_trace,
+            int is_cross_connect)
 {
   nsim_main_t *nsm = &nsim_main;
   u32 n_left_from, *from;
+  u32 *to_next, n_left_to_next;
+  u32 drops[VLIB_FRAME_SIZE], *drop;
   vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
+  u8 is_drop[4];
   u16 nexts[VLIB_FRAME_SIZE], *next;
   u32 my_thread_index = vm->thread_index;
   nsim_wheel_t *wp = nsm->wheel_by_thread[my_thread_index];
   f64 now = vlib_time_now (vm);
   f64 expires = now + nsm->delay;
-  int is_drop0;
-  u32 no_error = node->errors[NSIM_ERROR_BUFFERED];
+  f64 rnd[4];
   u32 no_buffer_error = node->errors[NSIM_ERROR_DROPPED];
   u32 loss_error = node->errors[NSIM_ERROR_LOSS];
+  u32 buffered = 0;
   nsim_wheel_entry_t *ep = 0;
 
   ASSERT (wp);
@@ -104,24 +108,246 @@ nsim_inline (vlib_main_t * vm,
   vlib_get_buffers (vm, from, bufs, n_left_from);
   b = bufs;
   next = nexts;
+  drop = drops;
+
+  while (n_left_from >= 8)
+    {
+      vlib_prefetch_buffer_header (b[4], STORE);
+      vlib_prefetch_buffer_header (b[5], STORE);
+      vlib_prefetch_buffer_header (b[6], STORE);
+      vlib_prefetch_buffer_header (b[7], STORE);
+
+      memset (&is_drop, 0, sizeof (is_drop));
+      next[0] = next[1] = next[2] = next[3] = NSIM_NEXT_DROP;
+      if (PREDICT_FALSE (wp->cursize + 4 >= wp->wheel_size))
+       goto slow_path;
+      if (PREDICT_FALSE (nsm->drop_fraction != 0.0))
+       {
+         rnd[0] = random_f64 (&nsm->seed);
+         rnd[1] = random_f64 (&nsm->seed);
+         rnd[2] = random_f64 (&nsm->seed);
+         rnd[3] = random_f64 (&nsm->seed);
+
+         if (rnd[0] <= nsm->drop_fraction)
+           {
+             b[0]->error = loss_error;
+             is_drop[0] = 1;
+           }
+         if (rnd[1] <= nsm->drop_fraction)
+           {
+             b[1]->error = loss_error;
+             is_drop[1] = 1;
+           }
+         if (rnd[2] <= nsm->drop_fraction)
+           {
+             b[2]->error = loss_error;
+             is_drop[2] = 1;
+           }
+         if (rnd[3] <= nsm->drop_fraction)
+           {
+             b[3]->error = loss_error;
+             is_drop[3] = 1;
+           }
+       }
+
+      if (PREDICT_TRUE (is_drop[0] == 0))
+       {
+         ep = wp->entries + wp->tail;
+         wp->tail++;
+         if (wp->tail == wp->wheel_size)
+           wp->tail = 0;
+         wp->cursize++;
+
+         ep->tx_time = expires;
+         ep->rx_sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX];
+         if (is_cross_connect)
+           {
+             ep->tx_sw_if_index =
+               (vnet_buffer (b[0])->sw_if_index[VLIB_RX] ==
+                nsm->sw_if_index0) ? nsm->sw_if_index1 : nsm->sw_if_index0;
+             ep->output_next_index =
+               (ep->tx_sw_if_index ==
+                nsm->sw_if_index0) ? nsm->
+               output_next_index0 : nsm->output_next_index1;
+           }
+         else                  /* output feature, even easier... */
+           {
+             ep->tx_sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_TX];
+             ep->output_next_index =
+               nsm->output_next_index_by_sw_if_index[ep->tx_sw_if_index];
+           }
+         ep->buffer_index = from[0];
+         buffered++;
+       }
+
+      if (is_trace)
+       {
+         if (b[1]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             nsim_trace_t *t = vlib_add_trace (vm, node, b[1], sizeof (*t));
+             t->expires = expires;
+             t->is_drop = is_drop[1];
+             t->is_lost = b[1]->error == loss_error;
+             t->tx_sw_if_index = (is_drop[1] == 0) ? ep->tx_sw_if_index : 0;
+           }
+       }
+
+      if (PREDICT_TRUE (is_drop[1] == 0))
+       {
+         ep = wp->entries + wp->tail;
+         wp->tail++;
+         if (wp->tail == wp->wheel_size)
+           wp->tail = 0;
+         wp->cursize++;
+
+         ep->tx_time = expires;
+         ep->rx_sw_if_index = vnet_buffer (b[1])->sw_if_index[VLIB_RX];
+         if (is_cross_connect)
+           {
+             ep->tx_sw_if_index =
+               (vnet_buffer (b[1])->sw_if_index[VLIB_RX] ==
+                nsm->sw_if_index0) ? nsm->sw_if_index1 : nsm->sw_if_index0;
+             ep->output_next_index =
+               (ep->tx_sw_if_index ==
+                nsm->sw_if_index0) ? nsm->
+               output_next_index0 : nsm->output_next_index1;
+           }
+         else                  /* output feature, even easier... */
+           {
+             ep->tx_sw_if_index = vnet_buffer (b[1])->sw_if_index[VLIB_TX];
+             ep->output_next_index =
+               nsm->output_next_index_by_sw_if_index[ep->tx_sw_if_index];
+           }
+         ep->buffer_index = from[1];
+         buffered++;
+       }
+
+      if (is_trace)
+       {
+         if (b[2]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             nsim_trace_t *t = vlib_add_trace (vm, node, b[2], sizeof (*t));
+             t->expires = expires;
+             t->is_drop = is_drop[2];
+             t->is_lost = b[2]->error == loss_error;
+             t->tx_sw_if_index = (is_drop[2] == 0) ? ep->tx_sw_if_index : 0;
+           }
+       }
+      if (PREDICT_TRUE (is_drop[2] == 0))
+       {
+         ep = wp->entries + wp->tail;
+         wp->tail++;
+         if (wp->tail == wp->wheel_size)
+           wp->tail = 0;
+         wp->cursize++;
+
+         ep->tx_time = expires;
+         ep->rx_sw_if_index = vnet_buffer (b[2])->sw_if_index[VLIB_RX];
+         if (is_cross_connect)
+           {
+             ep->tx_sw_if_index =
+               (vnet_buffer (b[2])->sw_if_index[VLIB_RX] ==
+                nsm->sw_if_index0) ? nsm->sw_if_index1 : nsm->sw_if_index0;
+             ep->output_next_index =
+               (ep->tx_sw_if_index ==
+                nsm->sw_if_index0) ? nsm->
+               output_next_index0 : nsm->output_next_index1;
+           }
+         else                  /* output feature, even easier... */
+           {
+             ep->tx_sw_if_index = vnet_buffer (b[2])->sw_if_index[VLIB_TX];
+             ep->output_next_index =
+               nsm->output_next_index_by_sw_if_index[ep->tx_sw_if_index];
+           }
+         ep->buffer_index = from[2];
+         buffered++;
+       }
+
+      if (is_trace)
+       {
+         if (b[2]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             nsim_trace_t *t = vlib_add_trace (vm, node, b[2], sizeof (*t));
+             t->expires = expires;
+             t->is_drop = is_drop[2];
+             t->is_lost = b[2]->error == loss_error;
+             t->tx_sw_if_index = (is_drop[2] == 0) ? ep->tx_sw_if_index : 0;
+           }
+       }
+      if (PREDICT_TRUE (is_drop[3] == 0))
+       {
+         ep = wp->entries + wp->tail;
+         wp->tail++;
+         if (wp->tail == wp->wheel_size)
+           wp->tail = 0;
+         wp->cursize++;
+
+         ep->tx_time = expires;
+         ep->rx_sw_if_index = vnet_buffer (b[3])->sw_if_index[VLIB_RX];
+         if (is_cross_connect)
+           {
+             ep->tx_sw_if_index =
+               (vnet_buffer (b[3])->sw_if_index[VLIB_RX] ==
+                nsm->sw_if_index0) ? nsm->sw_if_index1 : nsm->sw_if_index0;
+             ep->output_next_index =
+               (ep->tx_sw_if_index ==
+                nsm->sw_if_index0) ? nsm->
+               output_next_index0 : nsm->output_next_index1;
+           }
+         else                  /* output feature, even easier... */
+           {
+             ep->tx_sw_if_index = vnet_buffer (b[3])->sw_if_index[VLIB_TX];
+             ep->output_next_index =
+               nsm->output_next_index_by_sw_if_index[ep->tx_sw_if_index];
+           }
+         ep->buffer_index = from[3];
+         buffered++;
+       }
+
+      if (is_trace)
+       {
+         if (b[3]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             nsim_trace_t *t = vlib_add_trace (vm, node, b[3], sizeof (*t));
+             t->expires = expires;
+             t->is_drop = is_drop[3];
+             t->is_lost = b[3]->error == loss_error;
+             t->tx_sw_if_index = (is_drop[3] == 0) ? ep->tx_sw_if_index : 0;
+           }
+       }
+
+      if (PREDICT_FALSE (is_drop[0]))
+       *drop++ = from[0];
+      if (PREDICT_FALSE (is_drop[1]))
+       *drop++ = from[1];
+      if (PREDICT_FALSE (is_drop[2]))
+       *drop++ = from[2];
+      if (PREDICT_FALSE (is_drop[3]))
+       *drop++ = from[3];
+
+      b += 4;
+      next += 4;
+      from += 4;
+      n_left_from -= 4;
+    }
+
+slow_path:
 
-  /* There is no point in trying to do more than 1 pkt here */
   while (n_left_from > 0)
     {
-      b[0]->error = no_error;
       next[0] = NSIM_NEXT_DROP;
-      is_drop0 = 0;
+      is_drop[0] = 0;
       if (PREDICT_TRUE (wp->cursize < wp->wheel_size))
        {
          if (PREDICT_FALSE (nsm->drop_fraction != 0.0))
            {
              /* Get a random number on the closed interval [0,1] */
-             f64 rnd = random_f64 (&nsm->seed);
+             rnd[0] = random_f64 (&nsm->seed);
              /* Drop the pkt? */
-             if (rnd <= nsm->drop_fraction)
+             if (rnd[0] <= nsm->drop_fraction)
                {
                  b[0]->error = loss_error;
-                 is_drop0 = 1;
+                 is_drop[0] = 1;
                  goto do_trace;
                }
            }
@@ -133,18 +359,30 @@ nsim_inline (vlib_main_t * vm,
          wp->cursize++;
 
          ep->tx_time = expires;
-         ep->tx_sw_if_index =
-           (vnet_buffer (b[0])->sw_if_index[VLIB_RX] == nsm->sw_if_index0)
-           ? nsm->sw_if_index1 : nsm->sw_if_index0;
-         ep->current_length = vlib_buffer_length_in_chain (vm, b[0]);
-         ASSERT (ep->current_length <= WHEEL_ENTRY_DATA_SIZE);
-         clib_memcpy_fast (ep->data, vlib_buffer_get_current (b[0]),
-                           ep->current_length);
+         ep->rx_sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX];
+         if (is_cross_connect)
+           {
+             ep->tx_sw_if_index =
+               (vnet_buffer (b[0])->sw_if_index[VLIB_RX] ==
+                nsm->sw_if_index0) ? nsm->sw_if_index1 : nsm->sw_if_index0;
+             ep->output_next_index =
+               (ep->tx_sw_if_index ==
+                nsm->sw_if_index0) ? nsm->
+               output_next_index0 : nsm->output_next_index1;
+           }
+         else                  /* output feature, even easier... */
+           {
+             ep->tx_sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_TX];
+             ep->output_next_index =
+               nsm->output_next_index_by_sw_if_index[ep->tx_sw_if_index];
+           }
+         ep->buffer_index = from[0];
+         buffered++;
        }
       else                     /* out of wheel space, drop pkt */
        {
          b[0]->error = no_buffer_error;
-         is_drop0 = 1;
+         is_drop[0] = 1;
        }
 
     do_trace:
@@ -154,17 +392,42 @@ nsim_inline (vlib_main_t * vm,
            {
              nsim_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t));
              t->expires = expires;
-             t->is_drop = is_drop0;
+             t->is_drop = is_drop[0];
              t->is_lost = b[0]->error == loss_error;
-             t->tx_sw_if_index = (is_drop0 == 0) ? ep->tx_sw_if_index : 0;
+             t->tx_sw_if_index = (is_drop[0] == 0) ? ep->tx_sw_if_index : 0;
            }
        }
 
       b += 1;
       next += 1;
+      if (PREDICT_FALSE (is_drop[0]))
+       {
+         drop[0] = from[0];
+         drop++;
+       }
+      from++;
       n_left_from -= 1;
     }
-  vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors);
+  if (PREDICT_FALSE (drop > drops))
+    {
+      u32 n_left_to_drop = drop - drops;
+      drop = drops;
+
+      while (n_left_to_drop > 0)
+       {
+         u32 this_copy_size;
+         vlib_get_next_frame (vm, node, NSIM_NEXT_DROP, to_next,
+                              n_left_to_next);
+         this_copy_size = clib_min (n_left_to_drop, n_left_to_next);
+         clib_memcpy_fast (to_next, drop, this_copy_size * sizeof (u32));
+         n_left_to_next -= this_copy_size;
+         vlib_put_next_frame (vm, node, NSIM_NEXT_DROP, n_left_to_next);
+         drop += this_copy_size;
+         n_left_to_drop -= this_copy_size;
+       }
+    }
+  vlib_node_increment_counter (vm, node->node_index,
+                              NSIM_ERROR_BUFFERED, buffered);
   return frame->n_vectors;
 }
 
@@ -172,9 +435,11 @@ VLIB_NODE_FN (nsim_node) (vlib_main_t * vm, vlib_node_runtime_t * node,
                          vlib_frame_t * frame)
 {
   if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE))
-    return nsim_inline (vm, node, frame, 1 /* is_trace */ );
+    return nsim_inline (vm, node, frame,
+                       1 /* is_trace */ , 1 /* is_cross_connect */ );
   else
-    return nsim_inline (vm, node, frame, 0 /* is_trace */ );
+    return nsim_inline (vm, node, frame,
+                       0 /* is_trace */ , 1 /* is_cross_connect */ );
 }
 
 /* *INDENT-OFF* */
@@ -199,6 +464,40 @@ VLIB_REGISTER_NODE (nsim_node) =
 #endif /* CLIB_MARCH_VARIANT */
 /* *INDENT-ON* */
 
+VLIB_NODE_FN (nsim_feature_node) (vlib_main_t * vm,
+                                 vlib_node_runtime_t * node,
+                                 vlib_frame_t * frame)
+{
+  if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE))
+    return nsim_inline (vm, node, frame,
+                       1 /* is_trace */ , 0 /* is_cross_connect */ );
+  else
+    return nsim_inline (vm, node, frame,
+                       0 /* is_trace */ , 0 /* is_cross_connect */ );
+}
+
+/* *INDENT-OFF* */
+#ifndef CLIB_MARCH_VARIANT
+VLIB_REGISTER_NODE (nsim_feature_node) =
+{
+  .name = "nsim-output-feature",
+  .vector_size = sizeof (u32),
+  .format_trace = format_nsim_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN(nsim_error_strings),
+  .error_strings = nsim_error_strings,
+
+  .n_next_nodes = NSIM_N_NEXT,
+
+  /* edit / add dispositions here */
+  .next_nodes = {
+        [NSIM_NEXT_DROP] = "error-drop",
+  },
+};
+#endif /* CLIB_MARCH_VARIANT */
+/* *INDENT-ON* */
+
 /*
  * fd.io coding-style-patch-verification: ON
  *
index 7bb84ba..42531cd 100644 (file)
@@ -3,7 +3,7 @@
  * @brief VPP control-plane API messages for the network delay simulator
  */
 
-option version = "1.1.0";
+option version = "2.1.0";
 
 /** \brief enable / disable the network delay simulation cross-connect
     @param client_index - opaque cookie to identify the sender
@@ -12,7 +12,7 @@ option version = "1.1.0";
     @param sw_if_index0 - one interface to cross-connect
     @param sw_if_index1 - the other interface to cross-connect
 */
-autoreply define nsim_enable_disable 
+autoreply define nsim_cross_connect_enable_disable 
 {
   /* Client identifier, set from api_main.my_client_index */
   u32 client_index;
@@ -28,6 +28,27 @@ autoreply define nsim_enable_disable
   u32 sw_if_index1;
 };
 
+/** \brief enable / disable the network delay simulation output feature
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param enable_disable - enable or disable the feature
+    @param sw_if_index0 - interface 
+*/
+autoreply define nsim_output_feature_enable_disable 
+{
+  /* Client identifier, set from api_main.my_client_index */
+  u32 client_index;
+  
+  /* Arbitrary context, so client can match reply to request */
+  u32 context;
+  
+  /* Enable / disable the feature on the interfaces */
+  u8 enable_disable;
+  
+  /* Interface handles */
+  u32 sw_if_index;
+};
+
 /** \brief configure the network delay simulation cross-connect
     @param client_index - opaque cookie to identify the sender
     @param context - sender context, to match reply w/ request
@@ -49,3 +70,5 @@ autoreply define nsim_configure
   u64 bandwidth_in_bits_per_second;
   u32 packets_per_drop;
 };
+
+
index ce3396c..4120585 100644 (file)
@@ -60,15 +60,16 @@ nsim_main_t nsim_main;
 
 /* List of message types that this plugin understands */
 
-#define foreach_nsim_plugin_api_msg             \
-_(NSIM_ENABLE_DISABLE, nsim_enable_disable)     \
+#define foreach_nsim_plugin_api_msg                                     \
+_(NSIM_CROSS_CONNECT_ENABLE_DISABLE, nsim_cross_connect_enable_disable) \
+_(NSIM_OUTPUT_FEATURE_ENABLE_DISABLE, nsim_output_feature_enable_disable) \
 _(NSIM_CONFIGURE, nsim_configure)
 
-/* Action function shared between message handler and debug CLI */
+/* Action functions shared between message handlers and debug CLI */
 
 int
-nsim_enable_disable (nsim_main_t * nsm, u32 sw_if_index0,
-                    u32 sw_if_index1, int enable_disable)
+nsim_cross_connect_enable_disable (nsim_main_t * nsm, u32 sw_if_index0,
+                                  u32 sw_if_index1, int enable_disable)
 {
   vnet_sw_interface_t *sw;
   vnet_hw_interface_t *hw;
@@ -117,6 +118,41 @@ nsim_enable_disable (nsim_main_t * nsm, u32 sw_if_index0,
   return rv;
 }
 
+int
+nsim_output_feature_enable_disable (nsim_main_t * nsm, u32 sw_if_index,
+                                   int enable_disable)
+{
+  vnet_sw_interface_t *sw;
+  vnet_hw_interface_t *hw;
+  int rv = 0;
+
+  if (nsm->is_configured == 0)
+    return VNET_API_ERROR_CANNOT_ENABLE_DISABLE_FEATURE;
+
+  /* Utterly wrong? */
+  if (pool_is_free_index (nsm->vnet_main->interface_main.sw_interfaces,
+                         sw_if_index))
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  /* Not a physical port? */
+  sw = vnet_get_sw_interface (nsm->vnet_main, sw_if_index);
+  if (sw->type != VNET_SW_INTERFACE_TYPE_HARDWARE)
+    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+  /* Add a graph arc for the input / wheel scraper node */
+  hw = vnet_get_hw_interface (nsm->vnet_main, sw_if_index);
+  vec_validate_init_empty (nsm->output_next_index_by_sw_if_index, sw_if_index,
+                          ~0);
+  /* Note: use the tx node, this pkt has already visited the output node... */
+  nsm->output_next_index_by_sw_if_index[sw_if_index] =
+    vlib_node_add_next (nsm->vlib_main, nsim_input_node.index,
+                       hw->tx_node_index);
+
+  vnet_feature_enable_disable ("interface-output", "nsim-output-feature",
+                              sw_if_index, enable_disable, 0, 0);
+  return rv;
+}
+
 static int
 nsim_configure (nsim_main_t * nsm, f64 bandwidth, f64 delay, f64 packet_size,
                f64 drop_fraction)
@@ -134,7 +170,7 @@ nsim_configure (nsim_main_t * nsm, f64 bandwidth, f64 delay, f64 packet_size,
   if (delay == 0.0)
     return VNET_API_ERROR_INVALID_VALUE_2;
 
-  if (packet_size < 64.0 || packet_size > (f64) WHEEL_ENTRY_DATA_SIZE)
+  if (packet_size < 64.0 || packet_size > 9000.0)
     return VNET_API_ERROR_INVALID_VALUE_3;
 
   /* Toss the old wheel(s)... */
@@ -171,7 +207,6 @@ nsim_configure (nsim_main_t * nsm, f64 bandwidth, f64 delay, f64 packet_size,
   nsm->packet_size = packet_size;
 
   vec_validate (nsm->wheel_by_thread, num_workers);
-  vec_validate (nsm->buffer_indices_by_thread, num_workers);
 
   /* Initialize the output scheduler wheels */
   for (i = num_workers ? 1 : 0; i < num_workers + 1; i++)
@@ -192,8 +227,6 @@ nsim_configure (nsim_main_t * nsm, f64 bandwidth, f64 delay, f64 packet_size,
       wp->tail = 0;
       wp->entries = (void *) (wp + 1);
       nsm->wheel_by_thread[i] = wp;
-      vec_validate (nsm->buffer_indices_by_thread[i], VLIB_FRAME_SIZE - 1);
-      _vec_len (nsm->buffer_indices_by_thread[i]) = 0;
     }
 
   vlib_worker_thread_barrier_sync (vm);
@@ -217,22 +250,27 @@ nsim_configure (nsim_main_t * nsm, f64 bandwidth, f64 delay, f64 packet_size,
  * enable or disable the cross-connect
  */
 static clib_error_t *
-nsim_enable_disable_command_fn (vlib_main_t * vm,
-                               unformat_input_t * input,
-                               vlib_cli_command_t * cmd)
+nsim_cross_connect_enable_disable_command_fn (vlib_main_t * vm,
+                                             unformat_input_t * input,
+                                             vlib_cli_command_t * cmd)
 {
   nsim_main_t *nsm = &nsim_main;
+  unformat_input_t _line_input, *line_input = &_line_input;
   u32 sw_if_index0 = ~0;
   u32 sw_if_index1 = ~0;
   int enable_disable = 1;
   u32 tmp;
   int rv;
 
-  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+  /* 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 (input, "disable"))
+      if (unformat (line_input, "disable"))
        enable_disable = 0;
-      else if (unformat (input, "%U", unformat_vnet_sw_interface,
+      else if (unformat (line_input, "%U", unformat_vnet_sw_interface,
                         nsm->vnet_main, &tmp))
        {
          if (sw_if_index0 == ~0)
@@ -244,10 +282,13 @@ nsim_enable_disable_command_fn (vlib_main_t * vm,
        break;
     }
 
+  unformat_free (line_input);
+
   if (sw_if_index0 == ~0 || sw_if_index1 == ~0)
     return clib_error_return (0, "Please specify two interfaces...");
 
-  rv = nsim_enable_disable (nsm, sw_if_index0, sw_if_index1, enable_disable);
+  rv = nsim_cross_connect_enable_disable (nsm, sw_if_index0,
+                                         sw_if_index1, enable_disable);
 
   switch (rv)
     {
@@ -284,34 +325,49 @@ nsim_enable_disable_command_fn (vlib_main_t * vm,
  * @cliexpar
  * To enable or disable network simulation cross-connect
  * @clistart
- * nsim enable-disable TenGigabitEthernet2/0/0 TenGigabitEthernet2/0
- * nsim enable-disable TenGigabitEthernet2/0/0 TenGigabitEthernet2/0 disable
+ * nsim cross-connect enable-disable TenGigabitEthernet2/0/0 TenGigabitEthernet2/0
+ * nsim cross-connect enable-disable TenGigabitEthernet2/0/0 TenGigabitEthernet2/0 disable
  * @cliend
  * @cliexcmd{nsim enable-disable <intfc> <intfc> [disable]}
 ?*/
 /* *INDENT-OFF* */
 VLIB_CLI_COMMAND (nsim_enable_disable_command, static) =
 {
-  .path = "nsim enable-disable",
+  .path = "nsim cross-connect enable-disable",
   .short_help =
-  "nsim enable-disable <interface-name-1> <interface-name-2> [disable]",
-  .function = nsim_enable_disable_command_fn,
+  "nsim cross-connect enable-disable <interface-name-1> "
+  "<interface-name-2> [disable]",
+  .function = nsim_cross_connect_enable_disable_command_fn,
 };
 /* *INDENT-ON* */
 
 /* API message handler */
-static void vl_api_nsim_enable_disable_t_handler
-  (vl_api_nsim_enable_disable_t * mp)
+static void vl_api_nsim_cross_connect_enable_disable_t_handler
+  (vl_api_nsim_cross_connect_enable_disable_t * mp)
 {
-  vl_api_nsim_enable_disable_reply_t *rmp;
+  vl_api_nsim_cross_connect_enable_disable_reply_t *rmp;
   nsim_main_t *nsm = &nsim_main;
   int rv;
 
-  rv = nsim_enable_disable (nsm, ntohl (mp->sw_if_index0),
-                           ntohl (mp->sw_if_index1),
-                           (int) (mp->enable_disable));
+  rv = nsim_cross_connect_enable_disable (nsm, ntohl (mp->sw_if_index0),
+                                         ntohl (mp->sw_if_index1),
+                                         (int) (mp->enable_disable));
 
-  REPLY_MACRO (VL_API_NSIM_ENABLE_DISABLE_REPLY);
+  REPLY_MACRO (VL_API_NSIM_CROSS_CONNECT_ENABLE_DISABLE_REPLY);
+}
+
+/* API message handler */
+static void vl_api_nsim_output_feature_enable_disable_t_handler
+  (vl_api_nsim_output_feature_enable_disable_t * mp)
+{
+  vl_api_nsim_output_feature_enable_disable_reply_t *rmp;
+  nsim_main_t *nsm = &nsim_main;
+  int rv;
+
+  rv = nsim_output_feature_enable_disable (nsm, ntohl (mp->sw_if_index),
+                                          (int) (mp->enable_disable));
+
+  REPLY_MACRO (VL_API_NSIM_OUTPUT_FEATURE_ENABLE_DISABLE_REPLY);
 }
 
 /* API message handler */
@@ -340,6 +396,96 @@ vl_api_nsim_configure_t_handler (vl_api_nsim_configure_t * mp)
 }
 
 
+/*
+ * enable or disable the output_feature
+ */
+static clib_error_t *
+nsim_output_feature_enable_disable_command_fn (vlib_main_t * vm,
+                                              unformat_input_t * input,
+                                              vlib_cli_command_t * cmd)
+{
+  nsim_main_t *nsm = &nsim_main;
+  unformat_input_t _line_input, *line_input = &_line_input;
+  u32 sw_if_index = ~0;
+  int enable_disable = 1;
+  int rv;
+
+  /* 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, "disable"))
+       enable_disable = 0;
+      else if (unformat (line_input, "%U", unformat_vnet_sw_interface,
+                        nsm->vnet_main, &sw_if_index))
+       ;
+      else
+       {
+         clib_error_t *error = clib_error_return (0, "unknown input `%U'",
+                                                  format_unformat_error,
+                                                  line_input);
+         unformat_free (line_input);
+         return error;
+       }
+    }
+
+  unformat_free (line_input);
+
+  if (sw_if_index == ~0)
+    return clib_error_return (0, "Please specify one interface...");
+
+  rv = nsim_output_feature_enable_disable (nsm, sw_if_index, enable_disable);
+
+  switch (rv)
+    {
+    case 0:
+      break;
+
+    case VNET_API_ERROR_CANNOT_ENABLE_DISABLE_FEATURE:
+      return clib_error_return (0, "Not configured, please 'set nsim' first");
+
+    case VNET_API_ERROR_INVALID_SW_IF_INDEX:
+      return clib_error_return
+       (0, "Invalid interface, only works on physical ports");
+      break;
+
+    case VNET_API_ERROR_UNIMPLEMENTED:
+      return clib_error_return (0,
+                               "Device driver doesn't support redirection");
+      break;
+
+    default:
+      return clib_error_return
+       (0, "nsim_output_feature_enable_disable returned %d", rv);
+    }
+  return 0;
+}
+
+/*?
+ * Enable or disable network simulation output feature on an interface
+ * The network simulator must have already been configured, see
+ * the "nsim_configure" command.
+ *
+ * @cliexpar
+ * To enable or disable network simulation output feature
+ * @clistart
+ * nsim output-feature enable-disable TenGigabitEthernet2/0/0
+ * nsim output-feature enable-disable TenGigabitEthernet2/0/0 disable
+ * @cliend
+ * @cliexcmd{nsim output-feature enable-disable <intfc> [disable]}
+?*/
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (nsim_output_feature_enable_disable_command, static) =
+{
+  .path = "nsim output-feature enable-disable",
+  .short_help =
+  "nsim output-feature enable-disable <interface-name> [disable]",
+  .function = nsim_output_feature_enable_disable_command_fn,
+};
+/* *INDENT-ON* */
+
 /* Set up the API message handling tables */
 static clib_error_t *
 nsim_plugin_api_hookup (vlib_main_t * vm)
@@ -408,6 +554,15 @@ VNET_FEATURE_INIT (nsim, static) =
 };
 /* *INDENT-ON */
 
+/* *INDENT-OFF* */
+VNET_FEATURE_INIT (nsim_feature, static) =
+{
+  .arc_name = "interface-output",
+  .node_name = "nsim-output-feature",
+  .runs_before = VNET_FEATURES ("interface-tx"),
+};
+/* *INDENT-ON */
+
 /* *INDENT-OFF* */
 VLIB_PLUGIN_REGISTER () =
 {
@@ -541,7 +696,8 @@ set_nsim_command_fn (vlib_main_t * vm,
 VLIB_CLI_COMMAND (set_nsim_command, static) =
 {
   .path = "set nsim",
-  .short_help = "set nsim delay <time> bandwidth <bps> packet-size <nbytes>",
+  .short_help = "set nsim delay <time> bandwidth <bps> packet-size <nbytes>\n"
+  "    [packets-per-drop <nn>][drop-fraction <f64: 0.0 - 1.0>]",
   .function = set_nsim_command_fn,
 };
 /* *INDENT-ON*/
index c5264ec..6afe32d 100644 (file)
 #include <vppinfra/hash.h>
 #include <vppinfra/error.h>
 
-#define WHEEL_ENTRY_DATA_SIZE 1536     /* an even multiple of 64, pls */
-
 typedef struct
 {
   f64 tx_time;
+  u32 rx_sw_if_index;
   u32 tx_sw_if_index;
-  u32 current_length;
-    CLIB_CACHE_LINE_ALIGN_MARK (pad);
-  u8 data[WHEEL_ENTRY_DATA_SIZE];
+  u32 output_next_index;
+  u32 buffer_index;
+  u32 pad;                     /* pad to 32-bytes */
 } nsim_wheel_entry_t;
 
 typedef struct
@@ -54,12 +53,15 @@ typedef struct
   /* Two interfaces, cross-connected with delay */
   u32 sw_if_index0, sw_if_index1;
   u32 output_next_index0, output_next_index1;
+
+  /* N interfaces, using the output feature */
+  u32 *output_next_index_by_sw_if_index;
+
   /* Random seed for loss-rate simulation */
   u32 seed;
 
-  /* Per-thread buffer / scheduler wheels */
+  /* Per-thread scheduler wheels */
   nsim_wheel_t **wheel_by_thread;
-  u32 **buffer_indices_by_thread;
 
   /* Config parameters */
   f64 delay;
index 44c9f53..def6fcd 100644 (file)
@@ -24,7 +24,7 @@
 typedef struct
 {
   f64 expired;
-  u32 tx_sw_if_index;
+  u32 next_index;
 } nsim_tx_trace_t;
 
 #ifndef CLIB_MARCH_VARIANT
@@ -36,22 +36,20 @@ format_nsim_tx_trace (u8 * s, va_list * args)
   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
   nsim_tx_trace_t *t = va_arg (*args, nsim_tx_trace_t *);
 
-  s = format (s, "NSIM: tx at %.6f sw_if_index %d",
-             t->expired, t->tx_sw_if_index);
+  s = format (s, "NSIM: tx at %.6f next_index %d", t->expired, t->next_index);
   return s;
 }
 #endif /* CLIB_MARCH_VARIANT */
 
-#define foreach_nsim_tx_error                      \
-_(TX, "Packets transmitted")                    \
-_(DROPPED, "No buffer drops")
+#define foreach_nsim_tx_error                   \
+_(TRANSMITTED, "Packets transmitted")
 
 typedef enum
 {
 #define _(sym,str) NSIM_TX_ERROR_##sym,
   foreach_nsim_tx_error
 #undef _
-    NSIM_N_ERROR,
+    NSIM_TX_N_ERROR,
 } nsim_tx_error_t;
 
 #ifndef CLIB_MARCH_VARIANT
@@ -74,12 +72,9 @@ nsim_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
 {
   nsim_main_t *nsm = &nsim_main;
   u32 my_thread_index = vm->thread_index;
-  u32 *my_buffer_cache = nsm->buffer_indices_by_thread[my_thread_index];
   nsim_wheel_t *wp = nsm->wheel_by_thread[my_thread_index];
-  u32 n_trace = vlib_get_trace_count (vm, node);
   f64 now = vlib_time_now (vm);
-  uword n_rx_packets = 0;
-  vlib_buffer_t *b0;
+  uword n_tx_packets = 0;
   u32 bi0, next0;
   u32 *to_next;
   u32 next_index;
@@ -93,85 +88,30 @@ nsim_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
   /* First entry on the wheel isn't expired? */
   ep = wp->entries + wp->head;
   if (ep->tx_time > now)
-    return n_rx_packets;
+    return n_tx_packets;
 
-  /*
-   * We use per-thread buffer caches, so we need the freelist to
-   * initialize them...
-   */
   next_index = node->cached_next_index;
 
-  while (wp->cursize)
+  /* Be aware: this is not the usual coding pattern */
+  while (wp->cursize && ep->tx_time <= now)
     {
-      /* Be aware: this is not the usual coding pattern */
+      vlib_buffer_t *b0;
+
       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
 
-      while (n_left_to_next > 0 && ep->tx_time <= now)
+      while (n_left_to_next > 0)
        {
-         /* Out of local buffer cache? */
-         if (PREDICT_FALSE (_vec_len (my_buffer_cache) == 0))
-           {
-             u32 n =
-               vlib_buffer_alloc (vm, my_buffer_cache, VLIB_FRAME_SIZE);
-             _vec_len (my_buffer_cache) = n;
-
-             /* Ugh, drop the rest of the expired entries */
-             if (n == 0)
-               {
-                 u32 drops = 0;
-                 while (ep->tx_time <= now && wp->cursize)
-                   {
-                     wp->head++;
-                     if (wp->head == wp->wheel_size)
-                       wp->head = 0;
-                     ep = wp->entries + wp->head;
-                     wp->cursize--;
-                     drops++;
-                   }
-                 /* Count the drops */
-                 vlib_node_increment_counter (vm, node->node_index,
-                                              NSIM_TX_ERROR_DROPPED, drops);
-                 /* Ship any pkts we already processed */
-                 vlib_put_next_frame (vm, node, next_index, n_left_to_next);
-                 return n_rx_packets + drops;
-               }
-           }
-
-         /* Allocate a buffer */
-         bi0 = my_buffer_cache[_vec_len (my_buffer_cache) - 1];
-         _vec_len (my_buffer_cache) -= 1;
+         /* prefetch one line / 2 entries ahead */
+         if ((((uword) ep) & (CLIB_CACHE_LINE_BYTES - 1)) == 0)
+           CLIB_PREFETCH ((ep + 2), CLIB_CACHE_LINE_BYTES, LOAD);
+         /* Pick up buffer from the wheel */
+         bi0 = ep->buffer_index;
 
          to_next[0] = bi0;
          to_next += 1;
          n_left_to_next -= 1;
 
-         b0 = vlib_get_buffer (vm, bi0);
-         /* Initialize the buffer */
-
-         b0->current_data = 0;
-         b0->current_length = ep->current_length;
-
-         VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b0);
-
-         if (PREDICT_FALSE (n_trace))
-           {
-             nsim_tx_trace_t *t0;
-             vlib_trace_buffer (vm, node, next_index, b0,
-                                0 /* follow_chain */ );
-             t0 = vlib_add_trace (vm, node, b0, sizeof (*t0));
-             t0->expired = ep->tx_time;
-             t0->tx_sw_if_index = ep->tx_sw_if_index;
-           }
-
-         /* Copy data from the ring */
-         clib_memcpy_fast (b0->data, ep->data, ep->current_length);
-         b0->flags |= VLIB_BUFFER_TOTAL_LENGTH_VALID;
-         vnet_buffer (b0)->sw_if_index[VLIB_TX] = ep->tx_sw_if_index;
-         vnet_buffer (b0)->sw_if_index[VLIB_RX] =
-           (ep->tx_sw_if_index == nsm->sw_if_index0) ? nsm->sw_if_index1 :
-           nsm->sw_if_index0;
-         next0 = (ep->tx_sw_if_index == nsm->sw_if_index0) ?
-           nsm->output_next_index0 : nsm->output_next_index1;
+         next0 = ep->output_next_index;
 
          /* verify speculative enqueue, maybe switch current next frame */
          vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
@@ -183,7 +123,19 @@ nsim_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
            wp->head = 0;
          wp->cursize--;
          ep = wp->entries + wp->head;
-         n_rx_packets++;
+         n_tx_packets++;
+
+         if (is_trace)
+           {
+             b0 = vlib_get_buffer (vm, ep->buffer_index);
+             if (b0->flags & VLIB_BUFFER_IS_TRACED)
+               {
+                 nsim_tx_trace_t *t =
+                   vlib_add_trace (vm, node, b0, sizeof (*t));
+                 t->expired = now;
+                 t->next_index = next0;
+               }
+           }
 
          /* Out of ring entries? */
          if (PREDICT_FALSE (wp->cursize == 0))
@@ -196,7 +148,9 @@ nsim_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
       if (ep->tx_time > now)
        break;
     }
-  return n_rx_packets;
+  vlib_node_increment_counter (vm, node->node_index,
+                              NSIM_TX_ERROR_TRANSMITTED, n_tx_packets);
+  return n_tx_packets;
 }
 
 VLIB_NODE_FN (nsim_input_node) (vlib_main_t * vm, vlib_node_runtime_t * node,
@@ -221,7 +175,7 @@ VLIB_REGISTER_NODE (nsim_input_node) =
 
   .format_trace = format_nsim_tx_trace,
 
-  .n_errors = NSIM_N_ERROR,
+  .n_errors = NSIM_TX_N_ERROR,
   .error_strings = nsim_tx_error_strings,
 };
 #endif /* CLIB_MARCH_VARIANT */
index 7123703..35f222e 100644 (file)
@@ -60,7 +60,8 @@ nsim_test_main_t nsim_test_main;
 #include <vlibapi/vat_helper_macros.h>
 
 #define foreach_standard_reply_retval_handler   \
-_(nsim_enable_disable_reply)                    \
+_(nsim_cross_connect_enable_disable_reply)      \
+_(nsim_output_feature_enable_disable_reply)     \
 _(nsim_configure_reply)
 
 #define _(n)                                            \
@@ -83,19 +84,22 @@ foreach_standard_reply_retval_handler;
  * Table of message reply handlers, must include boilerplate handlers
  * we just generated
  */
-#define foreach_vpe_api_reply_msg                       \
-_(NSIM_ENABLE_DISABLE_REPLY, nsim_enable_disable_reply) \
+#define foreach_vpe_api_reply_msg               \
+_(NSIM_CROSS_CONNECT_ENABLE_DISABLE_REPLY,      \
+nsim_cross_connect_enable_disable_reply)        \
+_(NSIM_OUTPUT_FEATURE_ENABLE_DISABLE_REPLY,     \
+nsim_output_feature_enable_disable_reply)       \
 _(NSIM_CONFIGURE_REPLY, nsim_configure_reply)
 
 static int
-api_nsim_enable_disable (vat_main_t * vam)
+api_nsim_cross_connect_enable_disable (vat_main_t * vam)
 {
   unformat_input_t *i = vam->input;
   int enable_disable = 1;
   u32 sw_if_index0 = ~0;
   u32 sw_if_index1 = ~0;
   u32 tmp;
-  vl_api_nsim_enable_disable_t *mp;
+  vl_api_nsim_cross_connect_enable_disable_t *mp;
   int ret;
 
   /* Parse args required to build the message */
@@ -128,7 +132,7 @@ api_nsim_enable_disable (vat_main_t * vam)
     }
 
   /* Construct the API message */
-  M (NSIM_ENABLE_DISABLE, mp);
+  M (NSIM_CROSS_CONNECT_ENABLE_DISABLE, mp);
   mp->sw_if_index0 = ntohl (sw_if_index0);
   mp->sw_if_index1 = ntohl (sw_if_index1);
   mp->enable_disable = enable_disable;
@@ -141,6 +145,47 @@ api_nsim_enable_disable (vat_main_t * vam)
   return ret;
 }
 
+static int
+api_nsim_output_feature_enable_disable (vat_main_t * vam)
+{
+  unformat_input_t *i = vam->input;
+  int enable_disable = 1;
+  u32 sw_if_index = ~0;
+  vl_api_nsim_output_feature_enable_disable_t *mp;
+  int ret;
+
+  /* Parse args required to build the message */
+  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
+       ;
+      else if (unformat (i, "sw_if_index %d", &sw_if_index))
+       ;
+      else if (unformat (i, "disable"))
+       enable_disable = 0;
+      else
+       break;
+    }
+
+  if (sw_if_index == ~0)
+    {
+      errmsg ("missing interface name / explicit sw_if_index number \n");
+      return -99;
+    }
+
+  /* Construct the API message */
+  M (NSIM_OUTPUT_FEATURE_ENABLE_DISABLE, mp);
+  mp->sw_if_index = ntohl (sw_if_index);
+  mp->enable_disable = enable_disable;
+
+  /* send it... */
+  S (mp);
+
+  /* Wait for a reply... */
+  W (ret);
+  return ret;
+}
+
 static uword
 unformat_delay (unformat_input_t * input, va_list * args)
 {
@@ -229,9 +274,10 @@ api_nsim_configure (vat_main_t * vam)
  * and that the data plane plugin processes
  */
 #define foreach_vpe_api_msg                                             \
-_(nsim_enable_disable,                                                  \
+_(nsim_cross_connect_enable_disable,                                    \
 "[<intfc0> | sw_if_index <swif0>] [<intfc1> | sw_if_index <swif1>] [disable]") \
-_(nsim_configure, "delay <time> bandwidth <bw> [packet-size <nn>]" \
+_(nsim_output_feature_enable_disable,"[<intfc> | sw_if_index <nnn> [disable]") \
+_(nsim_configure, "delay <time> bandwidth <bw> [packet-size <nn>]"      \
 "[packets-per-drop <nnnn>]")
 
 static void