[L] Change in openvpn[master]: Implement basic multipeer support.

"plaisthos \(Code Review\) via Openvpn-devel" <[email protected]> Sun, 26 Jul 2026 00:06:51 +0000
Newsgroups gmane.network.openvpn.devel
Message-ID <[email protected]>
plaisthos has uploaded this change for review. ( http://gerrit.openvpn.net/c/openvpn/+/1824?usp=email )


Change subject: Implement basic multipeer support.
......................................................................

Implement basic multipeer support.

Current restriction:
- no tls-crypt-v2 support
- no TCP support
- no retry logic ( only one attempt at establishing a connection)
- only literal IP:port support for peer

Change-Id: Ic3a0809191addea024ff8bd69600f4dbad9c37a6
Signed-off-by: Arne Schwabe <[email protected]>
---
M src/openvpn/dco.c
M src/openvpn/init.c
M src/openvpn/init.h
M src/openvpn/mtcp.c
M src/openvpn/mudp.c
M src/openvpn/multi.c
M src/openvpn/multi.h
M src/openvpn/options.h
M src/openvpn/socket_util.c
M src/openvpn/socket_util.h
M src/openvpn/ssl.c
11 files changed, 342 insertions(+), 21 deletions(-)



  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/24/1824/1

diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
index d4beb04..9c3e71d 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -365,6 +365,13 @@
         }
     }
 
+    if (o->peer_list)
+    {
+        msg(msglevel, "Note: peer connections require asymmetric peer-id, "
+                      "disabling data channel offload");
+        return false;
+    }
+
 #if defined(_WIN32)
     if ((o->mode == MODE_SERVER) && !dco_win_supports_multipeer())
     {
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 0e649f8..e8644f4 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -4818,12 +4818,19 @@
 }
 
 void
-inherit_context_child(struct context *dest, const struct context *src, struct link_socket *sock)
+inherit_context_child(struct context *dest, const struct context *src, struct link_socket *sock, bool peer_connection)
 {
     CLEAR(*dest);
 
     /* proto_is_dgram will ASSERT(0) if proto is invalid */
-    dest->mode = proto_is_dgram(sock->info.proto) ? CM_CHILD_UDP : CM_CHILD_TCP;
+    if (peer_connection)
+    {
+        dest->mode = proto_is_dgram(sock->info.proto) ? CM_PEER_UDP : CM_PEER_TCP;
+    }
+    else
+    {
+        dest->mode = proto_is_dgram(sock->info.proto) ? CM_CHILD_UDP : CM_CHILD_TCP;
+    }
 
     dest->gc = gc_new();
 
@@ -4854,6 +4861,12 @@
 
     dest->c2.event_set = src->c2.event_set;
 
+    if (peer_connection)
+    {
+        dest->options.tls_client = true;
+        dest->options.tls_server = false;
+    }
+
     if (dest->mode == CM_CHILD_TCP)
     {
         /*
@@ -4893,7 +4906,7 @@
         ALLOC_OBJ_GC(dest->c2.link_socket_infos[0], struct link_socket_info, &dest->gc);
         *dest->c2.link_socket_infos[0] = sock->info;
 
-        /* locally override some link_socket_info fields */
+        /* locally override some link_socket_info fields. FIXME: copy from right socket?! */
         dest->c2.link_socket_infos[0]->lsa = &dest->c1.link_socket_addrs[0];
         dest->c2.link_socket_infos[0]->connection_established = false;
     }
diff --git a/src/openvpn/init.h b/src/openvpn/init.h
index 9d5050d..cc2f90a 100644
--- a/src/openvpn/init.h
+++ b/src/openvpn/init.h
@@ -102,7 +102,7 @@
 bool do_deferred_options(struct context *c, const uint64_t found, const bool is_update);
 
 void inherit_context_child(struct context *dest, const struct context *src,
-                           struct link_socket *sock);
+                           struct link_socket *sock, bool peer_connection);
 
 void inherit_context_top(struct context *dest, const struct context *src);
 
