[Openvpn-devel,v3] dco: track installed iroutes separately for IPv4 and IPv6

Message ID 20261114175853.14185-1-gert@greenie.muc.de
State New
Headers
Series [Openvpn-devel,v3] dco: track installed iroutes separately for IPv4 and IPv6 |

Commit Message

Gert Doering Nov. 14, 2026, 5:58 p.m. UTC
  From: Marco Baffo <marco@mandelbit.com>

Track successful route installation per address family and split route
deletion into IPv4 and IPv6 helpers. Update delayed-exit cleanup.
Return installation status from the Windows helpers and keep the
non-DCO declarations compatible.

Change-Id: Ic0b8b0a3249eb41e1de10f381932c1161cd75270
Signed-off-by: Marco Baffo <marco@mandelbit.com>
Acked-by: Razvan Cojocaru <razvanc@mailbox.org>
Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/2061
---

This change was reviewed on Gerrit and approved by at least one
developer. I request to merge it to master.

Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/2061
This mail reflects revision 3 of this Change.

Acked-by according to Gerrit (reflected above):
Razvan Cojocaru <razvanc@mailbox.org>
  

Patch

diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
index f3b5d44..f807002 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -706,8 +706,8 @@ 
     if (addrtype == MR_ADDR_IPV6)
     {
 #if defined(_WIN32)
-        dco_win_add_iroute_ipv6(&c->c1.tuntap->dco, addr->v6.addr, addr->netbits,
-                                c->c2.tls_multi->rx_peer_id);
+        bool installed = dco_win_add_iroute_ipv6(&c->c1.tuntap->dco, addr->v6.addr, addr->netbits,
+                                                 c->c2.tls_multi->rx_peer_id);
 #else
         const struct in6_addr *gateway = &mi->context.c2.push_ifconfig_ipv6_local;
         if (addr->type & MR_ONLINK_DCO_ADDR)
@@ -715,16 +715,18 @@ 
             gateway = NULL;
         }
 
-        net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, addr->netbits,
-                         gateway, c->c1.tuntap->actual_name, 0,
-                         DCO_IROUTE_METRIC);
+        bool installed = net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, addr->netbits,
+                                          gateway, c->c1.tuntap->actual_name, 0,
+                                          DCO_IROUTE_METRIC)
+                         == 0;
 #endif
+        mi->context.did_dco_iroutes_v6 |= installed;
     }
     else if (addrtype == MR_ADDR_IPV4)
     {
 #if defined(_WIN32)
-        dco_win_add_iroute_ipv4(&c->c1.tuntap->dco, addr->v4.addr, addr->netbits,
-                                c->c2.tls_multi->rx_peer_id);
+        bool installed = dco_win_add_iroute_ipv4(&c->c1.tuntap->dco, addr->v4.addr, addr->netbits,
+                                                 c->c2.tls_multi->rx_peer_id);
 #else
         in_addr_t dest = htonl(addr->v4.addr);
         const in_addr_t *gateway = &mi->context.c2.push_ifconfig_local;
@@ -733,15 +735,17 @@ 
             gateway = NULL;
         }
 
-        net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, gateway,
-                         c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC);
+        bool installed = net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits, gateway,
+                                          c->c1.tuntap->actual_name, 0, DCO_IROUTE_METRIC)
+                         == 0;
 #endif
+        mi->context.did_dco_iroutes_v4 |= installed;
     }
 #endif /* if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32) */
 }
 
 void
-dco_delete_iroutes(openvpn_net_ctx_t *net_ctx, const struct context *c)
+dco_delete_iroutes_v4(openvpn_net_ctx_t *net_ctx, const struct context *c)
 {
 #if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32)
     if (!dco_enabled(&c->options))
@@ -750,7 +754,7 @@ 
     }
     ASSERT(TUNNEL_TYPE(c->c1.tuntap) == DEV_TYPE_TUN);
 
-    msg(D_DCO, "DCO: attempt removing iroutes from system table");
+    msg(D_DCO, "DCO: attempt removing IPv4 iroutes from system table");
 
     if (c->c2.push_ifconfig_defined)
     {
@@ -778,6 +782,20 @@ 
         }
 #endif
     }
