Re: [PATCH ovpn net v2 4/5] ovpn: avoid in-place updates of peer bind local address

Ralf Lici <[email protected]>
Newsgroups gmane.network.openvpn.devel
Message-ID <[email protected]>
On Thu, 13 Aug 2026 18:55:33 +0200, Sabrina Dubroca <[email protected]> wrote:
> Hi Ralf,
>

Hi Sabrina,

> 2026-07-29, 09:20:35 +0200, Ralf Lici wrote:
> > 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.
>
> This feels like 2 or 3 different bugs. At least making the endpoint
> changes rebuild the bind object seems like a standalone patch.
>

Agreed. My plan is to split this in 3 patches:

1. Validate bind identity and route key before publishing a looked-up
   dst;
2. Make ovpn_peer_endpoints_update replace the bind when learning a new
   local endpoint;
3. Make the UDP output fallback replace the bind when clearing a stale
   local source.

> > 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 <[email protected]>
> > ---
> > No changes since v1 https://lore.kernel.org/openvpn-devel/082540583b9145d89e1cdd5a74c485ea3a53d285.1785253480.git.ralf@mandelbit.com/
> > 
> >  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
> [...]
> > @@ -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;
>
> nit: !salen is redundant? local_ip is also set in the float case.
>

Good catch.

> but this whole check could be skipped by adding a "break" in the
> "local endpoint changed" case and a "goto unlock" if nothing changed
> in both cases of the skb->protocol switch.
>

Right, that's cleaner. I'll break from the switch when the local
endpoint changed and go directly to unlock when nothing changed.

> >  
> > +	/* 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));
>
> nit: Maybe move that under the "learning local IPv*" blocks to avoid
> the sizeof ugliness here?
>

Yes, agreed. I'll move the copy into each local-endpoint learning block.

-- 
Ralf Lici
Mandelbit Srl
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.