[Openvpn-devel,M] Change in openvpn[master]: dco-win: kernel notifications

Message ID 9bf069abf30837051fe449c2b42c8083da4b822e-HTML@gerrit.openvpn.net
State New
Headers show
Series [Openvpn-devel,M] Change in openvpn[master]: dco-win: kernel notifications | 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/+/816?usp=email

to review the following change.


Change subject: dco-win: kernel notifications
......................................................................

dco-win: kernel notifications

The driver supports notifications mechanism, which
is used to notify userspace about various events,
such as peer keepalive timeout, key expire and so on.

This uses existing framework of subscribing and
receiving dco notifications, used by FreeBSD and Linux
implementations. On Windows we use overlapped IO,
which state we keep in DCO context. We create an event,
which is associated with overlapped operation,
and inject it into openvpn event loop. When event is
signalled, we read overlapped result into DCO context,
which is later used by already existing code which
handles peer deletion.

Change-Id: Iedc10616225f6769c66d3c29d4a462b622ebbc6e
Signed-off-by: Lev Stipakov <lev@openvpn.net>
---
M src/openvpn/dco_win.c
M src/openvpn/dco_win.h
M src/openvpn/forward.c
M src/openvpn/mudp.c
M src/openvpn/multi.c
5 files changed, 124 insertions(+), 9 deletions(-)



  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/16/816/1

Patch

diff --git a/src/openvpn/dco_win.c b/src/openvpn/dco_win.c
index 7be6fdb..702b4bf 100644
--- a/src/openvpn/dco_win.c
+++ b/src/openvpn/dco_win.c
@@ -136,6 +136,17 @@ 
         return true;
     }
 
+    /* Use manual reset event so it remains signalled until
+     * explicitly reset. This way we won't lose notifications
+     */
+    dco->ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+    if (dco->ov.hEvent == NULL)
+    {
+        msg(M_ERR, "Error: ovpn_dco_init: CreateEvent failed");
+    }
+
+    dco->rwhandle.read = dco->ov.hEvent;
+
     /* open DCO device */
     struct gc_arena gc = gc_new();
     const char *device_guid;
@@ -690,11 +701,63 @@ 
     }
 }
 
+static void
+dco_handle_overlapped_success(dco_context_t *dco, bool queued)
+{
+    DWORD bytes_read = 0;
+    BOOL res = GetOverlappedResult(dco->tt->hand, &dco->ov, &bytes_read, FALSE);
+    if (res)
+    {
+        msg(D_DCO_DEBUG, "%s: completion%s success [%ld]", __func__, queued ? "" : " non-queued", bytes_read);
+
+        dco->dco_message_peer_id = dco->notif_buf.PeerId;
+        dco->dco_message_type = dco->notif_buf.Cmd;
+        dco->dco_del_peer_reason = dco->notif_buf.DelPeerReason;
+    }
+    else
+    {
+        msg(D_DCO_DEBUG | M_ERRNO, "%s: completion%s error", __func__, queued ? "" : " non-queued");
+    }
+}
+
 int
 dco_do_read(dco_context_t *dco)
 {
-    /* no-op on windows */
-    ASSERT(0);
+    if (dco->mode != dco_mode_mp)
+    {
+        ASSERT(false);
+    }
+
+    dco->dco_message_peer_id = -1;
+    dco->dco_message_type = 0;
+
+    switch (dco->iostate)
+    {
+        case IOSTATE_QUEUED:
+            dco_handle_overlapped_success(dco, true);
+
+            ASSERT(ResetEvent(dco->ov.hEvent));
+            dco->iostate = IOSTATE_INITIAL;
+
+            break;
+
+        case IOSTATE_IMMEDIATE_RETURN:
+            dco->iostate = IOSTATE_INITIAL;
+            ASSERT(ResetEvent(dco->ov.hEvent));
+
+            if (dco->ov_ret == ERROR_SUCCESS)
+            {
+                dco_handle_overlapped_success(dco, false);
+            }
+            else
+            {
+                SetLastError(dco->ov_ret);
+                msg(D_DCO_DEBUG | M_ERRNO, "%s: completion non-queued error", __func__);
+            }
+
+            break;
+    }
+
     return 0;
 }
 
