diff mbox series

[net-next,v25,01/23] net: introduce OpenVPN Data Channel Offload (ovpn)

Message ID 20250407-b4-ovpn-v25-1-a04eae86e016@openvpn.net
State New
Headers show
Series Introducing OpenVPN Data Channel Offload | expand

Commit Message

Antonio Quartulli April 7, 2025, 7:46 p.m. UTC
OpenVPN is a userspace software existing since around 2005 that allows
users to create secure tunnels.

So far OpenVPN has implemented all operations in userspace, which
implies several back and forth between kernel and user land in order to
process packets (encapsulate/decapsulate, encrypt/decrypt, rerouting..).

With `ovpn` we intend to move the fast path (data channel) entirely
in kernel space and thus improve user measured throughput over the
tunnel.

`ovpn` is implemented as a simple virtual network device driver, that
can be manipulated by means of the standard RTNL APIs. A device of kind
`ovpn` allows only IPv4/6 traffic and can be of type:
* P2P (peer-to-peer): any packet sent over the interface will be
  encapsulated and transmitted to the other side (typical OpenVPN
  client or peer-to-peer behaviour);
* P2MP (point-to-multipoint): packets sent over the interface are
  transmitted to peers based on existing routes (typical OpenVPN
  server behaviour).

After the interface has been created, OpenVPN in userspace can
configure it using a new Netlink API. Specifically it is possible
to manage peers and their keys.

The OpenVPN control channel is multiplexed over the same transport
socket by means of OP codes. Anything that is not DATA_V2 (OpenVPN
OP code for data traffic) is sent to userspace and handled there.
This way the `ovpn` codebase is kept as compact as possible while
focusing on handling data traffic only (fast path).

Any OpenVPN control feature (like cipher negotiation, TLS handshake,
rekeying, etc.) is still fully handled by the userspace process.

When userspace establishes a new connection with a peer, it first
performs the handshake and then passes the socket to the `ovpn` kernel
module, which takes ownership. From this moment on `ovpn` will handle
data traffic for the new peer.
When control packets are received on the link, they are forwarded to
userspace through the same transport socket they were received on, as
userspace is still listening to them.

Some events (like peer deletion) are sent to a Netlink multicast group.

Although it wasn't easy to convince the community, `ovpn` implements
only a limited number of the data-channel features supported by the
userspace program.

Each feature that made it to `ovpn` was attentively vetted to
avoid carrying too much legacy along with us (and to give a clear cut to
old and probalby-not-so-useful features).

Notably, only encryption using AEAD ciphers (specifically
ChaCha20Poly1305 and AES-GCM) was implemented. Supporting any other
cipher out there was not deemed useful.

Both UDP and TCP sockets are supported.

As explained above, in case of P2MP mode, OpenVPN will use the main system
routing table to decide which packet goes to which peer. This implies
that no routing table was re-implemented in the `ovpn` kernel module.

This kernel module can be enabled by selecting the CONFIG_OVPN entry
in the networking drivers section.

NOTE: this first patch introduces the very basic framework only.
Features are then added patch by patch, however, although each patch
will compile and possibly not break at runtime, only after having
applied the full set it is expected to see the ovpn module fully working.

Cc: steffen.klassert@secunet.com
Cc: antony.antony@secunet.com
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
 MAINTAINERS               |   8 ++++
 drivers/net/Kconfig       |   8 ++++
 drivers/net/Makefile      |   1 +
 drivers/net/ovpn/Makefile |  10 +++++
 drivers/net/ovpn/main.c   | 112 ++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 139 insertions(+)

Comments

ALOK TIWARI April 10, 2025, 5:51 p.m. UTC | #1
On 08-04-2025 01:16, Antonio Quartulli wrote:
> Although it wasn't easy to convince the community, `ovpn` implements
> only a limited number of the data-channel features supported by the
> userspace program.
> 
> Each feature that made it to `ovpn` was attentively vetted to
> avoid carrying too much legacy along with us (and to give a clear cut to
> old and probalby-not-so-useful features).
> 

typo - probably

> Notably, only encryption using AEAD ciphers (specifically
> ChaCha20Poly1305 and AES-GCM) was implemented. Supporting any other
> cipher out there was not deemed useful.


Thanks,
Alok
Jakub Kicinski April 11, 2025, 2:54 a.m. UTC | #2
On Mon, 07 Apr 2025 21:46:09 +0200 Antonio Quartulli wrote:
> +static int ovpn_netdev_notifier_call(struct notifier_block *nb,
> +				     unsigned long state, void *ptr)
> +{
> +	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
> +
> +	if (!ovpn_dev_is_valid(dev))
> +		return NOTIFY_DONE;
> +
> +	switch (state) {
> +	case NETDEV_REGISTER:
> +		/* add device to internal list for later destruction upon
> +		 * unregistration
> +		 */
> +		break;
> +	case NETDEV_UNREGISTER:
> +		/* can be delivered multiple times, so check registered flag,
> +		 * then destroy the interface
> +		 */
> +		break;
> +	case NETDEV_POST_INIT:
> +	case NETDEV_GOING_DOWN:
> +	case NETDEV_DOWN:
> +	case NETDEV_UP:
> +	case NETDEV_PRE_UP:
> +	default:
> +		return NOTIFY_DONE;
> +	}

Why are you using a notifier to get events for your own device?

> +	return NOTIFY_OK;
> +}

> +MODULE_DESCRIPTION("OpenVPN data channel offload (ovpn)");
> +MODULE_AUTHOR("(C) 2020-2025 OpenVPN, Inc.");

Companies can't author code, only people. Note that MODULE_AUTHOR()
is optional.
Antonio Quartulli April 11, 2025, 8:04 a.m. UTC | #3
Hi Jakub,

thanks for taking the time to go through my patchset :)

On 11/04/2025 04:54, Jakub Kicinski wrote:
> On Mon, 07 Apr 2025 21:46:09 +0200 Antonio Quartulli wrote:
>> +static int ovpn_netdev_notifier_call(struct notifier_block *nb,
>> +				     unsigned long state, void *ptr)
>> +{
>> +	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
>> +
>> +	if (!ovpn_dev_is_valid(dev))
>> +		return NOTIFY_DONE;
>> +
>> +	switch (state) {
>> +	case NETDEV_REGISTER:
>> +		/* add device to internal list for later destruction upon
>> +		 * unregistration
>> +		 */
>> +		break;
>> +	case NETDEV_UNREGISTER:
>> +		/* can be delivered multiple times, so check registered flag,
>> +		 * then destroy the interface
>> +		 */
>> +		break;
>> +	case NETDEV_POST_INIT:
>> +	case NETDEV_GOING_DOWN:
>> +	case NETDEV_DOWN:
>> +	case NETDEV_UP:
>> +	case NETDEV_PRE_UP:
>> +	default:
>> +		return NOTIFY_DONE;
>> +	}
> 
> Why are you using a notifier to get events for your own device?

My understanding is that this is the standard approach to:
1) hook in the middle of registration/deregistration;
2) handle events generated by other components/routines.

I see in /drivers/net/ almost every driver registers a notifier for 
their own device.

Isn't this expected?

> 
>> +	return NOTIFY_OK;
>> +}
> 
>> +MODULE_DESCRIPTION("OpenVPN data channel offload (ovpn)");
>> +MODULE_AUTHOR("(C) 2020-2025 OpenVPN, Inc.");
> 
> Companies can't author code, only people. Note that MODULE_AUTHOR()
> is optional.

Ouch, thanks. Will get this addressed.

Regards,
Antonio Quartulli April 11, 2025, 8:47 a.m. UTC | #4
Hi Alok,

On 10/04/2025 19:51, ALOK TIWARI wrote:
> 
> 
> On 08-04-2025 01:16, Antonio Quartulli wrote:
>> Although it wasn't easy to convince the community, `ovpn` implements
>> only a limited number of the data-channel features supported by the
>> userspace program.
>>
>> Each feature that made it to `ovpn` was attentively vetted to
>> avoid carrying too much legacy along with us (and to give a clear cut to
>> old and probalby-not-so-useful features).
>>
> 
> typo - probably
> 
>> Notably, only encryption using AEAD ciphers (specifically
>> ChaCha20Poly1305 and AES-GCM) was implemented. Supporting any other
>> cipher out there was not deemed useful.
> 
> 
> Thanks,
> Alok

Thanks for reporting various typ0s.
Will fix them in the next version.

