[Openvpn-devel,v2,2/7] implement networking API for iproute2

Message ID 20181011184200.22175-3-a@unstable.cc
State Superseded
Headers show
Series
  • introduce networking API and add netlink support for Linux
Related show

Commit Message

Antonio Quartulli Oct. 11, 2018, 6:41 p.m.
iproute2 is the first user of the new networking API and
its one of the two currently supported functionalities on
Linux (the other being net-tools).

This patch simply copies the current code from tun.c/route.c
to networking_ip.c without introducing any funcional
change to the code.

Signed-off-by: Antonio Quartulli <a@unstable.cc>
---
 src/openvpn/Makefile.am     |   2 +-
 src/openvpn/networking_ip.c | 386 ++++++++++++++++++++++++++++++++++++
 src/openvpn/networking_ip.h |  36 ++++
 3 files changed, 423 insertions(+), 1 deletion(-)
 create mode 100644 src/openvpn/networking_ip.c
 create mode 100644 src/openvpn/networking_ip.h

Comments

Arne Schwabe Nov. 26, 2018, 12:44 p.m. | #1
Am 11.10.18 um 20:41 schrieb Antonio Quartulli:
> iproute2 is the first user of the new networking API and
> its one of the two currently supported functionalities on
> Linux (the other being net-tools).
> 
> This patch simply copies the current code from tun.c/route.c
> to networking_ip.c without introducing any funcional
> change to the code.
> 

I skipped the first patch and added notes about the proposed API in this
patch.

>  create mode 100644 src/openvpn/networking_ip.h
> 
> diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
> index 8afc4146..3caad17d 100644
> --- a/src/openvpn/Makefile.am
> +++ b/src/openvpn/Makefile.am
> @@ -80,7 +80,7 @@ openvpn_SOURCES = \
>  	mtu.c mtu.h \
>  	mudp.c mudp.h \
>  	multi.c multi.h \
> -	networking.h \
> +	networking_ip.c networking_ip.h networking.h \

As ip is not really but a linux specific tool, I think naming this
networking_linuxip.c is better as networking_ip sounds like something
generic.