diff --git a/src/openvpn/mtcp.c b/src/openvpn/mtcp.c
index eb4e944..dde38b7 100644
--- a/src/openvpn/mtcp.c
+++ b/src/openvpn/mtcp.c
@@ -43,7 +43,7 @@
     struct gc_arena gc = gc_new();
     struct multi_instance *mi = NULL;
 
-    mi = multi_create_instance(m, NULL, sock);
+    mi = multi_create_instance(m, NULL, sock, false);
     if (mi)
     {
         mi->real.proto = sock->info.proto;
diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index 657055c..f74e74c 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -231,7 +231,7 @@
              * connect-freq but not against connect-freq-initial */
             reflect_filter_rate_limit_decrease(m->initial_rate_limiter);
 
-            mi = multi_create_instance(m, real, sock);
+            mi = multi_create_instance(m, real, sock, false);
             if (mi)
             {
                 multi_assign_peer_id(m, mi);
@@ -283,11 +283,9 @@
 }
 
 
-static struct multi_instance *
-multi_get_instance_udp_control(struct multi_context *m)
+static inline struct session_id
+multi_get_extract_sesison_id(struct multi_context *m)
 {
-    /* Copy buffer, to a tmp buffer, so that reading the sesison does not
-     * modify the internal pointers */
     struct buffer tmp = m->top.c2.buf;
 
     /* op code */
@@ -296,7 +294,17 @@
 
     struct session_id sid = { 0 };
     session_id_read(&sid, &tmp);
+    return sid;
+}
 
+static struct multi_instance *
+multi_get_instance_udp_control(struct multi_context *m)
+{
+    /* Copy buffer, to a tmp buffer, so that reading the sesison does not
+     * modify the internal pointers */
+
+
+    struct session_id sid = multi_get_extract_sesison_id(m);
     struct hash_element *he_sid = multi_hash_sid_lookup(m, &sid);
 
     if (he_sid)
@@ -336,7 +344,16 @@
                 == sock->info.proto)
             {
                 mi = m->instances[peer_id];
-                *floated = !link_socket_actual_match(&mi->context.c2.from, &m->top.c2.from);
+
+                /* FIXME:figure out if we want to setup from at the right place */
+                if (mi->context.c2.from.dest.addr.sa.sa_family == 0)
+                {
+                    *floated = true;
+                }
+                else
+                {
+                    *floated = !link_socket_actual_match(&mi->context.c2.from, &m->top.c2.from);
+                }
 
                 if (*floated)
                 {
@@ -359,6 +376,44 @@
     return NULL;
 }
 
+static struct multi_instance *
+multi_get_instance_udp_peer(struct multi_context *m, struct mroute_addr *real)
+{
+    /* If we have an outgoing connection we need to look it
+     * up some way. Since this might be a tls-crypt-v2 session,
+     * we would need the tls-crypt-v2 key before decrypting
+     * the packet, so we fall back to a simple lookup by address
+     * here */
+    struct multi_instance *mi = multi_get_instance_udp_real(m, real);
+    if (!mi)
+    {
+        return NULL;
+    }
+
+    const struct key_state *ks = get_primary_key(mi->context.c2.tls_multi);
+    ASSERT(ks);
+    if (session_id_defined(&ks->session_id_remote))
+    {
+        struct gc_arena gc = gc_new();
+        msg(D_MULTI_MEDIUM, "Peer connection: Peer already has established "
+                            "session id %s.",
+            mroute_addr_print(real, &gc));
+        gc_free(&gc);
+        return NULL;
+    }
+    /* we now just assume that this a possible session id for the session
+     * and therefore add the hash of the remote session id to the hash map */
+
+    /* We might have had another reset packet before */
+    if (session_id_defined(&mi->sid_hashed_value))
+    {
+        multi_hash_sid_remove(m, &mi->sid_hashed_value);
+    }
+    struct session_id sid = multi_get_extract_sesison_id(m);
+    multi_hash_sid_add(m, &sid, mi);
+    return mi;
+}
+
 struct multi_instance *
 multi_get_create_instance_udp(struct multi_context *m, bool *floated, struct link_socket *sock)
 {
@@ -400,7 +455,14 @@
          * packets can create a session. Data packets cannot */
         if (!mi)
         {
-            mi = handle_connection_attempt(m, sock, &real);
+            if (op != P_CONTROL_HARD_RESET_SERVER_V2)
+            {
+                mi = handle_connection_attempt(m, sock, &real);
+            }
+            else
+            {
+                mi = multi_get_instance_udp_peer(m, &real);
+            }
         }
     }
 
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index ff9121d..0b7dd02 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -422,6 +422,13 @@
         event_timeout_init(&m->stale_routes_check_et, t->options.stale_routes_check_interval, 0);
     }
 
+    if (t->options.connection_list)
+    {
+        /* For now we will check every 7s. Might later add an option
+         * to change the interval as a user */
+        event_timeout_init(&m->peer_connection_et, 7, now);
+    }
+
     m->deferred_shutdown_signal.signal_received = 0;
 }
 
@@ -717,7 +724,7 @@
  */
 struct multi_instance *
 multi_create_instance(struct multi_context *m, const struct mroute_addr *real,
-                      struct link_socket *sock)
+                      struct link_socket *sock, bool peer_connection)
 {
     struct gc_arena gc = gc_new();
     struct multi_instance *mi;
@@ -738,7 +745,7 @@
         generate_prefix(mi);
     }
 
-    inherit_context_child(&mi->context, &m->top, sock);
+    inherit_context_child(&mi->context, &m->top, sock, peer_connection);
     if (IS_SIG(&mi->context))
     {
         goto err;
@@ -749,7 +756,7 @@
     if (hash_n_elements(m->hash) >= m->max_clients)
     {
         msg(D_MULTI_ERRORS,
-            "MULTI: new incoming connection would exceed maximum number of clients (%d)",
+            "MULTI: new connection would exceed maximum number of clients (%d)",
             m->max_clients);
         goto err;
     }
@@ -1791,7 +1798,7 @@
 
     /* Print a warning if we detect the client being in P2P mode. The
      * negotiations will be otherwise compatible */
-    if (proto & IV_PROTO_NCP_P2P)
+    if (proto & IV_PROTO_NCP_P2P && !cm_mode_peer(c->mode))
     {
         multi_client_set_protocol_warnings_p2p(c);
     }
@@ -2520,6 +2527,19 @@
 multi_client_connect_real_addr(struct multi_context *m, struct multi_instance *mi,
                                bool deferred, uint64_t *option_types_found)
 {
+    if (cm_mode_peer(mi->context.mode))
+    {
+        /* If we have a multi client instance it should already be assigned
+         * to its address in its creation */
+        bool address_assigned = hash_lookup(m->hash, &mi->real) == mi;
+        if (!address_assigned)
+        {
+            msg(D_MULTI_ERRORS, "MULTI: Address for client connection is "
+                                "not correctly set up. Terminating connection");
+        }
+        return address_assigned ? CC_RET_SUCCEEDED : CC_RET_FAILED;
+    }
+
     /* If the address is already taken up by another client we fail the new
      * connection */
     if (!multi_check_dest_addr_allowed(m, mi, &mi->real))
@@ -2537,6 +2557,30 @@
 }
 
 /**
+ * This checks if all the conditions of an outgoing peer connection are met.
+ */
+static enum client_connect_return
+multi_client_connect_peer(struct multi_context *m, struct multi_instance *mi,
+                          bool deferred, uint64_t *option_types_found)
+{
+    if (!cm_mode_peer(mi->context.mode))
+    {
+        return CC_RET_SUCCEEDED;
+    }
+
+    struct tls_multi *multi = mi->context.c2.tls_multi;
+    if (!multi->use_asymmetric_peer_id)
+    {
+        msg(D_MULTI_ERRORS, "Connecting to another server/peer requires "
+                            "asymmetric peer-id support. The peer lacks this "
+                            "support. Terminating connection.");
+        return CC_RET_FAILED;
+    }
+
+    return CC_RET_SUCCEEDED;
+}
+
+/**
  *  Do the necessary modification for doing the compress migrate. This is
  *  implemented as a connect handler as it fits the modify config for a client
  *  paradigm and also is early enough in the chain to be overwritten by another
@@ -2630,6 +2674,7 @@
     uint64_t *option_types_found);
 
 static const multi_client_connect_handler client_connect_handlers[] = {
+    multi_client_connect_peer,
     multi_client_connect_real_addr,
     multi_client_connect_compress_migrate,
     multi_client_connect_source_ccd,
@@ -3873,6 +3918,186 @@
     return event_timeout_trigger(&m->stale_routes_check_et, &null, ETT_DEFAULT);
 }
 
+static bool
+peer_connection_trigger(struct multi_context *m)
+{
+    struct timeval null;
+    CLEAR(null);
+    return event_timeout_trigger(&m->peer_connection_et, &null, ETT_DEFAULT);
+}
+
+static bool
+multi_resolve_peer_addr(struct connection_entry *ce, struct mroute_addr *real, struct openvpn_sockaddr *sa,
+                        bool have_udp4, bool have_udp6)
+{
+    unsigned int flags = GETADDR_RESOLVE | GETADDR_WARN_ON_SIGNAL;
+    if (proto_is_dgram(ce->proto))
+    {
+        if (!have_udp6 && !have_udp4)
+        {
+            msg(M_INFO, "Cannot connect to %s:%s: no UDP sockets available", ce->remote, ce->remote_port);
+            return false;
+        }
+        flags |= GETADDR_DATAGRAM;
+    }
+
+    if (have_udp6 && !have_udp4)
+    {
+        /* The address family needs to match the socket address family. If we only
+         * have IPv6 sockets, we force IPv6 mapped IPv4 address with this. For
+         * TCP client, we create new sockets and don't have to worry about this */
+        flags |= GETADDR_FORCE_FAMILY;
+    }
+
+    struct addrinfo *ai;
+    int status = openvpn_getaddrinfo(flags, ce->remote, ce->remote_port, 0,
+                                     NULL, AF_INET6, &ai);
+
+    bool ret = false;
+    if (status != 0)
+    {
+        msg(M_INFO, "Failure to resolve %s:%s as peer address. Currently only "
+                    "literal IP/IPv6 addresses are supported",
+            ce->remote, ce->remote_port);
+        goto error;
+    }
+
+    mroute_addr_init(real);
+    real->proto = proto_is_dgram(ce->proto) ? PROTO_UDP : PROTO_TCP;
+
+    if (ai->ai_family == AF_INET)
+    {
+        struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr;
+        sa->addr.in4 = *sin;
+    }
+    else if (ai->ai_family == AF_INET6)
+    {
+        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ai->ai_addr;
+        sa->addr.in6 = *sin6;
+    }
+    else
+    {
+        ASSERT(0);
+    }
+
+    if (!mroute_extract_openvpn_sockaddr(real, sa, true))
+    {
+        msg(M_INFO, "Failure to convert resolve result for  %s:%s", ce->remote, ce->remote_port);
+        goto error;
+    }
+
+    ret = true;
+
+error:
+    freeaddrinfo(ai);
+    return ret;
+}
+
+static void
+multi_create_peer_instance(struct multi_context *m, struct connection_entry *ce)
+{
+    struct mroute_addr real;
+    struct openvpn_sockaddr sa = { 0 };
+
+    /* Remember if we have a udp4 and udp6 link sockets */
+    struct link_socket *udp4_link_sock = NULL;
+    struct link_socket *udp6_link_sock = NULL;
+
+    /* Check what kind of UDP sockets we have. Currently this takes the first
+     * UDP socket with AF_INET and first AF_INET6. We might later consider
+     * giving the user a finer control about the outgoing socket selection.
+     */
+    for (int i = 0; i < m->top.c1.link_sockets_num; i++)
+    {
+        struct addrinfo *bindlocal = m->top.c1.link_socket_addrs[i].bind_local;
+        if (!udp6_link_sock && bindlocal->ai_family == AF_INET6 && bindlocal->ai_socktype == SOCK_DGRAM)
+        {
+            udp6_link_sock = m->top.c2.link_sockets[i];
+        }
+        if (!udp4_link_sock && bindlocal->ai_family == AF_INET && bindlocal->ai_socktype == SOCK_DGRAM)
+        {
+            udp4_link_sock = m->top.c2.link_sockets[i];
+        }
+    }
+
+    /* Check if we can resolve the remote address. This method will already
+     * print warnings if it cannot resolve, so just return here when it fails */
+    if (!multi_resolve_peer_addr(ce, &real, &sa, (udp4_link_sock != NULL), (udp6_link_sock != NULL)))
+    {
+        return;
+    }
+
+    struct multi_instance *existing_mi = hash_lookup(m->hash, &real);
+    if (existing_mi)
+    {
+        msg(D_MULTI_LOW, "Cannot establish connection to %s:%s. Client or peer "
+                         "instance with this IP and port already exists",
+            ce->remote, ce->remote_port);
+        return;
+    }
+
+    struct link_socket *link_sock = NULL;
+
+    if (proto_is_dgram(ce->proto) && (real.type & MR_ADDR_MASK) == MR_ADDR_IPV4)
+    {
+        link_sock = udp4_link_sock;
+    }
+    else if (proto_is_dgram(ce->proto) && (real.type & MR_ADDR_MASK) == MR_ADDR_IPV6)
+    {
+        link_sock = udp6_link_sock;
+    }
+
+    if (!link_sock)
+    {
+        msg(D_MULTI_LOW, "Cannot determine outgoing socket for %s:%s %s.",
+            ce->remote, ce->remote_port, proto2ascii(ce->proto, sa.addr.sa.sa_family, true));
+        return;
+    }
+
+    struct multi_instance *mi = multi_create_instance(m, &real, link_sock, true);
+
+    if (!mi)
+    {
+        msg(D_MULTI_LOW, "Cannot create multi instance for %s:%s %s.",
+            ce->remote, ce->remote_port, proto2ascii(ce->proto, sa.addr.sa.sa_family, true));
+        return;
+    }
+
+    multi_assign_peer_id(m, mi);
+
+    /* todo check */
+    mi->did_real_hash = true;
+    ASSERT(hash_add(m->hash, &mi->real, mi, false));
+
+
+    /* This should be probably handled by setting link_socket_info while
+     * creating the multi_instance */
+    struct key_state *ks = &mi->context.c2.tls_multi->session[TM_INITIAL].key[KS_PRIMARY];
+    ks->remote_addr.dest = sa;
+
+    mi->context.c2.tls_multi->n_sessions++;
+    ce->peer_connection_initiated = true;
+}
+
+static void
+check_peer_connections(struct multi_context *m)
+{
+    /* If no peers are configured, there is nothing to do */
+    if (!m->top.options.peer_list)
+    {
+        return;
+    }
+
+    for (int i = 0; i < m->top.options.peer_list->len; i++)
+    {
+        struct connection_entry *ce = m->top.options.peer_list->array[i];
+        if (!ce->peer_connection_initiated)
+        {
+            multi_create_peer_instance(m, ce);
+        }
+    }
+}
+
 /*
  * Process timers in the top-level context
  */