@@ -737,8 +800,48 @@ 
 void
 dco_event_set(dco_context_t *dco, struct event_set *es, void *arg)
 {
-    /* no-op on windows */
-    ASSERT(0);
+    if (dco->mode != dco_mode_mp)
+    {
+        /* mp only */
+        return;
+    }
+
+    event_ctl(es, &dco->rwhandle, EVENT_READ, arg);
+
+    if (dco->iostate == IOSTATE_INITIAL)
+    {
+        /* the overlapped IOCTL will signal this event on I/O completion */
+        ASSERT(ResetEvent(dco->ov.hEvent));
+
+        if (!DeviceIoControl(dco->tt->hand, OVPN_IOCTL_NOTIFY_EVENT, NULL, 0, &dco->notif_buf, sizeof(dco->notif_buf), NULL, &dco->ov))
+        {
+            DWORD err = GetLastError();
+            if (err == ERROR_IO_PENDING) /* operation queued? */
+            {
+                dco->iostate = IOSTATE_QUEUED;
+                dco->ov_ret = ERROR_SUCCESS;
+
+                msg(D_DCO_DEBUG, "%s: notify ioctl queued", __func__);
+            }
+            else
+            {
+                /* error occured */
+                ASSERT(SetEvent(dco->ov.hEvent));
+                dco->iostate = IOSTATE_IMMEDIATE_RETURN;
+                dco->ov_ret = err;
+
+                msg(D_DCO_DEBUG | M_ERRNO, "%s: notify ioctl error", __func__);
+            }
+        }
+        else
+        {
+            ASSERT(SetEvent(dco->ov.hEvent));
+            dco->iostate = IOSTATE_IMMEDIATE_RETURN;
+            dco->ov_ret = ERROR_SUCCESS;
+
+            msg(D_DCO_DEBUG, "%s: notify ioctl immediate return", __func__);
+        }
+    }
 }
 
 const char *
diff --git a/src/openvpn/dco_win.h b/src/openvpn/dco_win.h
index 3e1c2f3..65c5d26 100644
--- a/src/openvpn/dco_win.h
+++ b/src/openvpn/dco_win.h
@@ -41,6 +41,18 @@ 
     struct tuntap *tt;
     dco_mode_type mode;
 
+    OVPN_NOTIFY_EVENT notif_buf; /**< Buffer for incoming notifications. */
+    OVERLAPPED ov; /**< Used by overlapped I/O for async IOCTL. */
+    int iostate; /**< State of overlapped I/O; see definitions in win32.h. */
+    struct rw_handle rwhandle; /**< Used to hook async I/O to the OpenVPN event loop. */
+    int ov_ret; /**< Win32 error code for overlapped operation, 0 for success */
+
+    int dco_message_peer_id;
+    int dco_message_type;
+    int dco_del_peer_reason;
+
+    uint64_t dco_read_bytes;
+    uint64_t dco_write_bytes;
 };
 
 typedef struct dco_context dco_context_t;
diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index 0a3936a..6468011 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -2076,8 +2076,8 @@ 
 #ifdef ENABLE_ASYNC_PUSH
     static uintptr_t file_shift = FILE_SHIFT;
 #endif
-#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)
-    static uintptr_t dco_shift = DCO_SHIFT;    /* Event from DCO linux kernel module */
+#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(TARGET_WIN32)
+    static uintptr_t dco_shift = DCO_SHIFT;    /* Event from DCO kernel module */
 #endif
 
     /*
@@ -2187,7 +2187,7 @@ 
     socket_set(c->c2.link_socket, c->c2.event_set, socket,
                &c->c2.link_socket->ev_arg, NULL);
     tun_set(c->c1.tuntap, c->c2.event_set, tuntap, (void *)tun_shift, NULL);
-#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)
+#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(TARGET_WIN32)
     if (socket & EVENT_READ && c->c2.did_open_tun)
     {
         dco_event_set(&c->c1.tuntap->dco, c->c2.event_set, (void *)dco_shift);
diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index d8e3d69..959d0a2 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -404,7 +404,7 @@ 
         multi_process_file_closed(m, mpp_flags);
     }
 #endif
-#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD))
+#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(TARGET_WIN32))
     else if (status & DCO_READ)
     {
         if (!IS_SIG(&m->top))
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 45b3cfa..6ebf573 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -3219,7 +3219,7 @@ 
 }
 #endif
 
-#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD))
+#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD) || defined(TARGET_WIN32))
 static void
 process_incoming_del_peer(struct multi_context *m, struct multi_instance *mi,
                           dco_context_t *dco)