>  	ntlm.c ntlm.h \
>  	occ.c occ.h \
>  	openssl_compat.h \
> diff --git a/src/openvpn/networking_ip.c b/src/openvpn/networking_ip.c
> new file mode 100644
> index 00000000..ae667a9c
> --- /dev/null
> +++ b/src/openvpn/networking_ip.c
> @@ -0,0 +1,386 @@
> +/*
> + *  Networking API implementation for iproute2
> + *
> + *  Copyright (C) 2018 Antonio Quartulli <a@unstable.cc>

I think since you copied/pasted from other files this should also have
the normal copyrights, right?

> +
> +#if defined(TARGET_LINUX) && defined(ENABLE_IPROUTE)
> +
> +#include "syshead.h"
> +
> +#include "networking.h"
> +#include "networking_ip.h"
> +#include "misc.h"
> +#include "openvpn.h"
> +#include "run_command.h"
> +#include "socket.h"
> +
> +#include <stdbool.h>
> +#include <netinet/in.h>
> +
> +int
> +net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)
> +{
> +    ctx->es = NULL;
> +    if (c)
> +        ctx->es = c->es;
> +
> +    return 0;
> +}

I know that calling our execve with es==NULL is valid but should not we
aim here for consistency and always require c to be non NULL aka ASSERT(c)?


> +int
> +net_iface_up(openvpn_net_ctx_t *ctx, const char *iface, bool up)
> +{
> +    struct argv argv = argv_new();
> +
> +    argv_printf(&argv, "%s link set dev %s %s", iproute_path, iface,
> +                up ? "up" : "down");
> +    argv_msg(M_INFO, &argv);
> +    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip link set failed");
> +
> +    argv_reset(&argv);
> +
> +    return 0;
> +}
> +
> +int
> +net_iface_mtu_set(openvpn_net_ctx_t *ctx, const char *iface, uint32_t mtu)
> +{
> +    struct argv argv = argv_new();
> +
> +    argv_printf(&argv, "%s link set dev %s up mtu %d", iproute_path, iface,
> +                mtu);
> +    argv_msg(M_INFO, &argv);
> +    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip link set failed");
> +
> +    return 0;
> +}
> +
> +int
> +net_addr_v4_add(openvpn_net_ctx_t *ctx, const char *iface,
> +                const in_addr_t *addr, int prefixlen,
> +                const in_addr_t *broadcast)
> +{
> +    struct argv argv = argv_new();
> +
> +    char *addr_str = (char *)print_in_addr_t(*addr, 0, NULL);
> +    char *brd_str = (char *)print_in_addr_t(*broadcast, 0, NULL);
> +
> +    argv_printf(&argv, "%s addr add dev %s %s/%d broadcast %s", iproute_path,
> +                iface, addr_str, prefixlen, brd_str);
> +    argv_msg(M_INFO, &argv);
> +    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip addr add failed");
> +
> +    free(addr_str);
> +    free(brd_str);
> +
> +    argv_reset(&argv);
> +
> +    return 0;
> +}
> +

I initially written a comment that this method removes the ptp variant
of the method but later saw that there is also a ptp variant of the
method. I would suggest to either ranme this method to
net_addr_bcast_v4_add or have a bool ptp paramter and then call out to
two different methods.



> +net_addr_v6_add(openvpn_net_ctx_t *ctx, const char *iface,
> +                const struct in6_addr *addr, int prefixlen)

Same comment as for v4.
> +net_addr_v4_del(openvpn_net_ctx_t *ctx, const char *iface,
> +                const in_addr_t *addr, int prefixlen)

> +net_addr_v6_del(openvpn_net_ctx_t *ctx, const char *iface,
> +                const struct in6_addr *addr, int prefixlen)
>
These methods assume that removing ip on ptp and broadcast is the same
for ptp and boradcast on all platforms, is that really a sane assumption?

Arne
Antonio Quartulli Nov. 27, 2018, 1:04 a.m. | #2
On 26/11/2018 22:44, Arne Schwabe wrote:
> Am 11.10.18 um 20:41 schrieb Antonio Quartulli:
>> iproute2 is the first user of the new networking API and
>> its one of the two currently supported functionalities on
>> Linux (the other being net-tools).
>>
>> This patch simply copies the current code from tun.c/route.c
>> to networking_ip.c without introducing any funcional
>> change to the code.
>>
> 
> I skipped the first patch and added notes about the proposed API in this
> patch.
> 
>>  create mode 100644 src/openvpn/networking_ip.h
>>
>> diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
>> index 8afc4146..3caad17d 100644
>> --- a/src/openvpn/Makefile.am
>> +++ b/src/openvpn/Makefile.am
>> @@ -80,7 +80,7 @@ openvpn_SOURCES = \
>>  	mtu.c mtu.h \
>>  	mudp.c mudp.h \
>>  	multi.c multi.h \
>> -	networking.h \
>> +	networking_ip.c networking_ip.h networking.h \
> 
> As ip is not really but a linux specific tool, I think naming this
> networking_linuxip.c is better as networking_ip sounds like something
> generic.

I did not want to make any assumption - you never know if iproute will
later be ported to *BSD as well. But I am fine with either name.

> 
> 
>>  	ntlm.c ntlm.h \
>>  	occ.c occ.h \
>>  	openssl_compat.h \
>> diff --git a/src/openvpn/networking_ip.c b/src/openvpn/networking_ip.c
>> new file mode 100644
>> index 00000000..ae667a9c
>> --- /dev/null
>> +++ b/src/openvpn/networking_ip.c
>> @@ -0,0 +1,386 @@
>> +/*
>> + *  Networking API implementation for iproute2
>> + *
>> + *  Copyright (C) 2018 Antonio Quartulli <a@unstable.cc>
> 
> I think since you copied/pasted from other files this should also have
> the normal copyrights, right?

Yeah, it makes sense because in this file there is code coming from the
current implementation.

> 
>> +
>> +#if defined(TARGET_LINUX) && defined(ENABLE_IPROUTE)
>> +
>> +#include "syshead.h"
>> +
>> +#include "networking.h"
>> +#include "networking_ip.h"
>> +#include "misc.h"
>> +#include "openvpn.h"
>> +#include "run_command.h"
>> +#include "socket.h"
>> +
>> +#include <stdbool.h>
>> +#include <netinet/in.h>
>> +
>> +int
>> +net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)
>> +{
>> +    ctx->es = NULL;
>> +    if (c)
>> +        ctx->es = c->es;
>> +
>> +    return 0;
>> +}
> 
> I know that calling our execve with es==NULL is valid but should not we
> aim here for consistency and always require c to be non NULL aka ASSERT(c)?

No, because this can be invoked when calling openvpn with "--show-gateway".
In that case we need to talk to the network stack but there is no context.

> 
> 
>> +int
>> +net_iface_up(openvpn_net_ctx_t *ctx, const char *iface, bool up)
>> +{
>> +    struct argv argv = argv_new();
>> +
>> +    argv_printf(&argv, "%s link set dev %s %s", iproute_path, iface,
>> +                up ? "up" : "down");
>> +    argv_msg(M_INFO, &argv);
>> +    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip link set failed");
>> +
>> +    argv_reset(&argv);
>> +
>> +    return 0;
>> +}
>> +
>> +int
>> +net_iface_mtu_set(openvpn_net_ctx_t *ctx, const char *iface, uint32_t mtu)
>> +{
>> +    struct argv argv = argv_new();
>> +
>> +    argv_printf(&argv, "%s link set dev %s up mtu %d", iproute_path, iface,
>> +                mtu);
>> +    argv_msg(M_INFO, &argv);
>> +    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip link set failed");
>> +
>> +    return 0;
>> +}
>> +
>> +int
>> +net_addr_v4_add(openvpn_net_ctx_t *ctx, const char *iface,
>> +                const in_addr_t *addr, int prefixlen,
>> +                const in_addr_t *broadcast)
>> +{
>> +    struct argv argv = argv_new();
>> +
>> +    char *addr_str = (char *)print_in_addr_t(*addr, 0, NULL);
>> +    char *brd_str = (char *)print_in_addr_t(*broadcast, 0, NULL);
>> +
>> +    argv_printf(&argv, "%s addr add dev %s %s/%d broadcast %s", iproute_path,
>> +                iface, addr_str, prefixlen, brd_str);
>> +    argv_msg(M_INFO, &argv);
>> +    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip addr add failed");
>> +
>> +    free(addr_str);
>> +    free(brd_str);
>> +
>> +    argv_reset(&argv);
>> +
>> +    return 0;
>> +}
>> +
> 
> I initially written a comment that this method removes the ptp variant
> of the method but later saw that there is also a ptp variant of the
> method. I would suggest to either ranme this method to
> net_addr_bcast_v4_add or have a bool ptp paramter and then call out to
> two different methods.

To me it feels like the PtP case is a special case, therefore I
preferred to keep the "standard & simple" name for the non-PtP case and
then add a special function just for PtP.

Actually this function may even be called without a broadcast address.

Mh..dunno to be honest. Maybe somebody else has an opinion?

> 
> 
> 
>> +net_addr_v6_add(openvpn_net_ctx_t *ctx, const char *iface,
>> +                const struct in6_addr *addr, int prefixlen)
> 
> Same comment as for v4.
>> +net_addr_v4_del(openvpn_net_ctx_t *ctx, const char *iface,
>> +                const in_addr_t *addr, int prefixlen)
> 
>> +net_addr_v6_del(openvpn_net_ctx_t *ctx, const char *iface,
>> +                const struct in6_addr *addr, int prefixlen)
>>
> These methods assume that removing ip on ptp and broadcast is the same
> for ptp and boradcast on all platforms, is that really a sane assumption?
> 

Good point ! It is a sane assumption at least for netlink and iproute. I
am not sure about nettols.
How about Android?


Regards,
David Sommerseth Nov. 27, 2018, 12:13 p.m. | #3
On 27/11/2018 02:04, Antonio Quartulli wrote:
> 
[...snip...]
>>>  create mode 100644 src/openvpn/networking_ip.h
>>>
>>> diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
>>> index 8afc4146..3caad17d 100644
>>> --- a/src/openvpn/Makefile.am
>>> +++ b/src/openvpn/Makefile.am
>>> @@ -80,7 +80,7 @@ openvpn_SOURCES = \
>>>  	mtu.c mtu.h \
>>>  	mudp.c mudp.h \
>>>  	multi.c multi.h \
>>> -	networking.h \
>>> +	networking_ip.c networking_ip.h networking.h \
>>
>> As ip is not really but a linux specific tool, I think naming this
>> networking_linuxip.c is better as networking_ip sounds like something
>> generic.
> 
> I did not want to make any assumption - you never know if iproute will
> later be ported to *BSD as well. But I am fine with either name.

Just adding more paint to the shed :-P  networking_ip.[ch] sounds a bit too
generic, but I'd prefer networking_iproute2.[ch], which I consider more
consistent to what these files implement.

Otherwise, both Arne is right (iproute2 is currently only widely used on
Linux) and Antonio is perhaps right to (iproute2 might be ported in the future).

[...snip...]

>>> +int
>>> +net_addr_v4_add(openvpn_net_ctx_t *ctx, const char *iface,
>>> +                const in_addr_t *addr, int prefixlen,
>>> +                const in_addr_t *broadcast)
>>> +{
>>> +    struct argv argv = argv_new();
>>> +
>>> +    char *addr_str = (char *)print_in_addr_t(*addr, 0, NULL);
>>> +    char *brd_str = (char *)print_in_addr_t(*broadcast, 0, NULL);
>>> +
>>> +    argv_printf(&argv, "%s addr add dev %s %s/%d broadcast %s", iproute_path,
>>> +                iface, addr_str, prefixlen, brd_str);
>>> +    argv_msg(M_INFO, &argv);
>>> +    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip addr add failed");
>>> +
>>> +    free(addr_str);
>>> +    free(brd_str);
>>> +
>>> +    argv_reset(&argv);
>>> +
>>> +    return 0;
>>> +}
>>> +
>>
>> I initially written a comment that this method removes the ptp variant
>> of the method but later saw that there is also a ptp variant of the
>> method. I would suggest to either ranme this method to
>> net_addr_bcast_v4_add or have a bool ptp paramter and then call out to
>> two different methods.
> 
> To me it feels like the PtP case is a special case, therefore I
> preferred to keep the "standard & simple" name for the non-PtP case and
> then add a special function just for PtP.
> 
> Actually this function may even be called without a broadcast address.
> 
> Mh..dunno to be honest. Maybe somebody else has an opinion?

I agree with Antonio.  It's not just the broadcast which is different, the ptp
variant uses 'peer x.x.x.x' too.  It is a different configuration type, so
this makes it clearer to me when reading:

    net_addr_ptp_v4_addr(ctx, iface, local, remote);

Instead of:

    net_addr_v4_add(ctx, iface, local, 0, remote , true);

Of course, the 'true' could be a variable, but I don't see the real benefit of
making net_addr_v4_add() implemented wit different modes.  Especially when the
"broadcast" and "peer" passing would most likely go via a shared variable.
And it wouldn't make it cleared if peer and remote was separate arguments
either; as only one of them can be present at the same time.

So from code clarity, I like the distinction via the function names instead.
It is more straight to the point for me.

Patch

diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index 8afc4146..3caad17d 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -80,7 +80,7 @@  openvpn_SOURCES = \
 	mtu.c mtu.h \
 	mudp.c mudp.h \
 	multi.c multi.h \
-	networking.h \
+	networking_ip.c networking_ip.h networking.h \
 	ntlm.c ntlm.h \
 	occ.c occ.h \
 	openssl_compat.h \
diff --git a/src/openvpn/networking_ip.c b/src/openvpn/networking_ip.c
new file mode 100644
index 00000000..ae667a9c
--- /dev/null
+++ b/src/openvpn/networking_ip.c
@@ -0,0 +1,386 @@ 
+/*
+ *  Networking API implementation for iproute2
+ *
+ *  Copyright (C) 2018 Antonio Quartulli <a@unstable.cc>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program (see the file COPYING included with this
+ *  distribution); if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#if defined(TARGET_LINUX) && defined(ENABLE_IPROUTE)
+
+#include "syshead.h"
+
+#include "networking.h"
+#include "networking_ip.h"
+#include "misc.h"
+#include "openvpn.h"
+#include "run_command.h"
+#include "socket.h"
+
+#include <stdbool.h>
+#include <netinet/in.h>
+
+int
+net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)
+{
+    ctx->es = NULL;
+    if (c)
+        ctx->es = c->es;
+
+    return 0;
+}
+
+int
+net_iface_up(openvpn_net_ctx_t *ctx, const char *iface, bool up)
+{
+    struct argv argv = argv_new();
+
+    argv_printf(&argv, "%s link set dev %s %s", iproute_path, iface,
+                up ? "up" : "down");
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip link set failed");
+
+    argv_reset(&argv);
+
+    return 0;
+}
+
+int
+net_iface_mtu_set(openvpn_net_ctx_t *ctx, const char *iface, uint32_t mtu)
+{
+    struct argv argv = argv_new();
+
+    argv_printf(&argv, "%s link set dev %s up mtu %d", iproute_path, iface,
+                mtu);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip link set failed");
+
+    return 0;
+}
+
+int
+net_addr_v4_add(openvpn_net_ctx_t *ctx, const char *iface,
+                const in_addr_t *addr, int prefixlen,
+                const in_addr_t *broadcast)
+{
+    struct argv argv = argv_new();
+
+    char *addr_str = (char *)print_in_addr_t(*addr, 0, NULL);
+    char *brd_str = (char *)print_in_addr_t(*broadcast, 0, NULL);
+
+    argv_printf(&argv, "%s addr add dev %s %s/%d broadcast %s", iproute_path,
+                iface, addr_str, prefixlen, brd_str);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip addr add failed");
+
+    free(addr_str);
+    free(brd_str);
+
+    argv_reset(&argv);
+
+    return 0;
+}
+
+int
+net_addr_v6_add(openvpn_net_ctx_t *ctx, const char *iface,
+                const struct in6_addr *addr, int prefixlen)
+{
+    struct argv argv = argv_new();
+    char *addr_str = (char *)print_in6_addr(*addr, 0, NULL);
+
+    argv_printf(&argv, "%s -6 addr add %s/%d dev %s", iproute_path, addr_str,
+                prefixlen, iface);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, S_FATAL,
+                         "Linux ip -6 addr add failed");
+
+    free(addr_str);
+
+    argv_reset(&argv);
+
+    return 0;
+}
+
+int
+net_addr_v4_del(openvpn_net_ctx_t *ctx, const char *iface,
+                const in_addr_t *addr, int prefixlen)
+{
+    struct argv argv = argv_new();
+    char *addr_str = (char *)print_in_addr_t(*addr, 0, NULL);
+
+    argv_printf(&argv, "%s addr del dev %s %s/%d", iproute_path, iface,
+                addr_str, prefixlen);
+
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "Linux ip addr del failed");
+
+    free(addr_str);
+
+    argv_reset(&argv);
+
+    return 0;
+}
+
+int
+net_addr_v6_del(openvpn_net_ctx_t *ctx, const char *iface,
+                const struct in6_addr *addr, int prefixlen)
+{
+    struct argv argv = argv_new();
+    char *addr_str = (char *)print_in6_addr(*addr, 0, NULL);
+
+    argv_printf(&argv, "%s -6 addr del %s/%d dev %s", iproute_path,
+                addr_str, prefixlen, iface);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "Linux ip -6 addr del failed");
+
+    free(addr_str);
+
+    argv_reset(&argv);
+
+    return 0;
+}
+
+int
+net_addr_ptp_v4_add(openvpn_net_ctx_t *ctx, const char *iface,
+                    const in_addr_t *local, const in_addr_t *remote)
+{
+    struct argv argv = argv_new();
+    char *local_str = (char *)print_in_addr_t(*local, 0, NULL);
+    char *remote_str = (char *)print_in_addr_t(*remote, 0, NULL);
+
+    argv_printf(&argv, "%s addr add dev %s local %s peer %s", iproute_path,
+                iface, local_str, remote_str);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip addr add failed");
+
+    free(local_str);
+    free(remote_str);
+
+    argv_reset(&argv);
+
+    return 0;
+}
+
+int
+net_addr_ptp_v4_del(openvpn_net_ctx_t *ctx, const char *iface,
+                    const in_addr_t *local, const in_addr_t *remote)
+{
+    struct argv argv = argv_new();
+    char *local_str = (char *)print_in_addr_t(*local, 0, NULL);
+    char *remote_str = (char *)print_in_addr_t(*remote, 0, NULL);
+
+    argv_printf(&argv, "%s addr del dev %s local %s peer %s", iproute_path,
+                iface, local_str, remote_str);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "Linux ip addr del failed");
+
+    free(local_str);
+    free(remote_str);
+
+    argv_reset(&argv);
+
+    return 0;
+}
+
+int
+net_route_v4_add(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen,
+                 const in_addr_t *gw, const char *iface, uint32_t table,
+                 int metric)
+{
+    struct argv argv = argv_new();
+    char *dst_str = (char *)print_in_addr_t(*dst, 0, NULL);
+
+    argv_printf(&argv, "%s route add %s/%d", iproute_path, dst_str, prefixlen);
+
+    if (metric > 0)
+        argv_printf_cat(&argv, "metric %d", metric);
+
+    if (iface)
+        argv_printf_cat(&argv, "dev %s", iface);
+
+    if (gw)
+    {
+        char *gw_str = (char *)print_in_addr_t(*gw, 0, NULL);
+
+        argv_printf_cat(&argv, "via %s", gw_str);
+
+        free(gw_str);
+    }
+
+    argv_msg(D_ROUTE, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "ERROR: Linux route add command failed");
+
+    free(dst_str);
+
+    argv_reset(&argv);
+
+    return 0;
+}
+
+int
+net_route_v6_add(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+                 int prefixlen, const struct in6_addr *gw, const char *iface,
+                 uint32_t table, int metric)
+{
+    struct argv argv = argv_new();
+    char *dst_str = (char *)print_in6_addr(*dst, 0, NULL);
+
+    argv_printf(&argv, "%s -6 route add %s/%d dev %s", iproute_path, dst_str,
+                prefixlen, iface);
+
+    if (gw)
+    {
+        char *gw_str = (char *)print_in6_addr(*gw, 0, NULL);
+
+        argv_printf_cat(&argv, "via %s", gw_str);
+
+        free(gw_str);
+    }
+
+    if (metric > 0)
+        argv_printf_cat(&argv, "metric %d", metric);
+
+    argv_msg(D_ROUTE, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "ERROR: Linux route -6 add command failed");
+
+    free(dst_str);
+
+    argv_reset(&argv);
+
+    return 0;
+}
+
+int
+net_route_v4_del(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen,
+                 const in_addr_t *gw, const char *iface, uint32_t table,
+                 int metric)
+{
+    struct argv argv = argv_new();
+    char *dst_str = (char *)print_in_addr_t(*dst, 0, NULL);
+
+    argv_printf(&argv, "%s route del %s/%d", iproute_path, dst_str, prefixlen);
+
+    if (metric > 0)
+        argv_printf_cat(&argv, "metric %d", metric);
+
+    argv_msg(D_ROUTE, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "ERROR: Linux route delete command failed");
+
+    free(dst_str);
+
+    argv_reset(&argv);
+
+    return 0;
+}
+
+int
+net_route_v6_del(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+                 int prefixlen, const struct in6_addr *gw, const char *iface,
+                 uint32_t table, int metric)
+{
+    struct argv argv = argv_new();
+    char *dst_str = (char *)print_in6_addr(*dst, 0, NULL);
+
+    argv_printf(&argv, "%s -6 route del %s/%d dev %s", iproute_path, dst_str,
+                prefixlen, iface);
+
+    if (gw)
+    {
+        char *gw_str = (char *)print_in6_addr(*gw, 0, NULL);
+
+        argv_printf_cat(&argv, "via %s", gw_str);
+
+        free(gw_str);
+    }
+
+    if (metric > 0)
+        argv_printf_cat(&argv, "metric %d", metric);
+
+    argv_msg(D_ROUTE, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "ERROR: Linux route -6 del command failed");
+
+    free(dst_str);
+
+    argv_reset(&argv);
+
+    return 0;
+}
+
+int
+net_route_v4_best_gw(openvpn_net_ctx_t *ctx, const in_addr_t *dst,
+                     int prefixlen, in_addr_t *best_gw, char *best_iface)
+{
+    best_iface[0] = '\0';
+
+    FILE *fp = fopen("/proc/net/route", "r");
+    if (!fp)
+        return -1;
+
+    char line[256];
+    int count = 0;
+    unsigned int lowest_metric = UINT_MAX;
+    while (fgets(line, sizeof(line), fp) != NULL)
+    {
+        if (count)
+        {
+            unsigned int net_x = 0;
+            unsigned int mask_x = 0;
+            unsigned int gw_x = 0;
+            unsigned int metric = 0;
+            unsigned int flags = 0;
+            char name[16];
+            name[0] = '\0';
+
+            const int np = sscanf(line, "%15s\t%x\t%x\t%x\t%*s\t%*s\t%d\t%x",
+                                  name, &net_x, &gw_x, &flags, &metric,
+                                  &mask_x);
+
+            if (np == 6 && (flags & IFF_UP))
+            {
+                const in_addr_t net = ntohl(net_x);
+                const in_addr_t mask = ntohl(mask_x);
+                const in_addr_t gw = ntohl(gw_x);
+
+                if (!net && !mask && metric < lowest_metric)
+                {
+                    *best_gw = gw;
+                    strcpy(best_iface, name);
+                    lowest_metric = metric;
+                }
+            }
+        }
+        ++count;
+    }
+    fclose(fp);
+
+    return 0;
+}
+
+/*
+ * The following function is not implemented in the iproute backend as it
+ * already uses netlink in route.c.
+ *
+ * int
+ * net_route_v6_best_gw(const struct in6_addr *dst, int prefixlen,
+ *                      struct in6_addr *best_gw, char *best_iface)
+ */
+
+#endif /* ENABLE_IPROUTE && TARGET_LINUX */
diff --git a/src/openvpn/networking_ip.h b/src/openvpn/networking_ip.h
new file mode 100644
index 00000000..47b50a9f
--- /dev/null
+++ b/src/openvpn/networking_ip.h
@@ -0,0 +1,36 @@ 
+/*
+ *  Generic interface to platform specific networking code
+ *
+ *  Copyright (C) 2016-2018 Antonio Quartulli <a@unstable.cc>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program (see the file COPYING included with this
+ *  distribution); if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef NETWORKING_IP_H_
+#define NETWORKING_IP_H_
+
+#include "env_set.h"
+
+typedef char openvpn_net_iface_t;
+
+struct openvpn_net_ctx
+{
+    struct env_set *es;
+};
+
+typedef struct openvpn_net_ctx openvpn_net_ctx_t;
+
+#endif /* NETWORKING_IP_H_ */