@@ -3903,6 +4128,12 @@
     {
         check_stale_routes(m);
     }
+
+    /* Should we check for mesh peers that need to connect? */
+    if (m->top.options.connection_list && peer_connection_trigger(m))
+    {
+        check_peer_connections(m);
+    }
 }
 
 static void
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 98547b0..a5c4ce5 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -220,6 +220,9 @@
      */
     struct event_timeout stale_routes_check_et;
 
+    /** Timer object for peer connection check */
+    struct event_timeout peer_connection_et;
+
 #ifdef ENABLE_ASYNC_PUSH
     /* mapping between inotify watch descriptors and multi_instances */
     struct hash *inotify_watchers;
@@ -274,7 +277,8 @@
 
 struct multi_instance *multi_create_instance(struct multi_context *m,
                                              const struct mroute_addr *real,
-                                             struct link_socket *sock);
+                                             struct link_socket *sock,
+                                             bool peer_connection);
 
 void multi_close_instance(struct multi_context *m, struct multi_instance *mi, bool shutdown);
 
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 353a9a4..a7fa9fc 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -107,6 +107,8 @@
 
 struct connection_entry
 {
+    /* TODO: actually check */
+    bool peer_connection_initiated;
     struct local_list *local_list;
     int proto;
     sa_family_t af;
@@ -759,9 +761,10 @@
 #define OPT_P_INLINE          (1u << 29)
 #define OPT_P_PUSH_MTU        (1u << 30)
 #define OPT_P_ROUTE_TABLE     (1u << 31)
-#define OPT_P_PEER            (1u << 32)
+#define OPT_P_PEER            (1uLL << 32)
 
-#define OPT_P_DEFAULT (~(OPT_P_INSTANCE | OPT_P_PULL_MODE))
+/* Use 0uLL to force 64-bit here */
+#define OPT_P_DEFAULT (~(0uLL | OPT_P_INSTANCE | OPT_P_PULL_MODE))
 
 #define PULL_DEFINED(opt) ((opt)->pull)
 
diff --git a/src/openvpn/socket_util.c b/src/openvpn/socket_util.c
index 0194f38..b0ccfcf 100644
--- a/src/openvpn/socket_util.c
+++ b/src/openvpn/socket_util.c
@@ -590,7 +590,7 @@
 
     /* if hostname is not set, we want to bind to 'ANY', with
      * the correct address family - v4-only or v6/v6-dual-stack */
-    if (!hostname)
+    if (!hostname || (flags & GETADDR_FORCE_FAMILY))
     {
         hints.ai_family = ai_family;
     }
diff --git a/src/openvpn/socket_util.h b/src/openvpn/socket_util.h
index 13deeaa..cb4f9ab 100644
--- a/src/openvpn/socket_util.h
+++ b/src/openvpn/socket_util.h
@@ -126,6 +126,7 @@
 #define GETADDR_RANDOMIZE               (1u << 9)
 #define GETADDR_PASSIVE                 (1u << 10)
 #define GETADDR_DATAGRAM                (1u << 11)
+#define GETADDR_FORCE_FAMILY            (1u << 12)
 
 #define GETADDR_CACHE_MASK (GETADDR_DATAGRAM | GETADDR_PASSIVE)
 
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index 22b3f48..245f937 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -2458,7 +2458,7 @@
         setenv_del(session->opt->es, "exported_keying_material");
     }
 
-    if (!session->opt->server && !session->opt->pull && ks->key_id == 0)
+    if (!session->opt->server && !session->opt->pull && ks->key_id == 0 && session->opt->mode != MODE_SERVER)
     {
         /* We are a p2p tls-client without pull, enable common
          * protocol options */

-- 
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1824?usp=email
To unsubscribe, or for help writing mail filters, visit http://gerrit.openvpn.net/settings?usp=email

Gerrit-MessageType: newchange
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: Ic3a0809191addea024ff8bd69600f4dbad9c37a6
Gerrit-Change-Number: 1824
Gerrit-PatchSet: 1
Gerrit-Owner: plaisthos <[email protected]>
Gerrit-CC: openvpn-devel <[email protected]>

_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel