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
 -----BEGIN PGP SIGNATURE-----
 
 iJEEABYIADkWIQQr0db7q+Rc7Zog28Fc8QQzwdnOtwUCarECxxsUgAAAAAAEAA5t
 YW51MiwyLjUrMS4xMiwyLDIACgkQXPEEM8HZzrcvTAEA/r3oFnZ4EOtiOyA2OpZ/
 Iya+WAJ7LShj7tLymzujMe8BAIH6J2RyrWXnfDFvtSG8HGDEe4EBzVpP56tX0ZVn
 xc0N
 =fg0G
 -----END PGP SIGNATURE-----

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>
This commit is contained in:
Jakub Kicinski 2026-09-24 09:50:49 -07:00
commit 33a61f0923
7 changed files with 493 additions and 96 deletions

View File

@ -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 @@ static int ovpn_nl_peer_modify(struct ovpn_peer *peer, struct genl_info *info,
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 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info)
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,

View File

@ -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 @@ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr4(struct ovpn_priv *ovpn,
* 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;
@ -509,6 +513,64 @@ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr6(struct ovpn_priv *ovpn,
return NULL;
}
/**
* 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
@ -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) {

View File

@ -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>
@ -17,6 +18,16 @@
#include "socket.h"
#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
@ -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,

View File

@ -131,6 +131,77 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
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 @@ static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
* @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 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
* @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 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
* @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 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind,
* 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 @@ static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache,
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);
}

View File

@ -136,6 +136,19 @@ ovpn_create_ns() {
ip netns add "ovpn_peer${1}"
}
ovpn_peer_vpn_addr() {
local peer="$1"
local file
if [ "${OVPN_PROTO}" == "UDP" ]; then
file="${OVPN_UDP_PEERS_FILE}"
else
file="${OVPN_TCP_PEERS_FILE}"
fi
awk -v peer="${peer}" '$1 == peer {print $NF; exit}' "${file}"
}
ovpn_setup_ns() {
local peer="ovpn_peer${1}"
local server_ns="ovpn_peer0"

View File

@ -650,6 +650,26 @@ static int ovpn_connect(struct ovpn_ctx *ovpn)
return ret;
}
static int ovpn_nl_put_vpn_addr(struct nl_msg *msg,
const struct ovpn_ctx *ovpn)
{
if (!ovpn->peer_ip_set)
return 0;
switch (ovpn->peer_ip.in4.sin_family) {
case AF_INET:
return nla_put_u32(msg, OVPN_A_PEER_VPN_IPV4,
ovpn->peer_ip.in4.sin_addr.s_addr);
case AF_INET6:
return nla_put(msg, OVPN_A_PEER_VPN_IPV6,
sizeof(struct in6_addr),
&ovpn->peer_ip.in6.sin6_addr);
default:
fprintf(stderr, "Invalid family for peer address\n");
return -EAFNOSUPPORT;
}
}
static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp)
{
struct nlattr *attr;
@ -691,22 +711,9 @@ static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp)
}
}
if (ovpn->peer_ip_set) {
switch (ovpn->peer_ip.in4.sin_family) {
case AF_INET:
NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_VPN_IPV4,
ovpn->peer_ip.in4.sin_addr.s_addr);
break;
case AF_INET6:
NLA_PUT(ctx->nl_msg, OVPN_A_PEER_VPN_IPV6,
sizeof(struct in6_addr),
&ovpn->peer_ip.in6.sin6_addr);
break;
default:
fprintf(stderr, "Invalid family for peer address\n");
goto nla_put_failure;
}
}
ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn);
if (ret)
goto nla_put_failure;
nla_nest_end(ctx->nl_msg, attr);
@ -732,6 +739,10 @@ static int ovpn_set_peer(struct ovpn_ctx *ovpn)
ovpn->keepalive_interval);
NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_TIMEOUT,
ovpn->keepalive_timeout);
ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn);
if (ret)
goto nla_put_failure;
nla_nest_end(ctx->nl_msg, attr);
ret = ovpn_nl_msg_send(ctx, NULL);
@ -1730,13 +1741,14 @@ static void usage(const char *cmd)
fprintf(stderr, "\tmark: socket FW mark value\n");
fprintf(stderr,
"* set_peer <iface> <peer_id> <keepalive_interval> <keepalive_timeout>: set peer attributes\n");
"* set_peer <iface> <peer_id> <keepalive_interval> <keepalive_timeout> [vpnaddr]: set peer attributes\n");
fprintf(stderr, "\tiface: ovpn interface name\n");
fprintf(stderr, "\tpeer_id: peer ID of the peer to modify\n");
fprintf(stderr,
"\tkeepalive_interval: interval for sending ping messages\n");
fprintf(stderr,
"\tkeepalive_timeout: time after which a peer is timed out\n");
fprintf(stderr, "\tvpnaddr: peer VPN IP\n");
fprintf(stderr, "* del_peer <iface> <peer_id>: delete peer\n");
fprintf(stderr, "\tiface: ovpn interface name\n");
@ -2090,6 +2102,8 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn)
return ret;
ret = ovpn_new_peer(ovpn, false);
if (ret < 0)
return ret;
ovpn_waitbg();
break;
case CMD_NEW_MULTI_PEER:
@ -2331,6 +2345,12 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[])
"keepalive interval value out of range\n");
return -1;
}
if (argc > 6) {
ret = ovpn_parse_remote(ovpn, NULL, NULL, argv[6]);
if (ret < 0)
return -1;
}
break;
case CMD_DEL_PEER:
if (argc < 4)

View File

@ -56,6 +56,76 @@ ovpn_prepare_network() {
done
}
ovpn_new_test_peer() {
local peer_id="$1"
shift
ip netns exec ovpn_peer0 "${OVPN_CLI}" new_peer tun0 \
"${peer_id}" none 65000 10.10.1.2 1 "$@"
}
ovpn_set_peer_vpn_addr() {
ip netns exec ovpn_peer0 "${OVPN_CLI}" set_peer tun0 \
"$1" 60 120 "$2"
}
ovpn_run_vpn_addr_validation() {
local addr
local peer1_addr4
local test_peer_id=$((OVPN_NUM_PEERS + 1))
local test_peer_addr6="2001:db8::2"
# Do not include 0.0.0.0 or :: here. They are invalid on creation, but
# clear one address family on update and are valid if the other remains.
local -a invalid_addrs=(
"127.0.0.1"
"224.0.0.1"
"255.255.255.255"
"::1"
"::192.0.2.1"
"::ffff:192.0.2.1"
"ff02::1"
)
peer1_addr4=$(ovpn_peer_vpn_addr 1)
ovpn_cmd_fail "reject peer without VPN address" \
ovpn_new_test_peer "${test_peer_id}"
for addr in "0.0.0.0" "::" "${invalid_addrs[@]}"; do
ovpn_cmd_fail "reject new peer VPN address ${addr}" \
ovpn_new_test_peer "${test_peer_id}" "${addr}"
done
ovpn_cmd_fail "reject duplicate IPv4 address on peer creation" \
ovpn_new_test_peer "${test_peer_id}" "${peer1_addr4}"
ovpn_cmd_fail "reject clearing the last peer VPN address" \
ovpn_set_peer_vpn_addr 1 0.0.0.0
for addr in "${invalid_addrs[@]}"; do
ovpn_cmd_fail "reject updated peer VPN address ${addr}" \
ovpn_set_peer_vpn_addr 1 "${addr}"
done
ovpn_cmd_fail "reject duplicate IPv4 address on peer update" \
ovpn_set_peer_vpn_addr 2 "${peer1_addr4}"
ovpn_cmd_ok "add peer IPv6 address" \
ovpn_set_peer_vpn_addr 1 "${test_peer_addr6}"
ovpn_cmd_fail "reject duplicate IPv6 address on peer creation" \
ovpn_new_test_peer "${test_peer_id}" "${test_peer_addr6}"
ovpn_cmd_fail "reject duplicate IPv6 address on peer update" \
ovpn_set_peer_vpn_addr 2 "${test_peer_addr6}"
ovpn_cmd_ok "clear peer IPv4 address" \
ovpn_set_peer_vpn_addr 1 0.0.0.0
ovpn_cmd_fail "reject clearing the remaining peer IPv6 address" \
ovpn_set_peer_vpn_addr 1 ::
ovpn_cmd_ok "restore peer IPv4 address" \
ovpn_set_peer_vpn_addr 1 "${peer1_addr4}"
ovpn_cmd_ok "clear peer IPv6 address" \
ovpn_set_peer_vpn_addr 1 ::
}
ovpn_run_basic_traffic() {
local p
local header1
@ -293,15 +363,16 @@ trap ovpn_stage_err ERR
ktap_print_header
if [ "${OVPN_FLOAT}" == "1" ]; then
ktap_set_plan 13
ktap_set_plan 14
else
ktap_set_plan 12
ktap_set_plan 13
fi
ovpn_cleanup
modprobe -q ovpn || true
ovpn_run_stage "setup network topology" ovpn_prepare_network
ovpn_run_stage "validate peer VPN addresses" ovpn_run_vpn_addr_validation
ovpn_run_stage "run baseline data traffic" ovpn_run_basic_traffic
ovpn_run_stage "run LAN traffic behind peer1" ovpn_run_lan_traffic
[ "${OVPN_FLOAT}" == "1" ] && ovpn_run_stage "run floating peer checks" \