Add support for capturing packets on packet generator interfaces

This patch introduces following changes:
 - 4 predefined pg/stream[0-3] interfaces are removed
 - Interface naming is changed form pg/streamX to pgX where X can be
   any u32 value
 - one pgX interface can handle multiple streams
 - keyword "source pgX" is added to "packet-generator add" command, X is 0
   by default
 - new cli "packet-generator capture" is introduced
 - new cli "create packet-generator interface pgX"

Change-Id: I768d075b9d4a34f0b5073debdc5dd4a0880c682c
Signed-off-by: Damjan Marion <damarion@cisco.com>
diff --git a/vnet/vnet/cop/cop.c b/vnet/vnet/cop/cop.c
index a352b37..465d6c9 100644
--- a/vnet/vnet/cop/cop.c
+++ b/vnet/vnet/cop/cop.c
@@ -22,6 +22,7 @@
   cop_main_t * cm = &cop_main;
   cop_config_data_t _data, *data = &_data;
   vlib_main_t * vm = cm->vlib_main;
+  vnet_hw_interface_t * hi = vnet_get_sup_hw_interface (vnm, sw_if_index);;
   cop_config_main_t * ccm;
   int address_family;
   u32 ci, default_next;
@@ -32,7 +33,7 @@
    * Ignore local interface, pg interfaces. $$$ need a #define for the
    * first "real" interface. The answer is 5 at the moment.
    */
-  if (sw_if_index < 5)
+  if (hi->dev_class_index == vnet_local_interface_device_class.index)
     return 0;
 
    for (address_family = VNET_COP_IP4; address_family < VNET_N_COPS;
diff --git a/vnet/vnet/interface.h b/vnet/vnet/interface.h
index 30dcf27..11bb234 100644
--- a/vnet/vnet/interface.h
+++ b/vnet/vnet/interface.h
@@ -354,6 +354,8 @@
 
 } vnet_hw_interface_t;
 
+extern vnet_device_class_t vnet_local_interface_device_class;
+
 typedef enum {
   /* A hw interface. */
   VNET_SW_INTERFACE_TYPE_HARDWARE,
diff --git a/vnet/vnet/misc.c b/vnet/vnet/misc.c
index 9dbed8d..89bc15c 100644
--- a/vnet/vnet/misc.c
+++ b/vnet/vnet/misc.c
@@ -56,7 +56,7 @@
   return f->n_vectors;
 }
 
-VNET_DEVICE_CLASS (vnet_local_interface_device_class,static) = {
+VNET_DEVICE_CLASS (vnet_local_interface_device_class) = {
   .name = "local",
   .tx_function = vnet_local_interface_tx,
 };
diff --git a/vnet/vnet/pg/cli.c b/vnet/vnet/pg/cli.c
index 495eac2..428a3b6 100644
--- a/vnet/vnet/pg/cli.c
+++ b/vnet/vnet/pg/cli.c
@@ -37,6 +37,8 @@
  *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  */
 
+#include <sys/stat.h>
+
 #include <vnet/vnet.h>
 #include <vnet/pg/pg.h>
 
@@ -248,6 +250,7 @@
   s.node_index = ~0;
   s.max_packet_bytes = s.min_packet_bytes = 64;
   s.buffer_bytes = VLIB_BUFFER_DEFAULT_FREE_LIST_BYTES;
+  s.if_id = 0;
   pcap_file_name = 0;
   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
     {
@@ -267,6 +270,9 @@
 	  s.sw_if_index[VLIB_TX] = hi->sw_if_index;
 	}
 
+      else if (unformat (input, "source pg%u",&s.if_id))
+	;
+
       else if (unformat (input, "node %U",
 			 unformat_vlib_node, vm, &s.node_index))
 	;
@@ -431,6 +437,113 @@
   .function = change_stream_parameters,
 };
 
