lib, vtysh: support multiple VRFs by using linux netns

We realize VRFs with linux netns by default. The main job is
to associate a VRF with a netns. Currently this is done by
the configuration:

  [no] vrf N netns <netns-name>

This command is also available in vtysh and goes to only
zebra, because presently only zebra supports multiple VRF.

A file descriptor is added to "struct vrf". This is for the
associated netns file. Once the command "vrf N netns NAME"
is executed, the specified file is opened and the file
descriptor is stored in the VRF N. In this way the
association is formed.

In vrf_socket(), we first switch to the specified VRF by
using the stored file descriptor, and then can allocate
a socket which is working in the associated netns.

Signed-off-by: Feng Lu <lu.feng@6wind.com>
Reviewed-by: Alain Ritoux <alain.ritoux@6wind.com>
Signed-off-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
diff --git a/configure.ac b/configure.ac
index f68d86f..6da65f3 100755
--- a/configure.ac
+++ b/configure.ac
@@ -801,6 +801,14 @@
   ]
 )
 
+AC_CHECK_HEADER([asm-generic/unistd.h],
+                [AC_CHECK_DECL(__NR_setns,
+                               AC_DEFINE(HAVE_NETNS,, Have netns),,
+                               QUAGGA_INCLUDES [#include <asm-generic/unistd.h>
+                               ])
+                 AC_CHECK_FUNCS(setns, AC_DEFINE(HAVE_SETNS,, Have setns))]
+               )
+
 dnl ------------------------------------
 dnl Determine routing get and set method
 dnl ------------------------------------
diff --git a/lib/command.h b/lib/command.h
index a36a524..bb0122f 100644
--- a/lib/command.h
+++ b/lib/command.h
@@ -68,6 +68,7 @@
   AUTH_ENABLE_NODE,		/* Authentication mode for change enable. */
   ENABLE_NODE,			/* Enable node. */
   CONFIG_NODE,			/* Config node. Default mode of config file. */
+  VRF_NODE,			/* VRF node. */
   SERVICE_NODE, 		/* Service node. */
   DEBUG_NODE,			/* Debug node. */
   AAA_NODE,			/* AAA node. */
diff --git a/lib/vrf.c b/lib/vrf.c
index 683026e..89653a8 100644
--- a/lib/vrf.c
+++ b/lib/vrf.c
@@ -22,21 +22,57 @@
 
 #include <zebra.h>
 
+#ifdef HAVE_NETNS
+#undef  _GNU_SOURCE
+#define _GNU_SOURCE
+
+#include <sched.h>
+#endif
+
 #include "if.h"
 #include "vrf.h"
 #include "prefix.h"
 #include "table.h"
 #include "log.h"
 #include "memory.h"
+#include "command.h"
+#include "vty.h"
+
+#ifdef HAVE_NETNS
+
+#ifndef CLONE_NEWNET
+#define CLONE_NEWNET 0x40000000 /* New network namespace (lo, device, names sockets, etc) */
+#endif
+
+#ifndef HAVE_SETNS
+static inline int setns(int fd, int nstype)
+{
+#ifdef __NR_setns
+  return syscall(__NR_setns, fd, nstype);
+#else
+  errno = ENOSYS;
+  return -1;
+#endif
+}
+#endif /* HAVE_SETNS */
+
+#define VRF_RUN_DIR         "/var/run/netns"
+#define VRF_DEFAULT_NAME    "/proc/self/ns/net"
+
+#else /* !HAVE_NETNS */
 
 #define VRF_DEFAULT_NAME    "Default-IP-Routing-Table"
 
+#endif /* HAVE_NETNS */
+
 struct vrf
 {
   /* Identifier, same as the vector index */
   vrf_id_t vrf_id;
   /* Name */
   char *name;
+  /* File descriptor */
+  int fd;
 
   /* Master list of interfaces belonging to this VRF */
   struct list *iflist;
@@ -90,6 +126,7 @@
 
   vrf = XCALLOC (MTYPE_VRF, sizeof (struct vrf));
   vrf->vrf_id = vrf_id;
+  vrf->fd = -1;
   rn->info = vrf;
 
   /* Initialize interfaces. */
@@ -109,8 +146,7 @@
 {
   zlog_info ("VRF %u is to be deleted.", vrf->vrf_id);
 
-  if (vrf_is_enabled (vrf))
-    vrf_disable (vrf);
+  vrf_disable (vrf);
 
   if (vrf_master.vrf_delete_hook)
     (*vrf_master.vrf_delete_hook) (vrf->vrf_id, &vrf->info);
@@ -149,7 +185,11 @@
 static int
 vrf_is_enabled (struct vrf *vrf)
 {
-  return vrf && vrf->vrf_id == VRF_DEFAULT;
+#ifdef HAVE_NETNS
+  return vrf && vrf->fd >= 0;
+#else
+  return vrf && vrf->fd == -2 && vrf->vrf_id == VRF_DEFAULT;
+#endif
 }
 
 /*
@@ -162,18 +202,34 @@
 static int
 vrf_enable (struct vrf *vrf)
 {
-  /* Till now, only the default VRF can be enabled. */
-  if (vrf->vrf_id == VRF_DEFAULT)
-    {
-      zlog_info ("VRF %u is enabled.", vrf->vrf_id);
 
+  if (!vrf_is_enabled (vrf))
+    {
+#ifdef HAVE_NETNS
+      vrf->fd = open (vrf->name, O_RDONLY);
+#else
+      vrf->fd = -2; /* Remember that vrf_enable_hook has been called */
+      errno = -ENOTSUP;
+#endif
+
+      if (!vrf_is_enabled (vrf))
+        {
+          zlog_err ("Can not enable VRF %u: %s!",
+                    vrf->vrf_id, safe_strerror (errno));
+          return 0;
+        }
+
+#ifdef HAVE_NETNS
+      zlog_info ("VRF %u is associated with NETNS %s.",
+                 vrf->vrf_id, vrf->name);
+#endif
+
+      zlog_info ("VRF %u is enabled.", vrf->vrf_id);
       if (vrf_master.vrf_enable_hook)
         (*vrf_master.vrf_enable_hook) (vrf->vrf_id, &vrf->info);
-
-      return 1;
     }
 
-  return 0;
+  return 1;
 }
 
 /*
@@ -188,10 +244,13 @@
     {
       zlog_info ("VRF %u is to be disabled.", vrf->vrf_id);
 
-      /* Till now, nothing to be done for the default VRF. */
-
       if (vrf_master.vrf_disable_hook)
         (*vrf_master.vrf_disable_hook) (vrf->vrf_id, &vrf->info);
+
+#ifdef HAVE_NETNS
+      close (vrf->fd);
+#endif
+      vrf->fd = -1;
     }
 }
 
@@ -429,6 +488,144 @@
                      VRF_BITMAP_FLAG (offset)) ? 1 : 0;
 }
 
