[Openvpn-devel,XL] Change in openvpn[master]: multiproto: move generic event handling code in dedicated files

Message ID b652ef4fc6537557bc044bccd1b9f5817c4b69f9-HTML@gerrit.openvpn.net
State Not Applicable
Headers show
Series [Openvpn-devel,XL] Change in openvpn[master]: multiproto: move generic event handling code in dedicated files | expand

Commit Message

flichtenheld (Code Review) Nov. 15, 2023, 1:45 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/+/441?usp=email

to review the following change.


Change subject: multiproto: move generic event handling code in dedicated files
......................................................................

multiproto: move generic event handling code in dedicated files

Introduced multi_io.h and multi_io.c files to centralize
all codes related to multiple protocols.

Renamed the struct mtcp to struct multi_protocol since it
encompasses the event_set used by the parent context in server mode.

Several methods have also been renamed and moved to fit the
multiproto structure:

- multi_tcp_init() -> multi_protocol_init();
- multi_tcp_free() -> multi_protocol_free();
- multi_tcp_wait() -> multi_protocol_io_wait();
and so forth.

Change-Id: Id2d7957f5950115d9baade4c09fd9679b01f749b
Signed-off-by: Gianmarco De Gregori <gianmarco@mandelbit.com>
---
M CMakeLists.txt
M src/openvpn/Makefile.am
M src/openvpn/forward.c
M src/openvpn/forward.h
M src/openvpn/mtcp.c
M src/openvpn/mtcp.h
M src/openvpn/mudp.c
M src/openvpn/multi.c
M src/openvpn/multi.h
A src/openvpn/multi_io.c
A src/openvpn/multi_io.h
11 files changed, 821 insertions(+), 777 deletions(-)



  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/41/441/1

Patch

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d21c9bd..ee27465 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -402,6 +402,8 @@ 
     src/openvpn/mudp.h
     src/openvpn/multi.c
     src/openvpn/multi.h
+    src/openvpn/multi_io.c
+    src/openvpn/multi_io.h
     src/openvpn/ntlm.c
     src/openvpn/ntlm.h
     src/openvpn/occ.c
diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index 52deef8..17fcb1b 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -89,6 +89,7 @@ 
 	mtu.c mtu.h \
 	mudp.c mudp.h \
 	multi.c multi.h \
+	multi_io.c multi_io.h \
 	networking_freebsd.c \
 	networking_iproute2.c networking_iproute2.h \
 	networking_sitnl.c networking_sitnl.h \
diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index ee18f8b..0e5b611 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -2053,7 +2053,7 @@ 
  */
 
 void
-get_io_flags_dowork_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags)
+get_io_flags_dowork_udp(struct context *c, struct multi_protocol *multi_io, const unsigned int flags)
 {
     unsigned int socket = 0;
     unsigned int tuntap = 0;
@@ -2064,7 +2064,7 @@ 
      */
     if (flags & IOW_WAIT_SIGNAL)
     {
-        wait_signal(mtcp->es, (void *)err_shift);
+        wait_signal(multi_io->es, (void *)err_shift);
     }
 
     if (flags & IOW_TO_LINK)
@@ -2156,17 +2156,17 @@ 
     {
         if (proto_is_dgram(c->c2.link_sockets[i]->info.proto))
         {
-            socket_set(c->c2.link_sockets[i], mtcp->es, socket,
+            socket_set(c->c2.link_sockets[i], multi_io->es, socket,
                        &c->c2.link_sockets[i]->ev_arg, NULL);
         }
     }
-    mtcp->udp_flags = socket | tuntap;
+    multi_io->udp_flags = socket | tuntap;
 }
 
 void
-get_io_flags_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags)
+get_io_flags_udp(struct context *c, struct multi_protocol *multi_io, const unsigned int flags)
 {
-    mtcp->udp_flags = ES_ERROR;
+    multi_io->udp_flags = ES_ERROR;
     if (c->c2.fast_io && (flags & (IOW_TO_TUN | IOW_TO_LINK | IOW_MBUF)))
     {
         /* fast path -- only for TUN/TAP/UDP writes */
@@ -2179,7 +2179,7 @@ 
         {
             ret |= SOCKET_WRITE;
         }
-        mtcp->udp_flags = ret;
+        multi_io->udp_flags = ret;
     }
     else
     {
@@ -2205,13 +2205,13 @@ 
             {
                 ret |= TUN_READ;
             }
-            mtcp->udp_flags = ret;
+            multi_io->udp_flags = ret;
         }
         else
 #endif /* ifdef _WIN32 */
         {
             /* slow path - delegate to io_wait_dowork_udp to calculate flags */
-            get_io_flags_dowork_udp(c, mtcp, flags);
+            get_io_flags_dowork_udp(c, multi_io, flags);
         }
     }
 }
diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h
index 39ac975..a251d04 100644
--- a/src/openvpn/forward.h
+++ b/src/openvpn/forward.h
@@ -50,7 +50,7 @@ 
 #include "openvpn.h"
 #include "occ.h"
 #include "ping.h"
-#include "mtcp.h"
+#include "multi_io.h"
 
 #define IOW_TO_TUN          (1<<0)
 #define IOW_TO_LINK         (1<<1)
@@ -65,13 +65,19 @@ 
 
 #define IOW_READ            (IOW_READ_TUN|IOW_READ_LINK)
 
+
+/* forward declaration */
+/*struct multi_context;
+ * struct multi_protocol;
+ * struct context;*/
+
 extern counter_type link_read_bytes_global;
 
 extern counter_type link_write_bytes_global;
 
-void get_io_flags_dowork_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags);
+void get_io_flags_dowork_udp(struct context *c, struct multi_protocol *multi_io, const unsigned int flags);
 
-void get_io_flags_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags);
+void get_io_flags_udp(struct context *c, struct multi_protocol *multi_io, const unsigned int flags);
 
 void io_wait_dowork(struct context *c, const unsigned int flags);
 
diff --git a/src/openvpn/mtcp.c b/src/openvpn/mtcp.c
index 128a375..4efce35 100644
--- a/src/openvpn/mtcp.c
+++ b/src/openvpn/mtcp.c
@@ -29,6 +29,8 @@ 
 
 #include "multi.h"
 #include "forward.h"
+#include "mtcp.h"
+#include "multi_io.h"
 
 #include "memdbg.h"
 
@@ -36,70 +38,7 @@ 
 #include <sys/inotify.h>
 #endif
 
-/*
- * Special tags passed to event.[ch] functions
- */
-#define MTCP_SOCKET      ((void *)1)
-#define MTCP_TUN         ((void *)2)
-#define MTCP_SIG         ((void *)3) /* Only on Windows */
-#define MTCP_MANAGEMENT ((void *)4)
-#define MTCP_FILE_CLOSE_WRITE ((void *)5)
-#define MTCP_DCO        ((void *)6)
-
-struct ta_iow_flags
-{
-    unsigned int flags;
-    unsigned int ret;
-    unsigned int tun;
-    unsigned int sock;
-};
-
-#ifdef ENABLE_DEBUG
-static const char *
-pract(int action)
-{
-    switch (action)
-    {
-        case TA_UNDEF:
-            return "TA_UNDEF";
-
-        case TA_SOCKET_READ:
-            return "TA_SOCKET_READ";
-
-        case TA_SOCKET_READ_RESIDUAL:
-            return "TA_SOCKET_READ_RESIDUAL";
-
-        case TA_SOCKET_WRITE:
-            return "TA_SOCKET_WRITE";
-
-        case TA_SOCKET_WRITE_READY:
-            return "TA_SOCKET_WRITE_READY";
-
-        case TA_SOCKET_WRITE_DEFERRED:
-            return "TA_SOCKET_WRITE_DEFERRED";
-
-        case TA_TUN_READ:
-            return "TA_TUN_READ";
-
-        case TA_TUN_WRITE:
-            return "TA_TUN_WRITE";
-
-        case TA_INITIAL:
-            return "TA_INITIAL";
-
-        case TA_TIMEOUT:
-            return "TA_TIMEOUT";
-
-        case TA_TUN_WRITE_TIMEOUT:
-            return "TA_TUN_WRITE_TIMEOUT";
-
-        default:
-            return "?";
-    }
-}
-#endif /* ENABLE_DEBUG */
-
-static struct multi_instance *
+struct multi_instance *
 multi_create_instance_tcp(struct multi_context *m, struct link_socket *ls)
 {
     struct gc_arena gc = gc_new();
@@ -180,126 +119,19 @@ 
     mbuf_free(mi->tcp_link_out_deferred);
 }
 