Regards,
Sabrina Dubroca April 11, 2025, 1:50 p.m. UTC | #5
2025-04-11, 10:04:10 +0200, Antonio Quartulli wrote:
> Hi Jakub,
> 
> thanks for taking the time to go through my patchset :)
> 
> On 11/04/2025 04:54, Jakub Kicinski wrote:
> > On Mon, 07 Apr 2025 21:46:09 +0200 Antonio Quartulli wrote:
> > > +static int ovpn_netdev_notifier_call(struct notifier_block *nb,
> > > +				     unsigned long state, void *ptr)
> > > +{
> > > +	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
> > > +
> > > +	if (!ovpn_dev_is_valid(dev))
> > > +		return NOTIFY_DONE;
> > > +
> > > +	switch (state) {
> > > +	case NETDEV_REGISTER:
> > > +		/* add device to internal list for later destruction upon
> > > +		 * unregistration
> > > +		 */
> > > +		break;
> > > +	case NETDEV_UNREGISTER:
> > > +		/* can be delivered multiple times, so check registered flag,
> > > +		 * then destroy the interface
> > > +		 */
> > > +		break;
> > > +	case NETDEV_POST_INIT:
> > > +	case NETDEV_GOING_DOWN:
> > > +	case NETDEV_DOWN:
> > > +	case NETDEV_UP:
> > > +	case NETDEV_PRE_UP:
> > > +	default:
> > > +		return NOTIFY_DONE;
> > > +	}
> > 
> > Why are you using a notifier to get events for your own device?
> 
> My understanding is that this is the standard approach to:
> 1) hook in the middle of registration/deregistration;
> 2) handle events generated by other components/routines.
> 
> I see in /drivers/net/ almost every driver registers a notifier for their
> own device.

I think most of them register a notifier for their lower device
(bridge port, real device under a vlan, or similar).

I've mentioned at some point that it would be more usual to replace
this notifier with a custom dellink, and that ovpn->registered could
likely be replaced with checking for NETREG_REGISTERED. I just thought
it could be cleaned up a bit later, but it seems Jakub wants it done
before taking the patches :)
Jakub Kicinski April 11, 2025, 9:18 p.m. UTC | #6
On Fri, 11 Apr 2025 15:50:49 +0200 Sabrina Dubroca wrote:
> > My understanding is that this is the standard approach to:
> > 1) hook in the middle of registration/deregistration;
> > 2) handle events generated by other components/routines.
> > 
> > I see in /drivers/net/ almost every driver registers a notifier for their
> > own device.  
> 
> I think most of them register a notifier for their lower device
> (bridge port, real device under a vlan, or similar).
> 
> I've mentioned at some point that it would be more usual to replace
> this notifier with a custom dellink, and that ovpn->registered could
> likely be replaced with checking for NETREG_REGISTERED. I just thought
> it could be cleaned up a bit later, but it seems Jakub wants it done
> before taking the patches :)

