I wanted to have my vpn tunnel device in its own network namespace, and
while there are certainly a number of ways to do that (up and route
scripts, a plugin, running openvpn itself in the namespace, etc.) I decided
that the cleanest way to do so would be to just teach openvpn how to put
and configure the tun device in the namespace.  Afterall, openvpn was
already doing all the configuration I wanted, just in the wrong namespace.
 It seemed wrong to replicate that functionality in a script or plugin for
just a small difference.  This patch is the result.

I realize it's debatable that this is the best way to accomplish this, and
it might not be generally useful enough for inclusion in the main line, but
I thought I would pass it along and see what people here thought.  If there
is interest I'm happy to take suggestions and clean it up for inclusion.
 This is my first time looking at this code, so there is a good chance
there's plenty of things I missed.

Jared Casper
From 4592855db54e64ba83be5d9bc6c09d94c1b0622a Mon Sep 17 00:00:00 2001
From: Jared Casper <jaredcas...@gmail.com>
Date: Tue, 27 May 2014 15:59:04 -0700
Subject: [PATCH] Add namespace option when using iproute2.

Adds an option --ifconfig-namespace that indicates a network namespace
that the device should be moved to before it is configured.  The
option is currently only implemented, and thus available, in linux
when using iproute2.

If a namespace is provided, the implementation first runs "ip netns
list" to determine if the namespace exists, then runs "ip netns add
<namespace>" if it doesn't.  Before bringing the device up, "ip link
set dev <dev> netns <namespace>" is run to move it to the given
namespace.  All further calls to the ip command are then prefixed with
"ip netns exec <namespace>" to configure the device and set up routes
within the namespace.

Signed-off-by: Jared Casper <jaredcas...@gmail.com>
---
 doc/openvpn.8         |   8 ++++
 src/openvpn/init.c    |   1 +
 src/openvpn/options.c |  11 ++++++
 src/openvpn/options.h |   1 +
 src/openvpn/route.c   |  12 ++++--
 src/openvpn/tun.c     | 103 +++++++++++++++++++++++++++++++++++++++++++++++---
 src/openvpn/tun.h     |   5 +++
 7 files changed, 131 insertions(+), 10 deletions(-)

diff --git a/doc/openvpn.8 b/doc/openvpn.8
index 34894e5..010c23a 100644
--- a/doc/openvpn.8
+++ b/doc/openvpn.8
@@ -950,6 +950,14 @@ address conflicts which occasionally annoy more experienced
 users by triggering "false positive" warnings.
 .\"*********************************************************
 .TP
+.B \-\-ifconfig-namespace ns
+(Linux/iproute2 only) Move the device into the network namespace
+.B ns
+before it is configured.  All routing configuration will be done in
+namespace
+.B ns.
+.\"*********************************************************
+.TP
 .B \-\-route network/IP [netmask] [gateway] [metric]
 Add route to routing table after connection is established.
 Multiple routes can be specified.  Routes will be
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 467b98a..abbe5d8 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -1393,6 +1393,7 @@ do_init_tun (struct context *c)
 			   c->options.topology,
 			   c->options.ifconfig_local,
 			   c->options.ifconfig_remote_netmask,
+			   c->options.ifconfig_namespace,
 			   c->options.ifconfig_ipv6_local,
 			   c->options.ifconfig_ipv6_netbits,
 			   c->options.ifconfig_ipv6_remote,
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 57f8949..cce9064 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -208,6 +208,9 @@ static const char usage_message[] =
   "                    pass --ifconfig parms by environment to scripts.\n"
   "--ifconfig-nowarn : Don't warn if the --ifconfig option on this side of the\n"
   "                    connection doesn't match the remote side.\n"
+#ifdef ENABLE_IPROUTE
+  "--ifconfig-namespace ns : move device into namespace ns before configuring\n"
+#endif
   "--route network [netmask] [gateway] [metric] :\n"
   "                  Add route to routing table after connection\n"
   "                  is established.  Multiple routes can be specified.\n"
