[Openvpn-devel,L] Change in openvpn[master]: Bind to multiple ipv4/ipv6 addresses

Message ID 9d07277a7ccea9186aa16fea80935634ae99d10d-HTML@gerrit.openvpn.net
State Not Applicable
Headers show
Series [Openvpn-devel,L] Change in openvpn[master]: Bind to multiple ipv4/ipv6 addresses | 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/+/438?usp=email

to review the following change.


Change subject: Bind to multiple ipv4/ipv6 addresses
......................................................................

Bind to multiple ipv4/ipv6 addresses

Enables the binding of multiple listen
sockets based on the specified "--local"
directives.

The main server loop has been updated
to handle both TCP and UDP connections.

The hash function has also been modified
to include the protocol during the
creation of new client instances.

Change-Id: Ia122d5cdc42c2969eef6f32f438e30b52652721f
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/mroute.c
M src/openvpn/mroute.h
M src/openvpn/mtcp.c
M src/openvpn/mtcp.h
M src/openvpn/mudp.c
M src/openvpn/mudp.h
M src/openvpn/multi.c
M src/openvpn/multi.h
M src/openvpn/options.c
M src/openvpn/options.h
13 files changed, 613 insertions(+), 116 deletions(-)



  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/38/438/1

Patch

diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index 9a6dcd8..27415ee 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -1760,7 +1760,7 @@ 
             if (c->options.shaper)
             {
                 int overhead = datagram_overhead(c->c2.to_link_addr->dest.addr.sa.sa_family,
-                                                 c->options.ce.proto);
+                                                 ls->info.proto);
                 shaper_wrote_bytes(&c->c2.shaper,
                                    BLEN(&c->c2.to_link) + overhead);
             }
@@ -2050,6 +2050,250 @@ 
  */
 
 void
