]> git.ipfire.org Git - thirdparty/openvpn.git/commitdiff
dco: don't use NetLink to exchange control packets
authorAntonio Quartulli <a@unstable.cc>
Thu, 9 Mar 2023 21:03:44 +0000 (22:03 +0100)
committerGert Doering <gert@greenie.muc.de>
Mon, 13 Mar 2023 10:27:45 +0000 (11:27 +0100)
Using NetLink for control messages did not work out as it did lead to
kernel side buffer congestion during heavy client activity.

With this patch DCO will redirect control packets directly to the
transport socket without altering them, so that userspace can
happily process them as usual.

NOTE: this is an API breaking change.  Up to this commit, the userland
requests a kernel module called "ovpn-dco" which does control messages
via netlink.  From this commit on, OpenVPN requests a kernel module named
"ovpn-dco-v2" which brings the kernel change corresponding to this commit.

If the system only has "the wrong module" available (either way), OpenVPN
will log

   ... Kernel support for ovpn-dco missing, disabling data channel offload.

and proceed without kernel support.

Change-Id: Ia1297c3ae9a28b188ed21ad21ae96fff3d02ee4d
[lev@openvpn.net: ensure win_dco flag is still exposed]
Signed-off-by: Antonio Quartulli <a@unstable.cc>
Acked-by: Arne Schwabe <arne@rfc2549.org>
Message-Id: <20230309210344.5763-1-a@unstable.cc>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg26384.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
14 files changed:
src/openvpn/dco.c
src/openvpn/dco.h
src/openvpn/dco_freebsd.c
src/openvpn/dco_freebsd.h
src/openvpn/dco_linux.c
src/openvpn/dco_linux.h
src/openvpn/dco_win.c
src/openvpn/forward.c
src/openvpn/init.c
src/openvpn/mtcp.c
src/openvpn/multi.c
src/openvpn/ovpn_dco_linux.h
src/openvpn/socket.c
src/openvpn/socket.h

index b53332a8777a93057448f5887c887bb134dcf53d..308578b4703c8b836bf3a00f24bc7e1534ea60cf 100644 (file)
@@ -485,7 +485,6 @@ dco_p2p_add_new_peer(struct context *c)
     }
 
     c->c2.tls_multi->dco_peer_id = multi->peer_id;
-    c->c2.link_socket->dco_installed = true;
 
     return 0;
 }
@@ -605,17 +604,6 @@ dco_multi_add_new_peer(struct multi_context *m, struct multi_instance *mi)
 
     c->c2.tls_multi->dco_peer_id = peer_id;
 
-    if (c->mode == CM_CHILD_TCP)
-    {
-        multi_tcp_dereference_instance(m->mtcp, mi);
-        if (close(sd))
-        {
-            msg(D_DCO|M_ERRNO, "error closing TCP socket after DCO handover");
-        }
-        c->c2.link_socket->dco_installed = true;
-        c->c2.link_socket->sd = SOCKET_UNDEFINED;
-    }
-
     return 0;
 }
 
index 18a9d78be6f451850d2d0ccea6818698e3117997..2fe671bf6fcdeea8ecec8fb03831d6fbb6c6e811 100644 (file)
@@ -127,15 +127,6 @@ void close_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx);
  */
 int dco_do_read(dco_context_t *dco);
 
-/**
- * Write data to the DCO communication channel (control packet expected)
- *
- * @param dco       the DCO context
- * @param peer_id   the ID of the peer to send the data to
- * @param buf       the buffer containing the data to send
- */
-int dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf);
-
 /**
  * Install a DCO in the main event loop
  */
@@ -301,13 +292,6 @@ dco_do_read(dco_context_t *dco)
     return 0;
 }
 
