nsim: add packet loss simulation, docs

Change-Id: Ic9747541aad8148ebf7d520b525b99c4cc3961f3
Signed-off-by: Dave Barach <dave@barachs.net>
diff --git a/docs/usecases/index.rst b/docs/usecases/index.rst
index 735947c..3dabd78 100644
--- a/docs/usecases/index.rst
+++ b/docs/usecases/index.rst
@@ -14,5 +14,5 @@
    vhost/index.rst
    homegateway
    contiv/index.rst
-
+   networksim
 
diff --git a/docs/usecases/networksim.md b/docs/usecases/networksim.md
new file mode 100644
index 0000000..817ddf8
--- /dev/null
+++ b/docs/usecases/networksim.md
@@ -0,0 +1,90 @@
+Network Simulator Plugin
+========================
+
+Vpp includes a fairly capable network simulator plugin, which can
+simulate real-world round-trip times and a configurable network packet
+loss rate. It's perfect for evaluating the performance of a TCP stack
+under specified delay/bandwidth/loss conditions.
+
+The "nsim" plugin cross-connects two physical interfaces at layer 2,
+introducing the specified delay and network loss
+parameters. Reconfiguration on the fly is OK, with the proviso that
+packets held in the network simulator scheduling wheel will be lost.
+
+Configuration
+-------------
+
+Configuration by debug CLI is simple. First, specify the simulator
+configuration: unidirectional delay (half of the desired RTT), the
+link bandwidth, and the expected average packet size. These parameters
+allow the network simulator allocate the right amount of buffering to
+produce the requested delay/bandwidth product.
+
+```
+    set nsim delay 25.0 ms bandwidth 10 gbit packet-size 128 
+```
+
+To simulate network packet drops, add either "packets-per-drop <nnnnn>" or
+"drop-fraction [0.0 ... 1.0]" parameters:
+
+```
+    set nsim delay 25.0 ms bandwidth 10 gbit packet-size 128 packets-per-drop 10000
+```
+Remember to configure the layer-2 cross-connect:
+
+```
+    nsim enable-disable <interface-1> <interface-2>
+```
+
+Packet Generator Configuration
+------------------------------
+
+Here's a unit-test configuration for the vpp packet generator:
+
+```
+  loop cre
+  set int ip address loop0 11.22.33.1/24
+  set int state loop0 up
+
+  loop cre
+  set int ip address loop1 11.22.34.1/24
+  set int state loop1 up
+
+  set nsim delay 1.0 ms bandwidth 10 gbit packet-size 128 packets-per-drop 1000
+  nsim enable-disable loop0 loop1
+
+  packet-generator new {
+      name s0
+      limit 10000
+      size 128-128
+      interface loop0
+      node ethernet-input
+      data { IP4: 1.2.3 -> 4.5.6 
+             UDP: 11.22.33.44 -> 11.22.34.44
+             UDP: 1234 -> 2345
+             incrementing 114 
+      }
+  } 
+```
+
+For extra realism, the network simulator drops any specific packet
+with the specified probability. In this example, we see that slight
+variation from run to run occurs as it should.
+
+```
+    DBGvpp# pa en
+    DBGvpp# sh err
+       Count                    Node                  Reason
+          9991                  nsim                  Packets buffered
+             9                  nsim                  Network loss simulation drop packets
+          9991             ethernet-input             l3 mac mismatch
+
+    DBGvpp# clear err
+    DBGvpp# pa en
+    DBGvpp# sh err
+    sh err
+       Count                    Node                  Reason
+          9993                  nsim                  Packets buffered
+             7                  nsim                  Network loss simulation drop packets
+          9993             ethernet-input             l3 mac mismatch
+```
diff --git a/src/plugins/nsim/node.c b/src/plugins/nsim/node.c
index d641377..25112ab 100644
--- a/src/plugins/nsim/node.c
+++ b/src/plugins/nsim/node.c
@@ -25,6 +25,7 @@
   f64 expires;
   u32 tx_sw_if_index;
   int is_drop;
+  int is_lost;
 } nsim_trace_t;
 
 #ifndef CLIB_MARCH_VARIANT
@@ -38,7 +39,8 @@
   nsim_trace_t *t = va_arg (*args, nsim_trace_t *);
 
   if (t->is_drop)
-    s = format (s, "NSIM: ring drop");
+    s = format (s, "NSIM: dropped, %s", t->is_lost ?
+		"simulated network loss" : "no space in ring");
   else
     s = format (s, "NSIM: tx time %.6f sw_if_index %d",
 		t->expires, t->tx_sw_if_index);
@@ -51,7 +53,8 @@
 
 #define foreach_nsim_error                              \
 _(BUFFERED, "Packets buffered")                         \
-_(DROPPED, "Packets dropped due to lack of space")
+_(DROPPED, "Packets dropped due to lack of space")	\
+_(LOSS, "Network loss simulation drop packets")
 
 typedef enum
 {
@@ -90,6 +93,7 @@
   int is_drop0;
   u32 no_error = node->errors[NSIM_ERROR_BUFFERED];
   u32 no_buffer_error = node->errors[NSIM_ERROR_DROPPED];
+  u32 loss_error = node->errors[NSIM_ERROR_LOSS];
   nsim_wheel_entry_t *ep = 0;
 
   ASSERT (wp);
@@ -109,6 +113,19 @@
       is_drop0 = 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);
+	      /* Drop the pkt? */
+	      if (rnd <= nsm->drop_fraction)
+		{
+		  b[0]->error = loss_error;
+		  is_drop0 = 1;
+		  goto do_trace;
+		}
+	    }
+
 	  ep = wp->entries + wp->tail;
 	  wp->tail++;
 	  if (wp->tail == wp->wheel_size)
@@ -130,6 +147,7 @@
 	  is_drop0 = 1;
 	}
 
+    do_trace:
       if (is_trace)
 	{
 	  if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
@@ -137,6 +155,7 @@
 	      nsim_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t));
 	      t->expires = expires;
 	      t->is_drop = is_drop0;
+	      t->is_lost = b[0]->error == loss_error;
 	      t->tx_sw_if_index = (is_drop0 == 0) ? ep->tx_sw_if_index : 0;
 	    }
 	}
diff --git a/src/plugins/nsim/nsim.api b/src/plugins/nsim/nsim.api
index 8917426..7bb84ba 100644
--- a/src/plugins/nsim/nsim.api
+++ b/src/plugins/nsim/nsim.api
@@ -3,7 +3,7 @@
  * @brief VPP control-plane API messages for the network delay simulator
  */
 
-option version = "1.0.0";
+option version = "1.1.0";
 
 /** \brief enable / disable the network delay simulation cross-connect
     @param client_index - opaque cookie to identify the sender
@@ -47,4 +47,5 @@
   u32 delay_in_usec;
   u32 average_packet_size;
   u64 bandwidth_in_bits_per_second;
+  u32 packets_per_drop;
 };
diff --git a/src/plugins/nsim/nsim.c b/src/plugins/nsim/nsim.c
index ec79070..ce3396c 100644
--- a/src/plugins/nsim/nsim.c
+++ b/src/plugins/nsim/nsim.c
@@ -118,7 +118,8 @@
 }
 
 static int
-nsim_configure (nsim_main_t * nsm, f64 bandwidth, f64 delay, f64 packet_size)
+nsim_configure (nsim_main_t * nsm, f64 bandwidth, f64 delay, f64 packet_size,
+		f64 drop_fraction)
 {
   u64 total_buffer_size_in_bytes, per_worker_buffer_size;
   u64 wheel_slots_per_worker;
@@ -148,6 +149,7 @@
     }
 
   nsm->delay = delay;
+  nsm->drop_fraction = drop_fraction;
 
   /* delay in seconds, bandwidth in bits/sec */
   total_buffer_size_in_bytes = (u32) ((delay * bandwidth) / 8.0) + 0.5;
@@ -318,14 +320,21 @@
 {
   vl_api_nsim_configure_reply_t *rmp;
   nsim_main_t *nsm = &nsim_main;
-  f64 delay, bandwidth, packet_size;
+  f64 delay, bandwidth, packet_size, drop_fraction;
+  u32 packets_per_drop;
   int rv;
 
   delay = ((f64) (ntohl (mp->delay_in_usec))) * 1e-6;
   bandwidth = (f64) (clib_net_to_host_u64 (mp->bandwidth_in_bits_per_second));
   packet_size = (f64) (ntohl (mp->average_packet_size));
 
-  rv = nsim_configure (nsm, bandwidth, delay, packet_size);
+  packets_per_drop = ntohl (mp->packets_per_drop);
+  if (packets_per_drop > 0)
+    drop_fraction = 1.0 / (f64) (packets_per_drop);
+  else
+    drop_fraction = 0.0;
+
+  rv = nsim_configure (nsm, bandwidth, delay, packet_size, drop_fraction);
 
   REPLY_MACRO (VL_API_NSIM_CONFIGURE_REPLY);
 }
@@ -447,6 +456,8 @@
   nsim_main_t *nsm = &nsim_main;
   f64 delay, bandwidth;
   f64 packet_size = 1500.0;
+  f64 drop_fraction = 0.0;
+  u32 packets_per_drop;
   u32 num_workers = vlib_num_workers ();
   int rv;
 
@@ -459,11 +470,22 @@
 	;
       else if (unformat (input, "packet-size %f", &packet_size))
 	;
+      else if (unformat (input, "packets-per-drop %d", &packets_per_drop))
+	{
+	  if (packets_per_drop > 0)
+	    drop_fraction = 1.0 / ((f64) packets_per_drop);
+	}
+      else if (unformat (input, "drop-fraction %f", &drop_fraction))
+	{
+	  if (drop_fraction < 0.0 || drop_fraction > 1.0)
+	    return clib_error_return
+	      (0, "drop fraction must be between zero and 1");
+	}
       else
 	break;
     }
 
-  rv = nsim_configure (nsm, bandwidth, delay, packet_size);
+  rv = nsim_configure (nsm, bandwidth, delay, packet_size, drop_fraction);
 
   switch (rv)
     {
@@ -485,6 +507,10 @@
 
   vlib_cli_output (vm, "Configured link delay %.2f ms, %.2f ms round-trip",
 		   nsm->delay * 1e3, 2.0 * nsm->delay * 1e3);
+  if (nsm->drop_fraction > 0.0)
+    vlib_cli_output (vm, "... simulating a network drop fraction of %.5f",
+		     nsm->drop_fraction);
+
 
   if (num_workers)
     vlib_cli_output (vm, "Sim uses %llu bytes per thread, %llu bytes total",
@@ -548,6 +574,10 @@
 		   "...inserting link delay of %.2f ms, %.2f ms round-trip",
 		   nsm->delay * 1e3, 2.0 * nsm->delay * 1e3);
 
+  if (nsm->drop_fraction > 0.0)
+    vlib_cli_output (vm, "... simulating a network drop fraction of %.5f",
+		     nsm->drop_fraction);
+
   if (verbose)
     {
 
diff --git a/src/plugins/nsim/nsim.h b/src/plugins/nsim/nsim.h
index d0b5ed3..c5264ec 100644
--- a/src/plugins/nsim/nsim.h
+++ b/src/plugins/nsim/nsim.h
@@ -54,6 +54,8 @@
   /* Two interfaces, cross-connected with delay */
   u32 sw_if_index0, sw_if_index1;
   u32 output_next_index0, output_next_index1;
+  /* Random seed for loss-rate simulation */
+  u32 seed;
 
   /* Per-thread buffer / scheduler wheels */
   nsim_wheel_t **wheel_by_thread;
@@ -63,6 +65,7 @@
   f64 delay;
   f64 bandwidth;
   f64 packet_size;
+  f64 drop_fraction;
 
   u64 mmap_size;
 
diff --git a/src/plugins/nsim/nsim_input.c b/src/plugins/nsim/nsim_input.c
index 66bd8d1..2e328a5 100644
--- a/src/plugins/nsim/nsim_input.c
+++ b/src/plugins/nsim/nsim_input.c
@@ -76,6 +76,7 @@
   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;
@@ -153,6 +154,18 @@
 	  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;
diff --git a/src/plugins/nsim/nsim_test.c b/src/plugins/nsim/nsim_test.c
index 0e2a26a..7123703 100644
--- a/src/plugins/nsim/nsim_test.c
+++ b/src/plugins/nsim/nsim_test.c
@@ -182,6 +182,7 @@
   f64 delay = 0.0, bandwidth = 0.0;
   f64 packet_size = 1500.0;
   u32 num_workers = vlib_num_workers ();
+  u32 packets_per_drop = 0;
   int ret;
 
   while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
@@ -192,6 +193,8 @@
 	;
       else if (unformat (i, "packet-size %f", &packet_size))
 	;
+      else if (unformat (i, "packets-per-drop %u", &packets_per_drop))
+	;
       else
 	break;
     }
@@ -211,6 +214,7 @@
   mp->bandwidth_in_bits_per_second = (u64) (bandwidth);
   mp->bandwidth_in_bits_per_second =
     clib_host_to_net_u64 (mp->bandwidth_in_bits_per_second);
+  mp->packets_per_drop = ntohl (packets_per_drop);
 
   /* send it... */
   S (mp);
@@ -227,7 +231,8 @@
 #define foreach_vpe_api_msg                                             \
 _(nsim_enable_disable,                                                  \
 "[<intfc0> | sw_if_index <swif0>] [<intfc1> | sw_if_index <swif1>] [disable]") \
-_(nsim_configure, "delay <time> bandwidth <bw> [packet-size <nn>]")
+_(nsim_configure, "delay <time> bandwidth <bw> [packet-size <nn>]" \
+"[packets-per-drop <nnnn>]")
 
 static void
 nsim_api_hookup (vat_main_t * vam)