+#endif /* if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32) */
+}
+
+void
+dco_delete_iroutes_v6(openvpn_net_ctx_t *net_ctx, const struct context *c)
+{
+#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(_WIN32)
+    if (!dco_enabled(&c->options))
+    {
+        return;
+    }
+    ASSERT(TUNNEL_TYPE(c->c1.tuntap) == DEV_TYPE_TUN);
+
+    msg(D_DCO, "DCO: attempt removing IPv6 iroutes from system table");
 
     if (c->c2.push_ifconfig_ipv6_defined)
     {
diff --git a/src/openvpn/dco.h b/src/openvpn/dco.h
index c435f8c..ce44331 100644
--- a/src/openvpn/dco.h
+++ b/src/openvpn/dco.h
@@ -218,12 +218,20 @@ 
                         const struct mroute_addr *addr);
 
 /**
- * Remove all routes added through the specified client
+ * Remove all IPv4 routes added through the specified client
  *
  * @param net_ctx   the iface networking context
  * @param c         the client context for which routes have to be removed
  */
-void dco_delete_iroutes(openvpn_net_ctx_t *net_ctx, const struct context *c);
+void dco_delete_iroutes_v4(openvpn_net_ctx_t *net_ctx, const struct context *c);
+
+/**
+ * Remove all IPv6 routes added through the specified client
+ *
+ * @param net_ctx   the iface networking context
+ * @param c         the client context for which routes have to be removed
+ */
+void dco_delete_iroutes_v6(openvpn_net_ctx_t *net_ctx, const struct context *c);
 
 /**
  * Update traffic statistics for all peers
@@ -356,12 +364,17 @@ 
 }
 
 static inline void
-dco_install_iroute(struct multi_context *m, struct multi_instance *mi, struct mroute_addr *addr)
+dco_install_iroute(struct multi_context *m, struct multi_instance *mi, const struct mroute_addr *addr)
 {
 }
 
 static inline void
-dco_delete_iroutes(openvpn_net_ctx_t *net_ctx, const struct context *c)
+dco_delete_iroutes_v4(openvpn_net_ctx_t *net_ctx, const struct context *c)
+{
+}
+
+static inline void
+dco_delete_iroutes_v6(openvpn_net_ctx_t *net_ctx, const struct context *c)
 {
 }
 
diff --git a/src/openvpn/dco_win.c b/src/openvpn/dco_win.c
index 12b0666..68ae7c5 100644
--- a/src/openvpn/dco_win.c
+++ b/src/openvpn/dco_win.c
@@ -1007,7 +1007,7 @@ 
     return dco_get_version(&ver) && ver.Major >= 2;
 }
 
-void
+bool
 dco_win_add_iroute_ipv4(dco_context_t *dco, in_addr_t dst, unsigned int netbits,
                         unsigned int peer_id)
 {
@@ -1021,16 +1021,18 @@ 
         netbits, peer_id);
 
     DWORD bytes_returned = 0;
-    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_MP_ADD_IROUTE, &route, sizeof(route), NULL, 0,
-                         &bytes_returned, NULL))
+    bool installed = DeviceIoControl(dco->tt->hand, OVPN_IOCTL_MP_ADD_IROUTE, &route, sizeof(route), NULL, 0,
+                                     &bytes_returned, NULL);
+    if (!installed)
     {
         msg(M_WARN | M_ERRNO, "DeviceIoControl(OVPN_IOCTL_MP_ADD_IROUTE) failed");
     }
 
     gc_free(&gc);
+    return installed;
 }
 
-void
+bool
 dco_win_add_iroute_ipv6(dco_context_t *dco, struct in6_addr dst, unsigned int netbits,
                         unsigned int peer_id)
 {
@@ -1042,13 +1044,15 @@ 
         netbits, peer_id);
 
     DWORD bytes_returned = 0;
-    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_MP_ADD_IROUTE, &route, sizeof(route), NULL, 0,
-                         &bytes_returned, NULL))
+    bool installed = DeviceIoControl(dco->tt->hand, OVPN_IOCTL_MP_ADD_IROUTE, &route, sizeof(route), NULL, 0,
+                                     &bytes_returned, NULL);
+    if (!installed)
     {
         msg(M_WARN | M_ERRNO, "DeviceIoControl(OVPN_IOCTL_MP_ADD_IROUTE) failed");
     }
 
     gc_free(&gc);
+    return installed;
 }
 
 void
diff --git a/src/openvpn/dco_win.h b/src/openvpn/dco_win.h
index a92e5ad..28795b7 100644
--- a/src/openvpn/dco_win.h
+++ b/src/openvpn/dco_win.h
@@ -67,10 +67,10 @@ 
 
 bool dco_win_supports_multipeer(void);
 
-void dco_win_add_iroute_ipv4(dco_context_t *dco, in_addr_t dst, unsigned int netbits,
+bool dco_win_add_iroute_ipv4(dco_context_t *dco, in_addr_t dst, unsigned int netbits,
                              unsigned int peer_id);
 
-void dco_win_add_iroute_ipv6(dco_context_t *dco, struct in6_addr dst, unsigned int netbits,
+bool dco_win_add_iroute_ipv6(dco_context_t *dco, struct in6_addr dst, unsigned int netbits,
                              unsigned int peer_id);
 
 void dco_win_del_iroute_ipv4(dco_context_t *dco, in_addr_t dst, unsigned int netbits);
diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index eb41d03..6ef480e 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -555,10 +555,17 @@ 
      * last connecting client claiming the iroutes takes ownership. Therefore
      * they are not removed during delayed cleanup.
      */
-    if (c->did_dco_iroutes)
+
+    if (c->did_dco_iroutes_v4)
     {
-        c->did_dco_iroutes = false;
-        dco_delete_iroutes(&c->net_ctx, c);
+        c->did_dco_iroutes_v4 = false;
+        dco_delete_iroutes_v4(&c->net_ctx, c);
+    }
+
+    if (c->did_dco_iroutes_v6)
+    {
+        c->did_dco_iroutes_v6 = false;
+        dco_delete_iroutes_v6(&c->net_ctx, c);
     }
 
     tls_set_single_session(c->c2.tls_multi);
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 0027550..a850e72 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -479,6 +479,28 @@ 
     set_prefix(mi);
 }
 