+io_wait_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;
+
+    /*
+     * 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.
+     */
+    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)
+        {
+            /*
+             * If sending this packet would put us over our traffic shaping
+             * quota, don't send -- instead compute the delay we must wait
+             * until it will be OK to send the packet.
+             */
+            int delay = 0;
+
+            /* set traffic shaping delay in microseconds */
+            if (c->options.shaper)
+            {
+                delay = max_int(delay, shaper_delay(&c->c2.shaper));
+            }
+
+            if (delay < 1000)
+            {
+                socket |= EVENT_WRITE;
+            }
+            else
+            {
+                shaper_soonest_event(&c->c2.timeval, delay);
+            }
+        }
+        else
+        {
+            socket |= EVENT_WRITE;
+        }
+    }
+    else if (!((flags & IOW_FRAG) && TO_LINK_FRAG(c)))
+    {
+        if (flags & IOW_READ_TUN)
+        {
+            tuntap |= EVENT_READ;
+        }
+    }
+
+    /*
+     * If outgoing data (for TUN/TAP device) pending, wait for ready-to-send status
+     * from device.  Otherwise, wait for incoming data on TCP/UDP port.
+     */
+    if (flags & IOW_TO_TUN)
+    {
+        tuntap |= EVENT_WRITE;
+    }
+    else
+    {
+        if (flags & IOW_READ_LINK)
+        {
+            socket |= EVENT_READ;
+        }
+    }
+
+    /*
+     * outgoing bcast buffer waiting to be sent?
+     */
+    if (flags & IOW_MBUF)
+    {
+        socket |= EVENT_WRITE;
+    }
+
+    /*
+     * Force wait on TUN input, even if also waiting on TCP/UDP output
+     */
+    if (flags & IOW_READ_TUN_FORCE)
+    {
+        tuntap |= EVENT_READ;
+    }
+
+#ifdef _WIN32
+    if (tuntap_is_wintun(c->c1.tuntap))
+    {
+        /*
+         * With wintun we are only interested in read event. Ring buffer is
+         * always ready for write, so we don't do wait.
+         */
+        tuntap = EVENT_READ;
+    }
+#endif
+
+    /*
+     * Configure event wait based on socket, tuntap flags.
+     */
+    for (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))
+        {
+            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)
+            {
+                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;
+            }
+        }
+        else
+        {
+            mtcp->event_set_status = SOCKET_READ;
+        }
+    }
+
+    /* '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)
+    {
+        get_signal(&c->sig->signal_received);
+    }
+
+    dmsg(D_EVENT_WAIT, "I/O WAIT status=0x%04x", mtcp->event_set_status);
+}
+
+void
 io_wait_dowork(struct context *c, const unsigned int flags)
 {
     unsigned int socket = 0;
@@ -2291,6 +2535,7 @@ 
     dmsg(D_EVENT_WAIT, "I/O WAIT status=0x%04x", c->c2.event_set_status);
 }
 
+
 void
 process_io(struct context *c, struct link_socket *ls)
 {
diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h
index 333dcdc..cb184a8 100644
--- a/src/openvpn/forward.h
+++ b/src/openvpn/forward.h
@@ -50,6 +50,7 @@ 
 #include "openvpn.h"
 #include "occ.h"
 #include "ping.h"
+#include "mtcp.h"
 
 #define IOW_TO_TUN          (1<<0)
 #define IOW_TO_LINK         (1<<1)
@@ -68,6 +69,8 @@ 
 
 extern counter_type link_write_bytes_global;
 
+void io_wait_dowork_udp(struct context *c, struct multi_tcp *mtcp, const unsigned int flags);
+
 void io_wait_dowork(struct context *c, const unsigned int flags);
 
 void pre_select(struct context *c);
@@ -363,6 +366,58 @@ 
     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 659c9e3..762793a 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -3791,6 +3791,24 @@ 
     unsigned int sockflags = c->options.sockflags;
     int i;
 
+    int mode = LS_MODE_DEFAULT;
+    /* mode allows CM_CHILD_TCP
+     * instances to inherit acceptable fds
+     * from a top-level parent */
+    if (c->options.mode == MODE_SERVER)
+    {
+        /* initializing listening socket */
+        if (c->mode == CM_TOP)
+        {
+            mode = LS_MODE_TCP_LISTEN;
+        }
+        /* initializing socket to client */
+        else if (c->mode == CM_CHILD_TCP)
+        {
+            mode = LS_MODE_TCP_ACCEPT_FROM;
+        }
+    }
+
 #if PORT_SHARE
     if (c->options.port_share_host && c->options.port_share_port)
     {
@@ -3800,33 +3818,37 @@ 
 
     for (i = 0; i < c->c1.link_sockets_num; i++)
     {
-        int mode = LS_MODE_DEFAULT;
+        const char *host = c->options.ce.local_list->array[i]->local;
+        const char *port = c->options.ce.local_list->array[i]->port;
+        int proto = c->options.ce.local_list->array[i]->proto;
+        bool bind_local = c->options.ce.local_list->array[i]->bind_local;
 
-        /* mode allows CM_CHILD_TCP
-         * instances to inherit acceptable fds
-         * from a top-level parent */
-        if (c->options.mode == MODE_SERVER)
+        if (c->mode == CM_CHILD_TCP || c->mode == CM_CHILD_UDP)
         {
-            /* initializing listening socket */
-            if (c->mode == CM_TOP)
+            const struct link_socket *ls = NULL;
+            if (c->mode == CM_CHILD_TCP)
             {
-                mode = LS_MODE_TCP_LISTEN;
+                ls = c->c2.accept_from;
             }
-            /* initializing socket to client */
-            else if (c->mode == CM_CHILD_TCP)
+            else if (c->mode == CM_CHILD_UDP)
             {
-                mode = LS_MODE_TCP_ACCEPT_FROM;
+                ls = c->c2.link_sockets[0];
             }
+
+            host = ls->local_host;
+            port = ls->local_port;
+            proto = ls->info.proto;
+            bind_local = ls->bind_local;
         }
 
         /* init each socket with its specific port */
         link_socket_init_phase1(c->c2.link_sockets[i],
-                                c->options.ce.local_list->array[i]->local,
-                                c->options.ce.local_list->array[i]->port,
+                                host,
+                                port,
                                 c->options.ce.remote,
                                 c->options.ce.remote_port,
                                 c->c1.dns_cache,
-                                c->options.ce.proto,
+                                proto,
                                 c->options.ce.af,
                                 c->options.ce.bind_ipv6_only,
                                 mode,
@@ -3836,7 +3858,7 @@ 
 #ifdef ENABLE_DEBUG
                                 c->options.gremlin,
 #endif
-                                c->options.ce.local_list->array[i]->bind_local,
+                                bind_local,
                                 c->options.ce.remote_float,
                                 &c->c1.link_socket_addrs[i],
                                 c->options.ipchange,
@@ -4655,7 +4677,7 @@ 
     }
 
     /* our wait-for-i/o objects, different for posix vs. win32 */
-    if (c->mode == CM_P2P)
+    if (c->mode == CM_P2P || c->mode == CM_TOP)
     {
         do_event_set_init(c, SHAPER_DEFINED(&c->options));
     }
@@ -4919,7 +4941,7 @@ 
     CLEAR(*dest);
 
     /* proto_is_dgram will ASSERT(0) if proto is invalid */
-    dest->mode = proto_is_dgram(src->options.ce.proto) ? CM_CHILD_UDP : CM_CHILD_TCP;
+    dest->mode = proto_is_dgram(ls->info.proto) ? CM_CHILD_UDP : CM_CHILD_TCP;
 
     dest->gc = gc_new();
 
@@ -4947,6 +4969,8 @@ 
     dest->options = src->options;
     options_detach(&dest->options);
 
+    dest->c2.event_set = src->c2.event_set;
+
     if (dest->mode == CM_CHILD_TCP)
     {
         /*
diff --git a/src/openvpn/mroute.c b/src/openvpn/mroute.c
index 91af2b6..0017a48 100644
--- a/src/openvpn/mroute.c
+++ b/src/openvpn/mroute.c
@@ -421,6 +421,7 @@ 
                 {
                     buf_printf(&out, ":%d", ntohs(maddr.v4.port));
                 }
+                buf_printf(&out, ":%d", maddr.proto);
             }
             break;
 
diff --git a/src/openvpn/mroute.h b/src/openvpn/mroute.h
index a06e872..7c8972f 100644
--- a/src/openvpn/mroute.h
+++ b/src/openvpn/mroute.h
@@ -76,6 +76,7 @@ 
     uint8_t len;    /* length of address */
     uint8_t unused;
     uint8_t type;   /* MR_ADDR/MR_WITH flags */
+    uint8_t proto;
     uint8_t netbits; /* number of bits in network part of address,
                       * valid if MR_WITH_NETBITS is set */
     union {
@@ -211,6 +212,10 @@ 
     {
         return false;
     }
+    if (a1->proto != a2->proto)
+    {
+        return false;
+    }
     if (a1->netbits != a2->netbits)
     {
         return false;
@@ -232,7 +237,7 @@ 
 static inline uint32_t
 mroute_addr_hash_len(const struct mroute_addr *a)
 {
-    return (uint32_t) a->len + 2;
+    return (uint32_t) a->len + 3;
 }
 
 static inline void
diff --git a/src/openvpn/mtcp.c b/src/openvpn/mtcp.c
index 06073cd..d4ce642 100644
--- a/src/openvpn/mtcp.c
+++ b/src/openvpn/mtcp.c
@@ -37,21 +37,6 @@ 
 #endif
 
 /*
- * TCP 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
-
-/*
  * Special tags passed to event.[ch] functions
  */
 #define MTCP_SOCKET      ((void *)1)
@@ -122,8 +107,9 @@ 
     struct hash *hash = m->hash;
 
     mi = multi_create_instance(m, NULL, ls);
-    if (mi)
+    if (mi && !proto_is_dgram(ls->info.proto))
     {
+        printf("\nTCP add\n");
         struct hash_element *he;
         const uint32_t hv = hash_value(hash, &mi->real);
         struct hash_bucket *bucket = hash_bucket(hash, hv);
@@ -174,7 +160,7 @@ 
     ASSERT(mi->context.c2.link_sockets);
     ASSERT(mi->context.c2.link_sockets[0]);
     ASSERT(mi->context.c2.link_sockets[0]->info.lsa);
-    ASSERT(mi->context.c2.link_sockets[0]->mode == LS_MODE_TCP_ACCEPT_FROM);
+    /*ASSERT(mi->context.c2.link_sockets[0]->mode == LS_MODE_TCP_ACCEPT_FROM); */
     ASSERT(mi->context.c2.link_sockets[0]->info.lsa->actual.dest.addr.sa.sa_family == AF_INET
            || mi->context.c2.link_sockets[0]->info.lsa->actual.dest.addr.sa.sa_family == AF_INET6
            );
@@ -259,11 +245,11 @@ 
     }
 }
 
-static inline int
-multi_tcp_wait(const struct context *c,
+int
+multi_tcp_wait(struct context *c,
                struct multi_tcp *mtcp)
 {
-    int i, status;
+    int status, i;
     unsigned int *persistent = &mtcp->tun_rwflags;
 
     for (i = 0; i < c->c1.link_sockets_num; i++)
@@ -603,7 +589,7 @@ 
     return newaction;
 }
 
-static void
+void
 multi_tcp_action(struct multi_context *m, struct multi_instance *mi, int action, bool poll)
 {
     bool tun_input_pending = false;
@@ -691,10 +677,14 @@ 
     } while (action != TA_UNDEF);
 }
 
-static void
+void
 multi_tcp_process_io(struct multi_context *m)
 {
+    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)
@@ -735,14 +725,45 @@ 
                         msg(D_MULTI_ERRORS, "MULTI: mtcp_proc_io: null socket");
                         break;
                     }
-
                     socket_reset_listen_persistent(ev_arg->u.ls);
-                    mi = multi_create_instance_tcp(m, ev_arg->u.ls);
-                    if (mi)
+                    if (!proto_is_dgram(ev_arg->u.ls->info.proto))
                     {
-                        multi_tcp_action(m, mi, TA_INITIAL, false);
+                        mi = multi_create_instance_tcp(m, ev_arg->u.ls);
+                        if (mi)
+                        {
+                            multi_tcp_action(m, mi, TA_INITIAL, false);
+                        }
+                        break;
                     }
-                    break;
+                    else
+                    {
+                        if (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);
+                            }
+                        }
+                        multi_get_timeout(m, &m->top.c2.timeval);
+                        io_wait_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)
+                        {
+                            multi_process_timeout(m, MPP_PRE_SELECT | MPP_CLOSE_ON_SIGNAL);
+                        }
+                        else
+                        {
+                            multi_process_io_udp(m);
+                            MULTI_CHECK_SIG(m);
+                        }
+
+                        break;
+                    }
             }
         }
         else
