| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-10-27 10:35:38
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex, plaisthos.
Hello cron2, d12fk, flichtenheld, ordex, 
I'd like you to reexamine a change. Please visit
    http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email
to look at the new patch set (#12).
Change subject: Install host routes for ifconfig-push routes when DCO is enabled
......................................................................
Install host routes for ifconfig-push routes when DCO is enabled
ifconfig-push and ifconfig-ipv6-push can configure the IP address of a
client. If this IP address lies inside the network that is configured
on the ovpn/tun device this works as expected as the routing table point to
the ovpn/tun interface. However, if the IP address
is outside that range, the IP packets are not forwarded to the ovpn/tun
interface.
This patch adds logic to add host routes for these
ifconfig-push/ifconfig-ipv6-push addresses to ensure that traffic for
these IP addresses is also directed to the VPN.
For Linux it is important that these extra routes are routes using scope link
rather than static since otherwise routes via these IP addresses, like
iroute, will not work. On FreeBSD we also use interface routes as works and
routes that target interfaces instead of IP addresses are less brittle.
Tested using a server with ccd:
   openvpn --server 10.33.0.0 255.255.192.0 --server-ipv6 fd00:f00f::1/64  --client-config-dir ~/ccd [...]
and a client with lwipvonpn and the following ccd file:
   iroute-ipv6 FD00:F00F:CAFE::1001/64
   ifconfig-ipv6-push FD00:F00F:D00D::77/64
   push "setenv-safe ifconfig_ipv6_local_2 FD00:F00F:CAFE::1001"
   push "setenv-safe ifconfig_ipv6_netbits_2 64"
   iroute 10.234.234.0 255.255.255.0
   ifconfig-push 10.11.12.13 255.255.255.0
   push "setenv-safe ifconfig_local_2 10.234.234.12"
   push "setenv-safe ifconfig_netmask_2 255.255.255.0"
This setups an ifconfig-push addresses outside the --server/--server-ipv6
network and additionally configures a iroute behind that client. The
setenv-safe configure lwipovpn to use that additional IP addresses to allow
testing via ping.
Windows behaves like the user space implementation. It does require these
special routes but instead (like user space) needs static routes to redirect
IP traffic for these IP addresses to the tunnel interface. E.g. in the example
above the server config needs to have:
   route 10.234.234.0 255.255.255.0
   route 10.11.12.0 255.255.255.0
   route-ipv6 FD00:F00F:CAFE::1001/64
   route-ipv6 FD00:F00F:D00D::77/64
Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Signed-off-by: Arne Schwabe <ar...@rf...>
---
M doc/man-sections/server-options.rst
M src/openvpn/dco.c
M src/openvpn/mroute.h
M src/openvpn/multi.c
M src/openvpn/multi.h
5 files changed, 152 insertions(+), 7 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/12
diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst
index ccc1374..9c7fa46 100644
--- a/doc/man-sections/server-options.rst
+++ b/doc/man-sections/server-options.rst
@@ -314,6 +314,10 @@
   3.  Use ``--ifconfig-pool`` allocation for dynamic IP (last
       choice).
 
+  When DCO is enabled and the IP is not in contained in the network specified
+  by ``--ifconfig``, OpenVPN will install a /32 host route for the ``local``
+  IP address.
+
 --ifconfig-ipv6-push args
   for ``--client-config-dir`` per-client static IPv6 interface
   configuration, see ``--client-config-dir`` and ``--ifconfig-push`` for
@@ -324,6 +328,10 @@
 
      ifconfig-ipv6-push ipv6addr/bits ipv6remote
 
+  When DCO is enabled and the IP is not in contained in the network specified
+  by ``--ifconfig-ipv6``, OpenVPN will install a /128 host route for the
+  ``ipv6addr`` IP address.
+
 --multihome
   Configure a multi-homed UDP server. This option needs to be used when a
   server has more than one IP address (e.g. multiple interfaces, or
diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
index 8fb4662..d8ea269 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -664,6 +664,14 @@
         return;
     }
 
+#if defined(_WIN32)
+    if (addr->type & MR_ONLINK_DCO_ADDR)
+    {
+        /* Windows does not need these extra routes, so we ignore/skip them */
+        return;
+    }
+#endif
+
     struct context *c = &mi->context;
     if (addrtype == MR_ADDR_IPV6)
     {
@@ -671,8 +679,14 @@
         dco_win_add_iroute_ipv6(&c->c1.tuntap->dco, addr->v6.addr, addr->netbits,
                                 c->c2.tls_multi->peer_id);
 #else
+        const struct in6_addr *gateway = &mi->context.c2.push_ifconfig_ipv6_local;
+        if (addr->type & MR_ONLINK_DCO_ADDR)
+        {
+            gateway = NULL;
+        }
+
         net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, addr->netbits,
-                         &mi->context.c2.push_ifconfig_ipv6_local, c->c1.tuntap->actual_name, 0,
+                         gateway, c->c1.tuntap->actual_name, 0,
                          DCO_IROUTE_METRIC);
 #endif
     }
@@ -683,7 +697,13 @@
                                 c->c2.tls_multi->peer_id);
 #else
         in_addr_t dest = htonl(addr->v4.addr);
-        net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, &mi->context.c2.push_ifconfig_local,
+        const in_addr_t *gateway = &mi->context.c2.push_ifconfig_local;
+        if (addr->type & MR_ONLINK_DCO_ADDR)
+        {
+            gateway = NULL;
+        }
+
+        net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, gateway,
                          c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
 #endif
     }
@@ -714,6 +734,20 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+#if !defined(_WIN32)
+        /* Check if we added a host route as the assigned client IP address was
+         * not in the on link scope defined by --ifconfig */
+        in_addr_t ifconfig_local = mi->context.c2.push_ifconfig_local;
+
+        if (multi_check_push_ifconfig_extra_route(mi, ifconfig_local))
+        {
+            /* On windows we do not install these routes, so we also do not need to delete them */
+            net_route_v4_del(&m->top.net_ctx, &ifconfig_local,
+                             32, NULL, c->c1.tuntap->actual_name, 0,
+                             DCO_IROUTE_METRIC);
+        }
+#endif
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -728,6 +762,18 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Checked if we added a host route as the assigned client IP address was
+         * outside the --ifconfig-ipv6 tun interface config */
+#if !defined(_WIN32)
+        struct in6_addr *dest = &mi->context.c2.push_ifconfig_ipv6_local;
+        if (multi_check_push_ifconfig_ipv6_extra_route(mi, dest))
+        {
+            /* On windows we do not install these routes, so we also do not need to delete them */
+            net_route_v6_del(&m->top.net_ctx, dest, 128, NULL,
+                             c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
+        }
+#endif
     }
 #endif /* if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32) */
 }
diff --git a/src/openvpn/mroute.h b/src/openvpn/mroute.h
index 5b0c694..afd2e6c 100644
--- a/src/openvpn/mroute.h
+++ b/src/openvpn/mroute.h
@@ -20,6 +20,7 @@
  *  with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
+
 #ifndef MROUTE_H
 #define MROUTE_H
 
@@ -74,6 +75,9 @@
 /* Address type mask indicating that proto # is part of address */
 #define MR_WITH_PROTO 32
 
+/* MRoute is an on link/scope address needed for DCO on Unix platforms */
+#define MR_ONLINK_DCO_ADDR 64
+
 struct mroute_addr
 {
     uint8_t len;     /* length of address */
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index f60944d..ba35556 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -42,6 +42,7 @@
 #include "ssl_ncp.h"
 #include "vlan.h"
 #include "auth_token.h"
+#include "route.h"
 #include <inttypes.h>
 #include <string.h>
 
@@ -1231,11 +1232,18 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_extra_route(mi, addr.v4.addr))
     {
-        /* "primary" is the VPN ifconfig address of the peer and already
-         * known to DCO, so only install "extra" iroutes (primary = false)
-         */
+        /* "primary" is the VPN ifconfig address of the peer */
+        /* if it does not fall into the network defined by ifconfig_local
+         * we install this as extra onscope address on the interface  */
+        addr.netbits = 32;
+        addr.type |= MR_ONLINK_DCO_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+    }
+    else if (!primary)
+    {
         ASSERT(netbits >= 0); /* DCO requires populated netbits */
         dco_install_iroute(m, mi, &addr);
     }
@@ -1269,7 +1277,17 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi, &addr.v6.addr))
+    {
+        /* "primary" is the VPN ifconfig address of the peer */
+        /* if it does not fall into the network defined by ifconfig_local
+         * we install this as extra onscope address on the interface  */
+        addr.netbits = 128;
+        addr.type |= MR_ONLINK_DCO_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+    }
+    else if (!primary)
     {
         /* "primary" is the VPN ifconfig address of the peer and already
          * known to DCO, so only install "extra" iroutes (primary = false)
@@ -4391,3 +4409,49 @@
         }
     }
 }
+
+bool
+multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest)
+{
+    struct options *o = &mi->context.options;
+    in_addr_t local_addr, local_netmask;
+
+    if (!o->ifconfig_local || !o->ifconfig_remote_netmask)
+    {
+        /* If we do not have a local address, we just return false as
+         * this check doesn't make sense. */
+        return false;
+    }
+
+    /* if it falls into the network defined by ifconfig_local we assume
+     * it is already known to DCO and only install "extra" iroutes  */
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (dest & local_netmask);
+}
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi,
+                                           struct in6_addr *dest)
+{
+    struct options *o = &mi->context.options;
+
+    if (!o->ifconfig_ipv6_local || !o->ifconfig_ipv6_netbits)
+    {
+        /* If we do not have a local address, we just return false as
+         * this check doesn't make sense. */
+        return false;
+    }
+
+    /* if it falls into the network defined by ifconfig_local we assume
+     * it is already known to DCO and only install "extra" iroutes  */
+    struct in6_addr ifconfig_local;
+    if (inet_pton(AF_INET6, o->ifconfig_ipv6_local, &ifconfig_local) != 1)
+    {
+        return false;
+    }
+
+    return (!ipv6_net_contains_host(&ifconfig_local, o->ifconfig_ipv6_netbits,
+                                    dest));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 594ea3a..a26514d 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -665,6 +665,29 @@
     return ret;
 }
 
+/**
+ * Determines if the ifconfig_push_local address falls into the range of the local
+ * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask)
+ *
+ * @param mi           The multi-instance to check this condition for
+ *
+ * @return Returns true if ifconfig_push is outside that range and requires an extra
+ * route to be installed.
+ */
+bool
+multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest);
+
+/**
+ * Determines if the ifconfig_ipv6_local address falls into the range of the local
+ * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask)
+ *
+ * @return Returns true if ifconfig_push is outside that range and requires an extra
+ * route to be installed.
+ */
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi,
+                                           struct in6_addr *dest);
+
 /*
  * Check for signals.
  */
-- 
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email
To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Gerrit-Change-Number: 1192
Gerrit-PatchSet: 12
Gerrit-Owner: plaisthos <arn...@rf...>
Gerrit-Reviewer: cron2 <ge...@gr...>
Gerrit-Reviewer: d12fk <he...@op...>
Gerrit-Reviewer: flichtenheld <fr...@li...>
Gerrit-Reviewer: ordex <an...@ma...>
Gerrit-CC: openvpn-devel <ope...@li...>
Gerrit-Attention: plaisthos <arn...@rf...>
Gerrit-Attention: flichtenheld <fr...@li...>
Gerrit-Attention: ordex <an...@ma...>
Gerrit-Attention: d12fk <he...@op...>
 | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-10-27 12:20:05
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex, plaisthos.
Hello cron2, d12fk, flichtenheld, ordex, 
I'd like you to reexamine a change. Please visit
    http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email
to look at the new patch set (#13).
Change subject: Install host routes for ifconfig-push routes when DCO is enabled
......................................................................
Install host routes for ifconfig-push routes when DCO is enabled
ifconfig-push and ifconfig-ipv6-push can configure the IP address of a
client. If this IP address lies inside the network that is configured
on the ovpn/tun device this works as expected as the routing table point to
the ovpn/tun interface. However, if the IP address
is outside that range, the IP packets are not forwarded to the ovpn/tun
interface.
This patch adds logic to add host routes for these
ifconfig-push/ifconfig-ipv6-push addresses to ensure that traffic for
these IP addresses is also directed to the VPN.
For Linux it is important that these extra routes are routes using scope link
rather than static since otherwise routes via these IP addresses, like
iroute, will not work. On FreeBSD we also use interface routes as works and
routes that target interfaces instead of IP addresses are less brittle.
Tested using a server with ccd:
   openvpn --server 10.33.0.0 255.255.192.0 --server-ipv6 fd00:f00f::1/64  --client-config-dir ~/ccd [...]
and a client with lwipvonpn and the following ccd file:
   iroute-ipv6 FD00:F00F:CAFE::1001/64
   ifconfig-ipv6-push FD00:F00F:D00D::77/64
   push "setenv-safe ifconfig_ipv6_local_2 FD00:F00F:CAFE::1001"
   push "setenv-safe ifconfig_ipv6_netbits_2 64"
   iroute 10.234.234.0 255.255.255.0
   ifconfig-push 10.11.12.13 255.255.255.0
   push "setenv-safe ifconfig_local_2 10.234.234.12"
   push "setenv-safe ifconfig_netmask_2 255.255.255.0"
This setups an ifconfig-push addresses outside the --server/--server-ipv6
network and additionally configures a iroute behind that client. The
setenv-safe configure lwipovpn to use that additional IP addresses to allow
testing via ping.
Windows behaves like the user space implementation. It does require these
special routes but instead (like user space) needs static routes to redirect
IP traffic for these IP addresses to the tunnel interface. E.g. in the example
above the server config needs to have:
   route 10.234.234.0 255.255.255.0
   route 10.11.12.0 255.255.255.0
   route-ipv6 FD00:F00F:CAFE::1001/64
   route-ipv6 FD00:F00F:D00D::77/64
Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Signed-off-by: Arne Schwabe <ar...@rf...>
---
M doc/man-sections/server-options.rst
M src/openvpn/dco.c
M src/openvpn/mroute.h
M src/openvpn/multi.c
M src/openvpn/multi.h
5 files changed, 156 insertions(+), 7 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/13
diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst
index ccc1374..9c7fa46 100644
--- a/doc/man-sections/server-options.rst
+++ b/doc/man-sections/server-options.rst
@@ -314,6 +314,10 @@
   3.  Use ``--ifconfig-pool`` allocation for dynamic IP (last
       choice).
 
+  When DCO is enabled and the IP is not in contained in the network specified
+  by ``--ifconfig``, OpenVPN will install a /32 host route for the ``local``
+  IP address.
+
 --ifconfig-ipv6-push args
   for ``--client-config-dir`` per-client static IPv6 interface
   configuration, see ``--client-config-dir`` and ``--ifconfig-push`` for
@@ -324,6 +328,10 @@
 
      ifconfig-ipv6-push ipv6addr/bits ipv6remote
 
+  When DCO is enabled and the IP is not in contained in the network specified
+  by ``--ifconfig-ipv6``, OpenVPN will install a /128 host route for the
+  ``ipv6addr`` IP address.
+
 --multihome
   Configure a multi-homed UDP server. This option needs to be used when a
   server has more than one IP address (e.g. multiple interfaces, or
diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
index 8fb4662..d8ea269 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -664,6 +664,14 @@
         return;
     }
 
+#if defined(_WIN32)
+    if (addr->type & MR_ONLINK_DCO_ADDR)
+    {
+        /* Windows does not need these extra routes, so we ignore/skip them */
+        return;
+    }
+#endif
+
     struct context *c = &mi->context;
     if (addrtype == MR_ADDR_IPV6)
     {
@@ -671,8 +679,14 @@
         dco_win_add_iroute_ipv6(&c->c1.tuntap->dco, addr->v6.addr, addr->netbits,
                                 c->c2.tls_multi->peer_id);
 #else
+        const struct in6_addr *gateway = &mi->context.c2.push_ifconfig_ipv6_local;
+        if (addr->type & MR_ONLINK_DCO_ADDR)
+        {
+            gateway = NULL;
+        }
+
         net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, addr->netbits,
-                         &mi->context.c2.push_ifconfig_ipv6_local, c->c1.tuntap->actual_name, 0,
+                         gateway, c->c1.tuntap->actual_name, 0,
                          DCO_IROUTE_METRIC);
 #endif
     }
@@ -683,7 +697,13 @@
                                 c->c2.tls_multi->peer_id);
 #else
         in_addr_t dest = htonl(addr->v4.addr);
-        net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, &mi->context.c2.push_ifconfig_local,
+        const in_addr_t *gateway = &mi->context.c2.push_ifconfig_local;
+        if (addr->type & MR_ONLINK_DCO_ADDR)
+        {
+            gateway = NULL;
+        }
+
+        net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, gateway,
                          c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
 #endif
     }
@@ -714,6 +734,20 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+#if !defined(_WIN32)
+        /* Check if we added a host route as the assigned client IP address was
+         * not in the on link scope defined by --ifconfig */
+        in_addr_t ifconfig_local = mi->context.c2.push_ifconfig_local;
+
+        if (multi_check_push_ifconfig_extra_route(mi, ifconfig_local))
+        {
+            /* On windows we do not install these routes, so we also do not need to delete them */
+            net_route_v4_del(&m->top.net_ctx, &ifconfig_local,
+                             32, NULL, c->c1.tuntap->actual_name, 0,
+                             DCO_IROUTE_METRIC);
+        }
+#endif
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -728,6 +762,18 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Checked if we added a host route as the assigned client IP address was
+         * outside the --ifconfig-ipv6 tun interface config */
+#if !defined(_WIN32)
+        struct in6_addr *dest = &mi->context.c2.push_ifconfig_ipv6_local;
+        if (multi_check_push_ifconfig_ipv6_extra_route(mi, dest))
+        {
+            /* On windows we do not install these routes, so we also do not need to delete them */
+            net_route_v6_del(&m->top.net_ctx, dest, 128, NULL,
+                             c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
+        }
+#endif
     }
 #endif /* if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32) */
 }
diff --git a/src/openvpn/mroute.h b/src/openvpn/mroute.h
index 5b0c694..afd2e6c 100644
--- a/src/openvpn/mroute.h
+++ b/src/openvpn/mroute.h
@@ -20,6 +20,7 @@
  *  with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
+
 #ifndef MROUTE_H
 #define MROUTE_H
 
@@ -74,6 +75,9 @@
 /* Address type mask indicating that proto # is part of address */
 #define MR_WITH_PROTO 32
 
