summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-09-24 09:50:49 -0700
committerJakub Kicinski <kuba@kernel.org>2026-09-24 09:50:49 -0700
commit33a61f09232bbbc5db9ded09bf85f4b9b5744c04 (patch)
tree2d3503172121f63ef09798d7f62ea7c8400f55aa /drivers
parent3173cba1170131816972ed2b6185cb970ba8b747 (diff)
parent006208026819d5e9e5ec07b3e73d95960a059327 (diff)
downloadlinux-33a61f09232bbbc5db9ded09bf85f4b9b5744c04.tar.gz
linux-33a61f09232bbbc5db9ded09bf85f4b9b5744c04.zip
Merge tag 'ovpn-net-20260921' of https://github.com/OpenVPN/ovpn-net-next
Antonio Quartulli says: ==================== Included fixes: * add selftest coverage for peer VPN address validation * reject multicast, broadcast and loopback peer VPN addresses, which can never identify a peer * reject MP peers left with no usable VPN address, as they can never be selected for TX * reject duplicate peer VPN addresses, which made peer lookup return an arbitrary peer * fix stale entry left in the VPN address hashtable when an address is cleared * fix torn IPv6 address read on lockless TX when the unusable local source is cleared in place * fix torn IPv6 address read on lockless TX when a new local endpoint is learned in place * fix dst cache being populated with a route resolved from an already replaced bind * fix stale route being reused after the socket mark or UDP source port changed * fix bogus validation of an unspecified local source address, which must instead be left to route source autoselection * fix IPv6 link-local peer endpoints losing their scope id when configured via netlink, breaking route lookup * tag 'ovpn-net-20260921' of https://github.com/OpenVPN/ovpn-net-next: selftests: ovpn: validate peer VPN addresses ovpn: reject invalid peer VPN addresses ovpn: reject multipeer peers without VPN addresses ovpn: reject duplicate peer VPN addresses ovpn: always unhash old VPN addresses before rehashing ovpn: replace bind when clearing stale local source ovpn: replace bind when learning local endpoint ovpn: validate peer state before caching UDP dst ovpn: track UDP socket route key for peer dst cache ovpn: skip UDP source validation for unspecified addresses ovpn: preserve IPv6 scope id for netlink peer endpoints ==================== Link: https://patch.msgid.link/20260921102215.3599702-1-antonio@openvpn.net Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/net/ovpn/netlink.c108
-rw-r--r--drivers/net/ovpn/peer.c121
-rw-r--r--drivers/net/ovpn/peer.h25
-rw-r--r--drivers/net/ovpn/udp.c193
4 files changed, 370 insertions, 77 deletions
diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
index 4dad85294198..5432bc2eb8e8 100644
--- a/drivers/net/ovpn/netlink.c
+++ b/drivers/net/ovpn/netlink.c
@@ -100,6 +100,8 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs,
struct sockaddr_in6 *sin6;
struct sockaddr_in *sin;
struct in6_addr *in6;
+ struct nlattr *scope;
+ u32 scope_id = 0;
__be16 port = 0;
__be32 *in;
@@ -114,6 +116,9 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs,
} else if (attrs[OVPN_A_PEER_REMOTE_IPV6]) {
ss->ss_family = AF_INET6;
in6 = nla_data(attrs[OVPN_A_PEER_REMOTE_IPV6]);
+ scope = attrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID];
+ if (scope)
+ scope_id = nla_get_u32(scope);
} else {
return false;
}
@@ -126,6 +131,7 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs,
if (!ipv6_addr_v4mapped(in6)) {
sin6 = (struct sockaddr_in6 *)ss;
sin6->sin6_port = port;
+ sin6->sin6_scope_id = scope_id;
memcpy(&sin6->sin6_addr, in6, sizeof(*in6));
break;
}
@@ -179,6 +185,39 @@ static sa_family_t ovpn_nl_family_get(struct nlattr *addr4,
return AF_UNSPEC;
}
+static int ovpn_nl_peer_check_vpn_addrs(const struct in_addr *addr4,
+ const struct in6_addr *addr6,
+ struct genl_info *info)
+{
+ int addr6_type;
+
+ if (addr4->s_addr == htonl(INADDR_ANY) && ipv6_addr_any(addr6)) {
+ NL_SET_ERR_MSG_MOD(info->extack,
+ "at least one VPN IP must be configured in MP mode");
+ return -EINVAL;
+ }
+
+ if (ipv4_is_multicast(addr4->s_addr) || ipv4_is_lbcast(addr4->s_addr) ||
+ ipv4_is_loopback(addr4->s_addr)) {
+ NL_SET_ERR_MSG_MOD(info->extack,
+ "VPN IPv4 address must be valid unicast or any");
+ return -EADDRNOTAVAIL;
+ }
+
+ if (!ipv6_addr_any(addr6)) {
+ addr6_type = ipv6_addr_type(addr6);
+
+ if (!(addr6_type & IPV6_ADDR_UNICAST) ||
+ (addr6_type & (IPV6_ADDR_LOOPBACK | IPV6_ADDR_COMPATv4))) {
+ NL_SET_ERR_MSG_MOD(info->extack,
+ "VPN IPv6 address must be valid unicast or any");
+ return -EADDRNOTAVAIL;
+ }
+ }
+
+ return 0;
+}
+
static int ovpn_nl_peer_precheck(struct ovpn_priv *ovpn,
struct genl_info *info,
struct nlattr **attrs)
@@ -346,8 +385,10 @@ err_unlock:
int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info)
{
- struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
+ struct in_addr vpn_addr4 = { .s_addr = htonl(INADDR_ANY) };
+ struct in6_addr vpn_addr6 = IN6ADDR_ANY_INIT;
struct ovpn_priv *ovpn = info->user_ptr[0];
+ struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
struct ovpn_socket *ovpn_sock;
struct socket *sock = NULL;
struct ovpn_peer *peer;
@@ -371,11 +412,18 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info)
return -EINVAL;
/* in MP mode VPN IPs are required for selecting the right peer */
- if (ovpn->mode == OVPN_MODE_MP && !attrs[OVPN_A_PEER_VPN_IPV4] &&
- !attrs[OVPN_A_PEER_VPN_IPV6]) {
- NL_SET_ERR_MSG_FMT_MOD(info->extack,
- "VPN IP must be provided in MP mode");
- return -EINVAL;
+ if (ovpn->mode == OVPN_MODE_MP) {
+ if (attrs[OVPN_A_PEER_VPN_IPV4])
+ vpn_addr4.s_addr =
+ nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]);
+ if (attrs[OVPN_A_PEER_VPN_IPV6])
+ vpn_addr6 =
+ nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]);
+
+ ret = ovpn_nl_peer_check_vpn_addrs(&vpn_addr4, &vpn_addr6,
+ info);
+ if (ret < 0)
+ return ret;
}
peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]);
@@ -474,8 +522,10 @@ peer_release:
int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info)
{
- struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
struct ovpn_priv *ovpn = info->user_ptr[0];
+ struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
+ struct in6_addr vpn_addr6;
+ struct in_addr vpn_addr4;
struct ovpn_socket *sock;
struct ovpn_peer *peer;
u32 peer_id;
@@ -522,28 +572,58 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info)
rcu_read_unlock();
spin_lock_bh(&ovpn->lock);
- ret = ovpn_nl_peer_modify(peer, info, attrs);
- if (ret < 0) {
- spin_unlock_bh(&ovpn->lock);
- ovpn_peer_put(peer);
- return ret;
+
+ vpn_addr4 = peer->vpn_addrs.ipv4;
+ vpn_addr6 = peer->vpn_addrs.ipv6;
+
+ /* reject peer with conflicting VPN address */
+ if (attrs[OVPN_A_PEER_VPN_IPV4]) {
+ vpn_addr4.s_addr = nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]);
+ if (ovpn_peer_vpn_addr_conflict4(ovpn, peer, &vpn_addr4))
+ goto addr_conflict;
+ }
+ if (attrs[OVPN_A_PEER_VPN_IPV6]) {
+ vpn_addr6 = nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]);
+ if (ovpn_peer_vpn_addr_conflict6(ovpn, peer, &vpn_addr6))
+ goto addr_conflict;
}
+ /* in MP mode VPN IPs are required for selecting the right peer */
+ if (ovpn->mode == OVPN_MODE_MP) {
+ ret = ovpn_nl_peer_check_vpn_addrs(&vpn_addr4, &vpn_addr6,
+ info);
+ if (ret < 0)
+ goto unlock;
+ }
+
+ ret = ovpn_nl_peer_modify(peer, info, attrs);
+ if (ret < 0)
+ goto unlock;
+
/* ret == 1 means that VPN IPv4/6 has been modified and rehashing
* is required
*/
- if (ret > 0)
+ if (ret > 0) {
ovpn_peer_hash_vpn_ip(peer);
+ ret = 0;
+ }
/* if the remote endpoint was updated, the by_transp_addr hash bucket
* also needs to be refreshed, otherwise incoming packets from the new
* remote address would fail the lockless lookup
*/
if (attrs[OVPN_A_PEER_REMOTE_IPV4] || attrs[OVPN_A_PEER_REMOTE_IPV6])
ovpn_peer_hash_transp_addr(peer);
+
+unlock:
spin_unlock_bh(&ovpn->lock);
ovpn_peer_put(peer);
- return 0;
+ return ret;
+addr_conflict:
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "VPN IP is already assigned to another peer");
+ ret = -EADDRINUSE;
+ goto unlock;
}
static int ovpn_nl_send_peer(struct sk_buff *skb, const struct genl_info *info,
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index c95656ca7c35..2067825bb5b6 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -113,6 +113,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id)
RCU_INIT_POINTER(peer->bind, NULL);
ovpn_crypto_state_init(&peer->crypto);
spin_lock_init(&peer->lock);
+ seqcount_spinlock_init(&peer->route_key_seq, &peer->lock);
kref_init(&peer->refcount);
ovpn_peer_stats_init(&peer->vpn_stats);
ovpn_peer_stats_init(&peer->link_stats);
@@ -199,13 +200,12 @@ static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer,
*/
void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb)
{
+ 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;
+ bool floated = false;
spin_lock_bh(&peer->lock);
bind = rcu_dereference_protected(peer->bind,
@@ -232,8 +232,7 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb)
.sin_addr.s_addr = ip_hdr(skb)->saddr,
.sin_port = udp_hdr(skb)->source,
};
- salen = sizeof(*sa);
- reset_cache = true;
+ floated = true;
break;
}
@@ -245,10 +244,12 @@ 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;
+ memcpy(&ss, &bind->remote, sizeof(struct sockaddr_in));
+ break;
}
- break;
+ /* nothing changed */
+ goto unlock;
case htons(ETH_P_IPV6):
/* float check */
if (unlikely(!ovpn_bind_skb_src_match(bind, skb))) {
@@ -270,8 +271,7 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb)
ipv6_iface_scope_id(&ipv6_hdr(skb)->saddr,
skb->skb_iif),
};
- salen = sizeof(*sa6);
- reset_cache = true;
+ floated = true;
break;
}
@@ -284,26 +284,30 @@ 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;
+ memcpy(&ss, &bind->remote, sizeof(struct sockaddr_in6));
+ break;
}
- break;
+ /* nothing changed */
+ goto unlock;
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))
- goto unlock;
-
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 only the local address changed, bail out now */
+ if (!floated)
+ goto unlock;
+
net_dbg_ratelimited("%s: peer %d floated to %pIScp",
netdev_name(peer->ovpn->dev), peer->id, &ss);
@@ -484,7 +488,7 @@ begin:
* Return: the peer if found or NULL otherwise
*/
static struct ovpn_peer *ovpn_peer_get_by_vpn_addr6(struct ovpn_priv *ovpn,
- struct in6_addr *addr)
+ const struct in6_addr *addr)
{
struct hlist_nulls_head *nhead;
struct hlist_nulls_node *ntmp;
@@ -510,6 +514,64 @@ begin:
}
/**
+ * ovpn_peer_vpn_addr_conflict4 - check if the VPN v4 address is already in use
+ * @ovpn: the openvpn instance to search
+ * @peer: peer being added or updated, or NULL
+ * @addr: VPN IPv4 address to check
+ *
+ * Check whether @addr is already assigned to another peer. @peer is ignored
+ * when found, allowing peer updates that keep an existing address.
+ * Unspecified addresses are ignored.
+ *
+ * Note: the caller must hold @ovpn->lock.
+ *
+ * Return: true on conflict, false otherwise.
+ */
+bool ovpn_peer_vpn_addr_conflict4(struct ovpn_priv *ovpn,
+ const struct ovpn_peer *peer,
+ const struct in_addr *addr)
+{
+ struct ovpn_peer *tmp = NULL;
+
+ lockdep_assert_held(&ovpn->lock);
+
+ /* we don't hash INADDR_ANY, no conflict in that case */
+ if (addr->s_addr != htonl(INADDR_ANY))
+ tmp = ovpn_peer_get_by_vpn_addr4(ovpn, addr->s_addr);
+
+ return tmp && tmp != peer;
+}
+
+/**
+ * ovpn_peer_vpn_addr_conflict6 - check if the VPN v6 address is already in use
+ * @ovpn: the openvpn instance to search
+ * @peer: peer being added or updated, or NULL
+ * @addr: VPN IPv6 address to check
+ *
+ * Check whether @addr is already assigned to another peer. @peer is ignored
+ * when found, allowing peer updates that keep an existing address.
+ * Unspecified addresses are ignored.
+ *
+ * Note: the caller must hold @ovpn->lock.
+ *
+ * Return: true on conflict, false otherwise.
+ */
+bool ovpn_peer_vpn_addr_conflict6(struct ovpn_priv *ovpn,
+ const struct ovpn_peer *peer,
+ const struct in6_addr *addr)
+{
+ struct ovpn_peer *tmp = NULL;
+
+ lockdep_assert_held(&ovpn->lock);
+
+ /* we don't hash ::, no conflict in that case */
+ if (!ipv6_addr_any(addr))
+ tmp = ovpn_peer_get_by_vpn_addr6(ovpn, addr);
+
+ return tmp && tmp != peer;
+}
+
+/**
* ovpn_peer_transp_match - check if sockaddr and peer binding match
* @peer: the peer to get the binding from
* @ss: the sockaddr to match
@@ -990,10 +1052,11 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer)
if (hlist_unhashed(&peer->hash_entry_id))
return;
- if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) {
- /* remove potential old hashing */
- hlist_nulls_del_init_rcu(&peer->hash_entry_addr4);
+ /* remove potential old hashing */
+ hlist_nulls_del_init_rcu(&peer->hash_entry_addr4);
+ hlist_nulls_del_init_rcu(&peer->hash_entry_addr6);
+ if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) {
nhead = ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr4,
&peer->vpn_addrs.ipv4,
sizeof(peer->vpn_addrs.ipv4));
@@ -1001,9 +1064,6 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer)
}
if (!ipv6_addr_any(&peer->vpn_addrs.ipv6)) {
- /* remove potential old hashing */
- hlist_nulls_del_init_rcu(&peer->hash_entry_addr6);
-
nhead = ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr6,
&peer->vpn_addrs.ipv6,
sizeof(peer->vpn_addrs.ipv6));
@@ -1038,6 +1098,13 @@ static int ovpn_peer_add_mp(struct ovpn_priv *ovpn, struct ovpn_peer *peer)
goto out;
}
+ /* reject peer with conflicting VPN address */
+ if (ovpn_peer_vpn_addr_conflict4(ovpn, NULL, &peer->vpn_addrs.ipv4) ||
+ ovpn_peer_vpn_addr_conflict6(ovpn, NULL, &peer->vpn_addrs.ipv6)) {
+ ret = -EADDRINUSE;
+ goto out;
+ }
+
bind = rcu_dereference_protected(peer->bind, true);
/* peers connected via TCP have bind == NULL */
if (bind) {
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index dfa5c0037e02..1879bfb76992 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -10,6 +10,7 @@
#ifndef _NET_OVPN_OVPNPEER_H_
#define _NET_OVPN_OVPNPEER_H_
+#include <linux/seqlock.h>
#include <net/dst_cache.h>
#include <net/strparser.h>
@@ -18,6 +19,16 @@
#include "stats.h"
/**
+ * struct ovpn_route_key - route key used for the peer dst cache
+ * @mark: fwmark used for route lookup
+ * @sport: UDP source port used for route lookup
+ */
+struct ovpn_route_key {
+ u32 mark;
+ __be16 sport;
+};
+
+/**
* struct ovpn_peer - the main remote peer object
* @ovpn: main openvpn instance this peer belongs to
* @dev_tracker: reference tracker for associated dev
@@ -45,6 +56,8 @@
* @tcp.sk_cb.ops: pointer to the original prot_ops object (TCP only)
* @crypto: the crypto configuration (ciphers, keys, etc..)
* @dst_cache: cache for dst_entry used to send to peer
+ * @route_key: route key matching the current dst cache contents
+ * @route_key_seq: seqcount protecting lockless route_key reads
* @bind: remote peer binding
* @keepalive_interval: seconds after which a new keepalive should be sent
* @keepalive_xmit_exp: future timestamp when next keepalive should be sent
@@ -55,7 +68,7 @@
* @vpn_stats: per-peer in-VPN TX/RX stats
* @link_stats: per-peer link/transport TX/RX stats
* @delete_reason: why peer was deleted (i.e. timeout, transport error, ..)
- * @lock: protects binding to peer (bind) and keepalive* fields
+ * @lock: protects binding to peer (bind), route_key and keepalive* fields
* @refcount: reference counter
* @rcu: used to free peer in an RCU safe way
* @release_entry: entry for the socket release list
@@ -99,6 +112,8 @@ struct ovpn_peer {
} tcp;
struct ovpn_crypto_state crypto;
struct dst_cache dst_cache;
+ struct ovpn_route_key route_key;
+ seqcount_spinlock_t route_key_seq;
struct ovpn_bind __rcu *bind;
unsigned long keepalive_interval;
unsigned long keepalive_xmit_exp;
@@ -109,7 +124,7 @@ struct ovpn_peer {
struct ovpn_peer_stats vpn_stats;
struct ovpn_peer_stats link_stats;
enum ovpn_del_peer_reason delete_reason;
- spinlock_t lock; /* protects bind and keepalive* */
+ spinlock_t lock; /* protects bind, route_key and keepalive* */
struct kref refcount;
struct rcu_head rcu;
struct llist_node release_entry;
@@ -149,6 +164,12 @@ struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn,
struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id);
struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn,
struct sk_buff *skb);
+bool ovpn_peer_vpn_addr_conflict4(struct ovpn_priv *ovpn,
+ const struct ovpn_peer *peer,
+ const struct in_addr *addr);
+bool ovpn_peer_vpn_addr_conflict6(struct ovpn_priv *ovpn,
+ const struct ovpn_peer *peer,
+ const struct in6_addr *addr);
void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer);
void ovpn_peer_hash_transp_addr(struct ovpn_peer *peer);
bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb,
diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c
index 7f69e8890b5b..055cdb1bee13 100644
--- a/drivers/net/ovpn/udp.c
+++ b/drivers/net/ovpn/udp.c
@@ -131,6 +131,77 @@ drop_noovpn:
return 0;
}
+static bool ovpn_route_key_equal(const struct ovpn_route_key *a,
+ const struct ovpn_route_key *b)
+{
+ return a->mark == b->mark && a->sport == b->sport;
+}
+
+/**
+ * ovpn_dst_cache_check_key - reset peer dst cache after key changes
+ * @peer: the peer owning the dst cache
+ * @cache: the cache that might need to be reset
+ * @key: the route key for the packet being transmitted
+ *
+ * Reset the peer dst cache if it was populated for a different route key.
+ */
+static void ovpn_dst_cache_check_key(struct ovpn_peer *peer,
+ struct dst_cache *cache,
+ const struct ovpn_route_key *key)
+{
+ struct ovpn_route_key old_key;
+ unsigned int seq;
+
+ /* snapshot the saved key before deciding whether the cache matches */
+ do {
+ seq = read_seqcount_begin(&peer->route_key_seq);
+ old_key = peer->route_key;
+ } while (read_seqcount_retry(&peer->route_key_seq, seq));
+
+ /* nothing changed: the current cache can be reused */
+ if (likely(ovpn_route_key_equal(&old_key, key)))
+ return;
+
+ /* recheck under lock because another path may have updated the key */
+ spin_lock_bh(&peer->lock);
+ if (!ovpn_route_key_equal(&peer->route_key, key)) {
+ write_seqcount_begin(&peer->route_key_seq);
+ peer->route_key = *key;
+ dst_cache_reset(cache);
+ write_seqcount_end(&peer->route_key_seq);
+ }
+ 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
@@ -138,21 +209,26 @@ drop_noovpn:
* @cache: dst cache
* @sk: the socket to send the packet over
* @skb: the packet to send
+ * @key: the route key snapshot used for cache validation and flow lookup
*
* Return: 0 on success or a negative error code otherwise
*/
static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
struct dst_cache *cache, struct sock *sk,
- struct sk_buff *skb)
+ 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,
.daddr = bind->remote.in4.sin_addr.s_addr,
- .fl4_sport = inet_sk(sk)->inet_sport,
+ .fl4_sport = key->sport,
.fl4_dport = bind->remote.in4.sin_port,
.flowi4_proto = sk->sk_protocol,
- .flowi4_mark = sk->sk_mark,
+ .flowi4_mark = key->mark,
};
int ret;
@@ -161,26 +237,19 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
if (rt)
goto transmit;
- if (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
+ if (fl.saddr && unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0,
+ fl.saddr, RT_SCOPE_HOST))) {
+ /* 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);
}
@@ -193,7 +262,30 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
ret);
goto err;
}
- dst_cache_set_ip4(cache, &rt->dst, fl.saddr);
+
+ /* avoid storing a stale cache or local address */
+ spin_lock_bh(&peer->lock);
+ 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:
udp_tunnel_xmit_skb(rt, sk, skb, fl.saddr, fl.daddr, 0,
@@ -213,23 +305,28 @@ err:
* @cache: dst cache
* @sk: the socket to send the packet over
* @skb: the packet to send
+ * @key: the route key snapshot used for cache validation and flow lookup
*
* Return: 0 on success or a negative error code otherwise
*/
static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
struct dst_cache *cache, struct sock *sk,
- struct sk_buff *skb)
+ 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;
struct flowi6 fl = {
.saddr = bind->local.ipv6,
.daddr = bind->remote.in6.sin6_addr,
- .fl6_sport = inet_sk(sk)->inet_sport,
+ .fl6_sport = key->sport,
.fl6_dport = bind->remote.in6.sin6_port,
.flowi6_proto = sk->sk_protocol,
- .flowi6_mark = sk->sk_mark,
+ .flowi6_mark = key->mark,
.flowi6_oif = bind->remote.in6.sin6_scope_id,
};
@@ -238,16 +335,13 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
if (dst)
goto transmit;
- if (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
+ if (!ipv6_addr_any(&fl.saddr) &&
+ unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) {
+ /* 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);
@@ -258,7 +352,30 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
&bind->remote.in6, ret);
goto err;
}
- dst_cache_set_ip6(cache, dst, &fl.saddr);
+
+ /* avoid storing a stale cache or local address */
+ spin_lock_bh(&peer->lock);
+ 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:
/* user IPv6 packets may be larger than the transport interface
@@ -287,6 +404,7 @@ err:
* @cache: dst cache
* @sk: the socket to send the packet over
* @skb: the packet to send
+ * @key: route key snapshot used for cache validation and flow lookup
*
* rcu_read_lock should be held on entry.
* On return, the skb is consumed.
@@ -294,7 +412,8 @@ err:
* Return: 0 on success or a negative error code otherwise
*/
static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache,
- struct sock *sk, struct sk_buff *skb)
+ struct sock *sk, struct sk_buff *skb,
+ struct ovpn_route_key *key)
{
struct ovpn_bind *bind;
int ret;
@@ -314,11 +433,11 @@ static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache,
switch (bind->remote.in4.sin_family) {
case AF_INET:
- ret = ovpn_udp4_output(peer, bind, cache, sk, skb);
+ ret = ovpn_udp4_output(peer, bind, cache, sk, skb, key);
break;
#if IS_ENABLED(CONFIG_IPV6)
case AF_INET6:
- ret = ovpn_udp6_output(peer, bind, cache, sk, skb);
+ ret = ovpn_udp6_output(peer, bind, cache, sk, skb, key);
break;
#endif
default:
@@ -340,15 +459,21 @@ out:
void ovpn_udp_send_skb(struct ovpn_peer *peer, struct sock *sk,
struct sk_buff *skb)
{
+ struct ovpn_route_key key = {
+ .mark = READ_ONCE(sk->sk_mark),
+ .sport = READ_ONCE(inet_sk(sk)->inet_sport),
+ };
int ret;
skb->dev = peer->ovpn->dev;
- skb->mark = READ_ONCE(sk->sk_mark);
+ skb->mark = key.mark;
/* no checksum performed at this layer */
skb->ip_summed = CHECKSUM_NONE;
+ ovpn_dst_cache_check_key(peer, &peer->dst_cache, &key);
+
/* crypto layer -> transport (UDP) */
- ret = ovpn_udp_output(peer, &peer->dst_cache, sk, skb);
+ ret = ovpn_udp_output(peer, &peer->dst_cache, sk, skb, &key);
if (unlikely(ret < 0))
kfree_skb(skb);
}