[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20241202-b4-ovpn-v12-16-239ff733bf97@openvpn.net>
Date: Mon, 02 Dec 2024 16:07:34 +0100
From: Antonio Quartulli <antonio@...nvpn.net>
To: Eric Dumazet <edumazet@...gle.com>, Jakub Kicinski <kuba@...nel.org>,
Paolo Abeni <pabeni@...hat.com>, Donald Hunter <donald.hunter@...il.com>,
Antonio Quartulli <antonio@...nvpn.net>, Shuah Khan <shuah@...nel.org>,
donald.hunter@...il.com, sd@...asysnail.net, ryazanov.s.a@...il.com,
Andrew Lunn <andrew@...n.ch>
Cc: Simon Horman <horms@...nel.org>, netdev@...r.kernel.org,
linux-kernel@...r.kernel.org, linux-kselftest@...r.kernel.org
Subject: [PATCH net-next v12 16/22] ovpn: add support for peer floating
A peer connected via UDP may change its IP address without reconnecting
(float).
Add support for detecting and updating the new peer IP/port in case of
floating.
Signed-off-by: Antonio Quartulli <antonio@...nvpn.net>
---
drivers/net/ovpn/bind.c | 9 +-
drivers/net/ovpn/io.c | 4 +
drivers/net/ovpn/peer.c | 237 +++++++++++++++++++++++++++++++++++++-----------
drivers/net/ovpn/peer.h | 3 +-
4 files changed, 190 insertions(+), 63 deletions(-)
diff --git a/drivers/net/ovpn/bind.c b/drivers/net/ovpn/bind.c
index b4d2ccec2ceddf43bc445b489cc62a578ef0ad0a..c8ca340cca936a357409e9458807f27831511975 100644
--- a/drivers/net/ovpn/bind.c
+++ b/drivers/net/ovpn/bind.c
@@ -48,11 +48,8 @@ struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *ss)
*/
void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *new)
{
- struct ovpn_bind *old;
+ lockdep_assert_held(&peer->lock);
- spin_lock_bh(&peer->lock);
- old = rcu_replace_pointer(peer->bind, new, true);
- spin_unlock_bh(&peer->lock);
-
- kfree_rcu(old, rcu);
+ kfree_rcu(rcu_replace_pointer(peer->bind, new,
+ lockdep_is_held(&peer->lock)), rcu);
}
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index bad6c0782802a993e6da04ecc1629ffd3ec59f62..ee3bb650ed4700d8fa2c9a128b6d2318e1653f5c 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -135,6 +135,10 @@ 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());
+ if (peer->sock->sock->sk->sk_protocol == IPPROTO_UDP)
+ /* check if this peer changed local or remote endpoint */
+ ovpn_peer_endpoints_update(peer, skb);
+
/* point to encapsulated IP packet */
__skb_pull(skb, payload_offset);
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 7df9fedd593a74e2349922557ce299a0fcf03038..8c5643ca497f78282d6171a8ddbe9896cccef7ed 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -94,6 +94,188 @@ 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 u8 *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 (local_ip) {
+ 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;
+}
+
+#define ovpn_get_hash_slot(_key, _key_len, _tbl) ({ \
+ typeof(_tbl) *__tbl = &(_tbl); \
+ jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl); \
+})
+
+#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \
+ typeof(_tbl) *__tbl = &(_tbl); \
+ &(*__tbl)[ovpn_get_hash_slot(_key, _key_len, *__tbl)]; \
+})
+
+/**
+ * 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;
+ const u8 *local_ip = NULL;
+ struct sockaddr_in6 *sa6;
+ struct sockaddr_in *sa;
+ struct ovpn_bind *bind;
+ 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))) {
+ if (bind->remote.in4.sin_family == AF_INET)
+ local_ip = (u8 *)&bind->local;
+ 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);
+ break;
+ }
+
+ /* local endpoint update */
+ 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;
+ }
+ break;
+ case htons(ETH_P_IPV6):
+ /* float check */
+ if (unlikely(!ovpn_bind_skb_src_match(bind, skb))) {
+ if (bind->remote.in6.sin6_family == AF_INET6)
+ local_ip = (u8 *)&bind->local;
+ 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);
+ }
+
+ /* local endpoint update */
+ 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;
+ }
+ break;
+ default:
+ goto unlock;
+ }
+
+ /* 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;
+ }
+
+ /* his 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
@@ -197,16 +379,6 @@ static struct in6_addr ovpn_nexthop_from_skb6(struct sk_buff *skb)
return rt->rt6i_gateway;
}
-#define ovpn_get_hash_slot(_key, _key_len, _tbl) ({ \
- typeof(_tbl) *__tbl = &(_tbl); \
- jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl); \
-})
-
-#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \
- typeof(_tbl) *__tbl = &(_tbl); \
- &(*__tbl)[ovpn_get_hash_slot(_key, _key_len, *__tbl)]; \
-})
-
/**
* ovpn_peer_get_by_vpn_addr4 - retrieve peer by its VPN IPv4 address
* @ovpn: the openvpn instance to search
@@ -450,51 +622,6 @@ struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id)
return peer;
}
-/**
- * ovpn_peer_update_local_endpoint - update local endpoint for peer
- * @peer: peer to update the endpoint for
- * @skb: incoming packet to retrieve the destination address (local) from
- */
-void ovpn_peer_update_local_endpoint(struct ovpn_peer *peer,
- struct sk_buff *skb)
-{
- struct ovpn_bind *bind;
-
- rcu_read_lock();
- bind = rcu_dereference(peer->bind);
- if (unlikely(!bind))
- goto unlock;
-
- spin_lock_bh(&peer->lock);
- switch (skb->protocol) {
- case htons(ETH_P_IP):
- 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;
- }
- break;
- case htons(ETH_P_IPV6):
- 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;
- }
- break;
- default:
- break;
- }
- spin_unlock_bh(&peer->lock);
-
-unlock:
- rcu_read_unlock();
-}
-
/**
* ovpn_peer_get_by_dst - Lookup peer to send skb to
* @ovpn: the private data representing the current VPN session
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 196fa0a04f2db1986fffa6d51b42b68c9837c8e8..a95ec054a8c2224cc0a4ef3800d30e76138f1fe6 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -153,7 +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_update_local_endpoint(struct ovpn_peer *peer,
- struct sk_buff *skb);
+void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb);
#endif /* _NET_OVPN_OVPNPEER_H_ */
--
2.45.2
Powered by blists - more mailing lists