[Openvpn-devel,v2,1/2] Add --bind-dev option.

Message ID 1593427748-29801-2-git-send-email-max@rfc2324.org
State Accepted
Headers show
Series [Openvpn-devel,v2,1/2] Add --bind-dev option. | expand

Commit Message

Maximilian Wilhelm June 29, 2020, 12:49 a.m. UTC
From: Maximilian Wilhelm <max@sdn.clinic>

  This options allows the user to specify a network device the OpenVPN process
  should use when making a connection or binding to an address. This translates
  in setting the SO_BINDTODEVICE option to the corresponding socket (on Linux).

  When for example using VRFs on Linux [0] this allows making connections using
  the non-default VRF and having the tun/tap interface in the default VRF.

  Thanks to David Ahern (Cumulus Networks) for insights on this.

 [0] https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/networking/vrf.txt

Signed-off-by: Maximilian Wilhelm <max@sdn.clinic>
---
 src/openvpn/init.c    |  1 +
 src/openvpn/options.c | 10 ++++++++++
 src/openvpn/options.h |  1 +
 src/openvpn/socket.c  | 14 ++++++++++++++
 src/openvpn/socket.h  |  2 ++
 5 files changed, 28 insertions(+)

Comments

Gert Doering June 29, 2020, 1:31 a.m. UTC | #1
Acked-by: Gert Doering <gert@greenie.muc.de>

Your patch has been applied to the master branch.

I have stared at the code (looks reasonable) and run t_client tests on
Linux and FreeBSD (pass, no major surprise).

I have not actually tested the functionality, because I do not have a test 
rig with VRF (or multiple ethernet links) around.  From what I found so far,
what SO_BINDTODEVICE does is "ensure that packets sent via that socket go
out via the specified interface, and only that interface" and "packets
coming in have been received on that interface".   Besides "a network
interface" you can also specify the name of a "VRF device" (which seems
to be a Linux abstraction to group together "Interfaces and Routes" 
under one umbrella).

So you can use this for VRFs ("--bind-dev outer-vrf") or to select one
particular interface (*and* routes) inside "global" or "VRF".

I have tested the latter ("--bind-to lo") which makes connection setup
*fail* - obviously.  Trying to bind to nonexistant devices gives a proper
error message in v2 now

   2020-06-29 13:27:03 WARN: setsockopt SO_BINDTODEVICE=MyTunnelIsLongerThanYours failed: No such device (errno=19)


Googling for SO_BINDTODEVICE shows lots of confusion on how this is used
and what it does, but I consider the kernel documentation to be authoritative
(Documentation/networking/vrf.txt) and that says "what you do is correct":

   Applications
   ------------
   Applications that are to work within a VRF need to bind their socket to the
   VRF device:

       setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, dev, strlen(dev)+1);

(note the "strlen(dev)+1" part, and "not a struct ifreq")


Your patch has been applied to the master branch.

commit 19d3c602e7a3881cf7c2244b7c40b9958c0b7ebc
Author: Maximilian Wilhelm
Date:   Mon Jun 29 12:49:07 2020 +0200

     Add --bind-dev option.

     Signed-off-by: Maximilian Wilhelm <max@sdn.clinic>
     Acked-by: Gert Doering <gert@greenie.muc.de>
     Message-Id: <1593427748-29801-2-git-send-email-max@rfc2324.org>
     URL: https://www.mail-archive.com/search?l=mid&q=1593427748-29801-2-git-send-email-max@rfc2324.org
     Signed-off-by: Gert Doering <gert@greenie.muc.de>


--
kind regards,

Gert Doering

Patch

diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 2af25c1..dd1747f 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -3445,6 +3445,7 @@  do_init_socket_1(struct context *c, const int mode)
                             c->options.rcvbuf,
                             c->options.sndbuf,
                             c->options.mark,
+                            c->options.bind_dev,
                             &c->c2.server_poll_interval,
                             sockflags);
 }
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 9580d1d..34e2bfd 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -294,6 +294,9 @@  static const char usage_message[] =
 #if defined(TARGET_LINUX) && HAVE_DECL_SO_MARK
     "--mark value    : Mark encrypted packets being sent with value. The mark value\n"
     "                  can be matched in policy routing and packetfilter rules.\n"
+    "--bind-dev dev  : Bind to the given device when making connection to a peer or\n"
+    "                  listening for connections. This allows sending encrypted packets\n"
+    "                  via a VRF present on the system.\n"
 #endif
     "--txqueuelen n  : Set the tun/tap TX queue length to n (Linux only).\n"
 #ifdef ENABLE_MEMSTATS
@@ -6084,6 +6087,13 @@  add_option(struct options *options,
             }
         }
     }
+#ifdef TARGET_LINUX
+    else if (streq (p[0], "bind-dev") && p[1])
+    {
+        VERIFY_PERMISSION (OPT_P_SOCKFLAGS);
+        options->bind_dev = p[1];
+    }
+#endif
     else if (streq(p[0], "txqueuelen") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 375a4fc..c83a46a 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -352,6 +352,7 @@  struct options
 
     /* mark value */
     int mark;
+    char *bind_dev;
 
     /* socket flags */
     unsigned int sockflags;
diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c
index f2fb54d..61463bc 100644
--- a/src/openvpn/socket.c
+++ b/src/openvpn/socket.c
@@ -1138,6 +1138,18 @@  create_socket(struct link_socket *sock, struct addrinfo *addr)
     /* set socket to --mark packets with given value */
     socket_set_mark(sock->sd, sock->mark);
 
+#if defined(TARGET_LINUX)
+    if (sock->bind_dev)
+    {
+        msg (M_INFO, "Using bind-dev %s", sock->bind_dev);
+        if (setsockopt (sock->sd, SOL_SOCKET, SO_BINDTODEVICE, sock->bind_dev, strlen (sock->bind_dev) + 1) != 0)
+        {
+            msg(M_WARN|M_ERRNO, "WARN: setsockopt SO_BINDTODEVICE=%s failed", sock->bind_dev);
+        }
+
+    }
+#endif
+
     bind_local(sock, addr->ai_family);
 }
 
@@ -1880,6 +1892,7 @@  link_socket_init_phase1(struct link_socket *sock,
                         int rcvbuf,
                         int sndbuf,
                         int mark,
+                        const char *bind_dev,
                         struct event_timeout *server_poll_timeout,
                         unsigned int sockflags)
 {
@@ -1906,6 +1919,7 @@  link_socket_init_phase1(struct link_socket *sock,
 
     sock->sockflags = sockflags;
     sock->mark = mark;
+    sock->bind_dev = bind_dev;
 
     sock->info.proto = proto;
     sock->info.af = af;
diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h
index 38e5138..8db9e8b 100644
--- a/src/openvpn/socket.h
+++ b/src/openvpn/socket.h
@@ -212,6 +212,7 @@  struct link_socket
 #define SF_GETADDRINFO_DGRAM (1<<4)
     unsigned int sockflags;
     int mark;
+    const char *bind_dev;
 
     /* for stream sockets */
     struct stream_buf stream_buf;
@@ -326,6 +327,7 @@  link_socket_init_phase1(struct link_socket *sock,
                         int rcvbuf,
                         int sndbuf,
                         int mark,
+                        const char *bind_dev,
                         struct event_timeout *server_poll_timeout,
                         unsigned int sockflags);
 /* Reenable uncrustify *INDENT-ON* */