summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntonio Quartulli <antonio@openvpn.net>2026-07-28 13:48:52 +0200
committerAntonio Quartulli <antonio@openvpn.net>2026-07-30 11:28:30 +0200
commitb47a52dcd598a50207a33df304acdf45348a690f (patch)
treeb3cb00a9064c2f677cddb0ba8a8fe4cdca4273a1
parent3f012bdbabe211ccbc0c50ea5a1dbc60f8af1532 (diff)
downloadlinux-b47a52dcd598a50207a33df304acdf45348a690f.tar.gz
linux-b47a52dcd598a50207a33df304acdf45348a690f.zip
ovpn: hash floated peer by transport identity only
The by_transp_addr table is keyed on the peer's remote transport address, but the float rehash hashed bind->remote directly, while the two other sites that touch the table build a clean key first: ovpn_peer_add_mp() and the lookup in ovpn_peer_get_by_transp_addr() both hash a sockaddr holding only family/address/port. For a link-local IPv6 peer, bind->remote carries sin6_scope_id (set from ipv6_iface_scope_id() when the endpoint is learned), and that field is folded into the jhash() over sizeof(struct sockaddr_in6). The lookup never sets sin6_scope_id, so after such a peer floats it is rehashed into a scope_id-dependent bucket that lookups (scope_id 0) never visit, making the peer unreachable through the by_transp_addr fallback. ovpn_peer_transp_match() only compares address and port, so the hash was keying on a field the match ignores. sin6_scope_id must stay in bind->remote because the TX path uses it as flowi6_oif, so it cannot just be cleared there. Instead build the hash key from family/address/port only, exactly like ovpn_peer_add_mp() and the lookup, so all three sites agree on the bucket. Fixes: f0281c1d3732 ("ovpn: add support for updating local or remote UDP endpoint") Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
-rw-r--r--drivers/net/ovpn/peer.c25
1 files changed, 21 insertions, 4 deletions
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 33fb0a75e600..eada414a9d92 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -904,7 +904,10 @@ bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb,
static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer,
const struct ovpn_bind *bind)
{
+ struct sockaddr_storage sa = {};
struct hlist_nulls_head *nhead;
+ struct sockaddr_in6 *sa6;
+ struct sockaddr_in *sa4;
size_t salen;
lockdep_assert_held(&peer->ovpn->lock);
@@ -920,12 +923,26 @@ static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer,
if (unlikely(hlist_unhashed(&peer->hash_entry_id)))
return;
+ /* Build the hash key from the transport identity only
+ * (family/address/port), matching ovpn_peer_add_mp() and the lookup
+ * in ovpn_peer_get_by_transp_addr(). Hashing bind->remote directly
+ * would fold in sin6_scope_id (set on the float path but never by the
+ * lookup), scattering the peer into a bucket lookups cannot reach.
+ */
switch (bind->remote.in4.sin_family) {
case AF_INET:
- salen = sizeof(struct sockaddr_in);
+ sa4 = (struct sockaddr_in *)&sa;
+ sa4->sin_family = AF_INET;
+ sa4->sin_addr.s_addr = bind->remote.in4.sin_addr.s_addr;
+ sa4->sin_port = bind->remote.in4.sin_port;
+ salen = sizeof(*sa4);
break;
case AF_INET6:
- salen = sizeof(struct sockaddr_in6);
+ sa6 = (struct sockaddr_in6 *)&sa;
+ sa6->sin6_family = AF_INET6;
+ sa6->sin6_addr = bind->remote.in6.sin6_addr;
+ sa6->sin6_port = bind->remote.in6.sin6_port;
+ salen = sizeof(*sa6);
break;
default:
return;
@@ -934,8 +951,8 @@ static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer,
/* remove old hashing (no-op if entry is not currently linked) */
hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr);
/* re-add with current transport address */
- nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr,
- &bind->remote, salen);
+ nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr, &sa,
+ salen);
hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead);
}