+/* MRoute is an on link/scope address needed for DCO on Unix platforms */
+#define MR_ONLINK_DCO_ADDR 64
+
 struct mroute_addr
 {
     uint8_t len;     /* length of address */
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index f60944d..ba35556 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -42,6 +42,7 @@
 #include "ssl_ncp.h"
 #include "vlan.h"
 #include "auth_token.h"
+#include "route.h"
 #include <inttypes.h>
 #include <string.h>
 
@@ -1231,11 +1232,18 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_extra_route(mi, addr.v4.addr))
     {
-        /* "primary" is the VPN ifconfig address of the peer and already
-         * known to DCO, so only install "extra" iroutes (primary = false)
-         */
+        /* "primary" is the VPN ifconfig address of the peer */
+        /* if it does not fall into the network defined by ifconfig_local
+         * we install this as extra onscope address on the interface  */
+        addr.netbits = 32;
+        addr.type |= MR_ONLINK_DCO_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+    }
+    else if (!primary)
+    {
         ASSERT(netbits >= 0); /* DCO requires populated netbits */
         dco_install_iroute(m, mi, &addr);
     }
@@ -1269,7 +1277,17 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi, &addr.v6.addr))
+    {
+        /* "primary" is the VPN ifconfig address of the peer */
+        /* if it does not fall into the network defined by ifconfig_local
+         * we install this as extra onscope address on the interface  */
+        addr.netbits = 128;
+        addr.type |= MR_ONLINK_DCO_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+    }
+    else if (!primary)
     {
         /* "primary" is the VPN ifconfig address of the peer and already
          * known to DCO, so only install "extra" iroutes (primary = false)
@@ -4391,3 +4409,49 @@
         }
     }
 }
+
+bool
+multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest)
+{
+    struct options *o = &mi->context.options;
+    in_addr_t local_addr, local_netmask;
+
+    if (!o->ifconfig_local || !o->ifconfig_remote_netmask)
+    {
+        /* If we do not have a local address, we just return false as
+         * this check doesn't make sense. */
+        return false;
+    }
+
+    /* if it falls into the network defined by ifconfig_local we assume
+     * it is already known to DCO and only install "extra" iroutes  */
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (dest & local_netmask);
+}
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi,
+                                           struct in6_addr *dest)
+{
+    struct options *o = &mi->context.options;
+
+    if (!o->ifconfig_ipv6_local || !o->ifconfig_ipv6_netbits)
+    {
+        /* If we do not have a local address, we just return false as
+         * this check doesn't make sense. */
+        return false;
+    }
+
+    /* if it falls into the network defined by ifconfig_local we assume
+     * it is already known to DCO and only install "extra" iroutes  */
+    struct in6_addr ifconfig_local;
+    if (inet_pton(AF_INET6, o->ifconfig_ipv6_local, &ifconfig_local) != 1)
+    {
+        return false;
+    }
+
+    return (!ipv6_net_contains_host(&ifconfig_local, o->ifconfig_ipv6_netbits,
+                                    dest));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 594ea3a..2fbb433 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -665,6 +665,33 @@
     return ret;
 }
 
+/**
+ * Determines if the ifconfig_push_local address falls into the range of the local
+ * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask)
+ *
+ * @param mi           The multi-instance to check this condition for
+ * @param dest         The destination IP address to check
+ *
+ * @return Returns true if ifconfig_push is outside that range and requires an extra
+ * route to be installed.
+ */
+bool
+multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest);
+
+/**
+ * Determines if the ifconfig_ipv6_local address falls into the range of the local
+ * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask)
+ *
+ * @param mi           The multi-instance to check this condition for
+ * @param dest         The destination IPv6 address to check
+ *
+ * @return Returns true if ifconfig_push is outside that range and requires an extra
+ * route to be installed.
+ */
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi,
+                                           struct in6_addr *dest);
+
 /*
  * Check for signals.
  */
-- 
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email
To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Gerrit-Change-Number: 1192
Gerrit-PatchSet: 13
Gerrit-Owner: plaisthos <arn...@rf...>
Gerrit-Reviewer: cron2 <ge...@gr...>
Gerrit-Reviewer: d12fk <he...@op...>
Gerrit-Reviewer: flichtenheld <fr...@li...>
Gerrit-Reviewer: ordex <an...@ma...>
Gerrit-CC: openvpn-devel <ope...@li...>
Gerrit-Attention: plaisthos <arn...@rf...>
Gerrit-Attention: flichtenheld <fr...@li...>
Gerrit-Attention: ordex <an...@ma...>
Gerrit-Attention: d12fk <he...@op...>
 | 
| 
      
      
      From: cron2 (C. Review) <ge...@op...> - 2025-10-28 13:33:05
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex, plaisthos. cron2 has posted comments on this change by plaisthos. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes for ifconfig-push routes when DCO is enabled ...................................................................... Patch Set 13: Code-Review-2 (1 comment) Patchset: PS13: We are getting there, but the journey is not over, alas. Now installation/removal of routes "according to what we want" in general works fine, but we *always* try to remove the IPv4 host route at the end - here's for a regular pool IP: ``` Oct 28 14:25:08 fbsd14 tun-udp-p2mp[12610]: MULTI_sva: pool returned IPv4=10.114.2.3, IPv6=fd00:abcd:114:2::1001 Oct 28 14:25:08 fbsd14 tun-udp-p2mp[12610]: MULTI: Learn: 10.114.2.3 -> cron2-ubuntu-2004-amd64/udp6:195.30.8.84:44555 peer-id=1 Oct 28 14:25:08 fbsd14 tun-udp-p2mp[12610]: MULTI: primary virtual IP for cron2-ubuntu-2004-amd64/udp6:195.30.8.84:44555 peer-id=1: 10.114.2.3 ... Oct 28 14:26:25 fbsd14 tun-udp-p2mp[12610]: /sbin/route del -net 10.114.2.3/32 -iface tun0 -fib 0 -weight 16777115 Oct 28 14:26:25 fbsd14 tun-udp-p2mp[12610]: ERROR: FreeBSD route command failed: external program exited with error status: 1 ``` which obviously is not the way to go forward. Need to look what changed in IPv4 land here. -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email Gerrit-MessageType: comment Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 13 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: d12fk <he...@op...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: ordex <an...@ma...> Gerrit-Attention: d12fk <he...@op...> Gerrit-Comment-Date: Tue, 28 Oct 2025 13:32:29 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: Yes | 
| 
      
      
      From: cron2 (C. Review) <ge...@op...> - 2025-10-28 13:54:28
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex, plaisthos. cron2 has posted comments on this change by plaisthos. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes for ifconfig-push routes when DCO is enabled ...................................................................... Patch Set 13: (2 comments) Patchset: PS13: found it, `htonl()` again, the two calls are not symmetric wrt byte order - so the check works correctly for IPv4 add and fails for IPv4 delete. Patch suggestion in the comment. File src/openvpn/dco.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/9082ac20_ae5adc12?usp=email : PS13, Line 743: if (multi_check_push_ifconfig_extra_route(mi, ifconfig_local)) with patch v12, multi_check_push_ifconfig_extra_route() lost an htonl(), and the *other* caller has a `htonl(a)`, so this one needs a `htonl(ifconfig_local)` to make it symmetric, or change it everywhere. -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email Gerrit-MessageType: comment Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 13 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: d12fk <he...@op...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: ordex <an...@ma...> Gerrit-Attention: d12fk <he...@op...> Gerrit-Comment-Date: Tue, 28 Oct 2025 13:54:18 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No | 
| 
      
      
      From: cron2 (C. Review) <ge...@op...> - 2025-10-28 13:55:26
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex, plaisthos. cron2 has posted comments on this change by plaisthos. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes for ifconfig-push routes when DCO is enabled ...................................................................... Patch Set 13: (6 comments) Patchset: PS8: > Yeah not really sure what happend there during my rebase. […] Done Patchset: PS11: > This is not working right yet. […] Done Commit Message: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/4477c2fa_904f1747?usp=email : PS4, Line 7: Install host routes with onlink scope iroutes for ifconfig-push routes > how about: […] Done http://gerrit.openvpn.net/c/openvpn/+/1192/comment/f9794219_e00d035b?usp=email : PS4, Line 10: of the configured device need to be added to the operating system to > I agree with that, but what I meant in my comment is that we "need *routes* to be added" not IPs. […] Done http://gerrit.openvpn.net/c/openvpn/+/1192/comment/220f5023_3980d1f4?usp=email : PS4, Line 15: iroute, will not work. > should we add "because the server does not have an address in the same network as these IPs assigned […] Done File src/openvpn/multi.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/02b58eff_bb2dd3f8?usp=email : PS11, Line 4377: &o->push_ifconfig_ipv6_local)); > Maybe it would also be an idea to change `multi_check_push_ifconfig_ipv6_extra_route()` to not be pa […] Done -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email Gerrit-MessageType: comment Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 13 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: d12fk <he...@op...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: ordex <an...@ma...> Gerrit-Attention: d12fk <he...@op...> Gerrit-Comment-Date: Tue, 28 Oct 2025 13:55:11 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: plaisthos <arn...@rf...> Comment-In-Reply-To: cron2 <ge...@gr...> Comment-In-Reply-To: ordex <an...@ma...> | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-10-28 23:24:39
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex, plaisthos.
Hello cron2, d12fk, flichtenheld, ordex, 
I'd like you to reexamine a change. Please visit
    http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email
