[Openvpn-devel,L] Change in openvpn[master]: Using the same wait function for both TCP and UDP

Message ID cf863340b528c9e1903d181a54a2a96484427899-HTML@gerrit.openvpn.net
State Not Applicable
Headers show
Series [Openvpn-devel,L] Change in openvpn[master]: Using the same wait function for both TCP and UDP | expand

Commit Message

reynir (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/+/440?usp=email

to review the following change.


Change subject: Using the same wait function for both TCP and UDP
......................................................................

Using the same wait function for both TCP and UDP

FORWARD: added a new function to collect UDP flags.

MTCP: the mtcp structure has been modified to carry around those flags.
      The second wait function (in UDP case) has been removed.

MULTI: properly remove TCP instances by checking the multi_instance
       protocol instead of the global one.

TLS: set the tls_option xmit_hold bool value to true only in case of
     TCP child instance to avoid checking the global protocol
     value.

INIT: initialize the c->c2.event_set in the inherit_context_top()
      by default and not only in case of UDP since we could have
      multiple different sockets.

Change-Id: I81ec69d12abc9a661875c93c7f1bd97e525df55f
Signed-off-by: Gianmarco De Gregori <gianmarco@mandelbit.com>
---
M src/openvpn/forward.c
M src/openvpn/forward.h
M src/openvpn/init.c
M src/openvpn/mtcp.c
M src/openvpn/mtcp.h
M src/openvpn/mudp.c
M src/openvpn/multi.c
M src/openvpn/options.c
M src/openvpn/options.h
9 files changed, 177 insertions(+), 244 deletions(-)



  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/40/440/1

Patch

diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index 63a684b..ee18f8b 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -2053,48 +2053,20 @@ 
  */
 
 void
-io_wait_dowork_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags)
+get_io_flags_dowork_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags)
 {
     unsigned int socket = 0;
     unsigned int tuntap = 0;
-    struct event_set_return esr[4];
-
-    /* These shifts all depend on EVENT_READ and EVENT_WRITE */
-    static uintptr_t socket_shift = 0;   /* depends on SOCKET_READ and SOCKET_WRITE */
-    static uintptr_t tun_shift = 2;      /* depends on TUN_READ and TUN_WRITE */
-    static uintptr_t err_shift = 4;      /* depends on ES_ERROR */
-#ifdef ENABLE_MANAGEMENT
-    static uintptr_t management_shift = 6; /* depends on MANAGEMENT_READ and MANAGEMENT_WRITE */
-#endif
-#ifdef ENABLE_ASYNC_PUSH
-    static int file_shift = FILE_SHIFT;
-#endif
-#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)
-    static int dco_shift = DCO_SHIFT;    /* Event from DCO linux kernel module */
-#endif
-    int i;
+    static uintptr_t err_shift = 4;
 
     /*
-     * Decide what kind of events we want to wait for.
-     */
-    /*c->c2.event_set = mtcp->es; */
-    /*event_reset(mtcp->es); */
-    /*event_reset(c->c2.event_set); */
-
-    /*
-     * On win32 we use the keyboard or an event object as a source
-     * of asynchronous signals.
+     * Calculate the flags based on the provided 'flags' argument.
      */
     if (flags & IOW_WAIT_SIGNAL)
     {
         wait_signal(mtcp->es, (void *)err_shift);
     }
 
-    /*
-     * If outgoing data (for TCP/UDP port) pending, wait for ready-to-send
-     * status from TCP/UDP port. Otherwise, wait for incoming data on
-     * TUN/TAP device.
-     */
     if (flags & IOW_TO_LINK)
     {
         if (flags & IOW_SHAPER)
@@ -2180,117 +2152,68 @@ 
     /*
      * Configure event wait based on socket, tuntap flags.
      */
-    for (i = 0; i < c->c1.link_sockets_num; i++)
+    for (int i = 0; i < c->c1.link_sockets_num; i++)
     {
-        socket_set(c->c2.link_sockets[i], mtcp->es, socket,
-                   &c->c2.link_sockets[i]->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 (socket & EVENT_READ && c->c2.did_open_tun)
-    {
-        dco_event_set(&c->c1.tuntap->dco, mtcp->es, (void *)&dco_shift);
-    }
-#endif
-
-#ifdef ENABLE_MANAGEMENT
-    if (management)
-    {
-        management_socket_set(management, mtcp->es, (void *)management_shift, NULL);
-    }
-#endif
-
-#ifdef ENABLE_ASYNC_PUSH
-    /* arm inotify watcher */
-    if (c->options.mode == MODE_SERVER)
-    {
-        event_ctl(mtcp->es, c->c2.inotify_fd, EVENT_READ, (void *)&file_shift);
-    }
-#endif
-
-    /*
-     * Possible scenarios:
-     *  (1) tcp/udp port has data available to read
-     *  (2) tcp/udp port is ready to accept more data to write
-     *  (3) tun dev has data available to read
-     *  (4) tun dev is ready to accept more data to write
-     *  (5) we received a signal (handler sets signal_received)
-     *  (6) timeout (tv) expired
-     */
-
-    mtcp->event_set_status = ES_ERROR;
-
-    if (!c->sig->signal_received)
-    {
-        if (!(flags & IOW_CHECK_RESIDUAL) || !sockets_read_residual(c))
+        if (proto_is_dgram(c->c2.link_sockets[i]->info.proto))
         {
-            int status;
-
-#ifdef ENABLE_DEBUG
-            if (check_debug_level(D_EVENT_WAIT))
-            {
-                show_wait_status(c);
-            }
-#endif
-
-            /*
-             * Wait for something to happen.
-             */
-            status = event_wait(mtcp->es, &c->c2.timeval, esr, SIZE(esr));
-
-            check_status(status, "event_wait", NULL, NULL);
-
-            if (status > 0)
-            {
-                /*printf("\nstatus: %d\n", status); */
-                int i;
-                mtcp->event_set_status = 0;
-                for (i = 0; i < status; ++i)
-                {
-                    const struct event_set_return *e = &esr[i];
-                    uintptr_t shift;
-
-                    if (e->arg >= MULTI_N)
-                    {
-                        struct event_arg *ev_arg = (struct event_arg *)e->arg;
-                        if (ev_arg->type != EVENT_ARG_LINK_SOCKET)
-                        {
-                            mtcp->event_set_status = ES_ERROR;
-                            msg(D_LINK_ERRORS,
-                                "io_work: non socket event delivered");
-                            return;
-                        }
-
-                        shift = socket_shift;
-                        /* mark socket so that the multi code knows where we
-                         * have pending i/o */
-                        ev_arg->pending = true;
-                    }
-                    else
-                    {
-                        shift = (uintptr_t)e->arg;
-                    }
-
-                    mtcp->event_set_status |= ((e->rwflags & 3) << shift);
-                }
-            }
-            else if (status == 0)
-            {
-                mtcp->event_set_status = ES_TIMEOUT;
-            }
+            socket_set(c->c2.link_sockets[i], mtcp->es, socket,
+                       &c->c2.link_sockets[i]->ev_arg, NULL);
         }
     }
+    mtcp->udp_flags = socket | tuntap;
+}
 
-    /* 'now' should always be a reasonably up-to-date timestamp */
-    update_time();
-
-    /* set signal_received if a signal was received */
-    if (mtcp->event_set_status & ES_ERROR)
+void
+get_io_flags_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags)
+{
+    mtcp->udp_flags = ES_ERROR;
+    if (c->c2.fast_io && (flags & (IOW_TO_TUN | IOW_TO_LINK | IOW_MBUF)))
     {
-        get_signal(&c->sig->signal_received);
+        /* fast path -- only for TUN/TAP/UDP writes */
+        unsigned int ret = 0;
+        if (flags & IOW_TO_TUN)
+        {
+            ret |= TUN_WRITE;
+        }
+        if (flags & (IOW_TO_LINK | IOW_MBUF))
+        {
+            ret |= SOCKET_WRITE;
+        }
+        mtcp->udp_flags = ret;
     }
-
-    dmsg(D_EVENT_WAIT, "I/O WAIT status=0x%04x", mtcp->event_set_status);
+    else
+    {
+#ifdef _WIN32
+        bool skip_iowait = flags & IOW_TO_TUN;
+        if (flags & IOW_READ_TUN)
+        {
+            /*
+             * don't read from tun if we have pending write to link,
+             * since every tun read overwrites to_link buffer filled
+             * by previous tun read
+             */
+            skip_iowait = !(flags & IOW_TO_LINK);
+        }
+        if (tuntap_is_wintun(c->c1.tuntap) && skip_iowait)
+        {
+            unsigned int ret = 0;
+            if (flags & IOW_TO_TUN)
+            {
+                ret |= TUN_WRITE;
+            }
+            if (flags & IOW_READ_TUN)
+            {
+                ret |= TUN_READ;
+            }
+            mtcp->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);
+        }
+    }
 }
 
 void
@@ -2308,10 +2231,10 @@ 
     static uintptr_t management_shift = 6; /* depends on MANAGEMENT_READ and MANAGEMENT_WRITE */
 #endif
 #ifdef ENABLE_ASYNC_PUSH
-    static int file_shift = FILE_SHIFT;
+    static uintptr_t file_shift = FILE_SHIFT;
 #endif
 #if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)
-    static int dco_shift = DCO_SHIFT;    /* Event from DCO linux kernel module */
+    static uintptr_t dco_shift = DCO_SHIFT;    /* Event from DCO linux kernel module */
 #endif
     int i;
 
@@ -2428,7 +2351,7 @@ 
 #if defined(TARGET_LINUX) || defined(TARGET_FREEBSD)
     if (socket & EVENT_READ && c->c2.did_open_tun)
     {
-        dco_event_set(&c->c1.tuntap->dco, c->c2.event_set, (void *)&dco_shift);
+        dco_event_set(&c->c1.tuntap->dco, c->c2.event_set, (void *)dco_shift);
     }
 #endif
 
@@ -2443,7 +2366,7 @@ 
     /* arm inotify watcher */
     if (c->options.mode == MODE_SERVER)
     {
-        event_ctl(c->c2.event_set, c->c2.inotify_fd, EVENT_READ, (void *)&file_shift);
+        event_ctl(c->c2.event_set, c->c2.inotify_fd, EVENT_READ, (void *)file_shift);
     }
 #endif
 
diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h
index cb184a8..39ac975 100644
--- a/src/openvpn/forward.h
+++ b/src/openvpn/forward.h
@@ -69,7 +69,9 @@ 
 
 extern counter_type link_write_bytes_global;
 
-void io_wait_dowork_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags);
+void get_io_flags_dowork_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags);
+
+void get_io_flags_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags);
 
 void io_wait_dowork(struct context *c, const unsigned int flags);
 
