[Openvpn-devel,L] Change in openvpn[master]: dco-win: multipeer support

Message ID f583237d796aba17bc8b54fda13fae847d2370d0-HTML@gerrit.openvpn.net
State New
Headers show
Series [Openvpn-devel,L] Change in openvpn[master]: dco-win: multipeer support | expand

Commit Message

flichtenheld (Code Review) Nov. 21, 2024, 6:07 p.m. UTC
Attention is currently required from: flichtenheld, plaisthos.

Hello plaisthos, flichtenheld,

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

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

to review the following change.


Change subject: dco-win: multipeer support
......................................................................

dco-win: multipeer support

This is the main commit for dco-win multipeer
implementation.

This adds concept of "mode" to DCO implementation,
which is peer-to-peer or multipeer. Depends on mode,
some functions use MP-specific IOCTL commands, which
include peer-id as a part of input.

The driver initialization accomodates server mode,
in which tun device is created before transport.

Since on Windows the socket is visible to the kernel only,
control channel packets have to be prepended with remote
sockaddr of the peer - this allows userspace to distinguish
among peers. Sadly there is no reliable way to get peer local
address, such as on Linux/FreeBSD, so we have to do a bit of
guesswork figure out IP address based on remote IP and local
routing table, which may backfire if there are multiple IPs
assigned to the same network adapter. However, as for now
peer-specific local IP is not used by the driver. We use
instead the result of bind() to the listening address.

Existing sockethandle_finalize() function has been refactored
to accomodate packets with possibly prepended sockaddr.

Change-Id: Ia267276d61fa1425ba205f54ba6eb89021f32dba
Signed-off-by: Lev Stipakov <lev@openvpn.net>
---
M src/openvpn/dco.h
M src/openvpn/dco_freebsd.c
M src/openvpn/dco_linux.c
M src/openvpn/dco_win.c
M src/openvpn/dco_win.h
M src/openvpn/forward.c
M src/openvpn/init.c
M src/openvpn/ovpn_dco_win.h
M src/openvpn/socket.c
M src/openvpn/socket.h
10 files changed, 518 insertions(+), 103 deletions(-)



  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/15/815/1

Patch

diff --git a/src/openvpn/dco.h b/src/openvpn/dco.h
index 035474f..7f2021d 100644
--- a/src/openvpn/dco.h
+++ b/src/openvpn/dco.h
@@ -106,9 +106,10 @@ 
  *
  * @param mode      the instance operating mode (P2P or multi-peer)
  * @param dco       the context to initialize
+ * @param dev_node  device node, used on Windows to specify certain DCO adapter
  * @return          true on success, false otherwise
  */
-bool ovpn_dco_init(int mode, dco_context_t *dco);
+bool ovpn_dco_init(int mode, dco_context_t *dco, const char *dev_node);
 
 /**
  * Open/create a DCO interface
@@ -284,7 +285,7 @@ 
 }
 
 static inline bool
-ovpn_dco_init(int mode, dco_context_t *dco)
+ovpn_dco_init(int mode, dco_context_t *dco, const char *dev_node)
 {
     return true;
 }
diff --git a/src/openvpn/dco_freebsd.c b/src/openvpn/dco_freebsd.c
index f4c3b02..0e536de 100644
--- a/src/openvpn/dco_freebsd.c
+++ b/src/openvpn/dco_freebsd.c
@@ -165,7 +165,7 @@ 
 }
 
 bool
-ovpn_dco_init(int mode, dco_context_t *dco)
+ovpn_dco_init(int mode, dco_context_t *dco, const char *dev_node)
 {
     if (open_fd(dco) < 0)
     {
diff --git a/src/openvpn/dco_linux.c b/src/openvpn/dco_linux.c
index b038382..0a38a0e 100644
--- a/src/openvpn/dco_linux.c
+++ b/src/openvpn/dco_linux.c
@@ -391,7 +391,7 @@ 
 }
 
 bool
-ovpn_dco_init(int mode, dco_context_t *dco)
+ovpn_dco_init(int mode, dco_context_t *dco, const char *dev_node)
 {
     switch (mode)
     {
diff --git a/src/openvpn/dco_win.c b/src/openvpn/dco_win.c
index 3bc123b..7be6fdb 100644
--- a/src/openvpn/dco_win.c
+++ b/src/openvpn/dco_win.c
@@ -28,6 +28,7 @@ 
 #include "syshead.h"
 
 #include "dco.h"
+#include "forward.h"
 #include "tun.h"
 #include "crypto.h"
 #include "ssl_common.h"
@@ -42,16 +43,37 @@ 
 #endif
 
 struct tuntap
-create_dco_handle(const char *devname, struct gc_arena *gc)
+create_dco_handle(const char *devname, struct gc_arena *gc, int mode)
 {
     struct tuntap tt = { .backend_driver = DRIVER_DCO };
     const char *device_guid;
 
     tun_open_device(&tt, devname, &device_guid, gc);
 
+    /* for mp we set mode in ovpn_dco_init(), however for p2p it is too late */
+    tt.dco.mode = (mode == MODE_SERVER) ? dco_mode_mp : dco_mode_p2p;
+
     return tt;
 }
 
+/* Sometimes IP Helper API, which we use for setting IP address etc,
+ * complains that interface is not found. Give it some time to settle
+ */
+static void
+dco_wait_ready(DWORD idx)
+{
+    for (int i = 0; i < 20; ++i)
+    {
+        MIB_IPINTERFACE_ROW row = { .InterfaceIndex = idx, .Family = AF_INET };
+        if (GetIpInterfaceEntry(&row) != ERROR_NOT_FOUND)
+        {
+            break;
+        }
+        msg(D_DCO_DEBUG, "interface %ld not yet ready, retrying", idx);
+        Sleep(50);
+    }
+}
+
 /**
  * Gets version of dco-win driver
  *
@@ -104,8 +126,32 @@ 
 }
 
 bool
-ovpn_dco_init(int mode, dco_context_t *dco)
+ovpn_dco_init(int mode, dco_context_t *dco, const char *dev_node)
 {
+    /* for p2p dco->mode is already set */
+    dco->mode = (mode == MODE_POINT_TO_POINT) ? dco_mode_p2p : dco_mode_mp;
+
+    if (dco->mode == dco_mode_p2p)
+    {
+        return true;
+    }
+
+    /* open DCO device */
+    struct gc_arena gc = gc_new();
+    const char *device_guid;
+    tun_open_device(dco->tt, dev_node, &device_guid, &gc);
+    gc_free(&gc);
+
+    /* set mp mode */
+    OVPN_MODE m = OVPN_MODE_MP;
+    DWORD bytes_returned = 0;
+    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_SET_MODE, &m, sizeof(m), NULL, 0, &bytes_returned, NULL))
+    {
+        msg(M_ERR, "DeviceIoControl(OVPN_IOCTL_SET_MODE) failed");
+    }
+
+    dco_wait_ready(dco->tt->adapter_index);
+
     return true;
 }
 
@@ -116,19 +162,19 @@ 
     return 0;
 }
 
-static void
-dco_wait_ready(DWORD idx)
+void
+dco_p2p_start_vpn(struct tuntap *tt)
 {
-    for (int i = 0; i < 20; ++i)
+    DWORD bytes_returned = 0;
+    if (!DeviceIoControl(tt->hand, OVPN_IOCTL_START_VPN, NULL, 0, NULL, 0, &bytes_returned, NULL))
     {
-        MIB_IPINTERFACE_ROW row = {.InterfaceIndex = idx, .Family = AF_INET};
-        if (GetIpInterfaceEntry(&row) != ERROR_NOT_FOUND)
-        {
-            break;
-        }
-        msg(D_DCO_DEBUG, "interface %ld not yet ready, retrying", idx);
-        Sleep(50);
+        msg(M_ERR, "DeviceIoControl(OVPN_IOCTL_START_VPN) failed");
     }
+
+    /* Sometimes IP Helper API, which we use for setting IP address etc,
+     * complains that interface is not found. Give it some time to settle
+     */
+    dco_wait_ready(tt->adapter_index);
 }
 
 void
@@ -141,17 +187,10 @@ 
      */
     tt->dco.tt = tt;
 
-    DWORD bytes_returned = 0;
-    if (!DeviceIoControl(tt->hand, OVPN_IOCTL_START_VPN, NULL, 0, NULL, 0,
-                         &bytes_returned, NULL))
+    if (tt->dco.mode == dco_mode_p2p)
     {
-        msg(M_ERR, "DeviceIoControl(OVPN_IOCTL_START_VPN) failed");
+        dco_p2p_start_vpn(tt);
     }
-
-    /* Sometimes IP Helper API, which we use for setting IP address etc,
-     * complains that interface is not found. Give it some time to settle
-     */
-    dco_wait_ready(tt->adapter_index);
 }
 
 static void
@@ -207,14 +246,67 @@ 
 }
 
 void
-dco_create_socket(HANDLE handle, struct addrinfo *remoteaddr, bool bind_local,
-                  struct addrinfo *bind, int timeout,
-                  struct signal_info *sig_info)
+dco_mp_start_vpn(HANDLE handle, struct link_socket *sock)
+{
+    msg(D_DCO_DEBUG, "%s", __func__);
+
+    int ai_family = sock->info.lsa->bind_local->ai_family;
+    struct addrinfo *local = sock->info.lsa->bind_local;
+    struct addrinfo *cur = NULL;
+
+    for (cur = local; cur; cur = cur->ai_next)
+    {
+        if (cur->ai_family == ai_family)
+        {
+            break;
+        }
+    }
+    if (!cur)
+    {
+        msg(M_FATAL, "%s: Socket bind failed: Addr to bind has no %s record",
+            __func__, addr_family_name(ai_family));
+    }
+
+    OVPN_MP_START_VPN in, out;
+    in.IPv6Only = sock->info.bind_ipv6_only ? 1 : 0;
+    if (ai_family == AF_INET)
+    {
+        memcpy(&in.ListenAddress.Addr4, cur->ai_addr, sizeof(struct sockaddr_in));
+    }
+    else
+    {
+        memcpy(&in.ListenAddress.Addr6, cur->ai_addr, sizeof(struct sockaddr_in6));
+    }
+
+    /* in multipeer mode control channel packets are prepended with remote peer's sockaddr */
+    sock->sockflags |= SF_PREPEND_SA;
+
+    DWORD bytes_returned = 0;
+    if (!DeviceIoControl(handle, OVPN_IOCTL_MP_START_VPN, &in, sizeof(in), &out, sizeof(out),
+                         &bytes_returned, NULL))
+    {
+        msg(M_ERR, "DeviceIoControl(OVPN_IOCTL_MP_START_VPN) failed");
+    }
+
+    if (out.ListenAddress.Addr4.sin_family == AF_INET)
+    {
+        memcpy(&sock->info.lsa->actual.dest.addr.in4, &out.ListenAddress.Addr4, sizeof(struct sockaddr_in));
+    }
+    else
+    {
+        memcpy(&sock->info.lsa->actual.dest.addr.in6, &out.ListenAddress.Addr6, sizeof(struct sockaddr_in6));
+    }
+}
+
+void
+dco_p2p_new_peer(HANDLE handle, struct link_socket *sock, struct signal_info *sig_info)
 {
     msg(D_DCO_DEBUG, "%s", __func__);
 
     OVPN_NEW_PEER peer = { 0 };
 
+    struct addrinfo *remoteaddr = sock->info.lsa->current_remote;
+
     struct sockaddr *local = NULL;
     struct sockaddr *remote = remoteaddr->ai_addr;
 
@@ -228,9 +320,10 @@ 
         peer.Proto = OVPN_PROTO_UDP;
     }
 
-    if (bind_local)
+    if (sock->bind_local)
     {
         /* Use first local address with correct address family */
+        struct addrinfo *bind = sock->info.lsa->bind_local;
         while (bind && !local)
         {
             if (bind->ai_family == remote->sa_family)
@@ -241,7 +334,7 @@ 
         }
     }
 
-    if (bind_local && !local)
+    if (sock->bind_local && !local)
     {
         msg(M_FATAL, "DCO: Socket bind failed: Address to bind lacks %s record",
             addr_family_name(remote->sa_family));
@@ -290,17 +383,131 @@ 
         }
         else
         {
-            dco_connect_wait(handle, &ov, timeout, sig_info);
+            dco_connect_wait(handle, &ov, get_server_poll_remaining_time(sock->server_poll_timeout), sig_info);
         }
     }
 }
 