-struct multi_tcp *
-multi_tcp_init(int maxevents, int *maxclients)
-{
-    struct multi_tcp *mtcp;
-    const int extra_events = BASE_N_EVENTS;
-
-    ASSERT(maxevents >= 1);
-    ASSERT(maxclients);
-
-    ALLOC_OBJ_CLEAR(mtcp, struct multi_tcp);
-    mtcp->maxevents = maxevents + extra_events;
-    mtcp->es = event_set_init(&mtcp->maxevents, 0);
-    wait_signal(mtcp->es, MTCP_SIG);
-    ALLOC_ARRAY(mtcp->esr, struct event_set_return, mtcp->maxevents);
-    *maxclients = max_int(min_int(mtcp->maxevents - extra_events, *maxclients), 1);
-    msg(D_MULTI_LOW, "MULTI: TCP INIT maxclients=%d maxevents=%d", *maxclients, mtcp->maxevents);
-    return mtcp;
-}
-
 void
-multi_tcp_delete_event(struct multi_tcp *mtcp, event_t event)
-{
-    if (mtcp && mtcp->es)
-    {
-        event_del(mtcp->es, event);
-    }
-}
-
-void
-multi_tcp_free(struct multi_tcp *mtcp)
-{
-    if (mtcp)
-    {
-        event_free(mtcp->es);
-        free(mtcp->esr);
-        free(mtcp);
-    }
-}
-
-void
-multi_tcp_dereference_instance(struct multi_tcp *mtcp, struct multi_instance *mi)
+multi_tcp_dereference_instance(struct multi_protocol *multi_io, struct multi_instance *mi)
 {
     struct link_socket *ls = mi->context.c2.link_sockets[0];
     if (ls && mi->socket_set_called)
     {
-        event_del(mtcp->es, socket_event_handle(ls));
+        event_del(multi_io->es, socket_event_handle(ls));
         mi->socket_set_called = false;
     }
-    mtcp->n_esr = 0;
+    multi_io->n_esr = 0;
 }
 
-static inline void
-multi_tcp_set_global_rw_flags(struct multi_context *m, struct multi_instance *mi)
-{
-    if (mi)
-    {
-        mi->socket_set_called = true;
-        socket_set(mi->context.c2.link_sockets[0],
-                   m->mtcp->es,
-                   mbuf_defined(mi->tcp_link_out_deferred) ? EVENT_WRITE : EVENT_READ,
-                   &mi->ev_arg,
-                   &mi->tcp_rwflags);
-    }
-}
-
-int
-multi_tcp_wait(struct context *c,
-               struct multi_tcp *mtcp)
-{
-    int status, i;
-    unsigned int *persistent = &mtcp->tun_rwflags;
-
-    for (i = 0; i < c->c1.link_sockets_num; i++)
-    {
-        socket_set_listen_persistent(c->c2.link_sockets[i], mtcp->es,
-                                     &c->c2.link_sockets[i]->ev_arg);
-    }
-
-#ifdef _WIN32
-    if (tuntap_is_wintun(c->c1.tuntap))
-    {
-        if (!tuntap_ring_empty(c->c1.tuntap))
-        {
-            /* there is data in wintun ring buffer, read it immediately */
-            mtcp->esr[0].arg = MTCP_TUN;
-            mtcp->esr[0].rwflags = EVENT_READ;
-            mtcp->n_esr = 1;
-            return 1;
-        }
-        persistent = NULL;
-    }
-#endif
-    tun_set(c->c1.tuntap, mtcp->es, EVENT_READ, MTCP_TUN, persistent);
-#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)
-    dco_event_set(&c->c1.tuntap->dco, mtcp->es, MTCP_DCO);
-#endif
-
-#ifdef ENABLE_MANAGEMENT
-    if (management)
-    {
-        management_socket_set(management, mtcp->es, MTCP_MANAGEMENT, &mtcp->management_persist_flags);
-    }
-#endif
-
-#ifdef ENABLE_ASYNC_PUSH
-    /* arm inotify watcher */
-    event_ctl(mtcp->es, c->c2.inotify_fd, EVENT_READ, MTCP_FILE_CLOSE_WRITE);
-#endif
-
-    status = event_wait(mtcp->es, &c->c2.timeval, mtcp->esr, mtcp->maxevents);
-    update_time();
-    mtcp->n_esr = 0;
-    if (status > 0)
-    {
-        mtcp->n_esr = status;
-    }
-    return status;
-}
-
-static inline struct context *
+struct context *
 multi_tcp_context(struct multi_context *m, struct multi_instance *mi)
 {
     if (mi)
@@ -312,7 +144,7 @@ 
     }
 }
 
-static bool
+bool
 multi_tcp_process_outgoing_link_ready(struct multi_context *m, struct multi_instance *mi, const unsigned int mpp_flags)
 {
     struct mbuf_item item;
@@ -336,7 +168,7 @@ 
     return ret;
 }
 
-static bool
+bool
 multi_tcp_process_outgoing_link(struct multi_context *m, bool defer, const unsigned int mpp_flags)
 {
     struct multi_instance *mi = multi_process_outgoing_link_pre(m);
@@ -379,566 +211,3 @@ 
     }
     return ret;
 }