@@ -838,7 +859,7 @@ 
     }
 
     /* initialize global multi_context object */
-    multi_init(&multi, top, true);
+    multi_init(&multi, top);
 
     /* initialize our cloned top object */
     multi_top_init(&multi, top);
@@ -874,12 +895,12 @@ 
         if (status > 0)
         {
             /* process the I/O which triggered select */
-            multi_tcp_process_io(&multi);
+            /*multi_tcp_process_io(&multi); */
             MULTI_CHECK_SIG(&multi);
         }
         else if (status == 0)
         {
-            multi_tcp_action(&multi, NULL, TA_TIMEOUT, false);
+            /*multi_tcp_action(&multi, NULL, TA_TIMEOUT, false); */
         }
 
         perf_pop();
diff --git a/src/openvpn/mtcp.h b/src/openvpn/mtcp.h
index bacf723..b2fdb6e 100644
--- a/src/openvpn/mtcp.h
+++ b/src/openvpn/mtcp.h
@@ -31,6 +31,21 @@ 
 #include "event.h"
 
 /*
+ * TCP 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 TCP mode
  */
 struct multi_tcp
@@ -40,6 +55,7 @@ 
     int n_esr;
     int maxevents;
     unsigned int tun_rwflags;
+    unsigned int event_set_status;
 #ifdef ENABLE_MANAGEMENT
     unsigned int management_persist_flags;
 #endif