+static struct sockaddr *
+dco_get_source_ip_for_dst(const struct sockaddr *dst)
+{
+    SOCKADDR_INET dst_in = { 0 };
+
+    /* Cast the destination address to SOCKADDR_INET */
+    if (dst->sa_family == AF_INET)
+    {
+        dst_in.Ipv4 = *((struct sockaddr_in *)dst);
+        dst_in.si_family = AF_INET;
+    }
+    else if (dst->sa_family == AF_INET6)
+    {
+        dst_in.Ipv6 = *((struct sockaddr_in6 *)dst);
+        dst_in.si_family = AF_INET6;
+    }
+    else
+    {
+        msg(M_FATAL, "%s: unknown dst address family %d", __func__, dst->sa_family);
+    }
+
+    /* Get the best source address */
+    MIB_IPFORWARD_ROW2 row;
+    SOCKADDR_INET best_src = { 0 };
+    DWORD ret = GetBestRoute2(NULL, 0, NULL, &dst_in, 0, &row, &best_src);
+    if (ret != NO_ERROR)
+    {
+        msg(M_WARN, "%s: GetBestRoute2() failed with error: %ld\n", __func__, ret);
+        return NULL;
+    }
+
+    /* Allocate and copy the best source address to return as sockaddr */
+    struct sockaddr *src = NULL;
+    if (best_src.si_family == AF_INET6)
+    {
+        struct sockaddr_in6 *src_in6 = (struct sockaddr_in6 *)malloc(sizeof(struct sockaddr_in6));
+        if (src_in6 == NULL)
+        {
+            msg(M_FATAL, "%s: malloc failed", __func__);
+        }
+        *src_in6 = best_src.Ipv6;
+        src = (struct sockaddr *)src_in6;
+    }
+    else if (best_src.si_family == AF_INET)
+    {
+        struct sockaddr_in *src_in = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
+        if (src_in == NULL)
+        {
+            msg(M_FATAL, "%s: malloc failed", __func__);
+        }
+        *src_in = best_src.Ipv4;
+        src = (struct sockaddr *)src_in;
+    }
+    else
+    {
+        msg(M_FATAL, "%s: unknown src family %d", __func__, best_src.si_family);
+    }
+
+    return src;
+}
+
 int
 dco_new_peer(dco_context_t *dco, unsigned int peerid, int sd,
              struct sockaddr *localaddr, struct sockaddr *remoteaddr,
              struct in_addr *vpn_ipv4, struct in6_addr *vpn_ipv6)
 {
     msg(D_DCO_DEBUG, "%s: peer-id %d, fd %d", __func__, peerid, sd);
+
+    if (dco->mode == dco_mode_p2p)
+    {
+        /* no-op for p2p */
+        return 0;
+    }
+
+    OVPN_MP_NEW_PEER newPeer = {0};
+
+    if (remoteaddr)
+    {
+        struct sockaddr *local = dco_get_source_ip_for_dst(remoteaddr);
+        if (local)
+        {
+            if (local->sa_family == AF_INET)
+            {
+                memcpy(&newPeer.Local.Addr4, local, sizeof(struct sockaddr_in));
+            }
+            else
+            {
+                memcpy(&newPeer.Local.Addr6, local, sizeof(struct sockaddr_in6));
+            }
+            free(local);
+        }
+
+        if (remoteaddr->sa_family == AF_INET)
+        {
+            memcpy(&newPeer.Remote.Addr4, remoteaddr, sizeof(struct sockaddr_in));
+        }
+        else
+        {
+            memcpy(&newPeer.Remote.Addr6, remoteaddr, sizeof(struct sockaddr_in6));
+        }
+    }
+
+    if (vpn_ipv4)
+    {
+        newPeer.VpnAddr4 = *vpn_ipv4;
+    }
+
+    if (vpn_ipv6)
+    {
+        newPeer.VpnAddr6 = *vpn_ipv6;
+    }
+
+    newPeer.PeerId = peerid;
+
+    DWORD bytesReturned;
+    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_MP_NEW_PEER, &newPeer, sizeof(newPeer), NULL, 0, &bytesReturned, NULL))
+    {
+        msg(M_ERR, "DeviceIoControl(OVPN_IOCTL_MP_NEW_PEER) failed");
+    }
+
     return 0;
 }
 
@@ -309,9 +516,20 @@ 
 {
     msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peerid);
 
+    OVPN_MP_DEL_PEER del_peer = { peerid };
+    VOID *buf = NULL;
+    DWORD len = 0;
+    DWORD ioctl = OVPN_IOCTL_DEL_PEER;
+
+    if (dco->mode == dco_mode_mp)
+    {
+        ioctl = OVPN_IOCTL_MP_DEL_PEER;
+        buf = &del_peer;
+        len = sizeof(del_peer);
+    }
+
     DWORD bytes_returned = 0;
-    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_DEL_PEER, NULL,
-                         0, NULL, 0, &bytes_returned, NULL))
+    if (!DeviceIoControl(dco->tt->hand, ioctl, buf, len, NULL, 0, &bytes_returned, NULL))
     {
         msg(M_WARN | M_ERRNO, "DeviceIoControl(OVPN_IOCTL_DEL_PEER) failed");
         return -1;
@@ -326,19 +544,30 @@ 
     msg(D_DCO_DEBUG, "%s: peer-id %d, keepalive %d/%d, mss %d", __func__,
         peerid, keepalive_interval, keepalive_timeout, mss);
 
-    OVPN_SET_PEER peer;
+    OVPN_MP_SET_PEER mp_peer = { peerid, keepalive_interval, keepalive_timeout, mss };
+    OVPN_SET_PEER peer = { keepalive_interval, keepalive_timeout, mss };
+    VOID *buf = NULL;
+    DWORD len = 0;
+    DWORD ioctl = (dco->mode == dco_mode_mp) ? OVPN_IOCTL_MP_SET_PEER : OVPN_IOCTL_SET_PEER;
 
-    peer.KeepaliveInterval =  keepalive_interval;
-    peer.KeepaliveTimeout = keepalive_timeout;
-    peer.MSS = mss;
+    if (dco->mode == dco_mode_mp)
+    {
+        buf = &mp_peer;
+        len = sizeof(OVPN_MP_SET_PEER);
+    }
+    else
+    {
+        buf = &peer;
+        len = sizeof(OVPN_SET_PEER);
+    }
 
     DWORD bytes_returned = 0;
-    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_SET_PEER, &peer,
-                         sizeof(peer), NULL, 0, &bytes_returned, NULL))
+    if (!DeviceIoControl(dco->tt->hand, ioctl, buf, len, NULL, 0, &bytes_returned, NULL))
     {
-        msg(M_WARN | M_ERRNO, "DeviceIoControl(OVPN_IOCTL_SET_PEER) failed");
+        msg(M_WARN | M_ERRNO, "DeviceIoControl(OVPN_IOCTL_MP_SET_PEER) failed");
         return -1;
     }