-
-static int
-multi_tcp_wait_lite(struct multi_context *m, struct multi_instance *mi, const int action, bool *tun_input_pending)
-{
-    struct context *c = multi_tcp_context(m, mi);
-    unsigned int looking_for = 0;
-
-    dmsg(D_MULTI_DEBUG, "MULTI TCP: multi_tcp_wait_lite a=%s mi=" ptr_format,
-         pract(action),
-         (ptr_type)mi);
-
-    tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */
-
-    switch (action)
-    {
-        case TA_TUN_READ:
-            looking_for = TUN_READ;
-            tun_input_pending = NULL;
-            io_wait(c, IOW_READ_TUN);
-            break;
-
-        case TA_SOCKET_READ:
-            looking_for = SOCKET_READ;
-            tun_input_pending = NULL;
-            io_wait(c, IOW_READ_LINK);
-            break;
-
-        case TA_TUN_WRITE:
-            looking_for = TUN_WRITE;
-            tun_input_pending = NULL;
-            c->c2.timeval.tv_sec = 1; /* For some reason, the Linux 2.2 TUN/TAP driver hits this timeout */
-            perf_push(PERF_PROC_OUT_TUN_MTCP);
-            io_wait(c, IOW_TO_TUN);
-            perf_pop();
-            break;
-
-        case TA_SOCKET_WRITE:
-            looking_for = SOCKET_WRITE;
-            io_wait(c, IOW_TO_LINK|IOW_READ_TUN_FORCE);
-            break;
-
-        default:
-            msg(M_FATAL, "MULTI TCP: multi_tcp_wait_lite, unhandled action=%d", action);
-    }
-
-    if (tun_input_pending && (c->c2.event_set_status & TUN_READ))
-    {
-        *tun_input_pending = true;
-    }
-
-    if (c->c2.event_set_status & looking_for)
-    {
-        return action;
-    }
-    else
-    {
-        switch (action)
-        {
-            /* TCP socket output buffer is full */
-            case TA_SOCKET_WRITE:
-                return TA_SOCKET_WRITE_DEFERRED;
-
-            /* TUN device timed out on accepting write */
-            case TA_TUN_WRITE:
-                return TA_TUN_WRITE_TIMEOUT;
-        }
-
-        return TA_UNDEF;
-    }
-}
-
-static struct multi_instance *
-multi_tcp_dispatch(struct multi_context *m, struct multi_instance *mi, const int action)
-{
-    const unsigned int mpp_flags = MPP_PRE_SELECT|MPP_RECORD_TOUCH;
-    struct multi_instance *touched = mi;
-    m->mpp_touched = &touched;
-
-    dmsg(D_MULTI_DEBUG, "MULTI TCP: multi_tcp_dispatch a=%s mi=" ptr_format,
-         pract(action),
-         (ptr_type)mi);
-
-    switch (action)
-    {
-        case TA_TUN_READ:
-            read_incoming_tun(&m->top);
-            if (!IS_SIG(&m->top))
-            {
-                multi_process_incoming_tun(m, mpp_flags);
-            }
-            break;
-
-        case TA_SOCKET_READ:
-        case TA_SOCKET_READ_RESIDUAL:
-            ASSERT(mi);
-            ASSERT(mi->context.c2.link_sockets);
-            ASSERT(mi->context.c2.link_sockets[0]);
-            set_prefix(mi);
-            read_incoming_link(&mi->context, mi->context.c2.link_sockets[0]);
-            clear_prefix();
-            if (!IS_SIG(&mi->context))
-            {
-                multi_process_incoming_link(m, mi, mpp_flags,
-                                            mi->context.c2.link_sockets[0]);
-                if (!IS_SIG(&mi->context))
-                {
-                    stream_buf_read_setup(mi->context.c2.link_sockets[0]);
-                }
-            }
-            break;
-
-        case TA_TIMEOUT:
-            multi_process_timeout(m, mpp_flags);
-            break;
-
-        case TA_TUN_WRITE:
-            multi_process_outgoing_tun(m, mpp_flags);
-            break;
-
-        case TA_TUN_WRITE_TIMEOUT:
-            multi_process_drop_outgoing_tun(m, mpp_flags);
-            break;
-
-        case TA_SOCKET_WRITE_READY:
-            ASSERT(mi);
-            multi_tcp_process_outgoing_link_ready(m, mi, mpp_flags);
-            break;
-
-        case TA_SOCKET_WRITE:
-            multi_tcp_process_outgoing_link(m, false, mpp_flags);
-            break;
-
-        case TA_SOCKET_WRITE_DEFERRED:
-            multi_tcp_process_outgoing_link(m, true, mpp_flags);
-            break;
-
-        case TA_INITIAL:
-            ASSERT(mi);
-            multi_tcp_set_global_rw_flags(m, mi);
-            multi_process_post(m, mi, mpp_flags);
-            break;
-
-        default:
-            msg(M_FATAL, "MULTI TCP: multi_tcp_dispatch, unhandled action=%d", action);
-    }
-
-    m->mpp_touched = NULL;
-    return touched;
-}
-
-static int
-multi_tcp_post(struct multi_context *m, struct multi_instance *mi, const int action)
-{
-    struct context *c = multi_tcp_context(m, mi);
-    int newaction = TA_UNDEF;
-
-#define MTP_NONE         0
-#define MTP_TUN_OUT      (1<<0)
-#define MTP_LINK_OUT     (1<<1)
-    unsigned int flags = MTP_NONE;
-
-    if (TUN_OUT(c))
-    {
-        flags |= MTP_TUN_OUT;
-    }
-    if (LINK_OUT(c))
-    {
-        flags |= MTP_LINK_OUT;
-    }
-
-    switch (flags)
-    {
-        case MTP_TUN_OUT|MTP_LINK_OUT:
-        case MTP_TUN_OUT:
-            newaction = TA_TUN_WRITE;
-            break;
-
-        case MTP_LINK_OUT:
-            newaction = TA_SOCKET_WRITE;
-            break;
-
-        case MTP_NONE:
-            if (mi && sockets_read_residual(c))
-            {
-                newaction = TA_SOCKET_READ_RESIDUAL;
-            }
-            else
-            {
-                multi_tcp_set_global_rw_flags(m, mi);
-            }
-            break;
-
-        default:
-        {
-            struct gc_arena gc = gc_new();
-            msg(M_FATAL, "MULTI TCP: multi_tcp_post bad state, mi=%s flags=%d",
-                multi_instance_string(mi, false, &gc),
-                flags);
-            gc_free(&gc);
-            break;
-        }
-    }
-
-    dmsg(D_MULTI_DEBUG, "MULTI TCP: multi_tcp_post %s -> %s",
-         pract(action),
-         pract(newaction));
-
-    return newaction;
-}
-
-void
-multi_tcp_action(struct multi_context *m, struct multi_instance *mi, int action, bool poll)
-{
-    bool tun_input_pending = false;
-
-    do
-    {
-        dmsg(D_MULTI_DEBUG, "MULTI TCP: multi_tcp_action a=%s p=%d",
-             pract(action),
-             poll);
-
-        /*
-         * If TA_SOCKET_READ_RESIDUAL, it means we still have pending
-         * input packets which were read by a prior TCP recv.
-         *
-         * Otherwise do a "lite" wait, which means we wait with 0 timeout
-         * on I/O events only related to the current instance, not
-         * the big list of events.
-         *
-         * On our first pass, poll will be false because we already know
-         * that input is available, and to call io_wait would be redundant.
-         */
-        if (poll && action != TA_SOCKET_READ_RESIDUAL)
-        {
-            const int orig_action = action;
-            action = multi_tcp_wait_lite(m, mi, action, &tun_input_pending);
-            if (action == TA_UNDEF)
-            {
-                msg(M_FATAL, "MULTI TCP: I/O wait required blocking in multi_tcp_action, action=%d", orig_action);
-            }
-        }
-
-        /*
-         * Dispatch the action
-         */
-        struct multi_instance *touched = multi_tcp_dispatch(m, mi, action);
-
-        /*
-         * Signal received or TCP connection
-         * reset by peer?
-         */
-        if (touched && IS_SIG(&touched->context))
-        {
-            if (mi == touched)
-            {
-                mi = NULL;
-            }
-            multi_close_instance_on_signal(m, touched);
-        }
-
-
-        /*
-         * If dispatch produced any pending output
-         * for a particular instance, point to
-         * that instance.
-         */
-        if (m->pending)
-        {
-            mi = m->pending;
-        }
-
-        /*
-         * Based on the effects of the action,
-         * such as generating pending output,
-         * possibly transition to a new action state.
-         */
-        action = multi_tcp_post(m, mi, action);
-
-        /*
-         * If we are finished processing the original action,
-         * check if we have any TUN input.  If so, transition
-         * our action state to processing this input.
-         */
-        if (tun_input_pending && action == TA_UNDEF)
-        {
-            action = TA_TUN_READ;
-            mi = NULL;
-            tun_input_pending = false;
-            poll = false;
-        }
-        else
-        {
-            poll = true;
-        }
-
-    } while (action != TA_UNDEF);
-}
-
-void
-multi_tcp_process_io(struct multi_context *m)
-{
-    struct multi_tcp *mtcp = m->mtcp;
-    const unsigned int udp_status = mtcp->udp_flags;
-    const unsigned int mpp_flags = m->top.c2.fast_io
-                                   ? (MPP_CONDITIONAL_PRE_SELECT | MPP_CLOSE_ON_SIGNAL)
-                                   : (MPP_PRE_SELECT | MPP_CLOSE_ON_SIGNAL);
-    int i;
-
-    for (i = 0; i < mtcp->n_esr; ++i)
-    {
-        struct event_set_return *e = &mtcp->esr[i];
-        struct event_arg *ev_arg = (struct event_arg *)e->arg;
-
-        /* incoming data for instance or listening socket? */
-        if (e->arg >= MULTI_N)
-        {
-            switch (ev_arg->type)
-            {
-                struct multi_instance *mi;
-
-                /* react to event on child instance */
-                case EVENT_ARG_MULTI_INSTANCE:
-                    if (!ev_arg->u.mi)
-                    {
-                        msg(D_MULTI_ERRORS, "MULTI: mtcp_proc_io: null minstance");
-                        break;
-                    }
-
-                    mi = ev_arg->u.mi;
-                    if (e->rwflags & EVENT_WRITE)
-                    {
-                        multi_tcp_action(m, mi, TA_SOCKET_WRITE_READY, false);
-                    }
-                    else if (e->rwflags & EVENT_READ)
-                    {
-                        multi_tcp_action(m, mi, TA_SOCKET_READ, false);
-                    }
-                    break;
-
-                /* new incoming TCP client attempting to connect? */
-                case EVENT_ARG_LINK_SOCKET:
-                    if (!ev_arg->u.ls)
-                    {
-                        msg(D_MULTI_ERRORS, "MULTI: mtcp_proc_io: null socket");
-                        break;
-                    }
-
-                    if (!proto_is_dgram(ev_arg->u.ls->info.proto))
-                    {
-                        socket_reset_listen_persistent(ev_arg->u.ls);
-                        mi = multi_create_instance_tcp(m, ev_arg->u.ls);
-                        if (mi)
-                        {
-                            multi_tcp_action(m, mi, TA_INITIAL, false);
-                        }
-                        break;
-                    }
-                    else
-                    {
-                        if (e->arg >= MULTI_N)
-                        {
-                            struct event_arg *ev_arg = (struct event_arg *)e->arg;
-                            if (ev_arg->type != EVENT_ARG_LINK_SOCKET)
-                            {
-                                mtcp->udp_flags = ES_ERROR;
-                                msg(D_LINK_ERRORS,
-                                    "io_work: non socket event delivered");
-                                break;
-                            }
-                        }
-                        else
-                        {
-                            ev_arg->pending = true;
-                        }
-
-                        if (udp_status & SOCKET_READ)
-                        {
-                            read_incoming_link(&m->top, ev_arg->u.ls);
-                            if (!IS_SIG(&m->top))
-                            {
-                                multi_process_incoming_link(m, NULL, mpp_flags,
-                                                            ev_arg->u.ls);
-                            }
-                        }
-
-                        while (true)
-                        {
-                            multi_get_timeout(m, &m->top.c2.timeval);
-                            get_io_flags_udp(&m->top, m->mtcp, p2mp_iow_flags(m));
-                            MULTI_CHECK_SIG(m);
-
-                            multi_process_per_second_timers(m);
-
-                            if (m->mtcp->udp_flags == ES_TIMEOUT)
-                            {
-                                multi_process_timeout(m, MPP_PRE_SELECT | MPP_CLOSE_ON_SIGNAL);
-                            }
-                            else
-                            {
-                                multi_process_io_udp(m);
-                                MULTI_CHECK_SIG(m);
-                                break;
-                            }
-                        }
-                        break;
-                    }
-            }
-        }
-        else
-        {
-#ifdef ENABLE_MANAGEMENT
-            if (e->arg == MTCP_MANAGEMENT)
-            {
-                ASSERT(management);
-                management_io(management);
-            }
-            else
-#endif
-            /* incoming data on TUN? */
-            if (e->arg == MTCP_TUN)
-            {
-                if (e->rwflags & EVENT_WRITE)
-                {
-                    multi_tcp_action(m, NULL, TA_TUN_WRITE, false);
-                }
-                else if (e->rwflags & EVENT_READ)
-                {
-                    multi_tcp_action(m, NULL, TA_TUN_READ, false);
-                }
-            }
-            /* new incoming TCP client attempting to connect? */
-            else if (e->arg == MTCP_SOCKET)
-            {
-                struct multi_instance *mi;
-                ASSERT(m->top.c2.link_sockets[0]);
-                socket_reset_listen_persistent(m->top.c2.link_sockets[0]);
-                mi = multi_create_instance_tcp(m, m->top.c2.link_sockets[0]);
-                if (mi)
-                {
-                    multi_tcp_action(m, mi, TA_INITIAL, false);
-                }
-            }
-#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD))
-            /* incoming data on DCO? */
-            else if (e->arg == MTCP_DCO)
-            {
-                multi_process_incoming_dco(m);
-            }
-#endif
-            /* signal received? */
-            else if (e->arg == MTCP_SIG)
-            {
-                get_signal(&m->top.sig->signal_received);
-            }
-#ifdef ENABLE_ASYNC_PUSH
-            else if (e->arg == MTCP_FILE_CLOSE_WRITE)
-            {
-                multi_process_file_closed(m, MPP_PRE_SELECT | MPP_RECORD_TOUCH);
-            }
-#endif
-        }
-        if (IS_SIG(&m->top))
-        {
-            break;
-        }
-    }
-    mtcp->n_esr = 0;
-
-    /*
-     * Process queued mbuf packets destined for TCP socket
-     */
-    {
-        struct multi_instance *mi;
-        while (!IS_SIG(&m->top) && (mi = mbuf_peek(m->mbuf)) != NULL)
-        {
-            multi_tcp_action(m, mi, TA_SOCKET_WRITE, true);
-        }
-    }
-}
-
-/*
- * Top level event loop for single-threaded operation.
- * TCP mode.
- */
-void
-tunnel_server_tcp(struct context *top)
-{
-    struct multi_context multi;
-    int status;
-
-    top->mode = CM_TOP;
-    context_clear_2(top);
-
-    /* initialize top-tunnel instance */
-    init_instance_handle_signals(top, top->es, CC_HARD_USR1_TO_HUP);
-    if (IS_SIG(top))
-    {
-        return;
-    }
-
-    /* initialize global multi_context object */
-    multi_init(&multi, top);
-
-    /* initialize our cloned top object */
-    multi_top_init(&multi, top);
-
-    /* initialize management interface */
-    init_management_callback_multi(&multi);
-
-    /* finished with initialization */
-    initialization_sequence_completed(top, ISC_SERVER); /* --mode server --proto tcp-server */
-
-#ifdef ENABLE_ASYNC_PUSH
-    multi.top.c2.inotify_fd = inotify_init();
-    if (multi.top.c2.inotify_fd < 0)
-    {
-        msg(D_MULTI_ERRORS | M_ERRNO, "MULTI: inotify_init error");
-    }
-#endif
-
-    /* per-packet event loop */
-    while (true)
-    {
-        perf_push(PERF_EVENT_LOOP);
-
-        /* wait on tun/socket list */
-        multi_get_timeout(&multi, &multi.top.c2.timeval);
-        status = multi_tcp_wait(&multi.top, multi.mtcp);
-        MULTI_CHECK_SIG(&multi);
-
-        /* check on status of coarse timers */
-        multi_process_per_second_timers(&multi);
-
-        /* timeout? */
-        if (status > 0)
-        {
-            /* process the I/O which triggered select */
-            /*multi_tcp_process_io(&multi); */
-            MULTI_CHECK_SIG(&multi);
-        }
-        else if (status == 0)
-        {
-            /*multi_tcp_action(&multi, NULL, TA_TIMEOUT, false); */
-        }
-
-        perf_pop();
-    }
-
-#ifdef ENABLE_ASYNC_PUSH
-    close(top->c2.inotify_fd);
-#endif
-
-    /* shut down management interface */
-    uninit_management_callback();
-
-    /* save ifconfig-pool */
-    multi_ifconfig_pool_persist(&multi, true);
-
-    /* tear down tunnel instance (unless --persist-tun) */
-    multi_uninit(&multi);
-    multi_top_free(&multi);
-    close_instance(top);
-}
diff --git a/src/openvpn/mtcp.h b/src/openvpn/mtcp.h
index d55bdfd..b8a0191 100644
--- a/src/openvpn/mtcp.h
+++ b/src/openvpn/mtcp.h
@@ -63,26 +63,23 @@ 
 
 struct multi_instance;
 struct context;
