[Openvpn-devel,L] Change in openvpn[master]: Support CIDR on options and extend netbits usage

Message ID 36ae7618c415a2084827364fe4cf304b42c7987f-HTML@gerrit.openvpn.net
State New
Headers show
Series [Openvpn-devel,L] Change in openvpn[master]: Support CIDR on options and extend netbits usage | expand

Commit Message

plaisthos (Code Review) Sept. 9, 2024, 3:12 p.m. UTC
Attention is currently required from: flichtenheld, plaisthos.

Hello plaisthos, flichtenheld,

I'd like you to do a code review.
Please visit

    http://gerrit.openvpn.net/c/openvpn/+/739?usp=email

to review the following change.


Change subject: Support CIDR on options and extend netbits usage
......................................................................

Support CIDR on options and extend netbits usage

Add support for CIDR notation on all suitable options (client-nat,
ifconfig, ifconfig-pull, ifconfig-push, ifconfig-push-constraint,
iroute, route, server, server-bridge). Anyway push of ifconfig is done
without CIDR in order to support older peers. Also netmask env vars have
been preserved while also introducing the corresponding netbits vars.

Some of the internal logic has also been modified to use netbits instead
of netmasks.

Change-Id: Iae04ad8715e40dfc76475c2c5b9a766c9604efc9
Signed-off-by: Ralf Lici <ralf@mandelbit.com>
---
M src/openvpn/basic.h
M src/openvpn/clinat.c
M src/openvpn/clinat.h
M src/openvpn/helper.c
M src/openvpn/init.c
M src/openvpn/manage.c
M src/openvpn/multi.c
M src/openvpn/options.c
M src/openvpn/options.h
M src/openvpn/pool.c
M src/openvpn/push.c
M src/openvpn/route.c
M src/openvpn/route.h
M src/openvpn/socket.c
M src/openvpn/socket.h
M src/openvpn/tun.c
M src/openvpn/tun.h
17 files changed, 521 insertions(+), 284 deletions(-)



  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/39/739/1

Patch

diff --git a/src/openvpn/basic.h b/src/openvpn/basic.h
index d1075b2..6658008 100644
--- a/src/openvpn/basic.h
+++ b/src/openvpn/basic.h
@@ -33,5 +33,6 @@ 
 #define CLEAR(x) memset(&(x), 0, sizeof(x))
 
 #define IPV4_NETMASK_HOST 0xffffffffU
+#define IPV4_NETBITS_HOST 32U
 
 #endif
diff --git a/src/openvpn/clinat.c b/src/openvpn/clinat.c
index 2d3b359..eaf5abb 100644
--- a/src/openvpn/clinat.c
+++ b/src/openvpn/clinat.c
@@ -31,6 +31,7 @@ 
 #include "proto.h"
 #include "socket.h"
 #include "memdbg.h"
+#include "route.h"
 
 static bool
 add_entry(struct client_nat_option_list *dest,
@@ -60,11 +61,11 @@ 
         for (i = 0; i < list->n; ++i)
         {
             const struct client_nat_entry *e = &list->entries[i];
-            msg(msglevel, "  CNAT[%d] t=%d %s/%s/%s",
+            msg(msglevel, "  CNAT[%d] t=%d %s/%d %s",
                 i,
                 e->type,
                 print_in_addr_t(e->network, IA_NET_ORDER, &gc),
-                print_in_addr_t(e->netmask, IA_NET_ORDER, &gc),
+                e->netbits,
                 print_in_addr_t(e->foreign_network, IA_NET_ORDER, &gc));
         }
     }
@@ -111,6 +112,7 @@ 
                               int msglevel)
 {
     struct client_nat_entry e;
+    unsigned int netbits = 0;
     bool ok;
 
     if (!strcmp(type, "snat"))
@@ -127,19 +129,35 @@ 
         return;
     }
 
-    e.network = getaddr(0, network, 0, &ok, NULL);
+    e.network = getaddr(0, network, &netbits, 0, &ok, NULL);
     if (!ok)
     {
         msg(msglevel, "client-nat: bad network: %s", network);
         return;
     }
-    e.netmask = getaddr(0, netmask, 0, &ok, NULL);
-    if (!ok)
+    if (netmask)
     {
-        msg(msglevel, "client-nat: bad netmask: %s", netmask);
+        const in_addr_t mask = getaddr(0, netmask, NULL, 0, &ok, NULL);
+        const int bits = netmask_to_netbits2(mask);
+
+        if (!ok || bits <= 0)
+        {
+            msg(msglevel, "client-nat: bad netmask: %s", netmask);
+            return;
+        }
+        msg(M_WARN, "client-nat network parameter can also be specified as a CIDR address instead of using the separate netmask parameter");
+        e.netbits = bits;
+    }
+    else if (netbits > 0)
+    {
+        e.netbits = netbits;
+    }
+    else
+    {
+        msg(msglevel, "client-nat: netmask or bits must be specified");
         return;
     }
-    e.foreign_network = getaddr(0, foreign_network, 0, &ok, NULL);
+    e.foreign_network = getaddr(0, foreign_network, NULL, 0, &ok, NULL);
     if (!ok)
     {
         msg(msglevel, "client-nat: bad foreign network: %s", foreign_network);
@@ -210,6 +228,7 @@ 
     for (i = 0; i < list->n; ++i)
     {
         const struct client_nat_entry *e = &list->entries[i]; /* current NAT rule */
+        const in_addr_t netmask = netbits_to_netmask((int)e->netbits);
         if (e->type ^ direction)
         {
             addr = *(addr_ptr = &h->ip.daddr);
@@ -231,13 +250,13 @@ 
             to = &e->foreign_network;
         }
 
-        if (((addr & e->netmask) == *from) && !(amask & alog))
+        if (((addr & netmask) == *from) && !(amask & alog))
         {
             /* pre-adjust IP checksum */
             ADD_CHECKSUM_32(accumulate, addr);
 
             /* do NAT transform */
-            addr = (addr & ~e->netmask) | *to;
+            addr = (addr & ~netmask) | *to;
 
             /* post-adjust IP checksum */
             SUB_CHECKSUM_32(accumulate, addr);
diff --git a/src/openvpn/clinat.h b/src/openvpn/clinat.h
index 94141f5..bebd012 100644
--- a/src/openvpn/clinat.h
+++ b/src/openvpn/clinat.h
@@ -36,7 +36,7 @@ 
 #define CN_DNAT 1
     int type;
     in_addr_t network;
-    in_addr_t netmask;
+    unsigned int netbits;
     in_addr_t foreign_network;
 };
 
diff --git a/src/openvpn/helper.c b/src/openvpn/helper.c
index bbdbc04..30f3539 100644
--- a/src/openvpn/helper.c
+++ b/src/openvpn/helper.c
@@ -64,22 +64,21 @@ 
 }
 
 static const char *
-print_opt_route(const in_addr_t network, const in_addr_t netmask, struct gc_arena *gc)
+print_opt_route(const in_addr_t network, const int netbits, struct gc_arena *gc)
 {
     struct buffer out = alloc_buf_gc(128, gc);
     ASSERT(network);
 
-    if (netmask)
-    {
-        buf_printf(&out, "route %s %s",
-                   print_in_addr_t(network, 0, gc),
-                   print_in_addr_t(netmask, 0, gc));
-    }
-    else
+    if (netbits < 0)
     {
         buf_printf(&out, "route %s",
                    print_in_addr_t(network, 0, gc));
     }
+    else
+    {
+        buf_printf(&out, "route %s/%d",
+                   print_in_addr_t(network, 0, gc), netbits);
+    }
 
     return BSTR(&out);
 }
@@ -111,27 +110,28 @@ 
 }
 
 static void
-helper_add_route(const in_addr_t network, const in_addr_t netmask, struct options *o)
+helper_add_route(const in_addr_t network, const int netbits, struct options *o)
 {
     rol_check_alloc(o);
     add_route_to_option_list(o->routes,
                              print_in_addr_t(network, 0, &o->gc),
-                             print_in_addr_t(netmask, 0, &o->gc),
+                             print_in_addr_t(netbits_to_netmask(netbits), 0, &o->gc),
                              NULL,
                              NULL);
 }
 
 static void
-verify_common_subnet(const char *opt, const in_addr_t a, const in_addr_t b, const in_addr_t subnet)
+verify_common_subnet(const char *opt, const in_addr_t a, const in_addr_t b, const unsigned int netbits)
 {
     struct gc_arena gc = gc_new();
-    if ((a & subnet) != (b & subnet))
+    const unsigned int var = 32 - netbits;
+    if ((a >> var) != (b >> var))
     {
-        msg(M_USAGE, "%s IP addresses %s and %s are not in the same %s subnet",
+        msg(M_USAGE, "%s IP addresses %s and %s are not in the same /%d subnet",
             opt,
             print_in_addr_t(a, 0, &gc),
             print_in_addr_t(b, 0, &gc),
-            print_in_addr_t(subnet, 0, &gc));
+            netbits);
     }
     gc_free(&gc);
 }