+#ifdef HAVE_NETNS
+/*
+ * VRF realization with NETNS
+ */
+
+static char *
+vrf_netns_pathname (struct vty *vty, const char *name)
+{
+  static char pathname[PATH_MAX];
+  char *result;
+
+  if (name[0] == '/') /* absolute pathname */
+    result = realpath (name, pathname);
+  else /* relevant pathname */
+    {
+      char tmp_name[PATH_MAX];
+      snprintf (tmp_name, PATH_MAX, "%s/%s", VRF_RUN_DIR, name);
+      result = realpath (tmp_name, pathname);
+    }
+
+  if (! result)
+    {
+      vty_out (vty, "Invalid pathname: %s%s", safe_strerror (errno),
+               VTY_NEWLINE);
+      return NULL;
+    }
+  return pathname;
+}
+
+DEFUN (vrf_netns,
+       vrf_netns_cmd,
+       "vrf <1-65535> netns NAME",
+       "Enable a VRF\n"
+       "Specify the VRF identifier\n"
+       "Associate with a NETNS\n"
+       "The file name in " VRF_RUN_DIR ", or a full pathname\n")
+{
+  vrf_id_t vrf_id = VRF_DEFAULT;
+  struct vrf *vrf = NULL;
+  char *pathname = vrf_netns_pathname (vty, argv[1]);
+
+  if (!pathname)
+    return CMD_WARNING;
+
+  VTY_GET_INTEGER ("VRF ID", vrf_id, argv[0]);
+  vrf = vrf_get (vrf_id);
+
+  if (vrf->name && strcmp (vrf->name, pathname) != 0)
+    {
+      vty_out (vty, "VRF %u is already configured with NETNS %s%s",
+               vrf->vrf_id, vrf->name, VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+
+  if (!vrf->name)
+    vrf->name = XSTRDUP (MTYPE_VRF_NAME, pathname);
+
+  if (!vrf_enable (vrf))
+    {
+      vty_out (vty, "Can not associate VRF %u with NETNS %s%s",
+               vrf->vrf_id, vrf->name, VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_vrf_netns,
+       no_vrf_netns_cmd,
+       "no vrf <1-65535> netns NAME",
+       NO_STR
+       "Enable a VRF\n"
+       "Specify the VRF identifier\n"
+       "Associate with a NETNS\n"
+       "The file name in " VRF_RUN_DIR ", or a full pathname\n")
+{
+  vrf_id_t vrf_id = VRF_DEFAULT;
+  struct vrf *vrf = NULL;
+  char *pathname = vrf_netns_pathname (vty, argv[1]);
+
+  if (!pathname)
+    return CMD_WARNING;
+
+  VTY_GET_INTEGER ("VRF ID", vrf_id, argv[0]);
+  vrf = vrf_lookup (vrf_id);
+
+  if (!vrf)
+    {
+      vty_out (vty, "VRF %u is not found%s", vrf_id, VTY_NEWLINE);
+      return CMD_SUCCESS;
+    }
+
+  if (vrf->name && strcmp (vrf->name, pathname) != 0)
+    {
+      vty_out (vty, "Incorrect NETNS file name%s", VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+
+  vrf_disable (vrf);
+
+  if (vrf->name)
+    {
+      XFREE (MTYPE_VRF_NAME, vrf->name);
+      vrf->name = NULL;
+    }
+
+  return CMD_SUCCESS;
+}
+
+/* VRF node. */
+static struct cmd_node vrf_node =
+{
+  VRF_NODE,
+  "",       /* VRF node has no interface. */
+  1
+};
+
+/* VRF configuration write function. */
+static int
+vrf_config_write (struct vty *vty)
+{
+  struct route_node *rn;
+  struct vrf *vrf;
+  int write = 0;
+
+  for (rn = route_top (vrf_table); rn; rn = route_next (rn))
+    if ((vrf = rn->info) != NULL &&
+        vrf->vrf_id != VRF_DEFAULT && vrf->name)
+      {
+        vty_out (vty, "vrf %u netns %s%s", vrf->vrf_id, vrf->name, VTY_NEWLINE);
+        write++;
+      }
+
+  return write;
+}
+
+#endif /* HAVE_NETNS */
+
 /* Initialize VRF module. */
 void
 vrf_init (void)
@@ -455,6 +652,13 @@
       zlog_err ("vrf_init: failed to enable the default VRF!");
       exit (1);
     }
+
+#ifdef HAVE_NETNS
+  /* Install VRF commands. */
+  install_node (&vrf_node, vrf_config_write);
+  install_element (CONFIG_NODE, &vrf_netns_cmd);
+  install_element (CONFIG_NODE, &no_vrf_netns_cmd);
+#endif
 }
 
 /* Terminate VRF module. */
@@ -476,19 +680,26 @@
 int
 vrf_socket (int domain, int type, int protocol, vrf_id_t vrf_id)
 {
+  struct vrf *vrf = vrf_lookup (vrf_id);
   int ret = -1;
 
-  if (!vrf_is_enabled (vrf_lookup (vrf_id)))
+  if (!vrf_is_enabled (vrf))
     {
       errno = ENOSYS;
       return -1;
     }
 
-  if (vrf_id == VRF_DEFAULT)
-    ret = socket (domain, type, protocol);
-  else
-    errno = ENOSYS;
+#ifdef HAVE_NETNS
+  ret = (vrf_id != VRF_DEFAULT) ? setns (vrf->fd, CLONE_NEWNET) : 0;
+  if (ret >= 0)
+    {
+      ret = socket (domain, type, protocol);
+      if (vrf_id != VRF_DEFAULT)
+        setns (vrf_lookup (VRF_DEFAULT)->fd, CLONE_NEWNET);
+    }
+#else
+  ret = socket (domain, type, protocol);
+#endif
 
   return ret;
 }
-
diff --git a/vtysh/Makefile.am b/vtysh/Makefile.am
index d1ff69b..850b505 100644
--- a/vtysh/Makefile.am
+++ b/vtysh/Makefile.am
@@ -28,6 +28,7 @@
 		  $(top_srcdir)/lib/keychain.c $(top_srcdir)/lib/routemap.c \
 		  $(top_srcdir)/lib/filter.c $(top_srcdir)/lib/plist.c \
 		  $(top_srcdir)/lib/distribute.c $(top_srcdir)/lib/if_rmap.c \
+		  $(top_srcdir)/lib/vrf.c \
 		  $(top_srcdir)/lib/vty.c $(top_srcdir)/zebra/debug.c \
 		  $(top_srcdir)/zebra/interface.c \
 		  $(top_srcdir)/zebra/irdp_interface.c \
diff --git a/vtysh/extract.pl.in b/vtysh/extract.pl.in
index f057e24..aa90be4 100755
--- a/vtysh/extract.pl.in
+++ b/vtysh/extract.pl.in
@@ -99,6 +99,9 @@
         elsif ($file =~ /lib\/filter\.c$/) {
             $protocol = "VTYSH_ALL";
         }
+        elsif ($file =~ /lib\/vrf\.c$/) {
+            $protocol = "VTYSH_ZEBRA";
+        }
         elsif ($file =~ /lib\/plist\.c$/) {
             if ($defun_array[1] =~ m/ipv6/) {
                 $protocol = "VTYSH_RIPNGD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ZEBRA";