-static inline int
-dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf)
-{
-    ASSERT(false);
-    return 0;
-}
-
 static inline void
 dco_event_set(dco_context_t *dco, struct event_set *es, void *arg)
 {
index cd4083c4968b3cb6b5d469bd86048ea20067283c..92de5f04b1d412c0ee4f7f6957558511e18671d7 100644 (file)
@@ -142,7 +142,6 @@ open_fd(dco_context_t *dco)
     {
         dco->open = true;
     }
-    dco->dco_packet_in = alloc_buf(PAGE_SIZE);
 
     return dco->fd;
 }
@@ -560,15 +559,6 @@ dco_do_read(dco_context_t *dco)
     return 0;
 }
 
-int
-dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf)
-{
-    /* Control packets are passed through the socket, so this should never get
-     * called. See should_use_dco_socket(). */
-    ASSERT(0);
-    return -EINVAL;
-}
-
 bool
 dco_available(int msglevel)
 {
index 970beca0e74d008b6d803f81922128d53ac2c1af..a07f9b69fc98d6eeb62c8222d056aab7469069fb 100644 (file)
@@ -51,8 +51,6 @@ typedef struct dco_context {
 
     char ifname[IFNAMSIZ];
 
-    struct buffer dco_packet_in;
-
     int dco_message_type;
     int dco_message_peer_id;
     int dco_del_peer_reason;
index 3ca5b50be99a5284ec572aecde1ea11a411d3b50..2b349529fff2c37ac5577038650fc45667f8c0ca 100644 (file)
@@ -436,24 +436,6 @@ ovpn_dco_register(dco_context_t *dco)
     {
         msg(M_ERR, "%s: failed to join groups: %d", __func__, ret);
     }
-
-    /* Register for non-data packets that ovpn-dco may receive. They will be
-     * forwarded to userspace
-     */
-    struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_REGISTER_PACKET);
-    if (!nl_msg)
-    {
-        msg(M_ERR, "%s: cannot allocate message to register for control packets",
-            __func__);
-    }
-
-    ret = ovpn_nl_msg_send(dco, nl_msg, NULL, __func__);
-    if (ret)
-    {
-        msg(M_ERR, "%s: failed to register for control packets: %d", __func__,
-            ret);
-    }
-    nlmsg_free(nl_msg);
 }
 
 int
@@ -476,8 +458,6 @@ open_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev)
     }
 
     tt->actual_name = string_alloc(dev, NULL);
-    uint8_t *dcobuf = malloc(65536);
-    buf_set_write(&tt->dco.dco_packet_in, dcobuf, 65536);
     tt->dco.dco_message_peer_id = -1;
 
     ovpn_dco_register(&tt->dco);
@@ -492,7 +472,6 @@ close_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 
     net_iface_del(ctx, tt->actual_name);
     ovpn_dco_uninit_netlink(&tt->dco);
-    free(tt->dco.dco_packet_in.data);
 }
 
 int
@@ -823,51 +802,6 @@ ovpn_handle_msg(struct nl_msg *msg, void *arg)
             break;
         }
 