+static clib_error_t *
+pg_capture_cmd_fn (vlib_main_t * vm,
+		  unformat_input_t * input,
+		  vlib_cli_command_t * cmd)
+{
+  pg_main_t * pg = &pg_main;
+  clib_error_t * error = 0;
+  vnet_main_t * vnm = vnet_get_main();
+  unformat_input_t _line_input, * line_input = &_line_input;
+  vnet_hw_interface_t * hi = 0;
+  pg_interface_t * pi;
+  u8 * pcap_file_name = 0;
+  u32 hw_if_index;
+  u32 count = ~0;
+
+  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, "%U",
+			 unformat_vnet_hw_interface, vnm, &hw_if_index))
+	{
+	  hi = vnet_get_hw_interface (vnm, hw_if_index);
+	}
+
+      else if (unformat (line_input, "pcap %s", &pcap_file_name))
+	;
+      else if (unformat (line_input, "count %u", &count))
+	;
+
+      else
+	{
+	  error = clib_error_create ("unknown input `%U'",
+				     format_unformat_error, input);
+	  return error;
+	}
+    }
+
+  if (!hi)
+    return clib_error_return (0, "Please specify interface name");
+
+  if (hi->dev_class_index != pg_dev_class.index)
+    return clib_error_return (0, "Please specify packet-generator interface");
+
+  if (!pcap_file_name)
+    return clib_error_return (0, "Please specify pcap file name");
+
+  {
+    struct stat sb;
+    if (stat ((char *) pcap_file_name, &sb) != -1)
+      return clib_error_return (0, "Cannot create pcap file");
+  }
+
+  unformat_free (line_input);
+
+  pi = pool_elt_at_index (pg->interfaces, hi->dev_instance);
+  vec_free (pi->pcap_file_name);
+  pi->pcap_file_name = pcap_file_name;
+  memset (&pi->pcap_main, 0, sizeof (pi->pcap_main));
+  pi->pcap_main.file_name = (char *) pi->pcap_file_name;
+  pi->pcap_main.n_packets_to_capture = count;
+  pi->pcap_main.packet_type = PCAP_PACKET_TYPE_ethernet;
+
+  return 0;
+}
+
+VLIB_CLI_COMMAND (pg_capture_cmd, static) = {
+  .path = "packet-generator capture",
+  .short_help = "packet-generator capture <interface name> pcap <filename> [count <n>]",
+  .function = pg_capture_cmd_fn,
+};
+
+static clib_error_t *
+create_pg_if_cmd_fn (vlib_main_t * vm,
+		     unformat_input_t * input,
+		     vlib_cli_command_t * cmd)
+{
+  pg_main_t * pg = &pg_main;
+  unformat_input_t _line_input, * line_input = &_line_input;
+  u32 if_id;
+
+  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, "interface pg%u", &if_id))
+	;
+
+      else
+	return clib_error_create ("unknown input `%U'",
+				  format_unformat_error, input);
+    }
+
+  unformat_free (line_input);
+
+  pg_interface_add_or_get (pg, if_id);
+  return 0;
+}
+
+VLIB_CLI_COMMAND (create_pg_if_cmd, static) = {
+  .path = "create packet-generator",
+  .short_help = "create packet-generator interface <interface name>",
+  .function = create_pg_if_cmd_fn,
+};
+
 /* Dummy init function so that we can be linked in. */
 static clib_error_t * pg_cli_init (vlib_main_t * vm)
 { return 0; }
diff --git a/vnet/vnet/pg/init.c b/vnet/vnet/pg/init.c
index f598050..5cbb6db 100644
--- a/vnet/vnet/pg/init.c
+++ b/vnet/vnet/pg/init.c
@@ -47,9 +47,9 @@
 {
   clib_error_t * error;
   pg_main_t * pg = &pg_main;
-  int i, j;
 
   pg->vlib_main = vm;
+  pg->if_index_by_if_id = hash_create (0, sizeof (uword));
 
   if ((error = vlib_call_init_function (vm, vnet_main_init)))
     goto done;
@@ -57,20 +57,6 @@
   if ((error = vlib_call_init_function (vm, pg_cli_init)))
     goto done;
 
-  /* Create/free interfaces so that they exist and can be
-     used as a destination interface for streams.  Also, create
-     a fixed number of pg interfaces so that interface numbering can
-     be made to be deterministic (at least if <= 4 streams are ever used). */
-  for (i = 0; i < 4; i++)
-    {
-      j = pg_interface_find_free (pg, i);
-      ASSERT (j == i);
-    }
-
-  /* Free interfaces. */
-  for (i = j; i >= 0; i--)
-    vec_add1 (pg->free_interfaces, i);
-
  done:
   return error;
 }
diff --git a/vnet/vnet/pg/output.c b/vnet/vnet/pg/output.c
index cc098da..5ce3f90 100644
--- a/vnet/vnet/pg/output.c
+++ b/vnet/vnet/pg/output.c
@@ -38,15 +38,35 @@
  */
 
 #include <vlib/vlib.h>
+#include <vnet/vnet.h>
 #include <vnet/pg/pg.h>