@@ -268,9 +268,6 @@ 
 
     if (o->server_defined)
     {
-        int netbits = -2;
-        bool status = false;
-
         if (o->client)
         {
             msg(M_USAGE, "--server and --client cannot be used together");
@@ -296,34 +293,24 @@ 
             msg(M_USAGE, "--server directive only makes sense with --dev tun or --dev tap");
         }
 
-        status = netmask_to_netbits(o->server_network, o->server_netmask, &netbits);
-        if (!status)
-        {
-            msg(M_USAGE, "--server directive network/netmask combination is invalid");
-        }
-
-        if (netbits < 0)
-        {
-            msg(M_USAGE, "--server directive netmask is invalid");
-        }
-
-        if (netbits < IFCONFIG_POOL_MIN_NETBITS)
+        if (o->server_netbits < IFCONFIG_POOL_MIN_NETBITS)
         {
             msg(M_USAGE, "--server directive netmask allows for too many host addresses (subnet must be %s or higher)",
                 print_netmask(IFCONFIG_POOL_MIN_NETBITS, &gc));
         }
 
+        const in_addr_t server_netmask = netbits_to_netmask((int)o->server_netbits);
         if (dev == DEV_TYPE_TUN)
         {
             int pool_end_reserve = 4;
 
-            if (netbits > 29)
+            if (o->server_netbits > 29)
             {
                 msg(M_USAGE, "--server directive when used with --dev tun must define a subnet of %s or lower",
                     print_netmask(29, &gc));
             }
 
-            if (netbits == 29)
+            if (o->server_netbits == 29)
             {
                 pool_end_reserve = 0;
             }
@@ -342,14 +329,14 @@ 
                 {
                     o->ifconfig_pool_defined = true;
                     o->ifconfig_pool_start = o->server_network + 4;
-                    o->ifconfig_pool_end = (o->server_network | ~o->server_netmask) - pool_end_reserve;
+                    o->ifconfig_pool_end = (o->server_network | ~server_netmask) - pool_end_reserve;
                     ifconfig_pool_verify_range(M_USAGE, o->ifconfig_pool_start, o->ifconfig_pool_end);
                 }
 
-                helper_add_route(o->server_network, o->server_netmask, o);
+                helper_add_route(o->server_network, o->server_netbits, o);
                 if (o->enable_c2c)
                 {
-                    push_option(o, print_opt_route(o->server_network, o->server_netmask, &o->gc), M_USAGE);
+                    push_option(o, print_opt_route(o->server_network, o->server_netbits, &o->gc), M_USAGE);
                 }
                 else if (o->topology == TOP_NET30)
                 {
@@ -359,16 +346,16 @@ 
             else if (o->topology == TOP_SUBNET)
             {
                 o->ifconfig_local = print_in_addr_t(o->server_network + 1, 0, &o->gc);
-                o->ifconfig_remote_netmask = print_in_addr_t(o->server_netmask, 0, &o->gc);
+                o->ifconfig_remote_netmask = print_in_addr_t(server_netmask, 0, &o->gc);
 
                 if (!(o->server_flags & SF_NOPOOL))
                 {
                     o->ifconfig_pool_defined = true;
                     o->ifconfig_pool_start = o->server_network + 2;
-                    o->ifconfig_pool_end = (o->server_network | ~o->server_netmask) - 1;
+                    o->ifconfig_pool_end = (o->server_network | ~server_netmask) - 1;
                     ifconfig_pool_verify_range(M_USAGE, o->ifconfig_pool_start, o->ifconfig_pool_end);
                 }
-                o->ifconfig_pool_netmask = o->server_netmask;
+                o->ifconfig_pool_netbits = o->server_netbits;
 
                 push_option(o, print_opt_route_gateway(o->server_network + 1, &o->gc), M_USAGE);
                 if (!o->route_default_gateway)
@@ -393,7 +380,7 @@ 
         }
         else if (dev == DEV_TYPE_TAP)
         {
-            if (netbits > 30)
+            if (o->server_netbits > 30)
             {
                 msg(M_USAGE, "--server directive when used with --dev tap must define a subnet of %s or lower",
                     print_netmask(30, &gc));
@@ -402,16 +389,16 @@ 
             o->mode = MODE_SERVER;
             o->tls_server = true;
             o->ifconfig_local = print_in_addr_t(o->server_network + 1, 0, &o->gc);
-            o->ifconfig_remote_netmask = print_in_addr_t(o->server_netmask, 0, &o->gc);
+            o->ifconfig_remote_netmask = print_in_addr_t(server_netmask, 0, &o->gc);
 
             if (!(o->server_flags & SF_NOPOOL))
             {
                 o->ifconfig_pool_defined = true;
                 o->ifconfig_pool_start = o->server_network + 2;
-                o->ifconfig_pool_end = (o->server_network | ~o->server_netmask) - 1;
+                o->ifconfig_pool_end = (o->server_network | ~server_netmask) - 1;
                 ifconfig_pool_verify_range(M_USAGE, o->ifconfig_pool_start, o->ifconfig_pool_end);
             }
-            o->ifconfig_pool_netmask = o->server_netmask;
+            o->ifconfig_pool_netbits = o->server_netbits;
 
             push_option(o, print_opt_route_gateway(o->server_network + 1, &o->gc), M_USAGE);
         }
@@ -425,7 +412,7 @@ 
         {
             o->push_ifconfig_constraint_defined = true;
             o->push_ifconfig_constraint_network = o->server_network;
-            o->push_ifconfig_constraint_netmask = o->server_netmask;
+            o->push_ifconfig_constraint_netbits = o->server_netbits;
         }
     }
 
@@ -478,9 +465,9 @@ 
 
         if (o->server_bridge_defined)
         {
-            verify_common_subnet("--server-bridge", o->server_bridge_ip, o->server_bridge_pool_start, o->server_bridge_netmask);
-            verify_common_subnet("--server-bridge", o->server_bridge_pool_start, o->server_bridge_pool_end, o->server_bridge_netmask);
-            verify_common_subnet("--server-bridge", o->server_bridge_ip, o->server_bridge_pool_end, o->server_bridge_netmask);
+            verify_common_subnet("--server-bridge", o->server_bridge_ip, o->server_bridge_pool_start, o->server_bridge_netbits);
+            verify_common_subnet("--server-bridge", o->server_bridge_pool_start, o->server_bridge_pool_end, o->server_bridge_netbits);
+            verify_common_subnet("--server-bridge", o->server_bridge_ip, o->server_bridge_pool_end, o->server_bridge_netbits);
         }
 
         o->mode = MODE_SERVER;
@@ -492,7 +479,7 @@ 
             o->ifconfig_pool_start = o->server_bridge_pool_start;
             o->ifconfig_pool_end = o->server_bridge_pool_end;
             ifconfig_pool_verify_range(M_USAGE, o->ifconfig_pool_start, o->ifconfig_pool_end);
-            o->ifconfig_pool_netmask = o->server_bridge_netmask;
+            o->ifconfig_pool_netbits = o->server_bridge_netbits;
             push_option(o, print_opt_route_gateway(o->server_bridge_ip, &o->gc), M_USAGE);
         }
         else if (o->server_bridge_proxy_dhcp && !(o->server_flags & SF_NO_PUSH_ROUTE_GATEWAY))
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index dd56961..7f72f8d 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -115,6 +115,7 @@ 
             const char *dev_type,
             int tun_mtu,
             const char *ifconfig_local,
+            const int ifconfig_netbits,
             const char *ifconfig_remote,
             const char *context,
             const char *signal_text,
@@ -144,7 +145,14 @@ 
     }
     if (!ifconfig_remote)
     {
-        ifconfig_remote = "";
+        if (ifconfig_netbits > 0)
+        {
+            ifconfig_remote = print_in_addr_t(netbits_to_netmask(ifconfig_netbits), 0, &gc);
+        }
+        else
+        {
+            ifconfig_remote = "";
+        }
     }
     if (!context)
     {
@@ -1948,7 +1956,8 @@ 
                     dev_type_string(c->options.dev, c->options.dev_type),
                     c->c2.frame.tun_mtu,
                     print_in_addr_t(c->c1.tuntap->local, IA_EMPTY_IF_UNDEF, &gc),
-                    print_in_addr_t(c->c1.tuntap->remote_netmask, IA_EMPTY_IF_UNDEF, &gc),
+                    c->c1.tuntap->netbits,
+                    print_in_addr_t(c->c1.tuntap->remote, IA_EMPTY_IF_UNDEF, &gc),
                     "init",
                     NULL,
                     "up",
@@ -1988,7 +1997,8 @@ 
                         dev_type_string(c->options.dev, c->options.dev_type),
                         c->c2.frame.tun_mtu,
                         print_in_addr_t(c->c1.tuntap->local, IA_EMPTY_IF_UNDEF, &gc),
-                        print_in_addr_t(c->c1.tuntap->remote_netmask, IA_EMPTY_IF_UNDEF, &gc),
+                        c->c1.tuntap->netbits,
+                        print_in_addr_t(c->c1.tuntap->remote, IA_EMPTY_IF_UNDEF, &gc),
                         "restart",
                         NULL,
                         "up",
@@ -2045,7 +2055,8 @@ 
     struct gc_arena gc = gc_new();
     const char *tuntap_actual = string_alloc(c->c1.tuntap->actual_name, &gc);
     const in_addr_t local = c->c1.tuntap->local;
-    const in_addr_t remote_netmask = c->c1.tuntap->remote_netmask;
+    const int netbits = c->c1.tuntap->netbits;
+    const in_addr_t remote = c->c1.tuntap->remote;
     unsigned long adapter_index = 0;
 #ifdef _WIN32
     adapter_index = c->c1.tuntap->adapter_index;
@@ -2077,7 +2088,8 @@ 
                         NULL,
                         c->c2.frame.tun_mtu,
                         print_in_addr_t(local, IA_EMPTY_IF_UNDEF, &gc),
-                        print_in_addr_t(remote_netmask, IA_EMPTY_IF_UNDEF, &gc),
+                        netbits,
+                        print_in_addr_t(remote, IA_EMPTY_IF_UNDEF, &gc),
                         "init",
                         signal_description(c->sig->signal_received,
                                            c->sig->signal_text),
@@ -2107,7 +2119,8 @@ 
                     NULL,
                     c->c2.frame.tun_mtu,
                     print_in_addr_t(local, IA_EMPTY_IF_UNDEF, &gc),
-                    print_in_addr_t(remote_netmask, IA_EMPTY_IF_UNDEF, &gc),
+                    netbits,
+                    print_in_addr_t(remote, IA_EMPTY_IF_UNDEF, &gc),
                     "init",
                     signal_description(c->sig->signal_received,
                                        c->sig->signal_text),
@@ -2137,7 +2150,8 @@ 
                         NULL,
                         c->c2.frame.tun_mtu,
                         print_in_addr_t(local, IA_EMPTY_IF_UNDEF, &gc),
-                        print_in_addr_t(remote_netmask, IA_EMPTY_IF_UNDEF, &gc),
+                        netbits,
+                        print_in_addr_t(remote, IA_EMPTY_IF_UNDEF, &gc),
                         "restart",
                         signal_description(c->sig->signal_received,
                                            c->sig->signal_text),
diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c
index 05b5a1a..a88ee5f 100644
--- a/src/openvpn/manage.c
+++ b/src/openvpn/manage.c
@@ -554,7 +554,7 @@ 
         {
             /* IP:port specified */
             bool status;
-            const in_addr_t addr = getaddr(GETADDR_HOST_ORDER|GETADDR_MSG_VIRT_OUT, p1, 0, &status, NULL);
+            const in_addr_t addr = getaddr(GETADDR_HOST_ORDER|GETADDR_MSG_VIRT_OUT, p1, NULL, 0, &status, NULL);
             if (status)
             {
                 const int port = atoi(p2);
@@ -2803,6 +2803,7 @@ 
         "untrusted_ip=",
         "ifconfig_local=",
         "ifconfig_netmask=",
+        "ifconfig_netbits=",
         "daemon_start_time=",
         "daemon_pid=",
         "dev=",
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 0509911..aba1a0f 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -1433,7 +1433,8 @@ 
     const struct options *o = &c->options;
     if (o->push_ifconfig_constraint_defined && c->c2.push_ifconfig_defined)
     {
-        return (o->push_ifconfig_constraint_netmask & c->c2.push_ifconfig_local) == o->push_ifconfig_constraint_network;
+        const unsigned int var = 32 - o->push_ifconfig_constraint_netbits;
+        return (c->c2.push_ifconfig_local >> var) == (o->push_ifconfig_constraint_network >> var);
     }
     else
     {
@@ -1512,10 +1513,10 @@ 
                 mi->context.c2.push_ifconfig_local = remote;
                 if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET))
                 {
-                    mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.ifconfig_pool_netmask;
+                    mi->context.c2.push_ifconfig_remote_netmask = netbits_to_netmask((int)mi->context.options.ifconfig_pool_netbits);
                     if (!mi->context.c2.push_ifconfig_remote_netmask)
                     {
-                        mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->remote_netmask;
+                        mi->context.c2.push_ifconfig_remote_netmask = netbits_to_netmask((int)mi->context.c1.tuntap->netbits);
                     }
                 }
                 else if (tunnel_type == DEV_TYPE_TUN)
@@ -2425,15 +2426,14 @@ 
     {
         const char *ifconfig_constraint_network =
             print_in_addr_t(mi->context.options.push_ifconfig_constraint_network, 0, &gc);
-        const char *ifconfig_constraint_netmask =
-            print_in_addr_t(mi->context.options.push_ifconfig_constraint_netmask, 0, &gc);
 
         /* JYFIXME -- this should cause the connection to fail */
         msg(D_MULTI_ERRORS, "MULTI ERROR: primary virtual IP for %s (%s) "
-            "violates tunnel network/netmask constraint (%s/%s)",
+            "violates tunnel network/subnet constraint (%s/%d)",
             multi_instance_string(mi, false, &gc),
             print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc),
-            ifconfig_constraint_network, ifconfig_constraint_netmask);
+            ifconfig_constraint_network,
+            mi->context.options.push_ifconfig_constraint_netbits);
     }
 
     /*
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 649f48b..84e9db4 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -1075,7 +1075,7 @@ 
 #endif /* ifndef _WIN32 */
 
 static in_addr_t
-get_ip_addr(const char *ip_string, int msglevel, bool *error)
+get_ip_addr(const char *ip_string, unsigned int *netbits, int msglevel, bool *error)
 {
     unsigned int flags = GETADDR_HOST_ORDER;
     bool succeeded = false;
@@ -1086,7 +1086,7 @@ 
         flags |= GETADDR_FATAL;
     }
 
-    ret = getaddr(flags, ip_string, 0, &succeeded, NULL);
+    ret = getaddr(flags, ip_string, netbits, 0, &succeeded, NULL);
     if (!succeeded && error)
     {
         *error = true;
@@ -1333,7 +1333,7 @@ 
         if (ip_addr_dotted_quad_safe(parm)) /* FQDN -- IP address only */
         {
             bool error = false;
-            const in_addr_t addr = get_ip_addr(parm, msglevel, &error);
+            const in_addr_t addr = get_ip_addr(parm, NULL, msglevel, &error);
             if (!error)
             {
                 array[(*len)++] = addr;
@@ -1513,12 +1513,10 @@ 
 {
     struct gc_arena gc = gc_new();
 
-    msg(D_SHOW_PARMS, "  server_network = %s", print_in_addr_t(o->server_network, 0, &gc));
-    msg(D_SHOW_PARMS, "  server_netmask = %s", print_in_addr_t(o->server_netmask, 0, &gc));
+    msg(D_SHOW_PARMS, "  server_network = %s/%d", print_in_addr_t(o->server_network, 0, &gc), o->server_netbits);
     msg(D_SHOW_PARMS, "  server_network_ipv6 = %s", print_in6_addr(o->server_network_ipv6, 0, &gc) );
     SHOW_INT(server_netbits_ipv6);
-    msg(D_SHOW_PARMS, "  server_bridge_ip = %s", print_in_addr_t(o->server_bridge_ip, 0, &gc));
-    msg(D_SHOW_PARMS, "  server_bridge_netmask = %s", print_in_addr_t(o->server_bridge_netmask, 0, &gc));
+    msg(D_SHOW_PARMS, "  server_bridge_ip = %s/%d", print_in_addr_t(o->server_bridge_ip, 0, &gc), o->server_bridge_netbits);
     msg(D_SHOW_PARMS, "  server_bridge_pool_start = %s", print_in_addr_t(o->server_bridge_pool_start, 0, &gc));
     msg(D_SHOW_PARMS, "  server_bridge_pool_end = %s", print_in_addr_t(o->server_bridge_pool_end, 0, &gc));
     if (o->push_list.head)
@@ -1534,9 +1532,8 @@ 
         }
     }
     SHOW_BOOL(ifconfig_pool_defined);
-    msg(D_SHOW_PARMS, "  ifconfig_pool_start = %s", print_in_addr_t(o->ifconfig_pool_start, 0, &gc));
+    msg(D_SHOW_PARMS, "  ifconfig_pool_start = %s/%d", print_in_addr_t(o->ifconfig_pool_start, 0, &gc), o->ifconfig_pool_netbits);
     msg(D_SHOW_PARMS, "  ifconfig_pool_end = %s", print_in_addr_t(o->ifconfig_pool_end, 0, &gc));
-    msg(D_SHOW_PARMS, "  ifconfig_pool_netmask = %s", print_in_addr_t(o->ifconfig_pool_netmask, 0, &gc));
     SHOW_STR(ifconfig_pool_persist_filename);
     SHOW_INT(ifconfig_pool_persist_refresh_freq);
     SHOW_BOOL(ifconfig_ipv6_pool_defined);
@@ -1597,14 +1594,15 @@ 
               int msglevel)
 {
     struct iroute *ir;
+    unsigned int netbits = 0;
 
     ALLOC_OBJ_GC(ir, struct iroute, &o->gc);
-    ir->network = getaddr(GETADDR_HOST_ORDER, network_str, 0, NULL, NULL);
-    ir->netbits = 32;           /* host route if no netmask given */
+    ir->network = getaddr(GETADDR_HOST_ORDER, network_str, &netbits, 0, NULL, NULL);
+    ir->netbits = 32;           /* host route if no netmask or netbits given */
 
     if (netmask_str)
     {
-        const in_addr_t netmask = getaddr(GETADDR_HOST_ORDER, netmask_str, 0, NULL, NULL);
+        const in_addr_t netmask = getaddr(GETADDR_HOST_ORDER, netmask_str, NULL, 0, NULL, NULL);
         ir->netbits = netmask_to_netbits2(netmask);
 
         if (ir->netbits < 0)
@@ -1614,6 +1612,11 @@ 
                 netmask_str);
             return;
         }
+        msg(M_WARN, "iroute network parameter can also be specified as a CIDR address instead of using the separate netmask parameter");
+    }
+    else if (netbits)
+    {
+        ir->netbits = (int)netbits;
     }
 
     ir->next = o->iroutes;
@@ -2631,9 +2634,9 @@ 
         {
             msg(M_USAGE, "--connect-freq only works with --mode server --proto udp.  Try --max-clients instead.");
         }
-        if (!(dev == DEV_TYPE_TAP || (dev == DEV_TYPE_TUN && options->topology == TOP_SUBNET)) && options->ifconfig_pool_netmask)
+        if (!(dev == DEV_TYPE_TAP || (dev == DEV_TYPE_TUN && options->topology == TOP_SUBNET)) && options->ifconfig_pool_netbits > 0)
         {
-            msg(M_USAGE, "The third parameter to --ifconfig-pool (netmask) is only valid in --dev tap mode");
+            msg(M_USAGE, "The netbits parameter to --ifconfig-pool is only valid in --dev tap mode");
         }
         if (options->routes && (options->routes->flags & RG_ENABLE))
         {
@@ -6092,11 +6095,29 @@ 
         iproute_path = p[1];
     }
 #endif
-    else if (streq(p[0], "ifconfig") && p[1] && p[2] && !p[3])
+    else if (streq(p[0], "ifconfig") && p[1] && !p[3])
     {
+        bool error = false;
+
         VERIFY_PERMISSION(OPT_P_UP);
-        if (ip_or_dns_addr_safe(p[1], options->allow_pull_fqdn) && ip_or_dns_addr_safe(p[2], options->allow_pull_fqdn)) /* FQDN -- may be DNS name */
+        if (!p[2]) /* p[1] must be in CIDR format */
         {
+            unsigned int netbits = 0;
+            get_ip_addr(p[1], &netbits, msglevel, &error);
+            if (error || !netbits)
+            {
+                msg(msglevel, "ifconfig parm '%s' must be a valid address/bits", p[1]);
+                goto err;
+            }
+            options->ifconfig_local = strtok(p[1], "/");
+            /* convert netbits to netmask to unify the storage as string
+             * representation of in_addr_t variables and simplify parsing */
+            options->ifconfig_remote_netmask = print_in_addr_t(netbits_to_netmask((int)netbits), 0, &options->gc);
+        }
+        else if (ip_or_dns_addr_safe(p[1], options->allow_pull_fqdn) && ip_or_dns_addr_safe(p[2], options->allow_pull_fqdn)) /* FQDN -- may be DNS name */
+        {
+            /* don't suggest CIDR usage here since we don't know if it's a
+             * remote address or a netmask */
             options->ifconfig_local = p[1];
             options->ifconfig_remote_netmask = p[2];
         }
@@ -6999,35 +7020,62 @@ 
         VERIFY_PERMISSION(OPT_P_PERSIST_IP);
         options->persist_remote_ip = true;
     }
-    else if (streq(p[0], "client-nat") && p[1] && p[2] && p[3] && p[4] && !p[5])
+    else if (streq(p[0], "client-nat") && p[1] && p[2] && p[3] && !p[5])
     {
+        size_t i = 1;
+        const char *type = p[i++];
+        const char *network = p[i++];
+        const char *netmask = p[i+1] ? p[i++] : NULL;
+        const char *foreign_network = p[i];
+
         VERIFY_PERMISSION(OPT_P_ROUTE);
         cnol_check_alloc(options);
-        add_client_nat_to_option_list(options->client_nat, p[1], p[2], p[3], p[4], msglevel);
+        add_client_nat_to_option_list(options->client_nat, type, network, netmask, foreign_network, msglevel);
     }
     else if (streq(p[0], "route") && p[1] && !p[5])
     {
+        size_t i = 1;
+        char *cidr = strchr(p[i], '/');
+        if (cidr && !no_more_than_n_args(msglevel, p, 4, NM_QUOTE_HINT))
+        {
+            goto err;
+        }
+
         VERIFY_PERMISSION(OPT_P_ROUTE);
         rol_check_alloc(options);
         if (pull_mode)
         {
-            if (!ip_or_dns_addr_safe(p[1], options->allow_pull_fqdn) && !is_special_addr(p[1])) /* FQDN -- may be DNS name */
+
+            const char *network = strtok(p[i++], "/"); /* this modifies p[1] */
+            if (!ip_or_dns_addr_safe(network, options->allow_pull_fqdn) && !is_special_addr(p[1])) /* FQDN -- may be DNS name */
             {
-                msg(msglevel, "route parameter network/IP '%s' must be a valid address", p[1]);
+                msg(msglevel, "route parameter network/IP '%s' must be a valid address or address/bits", p[1]);
                 goto err;
             }
-            if (p[2] && !ip_addr_dotted_quad_safe(p[2])) /* FQDN -- must be IP address */
+            if (!cidr && p[i])
             {
-                msg(msglevel, "route parameter netmask '%s' must be an IP address", p[2]);
-                goto err;
+                if (!ip_addr_dotted_quad_safe(p[i++])) /* FQDN -- must be IP address */
+                {
+                    msg(msglevel, "route parameter netmask '%s' must be an IP address", p[2]);
+                    goto err;
+                }
             }
-            if (p[3] && !ip_or_dns_addr_safe(p[3], options->allow_pull_fqdn) && !is_special_addr(p[3])) /* FQDN -- may be DNS name */
+            if (p[i] && !ip_or_dns_addr_safe(p[i], options->allow_pull_fqdn) && !is_special_addr(p[i])) /* FQDN -- may be DNS name */
             {
-                msg(msglevel, "route parameter gateway '%s' must be a valid address", p[3]);
+                msg(msglevel, "route parameter gateway '%s' must be a valid address", p[i]);
                 goto err;
             }
         }
-        add_route_to_option_list(options->routes, p[1], p[2], p[3], p[4]);
+
+        if (cidr)
+        {
+            *cidr = (char)('/'); /* restore the original CIDR notation for p[1] */
+            add_route_to_option_list(options->routes, p[1], NULL, p[2], p[3]);
+        }
+        else
+        {
+            add_route_to_option_list(options->routes, p[1], p[2], p[3], p[4]);
+        }
     }
     else if (streq(p[0], "route-ipv6") && p[1] && !p[4])
     {
@@ -7348,33 +7396,57 @@ 
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->occ = false;
     }
-    else if (streq(p[0], "server") && p[1] && p[2] && !p[4])
+    else if (streq(p[0], "server") && p[1] && !p[4])
     {
         const int lev = M_WARN;
         bool error = false;
-        in_addr_t network, netmask;
+        in_addr_t network;
+        unsigned int netbits = 0;
+        size_t i = 1;
 
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        network = get_ip_addr(p[1], lev, &error);
-        netmask = get_ip_addr(p[2], lev, &error);
-        if (error || !network || !netmask)
+        network = get_ip_addr(p[i++], &netbits, lev, &error);
+        if (netbits)
+        {
+            if (!no_more_than_n_args(msglevel, p, 3, NM_QUOTE_HINT))
+            {
+                goto err;
+            }
+        }
+        else
+        {
+            const in_addr_t netmask = get_ip_addr(p[i++], NULL, lev, &error);
+            const int nb = netmask_to_netbits2(netmask);
+            if (netmask && nb > 0)
+            {
+                msg(M_WARN, "server network parameter can also be specified as a CIDR address instead of using the separate netmask parameter");
+                netbits = nb;
+            }
+            else
+            {
+                msg(M_USAGE, "--server directive network/netmask combination is invalid");
+                goto err;
+            }
+        }
+
+        if (error || !network || !netbits)
         {
             msg(msglevel, "error parsing --server parameters");
             goto err;
         }
         options->server_defined = true;
         options->server_network = network;
-        options->server_netmask = netmask;
+        options->server_netbits = netbits;
 
-        if (p[3])
+        if (p[i])
         {
-            if (streq(p[3], "nopool"))
+            if (streq(p[i], "nopool"))
             {
                 options->server_flags |= SF_NOPOOL;
             }
             else
             {
-                msg(msglevel, "error parsing --server: %s is not a recognized flag", p[3]);
+                msg(msglevel, "error parsing --server: %s is not a recognized flag", p[i]);
                 goto err;
             }
         }
@@ -7403,25 +7475,43 @@ 
         options->server_network_ipv6 = network;
         options->server_netbits_ipv6 = netbits;
     }
-    else if (streq(p[0], "server-bridge") && p[1] && p[2] && p[3] && p[4] && !p[5])
+    else if (streq(p[0], "server-bridge") && p[1] && p[2] && p[3] && !p[5])
     {
         const int lev = M_WARN;
         bool error = false;
-        in_addr_t ip, netmask, pool_start, pool_end;
+        in_addr_t ip, pool_start, pool_end;
+        unsigned int netbits = 0;
+        size_t i = 1;
 
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        ip = get_ip_addr(p[1], lev, &error);
-        netmask = get_ip_addr(p[2], lev, &error);
-        pool_start = get_ip_addr(p[3], lev, &error);
-        pool_end = get_ip_addr(p[4], lev, &error);
-        if (error || !ip || !netmask || !pool_start || !pool_end)
+        ip = get_ip_addr(p[i++], &netbits, lev, &error);
+        if (netbits)
+        {
+            if (!no_more_than_n_args(msglevel, p, 4, NM_QUOTE_HINT))
+            {
+                goto err;
+            }
+        }
+        else
+        {
+            const in_addr_t netmask = get_ip_addr(p[i++], NULL, lev, &error);
+            const int nb = netmask_to_netbits2(netmask);
+            if (netmask && nb > 0)
+            {
+                msg(M_WARN, "server-bridge gateway parameter can also be specified as a CIDR address instead of using the separate netmask parameter");
+                netbits = nb;
+            }
+        }
+        pool_start = get_ip_addr(p[i++], NULL, lev, &error);
+        pool_end = get_ip_addr(p[i], NULL, lev, &error);
+        if (error || !ip || !netbits || !pool_start || !pool_end)
         {
             msg(msglevel, "error parsing --server-bridge parameters");
             goto err;
         }
         options->server_bridge_defined = true;
         options->server_bridge_ip = ip;
-        options->server_bridge_netmask = netmask;
+        options->server_bridge_netbits = netbits;
         options->server_bridge_pool_start = pool_start;
         options->server_bridge_pool_end = pool_end;
     }
@@ -7456,15 +7546,27 @@ 
     {
         const int lev = M_WARN;
         bool error = false;
-        in_addr_t start, end, netmask = 0;
+        in_addr_t start, end;
+        unsigned int netbits = 0;
 
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        start = get_ip_addr(p[1], lev, &error);
-        end = get_ip_addr(p[2], lev, &error);
-        if (p[3])
+        start = get_ip_addr(p[1], &netbits, lev, &error);
+        end = get_ip_addr(p[2], NULL, lev, &error);
+        if (!netbits && p[3])
         {
-            netmask = get_ip_addr(p[3], lev, &error);
+            const in_addr_t netmask = get_ip_addr(p[3], NULL, lev, NULL);
+            const int nb = netmask_to_netbits2(netmask);
+            if (netmask && nb >= 0)
+            {
+                msg(M_WARN, "ifconfig-pool start-IP parameter can also be specified as a CIDR address instead of using the separate netmask parameter");
+                netbits = nb;
+            }
         }
+        else if (netbits && !no_more_than_n_args(msglevel, p, 3, NM_QUOTE_HINT))
+        {
+            goto err;
+        }
+
         if (error)
         {
             msg(msglevel, "error parsing --ifconfig-pool parameters");
@@ -7478,10 +7580,7 @@ 
         options->ifconfig_pool_defined = true;
         options->ifconfig_pool_start = start;
         options->ifconfig_pool_end = end;
-        if (netmask)
-        {
-            options->ifconfig_pool_netmask = netmask;
-        }
+        options->ifconfig_pool_netbits = netbits;
     }
     else if (streq(p[0], "ifconfig-pool-persist") && p[1] && !p[3])
     {
@@ -7802,45 +7901,77 @@ 
         VERIFY_PERMISSION(OPT_P_INSTANCE);
         option_iroute_ipv6(options, p[1], msglevel);
     }
-    else if (streq(p[0], "ifconfig-push") && p[1] && p[2] && !p[4])
+    else if (streq(p[0], "ifconfig-push") && p[1] && !p[4])
     {
-        in_addr_t local, remote_netmask;
+        in_addr_t local, remote_netmask = 0;
+        unsigned int netbits;
+        size_t i = 1;
 
         VERIFY_PERMISSION(OPT_P_INSTANCE);
-        local = getaddr(GETADDR_HOST_ORDER|GETADDR_RESOLVE, p[1], 0, NULL, NULL);
-        remote_netmask = getaddr(GETADDR_HOST_ORDER|GETADDR_RESOLVE, p[2], 0, NULL, NULL);
+        local = getaddr(GETADDR_HOST_ORDER|GETADDR_RESOLVE, p[i++], &netbits, 0, NULL, NULL);
+        if (netbits) /* convert netbits to netmask */
+        {
+            if (!no_more_than_n_args(msglevel, p, 3, NM_QUOTE_HINT))
+            {
+                goto err;
+            }
+            remote_netmask = netbits_to_netmask((int)netbits);
+        }
+        else if (p[i])
+        {
+            /* don't suggest CIDR usage here since we don't know if it's a
+             * remote address or a netmask */
+            remote_netmask = getaddr(GETADDR_HOST_ORDER|GETADDR_RESOLVE, p[i++], NULL, 0, NULL, NULL);
+        }
+
         if (local && remote_netmask)
         {
             options->push_ifconfig_defined = true;
             options->push_ifconfig_local = local;
             options->push_ifconfig_remote_netmask = remote_netmask;
-            if (p[3])
+            if (p[i])
             {
-                options->push_ifconfig_local_alias = getaddr(GETADDR_HOST_ORDER|GETADDR_RESOLVE, p[3], 0, NULL, NULL);
+                options->push_ifconfig_local_alias = getaddr(GETADDR_HOST_ORDER|GETADDR_RESOLVE, p[i], NULL, 0, NULL, NULL);
             }
         }
         else
         {
-            msg(msglevel, "cannot parse --ifconfig-push addresses");
+            msg(msglevel, "cannot parse --ifconfig-push parameters");
             goto err;
         }
     }
-    else if (streq(p[0], "ifconfig-push-constraint") && p[1] && p[2] && !p[3])
+    else if (streq(p[0], "ifconfig-push-constraint") && p[1] && !p[3])
     {
-        in_addr_t network, netmask;
+        in_addr_t network;
+        unsigned int netbits = 0;
+        size_t i = 1;
 
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        network = getaddr(GETADDR_HOST_ORDER|GETADDR_RESOLVE, p[1], 0, NULL, NULL);
-        netmask = getaddr(GETADDR_HOST_ORDER, p[2], 0, NULL, NULL);
-        if (network && netmask)
+        network = getaddr(GETADDR_HOST_ORDER|GETADDR_RESOLVE, p[i++], &netbits, 0, NULL, NULL);
+        if (!netbits && p[i])
+        {
+            const in_addr_t netmask = getaddr(GETADDR_HOST_ORDER, p[2], NULL, 0, NULL, NULL);
+            const int nb = netmask_to_netbits2(netmask);
+            if (netmask && nb > 0)
+            {
+                netbits = nb;
+                msg(M_WARN, "ifconfig-push-constraint network parameter can also be specified as a CIDR address instead of using the separate netmask parameter");
+            }
+        }
+        else if (netbits && !no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))
+        {
+            goto err;
+        }
+
+        if (network && netbits)
         {
             options->push_ifconfig_constraint_defined = true;
             options->push_ifconfig_constraint_network = network;
-            options->push_ifconfig_constraint_netmask = netmask;
+            options->push_ifconfig_constraint_netbits = netbits;
         }
         else
         {
-            msg(msglevel, "cannot parse --ifconfig-push-constraint addresses");
+            msg(msglevel, "cannot parse --ifconfig-push-constraint parameters");
             goto err;
         }
     }
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index f608cb8..88de24c 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -450,7 +450,7 @@ 
     const char *tmp_dir;
     bool server_defined;
     in_addr_t server_network;
-    in_addr_t server_netmask;
+    unsigned int server_netbits;
     bool server_ipv6_defined;                           /* IPv6 */
     struct in6_addr server_network_ipv6;                /* IPv6 */
     unsigned int server_netbits_ipv6;                   /* IPv6 */
@@ -464,7 +464,7 @@ 
 
     bool server_bridge_defined;
     in_addr_t server_bridge_ip;
-    in_addr_t server_bridge_netmask;
+    unsigned int server_bridge_netbits;
     in_addr_t server_bridge_pool_start;
     in_addr_t server_bridge_pool_end;
 
@@ -472,7 +472,7 @@ 
     bool ifconfig_pool_defined;
     in_addr_t ifconfig_pool_start;
     in_addr_t ifconfig_pool_end;
-    in_addr_t ifconfig_pool_netmask;
+    unsigned int ifconfig_pool_netbits;
     const char *ifconfig_pool_persist_filename;
     int ifconfig_pool_persist_refresh_freq;
 
@@ -499,7 +499,7 @@ 
     in_addr_t push_ifconfig_local_alias;
     bool push_ifconfig_constraint_defined;
     in_addr_t push_ifconfig_constraint_network;
-    in_addr_t push_ifconfig_constraint_netmask;
+    unsigned int push_ifconfig_constraint_netbits;
     bool push_ifconfig_ipv4_blocked;                    /* IPv4 */
     bool push_ifconfig_ipv6_defined;                    /* IPv6 */
     struct in6_addr push_ifconfig_ipv6_local;           /* IPv6 */
diff --git a/src/openvpn/pool.c b/src/openvpn/pool.c
index e3c3708..84227b6 100644
--- a/src/openvpn/pool.c
+++ b/src/openvpn/pool.c
@@ -652,8 +652,8 @@ 
             if (strlen(ip_buf) > 0)
             {
                 bool v4_ok = true;
-                in_addr_t addr = getaddr(GETADDR_HOST_ORDER, ip_buf, 0, &v4_ok,
-                                         NULL);
+                in_addr_t addr = getaddr(GETADDR_HOST_ORDER, ip_buf, NULL, 0,
+                                         &v4_ok, NULL);
 
                 if (!v4_ok)
                 {
diff --git a/src/openvpn/push.c b/src/openvpn/push.c
index 6c06374..b8ab470 100644
--- a/src/openvpn/push.c
+++ b/src/openvpn/push.c
@@ -1136,9 +1136,20 @@ 
                 if (p[0] && !strcmp(p[0], "route") && !p[3] && o->iroutes)
                 {
                     /* get route parameters */
-                    bool status1, status2;
-                    const in_addr_t network = getaddr(GETADDR_HOST_ORDER, p[1], 0, &status1, NULL);
-                    const in_addr_t netmask = getaddr(GETADDR_HOST_ORDER, p[2] ? p[2] : "255.255.255.255", 0, &status2, NULL);
+                    bool status1, status2 = true;
+                    int netbits = -1;
+
+                    /* get netbits from CIDR or convert netmask to netbits */
+                    const in_addr_t network = getaddr(GETADDR_HOST_ORDER, p[1], (unsigned int *)&netbits, 0, &status1, NULL);
+                    if (p[2])
+                    {
+                        const in_addr_t netmask = getaddr(GETADDR_HOST_ORDER, p[2], NULL, 0, &status2, NULL);
+                        netbits = netmask_to_netbits2(netmask);
+                    }
+                    if (netbits <= 0)
+                    {
+                        netbits = 32;
+                    }
 
                     /* did route parameters parse correctly? */
                     if (status1 && status2)
@@ -1148,7 +1159,7 @@ 
                         /* does route match an iroute? */
                         for (ir = o->iroutes; ir != NULL; ir = ir->next)
                         {
-                            if (network == ir->network && netmask == netbits_to_netmask(ir->netbits >= 0 ? ir->netbits : 32))
+                            if (network == ir->network && netbits == (ir->netbits >= 0 ? ir->netbits : 32))
                             {
                                 enable = false;
                                 break;
diff --git a/src/openvpn/route.c b/src/openvpn/route.c
index 91f2032..b743433 100644
--- a/src/openvpn/route.c
+++ b/src/openvpn/route.c
@@ -189,9 +189,9 @@ 
 route_string(const struct route_ipv4 *r, struct gc_arena *gc)
 {
     struct buffer out = alloc_buf_gc(256, gc);
-    buf_printf(&out, "ROUTE network %s netmask %s gateway %s",
+    buf_printf(&out, "ROUTE network %s/%d gateway %s",
                print_in_addr_t(r->network, 0, gc),
-               print_in_addr_t(r->netmask, 0, gc),
+               r->netbits,
                print_in_addr_t(r->gateway, 0, gc)
                );
     if (r->flags & RT_METRIC_DEFINED)
@@ -321,11 +321,17 @@ 
            const struct route_option *ro,
            const struct route_list *rl)
 {
-    const in_addr_t default_netmask = IPV4_NETMASK_HOST;
+    const in_addr_t default_netbits = IPV4_NETBITS_HOST;
     bool status;
     int ret;
     struct in_addr special = {0};
 
+    /* separate address and prefix if present */
+    char *network = strdup(ro->network);
+    const char *cidr = strchr(network, '/');
+    const char *addr = strtok(network, "/");
+    const char *prefix = strtok(NULL, "/");
+
     CLEAR(*r);
     r->option = ro;
 
@@ -355,7 +361,7 @@ 
     else
     {
         ret = openvpn_getaddrinfo(GETADDR_RESOLVE | GETADDR_WARN_ON_SIGNAL,
-                                  ro->network, NULL, 0, NULL, AF_INET, network_list);
+                                  addr, NULL, 0, NULL, AF_INET, network_list);
     }
 
     status = (ret == 0);
@@ -365,25 +371,41 @@ 
         goto fail;
     }
 
-    /* netmask */
+    /* netbits */
 
+    r->netbits = default_netbits;
     if (is_route_parm_defined(ro->netmask))
     {
-        r->netmask = getaddr(
+        const in_addr_t netmask = getaddr(
             GETADDR_HOST_ORDER
             | GETADDR_WARN_ON_SIGNAL,
             ro->netmask,
+            NULL,
             0,
             &status,
             NULL);
-        if (!status)
+        const int netbits = netmask_to_netbits2(netmask);
+
+        if (!status || netbits < 0)
         {
             goto fail;
         }
+        msg(M_WARN, "route parameter network/IP can also be specified as a CIDR address instead of using the separate netmask parameter");
+        r->netbits = netbits;
     }
-    else
+    else if (cidr)
     {
-        r->netmask = default_netmask;
+        if (!prefix) /* handle 'aa.bb.cc.dd/' case */
+        {
+            goto fail;
+        }
+        char *endp = NULL;
+        const unsigned long netbits = strtoul(prefix, &endp, 10);
+        if ((*endp != '\0') || (netbits > sizeof(in_addr_t) * 8))
+        {
+            goto fail;
+        }
+        r->netbits = netbits;
     }
 
     /* gateway */
@@ -397,6 +419,7 @@ 
                 | GETADDR_HOST_ORDER
                 | GETADDR_WARN_ON_SIGNAL,
                 ro->gateway,
+                NULL,
                 0,
                 &status,
                 NULL);
@@ -428,7 +451,7 @@ 
         if (r->metric < 0)
         {
             msg(M_WARN, PACKAGE_NAME " ROUTE: route metric for network %s (%s) must be >= 0",
-                ro->network,
+                addr,
                 ro->metric);
             goto fail;
         }
@@ -442,11 +465,13 @@ 
 
     r->flags |= RT_DEFINED;
 
+    free(network);
     return true;
 
 fail:
     msg(M_WARN, PACKAGE_NAME " ROUTE: failed to parse/resolve route for host/network: %s",
         ro->network);
+    free(network);
     return false;
 }
 
@@ -516,7 +541,7 @@ 
     struct route_option *ro;
     ALLOC_OBJ_GC(ro, struct route_option, l->gc);
     ro->network = network;
-    ro->netmask = netmask;
+    ro->netmask = netmask; /* NULL if network has CIDR notation */
     ro->gateway = gateway;
     ro->metric = metric;
     ro->next = l->routes;
@@ -582,7 +607,7 @@ 
         r1->flags = RT_DEFINED;
         r1->gateway = target;
         r1->network = gateway->addr & gateway->netmask;
-        r1->netmask = ~(l2-1);
+        r1->netbits = netmask_to_netbits2(gateway->netmask) + 1;
         r1->next = rl->routes;
         rl->routes = r1;
 
@@ -681,6 +706,7 @@ 
             | GETADDR_HOST_ORDER
             | GETADDR_WARN_ON_SIGNAL,
             remote_endpoint,
+            NULL,
             0,
             &defined,
             NULL);
@@ -921,7 +947,7 @@ 
 
 static bool
 add_route3(in_addr_t network,
-           in_addr_t netmask,
+           unsigned int netbits,
            in_addr_t gateway,
            const struct tuntap *tt,
            unsigned int flags,
@@ -933,14 +959,14 @@ 
     CLEAR(r);
     r.flags = RT_DEFINED;
     r.network = network;
-    r.netmask = netmask;
+    r.netbits = netbits;
     r.gateway = gateway;
     return add_route(&r, tt, flags, rgi, es, ctx);
 }
 
 static void
 del_route3(in_addr_t network,
-           in_addr_t netmask,
+           unsigned int netbits,
            in_addr_t gateway,
            const struct tuntap *tt,
            unsigned int flags,
@@ -952,7 +978,7 @@ 
     CLEAR(r);
     r.flags = RT_DEFINED|RT_ADDED;
     r.network = network;
-    r.netmask = netmask;
+    r.netbits = netbits;
     r.gateway = gateway;
     delete_route(&r, tt, flags, rgi, es, ctx);
 }
@@ -971,7 +997,7 @@ 
     {
         if (rb->bypass[i])
         {
-            ret = add_route3(rb->bypass[i], IPV4_NETMASK_HOST, gateway, tt,
+            ret = add_route3(rb->bypass[i], IPV4_NETBITS_HOST, gateway, tt,
                              flags | ROUTE_REF_GW, rgi, es, ctx) && ret;
         }
     }
@@ -993,7 +1019,7 @@ 
         if (rb->bypass[i])
         {
             del_route3(rb->bypass[i],
-                       IPV4_NETMASK_HOST,
+                       IPV4_NETBITS_HOST,
                        gateway,
                        tt,
                        flags | ROUTE_REF_GW,
@@ -1057,7 +1083,7 @@ 
                 if ((rl->spec.flags & RTSA_REMOTE_HOST)
                     && rl->spec.remote_host != IPV4_INVALID_ADDR)
                 {
-                    ret = add_route3(rl->spec.remote_host, IPV4_NETMASK_HOST,
+                    ret = add_route3(rl->spec.remote_host, IPV4_NETBITS_HOST,
                                      rl->rgi.gateway.addr, tt, flags | ROUTE_REF_GW,
                                      &rl->rgi, es, ctx);
                     rl->iflags |= RL_DID_LOCAL;
@@ -1078,11 +1104,11 @@ 
                 if (rl->flags & RG_DEF1)
                 {
                     /* add new default route (1st component) */
-                    ret = add_route3(0x00000000, 0x80000000, rl->spec.remote_endpoint,
+                    ret = add_route3(0x00000000, 1, rl->spec.remote_endpoint,
                                      tt, flags, &rl->rgi, es, ctx) && ret;
 
                     /* add new default route (2nd component) */
-                    ret = add_route3(0x80000000, 0x80000000, rl->spec.remote_endpoint,
+                    ret = add_route3(0x80000000, 1, rl->spec.remote_endpoint,
                                      tt, flags, &rl->rgi, es, ctx) && ret;
                 }
                 else
@@ -1120,7 +1146,7 @@ 
         if (rl->iflags & RL_DID_LOCAL)
         {
             del_route3(rl->spec.remote_host,
-                       IPV4_NETMASK_HOST,
+                       IPV4_NETBITS_HOST,
                        rl->rgi.gateway.addr,
                        tt,
                        flags | ROUTE_REF_GW,
@@ -1140,7 +1166,7 @@ 
             {
                 /* delete default route (1st component) */
                 del_route3(0x00000000,
-                           0x80000000,
+                           1,
                            rl->spec.remote_endpoint,
                            tt,
                            flags,
@@ -1150,7 +1176,7 @@ 
 
                 /* delete default route (2nd component) */
                 del_route3(0x80000000,
-                           0x80000000,
+                           1,
                            rl->spec.remote_endpoint,
                            tt,
                            flags,
@@ -1215,7 +1241,7 @@ 
 
         for (r = rl->routes; r; r = r->next)
         {
-            check_subnet_conflict(r->network, r->netmask, "route");
+            check_subnet_conflict(r->network, netbits_to_netmask((int)r->netbits), "route");
             if (flags & ROUTE_DELETE_FIRST)
             {
                 delete_route(r, tt, flags, &rl->rgi, es, ctx);
@@ -1432,14 +1458,18 @@ 
     if (r->flags & RT_DEFINED)
     {
         setenv_route_addr(es, "network", r->network, i);
-        setenv_route_addr(es, "netmask", r->netmask, i);
+        setenv_route_addr(es, "netmask", netbits_to_netmask(r->netbits), i);
         setenv_route_addr(es, "gateway", r->gateway, i);
 
+        struct buffer name1 = alloc_buf_gc(256, &gc);
+        buf_printf(&name1, "route_netbits_%d", i);
+        setenv_int(es, BSTR(&name1), (int)r->netbits);
+
         if (r->flags & RT_METRIC_DEFINED)
         {
-            struct buffer name = alloc_buf_gc(256, &gc);
-            buf_printf(&name, "route_metric_%d", i);
-            setenv_int(es, BSTR(&name), r->metric);
+            struct buffer name2 = alloc_buf_gc(256, &gc);
+            buf_printf(&name2, "route_metric_%d", i);
+            setenv_int(es, BSTR(&name2), r->metric);
         }
     }
     gc_free(&gc);
@@ -1519,7 +1549,7 @@ 
 
 static int
 local_route(in_addr_t network,
-            in_addr_t netmask,
+            unsigned int netbits,
             in_addr_t gateway,
             const struct route_gateway_info *rgi)
 {
@@ -1528,7 +1558,7 @@ 
     if (rgi
         && (rgi->flags & rgi_needed) == rgi_needed
         && gateway == rgi->gateway.addr
-        && netmask == 0xFFFFFFFF)
+        && netbits == IPV4_NETBITS_HOST)
     {
         if (((network ^  rgi->gateway.addr) & rgi->gateway.netmask) == 0)
         {
@@ -1583,12 +1613,12 @@ 
 #if !defined(TARGET_LINUX)
     const char *network = print_in_addr_t(r->network, 0, &gc);
 #if !defined(TARGET_AIX)
-    const char *netmask = print_in_addr_t(r->netmask, 0, &gc);
+    const char *netmask = print_in_addr_t(netbits_to_netmask((int)r->netbits), 0, &gc);
 #endif
     const char *gateway = print_in_addr_t(r->gateway, 0, &gc);
 #endif
 
-    is_local_route = local_route(r->network, r->netmask, r->gateway, rgi);
+    is_local_route = local_route(r->network, r->netbits, r->gateway, rgi);
     if (is_local_route == LR_ERROR)
     {
         goto done;
@@ -1609,7 +1639,7 @@ 
     }
 
     status = RTA_SUCCESS;
-    int ret = net_route_v4_add(ctx, &r->network, netmask_to_netbits2(r->netmask),
+    int ret = net_route_v4_add(ctx, &r->network, (int)r->netbits,
                                &r->gateway, iface, 0, metric);
     if (ret == -EEXIST)
     {
@@ -1840,10 +1870,9 @@ 
 #elif defined(TARGET_AIX)
 
     {
-        int netbits = netmask_to_netbits2(r->netmask);
         argv_printf(&argv, "%s add -net %s/%d %s",
                     ROUTE_PATH,
-                    network, netbits, gateway);
+                    network, r->netbits, gateway);
         argv_msg(D_ROUTE, &argv);
         bool ret = openvpn_execve_check(&argv, es, 0,
                                         "ERROR: AIX route add command failed");
@@ -2176,14 +2205,14 @@ 
 #if !defined(TARGET_LINUX)
     network = print_in_addr_t(r->network, 0, &gc);
 #if !defined(TARGET_AIX)
-    netmask = print_in_addr_t(r->netmask, 0, &gc);
+    netmask = print_in_addr_t(netbits_to_netmask((int)r->netbits), 0, &gc);
 #endif
 #if !defined(TARGET_ANDROID)
     gateway = print_in_addr_t(r->gateway, 0, &gc);
 #endif
 #endif
 
-    is_local_route = local_route(r->network, r->netmask, r->gateway, rgi);
+    is_local_route = local_route(r->network, r->netbits, r->gateway, rgi);
     if (is_local_route == LR_ERROR)
     {
         goto done;
@@ -2196,7 +2225,7 @@ 
         metric = r->metric;
     }
 
-    if (net_route_v4_del(ctx, &r->network, netmask_to_netbits2(r->netmask),
+    if (net_route_v4_del(ctx, &r->network, (int)r->netbits,
                          &r->gateway, NULL, 0, metric) < 0)
     {
         msg(M_WARN, "ERROR: Linux route delete command failed");
@@ -2318,10 +2347,9 @@ 
 #elif defined(TARGET_AIX)
 
     {
-        int netbits = netmask_to_netbits2(r->netmask);
         argv_printf(&argv, "%s delete -net %s/%d %s",
                     ROUTE_PATH,
-                    network, netbits, gateway);
+                    network, r->netbits, gateway);
         argv_msg(D_ROUTE, &argv);
         openvpn_execve_check(&argv, es, 0, "ERROR: AIX route delete command failed");
     }
@@ -2840,13 +2868,14 @@ 
     int ret = RTA_ERROR;
     DWORD status;
     const DWORD if_index = (adapter_index == TUN_ADAPTER_INDEX_INVALID) ? windows_route_find_if_index(r, tt) : adapter_index;
+    const in_addr_t netmask = netbits_to_netmask((int)r->netbits);
 
     if (if_index != TUN_ADAPTER_INDEX_INVALID)
     {
         MIB_IPFORWARDROW fr;
         CLEAR(fr);
         fr.dwForwardDest = htonl(r->network);
-        fr.dwForwardMask = htonl(r->netmask);
+        fr.dwForwardMask = htonl(netmask);
         fr.dwForwardPolicy = 0;
         fr.dwForwardNextHop = htonl(r->gateway);
         fr.dwForwardIfIndex = if_index;
@@ -2860,11 +2889,11 @@ 
         fr.dwForwardMetric4 = METRIC_NOT_USED;
         fr.dwForwardMetric5 = METRIC_NOT_USED;
 
-        if ((r->network & r->netmask) != r->network)
+        if ((r->network & netmask) != r->network)
         {
             msg(M_WARN, "Warning: address %s is not a network address in relation to netmask %s",
                 print_in_addr_t(r->network, 0, &gc),
-                print_in_addr_t(r->netmask, 0, &gc));
+                print_in_addr_t(netmask, 0, &gc));
         }
 
         status = CreateIpForwardEntry(&fr);
@@ -2932,6 +2961,7 @@ 
     bool ret = false;
     DWORD status;
     const DWORD if_index = windows_route_find_if_index(r, tt);
+    const in_addr_t netmask = netbits_to_netmask((int)r->netbits);
 
     if (if_index != TUN_ADAPTER_INDEX_INVALID)
     {
@@ -2939,7 +2969,7 @@ 
         CLEAR(fr);
 
         fr.dwForwardDest = htonl(r->network);
-        fr.dwForwardMask = htonl(r->netmask);
+        fr.dwForwardMask = htonl(netmask);
         fr.dwForwardPolicy = 0;
         fr.dwForwardNextHop = htonl(r->gateway);
         fr.dwForwardIfIndex = if_index;
@@ -3016,7 +3046,7 @@ 
         .metric = (r->flags & RT_METRIC_DEFINED ? r->metric : -1)
     };
 
-    netmask_to_netbits(r->network, r->netmask, &msg.prefix_len);
+    msg.prefix_len = r->netbits;
     if (msg.prefix_len == -1)
     {
         msg.prefix_len = 32;
@@ -3996,7 +4026,7 @@ 
     while (iplist)
     {
         bool succeed = false;
-        const in_addr_t ip = getaddr(GETADDR_HOST_ORDER, iplist->IpAddress.String, 0, &succeed, NULL);
+        const in_addr_t ip = getaddr(GETADDR_HOST_ORDER, iplist->IpAddress.String, NULL, 0, &succeed, NULL);
         if (succeed)
         {
             add_host_route_if_nonlocal(rb, ip);
@@ -4096,7 +4126,7 @@ 
 {
     if (rgi)
     {
-        if (local_route(addr, 0xFFFFFFFF, rgi->gateway.addr, rgi))
+        if (local_route(addr, IPV4_NETBITS_HOST, rgi->gateway.addr, rgi))
         {
             return TLA_LOCAL;
         }
diff --git a/src/openvpn/route.h b/src/openvpn/route.h
index 421e7d2..a117584 100644
--- a/src/openvpn/route.h
+++ b/src/openvpn/route.h
@@ -117,7 +117,7 @@ 
     unsigned int flags;
     const struct route_option *option;
     in_addr_t network;
-    in_addr_t netmask;
+    unsigned int netbits;
     in_addr_t gateway;
     int metric;
 };
diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c
index 17c5e76..767036f 100644
--- a/src/openvpn/socket.c
+++ b/src/openvpn/socket.c
@@ -179,6 +179,7 @@ 
 in_addr_t
 getaddr(unsigned int flags,
         const char *hostname,
+        unsigned int *netbits,
         int resolve_retry_seconds,
         bool *succeeded,
         struct signal_info *sig_info)
@@ -186,7 +187,7 @@ 
     in_addr_t addr;
     int status;
 
-    status = get_addr_generic(AF_INET, flags, hostname, &addr, NULL,
+    status = get_addr_generic(AF_INET, flags, hostname, &addr, netbits,
                               resolve_retry_seconds, sig_info,
                               M_WARN);
     if (status==0)
diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h
index 47083ad..f6165b2 100644
--- a/src/openvpn/socket.h
+++ b/src/openvpn/socket.h
@@ -524,6 +524,7 @@ 
  */
 in_addr_t getaddr(unsigned int flags,
                   const char *hostname,
+                  unsigned int *netbits,
                   int resolve_retry_seconds,
                   bool *succeeded,
                   struct signal_info *sig_info);
diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c
index 739e008..92c5e2b 100644
--- a/src/openvpn/tun.c
+++ b/src/openvpn/tun.c
@@ -113,7 +113,7 @@ 
     if (addr.family == AF_INET)
     {
         addr.address.ipv4.s_addr = htonl(tt->local);
-        addr.prefix_len = netmask_to_netbits2(tt->adapter_netmask);
+        addr.prefix_len = tt->netbits;
         msg(D_IFCONFIG, "INET address service: %s %s/%d",
             add ? "add" : "remove",
             print_in_addr_t(tt->local, 0, &gc), addr.prefix_len);
@@ -499,17 +499,17 @@ 
 static const char ifconfig_warn_how_to_silence[] = "(silence this warning with --ifconfig-nowarn)";
 
 /*
- * If !tun_p2p, make sure ifconfig_remote_netmask looks
- *  like a netmask.
+ * If !tun_p2p, make sure either ifconfig_netbits is
+ * set or ifconfig_remote looks like a netmask.
  *
- * If tun_p2p, make sure ifconfig_remote_netmask looks
+ * If tun_p2p, make sure ifconfig_remote looks
  *  like an IPv4 address.
  */
 static void
-ifconfig_sanity_check(bool tun_p2p, in_addr_t addr)
+ifconfig_sanity_check(bool tun_p2p, in_addr_t addr, int netbits)
 {
     struct gc_arena gc = gc_new();
-    const bool looks_like_netmask = ((addr & 0xFF000000) == 0xFF000000);
+    const bool looks_like_netmask = addr && ((addr & 0xFF000000) == 0xFF000000);
     if (tun_p2p)
     {
         if (looks_like_netmask)
@@ -521,9 +521,11 @@ 
     }
     else
     {
-        if (!looks_like_netmask)
+        if (netbits < 0 || !looks_like_netmask)
         {
-            msg(M_WARN, "WARNING: Since you are using subnet topology, the second argument to --ifconfig must be a netmask, for example something like 255.255.255.0. %s",
+            msg(M_WARN, "WARNING: Since you are using topology subnet, you must either "
+                "provide a valid prefix length in the first argument of --ifconfig (eg. 10.8.0.0/24) "
+                "or a valid netmask as the second argument (eg. 255.255.255.0). %s",
                 ifconfig_warn_how_to_silence);
         }
     }
@@ -539,34 +541,36 @@ 
                  int type,
                  in_addr_t public,
                  in_addr_t local,
-                 in_addr_t remote_netmask)
+                 in_addr_t remote,
+                 int netbits)
 {
     struct gc_arena gc = gc_new();
 #if 0
-    msg(M_INFO, "CHECK_ADDR_CLASH type=%d public=%s local=%s, remote_netmask=%s",
+    msg(M_INFO, "CHECK_ADDR_CLASH type=%d public=%s local=%s, netbits=%d, remote=%s",
         type,
         print_in_addr_t(public, 0, &gc),
         print_in_addr_t(local, 0, &gc),
-        print_in_addr_t(remote_netmask, 0, &gc));
+        netbits,
+        print_in_addr_t(remote, 0, &gc));
 #endif
 
     if (public)
     {
         if (type == DEV_TYPE_TUN)
         {
-            const in_addr_t test_netmask = 0xFFFFFF00;
-            const in_addr_t public_net = public &test_netmask;
-            const in_addr_t local_net = local & test_netmask;
-            const in_addr_t remote_net = remote_netmask & test_netmask;
+            const in_addr_t netmask = netbits < 0 ? 0xFFFFFF00 : netbits_to_netmask(netbits);
+            const in_addr_t public_net = public &netmask;
+            const in_addr_t local_net = local & netmask;
+            const in_addr_t remote_net = remote & netmask;
 
-            if (public == local || public == remote_netmask)
+            if (public == local || public == remote)
             {
                 msg(M_WARN,
                     "WARNING: --%s address [%s] conflicts with --ifconfig address pair [%s, %s]. %s",
                     name,
                     print_in_addr_t(public, 0, &gc),
                     print_in_addr_t(local, 0, &gc),
-                    print_in_addr_t(remote_netmask, 0, &gc),
+                    print_in_addr_t(remote, 0, &gc),
                     ifconfig_warn_how_to_silence);
             }
 
@@ -577,22 +581,23 @@ 
                     name,
                     print_in_addr_t(public, 0, &gc),
                     print_in_addr_t(local, 0, &gc),
-                    print_in_addr_t(remote_netmask, 0, &gc),
+                    print_in_addr_t(remote ? remote : netmask, 0, &gc),
                     ifconfig_warn_how_to_silence);
             }
         }
         else if (type == DEV_TYPE_TAP)
         {
-            const in_addr_t public_network = public &remote_netmask;
-            const in_addr_t virtual_network = local & remote_netmask;
-            if (public_network == virtual_network)
+            const in_addr_t netmask = netbits_to_netmask(netbits);
+            const in_addr_t public_network = public &netmask;
+            const in_addr_t virtual_network = local & netmask;
+            if (netmask && public_network == virtual_network)
             {
                 msg(M_WARN,
-                    "WARNING: --%s address [%s] conflicts with --ifconfig subnet [%s, %s] -- local and remote addresses cannot be inside of the --ifconfig subnet. %s",
+                    "WARNING: --%s address [%s] conflicts with --ifconfig subnet [%s/%d] -- local and remote addresses cannot be inside of the --ifconfig subnet. %s",
                     name,
                     print_in_addr_t(public, 0, &gc),
                     print_in_addr_t(local, 0, &gc),
-                    print_in_addr_t(remote_netmask, 0, &gc),
+                    netbits,
                     ifconfig_warn_how_to_silence);
             }
         }
@@ -669,9 +674,10 @@ 
     {
         if (!is_tun_p2p(tt))
         {
+            const in_addr_t netmask = netbits_to_netmask(tt->netbits);
             buf_printf(&out, "%s %s",
-                       print_in_addr_t(tt->local & tt->remote_netmask, 0, gc),
-                       print_in_addr_t(tt->remote_netmask, 0, gc));
+                       print_in_addr_t(tt->local & netmask, 0, gc),
+                       print_in_addr_t(netmask, 0, gc));
         }
         else if (tt->type == DEV_TYPE_TUN) /* tun p2p topology */
         {
@@ -679,12 +685,12 @@ 
             if (remote)
             {
                 r = print_in_addr_t(tt->local, 0, gc);
-                l = print_in_addr_t(tt->remote_netmask, 0, gc);
+                l = print_in_addr_t(tt->remote, 0, gc);
             }
             else
             {
                 l = print_in_addr_t(tt->local, 0, gc);
-                r = print_in_addr_t(tt->remote_netmask, 0, gc);
+                r = print_in_addr_t(tt->remote, 0, gc);
             }
             buf_printf(&out, "%s %s", r, l);
         }
@@ -765,7 +771,8 @@ 
 {
     struct gc_arena gc = gc_new();
     const char *ifconfig_local = print_in_addr_t(tt->local, 0, &gc);
-    const char *ifconfig_remote_netmask = print_in_addr_t(tt->remote_netmask, 0, &gc);
+    const char *ifconfig_remote = tt->remote ? print_in_addr_t(tt->remote, 0, &gc) : NULL;
+    const char *ifconfig_netmask = tt->netbits >= 0 ? print_in_addr_t(netbits_to_netmask(tt->netbits), 0, &gc) : NULL;
 
     /*
      * Set environmental variables with ifconfig parameters.
@@ -777,11 +784,12 @@ 
         setenv_str(es, "ifconfig_local", ifconfig_local);
         if (tun)
         {
-            setenv_str(es, "ifconfig_remote", ifconfig_remote_netmask);
+            setenv_str(es, "ifconfig_remote", ifconfig_remote);
         }
         else
         {
-            setenv_str(es, "ifconfig_netmask", ifconfig_remote_netmask);
+            setenv_int(es, "ifconfig_netbits", tt->netbits);
+            setenv_str(es, "ifconfig_netmask", ifconfig_netmask);
         }
     }
 
@@ -837,7 +845,7 @@ 
         bool tun_p2p = is_tun_p2p(tt);
 
         /*
-         * Convert arguments to binary IPv4 addresses.
+         * Convert arguments to binary IPv4 addresses and netbits.
          */
 
         tt->local = getaddr(
@@ -846,27 +854,48 @@ 
             | GETADDR_FATAL_ON_SIGNAL
             | GETADDR_FATAL,
             ifconfig_local_parm,
+            NULL,
             0,
             NULL,
             NULL);
 
-        tt->remote_netmask = getaddr(
+        tt->remote = getaddr(
             (tun_p2p ? GETADDR_RESOLVE : 0)
             | GETADDR_HOST_ORDER
             | GETADDR_FATAL_ON_SIGNAL
             | GETADDR_FATAL,
             ifconfig_remote_netmask_parm,
+            NULL,
             0,
             NULL,
             NULL);
 
+        /* in p2p mode discard the netbits otherwise discard the remote */
+        if (!tun_p2p)
+        {
+            if (tt->remote)
+            {
+                tt->netbits = netmask_to_netbits2(tt->remote);
+                tt->remote = 0;
+            }
+            else
+            {
+                msg(M_FATAL, "init_tun: can not figure out the subnet from the remote parameter (%s)", ifconfig_remote_netmask_parm);
+            }
+            tt->remote = 0;
+        }
+        else
+        {
+            tt->netbits = -1;
+        }
+
         /*
          * Look for common errors in --ifconfig parms
          */
         if (strict_warn)
         {
             struct addrinfo *curele;
-            ifconfig_sanity_check(tun_p2p, tt->remote_netmask);
+            ifconfig_sanity_check(tun_p2p, tt->remote, tt->netbits);
 
             /*
              * If local_public or remote_public addresses are defined,
@@ -877,11 +906,13 @@ 
             {
                 if (curele->ai_family == AF_INET)
                 {
+                    const in_addr_t local = ntohl(((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr);
                     check_addr_clash("local",
                                      tt->type,
-                                     ((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr,
+                                     local,
                                      tt->local,
-                                     tt->remote_netmask);
+                                     tt->remote,
+                                     tt->netbits);
                 }
             }
 
@@ -889,17 +920,19 @@ 
             {
                 if (curele->ai_family == AF_INET)
                 {
+                    const in_addr_t remote = ntohl(((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr);
                     check_addr_clash("remote",
                                      tt->type,
-                                     ((struct sockaddr_in *)curele->ai_addr)->sin_addr.s_addr,
+                                     remote,
                                      tt->local,
-                                     tt->remote_netmask);
+                                     tt->remote,
+                                     tt->netbits);
                 }
             }
 
             if (!tun_p2p)
             {
-                check_subnet_conflict(tt->local, tt->remote_netmask, "TUN/TAP adapter");
+                check_subnet_conflict(tt->local, netbits_to_netmask(tt->netbits), "TUN/TAP adapter");
             }
             else
             {
@@ -914,12 +947,12 @@ 
          */
         if (tun_p2p)
         {
-            verify_255_255_255_252(tt->local, tt->remote_netmask);
+            verify_255_255_255_252(tt->local, tt->remote);
             tt->adapter_netmask = ~3;
         }
         else
         {
-            tt->adapter_netmask = tt->remote_netmask;
+            tt->adapter_netmask = netbits_to_netmask(tt->netbits);
         }
 #endif
 
@@ -1062,8 +1095,9 @@ 
 create_arbitrary_remote( struct tuntap *tt )
 {
     in_addr_t remote;
+    const in_addr_t netmask = netbits_to_netmask(tt->netbits);
 
-    remote = (tt->local & tt->remote_netmask) +1;
+    remote = (tt->local & netmask) +1;
 
     if (remote == tt->local)
     {
@@ -1300,7 +1334,11 @@ 
 
 #if !defined(TARGET_LINUX)
     const char *ifconfig_local = NULL;
-    const char *ifconfig_remote_netmask = NULL;
+#if !defined(_WIN32)
+    const char *ifconfig_remote = NULL;
+#endif
+    const char *ifconfig_netmask = NULL;
+    const in_addr_t netmask = netbits_to_netmask(tt->netbits);
     struct argv argv = argv_new();
     struct gc_arena gc = gc_new();
 
@@ -1308,7 +1346,10 @@ 
      * Set ifconfig parameters
      */
     ifconfig_local = print_in_addr_t(tt->local, 0, &gc);
-    ifconfig_remote_netmask = print_in_addr_t(tt->remote_netmask, 0, &gc);
+#if !defined(_WIN32)
+    ifconfig_remote = print_in_addr_t(tt->remote, 0, &gc);
+#endif
+    ifconfig_netmask = print_in_addr_t(netmask, 0, &gc);
 #endif
 
 #if defined(TARGET_LINUX)
@@ -1324,16 +1365,14 @@ 
 
     if (tun_p2p)
     {
-        if (net_addr_ptp_v4_add(ctx, ifname, &tt->local,
-                                &tt->remote_netmask) < 0)
+        if (net_addr_ptp_v4_add(ctx, ifname, &tt->local, &tt->remote) < 0)
         {
             msg(M_FATAL, "Linux can't add IP to interface %s", ifname);
         }
     }
     else
     {
-        if (net_addr_v4_add(ctx, ifname, &tt->local,
-                            netmask_to_netbits2(tt->remote_netmask)) < 0)
+        if (net_addr_v4_add(ctx, ifname, &tt->local, tt->netbits) < 0)
         {
             msg(M_FATAL, "Linux can't add IP to interface %s", ifname);
         }
@@ -1341,8 +1380,11 @@ 
 #elif defined(TARGET_ANDROID)
     char out[64];
 
-    snprintf(out, sizeof(out), "%s %s %d %s", ifconfig_local,
-             ifconfig_remote_netmask, tun_mtu, print_topology(tt->topology));
+    snprintf(out, sizeof(out), "%s %s %d %s",
+             ifconfig_local,
+             ifconfig_remote ? ifconfig_remote : ifconfig_netmask,
+             tun_mtu,
+             print_topology(tt->topology));
     management_android_control(management, "IFCONFIG", out);
 
 #elif defined(TARGET_SOLARIS)
@@ -1354,7 +1396,7 @@ 
     if (tun_p2p)
     {
         argv_printf(&argv, "%s %s %s %s mtu %d up", IFCONFIG_PATH, ifname,
-                    ifconfig_local, ifconfig_remote_netmask, tun_mtu);
+                    ifconfig_local, ifconfig_remote, tun_mtu);
 
         argv_msg(M_INFO, &argv);
         if (!openvpn_execve_check(&argv, es, 0, "Solaris ifconfig phase-1 failed"))
@@ -1369,13 +1411,13 @@ 
     {
         argv_printf(&argv, "%s %s %s %s netmask %s mtu %d up", IFCONFIG_PATH,
                     ifname, ifconfig_local, ifconfig_local,
-                    ifconfig_remote_netmask, tun_mtu);
+                    ifconfig_netmask, tun_mtu);
     }
     else /* tap */
     {
         argv_printf(&argv, "%s %s %s netmask %s up",
                     IFCONFIG_PATH, ifname, ifconfig_local,
-                    ifconfig_remote_netmask);
+                    ifconfig_netmask);
     }
 
     argv_msg(M_INFO, &argv);
@@ -1390,8 +1432,8 @@ 
         struct route_ipv4 r;
         CLEAR(r);
         r.flags = RT_DEFINED | RT_METRIC_DEFINED;
-        r.network = tt->local & tt->remote_netmask;
-        r.netmask = tt->remote_netmask;
+        r.network = tt->local & netmask;
+        r.netbits = tt->netbits;
         r.gateway = tt->local;
         r.metric = 0;
         add_route(&r, tt, 0, NULL, es, NULL);
@@ -1413,7 +1455,7 @@ 
         argv_printf(&argv,
                     "%s %s %s %s mtu %d netmask 255.255.255.255 up -link0",
                     IFCONFIG_PATH, ifname, ifconfig_local,
-                    ifconfig_remote_netmask, tun_mtu);
+                    ifconfig_remote, tun_mtu);
     }
     else if (tt->type == DEV_TYPE_TUN)
     {
@@ -1421,13 +1463,13 @@ 
         argv_printf(&argv, "%s %s %s %s mtu %d netmask %s up -link0",
                     IFCONFIG_PATH, ifname, ifconfig_local,
                     print_in_addr_t(remote_end, 0, &gc), tun_mtu,
-                    ifconfig_remote_netmask);
+                    ifconfig_netmask);
     }
     else /* tap */
     {
         argv_printf(&argv, "%s %s %s netmask %s mtu %d link0",
                     IFCONFIG_PATH, ifname, ifconfig_local,
-                    ifconfig_remote_netmask, tun_mtu);
+                    ifconfig_netmask, tun_mtu);
     }
     argv_msg(M_INFO, &argv);
     openvpn_execve_check(&argv, es, S_FATAL, "OpenBSD ifconfig failed");
@@ -1438,8 +1480,8 @@ 
         struct route_ipv4 r;
         CLEAR(r);
         r.flags = RT_DEFINED;
-        r.network = tt->local & tt->remote_netmask;
-        r.netmask = tt->remote_netmask;
+        r.network = tt->local & netmask;
+        r.netbits = tt->netbits;
         r.gateway = remote_end;
         add_route(&r, tt, 0, NULL, es, NULL);
     }
@@ -1451,14 +1493,14 @@ 
     {
         argv_printf(&argv, "%s %s %s %s mtu %d netmask 255.255.255.255 up",
                     IFCONFIG_PATH, ifname, ifconfig_local,
-                    ifconfig_remote_netmask, tun_mtu);
+                    ifconfig_remote, tun_mtu);
     }
     else if (tt->type == DEV_TYPE_TUN)
     {
         remote_end = create_arbitrary_remote(tt);
         argv_printf(&argv, "%s %s %s %s mtu %d netmask %s up", IFCONFIG_PATH,
                     ifname, ifconfig_local, print_in_addr_t(remote_end, 0, &gc),
-                    tun_mtu, ifconfig_remote_netmask);
+                    tun_mtu, ifconfig_netmask);
     }
     else /* tap */
     {
@@ -1469,7 +1511,7 @@ 
          */
         argv_printf(&argv, "%s %s %s netmask %s mtu %d",
                     IFCONFIG_PATH, ifname, ifconfig_local,
-                    ifconfig_remote_netmask, tun_mtu);
+                    ifconfig_netmask, tun_mtu);
     }
     argv_msg(M_INFO, &argv);
     openvpn_execve_check(&argv, es, S_FATAL, "NetBSD ifconfig failed");
@@ -1480,8 +1522,8 @@ 
         struct route_ipv4 r;
         CLEAR(r);
         r.flags = RT_DEFINED;
-        r.network = tt->local & tt->remote_netmask;
-        r.netmask = tt->remote_netmask;
+        r.network = tt->local & netmask;
+        r.netbits = tt->netbits;
         r.gateway = remote_end;
         add_route(&r, tt, 0, NULL, es, NULL);
     }
@@ -1503,18 +1545,18 @@ 
     {
         argv_printf(&argv, "%s %s %s %s mtu %d netmask 255.255.255.255 up",
                     IFCONFIG_PATH, ifname, ifconfig_local,
-                    ifconfig_remote_netmask, tun_mtu);
+                    ifconfig_remote, tun_mtu);
     }
     else if (tt->type == DEV_TYPE_TUN)
     {
         argv_printf(&argv, "%s %s %s %s netmask %s mtu %d up",
                     IFCONFIG_PATH, ifname, ifconfig_local, ifconfig_local,
-                    ifconfig_remote_netmask, tun_mtu);
+                    ifconfig_netmask, tun_mtu);
     }
     else /* tap */
     {
         argv_printf(&argv, "%s %s %s netmask %s mtu %d up", IFCONFIG_PATH,
-                    ifname, ifconfig_local, ifconfig_remote_netmask,
+                    ifname, ifconfig_local, ifconfig_netmask,
                     tun_mtu);
     }
 
@@ -1527,8 +1569,8 @@ 
         struct route_ipv4 r;
         CLEAR(r);
         r.flags = RT_DEFINED;
-        r.network = tt->local & tt->remote_netmask;
-        r.netmask = tt->remote_netmask;
+        r.network = tt->local & netmask;
+        r.netbits = tt->netbits;
         r.gateway = tt->local;
         add_route(&r, tt, 0, NULL, es, NULL);
     }
@@ -1540,13 +1582,12 @@ 
     {
         argv_printf(&argv, "%s %s %s %s mtu %d netmask 255.255.255.255 up",
                     IFCONFIG_PATH, ifname, ifconfig_local,
-                    ifconfig_remote_netmask, tun_mtu);
+                    ifconfig_remote, tun_mtu);
     }
     else            /* tun with topology subnet and tap mode (always subnet) */
     {
-        int netbits = netmask_to_netbits2(tt->remote_netmask);
         argv_printf(&argv, "%s %s %s/%d mtu %d up", IFCONFIG_PATH,
-                    ifname, ifconfig_local, netbits, tun_mtu );
+                    ifname, ifconfig_local, tt->netbits, tun_mtu );
     }
 
     argv_msg(M_INFO, &argv);
@@ -1565,7 +1606,7 @@ 
 
         /* example: ifconfig tap0 172.30.1.1 netmask 255.255.254.0 up */
         argv_printf(&argv, "%s %s %s netmask %s mtu %d up", IFCONFIG_PATH,
-                    ifname, ifconfig_local, ifconfig_remote_netmask, tun_mtu);
+                    ifname, ifconfig_local, ifconfig_netmask, tun_mtu);
 
         argv_msg(M_INFO, &argv);
         openvpn_execve_check(&argv, aix_es, S_FATAL, "AIX ifconfig failed");
@@ -1578,7 +1619,7 @@ 
         msg(M_INFO,
             "******** NOTE:  Please manually set the IP/netmask of '%s' to %s/%s (if it is not already set)",
             ifname, ifconfig_local,
-            ifconfig_remote_netmask);
+            ifconfig_netmask);
     }
     else if (tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ || tt->options.ip_win32_type == IPW32_SET_ADAPTIVE)
     {
@@ -1660,12 +1701,10 @@ 
 undo_ifconfig_ipv4(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 {
 #if defined(TARGET_LINUX)
-    int netbits = netmask_to_netbits2(tt->remote_netmask);
-
     if (is_tun_p2p(tt))
     {
         if (net_addr_ptp_v4_del(ctx, tt->actual_name, &tt->local,
-                                &tt->remote_netmask) < 0)
+                                &tt->remote) < 0)
         {
             msg(M_WARN, "Linux can't del IP from iface %s",
                 tt->actual_name);
@@ -1673,7 +1712,7 @@ 
     }
     else
     {
-        if (net_addr_v4_del(ctx, tt->actual_name, &tt->local, netbits) < 0)
+        if (net_addr_v4_del(ctx, tt->actual_name, &tt->local, tt->netbits) < 0)
         {
             msg(M_WARN, "Linux can't del IP from iface %s",
                 tt->actual_name);
@@ -4683,8 +4722,8 @@ 
 
             if (ip_str && netmask_str && strlen(ip_str) && strlen(netmask_str))
             {
-                *ip = getaddr(getaddr_flags, ip_str, 0, &succeed1, NULL);
-                *netmask = getaddr(getaddr_flags, netmask_str, 0, &succeed2, NULL);
+                *ip = getaddr(getaddr_flags, ip_str, NULL, 0, &succeed1, NULL);
+                *netmask = getaddr(getaddr_flags, netmask_str, NULL, 0, &succeed2, NULL);
                 ret = (succeed1 == true && succeed2 == true);
             }
         }
@@ -5370,7 +5409,7 @@ 
             break;
         }
 
-        ip = getaddr(getaddr_flags, ip_str, 0, &succeed, NULL);
+        ip = getaddr(getaddr_flags, ip_str, NULL, 0, &succeed, NULL);
         if (!succeed)
         {
             break;
@@ -6428,11 +6467,12 @@ 
     if (!tt->did_ifconfig_setup || tt->topology == TOP_SUBNET)
     {
         in_addr_t ep[3];
+        const in_addr_t netmask = netbits_to_netmask(tt->netbits);
         BOOL status;
 
         ep[0] = htonl(tt->local);
-        ep[1] = htonl(tt->local & tt->remote_netmask);
-        ep[2] = htonl(tt->remote_netmask);
+        ep[1] = htonl(tt->local & netmask);
+        ep[2] = htonl(netmask);
 
         status = DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_TUN,
                                  ep, sizeof(ep),
@@ -6456,7 +6496,7 @@ 
     {
         in_addr_t ep[2];
         ep[0] = htonl(tt->local);
-        ep[1] = htonl(tt->remote_netmask);
+        ep[1] = htonl(tt->remote);
 
         if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT,
                              ep, sizeof(ep),
@@ -6485,11 +6525,11 @@ 
     {
         if (tt->topology == TOP_SUBNET)
         {
-            ep[2] = dhcp_masq_addr(tt->local, tt->remote_netmask, tt->options.dhcp_masq_custom_offset ? tt->options.dhcp_masq_offset : 0);
+            ep[2] = dhcp_masq_addr(tt->local, netbits_to_netmask(tt->netbits), tt->options.dhcp_masq_custom_offset ? tt->options.dhcp_masq_offset : 0);
         }
         else
         {
-            ep[2] = htonl(tt->remote_netmask);
+            ep[2] = htonl(tt->remote);
         }
     }
     else
diff --git a/src/openvpn/tun.h b/src/openvpn/tun.h
index 33b9552..68aa3b8 100644
--- a/src/openvpn/tun.h
+++ b/src/openvpn/tun.h
@@ -188,7 +188,8 @@ 
 
     /* ifconfig parameters */
     in_addr_t local;
-    in_addr_t remote_netmask;
+    in_addr_t remote;
+    int netbits;
 
     struct in6_addr local_ipv6;
     struct in6_addr remote_ipv6;