-        case OVPN_CMD_PACKET:
-        {
-            if (!attrs[OVPN_ATTR_PACKET])
-            {
-                msg(D_DCO, "ovpn-dco: no packet in OVPN_CMD_PACKET message");
-                return NL_SKIP;
-            }
-            struct nlattr *pkt_attrs[OVPN_PACKET_ATTR_MAX + 1];
-
-            if (nla_parse_nested(pkt_attrs, OVPN_PACKET_ATTR_MAX,
-                                 attrs[OVPN_ATTR_PACKET], NULL))
-            {
-                msg(D_DCO, "received bogus cmd packet data from ovpn-dco");
-                return NL_SKIP;
-            }
-            if (!pkt_attrs[OVPN_PACKET_ATTR_PEER_ID])
-            {
-                msg(D_DCO, "ovpn-dco: Received OVPN_CMD_PACKET message without peer id");
-                return NL_SKIP;
-            }
-            if (!pkt_attrs[OVPN_PACKET_ATTR_PACKET])
-            {
-                msg(D_DCO, "ovpn-dco: Received OVPN_CMD_PACKET message without packet");
-                return NL_SKIP;
-            }
-
-            unsigned int peerid = nla_get_u32(pkt_attrs[OVPN_PACKET_ATTR_PEER_ID]);
-
-            uint8_t *data = nla_data(pkt_attrs[OVPN_PACKET_ATTR_PACKET]);
-            int len = nla_len(pkt_attrs[OVPN_PACKET_ATTR_PACKET]);
-
-            msg(D_DCO_DEBUG, "ovpn-dco: received OVPN_PACKET_ATTR_PACKET, ifindex: %d peer-id: %d, len %d",
-                ifindex, peerid, len);
-            if (BLEN(&dco->dco_packet_in) > 0)
-            {
-                msg(D_DCO, "DCO packet buffer still full?!");
-                return NL_SKIP;
-            }
-            buf_init(&dco->dco_packet_in, 0);
-            buf_write(&dco->dco_packet_in, data, len);
-            dco->dco_message_peer_id = peerid;
-            dco->dco_message_type = OVPN_CMD_PACKET;
-            break;
-        }
-
         default:
             msg(D_DCO, "ovpn-dco: received unknown command: %d", gnlh->cmd);
             dco->dco_message_type = 0;
@@ -886,41 +820,6 @@ dco_do_read(dco_context_t *dco)
     return ovpn_nl_recvmsgs(dco, __func__);
 }
 
-int
-dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf)
-{
-    packet_size_type len = BLEN(buf);
-    dmsg(D_STREAM_DEBUG, "DCO: WRITE %d offset=%d", (int)len, buf->offset);
-
-    msg(D_DCO_DEBUG, "%s: peer-id %d, len=%d", __func__, peer_id, len);
-
-    struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_PACKET);
-
-    if (!nl_msg)
-    {
-        return -ENOMEM;
-    }
-
-    struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_PACKET);
-    int ret = -EMSGSIZE;
-    NLA_PUT_U32(nl_msg, OVPN_PACKET_ATTR_PEER_ID, peer_id);
-    NLA_PUT(nl_msg, OVPN_PACKET_ATTR_PACKET, len, BSTR(buf));
-    nla_nest_end(nl_msg, attr);
-
-    ret = ovpn_nl_msg_send(dco, nl_msg, NULL, __func__);
-    if (ret)
-    {
-        goto nla_put_failure;
-    }
-
-    /* return the length of the written data in case of success */
-    ret = len;
-
-nla_put_failure:
-    nlmsg_free(nl_msg);
-    return ret;
-}
-
 int
 dco_get_peer_stats_multi(dco_context_t *dco, struct multi_context *m)
 {
index 4d996d98c6b146a3b7741553b2efa4316d0170bf..d28e3658dff290c025358be0a54193fc6213488f 100644 (file)
@@ -48,8 +48,6 @@ typedef struct
 
     unsigned int ifindex;
 
-    struct buffer dco_packet_in;
-
     int dco_message_type;
     int dco_message_peer_id;
     int dco_del_peer_reason;
index 0931fb30a8cfbd4f3e1079093f2323c33269edb4..a805c2a03809c011f7bcc7c4f9b17ace3f8f2f0e 100644 (file)
@@ -393,14 +393,6 @@ dco_do_read(dco_context_t *dco)
     return 0;
 }
 