+
     return 0;
 }
 
@@ -397,9 +626,20 @@ 
 {
     msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peer_id);
 
+    OVPN_MP_SWAP_KEYS swap = {peer_id};
+    DWORD ioctl = OVPN_IOCTL_SWAP_KEYS;
+    VOID *buf = NULL;
+    DWORD len = 0;
+
+    if (dco->mode == dco_mode_mp)
+    {
+        ioctl = OVPN_IOCTL_MP_SWAP_KEYS;
+        buf = &swap;
+        len = sizeof(swap);
+    }
+
     DWORD bytes_returned = 0;
-    if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_SWAP_KEYS, NULL, 0, NULL, 0,
-                         &bytes_returned, NULL))
+    if (!DeviceIoControl(dco->tt->hand, ioctl, buf, len, NULL, 0, &bytes_returned, NULL))
     {
         msg(M_ERR, "DeviceIoControl(OVPN_IOCTL_SWAP_KEYS) failed");
         return -1;
diff --git a/src/openvpn/dco_win.h b/src/openvpn/dco_win.h
index a28436c..3e1c2f3 100644
--- a/src/openvpn/dco_win.h
+++ b/src/openvpn/dco_win.h
@@ -31,19 +31,28 @@ 
 typedef OVPN_KEY_SLOT dco_key_slot_t;
 typedef OVPN_CIPHER_ALG dco_cipher_t;
 
+typedef enum {
+    dco_mode_uninit,
+    dco_mode_p2p,
+    dco_mode_mp
+} dco_mode_type;
+
 struct dco_context {
     struct tuntap *tt;
+    dco_mode_type mode;
+
 };
 
 typedef struct dco_context dco_context_t;
 
 struct tuntap
-create_dco_handle(const char *devname, struct gc_arena *gc);
+create_dco_handle(const char *devname, struct gc_arena *gc, int mode);
 
 void
-dco_create_socket(HANDLE handle, struct addrinfo *remoteaddr, bool bind_local,
-                  struct addrinfo *bind, int timeout,
-                  struct signal_info *sig_info);
+dco_mp_start_vpn(HANDLE handle, struct link_socket *sock);
+
+void
+dco_p2p_new_peer(HANDLE handle, struct link_socket *sock, struct signal_info *sig_info);
 
 void
 dco_start_tun(struct tuntap *tt);
diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index d50b24c..0a3936a 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -1315,7 +1315,8 @@ 
     }
     else
     {
-        sockethandle_t sh = { .is_handle = true, .h = c->c1.tuntap->hand };
+        bool prepend_sa = c->options.mode == MODE_SERVER && dco_enabled(&c->options);
+        sockethandle_t sh = { .is_handle = true, .h = c->c1.tuntap->hand, .prepend_sa = prepend_sa };
         sockethandle_finalize(sh, &c->c1.tuntap->reads, &c->c2.buf, NULL);
     }
 #else  /* ifdef _WIN32 */
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 656fedf..b02e4f1 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -1987,7 +1987,7 @@ 
 #endif
         if (dco_enabled(&c->options))
         {
-            ovpn_dco_init(c->mode, &c->c1.tuntap->dco);
+            ovpn_dco_init(c->mode, &c->c1.tuntap->dco, c->options.dev_node);
         }
 
         /* open the tun device */
diff --git a/src/openvpn/ovpn_dco_win.h b/src/openvpn/ovpn_dco_win.h
index ea2a733..8003ace 100644
--- a/src/openvpn/ovpn_dco_win.h
+++ b/src/openvpn/ovpn_dco_win.h
@@ -47,6 +47,23 @@ 
 	OVPN_PROTO Proto;
 } OVPN_NEW_PEER, * POVPN_NEW_PEER;
 
+typedef struct _OVPN_MP_NEW_PEER {
+    union {
+        SOCKADDR_IN Addr4;
+        SOCKADDR_IN6 Addr6;
+    } Local;
+
+    union {
+        SOCKADDR_IN Addr4;
+        SOCKADDR_IN6 Addr6;
+    } Remote;
+
+    IN_ADDR VpnAddr4;
+    IN6_ADDR VpnAddr6;
+
+    int PeerId;
+} OVPN_MP_NEW_PEER, * POVPN_MP_NEW_PEER;
+
 typedef struct _OVPN_STATS {
 	LONG LostInControlPackets;
 	LONG LostOutControlPackets;
@@ -94,10 +111,25 @@ 
 	int PeerId;
 } OVPN_CRYPTO_DATA, * POVPN_CRYPTO_DATA;
 
+#define CRYPTO_OPTIONS_AEAD_TAG_END (1<<1)
+#define CRYPTO_OPTIONS_64BIT_PKTID  (1<<2)
+
+typedef struct _OVPN_CRYPTO_DATA_V2 {
+    OVPN_CRYPTO_DATA V1;
+    UINT32 CryptoOptions;
+} OVPN_CRYPTO_DATA_V2, * POVPN_CRYPTO_DATA_V2;
+
+typedef struct _OVPN_MP_SET_PEER {
+    int PeerId;
+    LONG KeepaliveInterval;
+    LONG KeepaliveTimeout;
+    LONG MSS;
+} OVPN_MP_SET_PEER, * POVPN_MP_SET_PEER;
+
 typedef struct _OVPN_SET_PEER {
-	LONG KeepaliveInterval;
-	LONG KeepaliveTimeout;
-	LONG MSS;
+    LONG KeepaliveInterval;
+    LONG KeepaliveTimeout;
+    LONG MSS;
 } OVPN_SET_PEER, * POVPN_SET_PEER;
 
 typedef struct _OVPN_VERSION {
@@ -106,6 +138,50 @@ 
     LONG Patch;
 } OVPN_VERSION, * POVPN_VERSION;
 
+typedef enum {
+    OVPN_MODE_P2P,
+    OVPN_MODE_MP
+} OVPN_MODE;
+
+typedef struct _OVPN_SET_MODE {
+    OVPN_MODE Mode;
+} OVPN_SET_MODE, * POVPN_SET_MODE;
+
+typedef struct _OVPN_MP_START_VPN {
+    union {
+        SOCKADDR_IN Addr4;
+        SOCKADDR_IN6 Addr6;
+    } ListenAddress;
+    int IPv6Only;
+} OVPN_MP_START_VPN, * POVPN_MP_START_VPN;
+
+typedef enum {
+    OVPN_CMD_DEL_PEER,
+    OVPN_CMD_SWAP_KEYS
+} OVPN_NOTIFY_CMD;
+
+typedef enum {
+    OVPN_DEL_PEER_REASON_TEARDOWN,
+    OVPN_DEL_PEER_REASON_USERSPACE,
+    OVPN_DEL_PEER_REASON_EXPIRED,
+    OVPN_DEL_PEER_REASON_TRANSPORT_ERROR,
+    OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT
+} OVPN_DEL_PEER_REASON;
+
+typedef struct _OVPN_NOTIFY_EVENT {
+    OVPN_NOTIFY_CMD Cmd;
+    int PeerId;
+    OVPN_DEL_PEER_REASON DelPeerReason;
+} OVPN_NOTIFY_EVENT, * POVPN_NOTIFY_EVENT;
+
+typedef struct _OVPN_MP_DEL_PEER {
+    int PeerId;
+} OVPN_MP_DEL_PEER, * POVPN_MP_DEL_PEER;
+
+typedef struct _OVPN_MP_SWAP_KEYS {
+    int PeerId;
+} OVPN_MP_SWAP_KEYS, * POVPN_MP_SWAP_KEYS;
+
 #define OVPN_IOCTL_NEW_PEER     CTL_CODE(FILE_DEVICE_UNKNOWN, 1, METHOD_BUFFERED, FILE_ANY_ACCESS)
 #define OVPN_IOCTL_GET_STATS    CTL_CODE(FILE_DEVICE_UNKNOWN, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
 #define OVPN_IOCTL_NEW_KEY      CTL_CODE(FILE_DEVICE_UNKNOWN, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)
@@ -114,3 +190,14 @@ 
 #define OVPN_IOCTL_START_VPN    CTL_CODE(FILE_DEVICE_UNKNOWN, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
 #define OVPN_IOCTL_DEL_PEER     CTL_CODE(FILE_DEVICE_UNKNOWN, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)
 #define OVPN_IOCTL_GET_VERSION  CTL_CODE(FILE_DEVICE_UNKNOWN, 8, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define OVPN_IOCTL_NEW_KEY_V2   CTL_CODE(FILE_DEVICE_UNKNOWN, 9, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define OVPN_IOCTL_SET_MODE     CTL_CODE(FILE_DEVICE_UNKNOWN, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define OVPN_IOCTL_MP_START_VPN   CTL_CODE(FILE_DEVICE_UNKNOWN, 11, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define OVPN_IOCTL_MP_NEW_PEER    CTL_CODE(FILE_DEVICE_UNKNOWN, 12, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define OVPN_IOCTL_MP_SET_PEER    CTL_CODE(FILE_DEVICE_UNKNOWN, 13, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define OVPN_IOCTL_NOTIFY_EVENT   CTL_CODE(FILE_DEVICE_UNKNOWN, 14, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define OVPN_IOCTL_MP_DEL_PEER    CTL_CODE(FILE_DEVICE_UNKNOWN, 15, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define OVPN_IOCTL_MP_SWAP_KEYS   CTL_CODE(FILE_DEVICE_UNKNOWN, 16, METHOD_BUFFERED, FILE_ANY_ACCESS)
diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c
index efd742c..6909e7a 100644
--- a/src/openvpn/socket.c
+++ b/src/openvpn/socket.c
@@ -2144,7 +2144,7 @@ 
         struct tuntap *tt;
         ALLOC_OBJ(tt, struct tuntap);
 
-        *tt = create_dco_handle(c->options.dev_node, &c->gc);
+        *tt = create_dco_handle(c->options.dev_node, &c->gc, c->options.mode);
 
         /* Ensure we can "safely" cast the handle to a socket */
         static_assert(sizeof(sock->sd) == sizeof(tt->hand), "HANDLE and SOCKET size differs");
@@ -2152,12 +2152,14 @@ 
         c->c1.tuntap = tt;
     }
 
-    dco_create_socket(c->c1.tuntap->hand,
-                      sock->info.lsa->current_remote,
-                      sock->bind_local, sock->info.lsa->bind_local,
-                      get_server_poll_remaining_time(sock->server_poll_timeout),
-                      sig_info);
-
+    if (c->options.mode == MODE_SERVER)
+    {
+        dco_mp_start_vpn(c->c1.tuntap->hand, sock);
+    }
+    else
+    {
+        dco_p2p_new_peer(c->c1.tuntap->hand, sock, sig_info);
+    }
     sock->sockflags |= SF_DCO_WIN;
 
     if (sig_info->signal_received)
@@ -2207,16 +2209,16 @@ 
     resolve_remote(sock, 2, &remote_dynamic,  sig_info);
 
     /* If a valid remote has been found, create the socket with its addrinfo */