to look at the new patch set (#14).
Change subject: Install host routes for ifconfig-push routes when DCO is enabled
......................................................................
Install host routes for ifconfig-push routes when DCO is enabled
ifconfig-push and ifconfig-ipv6-push can configure the IP address of a
client. If this IP address lies inside the network that is configured
on the ovpn/tun device this works as expected as the routing table point to
the ovpn/tun interface. However, if the IP address
is outside that range, the IP packets are not forwarded to the ovpn/tun
interface.
This patch adds logic to add host routes for these
ifconfig-push/ifconfig-ipv6-push addresses to ensure that traffic for
these IP addresses is also directed to the VPN.
For Linux it is important that these extra routes are routes using scope link
rather than static since otherwise routes via these IP addresses, like
iroute, will not work. On FreeBSD we also use interface routes as works and
routes that target interfaces instead of IP addresses are less brittle.
Tested using a server with ccd:
   openvpn --server 10.33.0.0 255.255.192.0 --server-ipv6 fd00:f00f::1/64  --client-config-dir ~/ccd [...]
and a client with lwipvonpn and the following ccd file:
   iroute-ipv6 FD00:F00F:CAFE::1001/64
   ifconfig-ipv6-push FD00:F00F:D00D::77/64
   push "setenv-safe ifconfig_ipv6_local_2 FD00:F00F:CAFE::1001"
   push "setenv-safe ifconfig_ipv6_netbits_2 64"
   iroute 10.234.234.0 255.255.255.0
   ifconfig-push 10.11.12.13 255.255.255.0
   push "setenv-safe ifconfig_local_2 10.234.234.12"
   push "setenv-safe ifconfig_netmask_2 255.255.255.0"
This setups an ifconfig-push addresses outside the --server/--server-ipv6
network and additionally configures a iroute behind that client. The
setenv-safe configure lwipovpn to use that additional IP addresses to allow
testing via ping.
Windows behaves like the user space implementation. It does require these
special routes but instead (like user space) needs static routes to redirect
IP traffic for these IP addresses to the tunnel interface. E.g. in the example
above the server config needs to have:
   route 10.234.234.0 255.255.255.0
   route 10.11.12.0 255.255.255.0
   route-ipv6 FD00:F00F:CAFE::1001/64
   route-ipv6 FD00:F00F:D00D::77/64
Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Signed-off-by: Arne Schwabe <ar...@rf...>
---
M doc/man-sections/server-options.rst
M src/openvpn/dco.c
M src/openvpn/mroute.h
M src/openvpn/multi.c
M src/openvpn/multi.h
5 files changed, 156 insertions(+), 7 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/14
diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst
index ccc1374..9c7fa46 100644
--- a/doc/man-sections/server-options.rst
+++ b/doc/man-sections/server-options.rst
@@ -314,6 +314,10 @@
   3.  Use ``--ifconfig-pool`` allocation for dynamic IP (last
       choice).
 
+  When DCO is enabled and the IP is not in contained in the network specified
+  by ``--ifconfig``, OpenVPN will install a /32 host route for the ``local``
+  IP address.
+
 --ifconfig-ipv6-push args
   for ``--client-config-dir`` per-client static IPv6 interface
   configuration, see ``--client-config-dir`` and ``--ifconfig-push`` for
@@ -324,6 +328,10 @@
 
      ifconfig-ipv6-push ipv6addr/bits ipv6remote
 
+  When DCO is enabled and the IP is not in contained in the network specified
+  by ``--ifconfig-ipv6``, OpenVPN will install a /128 host route for the
+  ``ipv6addr`` IP address.
+
 --multihome
   Configure a multi-homed UDP server. This option needs to be used when a
   server has more than one IP address (e.g. multiple interfaces, or
diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
index 8fb4662..7abdad3 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -664,6 +664,14 @@
         return;
     }
 
+#if defined(_WIN32)
+    if (addr->type & MR_ONLINK_DCO_ADDR)
+    {
+        /* Windows does not need these extra routes, so we ignore/skip them */
+        return;
+    }
+#endif
+
     struct context *c = &mi->context;
     if (addrtype == MR_ADDR_IPV6)
     {
@@ -671,8 +679,14 @@
         dco_win_add_iroute_ipv6(&c->c1.tuntap->dco, addr->v6.addr, addr->netbits,
                                 c->c2.tls_multi->peer_id);
 #else
+        const struct in6_addr *gateway = &mi->context.c2.push_ifconfig_ipv6_local;
+        if (addr->type & MR_ONLINK_DCO_ADDR)
+        {
+            gateway = NULL;
+        }
+
         net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, addr->netbits,
-                         &mi->context.c2.push_ifconfig_ipv6_local, c->c1.tuntap->actual_name, 0,
+                         gateway, c->c1.tuntap->actual_name, 0,
                          DCO_IROUTE_METRIC);
 #endif
     }
@@ -683,7 +697,13 @@
                                 c->c2.tls_multi->peer_id);
 #else
         in_addr_t dest = htonl(addr->v4.addr);
-        net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, &mi->context.c2.push_ifconfig_local,
+        const in_addr_t *gateway = &mi->context.c2.push_ifconfig_local;
+        if (addr->type & MR_ONLINK_DCO_ADDR)
+        {
+            gateway = NULL;
+        }
+
+        net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, gateway,
                          c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
 #endif
     }
@@ -714,6 +734,20 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+#if !defined(_WIN32)
+        /* Check if we added a host route as the assigned client IP address was
+         * not in the on link scope defined by --ifconfig */
+        in_addr_t ifconfig_local = mi->context.c2.push_ifconfig_local;
+
+        if (multi_check_push_ifconfig_extra_route(mi, htonl(ifconfig_local)))
+        {
+            /* On windows we do not install these routes, so we also do not need to delete them */
+            net_route_v4_del(&m->top.net_ctx, &ifconfig_local,
+                             32, NULL, c->c1.tuntap->actual_name, 0,
+                             DCO_IROUTE_METRIC);
+        }
+#endif
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -728,6 +762,18 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Checked if we added a host route as the assigned client IP address was
+         * outside the --ifconfig-ipv6 tun interface config */
+#if !defined(_WIN32)
+        struct in6_addr *dest = &mi->context.c2.push_ifconfig_ipv6_local;
+        if (multi_check_push_ifconfig_ipv6_extra_route(mi, dest))
+        {
+            /* On windows we do not install these routes, so we also do not need to delete them */
+            net_route_v6_del(&m->top.net_ctx, dest, 128, NULL,
+                             c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
+        }
+#endif
     }
 #endif /* if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32) */
 }
diff --git a/src/openvpn/mroute.h b/src/openvpn/mroute.h
index 5b0c694..afd2e6c 100644
--- a/src/openvpn/mroute.h
+++ b/src/openvpn/mroute.h
@@ -20,6 +20,7 @@
  *  with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
+
 #ifndef MROUTE_H
 #define MROUTE_H
 
@@ -74,6 +75,9 @@
 /* Address type mask indicating that proto # is part of address */
 #define MR_WITH_PROTO 32
 
+/* MRoute is an on link/scope address needed for DCO on Unix platforms */
+#define MR_ONLINK_DCO_ADDR 64
+
 struct mroute_addr
 {
     uint8_t len;     /* length of address */
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index f60944d..ba35556 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -42,6 +42,7 @@
 #include "ssl_ncp.h"
 #include "vlan.h"
 #include "auth_token.h"
+#include "route.h"
 #include <inttypes.h>
 #include <string.h>
 
@@ -1231,11 +1232,18 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_extra_route(mi, addr.v4.addr))
     {
-        /* "primary" is the VPN ifconfig address of the peer and already
-         * known to DCO, so only install "extra" iroutes (primary = false)
-         */
+        /* "primary" is the VPN ifconfig address of the peer */
+        /* if it does not fall into the network defined by ifconfig_local
+         * we install this as extra onscope address on the interface  */
+        addr.netbits = 32;
+        addr.type |= MR_ONLINK_DCO_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+    }
+    else if (!primary)
+    {
         ASSERT(netbits >= 0); /* DCO requires populated netbits */
         dco_install_iroute(m, mi, &addr);
     }
@@ -1269,7 +1277,17 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi, &addr.v6.addr))
+    {
+        /* "primary" is the VPN ifconfig address of the peer */
+        /* if it does not fall into the network defined by ifconfig_local
+         * we install this as extra onscope address on the interface  */
+        addr.netbits = 128;
+        addr.type |= MR_ONLINK_DCO_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+    }
+    else if (!primary)
     {
         /* "primary" is the VPN ifconfig address of the peer and already
          * known to DCO, so only install "extra" iroutes (primary = false)
@@ -4391,3 +4409,49 @@
         }
     }
 }
+
+bool
+multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest)
+{
+    struct options *o = &mi->context.options;
+    in_addr_t local_addr, local_netmask;
+
+    if (!o->ifconfig_local || !o->ifconfig_remote_netmask)
+    {
+        /* If we do not have a local address, we just return false as
+         * this check doesn't make sense. */
+        return false;
+    }
+
+    /* if it falls into the network defined by ifconfig_local we assume
+     * it is already known to DCO and only install "extra" iroutes  */
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (dest & local_netmask);
+}
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi,
+                                           struct in6_addr *dest)
+{
+    struct options *o = &mi->context.options;
+
+    if (!o->ifconfig_ipv6_local || !o->ifconfig_ipv6_netbits)
+    {
+        /* If we do not have a local address, we just return false as
+         * this check doesn't make sense. */
+        return false;
+    }
+
+    /* if it falls into the network defined by ifconfig_local we assume
+     * it is already known to DCO and only install "extra" iroutes  */
+    struct in6_addr ifconfig_local;
+    if (inet_pton(AF_INET6, o->ifconfig_ipv6_local, &ifconfig_local) != 1)
+    {
+        return false;
+    }
+
+    return (!ipv6_net_contains_host(&ifconfig_local, o->ifconfig_ipv6_netbits,
+                                    dest));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 594ea3a..2fbb433 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -665,6 +665,33 @@
     return ret;
 }
 
+/**
+ * Determines if the ifconfig_push_local address falls into the range of the local
+ * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask)
+ *
+ * @param mi           The multi-instance to check this condition for
+ * @param dest         The destination IP address to check
+ *
+ * @return Returns true if ifconfig_push is outside that range and requires an extra
+ * route to be installed.
+ */
+bool
+multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest);
+
+/**
+ * Determines if the ifconfig_ipv6_local address falls into the range of the local
+ * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask)
+ *
+ * @param mi           The multi-instance to check this condition for
+ * @param dest         The destination IPv6 address to check
+ *
+ * @return Returns true if ifconfig_push is outside that range and requires an extra
+ * route to be installed.
+ */
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi,
+                                           struct in6_addr *dest);
+
 /*
  * Check for signals.
  */
-- 
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email
To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email
Gerrit-MessageType: newpatchset
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Gerrit-Change-Number: 1192
Gerrit-PatchSet: 14
Gerrit-Owner: plaisthos <arn...@rf...>
Gerrit-Reviewer: cron2 <ge...@gr...>
Gerrit-Reviewer: d12fk <he...@op...>
Gerrit-Reviewer: flichtenheld <fr...@li...>
Gerrit-Reviewer: ordex <an...@ma...>
Gerrit-CC: openvpn-devel <ope...@li...>
Gerrit-Attention: plaisthos <arn...@rf...>
Gerrit-Attention: flichtenheld <fr...@li...>
Gerrit-Attention: ordex <an...@ma...>
Gerrit-Attention: d12fk <he...@op...>
 | 
| 
      
      
      From: cron2 (C. Review) <ge...@op...> - 2025-10-29 07:06:54
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex, plaisthos. cron2 has posted comments on this change by plaisthos. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes for ifconfig-push routes when DCO is enabled ...................................................................... Patch Set 14: Code-Review+2 (1 comment) Patchset: PS14: thanks :-) -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email Gerrit-MessageType: comment Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 14 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: d12fk <he...@op...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: ordex <an...@ma...> Gerrit-Attention: d12fk <he...@op...> Gerrit-Comment-Date: Wed, 29 Oct 2025 07:06:44 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: Yes | 
| 
      
      
      From: Gert D. <ge...@gr...> - 2025-10-29 07:07:16
       | 
| From: Arne Schwabe <ar...@rf...> ifconfig-push and ifconfig-ipv6-push can configure the IP address of a client. If this IP address lies inside the network that is configured on the ovpn/tun device this works as expected as the routing table point to the ovpn/tun interface. However, if the IP address is outside that range, the IP packets are not forwarded to the ovpn/tun interface. This patch adds logic to add host routes for these ifconfig-push/ifconfig-ipv6-push addresses to ensure that traffic for these IP addresses is also directed to the VPN. For Linux it is important that these extra routes are routes using scope link rather than static since otherwise routes via these IP addresses, like iroute, will not work. On FreeBSD we also use interface routes as works and routes that target interfaces instead of IP addresses are less brittle. Tested using a server with ccd: openvpn --server 10.33.0.0 255.255.192.0 --server-ipv6 fd00:f00f::1/64 --client-config-dir ~/ccd [...] and a client with lwipvonpn and the following ccd file: iroute-ipv6 FD00:F00F:CAFE::1001/64 ifconfig-ipv6-push FD00:F00F:D00D::77/64 push "setenv-safe ifconfig_ipv6_local_2 FD00:F00F:CAFE::1001" push "setenv-safe ifconfig_ipv6_netbits_2 64" iroute 10.234.234.0 255.255.255.0 ifconfig-push 10.11.12.13 255.255.255.0 push "setenv-safe ifconfig_local_2 10.234.234.12" push "setenv-safe ifconfig_netmask_2 255.255.255.0" This setups an ifconfig-push addresses outside the --server/--server-ipv6 network and additionally configures a iroute behind that client. The setenv-safe configure lwipovpn to use that additional IP addresses to allow testing via ping. Windows behaves like the user space implementation. It does require these special routes but instead (like user space) needs static routes to redirect IP traffic for these IP addresses to the tunnel interface. E.g. in the example above the server config needs to have: route 10.234.234.0 255.255.255.0 route 10.11.12.0 255.255.255.0 route-ipv6 FD00:F00F:CAFE::1001/64 route-ipv6 FD00:F00F:D00D::77/64 Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Signed-off-by: Arne Schwabe <ar...@rf...> Acked-by: Gert Doering <ge...@gr...> Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1192 --- This change was reviewed on Gerrit and approved by at least one developer. I request to merge it to master. Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1192 This mail reflects revision 14 of this Change. Acked-by according to Gerrit (reflected above): Gert Doering <ge...@gr...> diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst index ccc1374..9c7fa46 100644 --- a/doc/man-sections/server-options.rst +++ b/doc/man-sections/server-options.rst @@ -314,6 +314,10 @@ 3. Use ``--ifconfig-pool`` allocation for dynamic IP (last choice). + When DCO is enabled and the IP is not in contained in the network specified + by ``--ifconfig``, OpenVPN will install a /32 host route for the ``local`` + IP address. + --ifconfig-ipv6-push args for ``--client-config-dir`` per-client static IPv6 interface configuration, see ``--client-config-dir`` and ``--ifconfig-push`` for @@ -324,6 +328,10 @@ ifconfig-ipv6-push ipv6addr/bits ipv6remote + When DCO is enabled and the IP is not in contained in the network specified + by ``--ifconfig-ipv6``, OpenVPN will install a /128 host route for the + ``ipv6addr`` IP address. + --multihome Configure a multi-homed UDP server. This option needs to be used when a server has more than one IP address (e.g. multiple interfaces, or diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c index 8fb4662..7abdad3 100644 --- a/src/openvpn/dco.c +++ b/src/openvpn/dco.c @@ -664,6 +664,14 @@ return; } +#if defined(_WIN32) + if (addr->type & MR_ONLINK_DCO_ADDR) + { + /* Windows does not need these extra routes, so we ignore/skip them */ + return; + } +#endif + struct context *c = &mi->context; if (addrtype == MR_ADDR_IPV6) { @@ -671,8 +679,14 @@ dco_win_add_iroute_ipv6(&c->c1.tuntap->dco, addr->v6.addr, addr->netbits, c->c2.tls_multi->peer_id); #else + const struct in6_addr *gateway = &mi->context.c2.push_ifconfig_ipv6_local; + if (addr->type & MR_ONLINK_DCO_ADDR) + { + gateway = NULL; + } + net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, addr->netbits, - &mi->context.c2.push_ifconfig_ipv6_local, c->c1.tuntap->actual_name, 0, + gateway, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC); #endif } @@ -683,7 +697,13 @@ c->c2.tls_multi->peer_id); #else in_addr_t dest = htonl(addr->v4.addr); - net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, &mi->context.c2.push_ifconfig_local, + const in_addr_t *gateway = &mi->context.c2.push_ifconfig_local; + if (addr->type & MR_ONLINK_DCO_ADDR) + { + gateway = NULL; + } + + net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, gateway, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC); #endif } @@ -714,6 +734,20 @@ DCO_IROUTE_METRIC); #endif } + +#if !defined(_WIN32) + /* Check if we added a host route as the assigned client IP address was + * not in the on link scope defined by --ifconfig */ + in_addr_t ifconfig_local = mi->context.c2.push_ifconfig_local; + + if (multi_check_push_ifconfig_extra_route(mi, htonl(ifconfig_local))) + { + /* On windows we do not install these routes, so we also do not need to delete them */ + net_route_v4_del(&m->top.net_ctx, &ifconfig_local, + 32, NULL, c->c1.tuntap->actual_name, 0, + DCO_IROUTE_METRIC); + } +#endif } if (mi->context.c2.push_ifconfig_ipv6_defined) @@ -728,6 +762,18 @@ DCO_IROUTE_METRIC); #endif } + + /* Checked if we added a host route as the assigned client IP address was + * outside the --ifconfig-ipv6 tun interface config */ +#if !defined(_WIN32) + struct in6_addr *dest = &mi->context.c2.push_ifconfig_ipv6_local; + if (multi_check_push_ifconfig_ipv6_extra_route(mi, dest)) + { + /* On windows we do not install these routes, so we also do not need to delete them */ + net_route_v6_del(&m->top.net_ctx, dest, 128, NULL, + c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC); + } +#endif } #endif /* if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32) */ } diff --git a/src/openvpn/mroute.h b/src/openvpn/mroute.h index 5b0c694..afd2e6c 100644 --- a/src/openvpn/mroute.h +++ b/src/openvpn/mroute.h @@ -20,6 +20,7 @@ * with this program; if not, see <https://www.gnu.org/licenses/>. */ + #ifndef MROUTE_H #define MROUTE_H @@ -74,6 +75,9 @@ /* Address type mask indicating that proto # is part of address */ #define MR_WITH_PROTO 32 +/* MRoute is an on link/scope address needed for DCO on Unix platforms */ +#define MR_ONLINK_DCO_ADDR 64 + struct mroute_addr { uint8_t len; /* length of address */ diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index f60944d..ba35556 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -42,6 +42,7 @@ #include "ssl_ncp.h" #include "vlan.h" #include "auth_token.h" +#include "route.h" #include <inttypes.h> #include <string.h> @@ -1231,11 +1232,18 @@ management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary); } #endif - if (!primary) + if (primary && multi_check_push_ifconfig_extra_route(mi, addr.v4.addr)) { - /* "primary" is the VPN ifconfig address of the peer and already - * known to DCO, so only install "extra" iroutes (primary = false) - */ + /* "primary" is the VPN ifconfig address of the peer */ + /* if it does not fall into the network defined by ifconfig_local + * we install this as extra onscope address on the interface */ + addr.netbits = 32; + addr.type |= MR_ONLINK_DCO_ADDR; + + dco_install_iroute(m, mi, &addr); + } + else if (!primary) + { ASSERT(netbits >= 0); /* DCO requires populated netbits */ dco_install_iroute(m, mi, &addr); } @@ -1269,7 +1277,17 @@ management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary); } #endif - if (!primary) + if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi, &addr.v6.addr)) + { + /* "primary" is the VPN ifconfig address of the peer */ + /* if it does not fall into the network defined by ifconfig_local + * we install this as extra onscope address on the interface */ + addr.netbits = 128; + addr.type |= MR_ONLINK_DCO_ADDR; + + dco_install_iroute(m, mi, &addr); + } + else if (!primary) { /* "primary" is the VPN ifconfig address of the peer and already * known to DCO, so only install "extra" iroutes (primary = false) @@ -4391,3 +4409,49 @@ } } } + +bool +multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest) +{ + struct options *o = &mi->context.options; + in_addr_t local_addr, local_netmask; + + if (!o->ifconfig_local || !o->ifconfig_remote_netmask) + { + /* If we do not have a local address, we just return false as + * this check doesn't make sense. */ + return false; + } + + /* if it falls into the network defined by ifconfig_local we assume + * it is already known to DCO and only install "extra" iroutes */ + inet_pton(AF_INET, o->ifconfig_local, &local_addr); + inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask); + + return (local_addr & local_netmask) != (dest & local_netmask); +} + +bool +multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi, + struct in6_addr *dest) +{ + struct options *o = &mi->context.options; + + if (!o->ifconfig_ipv6_local || !o->ifconfig_ipv6_netbits) + { + /* If we do not have a local address, we just return false as + * this check doesn't make sense. */ + return false; + } + + /* if it falls into the network defined by ifconfig_local we assume + * it is already known to DCO and only install "extra" iroutes */ + struct in6_addr ifconfig_local; + if (inet_pton(AF_INET6, o->ifconfig_ipv6_local, &ifconfig_local) != 1) + { + return false; + } + + return (!ipv6_net_contains_host(&ifconfig_local, o->ifconfig_ipv6_netbits, + dest)); +} \ No newline at end of file diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 594ea3a..2fbb433 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -665,6 +665,33 @@ return ret; } +/** + * Determines if the ifconfig_push_local address falls into the range of the local + * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask) + * + * @param mi The multi-instance to check this condition for + * @param dest The destination IP address to check + * + * @return Returns true if ifconfig_push is outside that range and requires an extra + * route to be installed. + */ +bool +multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest); + +/** + * Determines if the ifconfig_ipv6_local address falls into the range of the local + * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask) + * + * @param mi The multi-instance to check this condition for + * @param dest The destination IPv6 address to check + * + * @return Returns true if ifconfig_push is outside that range and requires an extra + * route to be installed. + */ +bool +multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi, + struct in6_addr *dest); + /* * Check for signals. */ | 
| 
      
      
      From: Gert D. <ge...@gr...> - 2025-10-29 07:53:14
       | 
| Thanks for the midnight fix, just in time for 2.7_rc1 ;-)
So this has been a bit of a journey with the patch running up to v14...
getting bits and pieces right across the 3 potentially affected platforms
(Linux and FreeBSD need this, Windows has DCO but must not have this)
and IPv4/IPv6 is quite a few small snippets all over the place.
I have tested this on FreeBSD with DCO, with a client-connect script that
can setup ifconfig-push/ifconfig-ipv6-push/iroute/iroute-ipv6 controlled
from the client ("setenv UV_WANT_IP ...") and t_client.rc instances that
request IPv4/IPv6 addresses "outside the server subnet", verify that they
receive what they asked for, and then run pings to see if traffic actually
comes back.  This worked already in v11.
v11->v13->v14 was basically ensuring that we only install these routes
when we really need them (v11 installed the route "always!" for IPv6 due
to checking the wrong variable, v13 always tried to *delete* the route
for IPv4 due to missing htonl() in one of the calls).  In v14 we now
have exactly what we want - routes get only installed when needed, and
are only deleted when installed.
There might be dragons lurking here with clients reconnecting and 
learn/unlearn-address getting confused.  I ran my tests with EEN on the
client side to ensure the server always has a well-defined state.
I have also reworded the commit message a bit :-)
Your patch has been applied to the master branch.
commit f938d991a8222bb3304865f2cd7b368d7f8a9224
Author: Arne Schwabe
Date:   Wed Oct 29 08:06:56 2025 +0100
     Install host routes for out-of-subnet ifconfig-push addresses when DCO is enabled
     Signed-off-by: Arne Schwabe <ar...@rf...>
     Acked-by: Gert Doering <ge...@gr...>
     Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1192
     Message-Id: <202...@gr...>
     URL: https://www.mail-archive.com/ope...@li.../msg33991.html
     Signed-off-by: Gert Doering <ge...@gr...>
--
kind regards,
Gert Doering
 | 
| 
      
      
      From: cron2 (C. Review) <ge...@op...> - 2025-10-29 07:53:32
       | 