-int
-dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf)
-{
-    /* no-op on windows */
-    ASSERT(0);
-    return 0;
-}
-
 int
 dco_get_peer_stats_multi(dco_context_t *dco, struct multi_context *m)
 {
index 257c7c75c318607a3fcb64d4aa83fb3c95233b0f..0e86b58c67746db3d8f51172ed43b954c4a99850 100644 (file)
@@ -1191,7 +1191,6 @@ static void
 process_incoming_dco(struct context *c)
 {
 #if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD))
-    struct link_socket_info *lsi = get_link_socket_info(c);
     dco_context_t *dco = &c->c1.tuntap->dco;
 
     dco_do_read(dco);
@@ -1204,35 +1203,23 @@ process_incoming_dco(struct context *c)
         msg(D_DCO_DEBUG, "%s: received message for mismatching peer-id %d, "
             "expected %d", __func__, dco->dco_message_peer_id,
             c->c2.tls_multi->dco_peer_id);
-        /* ensure we also drop a message if there is one in the buffer */
-        buf_init(&dco->dco_packet_in, 0);
         return;
     }
 
-    if ((dco->dco_message_type == OVPN_CMD_DEL_PEER)
-        && (dco->dco_del_peer_reason == OVPN_DEL_PEER_REASON_EXPIRED))
+    if (dco->dco_message_type != OVPN_CMD_DEL_PEER)
     {
-        msg(D_DCO_DEBUG, "%s: received peer expired notification of for peer-id "
-            "%d", __func__, dco->dco_message_peer_id);
-        trigger_ping_timeout_signal(c);
+        msg(D_DCO_DEBUG, "%s: received message of type %u - ignoring", __func__,
+            dco->dco_message_type);
         return;
     }
 
-    if (dco->dco_message_type != OVPN_CMD_PACKET)
+    if (dco->dco_del_peer_reason == OVPN_DEL_PEER_REASON_EXPIRED)
     {
-        msg(D_DCO_DEBUG, "%s: received message of type %u - ignoring", __func__,
-            dco->dco_message_type);
+        msg(D_DCO_DEBUG, "%s: received peer expired notification of for peer-id "
+            "%d", __func__, dco->dco_message_peer_id);
+        trigger_ping_timeout_signal(c);
         return;
     }
-
-    struct buffer orig_buff = c->c2.buf;
-    c->c2.buf = dco->dco_packet_in;
-    c->c2.from = lsi->lsa->actual;
-
-    process_incoming_link(c);
-
-    c->c2.buf = orig_buff;
-    buf_init(&dco->dco_packet_in, 0);
 #endif /* if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD)) */
 }
 
@@ -1686,30 +1673,6 @@ process_ip_header(struct context *c, unsigned int flags, struct buffer *buf)
     }
 }
 
-/*
- * Linux DCO implementations pass the socket to the kernel and
- * disallow usage of it from userland for TCP, so (control) packets
- * sent and received by OpenVPN need to go through the DCO interface.
- *
- * Windows DCO needs control packets to be sent via the normal
- * standard Overlapped I/O.
- *
- * FreeBSD DCO allows control packets to pass through the socket in both
- * directions.
- *
- * Hide that complexity (...especially if more platforms show up
- * in the future...) in a small inline function.
- */
-static inline bool
-should_use_dco_socket(struct link_socket *ls)
-{
-#if defined(TARGET_LINUX)
-    return ls->dco_installed && proto_is_tcp(ls->info.proto);
-#else
-    return false;
-#endif
-}
-
 /*
  * Input: c->c2.to_link
  */
@@ -1783,17 +1746,7 @@ process_outgoing_link(struct context *c)
                 socks_preprocess_outgoing_link(c, &to_addr, &size_delta);
 
                 /* Send packet */
-                if (should_use_dco_socket(c->c2.link_socket))
-                {
-                    size = dco_do_write(&c->c1.tuntap->dco,
-                                        c->c2.tls_multi->dco_peer_id,
-                                        &c->c2.to_link);
-                }
-                else
-                {
-                    size = link_socket_write(c->c2.link_socket, &c->c2.to_link,
-                                             to_addr);
-                }
+                size = link_socket_write(c->c2.link_socket, &c->c2.to_link, to_addr);
 
                 /* Undo effect of prepend */
                 link_socket_write_post_size_adjust(&size, size_delta, &c->c2.to_link);