+struct multi_protocol;
 
-struct multi_tcp *multi_tcp_init(int maxevents, int *maxclients);
-
-void multi_tcp_free(struct multi_tcp *mtcp);
-
-void multi_tcp_dereference_instance(struct multi_tcp *mtcp, struct multi_instance *mi);
+void multi_tcp_dereference_instance(struct multi_protocol *multi_io, struct multi_instance *mi);
 
 bool multi_tcp_instance_specific_init(struct multi_context *m, struct multi_instance *mi);
 
 void multi_tcp_instance_specific_free(struct multi_instance *mi);
 
-int multi_tcp_wait(struct context *c, struct multi_tcp *mtcp);
+bool multi_tcp_process_outgoing_link(struct multi_context *m, bool defer, const unsigned int mpp_flags);
 
-void multi_tcp_process_io(struct multi_context *m);
+bool multi_tcp_process_outgoing_link_ready(struct multi_context *m, struct multi_instance *mi, const unsigned int mpp_flags);
 
-void multi_tcp_action(struct multi_context *m, struct multi_instance *mi, int action, bool poll);
-
+struct multi_instance *multi_create_instance_tcp(struct multi_context *m, struct link_socket *ls);
 
 void multi_tcp_link_out_deferred(struct multi_context *m, struct multi_instance *mi);
 
+struct context *multi_tcp_context(struct multi_context *m, struct multi_instance *mi);
 
 /**************************************************************************/
 /**
@@ -91,9 +88,6 @@ 
  *
  * @param top - Top-level context structure.
  */
-void tunnel_server_tcp(struct context *top);
-
-
-void multi_tcp_delete_event(struct multi_tcp *mtcp, event_t event);
+/*void tunnel_server_tcp(struct context *top); */
 
 #endif /* ifndef MTCP_H */
diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index 8945a55..d29f7a5 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -379,7 +379,7 @@ 
 void
 multi_process_io_udp(struct multi_context *m)
 {
-    unsigned int status = m->mtcp->udp_flags;
+    unsigned int status = m->multi_io->udp_flags;
     const unsigned int mpp_flags = m->top.c2.fast_io
                                    ? (MPP_CONDITIONAL_PRE_SELECT | MPP_CLOSE_ON_SIGNAL)
                                    : (MPP_PRE_SELECT | MPP_CLOSE_ON_SIGNAL);
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 108a5a8..b29134e 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -440,7 +440,7 @@ 
     /*
      * Initialize multi-socket TCP I/O wait object
      */
-    m->mtcp = multi_tcp_init(t->options.max_clients, &m->max_clients);
+    m->multi_io = multi_protocol_init(t->options.max_clients, &m->max_clients);
 
     m->tcp_queue_limit = t->options.tcp_queue_limit;
 
@@ -668,7 +668,7 @@ 
 
         if (!is_dgram)
         {
-            multi_tcp_dereference_instance(m->mtcp, mi);
+            multi_tcp_dereference_instance(m->multi_io, mi);
         }
 
         mbuf_dereference_instance(m->mbuf, mi);
@@ -746,7 +746,7 @@ 
         initial_rate_limit_free(m->initial_rate_limiter);
         multi_reap_free(m->reaper);
         mroute_helper_free(m->route_helper);
-        multi_tcp_free(m->mtcp);
+        multi_protocol_free(m->multi_io);
     }
 }
 
@@ -3997,9 +3997,9 @@ 
 management_delete_event(void *arg, event_t event)
 {
     struct multi_context *m = (struct multi_context *) arg;
-    if (m->mtcp)
+    if (m->multi_io)
     {
-        multi_tcp_delete_event(m->mtcp, event);
+        multi_protocol_delete_event(m->multi_io, event);
     }
 }
 
@@ -4177,7 +4177,7 @@ 
     ASSERT(mi->context.c2.tls_multi->peer_id < m->max_clients);
 }
 
-
+/* Actual multi protocol event loop in server mode */
 void
 tunnel_server_loop(struct multi_context *multi)
 {
@@ -4189,7 +4189,7 @@ 
 
         /* wait on tun/socket list */
         multi_get_timeout(multi, &multi->top.c2.timeval);
-        status = multi_tcp_wait(&multi->top, multi->mtcp);
+        status = multi_protocol_io_wait(&multi->top, multi->multi_io);
         MULTI_CHECK_SIG(multi);
 
         /* check on status of coarse timers */
@@ -4199,18 +4199,25 @@ 
         if (status > 0)
         {
             /* process the I/O which triggered select */
-            multi_tcp_process_io(multi);
+            multi_protocol_process_io(multi);
             MULTI_CHECK_SIG(multi);
         }
         else if (status == 0)
         {
-            multi_tcp_action(multi, NULL, TA_TIMEOUT, false);
+            multi_protocol_action(multi, NULL, TA_TIMEOUT, false);
         }
 
         perf_pop();
     }
 }
 