@@ -366,58 +368,6 @@ 
     return flags;
 }
 
-static inline void
-io_wait_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags)
-{
-    if (c->c2.fast_io && (flags & (IOW_TO_TUN|IOW_TO_LINK|IOW_MBUF)))
-    {
-        /* fast path -- only for TUN/TAP/UDP writes */
-        unsigned int ret = 0;
-        if (flags & IOW_TO_TUN)
-        {
-            ret |= TUN_WRITE;
-        }
-        if (flags & (IOW_TO_LINK|IOW_MBUF))
-        {
-            ret |= SOCKET_WRITE;
-        }
-        mtcp->event_set_status = ret;
-    }
-    else
-    {
-#ifdef _WIN32
-        bool skip_iowait = flags & IOW_TO_TUN;
-        if (flags & IOW_READ_TUN)
-        {
-            /*
-             * don't read from tun if we have pending write to link,
-             * since every tun read overwrites to_link buffer filled
-             * by previous tun read
-             */
-            skip_iowait = !(flags & IOW_TO_LINK);
-        }
-        if (tuntap_is_wintun(c->c1.tuntap) && skip_iowait)
-        {
-            unsigned int ret = 0;
-            if (flags & IOW_TO_TUN)
-            {
-                ret |= TUN_WRITE;
-            }
-            if (flags & IOW_READ_TUN)
-            {
-                ret |= TUN_READ;
-            }
-            mtcp->event_set_status = ret;
-        }
-        else
-#endif /* ifdef _WIN32 */
-        {
-            /* slow path */
-            io_wait_dowork_udp(c, mtcp, flags);
-        }
-    }
-}
-
 /*
  * This is the core I/O wait function, used for all I/O waits except
  * for TCP in server mode.
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 762793a..2b88551 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -230,7 +230,7 @@ 
         if (streq(p[1], "HTTP"))
         {
             struct http_proxy_options *ho;
-            if (ce->proto != PROTO_TCP && ce->proto != PROTO_TCP_CLIENT)
+            if (ce->proto != PROTO_TCP && c->mode != CM_CHILD_TCP)
             {
                 msg(M_WARN, "HTTP proxy support only works for TCP based connections");
                 return false;
@@ -607,7 +607,10 @@ 
             ce_defined = false;
         }
 
+        int proto = c->options.ce.proto;
         c->options.ce = *ce;
+        c->options.ce.proto = proto;
+
 #ifdef ENABLE_MANAGEMENT
         if (ce_defined && management && management_query_remote_enabled(management))
         {
@@ -2628,7 +2631,7 @@ 
 
     if (found & OPT_P_EXPLICIT_NOTIFY)
     {
-        if (!proto_is_udp(c->options.ce.proto) && c->options.ce.explicit_exit_notification)
+        if (!proto_is_udp(c->c2.link_sockets[0]->info.proto) && c->options.ce.explicit_exit_notification)
         {
             msg(D_PUSH, "OPTIONS IMPORT: --explicit-exit-notify can only be used with --proto udp");
             c->options.ce.explicit_exit_notification = 0;
@@ -2788,14 +2791,21 @@ 
     int sec = 2;
     int backoff = 0;
 
-    switch (c->options.ce.proto)
+    switch (c->mode)
     {
-        case PROTO_TCP_SERVER:
-            sec = 1;
+        case CM_TOP:
+            if (has_udp_in_local_list(&c->options))
+            {
+                sec = c->options.ce.connect_retry_seconds;
+            }
+            else
+            {
+                sec = 1;
+            }
             break;
 
-        case PROTO_UDP:
-        case PROTO_TCP_CLIENT:
+        case CM_CHILD_UDP:
+        case CM_CHILD_TCP:
             sec = c->options.ce.connect_retry_seconds;
             break;
     }
@@ -2813,7 +2823,7 @@ 
     }
 
     /* Slow down reconnection after 5 retries per remote -- for TCP client or UDP tls-client only */
-    if (c->options.ce.proto == PROTO_TCP_CLIENT
+    if (c->mode == CM_CHILD_TCP
         || (c->options.ce.proto == PROTO_UDP && c->options.tls_client))
     {
         backoff = (c->options.unsuccessful_attempts / c->options.connection_list->len) - 4;
@@ -3293,7 +3303,21 @@ 
     to.server = options->tls_server;
     to.replay_window = options->replay_window;
     to.replay_time = options->replay_time;
-    to.tcp_mode = link_socket_proto_connection_oriented(options->ce.proto);
+
+    if (c->options.ce.local_list->len > 1)
+    {
+        for (int i = 0; i < c->options.ce.local_list->len; i++)
+        {
+            if (proto_is_dgram(c->options.ce.local_list->array[i]->proto))
+            {
+                to.tcp_mode = false;
+            }
+        }
+    }
+    else
+    {
+        to.tcp_mode = link_socket_proto_connection_oriented(c->options.ce.local_list->array[0]->proto);
+    }
     to.config_ciphername = c->options.ciphername;
     to.config_ncp_ciphers = c->options.ncp_ciphers;
     to.transition_window = options->transition_window;
@@ -3338,7 +3362,7 @@ 
 
     /* should we not xmit any packets until we get an initial
      * response from client? */
-    if (to.server && options->ce.proto == PROTO_TCP_SERVER)
+    if (to.server && c->mode == CM_CHILD_TCP)
     {
         to.xmit_hold = true;
     }
@@ -4257,20 +4281,13 @@ 
 #ifdef _WIN32
         msg(M_INFO, "NOTE: --fast-io is disabled since we are running on Windows");
 #else
-        if (!proto_is_udp(c->options.ce.proto))
+        if (c->options.shaper)
         {
-            msg(M_INFO, "NOTE: --fast-io is disabled since we are not using UDP");
+            msg(M_INFO, "NOTE: --fast-io is disabled since we are using --shaper");
         }
         else
         {
-            if (c->options.shaper)
-            {
-                msg(M_INFO, "NOTE: --fast-io is disabled since we are using --shaper");
-            }
-            else
-            {
-                c->c2.fast_io = true;
-            }
+            c->c2.fast_io = true;
         }
 #endif
     }
@@ -4967,6 +4984,7 @@ 
 
     /* options */
     dest->options = src->options;
+    dest->options.ce.proto = ls->info.proto;
     options_detach(&dest->options);
 
     dest->c2.event_set = src->c2.event_set;
@@ -5065,10 +5083,7 @@ 
     dest->c2.es_owned = false;
 
     dest->c2.event_set = NULL;
-    if (proto_is_dgram(src->options.ce.proto))
-    {
-        do_event_set_init(dest, false);
-    }
+    do_event_set_init(dest, false);
 
 #ifdef USE_COMP
     dest->c2.comp_context = NULL;
diff --git a/src/openvpn/mtcp.c b/src/openvpn/mtcp.c
index ba0905e..128a375 100644
--- a/src/openvpn/mtcp.c
+++ b/src/openvpn/mtcp.c
@@ -680,11 +680,11 @@ 
 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);
-    struct multi_tcp *mtcp = m->mtcp;
-    const unsigned int status = mtcp->event_set_status;
     int i;
 
     for (i = 0; i < mtcp->n_esr; ++i)
@@ -725,9 +725,10 @@ 
                         msg(D_MULTI_ERRORS, "MULTI: mtcp_proc_io: null socket");
                         break;
                     }
-                    socket_reset_listen_persistent(ev_arg->u.ls);
+
                     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)
                         {
@@ -737,7 +738,23 @@ 
                     }
                     else
                     {
-                        if (status & SOCKET_READ)
+                        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))
@@ -750,12 +767,12 @@ 
                         while (true)
                         {
                             multi_get_timeout(m, &m->top.c2.timeval);
-                            io_wait_udp(&m->top, m->mtcp, p2mp_iow_flags(m));
+                            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->event_set_status == ES_TIMEOUT)
+                            if (m->mtcp->udp_flags == ES_TIMEOUT)
                             {
                                 multi_process_timeout(m, MPP_PRE_SELECT | MPP_CLOSE_ON_SIGNAL);
                             }
diff --git a/src/openvpn/mtcp.h b/src/openvpn/mtcp.h
index b2fdb6e..d55bdfd 100644
--- a/src/openvpn/mtcp.h
+++ b/src/openvpn/mtcp.h
@@ -55,7 +55,7 @@ 
     int n_esr;
     int maxevents;
     unsigned int tun_rwflags;