Ideally, yes. One fewer place for us to check when trying to figure 
out if we will break anything with the locking changes :(
Notifiers are very powerful but that comes at high maintenance cost.
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 4c5c2e2c127877a8283793637b0e935ceec27aff..599e821b64131e0b63f5f14be1a62e9ff570063a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18130,6 +18130,14 @@  F:	arch/openrisc/
 F:	drivers/irqchip/irq-ompic.c
 F:	drivers/irqchip/irq-or1k-*
 
+OPENVPN DATA CHANNEL OFFLOAD
+M:	Antonio Quartulli <antonio@openvpn.net>
+L:	openvpn-devel@lists.sourceforge.net (subscribers-only)
+L:	netdev@vger.kernel.org
+S:	Supported
+T:	git https://github.com/OpenVPN/linux-kernel-ovpn.git
+F:	drivers/net/ovpn/
+
 OPENVSWITCH
 M:	Aaron Conole <aconole@redhat.com>
 M:	Eelco Chaudron <echaudro@redhat.com>
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 271520510b5fc6866bbf4fc6a0d728d110e6b5e4..5fbe25ae1e11e558aa9aaa857cf110127e459854 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -115,6 +115,14 @@  config WIREGUARD_DEBUG
 
 	  Say N here unless you know what you're doing.
 
+config OVPN
+	tristate "OpenVPN data channel offload"
+	depends on NET && INET
+	depends on IPV6 || !IPV6
+	help
+	  This module enhances the performance of the OpenVPN userspace software
+	  by offloading the data channel processing to kernelspace.
+
 config EQUALIZER
 	tristate "EQL (serial line load balancing) support"
 	help
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 75333251a01a87e18d6c1bc9eec97b96b571b77b..73bc63ecd65ff45c9458a31d7f67447b37e18cdb 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -11,6 +11,7 @@  obj-$(CONFIG_IPVLAN) += ipvlan/
 obj-$(CONFIG_IPVTAP) += ipvlan/
 obj-$(CONFIG_DUMMY) += dummy.o
 obj-$(CONFIG_WIREGUARD) += wireguard/
+obj-$(CONFIG_OVPN) += ovpn/
 obj-$(CONFIG_EQUALIZER) += eql.o
 obj-$(CONFIG_IFB) += ifb.o
 obj-$(CONFIG_MACSEC) += macsec.o
diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..876800ebaa21a5f758ddf60f637801710437f70e
--- /dev/null
+++ b/drivers/net/ovpn/Makefile
@@ -0,0 +1,10 @@ 
+# SPDX-License-Identifier: GPL-2.0
+#
+# ovpn -- OpenVPN data channel offload in kernel space
+#
+# Copyright (C) 2020-2025 OpenVPN, Inc.
+#
+# Author:	Antonio Quartulli <antonio@openvpn.net>
+
+obj-$(CONFIG_OVPN) := ovpn.o
+ovpn-y += main.o
diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..e816e8fbbfeff1086a55c858b1941b7d82d7aba6
--- /dev/null
+++ b/drivers/net/ovpn/main.c
@@ -0,0 +1,112 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*  OpenVPN data channel offload
+ *
+ *  Copyright (C) 2020-2025 OpenVPN, Inc.
+ *
+ *  Author:	Antonio Quartulli <antonio@openvpn.net>
+ *		James Yonan <james@openvpn.net>
+ */
+
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <net/rtnetlink.h>
+
+static const struct net_device_ops ovpn_netdev_ops = {
+};
+
+/**
+ * ovpn_dev_is_valid - check if the netdevice is of type 'ovpn'
+ * @dev: the interface to check
+ *
+ * Return: whether the netdevice is of type 'ovpn'
+ */
+static bool ovpn_dev_is_valid(const struct net_device *dev)
+{
+	return dev->netdev_ops == &ovpn_netdev_ops;
+}
+
+static int ovpn_newlink(struct net_device *dev,
+			struct rtnl_newlink_params *params,
+			struct netlink_ext_ack *extack)
+{
+	return -EOPNOTSUPP;
+}
+
+static struct rtnl_link_ops ovpn_link_ops = {
+	.kind = "ovpn",
+	.netns_refund = false,
+	.newlink = ovpn_newlink,
+	.dellink = unregister_netdevice_queue,
+};
+
+static int ovpn_netdev_notifier_call(struct notifier_block *nb,
+				     unsigned long state, void *ptr)
+{
+	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+
+	if (!ovpn_dev_is_valid(dev))
+		return NOTIFY_DONE;
+
+	switch (state) {
+	case NETDEV_REGISTER:
+		/* add device to internal list for later destruction upon
+		 * unregistration
+		 */
+		break;
+	case NETDEV_UNREGISTER:
+		/* can be delivered multiple times, so check registered flag,
+		 * then destroy the interface
+		 */
+		break;
+	case NETDEV_POST_INIT:
+	case NETDEV_GOING_DOWN:
+	case NETDEV_DOWN:
+	case NETDEV_UP:
+	case NETDEV_PRE_UP:
+	default:
+		return NOTIFY_DONE;
+	}
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block ovpn_netdev_notifier = {
+	.notifier_call = ovpn_netdev_notifier_call,
+};
+
+static int __init ovpn_init(void)
+{
+	int err = register_netdevice_notifier(&ovpn_netdev_notifier);
+
+	if (err) {
+		pr_err("ovpn: can't register netdevice notifier: %d\n", err);
+		return err;
+	}
+
+	err = rtnl_link_register(&ovpn_link_ops);
+	if (err) {
+		pr_err("ovpn: can't register rtnl link ops: %d\n", err);
+		goto unreg_netdev;
+	}
+
+	return 0;
+
+unreg_netdev:
+	unregister_netdevice_notifier(&ovpn_netdev_notifier);
+	return err;
+}
+
+static __exit void ovpn_cleanup(void)
+{
+	rtnl_link_unregister(&ovpn_link_ops);
+	unregister_netdevice_notifier(&ovpn_netdev_notifier);
+
+	rcu_barrier();
+}
+
+module_init(ovpn_init);
+module_exit(ovpn_cleanup);
+
+MODULE_DESCRIPTION("OpenVPN data channel offload (ovpn)");
+MODULE_AUTHOR("(C) 2020-2025 OpenVPN, Inc.");
+MODULE_LICENSE("GPL");