+#include <vnet/ethernet/ethernet.h>
 
 uword
 pg_output (vlib_main_t * vm,
 	   vlib_node_runtime_t * node,
 	   vlib_frame_t * frame)
 {
+  pg_main_t * pg = &pg_main;
   u32 * buffers = vlib_frame_args (frame);
   uword n_buffers = frame->n_vectors;
-  vlib_buffer_free_no_next (vm, buffers, n_buffers);
+  uword n_left = n_buffers;
+  vnet_interface_output_runtime_t * rd = (void *) node->runtime_data;
+  pg_interface_t * pif = pool_elt_at_index (pg->interfaces, rd->dev_instance);
+
+  if (pif->pcap_file_name != 0)
+    {
+      while (n_left > 0)
+	{
+	  n_left--;
+	  u32 bi0 = buffers[0];
+	  buffers++;
+
+	  pcap_add_buffer (&pif->pcap_main, vm, bi0, ETHERNET_MAX_PACKET_BYTES);
+	}
+      pcap_write (&pif->pcap_main);
+    }
+
+  vlib_buffer_free_no_next (vm, vlib_frame_args (frame), n_buffers);
   return n_buffers;
 }
diff --git a/vnet/vnet/pg/pg.h b/vnet/vnet/pg/pg.h
index 63bfb18..750e7f7 100644
--- a/vnet/vnet/pg/pg.h
+++ b/vnet/vnet/pg/pg.h
@@ -43,6 +43,10 @@
 #include <vlib/vlib.h>		/* for VLIB_N_RX_TX */
 #include <vnet/pg/edit.h>
 #include <vppinfra/fifo.h>		/* for buffer_fifo */
+#include <vnet/unix/pcap.h>
+#include <vnet/interface.h>
+
+extern vnet_device_class_t pg_dev_class;
 
 struct pg_main_t;
 struct pg_stream_t;
@@ -140,6 +144,8 @@
   /* Output next index to reach output node from stream input node. */
   u32 next_index;
 
+  u32 if_id;
+
   /* Number of packets currently generated. */
   u64 n_packets_generated;
 
@@ -281,7 +287,10 @@
   u32 hw_if_index, sw_if_index;
 
   /* Identifies stream for this interface. */
-  u32 stream_index;
+  u32 id;
+
+  pcap_main_t pcap_main;
+  u8 * pcap_file_name;
 } pg_interface_t;
 
 /* Per VLIB node data. */
@@ -303,13 +312,12 @@
   /* Hash mapping name -> stream index. */
   uword * stream_index_by_name;
 
-  /* Vector of interfaces. */
+  /* Pool of interfaces. */
   pg_interface_t * interfaces;
+  uword * if_index_by_if_id;
 
   /* Per VLIB node information. */
   pg_node_t * nodes;
-
-  u32 * free_interfaces;
 } pg_main_t;
 
 /* Global main structure. */
@@ -329,7 +337,7 @@
 void pg_stream_enable_disable (pg_main_t * pg, pg_stream_t * s, int is_enable);
 
 /* Find/create free packet-generator interface index. */
-u32 pg_interface_find_free (pg_main_t * pg, uword stream_index);
+u32 pg_interface_add_or_get (pg_main_t * pg, uword stream_index);
 
 always_inline pg_node_t *
 pg_get_node (uword node_index)
diff --git a/vnet/vnet/pg/stream.c b/vnet/vnet/pg/stream.c
index 7146914..5883817 100644
--- a/vnet/vnet/pg/stream.c
+++ b/vnet/vnet/pg/stream.c
@@ -39,12 +39,13 @@
 
 #include <vnet/vnet.h>
 #include <vnet/pg/pg.h>