-    unsigned int event_set_status;
+    unsigned int udp_flags;
 #ifdef ENABLE_MANAGEMENT
     unsigned int management_persist_flags;
 #endif
diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index 4979751..8945a55 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -198,7 +198,6 @@ 
     if (mroute_extract_openvpn_sockaddr(&real, &m->top.c2.from.dest, true)
         && m->top.c2.buf.len > 0)
     {
-        /*real.proto = ls->info.proto; */
         struct hash_element *he;
         const uint32_t hv = hash_value(hash, &real);
         struct hash_bucket *bucket = hash_bucket(hash, hv);
@@ -380,9 +379,7 @@ 
 void
 multi_process_io_udp(struct multi_context *m)
 {
-    const unsigned int status = m->mtcp->event_set_status;
-    /*p2mp_iow_flags(m); */
-    /*m->top.c2.event_set_status; */
+    unsigned int status = m->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);
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 5098581..108a5a8 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -607,6 +607,7 @@ 
 
     ASSERT(!mi->halt);
     mi->halt = true;
+    bool is_dgram = proto_is_dgram(mi->context.c2.link_sockets[0]->info.proto);
 
     dmsg(D_MULTI_DEBUG, "MULTI: multi_close_instance called");
 
@@ -665,7 +666,7 @@ 
             mi->did_iroutes = false;
         }
 
-        if (m->mtcp && !proto_is_dgram(m->top.options.ce.proto))
+        if (!is_dgram)
         {
             multi_tcp_dereference_instance(m->mtcp, mi);
         }
@@ -3406,6 +3407,7 @@ 
 
             perf_push(PERF_PROC_IN_LINK);
             lsi = get_link_socket_info(c);