| cron2 has uploaded a new patch set (#15) to the change originally created by plaisthos. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) The following approvals got outdated and were removed: Code-Review+2 by cron2 Change subject: Install host routes for out-of-subnet ifconfig-push addresses when DCO is enabled ...................................................................... Install host routes for out-of-subnet ifconfig-push addresses when DCO is enabled ifconfig-push and ifconfig-ipv6-push can configure the IP address of a client. If this IP address lies inside the network that is configured on the ovpn/tun device this works as expected as the routing table point to the ovpn/tun interface. However, if the IP address is outside that range, the IP packets are not forwarded to the ovpn/tun interface and Linux and FreeBSD DCO implementations need a "connected" route so kernel routing knows that the IP in question is a peer VPN IP. This patch adds logic to add host routes for these ifconfig-push + ifconfig-ipv6-push addresses to ensure that traffic for these IP addresses is also directed to the VPN. For Linux it is important that these extra routes are routes using scope link rather than static since otherwise indirect routes via these IP addresses, like iroute, will not work. On FreeBSD we also use interface routes as that works and routes that target interfaces instead of next-hop IP addresses are less brittle. Tested using a server with ccd: openvpn --server 10.33.0.0 255.255.192.0 --server-ipv6 fd00:f00f::1/64 --client-config-dir ~/ccd [...] and a client with lwipvonpn and the following ccd file: iroute-ipv6 FD00:F00F:CAFE::1001/64 ifconfig-ipv6-push FD00:F00F:D00D::77/64 push "setenv-safe ifconfig_ipv6_local_2 FD00:F00F:CAFE::1001" push "setenv-safe ifconfig_ipv6_netbits_2 64" iroute 10.234.234.0 255.255.255.0 ifconfig-push 10.11.12.13 255.255.255.0 push "setenv-safe ifconfig_local_2 10.234.234.12" push "setenv-safe ifconfig_netmask_2 255.255.255.0" This setups an ifconfig-push addresses outside the --server/--server-ipv6 network and additionally configures a iroute behind that client. The setenv-safe configure lwipovpn to use that additional IP addresses to allow testing via ping. Windows behaves like the user space implementation. It does not require these special routes but instead (like user space) needs static routes to redirect IP traffic for these IP addresses to the tunnel interface. E.g. in the example above the server config needs to have: route 10.234.234.0 255.255.255.0 route 10.11.12.0 255.255.255.0 route-ipv6 FD00:F00F:CAFE::1001/64 route-ipv6 FD00:F00F:D00D::77/64 Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Signed-off-by: Arne Schwabe <ar...@rf...> Acked-by: Gert Doering <ge...@gr...> Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1192 Message-Id: <202...@gr...> URL: https://www.mail-archive.com/ope...@li.../msg33991.html Signed-off-by: Gert Doering <ge...@gr...> --- M doc/man-sections/server-options.rst M src/openvpn/dco.c M src/openvpn/mroute.h M src/openvpn/multi.c M src/openvpn/multi.h 5 files changed, 156 insertions(+), 7 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/15 diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst index 347a251..ade4d41 100644 --- a/doc/man-sections/server-options.rst +++ b/doc/man-sections/server-options.rst @@ -314,6 +314,10 @@ 3. Use ``--ifconfig-pool`` allocation for dynamic IP (last choice). + When DCO is enabled and the IP is not in contained in the network specified + by ``--ifconfig``, OpenVPN will install a /32 host route for the ``local`` + IP address. + --ifconfig-ipv6-push args for ``--client-config-dir`` per-client static IPv6 interface configuration, see ``--client-config-dir`` and ``--ifconfig-push`` for @@ -324,6 +328,10 @@ ifconfig-ipv6-push ipv6addr/bits ipv6remote + When DCO is enabled and the IP is not in contained in the network specified + by ``--ifconfig-ipv6``, OpenVPN will install a /128 host route for the + ``ipv6addr`` IP address. + --multihome Configure a multi-homed UDP server. This option needs to be used when a server has more than one IP address (e.g. multiple interfaces, or diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c index 8fb4662..7abdad3 100644 --- a/src/openvpn/dco.c +++ b/src/openvpn/dco.c @@ -664,6 +664,14 @@ return; } +#if defined(_WIN32) + if (addr->type & MR_ONLINK_DCO_ADDR) + { + /* Windows does not need these extra routes, so we ignore/skip them */ + return; + } +#endif + struct context *c = &mi->context; if (addrtype == MR_ADDR_IPV6) { @@ -671,8 +679,14 @@ dco_win_add_iroute_ipv6(&c->c1.tuntap->dco, addr->v6.addr, addr->netbits, c->c2.tls_multi->peer_id); #else + const struct in6_addr *gateway = &mi->context.c2.push_ifconfig_ipv6_local; + if (addr->type & MR_ONLINK_DCO_ADDR) + { + gateway = NULL; + } + net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, addr->netbits, - &mi->context.c2.push_ifconfig_ipv6_local, c->c1.tuntap->actual_name, 0, + gateway, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC); #endif } @@ -683,7 +697,13 @@ c->c2.tls_multi->peer_id); #else in_addr_t dest = htonl(addr->v4.addr); - net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, &mi->context.c2.push_ifconfig_local, + const in_addr_t *gateway = &mi->context.c2.push_ifconfig_local; + if (addr->type & MR_ONLINK_DCO_ADDR) + { + gateway = NULL; + } + + net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, gateway, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC); #endif } @@ -714,6 +734,20 @@ DCO_IROUTE_METRIC); #endif } + +#if !defined(_WIN32) + /* Check if we added a host route as the assigned client IP address was + * not in the on link scope defined by --ifconfig */ + in_addr_t ifconfig_local = mi->context.c2.push_ifconfig_local; + + if (multi_check_push_ifconfig_extra_route(mi, htonl(ifconfig_local))) + { + /* On windows we do not install these routes, so we also do not need to delete them */ + net_route_v4_del(&m->top.net_ctx, &ifconfig_local, + 32, NULL, c->c1.tuntap->actual_name, 0, + DCO_IROUTE_METRIC); + } +#endif } if (mi->context.c2.push_ifconfig_ipv6_defined) @@ -728,6 +762,18 @@ DCO_IROUTE_METRIC); #endif } + + /* Checked if we added a host route as the assigned client IP address was + * outside the --ifconfig-ipv6 tun interface config */ +#if !defined(_WIN32) + struct in6_addr *dest = &mi->context.c2.push_ifconfig_ipv6_local; + if (multi_check_push_ifconfig_ipv6_extra_route(mi, dest)) + { + /* On windows we do not install these routes, so we also do not need to delete them */ + net_route_v6_del(&m->top.net_ctx, dest, 128, NULL, + c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC); + } +#endif } #endif /* if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32) */ } diff --git a/src/openvpn/mroute.h b/src/openvpn/mroute.h index 5b0c694..afd2e6c 100644 --- a/src/openvpn/mroute.h +++ b/src/openvpn/mroute.h @@ -20,6 +20,7 @@ * with this program; if not, see <https://www.gnu.org/licenses/>. */ + #ifndef MROUTE_H #define MROUTE_H @@ -74,6 +75,9 @@ /* Address type mask indicating that proto # is part of address */ #define MR_WITH_PROTO 32 +/* MRoute is an on link/scope address needed for DCO on Unix platforms */ +#define MR_ONLINK_DCO_ADDR 64 + struct mroute_addr { uint8_t len; /* length of address */ diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 11e4d8c..285671d 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -42,6 +42,7 @@ #include "ssl_ncp.h" #include "vlan.h" #include "auth_token.h" +#include "route.h" #include <inttypes.h> #include <string.h> @@ -1231,11 +1232,18 @@ management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary); } #endif - if (!primary) + if (primary && multi_check_push_ifconfig_extra_route(mi, addr.v4.addr)) { - /* "primary" is the VPN ifconfig address of the peer and already - * known to DCO, so only install "extra" iroutes (primary = false) - */ + /* "primary" is the VPN ifconfig address of the peer */ + /* if it does not fall into the network defined by ifconfig_local + * we install this as extra onscope address on the interface */ + addr.netbits = 32; + addr.type |= MR_ONLINK_DCO_ADDR; + + dco_install_iroute(m, mi, &addr); + } + else if (!primary) + { ASSERT(netbits >= 0); /* DCO requires populated netbits */ dco_install_iroute(m, mi, &addr); } @@ -1269,7 +1277,17 @@ management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary); } #endif - if (!primary) + if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi, &addr.v6.addr)) + { + /* "primary" is the VPN ifconfig address of the peer */ + /* if it does not fall into the network defined by ifconfig_local + * we install this as extra onscope address on the interface */ + addr.netbits = 128; + addr.type |= MR_ONLINK_DCO_ADDR; + + dco_install_iroute(m, mi, &addr); + } + else if (!primary) { /* "primary" is the VPN ifconfig address of the peer and already * known to DCO, so only install "extra" iroutes (primary = false) @@ -4407,3 +4425,49 @@ } } } + +bool +multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest) +{ + struct options *o = &mi->context.options; + in_addr_t local_addr, local_netmask; + + if (!o->ifconfig_local || !o->ifconfig_remote_netmask) + { + /* If we do not have a local address, we just return false as + * this check doesn't make sense. */ + return false; + } + + /* if it falls into the network defined by ifconfig_local we assume + * it is already known to DCO and only install "extra" iroutes */ + inet_pton(AF_INET, o->ifconfig_local, &local_addr); + inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask); + + return (local_addr & local_netmask) != (dest & local_netmask); +} + +bool +multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi, + struct in6_addr *dest) +{ + struct options *o = &mi->context.options; + + if (!o->ifconfig_ipv6_local || !o->ifconfig_ipv6_netbits) + { + /* If we do not have a local address, we just return false as + * this check doesn't make sense. */ + return false; + } + + /* if it falls into the network defined by ifconfig_local we assume + * it is already known to DCO and only install "extra" iroutes */ + struct in6_addr ifconfig_local; + if (inet_pton(AF_INET6, o->ifconfig_ipv6_local, &ifconfig_local) != 1) + { + return false; + } + + return (!ipv6_net_contains_host(&ifconfig_local, o->ifconfig_ipv6_netbits, + dest)); +} \ No newline at end of file diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 50f8d10..a62b07a 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -666,6 +666,33 @@ return ret; } +/** + * Determines if the ifconfig_push_local address falls into the range of the local + * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask) + * + * @param mi The multi-instance to check this condition for + * @param dest The destination IP address to check + * + * @return Returns true if ifconfig_push is outside that range and requires an extra + * route to be installed. + */ +bool +multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest); + +/** + * Determines if the ifconfig_ipv6_local address falls into the range of the local + * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask) + * + * @param mi The multi-instance to check this condition for + * @param dest The destination IPv6 address to check + * + * @return Returns true if ifconfig_push is outside that range and requires an extra + * route to be installed. + */ +bool +multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi, + struct in6_addr *dest); + /* * Check for signals. */ -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email Gerrit-MessageType: newpatchset Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 15 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: d12fk <he...@op...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: openvpn-devel <ope...@li...> | 
| 
      
      
      From: cron2 (C. Review) <ge...@op...> - 2025-10-29 07:53:34
       | 
| cron2 has submitted this change. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes for out-of-subnet ifconfig-push addresses when DCO is enabled ...................................................................... Install host routes for out-of-subnet ifconfig-push addresses when DCO is enabled ifconfig-push and ifconfig-ipv6-push can configure the IP address of a client. If this IP address lies inside the network that is configured on the ovpn/tun device this works as expected as the routing table point to the ovpn/tun interface. However, if the IP address is outside that range, the IP packets are not forwarded to the ovpn/tun interface and Linux and FreeBSD DCO implementations need a "connected" route so kernel routing knows that the IP in question is a peer VPN IP. This patch adds logic to add host routes for these ifconfig-push + ifconfig-ipv6-push addresses to ensure that traffic for these IP addresses is also directed to the VPN. For Linux it is important that these extra routes are routes using scope link rather than static since otherwise indirect routes via these IP addresses, like iroute, will not work. On FreeBSD we also use interface routes as that works and routes that target interfaces instead of next-hop IP addresses are less brittle. Tested using a server with ccd: openvpn --server 10.33.0.0 255.255.192.0 --server-ipv6 fd00:f00f::1/64 --client-config-dir ~/ccd [...] and a client with lwipvonpn and the following ccd file: iroute-ipv6 FD00:F00F:CAFE::1001/64 ifconfig-ipv6-push FD00:F00F:D00D::77/64 push "setenv-safe ifconfig_ipv6_local_2 FD00:F00F:CAFE::1001" push "setenv-safe ifconfig_ipv6_netbits_2 64" iroute 10.234.234.0 255.255.255.0 ifconfig-push 10.11.12.13 255.255.255.0 push "setenv-safe ifconfig_local_2 10.234.234.12" push "setenv-safe ifconfig_netmask_2 255.255.255.0" This setups an ifconfig-push addresses outside the --server/--server-ipv6 network and additionally configures a iroute behind that client. The setenv-safe configure lwipovpn to use that additional IP addresses to allow testing via ping. Windows behaves like the user space implementation. It does not require these special routes but instead (like user space) needs static routes to redirect IP traffic for these IP addresses to the tunnel interface. E.g. in the example above the server config needs to have: route 10.234.234.0 255.255.255.0 route 10.11.12.0 255.255.255.0 route-ipv6 FD00:F00F:CAFE::1001/64 route-ipv6 FD00:F00F:D00D::77/64 Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Signed-off-by: Arne Schwabe <ar...@rf...> Acked-by: Gert Doering <ge...@gr...> Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1192 Message-Id: <202...@gr...> URL: https://www.mail-archive.com/ope...@li.../msg33991.html Signed-off-by: Gert Doering <ge...@gr...> --- M doc/man-sections/server-options.rst M src/openvpn/dco.c M src/openvpn/mroute.h M src/openvpn/multi.c M src/openvpn/multi.h 5 files changed, 156 insertions(+), 7 deletions(-) diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst index 347a251..ade4d41 100644 --- a/doc/man-sections/server-options.rst +++ b/doc/man-sections/server-options.rst @@ -314,6 +314,10 @@ 3. Use ``--ifconfig-pool`` allocation for dynamic IP (last choice). + When DCO is enabled and the IP is not in contained in the network specified + by ``--ifconfig``, OpenVPN will install a /32 host route for the ``local`` + IP address. + --ifconfig-ipv6-push args for ``--client-config-dir`` per-client static IPv6 interface configuration, see ``--client-config-dir`` and ``--ifconfig-push`` for @@ -324,6 +328,10 @@ ifconfig-ipv6-push ipv6addr/bits ipv6remote + When DCO is enabled and the IP is not in contained in the network specified + by ``--ifconfig-ipv6``, OpenVPN will install a /128 host route for the + ``ipv6addr`` IP address. + --multihome Configure a multi-homed UDP server. This option needs to be used when a server has more than one IP address (e.g. multiple interfaces, or diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c index 8fb4662..7abdad3 100644 --- a/src/openvpn/dco.c +++ b/src/openvpn/dco.c @@ -664,6 +664,14 @@ return; } +#if defined(_WIN32) + if (addr->type & MR_ONLINK_DCO_ADDR) + { + /* Windows does not need these extra routes, so we ignore/skip them */ + return; + } +#endif + struct context *c = &mi->context; if (addrtype == MR_ADDR_IPV6) { @@ -671,8 +679,14 @@ dco_win_add_iroute_ipv6(&c->c1.tuntap->dco, addr->v6.addr, addr->netbits, c->c2.tls_multi->peer_id); #else + const struct in6_addr *gateway = &mi->context.c2.push_ifconfig_ipv6_local; + if (addr->type & MR_ONLINK_DCO_ADDR) + { + gateway = NULL; + } + net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, addr->netbits, - &mi->context.c2.push_ifconfig_ipv6_local, c->c1.tuntap->actual_name, 0, + gateway, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC); #endif } @@ -683,7 +697,13 @@ c->c2.tls_multi->peer_id); #else in_addr_t dest = htonl(addr->v4.addr); - net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, &mi->context.c2.push_ifconfig_local, + const in_addr_t *gateway = &mi->context.c2.push_ifconfig_local; + if (addr->type & MR_ONLINK_DCO_ADDR) + { + gateway = NULL; + } + + net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, gateway, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC); #endif } @@ -714,6 +734,20 @@ DCO_IROUTE_METRIC); #endif } + +#if !defined(_WIN32) + /* Check if we added a host route as the assigned client IP address was + * not in the on link scope defined by --ifconfig */ + in_addr_t ifconfig_local = mi->context.c2.push_ifconfig_local; + + if (multi_check_push_ifconfig_extra_route(mi, htonl(ifconfig_local))) + { + /* On windows we do not install these routes, so we also do not need to delete them */ + net_route_v4_del(&m->top.net_ctx, &ifconfig_local, + 32, NULL, c->c1.tuntap->actual_name, 0, + DCO_IROUTE_METRIC); + } +#endif } if (mi->context.c2.push_ifconfig_ipv6_defined) @@ -728,6 +762,18 @@ DCO_IROUTE_METRIC); #endif } + + /* Checked if we added a host route as the assigned client IP address was + * outside the --ifconfig-ipv6 tun interface config */ +#if !defined(_WIN32) + struct in6_addr *dest = &mi->context.c2.push_ifconfig_ipv6_local; + if (multi_check_push_ifconfig_ipv6_extra_route(mi, dest)) + { + /* On windows we do not install these routes, so we also do not need to delete them */ + net_route_v6_del(&m->top.net_ctx, dest, 128, NULL, + c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC); + } +#endif } #endif /* if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32) */ } diff --git a/src/openvpn/mroute.h b/src/openvpn/mroute.h index 5b0c694..afd2e6c 100644 --- a/src/openvpn/mroute.h +++ b/src/openvpn/mroute.h @@ -20,6 +20,7 @@ * with this program; if not, see <https://www.gnu.org/licenses/>. */ + #ifndef MROUTE_H #define MROUTE_H @@ -74,6 +75,9 @@ /* Address type mask indicating that proto # is part of address */ #define MR_WITH_PROTO 32 +/* MRoute is an on link/scope address needed for DCO on Unix platforms */ +#define MR_ONLINK_DCO_ADDR 64 + struct mroute_addr { uint8_t len; /* length of address */ diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 11e4d8c..285671d 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -42,6 +42,7 @@ #include "ssl_ncp.h" #include "vlan.h" #include "auth_token.h" +#include "route.h" #include <inttypes.h> #include <string.h> @@ -1231,11 +1232,18 @@ management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary); } #endif - if (!primary) + if (primary && multi_check_push_ifconfig_extra_route(mi, addr.v4.addr)) { - /* "primary" is the VPN ifconfig address of the peer and already - * known to DCO, so only install "extra" iroutes (primary = false) - */ + /* "primary" is the VPN ifconfig address of the peer */ + /* if it does not fall into the network defined by ifconfig_local + * we install this as extra onscope address on the interface */ + addr.netbits = 32; + addr.type |= MR_ONLINK_DCO_ADDR; + + dco_install_iroute(m, mi, &addr); + } + else if (!primary) + { ASSERT(netbits >= 0); /* DCO requires populated netbits */ dco_install_iroute(m, mi, &addr); } @@ -1269,7 +1277,17 @@ management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary); } #endif - if (!primary) + if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi, &addr.v6.addr)) + { + /* "primary" is the VPN ifconfig address of the peer */ + /* if it does not fall into the network defined by ifconfig_local + * we install this as extra onscope address on the interface */ + addr.netbits = 128; + addr.type |= MR_ONLINK_DCO_ADDR; + + dco_install_iroute(m, mi, &addr); + } + else if (!primary) { /* "primary" is the VPN ifconfig address of the peer and already * known to DCO, so only install "extra" iroutes (primary = false) @@ -4407,3 +4425,49 @@ } } } + +bool +multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest) +{ + struct options *o = &mi->context.options; + in_addr_t local_addr, local_netmask; + + if (!o->ifconfig_local || !o->ifconfig_remote_netmask) + { + /* If we do not have a local address, we just return false as + * this check doesn't make sense. */ + return false; + } + + /* if it falls into the network defined by ifconfig_local we assume + * it is already known to DCO and only install "extra" iroutes */ + inet_pton(AF_INET, o->ifconfig_local, &local_addr); + inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask); + + return (local_addr & local_netmask) != (dest & local_netmask); +} + +bool +multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi, + struct in6_addr *dest) +{ + struct options *o = &mi->context.options; + + if (!o->ifconfig_ipv6_local || !o->ifconfig_ipv6_netbits) + { + /* If we do not have a local address, we just return false as + * this check doesn't make sense. */ + return false; + } + + /* if it falls into the network defined by ifconfig_local we assume + * it is already known to DCO and only install "extra" iroutes */ + struct in6_addr ifconfig_local; + if (inet_pton(AF_INET6, o->ifconfig_ipv6_local, &ifconfig_local) != 1) + { + return false; + } + + return (!ipv6_net_contains_host(&ifconfig_local, o->ifconfig_ipv6_netbits, + dest)); +} \ No newline at end of file diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 50f8d10..a62b07a 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -666,6 +666,33 @@ return ret; } +/** + * Determines if the ifconfig_push_local address falls into the range of the local + * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask) + * + * @param mi The multi-instance to check this condition for + * @param dest The destination IP address to check + * + * @return Returns true if ifconfig_push is outside that range and requires an extra + * route to be installed. + */ +bool +multi_check_push_ifconfig_extra_route(struct multi_instance *mi, in_addr_t dest); + +/** + * Determines if the ifconfig_ipv6_local address falls into the range of the local + * IP addresses of the VPN interface (ifconfig_local with ifconfig_remote_netmask) + * + * @param mi The multi-instance to check this condition for + * @param dest The destination IPv6 address to check + * + * @return Returns true if ifconfig_push is outside that range and requires an extra + * route to be installed. + */ +bool +multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi, + struct in6_addr *dest); + /* * Check for signals. */ -- To view, visit http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email Gerrit-MessageType: merged Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 15 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: cron2 <ge...@gr...> Gerrit-Reviewer: d12fk <he...@op...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: openvpn-devel <ope...@li...> |