Commit f0281c1d authored by Antonio Quartulli's avatar Antonio Quartulli Committed by Paolo Abeni
Browse files

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: default avatarAntonio Quartulli <antonio@openvpn.net>
Link: https://patch.msgid.link/20250415-b4-ovpn-v26-17-577f6097b964@openvpn.net


Reviewed-by: default avatarSabrina Dubroca <sd@queasysnail.net>
Tested-by: default avatarOleksandr Natalenko <oleksandr@natalenko.name>
Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent 3ecfd934
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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);

+200 −13
Original line number Diff line number Diff line
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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_ */