@@ -58,6 +74,13 @@ 
 
 void multi_tcp_instance_specific_free(struct multi_instance *mi);
 
+int multi_tcp_wait(struct context *c, struct multi_tcp *mtcp);
+
+void multi_tcp_process_io(struct multi_context *m);
+
+void multi_tcp_action(struct multi_context *m, struct multi_instance *mi, int action, bool poll);
+
+
 void multi_tcp_link_out_deferred(struct multi_context *m, struct multi_instance *mi);
 
 
diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index dd45720..e9182c8 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -192,10 +192,12 @@ 
     struct mroute_addr real;
     struct multi_instance *mi = NULL;
     struct hash *hash = m->hash;
+    real.proto = ls->info.proto;
 
     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);
@@ -318,18 +320,68 @@ 
         msg_set_prefix("Connection Attempt");
         m->top.c2.to_link = m->hmac_reply;
         m->top.c2.to_link_addr = m->hmac_reply_dest;
-        process_outgoing_link(&m->top, m->top.c2.link_sockets[0]);
+        for (int i = 0; i < m->top.c1.link_sockets_num; i++)
+        {
+            if (!proto_is_dgram(m->top.c2.link_sockets[i]->info.proto))
+            {
+                continue;
+            }
+
+            process_outgoing_link(&m->top, m->top.c2.link_sockets[i]);
+        }
         m->hmac_reply_dest = NULL;
     }
 }
 
 /*
+ * Return the io_wait() flags appropriate for
+ * a point-to-multipoint tunnel.
+ */
+unsigned int
+p2mp_iow_flags(const struct multi_context *m)
+{
+    unsigned int flags = IOW_WAIT_SIGNAL;
+    if (m->pending)
+    {
+        if (TUN_OUT(&m->pending->context))
+        {
+            flags |= IOW_TO_TUN;
+        }
+        if (LINK_OUT(&m->pending->context))
+        {
+            flags |= IOW_TO_LINK;
+        }
+    }
+    else if (mbuf_defined(m->mbuf))
+    {
+        flags |= IOW_MBUF;
+    }
+    else if (m->hmac_reply_dest)
+    {
+        flags |= IOW_TO_LINK;
+    }
+    else
+    {
+        flags |= IOW_READ;
+    }
+#ifdef _WIN32
+    if (tuntap_ring_empty(m->top.c1.tuntap))
+    {
+        flags &= ~IOW_READ_TUN;
+    }
+#endif
+    return flags;
+}
+
+/*
  * Process an I/O event.
  */
-static void
+void
 multi_process_io_udp(struct multi_context *m)
 {
-    const unsigned int status = m->top.c2.event_set_status;
+    const unsigned int status = m->mtcp->event_set_status;
+    /*p2mp_iow_flags(m); */
+    /*m->top.c2.event_set_status; */
     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);
@@ -428,47 +480,6 @@ 
 #endif
 }
 
-/*
- * Return the io_wait() flags appropriate for
- * a point-to-multipoint tunnel.
- */
-static inline unsigned int
-p2mp_iow_flags(const struct multi_context *m)
-{
-    unsigned int flags = IOW_WAIT_SIGNAL;
-    if (m->pending)
-    {
-        if (TUN_OUT(&m->pending->context))
-        {
-            flags |= IOW_TO_TUN;
-        }
-        if (LINK_OUT(&m->pending->context))
-        {
-            flags |= IOW_TO_LINK;
-        }
-    }
-    else if (mbuf_defined(m->mbuf))
-    {
-        flags |= IOW_MBUF;
-    }
-    else if (m->hmac_reply_dest)
-    {
-        flags |= IOW_TO_LINK;
-    }
-    else
-    {
-        flags |= IOW_READ;
-    }
-#ifdef _WIN32
-    if (tuntap_ring_empty(m->top.c1.tuntap))
-    {
-        flags &= ~IOW_READ_TUN;
-    }
-#endif
-    return flags;
-}
-
-
 void
 tunnel_server_udp(struct context *top)
 {
@@ -485,7 +496,7 @@ 
     }
 
     /* initialize global multi_context object */
-    multi_init(&multi, top, false);
+    multi_init(&multi, top);
 
     /* initialize our cloned top object */
     multi_top_init(&multi, top);
@@ -511,7 +522,7 @@ 
 
         /* set up and do the io_wait() */
         multi_get_timeout(&multi, &multi.top.c2.timeval);
-        io_wait(&multi.top, p2mp_iow_flags(&multi));
+        /*io_wait(&multi.top, p2mp_iow_flags(&multi)); */
         MULTI_CHECK_SIG(&multi);
 
         /* check on status of coarse timers */
@@ -525,7 +536,7 @@ 
         else
         {
             /* process I/O */
-            multi_process_io_udp(&multi);
+            /*multi_process_io_udp(&multi); */
             MULTI_CHECK_SIG(&multi);
         }
 
diff --git a/src/openvpn/mudp.h b/src/openvpn/mudp.h
index 1088246..75e5a5e 100644
--- a/src/openvpn/mudp.h
+++ b/src/openvpn/mudp.h
@@ -31,6 +31,8 @@ 
 struct context;
 struct multi_context;
 
+unsigned int
+p2mp_iow_flags(const struct multi_context *m);
 
 /**
  * Main event loop for OpenVPN in UDP server mode.
@@ -40,9 +42,11 @@ 
  *
  * @param top - Top-level context structure.
  */
+
+
 void tunnel_server_udp(struct context *top);
 
-
+void multi_process_io_udp(struct multi_context *m);
 /**************************************************************************/
 /**
  * Get, and if necessary create, the multi_instance associated with a
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index e9a3945..3522206 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -289,7 +289,7 @@ 
  * Main initialization function, init multi_context object.
  */
 void
