mirror of
https://github.com/torvalds/linux.git
synced 2026-05-25 23:52:08 +02:00
ovpn: add support for updating local or remote UDP endpoint
In case of UDP links, the local or remote endpoint used to communicate with a given peer may change without a connection restart. Add support for learning the new address in case of change. Signed-off-by: Antonio Quartulli <antonio@openvpn.net> Link: https://patch.msgid.link/20250415-b4-ovpn-v26-17-577f6097b964@openvpn.net Reviewed-by: Sabrina Dubroca <sd@queasysnail.net> Tested-by: Oleksandr Natalenko <oleksandr@natalenko.name> Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
parent
3ecfd9349f
commit
f0281c1d37
|
|
@ -96,6 +96,7 @@ void ovpn_decrypt_post(void *data, int ret)
|
|||
struct ovpn_crypto_key_slot *ks;
|
||||
unsigned int payload_offset = 0;
|
||||
struct sk_buff *skb = data;
|
||||
struct ovpn_socket *sock;
|
||||
struct ovpn_peer *peer;
|
||||
__be16 proto;
|
||||
__be32 *pid;
|
||||
|
|
@ -131,6 +132,13 @@ void ovpn_decrypt_post(void *data, int ret)
|
|||
/* keep track of last received authenticated packet for keepalive */
|
||||
WRITE_ONCE(peer->last_recv, ktime_get_real_seconds());
|
||||
|
||||
rcu_read_lock();
|
||||
sock = rcu_dereference(peer->sock);
|
||||
if (sock && sock->sock->sk->sk_protocol == IPPROTO_UDP)
|
||||
/* check if this peer changed local or remote endpoint */
|
||||
ovpn_peer_endpoints_update(peer, skb);
|
||||
rcu_read_unlock();
|
||||
|
||||
/* point to encapsulated IP packet */
|
||||
__skb_pull(skb, payload_offset);
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,206 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id)
|
|||
return peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_reset_sockaddr - recreate binding for peer
|
||||
* @peer: peer to recreate the binding for
|
||||
* @ss: sockaddr to use as remote endpoint for the binding
|
||||
* @local_ip: local IP for the binding
|
||||
*
|
||||
* Return: 0 on success or a negative error code otherwise
|
||||
*/
|
||||
static int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer,
|
||||
const struct sockaddr_storage *ss,
|
||||
const void *local_ip)
|
||||
{
|
||||
struct ovpn_bind *bind;
|
||||
size_t ip_len;
|
||||
|
||||
lockdep_assert_held(&peer->lock);
|
||||
|
||||
/* create new ovpn_bind object */
|
||||
bind = ovpn_bind_from_sockaddr(ss);
|
||||
if (IS_ERR(bind))
|
||||
return PTR_ERR(bind);
|
||||
|
||||
if (ss->ss_family == AF_INET) {
|
||||
ip_len = sizeof(struct in_addr);
|
||||
} else if (ss->ss_family == AF_INET6) {
|
||||
ip_len = sizeof(struct in6_addr);
|
||||
} else {
|
||||
net_dbg_ratelimited("%s: invalid family %u for remote endpoint for peer %u\n",
|
||||
netdev_name(peer->ovpn->dev),
|
||||
ss->ss_family, peer->id);
|
||||
kfree(bind);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memcpy(&bind->local, local_ip, ip_len);
|
||||
|
||||
/* set binding */
|
||||
ovpn_bind_reset(peer, bind);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* variable name __tbl2 needs to be different from __tbl1
|
||||
* in the macro below to avoid confusing clang
|
||||
*/
|
||||
#define ovpn_get_hash_slot(_tbl, _key, _key_len) ({ \
|
||||
typeof(_tbl) *__tbl2 = &(_tbl); \
|
||||
jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl2); \
|
||||
})
|
||||
|
||||
#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \
|
||||
typeof(_tbl) *__tbl1 = &(_tbl); \
|
||||
&(*__tbl1)[ovpn_get_hash_slot(*__tbl1, _key, _key_len)];\
|
||||
})
|
||||
|
||||
/**
|
||||
* ovpn_peer_endpoints_update - update remote or local endpoint for peer
|
||||
* @peer: peer to update the remote endpoint for
|
||||
* @skb: incoming packet to retrieve the source/destination address from
|
||||
*/
|
||||
void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb)
|
||||
{
|
||||
struct hlist_nulls_head *nhead;
|
||||
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);
|
||||
bind = rcu_dereference_protected(peer->bind,
|
||||
lockdep_is_held(&peer->lock));
|
||||
if (unlikely(!bind))
|
||||
goto unlock;
|
||||
|
||||
switch (skb->protocol) {
|
||||
case htons(ETH_P_IP):
|
||||
/* float check */
|
||||
if (unlikely(!ovpn_bind_skb_src_match(bind, skb))) {
|
||||
/* unconditionally save local endpoint in case
|
||||
* of float, as it may have changed as well
|
||||
*/
|
||||
local_ip = &ip_hdr(skb)->daddr;
|
||||
sa = (struct sockaddr_in *)&ss;
|
||||
sa->sin_family = AF_INET;
|
||||
sa->sin_addr.s_addr = ip_hdr(skb)->saddr;
|
||||
sa->sin_port = udp_hdr(skb)->source;
|
||||
salen = sizeof(*sa);
|
||||
reset_cache = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* if no float happened, let's double check if the local endpoint
|
||||
* has changed
|
||||
*/
|
||||
if (unlikely(bind->local.ipv4.s_addr != ip_hdr(skb)->daddr)) {
|
||||
net_dbg_ratelimited("%s: learning local IPv4 for peer %d (%pI4 -> %pI4)\n",
|
||||
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;
|
||||
}
|
||||
break;
|
||||
case htons(ETH_P_IPV6):
|
||||
/* float check */
|
||||
if (unlikely(!ovpn_bind_skb_src_match(bind, skb))) {
|
||||
/* unconditionally save local endpoint in case
|
||||
* of float, as it may have changed as well
|
||||
*/
|
||||
local_ip = &ipv6_hdr(skb)->daddr;
|
||||
sa6 = (struct sockaddr_in6 *)&ss;
|
||||
sa6->sin6_family = AF_INET6;
|
||||
sa6->sin6_addr = ipv6_hdr(skb)->saddr;
|
||||
sa6->sin6_port = udp_hdr(skb)->source;
|
||||
sa6->sin6_scope_id = ipv6_iface_scope_id(&ipv6_hdr(skb)->saddr,
|
||||
skb->skb_iif);
|
||||
salen = sizeof(*sa6);
|
||||
reset_cache = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* if no float happened, let's double check if the local endpoint
|
||||
* has changed
|
||||
*/
|
||||
if (unlikely(!ipv6_addr_equal(&bind->local.ipv6,
|
||||
&ipv6_hdr(skb)->daddr))) {
|
||||
net_dbg_ratelimited("%s: learning local IPv6 for peer %d (%pI6c -> %pI6c\n",
|
||||
netdev_name(peer->ovpn->dev),
|
||||
peer->id, &bind->local.ipv6,
|
||||
&ipv6_hdr(skb)->daddr);
|
||||
bind->local.ipv6 = ipv6_hdr(skb)->daddr;
|
||||
reset_cache = true;
|
||||
}
|
||||
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))
|
||||
goto unlock;
|
||||
|
||||
if (unlikely(ovpn_peer_reset_sockaddr(peer,
|
||||
(struct sockaddr_storage *)&ss,
|
||||
local_ip) < 0))
|
||||
goto unlock;
|
||||
|
||||
net_dbg_ratelimited("%s: peer %d floated to %pIScp",
|
||||
netdev_name(peer->ovpn->dev), peer->id, &ss);
|
||||
|
||||
spin_unlock_bh(&peer->lock);
|
||||
|
||||
/* rehashing is required only in MP mode as P2P has one peer
|
||||
* only and thus there is no hashtable
|
||||
*/
|
||||
if (peer->ovpn->mode == OVPN_MODE_MP) {
|
||||
spin_lock_bh(&peer->ovpn->lock);
|
||||
spin_lock_bh(&peer->lock);
|
||||
bind = rcu_dereference_protected(peer->bind,
|
||||
lockdep_is_held(&peer->lock));
|
||||
if (unlikely(!bind)) {
|
||||
spin_unlock_bh(&peer->lock);
|
||||
spin_unlock_bh(&peer->ovpn->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
/* This function may be invoked concurrently, therefore another
|
||||
* float may have happened in parallel: perform rehashing
|
||||
* using the peer->bind->remote directly as key
|
||||
*/
|
||||
|
||||
switch (bind->remote.in4.sin_family) {
|
||||
case AF_INET:
|
||||
salen = sizeof(*sa);
|
||||
break;
|
||||
case AF_INET6:
|
||||
salen = sizeof(*sa6);
|
||||
break;
|
||||
}
|
||||
|
||||
/* remove old hashing */
|
||||
hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr);
|
||||
/* re-add with new transport address */
|
||||
nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr,
|
||||
&bind->remote, salen);
|
||||
hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead);
|
||||
spin_unlock_bh(&peer->lock);
|
||||
spin_unlock_bh(&peer->ovpn->lock);
|
||||
}
|
||||
return;
|
||||
unlock:
|
||||
spin_unlock_bh(&peer->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* ovpn_peer_release_rcu - RCU callback performing last peer release steps
|
||||
* @head: RCU member of the ovpn_peer
|
||||
|
|
@ -230,19 +430,6 @@ static struct in6_addr ovpn_nexthop_from_skb6(struct sk_buff *skb)
|
|||
return rt->rt6i_gateway;
|
||||
}
|
||||
|
||||
/* variable name __tbl2 needs to be different from __tbl1
|
||||
* in the macro below to avoid confusing clang
|
||||
*/
|
||||
#define ovpn_get_hash_slot(_tbl, _key, _key_len) ({ \
|
||||
typeof(_tbl) *__tbl2 = &(_tbl); \
|
||||
jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl2); \
|
||||
})
|
||||
|
||||
#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \
|
||||
typeof(_tbl) *__tbl1 = &(_tbl); \
|
||||
&(*__tbl1)[ovpn_get_hash_slot(*__tbl1, _key, _key_len)];\
|
||||
})
|
||||
|
||||
/**
|
||||
* ovpn_peer_get_by_vpn_addr4 - retrieve peer by its VPN IPv4 address
|
||||
* @ovpn: the openvpn instance to search
|
||||
|
|
|
|||
|
|
@ -153,4 +153,6 @@ bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb,
|
|||
void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout);
|
||||
void ovpn_peer_keepalive_work(struct work_struct *work);
|
||||
|
||||
void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb);
|
||||
|
||||
#endif /* _NET_OVPN_OVPNPEER_H_ */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user