From nobody Mon Feb 9 22:20:20 2026 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4A83A22069F for ; Fri, 10 Jan 2025 22:26:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736547983; cv=none; b=PFplJOebYovX6O5RxKbMLVsN2OF2vpJaEWTs1ChI2c1u1K50fWZaSumrEVdajrKJ2aKMuDsItLFN357YD93ltR8V2VGmuzb8nODjFaO1/lwBSILCL0yPaQvGtTQvEHdMrJdi5wdcjVM84Nldiagp/lWIbJBE8TB4bDWL8iJXy3U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1736547983; c=relaxed/simple; bh=++uTZY6IrGG4jQRWpN0Bj2+vLApHFCeaSK0aYLEIoM8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=BGN3/r1L+sWKApka0Ih3Bw8TZcg14D4937PxMNLg5r9Zb9FScyOW9+SGE20u09GBr1d4RMK5u5ypkPz3XMUj8XQTRjLcs9Fo7cQXGr+Phj5WUYfpaNiJTIYWh4ruSlV5WKuFOT83MDz2Qn79uQKOCVFWvhd6rAtGrstuGuLxUy8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=JkYRQmbI; arc=none smtp.client-ip=209.85.128.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="JkYRQmbI" Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-43626213fffso22891305e9.1 for ; Fri, 10 Jan 2025 14:26:20 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1736547978; x=1737152778; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=MmCLrO4whOsLHYDZi/VRGEqwMgRUEdQZGP93iyQ6JTw=; b=JkYRQmbIJScmD9xPoeRPE/M926QZbR6hnXbqx7w1mUNLGH8AW6CE/6laDJm2UzQCVj 01B+Ahd3WCfDIYUdxw4CyUoZZ/gRASa4f8vZdq2Z/a+dwVzjyAa8C6ifxw4jLy4FXCy0 OmfKkqneRiLaHfeU/M2sQZvnEwaDTlsVeS4IcrkPO/7CipeJCYQsdknRL908cYlCF+cq WqNNE0B1z4rbrEvmvx4hYpDfBPqqwGiVAlBhUV4ConlgFaDadsWEA3HDA60o2mzlL3Ui v8d4eq8clSkttKtBsulCV4k087pgsVFXBKnqc0YpJtTtpMuPl6e5SqclRAth2mlL/J9o lYHw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1736547978; x=1737152778; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=MmCLrO4whOsLHYDZi/VRGEqwMgRUEdQZGP93iyQ6JTw=; b=o3uDSPfmvJnlcocqFgbaze6BgNOmBLOLKDqfs5SSgv8X8UmjEB1O6vcV+zAw4QQ9MT mjnQljENtfQZLYU2XYErBLGQZdQWKw8dJLyhU3sqlp1feIPVxqe9EBGoFQO6mX7EiqSm 0M50XPaA20LSCVXH8H+TrAmtR1fZ19x9ZoFIjOpM+IIwvxxkZvqqzGkiGsgfHl/yNWMo XuFWnOcJqK4SI6IspZsRtG+7WrvmCPrAuxYU0X5DEyHxm79GzP5g6VWrhSYZYj3PU502 +BQYBTkoUR3RGv7VmByG7XQDTjDNzo6ll8l36x+QpN1ZUAzPMYsO6tNQNtkgnJGEz3on +X/w== X-Forwarded-Encrypted: i=1; AJvYcCXsrR+EuyWKMQCuQR/NI1cE/ONhWlMuuVpGp5T9ywGX6hK83G3YCNKdrWnZ8Rg1rY820e5J3Yyf7+ljL5I=@vger.kernel.org X-Gm-Message-State: AOJu0YxILJeKkiEeMwkN3sm/2F6mdjhB+Ayox1VCLDbQyRKy2UwTCWmS 1zNbQqmbGISnRf6w2d9EBY4DRtQP5txp0OocfcDHMIWZPbgbAr2iCjivueoBSLE= X-Gm-Gg: ASbGnctK64Jtrfyt9CuEquGlSJJRJ0qrTcJZlwIiHNDruTbveFBN4ndwHs2W89DQJy8 gIB+XtRkwSNOHvD6VykKZnnwmHXs6yPSnSqpOi++32pWtYFNwf88riHmUend6de7YyvEov01WJu haUzXssVcAoENtt9TFhFbTvVXIyU5viOY1bFs+bX3WvmgfASZq0EFOEHhzU7jv8FvNrhg8+LgOI HHSfuVC2Ghgl+1vGNo8IflTxFyvTQi2rysOMPnm1415V2ev+78QFa81pnqDdkCAMzaT X-Google-Smtp-Source: AGHT+IFYKLram5+Vre556FVcGWRLH3z/fku7sQtByjxHAj8eUw3OrVRiEmA4UCw1YXDbnQGuefLtOg== X-Received: by 2002:a7b:cc05:0:b0:42c:baf1:4c7 with SMTP id 5b1f17b1804b1-436e881e844mr76097395e9.4.1736547978471; Fri, 10 Jan 2025 14:26:18 -0800 (PST) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:ef5f:9500:40ad:49a7]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-38a8e37d0fasm5704340f8f.19.2025.01.10.14.26.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 10 Jan 2025 14:26:18 -0800 (PST) From: Antonio Quartulli Date: Fri, 10 Jan 2025 23:26:21 +0100 Subject: [PATCH net-next v17 05/25] ovpn: introduce the ovpn_peer object Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250110-b4-ovpn-v17-5-47b2377e5613@openvpn.net> References: <20250110-b4-ovpn-v17-0-47b2377e5613@openvpn.net> In-Reply-To: <20250110-b4-ovpn-v17-0-47b2377e5613@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=22490; i=antonio@openvpn.net; h=from:subject:message-id; bh=++uTZY6IrGG4jQRWpN0Bj2+vLApHFCeaSK0aYLEIoM8=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBngZ6wzefXf4cFThrt8yejVDJ8EgDhF8CqjTMau djw04m+tv6JATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ4GesAAKCRALcOU6oDjV hwHbCACXDJfz5OiOMziVkdiPPS/L0WksRag0k0RozAO5R9LwPymIvB9s8oeLFYpdlOWLck4Ze42 kmDlyc2gF41LttmOzddPqlf3oEjHjxQ4sbpYuQ+I+98o0XI3LYTzocPQfpFa+tIJ6MCnL13H49d 5mwcQlHP1PmDUWaigMD18L20JQ8iUHtH2XzifEZ7BPARJKTN9zdWQYw6o7Ryn3Uz9piA2oeEnO6 +h9XJu4h6axzaYG3JHBCd3Uc8sKl1U899RQQu7sDnclQhBfXG7PRLOLxhrbPddZvfde5J7vbZhA W/PCkn5KsfWVBkBygqPVT9zbzCtBkuw0BeDHVIzVkd7zjdYW X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C An ovpn_peer object holds the whole status of a remote peer (regardless whether it is a server or a client). This includes status for crypto, tx/rx buffers, napi, etc. Only support for one peer is introduced (P2P mode). Multi peer support is introduced with a later patch. Along with the ovpn_peer, also the ovpn_bind object is introcued as the two are strictly related. An ovpn_bind object wraps a sockaddr representing the local coordinates being used to talk to a specific peer. Signed-off-by: Antonio Quartulli --- drivers/net/Kconfig | 1 + drivers/net/ovpn/Makefile | 2 + drivers/net/ovpn/bind.c | 58 +++++++ drivers/net/ovpn/bind.h | 101 +++++++++++ drivers/net/ovpn/main.c | 18 +- drivers/net/ovpn/ovpnstruct.h | 4 + drivers/net/ovpn/peer.c | 392 ++++++++++++++++++++++++++++++++++++++= ++++ drivers/net/ovpn/peer.h | 80 +++++++++ 8 files changed, 655 insertions(+), 1 deletion(-) diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 2ace5e27c37ed3bad2e0000775cd172cb6de3225..dfd1ad96230317c4118b63c9c98= d0a631f6cbb21 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -119,6 +119,7 @@ config OVPN tristate "OpenVPN data channel offload" depends on NET && INET depends on IPV6 || !IPV6 + select DST_CACHE help This module enhances the performance of the OpenVPN userspace software by offloading the data channel processing to kernelspace. diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 201dc001419f1d99ae95c0ee0f96e68f8a4eac16..ce13499b3e1775a7f2a9ce16c6c= b0aa088f93685 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -7,7 +7,9 @@ # Author: Antonio Quartulli =20 obj-$(CONFIG_OVPN) :=3D ovpn.o +ovpn-y +=3D bind.o ovpn-y +=3D main.o ovpn-y +=3D io.o ovpn-y +=3D netlink.o ovpn-y +=3D netlink-gen.o +ovpn-y +=3D peer.o diff --git a/drivers/net/ovpn/bind.c b/drivers/net/ovpn/bind.c new file mode 100644 index 0000000000000000000000000000000000000000..b4d2ccec2ceddf43bc445b489cc= 62a578ef0ad0a --- /dev/null +++ b/drivers/net/ovpn/bind.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2012-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include + +#include "ovpnstruct.h" +#include "bind.h" +#include "peer.h" + +/** + * ovpn_bind_from_sockaddr - retrieve binding matching sockaddr + * @ss: the sockaddr to match + * + * Return: the bind matching the passed sockaddr if found, NULL otherwise + */ +struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *s= s) +{ + struct ovpn_bind *bind; + size_t sa_len; + + if (ss->ss_family =3D=3D AF_INET) + sa_len =3D sizeof(struct sockaddr_in); + else if (ss->ss_family =3D=3D AF_INET6) + sa_len =3D sizeof(struct sockaddr_in6); + else + return ERR_PTR(-EAFNOSUPPORT); + + bind =3D kzalloc(sizeof(*bind), GFP_ATOMIC); + if (unlikely(!bind)) + return ERR_PTR(-ENOMEM); + + memcpy(&bind->remote, ss, sa_len); + + return bind; +} + +/** + * ovpn_bind_reset - assign new binding to peer + * @peer: the peer whose binding has to be replaced + * @new: the new bind to assign + */ +void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *new) +{ + struct ovpn_bind *old; + + spin_lock_bh(&peer->lock); + old =3D rcu_replace_pointer(peer->bind, new, true); + spin_unlock_bh(&peer->lock); + + kfree_rcu(old, rcu); +} diff --git a/drivers/net/ovpn/bind.h b/drivers/net/ovpn/bind.h new file mode 100644 index 0000000000000000000000000000000000000000..343e6055deef9223a11176f62b9= 4f6c8ed3eb18f --- /dev/null +++ b/drivers/net/ovpn/bind.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2012-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNBIND_H_ +#define _NET_OVPN_OVPNBIND_H_ + +#include +#include +#include +#include +#include +#include + +struct ovpn_peer; + +/** + * union ovpn_sockaddr - basic transport layer address + * @in4: IPv4 address + * @in6: IPv6 address + */ +union ovpn_sockaddr { + struct sockaddr_in in4; + struct sockaddr_in6 in6; +}; + +/** + * struct ovpn_bind - remote peer binding + * @remote: the remote peer sockaddress + * @local: local endpoint used to talk to the peer + * @local.ipv4: local IPv4 used to talk to the peer + * @local.ipv6: local IPv6 used to talk to the peer + * @rcu: used to schedule RCU cleanup job + */ +struct ovpn_bind { + union ovpn_sockaddr remote; /* remote sockaddr */ + + union { + struct in_addr ipv4; + struct in6_addr ipv6; + } local; + + struct rcu_head rcu; +}; + +/** + * ovpn_bind_skb_src_match - match packet source with binding + * @bind: the binding to match + * @skb: the packet to match + * + * Return: true if the packet source matches the remote peer sockaddr + * in the binding + */ +static inline bool ovpn_bind_skb_src_match(const struct ovpn_bind *bind, + const struct sk_buff *skb) +{ + const union ovpn_sockaddr *remote; + + if (unlikely(!bind)) + return false; + + remote =3D &bind->remote; + + switch (skb->protocol) { + case htons(ETH_P_IP): + if (unlikely(remote->in4.sin_family !=3D AF_INET)) + return false; + + if (unlikely(remote->in4.sin_addr.s_addr !=3D ip_hdr(skb)->saddr)) + return false; + + if (unlikely(remote->in4.sin_port !=3D udp_hdr(skb)->source)) + return false; + break; + case htons(ETH_P_IPV6): + if (unlikely(remote->in6.sin6_family !=3D AF_INET6)) + return false; + + if (unlikely(!ipv6_addr_equal(&remote->in6.sin6_addr, + &ipv6_hdr(skb)->saddr))) + return false; + + if (unlikely(remote->in6.sin6_port !=3D udp_hdr(skb)->source)) + return false; + break; + default: + return false; + } + + return true; +} + +struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *s= a); +void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *bind); + +#endif /* _NET_OVPN_OVPNBIND_H_ */ diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 97fcf284c06654a6581be592e45f77f0f78f566f..a2d953dccd2cd9372f486b6f354= 79fb0180760b8 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -19,8 +19,14 @@ #include "main.h" #include "netlink.h" #include "io.h" +#include "peer.h" #include "proto.h" =20 +static int ovpn_net_init(struct net_device *dev) +{ + return 0; +} + static int ovpn_net_open(struct net_device *dev) { struct ovpn_priv *ovpn =3D netdev_priv(dev); @@ -43,6 +49,7 @@ static int ovpn_net_stop(struct net_device *dev) } =20 static const struct net_device_ops ovpn_netdev_ops =3D { + .ndo_init =3D ovpn_net_init, .ndo_open =3D ovpn_net_open, .ndo_stop =3D ovpn_net_stop, .ndo_start_xmit =3D ovpn_net_xmit, @@ -115,6 +122,7 @@ static int ovpn_newlink(struct net *src_net, struct net= _device *dev, =20 ovpn->dev =3D dev; ovpn->mode =3D mode; + spin_lock_init(&ovpn->lock); =20 /* turn carrier explicitly off after registration, this way state is * clearly defined @@ -174,10 +182,18 @@ static int ovpn_netdev_notifier_call(struct notifier_= block *nb, =20 netif_carrier_off(dev); ovpn->registered =3D false; + + if (ovpn->mode =3D=3D OVPN_MODE_P2P) + ovpn_peer_release_p2p(ovpn, + OVPN_DEL_PEER_REASON_TEARDOWN); + break; + case NETDEV_DOWN: + if (ovpn->mode =3D=3D OVPN_MODE_P2P) + ovpn_peer_release_p2p(ovpn, + OVPN_DEL_PEER_REASON_ADMINDOWN); break; case NETDEV_POST_INIT: case NETDEV_GOING_DOWN: - case NETDEV_DOWN: case NETDEV_UP: case NETDEV_PRE_UP: break; diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h index 7dab340444c3dc211a501b788755e050ee571ab9..bcfbc5f59f92f5d666e27b42138= 2a714e45dd066 100644 --- a/drivers/net/ovpn/ovpnstruct.h +++ b/drivers/net/ovpn/ovpnstruct.h @@ -18,11 +18,15 @@ * @dev: the actual netdev representing the tunnel * @registered: whether dev is still registered with netdev or not * @mode: device operation mode (i.e. p2p, mp, ..) + * @lock: protect this object + * @peer: in P2P mode, this is the only remote peer */ struct ovpn_priv { struct net_device *dev; bool registered; enum ovpn_mode mode; + spinlock_t lock; /* protect writing to the ovpn_priv object */ + struct ovpn_peer __rcu *peer; }; =20 #endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c new file mode 100644 index 0000000000000000000000000000000000000000..cf55c3b4f4cfd9add0c8df543f9= 17cfa0563ebd2 --- /dev/null +++ b/drivers/net/ovpn/peer.c @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include + +#include "ovpnstruct.h" +#include "bind.h" +#include "io.h" +#include "main.h" +#include "netlink.h" +#include "peer.h" + +/** + * ovpn_peer_new - allocate and initialize a new peer object + * @ovpn: the openvpn instance inside which the peer should be created + * @id: the ID assigned to this peer + * + * Return: a pointer to the new peer on success or an error code otherwise + */ +struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id) +{ + struct ovpn_peer *peer; + int ret; + + /* alloc and init peer object */ + peer =3D kzalloc(sizeof(*peer), GFP_KERNEL); + if (!peer) + return ERR_PTR(-ENOMEM); + + peer->id =3D id; + peer->ovpn =3D ovpn; + + peer->vpn_addrs.ipv4.s_addr =3D htonl(INADDR_ANY); + peer->vpn_addrs.ipv6 =3D in6addr_any; + + RCU_INIT_POINTER(peer->bind, NULL); + spin_lock_init(&peer->lock); + kref_init(&peer->refcount); + + ret =3D dst_cache_init(&peer->dst_cache, GFP_KERNEL); + if (ret < 0) { + netdev_err(ovpn->dev, + "cannot initialize dst cache for peer %u\n", + peer->id); + kfree(peer); + return ERR_PTR(ret); + } + + netdev_hold(ovpn->dev, &peer->dev_tracker, GFP_KERNEL); + + return peer; +} + +/** + * ovpn_peer_release_rcu - RCU callback performing last peer release steps + * @head: RCU member of the ovpn_peer + */ +static void ovpn_peer_release_rcu(struct rcu_head *head) +{ + struct ovpn_peer *peer =3D container_of(head, struct ovpn_peer, rcu); + + /* this call will immediately free the dst_cache, therefore we + * perform it in the RCU callback, when all contexts are done + */ + dst_cache_destroy(&peer->dst_cache); + kfree(peer); +} + +/** + * ovpn_peer_release - release peer private members + * @peer: the peer to release + */ +static void ovpn_peer_release(struct ovpn_peer *peer) +{ + ovpn_bind_reset(peer, NULL); + netdev_put(peer->ovpn->dev, &peer->dev_tracker); + call_rcu(&peer->rcu, ovpn_peer_release_rcu); +} + +/** + * ovpn_peer_release_kref - callback for kref_put + * @kref: the kref object belonging to the peer + */ +void ovpn_peer_release_kref(struct kref *kref) +{ + struct ovpn_peer *peer =3D container_of(kref, struct ovpn_peer, refcount); + + ovpn_peer_release(peer); +} + +/** + * ovpn_peer_skb_to_sockaddr - fill sockaddr with skb source address + * @skb: the packet to extract data from + * @ss: the sockaddr to fill + * + * Return: sockaddr length on success or -1 otherwise + */ +static int ovpn_peer_skb_to_sockaddr(struct sk_buff *skb, + struct sockaddr_storage *ss) +{ + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; + + switch (skb->protocol) { + case htons(ETH_P_IP): + sa4 =3D (struct sockaddr_in *)ss; + sa4->sin_family =3D AF_INET; + sa4->sin_addr.s_addr =3D ip_hdr(skb)->saddr; + sa4->sin_port =3D udp_hdr(skb)->source; + return sizeof(*sa4); + case htons(ETH_P_IPV6): + sa6 =3D (struct sockaddr_in6 *)ss; + sa6->sin6_family =3D AF_INET6; + sa6->sin6_addr =3D ipv6_hdr(skb)->saddr; + sa6->sin6_port =3D udp_hdr(skb)->source; + return sizeof(*sa6); + } + + return -1; +} + +/** + * ovpn_peer_transp_match - check if sockaddr and peer binding match + * @peer: the peer to get the binding from + * @ss: the sockaddr to match + * + * Return: true if sockaddr and binding match or false otherwise + */ +static bool ovpn_peer_transp_match(const struct ovpn_peer *peer, + const struct sockaddr_storage *ss) +{ + struct ovpn_bind *bind =3D rcu_dereference(peer->bind); + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; + + if (unlikely(!bind)) + return false; + + if (ss->ss_family !=3D bind->remote.in4.sin_family) + return false; + + switch (ss->ss_family) { + case AF_INET: + sa4 =3D (struct sockaddr_in *)ss; + if (sa4->sin_addr.s_addr !=3D bind->remote.in4.sin_addr.s_addr) + return false; + if (sa4->sin_port !=3D bind->remote.in4.sin_port) + return false; + break; + case AF_INET6: + sa6 =3D (struct sockaddr_in6 *)ss; + if (!ipv6_addr_equal(&sa6->sin6_addr, + &bind->remote.in6.sin6_addr)) + return false; + if (sa6->sin6_port !=3D bind->remote.in6.sin6_port) + return false; + break; + default: + return false; + } + + return true; +} + +/** + * ovpn_peer_get_by_transp_addr_p2p - get peer by transport address in a P= 2P + * instance + * @ovpn: the openvpn instance to search + * @ss: the transport socket address + * + * Return: the peer if found or NULL otherwise + */ +static struct ovpn_peer * +ovpn_peer_get_by_transp_addr_p2p(struct ovpn_priv *ovpn, + struct sockaddr_storage *ss) +{ + struct ovpn_peer *tmp, *peer =3D NULL; + + rcu_read_lock(); + tmp =3D rcu_dereference(ovpn->peer); + if (likely(tmp && ovpn_peer_transp_match(tmp, ss) && + ovpn_peer_hold(tmp))) + peer =3D tmp; + rcu_read_unlock(); + + return peer; +} + +/** + * ovpn_peer_get_by_transp_addr - retrieve peer by transport address + * @ovpn: the openvpn instance to search + * @skb: the skb to retrieve the source transport address from + * + * Return: a pointer to the peer if found or NULL otherwise + */ +struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, + struct sk_buff *skb) +{ + struct ovpn_peer *peer =3D NULL; + struct sockaddr_storage ss =3D { 0 }; + + if (unlikely(!ovpn_peer_skb_to_sockaddr(skb, &ss))) + return NULL; + + if (ovpn->mode =3D=3D OVPN_MODE_P2P) + peer =3D ovpn_peer_get_by_transp_addr_p2p(ovpn, &ss); + + return peer; +} + +/** + * ovpn_peer_get_by_id_p2p - get peer by ID in a P2P instance + * @ovpn: the openvpn instance to search + * @peer_id: the ID of the peer to find + * + * Return: the peer if found or NULL otherwise + */ +static struct ovpn_peer *ovpn_peer_get_by_id_p2p(struct ovpn_priv *ovpn, + u32 peer_id) +{ + struct ovpn_peer *tmp, *peer =3D NULL; + + rcu_read_lock(); + tmp =3D rcu_dereference(ovpn->peer); + if (likely(tmp && tmp->id =3D=3D peer_id && ovpn_peer_hold(tmp))) + peer =3D tmp; + rcu_read_unlock(); + + return peer; +} + +/** + * ovpn_peer_get_by_id - retrieve peer by ID + * @ovpn: the openvpn instance to search + * @peer_id: the unique peer identifier to match + * + * Return: a pointer to the peer if found or NULL otherwise + */ +struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id) +{ + struct ovpn_peer *peer =3D NULL; + + if (ovpn->mode =3D=3D OVPN_MODE_P2P) + peer =3D ovpn_peer_get_by_id_p2p(ovpn, peer_id); + + return peer; +} + +static void ovpn_peer_remove_work(struct work_struct *work) +{ + struct ovpn_peer *peer =3D container_of(work, struct ovpn_peer, + remove_work); + + /* reference from ovpn->peer or hashtable dropped */ + ovpn_peer_put(peer); +} + +static void ovpn_peer_remove(struct ovpn_peer *peer, + enum ovpn_del_peer_reason reason) +{ + switch (peer->ovpn->mode) { + case OVPN_MODE_P2P: + RCU_INIT_POINTER(peer->ovpn->peer, NULL); + /* in P2P mode the carrier is switched off when the peer is + * deleted so that third party protocols can react accordingly + */ + netif_carrier_off(peer->ovpn->dev); + break; + default: + return; + } + + peer->delete_reason =3D reason; + + INIT_WORK(&peer->remove_work, ovpn_peer_remove_work); + schedule_work(&peer->remove_work); +} + +/** + * ovpn_peer_add_p2p - add peer to related tables in a P2P instance + * @ovpn: the instance to add the peer to + * @peer: the peer to add + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_peer_add_p2p(struct ovpn_priv *ovpn, struct ovpn_peer *pee= r) +{ + struct ovpn_peer *tmp; + + spin_lock_bh(&ovpn->lock); + /* in p2p mode it is possible to have a single peer only, therefore the + * old one is released and substituted by the new one + */ + tmp =3D rcu_dereference_protected(ovpn->peer, + lockdep_is_held(&ovpn->lock)); + if (tmp) + ovpn_peer_remove(tmp, OVPN_DEL_PEER_REASON_TEARDOWN); + + rcu_assign_pointer(ovpn->peer, peer); + /* in P2P mode the carrier is switched on when the peer is added */ + netif_carrier_on(ovpn->dev); + spin_unlock_bh(&ovpn->lock); + + return 0; +} + +/** + * ovpn_peer_add - add peer to the related tables + * @ovpn: the openvpn instance the peer belongs to + * @peer: the peer object to add + * + * Assume refcounter was increased by caller + * + * Return: 0 on success or a negative error code otherwise + */ +int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer) +{ + switch (ovpn->mode) { + case OVPN_MODE_P2P: + return ovpn_peer_add_p2p(ovpn, peer); + default: + return -EOPNOTSUPP; + } +} + +/** + * ovpn_peer_del_p2p - delete peer from related tables in a P2P instance + * @peer: the peer to delete + * @reason: reason why the peer was deleted (sent to userspace) + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_peer_del_p2p(struct ovpn_peer *peer, + enum ovpn_del_peer_reason reason) +{ + struct ovpn_peer *tmp; + + lockdep_assert_held(&peer->ovpn->lock); + + tmp =3D rcu_dereference_protected(peer->ovpn->peer, + lockdep_is_held(&peer->ovpn->lock)); + if (tmp !=3D peer) { + DEBUG_NET_WARN_ON_ONCE(1); + return -ENOENT; + } + + ovpn_peer_remove(peer, reason); + + return 0; +} + +/** + * ovpn_peer_release_p2p - release peer upon P2P device teardown + * @ovpn: the instance being torn down + * @reason: the reason for releasing the peer + */ +void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, + enum ovpn_del_peer_reason reason) +{ + struct ovpn_peer *tmp; + + spin_lock_bh(&ovpn->lock); + tmp =3D rcu_dereference_protected(ovpn->peer, + lockdep_is_held(&ovpn->lock)); + if (tmp) + ovpn_peer_del_p2p(tmp, reason); + spin_unlock_bh(&ovpn->lock); +} + +/** + * ovpn_peer_del - delete peer from related tables + * @peer: the peer object to delete + * @reason: reason for deleting peer (will be sent to userspace) + * + * Return: 0 on success or a negative error code otherwise + */ +int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason) +{ + switch (peer->ovpn->mode) { + case OVPN_MODE_P2P: + return ovpn_peer_del_p2p(peer, reason); + default: + return -EOPNOTSUPP; + } +} diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h new file mode 100644 index 0000000000000000000000000000000000000000..048f7c6fe5822e0d8a1e44102eb= 7f4bbd5f718ca --- /dev/null +++ b/drivers/net/ovpn/peer.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNPEER_H_ +#define _NET_OVPN_OVPNPEER_H_ + +#include + +/** + * struct ovpn_peer - the main remote peer object + * @ovpn: main openvpn instance this peer belongs to + * @dev_tracker: reference tracker for associated dev + * @id: unique identifier + * @vpn_addrs: IP addresses assigned over the tunnel + * @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel + * @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel + * @dst_cache: cache for dst_entry used to send to peer + * @bind: remote peer binding + * @delete_reason: why peer was deleted (i.e. timeout, transport error, ..) + * @lock: protects binding to peer (bind) + * @refcount: reference counter + * @rcu: used to free peer in an RCU safe way + * @remove_work: deferred release work, for code that may sleep + */ +struct ovpn_peer { + struct ovpn_priv *ovpn; + netdevice_tracker dev_tracker; + u32 id; + struct { + struct in_addr ipv4; + struct in6_addr ipv6; + } vpn_addrs; + struct dst_cache dst_cache; + struct ovpn_bind __rcu *bind; + enum ovpn_del_peer_reason delete_reason; + spinlock_t lock; /* protects bind */ + struct kref refcount; + struct rcu_head rcu; + struct work_struct remove_work; +}; + +/** + * ovpn_peer_hold - increase reference counter + * @peer: the peer whose counter should be increased + * + * Return: true if the counter was increased or false if it was zero alrea= dy + */ +static inline bool ovpn_peer_hold(struct ovpn_peer *peer) +{ + return kref_get_unless_zero(&peer->refcount); +} + +void ovpn_peer_release_kref(struct kref *kref); + +/** + * ovpn_peer_put - decrease reference counter + * @peer: the peer whose counter should be decreased + */ +static inline void ovpn_peer_put(struct ovpn_peer *peer) +{ + kref_put(&peer->refcount, ovpn_peer_release_kref); +} + +struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id); +int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer); +int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason= ); +void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, + enum ovpn_del_peer_reason reason); + +struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, + struct sk_buff *skb); +struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id); + +#endif /* _NET_OVPN_OVPNPEER_H_ */ --=20 2.45.2