Skip to contentSkip to conversation

Support non-root operation using ocproxy

PATCH In review Kevin Cernekee 5 participants 12 messages Apr 13, 2014 – Apr 24, 2014

lore
Share & tools
lore
#0 lore raw

cover Support non-root operation using ocproxy

Sometimes it is desirable to establish VPN connections without setting up a kernel tun/tap device. Possible use cases include:

- Routing traffic from different applications through different VPNs.

- Connecting to multiple VPNs (clients, sites, ...) that have overlapping IP ranges.

- Connecting to multiple VPNs that each advertise their own default route and/or DNS settings.

- Situations in which direct access to the VPN from all processes/UIDs is not necessarily wanted, impairs performance, or presents an unacceptable risk of intrusion or data leakage.

- Multiuser systems or container-based VPSes.

- Other situations in which the openvpn user or program is not trusted to reconfigure a tun/tap device.

For a long time, OpenConnect has had the ability to run as non-root[1], redirecting VPN traffic to/from a helper program over a socketpair. ocproxy[2] was written to take advantage of this interface. Now I am submitting changes to allow openvpn to be used in a similar manner. Sample usage:

openvpn --config foo.ovpn \
        --script-security 2 \
        --dev "|/usr/bin/ocproxy -L 2222:unix-host:22 -D 11080"

The -D and -L flags work just like their equivalents on ssh, enabling port forwarding and a SOCKS5 proxy on the specified local sockets. "--dev-type tun" is assumed if unspecified. The IP configuration is passed into ocproxy through "vpnc-script"-compatible environment variables.

This was tested on Linux with IPv4.

[1] http://www.infradead.org/openconnect/nonroot.html
[2] http://repo.or.cz/w/ocproxy.git

Kevin Cernekee (3):
Add S_NOWAIT and S_SETPGRP flags to openvpn_execve Move MTU field into struct tuntap
Add "pipe to external program" device

 doc/openvpn.8      |   29 +++++++++++++--
 src/openvpn/init.c |   17 ++++++---
 src/openvpn/misc.c |    9 ++++-
 src/openvpn/misc.h |    6 ++--
 src/openvpn/tun.c  |  102 ++++++++++++++++++++++++++++++++++++++++++++++++----
 src/openvpn/tun.h  |    4 ++-
 6 files changed, 149 insertions(+), 18 deletions(-)
Signature · 1 line
1.7.9.5
#1 lore raw

PATCH 1/3 Add S_NOWAIT and S_SETPGRP flags to openvpn_execve

This is to let openvpn_execve be used to create a process that runs in the background, and return its PID so that its process group can be nuked on exit.

---
 src/openvpn/misc.c |    9 ++++++++-
 src/openvpn/misc.h |    6 ++++--
 2 files changed, 12 insertions(+), 3 deletions(-)
src/openvpn/misc.c +8 -1
index 7483184..a8018ff 100644
@@ -308,6 +308,11 @@ openvpn_execve (const struct argv *a, const struct env_set *es, const unsigned i
           pid = fork ();
           if (pid == (pid_t)0) /* child side */
             {
+              if (flags & S_SETPGRP)
+                {
+                  if (setpgid (0, getpid ()) == -1)
+                    msg (M_WARN | M_ERRNO, "openvpn_execve: setpgid failed");
+                }
               execve (cmd, argv, envp);
               exit (127);
             }
@@ -315,7 +320,9 @@ openvpn_execve (const struct argv *a, const struct env_set *es, const unsigned i
             msg (M_ERR, "openvpn_execve: unable to fork");
           else /* parent side */
             {
-              if (waitpid (pid, &ret, 0) != pid)
+              if (flags & S_NOWAIT)
+                ret = (int)pid;
+              else if (waitpid (pid, &ret, 0) != pid)
                 ret = -1;
             }
         }
src/openvpn/misc.h +4 -2
index 41748bd..5b7aeee 100644
@@ -86,8 +86,10 @@ void write_pid (const struct pid_state *state);
 void warn_if_group_others_accessible(const char* filename);
 
 /* system flags */
-#define S_SCRIPT (1<<0)
-#define S_FATAL  (1<<1)
+#define S_SCRIPT    (1<<0)
+#define S_FATAL     (1<<1)
+#define S_NOWAIT    (1<<2)
+#define S_SETPGRP   (1<<3)
 
 const char *system_error_message (int, struct gc_arena *gc);
 
Signature · 1 line
1.7.9.5
#2 lore raw

PATCH 2/3 Move MTU field into struct tuntap

In reply to Kevin Cernekee

open_tun() will need to access this value, so we'll store it alongside the IP/netmask addresses.

---
 src/openvpn/init.c |    5 +++--
 src/openvpn/tun.c  |    2 +-
 src/openvpn/tun.h  |    2 +-
 3 files changed, 5 insertions(+), 4 deletions(-)
src/openvpn/init.c +3 -2
index c2907cd..139c625 100644
@@ -1448,6 +1448,7 @@ do_open_tun (struct context *c)
 	do_init_route_ipv6_list (&c->options, c->c1.route_ipv6_list, false, c->c2.es);
 
       /* do ifconfig */
+      c->c1.tuntap->mtu = TUN_MTU_SIZE (&c->c2.frame);
       if (!c->options.ifconfig_noexec
 	  && ifconfig_order () == IFCONFIG_BEFORE_TUN_OPEN)
 	{
@@ -1457,7 +1458,7 @@ do_open_tun (struct context *c)
 						c->options.dev_type,
 						c->options.dev_node,
 						&gc);
-	  do_ifconfig (c->c1.tuntap, guess, TUN_MTU_SIZE (&c->c2.frame), c->c2.es);
+	  do_ifconfig (c->c1.tuntap, guess, c->c2.es);
 	}
 
       /* possibly add routes */