-multi_init(struct multi_context *m, struct context *t, bool tcp_mode)
+multi_init(struct multi_context *m, struct context *t)
 {
     int dev = DEV_TYPE_UNDEF;
 
@@ -435,13 +435,13 @@ 
 
     m->instances = calloc(m->max_clients, sizeof(struct multi_instance *));
 
+    m->top.c2.event_set = t->c2.event_set;
+
     /*
      * Initialize multi-socket TCP I/O wait object
      */
-    if (tcp_mode)
-    {
-        m->mtcp = multi_tcp_init(t->options.max_clients, &m->max_clients);
-    }
+    m->mtcp = multi_tcp_init(t->options.max_clients, &m->max_clients);
+
     m->tcp_queue_limit = t->options.tcp_queue_limit;
 
     /*
@@ -665,7 +665,7 @@ 
             mi->did_iroutes = false;
         }
 
-        if (m->mtcp)
+        if (m->mtcp && !proto_is_dgram(m->top.options.ce.proto))
         {
             multi_tcp_dereference_instance(m->mtcp, mi);
         }
@@ -776,7 +776,6 @@ 
         mi->real = *real;
         generate_prefix(mi);
     }
-
     mi->did_open_context = true;
     inherit_context_child(&mi->context, &m->top, ls);
     if (IS_SIG(&mi->context))
@@ -798,6 +797,7 @@ 
         {
             goto err;
         }
+        mi->real.proto = ls->info.proto;
         generate_prefix(mi);
     }
 
@@ -4163,6 +4163,92 @@ 
 }
 
 
+void
+tunnel_server_loop(struct multi_context *multi)
+{
+    int status;
+
+    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();
+    }
+}
+
+void
+tunnel_server_init(struct context *top)
+{
+    struct multi_context multi;
+
+    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
+
+    tunnel_server_loop(&multi);
+
+    #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);
+}
+
 /*
  * Top level event loop.
  */
@@ -4171,12 +4257,6 @@ 
 {
     ASSERT(top->options.mode == MODE_SERVER);
 
-    if (proto_is_dgram(top->options.ce.proto))
-    {
-        tunnel_server_udp(top);
-    }
-    else
-    {
-        tunnel_server_tcp(top);
-    }
+    tunnel_server_init(top);
+
 }
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 0d4ec63..472c2a3 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -262,7 +262,7 @@ 
  * Called by mtcp.c, mudp.c, or other (to be written) protocol drivers
  */
 
-void multi_init(struct multi_context *m, struct context *t, bool tcp_mode);
+void multi_init(struct multi_context *m, struct context *t);
 
 void multi_uninit(struct multi_context *m);
 
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 9611423..9e800bb 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -2188,6 +2188,7 @@ 
     }
 
     ALLOC_OBJ_CLEAR_GC(e, struct local_entry, gc);
+    e->proto = PROTO_NONE;
     l->array[l->len++] = e;
 
     return e;
@@ -3154,14 +3155,30 @@ 
         if (ce->proto == PROTO_TCP)
         {
             ce->proto = PROTO_TCP_SERVER;
+            o->ce.proto = ce->proto;
+        }
+        if (ce->local_list)
+        {
+            for (int i = 0; i < ce->local_list->len; i++)
+            {
+                if (ce->local_list->array[i]->proto == PROTO_TCP)
+                {
+                    ce->local_list->array[i]->proto = PROTO_TCP_SERVER;
+                }
+                else if (ce->local_list->array[i]->proto == PROTO_NONE)
+                {
+                    ce->local_list->array[i]->proto = ce->proto;
+                }
+            }
         }
     }
 
-    if (o->client)
+    if (o->mode != MODE_SERVER)
     {
         if (ce->proto == PROTO_TCP)
         {
             ce->proto = PROTO_TCP_CLIENT;
+            o->ce.proto = ce->proto;
         }
     }
 
@@ -3312,6 +3329,10 @@ 
     {
         le->port = o->ce.local_port;
     }
+    if (!le->proto)
+    {
+        le->proto = o->ce.proto;
+    }
 }
 
 #ifdef _WIN32
@@ -3775,6 +3796,7 @@ 
         ASSERT(e);
         e->port = o->ce.local_port;
         e->bind_local = o->ce.bind_local;
+        e->proto = o->ce.proto;
     }
 
     /* use the same listen list for every outgoing connection */
@@ -6164,7 +6186,7 @@ 
         VERIFY_PERMISSION(OPT_P_UP);
         options->ifconfig_nowarn = true;
     }
-    else if (streq(p[0], "local") && p[1] && !p[3])
+    else if (streq(p[0], "local") && p[1] && !p[4])
     {
         struct local_entry *e;
 
@@ -6185,7 +6207,12 @@ 
         if (p[2])
         {
             e->port = p[2];
-            e->bind_local = true;
+            /*e->bind_local = true; */
+        }
+
+        if (p[3])
+        {
+            e->proto = ascii2proto(p[3]);
         }
     }
     else if (streq(p[0], "remote-random") && !p[1])
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 747ed56..cba0333 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -99,6 +99,7 @@ 
     const char *local;
     const char *port;
     bool bind_local;
+    int proto;
 };
 
 struct connection_entry