+static void
+multi_del_dco_iroutes_v4(struct multi_context *m, struct multi_instance *mi)
+{
+    /* Only remove DCO IPv4 iroutes this instance installed. */
+    if (mi->context.did_dco_iroutes_v4)
+    {
+        mi->context.did_dco_iroutes_v4 = false;
+        dco_delete_iroutes_v4(&m->top.net_ctx, &mi->context);
+    }
+}
+
+static void
+multi_del_dco_iroutes_v6(struct multi_context *m, struct multi_instance *mi)
+{
+    /* Only remove DCO IPv6 iroutes this instance installed. */
+    if (mi->context.did_dco_iroutes_v6)
+    {
+        mi->context.did_dco_iroutes_v6 = false;
+        dco_delete_iroutes_v6(&m->top.net_ctx, &mi->context);
+    }
+}
+
 /*
  * Tell the route helper about deleted iroutes so
  * that it can update its mask of currently used
@@ -490,12 +512,8 @@ 
     const struct iroute *ir;
     const struct iroute_ipv6 *ir6;
 
-    /* check if DCO iroutes were already removed when scheduling a delayed exit */
-    if (mi->context.did_dco_iroutes)
-    {
-        mi->context.did_dco_iroutes = false;
-        dco_delete_iroutes(&m->top.net_ctx, &mi->context);
-    }
+    multi_del_dco_iroutes_v4(m, mi);
+    multi_del_dco_iroutes_v6(m, mi);
 
     if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
     {
@@ -1309,8 +1327,6 @@ 
     if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
     {
         mi->did_iroutes = true;
-        /* multi_learn_in{6}_addr_t takes care of installing the DCO iroute */
-        mi->context.did_dco_iroutes = true;
         for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next)
         {
             if (ir->netbits >= 0)
diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h
index cdd02b6..72436fe 100644
--- a/src/openvpn/openvpn.h
+++ b/src/openvpn/openvpn.h
@@ -507,7 +507,8 @@ 
     bool did_we_daemonize;       /**< Whether demonization has already
                                   *   taken place. */
 
-    bool did_dco_iroutes;        /**< Whether DCO iroutes have been installed */
+    bool did_dco_iroutes_v4;     /**< Whether DCO IPv4 iroutes have been installed */
+    bool did_dco_iroutes_v6;     /**< Whether DCO IPv6 iroutes have been installed */
 
     struct context_persist persist;
     /**< Persistent %context. */