| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-09-15 17:39:19
       | 
| Attention is currently required from: flichtenheld.
Hello flichtenheld,
I'd like you to do a code review.
Please visit
    http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email
to review the following change.
Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes
......................................................................
Install host routes with onlink scope iroutes for ifconfig-push routes
Additional IP addresses for hosts that lie outside the primary network
of the configured device need to be added to the operating system to
ensure that traffic for these IP addresses is also directed to the VPN.
For Linux it is import that these extra routes are routes with scope link
rather than static since otherwise routes via these IP addresses, like
iroute, will not work.
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, 153 insertions(+), 5 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/1
diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst
index ccc1374..bb58330 100644
--- a/doc/man-sections/server-options.rst
+++ b/doc/man-sections/server-options.rst
@@ -314,6 +314,9 @@
   3.  Use ``--ifconfig-pool`` allocation for dynamic IP (last
       choice).
 
+  When DCO is enabled, OpenVPN will install a /32 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
diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
index 70a8c0a..8280fbb 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -670,8 +670,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_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
     }
@@ -682,7 +688,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_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
     }
@@ -713,6 +725,18 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Check if we added the local network as /32 route as it was not in
+         * the normal on link scope */
+        if (multi_check_push_ifconfig_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv4(&c->c1.tuntap->dco, push_ifconfig_local, 32);
+#else
+            net_route_v4_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_local, 32,
+                             NULL, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
+#endif
+        }
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -727,6 +751,15 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+        if (multi_check_push_ifconfig_ipv6_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv6(&c->c1.tuntap->dco, &mi->context.c2.push_ifconfig_ipv6_local, 128);
+#else
+            net_route_v6_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_ipv6_local, 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..f4e54ca 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  ather than a route */
+#define MR_ONLINK_ADDR 64
+
 struct mroute_addr
 {
     uint8_t len;     /* length of address */
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 6e4cc42..a9c753e 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>
 
@@ -1240,11 +1241,26 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
+    if (primary && multi_check_push_ifconfig_extra_route(mi))
+    {
+        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned to the interface of the DCO device */
+        msg(D_MULTI_LOW, "MULTI: Adding /32 on-link route for route %s -> %s",
+            print_in_addr_t(remote_si.addr.in4.sin_addr.s_addr, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 32;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
+
     if (!primary)
     {
-        /* "primary" is the VPN ifconfig address of the peer and already
-         * known to DCO, so only install "extra" iroutes (primary = false)
-         */
         ASSERT(netbits >= 0); /* DCO requires populated netbits */
         dco_install_iroute(m, mi, &addr);
     }
@@ -1278,6 +1294,24 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
+    if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi))
+    {
+        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned to the interface of the DCO device */
+        msg(D_MULTI_LOW, "MULTI: Adding /129 on-link route for "
+                         "ifconfig-ipv6-push %s -> %s",
+            print_in6_addr(mi->context.options.push_ifconfig_ipv6_local, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 128;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
     if (!primary)
     {
         /* "primary" is the VPN ifconfig address of the peer and already
@@ -4309,3 +4343,57 @@
      *  }
      */
 }
+
+/**
+ * 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)
+ *
+ * @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)
+{
+    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  */
+    in_addr_t push_ifconfig_local = htonl(mi->context.c2.push_ifconfig_local);
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (push_ifconfig_local & local_netmask);
+}
+
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi)
+{
+    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 (!route_ipv6_match_host(&ifconfig_local, o->ifconfig_ipv6_netbits,
+                                   &o->push_ifconfig_ipv6_local));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 087c0e6..5ec3904 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -666,6 +666,26 @@
     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)
+ *
+ * @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);
+
+/**
+ * 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);
+
 /*
  * 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
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Gerrit-Change-Number: 1192
Gerrit-PatchSet: 1
Gerrit-Owner: plaisthos <arn...@rf...>
Gerrit-Reviewer: flichtenheld <fr...@li...>
Gerrit-CC: openvpn-devel <ope...@li...>
Gerrit-Attention: flichtenheld <fr...@li...>
Gerrit-MessageType: newchange
 | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-09-15 17:41:02
       | 
| Attention is currently required from: flichtenheld. plaisthos has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes ...................................................................... Patch Set 1: (1 comment) Patchset: PS1: Currently only tested on Linux. Need to still test the other DCO platforms. -- 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 Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 1 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Comment-Date: Mon, 15 Sep 2025 17:40:48 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Gerrit-MessageType: comment | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-09-15 17:45:02
       | 
| Attention is currently required from: flichtenheld.
Hello flichtenheld, 
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 (#2).
Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes
......................................................................
Install host routes with onlink scope iroutes for ifconfig-push routes
Additional IP addresses for hosts that lie outside the primary network
of the configured device need to be added to the operating system 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 with scope link
rather than static since otherwise routes via these IP addresses, like
iroute, will not work.
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, 153 insertions(+), 5 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/2
diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst
index ccc1374..bb58330 100644
--- a/doc/man-sections/server-options.rst
+++ b/doc/man-sections/server-options.rst
@@ -314,6 +314,9 @@
   3.  Use ``--ifconfig-pool`` allocation for dynamic IP (last
       choice).
 
+  When DCO is enabled, OpenVPN will install a /32 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
diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
index 70a8c0a..8280fbb 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -670,8 +670,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_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
     }
@@ -682,7 +688,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_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
     }
@@ -713,6 +725,18 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Check if we added the local network as /32 route as it was not in
+         * the normal on link scope */
+        if (multi_check_push_ifconfig_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv4(&c->c1.tuntap->dco, push_ifconfig_local, 32);
+#else
+            net_route_v4_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_local, 32,
+                             NULL, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
+#endif
+        }
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -727,6 +751,15 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+        if (multi_check_push_ifconfig_ipv6_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv6(&c->c1.tuntap->dco, &mi->context.c2.push_ifconfig_ipv6_local, 128);
+#else
+            net_route_v6_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_ipv6_local, 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..f4e54ca 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  ather than a route */
+#define MR_ONLINK_ADDR 64
+
 struct mroute_addr
 {
     uint8_t len;     /* length of address */
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 6e4cc42..a9c753e 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>
 
@@ -1240,11 +1241,26 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
+    if (primary && multi_check_push_ifconfig_extra_route(mi))
+    {
+        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned to the interface of the DCO device */
+        msg(D_MULTI_LOW, "MULTI: Adding /32 on-link route for route %s -> %s",
+            print_in_addr_t(remote_si.addr.in4.sin_addr.s_addr, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 32;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
+
     if (!primary)
     {
-        /* "primary" is the VPN ifconfig address of the peer and already
-         * known to DCO, so only install "extra" iroutes (primary = false)
-         */
         ASSERT(netbits >= 0); /* DCO requires populated netbits */
         dco_install_iroute(m, mi, &addr);
     }
@@ -1278,6 +1294,24 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
+    if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi))
+    {
+        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned to the interface of the DCO device */
+        msg(D_MULTI_LOW, "MULTI: Adding /129 on-link route for "
+                         "ifconfig-ipv6-push %s -> %s",
+            print_in6_addr(mi->context.options.push_ifconfig_ipv6_local, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 128;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
     if (!primary)
     {
         /* "primary" is the VPN ifconfig address of the peer and already
@@ -4309,3 +4343,57 @@
      *  }
      */
 }
+
+/**
+ * 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)
+ *
+ * @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)
+{
+    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  */
+    in_addr_t push_ifconfig_local = htonl(mi->context.c2.push_ifconfig_local);
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (push_ifconfig_local & local_netmask);
+}
+
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi)
+{
+    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 (!route_ipv6_match_host(&ifconfig_local, o->ifconfig_ipv6_netbits,
+                                   &o->push_ifconfig_ipv6_local));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 087c0e6..5ec3904 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -666,6 +666,26 @@
     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)
+ *
+ * @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);
+
+/**
+ * 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);
+
 /*
  * 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
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Gerrit-Change-Number: 1192
Gerrit-PatchSet: 2
Gerrit-Owner: plaisthos <arn...@rf...>
Gerrit-Reviewer: flichtenheld <fr...@li...>
Gerrit-CC: openvpn-devel <ope...@li...>
Gerrit-Attention: flichtenheld <fr...@li...>
Gerrit-MessageType: newpatchset
 | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-09-16 13:50:14
       | 
| Attention is currently required from: flichtenheld.
Hello flichtenheld, 
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 (#3).
Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes
......................................................................
Install host routes with onlink scope iroutes for ifconfig-push routes
Additional IP addresses for hosts that lie outside the primary network
of the configured device need to be added to the operating system 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.
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"
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(+), 5 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/3
diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst
index ccc1374..3cd84dd 100644
--- a/doc/man-sections/server-options.rst
+++ b/doc/man-sections/server-options.rst
@@ -314,6 +314,9 @@
   3.  Use ``--ifconfig-pool`` allocation for dynamic IP (last
       choice).
 
+  When DCO is enabled, OpenVPN will install a /32 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 +327,9 @@
 
      ifconfig-ipv6-push ipv6addr/bits ipv6remote
 
+  When DCO is enabled, OpenVPN will install a /128 route for the local 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 70a8c0a..bbcda1d 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -670,8 +670,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_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
     }
@@ -682,7 +688,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_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
     }
@@ -713,6 +725,18 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Check if we added the local network as /32 route as it was not in
+         * the normal on link scope */
+        if (multi_check_push_ifconfig_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv4(&c->c1.tuntap->dco, mi->context.c2.push_ifconfig_local, 32);
+#else
+            net_route_v4_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_local, 32,
+                             NULL, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
+#endif
+        }
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -727,6 +751,15 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+        if (multi_check_push_ifconfig_ipv6_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv6(&c->c1.tuntap->dco, mi->context.c2.push_ifconfig_ipv6_local, 128);
+#else
+            net_route_v6_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_ipv6_local, 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..f4e54ca 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  ather than a route */
+#define MR_ONLINK_ADDR 64
+
 struct mroute_addr
 {
     uint8_t len;     /* length of address */
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 6e4cc42..efbaa22 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>
 
@@ -1240,11 +1241,26 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
+    if (primary && multi_check_push_ifconfig_extra_route(mi))
+    {
+        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned to the interface of the DCO device */
+        msg(D_MULTI_LOW, "MULTI: Adding /32 on-link route for route %s -> %s",
+            print_in_addr_t(remote_si.addr.in4.sin_addr.s_addr, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 32;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
+
     if (!primary)
     {
-        /* "primary" is the VPN ifconfig address of the peer and already
-         * known to DCO, so only install "extra" iroutes (primary = false)
-         */
         ASSERT(netbits >= 0); /* DCO requires populated netbits */
         dco_install_iroute(m, mi, &addr);
     }
@@ -1278,6 +1294,24 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
+    if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi))
+    {
+        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned to the interface of the DCO device */
+        msg(D_MULTI_LOW, "MULTI: Adding /128 on-link route for "
+                         "ifconfig-ipv6-push %s -> %s",
+            print_in6_addr(mi->context.options.push_ifconfig_ipv6_local, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 128;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
     if (!primary)
     {
         /* "primary" is the VPN ifconfig address of the peer and already
@@ -4309,3 +4343,57 @@
      *  }
      */
 }
+
+/**
+ * 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)
+ *
+ * @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)
+{
+    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  */
+    in_addr_t push_ifconfig_local = htonl(mi->context.c2.push_ifconfig_local);
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (push_ifconfig_local & local_netmask);
+}
+
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi)
+{
+    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 (!route_ipv6_match_host(&ifconfig_local, o->ifconfig_ipv6_netbits,
+                                   &o->push_ifconfig_ipv6_local));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 087c0e6..5ec3904 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -666,6 +666,26 @@
     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)
+ *
+ * @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);
+
+/**
+ * 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);
+
 /*
  * 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
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Gerrit-Change-Number: 1192
Gerrit-PatchSet: 3
Gerrit-Owner: plaisthos <arn...@rf...>
Gerrit-Reviewer: flichtenheld <fr...@li...>
Gerrit-CC: openvpn-devel <ope...@li...>
Gerrit-Attention: flichtenheld <fr...@li...>
Gerrit-MessageType: newpatchset
 | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-09-16 14:35:42
       | 
| Attention is currently required from: flichtenheld. plaisthos has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes ...................................................................... Patch Set 3: (1 comment) Patchset: PS1: > Currently only tested on Linux. Need to still test the other DCO platforms. Linux and FreeBSD work fine. Windows testing still pending. -- 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 Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 3 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Comment-Date: Tue, 16 Sep 2025 14:35:32 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: plaisthos <arn...@rf...> Gerrit-MessageType: comment | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-09-16 14:35:55
       | 
| Attention is currently required from: flichtenheld.
Hello flichtenheld, 
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 (#4).
Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes
......................................................................
Install host routes with onlink scope iroutes for ifconfig-push routes
Additional IP addresses for hosts that lie outside the primary network
of the configured device need to be added to the operating system 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.
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"
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(+), 5 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/4
diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst
index ccc1374..3cd84dd 100644
--- a/doc/man-sections/server-options.rst
+++ b/doc/man-sections/server-options.rst
@@ -314,6 +314,9 @@
   3.  Use ``--ifconfig-pool`` allocation for dynamic IP (last
       choice).
 
+  When DCO is enabled, OpenVPN will install a /32 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 +327,9 @@
 
      ifconfig-ipv6-push ipv6addr/bits ipv6remote
 
+  When DCO is enabled, OpenVPN will install a /128 route for the local 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 70a8c0a..bbcda1d 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -670,8 +670,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_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
     }
@@ -682,7 +688,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_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
     }
@@ -713,6 +725,18 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Check if we added the local network as /32 route as it was not in
+         * the normal on link scope */
+        if (multi_check_push_ifconfig_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv4(&c->c1.tuntap->dco, mi->context.c2.push_ifconfig_local, 32);
+#else
+            net_route_v4_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_local, 32,
+                             NULL, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
+#endif
+        }
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -727,6 +751,15 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+        if (multi_check_push_ifconfig_ipv6_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv6(&c->c1.tuntap->dco, mi->context.c2.push_ifconfig_ipv6_local, 128);
+#else
+            net_route_v6_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_ipv6_local, 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..852ccb6 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 rather than a route */
+#define MR_ONLINK_ADDR 64
+
 struct mroute_addr
 {
     uint8_t len;     /* length of address */
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 6e4cc42..efbaa22 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>
 
@@ -1240,11 +1241,26 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
+    if (primary && multi_check_push_ifconfig_extra_route(mi))
+    {
+        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned to the interface of the DCO device */
+        msg(D_MULTI_LOW, "MULTI: Adding /32 on-link route for route %s -> %s",
+            print_in_addr_t(remote_si.addr.in4.sin_addr.s_addr, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 32;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
+
     if (!primary)
     {
-        /* "primary" is the VPN ifconfig address of the peer and already
-         * known to DCO, so only install "extra" iroutes (primary = false)
-         */
         ASSERT(netbits >= 0); /* DCO requires populated netbits */
         dco_install_iroute(m, mi, &addr);
     }
@@ -1278,6 +1294,24 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
+    if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi))
+    {
+        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned to the interface of the DCO device */
+        msg(D_MULTI_LOW, "MULTI: Adding /128 on-link route for "
+                         "ifconfig-ipv6-push %s -> %s",
+            print_in6_addr(mi->context.options.push_ifconfig_ipv6_local, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 128;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
     if (!primary)
     {
         /* "primary" is the VPN ifconfig address of the peer and already
@@ -4309,3 +4343,57 @@
      *  }
      */
 }
+
+/**
+ * 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)
+ *
+ * @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)
+{
+    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  */
+    in_addr_t push_ifconfig_local = htonl(mi->context.c2.push_ifconfig_local);
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (push_ifconfig_local & local_netmask);
+}
+
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi)
+{
+    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 (!route_ipv6_match_host(&ifconfig_local, o->ifconfig_ipv6_netbits,
+                                   &o->push_ifconfig_ipv6_local));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 087c0e6..5ec3904 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -666,6 +666,26 @@
     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)
+ *
+ * @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);
+
+/**
+ * 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);
+
 /*
  * 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
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Gerrit-Change-Number: 1192
Gerrit-PatchSet: 4
Gerrit-Owner: plaisthos <arn...@rf...>
Gerrit-Reviewer: flichtenheld <fr...@li...>
Gerrit-CC: openvpn-devel <ope...@li...>
Gerrit-Attention: flichtenheld <fr...@li...>
Gerrit-MessageType: newpatchset
 | 
| 
      
      
      From: d12fk (C. Review) <ge...@op...> - 2025-09-16 20:52:11
       | 
| Attention is currently required from: flichtenheld, plaisthos. d12fk has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes ...................................................................... Patch Set 2: (3 comments) File doc/man-sections/server-options.rst: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/3599a99a_f568303b : PS2, Line 317: When DCO is enabled, OpenVPN will install a /32 route for the local IP Should this also be documented for `ifconfig-ipv6-push`? File src/openvpn/mroute.h: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/dca75073_98b8eff0 : PS2, Line 78: /* MRoute is an on link/scope address ather than a route */ Typo r`ather` File src/openvpn/multi.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/b815c5df_9c118436 : PS2, Line 4347: /** Doxygen is already in the header file -- 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 Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 2 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-CC: d12fk <he...@op...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Comment-Date: Tue, 16 Sep 2025 20:52:02 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Gerrit-MessageType: comment | 
| 
      
      
      From: ordex (C. Review) <ge...@op...> - 2025-09-16 21:19:54
       | 
| Attention is currently required from: flichtenheld, plaisthos. ordex has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes ...................................................................... Patch Set 4: Code-Review+1 (12 comments) Commit Message: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/6b060a08_b9b15038 : PS4, Line 7: Install host routes with onlink scope iroutes for ifconfig-push routes to be honest, this title feels a bit cryptic 😄 maybe it can be simplified a little, since we have the longer description below? http://gerrit.openvpn.net/c/openvpn/+/1192/comment/955bcbc5_de33bffb : PS4, Line 10: of the configured device need to be added to the operating system to the subject for "need" is "Additional IP addresses", but I think you meant "a route to those IPs", right? So this sentence should be massaged a little. Moreover, is `ifconfig-push` really creating an "Additional IP" or is it just substituting the IP assigned out of the server pool? http://gerrit.openvpn.net/c/openvpn/+/1192/comment/9e07abea_3c127d4d : 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 to the VPN interface" (some something alike)? Just to make sure in 5 months from now we recall why we did this. Patchset: PS4: patch looks good to me. I just have a few minor nitpicks.. File doc/man-sections/server-options.rst: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/a86c4865_793f1207 : PS4, Line 318: address s/for/to/ s/ /./ http://gerrit.openvpn.net/c/openvpn/+/1192/comment/bff8681a_b8c91a88 : PS4, Line 330: When DCO is enabled, OpenVPN will install a /128 route for the local IP s/for/to/ File src/openvpn/dco.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/d9ae49b2_0ce9bbcd : PS4, Line 729: /* Check if we added the local network as /32 route as it was not in should we call it "client network", as "local" seems to be something local to the server File src/openvpn/multi.h: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/c67d489a_5aa0fbc8 : PS4, Line 672: * forgot to document "mi" File src/openvpn/multi.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/d08232ec_d390068e : PS4, Line 1251: msg(D_MULTI_LOW, "MULTI: Adding /32 on-link route for route %s -> %s", s/for route/for ifconfig-push/ ? (like you wrote in the ipv6 case) http://gerrit.openvpn.net/c/openvpn/+/1192/comment/bed1bbae_c70f9021 : PS4, Line 1262: if (!primary) for clarity sake, shouldn't this be an "else if"? >From a functional perspective it shouldn't make a difference. http://gerrit.openvpn.net/c/openvpn/+/1192/comment/8760fe93_46153291 : PS4, Line 1315: if (!primary) same http://gerrit.openvpn.net/c/openvpn/+/1192/comment/4a7ec029_5c48f9ed : PS4, Line 4353: */ I think we normally put the doxygen in one place only (i.e. header file), so this can go. -- 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 Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 4 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: d12fk <he...@op...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Comment-Date: Tue, 16 Sep 2025 21:19:39 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: Yes Gerrit-MessageType: comment | 
| 
      
      
      From: ordex (C. Review) <ge...@op...> - 2025-09-16 21:20:46
       | 
| Attention is currently required from: d12fk, flichtenheld, plaisthos. ordex has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes ...................................................................... Patch Set 4: (1 comment) File doc/man-sections/server-options.rst: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/a7b6d431_9cacb904 : PS2, Line 317: When DCO is enabled, OpenVPN will install a /32 route for the local IP > Should this also be documented for `ifconfig-ipv6-push`? @he...@op... it's just a few lines below -- 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 Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 4 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: d12fk <he...@op...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: d12fk <he...@op...> Gerrit-Comment-Date: Tue, 16 Sep 2025 21:20:33 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: d12fk <he...@op...> Gerrit-MessageType: comment | 
| 
      
      
      From: d12fk (C. Review) <ge...@op...> - 2025-09-16 21:40:19
       | 
| Attention is currently required from: flichtenheld, ordex, plaisthos. d12fk has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes ...................................................................... Patch Set 4: (2 comments) File doc/man-sections/server-options.rst: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/a01854be_05971411 : PS2, Line 317: When DCO is enabled, OpenVPN will install a /32 route for the local IP > @heiko@openvpn. […] Yeah, was looking at an old version. File src/openvpn/mroute.h: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/8364575a_c2f0e56d : PS2, Line 78: /* MRoute is an on link/scope address ather than a route */ > Typo r`ather` 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 Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 4 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: d12fk <he...@op...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: ordex <an...@ma...> Gerrit-Comment-Date: Tue, 16 Sep 2025 21:40:03 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: ordex <an...@ma...> Comment-In-Reply-To: d12fk <he...@op...> Gerrit-MessageType: comment | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-09-17 10:19:56
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex. plaisthos has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes ...................................................................... Patch Set 4: (13 comments) Commit Message: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/db3aa800_00ffb55b : PS4, Line 7: Install host routes with onlink scope iroutes for ifconfig-push routes > to be honest, this title feels a bit cryptic 😄 […] I tried to make it a bit easier to understand what is happening here. http://gerrit.openvpn.net/c/openvpn/+/1192/comment/8599ca43_fae0473b : PS4, Line 10: of the configured device need to be added to the operating system to > the subject for "need" is "Additional IP addresses", but I think you meant "a route to those IPs", r […] ifconfig-push can be used for both. To either pick a specific IP address of the pool or to specify an address outside of the pool. But pool is not really correct here either as we really care about --ifconfig here and not about --ifconfig-pool but --server typically sets to be the same. File doc/man-sections/server-options.rst: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/86c25c1c_10f45ae8 : PS2, Line 317: When DCO is enabled, OpenVPN will install a /32 route for the local IP > Should this also be documented for `ifconfig-ipv6-push`? I already documented. You seem to have commented on version 2 after I uploaded v4. File doc/man-sections/server-options.rst: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/17f06b05_a6c4c15f : PS4, Line 318: address > s/for/to/ […] Done http://gerrit.openvpn.net/c/openvpn/+/1192/comment/6b55d068_07d64a50 : PS4, Line 330: When DCO is enabled, OpenVPN will install a /128 route for the local IP > s/for/to/ Done File src/openvpn/dco.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/b545dc09_22feb536 : PS4, Line 729: /* Check if we added the local network as /32 route as it was not in > should we call it "client network", as "local" seems to be something local to the server Done File src/openvpn/mroute.h: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/442f986d_d8c6b1d1 : PS2, Line 78: /* MRoute is an on link/scope address ather than a route */ > Typo r`ather` fixed already in v4. File src/openvpn/multi.h: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/413eed33_c08b5e0d : PS4, Line 672: * > forgot to document "mi" Done File src/openvpn/multi.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/f087988d_68fbfd53 : PS2, Line 4347: /** > Doxygen is already in the header file Done File src/openvpn/multi.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/8e1814c1_f9785b38 : PS4, Line 1251: msg(D_MULTI_LOW, "MULTI: Adding /32 on-link route for route %s -> %s", > s/for route/for ifconfig-push/ ? (like you wrote in the ipv6 case) Done http://gerrit.openvpn.net/c/openvpn/+/1192/comment/8e6958ef_74bd7374 : PS4, Line 1262: if (!primary) > for clarity sake, shouldn't this be an "else if"? […] Done http://gerrit.openvpn.net/c/openvpn/+/1192/comment/4d8bd6f0_6be3546b : PS4, Line 1315: if (!primary) > same Done http://gerrit.openvpn.net/c/openvpn/+/1192/comment/de75b4bc_4f184632 : PS4, Line 4353: */ > I think we normally put the doxygen in one place only (i.e. header file), so this can go. 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 Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 4 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: d12fk <he...@op...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: ordex <an...@ma...> Gerrit-Attention: d12fk <he...@op...> Gerrit-Comment-Date: Wed, 17 Sep 2025 10:19:40 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: ordex <an...@ma...> Comment-In-Reply-To: d12fk <he...@op...> Gerrit-MessageType: comment | 
| 
      
      
      From: ordex (C. Review) <ge...@op...> - 2025-09-17 11:01:54
       | 
| Attention is currently required from: d12fk, flichtenheld, plaisthos. ordex has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes with onlink scope iroutes for ifconfig-push routes ...................................................................... Patch Set 4: (3 comments) Commit Message: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/e68f700d_fb964762 : PS4, Line 7: Install host routes with onlink scope iroutes for ifconfig-push routes > I tried to make it a bit easier to understand what is happening here. how about: DCO: install onlink host iroute when pushed IP is out of server network ? http://gerrit.openvpn.net/c/openvpn/+/1192/comment/e81bb1a9_d227e995 : PS4, Line 10: of the configured device need to be added to the operating system to > ifconfig-push can be used for both. […] I agree with that, but what I meant in my comment is that we "need *routes* to be added" not IPs. In your text you're implying we need to add IPs "..to the operating system.." File doc/man-sections/server-options.rst: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/c74c5864_8023ffde : PS2, Line 317: When DCO is enabled, OpenVPN will install a /32 route for the local IP > I already documented. You seem to have commented on version 2 after I uploaded v4. 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 Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 4 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: d12fk <he...@op...> Gerrit-CC: openvpn-devel <ope...@li...> Gerrit-Attention: plaisthos <arn...@rf...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: d12fk <he...@op...> Gerrit-Comment-Date: Wed, 17 Sep 2025 11:01:39 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: plaisthos <arn...@rf...> Comment-In-Reply-To: ordex <an...@ma...> Comment-In-Reply-To: d12fk <he...@op...> Gerrit-MessageType: comment | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-09-17 13:22:25
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex, plaisthos.
Hello 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 (#5).
The following approvals got outdated and were removed:
Code-Review+1 by ordex
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.
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, 157 insertions(+), 7 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/5
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 70a8c0a..e533d65 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -670,8 +670,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_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
     }
@@ -682,7 +688,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_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
     }
@@ -713,6 +725,18 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Check if we added a host route as the assigned client IP address was
+         * not in the on link scope defined by --ifconfig */
+        if (multi_check_push_ifconfig_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv4(&c->c1.tuntap->dco, mi->context.c2.push_ifconfig_local, 32);
+#else
+            net_route_v4_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_local, 32,
+                             NULL, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
+#endif
+        }
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -727,6 +751,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 (multi_check_push_ifconfig_ipv6_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv6(&c->c1.tuntap->dco, mi->context.c2.push_ifconfig_ipv6_local, 128);
+#else
+            net_route_v6_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_ipv6_local, 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..852ccb6 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 rather than a route */
+#define MR_ONLINK_ADDR 64
+
 struct mroute_addr
 {
     uint8_t len;     /* length of address */
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 6e4cc42..43ed4be 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>
 
@@ -1240,11 +1241,26 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_extra_route(mi))
     {
-        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned inside the network of
+         * the DCO interface configured by --ifconfig */
+        msg(D_MULTI_LOW, "MULTI: Adding /32 on-link route for route %s -> %s",
+            print_in_addr_t(remote_si.addr.in4.sin_addr.s_addr, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 32;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
+    else if (!primary)
+    {
         ASSERT(netbits >= 0); /* DCO requires populated netbits */
         dco_install_iroute(m, mi, &addr);
     }
@@ -1278,7 +1294,25 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi))
+    {
+        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned to the interface of the DCO device */
+        msg(D_MULTI_LOW, "MULTI: Adding /128 on-link route for "
+                         "ifconfig-ipv6-push %s -> %s",
+            print_in6_addr(mi->context.options.push_ifconfig_ipv6_local, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 128;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
+    else if (!primary)
     {
         /* "primary" is the VPN ifconfig address of the peer and already
          * known to DCO, so only install "extra" iroutes (primary = false)
@@ -4309,3 +4343,49 @@
      *  }
      */
 }
+
+bool
+multi_check_push_ifconfig_extra_route(struct multi_instance *mi)
+{
+    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  */
+    in_addr_t push_ifconfig_local = htonl(mi->context.c2.push_ifconfig_local);
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (push_ifconfig_local & local_netmask);
+}
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi)
+{
+    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,
+                                    &o->push_ifconfig_ipv6_local));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 087c0e6..bc58d38 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -666,6 +666,28 @@
     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);
+
+/**
+ * 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);
+
 /*
  * 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
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Gerrit-Change-Number: 1192
Gerrit-PatchSet: 5
Gerrit-Owner: plaisthos <arn...@rf...>
Gerrit-Reviewer: flichtenheld <fr...@li...>
Gerrit-Reviewer: ordex <an...@ma...>
Gerrit-CC: d12fk <he...@op...>
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-MessageType: newpatchset
 | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-09-17 13:23:35
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex, plaisthos.
Hello 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 (#6).
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.
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, 165 insertions(+), 7 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/6
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 70a8c0a..aae0b23 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -663,6 +663,14 @@
         return;
     }
 
+#if defined(_WIN32)
+    if (addr->type & MR_ONLINK_ADDR)
+    {
+        msg(D_MULTI_ERRORS, "Adding extra interface routes on Windows is "
+                            "currently not supported.");
+    }
+#endif
+
     struct context *c = &mi->context;
     if (addrtype == MR_ADDR_IPV6)
     {
@@ -670,8 +678,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_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
     }
@@ -682,7 +696,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_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
     }
@@ -713,6 +733,18 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Check if we added a host route as the assigned client IP address was
+         * not in the on link scope defined by --ifconfig */
+        if (multi_check_push_ifconfig_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv4(&c->c1.tuntap->dco, mi->context.c2.push_ifconfig_local, 32);
+#else
+            net_route_v4_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_local, 32,
+                             NULL, c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
+#endif
+        }
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -727,6 +759,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 (multi_check_push_ifconfig_ipv6_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv6(&c->c1.tuntap->dco, mi->context.c2.push_ifconfig_ipv6_local, 128);
+#else
+            net_route_v6_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_ipv6_local, 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..852ccb6 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 rather than a route */
+#define MR_ONLINK_ADDR 64
+
 struct mroute_addr
 {
     uint8_t len;     /* length of address */
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 6e4cc42..43ed4be 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>
 
@@ -1240,11 +1241,26 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_extra_route(mi))
     {
-        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned inside the network of
+         * the DCO interface configured by --ifconfig */
+        msg(D_MULTI_LOW, "MULTI: Adding /32 on-link route for route %s -> %s",
+            print_in_addr_t(remote_si.addr.in4.sin_addr.s_addr, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 32;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
+    else if (!primary)
+    {
         ASSERT(netbits >= 0); /* DCO requires populated netbits */
         dco_install_iroute(m, mi, &addr);
     }
@@ -1278,7 +1294,25 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_ipv6_extra_route(mi))
+    {
+        /* "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  */
+        struct gc_arena gc = gc_new();
+        /* The IP address is not assigned to the interface of the DCO device */
+        msg(D_MULTI_LOW, "MULTI: Adding /128 on-link route for "
+                         "ifconfig-ipv6-push %s -> %s",
+            print_in6_addr(mi->context.options.push_ifconfig_ipv6_local, 0, &gc),
+            multi_instance_string(mi, false, &gc));
+
+        addr.netbits = 128;
+        addr.type |= MR_ONLINK_ADDR;
+
+        dco_install_iroute(m, mi, &addr);
+        gc_free(&gc);
+    }
+    else if (!primary)
     {
         /* "primary" is the VPN ifconfig address of the peer and already
          * known to DCO, so only install "extra" iroutes (primary = false)
@@ -4309,3 +4343,49 @@
      *  }
      */
 }
+
+bool
+multi_check_push_ifconfig_extra_route(struct multi_instance *mi)
+{
+    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  */
+    in_addr_t push_ifconfig_local = htonl(mi->context.c2.push_ifconfig_local);
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (push_ifconfig_local & local_netmask);
+}
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi)
+{
+    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,
+                                    &o->push_ifconfig_ipv6_local));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 087c0e6..bc58d38 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -666,6 +666,28 @@
     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);
+
+/**
+ * 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);
+
 /*
  * 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
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Gerrit-Change-Number: 1192
Gerrit-PatchSet: 6
Gerrit-Owner: plaisthos <arn...@rf...>
Gerrit-Reviewer: flichtenheld <fr...@li...>
Gerrit-Reviewer: ordex <an...@ma...>
Gerrit-CC: d12fk <he...@op...>
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-MessageType: newpatchset
 | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-09-17 13:57:09
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex, plaisthos.
Hello 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 (#7).
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, 147 insertions(+), 7 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/7
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 70a8c0a..4b79ef6 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -663,6 +663,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)
     {
@@ -670,8 +678,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_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
     }
@@ -682,7 +696,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_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
     }
@@ -713,6 +733,16 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Check if we added a host route as the assigned client IP address was
+         * not in the on link scope defined by --ifconfig */
+        if (multi_check_push_ifconfig_extra_route(mi))
+        {
+#if !defined(_WIN32)
+            /* On windows we do not install these routes, so we also do not need to delete them */
+            dco_win_del_iroute_ipv4(&c->c1.tuntap->dco, mi->context.c2.push_ifconfig_local, 32);
+#endif
+        }
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -727,6 +757,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 (multi_check_push_ifconfig_ipv6_extra_route(mi))
+        {
+#if defined(_WIN32)
+            dco_win_del_iroute_ipv6(&c->c1.tuntap->dco, mi->context.c2.push_ifconfig_ipv6_local, 128);
+#else
+            net_route_v6_del(&m->top.net_ctx, &mi->context.c2.push_ifconfig_ipv6_local, 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 6e4cc42..ebc8768 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>
 
@@ -1240,11 +1241,18 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_extra_route(mi))
     {
-        /* "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);
     }
@@ -1278,7 +1286,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))
+    {
+        /* "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)
@@ -4309,3 +4327,49 @@
      *  }
      */
 }
+
+bool
+multi_check_push_ifconfig_extra_route(struct multi_instance *mi)
+{
+    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  */
+    in_addr_t push_ifconfig_local = htonl(mi->context.c2.push_ifconfig_local);
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (push_ifconfig_local & local_netmask);
+}
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi)
+{
+    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,
+                                    &o->push_ifconfig_ipv6_local));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 087c0e6..bc58d38 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -666,6 +666,28 @@
     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);
+
+/**
+ * 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);
+
 /*
  * 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
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff
Gerrit-Change-Number: 1192
Gerrit-PatchSet: 7
Gerrit-Owner: plaisthos <arn...@rf...>
Gerrit-Reviewer: flichtenheld <fr...@li...>
Gerrit-Reviewer: ordex <an...@ma...>
Gerrit-CC: d12fk <he...@op...>
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-MessageType: newpatchset
 | 
| 
      
      
      From: d12fk (C. Review) <ge...@op...> - 2025-09-18 03:14:57
       | 
| Attention is currently required from: d12fk, flichtenheld, ordex, plaisthos. d12fk has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes for ifconfig-push routes when DCO is enabled ...................................................................... Patch Set 4: (1 comment) File src/openvpn/multi.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/6c873af0_913f1b52 : PS2, Line 4347: /** > Doxygen is already in the header file 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 Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 4 Gerrit-Owner: plaisthos <arn...@rf...> Gerrit-Reviewer: flichtenheld <fr...@li...> Gerrit-Reviewer: ordex <an...@ma...> Gerrit-CC: d12fk <he...@op...> 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: Thu, 18 Sep 2025 03:14:42 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: d12fk <he...@op...> Gerrit-MessageType: comment | 
| 
      
      
      From: d12fk (C. Review) <ge...@op...> - 2025-09-18 03:16:33
       | 
| Attention is currently required from: flichtenheld, ordex, plaisthos. d12fk has posted comments on this change. ( http://gerrit.openvpn.net/c/openvpn/+/1192?usp=email ) Change subject: Install host routes for ifconfig-push routes when DCO is enabled ...................................................................... Patch Set 7: Code-Review+1 -- 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 Gerrit-Project: openvpn Gerrit-Branch: master Gerrit-Change-Id: I83295e00d1a756dfa44050b0a4493095fb050fff Gerrit-Change-Number: 1192 Gerrit-PatchSet: 7 Gerrit-Owner: plaisthos <arn...@rf...> 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-Comment-Date: Thu, 18 Sep 2025 03:16:17 +0000 Gerrit-HasComments: No Gerrit-Has-Labels: Yes Gerrit-MessageType: comment | 
| 
      
      
      From: cron2 (C. Review) <ge...@op...> - 2025-10-12 12:55:27
       | 
| Attention is currently required from: 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 8: Code-Review-2 (5 comments) Patchset: PS8: this got broken during review / respin... conceptually I have nothing to complain, but the MR_ONLINK_ADDR vs. MR_ONLINK_DCO_ADDR needs to be fixed. Admittedly I do not understand why it compiles at all (!?)... ``` $ git grep MR_ONLINK src/openvpn/dco.c: if (addr->type & MR_ONLINK_DCO_ADDR) src/openvpn/dco.c: if (addr->type & MR_ONLINK_ADDR) src/openvpn/dco.c: if (addr->type & MR_ONLINK_ADDR) src/openvpn/mroute.h:#define MR_ONLINK_DCO_ADDR 64 src/openvpn/multi.c: addr.type |= MR_ONLINK_DCO_ADDR; src/openvpn/multi.c: addr.type |= MR_ONLINK_DCO_ADDR; ``` so MR_ONLINK_ADDR is referenced, but not defined anywhere - and it still compiles. Huh. File src/openvpn/dco.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/785dfadb_41bb07c6?usp=email : PS8, Line 686: This got broken in patch iterations - initially everything was `MR_ONLINK_ADDR` and then the define got changed to `MR_ONLINK_DCO_ADDR` but the "not win32" platform was not adjusted? http://gerrit.openvpn.net/c/openvpn/+/1192/comment/c26431d1_ead0937a?usp=email : PS8, Line 700: if (addr->type & MR_ONLINK_ADDR) _DCO_ http://gerrit.openvpn.net/c/openvpn/+/1192/comment/b9ffbe40_0e0e1234?usp=email : PS8, Line 745: } this looks wrong. The !not WIN32 code calls `dco_win_`? http://gerrit.openvpn.net/c/openvpn/+/1192/comment/6e19a3ae_10206ebe?usp=email : PS8, Line 771: } This is asymmetric to IPv4 - I guess this is from early tests, and the `dco_win_del...()` think needs to go out? -- 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: 8 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-Comment-Date: Sun, 12 Oct 2025 12:55:11 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: Yes | 
| 
      
      
      From: cron2 (C. Review) <ge...@op...> - 2025-10-12 12:59:49
       | 
| Attention is currently required from: 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 8: (1 comment) Patchset: PS8: Ah, it compiles because that particular test instance had `configure --disable-dco`. If configured without that, compilation bombs in the expected way ``` CC dco.o dco.c: In function 'dco_install_iroute': dco.c:683:26: error: 'MR_ONLINK_ADDR' undeclared (first use in this function); did you mean 'MR_ONLINK_DCO_ADDR'? 683 | if (addr->type & MR_ONLINK_ADDR) | ^~~~~~~~~~~~~~ | MR_ONLINK_DCO_ADDR dco.c:683:26: note: each undeclared identifier is reported only once for each function it appears in dco.c: In function 'dco_delete_iroutes': dco.c:744:13: error: implicit declaration of function 'dco_win_del_iroute_ipv4' [-Wimplicit-function-declaration] 744 | dco_win_del_iroute_ipv4(&c->c1.tuntap->dco, mi->context.c2.push_ifconfig_local, 32); | ^~~~~~~~~~~~~~~~~~~~~~~ ``` -- 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: 8 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-Comment-Date: Sun, 12 Oct 2025 12:59:34 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-10-13 14:24:12
       | 
| Attention is currently required from: cron2, flichtenheld, ordex. plaisthos 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 8: (5 comments) Patchset: PS8: > this got broken during review / respin... […] Yeah not really sure what happend there during my rebase. I am going through the patch again to see that it really works. File src/openvpn/dco.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/1d9fd552_43ebda64?usp=email : PS8, Line 686: > This got broken in patch iterations - initially everything was `MR_ONLINK_ADDR` and then the define […] Acknowledged http://gerrit.openvpn.net/c/openvpn/+/1192/comment/394a8910_234e7857?usp=email : PS8, Line 700: if (addr->type & MR_ONLINK_ADDR) > _DCO_ Done http://gerrit.openvpn.net/c/openvpn/+/1192/comment/d3123254_400e84c7?usp=email : PS8, Line 745: } > this looks wrong. […] Done http://gerrit.openvpn.net/c/openvpn/+/1192/comment/fba68590_cdaa0d71?usp=email : PS8, Line 771: } > This is asymmetric to IPv4 - I guess this is from early tests, and the `dco_win_del... […] Acknowledged -- 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: 8 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: cron2 <ge...@gr...> Gerrit-Attention: flichtenheld <fr...@li...> Gerrit-Attention: ordex <an...@ma...> Gerrit-Comment-Date: Mon, 13 Oct 2025 14:23:56 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: cron2 <ge...@gr...> | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-10-13 14:24:30
       | 
| Attention is currently required from: cron2, d12fk, flichtenheld, ordex.
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 (#9).
The following approvals got outdated and were removed:
Code-Review+1 by d12fk
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, 148 insertions(+), 7 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/9
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..f49d4ce 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,18 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Check if we added a host route as the assigned client IP address was
+         * not in the on link scope defined by --ifconfig */
+        if (multi_check_push_ifconfig_extra_route(mi))
+        {
+#if !defined(_WIN32)
+            /* 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, &mi->context.c2.push_ifconfig_local,
+                             32, NULL, c->c1.tuntap->actual_name, 0,
+                             DCO_IROUTE_METRIC);
+#endif
+        }
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -728,6 +760,17 @@
                              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 (multi_check_push_ifconfig_ipv6_extra_route(mi))
+        {
+#if defined(_WIN32)
+            /* 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, &mi->context.c2.push_ifconfig_ipv6_local, 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 2863ff1..4eac1d6 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>
 
@@ -1239,11 +1240,18 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_extra_route(mi))
     {
-        /* "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);
     }
@@ -1277,7 +1285,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))
+    {
+        /* "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)
@@ -4312,3 +4330,49 @@
      *  }
      */
 }
+
+bool
+multi_check_push_ifconfig_extra_route(struct multi_instance *mi)
+{
+    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  */
+    in_addr_t push_ifconfig_local = htonl(mi->context.c2.push_ifconfig_local);
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (push_ifconfig_local & local_netmask);
+}
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi)
+{
+    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,
+                                    &o->push_ifconfig_ipv6_local));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index b2b892b..cb9b27b 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -666,6 +666,28 @@
     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);
+
+/**
+ * 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);
+
 /*
  * 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: 9
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: cron2 <ge...@gr...>
Gerrit-Attention: flichtenheld <fr...@li...>
Gerrit-Attention: ordex <an...@ma...>
Gerrit-Attention: d12fk <he...@op...>
 | 
| 
      
      
      From: plaisthos (C. Review) <ge...@op...> - 2025-10-14 09:28:58
       | 
| Attention is currently required from: cron2, d12fk, flichtenheld, ordex.
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 (#10).
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, 148 insertions(+), 7 deletions(-)
  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/92/1192/10
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..a2b64da 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,18 @@
                              DCO_IROUTE_METRIC);
 #endif
         }
+
+        /* Check if we added a host route as the assigned client IP address was
+         * not in the on link scope defined by --ifconfig */
+        if (multi_check_push_ifconfig_extra_route(mi))
+        {
+#if !defined(_WIN32)
+            /* 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, &mi->context.c2.push_ifconfig_local,
+                             32, NULL, c->c1.tuntap->actual_name, 0,
+                             DCO_IROUTE_METRIC);
+#endif
+        }
     }
 
     if (mi->context.c2.push_ifconfig_ipv6_defined)
@@ -728,6 +760,17 @@
                              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 (multi_check_push_ifconfig_ipv6_extra_route(mi))
+        {
+#if !defined(_WIN32)
+            /* 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, &mi->context.c2.push_ifconfig_ipv6_local, 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 2863ff1..4eac1d6 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>
 
@@ -1239,11 +1240,18 @@
         management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
     }
 #endif
-    if (!primary)
+    if (primary && multi_check_push_ifconfig_extra_route(mi))
     {
-        /* "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);
     }
@@ -1277,7 +1285,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))
+    {
+        /* "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)
@@ -4312,3 +4330,49 @@
      *  }
      */
 }
+
+bool
+multi_check_push_ifconfig_extra_route(struct multi_instance *mi)
+{
+    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  */
+    in_addr_t push_ifconfig_local = htonl(mi->context.c2.push_ifconfig_local);
+    inet_pton(AF_INET, o->ifconfig_local, &local_addr);
+    inet_pton(AF_INET, o->ifconfig_remote_netmask, &local_netmask);
+
+    return (local_addr & local_netmask) != (push_ifconfig_local & local_netmask);
+}
+
+bool
+multi_check_push_ifconfig_ipv6_extra_route(struct multi_instance *mi)
+{
+    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,
+                                    &o->push_ifconfig_ipv6_local));
+}
\ No newline at end of file
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index b2b892b..cb9b27b 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -666,6 +666,28 @@
     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);
+
+/**
+ * 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);
+
 /*
  * 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: 10
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: cron2 <ge...@gr...>
Gerrit-Attention: flichtenheld <fr...@li...>
Gerrit-Attention: ordex <an...@ma...>
Gerrit-Attention: d12fk <he...@op...>
 | 
| 
      
      
      From: cron2 (C. Review) <ge...@op...> - 2025-10-18 21:18:55
       | 
| 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 11: Code-Review-2 (1 comment) Patchset: PS11: This is not working right yet. I am testing on FreeBSD/DCO, but the "do I need this route" logic should not make a difference. I've built a testbed to create `ifconfig-push` or `ifconfig-ipv6-push` at will (from the client side) and integrate that in t_client.sh - and noticed that I am always seeing IPv6 /128s added ``` /sbin/route -6 add -net fd00:abcd:114:2::1001/128 -iface tun0 -fib 0 ``` ... for a client that just got a pool address ``` # grep ^server tun-udp-p2mp/server.conf server 10.114.2.0 255.255.255.0 server-ipv6 fd00:abcd:114:2::/64 ``` will investigate more tomorrow, but maybe you can find it over night? ;-) -- 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: 11 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: Sat, 18 Oct 2025 21:18:40 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: Yes | 
| 
      
      
      From: cron2 (C. Review) <ge...@op...> - 2025-10-19 09:49:49
       | 
| 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 11: (1 comment) File src/openvpn/multi.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/55223ae1_ab140e86?usp=email : PS11, Line 4377: &o->push_ifconfig_ipv6_local)); Ah, here we go. This fails if there is *no* `ifconfig-ipv6-push` in the config, as then we have ``` (gdb) print o->push_ifconfig_ipv6_local $10 = {__u6_addr = {__u6_addr8 = '\000' <repeats 15 times>, __u6_addr16 = {0, 0, 0, 0, 0, 0, 0, 0}, __u6_addr32 = {0, 0, 0, 0}}} ``` ... and that obviously does not match. >From reading the code in `dco_delete_iroutes()`, this looks as if the check should be on `mi->context.c2.push_ifconfig_local` and not `mi->context.options.push_ifconfig_ipv6_local`? ... later... yep, this works: ``` struct context_2 *c2 = &mi->context.c2; return (!ipv6_net_contains_host(&ifconfig_local, o->ifconfig_ipv6_netbits, &c2->push_ifconfig_ipv6_local)); ``` -- 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: 11 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: Sun, 19 Oct 2025 09:49:39 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No | 
| 
      
      
      From: cron2 (C. Review) <ge...@op...> - 2025-10-19 09:56:45
       | 
| 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 11: (1 comment) File src/openvpn/multi.c: http://gerrit.openvpn.net/c/openvpn/+/1192/comment/e8420035_d33ff056?usp=email : PS11, Line 4377: &o->push_ifconfig_ipv6_local)); > Ah, here we go. This fails if there is *no* `ifconfig-ipv6-push` in the config, as then we have […] Maybe it would also be an idea to change `multi_check_push_ifconfig_ipv6_extra_route()` to not be passed a `mi` but the actual route we want to check? Both callers know what IPv6 address they want to be looked at (a6 / &mi->context.c2.push_ifconfig_ipv6_local) and actually passing *that* address would certainly make the code easier to understand... (For symmetry reasons, the same argument could be made for IPv4, of course) -- 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: 11 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: Sun, 19 Oct 2025 09:56:30 +0000 Gerrit-HasComments: Yes Gerrit-Has-Labels: No Comment-In-Reply-To: cron2 <ge...@gr...> |