+            /*lsi = &ls->info; */
             orig_buf = c->c2.buf.data;
             if (process_incoming_link_part1(c, lsi, floated))
             {
@@ -3856,7 +3858,7 @@ 
     while ((he = hash_iterator_next(&hi)))
     {
         struct multi_instance *mi = (struct multi_instance *) he->value;
-        if (!mi->halt)
+        if (!mi->halt && proto_is_dgram(mi->context.options.ce.proto))
         {
             send_control_channel_string(&mi->context, next_server ? "RESTART,[N]" : "RESTART", D_PUSH);
             multi_schedule_context_wakeup(m, mi);
@@ -3894,13 +3896,15 @@ 
         status_close(so);
         return false;
     }
-    else if (proto_is_dgram(m->top.options.ce.proto)
-             && is_exit_restart(m->top.sig->signal_received)
-             && (m->deferred_shutdown_signal.signal_received == 0)
-             && m->top.options.ce.explicit_exit_notification != 0)
+    else if (has_udp_in_local_list(&m->top.options))
     {
-        multi_push_restart_schedule_exit(m, m->top.options.ce.explicit_exit_notification == 2);
-        return false;
+        if (is_exit_restart(m->top.sig->signal_received)
+            && (m->deferred_shutdown_signal.signal_received == 0)
+            && m->top.options.ce.explicit_exit_notification != 0)
+        {
+            multi_push_restart_schedule_exit(m, m->top.options.ce.explicit_exit_notification == 2);
+            return false;
+        }
     }
     return true;
 }
@@ -4198,10 +4202,10 @@ 
             multi_tcp_process_io(multi);
             MULTI_CHECK_SIG(multi);
         }
-        /*else if (status == 0)
-         * {
-         *  multi_tcp_action(multi, NULL, TA_TIMEOUT, false);
-         * }*/
+        else if (status == 0)
+        {
+            multi_tcp_action(multi, NULL, TA_TIMEOUT, false);
+        }
 
         perf_pop();
     }
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 9e800bb..553bc26 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -987,10 +987,13 @@ 
                         const struct connection_entry *e,
                         const int i)
 {
-    setenv_str_i(es, "proto", proto2ascii(e->proto, e->af, false), i);
-    /* expected to befor single socket contexts only */
-    setenv_str_i(es, "local", e->local_list->array[0]->local, i);
-    setenv_str_i(es, "local_port", e->local_list->array[0]->port, i);
+    for (int j = 0; j < e->local_list->len; j++)
+    {
+        setenv_str_i(es, "proto", proto2ascii(e->local_list->array[j]->proto, e->af, false), i);
+        /* expected to befor single socket contexts only */
+        setenv_str_i(es, "local", e->local_list->array[j]->local, i);
+        setenv_str_i(es, "local_port", e->local_list->array[j]->port, i);
+    }
     setenv_str_i(es, "remote", e->remote, i);
     setenv_str_i(es, "remote_port", e->remote_port, i);
 
@@ -2455,7 +2458,7 @@ 
     {
         struct local_entry *le = ce->local_list->array[i];
 
-        if (proto_is_net(ce->proto)
+        if (proto_is_net(le->proto)
             && string_defined_equal(le->local, ce->remote)
             && string_defined_equal(le->port, ce->remote_port))
         {
@@ -3781,6 +3784,12 @@ 
         options_postprocess_mutate_ce(o, o->connection_list->array[i]);
     }
 
+    if (!has_udp_in_local_list(o))
+    {
+        o->fast_io = false;
+        msg(M_INFO, "NOTE: --fast-io is disabled while using multi-socket");
+    }
+
     if (o->ce.local_list)
     {
         for (i = 0; i < o->ce.local_list->len; i++)
@@ -9629,3 +9638,19 @@ 
 err:
     gc_free(&gc);
 }
+
+bool
+has_udp_in_local_list(const struct options *options)
+{
+    if (options->ce.local_list)
+    {
+        for (int i = 0; i < options->ce.local_list->len; i++)
+        {
+            if (proto_is_dgram(options->ce.local_list->array[i]->proto))
+            {
+                return true;
+            }
+        }
+    }
+    return false;
+}
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index cba0333..59035f8 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -913,6 +913,8 @@ 
 
 bool key_is_external(const struct options *options);
 
+bool has_udp_in_local_list(const struct options *options);
+
 /**
  * Returns whether the current configuration has dco enabled.
  */