index e67b93d3a7c4881bad9d55c1ee93818482aacb28..124ac76bd9f126ce6fb3d25cfdc5fa1a227c8969 100644 (file)
@@ -3914,8 +3914,7 @@ do_close_link_socket(struct context *c)
     /* in dco-win case, link socket is a tun handle which is
      * closed in do_close_tun(). Set it to UNDEFINED so
      * we won't use WinSock API to close it. */
-    if (tuntap_is_dco_win(c->c1.tuntap) && c->c2.link_socket
-        && c->c2.link_socket->dco_installed)
+    if (tuntap_is_dco_win(c->c1.tuntap) && c->c2.link_socket)
     {
         c->c2.link_socket->sd = SOCKET_UNDEFINED;
     }
index 59131ac9b4e0d0da3d2160e489801aa4a1c08c69..6c56a260c94ea9dbf6a3fb8610f47f49a500c7cb 100644 (file)
@@ -402,18 +402,6 @@ multi_tcp_wait_lite(struct multi_context *m, struct multi_instance *mi, const in
 
     tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */
 
-    if (mi && mi->context.c2.link_socket->dco_installed)
-    {
-        /* If we got a socket that has been handed over to the kernel
-         * we must not call the normal socket function to figure out
-         * if it is readable or writable */
-        /* Assert that we only have the DCO exptected flags */
-        ASSERT(action & (TA_SOCKET_READ | TA_SOCKET_WRITE));
-
-        /* We are always ready! */
-        return action;
-    }
-
     switch (action)
     {
         case TA_TUN_READ:
@@ -537,10 +525,7 @@ multi_tcp_dispatch(struct multi_context *m, struct multi_instance *mi, const int
 
         case TA_INITIAL:
             ASSERT(mi);
-            if (!mi->context.c2.link_socket->dco_installed)
-            {
-                multi_tcp_set_global_rw_flags(m, mi);
-            }
+            multi_tcp_set_global_rw_flags(m, mi);
             multi_process_post(m, mi, mpp_flags);
             break;
 
@@ -590,10 +575,7 @@ multi_tcp_post(struct multi_context *m, struct multi_instance *mi, const int act
             }
             else
             {
-                if (!c->c2.link_socket->dco_installed)
-                {
-                    multi_tcp_set_global_rw_flags(m, mi);
-                }
+                multi_tcp_set_global_rw_flags(m, mi);
             }
             break;
 
index 59c980b0df0cd54fecb94fa8216594ee73503eaa..53c17b3a4625a8be21c5d01389d51c61f28cd467 100644 (file)
@@ -3202,37 +3202,6 @@ multi_signal_instance(struct multi_context *m, struct multi_instance *mi, const
 #endif
 
 #if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD))
-static void
-process_incoming_dco_packet(struct multi_context *m, struct multi_instance *mi,
-                            dco_context_t *dco)
-{
-    if (BLEN(&dco->dco_packet_in) < 1)
-    {
-        msg(D_DCO, "Received too short packet for peer %d",
-            dco->dco_message_peer_id);
-        goto done;
-    }
-
-    uint8_t *ptr = BPTR(&dco->dco_packet_in);
-    uint8_t op = ptr[0] >> P_OPCODE_SHIFT;
-    if ((op == P_DATA_V1) || (op == P_DATA_V2))
-    {
-        msg(D_DCO, "DCO: received data channel packet for peer %d",
-            dco->dco_message_peer_id);
-        goto done;
-    }
-
-    struct buffer orig_buf = mi->context.c2.buf;
-    mi->context.c2.buf = dco->dco_packet_in;
-
-    multi_process_incoming_link(m, mi, 0);
-
-    mi->context.c2.buf = orig_buf;
-
-done:
-    buf_init(&dco->dco_packet_in, 0);
-}
-
 static void
 process_incoming_del_peer(struct multi_context *m, struct multi_instance *mi,
                           dco_context_t *dco)
@@ -3299,11 +3268,7 @@ multi_process_incoming_dco(struct multi_context *m)
     if ((peer_id < m->max_clients) && (m->instances[peer_id]))
     {
         mi = m->instances[peer_id];
-        if (dco->dco_message_type == OVPN_CMD_PACKET)
-        {
-            process_incoming_dco_packet(m, mi, dco);
-        }
-        else if (dco->dco_message_type == OVPN_CMD_DEL_PEER)
+        if (dco->dco_message_type == OVPN_CMD_DEL_PEER)
         {
             process_incoming_del_peer(m, mi, dco);
         }
@@ -3326,8 +3291,6 @@ multi_process_incoming_dco(struct multi_context *m)
         msg(msglevel, "Received DCO message for unknown peer-id: %d, "
             "type %d, del_peer_reason %d", peer_id, dco->dco_message_type,
             dco->dco_del_peer_reason);
-        /* Also clear the buffer if this was incoming packet for a dropped peer */
-        buf_init(&dco->dco_packet_in, 0);
     }
 
     dco->dco_message_type = 0;
index 96395886bf9294f17523b13bee139d0af4d7faae..d3fd9a8926c19d7976a5bda2e9beefe82463ff0d 100644 (file)
@@ -11,7 +11,7 @@
 #ifndef _UAPI_LINUX_OVPN_DCO_H_
 #define _UAPI_LINUX_OVPN_DCO_H_
 
-#define OVPN_NL_NAME "ovpn-dco"
+#define OVPN_NL_NAME "ovpn-dco-v2"
 
 #define OVPN_NL_MULTICAST_GROUP_PEERS "peers"
 
@@ -45,19 +45,6 @@ enum ovpn_nl_commands {
 
        OVPN_CMD_DEL_KEY,
 
-       /**
-        * @OVPN_CMD_REGISTER_PACKET: Register for specific packet types to be
-        * forwarded to userspace
-        */
-       OVPN_CMD_REGISTER_PACKET,
-
-       /**
-        * @OVPN_CMD_PACKET: Send a packet from userspace to kernelspace. Also
-        * used to send to userspace packets for which a process had registered
-        * with OVPN_CMD_REGISTER_PACKET
-        */
-       OVPN_CMD_PACKET,
-
        /**
         * @OVPN_CMD_GET_PEER: Retrieve the status of a peer or all peers
         */
@@ -105,7 +92,6 @@ enum ovpn_netlink_attrs {
        OVPN_ATTR_NEW_KEY,
        OVPN_ATTR_SWAP_KEYS,
        OVPN_ATTR_DEL_KEY,
-       OVPN_ATTR_PACKET,
        OVPN_ATTR_GET_PEER,
 
        __OVPN_ATTR_AFTER_LAST,
index eff21ca5663a9f142ff29fb494034975cf4a9539..216f2ad7696fa4a6ec8f4489cdca63acf3f568da 100644 (file)
@@ -2151,7 +2151,7 @@ create_socket_dco_win(struct context *c, struct link_socket *sock,
                       get_server_poll_remaining_time(sock->server_poll_timeout),
                       sig_info);
 
-    sock->dco_installed = true;
+    sock->sockflags |= SF_DCO_WIN;
 
     if (sig_info->signal_received)
     {
@@ -3505,7 +3505,7 @@ link_socket_write_udp_posix_sendmsg(struct link_socket *sock,
 static int
 socket_get_last_error(const struct link_socket *sock)
 {
-    if (sock->dco_installed)
+    if (socket_is_dco_win(sock))
     {
         return GetLastError();
     }
@@ -3546,7 +3546,7 @@ socket_recv_queue(struct link_socket *sock, int maxsize)
         ASSERT(ResetEvent(sock->reads.overlapped.hEvent));
         sock->reads.flags = 0;
 
-        if (sock->dco_installed)
+        if (socket_is_dco_win(sock))
         {
             status = ReadFile((HANDLE)sock->sd, wsabuf[0].buf, wsabuf[0].len,
                               &sock->reads.size, &sock->reads.overlapped);
@@ -3651,7 +3651,7 @@ socket_send_queue(struct link_socket *sock, struct buffer *buf, const struct lin
         ASSERT(ResetEvent(sock->writes.overlapped.hEvent));
         sock->writes.flags = 0;
 
-        if (sock->dco_installed)
+        if (socket_is_dco_win(sock))
         {
             status = WriteFile((HANDLE)sock->sd, wsabuf[0].buf, wsabuf[0].len,
                                &sock->writes.size, &sock->writes.overlapped);
index 605b6ad2f32058ca37b532798651f1bbac986c77..bfc1253bee24ef653a04c341f34021c22b252d82 100644 (file)
@@ -168,7 +168,6 @@ struct link_socket
 
     socket_descriptor_t sd;
     socket_descriptor_t ctrl_sd; /* only used for UDP over Socks */
-    bool dco_installed;
 
 #ifdef _WIN32
     struct overlapped_io reads;
@@ -207,6 +206,7 @@ struct link_socket
 #define SF_PORT_SHARE (1<<2)
 #define SF_HOST_RANDOMIZE (1<<3)
 #define SF_GETADDRINFO_DGRAM (1<<4)
+#define SF_DCO_WIN (1<<5)
     unsigned int sockflags;
     int mark;
     const char *bind_dev;
@@ -1021,6 +1021,17 @@ stream_buf_read_setup(struct link_socket *sock)
     }
 }
 
+/**
+ * Returns true if we are on Windows and this link is running on DCO-WIN.
+ * This helper is used to enable DCO-WIN specific logic that is not relevant
+ * to other platforms.
+ */
+static inline bool
+socket_is_dco_win(const struct link_socket *s)
+{
+    return s->sockflags & SF_DCO_WIN;
+}
+
 /*
  * Socket Read Routines
  */
@@ -1036,7 +1047,7 @@ link_socket_read_udp_win32(struct link_socket *sock,
                            struct link_socket_actual *from)
 {
     sockethandle_t sh = { .s = sock->sd };
-    if (sock->dco_installed)
+    if (socket_is_dco_win(sock))
     {
         *from = sock->info.lsa->actual;
         sh.is_handle = true;
@@ -1058,12 +1069,8 @@ link_socket_read(struct link_socket *sock,
                  struct buffer *buf,
                  struct link_socket_actual *from)
 {
-#ifdef _WIN32
-    if (proto_is_udp(sock->info.proto) || sock->dco_installed)
-#else
-    if (proto_is_udp(sock->info.proto))
-#endif
-    /* unified UDPv4 and UDPv6, for DCO the kernel
+    if (proto_is_udp(sock->info.proto) || socket_is_dco_win(sock))
+    /* unified UDPv4 and UDPv6, for DCO-WIN the kernel
      * will strip the length header */
     {
         int res;
@@ -1105,7 +1112,7 @@ link_socket_write_win32(struct link_socket *sock,
 {
     int err = 0;
     int status = 0;
-    sockethandle_t sh = { .s = sock->sd, .is_handle = sock->dco_installed };
+    sockethandle_t sh = { .s = sock->sd, .is_handle = socket_is_dco_win(sock) };
     if (overlapped_io_active(&sock->writes))
     {
         status = sockethandle_finalize(sh, &sock->writes, NULL, NULL);
@@ -1179,9 +1186,9 @@ link_socket_write(struct link_socket *sock,
                   struct buffer *buf,
                   struct link_socket_actual *to)
 {
-    if (proto_is_udp(sock->info.proto) || sock->dco_installed)
+    if (proto_is_udp(sock->info.proto) || socket_is_dco_win(sock))
     {
-        /* unified UDPv4 and UDPv6 and DCO (kernel adds size header) */
+        /* unified UDPv4, UDPv6 and DCO-WIN (driver adds length header) */
         return link_socket_write_udp(sock, buf, to);
     }
     else if (proto_is_tcp(sock->info.proto)) /* unified TCPv4 and TCPv6 */