+/**************************************************************************/
+/**
+ * Main event loop for OpenVPN in server mode.
+ * @ingroup eventloop
+ *
+ * @param top - Top-level context structure.
+ */
 void
 tunnel_server_init(struct context *top)
 {
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 472c2a3..dd7a29c 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -37,6 +37,7 @@ 
 #include "pool.h"
 #include "mudp.h"
 #include "mtcp.h"
+#include "multi_io.h"
 #include "perf.h"
 #include "vlan.h"
 #include "reflect_filter.h"
@@ -169,8 +170,8 @@ 
     struct mbuf_set *mbuf;      /**< Set of buffers for passing data
                                  *   channel packets between VPN tunnel
                                  *   instances. */
-    struct multi_tcp *mtcp;     /**< State specific to OpenVPN using TCP
-                                 *   as external transport. */
+    struct multi_protocol *multi_io;     /**< State specific to OpenVPN using both TCP
+                                          *   and UDP as external transport. */
     struct ifconfig_pool *ifconfig_pool;
     struct frequency_limit *new_connection_limiter;
     struct initial_packet_rate_limit *initial_rate_limiter;
diff --git a/src/openvpn/multi_io.c b/src/openvpn/multi_io.c
new file mode 100644
index 0000000..a1ea7fb
--- /dev/null
+++ b/src/openvpn/multi_io.c
@@ -0,0 +1,687 @@ 
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2002-2023 OpenVPN Inc <sales@openvpn.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "syshead.h"
+
+#include "memdbg.h"
+
+#include "multi.h"
+#include "forward.h"
+#include "multi_io.h"
+
+#ifdef HAVE_SYS_INOTIFY_H
+#include <sys/inotify.h>
+#endif
+
+/*
+ * Special tags passed to event.[ch] functions
+ */
+#define MULTI_IO_SOCKET      ((void *)1)
+#define MULTI_IO_TUN         ((void *)2)
+#define MULTI_IO_SIG         ((void *)3) /* Only on Windows */
+#define MULTI_IO_MANAGEMENT ((void *)4)
+#define MULTI_IO_FILE_CLOSE_WRITE ((void *)5)
+#define MULTI_IO_DCO        ((void *)6)
+
+struct ta_iow_flags
+{
+    unsigned int flags;
+    unsigned int ret;
+    unsigned int tun;
+    unsigned int sock;
+};
+
+#ifdef ENABLE_DEBUG
+static const char *
+pract(int action)
+{
+    switch (action)
+    {
+        case TA_UNDEF:
+            return "TA_UNDEF";
+
+        case TA_SOCKET_READ:
+            return "TA_SOCKET_READ";
+
+        case TA_SOCKET_READ_RESIDUAL:
+            return "TA_SOCKET_READ_RESIDUAL";
+
+        case TA_SOCKET_WRITE:
+            return "TA_SOCKET_WRITE";
+
+        case TA_SOCKET_WRITE_READY:
+            return "TA_SOCKET_WRITE_READY";
+
+        case TA_SOCKET_WRITE_DEFERRED:
+            return "TA_SOCKET_WRITE_DEFERRED";
+
+        case TA_TUN_READ:
+            return "TA_TUN_READ";
+
+        case TA_TUN_WRITE:
+            return "TA_TUN_WRITE";
+
+        case TA_INITIAL:
+            return "TA_INITIAL";
+
+        case TA_TIMEOUT:
+            return "TA_TIMEOUT";
+
+        case TA_TUN_WRITE_TIMEOUT:
+            return "TA_TUN_WRITE_TIMEOUT";
+
+        default:
+            return "?";
+    }
+}
+#endif /* ENABLE_DEBUG */
+
+struct multi_protocol *
+multi_protocol_init(int maxevents, int *maxclients)
+{
+    struct multi_protocol *multi_io;
+    const int extra_events = BASE_N_EVENTS;
+
+    ASSERT(maxevents >= 1);
+    ASSERT(maxclients);
+
+    ALLOC_OBJ_CLEAR(multi_io, struct multi_protocol);
+    multi_io->maxevents = maxevents + extra_events;
+    multi_io->es = event_set_init(&multi_io->maxevents, 0);
+    wait_signal(multi_io->es, MULTI_IO_SIG);
+    ALLOC_ARRAY(multi_io->esr, struct event_set_return, multi_io->maxevents);
+    *maxclients = max_int(min_int(multi_io->maxevents - extra_events, *maxclients), 1);
+    msg(D_MULTI_LOW, "MULTI: TCP INIT maxclients=%d maxevents=%d", *maxclients, multi_io->maxevents);
+    return multi_io;
+}
+
+void
+multi_protocol_free(struct multi_protocol *multi_io)
+{
+    if (multi_io)
+    {
+        event_free(multi_io->es);
+        free(multi_io->esr);
+        free(multi_io);
+    }
+}
+
+int
+multi_protocol_io_wait(struct context *c,
+                       struct multi_protocol *multi_io)
+{
+    int status, i;
+    unsigned int *persistent = &multi_io->tun_rwflags;
+
+    for (i = 0; i < c->c1.link_sockets_num; i++)
+    {
+        socket_set_listen_persistent(c->c2.link_sockets[i], multi_io->es,
+                                     &c->c2.link_sockets[i]->ev_arg);
+    }
+
+#ifdef _WIN32
+    if (tuntap_is_wintun(c->c1.tuntap))
+    {
+        if (!tuntap_ring_empty(c->c1.tuntap))
+        {
+            /* there is data in wintun ring buffer, read it immediately */
+            multi_io->esr[0].arg = MULTI_IO_TUN;
+            multi_io->esr[0].rwflags = EVENT_READ;
+            multi_io->n_esr = 1;
+            return 1;
+        }
+        persistent = NULL;
+    }
+#endif
+    tun_set(c->c1.tuntap, multi_io->es, EVENT_READ, MULTI_IO_TUN, persistent);
+#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)
+    dco_event_set(&c->c1.tuntap->dco, multi_io->es, MULTI_IO_DCO);
+#endif
+
+#ifdef ENABLE_MANAGEMENT
+    if (management)
+    {
+        management_socket_set(management, multi_io->es, MULTI_IO_MANAGEMENT, &multi_io->management_persist_flags);
+    }
+#endif
+
+#ifdef ENABLE_ASYNC_PUSH
+    /* arm inotify watcher */
+    event_ctl(multi_io->es, c->c2.inotify_fd, EVENT_READ, MULTI_IO_FILE_CLOSE_WRITE);
+#endif
+
+    status = event_wait(multi_io->es, &c->c2.timeval, multi_io->esr, multi_io->maxevents);
+    update_time();
+    multi_io->n_esr = 0;
+    if (status > 0)
+    {
+        multi_io->n_esr = status;
+    }
+    return status;
+}
+
+static int
+multi_protocol_wait_lite(struct multi_context *m, struct multi_instance *mi, const int action, bool *tun_input_pending)
+{
+    struct context *c = multi_tcp_context(m, mi);
+    unsigned int looking_for = 0;
+
+    dmsg(D_MULTI_DEBUG, "MULTI PROTOCOL: multi_protocol_wait_lite a=%s mi=" ptr_format,
+         pract(action),
+         (ptr_type)mi);
+
+    tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */
+
+    switch (action)
+    {
+        case TA_TUN_READ:
+            looking_for = TUN_READ;
+            tun_input_pending = NULL;
+            io_wait(c, IOW_READ_TUN);
+            break;
+
+        case TA_SOCKET_READ:
+            looking_for = SOCKET_READ;
+            tun_input_pending = NULL;
+            io_wait(c, IOW_READ_LINK);
+            break;
+
+        case TA_TUN_WRITE:
+            looking_for = TUN_WRITE;
+            tun_input_pending = NULL;
+            c->c2.timeval.tv_sec = 1; /* For some reason, the Linux 2.2 TUN/TAP driver hits this timeout */
+            perf_push(PERF_PROC_OUT_TUN_MTCP);
+            io_wait(c, IOW_TO_TUN);
+            perf_pop();
+            break;
+
+        case TA_SOCKET_WRITE:
+            looking_for = SOCKET_WRITE;
+            io_wait(c, IOW_TO_LINK|IOW_READ_TUN_FORCE);
+            break;
+
+        default:
+            msg(M_FATAL, "MULTI PROTOCOL: multi_protocol_wait_lite, unhandled action=%d", action);
+    }
+
+    if (tun_input_pending && (c->c2.event_set_status & TUN_READ))
+    {
+        *tun_input_pending = true;
+    }
+
+    if (c->c2.event_set_status & looking_for)
+    {
+        return action;
+    }
+    else
+    {
+        switch (action)
+        {
+            /* MULTI PROTOCOL socket output buffer is full */
+            case TA_SOCKET_WRITE:
+                return TA_SOCKET_WRITE_DEFERRED;
+
+            /* TUN device timed out on accepting write */
+            case TA_TUN_WRITE:
+                return TA_TUN_WRITE_TIMEOUT;
+        }
+
+        return TA_UNDEF;
+    }
+}
+
+static inline void
+multi_protocol_set_global_rw_flags(struct multi_context *m, struct multi_instance *mi)
+{
+    if (mi)
+    {
+        mi->socket_set_called = true;
+        socket_set(mi->context.c2.link_sockets[0],
+                   m->multi_io->es,
+                   mbuf_defined(mi->tcp_link_out_deferred) ? EVENT_WRITE : EVENT_READ,
+                   &mi->ev_arg,
+                   &mi->tcp_rwflags);
+    }
+}
+
+static struct multi_instance *
+multi_protocol_dispatch(struct multi_context *m, struct multi_instance *mi, const int action)
+{
+    const unsigned int mpp_flags = MPP_PRE_SELECT|MPP_RECORD_TOUCH;
+    struct multi_instance *touched = mi;
+    m->mpp_touched = &touched;
+
+    dmsg(D_MULTI_DEBUG, "MULTI PROTOCOL: multi_protocol_dispatch a=%s mi=" ptr_format,
+         pract(action),
+         (ptr_type)mi);
+
+    switch (action)
+    {
+        case TA_TUN_READ:
+            read_incoming_tun(&m->top);
+            if (!IS_SIG(&m->top))
+            {
+                multi_process_incoming_tun(m, mpp_flags);
+            }
+            break;
+
+        case TA_SOCKET_READ:
+        case TA_SOCKET_READ_RESIDUAL:
+            ASSERT(mi);
+            ASSERT(mi->context.c2.link_sockets);
+            ASSERT(mi->context.c2.link_sockets[0]);
+            set_prefix(mi);
+            read_incoming_link(&mi->context, mi->context.c2.link_sockets[0]);
+            clear_prefix();
+            if (!IS_SIG(&mi->context))
+            {
+                multi_process_incoming_link(m, mi, mpp_flags,
+                                            mi->context.c2.link_sockets[0]);
+                if (!IS_SIG(&mi->context))
+                {
+                    stream_buf_read_setup(mi->context.c2.link_sockets[0]);
+                }
+            }
+            break;
+
+        case TA_TIMEOUT:
+            multi_process_timeout(m, mpp_flags);
+            break;
+
+        case TA_TUN_WRITE:
+            multi_process_outgoing_tun(m, mpp_flags);
+            break;
+
+        case TA_TUN_WRITE_TIMEOUT:
+            multi_process_drop_outgoing_tun(m, mpp_flags);
+            break;
+
+        case TA_SOCKET_WRITE_READY:
+            ASSERT(mi);
+            multi_tcp_process_outgoing_link_ready(m, mi, mpp_flags);
+            break;
+
+        case TA_SOCKET_WRITE:
+            multi_tcp_process_outgoing_link(m, false, mpp_flags);
+            break;
+
+        case TA_SOCKET_WRITE_DEFERRED:
+            multi_tcp_process_outgoing_link(m, true, mpp_flags);
+            break;
+
+        case TA_INITIAL:
+            ASSERT(mi);
+            multi_protocol_set_global_rw_flags(m, mi);
+            multi_process_post(m, mi, mpp_flags);
+            break;
+
+        default:
+            msg(M_FATAL, "MULTI PROTOCOL: multi_protocol_dispatch, unhandled action=%d", action);
+    }
+
+    m->mpp_touched = NULL;
+    return touched;
+}
+
+static int
+multi_protocol_post(struct multi_context *m, struct multi_instance *mi, const int action)
+{
+    struct context *c = multi_tcp_context(m, mi);
+    int newaction = TA_UNDEF;
+
+#define MTP_NONE         0
+#define MTP_TUN_OUT      (1<<0)
+#define MTP_LINK_OUT     (1<<1)
+    unsigned int flags = MTP_NONE;
+
+    if (TUN_OUT(c))
+    {
+        flags |= MTP_TUN_OUT;
+    }
+    if (LINK_OUT(c))
+    {
+        flags |= MTP_LINK_OUT;
+    }
+
+    switch (flags)
+    {
+        case MTP_TUN_OUT|MTP_LINK_OUT:
+        case MTP_TUN_OUT:
+            newaction = TA_TUN_WRITE;
+            break;
+
+        case MTP_LINK_OUT:
+            newaction = TA_SOCKET_WRITE;
+            break;
+
+        case MTP_NONE:
+            if (mi && sockets_read_residual(c))
+            {
+                newaction = TA_SOCKET_READ_RESIDUAL;
+            }
+            else
+            {
+                multi_protocol_set_global_rw_flags(m, mi);
+            }
+            break;
+
+        default:
+        {
+            struct gc_arena gc = gc_new();
+            msg(M_FATAL, "MULTI PROTOCOL: multi_protocol_post bad state, mi=%s flags=%d",
+                multi_instance_string(mi, false, &gc),
+                flags);
+            gc_free(&gc);
+            break;
+        }
+    }
+
+    dmsg(D_MULTI_DEBUG, "MULTI PROTOCOL: multi_protocol_post %s -> %s",
+         pract(action),
+         pract(newaction));
+
+    return newaction;
+}
+
+void
+multi_protocol_process_io(struct multi_context *m)
+{
+    struct multi_protocol *multi_io = m->multi_io;
+    const unsigned int udp_status = multi_io->udp_flags;
+    const unsigned int mpp_flags = m->top.c2.fast_io
+                                   ? (MPP_CONDITIONAL_PRE_SELECT | MPP_CLOSE_ON_SIGNAL)
+                                   : (MPP_PRE_SELECT | MPP_CLOSE_ON_SIGNAL);
+    int i;
+
+    for (i = 0; i < multi_io->n_esr; ++i)
+    {
+        struct event_set_return *e = &multi_io->esr[i];
+        struct event_arg *ev_arg = (struct event_arg *)e->arg;
+
+        /* incoming data for instance or listening socket? */
+        if (e->arg >= MULTI_N)
+        {
+            switch (ev_arg->type)
+            {
+                struct multi_instance *mi;
+
+                /* react to event on child instance */
+                case EVENT_ARG_MULTI_INSTANCE:
+                    if (!ev_arg->u.mi)
+                    {
+                        msg(D_MULTI_ERRORS, "MULTI PROTOCOL: multi_proto_proc_io: null minstance");
+                        break;
+                    }
+
+                    mi = ev_arg->u.mi;
+                    if (e->rwflags & EVENT_WRITE)
+                    {
+                        multi_protocol_action(m, mi, TA_SOCKET_WRITE_READY, false);
+                    }
+                    else if (e->rwflags & EVENT_READ)
+                    {
+                        multi_protocol_action(m, mi, TA_SOCKET_READ, false);
+                    }
+                    break;
+
+                /* new incoming TCP client attempting to connect? */
+                case EVENT_ARG_LINK_SOCKET:
+                    if (!ev_arg->u.ls)
+                    {
+                        msg(D_MULTI_ERRORS, "MULTI PROTOCOL: multi_proto_proc_io: null socket");
+                        break;
+                    }
+
+                    if (!proto_is_dgram(ev_arg->u.ls->info.proto))
+                    {
+                        socket_reset_listen_persistent(ev_arg->u.ls);
+                        mi = multi_create_instance_tcp(m, ev_arg->u.ls);
+                        if (mi)
+                        {
+                            multi_protocol_action(m, mi, TA_INITIAL, false);
+                        }
+                        break;
+                    }
+                    else
+                    {
+                        if (e->arg >= MULTI_N)
+                        {
+                            struct event_arg *ev_arg = (struct event_arg *)e->arg;
+                            if (ev_arg->type != EVENT_ARG_LINK_SOCKET)
+                            {
+                                multi_io->udp_flags = ES_ERROR;
+                                msg(D_LINK_ERRORS,
+                                    "MULTI PROTOCOL: io_work: non socket event delivered");
+                                break;
+                            }
+                        }
+                        else
+                        {
+                            ev_arg->pending = true;
+                        }
+
+                        if (udp_status & SOCKET_READ)
+                        {
+                            read_incoming_link(&m->top, ev_arg->u.ls);
+                            if (!IS_SIG(&m->top))
+                            {
+                                multi_process_incoming_link(m, NULL, mpp_flags,
+                                                            ev_arg->u.ls);
+                            }
+                        }
+
+                        while (true)
+                        {
+                            multi_get_timeout(m, &m->top.c2.timeval);
+                            get_io_flags_udp(&m->top, m->multi_io, p2mp_iow_flags(m));
+                            MULTI_CHECK_SIG(m);
+
+                            multi_process_per_second_timers(m);
+
+                            if (m->multi_io->udp_flags == ES_TIMEOUT)
+                            {
+                                multi_process_timeout(m, MPP_PRE_SELECT | MPP_CLOSE_ON_SIGNAL);
+                            }
+                            else
+                            {
+                                multi_process_io_udp(m);
+                                MULTI_CHECK_SIG(m);
+                                break;
+                            }
+                        }
+                        break;
+                    }
+            }
+        }
+        else
+        {
+#ifdef ENABLE_MANAGEMENT
+            if (e->arg == MULTI_IO_MANAGEMENT)
+            {
+                ASSERT(management);
+                management_io(management);
+            }
+            else
+#endif
+            /* incoming data on TUN? */
+            if (e->arg == MULTI_IO_TUN)
+            {
+                if (e->rwflags & EVENT_WRITE)
+                {
+                    multi_protocol_action(m, NULL, TA_TUN_WRITE, false);
+                }
+                else if (e->rwflags & EVENT_READ)
+                {
+                    multi_protocol_action(m, NULL, TA_TUN_READ, false);
+                }
+            }
+            /* new incoming TCP client attempting to connect? */
+            else if (e->arg == MULTI_IO_SOCKET)
+            {
+                struct multi_instance *mi;
+                ASSERT(m->top.c2.link_sockets[0]);
+                socket_reset_listen_persistent(m->top.c2.link_sockets[0]);
+                mi = multi_create_instance_tcp(m, m->top.c2.link_sockets[0]);
+                if (mi)
+                {
+                    multi_protocol_action(m, mi, TA_INITIAL, false);
+                }
+            }
+#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD))
+            /* incoming data on DCO? */
+            else if (e->arg == MULTI_IO_DCO)
+            {
+                multi_process_incoming_dco(m);
+            }
+#endif
+            /* signal received? */
+            else if (e->arg == MULTI_IO_SIG)
+            {
+                get_signal(&m->top.sig->signal_received);
+            }
+#ifdef ENABLE_ASYNC_PUSH
+            else if (e->arg == MULTI_IO_FILE_CLOSE_WRITE)
+            {
+                multi_process_file_closed(m, MPP_PRE_SELECT | MPP_RECORD_TOUCH);
+            }
+#endif
+        }
+        if (IS_SIG(&m->top))
+        {
+            break;
+        }
+    }
+    multi_io->n_esr = 0;
+
+    /*
+     * Process queued mbuf packets destined for TCP socket
+     */
+    {
+        struct multi_instance *mi;
+        while (!IS_SIG(&m->top) && (mi = mbuf_peek(m->mbuf)) != NULL)
+        {
+            multi_protocol_action(m, mi, TA_SOCKET_WRITE, true);
+        }
+    }
+}
+
+void
+multi_protocol_action(struct multi_context *m, struct multi_instance *mi, int action, bool poll)
+{
+    bool tun_input_pending = false;
+
+    do
+    {
+        dmsg(D_MULTI_DEBUG, "MULTI PROTOCOL: multi_protocol_action a=%s p=%d",
+             pract(action),
+             poll);
+
+        /*
+         * If TA_SOCKET_READ_RESIDUAL, it means we still have pending
+         * input packets which were read by a prior recv.
+         *
+         * Otherwise do a "lite" wait, which means we wait with 0 timeout
+         * on I/O events only related to the current instance, not
+         * the big list of events.
+         *
+         * On our first pass, poll will be false because we already know
+         * that input is available, and to call io_wait would be redundant.
+         */
+        if (poll && action != TA_SOCKET_READ_RESIDUAL)
+        {
+            const int orig_action = action;
+            action = multi_protocol_wait_lite(m, mi, action, &tun_input_pending);
+            if (action == TA_UNDEF)
+            {
+                msg(M_FATAL, "MULTI PROTOCOL: I/O wait required blocking in multi_protocol_action, action=%d", orig_action);
+            }
+        }
+
+        /*
+         * Dispatch the action
+         */
+        struct multi_instance *touched = multi_protocol_dispatch(m, mi, action);
+
+        /*
+         * Signal received or connection
+         * reset by peer?
+         */
+        if (touched && IS_SIG(&touched->context))
+        {
+            if (mi == touched)
+            {
+                mi = NULL;
+            }
+            multi_close_instance_on_signal(m, touched);
+        }
+
+
+        /*
+         * If dispatch produced any pending output
+         * for a particular instance, point to
+         * that instance.
+         */
+        if (m->pending)
+        {
+            mi = m->pending;
+        }
+
+        /*
+         * Based on the effects of the action,
+         * such as generating pending output,
+         * possibly transition to a new action state.
+         */
+        action = multi_protocol_post(m, mi, action);
+
+        /*
+         * If we are finished processing the original action,
+         * check if we have any TUN input.  If so, transition
+         * our action state to processing this input.
+         */
+        if (tun_input_pending && action == TA_UNDEF)
+        {
+            action = TA_TUN_READ;
+            mi = NULL;
+            tun_input_pending = false;
+            poll = false;
+        }
+        else
+        {
+            poll = true;
+        }
+
+    } while (action != TA_UNDEF);
+}
+
+void
+multi_protocol_delete_event(struct multi_protocol *multi_io, event_t event)
+{
+    if (multi_io && multi_io->es)
+    {
+        event_del(multi_io->es, event);
+    }
+}
diff --git a/src/openvpn/multi_io.h b/src/openvpn/multi_io.h
new file mode 100644
index 0000000..bd27f08
--- /dev/null
+++ b/src/openvpn/multi_io.h
@@ -0,0 +1,77 @@ 
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2002-2023 OpenVPN Inc <sales@openvpn.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Multi-protocol specific code for --mode server
+ */
+
+#ifndef MULTI_IO_H
+#define MULTI_IO_H
+
+#include "event.h"
+
+/*
+ * I/O processing States
+ */
+
+#define TA_UNDEF                 0
+#define TA_SOCKET_READ           1
+#define TA_SOCKET_READ_RESIDUAL  2
+#define TA_SOCKET_WRITE          3
+#define TA_SOCKET_WRITE_READY    4
+#define TA_SOCKET_WRITE_DEFERRED 5
+#define TA_TUN_READ              6
+#define TA_TUN_WRITE             7
+#define TA_INITIAL               8
+#define TA_TIMEOUT               9
+#define TA_TUN_WRITE_TIMEOUT     10
+
+/*
+ * Extra state info needed for Multi-protocol
+ */
+struct multi_protocol
+{
+    struct event_set *es;
+    struct event_set_return *esr;
+    int n_esr;
+    int maxevents;
+    unsigned int tun_rwflags;
+    unsigned int udp_flags;
+#ifdef ENABLE_MANAGEMENT
+    unsigned int management_persist_flags;
+#endif
+};
+
+struct multi_protocol *multi_protocol_init(int maxevents, int *maxclients);
+
+void multi_protocol_free(struct multi_protocol *multi_io);
+
+int multi_protocol_io_wait(struct context *c, struct multi_protocol *multi_io);
+
+void multi_protocol_process_io(struct multi_context *m);
+
+void multi_protocol_action(struct multi_context *m, struct multi_instance *mi, int action, bool poll);
+
+void multi_protocol_delete_event(struct multi_protocol *multi_io, event_t event);
+
+#endif /* ifndef MULTI_IO_H */