+#include <vnet/ethernet/ethernet.h>
 
 /* Mark stream active or inactive. */
 void pg_stream_enable_disable (pg_main_t * pg, pg_stream_t * s, int want_enabled)
 {
   vnet_main_t * vnm = vnet_get_main();
-  pg_interface_t * pi = vec_elt_at_index (pg->interfaces, s->pg_if_index);
+  pg_interface_t * pi = pool_elt_at_index (pg->interfaces, s->pg_if_index);
 
   want_enabled = want_enabled != 0;
 
@@ -63,15 +64,14 @@
   pg->enabled_streams
     = clib_bitmap_set (pg->enabled_streams, s - pg->streams, want_enabled);
 
-  vnet_hw_interface_set_flags (vnm, pi->hw_if_index,
-			       (want_enabled
-				? VNET_HW_INTERFACE_FLAG_LINK_UP
-				: 0));
+  if (want_enabled)
+    {
+      vnet_hw_interface_set_flags (vnm, pi->hw_if_index,
+				    VNET_HW_INTERFACE_FLAG_LINK_UP);
 
-  vnet_sw_interface_set_flags (vnm, pi->sw_if_index,
-			       (want_enabled
-				? VNET_SW_INTERFACE_FLAG_ADMIN_UP
-				: 0));
+      vnet_sw_interface_set_flags (vnm, pi->sw_if_index,
+				    VNET_SW_INTERFACE_FLAG_ADMIN_UP);
+    }
 			       
   vlib_node_set_state (pg->vlib_main,
 		       pg_input_node.index,
@@ -89,16 +89,30 @@
   u32 if_index = va_arg (*args, u32);
   pg_interface_t * pi;
 
-  pi = vec_elt_at_index (pg->interfaces, if_index);
-  s = format (s, "pg/stream-%d", pi->stream_index);
+  pi = pool_elt_at_index (pg->interfaces, if_index);
+  s = format (s, "pg%d", pi->id);
 
   return s;
 }
 
-VNET_DEVICE_CLASS (pg_dev_class,static) = {
+static clib_error_t *
+pg_interface_admin_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
+{
+  u32 hw_flags = 0;
+
+  if (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP)
+    hw_flags = VNET_HW_INTERFACE_FLAG_LINK_UP;
+
+  vnet_hw_interface_set_flags(vnm, hw_if_index, hw_flags);
+
+  return 0;
+}
+
+VNET_DEVICE_CLASS (pg_dev_class) = {
   .name = "pg",
   .tx_function = pg_output,
   .format_device_name = format_pg_interface_name,
+  .admin_up_down_function = pg_interface_admin_up_down,
 };
 
 static uword pg_set_rewrite (vnet_main_t * vnm,
@@ -122,31 +136,50 @@
   .set_rewrite = pg_set_rewrite,
 };
 
-u32 pg_interface_find_free (pg_main_t * pg, uword stream_index)
+static u32
+pg_eth_flag_change (vnet_main_t * vnm, vnet_hw_interface_t * hi, u32 flags)
+{
+  /* nothing for now */
+  return 0;
+}
+
+u32 pg_interface_add_or_get (pg_main_t * pg, uword if_id)
 {
   vnet_main_t * vnm = vnet_get_main();
+  vlib_main_t * vm = vlib_get_main();
   pg_interface_t * pi;
   vnet_hw_interface_t * hi;
-  u32 i, l;
+  uword *p;
+  u32 i;
 
-  if ((l = vec_len (pg->free_interfaces)) > 0)
+  p = hash_get (pg->if_index_by_if_id, if_id);
+
+  if (p)
     {
-      i = pg->free_interfaces[l - 1];
-      _vec_len (pg->free_interfaces) = l - 1;
-      pi = vec_elt_at_index (pg->interfaces, i);
-      pi->stream_index = stream_index;
-    }    
+      return p[0];
+    }
   else
     {
-      i = vec_len (pg->interfaces);
-      vec_add2 (pg->interfaces, pi, 1);
+      u8 hw_addr[6];
+      f64 now = vlib_time_now(vm);
+      u32 rnd;
 
-      pi->stream_index = stream_index;
-      pi->hw_if_index = vnet_register_interface (vnm,
-						 pg_dev_class.index, i,
-						 pg_interface_class.index, stream_index);
+      pool_get (pg->interfaces, pi);
+      i = pi - pg->interfaces;
+
+      rnd = (u32) (now * 1e6);
+      rnd = random_u32 (&rnd);
+      clib_memcpy (hw_addr+2, &rnd, sizeof(rnd));
+      hw_addr[0] = 2;
+      hw_addr[1] = 0xfe;
+
+      pi->id = if_id;
+      ethernet_register_interface (vnm, pg_dev_class.index, i, hw_addr,
+				   &pi->hw_if_index, pg_eth_flag_change);
       hi = vnet_get_hw_interface (vnm, pi->hw_if_index);
       pi->sw_if_index = hi->sw_if_index;
+
+      hash_set (pg->if_index_by_if_id, if_id, i);
     }
 
   return i;
@@ -379,10 +412,10 @@
   }
 
   /* Find an interface to use. */
-  s->pg_if_index = pg_interface_find_free (pg, s - pg->streams);
+  s->pg_if_index = pg_interface_add_or_get (pg, s->if_id);
 
   {
-    pg_interface_t * pi = vec_elt_at_index (pg->interfaces, s->pg_if_index);
+    pg_interface_t * pi = pool_elt_at_index (pg->interfaces, s->pg_if_index);
     vlib_rx_or_tx_t rx_or_tx;
 
     vlib_foreach_rx_tx (rx_or_tx)
@@ -405,7 +438,6 @@
   s = pool_elt_at_index (pg->streams, index);
 
   pg_stream_enable_disable (pg, s, /* want_enabled */ 0);
-  vec_add1 (pg->free_interfaces, s->pg_if_index);
   hash_unset_mem (pg->stream_index_by_name, s->name);
 
   vec_foreach (bi, s->buffer_indices)