@@ -1482,7 +1483,7 @@ do_open_tun (struct context *c)
       if (!c->options.ifconfig_noexec
 	  && ifconfig_order () == IFCONFIG_AFTER_TUN_OPEN)
 	{
-	  do_ifconfig (c->c1.tuntap, c->c1.tuntap->actual_name, TUN_MTU_SIZE (&c->c2.frame), c->c2.es);
+	  do_ifconfig (c->c1.tuntap, c->c1.tuntap->actual_name, c->c2.es);
 	}
 
       /* run the up script */
src/openvpn/tun.c +1 -1
index 4df271d..31bb583 100644
@@ -631,10 +631,10 @@ void delete_route_connected_v6_net(struct tuntap * tt,
 void
 do_ifconfig (struct tuntap *tt,
 	     const char *actual,    /* actual device name */
-	     int tun_mtu,
 	     const struct env_set *es)
 {
   struct gc_arena gc = gc_new ();
+  int tun_mtu = tt->mtu;
 
   if (tt->did_ifconfig_setup)
     {
src/openvpn/tun.h +1 -1
index 631b53c..1b510d0 100644
@@ -150,6 +150,7 @@ struct tuntap
   in_addr_t local;
   in_addr_t remote_netmask;
   in_addr_t broadcast;
+  int mtu;
 
   struct in6_addr local_ipv6;
   struct in6_addr remote_ipv6;
@@ -243,7 +244,6 @@ void init_tun_post (struct tuntap *tt,
 
 void do_ifconfig (struct tuntap *tt,
 		  const char *actual,    /* actual device name */
-		  int tun_mtu,
 		  const struct env_set *es);
 
 bool is_dev_type (const char *dev, const char *dev_type, const char *match_type);
Signature · 1 line
1.7.9.5
#3 lore raw

PATCH 3/3 Add "pipe to external program" device

In reply to Kevin Cernekee

Allow the tunneled traffic to be handled by an external program rather than by a real tun/tap kernel device. This allows non-root users to connect to a VPN through a userland TCP/IP stack.

---
 doc/openvpn.8      |   29 +++++++++++++--
 src/openvpn/init.c |   12 +++++--
 src/openvpn/tun.c  |  100 ++++++++++++++++++++++++++++++++++++++++++++++++----
 src/openvpn/tun.h  |    2 ++
 4 files changed, 132 insertions(+), 11 deletions(-)
doc/openvpn.8 +27 -2
index 3a58317..00efeb8 100644
@@ -709,17 +709,42 @@ peers which will be initiating connections by using the
 option.
 .\"*********************************************************
 .TP
-.B \-\-dev tunX | tapX | null
+.B \-\-dev tunX | tapX | null | <program>
 TUN/TAP virtual network device (
 .B X
 can be omitted for a dynamic device.)
 
+.B tunX
+or
+.B tapX
+is the name of a network interface that openvpn will create.
+
+.B null
+is a special value used for testing only.  It will not pass traffic.
+
+.B <program>
+is the full path to an executable, such as
+.B |/usr/bin/ocproxy,
+which will pass traffic to and from openvpn over a socketpair.  This is an
+alternative to using a tun/tap interface to pass traffic to and from the
+OS kernel; unlike tun/tap it does not require any special privileges.  The
+path must start with a `|' (pipe) character.  This works with either
+.B \-\-dev-type tun
+or
+.B \-\-dev-type tap.
+If left unspecified, it will default to tun.
+
 See examples section below
 for an example on setting up a TUN device.
 
 You must use either tun devices on both ends of the connection
 or tap devices on both ends.  You cannot mix them, as they
-represent different underlying network layers.
+represent different underlying network layers.  Similarly, endpoints using
+device type
+.B null
+will only interoperate with other
+.B null
+endpoints.
 
 .B tun
 devices encapsulate IPv4 or IPv6 (OSI Layer 3) while
src/openvpn/init.c +9 -3
index 139c625..1158ad0 100644
@@ -1326,7 +1326,9 @@ do_route (const struct options *options,
 	  const struct plugin_list *plugins,
 	  struct env_set *es)
 {
-  if (!options->route_noexec && ( route_list || route_ipv6_list ) )
+  if (!options->route_noexec &&
+      !tt->is_pipe &&
+      ( route_list || route_ipv6_list ) )
     {
       add_routes (route_list, route_ipv6_list, tt, ROUTE_OPTION_FLAGS (options), es);
       setenv_int (es, "redirect_gateway", route_did_redirect_default_gateway(route_list));
@@ -1450,6 +1452,7 @@ do_open_tun (struct context *c)
       /* do ifconfig */
       c->c1.tuntap->mtu = TUN_MTU_SIZE (&c->c2.frame);
       if (!c->options.ifconfig_noexec
+	  && !c->c1.tuntap->is_pipe
 	  && ifconfig_order () == IFCONFIG_BEFORE_TUN_OPEN)
 	{
 	  /* guess actual tun/tap unit number that will be returned
@@ -1462,7 +1465,8 @@ do_open_tun (struct context *c)
 	}
 
       /* possibly add routes */
-      if (route_order() == ROUTE_BEFORE_TUN) {
+      if (route_order() == ROUTE_BEFORE_TUN
+	  && !c->c1.tuntap->is_pipe) {
         /* Ignore route_delay, would cause ROUTE_BEFORE_TUN to be ignored */
         do_route (&c->options, c->c1.route_list, c->c1.route_ipv6_list,
                   c->c1.tuntap, c->plugins, c->c2.es);
@@ -1476,11 +1480,13 @@ do_open_tun (struct context *c)
 		c->c1.tuntap);
 
       /* set the hardware address */
-      if (c->options.lladdr)
+      if (c->options.lladdr
+	  && !c->c1.tuntap->is_pipe)
 	  set_lladdr(c->c1.tuntap->actual_name, c->options.lladdr, c->c2.es);
 
       /* do ifconfig */
       if (!c->options.ifconfig_noexec
+	  && !c->c1.tuntap->is_pipe
 	  && ifconfig_order () == IFCONFIG_AFTER_TUN_OPEN)
 	{
 	  do_ifconfig (c->c1.tuntap, c->c1.tuntap->actual_name, c->c2.es);
src/openvpn/tun.c +94 -6

149 lines not on this page · raw message

src/openvpn/tun.h +2 -0
index 1b510d0..0c93a1d 100644
@@ -131,6 +131,8 @@ struct tuntap
 # define TUNNEL_TOPOLOGY(tt) ((tt) ? ((tt)->topology) : TOP_UNDEF)
   int topology; /* one of the TOP_x values */
 
+  bool is_pipe;
+  pid_t pipe_pid;
   bool did_ifconfig_setup;
   bool did_ifconfig_ipv6_setup;
   bool did_ifconfig;
Signature · 1 line
1.7.9.5
#4 lore raw
In reply to Kevin Cernekee

Hi,

Quoted 11 lines · Kevin Cernekee

Sometimes it is desirable to establish VPN connections without setting up a kernel tun/tap device. Possible use cases include:

- Routing traffic from different applications through different VPNs.

- Connecting to multiple VPNs (clients, sites, ...) that have overlapping IP ranges.

- Connecting to multiple VPNs that each advertise their own default route and/or DNS settings.

Quoted 8 lines

- Situations in which direct access to the VPN from all processes/UIDs is not necessarily wanted, impairs performance, or presents an unacceptable risk of intrusion or data leakage.

- Multiuser systems or container-based VPSes.

- Other situations in which the openvpn user or program is not trusted to reconfigure a tun/tap device.

I can see that you might want that, but I do not think we want this in OpenVPN.

It brings in a fair bit of extra code complexity, more arcane options, and code that needs to maintained and tested across all supported platforms.

I think the goal can in most cases be achieved using SSH port forwarding - or OpenConnect, or a myriad of other "tunnel this file descriptor for me" tools. OpenVPN will do tun/tap, and do that well.

gert

Signature · 4 lines
USENET is *not* the non-clickable part of WWW!
                                                           //www.muc.de/~gert/
Gert Doering - Munich, Germany                             [email protected]...
fax: +49-89-35655025                        [email protected]...
#5 lore raw
Quoted 3 lines · Gert Doering

I think the goal can in most cases be achieved using SSH port forwarding - or OpenConnect, or a myriad of other "tunnel this file descriptor for me" tools. OpenVPN will do tun/tap, and do that well.

The problem with all of these alternatives (at least in my case) is that the other end is already running OpenVPN, not one of the other tools. Right now, there aren't a lot of good ways to connect to somebody's OpenVPN service without running as root. Prior to this work I had been using separate VMs.

Are there any changes you could suggest that would make this feature more palatable?

#6 lore raw
Quoted 4 lines · Kevin Cernekee
Quoted 3 lines · Gert Doering

I think the goal can in most cases be achieved using SSH port forwarding - or OpenConnect, or a myriad of other "tunnel this file descriptor for me" tools. OpenVPN will do tun/tap, and do that well.

Quoted 10 lines

The problem with all of these alternatives (at least in my case) is that the other end is already running OpenVPN, not one of the other tools. Right now, there aren't a lot of good ways to connect to somebody's OpenVPN service without running as root. Prior to this work I had been using separate VMs.

Are there any changes you could suggest that would make this feature more palatable?

You could look at the TARGET_ANDROID. That uses the management interface and fds over unix socket to achieve something similar.

Arne

#7 lore raw
Quoted 2 lines · Arne Schwabe

You could look at the TARGET_ANDROID. That uses the management interface and fds over unix socket to achieve something similar.

Do you think it would be feasible to enable TARGET_ANDROID by default in the Linux OpenVPN builds, and change the logic so that if the user passes e.g. "--android" it will accept commands over the management socket instead of using the standard CLI?

This would not work with ocproxy as-is, but I could write a wrapper program.

Let me know if you think it's worth putting together a patch set.

BTW, here is an example of how my current patch works with an existing OpenVPN service:

$ ./src/openvpn/openvpn --config /tmp/vpnbook/vpnbook-us1-udp25000.ovpn \
        --verb 0 --script-security 2 \
        --dev "|/usr/bin/ocproxy -D 12345" &
Sun Apr 13 08:46:44 2014 WARNING: file '/tmp/vpnbook/user.txt' is
group or others accessible
Sun Apr 13 08:46:44 2014 WARNING: No server certificate verification
method has been enabled.  See http://openvpn.net/howto.html#mitm for
more info.
Sun Apr 13 08:46:44 2014 WARNING: this configuration may cache
passwords in memory -- use the auth-nocache option to prevent this
$ SOCKS5_SERVER=127.0.0.1:12345 socksify links -source ipchicken.com |
grep -A1 Address
            Address:
            198.7.62.204            </font></td>

Using port forwarding with "ocproxy -L", socksify, or FoxyProxy, individual connections / applications / URLs can be seamlessly forwarded over different VPN links. Or, if I omit the --dev option, OpenVPN will fall back to standard "create tunX" behavior.

#8 lore raw
Quoted 7 lines · Kevin Cernekee
Quoted 2 lines · Arne Schwabe

You could look at the TARGET_ANDROID. That uses the management interface and fds over unix socket to achieve something similar.

Do you think it would be feasible to enable TARGET_ANDROID by default in the Linux OpenVPN builds, and change the logic so that if the user passes e.g. "--android" it will accept commands over the management socket instead of using the standard CLI?

I am sure how invasive a patch that allows --android would be.

Arne

#9 lore raw
In reply to Kevin Cernekee

Hi

Quoted 12 lines · Kevin Cernekee
Quoted 3 lines · Arne Schwabe

You could look at the TARGET_ANDROID. That uses the management interface and fds over unix socket to achieve something similar.

Do you think it would be feasible to enable TARGET_ANDROID by default in the Linux OpenVPN builds, and change the logic so that if the user passes e.g. "--android" it will accept commands over the management socket instead of using the standard CLI?

I'd vote against enabling TARGET_ANDROID by default - there's a reason the code is split into
TARGET_LINUX
TARGET_FREEBSD
TARGET_SOLARIS
etc and I'd say you're asking for trouble if you enable two TARGETs on one platform.

Quoted 3 lines

This would not work with ocproxy as-is, but I could write a wrapper program.

I do like the idea of not needing root access to run openvpn - esp windows users could benefit from this, as they're not always allowed to install the tap-win adapter. Then again, it goes against the UNIX/Linux philosophy that each tool (e.g. openvpn) should do one thing and it should do it well. By adding patches like this left and right we're steering away from that philosophy.

cheers,

JJK

#10 lore raw
Quoted 1 line · Jan Just Keijser

I'd vote against enabling TARGET_ANDROID by default

This would not be my first choice either, but it would allow non-root processes to set up a fully functional VPN connection on Linux hosts without a special build.

If I'm going to have to make a special build anyway, I'll just apply my three patches locally. It's working for me now and I posted packages in my Ubuntu PPA. But that is somewhat inconvenient to maintain, and it doesn't benefit other OpenVPN users (almost all of whom are using binaries built from the official sources).

Quoted 6 lines

there's a reason the code is split into
TARGET_LINUX
TARGET_FREEBSD
TARGET_SOLARIS
etc and I'd say you're asking for trouble if you enable two TARGETs on one platform.

Yes, the way the code is currently written, enabling more than one TARGET_* option is a problem.

But, stepping back a bit, does Android really need its own TARGET? Or can it be treated as a special operating mode on a standard TARGET_LINUX build? Any Linux host, Android or PC, should be able to pass its traffic over either tunX or over a file descriptor passed in from outside. It would be nice if this choice was available at runtime.

Quoted 3 lines

I do like the idea of not needing root access to run openvpn - esp windows users could benefit from this, as they're not always allowed to install the tap-win adapter.

There are also a number of cases where it is useful to have fine-grained control over which applications use the VPN connection, e.g.

https://pay.reddit.com/r/techsupport/comments/22x2oo/routing_clients_through_vpn_connected_through/

I don't know if my solution is necessarily the best option for this user, but it's something to consider at least...

Quoted 4 lines

Then again, it goes against the UNIX/Linux philosophy that each tool (e.g. openvpn) should do one thing and it should do it well. By adding patches like this left and right we're steering away from that philosophy.

We could always split {route,tun,lladdr}.c into a separate program and invoke it with "openvpn --dev '|tunhelper'". :-)

#11 lore raw
In reply to Jan Just Keijser
Quoted 6 lines · Jan Just Keijser

I do like the idea of not needing root access to run openvpn - esp windows users could benefit from this, as they're not always allowed to install the tap-win adapter. Then again, it goes against the UNIX/Linux philosophy that each tool (e.g. openvpn) should do one thing and it should do it well. By adding patches like this left and right we're steering away from that philosophy.

I'm inclined to agree about the philosophy, but I think it's a design decision made long ago in OpenVPN, and I wouldn't necessarily advocate changing it.

One way of applying the UNIX philosophy in this context is to suggest that the "one thing" that a VPN tool should do well is the transfer of packets between an encrypted network link and a local one.

If you take a look at vpnc and openconnect, you'll see that the nasty details of how you *configure* a network connection for every platform under the sun was considered to be outside the scope. It was farmed out to a separate tool, vpnc-script. Which allows us to *share* that vpnc-script between the two clients rather than reproducing the effort.

If you look at the situation from that point of view, then yes — adding "patches like this left and right" is definitely steering away from the UNIX philosophy. But the "patches like this" could be seen to include all the existing support for configuring specific platforms such as Linux, *BSD, Solaris, Windows, OSX, Android, etc. :)

But there are good reasons why openvpn does this stuff for itself, and I don't think it's very practical to change it. Kevin's patches are in line with the existing design.

We have a lot of configuration cruft in openvpn, and fundamentally what it does is give the core code a file descriptor that it can shove packets in and out of. If Kevin offers a relatively clean way for that file descriptor to be a UNIX dgram socket which we handed to a spawned child process, instead of a handle to the kernel's /dev/net/tun device, I don't see why we'd consider that a violation of the philosophy.

Signature · 2 lines
David Woodhouse                            Open Source Technology Centre
[email protected]...                              Intel Corporation