-    if (sock->info.lsa->current_remote)
-    {
 #if defined(_WIN32)
-        if (dco_enabled(&c->options))
-        {
-            create_socket_dco_win(c, sock, sig_info);
-            goto done;
-        }
-        else
+    if (dco_enabled(&c->options))
+    {
+        create_socket_dco_win(c, sock, sig_info);
+        goto done;
+    }
+    else
 #endif
+    {
+        if (sock->info.lsa->current_remote)
         {
             create_socket(sock, sock->info.lsa->current_remote);
         }
@@ -3753,6 +3755,91 @@ 
     return sock->writes.iostate;
 }
 
+void
+read_sockaddr_from_overlapped(struct overlapped_io *io, struct sockaddr *dst, int overlapped_ret)
+{
+    if (overlapped_ret >= 0 && io->addr_defined)
+    {
+        /* TODO(jjo): streamline this mess */
+        /* in this func we don't have relevant info about the PF_ of this
+         * endpoint, as link_socket_actual will be zero for the 1st received packet
+         *
+         * Test for inets PF_ possible sizes
+         */
+        switch (io->addrlen)
+        {
+            case sizeof(struct sockaddr_in):
+            case sizeof(struct sockaddr_in6):
+            /* TODO(jjo): for some reason (?) I'm getting 24,28 for AF_INET6
+             * under _WIN32*/
+            case sizeof(struct sockaddr_in6) - 4:
+                break;
+
+            default:
+                bad_address_length(io->addrlen, af_addr_size(io->addr.sin_family));
+        }
+
+        switch (io->addr.sin_family)
+        {
+            case AF_INET:
+                memcpy(dst, &io->addr, sizeof(struct sockaddr_in));
+                break;
+
+            case AF_INET6:
+                memcpy(dst, &io->addr, sizeof(struct sockaddr_in));
+                break;
+        }
+    }
+    else
+    {
+        CLEAR(*dst);
+    }
+}
+
+/**
+ * @brief Extracts a sockaddr from a packet payload.
+ *
+ * Reads a sockaddr structure from the start of the packet buffer and writes it to `dst`.
+ *
+ * @param[in] buf Packet buffer containing the payload.
+ * @param[out] dst Destination buffer for the extracted sockaddr.
+ * @return Length of the extracted sockaddr
+ */
+static int
+read_sockaddr_from_packet(struct buffer *buf, struct sockaddr *dst)
+{
+    int sa_len = 0;
+
+    const struct sockaddr *sa = (const struct sockaddr *)BPTR(buf);
+    switch (sa->sa_family)
+    {
+        case AF_INET:
+            if (buf_len(buf) < sizeof(struct sockaddr_in))
+            {
+                msg(M_FATAL, "ERROR: received incoming packet with too short length of %d -- must be at least %zu.", buf_len(buf), sizeof(struct sockaddr_in));
+            }
+            memcpy(dst, sa, sizeof(struct sockaddr_in));
+            buf_advance(buf, sizeof(struct sockaddr_in));
+            sa_len = sizeof(struct sockaddr_in);
+            break;
+
+        case AF_INET6:
+            if (buf_len(buf) < sizeof(struct sockaddr_in6))
+            {
+                msg(M_FATAL, "ERROR: received incoming packet with too short length of %d -- must be at least %zu.", buf_len(buf), sizeof(struct sockaddr_in6));
+            }
+            memcpy(dst, sa, sizeof(struct sockaddr_in6));
+            buf_advance(buf, sizeof(struct sockaddr_in6));
+            sa_len = sizeof(struct sockaddr_in6);
+            break;
+
+        default:
+            msg(M_FATAL, "ERROR: received incoming packet with invalid address family %d.", sa->sa_family);
+    }
+
+    return sa_len;
+}
+
 /* Returns the number of bytes successfully read */
 int
 sockethandle_finalize(sockethandle_t sh,
@@ -3826,45 +3913,14 @@ 
             ASSERT(0);
     }
 
-    /* return from address if requested */
+    if (from && ret > 0 && sh.is_handle && sh.prepend_sa)
+    {
+        ret -= read_sockaddr_from_packet(buf, &from->dest.addr.sa);
+    }
+
     if (!sh.is_handle && from)
     {
-        if (ret >= 0 && io->addr_defined)
-        {
-            /* TODO(jjo): streamline this mess */
-            /* in this func we don't have relevant info about the PF_ of this
-             * endpoint, as link_socket_actual will be zero for the 1st received packet
-             *
-             * Test for inets PF_ possible sizes
-             */
-            switch (io->addrlen)
-            {
-                case sizeof(struct sockaddr_in):
-                case sizeof(struct sockaddr_in6):
-                /* TODO(jjo): for some reason (?) I'm getting 24,28 for AF_INET6
-                 * under _WIN32*/
-                case sizeof(struct sockaddr_in6)-4:
-                    break;
-
-                default:
-                    bad_address_length(io->addrlen, af_addr_size(io->addr.sin_family));
-            }
-
-            switch (io->addr.sin_family)
-            {
-                case AF_INET:
-                    from->dest.addr.in4 = io->addr;
-                    break;
-
-                case AF_INET6:
-                    from->dest.addr.in6 = io->addr6;
-                    break;
-            }
-        }
-        else
-        {
-            CLEAR(from->dest.addr);
-        }
+        read_sockaddr_from_overlapped(io, &from->dest.addr.sa, ret);
     }
 
     if (buf)
diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h
index 465d92b..36c683c 100644
--- a/src/openvpn/socket.h
+++ b/src/openvpn/socket.h
@@ -224,6 +224,7 @@ 
 #define SF_HOST_RANDOMIZE (1<<3)
 #define SF_GETADDRINFO_DGRAM (1<<4)
 #define SF_DCO_WIN (1<<5)
+#define SF_PREPEND_SA (1<<6)
     unsigned int sockflags;
     int mark;
     const char *bind_dev;
@@ -287,6 +288,7 @@ 
         HANDLE h;
     };
     bool is_handle;
+    bool prepend_sa; /* are incoming packets prepended with sockaddr? */
 } sockethandle_t;
 
 int sockethandle_finalize(sockethandle_t sh,
@@ -1042,6 +1044,7 @@ 
     {
         *from = sock->info.lsa->actual;
         sh.is_handle = true;
+        sh.prepend_sa = sock->sockflags & SF_PREPEND_SA;
     }
     return sockethandle_finalize(sh, &sock->reads, buf, from);
 }
@@ -1112,6 +1115,24 @@ 
             err = SocketHandleGetLastError(sh);
         }
     }
+
+    /* dco-win mp requires control packets to be prepended with sockaddr */
+    if (sock->sockflags & SF_PREPEND_SA)
+    {
+        if (to->dest.addr.sa.sa_family == AF_INET)
+        {
+            struct sockaddr_in sa;
+            memcpy(&sa, &to->dest.addr.in4, sizeof(sa));
+            buf_write_prepend(buf, &sa, sizeof(sa));
+        }
+        else
+        {
+            struct sockaddr_in6 sa;
+            memcpy(&sa, &to->dest.addr.in6, sizeof(sa));
+            buf_write_prepend(buf, &sa, sizeof(sa));
+        }
+    }
+
     socket_send_queue(sock, buf, to);
     if (status < 0)
     {