From patchwork Fri Jul 18 12:22:24 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 4314 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1914:b0:671:5a2c:6455 with SMTP id g20csp314001maz; Fri, 18 Jul 2025 08:04:32 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCVCLBipJqmZJlJnO+mmYHkerV1SryH3sq75r2EFahRQEHTD+9aa3O1uiKkK/1tUAvgBvqn7eytoi7I=@openvpn.net X-Google-Smtp-Source: AGHT+IFkz6bxaVkKKq1bsTOR2GEuSKI2vNVcLXt/QVyOu1oyuApbcKIu0u4kUhFJ14PRD7/4haRz X-Received: by 2002:a05:6808:250a:b0:407:59ac:d72e with SMTP id 5614622812f47-41d038d8ed8mr6668955b6e.12.1752851072314; Fri, 18 Jul 2025 08:04:32 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1752851072; cv=none; d=google.com; s=arc-20240605; b=MSolxGNnuC5kIiCAPpqqcNiPj0aTd//wgOyUco5igT8jdBYZf5iSUDr5PdBg79yBJq qbEGkvBG7pnn7n3sy6wWAp33LQpaSJ1I3CAyqSZ72qHFnzrFLsHYPXYZTP0574XlR6sx 2PNqmny4IgZEzDI7YILpCTOaanciR8J3IJ4vX+LL94tDLPTkk2OoD809UAUrQ1F3XCeY cKEpYlFic/m4AfGNMb7Pj+PJFpe1ZeKZo5qNsKmbgu9Y/PwfEt69vkXjCAhQxpCMmSli Z0528NmBhJgk9+ZpNuCppRmVYugnap8vmI7ACSuLHiQWVtMkRzQRvPI/KOegoKPvrGYr 5GXA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature; bh=JqseOMZqAWuMXQDkJN4xJa0W5iUtkA3QNWQpJ/EDga8=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=Q0cufbLROOhyvYcLCqlPNXKTlm+uHSBVBENoHdzQB9oVh62+0XSfDrkgh0zeEdPLgs 1rEjuG5mY6l3gYWLsOC+ziXiHiStrP5jocdV2pQmZ5iyopH57DGle7OHAiqf5/gxwtsr vyXH3X/5qBjcXBLd7lZYFbKS+ivg5Ek/fFMwDMwb/Y7OVLpAxRv0mykfKzFXMDexHjks StNM0C3/03EUZ08yMbElImkyXHJKuV2Slm1cV9F7WCxww8l6AcIlpL8U7ikhDcMde8/e GXL4K7ftsPGSMduiI4puiywHeX6yGF2J0Z4m2PXNVnWm+nRqmqJS7dHM7uW/tnX2v5DX h2oA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b="I/3MZhkL"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=DC9dWplS; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=Ifgtoyel; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 5614622812f47-41fd428af62si656980b6e.155.2025.07.18.08.04.31 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 18 Jul 2025 08:04:32 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b="I/3MZhkL"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=DC9dWplS; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=Ifgtoyel; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=JqseOMZqAWuMXQDkJN4xJa0W5iUtkA3QNWQpJ/EDga8=; b=I/3MZhkLaaLpzwW/LLZN1m2wK6 958eBXEChmtyuXc7bm6LiFWjjJsqJurH9UG86grmxad6B/5UyHtLcNpIT2qASZ0wWL12J1K/g1cJC ZPYAPMZGqOf/Igpsb8yZH/1sNatdwIHuuP5JbzBeCrK1cXxspheIOLDlhYT3NrGGwDdU=; Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1ucmdO-0005ta-7M; Fri, 18 Jul 2025 15:04:26 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1ucmdM-0005tQ-3O for openvpn-devel@lists.sourceforge.net; Fri, 18 Jul 2025 15:04:24 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=0v87IfJgXhtQbP+xUvWn+geUTyxbS3gWFbz765yYQMg=; b=DC9dWplSWSbehDJxAvXOaqy9uw gZkL4ZDxzeVQru0RHwxI8Qc4XEWQN0i00zK8Z2DxvsPdRrHo8gILH6Dnyp3f7H33Sb8OIqSr5ymqf A5uhsCtTGWDcKUvDHa3ihQf5n2nDiHSXKDaS+/p/GsDdaXAGz51Xx9iWlSzGiWjduDZs=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=0v87IfJgXhtQbP+xUvWn+geUTyxbS3gWFbz765yYQMg=; b=Ifgtoyel68oqqRdG5ZJji3Kqb9 pP6A679LIgT6I940SO2HLT9pULYnMWaCqEvR1BczTxnkFERjcvxGLowBEfFKUTRsABYC0OPO5UrAL loPxBDRUR5WJd1ZojSIUBjkXAL7aPkqOlA6fVje7+NkI9M1/FzrTA74c+bWmG6Hm/0No=; Received: from [193.149.48.143] (helo=blue.greenie.muc.de) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1ucmdK-0006Lm-Na for openvpn-devel@lists.sourceforge.net; Fri, 18 Jul 2025 15:04:24 +0000 Received: from blue.greenie.muc.de (localhost [127.0.0.1]) by blue.greenie.muc.de (8.17.1.9/8.17.1.9) with ESMTP id 56ICMUcG014033 for ; Fri, 18 Jul 2025 14:22:30 +0200 Received: (from gert@localhost) by blue.greenie.muc.de (8.17.1.9/8.17.1.9/Submit) id 56ICMUQb014032 for openvpn-devel@lists.sourceforge.net; Fri, 18 Jul 2025 14:22:30 +0200 From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Fri, 18 Jul 2025 14:22:24 +0200 Message-ID: <20250718122230.14008-1-gert@greenie.muc.de> X-Mailer: git-send-email 2.49.0 In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: 1.3 (+) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: From: Ralf Lici When a peer changes its UDP endpoint, the DCO module emits a notification to userpace. The message is parsed and the relevant information are extracted in order to process the floating operation. Content analysis details: (1.3 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS X-Headers-End: 1ucmdK-0006Lm-Na Subject: [Openvpn-devel] [PATCH v6] dco: Add support for float notifications X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1837821723402930993?= X-GMAIL-MSGID: =?utf-8?q?1837997565769822794?= From: Ralf Lici When a peer changes its UDP endpoint, the DCO module emits a notification to userpace. The message is parsed and the relevant information are extracted in order to process the floating operation. Note that we preserve IPv4-mapped IPv6 addresses in userspace when receiving a pure IPv4 address from the module, otherwise openvpn wouldn't be able to retrieve the multi_instance using the transport address hash table lookup. It may happen that a netlink notification gets lost, causing us to skip a float step. If the peer then floats back to its previous address, userspace closes the only valid instance while trying to process the float, leading to a segfault. To prevent this, we ignore float attempts to an address already taken by a peer with the same peer ID. Change-Id: I33e9272b4196c7634db2fb33a75ae4261660867f Signed-off-by: Ralf Lici Acked-by: Gert Doering --- This change was reviewed on Gerrit and approved by at least one developer. I request to merge it to master. Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1084 This mail reflects revision 6 of this Change. Acked-by according to Gerrit (reflected above): Gert Doering diff --git a/src/openvpn/dco_linux.c b/src/openvpn/dco_linux.c index 22a445a..f04ebfe 100644 --- a/src/openvpn/dco_linux.c +++ b/src/openvpn/dco_linux.c @@ -768,6 +768,44 @@ return ret; } +static bool +ovpn_parse_float_addr(struct nlattr **attrs, struct sockaddr *out) +{ + if (!attrs[OVPN_A_PEER_REMOTE_PORT]) + { + msg(D_DCO, "ovpn-dco: no remote port in PEER_FLOAT_NTF message"); + return false; + } + + if (attrs[OVPN_A_PEER_REMOTE_IPV4]) + { + struct sockaddr_in *addr4 = (struct sockaddr_in *)out; + CLEAR(*addr4); + addr4->sin_family = AF_INET; + addr4->sin_port = nla_get_u16(attrs[OVPN_A_PEER_REMOTE_PORT]); + addr4->sin_addr.s_addr = nla_get_u32(attrs[OVPN_A_PEER_REMOTE_IPV4]); + return true; + } + else if (attrs[OVPN_A_PEER_REMOTE_IPV6] + && nla_len(attrs[OVPN_A_PEER_REMOTE_IPV6]) == sizeof(struct in6_addr)) + { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)out; + CLEAR(*addr6); + addr6->sin6_family = AF_INET6; + addr6->sin6_port = nla_get_u16(attrs[OVPN_A_PEER_REMOTE_PORT]); + memcpy(&addr6->sin6_addr, nla_data(attrs[OVPN_A_PEER_REMOTE_IPV6]), + sizeof(addr6->sin6_addr)); + if (attrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]) + { + addr6->sin6_scope_id = nla_get_u32(attrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]); + } + return true; + } + + msg(D_DCO, "ovpn-dco: no valid remote IP address in PEER_FLOAT_NTF message"); + return false; +} + /* This function parses any netlink message sent by ovpn-dco to userspace */ static int ovpn_handle_msg(struct nl_msg *msg, void *arg) @@ -856,6 +894,45 @@ break; } + case OVPN_CMD_PEER_FLOAT_NTF: + { + if (!attrs[OVPN_A_PEER]) + { + msg(D_DCO, "ovpn-dco: no peer in PEER_FLOAT_NTF message"); + return NL_STOP; + } + + struct nlattr *fp_attrs[OVPN_A_PEER_MAX + 1]; + if (nla_parse_nested(fp_attrs, OVPN_A_PEER_MAX, attrs[OVPN_A_PEER], + NULL)) + { + msg(D_DCO, "ovpn-dco: can't parse peer in PEER_FLOAT_NTF messsage"); + return NL_STOP; + } + + if (!fp_attrs[OVPN_A_PEER_ID]) + { + msg(D_DCO, "ovpn-dco: no peer-id in PEER_FLOAT_NTF message"); + return NL_STOP; + } + uint32_t peerid = nla_get_u32(fp_attrs[OVPN_A_PEER_ID]); + + if (!ovpn_parse_float_addr(fp_attrs, (struct sockaddr *)&dco->dco_float_peer_ss)) + { + return NL_STOP; + } + + struct gc_arena gc = gc_new(); + msg(D_DCO_DEBUG, + "ovpn-dco: received CMD_PEER_FLOAT_NTF, ifindex: %u, peer-id %u, address: %s", + ifindex, peerid, print_sockaddr((struct sockaddr *)&dco->dco_float_peer_ss, &gc)); + dco->dco_message_peer_id = (int)peerid; + dco->dco_message_type = OVPN_CMD_PEER_FLOAT_NTF; + + gc_free(&gc); + break; + } + case OVPN_CMD_KEY_SWAP_NTF: { if (!attrs[OVPN_A_KEYCONF]) diff --git a/src/openvpn/dco_linux.h b/src/openvpn/dco_linux.h index 4e441ec..676b8cd 100644 --- a/src/openvpn/dco_linux.h +++ b/src/openvpn/dco_linux.h @@ -34,6 +34,7 @@ /* Defines to avoid mismatching with other platforms */ #define OVPN_CMD_DEL_PEER OVPN_CMD_PEER_DEL_NTF #define OVPN_CMD_SWAP_KEYS OVPN_CMD_KEY_SWAP_NTF +#define OVPN_CMD_FLOAT_PEER OVPN_CMD_PEER_FLOAT_NTF typedef enum ovpn_key_slot dco_key_slot_t; typedef enum ovpn_cipher_alg dco_cipher_t; @@ -75,6 +76,7 @@ int dco_message_peer_id; int dco_message_key_id; int dco_del_peer_reason; + struct sockaddr_storage dco_float_peer_ss; uint64_t dco_read_bytes; uint64_t dco_write_bytes; } dco_context_t; diff --git a/src/openvpn/dco_win.c b/src/openvpn/dco_win.c index 2a13658..83db739 100644 --- a/src/openvpn/dco_win.c +++ b/src/openvpn/dco_win.c @@ -663,6 +663,7 @@ dco->dco_message_peer_id = dco->notif_buf.PeerId; dco->dco_message_type = dco->notif_buf.Cmd; dco->dco_del_peer_reason = dco->notif_buf.DelPeerReason; + dco->dco_float_peer_ss = dco->notif_buf.FloatAddress; } else { diff --git a/src/openvpn/dco_win.h b/src/openvpn/dco_win.h index 4513f3f..b9d93fa 100644 --- a/src/openvpn/dco_win.h +++ b/src/openvpn/dco_win.h @@ -52,6 +52,7 @@ int dco_message_peer_id; int dco_message_type; int dco_del_peer_reason; + struct sockaddr_storage dco_float_peer_ss; uint64_t dco_read_bytes; uint64_t dco_write_bytes; diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c index a4f260a..dfc6708 100644 --- a/src/openvpn/forward.c +++ b/src/openvpn/forward.c @@ -1243,6 +1243,41 @@ perf_pop(); } +void +extract_dco_float_peer_addr(const sa_family_t socket_family, + struct openvpn_sockaddr *out_osaddr, + const struct sockaddr *float_sa) +{ + if (float_sa->sa_family == AF_INET) + { + struct sockaddr_in *float4 = (struct sockaddr_in *)float_sa; + /* DCO treats IPv4-mapped IPv6 addresses as pure IPv4. However, on a + * dual-stack socket, we need to preserve the mapping otherwise openvpn + * will not be able to find the peer by its transport address. + */ + if (socket_family == AF_INET6) + { + out_osaddr->addr.in6.sin6_family = AF_INET6; + out_osaddr->addr.in6.sin6_port = float4->sin_port; + + memset(&out_osaddr->addr.in6.sin6_addr.s6_addr, 0, 10); + out_osaddr->addr.in6.sin6_addr.s6_addr[10] = 0xff; + out_osaddr->addr.in6.sin6_addr.s6_addr[11] = 0xff; + memcpy(&out_osaddr->addr.in6.sin6_addr.s6_addr[12], + &float4->sin_addr.s_addr, sizeof(in_addr_t)); + } + else + { + memcpy(&out_osaddr->addr.in4, float4, sizeof(struct sockaddr_in)); + } + } + else + { + struct sockaddr_in6 *float6 = (struct sockaddr_in6 *)float_sa; + memcpy(&out_osaddr->addr.in6, float6, sizeof(struct sockaddr_in6)); + } +} + static void process_incoming_dco(struct context *c) { diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h index 318691f..2818fd1 100644 --- a/src/openvpn/forward.h +++ b/src/openvpn/forward.h @@ -196,6 +196,21 @@ void process_incoming_link_part2(struct context *c, struct link_socket_info *lsi, const uint8_t *orig_buf); /** + * Transfers \c float_sa data extracted from an incoming DCO + * PEER_FLOAT_NTF to \c out_osaddr for later processing. + * + * @param socket_family - The address family of the socket + * @param out_osaddr - openvpn_sockaddr struct that will be filled the new + * address data + * @param float_sa - The sockaddr struct containing the data received from the + * DCO notification + */ +void +extract_dco_float_peer_addr(sa_family_t socket_family, + struct openvpn_sockaddr *out_osaddr, + const struct sockaddr *float_sa); + +/** * Write a packet to the external network interface. * @ingroup external_multiplexer * diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index a760e07..ead3dd0 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -3251,6 +3251,18 @@ goto done; } + /* It doesn't make sense to let a peer float to the address it already + * has, so we disallow it. This can happen if a DCO netlink notification + * gets lost and we miss a floating step. + */ + if (m1->peer_id == m2->peer_id) + { + msg(M_WARN, "disallowing peer %" PRIu32 " (%s) from floating to " + "its own address (%s)", + m1->peer_id, tls_common_name(mi->context.c2.tls_multi, false), + mroute_addr_print(&mi->real, &gc)); + goto done; + } msg(D_MULTI_MEDIUM, "closing instance %s", multi_instance_string(ex_mi, false, &gc)); multi_close_instance(m, ex_mi, false); } @@ -3384,6 +3396,17 @@ { process_incoming_del_peer(m, mi, dco); } +#if defined(TARGET_LINUX) || defined(TARGET_WIN32) + else if (dco->dco_message_type == OVPN_CMD_FLOAT_PEER) + { + ASSERT(mi->context.c2.link_sockets[0]); + extract_dco_float_peer_addr(mi->context.c2.link_sockets[0]->info.af, + &m->top.c2.from.dest, + (struct sockaddr *)&dco->dco_float_peer_ss); + multi_process_float(m, mi, mi->context.c2.link_sockets[0]); + CLEAR(dco->dco_float_peer_ss); + } +#endif /* if defined(TARGET_LINUX) || defined(TARGET_WIN32) */ else if (dco->dco_message_type == OVPN_CMD_SWAP_KEYS) { tls_session_soft_reset(mi->context.c2.tls_multi); diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index 40f7519..fe9e847 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -322,7 +322,7 @@ /** * Process an incoming DCO message (from kernel space). * - * @param m - The single \c multi_context structur.e + * @param m - The single \c multi_context structure. * * @return * - True, if the message was received correctly. diff --git a/src/openvpn/ovpn_dco_linux.h b/src/openvpn/ovpn_dco_linux.h index 680d152..b3c9ff0 100644 --- a/src/openvpn/ovpn_dco_linux.h +++ b/src/openvpn/ovpn_dco_linux.h @@ -99,6 +99,7 @@ OVPN_CMD_KEY_SWAP, OVPN_CMD_KEY_SWAP_NTF, OVPN_CMD_KEY_DEL, + OVPN_CMD_PEER_FLOAT_NTF, __OVPN_CMD_MAX, OVPN_CMD_MAX = (__OVPN_CMD_MAX - 1) diff --git a/src/openvpn/ovpn_dco_win.h b/src/openvpn/ovpn_dco_win.h index 865bb38..dd6b7ce 100644 --- a/src/openvpn/ovpn_dco_win.h +++ b/src/openvpn/ovpn_dco_win.h @@ -149,7 +149,8 @@ typedef enum { OVPN_CMD_DEL_PEER, - OVPN_CMD_SWAP_KEYS + OVPN_CMD_SWAP_KEYS, + OVPN_CMD_FLOAT_PEER } OVPN_NOTIFY_CMD; typedef enum { @@ -164,6 +165,7 @@ OVPN_NOTIFY_CMD Cmd; int PeerId; OVPN_DEL_PEER_REASON DelPeerReason; + struct sockaddr_storage FloatAddress; } OVPN_NOTIFY_EVENT, * POVPN_NOTIFY_EVENT; typedef struct _OVPN_MP_DEL_PEER {