@@ -2958,6 +2961,7 @@ options_string (const struct options *o,
 		     o->topology,
 		     o->ifconfig_local,
 		     o->ifconfig_remote_netmask,
+		     o->ifconfig_namespace,
 		     o->ifconfig_ipv6_local,
 		     o->ifconfig_ipv6_netbits,
 		     o->ifconfig_ipv6_remote,
@@ -4435,6 +4439,13 @@ add_option (struct options *options,
       VERIFY_PERMISSION (OPT_P_UP);
       options->ifconfig_nowarn = true;
     }
+#ifdef ENABLE_IPROUTE
+  else if (streq (p[0], "ifconfig-namespace") && p[1])
+    {
+      VERIFY_PERMISSION (OPT_P_UP);
+      options->ifconfig_namespace = p[1];
+    }
+#endif
   else if (streq (p[0], "local") && p[1])
     {
       VERIFY_PERMISSION (OPT_P_GENERAL|OPT_P_CONNECTION);
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index a3b76d3..0abdca3 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -237,6 +237,7 @@ struct options
   const char *ifconfig_ipv6_remote;
   bool ifconfig_noexec;
   bool ifconfig_nowarn;
+  const char *ifconfig_namespace;
 #ifdef ENABLE_FEATURE_SHAPER
   int shaper;
 #endif
diff --git a/src/openvpn/route.c b/src/openvpn/route.c
index 5531eda..720f7b9 100644
--- a/src/openvpn/route.c
+++ b/src/openvpn/route.c
@@ -1286,7 +1286,8 @@ add_route (struct route_ipv4 *r,
 #if defined(TARGET_LINUX)
 #ifdef ENABLE_IPROUTE
   /* FIXME -- add on-link support for ENABLE_IPROUTE */
-  argv_printf (&argv, "%s route add %s/%d via %s",
+  argv_printf (&argv, "%sc %s route add %s/%d via %s",
+	      tt->iproute_namespace_prefix,
   	      iproute_path,
 	      network,
 	      count_netmask_bits(netmask),
@@ -1569,7 +1570,8 @@ add_route_ipv6 (struct route_ipv6 *r6, const struct tuntap *tt, unsigned int fla
 
 #if defined(TARGET_LINUX)
 #ifdef ENABLE_IPROUTE
-  argv_printf (&argv, "%s -6 route add %s/%d dev %s",
+  argv_printf (&argv, "%sc %s -6 route add %s/%d dev %s",
+	      tt->iproute_namespace_prefix,
   	      iproute_path,
 	      network,
 	      r6->netbits,
@@ -1740,7 +1742,8 @@ delete_route (struct route_ipv4 *r,
 
 #if defined(TARGET_LINUX)
 #ifdef ENABLE_IPROUTE
-  argv_printf (&argv, "%s route del %s/%d",
+  argv_printf (&argv, "%sc %s route del %s/%d",
+	      tt->iproute_namespace_prefix,
   	      iproute_path,
 	      network,
 	      count_netmask_bits(netmask));
@@ -1911,7 +1914,8 @@ delete_route_ipv6 (const struct route_ipv6 *r6, const struct tuntap *tt, unsigne
 
 #if defined(TARGET_LINUX)
 #ifdef ENABLE_IPROUTE
-  argv_printf (&argv, "%s -6 route del %s/%d dev %s",
+  argv_printf (&argv, "%sc %s -6 route del %s/%d dev %s",
+	      tt->iproute_namespace_prefix,
   	      iproute_path,
 	      network,
 	      r6->netbits,
diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c
index 482f640..99b1a42 100644
--- a/src/openvpn/tun.c
+++ b/src/openvpn/tun.c
@@ -408,6 +408,7 @@ init_tun (const char *dev,       /* --dev option */
 	  int topology,          /* one of the TOP_x values */
 	  const char *ifconfig_local_parm,          /* --ifconfig parm 1 */
 	  const char *ifconfig_remote_netmask_parm, /* --ifconfig parm 2 */
+	  const char *ifconfig_namespace_parm,
 	  const char *ifconfig_ipv6_local_parm,     /* --ifconfig parm 1 IPv6 */
 	  int         ifconfig_ipv6_netbits_parm,
 	  const char *ifconfig_ipv6_remote_parm,    /* --ifconfig parm 2 IPv6 */
@@ -424,6 +425,29 @@ init_tun (const char *dev,       /* --dev option */
 
   tt->type = dev_type_enum (dev, dev_type);
   tt->topology = topology;
+  tt->namespace = ifconfig_namespace_parm;
+
+#ifdef ENABLE_IPROUTE
+  if (tt->namespace)
+    {
+      size_t size = strlen(iproute_path)
+	+ strlen(" netns exec ")
+	+ strlen(ifconfig_namespace_parm)
+	+ 1;
+      tt->iproute_namespace_prefix = gc_malloc(size, false, NULL);
+      openvpn_snprintf (tt->iproute_namespace_prefix, size,
+			"%s netns exec %s",
+			iproute_path,
+			ifconfig_namespace_parm);
+      if (es)
+	setenv_str (es, "ifconfig_namespace", ifconfig_namespace_parm);
+    }
+  else
+    {
+      tt->iproute_namespace_prefix = gc_malloc(1, false, NULL);
+      tt->iproute_namespace_prefix[0] = 0;
+    }
+#endif
 
   if (ifconfig_local_parm && ifconfig_remote_netmask_parm)
     {
@@ -626,6 +650,50 @@ void delete_route_connected_v6_net(struct tuntap * tt,
 }
 #endif
 
+#ifdef ENABLE_IPROUTE
+static void
+create_netns(const char *name)
+{
+  int list_out_fd;
+  FILE *list_out;
+  struct argv argv;
+  char *line = NULL;
+  size_t len = 0;
+  ssize_t read;
+  int create = 1;
+  /*
+   * Search to see if the namespace already exists
+   */
+  argv_init (&argv);
+  argv_printf (&argv, "%s netns list", iproute_path);
+  argv_msg (M_INFO, &argv);
+  if ((list_out_fd = openvpn_popen (&argv, NULL)) < 0)
+    msg (M_FATAL, "Linux ip netns list failed.");
+
+  if ((list_out = fdopen(list_out_fd, "r")) == NULL)
+    msg (M_FATAL, "Linux open stream from \"ip netns list\" stdout failed.");
+
+  while ((read = getline (&line, &len, list_out)) != -1) {
+    chomp(line);
+    if (strcmp (name, line) == 0)
+      {
+	create = 0; /* namespace already exists */
+	break;
+      }
+  }
+
+  fclose (list_out);
+
+  if (create)
+    {
+      argv_printf (&argv, "%s netns add %s", iproute_path, name);
+      argv_msg (M_INFO, &argv);
+      openvpn_execve_check (&argv, NULL, S_FATAL, "Linux ip netns add failed");
+    }
+
+  argv_reset (&argv);
+}
+#endif
 
 /* execute the ifconfig command through the shell */
 void
@@ -691,10 +759,26 @@ do_ifconfig (struct tuntap *tt,
 #if defined(TARGET_LINUX)
 #ifdef ENABLE_IPROUTE
 	/*
+	 * Put the device in the requested namespace, if any
+	 */
+	if (tt->namespace)
+	  {
+	    create_netns (tt->namespace);
+	    argv_printf (&argv,
+			      "%s link set dev %s netns %s",
+			      iproute_path,
+			      actual,
+			      tt->namespace
+			      );
+	      argv_msg (M_INFO, &argv);
+	      openvpn_execve_check (&argv, es, S_FATAL, "Linux ip link set netns failed");
+	  }
+	/*
 	 * Set the MTU for the device
 	 */
 	argv_printf (&argv,
-			  "%s link set dev %s up mtu %d",
+			  "%sc %s link set dev %s up mtu %d",
+			  tt->iproute_namespace_prefix,
 			  iproute_path,
 			  actual,
 			  tun_mtu
@@ -708,7 +792,8 @@ do_ifconfig (struct tuntap *tt,
 		 * Set the address for the device
 		 */
 		argv_printf (&argv,
-				  "%s addr add dev %s local %s peer %s",
+				  "%sc %s addr add dev %s local %s peer %s",
+				  tt->iproute_namespace_prefix,
 				  iproute_path,
 				  actual,
 				  ifconfig_local,
@@ -718,7 +803,8 @@ do_ifconfig (struct tuntap *tt,
 		  openvpn_execve_check (&argv, es, S_FATAL, "Linux ip addr add failed");
 	} else {
 		argv_printf (&argv,
-				  "%s addr add dev %s %s/%d broadcast %s",
+				  "%sc %s addr add dev %s %s/%d broadcast %s",
+				  tt->iproute_namespace_prefix,
 				  iproute_path,
 				  actual,
 				  ifconfig_local,
@@ -731,7 +817,8 @@ do_ifconfig (struct tuntap *tt,
       if ( do_ipv6 )
 	{
 	  argv_printf( &argv,
-		      "%s -6 addr add %s/%d dev %s",
+		      "%sc %s -6 addr add %s/%d dev %s",
+		      tt->iproute_namespace_prefix,
 		      iproute_path,
 		      ifconfig_ipv6_local,
 		      tt->netbits_ipv6,
@@ -1768,7 +1855,8 @@ close_tun (struct tuntap *tt)
 	    if (is_tun_p2p (tt))
 	      {
 		argv_printf (&argv,
-			"%s addr del dev %s local %s peer %s",
+			"%sc %s addr del dev %s local %s peer %s",
+			tt->iproute_namespace_prefix,
 			iproute_path,
 			tt->actual_name,
 			print_in_addr_t (tt->local, 0, &gc),
@@ -1778,13 +1866,16 @@ close_tun (struct tuntap *tt)
 	    else
 	      {
 		argv_printf (&argv,
-			"%s addr del dev %s %s/%d",
+			"%sc %s addr del dev %s %s/%d",
+			tt->iproute_namespace_prefix,
 			iproute_path,
 			tt->actual_name,
 			print_in_addr_t (tt->local, 0, &gc),
 			count_netmask_bits(print_in_addr_t (tt->remote_netmask, 0, &gc))
 			);
 	      }
+	    if (tt->iproute_namespace_prefix)
+	      free (tt->iproute_namespace_prefix);
 #else
 	    argv_printf (&argv,
 			"%s %s 0.0.0.0",
diff --git a/src/openvpn/tun.h b/src/openvpn/tun.h
index 631b53c..9dd4d9c 100644
--- a/src/openvpn/tun.h
+++ b/src/openvpn/tun.h
@@ -142,6 +142,10 @@ struct tuntap
   struct tuntap_options options; /* options set on command line */
 
   char *actual_name; /* actual name of TUN/TAP dev, usually including unit number */
+  const char *namespace; /* the network namespace this devices is in */
+#ifdef ENABLE_IPROUTE
+  char *iproute_namespace_prefix;
+#endif
 
   /* number of TX buffers */
   int txqueuelen;
@@ -229,6 +233,7 @@ struct tuntap *init_tun (const char *dev,       /* --dev option */
 			 int topology,          /* one of the TOP_x values */
 			 const char *ifconfig_local_parm,          /* --ifconfig parm 1 */
 			 const char *ifconfig_remote_netmask_parm, /* --ifconfig parm 2 */
+			 const char *ifconfig_namespace_parm,
 			 const char *ifconfig_ipv6_local_parm,     /* --ifconfig parm 1 / IPv6 */
 			 int ifconfig_ipv6_netbits_parm,           /* --ifconfig parm 1 / bits */
 			 const char *ifconfig_ipv6_remote_parm,    /* --ifconfig parm 2 / IPv6 */
-- 
1.9.2

Reply via email to