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";