[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