tapv2: multiple improvements

- change interface naming scheme
- rework netlink code
- add option to set link address, namespace

Change-Id: Icf667babb3077a07617b0b87c45c957e345cb4d1
Signed-off-by: Damjan Marion <damarion@cisco.com>
diff --git a/src/vnet/devices/tap/cli.c b/src/vnet/devices/tap/cli.c
index f7fc1e6..c86995c 100644
--- a/src/vnet/devices/tap/cli.c
+++ b/src/vnet/devices/tap/cli.c
@@ -38,38 +38,47 @@
   tap_create_if_args_t args = { 0 };
   int ip_addr_set = 0;
 
-  /* Get a line of input. */
-  if (!unformat_user (input, unformat_line_input, line_input))
-    return clib_error_return (0, "Missing name <interface>");
+  args.id = ~0;
 
-  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+  /* Get a line of input. */
+  if (unformat_user (input, unformat_line_input, line_input))
     {
-      if (unformat (line_input, "name %s", &args.name))
-	;
-      else if (unformat (line_input, "host-ns %s", &args.host_namespace))
-	;
-      else if (unformat (line_input, "host-bridge %s", &args.host_bridge))
-	;
-      else if (unformat (line_input, "host-ip4-addr %U/%d",
-			 unformat_ip4_address, &args.host_ip4_addr,
-			 &args.host_ip4_prefix_len))
-	ip_addr_set = 1;
-      else if (unformat (line_input, "host-ip6-addr %U/%d",
-			 unformat_ip6_address, &args.host_ip6_addr,
-			 &args.host_ip6_prefix_len))
-	ip_addr_set = 1;
-      else if (unformat (line_input, "rx-ring-size %d", &args.rx_ring_sz))
-	;
-      else if (unformat (line_input, "tx-ring-size %d", &args.tx_ring_sz))
-	;
-      else if (unformat (line_input, "hw-addr %U",
-			 unformat_ethernet_address, args.hw_addr))
-	args.hw_addr_set = 1;
-      else
-	return clib_error_return (0, "unknown input `%U'",
-				  format_unformat_error, input);
+
+      while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+	{
+	  if (unformat (line_input, "id %u", &args.id))
+	    ;
+	  else
+	    if (unformat (line_input, "host-if-name %s", &args.host_if_name))
+	    ;
+	  else if (unformat (line_input, "host-ns %s", &args.host_namespace))
+	    ;
+	  else if (unformat (line_input, "host-mac-addr %U",
+			     unformat_ethernet_address, args.host_mac_addr))
+	    ;
+	  else if (unformat (line_input, "host-bridge %s", &args.host_bridge))
+	    ;
+	  else if (unformat (line_input, "host-ip4-addr %U/%d",
+			     unformat_ip4_address, &args.host_ip4_addr,
+			     &args.host_ip4_prefix_len))
+	    ip_addr_set = 1;
+	  else if (unformat (line_input, "host-ip6-addr %U/%d",
+			     unformat_ip6_address, &args.host_ip6_addr,
+			     &args.host_ip6_prefix_len))
+	    ip_addr_set = 1;
+	  else if (unformat (line_input, "rx-ring-size %d", &args.rx_ring_sz))
+	    ;
+	  else if (unformat (line_input, "tx-ring-size %d", &args.tx_ring_sz))
+	    ;
+	  else if (unformat (line_input, "hw-addr %U",
+			     unformat_ethernet_address, args.mac_addr))
+	    args.mac_addr_set = 1;
+	  else
+	    return clib_error_return (0, "unknown input `%U'",
+				      format_unformat_error, input);
+	}
+      unformat_free (line_input);
     }
-  unformat_free (line_input);
 
   if (ip_addr_set && args.host_bridge)
     return clib_error_return (0, "Please specify either host ip address or "
@@ -77,7 +86,7 @@
 
   tap_create_if (vm, &args);
 
-  vec_free (args.name);
+  vec_free (args.host_if_name);
   vec_free (args.host_namespace);
   vec_free (args.host_bridge);
 
@@ -88,10 +97,10 @@
 /* *INDENT-OFF* */
 VLIB_CLI_COMMAND (tap_create_command, static) = {
   .path = "create tap",
-  .short_help = "create tap {name <if-name>} [hw-addr <mac-address>] "
+  .short_help = "create tap {id <if-id>} [hw-addr <mac-address>] "
     "[rx-ring-size <size>] [tx-ring-size <size>] [host-ns <netns>] "
     "[host-bridge <bridge-name>] [host-ip4-addr <ip4addr/mask>] "
-    "[host-ip6-addr <ip6-addr]",
+    "[host-ip6-addr <ip6-addr] [host-if-name <name>]",
   .function = tap_create_command_fn,
 };
 /* *INDENT-ON* */
@@ -209,8 +218,8 @@
       vif = pool_elt_at_index (mm->interfaces, hi->dev_instance);
       vlib_cli_output (vm, "interface %U", format_vnet_sw_if_index_name,
 		       vnm, vif->sw_if_index);
-      if (vif->name)
-	vlib_cli_output (vm, "  name \"%s\"", vif->name);
+      if (vif->host_if_name)
+	vlib_cli_output (vm, "  name \"%s\"", vif->host_if_name);
       if (vif->net_ns)
 	vlib_cli_output (vm, "  host-ns \"%s\"", vif->net_ns);
       vlib_cli_output (vm, "  flags 0x%x", vif->flags);
diff --git a/src/vnet/devices/tap/tap.c b/src/vnet/devices/tap/tap.c
index b4004f7..f31548c 100644
--- a/src/vnet/devices/tap/tap.c
+++ b/src/vnet/devices/tap/tap.c
@@ -15,6 +15,7 @@
  *------------------------------------------------------------------
  */
 
+#define _GNU_SOURCE
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
@@ -24,6 +25,7 @@
 #include <linux/virtio_net.h>
 #include <linux/vhost.h>
 #include <sys/eventfd.h>
+#include <sched.h>
 
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
@@ -37,6 +39,8 @@
 #include <vnet/devices/virtio/virtio.h>
 #include <vnet/devices/tap/tap.h>
 
+tap_main_t tap_main;
+
 #define _IOCTL(fd,a,...) \
   if (ioctl (fd, a, __VA_ARGS__) < 0) \
     { \
@@ -53,24 +57,79 @@
   return 0;
 }
 
+static int
+open_netns_fd (char *netns)
+{
+  u8 *s = 0;
+  int fd;
+
+  if (strncmp (netns, "pid:", 4) == 0)
+    s = format (0, "/proc/%u/ns/net%c", atoi (netns + 4), 0);
+  else if (netns[0] == '/')
+    s = format (0, "%s%c", netns, 0);
+  else
+    s = format (0, "/var/run/netns/%s%c", netns, 0);
+
+  fd = open ((char *) s, O_RDONLY);
+  vec_free (s);
+  return fd;
+}
+
+
 void
 tap_create_if (vlib_main_t * vm, tap_create_if_args_t * args)
 {
   vnet_main_t *vnm = vnet_get_main ();
   virtio_main_t *vim = &virtio_main;
+  tap_main_t *tm = &tap_main;
   vnet_sw_interface_t *sw;
   vnet_hw_interface_t *hw;
   int i, fd = -1;
+  int old_netns_fd = -1;
   struct ifreq ifr;
   size_t hdrsz;
   struct vhost_memory *vhost_mem = 0;
   virtio_if_t *vif = 0;
   clib_error_t *err = 0;
+  uword *p;
+
+  if (args->id != ~0)
+    {
+      p = hash_get (tm->dev_instance_by_interface_id, args->id);
+      if (p)
+	{
+	  args->rv = VNET_API_ERROR_INVALID_INTERFACE;
+	  args->error = clib_error_return (0, "interface already exists");
+	  return;
+	}
+    }
+  else
+    {
+      int tries = 1000;
+      while (--tries)
+	{
+	  args->id = tm->last_used_interface_id++;
+	  p = hash_get (tm->dev_instance_by_interface_id, args->id);
+	  if (!p)
+	    break;
+	}
+
+      if (!tries)
+	{
+	  args->rv = VNET_API_ERROR_UNSPECIFIED;
+	  args->error =
+	    clib_error_return (0, "cannot find free interface id");
+	  return;
+	}
+    }
 
   memset (&ifr, 0, sizeof (ifr));
   pool_get (vim->interfaces, vif);
   vif->dev_instance = vif - vim->interfaces;
   vif->tap_fd = -1;
+  vif->id = args->id;
+
+  hash_set (tm->dev_instance_by_interface_id, vif->id, vif->dev_instance);
 
   if ((vif->fd = open ("/dev/vhost-net", O_RDWR | O_NONBLOCK)) < 0)
     {
@@ -119,10 +178,8 @@
     }
 
   ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR;
-  strncpy (ifr.ifr_ifrn.ifrn_name, (char *) args->name, IF_NAMESIZE - 1);
   _IOCTL (vif->tap_fd, TUNSETIFF, (void *) &ifr);
-
-  vif->ifindex = if_nametoindex ((char *) args->name);
+  vif->ifindex = if_nametoindex (ifr.ifr_ifrn.ifrn_name);
 
   unsigned int offload = 0;
   hdrsz = sizeof (struct virtio_net_hdr_v1);
@@ -130,10 +187,61 @@
   _IOCTL (vif->tap_fd, TUNSETVNETHDRSZ, &hdrsz);
   _IOCTL (vif->fd, VHOST_SET_OWNER, 0);
 
-  if (args->host_bridge)
+  /* if namespace is specified, all further netlink messages should be excuted
+     after we change our net namespace */
+  if (args->host_namespace)
     {
-      int master_ifindex = if_nametoindex ((char *) args->host_bridge);
-      args->error = vnet_netlink_set_if_master (vif->ifindex, master_ifindex);
+      int fd;
+      old_netns_fd = open ("/proc/self/ns/net", O_RDONLY);
+      if ((fd = open_netns_fd ((char *) args->host_namespace)) == -1)
+	{
+	  args->rv = VNET_API_ERROR_SYSCALL_ERROR_2;
+	  args->error = clib_error_return_unix (0, "open_netns_fd '%s'",
+						args->host_namespace);
+	  goto error;
+	}
+      args->error = vnet_netlink_set_link_netns (vif->ifindex, fd,
+						 (char *) args->host_if_name);
+      if (args->error)
+	{
+	  args->rv = VNET_API_ERROR_NETLINK_ERROR;
+	  goto error;
+	}
+      if (setns (fd, CLONE_NEWNET) == -1)
+	{
+	  args->rv = VNET_API_ERROR_SYSCALL_ERROR_3;
+	  args->error = clib_error_return_unix (0, "setns '%s'",
+						args->host_namespace);
+	  goto error;
+	}
+      close (fd);
+      if ((vif->ifindex = if_nametoindex ((char *) args->host_if_name)) == 0)
+	{
+	  args->rv = VNET_API_ERROR_SYSCALL_ERROR_3;
+	  args->error = clib_error_return_unix (0, "if_nametoindex '%s'",
+						args->host_if_name);
+	  goto error;
+	}
+    }
+  else
+    {
+      if (args->host_if_name)
+	{
+	  args->error = vnet_netlink_set_link_name (vif->ifindex,
+						    (char *)
+						    args->host_if_name);
+	  if (args->error)
+	    {
+	      args->rv = VNET_API_ERROR_NETLINK_ERROR;
+	      goto error;
+	    }
+	}
+    }
+
+  if (!ethernet_mac_address_is_zero (args->host_mac_addr))
+    {
+      args->error = vnet_netlink_set_link_addr (vif->ifindex,
+						args->host_mac_addr);
       if (args->error)
 	{
 	  args->rv = VNET_API_ERROR_NETLINK_ERROR;
@@ -141,11 +249,10 @@
 	}
     }
 
-  if (args->host_namespace)
+  if (args->host_bridge)
     {
-      args->error = vnet_netlink_set_if_namespace (vif->ifindex,
-						   (char *)
-						   args->host_namespace);
+      args->error = vnet_netlink_set_link_master (vif->ifindex,
+						  (char *) args->host_bridge);
       if (args->error)
 	{
 	  args->rv = VNET_API_ERROR_NETLINK_ERROR;
@@ -153,6 +260,7 @@
 	}
     }
 
+
   if (args->host_ip4_prefix_len)
     {
       args->error = vnet_netlink_add_ip4_addr (vif->ifindex,
@@ -177,6 +285,25 @@
 	}
     }
 
+  args->error = vnet_netlink_set_link_state (vif->ifindex, 1 /* UP */ );
+  if (args->error)
+    {
+      args->rv = VNET_API_ERROR_NETLINK_ERROR;
+      goto error;
+    }
+
+  /* switch back to old net namespace */
+  if (args->host_namespace)
+    {
+      if (setns (old_netns_fd, CLONE_NEWNET) == -1)
+	{
+	  args->rv = VNET_API_ERROR_SYSCALL_ERROR_2;
+	  args->error = clib_error_return_unix (0, "setns '%s'",
+						args->host_namespace);
+	  goto error;
+	}
+    }
+
   /* Set vhost memory table */
   i = sizeof (struct vhost_memory) + sizeof (struct vhost_memory_region);
   vhost_mem = clib_mem_alloc (i);
@@ -197,33 +324,24 @@
       goto error;
     }
 
-  /* set host side up */
-  if ((fd = socket (AF_INET, SOCK_STREAM, 0)) > 0)
-    {
-      memset (&ifr, 0, sizeof (struct ifreq));
-      strncpy (ifr.ifr_name, (char *) args->name, sizeof (ifr.ifr_name) - 1);
-      _IOCTL (fd, SIOCGIFFLAGS, (void *) &ifr);
-      ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
-      _IOCTL (fd, SIOCSIFFLAGS, (void *) &ifr);
-    }
-
-  if (!args->hw_addr_set)
+  if (!args->mac_addr_set)
     {
       f64 now = vlib_time_now (vm);
       u32 rnd;
       rnd = (u32) (now * 1e6);
       rnd = random_u32 (&rnd);
 
-      memcpy (args->hw_addr + 2, &rnd, sizeof (rnd));
-      args->hw_addr[0] = 2;
-      args->hw_addr[1] = 0xfe;
+      memcpy (args->mac_addr + 2, &rnd, sizeof (rnd));
+      args->mac_addr[0] = 2;
+      args->mac_addr[1] = 0xfe;
     }
-  vif->name = args->name;
-  args->name = 0;
+  vif->host_if_name = args->host_if_name;
+  args->host_if_name = 0;
   vif->net_ns = args->host_namespace;
   args->host_namespace = 0;
   args->error = ethernet_register_interface (vnm, virtio_device_class.index,
-					     vif->dev_instance, args->hw_addr,
+					     vif->dev_instance,
+					     args->mac_addr,
 					     &vif->hw_if_index,
 					     virtio_eth_flag_change);
   if (args->error)
@@ -276,6 +394,7 @@
 {
   vnet_main_t *vnm = vnet_get_main ();
   virtio_main_t *mm = &virtio_main;
+  tap_main_t *tm = &tap_main;
   int i;
   virtio_if_t *vif;
   vnet_hw_interface_t *hw;
@@ -301,6 +420,7 @@
   vec_foreach_index (i, vif->vrings) virtio_vring_free (vif, i);
   vec_free (vif->vrings);
 
+  hash_unset (tm->dev_instance_by_interface_id, vif->id);
   memset (vif, 0, sizeof (*vif));
   pool_put (mm->interfaces, vif);
 
@@ -337,7 +457,8 @@
 static clib_error_t *
 tap_init (vlib_main_t * vm)
 {
-
+  tap_main_t *tm = &tap_main;
+  tm->dev_instance_by_interface_id = hash_create (0, sizeof (uword));
   return 0;
 }
 
diff --git a/src/vnet/devices/tap/tap.h b/src/vnet/devices/tap/tap.h
index 0e0f8cb..7d07ffb 100644
--- a/src/vnet/devices/tap/tap.h
+++ b/src/vnet/devices/tap/tap.h
@@ -24,12 +24,14 @@
 
 typedef struct
 {
-  u8 *name;
-  u8 hw_addr_set;
-  u8 hw_addr[6];
+  u32 id;
+  u8 mac_addr_set;
+  u8 mac_addr[6];
   u16 rx_ring_sz;
   u16 tx_ring_sz;
   u8 *host_namespace;
+  u8 *host_if_name;
+  u8 host_mac_addr[6];
   u8 *host_bridge;
   ip4_address_t host_ip4_addr;
   u32 host_ip4_prefix_len;
@@ -48,6 +50,12 @@
   u8 dev_name[64];
 } tap_interface_details_t;
 
+typedef struct
+{
+  u32 last_used_interface_id;
+  uword *dev_instance_by_interface_id;
+} tap_main_t;
+
 void tap_create_if (vlib_main_t * vm, tap_create_if_args_t * args);
 int tap_delete_if (vlib_main_t * vm, u32 sw_if_index);
 int tap_dump_ifs (tap_interface_details_t ** out_tapids);
diff --git a/src/vnet/devices/tap/tapv2.api b/src/vnet/devices/tap/tapv2.api
index 0378860..a206269 100644
--- a/src/vnet/devices/tap/tapv2.api
+++ b/src/vnet/devices/tap/tapv2.api
@@ -24,11 +24,15 @@
 /** \brief Initialize a new tap interface with the given paramters
     @param client_index - opaque cookie to identify the sender
     @param context - sender context, to match reply w/ request
+    @param id - interface id, 0xffff means auto
     @param use_random_mac - let the system generate a unique mac address
-    @param tap_name - name to associate with the new interface
     @param mac_address - mac addr to assign to the interface if use_radom not set
     @param tx_ring_sz - the number of entries of TX ring
     @param rx_ring_sz - the number of entries of RX ring
+    @param host_mac_addr_set - host side interface mac address should be set
+    @param host_mac_addr - host side interface mac address
+    @param host_if_name_set - host side interface name should be set
+    @param host_if_name - host side interface name
     @param host_namespace_set - host namespece should be set
     @param host_namespace - host namespace to attach interface to
     @param host_bridge_set - host bridge should be set
@@ -44,13 +48,17 @@
 {
   u32 client_index;
   u32 context;
+  u32 id;
   u8 use_random_mac;
-  u8 tap_name[64];
   u8 mac_address[6];
   u16 tx_ring_sz; /* optional, default is 256 entries, must be power of 2 */
   u16 rx_ring_sz; /* optional, default is 256 entries, must be power of 2 */
   u8 host_namespace_set;
   u8 host_namespace[64];
+  u8 host_mac_addr_set;
+  u8 host_mac_addr[6];
+  u8 host_if_name_set;
+  u8 host_if_name[64];
   u8 host_bridge_set;
   u8 host_bridge[64];
   u8 host_ip4_addr_set;
diff --git a/src/vnet/devices/tap/tapv2_api.c b/src/vnet/devices/tap/tapv2_api.c
index 2b324d6..3cededb 100644
--- a/src/vnet/devices/tap/tapv2_api.c
+++ b/src/vnet/devices/tap/tapv2_api.c
@@ -59,16 +59,25 @@
 
   memset (ap, 0, sizeof (*ap));
 
-  ap->name = mp->tap_name;
+  ap->id = mp->id;
   if (!mp->use_random_mac)
     {
-      clib_memcpy (ap->hw_addr, mp->mac_address, 6);
-      ap->hw_addr_set = 1;
+      clib_memcpy (ap->mac_addr, mp->mac_address, 6);
+      ap->mac_addr_set = 1;
     }
   ap->rx_ring_sz = ntohs (mp->rx_ring_sz);
   ap->tx_ring_sz = ntohs (mp->tx_ring_sz);
   ap->sw_if_index = (u32) ~ 0;
 
+  if (mp->host_if_name_set)
+    ap->host_if_name = mp->host_if_name;
+
+  if (mp->host_mac_addr_set)
+    {
+      clib_memcpy (ap->host_mac_addr, mp->host_mac_addr, 6);
+      ap->mac_addr_set = 1;
+    }
+
   if (mp->host_namespace_set)
     ap->host_namespace = mp->host_namespace;