From patchwork Tue Jul 28 15:50:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5154 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:fd0b:b0:87d:ab56:3700 with SMTP id cw11csp806219mac; Tue, 28 Jul 2026 08:50:51 -0700 (PDT) X-Forwarded-Encrypted: i=2; AHgh+RrOabZ/heGHBkifANiHU6tHsBS89+21bPK+G6o8gs+aRptNW4Wvj08b34ZQap3OxCaZfkuySFB2uVc=@openvpn.net X-Received: by 2002:a05:6870:8906:b0:456:4dc2:d94e with SMTP id 586e51a60fabf-4586cd67845mr1616170fac.37.1785253850999; Tue, 28 Jul 2026 08:50:50 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1785253850; cv=none; d=google.com; s=arc-20260327; b=kyHp2GUWtFsw0OCQ/j3iaAja85DGtjX0dt6lvE9oZv0ewJzMxpy69nKK25D1Qik/Lx o1WbkZ7xQO53mjixufb0oa6yEMkg7x5VZbcsTEgkl3pRIhGPrGLjhI/WB7MP3+5E+PkL a+PiFrp8cEzMCJ6mV84ET6ZwVKxPjcK36E/E7O38eh/Pb8F/Dt4+Du3/D2XplXO9zbzp G5cFi1UYyekghm77KIpxMjeCoIhfJL0Sd83l+c27gNBPrsHcRc9ZUS4r6ZfPBGWHg5GB q2IBzP2QLN5arOOc6EbXanb+5CNqTRp7eqpFp/J6jtMQ33HkvwBzq4uNkbWesZxJgcNk haxw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; 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:dkim-signature; bh=OwQp8eu25PJ6IZbJmB0VcQLgk6sfQqaiunUsFBTwmpI=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=GoGiHlxXGoC6MZN8iiM0L55qMnM6TVtYP+zuXS2v2cLqL8qFD+2QUaecPFiR3y7+50 QOg9BLGq9qN6hOytI6rRx+Z1z1iCTWiWJuPYkIvQXB5Yx8/K46GvNNeG01Zuc46yXgEW ryMKQkp62VmfMQ1SeGrBQUgg0qNQlZGRx+Yv9jl6svin40ch9UDBqVH2BT4C1vUJGziM p0x623nSVaM8Ihtg8iGAhVo6dDdvnH3Na0wxsnUixyfwLKW5nSGfP8Uw1Q3ynPr9sjzF D7cWRuArvd7I95OYuQyN+f1E+S2PTGko3PSX2e261NP1BoJHvtMAqmdPIccttAzVk/yZ Y76g==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=R0Q3sCy+; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="F/3N6dOU"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=IeEIJ3Yg; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=C8bc5yrl; 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 Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-45886ccbd52si187574fac.367.2026.07.28.08.50.50 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Tue, 28 Jul 2026 08:50:50 -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=R0Q3sCy+; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="F/3N6dOU"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=IeEIJ3Yg; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=C8bc5yrl; 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 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=OwQp8eu25PJ6IZbJmB0VcQLgk6sfQqaiunUsFBTwmpI=; b=R0Q3sCy++DlbZon29Yy8D6jcY3 uMvla5H3iiFOdRrJAvoIzQzxZaNt7lIQvyGXo/zht45K1q0fV8xibSppr5gChK0bLQ6oVkAIfd41D pYGRFDigrXUhvm6GFQ+JIKvbuAshYxlv26Wo6k7tU/YJFnWef5Ax9mAneYFI02V/SyVw=; Received: from [127.0.0.1] (helo=sfs-ml-3.v29.lw.sourceforge.com) by sfs-ml-3.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wok4s-00031Z-UV; Tue, 28 Jul 2026 15:50:47 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-3.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wok4r-00031M-7V for openvpn-devel@lists.sourceforge.net; Tue, 28 Jul 2026 15:50:45 +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:Cc:To:From:Sender:Reply-To: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=Qsp08g55trqf8aKQYWH1lajPuOiZlhiBrAgqh+hRgMw=; b=F/3N6dOUl5341VOUGXs29A8L2S 9PotIygkkxM35DWXJv4oxucgr5SFBZKBbkrb1ZsRjFZF1spTvMblFrcZ1gm1AJA2FtnNh9glLU2aH f2taviqvwcf9GBkcUWkdDR76xtQuv94Ik9cTRB9eCChJq2XaCNcnRfczj+MK4WmKt6tY=; 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:Cc:To:From:Sender:Reply-To: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=Qsp08g55trqf8aKQYWH1lajPuOiZlhiBrAgqh+hRgMw=; b=IeEIJ3YgUTIcfWwl4znhl451ic tJVZrc077N84Rmzgoltm5QjjNN9qoN8kzX7kAX0X9Hhk6nk1qULh8mkw1eD8jbZem7A04jeW2C5/H fnZri3VuxzC8qkNKVfAdh6CfzxwX8QV2q8u7cLvXiOi1IIiMRfX8e6Do2S8vE/qFaao0=; Received: from mout-b-105.mailbox.org ([195.10.208.50]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wok4m-00027P-IF for openvpn-devel@lists.sourceforge.net; Tue, 28 Jul 2026 15:50:45 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [IPv6:2001:67c:2050:b231:465::202]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-105.mailbox.org (Postfix) with ESMTPS id 4h8g0S4lBJz9s7V; Tue, 28 Jul 2026 17:50:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1785253832; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Qsp08g55trqf8aKQYWH1lajPuOiZlhiBrAgqh+hRgMw=; b=C8bc5yrlXVAVw0SnwK79stX3KNXF5E18CBqlzH1pFXDYW9XyXhK4US3Fx9f1sn/apIj2D/ lYEpUUbFCa1/Pa0tn8RWFDr96o9kqy0RC/FhuBu44bBJgmd+1RuuQ84rbcHJPEXL6dcjhL ds4U5AboOQzctXbjsiqEp4VNt8rfrz3pRd+dYx/wm+tAanAH4ZpFtouJrNM0r2Wc3Anxh/ cJCUkK5Tflsi+mLBY9JwMznHJ7/Jao15VU+aOy0OwfoJlGdvDKqizGiMDazUiffJC9ya+A Nr37UNsTnq5JQJ7Z8PcefMN2zswlYO+DajeNy1/s7n0Ije8C/HcNrgOEJyoYgQ== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::202 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Tue, 28 Jul 2026 17:50:14 +0200 Message-ID: <082540583b9145d89e1cdd5a74c485ea3a53d285.1785253480.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4h8g0S4lBJz9s7V X-Spam-Score: -0.2 (/) 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: struct ovpn_bind is published through peer->bind with RCU. Remote endpoint changes already replace the whole bind object, but local endpoint learning and UDP source fallback still updated bind->local [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [195.10.208.50 listed in wl.mailspike.net] X-Headers-End: 1wok4m-00027P-IF Subject: [Openvpn-devel] [PATCH ovpn net 4/5] ovpn: avoid in-place updates of peer bind local address 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: 1871974341964309312 X-GMAIL-MSGID: 1871974341964309312 struct ovpn_bind is published through peer->bind with RCU. Remote endpoint changes already replace the whole bind object, but local endpoint learning and UDP source fallback still updated bind->local in place. UDP TX can read it locklessly while another CPU updates it under peer->lock. For IPv6, that can produce torn reads of the address field. Fix this by making the local endpoint immutable after publication too: build a new bind object with the updated local address and publish it through peer->bind. When UDP TX discovers that the remembered local source is no longer usable, retry route lookup with a wildcard source. If the lookup succeeds and the bind used for the lookup is still current, invalidate the peer dst cache and best-effort publish a replacement bind with wildcard local address. The current packet can still be transmitted with the resolved route even if that bind replacement fails; a later cache miss will retry the repair. Only store the resolved dst when the local address did not need to be reset. A local address change invalidates all per-CPU dst cache entries, while dst_cache_set_ip4 and dst_cache_set_ip6 only update the current CPU slot. Avoid the old reset-then-set pattern and let the next TX repopulate the cache from the new bind state. Fixes: f0281c1d3732 ("ovpn: add support for updating local or remote UDP endpoint") Fixes: 08857b5ec5d9 ("ovpn: implement basic TX path (UDP)") Signed-off-by: Ralf Lici --- drivers/net/ovpn/peer.c | 36 +++++++++----- drivers/net/ovpn/udp.c | 108 +++++++++++++++++++++++++++++++--------- 2 files changed, 107 insertions(+), 37 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 4806e942be27..383d712582c9 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -197,12 +197,11 @@ int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) { struct hlist_nulls_head *nhead; + const void *local_ip = NULL; struct sockaddr_storage ss; struct sockaddr_in6 *sa6; - bool reset_cache = false; struct sockaddr_in *sa; struct ovpn_bind *bind; - const void *local_ip; size_t salen = 0; spin_lock_bh(&peer->lock); @@ -224,7 +223,6 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) sa->sin_addr.s_addr = ip_hdr(skb)->saddr; sa->sin_port = udp_hdr(skb)->source; salen = sizeof(*sa); - reset_cache = true; break; } @@ -236,8 +234,7 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) netdev_name(peer->ovpn->dev), peer->id, &bind->local.ipv4.s_addr, &ip_hdr(skb)->daddr); - bind->local.ipv4.s_addr = ip_hdr(skb)->daddr; - reset_cache = true; + local_ip = &ip_hdr(skb)->daddr; } break; case htons(ETH_P_IPV6): @@ -254,7 +251,6 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) sa6->sin6_scope_id = ipv6_iface_scope_id(&ipv6_hdr(skb)->saddr, skb->skb_iif); salen = sizeof(*sa6); - reset_cache = true; break; } @@ -267,26 +263,40 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) netdev_name(peer->ovpn->dev), peer->id, &bind->local.ipv6, &ipv6_hdr(skb)->daddr); - bind->local.ipv6 = ipv6_hdr(skb)->daddr; - reset_cache = true; + local_ip = &ipv6_hdr(skb)->daddr; } break; default: goto unlock; } - if (unlikely(reset_cache)) - dst_cache_reset(&peer->dst_cache); - - /* if the peer did not float, we can bail out now */ - if (likely(!salen)) + /* if there was no float and the local address is unchanged, bail out */ + if (likely(!salen && !local_ip)) goto unlock; + /* if only the local address changed, populate ss with the current + * remote + */ + if (!salen) + memcpy(&ss, &bind->remote, + bind->remote.in4.sin_family == AF_INET ? + sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6)); + if (unlikely(ovpn_peer_reset_sockaddr(peer, (struct sockaddr_storage *)&ss, local_ip) < 0)) goto unlock; + /* reset the cache only after a successful bind update to avoid useless + * cache misses on concurrent TX + */ + dst_cache_reset(&peer->dst_cache); + + /* if the peer did not float, we can bail out now */ + if (!salen) + goto unlock; + net_dbg_ratelimited("%s: peer %d floated to %pIScp", netdev_name(peer->ovpn->dev), peer->id, &ss); diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index e43b946c8289..ced4f9ff4a08 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -173,6 +173,35 @@ static void ovpn_dst_cache_check_key(struct ovpn_peer *peer, spin_unlock_bh(&peer->lock); } +/** + * ovpn_dst_cache_current - check whether a route lookup matches peer state + * @peer: the peer owning the bind and dst cache + * @bind: the RCU bind used for the route lookup + * @key: the route key used for the route lookup + * + * Check that @bind is still the current peer bind and that @key still matches + * the peer route key. The caller must hold @peer->lock. The TX path keeps + * @bind inside an RCU read-side critical section, so pointer identity is enough + * to detect whether the bind was replaced while the route lookup was running. + * + * Return: true if the lookup result still matches the current peer state and + * may update the dst cache or replace the bind. + */ +static bool ovpn_dst_cache_current(const struct ovpn_peer *peer, + const struct ovpn_bind *bind, + const struct ovpn_route_key *key) +{ + const struct ovpn_bind *curr_bind; + + lockdep_assert_held(&peer->lock); + + curr_bind = rcu_dereference_protected(peer->bind, + lockdep_is_held(&peer->lock)); + + return curr_bind == bind && + ovpn_route_key_equal(key, &peer->route_key); +} + /** * ovpn_udp4_output - send IPv4 packet over udp socket * @peer: the destination peer @@ -189,6 +218,9 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct sk_buff *skb, const struct ovpn_route_key *key) { + struct sockaddr_storage remote; + struct in_addr local = {}; + bool reset_local = false; struct rtable *rt; struct flowi4 fl = { .saddr = bind->local.ipv4.s_addr, @@ -207,24 +239,17 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (fl.saddr && unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, fl.saddr, RT_SCOPE_HOST))) { - /* we may end up here when the cached address is not usable - * anymore. In this case we reset address/cache and perform a - * new look up + /* The learned local address is not usable anymore. + * Retry with source address autoselection. */ fl.saddr = 0; - spin_lock_bh(&peer->lock); - bind->local.ipv4.s_addr = 0; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; } rt = ip_route_output_flow(sock_net(sk), &fl, sk); if (IS_ERR(rt) && PTR_ERR(rt) == -EINVAL) { fl.saddr = 0; - spin_lock_bh(&peer->lock); - bind->local.ipv4.s_addr = 0; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; rt = ip_route_output_flow(sock_net(sk), &fl, sk); } @@ -238,10 +263,28 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, goto err; } - /* avoid storing a stale cache */ + /* avoid storing a stale cache or local address */ spin_lock_bh(&peer->lock); - if (likely(ovpn_route_key_equal(key, &peer->route_key))) - dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + if (likely(ovpn_dst_cache_current(peer, bind, key))) { + if (!reset_local) { + dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + spin_unlock_bh(&peer->lock); + goto transmit; + } + + /* invalidate per-CPU dst entries that may still carry + * the stale source + */ + dst_cache_reset(cache); + + /* preserve the current remote */ + memcpy(&remote, &bind->remote, sizeof(struct sockaddr_in)); + /* The current packet already has a valid wildcard-source route. + * If replacing the bind fails, leave the stale local in place; + * a later cache miss will retry the repair. + */ + ovpn_peer_reset_sockaddr(peer, &remote, &local); + } spin_unlock_bh(&peer->lock); transmit: @@ -271,6 +314,9 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct sk_buff *skb, const struct ovpn_route_key *key) { + struct in6_addr local = in6addr_any; + struct sockaddr_storage remote; + bool reset_local = false; struct dst_entry *dst; int ret; @@ -291,15 +337,11 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (!ipv6_addr_any(&fl.saddr) && unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) { - /* we may end up here when the cached address is not usable - * anymore. In this case we reset address/cache and perform a - * new look up + /* The learned local address is not usable anymore. + * Retry with source address autoselection. */ fl.saddr = in6addr_any; - spin_lock_bh(&peer->lock); - bind->local.ipv6 = in6addr_any; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; } dst = ip6_dst_lookup_flow(sock_net(sk), sk, &fl, NULL); @@ -311,10 +353,28 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, goto err; } - /* avoid storing a stale cache */ + /* avoid storing a stale cache or local address */ spin_lock_bh(&peer->lock); - if (likely(ovpn_route_key_equal(key, &peer->route_key))) - dst_cache_set_ip6(cache, dst, &fl.saddr); + if (likely(ovpn_dst_cache_current(peer, bind, key))) { + if (!reset_local) { + dst_cache_set_ip6(cache, dst, &fl.saddr); + spin_unlock_bh(&peer->lock); + goto transmit; + } + + /* invalidate per-CPU dst entries that may still carry + * the stale source + */ + dst_cache_reset(cache); + + /* preserve the current remote */ + memcpy(&remote, &bind->remote, sizeof(struct sockaddr_in6)); + /* The current packet already has a valid wildcard-source route. + * If replacing the bind fails, leave the stale local in place; + * a later cache miss will retry the repair. + */ + ovpn_peer_reset_sockaddr(peer, &remote, &local); + } spin_unlock_bh(&peer->lock); transmit: