* [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload
@ 2024-06-27 13:08 Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 01/25] netlink: add NLA_POLICY_MAX_LEN macro Antonio Quartulli
` (24 more replies)
0 siblings, 25 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
Hi all,
here I am with v5 of the ovpn patchset.
The only differences from v4 are:
* moved the ovpn kselftest to the net/ subfolder
* renamed kselftest scripts to something more reasonable
* removed kselftest wrapper script run.sh, now each test runs
individually
* made checkpatch happy regarding the ovpn-cli.c kselftest tool
* CCed more maintainers to a few individual patches, as requested by
checkpatch
* fixed one remaining kdoc warning in patch 4
* properly updated MAINTAINERS file in patch 4
* fixed clang build warning in patch 22
(The only remaining failure on patchwork is about series being larger
than 15 patches)
Nothing else has been touched.
For covenience, here is the summary I sent for v4:
Thanks to Sabrina's feedback, several parts of the code are now
simpler than before.
There is an extra kernel patch, compared to v3, that is 02/25.
This patch is required to properly handle virtual devices not
implementing a dellink upon invocation of rtnl_unregister_ops().
Here is an overview of the changesfrom v3, sorted more or less by
importance (very minor changes haven't been mentioned):
* got rid of the TX/RX queues entirely
* got rid of the workqueues entirely
** the only sporadic scheduled event, TCP TX retry, is scheduled on the
global kernel queue
* use strparser for receiving TCP data
* use gro_cells and get rid of any napi and netif_rx_ring related code
* crypto code now follows the classic async paradigma
* removed synchronize_net()
* avoided unregister_netdevice() double call
* added empty rtnl_link_ops to ensure ifaces are destroyed upon ns exit
* convert pkt counters to 64 bit
* counted dropped packets (in core stats)
* simplified peer lookup routines (no need to hold ref every time)
* simplified TCP recvmsg implementation
* peer collection for MultiPeer mode is now allocated dynamically
* use GFP_ATOMIC for sending nl notifications out of process context
* documented how EALREADY and EBUSY are used in UDP socket attach
* used GENL_REQ_ATTR_CHECK in ovpn_get_dev_from_attrs/pre_doit
* used NL_SET_BAD_ATTR/GENL_SET_ERR_MSG in ovpn_get_dev_from_attrs/pre_doit
* used NL_SET_ERR_MSG_MOD for reporting back error strings
* dropped netlink 'pad' attribute
* used genlmsg_iput
* set pcpu_stat_type to let core handle stats internally
* used nla_put_string/in_addr/in6_addr when possibly
* used ipv6 helpers when possible (ipv6_addr_equal/any)
* added various calls to DEBUG_NET_WARN_ON_ONCE/WARN_ON
* removed unworthy error message in case of netlink message size errors
* used -EOPNOTSUPP instead of -ENOTSUPP
* userspace testing tool improved
* various code rearrangments based on provded feedback ..
The latest code can also be found at:
https://github.com/OpenVPN/linux-kernel-ovpn
Below is the original description posted with the first patchest:
===================================================================
`ovpn` is essentialy a device driver that allows creating a virtual
network interface to handle the OpenVPN data channel. Any traffic
entering the interface is encrypted, encapsulated and sent to the
appropriate destination.
`ovpn` requires OpenVPN in userspace
to run along its side in order to be properly configured and maintained
during its life cycle.
The `ovpn` interface can be created/destroyed and then
configured via Netlink API.
Specifically OpenVPN in userspace will:
* create the `ovpn` interface
* establish the connection with one or more peers
* perform TLS handshake and negotiate any protocol parameter
* configure the `ovpn` interface with peer data (ip/port, keys, etc.)
* handle any subsequent control channel communication
I'd like to point out the control channel is fully handles in userspace.
The idea is to keep the `ovpn` kernel module as simple as possible and
let userspace handle all the non-data (non-fast-path) features.
NOTE: some of you may already know `ovpn-dco` the out-of-tree predecessor
of `ovpn`. However, be aware that the two are not API compatible and
therefore OpenVPN 2.6 will not work with this new `ovpn` module.
More adjustments are required.
For more technical details please refer to the actual patches.
Any comment, concern or statement will be appreciated!
Thanks a lot!!
Best Regards,
Antonio Quartulli
OpenVPN Inc.
======================
Antonio Quartulli (25):
netlink: add NLA_POLICY_MAX_LEN macro
rtnetlink: don't crash on unregister if no dellink exists
net: introduce OpenVPN Data Channel Offload (ovpn)
ovpn: add basic netlink support
ovpn: add basic interface creation/destruction/management routines
ovpn: implement interface creation/destruction via netlink
ovpn: keep carrier always on
ovpn: introduce the ovpn_peer object
ovpn: introduce the ovpn_socket object
ovpn: implement basic TX path (UDP)
ovpn: implement basic RX path (UDP)
ovpn: implement packet processing
ovpn: store tunnel and transport statistics
ovpn: implement TCP transport
ovpn: implement multi-peer support
ovpn: implement peer lookup logic
ovpn: implement keepalive mechanism
ovpn: add support for updating local UDP endpoint
ovpn: add support for peer floating
ovpn: implement peer add/dump/delete via netlink
ovpn: implement key add/del/swap via netlink
ovpn: kill key and notify userspace in case of IV exhaustion
ovpn: notify userspace when a peer is deleted
ovpn: add basic ethtool support
testing/selftest: add test tool and scripts for ovpn module
Documentation/netlink/specs/ovpn.yaml | 327 +++
MAINTAINERS | 8 +
drivers/net/Kconfig | 14 +
drivers/net/Makefile | 1 +
drivers/net/ovpn/Makefile | 22 +
drivers/net/ovpn/bind.c | 58 +
drivers/net/ovpn/bind.h | 119 ++
drivers/net/ovpn/crypto.c | 161 ++
drivers/net/ovpn/crypto.h | 138 ++
drivers/net/ovpn/crypto_aead.c | 347 ++++
drivers/net/ovpn/crypto_aead.h | 30 +
drivers/net/ovpn/io.c | 438 ++++
drivers/net/ovpn/io.h | 21 +
drivers/net/ovpn/main.c | 360 ++++
drivers/net/ovpn/main.h | 29 +
drivers/net/ovpn/netlink-gen.c | 206 ++
drivers/net/ovpn/netlink-gen.h | 41 +
drivers/net/ovpn/netlink.c | 960 +++++++++
drivers/net/ovpn/netlink.h | 31 +
drivers/net/ovpn/ovpnstruct.h | 52 +
drivers/net/ovpn/packet.h | 40 +
drivers/net/ovpn/peer.c | 1047 ++++++++++
drivers/net/ovpn/peer.h | 202 ++
drivers/net/ovpn/pktid.c | 130 ++
drivers/net/ovpn/pktid.h | 87 +
drivers/net/ovpn/proto.h | 106 +
drivers/net/ovpn/skb.h | 56 +
drivers/net/ovpn/socket.c | 165 ++
drivers/net/ovpn/socket.h | 54 +
drivers/net/ovpn/stats.c | 21 +
drivers/net/ovpn/stats.h | 47 +
drivers/net/ovpn/tcp.c | 502 +++++
drivers/net/ovpn/tcp.h | 42 +
drivers/net/ovpn/udp.c | 404 ++++
drivers/net/ovpn/udp.h | 26 +
include/net/netlink.h | 1 +
include/uapi/linux/ovpn.h | 108 +
include/uapi/linux/udp.h | 1 +
net/core/rtnetlink.c | 8 +-
tools/net/ynl/ynl-gen-c.py | 2 +
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/net/ovpn/.gitignore | 2 +
tools/testing/selftests/net/ovpn/Makefile | 17 +
tools/testing/selftests/net/ovpn/config | 8 +
.../selftests/net/ovpn/data-test-tcp.sh | 9 +
tools/testing/selftests/net/ovpn/data-test.sh | 130 ++
tools/testing/selftests/net/ovpn/data64.key | 5 +
.../testing/selftests/net/ovpn/float-test.sh | 115 ++
tools/testing/selftests/net/ovpn/ovpn-cli.c | 1787 +++++++++++++++++
.../testing/selftests/net/ovpn/tcp_peers.txt | 1 +
.../testing/selftests/net/ovpn/udp_peers.txt | 5 +
51 files changed, 8490 insertions(+), 2 deletions(-)
create mode 100644 Documentation/netlink/specs/ovpn.yaml
create mode 100644 drivers/net/ovpn/Makefile
create mode 100644 drivers/net/ovpn/bind.c
create mode 100644 drivers/net/ovpn/bind.h
create mode 100644 drivers/net/ovpn/crypto.c
create mode 100644 drivers/net/ovpn/crypto.h
create mode 100644 drivers/net/ovpn/crypto_aead.c
create mode 100644 drivers/net/ovpn/crypto_aead.h
create mode 100644 drivers/net/ovpn/io.c
create mode 100644 drivers/net/ovpn/io.h
create mode 100644 drivers/net/ovpn/main.c
create mode 100644 drivers/net/ovpn/main.h
create mode 100644 drivers/net/ovpn/netlink-gen.c
create mode 100644 drivers/net/ovpn/netlink-gen.h
create mode 100644 drivers/net/ovpn/netlink.c
create mode 100644 drivers/net/ovpn/netlink.h
create mode 100644 drivers/net/ovpn/ovpnstruct.h
create mode 100644 drivers/net/ovpn/packet.h
create mode 100644 drivers/net/ovpn/peer.c
create mode 100644 drivers/net/ovpn/peer.h
create mode 100644 drivers/net/ovpn/pktid.c
create mode 100644 drivers/net/ovpn/pktid.h
create mode 100644 drivers/net/ovpn/proto.h
create mode 100644 drivers/net/ovpn/skb.h
create mode 100644 drivers/net/ovpn/socket.c
create mode 100644 drivers/net/ovpn/socket.h
create mode 100644 drivers/net/ovpn/stats.c
create mode 100644 drivers/net/ovpn/stats.h
create mode 100644 drivers/net/ovpn/tcp.c
create mode 100644 drivers/net/ovpn/tcp.h
create mode 100644 drivers/net/ovpn/udp.c
create mode 100644 drivers/net/ovpn/udp.h
create mode 100644 include/uapi/linux/ovpn.h
create mode 100644 tools/testing/selftests/net/ovpn/.gitignore
create mode 100644 tools/testing/selftests/net/ovpn/Makefile
create mode 100644 tools/testing/selftests/net/ovpn/config
create mode 100755 tools/testing/selftests/net/ovpn/data-test-tcp.sh
create mode 100755 tools/testing/selftests/net/ovpn/data-test.sh
create mode 100644 tools/testing/selftests/net/ovpn/data64.key
create mode 100755 tools/testing/selftests/net/ovpn/float-test.sh
create mode 100644 tools/testing/selftests/net/ovpn/ovpn-cli.c
create mode 100644 tools/testing/selftests/net/ovpn/tcp_peers.txt
create mode 100644 tools/testing/selftests/net/ovpn/udp_peers.txt
--
2.44.2
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH net-next v5 01/25] netlink: add NLA_POLICY_MAX_LEN macro
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 02/25] rtnetlink: don't crash on unregister if no dellink exists Antonio Quartulli
` (23 subsequent siblings)
24 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev
Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli, donald.hunter
Similarly to NLA_POLICY_MIN_LEN, NLA_POLICY_MAX_LEN defines a policy
with a maximum length value.
The netlink generator for YAML specs has been extended accordingly.
Cc: donald.hunter@gmail.com
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
include/net/netlink.h | 1 +
tools/net/ynl/ynl-gen-c.py | 2 ++
2 files changed, 3 insertions(+)
diff --git a/include/net/netlink.h b/include/net/netlink.h
index e78ce008e07c..31d83f6a465b 100644
--- a/include/net/netlink.h
+++ b/include/net/netlink.h
@@ -469,6 +469,7 @@ struct nla_policy {
.max = _len \
}
#define NLA_POLICY_MIN_LEN(_len) NLA_POLICY_MIN(NLA_BINARY, _len)
+#define NLA_POLICY_MAX_LEN(_len) NLA_POLICY_MAX(NLA_BINARY, _len)
/**
* struct nl_info - netlink source information
diff --git a/tools/net/ynl/ynl-gen-c.py b/tools/net/ynl/ynl-gen-c.py
index 374ca5e86e24..5910ecd38081 100755
--- a/tools/net/ynl/ynl-gen-c.py
+++ b/tools/net/ynl/ynl-gen-c.py
@@ -466,6 +466,8 @@ class TypeBinary(Type):
def _attr_policy(self, policy):
if 'exact-len' in self.checks:
mem = 'NLA_POLICY_EXACT_LEN(' + str(self.get_limit('exact-len')) + ')'
+ elif 'max-len' in self.checks:
+ mem = 'NLA_POLICY_MAX_LEN(' + str(self.get_limit('max-len')) + ')'
else:
mem = '{ '
if len(self.checks) == 1 and 'min-len' in self.checks:
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 02/25] rtnetlink: don't crash on unregister if no dellink exists
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 01/25] netlink: add NLA_POLICY_MAX_LEN macro Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 03/25] net: introduce OpenVPN Data Channel Offload (ovpn) Antonio Quartulli
` (22 subsequent siblings)
24 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
rtnl_unregister_ops calls __rtnl_links_kill which calls
unconditionally ops->dellink on any related interface.
However, if a module hasn't implemented dellink, this
invocation will crash.
Add a check to avoid calling dellink when NULL and rather
invoke netdevice_unregister_queue to schedule unregistering
the device as usual.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
net/core/rtnetlink.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index eabfc8290f5e..be79c2a051b2 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -453,8 +453,12 @@ static void __rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops)
LIST_HEAD(list_kill);
for_each_netdev(net, dev) {
- if (dev->rtnl_link_ops == ops)
- ops->dellink(dev, &list_kill);
+ if (dev->rtnl_link_ops == ops) {
+ if (ops->dellink)
+ ops->dellink(dev, &list_kill);
+ else
+ unregister_netdevice_queue(dev, &list_kill);
+ }
}
unregister_netdevice_many(&list_kill);
}
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 03/25] net: introduce OpenVPN Data Channel Offload (ovpn)
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 01/25] netlink: add NLA_POLICY_MAX_LEN macro Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 02/25] rtnetlink: don't crash on unregister if no dellink exists Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 04/25] ovpn: add basic netlink support Antonio Quartulli
` (21 subsequent siblings)
24 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev
Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli, steffen.klassert, antony.antony
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 ae 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 | 7 +++
drivers/net/Kconfig | 13 +++++
drivers/net/Makefile | 1 +
drivers/net/ovpn/Makefile | 11 ++++
drivers/net/ovpn/io.c | 22 ++++++++
drivers/net/ovpn/io.h | 15 ++++++
drivers/net/ovpn/main.c | 109 ++++++++++++++++++++++++++++++++++++++
drivers/net/ovpn/main.h | 15 ++++++
include/uapi/linux/udp.h | 1 +
9 files changed, 194 insertions(+)
create mode 100644 drivers/net/ovpn/Makefile
create mode 100644 drivers/net/ovpn/io.c
create mode 100644 drivers/net/ovpn/io.h
create mode 100644 drivers/net/ovpn/main.c
create mode 100644 drivers/net/ovpn/main.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 0e3cb040fc16..937f072f5189 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16908,6 +16908,13 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.git
F: Documentation/filesystems/overlayfs.rst
F: fs/overlayfs/
+OPENVPN DATA CHANNEL OFFLOAD
+M: Antonio Quartulli <antonio@openvpn.net>
+L: openvpn-devel@lists.sourceforge.net (moderated for non-subscribers)
+L: netdev@vger.kernel.org
+S: Maintained
+F: drivers/net/ovpn/
+
P54 WIRELESS DRIVER
M: Christian Lamparter <chunkeey@googlemail.com>
L: linux-wireless@vger.kernel.org
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 9920b3a68ed1..c5743288242d 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -115,6 +115,19 @@ config WIREGUARD_DEBUG
Say N here unless you know what you're doing.
+config OVPN
+ tristate "OpenVPN data channel offload"
+ depends on NET && INET
+ select NET_UDP_TUNNEL
+ select DST_CACHE
+ select CRYPTO
+ select CRYPTO_AES
+ select CRYPTO_GCM
+ select CRYPTO_CHACHA20POLY1305
+ 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 13743d0e83b5..5152b3330e28 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 000000000000..53fb197027d7
--- /dev/null
+++ b/drivers/net/ovpn/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# ovpn -- OpenVPN data channel offload in kernel space
+#
+# Copyright (C) 2020-2024 OpenVPN, Inc.
+#
+# Author: Antonio Quartulli <antonio@openvpn.net>
+
+obj-$(CONFIG_OVPN) := ovpn.o
+ovpn-y += main.o
+ovpn-y += io.o
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
new file mode 100644
index 000000000000..ad3813419c33
--- /dev/null
+++ b/drivers/net/ovpn/io.c
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2019-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+
+#include "io.h"
+
+/* Send user data to the network
+ */
+netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ skb_tx_error(skb);
+ kfree_skb(skb);
+ return NET_XMIT_DROP;
+}
diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h
new file mode 100644
index 000000000000..aa259be66441
--- /dev/null
+++ b/drivers/net/ovpn/io.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2019-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_OVPN_H_
+#define _NET_OVPN_OVPN_H_
+
+netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev);
+
+#endif /* _NET_OVPN_OVPN_H_ */
diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
new file mode 100644
index 000000000000..8a90319e4600
--- /dev/null
+++ b/drivers/net/ovpn/main.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ * James Yonan <james@openvpn.net>
+ */
+
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/version.h>
+#include <net/rtnetlink.h>
+
+#include "main.h"
+#include "io.h"
+
+/* Driver info */
+#define DRV_DESCRIPTION "OpenVPN data channel offload (ovpn)"
+#define DRV_COPYRIGHT "(C) 2020-2024 OpenVPN, Inc."
+
+/**
+ * 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'
+ */
+bool ovpn_dev_is_valid(const struct net_device *dev)
+{
+ return dev->netdev_ops->ndo_start_xmit == ovpn_net_xmit;
+}
+
+/* we register with rtnl to let core know that ovpn is a virtual driver and
+ * therefore ifaces should be destroyed when exiting a netns
+ */
+static struct rtnl_link_ops ovpn_link_ops = {
+};
+
+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(DRV_DESCRIPTION);
+MODULE_AUTHOR(DRV_COPYRIGHT);
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ovpn/main.h b/drivers/net/ovpn/main.h
new file mode 100644
index 000000000000..a3215316c49b
--- /dev/null
+++ b/drivers/net/ovpn/main.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2019-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_MAIN_H_
+#define _NET_OVPN_MAIN_H_
+
+bool ovpn_dev_is_valid(const struct net_device *dev);
+
+#endif /* _NET_OVPN_MAIN_H_ */
diff --git a/include/uapi/linux/udp.h b/include/uapi/linux/udp.h
index 1a0fe8b151fb..f9f8ffddfd0c 100644
--- a/include/uapi/linux/udp.h
+++ b/include/uapi/linux/udp.h
@@ -43,5 +43,6 @@ struct udphdr {
#define UDP_ENCAP_GTP1U 5 /* 3GPP TS 29.060 */
#define UDP_ENCAP_RXRPC 6
#define TCP_ENCAP_ESPINTCP 7 /* Yikes, this is really xfrm encap types. */
+#define UDP_ENCAP_OVPNINUDP 8 /* OpenVPN traffic */
#endif /* _UAPI_LINUX_UDP_H */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 04/25] ovpn: add basic netlink support
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (2 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 03/25] net: introduce OpenVPN Data Channel Offload (ovpn) Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 05/25] ovpn: add basic interface creation/destruction/management routines Antonio Quartulli
` (20 subsequent siblings)
24 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev
Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli, donald.hunter
This commit introduces basic netlink support with family
registration/unregistration functionalities and stub pre/post-doit.
More importantly it introduces the YAML uAPI description along
with its auto-generated files:
- include/uapi/linux/ovpn.h
- drivers/net/ovpn/netlink-gen.c
- drivers/net/ovpn/netlink-gen.h
Cc: donald.hunter@gmail.com
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
Documentation/netlink/specs/ovpn.yaml | 327 ++++++++++++++++++++++++++
MAINTAINERS | 1 +
drivers/net/ovpn/Makefile | 2 +
drivers/net/ovpn/main.c | 14 ++
drivers/net/ovpn/netlink-gen.c | 206 ++++++++++++++++
drivers/net/ovpn/netlink-gen.h | 41 ++++
drivers/net/ovpn/netlink.c | 153 ++++++++++++
drivers/net/ovpn/netlink.h | 15 ++
drivers/net/ovpn/ovpnstruct.h | 21 ++
include/uapi/linux/ovpn.h | 108 +++++++++
10 files changed, 888 insertions(+)
create mode 100644 Documentation/netlink/specs/ovpn.yaml
create mode 100644 drivers/net/ovpn/netlink-gen.c
create mode 100644 drivers/net/ovpn/netlink-gen.h
create mode 100644 drivers/net/ovpn/netlink.c
create mode 100644 drivers/net/ovpn/netlink.h
create mode 100644 drivers/net/ovpn/ovpnstruct.h
create mode 100644 include/uapi/linux/ovpn.h
diff --git a/Documentation/netlink/specs/ovpn.yaml b/Documentation/netlink/specs/ovpn.yaml
new file mode 100644
index 000000000000..68ed88d03732
--- /dev/null
+++ b/Documentation/netlink/specs/ovpn.yaml
@@ -0,0 +1,327 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+#
+# Author: Antonio Quartulli <antonio@openvpn.net>
+#
+# Copyright (c) 2024, OpenVPN Inc.
+#
+
+name: ovpn
+
+protocol: genetlink
+
+doc: Netlink protocol to control OpenVPN network devices
+
+definitions:
+ -
+ type: const
+ name: nonce-tail-size
+ value: 8
+ -
+ type: enum
+ name: cipher-alg
+ value-start: 0
+ entries: [ none, aes-gcm, chacha20_poly1305 ]
+ -
+ type: enum
+ name: del-peer_reason
+ value-start: 0
+ entries: [ teardown, userspace, expired, transport-error, transport_disconnect ]
+ -
+ type: enum
+ name: key-slot
+ value-start: 0
+ entries: [ primary, secondary ]
+ -
+ type: enum
+ name: mode
+ value-start: 0
+ entries: [ p2p, mp ]
+
+attribute-sets:
+ -
+ name: peer
+ attributes:
+ -
+ name: id
+ type: u32
+ doc: |
+ The unique Id of the peer. To be used to identify peers during
+ operations
+ checks:
+ max: 0xFFFFFF
+ -
+ name: sockaddr-remote
+ type: binary
+ doc: |
+ The sockaddr_in/in6 object identifying the remote address/port of the
+ peer
+ -
+ name: socket
+ type: u32
+ doc: The socket to be used to communicate with the peer
+ -
+ name: vpn-ipv4
+ type: u32
+ doc: The IPv4 assigned to the peer by the server
+ display-hint: ipv4
+ -
+ name: vpn-ipv6
+ type: binary
+ doc: The IPv6 assigned to the peer by the server
+ display-hint: ipv6
+ checks:
+ exact-len: 16
+ -
+ name: local-ip
+ type: binary
+ doc: The local IP to be used to send packets to the peer (UDP only)
+ checks:
+ max-len: 16
+ -
+ name: local-port
+ type: u32
+ doc: The local port to be used to send packets to the peer (UDP only)
+ checks:
+ min: 1
+ max: u16-max
+ -
+ name: keepalive-interval
+ type: u32
+ doc: |
+ The number of seconds after which a keep alive message is sent to the
+ peer
+ -
+ name: keepalive-timeout
+ type: u32
+ doc: |
+ The number of seconds from the last activity after which the peer is
+ assumed dead
+ -
+ name: del-reason
+ type: u32
+ doc: The reason why a peer was deleted
+ enum: del-peer_reason
+ -
+ name: keyconf
+ type: nest
+ doc: Peer specific cipher configuration
+ nested-attributes: keyconf
+ -
+ name: vpn-rx_bytes
+ type: uint
+ doc: Number of bytes received over the tunnel
+ -
+ name: vpn-tx_bytes
+ type: uint
+ doc: Number of bytes transmitted over the tunnel
+ -
+ name: vpn-rx_packets
+ type: uint
+ doc: Number of packets received over the tunnel
+ -
+ name: vpn-tx_packets
+ type: uint
+ doc: Number of packets transmitted over the tunnel
+ -
+ name: link-rx_bytes
+ type: uint
+ doc: Number of bytes received at the transport level
+ -
+ name: link-tx_bytes
+ type: uint
+ doc: Number of bytes transmitted at the transport level
+ -
+ name: link-rx_packets
+ type: u32
+ doc: Number of packets received at the transport level
+ -
+ name: link-tx_packets
+ type: u32
+ doc: Number of packets transmitted at the transport level
+ -
+ name: keyconf
+ attributes:
+ -
+ name: slot
+ type: u32
+ doc: The slot where the key should be stored
+ enum: key-slot
+ -
+ name: key-id
+ doc: |
+ The unique ID for the key. Used to fetch the correct key upon
+ decryption
+ type: u32
+ checks:
+ max: 2
+ -
+ name: cipher-alg
+ type: u32
+ doc: The cipher to be used when communicating with the peer
+ enum: cipher-alg
+ -
+ name: encrypt-dir
+ type: nest
+ doc: Key material for encrypt direction
+ nested-attributes: keydir
+ -
+ name: decrypt-dir
+ type: nest
+ doc: Key material for decrypt direction
+ nested-attributes: keydir
+ -
+ name: keydir
+ attributes:
+ -
+ name: cipher-key
+ type: binary
+ doc: The actual key to be used by the cipher
+ checks:
+ max-len: 256
+ -
+ name: nonce-tail
+ type: binary
+ doc: |
+ Random nonce to be concatenated to the packet ID, in order to
+ obtain the actua cipher IV
+ checks:
+ exact-len: nonce-tail-size
+ -
+ name: ovpn
+ attributes:
+ -
+ name: ifindex
+ type: u32
+ doc: Index of the ovpn interface to operate on
+ -
+ name: ifname
+ type: string
+ doc: Name of the ovpn interface that is being created
+ -
+ name: mode
+ type: u32
+ enum: mode
+ doc: |
+ Oper mode instructing an interface to act as Point2Point or
+ MultiPoint
+ -
+ name: peer
+ type: nest
+ doc: |
+ The peer object containing the attributed of interest for the specific
+ operation
+ nested-attributes: peer
+
+operations:
+ list:
+ -
+ name: new-iface
+ attribute-set: ovpn
+ flags: [ admin-perm ]
+ doc: Create a new interface
+ do:
+ request:
+ attributes:
+ - ifname
+ - mode
+ reply:
+ attributes:
+ - ifname
+ -
+ name: del-iface
+ attribute-set: ovpn
+ flags: [ admin-perm ]
+ doc: Delete existing interface
+ do:
+ pre: ovpn-nl-pre-doit
+ post: ovpn-nl-post-doit
+ request:
+ attributes:
+ - ifindex
+ -
+ name: set-peer
+ attribute-set: ovpn
+ flags: [ admin-perm ]
+ doc: Add or modify a remote peer
+ do:
+ pre: ovpn-nl-pre-doit
+ post: ovpn-nl-post-doit
+ request:
+ attributes:
+ - ifindex
+ - peer
+ -
+ name: get-peer
+ attribute-set: ovpn
+ flags: [ admin-perm ]
+ doc: Retrieve data about existing remote peers (or a specific one)
+ do:
+ pre: ovpn-nl-pre-doit
+ post: ovpn-nl-post-doit
+ request:
+ attributes:
+ - ifindex
+ - peer
+ reply:
+ attributes:
+ - peer
+ dump:
+ request:
+ attributes:
+ - ifindex
+ reply:
+ attributes:
+ - peer
+ -
+ name: del-peer
+ attribute-set: ovpn
+ flags: [ admin-perm ]
+ doc: Delete existing remote peer
+ do:
+ pre: ovpn-nl-pre-doit
+ post: ovpn-nl-post-doit
+ request:
+ attributes:
+ - ifindex
+ - peer
+ -
+ name: set-key
+ attribute-set: ovpn
+ flags: [ admin-perm ]
+ doc: Add or modify a cipher key for a specific peer
+ do:
+ pre: ovpn-nl-pre-doit
+ post: ovpn-nl-post-doit
+ request:
+ attributes:
+ - ifindex
+ - peer
+ -
+ name: swap-keys
+ attribute-set: ovpn
+ flags: [ admin-perm ]
+ doc: Swap primary and secondary session keys for a specific peer
+ do:
+ pre: ovpn-nl-pre-doit
+ post: ovpn-nl-post-doit
+ request:
+ attributes:
+ - ifindex
+ - peer
+ -
+ name: del-key
+ attribute-set: ovpn
+ flags: [ admin-perm ]
+ doc: Delete cipher key for a specific peer
+ do:
+ pre: ovpn-nl-pre-doit
+ post: ovpn-nl-post-doit
+ request:
+ attributes:
+ - ifindex
+ - peer
+
+mcast-groups:
+ list:
+ -
+ name: peers
diff --git a/MAINTAINERS b/MAINTAINERS
index 937f072f5189..1620c49ff469 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16914,6 +16914,7 @@ L: openvpn-devel@lists.sourceforge.net (moderated for non-subscribers)
L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/ovpn/
+F: include/uapi/linux/ovpn.h
P54 WIRELESS DRIVER
M: Christian Lamparter <chunkeey@googlemail.com>
diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile
index 53fb197027d7..201dc001419f 100644
--- a/drivers/net/ovpn/Makefile
+++ b/drivers/net/ovpn/Makefile
@@ -9,3 +9,5 @@
obj-$(CONFIG_OVPN) := ovpn.o
ovpn-y += main.o
ovpn-y += io.o
+ovpn-y += netlink.o
+ovpn-y += netlink-gen.o
diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
index 8a90319e4600..7c35697cb596 100644
--- a/drivers/net/ovpn/main.c
+++ b/drivers/net/ovpn/main.c
@@ -7,12 +7,16 @@
* James Yonan <james@openvpn.net>
*/
+#include <linux/genetlink.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/version.h>
#include <net/rtnetlink.h>
+#include <uapi/linux/ovpn.h>
+#include "ovpnstruct.h"
#include "main.h"
+#include "netlink.h"
#include "io.h"
/* Driver info */
@@ -34,6 +38,7 @@ bool ovpn_dev_is_valid(const struct net_device *dev)
* therefore ifaces should be destroyed when exiting a netns
*/
static struct rtnl_link_ops ovpn_link_ops = {
+ .kind = OVPN_FAMILY_NAME,
};
static int ovpn_netdev_notifier_call(struct notifier_block *nb,
@@ -86,8 +91,16 @@ static int __init ovpn_init(void)
goto unreg_netdev;
}
+ err = ovpn_nl_register();
+ if (err) {
+ pr_err("ovpn: can't register netlink family: %d\n", err);
+ goto unreg_rtnl;
+ }
+
return 0;
+unreg_rtnl:
+ rtnl_link_unregister(&ovpn_link_ops);
unreg_netdev:
unregister_netdevice_notifier(&ovpn_netdev_notifier);
return err;
@@ -95,6 +108,7 @@ static int __init ovpn_init(void)
static __exit void ovpn_cleanup(void)
{
+ ovpn_nl_unregister();
rtnl_link_unregister(&ovpn_link_ops);
unregister_netdevice_notifier(&ovpn_netdev_notifier);
diff --git a/drivers/net/ovpn/netlink-gen.c b/drivers/net/ovpn/netlink-gen.c
new file mode 100644
index 000000000000..594eb2c50eb5
--- /dev/null
+++ b/drivers/net/ovpn/netlink-gen.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/ovpn.yaml */
+/* YNL-GEN kernel source */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "netlink-gen.h"
+
+#include <uapi/linux/ovpn.h>
+
+/* Integer value ranges */
+static const struct netlink_range_validation ovpn_a_peer_id_range = {
+ .max = 16777215ULL,
+};
+
+static const struct netlink_range_validation ovpn_a_peer_local_port_range = {
+ .min = 1ULL,
+ .max = 65535ULL,
+};
+
+/* Common nested types */
+const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR + 1] = {
+ [OVPN_A_KEYCONF_SLOT] = NLA_POLICY_MAX(NLA_U32, 1),
+ [OVPN_A_KEYCONF_KEY_ID] = NLA_POLICY_MAX(NLA_U32, 2),
+ [OVPN_A_KEYCONF_CIPHER_ALG] = NLA_POLICY_MAX(NLA_U32, 2),
+ [OVPN_A_KEYCONF_ENCRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy),
+ [OVPN_A_KEYCONF_DECRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy),
+};
+
+const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1] = {
+ [OVPN_A_KEYDIR_CIPHER_KEY] = NLA_POLICY_MAX_LEN(256),
+ [OVPN_A_KEYDIR_NONCE_TAIL] = NLA_POLICY_EXACT_LEN(OVPN_NONCE_TAIL_SIZE),
+};
+
+const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_LINK_TX_PACKETS + 1] = {
+ [OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range),
+ [OVPN_A_PEER_SOCKADDR_REMOTE] = { .type = NLA_BINARY, },
+ [OVPN_A_PEER_SOCKET] = { .type = NLA_U32, },
+ [OVPN_A_PEER_VPN_IPV4] = { .type = NLA_U32, },
+ [OVPN_A_PEER_VPN_IPV6] = NLA_POLICY_EXACT_LEN(16),
+ [OVPN_A_PEER_LOCAL_IP] = NLA_POLICY_MAX_LEN(16),
+ [OVPN_A_PEER_LOCAL_PORT] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_local_port_range),
+ [OVPN_A_PEER_KEEPALIVE_INTERVAL] = { .type = NLA_U32, },
+ [OVPN_A_PEER_KEEPALIVE_TIMEOUT] = { .type = NLA_U32, },
+ [OVPN_A_PEER_DEL_REASON] = NLA_POLICY_MAX(NLA_U32, 4),
+ [OVPN_A_PEER_KEYCONF] = NLA_POLICY_NESTED(ovpn_keyconf_nl_policy),
+ [OVPN_A_PEER_VPN_RX_BYTES] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_VPN_TX_BYTES] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_VPN_RX_PACKETS] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_VPN_TX_PACKETS] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_LINK_RX_BYTES] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_LINK_TX_BYTES] = { .type = NLA_UINT, },
+ [OVPN_A_PEER_LINK_RX_PACKETS] = { .type = NLA_U32, },
+ [OVPN_A_PEER_LINK_TX_PACKETS] = { .type = NLA_U32, },
+};
+
+/* OVPN_CMD_NEW_IFACE - do */
+static const struct nla_policy ovpn_new_iface_nl_policy[OVPN_A_MODE + 1] = {
+ [OVPN_A_IFNAME] = { .type = NLA_NUL_STRING, },
+ [OVPN_A_MODE] = NLA_POLICY_MAX(NLA_U32, 1),
+};
+
+/* OVPN_CMD_DEL_IFACE - do */
+static const struct nla_policy ovpn_del_iface_nl_policy[OVPN_A_IFINDEX + 1] = {
+ [OVPN_A_IFINDEX] = { .type = NLA_U32, },
+};
+
+/* OVPN_CMD_SET_PEER - do */
+static const struct nla_policy ovpn_set_peer_nl_policy[OVPN_A_PEER + 1] = {
+ [OVPN_A_IFINDEX] = { .type = NLA_U32, },
+ [OVPN_A_PEER] = NLA_POLICY_NESTED(ovpn_peer_nl_policy),
+};
+
+/* OVPN_CMD_GET_PEER - do */
+static const struct nla_policy ovpn_get_peer_do_nl_policy[OVPN_A_PEER + 1] = {
+ [OVPN_A_IFINDEX] = { .type = NLA_U32, },
+ [OVPN_A_PEER] = NLA_POLICY_NESTED(ovpn_peer_nl_policy),
+};
+
+/* OVPN_CMD_GET_PEER - dump */
+static const struct nla_policy ovpn_get_peer_dump_nl_policy[OVPN_A_IFINDEX + 1] = {
+ [OVPN_A_IFINDEX] = { .type = NLA_U32, },
+};
+
+/* OVPN_CMD_DEL_PEER - do */
+static const struct nla_policy ovpn_del_peer_nl_policy[OVPN_A_PEER + 1] = {
+ [OVPN_A_IFINDEX] = { .type = NLA_U32, },
+ [OVPN_A_PEER] = NLA_POLICY_NESTED(ovpn_peer_nl_policy),
+};
+
+/* OVPN_CMD_SET_KEY - do */
+static const struct nla_policy ovpn_set_key_nl_policy[OVPN_A_PEER + 1] = {
+ [OVPN_A_IFINDEX] = { .type = NLA_U32, },
+ [OVPN_A_PEER] = NLA_POLICY_NESTED(ovpn_peer_nl_policy),
+};
+
+/* OVPN_CMD_SWAP_KEYS - do */
+static const struct nla_policy ovpn_swap_keys_nl_policy[OVPN_A_PEER + 1] = {
+ [OVPN_A_IFINDEX] = { .type = NLA_U32, },
+ [OVPN_A_PEER] = NLA_POLICY_NESTED(ovpn_peer_nl_policy),
+};
+
+/* OVPN_CMD_DEL_KEY - do */
+static const struct nla_policy ovpn_del_key_nl_policy[OVPN_A_PEER + 1] = {
+ [OVPN_A_IFINDEX] = { .type = NLA_U32, },
+ [OVPN_A_PEER] = NLA_POLICY_NESTED(ovpn_peer_nl_policy),
+};
+
+/* Ops table for ovpn */
+static const struct genl_split_ops ovpn_nl_ops[] = {
+ {
+ .cmd = OVPN_CMD_NEW_IFACE,
+ .doit = ovpn_nl_new_iface_doit,
+ .policy = ovpn_new_iface_nl_policy,
+ .maxattr = OVPN_A_MODE,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = OVPN_CMD_DEL_IFACE,
+ .pre_doit = ovpn_nl_pre_doit,
+ .doit = ovpn_nl_del_iface_doit,
+ .post_doit = ovpn_nl_post_doit,
+ .policy = ovpn_del_iface_nl_policy,
+ .maxattr = OVPN_A_IFINDEX,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = OVPN_CMD_SET_PEER,
+ .pre_doit = ovpn_nl_pre_doit,
+ .doit = ovpn_nl_set_peer_doit,
+ .post_doit = ovpn_nl_post_doit,
+ .policy = ovpn_set_peer_nl_policy,
+ .maxattr = OVPN_A_PEER,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = OVPN_CMD_GET_PEER,
+ .pre_doit = ovpn_nl_pre_doit,
+ .doit = ovpn_nl_get_peer_doit,
+ .post_doit = ovpn_nl_post_doit,
+ .policy = ovpn_get_peer_do_nl_policy,
+ .maxattr = OVPN_A_PEER,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = OVPN_CMD_GET_PEER,
+ .dumpit = ovpn_nl_get_peer_dumpit,
+ .policy = ovpn_get_peer_dump_nl_policy,
+ .maxattr = OVPN_A_IFINDEX,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
+ },
+ {
+ .cmd = OVPN_CMD_DEL_PEER,
+ .pre_doit = ovpn_nl_pre_doit,
+ .doit = ovpn_nl_del_peer_doit,
+ .post_doit = ovpn_nl_post_doit,
+ .policy = ovpn_del_peer_nl_policy,
+ .maxattr = OVPN_A_PEER,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = OVPN_CMD_SET_KEY,
+ .pre_doit = ovpn_nl_pre_doit,
+ .doit = ovpn_nl_set_key_doit,
+ .post_doit = ovpn_nl_post_doit,
+ .policy = ovpn_set_key_nl_policy,
+ .maxattr = OVPN_A_PEER,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = OVPN_CMD_SWAP_KEYS,
+ .pre_doit = ovpn_nl_pre_doit,
+ .doit = ovpn_nl_swap_keys_doit,
+ .post_doit = ovpn_nl_post_doit,
+ .policy = ovpn_swap_keys_nl_policy,
+ .maxattr = OVPN_A_PEER,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = OVPN_CMD_DEL_KEY,
+ .pre_doit = ovpn_nl_pre_doit,
+ .doit = ovpn_nl_del_key_doit,
+ .post_doit = ovpn_nl_post_doit,
+ .policy = ovpn_del_key_nl_policy,
+ .maxattr = OVPN_A_PEER,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+};
+
+static const struct genl_multicast_group ovpn_nl_mcgrps[] = {
+ [OVPN_NLGRP_PEERS] = { "peers", },
+};
+
+struct genl_family ovpn_nl_family __ro_after_init = {
+ .name = OVPN_FAMILY_NAME,
+ .version = OVPN_FAMILY_VERSION,
+ .netnsok = true,
+ .parallel_ops = true,
+ .module = THIS_MODULE,
+ .split_ops = ovpn_nl_ops,
+ .n_split_ops = ARRAY_SIZE(ovpn_nl_ops),
+ .mcgrps = ovpn_nl_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(ovpn_nl_mcgrps),
+};
diff --git a/drivers/net/ovpn/netlink-gen.h b/drivers/net/ovpn/netlink-gen.h
new file mode 100644
index 000000000000..ce11f74e1b56
--- /dev/null
+++ b/drivers/net/ovpn/netlink-gen.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/ovpn.yaml */
+/* YNL-GEN kernel header */
+
+#ifndef _LINUX_OVPN_GEN_H
+#define _LINUX_OVPN_GEN_H
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/ovpn.h>
+
+/* Common nested types */
+extern const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR + 1];
+extern const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1];
+extern const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_LINK_TX_PACKETS + 1];
+
+int ovpn_nl_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
+ struct genl_info *info);
+void
+ovpn_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
+ struct genl_info *info);
+
+int ovpn_nl_new_iface_doit(struct sk_buff *skb, struct genl_info *info);
+int ovpn_nl_del_iface_doit(struct sk_buff *skb, struct genl_info *info);
+int ovpn_nl_set_peer_doit(struct sk_buff *skb, struct genl_info *info);
+int ovpn_nl_get_peer_doit(struct sk_buff *skb, struct genl_info *info);
+int ovpn_nl_get_peer_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
+int ovpn_nl_del_peer_doit(struct sk_buff *skb, struct genl_info *info);
+int ovpn_nl_set_key_doit(struct sk_buff *skb, struct genl_info *info);
+int ovpn_nl_swap_keys_doit(struct sk_buff *skb, struct genl_info *info);
+int ovpn_nl_del_key_doit(struct sk_buff *skb, struct genl_info *info);
+
+enum {
+ OVPN_NLGRP_PEERS,
+};
+
+extern struct genl_family ovpn_nl_family;
+
+#endif /* _LINUX_OVPN_GEN_H */
diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
new file mode 100644
index 000000000000..3585c7401b22
--- /dev/null
+++ b/drivers/net/ovpn/netlink.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <linux/netdevice.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/ovpn.h>
+
+#include "ovpnstruct.h"
+#include "main.h"
+#include "io.h"
+#include "netlink.h"
+#include "netlink-gen.h"
+
+MODULE_ALIAS_GENL_FAMILY(OVPN_FAMILY_NAME);
+
+/**
+ * ovpn_get_dev_from_attrs - retrieve the netdevice a netlink message is
+ * targeting
+ * @net: network namespace where to look for the interface
+ * @info: generic netlink info from the user request
+ *
+ * Return: the netdevice, if found, or an error otherwise
+ */
+static struct net_device *
+ovpn_get_dev_from_attrs(struct net *net, struct genl_info *info)
+{
+ struct net_device *dev;
+ int ifindex;
+
+ if (GENL_REQ_ATTR_CHECK(info, OVPN_A_IFINDEX))
+ return ERR_PTR(-EINVAL);
+
+ ifindex = nla_get_u32(info->attrs[OVPN_A_IFINDEX]);
+
+ dev = dev_get_by_index(net, ifindex);
+ if (!dev) {
+ NL_SET_ERR_MSG_MOD(info->extack,
+ "ifindex does not match any interface");
+ return ERR_PTR(-ENODEV);
+ }
+
+ if (!ovpn_dev_is_valid(dev))
+ goto err_put_dev;
+
+ return dev;
+
+err_put_dev:
+ netdev_put(dev, NULL);
+
+ NL_SET_ERR_MSG_MOD(info->extack, "specified interface is not ovpn");
+ NL_SET_BAD_ATTR(info->extack, info->attrs[OVPN_A_IFINDEX]);
+
+ return ERR_PTR(-EINVAL);
+}
+
+int ovpn_nl_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct net *net = genl_info_net(info);
+ struct net_device *dev = ovpn_get_dev_from_attrs(net, info);
+
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ info->user_ptr[0] = netdev_priv(dev);
+
+ return 0;
+}
+
+void ovpn_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct ovpn_struct *ovpn = info->user_ptr[0];
+
+ if (ovpn)
+ netdev_put(ovpn->dev, NULL);
+}
+
+int ovpn_nl_new_iface_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ return -EOPNOTSUPP;
+}
+
+int ovpn_nl_del_iface_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ return -EOPNOTSUPP;
+}
+
+int ovpn_nl_set_peer_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ return -EOPNOTSUPP;
+}
+
+int ovpn_nl_get_peer_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ return -EOPNOTSUPP;
+}
+
+int ovpn_nl_get_peer_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ return -EOPNOTSUPP;
+}
+
+int ovpn_nl_del_peer_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ return -EOPNOTSUPP;
+}
+
+int ovpn_nl_set_key_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ return -EOPNOTSUPP;
+}
+
+int ovpn_nl_swap_keys_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ return -EOPNOTSUPP;
+}
+
+int ovpn_nl_del_key_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ return -EOPNOTSUPP;
+}
+
+/**
+ * ovpn_nl_register - perform any needed registration in the NL subsustem
+ *
+ * Return: 0 on success, a negative error code otherwise
+ */
+int __init ovpn_nl_register(void)
+{
+ int ret = genl_register_family(&ovpn_nl_family);
+
+ if (ret) {
+ pr_err("ovpn: genl_register_family failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * ovpn_nl_unregister - undo any module wide netlink registration
+ */
+void ovpn_nl_unregister(void)
+{
+ genl_unregister_family(&ovpn_nl_family);
+}
diff --git a/drivers/net/ovpn/netlink.h b/drivers/net/ovpn/netlink.h
new file mode 100644
index 000000000000..9e87cf11d1e9
--- /dev/null
+++ b/drivers/net/ovpn/netlink.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_NETLINK_H_
+#define _NET_OVPN_NETLINK_H_
+
+int ovpn_nl_register(void);
+void ovpn_nl_unregister(void);
+
+#endif /* _NET_OVPN_NETLINK_H_ */
diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h
new file mode 100644
index 000000000000..ff248cad1401
--- /dev/null
+++ b/drivers/net/ovpn/ovpnstruct.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2019-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_OVPNSTRUCT_H_
+#define _NET_OVPN_OVPNSTRUCT_H_
+
+/**
+ * struct ovpn_struct - per ovpn interface state
+ * @dev: the actual netdev representing the tunnel
+ */
+struct ovpn_struct {
+ struct net_device *dev;
+};
+
+#endif /* _NET_OVPN_OVPNSTRUCT_H_ */
diff --git a/include/uapi/linux/ovpn.h b/include/uapi/linux/ovpn.h
new file mode 100644
index 000000000000..677a8105b61b
--- /dev/null
+++ b/include/uapi/linux/ovpn.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/ovpn.yaml */
+/* YNL-GEN uapi header */
+
+#ifndef _UAPI_LINUX_OVPN_H
+#define _UAPI_LINUX_OVPN_H
+
+#define OVPN_FAMILY_NAME "ovpn"
+#define OVPN_FAMILY_VERSION 1
+
+#define OVPN_NONCE_TAIL_SIZE 8
+
+enum ovpn_cipher_alg {
+ OVPN_CIPHER_ALG_NONE,
+ OVPN_CIPHER_ALG_AES_GCM,
+ OVPN_CIPHER_ALG_CHACHA20_POLY1305,
+};
+
+enum ovpn_del_peer_reason {
+ OVPN_DEL_PEER_REASON_TEARDOWN,
+ OVPN_DEL_PEER_REASON_USERSPACE,
+ OVPN_DEL_PEER_REASON_EXPIRED,
+ OVPN_DEL_PEER_REASON_TRANSPORT_ERROR,
+ OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT,
+};
+
+enum ovpn_key_slot {
+ OVPN_KEY_SLOT_PRIMARY,
+ OVPN_KEY_SLOT_SECONDARY,
+};
+
+enum ovpn_mode {
+ OVPN_MODE_P2P,
+ OVPN_MODE_MP,
+};
+
+enum {
+ OVPN_A_PEER_ID = 1,
+ OVPN_A_PEER_SOCKADDR_REMOTE,
+ OVPN_A_PEER_SOCKET,
+ OVPN_A_PEER_VPN_IPV4,
+ OVPN_A_PEER_VPN_IPV6,
+ OVPN_A_PEER_LOCAL_IP,
+ OVPN_A_PEER_LOCAL_PORT,
+ OVPN_A_PEER_KEEPALIVE_INTERVAL,
+ OVPN_A_PEER_KEEPALIVE_TIMEOUT,
+ OVPN_A_PEER_DEL_REASON,
+ OVPN_A_PEER_KEYCONF,
+ OVPN_A_PEER_VPN_RX_BYTES,
+ OVPN_A_PEER_VPN_TX_BYTES,
+ OVPN_A_PEER_VPN_RX_PACKETS,
+ OVPN_A_PEER_VPN_TX_PACKETS,
+ OVPN_A_PEER_LINK_RX_BYTES,
+ OVPN_A_PEER_LINK_TX_BYTES,
+ OVPN_A_PEER_LINK_RX_PACKETS,
+ OVPN_A_PEER_LINK_TX_PACKETS,
+
+ __OVPN_A_PEER_MAX,
+ OVPN_A_PEER_MAX = (__OVPN_A_PEER_MAX - 1)
+};
+
+enum {
+ OVPN_A_KEYCONF_SLOT = 1,
+ OVPN_A_KEYCONF_KEY_ID,
+ OVPN_A_KEYCONF_CIPHER_ALG,
+ OVPN_A_KEYCONF_ENCRYPT_DIR,
+ OVPN_A_KEYCONF_DECRYPT_DIR,
+
+ __OVPN_A_KEYCONF_MAX,
+ OVPN_A_KEYCONF_MAX = (__OVPN_A_KEYCONF_MAX - 1)
+};
+
+enum {
+ OVPN_A_KEYDIR_CIPHER_KEY = 1,
+ OVPN_A_KEYDIR_NONCE_TAIL,
+
+ __OVPN_A_KEYDIR_MAX,
+ OVPN_A_KEYDIR_MAX = (__OVPN_A_KEYDIR_MAX - 1)
+};
+
+enum {
+ OVPN_A_IFINDEX = 1,
+ OVPN_A_IFNAME,
+ OVPN_A_MODE,
+ OVPN_A_PEER,
+
+ __OVPN_A_MAX,
+ OVPN_A_MAX = (__OVPN_A_MAX - 1)
+};
+
+enum {
+ OVPN_CMD_NEW_IFACE = 1,
+ OVPN_CMD_DEL_IFACE,
+ OVPN_CMD_SET_PEER,
+ OVPN_CMD_GET_PEER,
+ OVPN_CMD_DEL_PEER,
+ OVPN_CMD_SET_KEY,
+ OVPN_CMD_SWAP_KEYS,
+ OVPN_CMD_DEL_KEY,
+
+ __OVPN_CMD_MAX,
+ OVPN_CMD_MAX = (__OVPN_CMD_MAX - 1)
+};
+
+#define OVPN_MCGRP_PEERS "peers"
+
+#endif /* _UAPI_LINUX_OVPN_H */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 05/25] ovpn: add basic interface creation/destruction/management routines
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (3 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 04/25] ovpn: add basic netlink support Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-06-28 22:11 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 06/25] ovpn: implement interface creation/destruction via netlink Antonio Quartulli
` (19 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
Add basic infrastructure for handling ovpn interfaces.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/main.c | 162 +++++++++++++++++++++++++++++++++-
drivers/net/ovpn/main.h | 10 +++
drivers/net/ovpn/ovpnstruct.h | 8 ++
drivers/net/ovpn/packet.h | 40 +++++++++
4 files changed, 217 insertions(+), 3 deletions(-)
create mode 100644 drivers/net/ovpn/packet.h
diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
index 7c35697cb596..7e3e9963d2fc 100644
--- a/drivers/net/ovpn/main.c
+++ b/drivers/net/ovpn/main.c
@@ -10,19 +10,62 @@
#include <linux/genetlink.h>
#include <linux/module.h>
#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
#include <linux/version.h>
+#include <net/ip.h>
#include <net/rtnetlink.h>
+#include <uapi/linux/if_arp.h>
#include <uapi/linux/ovpn.h>
#include "ovpnstruct.h"
#include "main.h"
#include "netlink.h"
#include "io.h"
+#include "packet.h"
/* Driver info */
#define DRV_DESCRIPTION "OpenVPN data channel offload (ovpn)"
#define DRV_COPYRIGHT "(C) 2020-2024 OpenVPN, Inc."
+/**
+ * ovpn_struct_init - Initialize the netdevice private area
+ * @dev: the device to initialize
+ * @mode: device operation mode (i.e. p2p, mp, ..)
+ *
+ * Return: 0 on success or a negative error code otherwise
+ */
+static int ovpn_struct_init(struct net_device *dev, enum ovpn_mode mode)
+{
+ struct ovpn_struct *ovpn = netdev_priv(dev);
+
+ ovpn->dev = dev;
+ ovpn->mode = mode;
+
+ return 0;
+}
+
+static void ovpn_struct_free(struct net_device *net)
+{
+}
+
+static int ovpn_net_open(struct net_device *dev)
+{
+ netif_tx_start_all_queues(dev);
+ return 0;
+}
+
+static int ovpn_net_stop(struct net_device *dev)
+{
+ netif_tx_stop_all_queues(dev);
+ return 0;
+}
+
+static const struct net_device_ops ovpn_netdev_ops = {
+ .ndo_open = ovpn_net_open,
+ .ndo_stop = ovpn_net_stop,
+ .ndo_start_xmit = ovpn_net_xmit,
+};
+
/**
* ovpn_dev_is_valid - check if the netdevice is of type 'ovpn'
* @dev: the interface to check
@@ -41,30 +84,143 @@ static struct rtnl_link_ops ovpn_link_ops = {
.kind = OVPN_FAMILY_NAME,
};
+static void ovpn_setup(struct net_device *dev)
+{
+ /* compute the overhead considering AEAD encryption */
+ const int overhead = sizeof(u32) + NONCE_WIRE_SIZE + 16 +
+ sizeof(struct udphdr) +
+ max(sizeof(struct ipv6hdr), sizeof(struct iphdr));
+
+ netdev_features_t feat = NETIF_F_SG | NETIF_F_LLTX |
+ NETIF_F_HW_CSUM | NETIF_F_RXCSUM |
+ NETIF_F_GSO | NETIF_F_GSO_SOFTWARE |
+ NETIF_F_HIGHDMA;
+
+ dev->needs_free_netdev = true;
+
+ dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS;
+
+ dev->netdev_ops = &ovpn_netdev_ops;
+ dev->rtnl_link_ops = &ovpn_link_ops;
+
+ dev->priv_destructor = ovpn_struct_free;
+
+ dev->hard_header_len = 0;
+ dev->addr_len = 0;
+ dev->mtu = ETH_DATA_LEN - overhead;
+ dev->min_mtu = IPV4_MIN_MTU;
+ dev->max_mtu = IP_MAX_MTU - overhead;
+
+ dev->type = ARPHRD_NONE;
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+
+ dev->features |= feat;
+ dev->hw_features |= feat;
+ dev->hw_enc_features |= feat;
+
+ dev->needed_headroom = OVPN_HEAD_ROOM;
+ dev->needed_tailroom = OVPN_MAX_PADDING;
+}
+
+/**
+ * ovpn_iface_create - create and initialize a new 'ovpn' netdevice
+ * @name: the name of the new device
+ * @mode: the OpenVPN mode to set this device to
+ * @net: the netns this device should be created in
+ *
+ * A new netdevice is created and registered.
+ * Its private area is initialized with an empty ovpn_struct object.
+ *
+ * Return: a pointer to the new device on success or a negative error code
+ * otherwise
+ */
+struct net_device *ovpn_iface_create(const char *name, enum ovpn_mode mode,
+ struct net *net)
+{
+ struct net_device *dev;
+ int ret;
+
+ dev = alloc_netdev(sizeof(struct ovpn_struct), name, NET_NAME_USER,
+ ovpn_setup);
+ if (!dev)
+ return ERR_PTR(-ENOMEM);
+
+ dev_net_set(dev, net);
+
+ ret = ovpn_struct_init(dev, mode);
+ if (ret < 0)
+ goto err;
+
+ rtnl_lock();
+ ret = register_netdevice(dev);
+ if (ret < 0) {
+ netdev_err(dev, "cannot register interface: %d\n", ret);
+ rtnl_unlock();
+ goto err;
+ }
+ /* turn carrier explicitly off after registration, this way state is
+ * clearly defined
+ */
+ netif_carrier_off(dev);
+ rtnl_unlock();
+
+ return dev;
+
+err:
+ free_netdev(dev);
+ return ERR_PTR(ret);
+}
+
+/**
+ * ovpn_iface_destruct - tear down netdevice
+ * @ovpn: the ovpn instance objected related to the interface to tear down
+ *
+ * This function takes care of tearing down an ovpn device and can be invoked
+ * internally or upon UNREGISTER netdev event
+ */
+void ovpn_iface_destruct(struct ovpn_struct *ovpn)
+{
+ ASSERT_RTNL();
+
+ netif_carrier_off(ovpn->dev);
+
+ ovpn->registered = false;
+}
+
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);
+ struct ovpn_struct *ovpn;
if (!ovpn_dev_is_valid(dev))
return NOTIFY_DONE;
+ ovpn = netdev_priv(dev);
+
switch (state) {
case NETDEV_REGISTER:
- /* add device to internal list for later destruction upon
- * unregistration
- */
+ ovpn->registered = true;
break;
case NETDEV_UNREGISTER:
+ /* twiddle thumbs on netns device moves */
+ if (dev->reg_state != NETREG_UNREGISTERING)
+ break;
+
/* can be delivered multiple times, so check registered flag,
* then destroy the interface
*/
+ if (!ovpn->registered)
+ return NOTIFY_DONE;
+
+ ovpn_iface_destruct(ovpn);
break;
case NETDEV_POST_INIT:
case NETDEV_GOING_DOWN:
case NETDEV_DOWN:
case NETDEV_UP:
case NETDEV_PRE_UP:
+ break;
default:
return NOTIFY_DONE;
}
diff --git a/drivers/net/ovpn/main.h b/drivers/net/ovpn/main.h
index a3215316c49b..4dfcba9deb59 100644
--- a/drivers/net/ovpn/main.h
+++ b/drivers/net/ovpn/main.h
@@ -10,6 +10,16 @@
#ifndef _NET_OVPN_MAIN_H_
#define _NET_OVPN_MAIN_H_
+struct net_device *ovpn_iface_create(const char *name, enum ovpn_mode mode,
+ struct net *net);
+void ovpn_iface_destruct(struct ovpn_struct *ovpn);
bool ovpn_dev_is_valid(const struct net_device *dev);
+#define SKB_HEADER_LEN \
+ (max(sizeof(struct iphdr), sizeof(struct ipv6hdr)) + \
+ sizeof(struct udphdr) + NET_SKB_PAD)
+
+#define OVPN_HEAD_ROOM ALIGN(16 + SKB_HEADER_LEN, 4)
+#define OVPN_MAX_PADDING 16
+
#endif /* _NET_OVPN_MAIN_H_ */
diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h
index ff248cad1401..ee05b8a2c61d 100644
--- a/drivers/net/ovpn/ovpnstruct.h
+++ b/drivers/net/ovpn/ovpnstruct.h
@@ -10,12 +10,20 @@
#ifndef _NET_OVPN_OVPNSTRUCT_H_
#define _NET_OVPN_OVPNSTRUCT_H_
+#include <uapi/linux/ovpn.h>
+
/**
* struct ovpn_struct - per ovpn interface state
* @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, ..)
+ * @dev_list: entry for the module wide device list
*/
struct ovpn_struct {
struct net_device *dev;
+ bool registered;
+ enum ovpn_mode mode;
+ struct list_head dev_list;
};
#endif /* _NET_OVPN_OVPNSTRUCT_H_ */
diff --git a/drivers/net/ovpn/packet.h b/drivers/net/ovpn/packet.h
new file mode 100644
index 000000000000..7ed146f5932a
--- /dev/null
+++ b/drivers/net/ovpn/packet.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ * James Yonan <james@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_PACKET_H_
+#define _NET_OVPN_PACKET_H_
+
+/* When the OpenVPN protocol is ran in AEAD mode, use
+ * the OpenVPN packet ID as the AEAD nonce:
+ *
+ * 00000005 521c3b01 4308c041
+ * [seq # ] [ nonce_tail ]
+ * [ 12-byte full IV ] -> NONCE_SIZE
+ * [4-bytes -> NONCE_WIRE_SIZE
+ * on wire]
+ */
+
+/* OpenVPN nonce size */
+#define NONCE_SIZE 12
+
+/* OpenVPN nonce size reduced by 8-byte nonce tail -- this is the
+ * size of the AEAD Associated Data (AD) sent over the wire
+ * and is normally the head of the IV
+ */
+#define NONCE_WIRE_SIZE (NONCE_SIZE - sizeof(struct ovpn_nonce_tail))
+
+/* Last 8 bytes of AEAD nonce
+ * Provided by userspace and usually derived from
+ * key material generated during TLS handshake
+ */
+struct ovpn_nonce_tail {
+ u8 u8[OVPN_NONCE_TAIL_SIZE];
+};
+
+#endif /* _NET_OVPN_PACKET_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 06/25] ovpn: implement interface creation/destruction via netlink
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (4 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 05/25] ovpn: add basic interface creation/destruction/management routines Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-03 21:27 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 07/25] ovpn: keep carrier always on Antonio Quartulli
` (18 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
Allow userspace to create and destroy an interface using netlink
commands.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/main.h | 2 ++
drivers/net/ovpn/netlink.c | 53 ++++++++++++++++++++++++++++++++++++--
2 files changed, 53 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ovpn/main.h b/drivers/net/ovpn/main.h
index 4dfcba9deb59..c664d9c65573 100644
--- a/drivers/net/ovpn/main.h
+++ b/drivers/net/ovpn/main.h
@@ -10,6 +10,8 @@
#ifndef _NET_OVPN_MAIN_H_
#define _NET_OVPN_MAIN_H_
+#define OVPN_DEFAULT_IFNAME "ovpn%d"
+
struct net_device *ovpn_iface_create(const char *name, enum ovpn_mode mode,
struct net *net);
void ovpn_iface_destruct(struct ovpn_struct *ovpn);
diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
index 3585c7401b22..ecac4721f0c6 100644
--- a/drivers/net/ovpn/netlink.c
+++ b/drivers/net/ovpn/netlink.c
@@ -7,6 +7,7 @@
*/
#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
#include <net/genetlink.h>
#include <uapi/linux/ovpn.h>
@@ -84,12 +85,60 @@ void ovpn_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
int ovpn_nl_new_iface_doit(struct sk_buff *skb, struct genl_info *info)
{
- return -EOPNOTSUPP;
+ const char *ifname = OVPN_DEFAULT_IFNAME;
+ enum ovpn_mode mode = OVPN_MODE_P2P;
+ struct net_device *dev;
+ struct sk_buff *msg;
+ void *hdr;
+
+ if (info->attrs[OVPN_A_IFNAME])
+ ifname = nla_data(info->attrs[OVPN_A_IFNAME]);
+
+ if (info->attrs[OVPN_A_MODE]) {
+ mode = nla_get_u32(info->attrs[OVPN_A_MODE]);
+ pr_debug("ovpn: setting device (%s) mode: %u\n", ifname, mode);
+ }
+
+ dev = ovpn_iface_create(ifname, mode, genl_info_net(info));
+ if (IS_ERR(dev)) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "error while creating interface: %ld",
+ PTR_ERR(dev));
+ return PTR_ERR(dev);
+ }
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_iput(msg, info);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return -ENOBUFS;
+ }
+
+ if (nla_put_string(msg, OVPN_A_IFNAME, dev->name)) {
+ genlmsg_cancel(msg, hdr);
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+ }
+
+ genlmsg_end(msg, hdr);
+
+ return genlmsg_reply(msg, info);
}
int ovpn_nl_del_iface_doit(struct sk_buff *skb, struct genl_info *info)
{
- return -EOPNOTSUPP;
+ struct ovpn_struct *ovpn = info->user_ptr[0];
+
+ rtnl_lock();
+ ovpn_iface_destruct(ovpn);
+ unregister_netdevice(ovpn->dev);
+ netdev_put(ovpn->dev, NULL);
+ rtnl_unlock();
+
+ return 0;
}
int ovpn_nl_set_peer_doit(struct sk_buff *skb, struct genl_info *info)
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 07/25] ovpn: keep carrier always on
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (5 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 06/25] ovpn: implement interface creation/destruction via netlink Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-06-27 16:25 ` Andrew Lunn
2024-06-27 13:08 ` [PATCH net-next v5 08/25] ovpn: introduce the ovpn_peer object Antonio Quartulli
` (17 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
An ovpn interface will keep carrier always on and let the user
decide when an interface should be considered disconnected.
This way, even if an ovpn interface is not connected to any peer,
it can still retain all IPs and routes and thus prevent any data
leak.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/main.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
index 7e3e9963d2fc..d0abe8b91a86 100644
--- a/drivers/net/ovpn/main.c
+++ b/drivers/net/ovpn/main.c
@@ -50,6 +50,13 @@ static void ovpn_struct_free(struct net_device *net)
static int ovpn_net_open(struct net_device *dev)
{
+ /* ovpn keeps the carrier always on to avoid losing IP or route
+ * configuration upon disconnection. This way it can prevent leaks
+ * of traffic outside of the VPN tunnel.
+ * The user may override this behaviour by tearing down the interface
+ * manually.
+ */
+ netif_carrier_on(dev);
netif_tx_start_all_queues(dev);
return 0;
}
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 08/25] ovpn: introduce the ovpn_peer object
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (6 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 07/25] ovpn: keep carrier always on Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-03 21:37 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 09/25] ovpn: introduce the ovpn_socket object Antonio Quartulli
` (16 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
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 <antonio@openvpn.net>
---
drivers/net/ovpn/Makefile | 2 +
drivers/net/ovpn/bind.c | 58 ++++++
drivers/net/ovpn/bind.h | 119 +++++++++++
drivers/net/ovpn/main.c | 12 ++
drivers/net/ovpn/main.h | 2 +
drivers/net/ovpn/ovpnstruct.h | 4 +
drivers/net/ovpn/peer.c | 358 ++++++++++++++++++++++++++++++++++
drivers/net/ovpn/peer.h | 82 ++++++++
8 files changed, 637 insertions(+)
create mode 100644 drivers/net/ovpn/bind.c
create mode 100644 drivers/net/ovpn/bind.h
create mode 100644 drivers/net/ovpn/peer.c
create mode 100644 drivers/net/ovpn/peer.h
diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile
index 201dc001419f..ce13499b3e17 100644
--- a/drivers/net/ovpn/Makefile
+++ b/drivers/net/ovpn/Makefile
@@ -7,7 +7,9 @@
# Author: Antonio Quartulli <antonio@openvpn.net>
obj-$(CONFIG_OVPN) := ovpn.o
+ovpn-y += bind.o
ovpn-y += main.o
ovpn-y += io.o
ovpn-y += netlink.o
ovpn-y += netlink-gen.o
+ovpn-y += peer.o
diff --git a/drivers/net/ovpn/bind.c b/drivers/net/ovpn/bind.c
new file mode 100644
index 000000000000..07b22753e99f
--- /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 <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <linux/netdevice.h>
+#include <linux/socket.h>
+
+#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 *ss)
+{
+ struct ovpn_bind *bind;
+ size_t sa_len;
+
+ if (ss->ss_family == AF_INET)
+ sa_len = sizeof(struct sockaddr_in);
+ else if (ss->ss_family == AF_INET6)
+ sa_len = sizeof(struct sockaddr_in6);
+ else
+ return ERR_PTR(-EAFNOSUPPORT);
+
+ bind = kzalloc(sizeof(*bind), GFP_ATOMIC);
+ if (unlikely(!bind))
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(&bind->sa, 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 = 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 000000000000..4add92454d28
--- /dev/null
+++ b/drivers/net/ovpn/bind.h
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2012-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_OVPNBIND_H_
+#define _NET_OVPN_OVPNBIND_H_
+
+#include <net/ip.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/rcupdate.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+
+struct ovpn_peer;
+
+/**
+ * struct ovpn_sockaddr - basic transport layer address
+ * @in4: IPv4 address
+ * @in6: IPv6 address
+ */
+struct ovpn_sockaddr {
+ union {
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ };
+};
+
+/**
+ * struct ovpn_bind - remote peer binding
+ * @sa: 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 {
+ struct ovpn_sockaddr sa; /* remote sockaddr */
+
+ union {
+ struct in_addr ipv4;
+ struct in6_addr ipv6;
+ } local;
+
+ struct rcu_head rcu;
+};
+
+/**
+ * skb_protocol_to_family - translate skb->protocol to AF_INET or AF_INET6
+ * @skb: the packet sk_buff to inspect
+ *
+ * Return: AF_INET, AF_INET6 or 0 in case of unknown protocol
+ */
+static inline unsigned short skb_protocol_to_family(const struct sk_buff *skb)
+{
+ switch (skb->protocol) {
+ case htons(ETH_P_IP):
+ return AF_INET;
+ case htons(ETH_P_IPV6):
+ return AF_INET6;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * 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 unsigned short family = skb_protocol_to_family(skb);
+ const struct ovpn_sockaddr *sa;
+
+ if (unlikely(!bind))
+ return false;
+
+ sa = &bind->sa;
+
+ if (unlikely(sa->in4.sin_family != family))
+ return false;
+
+ switch (family) {
+ case AF_INET:
+ if (unlikely(sa->in4.sin_addr.s_addr != ip_hdr(skb)->saddr))
+ return false;
+
+ if (unlikely(sa->in4.sin_port != udp_hdr(skb)->source))
+ return false;
+ break;
+ case AF_INET6:
+ if (unlikely(!ipv6_addr_equal(&sa->in6.sin6_addr,
+ &ipv6_hdr(skb)->saddr)))
+ return false;
+
+ if (unlikely(sa->in6.sin6_port != udp_hdr(skb)->source))
+ return false;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *sa);
+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 d0abe8b91a86..8a57088491b5 100644
--- a/drivers/net/ovpn/main.c
+++ b/drivers/net/ovpn/main.c
@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
+//#include <linux/rcupdate.h>
#include <linux/version.h>
#include <net/ip.h>
#include <net/rtnetlink.h>
@@ -22,6 +23,7 @@
#include "netlink.h"
#include "io.h"
#include "packet.h"
+#include "peer.h"
/* Driver info */
#define DRV_DESCRIPTION "OpenVPN data channel offload (ovpn)"
@@ -40,6 +42,7 @@ static int ovpn_struct_init(struct net_device *dev, enum ovpn_mode mode)
ovpn->dev = dev;
ovpn->mode = mode;
+ spin_lock_init(&ovpn->lock);
return 0;
}
@@ -48,6 +51,11 @@ static void ovpn_struct_free(struct net_device *net)
{
}
+static int ovpn_net_init(struct net_device *dev)
+{
+ return 0;
+}
+
static int ovpn_net_open(struct net_device *dev)
{
/* ovpn keeps the carrier always on to avoid losing IP or route
@@ -68,6 +76,7 @@ static int ovpn_net_stop(struct net_device *dev)
}
static const struct net_device_ops ovpn_netdev_ops = {
+ .ndo_init = ovpn_net_init,
.ndo_open = ovpn_net_open,
.ndo_stop = ovpn_net_stop,
.ndo_start_xmit = ovpn_net_xmit,
@@ -192,6 +201,9 @@ void ovpn_iface_destruct(struct ovpn_struct *ovpn)
netif_carrier_off(ovpn->dev);
ovpn->registered = false;
+
+ if (ovpn->mode == OVPN_MODE_P2P)
+ ovpn_peer_release_p2p(ovpn);
}
static int ovpn_netdev_notifier_call(struct notifier_block *nb,
diff --git a/drivers/net/ovpn/main.h b/drivers/net/ovpn/main.h
index c664d9c65573..7e957248af6a 100644
--- a/drivers/net/ovpn/main.h
+++ b/drivers/net/ovpn/main.h
@@ -24,4 +24,6 @@ bool ovpn_dev_is_valid(const struct net_device *dev);
#define OVPN_HEAD_ROOM ALIGN(16 + SKB_HEADER_LEN, 4)
#define OVPN_MAX_PADDING 16
+#define OVPN_QUEUE_LEN 1024
+
#endif /* _NET_OVPN_MAIN_H_ */
diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h
index ee05b8a2c61d..4d1616628430 100644
--- a/drivers/net/ovpn/ovpnstruct.h
+++ b/drivers/net/ovpn/ovpnstruct.h
@@ -17,12 +17,16 @@
* @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
* @dev_list: entry for the module wide device list
*/
struct ovpn_struct {
struct net_device *dev;
bool registered;
enum ovpn_mode mode;
+ spinlock_t lock; /* protect writing to the ovpn_struct object */
+ struct ovpn_peer __rcu *peer;
struct list_head dev_list;
};
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
new file mode 100644
index 000000000000..88ed93e1a0e5
--- /dev/null
+++ b/drivers/net/ovpn/peer.c
@@ -0,0 +1,358 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <linux/skbuff.h>
+#include <linux/list.h>
+#include <linux/workqueue.h>
+
+#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_struct *ovpn, u32 id)
+{
+ struct ovpn_peer *peer;
+ int ret;
+
+ /* alloc and init peer object */
+ peer = kzalloc(sizeof(*peer), GFP_KERNEL);
+ if (!peer)
+ return ERR_PTR(-ENOMEM);
+
+ peer->id = id;
+ peer->halt = false;
+ peer->ovpn = ovpn;
+
+ peer->vpn_addrs.ipv4.s_addr = htonl(INADDR_ANY);
+ peer->vpn_addrs.ipv6 = in6addr_any;
+
+ RCU_INIT_POINTER(peer->bind, NULL);
+ spin_lock_init(&peer->lock);
+ kref_init(&peer->refcount);
+
+ ret = dst_cache_init(&peer->dst_cache, GFP_KERNEL);
+ if (ret < 0) {
+ netdev_err(ovpn->dev, "%s: cannot initialize dst cache\n",
+ __func__);
+ kfree(peer);
+ return ERR_PTR(ret);
+ }
+
+ netdev_hold(ovpn->dev, NULL, GFP_KERNEL);
+
+ return peer;
+}
+
+#define ovpn_peer_index(_tbl, _key, _key_len) \
+ (jhash(_key, _key_len, 0) % HASH_SIZE(_tbl)) \
+
+/**
+ * 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);
+
+ dst_cache_destroy(&peer->dst_cache);
+}
+
+/**
+ * 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 = container_of(kref, struct ovpn_peer, refcount);
+
+ ovpn_peer_release(peer);
+ netdev_put(peer->ovpn->dev, NULL);
+ kfree_rcu(peer, rcu);
+}
+
+/**
+ * ovpn_peer_skb_to_sockaddr - fill sockaddr with skb source address
+ * @skb: the packet to extract data from
+ * @ss: the sockaddr to fill
+ *
+ * Return: true on success or false otherwise
+ */
+static bool ovpn_peer_skb_to_sockaddr(struct sk_buff *skb,
+ struct sockaddr_storage *ss)
+{
+ struct sockaddr_in6 *sa6;
+ struct sockaddr_in *sa4;
+
+ ss->ss_family = skb_protocol_to_family(skb);
+ switch (ss->ss_family) {
+ case AF_INET:
+ sa4 = (struct sockaddr_in *)ss;
+ sa4->sin_family = AF_INET;
+ sa4->sin_addr.s_addr = ip_hdr(skb)->saddr;
+ sa4->sin_port = udp_hdr(skb)->source;
+ break;
+ case AF_INET6:
+ sa6 = (struct sockaddr_in6 *)ss;
+ sa6->sin6_family = AF_INET6;
+ sa6->sin6_addr = ipv6_hdr(skb)->saddr;
+ sa6->sin6_port = udp_hdr(skb)->source;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * 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 = rcu_dereference(peer->bind);
+ struct sockaddr_in6 *sa6;
+ struct sockaddr_in *sa4;
+
+ if (unlikely(!bind))
+ return false;
+
+ if (ss->ss_family != bind->sa.in4.sin_family)
+ return false;
+
+ switch (ss->ss_family) {
+ case AF_INET:
+ sa4 = (struct sockaddr_in *)ss;
+ if (sa4->sin_addr.s_addr != bind->sa.in4.sin_addr.s_addr)
+ return false;
+ if (sa4->sin_port != bind->sa.in4.sin_port)
+ return false;
+ break;
+ case AF_INET6:
+ sa6 = (struct sockaddr_in6 *)ss;
+ if (!ipv6_addr_equal(&sa6->sin6_addr, &bind->sa.in6.sin6_addr))
+ return false;
+ if (sa6->sin6_port != bind->sa.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 P2P
+ * 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_struct *ovpn,
+ struct sockaddr_storage *ss)
+{
+ struct ovpn_peer *tmp, *peer = NULL;
+
+ rcu_read_lock();
+ tmp = rcu_dereference(ovpn->peer);
+ if (likely(tmp && ovpn_peer_transp_match(tmp, ss) &&
+ ovpn_peer_hold(tmp)))
+ peer = 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_struct *ovpn,
+ struct sk_buff *skb)
+{
+ struct ovpn_peer *peer = NULL;
+ struct sockaddr_storage ss = { 0 };
+
+ if (unlikely(!ovpn_peer_skb_to_sockaddr(skb, &ss)))
+ return NULL;
+
+ if (ovpn->mode == OVPN_MODE_P2P)
+ peer = 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_struct *ovpn,
+ u32 peer_id)
+{
+ struct ovpn_peer *tmp, *peer = NULL;
+
+ rcu_read_lock();
+ tmp = rcu_dereference(ovpn->peer);
+ if (likely(tmp && tmp->id == peer_id && ovpn_peer_hold(tmp)))
+ peer = 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_struct *ovpn, u32 peer_id)
+{
+ struct ovpn_peer *peer = NULL;
+
+ if (ovpn->mode == OVPN_MODE_P2P)
+ peer = ovpn_peer_get_by_id_p2p(ovpn, peer_id);
+
+ return peer;
+}
+
+/**
+ * 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_struct *ovpn, struct ovpn_peer *peer)
+{
+ 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 = rcu_dereference_protected(ovpn->peer,
+ lockdep_is_held(&ovpn->lock));
+ if (tmp) {
+ tmp->delete_reason = OVPN_DEL_PEER_REASON_TEARDOWN;
+ ovpn_peer_put(tmp);
+ }
+
+ rcu_assign_pointer(ovpn->peer, peer);
+ 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_struct *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;
+ int ret = -ENOENT;
+
+ spin_lock_bh(&peer->ovpn->lock);
+ tmp = rcu_dereference_protected(peer->ovpn->peer,
+ lockdep_is_held(&peer->ovpn->lock));
+ if (tmp != peer) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ goto unlock;
+ }
+
+ ovpn_peer_put(tmp);
+ tmp->delete_reason = reason;
+ RCU_INIT_POINTER(peer->ovpn->peer, NULL);
+ ret = 0;
+
+unlock:
+ spin_unlock_bh(&peer->ovpn->lock);
+
+ return ret;
+}
+
+/**
+ * ovpn_peer_release_p2p - release peer upon P2P device teardown
+ * @ovpn: the instance being torn down
+ */
+void ovpn_peer_release_p2p(struct ovpn_struct *ovpn)
+{
+ struct ovpn_peer *tmp;
+
+ rcu_read_lock();
+ tmp = rcu_dereference(ovpn->peer);
+ if (tmp)
+ ovpn_peer_del_p2p(tmp, OVPN_DEL_PEER_REASON_TEARDOWN);
+ rcu_read_unlock();
+}
+
+/**
+ * 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 000000000000..6c51959363c7
--- /dev/null
+++ b/drivers/net/ovpn/peer.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_OVPNPEER_H_
+#define _NET_OVPN_OVPNPEER_H_
+
+#include "bind.h"
+
+#include <net/dst_cache.h>
+#include <uapi/linux/ovpn.h>
+
+/**
+ * struct ovpn_peer - the main remote peer object
+ * @ovpn: main openvpn instance this peer belongs to
+ * @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
+ * @halt: true if ovpn_peer_mark_delete was called
+ * @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
+ * @delete_work: deferred cleanup work, used to notify userspace
+ */
+struct ovpn_peer {
+ struct ovpn_struct *ovpn;
+ u32 id;
+ struct {
+ struct in_addr ipv4;
+ struct in6_addr ipv6;
+ } vpn_addrs;
+ struct dst_cache dst_cache;
+ struct ovpn_bind __rcu *bind;
+ bool halt;
+ enum ovpn_del_peer_reason delete_reason;
+ spinlock_t lock; /* protects bind */
+ struct kref refcount;
+ struct rcu_head rcu;
+ struct work_struct delete_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 already
+ */
+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_struct *ovpn, u32 id);
+int ovpn_peer_add(struct ovpn_struct *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_struct *ovpn);
+
+struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn,
+ struct sk_buff *skb);
+struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id);
+
+#endif /* _NET_OVPN_OVPNPEER_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 09/25] ovpn: introduce the ovpn_socket object
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (7 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 08/25] ovpn: introduce the ovpn_peer object Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 10/25] ovpn: implement basic TX path (UDP) Antonio Quartulli
` (15 subsequent siblings)
24 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
This specific structure is used in the ovpn kernel module
to wrap and carry around a standard kernel socket.
ovpn takes ownership of passed sockets and therefore an ovpn
specific objects is attached to them for status tracking
purposes.
Initially only UDP support is introduced. TCP will come in a later
patch.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/Makefile | 2 +
drivers/net/ovpn/socket.c | 120 ++++++++++++++++++++++++++++++++++++++
drivers/net/ovpn/socket.h | 49 ++++++++++++++++
drivers/net/ovpn/udp.c | 72 +++++++++++++++++++++++
drivers/net/ovpn/udp.h | 17 ++++++
5 files changed, 260 insertions(+)
create mode 100644 drivers/net/ovpn/socket.c
create mode 100644 drivers/net/ovpn/socket.h
create mode 100644 drivers/net/ovpn/udp.c
create mode 100644 drivers/net/ovpn/udp.h
diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile
index ce13499b3e17..56bddc9bef83 100644
--- a/drivers/net/ovpn/Makefile
+++ b/drivers/net/ovpn/Makefile
@@ -13,3 +13,5 @@ ovpn-y += io.o
ovpn-y += netlink.o
ovpn-y += netlink-gen.o
ovpn-y += peer.o
+ovpn-y += socket.o
+ovpn-y += udp.o
diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c
new file mode 100644
index 000000000000..090a3232ab0e
--- /dev/null
+++ b/drivers/net/ovpn/socket.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <linux/net.h>
+#include <linux/netdevice.h>
+
+#include "ovpnstruct.h"
+#include "main.h"
+#include "io.h"
+#include "peer.h"
+#include "socket.h"
+#include "udp.h"
+
+static void ovpn_socket_detach(struct socket *sock)
+{
+ if (!sock)
+ return;
+
+ sockfd_put(sock);
+}
+
+/**
+ * ovpn_socket_release_kref - kref_put callback
+ * @kref: the kref object
+ */
+void ovpn_socket_release_kref(struct kref *kref)
+{
+ struct ovpn_socket *sock = container_of(kref, struct ovpn_socket,
+ refcount);
+
+ ovpn_socket_detach(sock->sock);
+ kfree_rcu(sock, rcu);
+}
+
+static bool ovpn_socket_hold(struct ovpn_socket *sock)
+{
+ return kref_get_unless_zero(&sock->refcount);
+}
+
+static struct ovpn_socket *ovpn_socket_get(struct socket *sock)
+{
+ struct ovpn_socket *ovpn_sock;
+
+ rcu_read_lock();
+ ovpn_sock = rcu_dereference_sk_user_data(sock->sk);
+ if (!ovpn_socket_hold(ovpn_sock)) {
+ pr_warn("%s: found ovpn_socket with ref = 0\n", __func__);
+ ovpn_sock = NULL;
+ }
+ rcu_read_unlock();
+
+ return ovpn_sock;
+}
+
+static int ovpn_socket_attach(struct socket *sock, struct ovpn_peer *peer)
+{
+ int ret = -EOPNOTSUPP;
+
+ if (!sock || !peer)
+ return -EINVAL;
+
+ if (sock->sk->sk_protocol == IPPROTO_UDP)
+ ret = ovpn_udp_socket_attach(sock, peer->ovpn);
+
+ return ret;
+}
+
+/**
+ * ovpn_socket_new - create a new socket and initialize it
+ * @sock: the kernel socket to embed
+ * @peer: the peer reachable via this socket
+ *
+ * Return: an openvpn socket on success or a negative error code otherwise
+ */
+struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer)
+{
+ struct ovpn_socket *ovpn_sock;
+ int ret;
+
+ ret = ovpn_socket_attach(sock, peer);
+ if (ret < 0 && ret != -EALREADY)
+ return ERR_PTR(ret);
+
+ /* if this socket is already owned by this interface, just increase the
+ * refcounter and use it as expected.
+ *
+ * Since UDP sockets can be used to talk to multiple remote endpoints,
+ * openvpn normally instantiates only one socket and shares it among all
+ * its peers. For this reason, when we find out that a socket is already
+ * used for some other peer in *this* instance, we can happily increase
+ * its refcounter and use it normally.
+ */
+ if (ret == -EALREADY) {
+ /* caller is expected to increase the sock refcounter before
+ * passing it to this function. For this reason we drop it if
+ * not needed, like when this socket is already owned.
+ */
+ ovpn_sock = ovpn_socket_get(sock);
+ sockfd_put(sock);
+ return ovpn_sock;
+ }
+
+ ovpn_sock = kzalloc(sizeof(*ovpn_sock), GFP_KERNEL);
+ if (!ovpn_sock)
+ return ERR_PTR(-ENOMEM);
+
+ ovpn_sock->ovpn = peer->ovpn;
+ ovpn_sock->sock = sock;
+ kref_init(&ovpn_sock->refcount);
+
+ rcu_assign_sk_user_data(sock->sk, ovpn_sock);
+
+ return ovpn_sock;
+}
diff --git a/drivers/net/ovpn/socket.h b/drivers/net/ovpn/socket.h
new file mode 100644
index 000000000000..08ca1ec25aa2
--- /dev/null
+++ b/drivers/net/ovpn/socket.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_SOCK_H_
+#define _NET_OVPN_SOCK_H_
+
+#include <linux/net.h>
+#include <linux/kref.h>
+#include <linux/ptr_ring.h>
+#include <net/sock.h>
+
+struct ovpn_struct;
+struct ovpn_peer;
+
+/**
+ * struct ovpn_socket - a kernel socket referenced in the ovpn code
+ * @ovpn: ovpn instance owning this socket (UDP only)
+ * @sock: the low level sock object
+ * @refcount: amount of contexts currently referencing this object
+ * @rcu: member used to schedule RCU destructor callback
+ */
+struct ovpn_socket {
+ struct ovpn_struct *ovpn;
+ struct socket *sock;
+ struct kref refcount;
+ struct rcu_head rcu;
+};
+
+void ovpn_socket_release_kref(struct kref *kref);
+
+/**
+ * ovpn_socket_put - decrease reference counter
+ * @sock: the socket whose reference counter should be decreased
+ */
+static inline void ovpn_socket_put(struct ovpn_socket *sock)
+{
+ kref_put(&sock->refcount, ovpn_socket_release_kref);
+}
+
+struct ovpn_socket *ovpn_socket_new(struct socket *sock,
+ struct ovpn_peer *peer);
+
+#endif /* _NET_OVPN_SOCK_H_ */
diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c
new file mode 100644
index 000000000000..c10474d252e1
--- /dev/null
+++ b/drivers/net/ovpn/udp.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2019-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <linux/netdevice.h>
+#include <linux/socket.h>
+#include <net/udp.h>
+
+#include "ovpnstruct.h"
+#include "main.h"
+#include "socket.h"
+#include "udp.h"
+
+/**
+ * ovpn_udp_socket_attach - set udp-tunnel CBs on socket and link it to ovpn
+ * @sock: socket to configure
+ * @ovpn: the openvp instance to link
+ *
+ * After invoking this function, the sock will be controlled by ovpn so that
+ * any incoming packet may be processed by ovpn first.
+ *
+ * Return: 0 on success or a negative error code otherwise
+ */
+int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn)
+{
+ struct ovpn_socket *old_data;
+ int ret = 0;
+
+ /* sanity check */
+ if (sock->sk->sk_protocol != IPPROTO_UDP) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ /* make sure no pre-existing encapsulation handler exists */
+ rcu_read_lock();
+ old_data = rcu_dereference_sk_user_data(sock->sk);
+ if (!old_data) {
+ /* socket is currently unused - we can take it */
+ rcu_read_unlock();
+ return 0;
+ }
+
+ /* socket is in use. We need to understand if it's owned by this ovpn
+ * instance or by something else.
+ * In the former case, we can increase the refcounter and happily
+ * use it, because the same UDP socket is expected to be shared among
+ * different peers.
+ *
+ * Unlikely TCP, a single UDP socket can be used to talk to many remote
+ * hosts and therefore openvpn instantiates one only for all its peers
+ */
+ if ((READ_ONCE(udp_sk(sock->sk)->encap_type) == UDP_ENCAP_OVPNINUDP) &&
+ old_data->ovpn == ovpn) {
+ netdev_dbg(ovpn->dev,
+ "%s: provided socket already owned by this interface\n",
+ __func__);
+ ret = -EALREADY;
+ } else {
+ netdev_err(ovpn->dev,
+ "%s: provided socket already taken by other user\n",
+ __func__);
+ ret = -EBUSY;
+ }
+ rcu_read_unlock();
+
+ return ret;
+}
diff --git a/drivers/net/ovpn/udp.h b/drivers/net/ovpn/udp.h
new file mode 100644
index 000000000000..f2507f8f2c71
--- /dev/null
+++ b/drivers/net/ovpn/udp.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2019-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_UDP_H_
+#define _NET_OVPN_UDP_H_
+
+struct ovpn_struct;
+struct socket;
+
+int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn);
+
+#endif /* _NET_OVPN_UDP_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 10/25] ovpn: implement basic TX path (UDP)
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (8 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 09/25] ovpn: introduce the ovpn_socket object Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-18 10:07 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 11/25] ovpn: implement basic RX " Antonio Quartulli
` (14 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
Packets sent over the ovpn interface are processed and transmitted to the
connected peer, if any.
Implementation is UDP only. TCP will be added by a later patch.
Note: no crypto/encapsulation exists yet. packets are just captured and
sent.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/io.c | 139 +++++++++++++++++++++++-
drivers/net/ovpn/io.h | 2 +
drivers/net/ovpn/peer.c | 37 ++++++-
drivers/net/ovpn/peer.h | 5 +
drivers/net/ovpn/skb.h | 54 ++++++++++
drivers/net/ovpn/udp.c | 230 ++++++++++++++++++++++++++++++++++++++++
drivers/net/ovpn/udp.h | 8 ++
7 files changed, 472 insertions(+), 3 deletions(-)
create mode 100644 drivers/net/ovpn/skb.h
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index ad3813419c33..20997a682098 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -9,14 +9,151 @@
#include <linux/netdevice.h>
#include <linux/skbuff.h>
+#include <net/gso.h>
#include "io.h"
+#include "ovpnstruct.h"
+#include "peer.h"
+#include "udp.h"
+#include "skb.h"
+
+static void ovpn_encrypt_post(struct sk_buff *skb, int ret)
+{
+ struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer;
+
+ if (unlikely(ret < 0))
+ goto err;
+
+ skb_mark_not_on_list(skb);
+
+ switch (peer->sock->sock->sk->sk_protocol) {
+ case IPPROTO_UDP:
+ ovpn_udp_send_skb(peer->ovpn, peer, skb);
+ break;
+ default:
+ /* no transport configured yet */
+ goto err;
+ }
+ /* skb passed down the stack - don't free it */
+ skb = NULL;
+err:
+ if (unlikely(skb)) {
+ dev_core_stats_tx_dropped_inc(peer->ovpn->dev);
+ kfree_skb(skb);
+ }
+ ovpn_peer_put(peer);
+}
+
+static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)
+{
+ ovpn_skb_cb(skb)->peer = peer;
+
+ /* take a reference to the peer because the crypto code may run async.
+ * ovpn_encrypt_post() will release it upon completion
+ */
+ DEBUG_NET_WARN_ON_ONCE(!ovpn_peer_hold(peer));
+ ovpn_encrypt_post(skb, 0);
+ return true;
+}
+
+/* send skb to connected peer, if any */
+static void ovpn_send(struct ovpn_struct *ovpn, struct sk_buff *skb,
+ struct ovpn_peer *peer)
+{
+ struct sk_buff *curr, *next;
+
+ if (likely(!peer))
+ /* retrieve peer serving the destination IP of this packet */
+ peer = ovpn_peer_get_by_dst(ovpn, skb);
+ if (unlikely(!peer)) {
+ net_dbg_ratelimited("%s: no peer to send data to\n",
+ ovpn->dev->name);
+ dev_core_stats_tx_dropped_inc(ovpn->dev);
+ goto drop;
+ }
+
+ /* this might be a GSO-segmented skb list: process each skb
+ * independently
+ */
+ skb_list_walk_safe(skb, curr, next)
+ if (unlikely(!ovpn_encrypt_one(peer, curr))) {
+ dev_core_stats_tx_dropped_inc(ovpn->dev);
+ kfree_skb(curr);
+ }
+
+ /* skb passed over, no need to free */
+ skb = NULL;
+drop:
+ if (likely(peer))
+ ovpn_peer_put(peer);
+ kfree_skb_list(skb);
+}
/* Send user data to the network
*/
netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
+ struct ovpn_struct *ovpn = netdev_priv(dev);
+ struct sk_buff *segments, *tmp, *curr, *next;
+ struct sk_buff_head skb_list;
+ __be16 proto;
+ int ret;
+
+ /* reset netfilter state */
+ nf_reset_ct(skb);
+
+ /* verify IP header size in network packet */
+ proto = ovpn_ip_check_protocol(skb);
+ if (unlikely(!proto || skb->protocol != proto)) {
+ net_err_ratelimited("%s: dropping malformed payload packet\n",
+ dev->name);
+ dev_core_stats_tx_dropped_inc(ovpn->dev);
+ goto drop;
+ }
+
+ if (skb_is_gso(skb)) {
+ segments = skb_gso_segment(skb, 0);
+ if (IS_ERR(segments)) {
+ ret = PTR_ERR(segments);
+ net_err_ratelimited("%s: cannot segment packet: %d\n",
+ dev->name, ret);
+ dev_core_stats_tx_dropped_inc(ovpn->dev);
+ goto drop;
+ }
+
+ consume_skb(skb);
+ skb = segments;
+ }
+
+ /* from this moment on, "skb" might be a list */
+
+ __skb_queue_head_init(&skb_list);
+ skb_list_walk_safe(skb, curr, next) {
+ skb_mark_not_on_list(curr);
+
+ tmp = skb_share_check(curr, GFP_ATOMIC);
+ if (unlikely(!tmp)) {
+ kfree_skb_list(next);
+ net_err_ratelimited("%s: skb_share_check failed\n",
+ dev->name);
+ goto drop_list;
+ }
+
+ __skb_queue_tail(&skb_list, tmp);
+ }
+ skb_list.prev->next = NULL;
+
+ ovpn_send(ovpn, skb_list.next, NULL);
+
+ return NETDEV_TX_OK;
+
+drop_list:
+ skb_queue_walk_safe(&skb_list, curr, next) {
+ dev_core_stats_tx_dropped_inc(ovpn->dev);
+ kfree_skb(curr);
+ }
+drop:
skb_tx_error(skb);
- kfree_skb(skb);
+ kfree_skb_list(skb);
return NET_XMIT_DROP;
}
diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h
index aa259be66441..95568671d5ae 100644
--- a/drivers/net/ovpn/io.h
+++ b/drivers/net/ovpn/io.h
@@ -12,4 +12,6 @@
netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev);
+void ovpn_encrypt_work(struct work_struct *work);
+
#endif /* _NET_OVPN_OVPN_H_ */
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 88ed93e1a0e5..746b6cc0d516 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -9,7 +9,6 @@
#include <linux/skbuff.h>
#include <linux/list.h>
-#include <linux/workqueue.h>
#include "ovpnstruct.h"
#include "bind.h"
@@ -68,8 +67,10 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
*/
static void ovpn_peer_release(struct ovpn_peer *peer)
{
- ovpn_bind_reset(peer, NULL);
+ if (peer->sock)
+ ovpn_socket_put(peer->sock);
+ ovpn_bind_reset(peer, NULL);
dst_cache_destroy(&peer->dst_cache);
}
@@ -246,6 +247,38 @@ struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id)
return peer;
}
+/**
+ * ovpn_peer_get_by_dst - Lookup peer to send skb to
+ * @ovpn: the private data representing the current VPN session
+ * @skb: the skb to extract the destination address from
+ *
+ * This function takes a tunnel packet and looks up the peer to send it to
+ * after encapsulation. The skb is expected to be the in-tunnel packet, without
+ * any OpenVPN related header.
+ *
+ * Assume that the IP header is accessible in the skb data.
+ *
+ * Return: the peer if found or NULL otherwise.
+ */
+struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn,
+ struct sk_buff *skb)
+{
+ struct ovpn_peer *peer = NULL;
+
+ /* in P2P mode, no matter the destination, packets are always sent to
+ * the single peer listening on the other side
+ */
+ if (ovpn->mode == OVPN_MODE_P2P) {
+ rcu_read_lock();
+ peer = rcu_dereference(ovpn->peer);
+ if (unlikely(peer && !ovpn_peer_hold(peer)))
+ peer = NULL;
+ rcu_read_unlock();
+ }
+
+ return peer;
+}
+
/**
* ovpn_peer_add_p2p - add peer to related tables in a P2P instance
* @ovpn: the instance to add the peer to
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 6c51959363c7..37de5aff54a8 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -11,6 +11,7 @@
#define _NET_OVPN_OVPNPEER_H_
#include "bind.h"
+#include "socket.h"
#include <net/dst_cache.h>
#include <uapi/linux/ovpn.h>
@@ -22,6 +23,7 @@
* @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
+ * @sock: the socket being used to talk to this peer
* @dst_cache: cache for dst_entry used to send to peer
* @bind: remote peer binding
* @halt: true if ovpn_peer_mark_delete was called
@@ -38,6 +40,7 @@ struct ovpn_peer {
struct in_addr ipv4;
struct in6_addr ipv6;
} vpn_addrs;
+ struct ovpn_socket *sock;
struct dst_cache dst_cache;
struct ovpn_bind __rcu *bind;
bool halt;
@@ -78,5 +81,7 @@ void ovpn_peer_release_p2p(struct ovpn_struct *ovpn);
struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn,
struct sk_buff *skb);
struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id);
+struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn,
+ struct sk_buff *skb);
#endif /* _NET_OVPN_OVPNPEER_H_ */
diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h
new file mode 100644
index 000000000000..7966a10d915f
--- /dev/null
+++ b/drivers/net/ovpn/skb.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ * James Yonan <james@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_SKB_H_
+#define _NET_OVPN_SKB_H_
+
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/socket.h>
+#include <linux/types.h>
+
+struct ovpn_cb {
+ struct aead_request *req;
+ struct ovpn_peer *peer;
+ struct ovpn_crypto_key_slot *ks;
+ unsigned int payload_offset;
+};
+
+static inline struct ovpn_cb *ovpn_skb_cb(struct sk_buff *skb)
+{
+ BUILD_BUG_ON(sizeof(struct ovpn_cb) > sizeof(skb->cb));
+ return (struct ovpn_cb *)skb->cb;
+}
+
+/* Return IP protocol version from skb header.
+ * Return 0 if protocol is not IPv4/IPv6 or cannot be read.
+ */
+static inline __be16 ovpn_ip_check_protocol(struct sk_buff *skb)
+{
+ __be16 proto = 0;
+
+ /* skb could be non-linear,
+ * make sure IP header is in non-fragmented part
+ */
+ if (!pskb_network_may_pull(skb, sizeof(struct iphdr)))
+ return 0;
+
+ if (ip_hdr(skb)->version == 4)
+ proto = htons(ETH_P_IP);
+ else if (ip_hdr(skb)->version == 6)
+ proto = htons(ETH_P_IPV6);
+
+ return proto;
+}
+
+#endif /* _NET_OVPN_SKB_H_ */
diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c
index c10474d252e1..4f520a8ce818 100644
--- a/drivers/net/ovpn/udp.c
+++ b/drivers/net/ovpn/udp.c
@@ -7,14 +7,244 @@
*/
#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
#include <linux/socket.h>
+#include <net/addrconf.h>
+#include <net/dst_cache.h>
+#include <net/route.h>
+#include <net/ipv6_stubs.h>
#include <net/udp.h>
+#include <net/udp_tunnel.h>
#include "ovpnstruct.h"
#include "main.h"
+#include "bind.h"
+#include "io.h"
+#include "peer.h"
#include "socket.h"
#include "udp.h"
+/**
+ * ovpn_udp4_output - send IPv4 packet over udp socket
+ * @ovpn: the openvpn instance
+ * @bind: the binding related to the destination peer
+ * @cache: dst cache
+ * @sk: the socket to send the packet over
+ * @skb: the packet to send
+ *
+ * Return: 0 on success or a negative error code otherwise
+ */
+static int ovpn_udp4_output(struct ovpn_struct *ovpn, struct ovpn_bind *bind,
+ struct dst_cache *cache, struct sock *sk,
+ struct sk_buff *skb)
+{
+ struct rtable *rt;
+ struct flowi4 fl = {
+ .saddr = bind->local.ipv4.s_addr,
+ .daddr = bind->sa.in4.sin_addr.s_addr,
+ .fl4_sport = inet_sk(sk)->inet_sport,
+ .fl4_dport = bind->sa.in4.sin_port,
+ .flowi4_proto = sk->sk_protocol,
+ .flowi4_mark = sk->sk_mark,
+ };
+ int ret;
+
+ local_bh_disable();
+ rt = dst_cache_get_ip4(cache, &fl.saddr);
+ if (rt)
+ goto transmit;
+
+ if (unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, fl.saddr,
+ RT_SCOPE_HOST))) {
+ /* we may end up here when the cached address is not usable
+ * anymore. In this case we reset address/cache and perform a
+ * new look up
+ */
+ fl.saddr = 0;
+ bind->local.ipv4.s_addr = 0;
+ dst_cache_reset(cache);
+ }
+
+ rt = ip_route_output_flow(sock_net(sk), &fl, sk);
+ if (IS_ERR(rt) && PTR_ERR(rt) == -EINVAL) {
+ fl.saddr = 0;
+ bind->local.ipv4.s_addr = 0;
+ dst_cache_reset(cache);
+
+ rt = ip_route_output_flow(sock_net(sk), &fl, sk);
+ }
+
+ if (IS_ERR(rt)) {
+ ret = PTR_ERR(rt);
+ net_dbg_ratelimited("%s: no route to host %pISpc: %d\n",
+ ovpn->dev->name, &bind->sa.in4, ret);
+ goto err;
+ }
+ dst_cache_set_ip4(cache, &rt->dst, fl.saddr);
+
+transmit:
+ udp_tunnel_xmit_skb(rt, sk, skb, fl.saddr, fl.daddr, 0,
+ ip4_dst_hoplimit(&rt->dst), 0, fl.fl4_sport,
+ fl.fl4_dport, false, sk->sk_no_check_tx);
+ ret = 0;
+err:
+ local_bh_enable();
+ return ret;
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+/**
+ * ovpn_udp6_output - send IPv6 packet over udp socket
+ * @ovpn: the openvpn instance
+ * @bind: the binding related to the destination peer
+ * @cache: dst cache
+ * @sk: the socket to send the packet over
+ * @skb: the packet to send
+ *
+ * Return: 0 on success or a negative error code otherwise
+ */
+static int ovpn_udp6_output(struct ovpn_struct *ovpn, struct ovpn_bind *bind,
+ struct dst_cache *cache, struct sock *sk,
+ struct sk_buff *skb)
+{
+ struct dst_entry *dst;
+ int ret;
+
+ struct flowi6 fl = {
+ .saddr = bind->local.ipv6,
+ .daddr = bind->sa.in6.sin6_addr,
+ .fl6_sport = inet_sk(sk)->inet_sport,
+ .fl6_dport = bind->sa.in6.sin6_port,
+ .flowi6_proto = sk->sk_protocol,
+ .flowi6_mark = sk->sk_mark,
+ .flowi6_oif = bind->sa.in6.sin6_scope_id,
+ };
+
+ local_bh_disable();
+ dst = dst_cache_get_ip6(cache, &fl.saddr);
+ if (dst)
+ goto transmit;
+
+ if (unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) {
+ /* we may end up here when the cached address is not usable
+ * anymore. In this case we reset address/cache and perform a
+ * new look up
+ */
+ fl.saddr = in6addr_any;
+ bind->local.ipv6 = in6addr_any;
+ dst_cache_reset(cache);
+ }
+
+ dst = ipv6_stub->ipv6_dst_lookup_flow(sock_net(sk), sk, &fl, NULL);
+ if (IS_ERR(dst)) {
+ ret = PTR_ERR(dst);
+ net_dbg_ratelimited("%s: no route to host %pISpc: %d\n",
+ ovpn->dev->name, &bind->sa.in6, ret);
+ goto err;
+ }
+ dst_cache_set_ip6(cache, dst, &fl.saddr);
+
+transmit:
+ udp_tunnel6_xmit_skb(dst, sk, skb, skb->dev, &fl.saddr, &fl.daddr, 0,
+ ip6_dst_hoplimit(dst), 0, fl.fl6_sport,
+ fl.fl6_dport, udp_get_no_check6_tx(sk));
+ ret = 0;
+err:
+ local_bh_enable();
+ return ret;
+}
+#endif
+
+/**
+ * ovpn_udp_output - transmit skb using udp-tunnel
+ * @ovpn: the openvpn instance
+ * @bind: the binding related to the destination peer
+ * @cache: dst cache
+ * @sk: the socket to send the packet over
+ * @skb: the packet to send
+ *
+ * rcu_read_lock should be held on entry.
+ * On return, the skb is consumed.
+ *
+ * Return: 0 on success or a negative error code otherwise
+ */
+static int ovpn_udp_output(struct ovpn_struct *ovpn, struct ovpn_bind *bind,
+ struct dst_cache *cache, struct sock *sk,
+ struct sk_buff *skb)
+{
+ int ret;
+
+ /* set sk to null if skb is already orphaned */
+ if (!skb->destructor)
+ skb->sk = NULL;
+
+ /* always permit openvpn-created packets to be (outside) fragmented */
+ skb->ignore_df = 1;
+
+ switch (bind->sa.in4.sin_family) {
+ case AF_INET:
+ ret = ovpn_udp4_output(ovpn, bind, cache, sk, skb);
+ break;
+#if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6:
+ ret = ovpn_udp6_output(ovpn, bind, cache, sk, skb);
+ break;
+#endif
+ default:
+ ret = -EAFNOSUPPORT;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * ovpn_udp_send_skb - prepare skb and send it over via UDP
+ * @ovpn: the openvpn instance
+ * @peer: the destination peer
+ * @skb: the packet to send
+ */
+void ovpn_udp_send_skb(struct ovpn_struct *ovpn, struct ovpn_peer *peer,
+ struct sk_buff *skb)
+{
+ struct ovpn_bind *bind;
+ struct socket *sock;
+ int ret = -1;
+
+ skb->dev = ovpn->dev;
+ /* no checksum performed at this layer */
+ skb->ip_summed = CHECKSUM_NONE;
+
+ /* get socket info */
+ sock = peer->sock->sock;
+ if (unlikely(!sock)) {
+ net_warn_ratelimited("%s: no sock for remote peer\n", __func__);
+ goto out;
+ }
+
+ rcu_read_lock();
+ /* get binding */
+ bind = rcu_dereference(peer->bind);
+ if (unlikely(!bind)) {
+ net_warn_ratelimited("%s: no bind for remote peer\n", __func__);
+ goto out_unlock;
+ }
+
+ /* crypto layer -> transport (UDP) */
+ ret = ovpn_udp_output(ovpn, bind, &peer->dst_cache, sock->sk, skb);
+
+out_unlock:
+ rcu_read_unlock();
+out:
+ if (unlikely(ret < 0)) {
+ dev_core_stats_tx_dropped_inc(ovpn->dev);
+ kfree_skb(skb);
+ return;
+ }
+
+ dev_sw_netstats_tx_add(ovpn->dev, 1, skb->len);
+}
+
/**
* ovpn_udp_socket_attach - set udp-tunnel CBs on socket and link it to ovpn
* @sock: socket to configure
diff --git a/drivers/net/ovpn/udp.h b/drivers/net/ovpn/udp.h
index f2507f8f2c71..e60f8cd2b4ac 100644
--- a/drivers/net/ovpn/udp.h
+++ b/drivers/net/ovpn/udp.h
@@ -9,9 +9,17 @@
#ifndef _NET_OVPN_UDP_H_
#define _NET_OVPN_UDP_H_
+#include <linux/skbuff.h>
+#include <net/sock.h>
+
+struct ovpn_peer;
struct ovpn_struct;
+struct sk_buff;
struct socket;
int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn);
+void ovpn_udp_send_skb(struct ovpn_struct *ovpn, struct ovpn_peer *peer,
+ struct sk_buff *skb);
+
#endif /* _NET_OVPN_UDP_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 11/25] ovpn: implement basic RX path (UDP)
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (9 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 10/25] ovpn: implement basic TX path (UDP) Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-08 16:11 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 12/25] ovpn: implement packet processing Antonio Quartulli
` (13 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
Packets received over the socket are forwarded to the user device.
Implementation is UDP only. TCP will be added by a later patch.
Note: no decryption/decapsulation exists yet, packets are forwarded as
they arrive without much processing.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/io.c | 66 ++++++++++++++++++++-
drivers/net/ovpn/io.h | 2 +-
drivers/net/ovpn/main.c | 9 ++-
drivers/net/ovpn/ovpnstruct.h | 3 +
drivers/net/ovpn/proto.h | 75 ++++++++++++++++++++++++
drivers/net/ovpn/skb.h | 3 -
drivers/net/ovpn/socket.c | 24 ++++++++
drivers/net/ovpn/udp.c | 104 +++++++++++++++++++++++++++++++++-
drivers/net/ovpn/udp.h | 3 +-
9 files changed, 281 insertions(+), 8 deletions(-)
create mode 100644 drivers/net/ovpn/proto.h
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index 20997a682098..643637572a40 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -9,14 +9,78 @@
#include <linux/netdevice.h>
#include <linux/skbuff.h>
+#include <net/gro_cells.h>
#include <net/gso.h>
-#include "io.h"
#include "ovpnstruct.h"
#include "peer.h"
+#include "io.h"
+#include "netlink.h"
+#include "proto.h"
#include "udp.h"
#include "skb.h"
+/* Called after decrypt to write the IP packet to the device.
+ * This method is expected to manage/free the skb.
+ */
+static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb)
+{
+ /* packet integrity was verified on the VPN layer - no need to perform
+ * any additional check along the stack
+ */
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ skb->csum_level = ~0;
+
+ /* skb hash for transport packet no longer valid after decapsulation */
+ skb_clear_hash(skb);
+
+ /* post-decrypt scrub -- prepare to inject encapsulated packet onto the
+ * interface, based on __skb_tunnel_rx() in dst.h
+ */
+ skb->dev = peer->ovpn->dev;
+ skb_set_queue_mapping(skb, 0);
+ skb_scrub_packet(skb, true);
+
+ skb_reset_network_header(skb);
+ skb_reset_transport_header(skb);
+ skb_probe_transport_header(skb);
+ skb_reset_inner_headers(skb);
+
+ memset(skb->cb, 0, sizeof(skb->cb));
+
+ /* cause packet to be "received" by the interface */
+ if (likely(gro_cells_receive(&peer->ovpn->gro_cells,
+ skb) == NET_RX_SUCCESS))
+ /* update RX stats with the size of decrypted packet */
+ dev_sw_netstats_rx_add(peer->ovpn->dev, skb->len);
+ else
+ dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
+}
+
+static void ovpn_decrypt_post(struct sk_buff *skb, int ret)
+{
+ struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer;
+
+ if (unlikely(ret < 0))
+ goto drop;
+
+ ovpn_netdev_write(peer, skb);
+ /* skb is passed to upper layer - don't free it */
+ skb = NULL;
+drop:
+ if (unlikely(skb))
+ dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
+ kfree_skb(skb);
+ ovpn_peer_put(peer);
+}
+
+/* pick next packet from RX queue, decrypt and forward it to the device */
+void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb)
+{
+ ovpn_skb_cb(skb)->peer = peer;
+ ovpn_decrypt_post(skb, 0);
+}
+
static void ovpn_encrypt_post(struct sk_buff *skb, int ret)
{
struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer;
diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h
index 95568671d5ae..9667a0a470e0 100644
--- a/drivers/net/ovpn/io.h
+++ b/drivers/net/ovpn/io.h
@@ -12,6 +12,6 @@
netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev);
-void ovpn_encrypt_work(struct work_struct *work);
+void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb);
#endif /* _NET_OVPN_OVPN_H_ */
diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
index 8a57088491b5..4e6f6fd5ae56 100644
--- a/drivers/net/ovpn/main.c
+++ b/drivers/net/ovpn/main.c
@@ -13,6 +13,7 @@
#include <linux/inetdevice.h>
//#include <linux/rcupdate.h>
#include <linux/version.h>
+#include <net/gro_cells.h>
#include <net/ip.h>
#include <net/rtnetlink.h>
#include <uapi/linux/if_arp.h>
@@ -49,11 +50,17 @@ static int ovpn_struct_init(struct net_device *dev, enum ovpn_mode mode)
static void ovpn_struct_free(struct net_device *net)
{
+ struct ovpn_struct *ovpn = netdev_priv(net);
+
+ gro_cells_destroy(&ovpn->gro_cells);
+ rcu_barrier();
}
static int ovpn_net_init(struct net_device *dev)
{
- return 0;
+ struct ovpn_struct *ovpn = netdev_priv(dev);
+
+ return gro_cells_init(&ovpn->gro_cells, dev);
}
static int ovpn_net_open(struct net_device *dev)
diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h
index 4d1616628430..af39ee86f168 100644
--- a/drivers/net/ovpn/ovpnstruct.h
+++ b/drivers/net/ovpn/ovpnstruct.h
@@ -10,6 +10,7 @@
#ifndef _NET_OVPN_OVPNSTRUCT_H_
#define _NET_OVPN_OVPNSTRUCT_H_
+#include <net/gro_cells.h>
#include <uapi/linux/ovpn.h>
/**
@@ -20,6 +21,7 @@
* @lock: protect this object
* @peer: in P2P mode, this is the only remote peer
* @dev_list: entry for the module wide device list
+ * @gro_cells: pointer to the Generic Receive Offload cell
*/
struct ovpn_struct {
struct net_device *dev;
@@ -28,6 +30,7 @@ struct ovpn_struct {
spinlock_t lock; /* protect writing to the ovpn_struct object */
struct ovpn_peer __rcu *peer;
struct list_head dev_list;
+ struct gro_cells gro_cells;
};
#endif /* _NET_OVPN_OVPNSTRUCT_H_ */
diff --git a/drivers/net/ovpn/proto.h b/drivers/net/ovpn/proto.h
new file mode 100644
index 000000000000..69604cf26bbf
--- /dev/null
+++ b/drivers/net/ovpn/proto.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ * James Yonan <james@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_OVPNPROTO_H_
+#define _NET_OVPN_OVPNPROTO_H_
+
+#include "main.h"
+
+#include <linux/skbuff.h>
+
+/* Methods for operating on the initial command
+ * byte of the OpenVPN protocol.
+ */
+
+/* packet opcode (high 5 bits) and key-id (low 3 bits) are combined in
+ * one byte
+ */
+#define OVPN_KEY_ID_MASK 0x07
+#define OVPN_OPCODE_SHIFT 3
+#define OVPN_OPCODE_MASK 0x1F
+/* upper bounds on opcode and key ID */
+#define OVPN_KEY_ID_MAX (OVPN_KEY_ID_MASK + 1)
+#define OVPN_OPCODE_MAX (OVPN_OPCODE_MASK + 1)
+/* packet opcodes of interest to us */
+#define OVPN_DATA_V1 6 /* data channel V1 packet */
+#define OVPN_DATA_V2 9 /* data channel V2 packet */
+/* size of initial packet opcode */
+#define OVPN_OP_SIZE_V1 1
+#define OVPN_OP_SIZE_V2 4
+#define OVPN_PEER_ID_MASK 0x00FFFFFF
+#define OVPN_PEER_ID_UNDEF 0x00FFFFFF
+/* first byte of keepalive message */
+#define OVPN_KEEPALIVE_FIRST_BYTE 0x2a
+/* first byte of exit message */
+#define OVPN_EXPLICIT_EXIT_NOTIFY_FIRST_BYTE 0x28
+
+/**
+ * ovpn_opcode_from_skb - extract OP code from skb at specified offset
+ * @skb: the packet to extract the OP code from
+ * @offset: the offset in the data buffer where the OP code is located
+ *
+ * Note: this function assumes that the skb head was pulled enough
+ * to access the first byte.
+ *
+ * Return: the OP code
+ */
+static inline u8 ovpn_opcode_from_skb(const struct sk_buff *skb, u16 offset)
+{
+ u8 byte = *(skb->data + offset);
+
+ return byte >> OVPN_OPCODE_SHIFT;
+}
+
+/**
+ * ovpn_peer_id_from_skb - extract peer ID from skb at specified offset
+ * @skb: the packet to extract the OP code from
+ * @offset: the offset in the data buffer where the OP code is located
+ *
+ * Note: this function assumes that the skb head was pulled enough
+ * to access the first 4 bytes.
+ *
+ * Return: the peer ID.
+ */
+static inline u32 ovpn_peer_id_from_skb(const struct sk_buff *skb, u16 offset)
+{
+ return ntohl(*(__be32 *)(skb->data + offset)) & OVPN_PEER_ID_MASK;
+}
+
+#endif /* _NET_OVPN_OVPNPROTO_H_ */
diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h
index 7966a10d915f..e070fe6f448c 100644
--- a/drivers/net/ovpn/skb.h
+++ b/drivers/net/ovpn/skb.h
@@ -18,10 +18,7 @@
#include <linux/types.h>
struct ovpn_cb {
- struct aead_request *req;
struct ovpn_peer *peer;
- struct ovpn_crypto_key_slot *ks;
- unsigned int payload_offset;
};
static inline struct ovpn_cb *ovpn_skb_cb(struct sk_buff *skb)
diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c
index 090a3232ab0e..964b566de69f 100644
--- a/drivers/net/ovpn/socket.c
+++ b/drivers/net/ovpn/socket.c
@@ -22,6 +22,9 @@ static void ovpn_socket_detach(struct socket *sock)
if (!sock)
return;
+ if (sock->sk->sk_protocol == IPPROTO_UDP)
+ ovpn_udp_socket_detach(sock);
+
sockfd_put(sock);
}
@@ -71,6 +74,27 @@ static int ovpn_socket_attach(struct socket *sock, struct ovpn_peer *peer)
return ret;
}
+/* Retrieve the corresponding ovpn object from a UDP socket
+ * rcu_read_lock must be held on entry
+ */
+struct ovpn_struct *ovpn_from_udp_sock(struct sock *sk)
+{
+ struct ovpn_socket *ovpn_sock;
+
+ if (unlikely(READ_ONCE(udp_sk(sk)->encap_type) != UDP_ENCAP_OVPNINUDP))
+ return NULL;
+
+ ovpn_sock = rcu_dereference_sk_user_data(sk);
+ if (unlikely(!ovpn_sock))
+ return NULL;
+
+ /* make sure that sk matches our stored transport socket */
+ if (unlikely(!ovpn_sock->sock || sk != ovpn_sock->sock->sk))
+ return NULL;
+
+ return ovpn_sock->ovpn;
+}
+
/**
* ovpn_socket_new - create a new socket and initialize it
* @sock: the kernel socket to embed
diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c
index 4f520a8ce818..37feb64705c0 100644
--- a/drivers/net/ovpn/udp.c
+++ b/drivers/net/ovpn/udp.c
@@ -21,9 +21,94 @@
#include "bind.h"
#include "io.h"
#include "peer.h"
+#include "proto.h"
#include "socket.h"
#include "udp.h"
+/**
+ * ovpn_udp_encap_recv - Start processing a received UDP packet.
+ * @sk: socket over which the packet was received
+ * @skb: the received packet
+ *
+ * If the first byte of the payload is DATA_V2, the packet is further processed,
+ * otherwise it is forwarded to the UDP stack for delivery to user space.
+ *
+ * Return:
+ * 0 if skb was consumed or dropped
+ * >0 if skb should be passed up to userspace as UDP (packet not consumed)
+ * <0 if skb should be resubmitted as proto -N (packet not consumed)
+ */
+static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
+{
+ struct ovpn_peer *peer = NULL;
+ struct ovpn_struct *ovpn;
+ u32 peer_id;
+ u8 opcode;
+
+ ovpn = ovpn_from_udp_sock(sk);
+ if (unlikely(!ovpn)) {
+ net_err_ratelimited("%s: cannot obtain ovpn object from UDP socket\n",
+ __func__);
+ goto drop;
+ }
+
+ /* Make sure the first 4 bytes of the skb data buffer after the UDP
+ * header are accessible.
+ * They are required to fetch the OP code, the key ID and the peer ID.
+ */
+ if (unlikely(!pskb_may_pull(skb, sizeof(struct udphdr) +
+ OVPN_OP_SIZE_V2))) {
+ net_dbg_ratelimited("%s: packet too small\n", __func__);
+ goto drop;
+ }
+
+ opcode = ovpn_opcode_from_skb(skb, sizeof(struct udphdr));
+ if (unlikely(opcode != OVPN_DATA_V2)) {
+ /* DATA_V1 is not supported */
+ if (opcode == OVPN_DATA_V1)
+ goto drop;
+
+ /* unknown or control packet: let it bubble up to userspace */
+ return 1;
+ }
+
+ peer_id = ovpn_peer_id_from_skb(skb, sizeof(struct udphdr));
+ /* some OpenVPN server implementations send data packets with the
+ * peer-id set to undef. In this case we skip the peer lookup by peer-id
+ * and we try with the transport address
+ */
+ if (peer_id != OVPN_PEER_ID_UNDEF) {
+ peer = ovpn_peer_get_by_id(ovpn, peer_id);
+ if (!peer) {
+ net_err_ratelimited("%s: received data from unknown peer (id: %d)\n",
+ __func__, peer_id);
+ goto drop;
+ }
+ }
+
+ if (!peer) {
+ /* data packet with undef peer-id */
+ peer = ovpn_peer_get_by_transp_addr(ovpn, skb);
+ if (unlikely(!peer)) {
+ net_dbg_ratelimited("%s: received data with undef peer-id from unknown source\n",
+ __func__);
+ goto drop;
+ }
+ }
+
+ /* pop off outer UDP header */
+ __skb_pull(skb, sizeof(struct udphdr));
+ ovpn_recv(peer, skb);
+ return 0;
+
+drop:
+ if (peer)
+ ovpn_peer_put(peer);
+ dev_core_stats_rx_dropped_inc(ovpn->dev);
+ kfree_skb(skb);
+ return 0;
+}
+
/**
* ovpn_udp4_output - send IPv4 packet over udp socket
* @ovpn: the openvpn instance
@@ -257,8 +342,13 @@ void ovpn_udp_send_skb(struct ovpn_struct *ovpn, struct ovpn_peer *peer,
*/
int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn)
{
+ struct udp_tunnel_sock_cfg cfg = {
+ .sk_user_data = ovpn,
+ .encap_type = UDP_ENCAP_OVPNINUDP,
+ .encap_rcv = ovpn_udp_encap_recv,
+ };
struct ovpn_socket *old_data;
- int ret = 0;
+ int ret;
/* sanity check */
if (sock->sk->sk_protocol != IPPROTO_UDP) {
@@ -272,6 +362,7 @@ int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn)
if (!old_data) {
/* socket is currently unused - we can take it */
rcu_read_unlock();
+ setup_udp_tunnel_sock(sock_net(sock->sk), sock, &cfg);
return 0;
}
@@ -300,3 +391,14 @@ int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn)
return ret;
}
+
+/**
+ * ovpn_udp_socket_detach - clean udp-tunnel status for this socket
+ * @sock: the socket to clean
+ */
+void ovpn_udp_socket_detach(struct socket *sock)
+{
+ struct udp_tunnel_sock_cfg cfg = { };
+
+ setup_udp_tunnel_sock(sock_net(sock->sk), sock, &cfg);
+}
diff --git a/drivers/net/ovpn/udp.h b/drivers/net/ovpn/udp.h
index e60f8cd2b4ac..fecb68464896 100644
--- a/drivers/net/ovpn/udp.h
+++ b/drivers/net/ovpn/udp.h
@@ -18,8 +18,9 @@ struct sk_buff;
struct socket;
int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn);
-
+void ovpn_udp_socket_detach(struct socket *sock);
void ovpn_udp_send_skb(struct ovpn_struct *ovpn, struct ovpn_peer *peer,
struct sk_buff *skb);
+struct ovpn_struct *ovpn_from_udp_sock(struct sock *sk);
#endif /* _NET_OVPN_UDP_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 12/25] ovpn: implement packet processing
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (10 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 11/25] ovpn: implement basic RX " Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-09 8:51 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 13/25] ovpn: store tunnel and transport statistics Antonio Quartulli
` (12 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
This change implements encryption/decryption and
encapsulation/decapsulation of OpenVPN packets.
Support for generic crypto state is added along with
a wrapper for the AEAD crypto kernel API.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/Makefile | 3 +
drivers/net/ovpn/crypto.c | 161 +++++++++++++++
drivers/net/ovpn/crypto.h | 138 +++++++++++++
drivers/net/ovpn/crypto_aead.c | 347 +++++++++++++++++++++++++++++++++
drivers/net/ovpn/crypto_aead.h | 30 +++
drivers/net/ovpn/io.c | 113 ++++++++++-
drivers/net/ovpn/io.h | 3 +
drivers/net/ovpn/packet.h | 2 +-
drivers/net/ovpn/peer.c | 29 +++
drivers/net/ovpn/peer.h | 6 +
drivers/net/ovpn/pktid.c | 130 ++++++++++++
drivers/net/ovpn/pktid.h | 87 +++++++++
drivers/net/ovpn/proto.h | 31 +++
drivers/net/ovpn/skb.h | 4 +
14 files changed, 1079 insertions(+), 5 deletions(-)
create mode 100644 drivers/net/ovpn/crypto.c
create mode 100644 drivers/net/ovpn/crypto.h
create mode 100644 drivers/net/ovpn/crypto_aead.c
create mode 100644 drivers/net/ovpn/crypto_aead.h
create mode 100644 drivers/net/ovpn/pktid.c
create mode 100644 drivers/net/ovpn/pktid.h
diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile
index 56bddc9bef83..ccdaeced1982 100644
--- a/drivers/net/ovpn/Makefile
+++ b/drivers/net/ovpn/Makefile
@@ -8,10 +8,13 @@
obj-$(CONFIG_OVPN) := ovpn.o
ovpn-y += bind.o
+ovpn-y += crypto.o
+ovpn-y += crypto_aead.o
ovpn-y += main.o
ovpn-y += io.o
ovpn-y += netlink.o
ovpn-y += netlink-gen.o
ovpn-y += peer.o
+ovpn-y += pktid.o
ovpn-y += socket.o
ovpn-y += udp.o
diff --git a/drivers/net/ovpn/crypto.c b/drivers/net/ovpn/crypto.c
new file mode 100644
index 000000000000..7382c02e15d3
--- /dev/null
+++ b/drivers/net/ovpn/crypto.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <linux/types.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <uapi/linux/ovpn.h>
+
+#include "ovpnstruct.h"
+#include "main.h"
+#include "packet.h"
+#include "pktid.h"
+#include "crypto_aead.h"
+#include "crypto.h"
+
+static void ovpn_ks_destroy_rcu(struct rcu_head *head)
+{
+ struct ovpn_crypto_key_slot *ks;
+
+ ks = container_of(head, struct ovpn_crypto_key_slot, rcu);
+ ovpn_aead_crypto_key_slot_destroy(ks);
+}
+
+void ovpn_crypto_key_slot_release(struct kref *kref)
+{
+ struct ovpn_crypto_key_slot *ks;
+
+ ks = container_of(kref, struct ovpn_crypto_key_slot, refcount);
+ call_rcu(&ks->rcu, ovpn_ks_destroy_rcu);
+}
+
+/* can only be invoked when all peer references have been dropped (i.e. RCU
+ * release routine)
+ */
+void ovpn_crypto_state_release(struct ovpn_crypto_state *cs)
+{
+ struct ovpn_crypto_key_slot *ks;
+
+ ks = rcu_access_pointer(cs->primary);
+ if (ks) {
+ RCU_INIT_POINTER(cs->primary, NULL);
+ ovpn_crypto_key_slot_put(ks);
+ }
+
+ ks = rcu_access_pointer(cs->secondary);
+ if (ks) {
+ RCU_INIT_POINTER(cs->secondary, NULL);
+ ovpn_crypto_key_slot_put(ks);
+ }
+
+ mutex_destroy(&cs->mutex);
+}
+
+/* removes the primary key from the crypto context */
+void ovpn_crypto_kill_primary(struct ovpn_crypto_state *cs)
+{
+ struct ovpn_crypto_key_slot *ks;
+
+ mutex_lock(&cs->mutex);
+ ks = rcu_replace_pointer(cs->primary, NULL,
+ lockdep_is_held(&cs->mutex));
+ ovpn_crypto_key_slot_put(ks);
+ mutex_unlock(&cs->mutex);
+}
+
+/* Reset the ovpn_crypto_state object in a way that is atomic
+ * to RCU readers.
+ */
+int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs,
+ const struct ovpn_peer_key_reset *pkr)
+{
+ struct ovpn_crypto_key_slot *old = NULL, *new;
+
+ if (pkr->slot != OVPN_KEY_SLOT_PRIMARY &&
+ pkr->slot != OVPN_KEY_SLOT_SECONDARY)
+ return -EINVAL;
+
+ new = ovpn_aead_crypto_key_slot_new(&pkr->key);
+ if (IS_ERR(new))
+ return PTR_ERR(new);
+
+ mutex_lock(&cs->mutex);
+ switch (pkr->slot) {
+ case OVPN_KEY_SLOT_PRIMARY:
+ old = rcu_replace_pointer(cs->primary, new,
+ lockdep_is_held(&cs->mutex));
+ break;
+ case OVPN_KEY_SLOT_SECONDARY:
+ old = rcu_replace_pointer(cs->secondary, new,
+ lockdep_is_held(&cs->mutex));
+ break;
+ }
+ mutex_unlock(&cs->mutex);
+
+ if (old)
+ ovpn_crypto_key_slot_put(old);
+
+ return 0;
+}
+
+void ovpn_crypto_key_slot_delete(struct ovpn_crypto_state *cs,
+ enum ovpn_key_slot slot)
+{
+ struct ovpn_crypto_key_slot *ks = NULL;
+
+ if (slot != OVPN_KEY_SLOT_PRIMARY &&
+ slot != OVPN_KEY_SLOT_SECONDARY) {
+ pr_warn("Invalid slot to release: %u\n", slot);
+ return;
+ }
+
+ mutex_lock(&cs->mutex);
+ switch (slot) {
+ case OVPN_KEY_SLOT_PRIMARY:
+ ks = rcu_replace_pointer(cs->primary, NULL,
+ lockdep_is_held(&cs->mutex));
+ break;
+ case OVPN_KEY_SLOT_SECONDARY:
+ ks = rcu_replace_pointer(cs->secondary, NULL,
+ lockdep_is_held(&cs->mutex));
+ break;
+ }
+ mutex_unlock(&cs->mutex);
+
+ if (!ks) {
+ pr_debug("Key slot already released: %u\n", slot);
+ return;
+ }
+
+ pr_debug("deleting key slot %u, key_id=%u\n", slot, ks->key_id);
+ ovpn_crypto_key_slot_put(ks);
+}
+
+/* this swap is not atomic, but there will be a very short time frame where the
+ * old_secondary key won't be available. This should not be a big deal as most
+ * likely both peers are already using the new primary at this point.
+ */
+void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs)
+{
+ const struct ovpn_crypto_key_slot *old_primary, *old_secondary;
+
+ mutex_lock(&cs->mutex);
+
+ old_secondary = rcu_dereference_protected(cs->secondary,
+ lockdep_is_held(&cs->mutex));
+ old_primary = rcu_replace_pointer(cs->primary, old_secondary,
+ lockdep_is_held(&cs->mutex));
+ rcu_assign_pointer(cs->secondary, old_primary);
+
+ pr_debug("key swapped: %u <-> %u\n",
+ old_primary ? old_primary->key_id : 0,
+ old_secondary ? old_secondary->key_id : 0);
+
+ mutex_unlock(&cs->mutex);
+}
diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h
new file mode 100644
index 000000000000..0b6796850e60
--- /dev/null
+++ b/drivers/net/ovpn/crypto.h
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_OVPNCRYPTO_H_
+#define _NET_OVPN_OVPNCRYPTO_H_
+
+struct ovpn_peer;
+struct ovpn_crypto_key_slot;
+
+/* info needed for both encrypt and decrypt directions */
+struct ovpn_key_direction {
+ const u8 *cipher_key;
+ size_t cipher_key_size;
+ const u8 *nonce_tail; /* only needed for GCM modes */
+ size_t nonce_tail_size; /* only needed for GCM modes */
+};
+
+/* all info for a particular symmetric key (primary or secondary) */
+struct ovpn_key_config {
+ enum ovpn_cipher_alg cipher_alg;
+ u8 key_id;
+ struct ovpn_key_direction encrypt;
+ struct ovpn_key_direction decrypt;
+};
+
+/* used to pass settings from netlink to the crypto engine */
+struct ovpn_peer_key_reset {
+ enum ovpn_key_slot slot;
+ struct ovpn_key_config key;
+};
+
+struct ovpn_crypto_key_slot {
+ u8 key_id;
+
+ struct crypto_aead *encrypt;
+ struct crypto_aead *decrypt;
+ struct ovpn_nonce_tail nonce_tail_xmit;
+ struct ovpn_nonce_tail nonce_tail_recv;
+
+ struct ovpn_pktid_recv pid_recv ____cacheline_aligned_in_smp;
+ struct ovpn_pktid_xmit pid_xmit ____cacheline_aligned_in_smp;
+ struct kref refcount;
+ struct rcu_head rcu;
+};
+
+struct ovpn_crypto_state {
+ struct ovpn_crypto_key_slot __rcu *primary;
+ struct ovpn_crypto_key_slot __rcu *secondary;
+
+ /* protects primary and secondary slots */
+ struct mutex mutex;
+};
+
+static inline bool ovpn_crypto_key_slot_hold(struct ovpn_crypto_key_slot *ks)
+{
+ return kref_get_unless_zero(&ks->refcount);
+}
+
+static inline void ovpn_crypto_state_init(struct ovpn_crypto_state *cs)
+{
+ RCU_INIT_POINTER(cs->primary, NULL);
+ RCU_INIT_POINTER(cs->secondary, NULL);
+ mutex_init(&cs->mutex);
+}
+
+static inline struct ovpn_crypto_key_slot *
+ovpn_crypto_key_id_to_slot(const struct ovpn_crypto_state *cs, u8 key_id)
+{
+ struct ovpn_crypto_key_slot *ks;
+
+ if (unlikely(!cs))
+ return NULL;
+
+ rcu_read_lock();
+ ks = rcu_dereference(cs->primary);
+ if (ks && ks->key_id == key_id) {
+ if (unlikely(!ovpn_crypto_key_slot_hold(ks)))
+ ks = NULL;
+ goto out;
+ }
+
+ ks = rcu_dereference(cs->secondary);
+ if (ks && ks->key_id == key_id) {
+ if (unlikely(!ovpn_crypto_key_slot_hold(ks)))
+ ks = NULL;
+ goto out;
+ }
+
+ /* when both key slots are occupied but no matching key ID is found, ks
+ * has to be reset to NULL to avoid carrying a stale pointer
+ */
+ ks = NULL;
+out:
+ rcu_read_unlock();
+
+ return ks;
+}
+
+static inline struct ovpn_crypto_key_slot *
+ovpn_crypto_key_slot_primary(const struct ovpn_crypto_state *cs)
+{
+ struct ovpn_crypto_key_slot *ks;
+
+ rcu_read_lock();
+ ks = rcu_dereference(cs->primary);
+ if (unlikely(ks && !ovpn_crypto_key_slot_hold(ks)))
+ ks = NULL;
+ rcu_read_unlock();
+
+ return ks;
+}
+
+void ovpn_crypto_key_slot_release(struct kref *kref);
+
+static inline void ovpn_crypto_key_slot_put(struct ovpn_crypto_key_slot *ks)
+{
+ kref_put(&ks->refcount, ovpn_crypto_key_slot_release);
+}
+
+int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs,
+ const struct ovpn_peer_key_reset *pkr);
+
+void ovpn_crypto_key_slot_delete(struct ovpn_crypto_state *cs,
+ enum ovpn_key_slot slot);
+
+void ovpn_crypto_state_release(struct ovpn_crypto_state *cs);
+
+void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs);
+
+void ovpn_crypto_kill_primary(struct ovpn_crypto_state *cs);
+
+#endif /* _NET_OVPN_OVPNCRYPTO_H_ */
diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c
new file mode 100644
index 000000000000..a46ed49e2fb9
--- /dev/null
+++ b/drivers/net/ovpn/crypto_aead.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <crypto/aead.h>
+#include <linux/skbuff.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/udp.h>
+
+#include "ovpnstruct.h"
+#include "main.h"
+#include "io.h"
+#include "packet.h"
+#include "pktid.h"
+#include "crypto_aead.h"
+#include "crypto.h"
+#include "proto.h"
+#include "skb.h"
+
+#define AUTH_TAG_SIZE 16
+
+static int ovpn_aead_encap_overhead(const struct ovpn_crypto_key_slot *ks)
+{
+ return OVPN_OP_SIZE_V2 + /* OP header size */
+ 4 + /* Packet ID */
+ crypto_aead_authsize(ks->encrypt); /* Auth Tag */
+}
+
+static void ovpn_aead_encrypt_done(void *data, int ret)
+{
+ struct sk_buff *skb = data;
+
+ aead_request_free(ovpn_skb_cb(skb)->req);
+ ovpn_encrypt_post(skb, ret);
+}
+
+int ovpn_aead_encrypt(struct ovpn_crypto_key_slot *ks, struct sk_buff *skb,
+ u32 peer_id)
+{
+ const unsigned int tag_size = crypto_aead_authsize(ks->encrypt);
+ const unsigned int head_size = ovpn_aead_encap_overhead(ks);
+ struct scatterlist sg[MAX_SKB_FRAGS + 2];
+ DECLARE_CRYPTO_WAIT(wait);
+ struct aead_request *req;
+ struct sk_buff *trailer;
+ u8 iv[NONCE_SIZE];
+ int nfrags, ret;
+ u32 pktid, op;
+
+ /* Sample AEAD header format:
+ * 48000001 00000005 7e7046bd 444a7e28 cc6387b1 64a4d6c1 380275a...
+ * [ OP32 ] [seq # ] [ auth tag ] [ payload ... ]
+ * [4-byte
+ * IV head]
+ */
+
+ /* check that there's enough headroom in the skb for packet
+ * encapsulation, after adding network header and encryption overhead
+ */
+ if (unlikely(skb_cow_head(skb, OVPN_HEAD_ROOM + head_size)))
+ return -ENOBUFS;
+
+ /* get number of skb frags and ensure that packet data is writable */
+ nfrags = skb_cow_data(skb, 0, &trailer);
+ if (unlikely(nfrags < 0))
+ return nfrags;
+
+ if (unlikely(nfrags + 2 > ARRAY_SIZE(sg)))
+ return -ENOSPC;
+
+ /* sg table:
+ * 0: op, wire nonce (AD, len=OVPN_OP_SIZE_V2+NONCE_WIRE_SIZE),
+ * 1, 2, 3, ..., n: payload,
+ * n+1: auth_tag (len=tag_size)
+ */
+ sg_init_table(sg, nfrags + 2);
+
+ /* build scatterlist to encrypt packet payload */
+ ret = skb_to_sgvec_nomark(skb, sg + 1, 0, skb->len);
+ if (unlikely(nfrags != ret))
+ return -EINVAL;
+
+ /* append auth_tag onto scatterlist */
+ __skb_push(skb, tag_size);
+ sg_set_buf(sg + nfrags + 1, skb->data, tag_size);
+
+ /* obtain packet ID, which is used both as a first
+ * 4 bytes of nonce and last 4 bytes of associated data.
+ */
+ ret = ovpn_pktid_xmit_next(&ks->pid_xmit, &pktid);
+ if (unlikely(ret < 0))
+ return ret;
+
+ /* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes
+ * nonce
+ */
+ ovpn_pktid_aead_write(pktid, &ks->nonce_tail_xmit, iv);
+
+ /* make space for packet id and push it to the front */
+ __skb_push(skb, NONCE_WIRE_SIZE);
+ memcpy(skb->data, iv, NONCE_WIRE_SIZE);
+
+ /* add packet op as head of additional data */
+ op = ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer_id);
+ __skb_push(skb, OVPN_OP_SIZE_V2);
+ BUILD_BUG_ON(sizeof(op) != OVPN_OP_SIZE_V2);
+ *((__force __be32 *)skb->data) = htonl(op);
+
+ /* AEAD Additional data */
+ sg_set_buf(sg, skb->data, OVPN_OP_SIZE_V2 + NONCE_WIRE_SIZE);
+
+ req = aead_request_alloc(ks->encrypt, GFP_ATOMIC);
+ if (unlikely(!req))
+ return -ENOMEM;
+
+ /* setup async crypto operation */
+ aead_request_set_tfm(req, ks->encrypt);
+ aead_request_set_callback(req, 0, ovpn_aead_encrypt_done, NULL);
+ aead_request_set_crypt(req, sg, sg, skb->len - head_size, iv);
+ aead_request_set_ad(req, OVPN_OP_SIZE_V2 + NONCE_WIRE_SIZE);
+
+ ovpn_skb_cb(skb)->req = req;
+ ovpn_skb_cb(skb)->ks = ks;
+
+ /* encrypt it */
+ return crypto_aead_encrypt(req);
+}
+
+static void ovpn_aead_decrypt_done(void *data, int ret)
+{
+ struct sk_buff *skb = data;
+
+ aead_request_free(ovpn_skb_cb(skb)->req);
+ ovpn_decrypt_post(skb, ret);
+}
+
+int ovpn_aead_decrypt(struct ovpn_crypto_key_slot *ks, struct sk_buff *skb)
+{
+ const unsigned int tag_size = crypto_aead_authsize(ks->decrypt);
+ struct scatterlist sg[MAX_SKB_FRAGS + 2];
+ int ret, payload_len, nfrags;
+ u8 *sg_data, iv[NONCE_SIZE];
+ unsigned int payload_offset;
+ DECLARE_CRYPTO_WAIT(wait);
+ struct aead_request *req;
+ struct sk_buff *trailer;
+ unsigned int sg_len;
+
+ payload_offset = OVPN_OP_SIZE_V2 + NONCE_WIRE_SIZE + tag_size;
+ payload_len = skb->len - payload_offset;
+
+ /* sanity check on packet size, payload size must be >= 0 */
+ if (unlikely(payload_len < 0))
+ return -EINVAL;
+
+ /* Prepare the skb data buffer to be accessed up until the auth tag.
+ * This is required because this area is directly mapped into the sg
+ * list.
+ */
+ if (unlikely(!pskb_may_pull(skb, payload_offset)))
+ return -ENODATA;
+
+ /* get number of skb frags and ensure that packet data is writable */
+ nfrags = skb_cow_data(skb, 0, &trailer);
+ if (unlikely(nfrags < 0))
+ return nfrags;
+
+ if (unlikely(nfrags + 2 > ARRAY_SIZE(sg)))
+ return -ENOSPC;
+
+ /* sg table:
+ * 0: op, wire nonce (AD, len=OVPN_OP_SIZE_V2+NONCE_WIRE_SIZE),
+ * 1, 2, 3, ..., n: payload,
+ * n+1: auth_tag (len=tag_size)
+ */
+ sg_init_table(sg, nfrags + 2);
+
+ /* packet op is head of additional data */
+ sg_data = skb->data;
+ sg_len = OVPN_OP_SIZE_V2 + NONCE_WIRE_SIZE;
+ sg_set_buf(sg, sg_data, sg_len);
+
+ /* build scatterlist to decrypt packet payload */
+ ret = skb_to_sgvec_nomark(skb, sg + 1, payload_offset, payload_len);
+ if (unlikely(nfrags != ret))
+ return -EINVAL;
+
+ /* append auth_tag onto scatterlist */
+ sg_set_buf(sg + nfrags + 1, skb->data + sg_len, tag_size);
+
+ /* copy nonce into IV buffer */
+ memcpy(iv, skb->data + OVPN_OP_SIZE_V2, NONCE_WIRE_SIZE);
+ memcpy(iv + NONCE_WIRE_SIZE, ks->nonce_tail_recv.u8,
+ sizeof(struct ovpn_nonce_tail));
+
+ req = aead_request_alloc(ks->decrypt, GFP_ATOMIC);
+ if (unlikely(!req))
+ return -ENOMEM;
+
+ /* setup async crypto operation */
+ aead_request_set_tfm(req, ks->decrypt);
+ aead_request_set_callback(req, 0, ovpn_aead_decrypt_done, NULL);
+ aead_request_set_crypt(req, sg, sg, payload_len + tag_size, iv);
+
+ aead_request_set_ad(req, NONCE_WIRE_SIZE + OVPN_OP_SIZE_V2);
+
+ ovpn_skb_cb(skb)->payload_offset = payload_offset;
+ ovpn_skb_cb(skb)->req = req;
+ ovpn_skb_cb(skb)->ks = ks;
+
+ /* decrypt it */
+ return crypto_aead_decrypt(req);
+}
+
+/* Initialize a struct crypto_aead object */
+struct crypto_aead *ovpn_aead_init(const char *title, const char *alg_name,
+ const unsigned char *key,
+ unsigned int keylen)
+{
+ struct crypto_aead *aead;
+ int ret;
+
+ aead = crypto_alloc_aead(alg_name, 0, 0);
+ if (IS_ERR(aead)) {
+ ret = PTR_ERR(aead);
+ pr_err("%s crypto_alloc_aead failed, err=%d\n", title, ret);
+ aead = NULL;
+ goto error;
+ }
+
+ ret = crypto_aead_setkey(aead, key, keylen);
+ if (ret) {
+ pr_err("%s crypto_aead_setkey size=%u failed, err=%d\n", title,
+ keylen, ret);
+ goto error;
+ }
+
+ ret = crypto_aead_setauthsize(aead, AUTH_TAG_SIZE);
+ if (ret) {
+ pr_err("%s crypto_aead_setauthsize failed, err=%d\n", title,
+ ret);
+ goto error;
+ }
+
+ /* basic AEAD assumption */
+ if (crypto_aead_ivsize(aead) != NONCE_SIZE) {
+ pr_err("%s IV size must be %d\n", title, NONCE_SIZE);
+ ret = -EINVAL;
+ goto error;
+ }
+
+ pr_debug("********* Cipher %s (%s)\n", alg_name, title);
+ pr_debug("*** IV size=%u\n", crypto_aead_ivsize(aead));
+ pr_debug("*** req size=%u\n", crypto_aead_reqsize(aead));
+ pr_debug("*** block size=%u\n", crypto_aead_blocksize(aead));
+ pr_debug("*** auth size=%u\n", crypto_aead_authsize(aead));
+ pr_debug("*** alignmask=0x%x\n", crypto_aead_alignmask(aead));
+
+ return aead;
+
+error:
+ crypto_free_aead(aead);
+ return ERR_PTR(ret);
+}
+
+void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks)
+{
+ if (!ks)
+ return;
+
+ crypto_free_aead(ks->encrypt);
+ crypto_free_aead(ks->decrypt);
+ kfree(ks);
+}
+
+struct ovpn_crypto_key_slot *
+ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc)
+{
+ struct ovpn_crypto_key_slot *ks = NULL;
+ const char *alg_name;
+ int ret;
+
+ /* validate crypto alg */
+ switch (kc->cipher_alg) {
+ case OVPN_CIPHER_ALG_AES_GCM:
+ alg_name = "gcm(aes)";
+ break;
+ case OVPN_CIPHER_ALG_CHACHA20_POLY1305:
+ alg_name = "rfc7539(chacha20,poly1305)";
+ break;
+ default:
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+
+ if (sizeof(struct ovpn_nonce_tail) != kc->encrypt.nonce_tail_size ||
+ sizeof(struct ovpn_nonce_tail) != kc->decrypt.nonce_tail_size)
+ return ERR_PTR(-EINVAL);
+
+ /* build the key slot */
+ ks = kmalloc(sizeof(*ks), GFP_KERNEL);
+ if (!ks)
+ return ERR_PTR(-ENOMEM);
+
+ ks->encrypt = NULL;
+ ks->decrypt = NULL;
+ kref_init(&ks->refcount);
+ ks->key_id = kc->key_id;
+
+ ks->encrypt = ovpn_aead_init("encrypt", alg_name,
+ kc->encrypt.cipher_key,
+ kc->encrypt.cipher_key_size);
+ if (IS_ERR(ks->encrypt)) {
+ ret = PTR_ERR(ks->encrypt);
+ ks->encrypt = NULL;
+ goto destroy_ks;
+ }
+
+ ks->decrypt = ovpn_aead_init("decrypt", alg_name,
+ kc->decrypt.cipher_key,
+ kc->decrypt.cipher_key_size);
+ if (IS_ERR(ks->decrypt)) {
+ ret = PTR_ERR(ks->decrypt);
+ ks->decrypt = NULL;
+ goto destroy_ks;
+ }
+
+ memcpy(ks->nonce_tail_xmit.u8, kc->encrypt.nonce_tail,
+ sizeof(struct ovpn_nonce_tail));
+ memcpy(ks->nonce_tail_recv.u8, kc->decrypt.nonce_tail,
+ sizeof(struct ovpn_nonce_tail));
+
+ /* init packet ID generation/validation */
+ ovpn_pktid_xmit_init(&ks->pid_xmit);
+ ovpn_pktid_recv_init(&ks->pid_recv);
+
+ return ks;
+
+destroy_ks:
+ ovpn_aead_crypto_key_slot_destroy(ks);
+ return ERR_PTR(ret);
+}
diff --git a/drivers/net/ovpn/crypto_aead.h b/drivers/net/ovpn/crypto_aead.h
new file mode 100644
index 000000000000..c876e6a711cd
--- /dev/null
+++ b/drivers/net/ovpn/crypto_aead.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_OVPNAEAD_H_
+#define _NET_OVPN_OVPNAEAD_H_
+
+#include "crypto.h"
+
+#include <asm/types.h>
+#include <linux/skbuff.h>
+
+struct crypto_aead *ovpn_aead_init(const char *title, const char *alg_name,
+ const unsigned char *key,
+ unsigned int keylen);
+
+int ovpn_aead_encrypt(struct ovpn_crypto_key_slot *ks, struct sk_buff *skb,
+ u32 peer_id);
+int ovpn_aead_decrypt(struct ovpn_crypto_key_slot *ks, struct sk_buff *skb);
+
+struct ovpn_crypto_key_slot *
+ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc);
+void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks);
+
+#endif /* _NET_OVPN_OVPNAEAD_H_ */
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index 643637572a40..7da1e7e27533 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -15,6 +15,8 @@
#include "ovpnstruct.h"
#include "peer.h"
#include "io.h"
+#include "crypto.h"
+#include "crypto_aead.h"
#include "netlink.h"
#include "proto.h"
#include "udp.h"
@@ -57,13 +59,67 @@ static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb)
dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
}
-static void ovpn_decrypt_post(struct sk_buff *skb, int ret)
+void ovpn_decrypt_post(struct sk_buff *skb, int ret)
{
+ struct ovpn_crypto_key_slot *ks = ovpn_skb_cb(skb)->ks;
struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer;
+ __be16 proto;
+ __be32 *pid;
+
+ /* crypto is happening asyncronously. this function will be called
+ * again later by the crypto callback with a proper return code
+ */
+ if (unlikely(ret == -EINPROGRESS))
+ return;
+
+ if (unlikely(ret < 0)) {
+ net_err_ratelimited("%s: error during decryption for peer %u, key-id %u: %d\n",
+ peer->ovpn->dev->name, peer->id, ks->key_id,
+ ret);
+ goto drop;
+ }
+ /* PID sits after the op */
+ pid = (__force __be32 *)(skb->data + OVPN_OP_SIZE_V2);
+ ret = ovpn_pktid_recv(&ks->pid_recv, ntohl(*pid), 0);
if (unlikely(ret < 0))
goto drop;
+ /* point to encapsulated IP packet */
+ __skb_pull(skb, ovpn_skb_cb(skb)->payload_offset);
+
+ /* check if this is a valid datapacket that has to be delivered to the
+ * ovpn interface
+ */
+ skb_reset_network_header(skb);
+ proto = ovpn_ip_check_protocol(skb);
+ if (unlikely(!proto)) {
+ /* check if null packet */
+ if (unlikely(!pskb_may_pull(skb, 1))) {
+ net_info_ratelimited("%s: NULL packet received from peer %u\n",
+ peer->ovpn->dev->name, peer->id);
+ goto drop;
+ }
+
+ net_info_ratelimited("%s: unsupported protocol received from peer %u\n",
+ peer->ovpn->dev->name, peer->id);
+ goto drop;
+ }
+ skb->protocol = proto;
+
+ /* perform Reverse Path Filtering (RPF) */
+ if (unlikely(!ovpn_peer_check_by_src(peer->ovpn, skb, peer))) {
+ if (skb_protocol_to_family(skb) == AF_INET6)
+ net_dbg_ratelimited("%s: RPF dropped packet from peer %u, src: %pI6c\n",
+ peer->ovpn->dev->name, peer->id,
+ &ipv6_hdr(skb)->saddr);
+ else
+ net_dbg_ratelimited("%s: RPF dropped packet from peer %u, src: %pI4\n",
+ peer->ovpn->dev->name, peer->id,
+ &ip_hdr(skb)->saddr);
+ goto drop;
+ }
+
ovpn_netdev_write(peer, skb);
/* skb is passed to upper layer - don't free it */
skb = NULL;
@@ -77,14 +133,45 @@ static void ovpn_decrypt_post(struct sk_buff *skb, int ret)
/* pick next packet from RX queue, decrypt and forward it to the device */
void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb)
{
+ struct ovpn_crypto_key_slot *ks;
+ u8 key_id;
+
+ /* get the key slot matching the key ID in the received packet */
+ key_id = ovpn_key_id_from_skb(skb);
+ ks = ovpn_crypto_key_id_to_slot(&peer->crypto, key_id);
+ if (unlikely(!ks)) {
+ net_info_ratelimited("%s: no available key for peer %u, key-id: %u\n",
+ peer->ovpn->dev->name, peer->id, key_id);
+ dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
+ kfree_skb(skb);
+ return;
+ }
+
ovpn_skb_cb(skb)->peer = peer;
- ovpn_decrypt_post(skb, 0);
+ ovpn_decrypt_post(skb, ovpn_aead_decrypt(ks, skb));
}
-static void ovpn_encrypt_post(struct sk_buff *skb, int ret)
+void ovpn_encrypt_post(struct sk_buff *skb, int ret)
{
+ struct ovpn_crypto_key_slot *ks = ovpn_skb_cb(skb)->ks;
struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer;
+ /* encryption is happening asynchronously. This function will be
+ * called later by the crypto callback with a proper return value
+ */
+ if (unlikely(ret == -EINPROGRESS))
+ return;
+
+ if (unlikely(ret == -ERANGE)) {
+ /* we ran out of IVs and we must kill the key as it can't be
+ * usea nymore
+ */
+ netdev_warn(peer->ovpn->dev,
+ "killing primary key for peer %u\n", peer->id);
+ ovpn_crypto_kill_primary(&peer->crypto);
+ goto err;
+ }
+
if (unlikely(ret < 0))
goto err;
@@ -105,18 +192,36 @@ static void ovpn_encrypt_post(struct sk_buff *skb, int ret)
dev_core_stats_tx_dropped_inc(peer->ovpn->dev);
kfree_skb(skb);
}
+ ovpn_crypto_key_slot_put(ks);
ovpn_peer_put(peer);
}
static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)
{
+ struct ovpn_crypto_key_slot *ks;
+
+ if (unlikely(skb->ip_summed == CHECKSUM_PARTIAL &&
+ skb_checksum_help(skb))) {
+ net_warn_ratelimited("%s: cannot compute checksum for outgoing packet\n",
+ peer->ovpn->dev->name);
+ return false;
+ }
+
+ /* get primary key to be used for encrypting data */
+ ks = ovpn_crypto_key_slot_primary(&peer->crypto);
+ if (unlikely(!ks)) {
+ net_warn_ratelimited("%s: error while retrieving primary key slot for peer %u\n",
+ peer->ovpn->dev->name, peer->id);
+ return false;
+ }
+
ovpn_skb_cb(skb)->peer = peer;
/* take a reference to the peer because the crypto code may run async.
* ovpn_encrypt_post() will release it upon completion
*/
DEBUG_NET_WARN_ON_ONCE(!ovpn_peer_hold(peer));
- ovpn_encrypt_post(skb, 0);
+ ovpn_encrypt_post(skb, ovpn_aead_encrypt(ks, skb, peer->id));
return true;
}
diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h
index 9667a0a470e0..ad741552d742 100644
--- a/drivers/net/ovpn/io.h
+++ b/drivers/net/ovpn/io.h
@@ -14,4 +14,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev);
void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb);
+void ovpn_encrypt_post(struct sk_buff *skb, int ret);
+void ovpn_decrypt_post(struct sk_buff *skb, int ret);
+
#endif /* _NET_OVPN_OVPN_H_ */
diff --git a/drivers/net/ovpn/packet.h b/drivers/net/ovpn/packet.h
index 7ed146f5932a..e14c9bf464f7 100644
--- a/drivers/net/ovpn/packet.h
+++ b/drivers/net/ovpn/packet.h
@@ -10,7 +10,7 @@
#ifndef _NET_OVPN_PACKET_H_
#define _NET_OVPN_PACKET_H_
-/* When the OpenVPN protocol is ran in AEAD mode, use
+/* When the OpenVPN protocol is run in AEAD mode, use
* the OpenVPN packet ID as the AEAD nonce:
*
* 00000005 521c3b01 4308c041
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 746b6cc0d516..2a89893b3a50 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -12,6 +12,8 @@
#include "ovpnstruct.h"
#include "bind.h"
+#include "pktid.h"
+#include "crypto.h"
#include "io.h"
#include "main.h"
#include "netlink.h"
@@ -42,6 +44,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
peer->vpn_addrs.ipv6 = in6addr_any;
RCU_INIT_POINTER(peer->bind, NULL);
+ ovpn_crypto_state_init(&peer->crypto);
spin_lock_init(&peer->lock);
kref_init(&peer->refcount);
@@ -70,6 +73,7 @@ static void ovpn_peer_release(struct ovpn_peer *peer)
if (peer->sock)
ovpn_socket_put(peer->sock);
+ ovpn_crypto_state_release(&peer->crypto);
ovpn_bind_reset(peer, NULL);
dst_cache_destroy(&peer->dst_cache);
}
@@ -279,6 +283,31 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn,
return peer;
}
+/**
+ * ovpn_peer_check_by_src - check that skb source is routed via peer
+ * @ovpn: the openvpn instance to search
+ * @skb: the packet to extra source address from
+ * @peer: the peer to check against the source address
+ *
+ * Return: true if the peer is matching or false otherwise
+ */
+bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb,
+ struct ovpn_peer *peer)
+{
+ bool match = false;
+
+ if (ovpn->mode == OVPN_MODE_P2P) {
+ /* in P2P mode, no matter the destination, packets are always
+ * sent to the single peer listening on the other side
+ */
+ rcu_read_lock();
+ match = (peer == rcu_dereference(ovpn->peer));
+ rcu_read_unlock();
+ }
+
+ return match;
+}
+
/**
* ovpn_peer_add_p2p - add peer to related tables in a P2P instance
* @ovpn: the instance to add the peer to
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 37de5aff54a8..70d92cd5bd63 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -11,6 +11,8 @@
#define _NET_OVPN_OVPNPEER_H_
#include "bind.h"
+#include "pktid.h"
+#include "crypto.h"
#include "socket.h"
#include <net/dst_cache.h>
@@ -24,6 +26,7 @@
* @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel
* @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel
* @sock: the socket being used to talk to this peer
+ * @crypto: the crypto configuration (ciphers, keys, etc..)
* @dst_cache: cache for dst_entry used to send to peer
* @bind: remote peer binding
* @halt: true if ovpn_peer_mark_delete was called
@@ -41,6 +44,7 @@ struct ovpn_peer {
struct in6_addr ipv6;
} vpn_addrs;
struct ovpn_socket *sock;
+ struct ovpn_crypto_state crypto;
struct dst_cache dst_cache;
struct ovpn_bind __rcu *bind;
bool halt;
@@ -83,5 +87,7 @@ struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn,
struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id);
struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn,
struct sk_buff *skb);
+bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb,
+ struct ovpn_peer *peer);
#endif /* _NET_OVPN_OVPNPEER_H_ */
diff --git a/drivers/net/ovpn/pktid.c b/drivers/net/ovpn/pktid.c
new file mode 100644
index 000000000000..96dc87635670
--- /dev/null
+++ b/drivers/net/ovpn/pktid.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ * James Yonan <james@openvpn.net>
+ */
+
+#include <linux/atomic.h>
+#include <linux/jiffies.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/types.h>
+
+#include "ovpnstruct.h"
+#include "main.h"
+#include "packet.h"
+#include "pktid.h"
+
+void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid)
+{
+ atomic64_set(&pid->seq_num, 1);
+}
+
+void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr)
+{
+ memset(pr, 0, sizeof(*pr));
+ spin_lock_init(&pr->lock);
+}
+
+/* Packet replay detection.
+ * Allows ID backtrack of up to REPLAY_WINDOW_SIZE - 1.
+ */
+int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time)
+{
+ const unsigned long now = jiffies;
+ int ret;
+
+ /* ID must not be zero */
+ if (unlikely(pkt_id == 0))
+ return -EINVAL;
+
+ spin_lock_bh(&pr->lock);
+
+ /* expire backtracks at or below pr->id after PKTID_RECV_EXPIRE time */
+ if (unlikely(time_after_eq(now, pr->expire)))
+ pr->id_floor = pr->id;
+
+ /* time changed? */
+ if (unlikely(pkt_time != pr->time)) {
+ if (pkt_time > pr->time) {
+ /* time moved forward, accept */
+ pr->base = 0;
+ pr->extent = 0;
+ pr->id = 0;
+ pr->time = pkt_time;
+ pr->id_floor = 0;
+ } else {
+ /* time moved backward, reject */
+ ret = -ETIME;
+ goto out;
+ }
+ }
+
+ if (likely(pkt_id == pr->id + 1)) {
+ /* well-formed ID sequence (incremented by 1) */
+ pr->base = REPLAY_INDEX(pr->base, -1);
+ pr->history[pr->base / 8] |= (1 << (pr->base % 8));
+ if (pr->extent < REPLAY_WINDOW_SIZE)
+ ++pr->extent;
+ pr->id = pkt_id;
+ } else if (pkt_id > pr->id) {
+ /* ID jumped forward by more than one */
+ const unsigned int delta = pkt_id - pr->id;
+
+ if (delta < REPLAY_WINDOW_SIZE) {
+ unsigned int i;
+
+ pr->base = REPLAY_INDEX(pr->base, -delta);
+ pr->history[pr->base / 8] |= (1 << (pr->base % 8));
+ pr->extent += delta;
+ if (pr->extent > REPLAY_WINDOW_SIZE)
+ pr->extent = REPLAY_WINDOW_SIZE;
+ for (i = 1; i < delta; ++i) {
+ unsigned int newb = REPLAY_INDEX(pr->base, i);
+
+ pr->history[newb / 8] &= ~BIT(newb % 8);
+ }
+ } else {
+ pr->base = 0;
+ pr->extent = REPLAY_WINDOW_SIZE;
+ memset(pr->history, 0, sizeof(pr->history));
+ pr->history[0] = 1;
+ }
+ pr->id = pkt_id;
+ } else {
+ /* ID backtrack */
+ const unsigned int delta = pr->id - pkt_id;
+
+ if (delta > pr->max_backtrack)
+ pr->max_backtrack = delta;
+ if (delta < pr->extent) {
+ if (pkt_id > pr->id_floor) {
+ const unsigned int ri = REPLAY_INDEX(pr->base,
+ delta);
+ u8 *p = &pr->history[ri / 8];
+ const u8 mask = (1 << (ri % 8));
+
+ if (*p & mask) {
+ ret = -EINVAL;
+ goto out;
+ }
+ *p |= mask;
+ } else {
+ ret = -EINVAL;
+ goto out;
+ }
+ } else {
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+ pr->expire = now + PKTID_RECV_EXPIRE;
+ ret = 0;
+out:
+ spin_unlock_bh(&pr->lock);
+ return ret;
+}
diff --git a/drivers/net/ovpn/pktid.h b/drivers/net/ovpn/pktid.h
new file mode 100644
index 000000000000..fe02f0667e1a
--- /dev/null
+++ b/drivers/net/ovpn/pktid.h
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ * James Yonan <james@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_OVPNPKTID_H_
+#define _NET_OVPN_OVPNPKTID_H_
+
+#include "packet.h"
+
+/* If no packets received for this length of time, set a backtrack floor
+ * at highest received packet ID thus far.
+ */
+#define PKTID_RECV_EXPIRE (30 * HZ)
+
+/* Packet-ID state for transmitter */
+struct ovpn_pktid_xmit {
+ atomic64_t seq_num;
+};
+
+/* replay window sizing in bytes = 2^REPLAY_WINDOW_ORDER */
+#define REPLAY_WINDOW_ORDER 8
+
+#define REPLAY_WINDOW_BYTES BIT(REPLAY_WINDOW_ORDER)
+#define REPLAY_WINDOW_SIZE (REPLAY_WINDOW_BYTES * 8)
+#define REPLAY_INDEX(base, i) (((base) + (i)) & (REPLAY_WINDOW_SIZE - 1))
+
+/* Packet-ID state for receiver.
+ * Other than lock member, can be zeroed to initialize.
+ */
+struct ovpn_pktid_recv {
+ /* "sliding window" bitmask of recent packet IDs received */
+ u8 history[REPLAY_WINDOW_BYTES];
+ /* bit position of deque base in history */
+ unsigned int base;
+ /* extent (in bits) of deque in history */
+ unsigned int extent;
+ /* expiration of history in jiffies */
+ unsigned long expire;
+ /* highest sequence number received */
+ u32 id;
+ /* highest time stamp received */
+ u32 time;
+ /* we will only accept backtrack IDs > id_floor */
+ u32 id_floor;
+ unsigned int max_backtrack;
+ /* protects entire pktd ID state */
+ spinlock_t lock;
+};
+
+/* Get the next packet ID for xmit */
+static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, u32 *pktid)
+{
+ const s64 seq_num = atomic64_fetch_add_unless(&pid->seq_num, 1,
+ 0x100000000LL);
+ /* when the 32bit space is over, we return an error because the packet
+ * ID is used to create the cipher IV and we do not want to reuse the
+ * same value more than once
+ */
+ if (unlikely(seq_num == 0x100000000LL))
+ return -ERANGE;
+
+ *pktid = (u32)seq_num;
+
+ return 0;
+}
+
+/* Write 12-byte AEAD IV to dest */
+static inline void ovpn_pktid_aead_write(const u32 pktid,
+ const struct ovpn_nonce_tail *nt,
+ unsigned char *dest)
+{
+ *(__force __be32 *)(dest) = htonl(pktid);
+ BUILD_BUG_ON(4 + sizeof(struct ovpn_nonce_tail) != NONCE_SIZE);
+ memcpy(dest + 4, nt->u8, sizeof(struct ovpn_nonce_tail));
+}
+
+void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid);
+void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr);
+
+int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time);
+
+#endif /* _NET_OVPN_OVPNPKTID_H_ */
diff --git a/drivers/net/ovpn/proto.h b/drivers/net/ovpn/proto.h
index 69604cf26bbf..32af6b8e5743 100644
--- a/drivers/net/ovpn/proto.h
+++ b/drivers/net/ovpn/proto.h
@@ -72,4 +72,35 @@ static inline u32 ovpn_peer_id_from_skb(const struct sk_buff *skb, u16 offset)
return ntohl(*(__be32 *)(skb->data + offset)) & OVPN_PEER_ID_MASK;
}
+/**
+ * ovpn_key_id_from_skb - extract key ID from the skb head
+ * @skb: the packet to extract the key ID code from
+ *
+ * Note: this function assumes that the skb head was pulled enough
+ * to access the first byte.
+ *
+ * Return: the key ID
+ */
+static inline u8 ovpn_key_id_from_skb(const struct sk_buff *skb)
+{
+ return *skb->data & OVPN_KEY_ID_MASK;
+}
+
+/**
+ * ovpn_opcode_compose - combine OP code, key ID and peer ID to wire format
+ * @opcode: the OP code
+ * @key_id: the key ID
+ * @peer_id: the peer ID
+ *
+ * Return: a 4 bytes integer obtained combining all input values following the
+ * OpenVPN wire format. This integer can then be written to the packet header.
+ */
+static inline u32 ovpn_opcode_compose(u8 opcode, u8 key_id, u32 peer_id)
+{
+ const u8 op = (opcode << OVPN_OPCODE_SHIFT) |
+ (key_id & OVPN_KEY_ID_MASK);
+
+ return (op << 24) | (peer_id & OVPN_PEER_ID_MASK);
+}
+
#endif /* _NET_OVPN_OVPNPROTO_H_ */
diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h
index e070fe6f448c..44786e34b704 100644
--- a/drivers/net/ovpn/skb.h
+++ b/drivers/net/ovpn/skb.h
@@ -19,6 +19,10 @@
struct ovpn_cb {
struct ovpn_peer *peer;
+ struct sk_buff *skb;
+ struct ovpn_crypto_key_slot *ks;
+ struct aead_request *req;
+ unsigned int payload_offset;
};
static inline struct ovpn_cb *ovpn_skb_cb(struct sk_buff *skb)
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 13/25] ovpn: store tunnel and transport statistics
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (11 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 12/25] ovpn: implement packet processing Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 14/25] ovpn: implement TCP transport Antonio Quartulli
` (11 subsequent siblings)
24 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
Byte/packet counters for in-tunnel and transport streams
are now initialized and updated as needed.
To be exported via netlink.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/Makefile | 1 +
drivers/net/ovpn/io.c | 12 ++++++++++
drivers/net/ovpn/peer.c | 3 +++
drivers/net/ovpn/peer.h | 9 ++++++++
drivers/net/ovpn/skb.h | 1 +
drivers/net/ovpn/stats.c | 21 +++++++++++++++++
drivers/net/ovpn/stats.h | 47 +++++++++++++++++++++++++++++++++++++++
7 files changed, 94 insertions(+)
create mode 100644 drivers/net/ovpn/stats.c
create mode 100644 drivers/net/ovpn/stats.h
diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile
index ccdaeced1982..d43fda72646b 100644
--- a/drivers/net/ovpn/Makefile
+++ b/drivers/net/ovpn/Makefile
@@ -17,4 +17,5 @@ ovpn-y += netlink-gen.o
ovpn-y += peer.o
ovpn-y += pktid.o
ovpn-y += socket.o
+ovpn-y += stats.o
ovpn-y += udp.o
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index 7da1e7e27533..0475440642dd 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -11,6 +11,7 @@
#include <linux/skbuff.h>
#include <net/gro_cells.h>
#include <net/gso.h>
+#include <net/ip.h>
#include "ovpnstruct.h"
#include "peer.h"
@@ -19,6 +20,7 @@
#include "crypto_aead.h"
#include "netlink.h"
#include "proto.h"
+#include "socket.h"
#include "udp.h"
#include "skb.h"
@@ -120,6 +122,11 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
goto drop;
}
+ /* increment RX stats */
+ ovpn_peer_stats_increment_rx(&peer->vpn_stats, skb->len);
+ ovpn_peer_stats_increment_rx(&peer->link_stats,
+ ovpn_skb_cb(skb)->orig_len);
+
ovpn_netdev_write(peer, skb);
/* skb is passed to upper layer - don't free it */
skb = NULL;
@@ -148,6 +155,7 @@ void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb)
}
ovpn_skb_cb(skb)->peer = peer;
+ ovpn_skb_cb(skb)->orig_len = skb->len;
ovpn_decrypt_post(skb, ovpn_aead_decrypt(ks, skb));
}
@@ -176,6 +184,9 @@ void ovpn_encrypt_post(struct sk_buff *skb, int ret)
goto err;
skb_mark_not_on_list(skb);
+ ovpn_peer_stats_increment_tx(&peer->link_stats, skb->len);
+ ovpn_peer_stats_increment_tx(&peer->vpn_stats,
+ ovpn_skb_cb(skb)->orig_len);
switch (peer->sock->sock->sk->sk_protocol) {
case IPPROTO_UDP:
@@ -216,6 +227,7 @@ static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)
}
ovpn_skb_cb(skb)->peer = peer;
+ ovpn_skb_cb(skb)->orig_len = skb->len;
/* take a reference to the peer because the crypto code may run async.
* ovpn_encrypt_post() will release it upon completion
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 2a89893b3a50..f633b70bb140 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -18,6 +18,7 @@
#include "main.h"
#include "netlink.h"
#include "peer.h"
+#include "socket.h"
/**
* ovpn_peer_new - allocate and initialize a new peer object
@@ -47,6 +48,8 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
ovpn_crypto_state_init(&peer->crypto);
spin_lock_init(&peer->lock);
kref_init(&peer->refcount);
+ ovpn_peer_stats_init(&peer->vpn_stats);
+ ovpn_peer_stats_init(&peer->link_stats);
ret = dst_cache_init(&peer->dst_cache, GFP_KERNEL);
if (ret < 0) {
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 70d92cd5bd63..dd4d91dfabb5 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -10,10 +10,15 @@
#ifndef _NET_OVPN_OVPNPEER_H_
#define _NET_OVPN_OVPNPEER_H_
+#include <linux/ptr_ring.h>
+#include <net/dst_cache.h>
+#include <uapi/linux/ovpn.h>
+
#include "bind.h"
#include "pktid.h"
#include "crypto.h"
#include "socket.h"
+#include "stats.h"
#include <net/dst_cache.h>
#include <uapi/linux/ovpn.h>
@@ -30,6 +35,8 @@
* @dst_cache: cache for dst_entry used to send to peer
* @bind: remote peer binding
* @halt: true if ovpn_peer_mark_delete was called
+ * @vpn_stats: per-peer in-VPN TX/RX stays
+ * @link_stats: per-peer link/transport TX/RX stats
* @delete_reason: why peer was deleted (i.e. timeout, transport error, ..)
* @lock: protects binding to peer (bind)
* @refcount: reference counter
@@ -48,6 +55,8 @@ struct ovpn_peer {
struct dst_cache dst_cache;
struct ovpn_bind __rcu *bind;
bool halt;
+ struct ovpn_peer_stats vpn_stats;
+ struct ovpn_peer_stats link_stats;
enum ovpn_del_peer_reason delete_reason;
spinlock_t lock; /* protects bind */
struct kref refcount;
diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h
index 44786e34b704..99e5bfe252c0 100644
--- a/drivers/net/ovpn/skb.h
+++ b/drivers/net/ovpn/skb.h
@@ -22,6 +22,7 @@ struct ovpn_cb {
struct sk_buff *skb;
struct ovpn_crypto_key_slot *ks;
struct aead_request *req;
+ size_t orig_len;
unsigned int payload_offset;
};
diff --git a/drivers/net/ovpn/stats.c b/drivers/net/ovpn/stats.c
new file mode 100644
index 000000000000..a383842c3449
--- /dev/null
+++ b/drivers/net/ovpn/stats.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <linux/atomic.h>
+
+#include "stats.h"
+
+void ovpn_peer_stats_init(struct ovpn_peer_stats *ps)
+{
+ atomic64_set(&ps->rx.bytes, 0);
+ atomic64_set(&ps->rx.packets, 0);
+
+ atomic64_set(&ps->tx.bytes, 0);
+ atomic64_set(&ps->tx.packets, 0);
+}
diff --git a/drivers/net/ovpn/stats.h b/drivers/net/ovpn/stats.h
new file mode 100644
index 000000000000..868f49d25eaa
--- /dev/null
+++ b/drivers/net/ovpn/stats.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: James Yonan <james@openvpn.net>
+ * Antonio Quartulli <antonio@openvpn.net>
+ * Lev Stipakov <lev@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_OVPNSTATS_H_
+#define _NET_OVPN_OVPNSTATS_H_
+
+/* one stat */
+struct ovpn_peer_stat {
+ atomic64_t bytes;
+ atomic64_t packets;
+};
+
+/* rx and tx stats combined */
+struct ovpn_peer_stats {
+ struct ovpn_peer_stat rx;
+ struct ovpn_peer_stat tx;
+};
+
+void ovpn_peer_stats_init(struct ovpn_peer_stats *ps);
+
+static inline void ovpn_peer_stats_increment(struct ovpn_peer_stat *stat,
+ const unsigned int n)
+{
+ atomic64_add(n, &stat->bytes);
+ atomic64_inc(&stat->packets);
+}
+
+static inline void ovpn_peer_stats_increment_rx(struct ovpn_peer_stats *stats,
+ const unsigned int n)
+{
+ ovpn_peer_stats_increment(&stats->rx, n);
+}
+
+static inline void ovpn_peer_stats_increment_tx(struct ovpn_peer_stats *stats,
+ const unsigned int n)
+{
+ ovpn_peer_stats_increment(&stats->tx, n);
+}
+
+#endif /* _NET_OVPN_OVPNSTATS_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 14/25] ovpn: implement TCP transport
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (12 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 13/25] ovpn: store tunnel and transport statistics Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-15 9:59 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 15/25] ovpn: implement multi-peer support Antonio Quartulli
` (10 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
With this change ovpn is allowed to communicate to peers also via TCP.
Parsing of incoming messages is implemented through the strparser API.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/Kconfig | 1 +
drivers/net/ovpn/Makefile | 1 +
drivers/net/ovpn/io.c | 9 +-
drivers/net/ovpn/main.c | 3 +
drivers/net/ovpn/peer.h | 38 ++-
drivers/net/ovpn/socket.c | 27 +-
drivers/net/ovpn/socket.h | 7 +-
drivers/net/ovpn/tcp.c | 502 ++++++++++++++++++++++++++++++++++++++
drivers/net/ovpn/tcp.h | 42 ++++
9 files changed, 624 insertions(+), 6 deletions(-)
create mode 100644 drivers/net/ovpn/tcp.c
create mode 100644 drivers/net/ovpn/tcp.h
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index c5743288242d..ddc65bc1e218 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -118,6 +118,7 @@ config WIREGUARD_DEBUG
config OVPN
tristate "OpenVPN data channel offload"
depends on NET && INET
+ select STREAM_PARSER
select NET_UDP_TUNNEL
select DST_CACHE
select CRYPTO
diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile
index d43fda72646b..f4d4bd87c851 100644
--- a/drivers/net/ovpn/Makefile
+++ b/drivers/net/ovpn/Makefile
@@ -18,4 +18,5 @@ ovpn-y += peer.o
ovpn-y += pktid.o
ovpn-y += socket.o
ovpn-y += stats.o
+ovpn-y += tcp.o
ovpn-y += udp.o
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index 0475440642dd..764b3df996bc 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -21,6 +21,7 @@
#include "netlink.h"
#include "proto.h"
#include "socket.h"
+#include "tcp.h"
#include "udp.h"
#include "skb.h"
@@ -84,8 +85,11 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
/* PID sits after the op */
pid = (__force __be32 *)(skb->data + OVPN_OP_SIZE_V2);
ret = ovpn_pktid_recv(&ks->pid_recv, ntohl(*pid), 0);
- if (unlikely(ret < 0))
+ if (unlikely(ret < 0)) {
+ net_err_ratelimited("%s: PKT ID RX error: %d\n",
+ peer->ovpn->dev->name, ret);
goto drop;
+ }
/* point to encapsulated IP packet */
__skb_pull(skb, ovpn_skb_cb(skb)->payload_offset);
@@ -192,6 +196,9 @@ void ovpn_encrypt_post(struct sk_buff *skb, int ret)
case IPPROTO_UDP:
ovpn_udp_send_skb(peer->ovpn, peer, skb);
break;
+ case IPPROTO_TCP:
+ ovpn_tcp_send_skb(peer, skb);
+ break;
default:
/* no transport configured yet */
goto err;
diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
index 4e6f6fd5ae56..19f7c0ff679b 100644
--- a/drivers/net/ovpn/main.c
+++ b/drivers/net/ovpn/main.c
@@ -25,6 +25,7 @@
#include "io.h"
#include "packet.h"
#include "peer.h"
+#include "tcp.h"
/* Driver info */
#define DRV_DESCRIPTION "OpenVPN data channel offload (ovpn)"
@@ -279,6 +280,8 @@ static int __init ovpn_init(void)
goto unreg_rtnl;
}
+ ovpn_tcp_init();
+
return 0;
unreg_rtnl:
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index dd4d91dfabb5..86d4696b1529 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -10,8 +10,8 @@
#ifndef _NET_OVPN_OVPNPEER_H_
#define _NET_OVPN_OVPNPEER_H_
-#include <linux/ptr_ring.h>
#include <net/dst_cache.h>
+#include <net/strparser.h>
#include <uapi/linux/ovpn.h>
#include "bind.h"
@@ -31,6 +31,18 @@
* @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel
* @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel
* @sock: the socket being used to talk to this peer
+ * @tcp: keeps track of TCP specific state
+ * @tcp.strp: stream parser context (TCP only)
+ * @tcp.tx_work: work for deferring outgoing packet processing (TCP only)
+ * @tcp.user_queue: received packets that have to go to userspace (TCP only)
+ * @tcp.tx_in_progress: true if TX is already ongoing (TCP only)
+ * @tcp.out_msg.skb: packet scheduled for sending (TCP only)
+ * @tcp.out_msg.offset: offset where next send should start (TCP only)
+ * @tcp.out_msg.len: remaining data to send within packet (TCP only)
+ * @tcp.sk_cb.sk_data_ready: pointer to original cb (TCP only)
+ * @tcp.sk_cb.sk_write_space: pointer to original cb (TCP only)
+ * @tcp.sk_cb.prot: pointer to original prot object (TCP only)
+ * @tcp.sk_cb.ops: pointer to the original prot_ops object (TCP only)
* @crypto: the crypto configuration (ciphers, keys, etc..)
* @dst_cache: cache for dst_entry used to send to peer
* @bind: remote peer binding
@@ -51,6 +63,30 @@ struct ovpn_peer {
struct in6_addr ipv6;
} vpn_addrs;
struct ovpn_socket *sock;
+
+ /* state of the TCP reading. Needed to keep track of how much of a
+ * single packet has already been read from the stream and how much is
+ * missing
+ */
+ struct {
+ struct strparser strp;
+ struct work_struct tx_work;
+ struct sk_buff_head user_queue;
+ bool tx_in_progress;
+
+ struct {
+ struct sk_buff *skb;
+ int offset;
+ int len;
+ } out_msg;
+
+ struct {
+ void (*sk_data_ready)(struct sock *sk);
+ void (*sk_write_space)(struct sock *sk);
+ struct proto *prot;
+ const struct proto_ops *ops;
+ } sk_cb;
+ } tcp;
struct ovpn_crypto_state crypto;
struct dst_cache dst_cache;
struct ovpn_bind __rcu *bind;
diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c
index 964b566de69f..8bb3b0cb18ec 100644
--- a/drivers/net/ovpn/socket.c
+++ b/drivers/net/ovpn/socket.c
@@ -15,6 +15,7 @@
#include "io.h"
#include "peer.h"
#include "socket.h"
+#include "tcp.h"
#include "udp.h"
static void ovpn_socket_detach(struct socket *sock)
@@ -24,6 +25,8 @@ static void ovpn_socket_detach(struct socket *sock)
if (sock->sk->sk_protocol == IPPROTO_UDP)
ovpn_udp_socket_detach(sock);
+ else if (sock->sk->sk_protocol == IPPROTO_TCP)
+ ovpn_tcp_socket_detach(sock);
sockfd_put(sock);
}
@@ -70,6 +73,8 @@ static int ovpn_socket_attach(struct socket *sock, struct ovpn_peer *peer)
if (sock->sk->sk_protocol == IPPROTO_UDP)
ret = ovpn_udp_socket_attach(sock, peer->ovpn);
+ else if (sock->sk->sk_protocol == IPPROTO_TCP)
+ ret = ovpn_tcp_socket_attach(sock, peer);
return ret;
}
@@ -131,14 +136,30 @@ struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer)
}
ovpn_sock = kzalloc(sizeof(*ovpn_sock), GFP_KERNEL);
- if (!ovpn_sock)
- return ERR_PTR(-ENOMEM);
+ if (!ovpn_sock) {
+ ret = -ENOMEM;
+ goto err;
+ }
- ovpn_sock->ovpn = peer->ovpn;
ovpn_sock->sock = sock;
kref_init(&ovpn_sock->refcount);
+ /* TCP sockets are per-peer, therefore they are linked to their unique
+ * peer
+ */
+ if (sock->sk->sk_protocol == IPPROTO_TCP) {
+ ovpn_sock->peer = peer;
+ } else {
+ /* in UDP we only link the ovpn instance since the socket is
+ * shared among multiple peers
+ */
+ ovpn_sock->ovpn = peer->ovpn;
+ }
+
rcu_assign_sk_user_data(sock->sk, ovpn_sock);
return ovpn_sock;
+err:
+ ovpn_socket_detach(sock);
+ return ERR_PTR(ret);
}
diff --git a/drivers/net/ovpn/socket.h b/drivers/net/ovpn/socket.h
index 08ca1ec25aa2..65883231f6ab 100644
--- a/drivers/net/ovpn/socket.h
+++ b/drivers/net/ovpn/socket.h
@@ -21,12 +21,17 @@ struct ovpn_peer;
/**
* struct ovpn_socket - a kernel socket referenced in the ovpn code
* @ovpn: ovpn instance owning this socket (UDP only)
+ * @peer: unique peer transmitting over this socket (TCP only)
* @sock: the low level sock object
* @refcount: amount of contexts currently referencing this object
* @rcu: member used to schedule RCU destructor callback
*/
struct ovpn_socket {
- struct ovpn_struct *ovpn;
+ union {
+ struct ovpn_struct *ovpn;
+ struct ovpn_peer *peer;
+ };
+
struct socket *sock;
struct kref refcount;
struct rcu_head rcu;
diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
new file mode 100644
index 000000000000..357aeed977ee
--- /dev/null
+++ b/drivers/net/ovpn/tcp.c
@@ -0,0 +1,502 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2019-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <linux/skbuff.h>
+#include <net/hotdata.h>
+#include <net/inet_common.h>
+#include <net/tcp.h>
+#include <net/route.h>
+#include <trace/events/sock.h>
+
+#include "ovpnstruct.h"
+#include "main.h"
+#include "io.h"
+#include "packet.h"
+#include "peer.h"
+#include "proto.h"
+#include "skb.h"
+#include "socket.h"
+#include "tcp.h"
+
+static struct proto ovpn_tcp_prot __ro_after_init;
+static struct proto_ops ovpn_tcp_ops __ro_after_init;
+static struct proto ovpn_tcp6_prot;
+static struct proto_ops ovpn_tcp6_ops;
+static DEFINE_MUTEX(tcp6_prot_mutex);
+
+static int ovpn_tcp_parse(struct strparser *strp, struct sk_buff *skb)
+{
+ struct strp_msg *rxm = strp_msg(skb);
+ __be16 blen;
+ u16 len;
+ int err;
+
+ /* when packets are written to the TCP stream, they are prepended with
+ * two bytes indicating the actual packet size.
+ * Here we read those two bytes and move the skb data pointer to the
+ * beginning of the packet
+ */
+
+ if (skb->len < rxm->offset + 2)
+ return 0;
+
+ err = skb_copy_bits(skb, rxm->offset, &blen, sizeof(blen));
+ if (err < 0)
+ return err;
+
+ len = be16_to_cpu(blen);
+ if (len < 2)
+ return -EINVAL;
+
+ return len + 2;
+}
+
+/* queue skb for sending to userspace via recvmsg on the socket */
+static int ovpn_tcp_to_userspace(struct ovpn_socket *sock, struct sk_buff *skb)
+{
+ struct sock *sk = sock->sock->sk;
+
+ skb_set_owner_r(skb, sk);
+ memset(skb->cb, 0, sizeof(skb->cb));
+ skb_queue_tail(&sock->peer->tcp.user_queue, skb);
+ sock->peer->tcp.sk_cb.sk_data_ready(sk);
+
+ return 0;
+}
+
+static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
+{
+ struct ovpn_peer *peer = container_of(strp, struct ovpn_peer, tcp.strp);
+ struct strp_msg *msg = strp_msg(skb);
+ size_t pkt_len = msg->full_len - 2;
+ size_t off = msg->offset + 2;
+
+ /* ensure skb->data points to the beginning of the openvpn packet */
+ if (!pskb_pull(skb, off)) {
+ net_warn_ratelimited("%s: packet too small\n",
+ peer->ovpn->dev->name);
+ goto err;
+ }
+
+ /* strparser does not trim the skb for us, therefore we do it now */
+ if (pskb_trim(skb, pkt_len) != 0) {
+ net_warn_ratelimited("%s: trimming skb failed\n",
+ peer->ovpn->dev->name);
+ goto err;
+ }
+
+ /* we need the first byte of data to be accessible
+ * to extract the opcode and the key ID later on
+ */
+ if (!pskb_may_pull(skb, 1)) {
+ net_warn_ratelimited("%s: packet too small to fetch opcode\n",
+ peer->ovpn->dev->name);
+ goto err;
+ }
+
+ /* DATA_V2 packets are handled in kernel, the rest goes to user space */
+ if (likely(ovpn_opcode_from_skb(skb, 0) == OVPN_DATA_V2)) {
+ /* hold reference to peer as required by ovpn_recv().
+ *
+ * NOTE: in this context we should already be holding a
+ * reference to this peer, therefore ovpn_peer_hold() is
+ * not expected to fail
+ */
+ WARN_ON(!ovpn_peer_hold(peer));
+ ovpn_recv(peer, skb);
+ } else {
+ /* The packet size header must be there when sending the packet
+ * to userspace, therefore we put it back
+ */
+ skb_push(skb, 2);
+ memset(skb->cb, 0, sizeof(skb->cb));
+ if (ovpn_tcp_to_userspace(peer->sock, skb) < 0) {
+ net_warn_ratelimited("%s: cannot send skb to userspace\n",
+ peer->ovpn->dev->name);
+ goto err;
+ }
+ }
+
+ return;
+err:
+ netdev_err(peer->ovpn->dev,
+ "cannot process incoming TCP data for peer %u\n", peer->id);
+ dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
+ kfree_skb(skb);
+ ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_ERROR);
+}
+
+static int ovpn_tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
+ int flags, int *addr_len)
+{
+ int err = 0, off, copied = 0, ret;
+ struct ovpn_socket *sock;
+ struct ovpn_peer *peer;
+ struct sk_buff *skb;
+
+ rcu_read_lock();
+ sock = rcu_dereference_sk_user_data(sk);
+ if (!sock || !sock->peer) {
+ rcu_read_unlock();
+ return -EBADF;
+ }
+ /* we take a reference to the peer linked to this TCP socket, because
+ * in turn the peer holds a reference to the socket itself.
+ * By doing so we also ensure that the peer stays alive along with
+ * the socket while executing this function
+ */
+ ovpn_peer_hold(sock->peer);
+ peer = sock->peer;
+ rcu_read_unlock();
+
+ skb = __skb_recv_datagram(sk, &peer->tcp.user_queue, flags, &off, &err);
+ if (!skb) {
+ if (err == -EAGAIN && sk->sk_shutdown & RCV_SHUTDOWN) {
+ ret = 0;
+ goto out;
+ }
+ ret = err;
+ goto out;
+ }
+
+ copied = len;
+ if (copied > skb->len)
+ copied = skb->len;
+ else if (copied < skb->len)
+ msg->msg_flags |= MSG_TRUNC;
+
+ err = skb_copy_datagram_msg(skb, 0, msg, copied);
+ if (unlikely(err)) {
+ kfree_skb(skb);
+ ret = err;
+ goto out;
+ }
+
+ if (flags & MSG_TRUNC)
+ copied = skb->len;
+ kfree_skb(skb);
+ ret = copied;
+out:
+ ovpn_peer_put(peer);
+ return ret;
+}
+
+void ovpn_tcp_socket_detach(struct socket *sock)
+{
+ struct ovpn_socket *ovpn_sock;
+ struct ovpn_peer *peer;
+
+ if (!sock)
+ return;
+
+ rcu_read_lock();
+ ovpn_sock = rcu_dereference_sk_user_data(sock->sk);
+
+ if (!ovpn_sock->peer) {
+ rcu_read_unlock();
+ return;
+ }
+
+ peer = ovpn_sock->peer;
+ strp_stop(&peer->tcp.strp);
+
+ skb_queue_purge(&peer->tcp.user_queue);
+
+ /* restore CBs that were saved in ovpn_sock_set_tcp_cb() */
+ sock->sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready;
+ sock->sk->sk_write_space = peer->tcp.sk_cb.sk_write_space;
+ sock->sk->sk_prot = peer->tcp.sk_cb.prot;
+ sock->sk->sk_socket->ops = peer->tcp.sk_cb.ops;
+ rcu_assign_sk_user_data(sock->sk, NULL);
+
+ /* cancel any ongoing work. Done after removing the CBs so that these
+ * workers cannot be re-armed
+ */
+ cancel_work_sync(&peer->tcp.tx_work);
+ strp_done(&peer->tcp.strp);
+ rcu_read_unlock();
+}
+
+static void ovpn_tcp_send_sock(struct ovpn_peer *peer)
+{
+ struct sk_buff *skb = peer->tcp.out_msg.skb;
+
+ if (!skb)
+ return;
+
+ if (peer->tcp.tx_in_progress)
+ return;
+
+ peer->tcp.tx_in_progress = true;
+
+ do {
+ int ret = skb_send_sock_locked(peer->sock->sock->sk, skb,
+ peer->tcp.out_msg.offset,
+ peer->tcp.out_msg.len);
+ if (unlikely(ret < 0)) {
+ if (ret == -EAGAIN)
+ goto out;
+
+ net_warn_ratelimited("%s: TCP error to peer %u: %d\n",
+ peer->ovpn->dev->name, peer->id,
+ ret);
+
+ /* in case of TCP error we can't recover the VPN
+ * stream therefore we abort the connection
+ */
+ ovpn_peer_del(peer,
+ OVPN_DEL_PEER_REASON_TRANSPORT_ERROR);
+ break;
+ }
+
+ peer->tcp.out_msg.len -= ret;
+ peer->tcp.out_msg.offset += ret;
+ } while (peer->tcp.out_msg.len > 0);
+
+ if (!peer->tcp.out_msg.len)
+ dev_sw_netstats_tx_add(peer->ovpn->dev, 1, skb->len);
+
+ kfree_skb(peer->tcp.out_msg.skb);
+ peer->tcp.out_msg.skb = NULL;
+ peer->tcp.out_msg.len = 0;
+ peer->tcp.out_msg.offset = 0;
+
+out:
+ peer->tcp.tx_in_progress = false;
+}
+
+static void ovpn_tcp_tx_work(struct work_struct *work)
+{
+ struct ovpn_peer *peer;
+
+ peer = container_of(work, struct ovpn_peer, tcp.tx_work);
+
+ lock_sock(peer->sock->sock->sk);
+ ovpn_tcp_send_sock(peer);
+ release_sock(peer->sock->sock->sk);
+}
+
+void ovpn_tcp_send_sock_skb(struct ovpn_peer *peer, struct sk_buff *skb)
+{
+ if (peer->tcp.out_msg.skb)
+ return;
+
+ peer->tcp.out_msg.skb = skb;
+ peer->tcp.out_msg.len = skb->len;
+ peer->tcp.out_msg.offset = 0;
+
+ ovpn_tcp_send_sock(peer);
+}
+
+static int ovpn_tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
+{
+ struct ovpn_socket *sock;
+ int ret, linear = PAGE_SIZE;
+ struct ovpn_peer *peer;
+ struct sk_buff *skb;
+
+ rcu_read_lock();
+ sock = rcu_dereference_sk_user_data(sk);
+ peer = sock->peer;
+ rcu_read_unlock();
+
+ if (msg->msg_flags & ~MSG_DONTWAIT)
+ return -EOPNOTSUPP;
+
+ lock_sock(sk);
+
+ if (peer->tcp.out_msg.skb) {
+ ret = -EAGAIN;
+ goto unlock;
+ }
+
+ if (size < linear)
+ linear = size;
+
+ skb = sock_alloc_send_pskb(sk, linear, size - linear,
+ msg->msg_flags & MSG_DONTWAIT, &ret, 0);
+ if (!skb) {
+ net_err_ratelimited("%s: skb alloc failed: %d\n",
+ sock->peer->ovpn->dev->name, ret);
+ goto unlock;
+ }
+
+ skb_put(skb, linear);
+ skb->len = size;
+ skb->data_len = size - linear;
+
+ ret = skb_copy_datagram_from_iter(skb, 0, &msg->msg_iter, size);
+ if (ret) {
+ kfree_skb(skb);
+ net_err_ratelimited("%s: skb copy from iter failed: %d\n",
+ sock->peer->ovpn->dev->name, ret);
+ goto unlock;
+ }
+
+ ovpn_tcp_send_sock_skb(sock->peer, skb);
+ ret = size;
+unlock:
+ release_sock(sk);
+ return ret;
+}
+
+static void ovpn_tcp_data_ready(struct sock *sk)
+{
+ struct ovpn_socket *sock;
+
+ trace_sk_data_ready(sk);
+
+ rcu_read_lock();
+ sock = rcu_dereference_sk_user_data(sk);
+ strp_data_ready(&sock->peer->tcp.strp);
+ rcu_read_unlock();
+}
+
+static void ovpn_tcp_write_space(struct sock *sk)
+{
+ struct ovpn_socket *sock;
+
+ rcu_read_lock();
+ sock = rcu_dereference_sk_user_data(sk);
+ schedule_work(&sock->peer->tcp.tx_work);
+ sock->peer->tcp.sk_cb.sk_write_space(sk);
+ rcu_read_unlock();
+}
+
+static void ovpn_tcp_build_protos(struct proto *ovpn_tcp_prot,
+ struct proto_ops *ovpn_tcp_ops,
+ const struct proto *orig_prot,
+ const struct proto_ops *orig_ops);
+
+/* Set TCP encapsulation callbacks */
+int ovpn_tcp_socket_attach(struct socket *sock, struct ovpn_peer *peer)
+{
+ struct strp_callbacks cb = {
+ .rcv_msg = ovpn_tcp_rcv,
+ .parse_msg = ovpn_tcp_parse,
+ };
+ int ret;
+
+ /* make sure no pre-existing encapsulation handler exists */
+ if (sock->sk->sk_user_data)
+ return -EBUSY;
+
+ /* sanity check */
+ if (sock->sk->sk_protocol != IPPROTO_TCP) {
+ netdev_err(peer->ovpn->dev,
+ "provided socket is not TCP as expected\n");
+ return -EINVAL;
+ }
+
+ /* only a fully connected socket are expected. Connection should be
+ * handled in userspace
+ */
+ if (sock->sk->sk_state != TCP_ESTABLISHED) {
+ netdev_err(peer->ovpn->dev,
+ "provided TCP socket is not in ESTABLISHED state: %d\n",
+ sock->sk->sk_state);
+ return -EINVAL;
+ }
+
+ lock_sock(sock->sk);
+
+ ret = strp_init(&peer->tcp.strp, sock->sk, &cb);
+ if (ret < 0) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ release_sock(sock->sk);
+ return ret;
+ }
+
+ INIT_WORK(&peer->tcp.tx_work, ovpn_tcp_tx_work);
+ __sk_dst_reset(sock->sk);
+ strp_check_rcv(&peer->tcp.strp);
+ skb_queue_head_init(&peer->tcp.user_queue);
+
+ /* save current CBs so that they can be restored upon socket release */
+ peer->tcp.sk_cb.sk_data_ready = sock->sk->sk_data_ready;
+ peer->tcp.sk_cb.sk_write_space = sock->sk->sk_write_space;
+ peer->tcp.sk_cb.prot = sock->sk->sk_prot;
+ peer->tcp.sk_cb.ops = sock->sk->sk_socket->ops;
+
+ /* assign our static CBs and prot/ops */
+ sock->sk->sk_data_ready = ovpn_tcp_data_ready;
+ sock->sk->sk_write_space = ovpn_tcp_write_space;
+
+ if (sock->sk->sk_family == AF_INET) {
+ sock->sk->sk_prot = &ovpn_tcp_prot;
+ sock->sk->sk_socket->ops = &ovpn_tcp_ops;
+ } else {
+ mutex_lock(&tcp6_prot_mutex);
+ if (!ovpn_tcp6_prot.recvmsg)
+ ovpn_tcp_build_protos(&ovpn_tcp6_prot, &ovpn_tcp6_ops,
+ sock->sk->sk_prot,
+ sock->sk->sk_socket->ops);
+ mutex_unlock(&tcp6_prot_mutex);
+
+ sock->sk->sk_prot = &ovpn_tcp6_prot;
+ sock->sk->sk_socket->ops = &ovpn_tcp6_ops;
+ }
+
+ /* avoid using task_frag */
+ sock->sk->sk_allocation = GFP_ATOMIC;
+ sock->sk->sk_use_task_frag = false;
+
+ release_sock(sock->sk);
+ return 0;
+}
+
+static void ovpn_tcp_close(struct sock *sk, long timeout)
+{
+ struct ovpn_socket *sock;
+
+ rcu_read_lock();
+ sock = rcu_dereference_sk_user_data(sk);
+
+ strp_stop(&sock->peer->tcp.strp);
+ barrier();
+
+ tcp_close(sk, timeout);
+
+ ovpn_peer_del(sock->peer, OVPN_DEL_PEER_REASON_TRANSPORT_ERROR);
+ rcu_read_unlock();
+}
+
+static __poll_t ovpn_tcp_poll(struct file *file, struct socket *sock,
+ poll_table *wait)
+{
+ __poll_t mask = datagram_poll(file, sock, wait);
+ struct ovpn_socket *ovpn_sock;
+
+ rcu_read_lock();
+ ovpn_sock = rcu_dereference_sk_user_data(sock->sk);
+ if (!skb_queue_empty(&ovpn_sock->peer->tcp.user_queue))
+ mask |= EPOLLIN | EPOLLRDNORM;
+ rcu_read_unlock();
+
+ return mask;
+}
+
+static void ovpn_tcp_build_protos(struct proto *tcp_prot,
+ struct proto_ops *tcp_ops,
+ const struct proto *orig_prot,
+ const struct proto_ops *orig_ops)
+{
+ memcpy(tcp_prot, orig_prot, sizeof(struct proto));
+ memcpy(tcp_ops, orig_ops, sizeof(struct proto_ops));
+ tcp_prot->recvmsg = ovpn_tcp_recvmsg;
+ tcp_prot->sendmsg = ovpn_tcp_sendmsg;
+ tcp_prot->close = ovpn_tcp_close;
+ tcp_ops->poll = ovpn_tcp_poll;
+}
+
+/* Initialize TCP static objects */
+void __init ovpn_tcp_init(void)
+{
+ ovpn_tcp_build_protos(&ovpn_tcp_prot, &ovpn_tcp_ops, &tcp_prot,
+ &inet_stream_ops);
+}
diff --git a/drivers/net/ovpn/tcp.h b/drivers/net/ovpn/tcp.h
new file mode 100644
index 000000000000..11c12a9cbe1b
--- /dev/null
+++ b/drivers/net/ovpn/tcp.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* OpenVPN data channel offload
+ *
+ * Copyright (C) 2019-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#ifndef _NET_OVPN_TCP_H_
+#define _NET_OVPN_TCP_H_
+
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+
+#include "peer.h"
+
+void __init ovpn_tcp_init(void);
+
+int ovpn_tcp_socket_attach(struct socket *sock, struct ovpn_peer *peer);
+void ovpn_tcp_socket_detach(struct socket *sock);
+void ovpn_tcp_send_sock_skb(struct ovpn_peer *peer, struct sk_buff *skb);
+
+/* Prepare skb and enqueue it for sending to peer.
+ *
+ * Preparation consist in prepending the skb payload with its size.
+ * Required by the OpenVPN protocol in order to extract packets from
+ * the TCP stream on the receiver side.
+ */
+static inline void ovpn_tcp_send_skb(struct ovpn_peer *peer,
+ struct sk_buff *skb)
+{
+ u16 len = skb->len;
+
+ *(__be16 *)__skb_push(skb, sizeof(u16)) = htons(len);
+
+ bh_lock_sock(peer->sock->sock->sk);
+ ovpn_tcp_send_sock_skb(peer, skb);
+ bh_unlock_sock(peer->sock->sock->sk);
+}
+
+#endif /* _NET_OVPN_TCP_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 15/25] ovpn: implement multi-peer support
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (13 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 14/25] ovpn: implement TCP transport Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-15 10:40 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 16/25] ovpn: implement peer lookup logic Antonio Quartulli
` (9 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
With this change an ovpn instance will be able to stay connected to
multiple remote endpoints.
This functionality is strictly required when running ovpn on an
OpenVPN server.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/main.c | 41 ++++++++-
drivers/net/ovpn/ovpnstruct.h | 16 ++++
drivers/net/ovpn/peer.c | 152 ++++++++++++++++++++++++++++++++++
drivers/net/ovpn/peer.h | 9 ++
4 files changed, 216 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
index 19f7c0ff679b..cefd7010ab37 100644
--- a/drivers/net/ovpn/main.c
+++ b/drivers/net/ovpn/main.c
@@ -46,6 +46,17 @@ static int ovpn_struct_init(struct net_device *dev, enum ovpn_mode mode)
ovpn->mode = mode;
spin_lock_init(&ovpn->lock);
+ if (mode == OVPN_MODE_MP) {
+ /* the peer container is fairly large, therefore we dynamically
+ * allocate it only when needed
+ */
+ ovpn->peers = kzalloc(sizeof(*ovpn->peers), GFP_KERNEL);
+ if (!ovpn->peers)
+ return -ENOMEM;
+
+ spin_lock_init(&ovpn->peers->lock);
+ }
+
return 0;
}
@@ -54,14 +65,34 @@ static void ovpn_struct_free(struct net_device *net)
struct ovpn_struct *ovpn = netdev_priv(net);
gro_cells_destroy(&ovpn->gro_cells);
+ kfree(ovpn->peers);
rcu_barrier();
}
static int ovpn_net_init(struct net_device *dev)
{
struct ovpn_struct *ovpn = netdev_priv(dev);
+ int err = gro_cells_init(&ovpn->gro_cells, dev);
+ struct in_device *dev_v4;
+
+ if (err)
+ return err;
- return gro_cells_init(&ovpn->gro_cells, dev);
+ if (ovpn->mode == OVPN_MODE_MP) {
+ dev_v4 = __in_dev_get_rtnl(dev);
+ if (dev_v4) {
+ /* disable redirects as Linux gets confused by ovpn
+ * handling same-LAN routing.
+ * This happens because a multipeer interface is used as
+ * relay point between hosts in the same subnet, while
+ * in a classic LAN this would not be needed because the
+ * two hosts would be able to talk directly.
+ */
+ IN_DEV_CONF_SET(dev_v4, SEND_REDIRECTS, false);
+ IPV4_DEVCONF_ALL(dev_net(dev), SEND_REDIRECTS) = false;
+ }
+ }
+ return 0;
}
static int ovpn_net_open(struct net_device *dev)
@@ -210,8 +241,14 @@ void ovpn_iface_destruct(struct ovpn_struct *ovpn)
ovpn->registered = false;
- if (ovpn->mode == OVPN_MODE_P2P)
+ switch (ovpn->mode) {
+ case OVPN_MODE_P2P:
ovpn_peer_release_p2p(ovpn);
+ break;
+ default:
+ ovpn_peers_free(ovpn);
+ break;
+ }
}
static int ovpn_netdev_notifier_call(struct notifier_block *nb,
diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h
index af39ee86f168..482e02b918e4 100644
--- a/drivers/net/ovpn/ovpnstruct.h
+++ b/drivers/net/ovpn/ovpnstruct.h
@@ -13,12 +13,27 @@
#include <net/gro_cells.h>
#include <uapi/linux/ovpn.h>
+/**
+ * struct ovpn_peer_collection - container of peers for MultiPeer mode
+ * @by_id: table of peers index by ID
+ * @by_transp_addr: table of peers indexed by transport address
+ * @by_vpn_addr: table of peers indexed by VPN IP address
+ * @lock: protects writes to peers tables
+ */
+struct ovpn_peer_collection {
+ DECLARE_HASHTABLE(by_id, 12);
+ DECLARE_HASHTABLE(by_transp_addr, 12);
+ DECLARE_HASHTABLE(by_vpn_addr, 12);
+ spinlock_t lock; /* protects writes to peers tables */
+};
+
/**
* struct ovpn_struct - per ovpn interface state
* @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
+ * @peers: data structures holding multi-peer references
* @peer: in P2P mode, this is the only remote peer
* @dev_list: entry for the module wide device list
* @gro_cells: pointer to the Generic Receive Offload cell
@@ -28,6 +43,7 @@ struct ovpn_struct {
bool registered;
enum ovpn_mode mode;
spinlock_t lock; /* protect writing to the ovpn_struct object */
+ struct ovpn_peer_collection *peers;
struct ovpn_peer __rcu *peer;
struct list_head dev_list;
struct gro_cells gro_cells;
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index f633b70bb140..2b5e2bbb2578 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -9,6 +9,7 @@
#include <linux/skbuff.h>
#include <linux/list.h>
+#include <linux/hashtable.h>
#include "ovpnstruct.h"
#include "bind.h"
@@ -311,6 +312,90 @@ bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb,
return match;
}
+/**
+ * ovpn_peer_add_mp - add peer to related tables in a MP 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_mp(struct ovpn_struct *ovpn, struct ovpn_peer *peer)
+{
+ struct sockaddr_storage sa = { 0 };
+ struct sockaddr_in6 *sa6;
+ struct sockaddr_in *sa4;
+ struct ovpn_bind *bind;
+ struct ovpn_peer *tmp;
+ size_t salen;
+ int ret = 0;
+ u32 index;
+
+ spin_lock_bh(&ovpn->peers->lock);
+ /* do not add duplicates */
+ tmp = ovpn_peer_get_by_id(ovpn, peer->id);
+ if (tmp) {
+ ovpn_peer_put(tmp);
+ ret = -EEXIST;
+ goto unlock;
+ }
+
+ bind = rcu_dereference_protected(peer->bind, true);
+ /* peers connected via TCP have bind == NULL */
+ if (bind) {
+ switch (bind->sa.in4.sin_family) {
+ case AF_INET:
+ sa4 = (struct sockaddr_in *)&sa;
+
+ sa4->sin_family = AF_INET;
+ sa4->sin_addr.s_addr = bind->sa.in4.sin_addr.s_addr;
+ sa4->sin_port = bind->sa.in4.sin_port;
+ salen = sizeof(*sa4);
+ break;
+ case AF_INET6:
+ sa6 = (struct sockaddr_in6 *)&sa;
+
+ sa6->sin6_family = AF_INET6;
+ sa6->sin6_addr = bind->sa.in6.sin6_addr;
+ sa6->sin6_port = bind->sa.in6.sin6_port;
+ salen = sizeof(*sa6);
+ break;
+ default:
+ ret = -EPROTONOSUPPORT;
+ goto unlock;
+ }
+
+ index = ovpn_peer_index(ovpn->peers->by_transp_addr, &sa,
+ salen);
+ hlist_add_head_rcu(&peer->hash_entry_transp_addr,
+ &ovpn->peers->by_transp_addr[index]);
+ }
+
+ index = ovpn_peer_index(ovpn->peers->by_id, &peer->id,
+ sizeof(peer->id));
+ hlist_add_head_rcu(&peer->hash_entry_id, &ovpn->peers->by_id[index]);
+
+ if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) {
+ index = ovpn_peer_index(ovpn->peers->by_vpn_addr,
+ &peer->vpn_addrs.ipv4,
+ sizeof(peer->vpn_addrs.ipv4));
+ hlist_add_head_rcu(&peer->hash_entry_addr4,
+ &ovpn->peers->by_vpn_addr[index]);
+ }
+
+ if (!ipv6_addr_any(&peer->vpn_addrs.ipv6)) {
+ index = ovpn_peer_index(ovpn->peers->by_vpn_addr,
+ &peer->vpn_addrs.ipv6,
+ sizeof(peer->vpn_addrs.ipv6));
+ hlist_add_head_rcu(&peer->hash_entry_addr6,
+ &ovpn->peers->by_vpn_addr[index]);
+ }
+
+unlock:
+ spin_unlock_bh(&ovpn->peers->lock);
+
+ return ret;
+}
+
/**
* ovpn_peer_add_p2p - add peer to related tables in a P2P instance
* @ovpn: the instance to add the peer to
@@ -351,6 +436,8 @@ static int ovpn_peer_add_p2p(struct ovpn_struct *ovpn, struct ovpn_peer *peer)
int ovpn_peer_add(struct ovpn_struct *ovpn, struct ovpn_peer *peer)
{
switch (ovpn->mode) {
+ case OVPN_MODE_MP:
+ return ovpn_peer_add_mp(ovpn, peer);
case OVPN_MODE_P2P:
return ovpn_peer_add_p2p(ovpn, peer);
default:
@@ -358,6 +445,53 @@ int ovpn_peer_add(struct ovpn_struct *ovpn, struct ovpn_peer *peer)
}
}
+/**
+ * ovpn_peer_unhash - remove peer reference from all hashtables
+ * @peer: the peer to remove
+ * @reason: the delete reason to attach to the peer
+ */
+static void ovpn_peer_unhash(struct ovpn_peer *peer,
+ enum ovpn_del_peer_reason reason)
+{
+ hlist_del_init_rcu(&peer->hash_entry_id);
+ hlist_del_init_rcu(&peer->hash_entry_addr4);
+ hlist_del_init_rcu(&peer->hash_entry_addr6);
+ hlist_del_init_rcu(&peer->hash_entry_transp_addr);
+
+ ovpn_peer_put(peer);
+ peer->delete_reason = reason;
+}
+
+/**
+ * ovpn_peer_del_mp - delete peer from related tables in a MP 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_mp(struct ovpn_peer *peer,
+ enum ovpn_del_peer_reason reason)
+{
+ struct ovpn_peer *tmp;
+ int ret = 0;
+
+ spin_lock_bh(&peer->ovpn->peers->lock);
+ tmp = ovpn_peer_get_by_id(peer->ovpn, peer->id);
+ if (tmp != peer) {
+ ret = -ENOENT;
+ goto unlock;
+ }
+ ovpn_peer_unhash(peer, reason);
+
+unlock:
+ spin_unlock_bh(&peer->ovpn->peers->lock);
+
+ if (tmp)
+ ovpn_peer_put(tmp);
+
+ return ret;
+}
+
/**
* ovpn_peer_del_p2p - delete peer from related tables in a P2P instance
* @peer: the peer to delete
@@ -415,9 +549,27 @@ void ovpn_peer_release_p2p(struct ovpn_struct *ovpn)
int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason)
{
switch (peer->ovpn->mode) {
+ case OVPN_MODE_MP:
+ return ovpn_peer_del_mp(peer, reason);
case OVPN_MODE_P2P:
return ovpn_peer_del_p2p(peer, reason);
default:
return -EOPNOTSUPP;
}
}
+
+/**
+ * ovpn_peers_free - free all peers in the instance
+ * @ovpn: the instance whose peers should be released
+ */
+void ovpn_peers_free(struct ovpn_struct *ovpn)
+{
+ struct hlist_node *tmp;
+ struct ovpn_peer *peer;
+ int bkt;
+
+ spin_lock_bh(&ovpn->peers->lock);
+ hash_for_each_safe(ovpn->peers->by_id, bkt, tmp, peer, hash_entry_id)
+ ovpn_peer_unhash(peer, OVPN_DEL_PEER_REASON_TEARDOWN);
+ spin_unlock_bh(&ovpn->peers->lock);
+}
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 86d4696b1529..6e92e09a3504 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -30,6 +30,10 @@
* @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
+ * @hash_entry_id: entry in the peer ID hashtable
+ * @hash_entry_addr4: entry in the peer IPv4 hashtable
+ * @hash_entry_addr6: entry in the peer IPv6 hashtable
+ * @hash_entry_transp_addr: entry in the peer transport address hashtable
* @sock: the socket being used to talk to this peer
* @tcp: keeps track of TCP specific state
* @tcp.strp: stream parser context (TCP only)
@@ -62,6 +66,10 @@ struct ovpn_peer {
struct in_addr ipv4;
struct in6_addr ipv6;
} vpn_addrs;
+ struct hlist_node hash_entry_id;
+ struct hlist_node hash_entry_addr4;
+ struct hlist_node hash_entry_addr6;
+ struct hlist_node hash_entry_transp_addr;
struct ovpn_socket *sock;
/* state of the TCP reading. Needed to keep track of how much of a
@@ -126,6 +134,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id);
int ovpn_peer_add(struct ovpn_struct *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_struct *ovpn);
+void ovpn_peers_free(struct ovpn_struct *ovpn);
struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn,
struct sk_buff *skb);
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 16/25] ovpn: implement peer lookup logic
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (14 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 15/25] ovpn: implement multi-peer support Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-15 13:11 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism Antonio Quartulli
` (8 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
In a multi-peer scenario there are a number of situations when a
specific peer needs to be looked up.
We may want to lookup a peer by:
1. its ID
2. its VPN destination IP
3. its transport IP/port couple
For each of the above, there is a specific routing table referencing all
peers for fast look up.
Case 2. is a bit special in the sense that an outgoing packet may not be
sent to the peer VPN IP directly, but rather to a network behind it. For
this reason we first perform a nexthop lookup in the system routing
table and then we use the retrieved nexthop as peer search key.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/peer.c | 299 ++++++++++++++++++++++++++++++++++++----
1 file changed, 274 insertions(+), 25 deletions(-)
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 2b5e2bbb2578..8c23268db916 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -10,6 +10,7 @@
#include <linux/skbuff.h>
#include <linux/list.h>
#include <linux/hashtable.h>
+#include <net/ip6_route.h>
#include "ovpnstruct.h"
#include "bind.h"
@@ -65,9 +66,6 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
return peer;
}
-#define ovpn_peer_index(_tbl, _key, _key_len) \
- (jhash(_key, _key_len, 0) % HASH_SIZE(_tbl)) \
-
/**
* ovpn_peer_release - release peer private members
* @peer: the peer to release
@@ -129,6 +127,91 @@ static bool ovpn_peer_skb_to_sockaddr(struct sk_buff *skb,
return true;
}
+/**
+ * ovpn_nexthop_from_skb4 - retrieve IPv4 nexthop for outgoing skb
+ * @skb: the outgoing packet
+ *
+ * Return: the IPv4 of the nexthop
+ */
+static __be32 ovpn_nexthop_from_skb4(struct sk_buff *skb)
+{
+ const struct rtable *rt = skb_rtable(skb);
+
+ if (rt && rt->rt_uses_gateway)
+ return rt->rt_gw4;
+
+ return ip_hdr(skb)->daddr;
+}
+
+/**
+ * ovpn_nexthop_from_skb6 - retrieve IPv6 nexthop for outgoing skb
+ * @skb: the outgoing packet
+ *
+ * Return: the IPv6 of the nexthop
+ */
+static struct in6_addr ovpn_nexthop_from_skb6(struct sk_buff *skb)
+{
+ const struct rt6_info *rt = skb_rt6_info(skb);
+
+ if (!rt || !(rt->rt6i_flags & RTF_GATEWAY))
+ return ipv6_hdr(skb)->daddr;
+
+ return rt->rt6i_gateway;
+}
+
+#define ovpn_get_hash_head(_tbl, _key, _key_len) \
+ (&(_tbl)[jhash(_key, _key_len, 0) % HASH_SIZE(_tbl)]) \
+
+/**
+ * ovpn_peer_get_by_vpn_addr4 - retrieve peer by its VPN IPv4 address
+ * @ovpn: the openvpn instance to search
+ * @addr: VPN IPv4 to use as search key
+ *
+ * Refcounter is not increased for the returned peer.
+ *
+ * Return: the peer if found or NULL otherwise
+ */
+static struct ovpn_peer *ovpn_peer_get_by_vpn_addr4(struct ovpn_struct *ovpn,
+ __be32 addr)
+{
+ struct hlist_head *head;
+ struct ovpn_peer *tmp;
+
+ head = ovpn_get_hash_head(ovpn->peers->by_vpn_addr, &addr,
+ sizeof(addr));
+
+ hlist_for_each_entry_rcu(tmp, head, hash_entry_addr4)
+ if (addr == tmp->vpn_addrs.ipv4.s_addr)
+ return tmp;
+
+ return NULL;
+}
+
+/**
+ * ovpn_peer_get_by_vpn_addr6 - retrieve peer by its VPN IPv6 address
+ * @ovpn: the openvpn instance to search
+ * @addr: VPN IPv6 to use as search key
+ *
+ * Refcounter is not increased for the returned peer.
+ *
+ * Return: the peer if found or NULL otherwise
+ */
+static struct ovpn_peer *ovpn_peer_get_by_vpn_addr6(struct ovpn_struct *ovpn,
+ struct in6_addr *addr)
+{
+ struct hlist_head *head;
+ struct ovpn_peer *tmp;
+
+ head = ovpn_get_hash_head(ovpn->peers->by_vpn_addr, addr,
+ sizeof(*addr));
+
+ hlist_for_each_entry_rcu(tmp, head, hash_entry_addr6)
+ if (ipv6_addr_equal(addr, &tmp->vpn_addrs.ipv6))
+ return tmp;
+
+ return NULL;
+}
+
/**
* ovpn_peer_transp_match - check if sockaddr and peer binding match
* @peer: the peer to get the binding from
@@ -205,14 +288,42 @@ ovpn_peer_get_by_transp_addr_p2p(struct ovpn_struct *ovpn,
struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn,
struct sk_buff *skb)
{
- struct ovpn_peer *peer = NULL;
+ struct ovpn_peer *tmp, *peer = NULL;
struct sockaddr_storage ss = { 0 };
+ struct hlist_head *head;
+ size_t sa_len;
if (unlikely(!ovpn_peer_skb_to_sockaddr(skb, &ss)))
return NULL;
if (ovpn->mode == OVPN_MODE_P2P)
- peer = ovpn_peer_get_by_transp_addr_p2p(ovpn, &ss);
+ return ovpn_peer_get_by_transp_addr_p2p(ovpn, &ss);
+
+ switch (ss.ss_family) {
+ case AF_INET:
+ sa_len = sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ sa_len = sizeof(struct sockaddr_in6);
+ break;
+ default:
+ return NULL;
+ }
+
+ head = ovpn_get_hash_head(ovpn->peers->by_transp_addr, &ss, sa_len);
+
+ rcu_read_lock();
+ hlist_for_each_entry_rcu(tmp, head, hash_entry_transp_addr) {
+ if (!ovpn_peer_transp_match(tmp, &ss))
+ continue;
+
+ if (!ovpn_peer_hold(tmp))
+ continue;
+
+ peer = tmp;
+ break;
+ }
+ rcu_read_unlock();
return peer;
}
@@ -247,10 +358,27 @@ static struct ovpn_peer *ovpn_peer_get_by_id_p2p(struct ovpn_struct *ovpn,
*/
struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id)
{
- struct ovpn_peer *peer = NULL;
+ struct ovpn_peer *tmp, *peer = NULL;
+ struct hlist_head *head;
if (ovpn->mode == OVPN_MODE_P2P)
- peer = ovpn_peer_get_by_id_p2p(ovpn, peer_id);
+ return ovpn_peer_get_by_id_p2p(ovpn, peer_id);
+
+ head = ovpn_get_hash_head(ovpn->peers->by_id, &peer_id,
+ sizeof(peer_id));
+
+ rcu_read_lock();
+ hlist_for_each_entry_rcu(tmp, head, hash_entry_id) {
+ if (tmp->id != peer_id)
+ continue;
+
+ if (!ovpn_peer_hold(tmp))
+ continue;
+
+ peer = tmp;
+ break;
+ }
+ rcu_read_unlock();
return peer;
}
@@ -272,6 +400,8 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn,
struct sk_buff *skb)
{
struct ovpn_peer *peer = NULL;
+ struct in6_addr addr6;
+ __be32 addr4;
/* in P2P mode, no matter the destination, packets are always sent to
* the single peer listening on the other side
@@ -282,11 +412,109 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn,
if (unlikely(peer && !ovpn_peer_hold(peer)))
peer = NULL;
rcu_read_unlock();
+ return peer;
+ }
+
+ rcu_read_lock();
+ switch (skb_protocol_to_family(skb)) {
+ case AF_INET:
+ addr4 = ovpn_nexthop_from_skb4(skb);
+ peer = ovpn_peer_get_by_vpn_addr4(ovpn, addr4);
+ break;
+ case AF_INET6:
+ addr6 = ovpn_nexthop_from_skb6(skb);
+ peer = ovpn_peer_get_by_vpn_addr6(ovpn, &addr6);
+ break;
}
+ if (unlikely(peer && !ovpn_peer_hold(peer)))
+ peer = NULL;
+ rcu_read_unlock();
+
return peer;
}
+/**
+ * ovpn_nexthop_from_rt4 - look up the IPv4 nexthop for the given destination
+ * @ovpn: the private data representing the current VPN session
+ * @dest: the destination to be looked up
+ *
+ * Looks up in the IPv4 system routing table the IP of the nexthop to be used
+ * to reach the destination passed as argument. If no nexthop can be found, the
+ * destination itself is returned as it probably has to be used as nexthop.
+ *
+ * Return: the IP of the next hop if found or dest itself otherwise
+ */
+static __be32 ovpn_nexthop_from_rt4(struct ovpn_struct *ovpn, __be32 dest)
+{
+ struct rtable *rt;
+ struct flowi4 fl = {
+ .daddr = dest
+ };
+
+ rt = ip_route_output_flow(dev_net(ovpn->dev), &fl, NULL);
+ if (IS_ERR(rt)) {
+ net_dbg_ratelimited("%s: no route to host %pI4\n", __func__,
+ &dest);
+ /* if we end up here this packet is probably going to be
+ * thrown away later
+ */
+ return dest;
+ }
+
+ if (!rt->rt_uses_gateway)
+ goto out;
+
+ dest = rt->rt_gw4;
+out:
+ ip_rt_put(rt);
+ return dest;
+}
+
+/**
+ * ovpn_nexthop_from_rt6 - look up the IPv6 nexthop for the given destination
+ * @ovpn: the private data representing the current VPN session
+ * @dest: the destination to be looked up
+ *
+ * Looks up in the IPv6 system routing table the IP of the nexthop to be used
+ * to reach the destination passed as argument. If no nexthop can be found, the
+ * destination itself is returned as it probably has to be used as nexthop.
+ *
+ * Return: the IP of the next hop if found or dest itself otherwise
+ */
+static struct in6_addr ovpn_nexthop_from_rt6(struct ovpn_struct *ovpn,
+ struct in6_addr dest)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+ struct dst_entry *entry;
+ struct rt6_info *rt;
+ struct flowi6 fl = {
+ .daddr = dest,
+ };
+
+ entry = ipv6_stub->ipv6_dst_lookup_flow(dev_net(ovpn->dev), NULL, &fl,
+ NULL);
+ if (IS_ERR(entry)) {
+ net_dbg_ratelimited("%s: no route to host %pI6c\n", __func__,
+ &dest);
+ /* if we end up here this packet is probably going to be
+ * thrown away later
+ */
+ return dest;
+ }
+
+ rt = dst_rt6_info(entry);
+
+ if (!(rt->rt6i_flags & RTF_GATEWAY))
+ goto out;
+
+ dest = rt->rt6i_gateway;
+out:
+ dst_release((struct dst_entry *)rt);
+#endif
+ return dest;
+}
+
/**
* ovpn_peer_check_by_src - check that skb source is routed via peer
* @ovpn: the openvpn instance to search
@@ -299,6 +527,8 @@ bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb,
struct ovpn_peer *peer)
{
bool match = false;
+ struct in6_addr addr6;
+ __be32 addr4;
if (ovpn->mode == OVPN_MODE_P2P) {
/* in P2P mode, no matter the destination, packets are always
@@ -307,6 +537,28 @@ bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb,
rcu_read_lock();
match = (peer == rcu_dereference(ovpn->peer));
rcu_read_unlock();
+ return match;
+ }
+
+ /* This function performs a reverse path check, therefore we now
+ * lookup the nexthop we would use if we wanted to route a packet
+ * to the source IP. If the nexthop matches the sender we know the
+ * latter is valid and we allow the packet to come in
+ */
+
+ switch (skb_protocol_to_family(skb)) {
+ case AF_INET:
+ addr4 = ovpn_nexthop_from_rt4(ovpn, ip_hdr(skb)->saddr);
+ rcu_read_lock();
+ match = (peer == ovpn_peer_get_by_vpn_addr4(ovpn, addr4));
+ rcu_read_unlock();
+ break;
+ case AF_INET6:
+ addr6 = ovpn_nexthop_from_rt6(ovpn, ipv6_hdr(skb)->saddr);
+ rcu_read_lock();
+ match = (peer == ovpn_peer_get_by_vpn_addr6(ovpn, &addr6));
+ rcu_read_unlock();
+ break;
}
return match;
@@ -324,11 +576,11 @@ static int ovpn_peer_add_mp(struct ovpn_struct *ovpn, struct ovpn_peer *peer)
struct sockaddr_storage sa = { 0 };
struct sockaddr_in6 *sa6;
struct sockaddr_in *sa4;
+ struct hlist_head *head;
struct ovpn_bind *bind;
struct ovpn_peer *tmp;
size_t salen;
int ret = 0;
- u32 index;
spin_lock_bh(&ovpn->peers->lock);
/* do not add duplicates */
@@ -364,30 +616,27 @@ static int ovpn_peer_add_mp(struct ovpn_struct *ovpn, struct ovpn_peer *peer)
goto unlock;
}
- index = ovpn_peer_index(ovpn->peers->by_transp_addr, &sa,
- salen);
- hlist_add_head_rcu(&peer->hash_entry_transp_addr,
- &ovpn->peers->by_transp_addr[index]);
+ head = ovpn_get_hash_head(ovpn->peers->by_transp_addr, &sa,
+ salen);
+ hlist_add_head_rcu(&peer->hash_entry_transp_addr, head);
}
- index = ovpn_peer_index(ovpn->peers->by_id, &peer->id,
- sizeof(peer->id));
- hlist_add_head_rcu(&peer->hash_entry_id, &ovpn->peers->by_id[index]);
+ hlist_add_head_rcu(&peer->hash_entry_id,
+ ovpn_get_hash_head(ovpn->peers->by_id, &peer->id,
+ sizeof(peer->id)));
if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) {
- index = ovpn_peer_index(ovpn->peers->by_vpn_addr,
- &peer->vpn_addrs.ipv4,
- sizeof(peer->vpn_addrs.ipv4));
- hlist_add_head_rcu(&peer->hash_entry_addr4,
- &ovpn->peers->by_vpn_addr[index]);
+ head = ovpn_get_hash_head(ovpn->peers->by_vpn_addr,
+ &peer->vpn_addrs.ipv4,
+ sizeof(peer->vpn_addrs.ipv4));
+ hlist_add_head_rcu(&peer->hash_entry_addr4, head);
}
if (!ipv6_addr_any(&peer->vpn_addrs.ipv6)) {
- index = ovpn_peer_index(ovpn->peers->by_vpn_addr,
- &peer->vpn_addrs.ipv6,
- sizeof(peer->vpn_addrs.ipv6));
- hlist_add_head_rcu(&peer->hash_entry_addr6,
- &ovpn->peers->by_vpn_addr[index]);
+ head = ovpn_get_hash_head(ovpn->peers->by_vpn_addr,
+ &peer->vpn_addrs.ipv6,
+ sizeof(peer->vpn_addrs.ipv6));
+ hlist_add_head_rcu(&peer->hash_entry_addr6, head);
}
unlock:
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (15 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 16/25] ovpn: implement peer lookup logic Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-15 14:44 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 18/25] ovpn: add support for updating local UDP endpoint Antonio Quartulli
` (7 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
OpenVPN supports configuring a periodic keepalive "ping"
message to allow the remote endpoint detect link failures.
This change implements the ping sending and timer expiring logic.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/io.c | 82 +++++++++++++++++++++++++++++++++++++++++
drivers/net/ovpn/io.h | 1 +
drivers/net/ovpn/peer.c | 72 ++++++++++++++++++++++++++++++++++++
drivers/net/ovpn/peer.h | 45 ++++++++++++++++++++++
4 files changed, 200 insertions(+)
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index 764b3df996bc..dedc4d4a1c72 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -25,6 +25,31 @@
#include "udp.h"
#include "skb.h"
+static const unsigned char ovpn_keepalive_message[] = {
+ 0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
+ 0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48
+};
+
+/**
+ * ovpn_is_keepalive - check if skb contains a keepalive message
+ * @skb: packet to check
+ *
+ * Assumes that the first byte of skb->data is defined.
+ *
+ * Return: true if skb contains a keepalive or false otherwise
+ */
+static bool ovpn_is_keepalive(struct sk_buff *skb)
+{
+ if (*skb->data != OVPN_KEEPALIVE_FIRST_BYTE)
+ return false;
+
+ if (!pskb_may_pull(skb, sizeof(ovpn_keepalive_message)))
+ return false;
+
+ return !memcmp(skb->data, ovpn_keepalive_message,
+ sizeof(ovpn_keepalive_message));
+}
+
/* Called after decrypt to write the IP packet to the device.
* This method is expected to manage/free the skb.
*/
@@ -91,6 +116,9 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
goto drop;
}
+ /* note event of authenticated packet received for keepalive */
+ ovpn_peer_keepalive_recv_reset(peer);
+
/* point to encapsulated IP packet */
__skb_pull(skb, ovpn_skb_cb(skb)->payload_offset);
@@ -107,6 +135,12 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
goto drop;
}
+ if (ovpn_is_keepalive(skb)) {
+ netdev_dbg(peer->ovpn->dev,
+ "ping received from peer %u\n", peer->id);
+ goto drop;
+ }
+
net_info_ratelimited("%s: unsupported protocol received from peer %u\n",
peer->ovpn->dev->name, peer->id);
goto drop;
@@ -203,6 +237,7 @@ void ovpn_encrypt_post(struct sk_buff *skb, int ret)
/* no transport configured yet */
goto err;
}
+ ovpn_peer_keepalive_xmit_reset(peer);
/* skb passed down the stack - don't free it */
skb = NULL;
err:
@@ -345,3 +380,50 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
kfree_skb_list(skb);
return NET_XMIT_DROP;
}
+
+/**
+ * ovpn_xmit_special - encrypt and transmit an out-of-band message to peer
+ * @peer: peer to send the message to
+ * @data: message content
+ * @len: message length
+ *
+ * Assumes that caller holds a reference to peer
+ */
+static void ovpn_xmit_special(struct ovpn_peer *peer, const void *data,
+ const unsigned int len)
+{
+ struct ovpn_struct *ovpn;
+ struct sk_buff *skb;
+
+ ovpn = peer->ovpn;
+ if (unlikely(!ovpn))
+ return;
+
+ skb = alloc_skb(256 + len, GFP_ATOMIC);
+ if (unlikely(!skb))
+ return;
+
+ skb_reserve(skb, 128);
+ skb->priority = TC_PRIO_BESTEFFORT;
+ memcpy(__skb_put(skb, len), data, len);
+
+ /* increase reference counter when passing peer to sending queue */
+ if (!ovpn_peer_hold(peer)) {
+ netdev_dbg(ovpn->dev, "%s: cannot hold peer reference for sending special packet\n",
+ __func__);
+ kfree_skb(skb);
+ return;
+ }
+
+ ovpn_send(ovpn, skb, peer);
+}
+
+/**
+ * ovpn_keepalive_xmit - send keepalive message to peer
+ * @peer: the peer to send the message to
+ */
+void ovpn_keepalive_xmit(struct ovpn_peer *peer)
+{
+ ovpn_xmit_special(peer, ovpn_keepalive_message,
+ sizeof(ovpn_keepalive_message));
+}
diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h
index ad741552d742..63fcd576ddaf 100644
--- a/drivers/net/ovpn/io.h
+++ b/drivers/net/ovpn/io.h
@@ -13,6 +13,7 @@
netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev);
void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb);
+void ovpn_keepalive_xmit(struct ovpn_peer *peer);
void ovpn_encrypt_post(struct sk_buff *skb, int ret);
void ovpn_decrypt_post(struct sk_buff *skb, int ret);
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 8c23268db916..289fe3f84ed4 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -22,6 +22,63 @@
#include "peer.h"
#include "socket.h"
+/**
+ * ovpn_peer_ping - timer task for sending periodic keepalive
+ * @t: timer object that triggered the task
+ */
+static void ovpn_peer_ping(struct timer_list *t)
+{
+ struct ovpn_peer *peer = from_timer(peer, t, keepalive_xmit);
+
+ netdev_dbg(peer->ovpn->dev, "%s: sending ping to peer %u\n", __func__,
+ peer->id);
+ ovpn_keepalive_xmit(peer);
+}
+
+/**
+ * ovpn_peer_expire - timer task for incoming keepialive timeout
+ * @t: the timer that triggered the task
+ */
+static void ovpn_peer_expire(struct timer_list *t)
+{
+ struct ovpn_peer *peer = from_timer(peer, t, keepalive_recv);
+
+ netdev_dbg(peer->ovpn->dev, "%s: peer %u expired\n", __func__,
+ peer->id);
+ ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_EXPIRED);
+}
+
+/**
+ * ovpn_peer_keepalive_set - configure keepalive values for peer
+ * @peer: the peer to configure
+ * @interval: outgoing keepalive interval
+ * @timeout: incoming keepalive timeout
+ */
+void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout)
+{
+ u32 delta;
+
+ netdev_dbg(peer->ovpn->dev,
+ "%s: scheduling keepalive for peer %u: interval=%u timeout=%u\n",
+ __func__, peer->id, interval, timeout);
+
+ peer->keepalive_interval = interval;
+ if (interval > 0) {
+ delta = msecs_to_jiffies(interval * MSEC_PER_SEC);
+ mod_timer(&peer->keepalive_xmit, jiffies + delta);
+ } else {
+ timer_delete(&peer->keepalive_xmit);
+ }
+
+ peer->keepalive_timeout = timeout;
+ if (timeout) {
+ delta = msecs_to_jiffies(timeout * MSEC_PER_SEC);
+ mod_timer(&peer->keepalive_recv, jiffies + delta);
+ } else {
+ timer_delete(&peer->keepalive_recv);
+ }
+}
+
/**
* ovpn_peer_new - allocate and initialize a new peer object
* @ovpn: the openvpn instance inside which the peer should be created
@@ -63,9 +120,22 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
netdev_hold(ovpn->dev, NULL, GFP_KERNEL);
+ timer_setup(&peer->keepalive_xmit, ovpn_peer_ping, 0);
+ timer_setup(&peer->keepalive_recv, ovpn_peer_expire, 0);
+
return peer;
}
+/**
+ * ovpn_peer_timer_delete_all - killall keepalive timers
+ * @peer: peer for which timers should be killed
+ */
+static void ovpn_peer_timer_delete_all(struct ovpn_peer *peer)
+{
+ timer_shutdown_sync(&peer->keepalive_xmit);
+ timer_shutdown_sync(&peer->keepalive_recv);
+}
+
/**
* ovpn_peer_release - release peer private members
* @peer: the peer to release
@@ -77,6 +147,8 @@ static void ovpn_peer_release(struct ovpn_peer *peer)
ovpn_crypto_state_release(&peer->crypto);
ovpn_bind_reset(peer, NULL);
+ ovpn_peer_timer_delete_all(peer);
+
dst_cache_destroy(&peer->dst_cache);
}
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 6e92e09a3504..fd7e9d57b38a 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -50,6 +50,10 @@
* @crypto: the crypto configuration (ciphers, keys, etc..)
* @dst_cache: cache for dst_entry used to send to peer
* @bind: remote peer binding
+ * @keepalive_xmit: timer used to send the next keepalive
+ * @keepalive_interval: seconds after which a new keepalive should be sent
+ * @keepalive_recv: timer used to check for received keepalives
+ * @keepalive_timeout: seconds after which an inactive peer is considered dead
* @halt: true if ovpn_peer_mark_delete was called
* @vpn_stats: per-peer in-VPN TX/RX stays
* @link_stats: per-peer link/transport TX/RX stats
@@ -98,6 +102,10 @@ struct ovpn_peer {
struct ovpn_crypto_state crypto;
struct dst_cache dst_cache;
struct ovpn_bind __rcu *bind;
+ struct timer_list keepalive_xmit;
+ unsigned long keepalive_interval;
+ struct timer_list keepalive_recv;
+ unsigned long keepalive_timeout;
bool halt;
struct ovpn_peer_stats vpn_stats;
struct ovpn_peer_stats link_stats;
@@ -144,4 +152,41 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn,
bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb,
struct ovpn_peer *peer);
+/**
+ * ovpn_peer_keepalive_recv_reset - reset keepalive timeout
+ * @peer: peer for which the timeout should be reset
+ *
+ * To be invoked upon reception of an authenticated packet from peer in order
+ * to report valid activity and thus reset the keepalive timeout
+ */
+static inline void ovpn_peer_keepalive_recv_reset(struct ovpn_peer *peer)
+{
+ u32 delta = msecs_to_jiffies(peer->keepalive_timeout * MSEC_PER_SEC);
+
+ if (unlikely(!delta))
+ return;
+
+ mod_timer(&peer->keepalive_recv, jiffies + delta);
+}
+
+/**
+ * ovpn_peer_keepalive_xmit_reset - reset keepalive sending timer
+ * @peer: peer for which the timer should be reset
+ *
+ * To be invoked upon sending of an authenticated packet to peer in order
+ * to report valid outgoing activity and thus reset the keepalive sending
+ * timer
+ */
+static inline void ovpn_peer_keepalive_xmit_reset(struct ovpn_peer *peer)
+{
+ u32 delta = msecs_to_jiffies(peer->keepalive_interval * MSEC_PER_SEC);
+
+ if (unlikely(!delta))
+ return;
+
+ mod_timer(&peer->keepalive_xmit, jiffies + delta);
+}
+
+void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout);
+
#endif /* _NET_OVPN_OVPNPEER_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 18/25] ovpn: add support for updating local UDP endpoint
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (16 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 19/25] ovpn: add support for peer floating Antonio Quartulli
` (6 subsequent siblings)
24 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
In case of UDP links, the local 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>
---
drivers/net/ovpn/io.c | 5 +++++
drivers/net/ovpn/peer.c | 42 +++++++++++++++++++++++++++++++++++++++++
drivers/net/ovpn/peer.h | 3 +++
3 files changed, 50 insertions(+)
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index dedc4d4a1c72..9188afe0f47e 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -119,6 +119,11 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
/* note event of authenticated packet received for keepalive */
ovpn_peer_keepalive_recv_reset(peer);
+ if (peer->sock->sock->sk->sk_protocol == IPPROTO_UDP) {
+ /* update source endpoint for this peer */
+ ovpn_peer_update_local_endpoint(peer, skb);
+ }
+
/* point to encapsulated IP packet */
__skb_pull(skb, ovpn_skb_cb(skb)->payload_offset);
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 289fe3f84ed4..ec3064438753 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -455,6 +455,48 @@ struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *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;
+
+ switch (skb_protocol_to_family(skb)) {
+ case AF_INET:
+ if (unlikely(bind->local.ipv4.s_addr != ip_hdr(skb)->daddr)) {
+ netdev_dbg(peer->ovpn->dev,
+ "%s: learning local IPv4 for peer %d (%pI4 -> %pI4)\n",
+ __func__, peer->id, &bind->local.ipv4.s_addr,
+ &ip_hdr(skb)->daddr);
+ bind->local.ipv4.s_addr = ip_hdr(skb)->daddr;
+ }
+ break;
+ case AF_INET6:
+ if (unlikely(memcmp(&bind->local.ipv6, &ipv6_hdr(skb)->daddr,
+ sizeof(bind->local.ipv6)))) {
+ netdev_dbg(peer->ovpn->dev,
+ "%s: learning local IPv6 for peer %d (%pI6c -> %pI6c\n",
+ __func__, peer->id, &bind->local.ipv6,
+ &ipv6_hdr(skb)->daddr);
+ bind->local.ipv6 = ipv6_hdr(skb)->daddr;
+ }
+ break;
+ default:
+ break;
+ }
+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 fd7e9d57b38a..1f12ba141d80 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -189,4 +189,7 @@ static inline void ovpn_peer_keepalive_xmit_reset(struct ovpn_peer *peer)
void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout);
+void ovpn_peer_update_local_endpoint(struct ovpn_peer *peer,
+ struct sk_buff *skb);
+
#endif /* _NET_OVPN_OVPNPEER_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 19/25] ovpn: add support for peer floating
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (17 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 18/25] ovpn: add support for updating local UDP endpoint Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-17 17:15 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 20/25] ovpn: implement peer add/dump/delete via netlink Antonio Quartulli
` (5 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
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@openvpn.net>
---
drivers/net/ovpn/io.c | 4 ++
drivers/net/ovpn/peer.c | 114 ++++++++++++++++++++++++++++++++++++++--
drivers/net/ovpn/peer.h | 2 +
3 files changed, 117 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index 9188afe0f47e..4c6a50f3f0d0 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -120,6 +120,10 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
ovpn_peer_keepalive_recv_reset(peer);
if (peer->sock->sock->sk->sk_protocol == IPPROTO_UDP) {
+ /* check if this peer changed it's IP address and update
+ * state
+ */
+ ovpn_peer_float(peer, skb);
/* update source endpoint for this peer */
ovpn_peer_update_local_endpoint(peer, skb);
}
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index ec3064438753..c07d148c52b4 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -126,6 +126,117 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *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;
+
+ /* 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 {
+ netdev_dbg(peer->ovpn->dev, "%s: invalid family for remote endpoint\n",
+ __func__);
+ kfree(bind);
+ return -EINVAL;
+ }
+
+ memcpy(&bind->local, local_ip, ip_len);
+ }
+
+ /* set binding */
+ ovpn_bind_reset(peer, bind);
+
+ return 0;
+}
+
+#define ovpn_get_hash_head(_tbl, _key, _key_len) \
+ (&(_tbl)[jhash(_key, _key_len, 0) % HASH_SIZE(_tbl)]) \
+
+/**
+ * ovpn_peer_float - update remote endpoint for peer
+ * @peer: peer to update the remote endpoint for
+ * @skb: incoming packet to retrieve the source address (remote) from
+ */
+void ovpn_peer_float(struct ovpn_peer *peer, struct sk_buff *skb)
+{
+ struct sockaddr_storage ss;
+ const u8 *local_ip = NULL;
+ struct sockaddr_in6 *sa6;
+ struct sockaddr_in *sa;
+ struct ovpn_bind *bind;
+ sa_family_t family;
+ size_t salen;
+
+ rcu_read_lock();
+ bind = rcu_dereference(peer->bind);
+ if (unlikely(!bind))
+ goto unlock;
+
+ if (likely(ovpn_bind_skb_src_match(bind, skb)))
+ goto unlock;
+
+ family = skb_protocol_to_family(skb);
+
+ if (bind->sa.in4.sin_family == family)
+ local_ip = (u8 *)&bind->local;
+
+ switch (family) {
+ case AF_INET:
+ 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;
+ case AF_INET6:
+ 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);
+ break;
+ default:
+ goto unlock;
+ }
+
+ netdev_dbg(peer->ovpn->dev, "%s: peer %d floated to %pIScp", __func__,
+ peer->id, &ss);
+ ovpn_peer_reset_sockaddr(peer, (struct sockaddr_storage *)&ss,
+ local_ip);
+
+ spin_lock_bh(&peer->ovpn->peers->lock);
+ /* remove old hashing */
+ hlist_del_init_rcu(&peer->hash_entry_transp_addr);
+ /* re-add with new transport address */
+ hlist_add_head_rcu(&peer->hash_entry_transp_addr,
+ ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr,
+ &ss, salen));
+ spin_unlock_bh(&peer->ovpn->peers->lock);
+
+unlock:
+ rcu_read_unlock();
+}
+
/**
* ovpn_peer_timer_delete_all - killall keepalive timers
* @peer: peer for which timers should be killed
@@ -231,9 +342,6 @@ static struct in6_addr ovpn_nexthop_from_skb6(struct sk_buff *skb)
return rt->rt6i_gateway;
}
-#define ovpn_get_hash_head(_tbl, _key, _key_len) \
- (&(_tbl)[jhash(_key, _key_len, 0) % HASH_SIZE(_tbl)]) \
-
/**
* ovpn_peer_get_by_vpn_addr4 - retrieve peer by its VPN IPv4 address
* @ovpn: the openvpn instance to search
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 1f12ba141d80..691cf20bd870 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -192,4 +192,6 @@ void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout);
void ovpn_peer_update_local_endpoint(struct ovpn_peer *peer,
struct sk_buff *skb);
+void ovpn_peer_float(struct ovpn_peer *peer, struct sk_buff *skb);
+
#endif /* _NET_OVPN_OVPNPEER_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 20/25] ovpn: implement peer add/dump/delete via netlink
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (18 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 19/25] ovpn: add support for peer floating Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-16 13:41 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 21/25] ovpn: implement key add/del/swap " Antonio Quartulli
` (4 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
This change introduces the netlink command needed to add, delete and
retrieve/dump known peers. Userspace is expected to use these commands
to handle known peer lifecycles.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/netlink.c | 487 ++++++++++++++++++++++++++++++++++++-
drivers/net/ovpn/peer.c | 8 +-
drivers/net/ovpn/peer.h | 4 +
3 files changed, 490 insertions(+), 9 deletions(-)
diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
index ecac4721f0c6..e0d35c4ac2fb 100644
--- a/drivers/net/ovpn/netlink.c
+++ b/drivers/net/ovpn/netlink.c
@@ -8,6 +8,7 @@
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
+#include <linux/types.h>
#include <net/genetlink.h>
#include <uapi/linux/ovpn.h>
@@ -17,6 +18,10 @@
#include "io.h"
#include "netlink.h"
#include "netlink-gen.h"
+#include "bind.h"
+#include "packet.h"
+#include "peer.h"
+#include "socket.h"
MODULE_ALIAS_GENL_FAMILY(OVPN_FAMILY_NAME);
@@ -29,7 +34,7 @@ MODULE_ALIAS_GENL_FAMILY(OVPN_FAMILY_NAME);
* Return: the netdevice, if found, or an error otherwise
*/
static struct net_device *
-ovpn_get_dev_from_attrs(struct net *net, struct genl_info *info)
+ovpn_get_dev_from_attrs(struct net *net, const struct genl_info *info)
{
struct net_device *dev;
int ifindex;
@@ -141,24 +146,496 @@ int ovpn_nl_del_iface_doit(struct sk_buff *skb, struct genl_info *info)
return 0;
}
+static u8 *ovpn_nl_attr_local_ip(struct genl_info *info,
+ struct ovpn_struct *ovpn,
+ struct nlattr **attrs, int sock_fam)
+{
+ size_t ip_len = nla_len(attrs[OVPN_A_PEER_LOCAL_IP]);
+ u8 *local_ip = nla_data(attrs[OVPN_A_PEER_LOCAL_IP]);
+ bool is_mapped;
+
+ if (ip_len == sizeof(struct in_addr)) {
+ if (sock_fam != AF_INET) {
+ NL_SET_ERR_MSG_MOD(info->extack,
+ "mismatching AF between local IP (v4) and peer");
+ return ERR_PTR(-EINVAL);
+ }
+ } else if (ip_len == sizeof(struct in6_addr)) {
+ is_mapped = ipv6_addr_v4mapped((struct in6_addr *)local_ip);
+
+ if (sock_fam != AF_INET6 && !is_mapped) {
+ NL_SET_ERR_MSG_MOD(info->extack,
+ "mismatching AF between local IP (v6) and peer");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (is_mapped)
+ /* this is an IPv6-mapped IPv4
+ * address, therefore extract
+ * the actual v4 address from
+ * the last 4 bytes
+ */
+ local_ip += 12;
+ } else {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "invalid local IP length: %zu", ip_len);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return local_ip;
+}
+
int ovpn_nl_set_peer_doit(struct sk_buff *skb, struct genl_info *info)
{
- return -EOPNOTSUPP;
+ bool keepalive_set = false, new_peer = false;
+ struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
+ struct ovpn_struct *ovpn = info->user_ptr[0];
+ struct sockaddr_storage *ss = NULL;
+ u32 sockfd, id, interv, timeout;
+ struct socket *sock = NULL;
+ struct sockaddr_in mapped;
+ struct sockaddr_in6 *in6;
+ struct ovpn_peer *peer;
+ u8 *local_ip = NULL;
+ size_t sa_len;
+ int ret;
+
+ if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER))
+ return -EINVAL;
+
+ ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER],
+ ovpn_peer_nl_policy, info->extack);
+ if (ret)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs,
+ OVPN_A_PEER_ID))
+ return -EINVAL;
+
+ id = nla_get_u32(attrs[OVPN_A_PEER_ID]);
+ /* check if the peer exists first, otherwise create a new one */
+ peer = ovpn_peer_get_by_id(ovpn, id);
+ if (!peer) {
+ peer = ovpn_peer_new(ovpn, id);
+ new_peer = true;
+ if (IS_ERR(peer)) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "cannot create new peer object for peer %u (sockaddr=%pIScp): %ld",
+ id, ss, PTR_ERR(peer));
+ return PTR_ERR(peer);
+ }
+ }
+
+ if (new_peer && NL_REQ_ATTR_CHECK(info->extack,
+ info->attrs[OVPN_A_PEER], attrs,
+ OVPN_A_PEER_SOCKET)) {
+ ret = -EINVAL;
+ goto peer_release;
+ }
+
+ if (new_peer && ovpn->mode == OVPN_MODE_MP &&
+ !attrs[OVPN_A_PEER_VPN_IPV4] && !attrs[OVPN_A_PEER_VPN_IPV6]) {
+ NL_SET_ERR_MSG_MOD(info->extack,
+ "a VPN IP is required when adding a peer in MP mode");
+ ret = -EINVAL;
+ goto peer_release;
+ }
+
+ if (attrs[OVPN_A_PEER_SOCKET]) {
+ /* lookup the fd in the kernel table and extract the socket
+ * object
+ */
+ sockfd = nla_get_u32(attrs[OVPN_A_PEER_SOCKET]);
+ /* sockfd_lookup() increases sock's refcounter */
+ sock = sockfd_lookup(sockfd, &ret);
+ if (!sock) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "cannot lookup peer socket (fd=%u): %d",
+ sockfd, ret);
+ ret = -ENOTSOCK;
+ goto peer_release;
+ }
+
+ if (peer->sock)
+ ovpn_socket_put(peer->sock);
+
+ peer->sock = ovpn_socket_new(sock, peer);
+ if (IS_ERR(peer->sock)) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "cannot encapsulate socket: %ld",
+ PTR_ERR(peer->sock));
+ sockfd_put(sock);
+ peer->sock = NULL;
+ ret = -ENOTSOCK;
+ goto peer_release;
+ }
+ }
+
+ /* Only when using UDP as transport protocol the remote endpoint
+ * can be configured so that ovpn knows where to send packets
+ * to.
+ *
+ * In case of TCP, the socket is connected to the peer and ovpn
+ * will just send bytes over it, without the need to specify a
+ * destination.
+ */
+ if (peer->sock->sock->sk->sk_protocol == IPPROTO_UDP &&
+ attrs[OVPN_A_PEER_SOCKADDR_REMOTE]) {
+ ss = nla_data(attrs[OVPN_A_PEER_SOCKADDR_REMOTE]);
+ sa_len = nla_len(attrs[OVPN_A_PEER_SOCKADDR_REMOTE]);
+ switch (sa_len) {
+ case sizeof(struct sockaddr_in):
+ if (ss->ss_family == AF_INET)
+ /* valid sockaddr */
+ break;
+
+ NL_SET_ERR_MSG_MOD(info->extack,
+ "remote sockaddr_in has invalid family");
+ ret = -EINVAL;
+ goto peer_release;
+ case sizeof(struct sockaddr_in6):
+ if (ss->ss_family == AF_INET6)
+ /* valid sockaddr */
+ break;
+
+ NL_SET_ERR_MSG_MOD(info->extack,
+ "remote sockaddr_in6 has invalid family");
+ ret = -EINVAL;
+ goto peer_release;
+ default:
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "invalid size for sockaddr: %zd",
+ sa_len);
+ ret = -EINVAL;
+ goto peer_release;
+ }
+
+ /* if this is a v6-mapped-v4, convert the sockaddr
+ * object from AF_INET6 to AF_INET before continue
+ * processing
+ */
+ if (ss->ss_family == AF_INET6) {
+ in6 = (struct sockaddr_in6 *)ss;
+
+ if (ipv6_addr_v4mapped(&in6->sin6_addr)) {
+ mapped.sin_family = AF_INET;
+ mapped.sin_addr.s_addr =
+ in6->sin6_addr.s6_addr32[3];
+ mapped.sin_port = in6->sin6_port;
+ ss = (struct sockaddr_storage *)&mapped;
+ }
+ }
+
+ if (attrs[OVPN_A_PEER_LOCAL_IP]) {
+ local_ip = ovpn_nl_attr_local_ip(info, ovpn,
+ attrs,
+ ss->ss_family);
+ if (IS_ERR(local_ip)) {
+ ret = PTR_ERR(local_ip);
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "cannot retrieve local IP: %d",
+ ret);
+ goto peer_release;
+ }
+ }
+
+ /* set peer sockaddr */
+ ret = ovpn_peer_reset_sockaddr(peer, ss, local_ip);
+ if (ret < 0) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "cannot set peer sockaddr: %d",
+ ret);
+ goto peer_release;
+ }
+ }
+
+ /* VPN IPs cannot be updated, because they are hashed */
+ if (new_peer && attrs[OVPN_A_PEER_VPN_IPV4])
+ peer->vpn_addrs.ipv4.s_addr =
+ nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]);
+
+ /* VPN IPs cannot be updated, because they are hashed */
+ if (new_peer && attrs[OVPN_A_PEER_VPN_IPV6])
+ peer->vpn_addrs.ipv6 =
+ nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]);
+
+ /* when setting the keepalive, both parameters have to be configured */
+ if (attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL] &&
+ attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]) {
+ keepalive_set = true;
+ interv = nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]);
+ timeout = nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]);
+ }
+
+ if (keepalive_set)
+ ovpn_peer_keepalive_set(peer, interv, timeout);
+
+ netdev_dbg(ovpn->dev,
+ "%s: %s peer with endpoint=%pIScp/%s id=%u VPN-IPv4=%pI4 VPN-IPv6=%pI6c\n",
+ __func__, (new_peer ? "adding" : "modifying"), ss,
+ peer->sock->sock->sk->sk_prot_creator->name, peer->id,
+ &peer->vpn_addrs.ipv4.s_addr, &peer->vpn_addrs.ipv6);
+
+ if (new_peer) {
+ ret = ovpn_peer_add(ovpn, peer);
+ if (ret < 0) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "cannot add new peer (id=%u) to hashtable: %d\n",
+ peer->id, ret);
+ goto peer_release;
+ }
+ } else {
+ ovpn_peer_put(peer);
+ }
+
+ return 0;
+
+peer_release:
+ if (new_peer) {
+ /* release right away because peer is not really used in any
+ * context
+ */
+ ovpn_peer_release(peer);
+ kfree(peer);
+ } else {
+ ovpn_peer_put(peer);
+ }
+
+ return ret;
+}
+
+static int ovpn_nl_send_peer(struct sk_buff *skb, const struct genl_info *info,
+ const struct ovpn_peer *peer, u32 portid, u32 seq,
+ int flags)
+{
+ const struct ovpn_bind *bind;
+ struct nlattr *attr;
+ void *hdr;
+
+ hdr = genlmsg_put(skb, portid, seq, &ovpn_nl_family, flags,
+ OVPN_CMD_SET_PEER);
+ if (!hdr)
+ return -ENOBUFS;
+
+ attr = nla_nest_start(skb, OVPN_A_PEER);
+ if (!attr)
+ goto err;
+
+ if (nla_put_u32(skb, OVPN_A_PEER_ID, peer->id))
+ goto err;
+
+ if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY))
+ if (nla_put_in_addr(skb, OVPN_A_PEER_VPN_IPV4,
+ peer->vpn_addrs.ipv4.s_addr))
+ goto err;
+
+ if (!ipv6_addr_equal(&peer->vpn_addrs.ipv6, &in6addr_any))
+ if (nla_put_in6_addr(skb, OVPN_A_PEER_VPN_IPV6,
+ &peer->vpn_addrs.ipv6))
+ goto err;
+
+ if (nla_put_u32(skb, OVPN_A_PEER_KEEPALIVE_INTERVAL,
+ peer->keepalive_interval) ||
+ nla_put_u32(skb, OVPN_A_PEER_KEEPALIVE_TIMEOUT,
+ peer->keepalive_timeout))
+ goto err;
+
+ rcu_read_lock();
+ bind = rcu_dereference(peer->bind);
+ if (bind) {
+ if (bind->sa.in4.sin_family == AF_INET) {
+ if (nla_put(skb, OVPN_A_PEER_SOCKADDR_REMOTE,
+ sizeof(bind->sa.in4), &bind->sa.in4) ||
+ nla_put(skb, OVPN_A_PEER_LOCAL_IP,
+ sizeof(bind->local.ipv4),
+ &bind->local.ipv4))
+ goto err_unlock;
+ } else if (bind->sa.in4.sin_family == AF_INET6) {
+ if (nla_put(skb, OVPN_A_PEER_SOCKADDR_REMOTE,
+ sizeof(bind->sa.in6), &bind->sa.in6) ||
+ nla_put(skb, OVPN_A_PEER_LOCAL_IP,
+ sizeof(bind->local.ipv6),
+ &bind->local.ipv6))
+ goto err_unlock;
+ }
+ }
+ rcu_read_unlock();
+
+ if (nla_put_net16(skb, OVPN_A_PEER_LOCAL_PORT,
+ inet_sk(peer->sock->sock->sk)->inet_sport) ||
+ /* VPN RX stats */
+ nla_put_uint(skb, OVPN_A_PEER_VPN_RX_BYTES,
+ atomic64_read(&peer->vpn_stats.rx.bytes)) ||
+ nla_put_uint(skb, OVPN_A_PEER_VPN_RX_PACKETS,
+ atomic64_read(&peer->vpn_stats.rx.packets)) ||
+ /* VPN TX stats */
+ nla_put_uint(skb, OVPN_A_PEER_VPN_TX_BYTES,
+ atomic64_read(&peer->vpn_stats.tx.bytes)) ||
+ nla_put_uint(skb, OVPN_A_PEER_VPN_TX_PACKETS,
+ atomic64_read(&peer->vpn_stats.tx.packets)) ||
+ /* link RX stats */
+ nla_put_uint(skb, OVPN_A_PEER_LINK_RX_BYTES,
+ atomic64_read(&peer->link_stats.rx.bytes)) ||
+ nla_put_uint(skb, OVPN_A_PEER_LINK_RX_PACKETS,
+ atomic64_read(&peer->link_stats.rx.packets)) ||
+ /* link TX stats */
+ nla_put_uint(skb, OVPN_A_PEER_LINK_TX_BYTES,
+ atomic64_read(&peer->link_stats.tx.bytes)) ||
+ nla_put_uint(skb, OVPN_A_PEER_LINK_TX_PACKETS,
+ atomic64_read(&peer->link_stats.tx.packets)))
+ goto err;
+
+ nla_nest_end(skb, attr);
+ genlmsg_end(skb, hdr);
+
+ return 0;
+err_unlock:
+ rcu_read_unlock();
+err:
+ genlmsg_cancel(skb, hdr);
+ return -EMSGSIZE;
}
int ovpn_nl_get_peer_doit(struct sk_buff *skb, struct genl_info *info)
{
- return -EOPNOTSUPP;
+ struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
+ struct ovpn_struct *ovpn = info->user_ptr[0];
+ struct ovpn_peer *peer;
+ struct sk_buff *msg;
+ u32 peer_id;
+ int ret;
+
+ if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER))
+ return -EINVAL;
+
+ ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER],
+ ovpn_peer_nl_policy, info->extack);
+ if (ret)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs,
+ OVPN_A_PEER_ID))
+ return -EINVAL;
+
+ peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]);
+ peer = ovpn_peer_get_by_id(ovpn, peer_id);
+ if (!peer) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "cannot find peer with id %u", peer_id);
+ return -ENOENT;
+ }
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ ret = ovpn_nl_send_peer(msg, info, peer, info->snd_portid,
+ info->snd_seq, 0);
+ if (ret < 0) {
+ nlmsg_free(msg);
+ goto err;
+ }
+
+ ret = genlmsg_reply(msg, info);
+err:
+ ovpn_peer_put(peer);
+ return ret;
}
int ovpn_nl_get_peer_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
{
- return -EOPNOTSUPP;
+ const struct genl_info *info = genl_info_dump(cb);
+ struct ovpn_struct *ovpn;
+ struct ovpn_peer *peer;
+ struct net_device *dev;
+ int bkt, last_idx = cb->args[1], dumped = 0;
+
+ dev = ovpn_get_dev_from_attrs(sock_net(cb->skb->sk), info);
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ ovpn = netdev_priv(dev);
+
+ if (ovpn->mode == OVPN_MODE_P2P) {
+ /* if we already dumped a peer it means we are done */
+ if (last_idx)
+ goto out;
+
+ rcu_read_lock();
+ peer = rcu_dereference(ovpn->peer);
+ if (peer) {
+ if (ovpn_nl_send_peer(skb, info, peer,
+ NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq,
+ NLM_F_MULTI) == 0)
+ dumped++;
+ }
+ rcu_read_unlock();
+ } else {
+ rcu_read_lock();
+ hash_for_each_rcu(ovpn->peers->by_id, bkt, peer,
+ hash_entry_id) {
+ /* skip already dumped peers that were dumped by
+ * previous invocations
+ */
+ if (last_idx > 0) {
+ last_idx--;
+ continue;
+ }
+
+ if (ovpn_nl_send_peer(skb, info, peer,
+ NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq,
+ NLM_F_MULTI) < 0)
+ break;
+
+ /* count peers being dumped during this invocation */
+ dumped++;
+ }
+ rcu_read_unlock();
+ }
+
+out:
+ netdev_put(dev, NULL);
+
+ /* sum up peers dumped in this message, so that at the next invocation
+ * we can continue from where we left
+ */
+ cb->args[1] += dumped;
+ return skb->len;
}
int ovpn_nl_del_peer_doit(struct sk_buff *skb, struct genl_info *info)
{
- return -EOPNOTSUPP;
+ struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
+ struct ovpn_struct *ovpn = info->user_ptr[0];
+ struct ovpn_peer *peer;
+ u32 peer_id;
+ int ret;
+
+ if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER))
+ return -EINVAL;
+
+ ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER],
+ ovpn_peer_nl_policy, info->extack);
+ if (ret)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs,
+ OVPN_A_PEER_ID))
+ return -EINVAL;
+
+ peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]);
+
+ peer = ovpn_peer_get_by_id(ovpn, peer_id);
+ if (!peer)
+ return -ENOENT;
+
+ netdev_dbg(ovpn->dev, "%s: peer id=%u\n", __func__, peer->id);
+ ret = ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_USERSPACE);
+ ovpn_peer_put(peer);
+
+ return ret;
}
int ovpn_nl_set_key_doit(struct sk_buff *skb, struct genl_info *info)
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index c07d148c52b4..2105bcc981fa 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -134,9 +134,9 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
*
* 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)
+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;
@@ -251,7 +251,7 @@ static void ovpn_peer_timer_delete_all(struct ovpn_peer *peer)
* ovpn_peer_release - release peer private members
* @peer: the peer to release
*/
-static void ovpn_peer_release(struct ovpn_peer *peer)
+void ovpn_peer_release(struct ovpn_peer *peer)
{
if (peer->sock)
ovpn_socket_put(peer->sock);
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 691cf20bd870..8d24a8fdd03e 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -127,6 +127,7 @@ static inline bool ovpn_peer_hold(struct ovpn_peer *peer)
return kref_get_unless_zero(&peer->refcount);
}
+void ovpn_peer_release(struct ovpn_peer *peer);
void ovpn_peer_release_kref(struct kref *kref);
/**
@@ -193,5 +194,8 @@ void ovpn_peer_update_local_endpoint(struct ovpn_peer *peer,
struct sk_buff *skb);
void ovpn_peer_float(struct ovpn_peer *peer, struct sk_buff *skb);
+int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer,
+ const struct sockaddr_storage *ss,
+ const u8 *local_ip);
#endif /* _NET_OVPN_OVPNPEER_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 21/25] ovpn: implement key add/del/swap via netlink
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (19 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 20/25] ovpn: implement peer add/dump/delete via netlink Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-17 17:17 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 22/25] ovpn: kill key and notify userspace in case of IV exhaustion Antonio Quartulli
` (3 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
This change introduces the netlink commands needed to add, delete and
swap keys for a specific peer.
Userspace is expected to use these commands to create, destroy and
rotate session keys for a specific peer.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
Documentation/netlink/specs/ovpn.yaml | 2 +-
drivers/net/ovpn/netlink-gen.c | 2 +-
drivers/net/ovpn/netlink.c | 199 +++++++++++++++++++++++++-
3 files changed, 198 insertions(+), 5 deletions(-)
diff --git a/Documentation/netlink/specs/ovpn.yaml b/Documentation/netlink/specs/ovpn.yaml
index 68ed88d03732..21c89f0bdcbb 100644
--- a/Documentation/netlink/specs/ovpn.yaml
+++ b/Documentation/netlink/specs/ovpn.yaml
@@ -153,7 +153,7 @@ attribute-sets:
decryption
type: u32
checks:
- max: 2
+ max: 7
-
name: cipher-alg
type: u32
diff --git a/drivers/net/ovpn/netlink-gen.c b/drivers/net/ovpn/netlink-gen.c
index 594eb2c50eb5..f7c4c448b263 100644
--- a/drivers/net/ovpn/netlink-gen.c
+++ b/drivers/net/ovpn/netlink-gen.c
@@ -23,7 +23,7 @@ static const struct netlink_range_validation ovpn_a_peer_local_port_range = {
/* Common nested types */
const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR + 1] = {
[OVPN_A_KEYCONF_SLOT] = NLA_POLICY_MAX(NLA_U32, 1),
- [OVPN_A_KEYCONF_KEY_ID] = NLA_POLICY_MAX(NLA_U32, 2),
+ [OVPN_A_KEYCONF_KEY_ID] = NLA_POLICY_MAX(NLA_U32, 7),
[OVPN_A_KEYCONF_CIPHER_ALG] = NLA_POLICY_MAX(NLA_U32, 2),
[OVPN_A_KEYCONF_ENCRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy),
[OVPN_A_KEYCONF_DECRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy),
diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
index e0d35c4ac2fb..31c58cda6a3d 100644
--- a/drivers/net/ovpn/netlink.c
+++ b/drivers/net/ovpn/netlink.c
@@ -638,19 +638,212 @@ int ovpn_nl_del_peer_doit(struct sk_buff *skb, struct genl_info *info)
return ret;
}
+static int ovpn_nl_get_key_dir(struct genl_info *info, struct nlattr *key,
+ enum ovpn_cipher_alg cipher,
+ struct ovpn_key_direction *dir)
+{
+ struct nlattr *attrs[OVPN_A_KEYDIR_MAX + 1];
+ int ret;
+
+ ret = nla_parse_nested(attrs, OVPN_A_KEYDIR_MAX, key,
+ ovpn_keydir_nl_policy, info->extack);
+ if (ret)
+ return ret;
+
+ switch (cipher) {
+ case OVPN_CIPHER_ALG_AES_GCM:
+ case OVPN_CIPHER_ALG_CHACHA20_POLY1305:
+ if (NL_REQ_ATTR_CHECK(info->extack, key, attrs,
+ OVPN_A_KEYDIR_CIPHER_KEY) ||
+ NL_REQ_ATTR_CHECK(info->extack, key, attrs,
+ OVPN_A_KEYDIR_NONCE_TAIL))
+ return -EINVAL;
+
+ dir->cipher_key = nla_data(attrs[OVPN_A_KEYDIR_CIPHER_KEY]);
+ dir->cipher_key_size = nla_len(attrs[OVPN_A_KEYDIR_CIPHER_KEY]);
+
+ /* These algorithms require a 96bit nonce,
+ * Construct it by combining 4-bytes packet id and
+ * 8-bytes nonce-tail from userspace
+ */
+ dir->nonce_tail = nla_data(attrs[OVPN_A_KEYDIR_NONCE_TAIL]);
+ dir->nonce_tail_size = nla_len(attrs[OVPN_A_KEYDIR_NONCE_TAIL]);
+ break;
+ default:
+ NL_SET_ERR_MSG_MOD(info->extack, "unsupported cipher");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
int ovpn_nl_set_key_doit(struct sk_buff *skb, struct genl_info *info)
{
- return -EOPNOTSUPP;
+ struct nlattr *p_attrs[OVPN_A_PEER_MAX + 1];
+ struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1];
+ struct ovpn_struct *ovpn = info->user_ptr[0];
+ struct ovpn_peer_key_reset pkr;
+ struct ovpn_peer *peer;
+ u32 peer_id;
+ int ret;
+
+ if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER))
+ return -EINVAL;
+
+ ret = nla_parse_nested(p_attrs, OVPN_A_PEER_MAX,
+ info->attrs[OVPN_A_PEER], ovpn_peer_nl_policy,
+ info->extack);
+ if (ret)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], p_attrs,
+ OVPN_A_PEER_ID) ||
+ NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], p_attrs,
+ OVPN_A_PEER_KEYCONF))
+ return -EINVAL;
+
+ ret = nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX,
+ p_attrs[OVPN_A_PEER_KEYCONF],
+ ovpn_keyconf_nl_policy, info->extack);
+ if (ret)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(info->extack, p_attrs[OVPN_A_PEER_KEYCONF], attrs,
+ OVPN_A_KEYCONF_SLOT) ||
+ NL_REQ_ATTR_CHECK(info->extack, p_attrs[OVPN_A_PEER_KEYCONF], attrs,
+ OVPN_A_KEYCONF_KEY_ID) ||
+ NL_REQ_ATTR_CHECK(info->extack, p_attrs[OVPN_A_PEER_KEYCONF], attrs,
+ OVPN_A_KEYCONF_CIPHER_ALG) ||
+ NL_REQ_ATTR_CHECK(info->extack, p_attrs[OVPN_A_PEER_KEYCONF], attrs,
+ OVPN_A_KEYCONF_ENCRYPT_DIR) ||
+ NL_REQ_ATTR_CHECK(info->extack, p_attrs[OVPN_A_PEER_KEYCONF], attrs,
+ OVPN_A_KEYCONF_DECRYPT_DIR))
+ return -EINVAL;
+
+ peer_id = nla_get_u32(p_attrs[OVPN_A_PEER_ID]);
+ pkr.slot = nla_get_u8(attrs[OVPN_A_KEYCONF_SLOT]);
+ pkr.key.key_id = nla_get_u16(attrs[OVPN_A_KEYCONF_KEY_ID]);
+ pkr.key.cipher_alg = nla_get_u16(attrs[OVPN_A_KEYCONF_CIPHER_ALG]);
+
+ ret = ovpn_nl_get_key_dir(info, attrs[OVPN_A_KEYCONF_ENCRYPT_DIR],
+ pkr.key.cipher_alg, &pkr.key.encrypt);
+ if (ret < 0)
+ return ret;
+
+ ret = ovpn_nl_get_key_dir(info, attrs[OVPN_A_KEYCONF_DECRYPT_DIR],
+ pkr.key.cipher_alg, &pkr.key.decrypt);
+ if (ret < 0)
+ return ret;
+
+ peer = ovpn_peer_get_by_id(ovpn, peer_id);
+ if (!peer) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "no peer with id %u to set key for",
+ peer_id);
+ return -ENOENT;
+ }
+
+ ret = ovpn_crypto_state_reset(&peer->crypto, &pkr);
+ if (ret < 0) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "cannot install new key for peer %u",
+ peer_id);
+ goto out;
+ }
+
+ netdev_dbg(ovpn->dev, "%s: new key installed (id=%u) for peer %u\n",
+ __func__, pkr.key.key_id, peer_id);
+out:
+ ovpn_peer_put(peer);
+ return ret;
}
int ovpn_nl_swap_keys_doit(struct sk_buff *skb, struct genl_info *info)
{
- return -EOPNOTSUPP;
+ struct ovpn_struct *ovpn = info->user_ptr[0];
+ struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
+ struct ovpn_peer *peer;
+ u32 peer_id;
+ int ret;
+
+ if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER))
+ return -EINVAL;
+
+ ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER],
+ ovpn_peer_nl_policy, info->extack);
+ if (ret)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs,
+ OVPN_A_PEER_ID))
+ return -EINVAL;
+
+ peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]);
+
+ peer = ovpn_peer_get_by_id(ovpn, peer_id);
+ if (!peer) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "no peer with id %u to swap keys for",
+ peer_id);
+ return -ENOENT;
+ }
+
+ ovpn_crypto_key_slots_swap(&peer->crypto);
+ ovpn_peer_put(peer);
+
+ return 0;
}
int ovpn_nl_del_key_doit(struct sk_buff *skb, struct genl_info *info)
{
- return -EOPNOTSUPP;
+ struct nlattr *p_attrs[OVPN_A_PEER_MAX + 1];
+ struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1];
+ struct ovpn_struct *ovpn = info->user_ptr[0];
+ enum ovpn_key_slot slot;
+ struct ovpn_peer *peer;
+ u32 peer_id;
+ int ret;
+
+ if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER))
+ return -EINVAL;
+
+ ret = nla_parse_nested(p_attrs, OVPN_A_PEER_MAX,
+ info->attrs[OVPN_A_PEER], ovpn_peer_nl_policy,
+ info->extack);
+ if (ret)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], p_attrs,
+ OVPN_A_PEER_ID) ||
+ NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], p_attrs,
+ OVPN_A_PEER_KEYCONF))
+ return -EINVAL;
+
+ ret = nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX,
+ p_attrs[OVPN_A_PEER_KEYCONF],
+ ovpn_keyconf_nl_policy, info->extack);
+ if (ret)
+ return ret;
+
+ if (NL_REQ_ATTR_CHECK(info->extack, p_attrs[OVPN_A_PEER_KEYCONF], attrs,
+ OVPN_A_KEYCONF_SLOT))
+ return -EINVAL;
+
+ peer_id = nla_get_u32(p_attrs[OVPN_A_PEER_ID]);
+ slot = nla_get_u8(attrs[OVPN_A_KEYCONF_SLOT]);
+
+ peer = ovpn_peer_get_by_id(ovpn, peer_id);
+ if (!peer) {
+ NL_SET_ERR_MSG_FMT_MOD(info->extack,
+ "no peer with id %u to delete key for",
+ peer_id);
+ return -ENOENT;
+ }
+
+ ovpn_crypto_key_slot_delete(&peer->crypto, slot);
+ ovpn_peer_put(peer);
+
+ return 0;
}
/**
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 22/25] ovpn: kill key and notify userspace in case of IV exhaustion
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (20 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 21/25] ovpn: implement key add/del/swap " Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-17 10:42 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 23/25] ovpn: notify userspace when a peer is deleted Antonio Quartulli
` (2 subsequent siblings)
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
IV wrap-around is cryptographically dangerous for a number of ciphers,
therefore kill the key and inform userspace (via netlink) should the
IV space go exhausted.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/netlink.c | 39 ++++++++++++++++++++++++++++++++++++++
drivers/net/ovpn/netlink.h | 8 ++++++++
2 files changed, 47 insertions(+)
diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
index 31c58cda6a3d..e43bbc9ad5d2 100644
--- a/drivers/net/ovpn/netlink.c
+++ b/drivers/net/ovpn/netlink.c
@@ -846,6 +846,45 @@ int ovpn_nl_del_key_doit(struct sk_buff *skb, struct genl_info *info)
return 0;
}
+int ovpn_nl_notify_swap_keys(struct ovpn_peer *peer)
+{
+ struct sk_buff *msg;
+ int ret = -EMSGSIZE;
+ void *hdr;
+
+ netdev_info(peer->ovpn->dev, "peer with id %u must rekey - primary key unusable.\n",
+ peer->id);
+
+ msg = nlmsg_new(100, GFP_ATOMIC);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, 0, 0, &ovpn_nl_family, 0, OVPN_CMD_SWAP_KEYS);
+ if (!hdr) {
+ ret = -ENOBUFS;
+ goto err_free_msg;
+ }
+
+ if (nla_put_u32(msg, OVPN_A_IFINDEX, peer->ovpn->dev->ifindex))
+ goto err_cancel_msg;
+
+ if (nla_put_u32(msg, OVPN_A_PEER_ID, peer->id))
+ goto err_cancel_msg;
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_multicast_netns(&ovpn_nl_family, dev_net(peer->ovpn->dev), msg,
+ 0, OVPN_NLGRP_PEERS, GFP_ATOMIC);
+
+ return 0;
+
+err_cancel_msg:
+ genlmsg_cancel(msg, hdr);
+err_free_msg:
+ nlmsg_free(msg);
+ return ret;
+}
+
/**
* ovpn_nl_register - perform any needed registration in the NL subsustem
*
diff --git a/drivers/net/ovpn/netlink.h b/drivers/net/ovpn/netlink.h
index 9e87cf11d1e9..c86cd102eeef 100644
--- a/drivers/net/ovpn/netlink.h
+++ b/drivers/net/ovpn/netlink.h
@@ -12,4 +12,12 @@
int ovpn_nl_register(void);
void ovpn_nl_unregister(void);
+/**
+ * ovpn_nl_notify_swap_keys - notify userspace peer's key must be renewed
+ * @peer: the peer whose key needs to be renewed
+ *
+ * Return: 0 on success or a negative error code otherwise
+ */
+int ovpn_nl_notify_swap_keys(struct ovpn_peer *peer);
+
#endif /* _NET_OVPN_NETLINK_H_ */
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 23/25] ovpn: notify userspace when a peer is deleted
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (21 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 22/25] ovpn: kill key and notify userspace in case of IV exhaustion Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-07-17 10:54 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 24/25] ovpn: add basic ethtool support Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 25/25] testing/selftest: add test tool and scripts for ovpn module Antonio Quartulli
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
Whenever a peer is deleted, send a notification to userspace so that it
can react accordingly.
This is most important when a peer is deleted due to ping timeout,
because it all happens in kernelspace and thus userspace has no direct
way to learn about it.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/netlink.c | 49 ++++++++++++++++++++++++++++++++++++++
drivers/net/ovpn/netlink.h | 8 +++++++
drivers/net/ovpn/peer.c | 1 +
drivers/net/ovpn/peer.h | 1 +
4 files changed, 59 insertions(+)
diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
index e43bbc9ad5d2..e920bb071750 100644
--- a/drivers/net/ovpn/netlink.c
+++ b/drivers/net/ovpn/netlink.c
@@ -846,6 +846,55 @@ int ovpn_nl_del_key_doit(struct sk_buff *skb, struct genl_info *info)
return 0;
}
+int ovpn_nl_notify_del_peer(struct ovpn_peer *peer)
+{
+ struct sk_buff *msg;
+ struct nlattr *attr;
+ int ret = -EMSGSIZE;
+ void *hdr;
+
+ netdev_info(peer->ovpn->dev, "deleting peer with id %u, reason %d\n",
+ peer->id, peer->delete_reason);
+
+ msg = nlmsg_new(100, GFP_ATOMIC);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, 0, 0, &ovpn_nl_family, 0, OVPN_CMD_DEL_PEER);
+ if (!hdr) {
+ ret = -ENOBUFS;
+ goto err_free_msg;
+ }
+
+ if (nla_put_u32(msg, OVPN_A_IFINDEX, peer->ovpn->dev->ifindex))
+ goto err_cancel_msg;
+
+ attr = nla_nest_start(msg, OVPN_A_PEER);
+ if (!attr)
+ goto err_cancel_msg;
+
+ if (nla_put_u8(msg, OVPN_A_PEER_DEL_REASON, peer->delete_reason))
+ goto err_cancel_msg;
+
+ if (nla_put_u32(msg, OVPN_A_PEER_ID, peer->id))
+ goto err_cancel_msg;
+
+ nla_nest_end(msg, attr);
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_multicast_netns(&ovpn_nl_family, dev_net(peer->ovpn->dev), msg,
+ 0, OVPN_NLGRP_PEERS, GFP_ATOMIC);
+
+ return 0;
+
+err_cancel_msg:
+ genlmsg_cancel(msg, hdr);
+err_free_msg:
+ nlmsg_free(msg);
+ return ret;
+}
+
int ovpn_nl_notify_swap_keys(struct ovpn_peer *peer)
{
struct sk_buff *msg;
diff --git a/drivers/net/ovpn/netlink.h b/drivers/net/ovpn/netlink.h
index c86cd102eeef..8ce58c4ee193 100644
--- a/drivers/net/ovpn/netlink.h
+++ b/drivers/net/ovpn/netlink.h
@@ -12,6 +12,14 @@
int ovpn_nl_register(void);
void ovpn_nl_unregister(void);
+/**
+ * ovpn_nl_notify_del_peer - notify userspace about peer being deleted
+ * @peer: the peer being deleted
+ *
+ * Return: 0 on success or a negative error code otherwise
+ */
+int ovpn_nl_notify_del_peer(struct ovpn_peer *peer);
+
/**
* ovpn_nl_notify_swap_keys - notify userspace peer's key must be renewed
* @peer: the peer whose key needs to be renewed
diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
index 2105bcc981fa..23418204fa8e 100644
--- a/drivers/net/ovpn/peer.c
+++ b/drivers/net/ovpn/peer.c
@@ -273,6 +273,7 @@ void ovpn_peer_release_kref(struct kref *kref)
ovpn_peer_release(peer);
netdev_put(peer->ovpn->dev, NULL);
+ ovpn_nl_notify_del_peer(peer);
kfree_rcu(peer, rcu);
}
diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
index 8d24a8fdd03e..971603a70090 100644
--- a/drivers/net/ovpn/peer.h
+++ b/drivers/net/ovpn/peer.h
@@ -129,6 +129,7 @@ static inline bool ovpn_peer_hold(struct ovpn_peer *peer)
void ovpn_peer_release(struct ovpn_peer *peer);
void ovpn_peer_release_kref(struct kref *kref);
+void ovpn_peer_release(struct ovpn_peer *peer);
/**
* ovpn_peer_put - decrease reference counter
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 24/25] ovpn: add basic ethtool support
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (22 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 23/25] ovpn: notify userspace when a peer is deleted Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
2024-06-27 16:25 ` Andrew Lunn
2024-06-27 13:08 ` [PATCH net-next v5 25/25] testing/selftest: add test tool and scripts for ovpn module Antonio Quartulli
24 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev; +Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli
Implement support for basic ethtool functionality.
Note that ovpn is a virtual device driver, therefore
various ethtool APIs are just not meaningful and thus
not implemented.
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/main.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c
index cefd7010ab37..985dfcf1744c 100644
--- a/drivers/net/ovpn/main.c
+++ b/drivers/net/ovpn/main.c
@@ -7,6 +7,7 @@
* James Yonan <james@openvpn.net>
*/
+#include <linux/ethtool.h>
#include <linux/genetlink.h>
#include <linux/module.h>
#include <linux/netdevice.h>
@@ -132,6 +133,19 @@ bool ovpn_dev_is_valid(const struct net_device *dev)
return dev->netdev_ops->ndo_start_xmit == ovpn_net_xmit;
}
+static void ovpn_get_drvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *info)
+{
+ strscpy(info->driver, OVPN_FAMILY_NAME, sizeof(info->driver));
+ strscpy(info->bus_info, "ovpn", sizeof(info->bus_info));
+}
+
+static const struct ethtool_ops ovpn_ethtool_ops = {
+ .get_drvinfo = ovpn_get_drvinfo,
+ .get_link = ethtool_op_get_link,
+ .get_ts_info = ethtool_op_get_ts_info,
+};
+
/* we register with rtnl to let core know that ovpn is a virtual driver and
* therefore ifaces should be destroyed when exiting a netns
*/
@@ -155,6 +169,7 @@ static void ovpn_setup(struct net_device *dev)
dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS;
+ dev->ethtool_ops = &ovpn_ethtool_ops;
dev->netdev_ops = &ovpn_netdev_ops;
dev->rtnl_link_ops = &ovpn_link_ops;
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* [PATCH net-next v5 25/25] testing/selftest: add test tool and scripts for ovpn module
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
` (23 preceding siblings ...)
2024-06-27 13:08 ` [PATCH net-next v5 24/25] ovpn: add basic ethtool support Antonio Quartulli
@ 2024-06-27 13:08 ` Antonio Quartulli
24 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-06-27 13:08 UTC (permalink / raw)
To: netdev
Cc: kuba, ryazanov.s.a, pabeni, edumazet, andrew, sd,
Antonio Quartulli, shuah, linux-kselftest
The ovpn-cli tool can be compiled and used as selftest for the ovpn
kernel module.
It implementes the netlink API and can thus be integrated in any
script for more automated testing.
Along with the tool, 2 scripts are added that perform basic
functionality tests by means of network namespaces.
The scripts can be performed in sequence by running run.sh
Cc: shuah@kernel.org
Cc: linux-kselftest@vger.kernel.org
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/net/ovpn/.gitignore | 2 +
tools/testing/selftests/net/ovpn/Makefile | 17 +
tools/testing/selftests/net/ovpn/config | 8 +
.../selftests/net/ovpn/data-test-tcp.sh | 9 +
tools/testing/selftests/net/ovpn/data-test.sh | 130 ++
tools/testing/selftests/net/ovpn/data64.key | 5 +
.../testing/selftests/net/ovpn/float-test.sh | 115 ++
tools/testing/selftests/net/ovpn/ovpn-cli.c | 1787 +++++++++++++++++
.../testing/selftests/net/ovpn/tcp_peers.txt | 1 +
.../testing/selftests/net/ovpn/udp_peers.txt | 5 +
11 files changed, 2080 insertions(+)
create mode 100644 tools/testing/selftests/net/ovpn/.gitignore
create mode 100644 tools/testing/selftests/net/ovpn/Makefile
create mode 100644 tools/testing/selftests/net/ovpn/config
create mode 100755 tools/testing/selftests/net/ovpn/data-test-tcp.sh
create mode 100755 tools/testing/selftests/net/ovpn/data-test.sh
create mode 100644 tools/testing/selftests/net/ovpn/data64.key
create mode 100755 tools/testing/selftests/net/ovpn/float-test.sh
create mode 100644 tools/testing/selftests/net/ovpn/ovpn-cli.c
create mode 100644 tools/testing/selftests/net/ovpn/tcp_peers.txt
create mode 100644 tools/testing/selftests/net/ovpn/udp_peers.txt
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 9039f3709aff..26ebd6917bfc 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -64,6 +64,7 @@ TARGETS += net/forwarding
TARGETS += net/hsr
TARGETS += net/mptcp
TARGETS += net/openvswitch
+TARGETS += net/ovpn
TARGETS += net/tcp_ao
TARGETS += net/netfilter
TARGETS += nsfs
diff --git a/tools/testing/selftests/net/ovpn/.gitignore b/tools/testing/selftests/net/ovpn/.gitignore
new file mode 100644
index 000000000000..ee44c081ca7c
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0+
+ovpn-cli
diff --git a/tools/testing/selftests/net/ovpn/Makefile b/tools/testing/selftests/net/ovpn/Makefile
new file mode 100644
index 000000000000..171cf047497c
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/Makefile
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2020-2024 OpenVPN, Inc.
+#
+CFLAGS = -Wall -I../../../../../usr/include
+CFLAGS += $(shell pkg-config --cflags libnl-3.0 libnl-genl-3.0)
+
+LDFLAGS = -lmbedtls -lmbedcrypto
+LDFLAGS += $(shell pkg-config --libs libnl-3.0 libnl-genl-3.0)
+
+ovpn-cli: ovpn-cli.c
+
+TEST_PROGS = data-test.sh \
+ data-test-tcp.sh \
+ float-test.sh
+TEST_GEN_FILES = ovpn-cli
+
+include ../../lib.mk
diff --git a/tools/testing/selftests/net/ovpn/config b/tools/testing/selftests/net/ovpn/config
new file mode 100644
index 000000000000..5ff47de23c12
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/config
@@ -0,0 +1,8 @@
+CONFIG_NET=y
+CONFIG_INET=y
+CONFIG_NET_UDP_TUNNEL=y
+CONFIG_DST_CACHE=y
+CONFIG_CRYPTO_AES=y
+CONFIG_CRYPTO_GCM=y
+CONFIG_CRYPTO_CHACHA20POLY1305=y
+CONFIG_OVPN=y
diff --git a/tools/testing/selftests/net/ovpn/data-test-tcp.sh b/tools/testing/selftests/net/ovpn/data-test-tcp.sh
new file mode 100755
index 000000000000..65f05659b5fd
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/data-test-tcp.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2024 OpenVPN, Inc.
+#
+# Author: Antonio Quartulli <antonio@openvpn.net>
+
+PROTO="TCP"
+
+source data-test.sh
diff --git a/tools/testing/selftests/net/ovpn/data-test.sh b/tools/testing/selftests/net/ovpn/data-test.sh
new file mode 100755
index 000000000000..b192635dfd3c
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/data-test.sh
@@ -0,0 +1,130 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2020-2024 OpenVPN, Inc.
+#
+# Author: Antonio Quartulli <antonio@openvpn.net>
+
+#set -x
+set -e
+
+UDP_PEERS_FILE=${UDP_PEERS_FILE:-udp_peers.txt}
+TCP_PEERS_FILE=${TCP_PEERS_FILE:-tcp_peers.txt}
+OVPN_CLI=${OVPN_CLI:-./ovpn-cli}
+ALG=${ALG:-aes}
+PROTO=${PROTO:-UDP}
+
+create_ns() {
+ ip netns add peer${1}
+}
+
+setup_ns() {
+ MODE="P2P"
+
+ if [ ${1} -eq 0 ]; then
+ MODE="MP"
+ for p in $(seq 1 ${NUM_PEERS}); do
+ ip link add veth${p} netns peer0 type veth peer name veth${p} netns peer${p}
+
+ ip -n peer0 addr add 10.10.${p}.1/24 dev veth${p}
+ ip -n peer0 link set veth${p} up
+
+ ip -n peer${p} addr add 10.10.${p}.2/24 dev veth${p}
+ ip -n peer${p} link set veth${p} up
+ done
+ fi
+
+ ip netns exec peer${1} ${OVPN_CLI} new_iface tun${1} $MODE
+ ip -n peer${1} addr add ${2} dev tun${1}
+ ip -n peer${1} link set tun${1} up
+}
+
+add_peer() {
+ if [ "${PROTO}" == "UDP" ]; then
+ if [ ${1} -eq 0 ]; then
+ ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 ${UDP_PEERS_FILE}
+
+ for p in $(seq 1 ${NUM_PEERS}); do
+ # ip netns exec peer0 ${OVPN_CLI} new_peer tun0 ${p} ${p} 10.10.${p}.2 1 5.5.5.$((${p} + 1))
+ ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 ${ALG} 0 data64.key
+ done
+ else
+ ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} 1 ${1} 10.10.${1}.1 1 5.5.5.1
+ ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} ${1} 1 0 ${ALG} 1 data64.key
+ fi
+ else
+ if [ ${1} -eq 0 ]; then
+ (ip netns exec peer0 ${OVPN_CLI} listen tun0 1 ${TCP_PEERS_FILE} && {
+ for p in $(seq 1 ${NUM_PEERS}); do
+ ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 ${ALG} 0 data64.key
+ done
+ }) &
+ sleep 5
+ else
+ ip netns exec peer${1} ${OVPN_CLI} connect tun${1} ${1} 10.10.${1}.1 1 5.5.5.1 data64.key
+ fi
+ fi
+}
+
+cleanup() {
+ for p in $(seq 1 10); do
+ ip -n peer0 link del veth${p} 2>/dev/null || true
+ done
+ for p in $(seq 0 10); do
+ ip netns exec peer${p} ${OVPN_CLI} del_iface tun${p} 2>/dev/null || true
+ ip netns del peer${p} 2>/dev/null || true
+ done
+}
+
+if [ "${PROTO}" == "UDP" ]; then
+ NUM_PEERS=${NUM_PEERS:-$(wc -l ${UDP_PEERS_FILE} | awk '{print $1}')}
+else
+ NUM_PEERS=${NUM_PEERS:-$(wc -l ${TCP_PEERS_FILE} | awk '{print $1}')}
+fi
+
+cleanup
+
+for p in $(seq 0 ${NUM_PEERS}); do
+ create_ns ${p}
+done
+
+for p in $(seq 0 ${NUM_PEERS}); do
+ setup_ns ${p} 5.5.5.$((${p} + 1))/24
+done
+
+for p in $(seq 0 ${NUM_PEERS}); do
+ add_peer ${p}
+done
+
+for p in $(seq 1 ${NUM_PEERS}); do
+ ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 60 120
+ ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 60 120
+done
+
+for p in $(seq 1 ${NUM_PEERS}); do
+ ip netns exec peer0 ping -qfc 1000 -w 5 5.5.5.$((${p} + 1))
+done
+
+ip netns exec peer0 iperf3 -1 -s &
+sleep 1
+ip netns exec peer1 iperf3 -Z -t 3 -c 5.5.5.1
+
+for p in $(seq 1 ${NUM_PEERS}); do
+ ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 2 1 ${ALG} 0 data64.key
+ ip netns exec peer${p} ${OVPN_CLI} new_key tun${p} ${p} 2 1 ${ALG} 1 data64.key
+ ip netns exec peer${p} ${OVPN_CLI} swap_keys tun${p} ${p}
+done
+
+sleep 1
+echo "Querying all peers:"
+ip netns exec peer0 ${OVPN_CLI} get_peer tun0
+ip netns exec peer1 ${OVPN_CLI} get_peer tun1
+
+echo "Querying peer 1:"
+ip netns exec peer0 ${OVPN_CLI} get_peer tun0 1
+
+echo "Querying non-existent peer 10:"
+ip netns exec peer0 ${OVPN_CLI} get_peer tun0 10 || true
+
+ip netns exec peer0 ${OVPN_CLI} del_peer tun0 1
+
+cleanup
diff --git a/tools/testing/selftests/net/ovpn/data64.key b/tools/testing/selftests/net/ovpn/data64.key
new file mode 100644
index 000000000000..a99e88c4e290
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/data64.key
@@ -0,0 +1,5 @@
+jRqMACN7d7/aFQNT8S7jkrBD8uwrgHbG5OQZP2eu4R1Y7tfpS2bf5RHv06Vi163CGoaIiTX99R3B
+ia9ycAH8Wz1+9PWv51dnBLur9jbShlgZ2QHLtUc4a/gfT7zZwULXuuxdLnvR21DDeMBaTbkgbai9
+uvAa7ne1liIgGFzbv+Bas4HDVrygxIxuAnP5Qgc3648IJkZ0QEXPF+O9f0n5+QIvGCxkAUVx+5K6
+KIs+SoeWXnAopELmoGSjUpFtJbagXK82HfdqpuUxT2Tnuef0/14SzVE/vNleBNu2ZbyrSAaah8tE
+BofkPJUBFY+YQcfZNM5Dgrw3i+Bpmpq/gpdg5w==
diff --git a/tools/testing/selftests/net/ovpn/float-test.sh b/tools/testing/selftests/net/ovpn/float-test.sh
new file mode 100755
index 000000000000..3095f0686744
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/float-test.sh
@@ -0,0 +1,115 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2020-2024 OpenVPN, Inc.
+#
+# Author: Antonio Quartulli <antonio@openvpn.net>
+
+#set -x
+set -e
+
+UDP_PEERS_FILE=${UDP_PEERS_FILE:-udp_peers.txt}
+TCP_PEERS_FILE=${TCP_PEERS_FILE:-tcp_peers.txt}
+OVPN_CLI=${OVPN_CLI:-./ovpn-cli}
+ALG=${ALG:-aes}
+PROTO=${PROTO:-UDP}
+
+create_ns() {
+ ip netns add peer${1}
+}
+
+setup_ns() {
+ MODE="P2P"
+
+ if [ ${1} -eq 0 ]; then
+ MODE="MP"
+ for p in $(seq 1 ${NUM_PEERS}); do
+ ip link add veth${p} netns peer0 type veth peer name veth${p} netns peer${p}
+
+ ip -n peer0 addr add 10.10.${p}.1/24 dev veth${p}
+ ip -n peer0 link set veth${p} up
+
+ ip -n peer${p} addr add 10.10.${p}.2/24 dev veth${p}
+ ip -n peer${p} link set veth${p} up
+ done
+ fi
+
+ ip netns exec peer${1} ${OVPN_CLI} new_iface tun${1} $MODE
+ ip -n peer${1} addr add ${2} dev tun${1}
+ ip -n peer${1} link set tun${1} up
+}
+
+add_peer() {
+ if [ "${PROTO}" == "UDP" ]; then
+ if [ ${1} -eq 0 ]; then
+ ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 ${UDP_PEERS_FILE}
+
+ for p in $(seq 1 ${NUM_PEERS}); do
+ # ip netns exec peer0 ${OVPN_CLI} new_peer tun0 ${p} ${p} 10.10.${p}.2 1 5.5.5.$((${p} + 1))
+ ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 ${ALG} 0 data64.key
+ done
+ else
+ ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} 1 ${1} 10.10.${1}.1 1 5.5.5.1
+ ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} ${1} 1 0 ${ALG} 1 data64.key
+ fi
+ else
+ if [ ${1} -eq 0 ]; then
+ (ip netns exec peer0 ${OVPN_CLI} listen tun0 1 ${TCP_PEERS_FILE} && {
+ for p in $(seq 1 ${NUM_PEERS}); do
+ ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 ${ALG} 0 data64.key
+ done
+ }) &
+ sleep 5
+ else
+ ip netns exec peer${1} ${OVPN_CLI} connect tun${1} ${1} 10.10.${1}.1 1 5.5.5.1 data64.key
+ fi
+ fi
+}
+
+cleanup() {
+ for p in $(seq 1 10); do
+ ip -n peer0 link del veth${p} 2>/dev/null || true
+ done
+ for p in $(seq 0 10); do
+ ip netns exec peer${p} ${OVPN_CLI} del_iface tun${p} 2>/dev/null || true
+ ip netns del peer${p} 2>/dev/null || true
+ done
+}
+
+if [ "${PROTO}" == "UDP" ]; then
+ NUM_PEERS=${NUM_PEERS:-$(wc -l ${UDP_PEERS_FILE} | awk '{print $1}')}
+else
+ NUM_PEERS=${NUM_PEERS:-$(wc -l ${TCP_PEERS_FILE} | awk '{print $1}')}
+fi
+
+cleanup
+
+for p in $(seq 0 ${NUM_PEERS}); do
+ create_ns ${p}
+done
+
+for p in $(seq 0 ${NUM_PEERS}); do
+ setup_ns ${p} 5.5.5.$((${p} + 1))/24
+done
+
+for p in $(seq 0 ${NUM_PEERS}); do
+ add_peer ${p}
+done
+
+for p in $(seq 1 ${NUM_PEERS}); do
+ ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 60 120
+ ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 60 120
+done
+
+for p in $(seq 1 ${NUM_PEERS}); do
+ ip netns exec peer0 ping -qfc 1000 -w 5 5.5.5.$((${p} + 1))
+done
+# make clients float..
+for p in $(seq 1 ${NUM_PEERS}); do
+ ip -n peer${p} addr del 10.10.${p}.2/24 dev veth${p}
+ ip -n peer${p} addr add 10.10.${p}.3/24 dev veth${p}
+done
+for p in $(seq 1 ${NUM_PEERS}); do
+ ip netns exec peer${p} ping -qfc 1000 -w 5 5.5.5.1
+done
+
+cleanup
diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c
new file mode 100644
index 000000000000..8cb37c465343
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c
@@ -0,0 +1,1787 @@
+// SPDX-License-Identifier: GPL-2.0
+/* OpenVPN data channel accelerator
+ *
+ * Copyright (C) 2020-2024 OpenVPN, Inc.
+ *
+ * Author: Antonio Quartulli <antonio@openvpn.net>
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+
+#include <linux/ovpn.h>
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+#include <netlink/socket.h>
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/family.h>
+#include <netlink/genl/ctrl.h>
+
+#include <mbedtls/base64.h>
+#include <mbedtls/error.h>
+
+#include <sys/socket.h>
+
+/* we use strscpy to make checkpatch happy */
+#define strscpy strncpy
+
+/* libnl < 3.5.0 does not set the NLA_F_NESTED on its own, therefore we
+ * have to explicitly do it to prevent the kernel from failing upon
+ * parsing of the message
+ */
+#define nla_nest_start(_msg, _type) \
+ nla_nest_start(_msg, (_type) | NLA_F_NESTED)
+
+uint64_t nla_get_uint(struct nlattr *attr)
+{
+ if (nla_len(attr) == sizeof(uint32_t))
+ return nla_get_u32(attr);
+ else
+ return nla_get_u64(attr);
+}
+
+typedef int (*ovpn_nl_cb)(struct nl_msg *msg, void *arg);
+
+enum ovpn_key_direction {
+ KEY_DIR_IN = 0,
+ KEY_DIR_OUT,
+};
+
+#define KEY_LEN (256 / 8)
+#define NONCE_LEN 8
+
+#define PEER_ID_UNDEF 0x00FFFFFF
+
+struct nl_ctx {
+ struct nl_sock *nl_sock;
+ struct nl_msg *nl_msg;
+ struct nl_cb *nl_cb;
+
+ int ovpn_dco_id;
+};
+
+struct ovpn_ctx {
+ __u8 key_enc[KEY_LEN];
+ __u8 key_dec[KEY_LEN];
+ __u8 nonce[NONCE_LEN];
+
+ enum ovpn_cipher_alg cipher;
+
+ sa_family_t sa_family;
+
+ __u32 peer_id;
+ __u16 lport;
+
+ union {
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ } remote;
+
+ union {
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ } peer_ip;
+
+ unsigned int ifindex;
+ char ifname[IFNAMSIZ];
+ enum ovpn_mode mode;
+ bool mode_set;
+
+ int socket;
+ int cli_socket;
+
+ __u32 keepalive_interval;
+ __u32 keepalive_timeout;
+
+ enum ovpn_key_direction key_dir;
+ enum ovpn_key_slot key_slot;
+ int key_id;
+};
+
+static int ovpn_nl_recvmsgs(struct nl_ctx *ctx)
+{
+ int ret;
+
+ ret = nl_recvmsgs(ctx->nl_sock, ctx->nl_cb);
+
+ switch (ret) {
+ case -NLE_INTR:
+ fprintf(stderr,
+ "netlink received interrupt due to signal - ignoring\n");
+ break;
+ case -NLE_NOMEM:
+ fprintf(stderr, "netlink out of memory error\n");
+ break;
+ case -NLE_AGAIN:
+ fprintf(stderr,
+ "netlink reports blocking read - aborting wait\n");
+ break;
+ default:
+ if (ret)
+ fprintf(stderr, "netlink reports error (%d): %s\n",
+ ret, nl_geterror(-ret));
+ break;
+ }
+
+ return ret;
+}
+
+static struct nl_ctx *nl_ctx_alloc_flags(struct ovpn_ctx *ovpn, int cmd,
+ int flags)
+{
+ struct nl_ctx *ctx;
+ int ret;
+
+ ctx = calloc(1, sizeof(*ctx));
+ if (!ctx)
+ return NULL;
+
+ ctx->nl_sock = nl_socket_alloc();
+ if (!ctx->nl_sock) {
+ fprintf(stderr, "cannot allocate netlink socket\n");
+ goto err_free;
+ }
+
+ nl_socket_set_buffer_size(ctx->nl_sock, 8192, 8192);
+
+ ret = genl_connect(ctx->nl_sock);
+ if (ret) {
+ fprintf(stderr, "cannot connect to generic netlink: %s\n",
+ nl_geterror(ret));
+ goto err_sock;
+ }
+
+ ctx->ovpn_dco_id = genl_ctrl_resolve(ctx->nl_sock, OVPN_FAMILY_NAME);
+ if (ctx->ovpn_dco_id < 0) {
+ fprintf(stderr, "cannot find ovpn_dco netlink component: %d\n",
+ ctx->ovpn_dco_id);
+ goto err_free;
+ }
+
+ ctx->nl_msg = nlmsg_alloc();
+ if (!ctx->nl_msg) {
+ fprintf(stderr, "cannot allocate netlink message\n");
+ goto err_sock;
+ }
+
+ ctx->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
+ if (!ctx->nl_cb) {
+ fprintf(stderr, "failed to allocate netlink callback\n");
+ goto err_msg;
+ }
+
+ nl_socket_set_cb(ctx->nl_sock, ctx->nl_cb);
+
+ genlmsg_put(ctx->nl_msg, 0, 0, ctx->ovpn_dco_id, 0, flags, cmd, 0);
+
+ if (ovpn->ifindex > 0)
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_IFINDEX, ovpn->ifindex);
+
+ return ctx;
+nla_put_failure:
+err_msg:
+ nlmsg_free(ctx->nl_msg);
+err_sock:
+ nl_socket_free(ctx->nl_sock);
+err_free:
+ free(ctx);
+ return NULL;
+}
+
+static struct nl_ctx *nl_ctx_alloc(struct ovpn_ctx *ovpn, int cmd)
+{
+ return nl_ctx_alloc_flags(ovpn, cmd, 0);
+}
+
+static void nl_ctx_free(struct nl_ctx *ctx)
+{
+ if (!ctx)
+ return;
+
+ nl_socket_free(ctx->nl_sock);
+ nlmsg_free(ctx->nl_msg);
+ nl_cb_put(ctx->nl_cb);
+ free(ctx);
+}
+
+static int ovpn_nl_cb_error(struct sockaddr_nl (*nla)__attribute__((unused)),
+ struct nlmsgerr *err, void *arg)
+{
+ struct nlmsghdr *nlh = (struct nlmsghdr *)err - 1;
+ struct nlattr *tb_msg[NLMSGERR_ATTR_MAX + 1];
+ int len = nlh->nlmsg_len;
+ struct nlattr *attrs;
+ int *ret = arg;
+ int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh);
+
+ *ret = err->error;
+
+ if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
+ return NL_STOP;
+
+ if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
+ ack_len += err->msg.nlmsg_len - sizeof(*nlh);
+
+ if (len <= ack_len)
+ return NL_STOP;
+
+ attrs = (void *)((unsigned char *)nlh + ack_len);
+ len -= ack_len;
+
+ nla_parse(tb_msg, NLMSGERR_ATTR_MAX, attrs, len, NULL);
+ if (tb_msg[NLMSGERR_ATTR_MSG]) {
+ len = strnlen((char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]),
+ nla_len(tb_msg[NLMSGERR_ATTR_MSG]));
+ fprintf(stderr, "kernel error: %*s\n", len,
+ (char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]));
+ }
+
+ return NL_STOP;
+}
+
+static int ovpn_nl_cb_finish(struct nl_msg (*msg)__attribute__((unused)),
+ void *arg)
+{
+ int *status = arg;
+
+ *status = 0;
+ return NL_SKIP;
+}
+
+static int ovpn_nl_msg_send(struct nl_ctx *ctx, ovpn_nl_cb cb)
+{
+ int status = 1;
+
+ nl_cb_err(ctx->nl_cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &status);
+ nl_cb_set(ctx->nl_cb, NL_CB_FINISH, NL_CB_CUSTOM, ovpn_nl_cb_finish,
+ &status);
+ nl_cb_set(ctx->nl_cb, NL_CB_ACK, NL_CB_CUSTOM, ovpn_nl_cb_finish,
+ &status);
+
+ if (cb)
+ nl_cb_set(ctx->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, cb, ctx);
+
+ nl_send_auto_complete(ctx->nl_sock, ctx->nl_msg);
+
+ while (status == 1)
+ ovpn_nl_recvmsgs(ctx);
+
+ if (status < 0)
+ fprintf(stderr, "failed to send netlink message: %s (%d)\n",
+ strerror(-status), status);
+
+ return status;
+}
+
+static int ovpn_read_key(const char *file, struct ovpn_ctx *ctx)
+{
+ int idx_enc, idx_dec, ret = -1;
+ unsigned char *ckey = NULL;
+ __u8 *bkey = NULL;
+ size_t olen = 0;
+ long ckey_len;
+ FILE *fp;
+
+ fp = fopen(file, "r");
+ if (!fp) {
+ fprintf(stderr, "cannot open: %s\n", file);
+ return -1;
+ }
+
+ /* get file size */
+ fseek(fp, 0L, SEEK_END);
+ ckey_len = ftell(fp);
+ rewind(fp);
+
+ /* if the file is longer, let's just read a portion */
+ if (ckey_len > 256)
+ ckey_len = 256;
+
+ ckey = malloc(ckey_len);
+ if (!ckey)
+ goto err;
+
+ ret = fread(ckey, 1, ckey_len, fp);
+ if (ret != ckey_len) {
+ fprintf(stderr,
+ "couldn't read enough data from key file: %dbytes read\n",
+ ret);
+ goto err;
+ }
+
+ olen = 0;
+ ret = mbedtls_base64_decode(NULL, 0, &olen, ckey, ckey_len);
+ if (ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
+ char buf[256];
+
+ mbedtls_strerror(ret, buf, sizeof(buf));
+ fprintf(stderr, "unexpected base64 error1: %s (%d)\n", buf,
+ ret);
+
+ goto err;
+ }
+
+ bkey = malloc(olen);
+ if (!bkey) {
+ fprintf(stderr, "cannot allocate binary key buffer\n");
+ goto err;
+ }
+
+ ret = mbedtls_base64_decode(bkey, olen, &olen, ckey, ckey_len);
+ if (ret) {
+ char buf[256];
+
+ mbedtls_strerror(ret, buf, sizeof(buf));
+ fprintf(stderr, "unexpected base64 error2: %s (%d)\n", buf,
+ ret);
+
+ goto err;
+ }
+
+ if (olen < 2 * KEY_LEN + NONCE_LEN) {
+ fprintf(stderr,
+ "not enough data in key file, found %zdB but needs %dB\n",
+ olen, 2 * KEY_LEN + NONCE_LEN);
+ goto err;
+ }
+
+ switch (ctx->key_dir) {
+ case KEY_DIR_IN:
+ idx_enc = 0;
+ idx_dec = 1;
+ break;
+ case KEY_DIR_OUT:
+ idx_enc = 1;
+ idx_dec = 0;
+ break;
+ }
+
+ memcpy(ctx->key_enc, bkey + KEY_LEN * idx_enc, KEY_LEN);
+ memcpy(ctx->key_dec, bkey + KEY_LEN * idx_dec, KEY_LEN);
+ memcpy(ctx->nonce, bkey + 2 * KEY_LEN, NONCE_LEN);
+
+ ret = 0;
+
+err:
+ fclose(fp);
+ free(bkey);
+ free(ckey);
+
+ return ret;
+}
+
+static int ovpn_read_cipher(const char *cipher, struct ovpn_ctx *ctx)
+{
+ if (strcmp(cipher, "aes") == 0)
+ ctx->cipher = OVPN_CIPHER_ALG_AES_GCM;
+ else if (strcmp(cipher, "chachapoly") == 0)
+ ctx->cipher = OVPN_CIPHER_ALG_CHACHA20_POLY1305;
+ else if (strcmp(cipher, "none") == 0)
+ ctx->cipher = OVPN_CIPHER_ALG_NONE;
+ else
+ return -ENOTSUP;
+
+ return 0;
+}
+
+static int ovpn_read_key_direction(const char *dir, struct ovpn_ctx *ctx)
+{
+ int in_dir;
+
+ in_dir = strtoll(dir, NULL, 10);
+ switch (in_dir) {
+ case KEY_DIR_IN:
+ case KEY_DIR_OUT:
+ ctx->key_dir = in_dir;
+ break;
+ default:
+ fprintf(stderr,
+ "invalid key direction provided. Can be 0 or 1 only\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ovpn_socket(struct ovpn_ctx *ctx, sa_family_t family, int proto)
+{
+ struct sockaddr_storage local_sock;
+ struct sockaddr_in6 *in6;
+ struct sockaddr_in *in;
+ int ret, s, sock_type;
+ size_t sock_len;
+
+ if (proto == IPPROTO_UDP)
+ sock_type = SOCK_DGRAM;
+ else if (proto == IPPROTO_TCP)
+ sock_type = SOCK_STREAM;
+ else
+ return -EINVAL;
+
+ s = socket(family, sock_type, 0);
+ if (s < 0) {
+ perror("cannot create socket");
+ return -1;
+ }
+
+ memset((char *)&local_sock, 0, sizeof(local_sock));
+
+ switch (family) {
+ case AF_INET:
+ in = (struct sockaddr_in *)&local_sock;
+ in->sin_family = family;
+ in->sin_port = htons(ctx->lport);
+ in->sin_addr.s_addr = htonl(INADDR_ANY);
+ sock_len = sizeof(*in);
+ break;
+ case AF_INET6:
+ in6 = (struct sockaddr_in6 *)&local_sock;
+ in6->sin6_family = family;
+ in6->sin6_port = htons(ctx->lport);
+ in6->sin6_addr = in6addr_any;
+ sock_len = sizeof(*in6);
+ break;
+ default:
+ return -1;
+ }
+
+ int opt = 1;
+
+ ret = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+ if (ret < 0) {
+ perror("setsockopt for SO_REUSEADDR");
+ return ret;
+ }
+
+ ret = setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
+ if (ret < 0) {
+ perror("setsockopt for SO_REUSEPORT");
+ return ret;
+ }
+
+ if (family == AF_INET6) {
+ opt = 0;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &opt,
+ sizeof(opt))) {
+ perror("failed to set IPV6_V6ONLY");
+ return -1;
+ }
+ }
+
+ ret = bind(s, (struct sockaddr *)&local_sock, sock_len);
+ if (ret < 0) {
+ perror("cannot bind socket");
+ goto err_socket;
+ }
+
+ ctx->socket = s;
+ ctx->sa_family = family;
+ return 0;
+
+err_socket:
+ close(s);
+ return -1;
+}
+
+static int ovpn_udp_socket(struct ovpn_ctx *ctx, sa_family_t family)
+{
+ return ovpn_socket(ctx, family, IPPROTO_UDP);
+}
+
+static int ovpn_listen(struct ovpn_ctx *ctx, sa_family_t family)
+{
+ int ret;
+
+ ret = ovpn_socket(ctx, family, IPPROTO_TCP);
+ if (ret < 0)
+ return ret;
+
+ ret = listen(ctx->socket, 10);
+ if (ret < 0) {
+ perror("listen");
+ close(ctx->socket);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ovpn_accept(struct ovpn_ctx *ctx)
+{
+ socklen_t socklen;
+ int ret;
+
+ socklen = sizeof(ctx->remote);
+ ret = accept(ctx->socket, (struct sockaddr *)&ctx->remote, &socklen);
+ if (ret < 0) {
+ perror("accept");
+ goto err;
+ }
+
+ fprintf(stderr, "Connection received!\n");
+
+ switch (socklen) {
+ case sizeof(struct sockaddr_in):
+ case sizeof(struct sockaddr_in6):
+ break;
+ default:
+ fprintf(stderr, "error: expecting IPv4 or IPv6 connection\n");
+ close(ret);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ return ret;
+err:
+ close(ctx->socket);
+ return ret;
+}
+
+static int ovpn_connect(struct ovpn_ctx *ovpn)
+{
+ socklen_t socklen;
+ int s, ret;
+
+ s = socket(ovpn->remote.in4.sin_family, SOCK_STREAM, 0);
+ if (s < 0) {
+ perror("cannot create socket");
+ return -1;
+ }
+
+ switch (ovpn->remote.in4.sin_family) {
+ case AF_INET:
+ socklen = sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ socklen = sizeof(struct sockaddr_in6);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ ret = connect(s, (struct sockaddr *)&ovpn->remote, socklen);
+ if (ret < 0) {
+ perror("connect");
+ goto err;
+ }
+
+ fprintf(stderr, "connected\n");
+
+ ovpn->socket = s;
+
+ return 0;
+err:
+ close(s);
+ return ret;
+}
+
+static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp)
+{
+ struct nlattr *attr;
+ struct nl_ctx *ctx;
+ size_t alen;
+ int ret = -1;
+
+ ctx = nl_ctx_alloc(ovpn, OVPN_CMD_SET_PEER);
+ if (!ctx)
+ return -ENOMEM;
+
+ attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_SOCKET, ovpn->socket);
+
+ if (!is_tcp) {
+ switch (ovpn->remote.in4.sin_family) {
+ case AF_INET:
+ alen = sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ alen = sizeof(struct sockaddr_in6);
+ break;
+ default:
+ fprintf(stderr,
+ "Invalid family for remote socket address\n");
+ goto nla_put_failure;
+ }
+ NLA_PUT(ctx->nl_msg, OVPN_A_PEER_SOCKADDR_REMOTE, alen,
+ &ovpn->remote);
+ }
+
+ switch (ovpn->peer_ip.in4.sin_family) {
+ case AF_INET:
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_VPN_IPV4,
+ ovpn->peer_ip.in4.sin_addr.s_addr);
+ break;
+ case AF_INET6:
+ NLA_PUT(ctx->nl_msg, OVPN_A_PEER_VPN_IPV6,
+ sizeof(struct in6_addr), &ovpn->peer_ip.in6.sin6_addr);
+ break;
+ default:
+ fprintf(stderr, "Invalid family for peer address\n");
+ goto nla_put_failure;
+ }
+
+ nla_nest_end(ctx->nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(ctx, NULL);
+nla_put_failure:
+ nl_ctx_free(ctx);
+ return ret;
+}
+
+static int ovpn_set_peer(struct ovpn_ctx *ovpn)
+{
+ struct nlattr *attr;
+ struct nl_ctx *ctx;
+ int ret = -1;
+
+ ctx = nl_ctx_alloc(ovpn, OVPN_CMD_SET_PEER);
+ if (!ctx)
+ return -ENOMEM;
+
+ attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_INTERVAL,
+ ovpn->keepalive_interval);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_TIMEOUT,
+ ovpn->keepalive_timeout);
+ nla_nest_end(ctx->nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(ctx, NULL);
+nla_put_failure:
+ nl_ctx_free(ctx);
+ return ret;
+}
+
+static int ovpn_del_peer(struct ovpn_ctx *ovpn)
+{
+ struct nlattr *attr;
+ struct nl_ctx *ctx;
+ int ret = -1;
+
+ ctx = nl_ctx_alloc(ovpn, OVPN_CMD_DEL_PEER);
+ if (!ctx)
+ return -ENOMEM;
+
+ attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id);
+ nla_nest_end(ctx->nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(ctx, NULL);
+nla_put_failure:
+ nl_ctx_free(ctx);
+ return ret;
+}
+
+static int ovpn_handle_peer(struct nl_msg *msg, void *arg)
+{
+ struct nlattr *attrs_peer[OVPN_A_PEER_MAX + 1];
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *attrs[OVPN_A_MAX + 1];
+ __u16 port = 0;
+
+ nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL);
+
+ if (!attrs[OVPN_A_PEER]) {
+ fprintf(stderr, "no packet content in netlink message\n");
+ return NL_SKIP;
+ }
+
+ nla_parse(attrs_peer, OVPN_A_PEER_MAX, nla_data(attrs[OVPN_A_PEER]),
+ nla_len(attrs[OVPN_A_PEER]), NULL);
+
+ if (attrs_peer[OVPN_A_PEER_ID])
+ fprintf(stderr, "* Peer %u\n",
+ nla_get_u32(attrs_peer[OVPN_A_PEER_ID]));
+
+ if (attrs_peer[OVPN_A_PEER_VPN_IPV4]) {
+ char buf[INET_ADDRSTRLEN];
+
+ inet_ntop(AF_INET, nla_data(attrs_peer[OVPN_A_PEER_VPN_IPV4]),
+ buf, sizeof(buf));
+ fprintf(stderr, "\tVPN IPv4: %s\n", buf);
+ }
+
+ if (attrs_peer[OVPN_A_PEER_VPN_IPV6]) {
+ char buf[INET6_ADDRSTRLEN];
+
+ inet_ntop(AF_INET6, nla_data(attrs_peer[OVPN_A_PEER_VPN_IPV6]),
+ buf, sizeof(buf));
+ fprintf(stderr, "\tVPN IPv6: %s\n", buf);
+ }
+
+ if (attrs_peer[OVPN_A_PEER_LOCAL_PORT])
+ port = ntohs(nla_get_u16(attrs_peer[OVPN_A_PEER_LOCAL_PORT]));
+
+ if (attrs_peer[OVPN_A_PEER_SOCKADDR_REMOTE]) {
+ struct sockaddr_storage ss;
+ struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&ss;
+ struct sockaddr_in *in = (struct sockaddr_in *)&ss;
+
+ memcpy(&ss, nla_data(attrs_peer[OVPN_A_PEER_SOCKADDR_REMOTE]),
+ nla_len(attrs_peer[OVPN_A_PEER_SOCKADDR_REMOTE]));
+
+ if (in->sin_family == AF_INET) {
+ char buf[INET_ADDRSTRLEN];
+
+ if (attrs_peer[OVPN_A_PEER_LOCAL_IP]) {
+ void *p = attrs_peer[OVPN_A_PEER_LOCAL_IP];
+
+ inet_ntop(AF_INET, nla_data(p), buf,
+ sizeof(buf));
+ fprintf(stderr, "\tLocal: %s:%hu\n", buf,
+ port);
+ }
+
+ inet_ntop(AF_INET, &in->sin_addr, buf, sizeof(buf));
+ fprintf(stderr, "\tRemote: %s:%u\n", buf,
+ ntohs(in->sin_port));
+ } else if (in->sin_family == AF_INET6) {
+ char buf[INET6_ADDRSTRLEN];
+
+ if (attrs_peer[OVPN_A_PEER_LOCAL_IP]) {
+ void *p = attrs_peer[OVPN_A_PEER_LOCAL_IP];
+
+ inet_ntop(AF_INET6, nla_data(p), buf,
+ sizeof(buf));
+ fprintf(stderr, "\tLocal: %s\n", buf);
+ }
+
+ inet_ntop(AF_INET6, &in6->sin6_addr, buf, sizeof(buf));
+ fprintf(stderr, "\tRemote: %s:%u (scope-id: %u)\n", buf,
+ ntohs(in6->sin6_port),
+ ntohl(in6->sin6_scope_id));
+ }
+ }
+
+ if (attrs_peer[OVPN_A_PEER_KEEPALIVE_INTERVAL]) {
+ void *p = attrs_peer[OVPN_A_PEER_KEEPALIVE_INTERVAL];
+
+ fprintf(stderr, "\tKeepalive interval: %u sec\n",
+ nla_get_u32(p));
+ }
+
+ if (attrs_peer[OVPN_A_PEER_KEEPALIVE_TIMEOUT])
+ fprintf(stderr, "\tKeepalive timeout: %u sec\n",
+ nla_get_u32(attrs_peer[OVPN_A_PEER_KEEPALIVE_TIMEOUT]));
+
+ if (attrs_peer[OVPN_A_PEER_VPN_RX_BYTES])
+ fprintf(stderr, "\tVPN RX bytes: %" PRIu64 "\n",
+ nla_get_uint(attrs_peer[OVPN_A_PEER_VPN_RX_BYTES]));
+
+ if (attrs_peer[OVPN_A_PEER_VPN_TX_BYTES])
+ fprintf(stderr, "\tVPN TX bytes: %" PRIu64 "\n",
+ nla_get_uint(attrs_peer[OVPN_A_PEER_VPN_TX_BYTES]));
+
+ if (attrs_peer[OVPN_A_PEER_VPN_RX_PACKETS])
+ fprintf(stderr, "\tVPN RX packets: %" PRIu64 "\n",
+ nla_get_uint(attrs_peer[OVPN_A_PEER_VPN_RX_PACKETS]));
+
+ if (attrs_peer[OVPN_A_PEER_VPN_TX_PACKETS])
+ fprintf(stderr, "\tVPN TX packets: %" PRIu64 "\n",
+ nla_get_uint(attrs_peer[OVPN_A_PEER_VPN_TX_PACKETS]));
+
+ if (attrs_peer[OVPN_A_PEER_LINK_RX_BYTES])
+ fprintf(stderr, "\tLINK RX bytes: %" PRIu64 "\n",
+ nla_get_uint(attrs_peer[OVPN_A_PEER_LINK_RX_BYTES]));
+
+ if (attrs_peer[OVPN_A_PEER_LINK_TX_BYTES])
+ fprintf(stderr, "\tLINK TX bytes: %" PRIu64 "\n",
+ nla_get_uint(attrs_peer[OVPN_A_PEER_LINK_TX_BYTES]));
+
+ if (attrs_peer[OVPN_A_PEER_LINK_RX_PACKETS])
+ fprintf(stderr, "\tLINK RX packets: %" PRIu64 "\n",
+ nla_get_uint(attrs_peer[OVPN_A_PEER_LINK_RX_PACKETS]));
+
+ if (attrs_peer[OVPN_A_PEER_LINK_TX_PACKETS])
+ fprintf(stderr, "\tLINK TX packets: %" PRIu64 "\n",
+ nla_get_uint(attrs_peer[OVPN_A_PEER_LINK_TX_PACKETS]));
+
+ return NL_SKIP;
+}
+
+static int ovpn_get_peer(struct ovpn_ctx *ovpn)
+{
+ int flags = 0, ret = -1;
+ struct nlattr *attr;
+ struct nl_ctx *ctx;
+
+ if (ovpn->peer_id == PEER_ID_UNDEF)
+ flags = NLM_F_DUMP;
+
+ ctx = nl_ctx_alloc_flags(ovpn, OVPN_CMD_GET_PEER, flags);
+ if (!ctx)
+ return -ENOMEM;
+
+ if (ovpn->peer_id != PEER_ID_UNDEF) {
+ attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id);
+ nla_nest_end(ctx->nl_msg, attr);
+ }
+
+ ret = ovpn_nl_msg_send(ctx, ovpn_handle_peer);
+nla_put_failure:
+ nl_ctx_free(ctx);
+ return ret;
+}
+
+static int ovpn_new_key(struct ovpn_ctx *ovpn)
+{
+ struct nlattr *peer, *keyconf, *key_dir;
+ struct nl_ctx *ctx;
+ int ret = -1;
+
+ ctx = nl_ctx_alloc(ovpn, OVPN_CMD_SET_KEY);
+ if (!ctx)
+ return -ENOMEM;
+
+ peer = nla_nest_start(ctx->nl_msg, OVPN_A_PEER);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id);
+
+ keyconf = nla_nest_start(ctx->nl_msg, OVPN_A_PEER_KEYCONF);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_SLOT, ovpn->key_slot);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_KEY_ID, ovpn->key_id);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_CIPHER_ALG, ovpn->cipher);
+
+ key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_ENCRYPT_DIR);
+ NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_enc);
+ NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce);
+ nla_nest_end(ctx->nl_msg, key_dir);
+
+ key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_DECRYPT_DIR);
+ NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_dec);
+ NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce);
+ nla_nest_end(ctx->nl_msg, key_dir);
+
+ nla_nest_end(ctx->nl_msg, keyconf);
+
+ nla_nest_end(ctx->nl_msg, peer);
+
+ ret = ovpn_nl_msg_send(ctx, NULL);
+nla_put_failure:
+ nl_ctx_free(ctx);
+ return ret;
+}
+
+static int ovpn_del_key(struct ovpn_ctx *ovpn)
+{
+ struct nlattr *peer, *keyconf;
+ struct nl_ctx *ctx;
+ int ret = -1;
+
+ ctx = nl_ctx_alloc(ovpn, OVPN_CMD_DEL_KEY);
+ if (!ctx)
+ return -ENOMEM;
+
+ peer = nla_nest_start(ctx->nl_msg, OVPN_A_PEER);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id);
+
+ keyconf = nla_nest_start(ctx->nl_msg, OVPN_A_PEER_KEYCONF);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_SLOT, OVPN_KEY_SLOT_PRIMARY);
+ nla_nest_end(ctx->nl_msg, keyconf);
+
+ nla_nest_end(ctx->nl_msg, peer);
+
+ ret = ovpn_nl_msg_send(ctx, NULL);
+nla_put_failure:
+ nl_ctx_free(ctx);
+ return ret;
+}
+
+static int ovpn_swap_keys(struct ovpn_ctx *ovpn)
+{
+ struct nlattr *peer;
+ struct nl_ctx *ctx;
+ int ret = -1;
+
+ ctx = nl_ctx_alloc(ovpn, OVPN_CMD_SWAP_KEYS);
+ if (!ctx)
+ return -ENOMEM;
+
+ peer = nla_nest_start(ctx->nl_msg, OVPN_A_PEER);
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id);
+ nla_nest_end(ctx->nl_msg, peer);
+
+ ret = ovpn_nl_msg_send(ctx, NULL);
+nla_put_failure:
+ nl_ctx_free(ctx);
+ return ret;
+}
+
+static int ovpn_handle_iface(struct nl_msg *msg, void *arg)
+{
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *attrs[OVPN_A_MAX + 1];
+
+ nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL);
+
+ if (!attrs[OVPN_A_IFNAME]) {
+ fprintf(stderr, "no ifname in netlink message\n");
+ return NL_SKIP;
+ }
+
+ fprintf(stderr, "Created ifname: %s\n",
+ (char *)nla_data(attrs[OVPN_A_IFNAME]));
+
+ return NL_SKIP;
+}
+
+static int ovpn_new_iface(struct ovpn_ctx *ovpn)
+{
+ struct nl_ctx *ctx;
+ int ret = -1;
+
+ ctx = nl_ctx_alloc(ovpn, OVPN_CMD_NEW_IFACE);
+ if (!ctx)
+ return -ENOMEM;
+
+ NLA_PUT(ctx->nl_msg, OVPN_A_IFNAME, strlen(ovpn->ifname) + 1,
+ ovpn->ifname);
+
+ if (ovpn->mode_set)
+ NLA_PUT_U32(ctx->nl_msg, OVPN_A_MODE, ovpn->mode);
+
+ fprintf(stdout, "Creating interface %s with mode %u\n", ovpn->ifname,
+ ovpn->mode);
+
+ ret = ovpn_nl_msg_send(ctx, ovpn_handle_iface);
+nla_put_failure:
+ nl_ctx_free(ctx);
+ return ret;
+}
+
+static int ovpn_del_iface(struct ovpn_ctx *ovpn)
+{
+ struct nl_ctx *ctx;
+ int ret = -1;
+
+ ctx = nl_ctx_alloc(ovpn, OVPN_CMD_DEL_IFACE);
+ if (!ctx)
+ return -ENOMEM;
+
+ ret = ovpn_nl_msg_send(ctx, NULL);
+ nl_ctx_free(ctx);
+ return ret;
+}
+
+static int nl_seq_check(struct nl_msg *msg, void *arg)
+{
+ return NL_OK;
+}
+
+struct mcast_handler_args {
+ const char *group;
+ int id;
+};
+
+static int mcast_family_handler(struct nl_msg *msg, void *arg)
+{
+ struct mcast_handler_args *grp = arg;
+ struct nlattr *tb[CTRL_ATTR_MAX + 1];
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *mcgrp;
+ int rem_mcgrp;
+
+ nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL);
+
+ if (!tb[CTRL_ATTR_MCAST_GROUPS])
+ return NL_SKIP;
+
+ nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) {
+ struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
+
+ nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX,
+ nla_data(mcgrp), nla_len(mcgrp), NULL);
+
+ if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] ||
+ !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
+ continue;
+ if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]),
+ grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])))
+ continue;
+ grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
+ break;
+ }
+
+ return NL_SKIP;
+}
+
+static int mcast_error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
+ void *arg)
+{
+ int *ret = arg;
+
+ *ret = err->error;
+ return NL_STOP;
+}
+
+static int mcast_ack_handler(struct nl_msg *msg, void *arg)
+{
+ int *ret = arg;
+
+ *ret = 0;
+ return NL_STOP;
+}
+
+static int ovpn_handle_msg(struct nl_msg *msg, void *arg)
+{
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *attrs[OVPN_A_MAX + 1];
+ struct nlmsghdr *nlh = nlmsg_hdr(msg);
+ //enum ovpn_del_peer_reason reason;
+ char ifname[IF_NAMESIZE];
+ __u32 ifindex;
+
+ fprintf(stderr, "received message from ovpn-dco\n");
+
+ if (!genlmsg_valid_hdr(nlh, 0)) {
+ fprintf(stderr, "invalid header\n");
+ return NL_STOP;
+ }
+
+ if (nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL)) {
+ fprintf(stderr, "received bogus data from ovpn-dco\n");
+ return NL_STOP;
+ }
+
+ if (!attrs[OVPN_A_IFINDEX]) {
+ fprintf(stderr, "no ifindex in this message\n");
+ return NL_STOP;
+ }
+
+ ifindex = nla_get_u32(attrs[OVPN_A_IFINDEX]);
+ if (!if_indextoname(ifindex, ifname)) {
+ fprintf(stderr, "cannot resolve ifname for ifindex: %u\n",
+ ifindex);
+ return NL_STOP;
+ }
+
+ switch (gnlh->cmd) {
+ case OVPN_CMD_DEL_PEER:
+ /*if (!attrs[OVPN_A_DEL_PEER_REASON]) {
+ * fprintf(stderr, "no reason in DEL_PEER message\n");
+ * return NL_STOP;
+ *}
+ *
+ *reason = nla_get_u8(attrs[OVPN_A_DEL_PEER_REASON]);
+ *fprintf(stderr,
+ * "received CMD_DEL_PEER, ifname: %s reason: %d\n",
+ * ifname, reason);
+ */
+ fprintf(stdout, "received CMD_DEL_PEER\n");
+ break;
+ default:
+ fprintf(stderr, "received unknown command: %d\n", gnlh->cmd);
+ return NL_STOP;
+ }
+
+ return NL_OK;
+}
+
+static int ovpn_get_mcast_id(struct nl_sock *sock, const char *family,
+ const char *group)
+{
+ struct nl_msg *msg;
+ struct nl_cb *cb;
+ int ret, ctrlid;
+ struct mcast_handler_args grp = {
+ .group = group,
+ .id = -ENOENT,
+ };
+
+ msg = nlmsg_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ cb = nl_cb_alloc(NL_CB_DEFAULT);
+ if (!cb) {
+ ret = -ENOMEM;
+ goto out_fail_cb;
+ }
+
+ ctrlid = genl_ctrl_resolve(sock, "nlctrl");
+
+ genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
+
+ ret = -ENOBUFS;
+ NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family);
+
+ ret = nl_send_auto_complete(sock, msg);
+ if (ret < 0)
+ goto nla_put_failure;
+
+ ret = 1;
+
+ nl_cb_err(cb, NL_CB_CUSTOM, mcast_error_handler, &ret);
+ nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, mcast_ack_handler, &ret);
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, mcast_family_handler, &grp);
+
+ while (ret > 0)
+ nl_recvmsgs(sock, cb);
+
+ if (ret == 0)
+ ret = grp.id;
+ nla_put_failure:
+ nl_cb_put(cb);
+ out_fail_cb:
+ nlmsg_free(msg);
+ return ret;
+}
+
+static void ovpn_listen_mcast(void)
+{
+ struct nl_sock *sock;
+ struct nl_cb *cb;
+ int mcid, ret;
+
+ sock = nl_socket_alloc();
+ if (!sock) {
+ fprintf(stderr, "cannot allocate netlink socket\n");
+ goto err_free;
+ }
+
+ nl_socket_set_buffer_size(sock, 8192, 8192);
+
+ ret = genl_connect(sock);
+ if (ret < 0) {
+ fprintf(stderr, "cannot connect to generic netlink: %s\n",
+ nl_geterror(ret));
+ goto err_free;
+ }
+
+ mcid = ovpn_get_mcast_id(sock, OVPN_FAMILY_NAME, OVPN_MCGRP_PEERS);
+ if (mcid < 0) {
+ fprintf(stderr, "cannot get mcast group: %s\n",
+ nl_geterror(mcid));
+ goto err_free;
+ }
+
+ ret = nl_socket_add_membership(sock, mcid);
+ if (ret) {
+ fprintf(stderr, "failed to join mcast group: %d\n", ret);
+ goto err_free;
+ }
+
+ ret = 0;
+ cb = nl_cb_alloc(NL_CB_DEFAULT);
+ nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nl_seq_check, NULL);
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, ovpn_handle_msg, &ret);
+ nl_cb_err(cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &ret);
+
+ while (ret != -EINTR)
+ ret = nl_recvmsgs(sock, cb);
+
+ nl_cb_put(cb);
+err_free:
+ nl_socket_free(sock);
+}
+
+static void usage(const char *cmd)
+{
+ fprintf(stderr, "Error: invalid arguments.\n\n");
+ fprintf(stderr,
+ "Usage %s <iface> <connect|listen|new_peer|new_multi_peer|set_peer|del_peer|new_key|del_key|recv|send|listen_mcast> [arguments..]\n",
+ cmd);
+ fprintf(stderr, "\tiface: tun interface name\n\n");
+
+ fprintf(stderr,
+ "* connect <peer_id> <raddr> <rport> <vpnaddr>: start connecting peer of TCP-based VPN session\n");
+ fprintf(stderr, "\tpeer-id: peer ID of the connecting peer\n");
+ fprintf(stderr, "\tremote-addr: peer IP address\n");
+ fprintf(stderr, "\tremote-port: peer TCP port\n");
+ fprintf(stderr, "\tvpn-ip: peer VPN IP\n\n");
+
+ fprintf(stderr,
+ "* listen <lport> <peers_file>: listen for incoming peer TCP connections\n");
+ fprintf(stderr, "\tlport: src TCP port\n");
+ fprintf(stderr,
+ "\tpeers_file: file containing one peer per line: Line format:\n");
+ fprintf(stderr, "\t\t<peer_id> <vpnaddr>\n\n");
+
+ fprintf(stderr,
+ "* new_peer <lport> <peer-id> <raddr> <rport> <vpnaddr>: add new peer\n");
+ fprintf(stderr,
+ "\tpeer-id: peer ID to be used in data packets to/from this peer\n");
+ fprintf(stderr, "\tlocal-port: local UDP port\n");
+ fprintf(stderr, "\tremote-addr: peer IP address\n");
+ fprintf(stderr, "\tremote-port: peer UDP port\n");
+ fprintf(stderr, "\tvpnaddr: peer VPN IP\n\n");
+
+ fprintf(stderr,
+ "* new_multi_peer <lport> <file>: add multiple peers as listed in the file\n");
+ fprintf(stderr, "\tlport: local UDP port to bind to\n");
+ fprintf(stderr,
+ "\tfile: text file containing one peer per line. Line format:\n");
+ fprintf(stderr, "\t\t<peer-id> <raddr> <rport> <vpnaddr>\n\n");
+
+ fprintf(stderr,
+ "* set_peer <peer-id> <keepalive_interval> <keepalive_timeout>: set peer attributes\n");
+ fprintf(stderr, "\tpeer-id: peer ID of the peer to modify\n");
+ fprintf(stderr,
+ "\tkeepalive_interval: interval for sending ping messages\n");
+ fprintf(stderr,
+ "\tkeepalive_timeout: time after which a peer is timed out\n\n");
+
+ fprintf(stderr, "* del_peer <peer-id>: delete peer\n");
+ fprintf(stderr, "\tpeer-id: peer ID of the peer to delete\n\n");
+
+ fprintf(stderr,
+ "* new_key <peer-id> <slot> <key_id> <cipher> <key_dir> <key_file>: set data channel key\n");
+ fprintf(stderr,
+ "\tpeer-id: peer ID of the peer to configure the key for\n");
+ fprintf(stderr,
+ "\tcipher: cipher to use, supported: aes (AES-GCM), chachapoly (CHACHA20POLY1305), none\n");
+ fprintf(stderr,
+ "\tkey_dir: key direction, must 0 on one host and 1 on the other\n");
+ fprintf(stderr, "\tkey_file: file containing the pre-shared key\n\n");
+
+ fprintf(stderr,
+ "* del_key <peer-id>: erase existing data channel key\n");
+ fprintf(stderr, "\tpeer-id: peer ID of the peer to modify\n\n");
+
+ fprintf(stderr,
+ "* swap_keys <peer-id>: swap primary and seconday key slots\n");
+ fprintf(stderr, "\tpeer-id: peer ID of the peer to modify\n\n");
+
+ fprintf(stderr,
+ "* listen_mcast: listen to ovpn-dco netlink multicast messages\n");
+}
+
+static int ovpn_parse_remote(struct ovpn_ctx *ovpn, const char *host,
+ const char *service, const char *vpn_addr)
+{
+ int ret;
+ struct addrinfo *result;
+ struct addrinfo hints = {
+ .ai_family = ovpn->sa_family,
+ .ai_socktype = SOCK_DGRAM,
+ .ai_protocol = IPPROTO_UDP
+ };
+
+ if (host) {
+ ret = getaddrinfo(host, service, &hints, &result);
+ if (ret == EAI_NONAME || ret == EAI_FAIL)
+ return -1;
+
+ if (!(result->ai_family == AF_INET &&
+ result->ai_addrlen == sizeof(struct sockaddr_in)) &&
+ !(result->ai_family == AF_INET6 &&
+ result->ai_addrlen == sizeof(struct sockaddr_in6))) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ memcpy(&ovpn->remote, result->ai_addr, result->ai_addrlen);
+ }
+
+ ret = getaddrinfo(vpn_addr, NULL, &hints, &result);
+ if (ret == EAI_NONAME || ret == EAI_FAIL)
+ return -1;
+
+ if (!(result->ai_family == AF_INET &&
+ result->ai_addrlen == sizeof(struct sockaddr_in)) &&
+ !(result->ai_family == AF_INET6 &&
+ result->ai_addrlen == sizeof(struct sockaddr_in6))) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ memcpy(&ovpn->peer_ip, result->ai_addr, result->ai_addrlen);
+ ovpn->sa_family = result->ai_family;
+
+ ret = 0;
+out:
+ freeaddrinfo(result);
+ return ret;
+}
+
+static int ovpn_parse_new_peer(struct ovpn_ctx *ovpn, const char *peer_id,
+ const char *raddr, const char *rport,
+ const char *vpnip)
+{
+ ovpn->peer_id = strtoul(peer_id, NULL, 10);
+ if (errno == ERANGE) {
+ fprintf(stderr, "peer ID value out of range\n");
+ return -1;
+ }
+
+ return ovpn_parse_remote(ovpn, raddr, rport, vpnip);
+}
+
+static void ovpn_send_tcp_data(int socket)
+{
+ uint16_t len = htons(1000);
+ uint8_t buf[1002];
+ int ret;
+
+ memcpy(buf, &len, sizeof(len));
+ memset(buf + sizeof(len), 0x86, sizeof(buf) - sizeof(len));
+
+ ret = send(socket, buf, sizeof(buf), 0);
+
+ fprintf(stdout, "Sent %u bytes over TCP socket\n", ret);
+}
+
+static void ovpn_recv_tcp_data(int socket)
+{
+ uint8_t buf[1002];
+ uint16_t len;
+ int ret;
+
+ ret = recv(socket, buf, sizeof(buf), 0);
+
+ if (ret < 2) {
+ fprintf(stderr, ">>>> Error while reading TCP data: %d\n", ret);
+ return;
+ }
+
+ memcpy(&len, buf, sizeof(len));
+ len = ntohs(len);
+
+ fprintf(stdout, ">>>> Received %u bytes over TCP socket, header: %u\n",
+ ret, len);
+
+/* int i;
+ * for (i = 2; i < ret; i++) {
+ * fprintf(stdout, "0x%.2x ", buf[i]);
+ * if (i && !((i - 2) % 16))
+ * fprintf(stdout, "\n");
+ * }
+ * fprintf(stdout, "\n");
+ */
+}
+
+static int ovpn_parse_set_peer(struct ovpn_ctx *ovpn, int argc, char *argv[])
+{
+ if (argc < 5) {
+ usage(argv[0]);
+ return -1;
+ }
+
+ ovpn->keepalive_interval = strtoul(argv[3], NULL, 10);
+ if (errno == ERANGE) {
+ fprintf(stderr, "keepalive interval value out of range\n");
+ return -1;
+ }
+
+ ovpn->keepalive_timeout = strtoul(argv[4], NULL, 10);
+ if (errno == ERANGE) {
+ fprintf(stderr, "keepalive interval value out of range\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct ovpn_ctx ovpn;
+// struct nl_ctx *ctx;
+ int ret;
+
+ if (argc < 2) {
+ usage(argv[0]);
+ return -1;
+ }
+
+ memset(&ovpn, 0, sizeof(ovpn));
+ ovpn.sa_family = AF_INET;
+ ovpn.cli_socket = -1;
+
+ if (argc > 2) {
+ strscpy(ovpn.ifname, argv[2], IFNAMSIZ - 1);
+ ovpn.ifname[IFNAMSIZ - 1] = '\0';
+ }
+
+ /* all commands except new_iface expect a valid ifindex */
+ if (strcmp(argv[1], "new_iface")) {
+ /* in this case a ifname MUST be defined */
+ if (argc < 3) {
+ usage(argv[0]);
+ return -1;
+ }
+
+ ovpn.ifindex = if_nametoindex(ovpn.ifname);
+ if (!ovpn.ifindex) {
+ fprintf(stderr, "cannot find interface: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ }
+
+ if (!strcmp(argv[1], "new_iface")) {
+ if (argc > 3) {
+ if (!strcmp(argv[3], "P2P")) {
+ ovpn.mode = OVPN_MODE_P2P;
+ } else if (!strcmp(argv[3], "MP")) {
+ ovpn.mode = OVPN_MODE_MP;
+ } else {
+ fprintf(stderr, "Cannot parse iface mode: %s\n",
+ argv[3]);
+ return -1;
+ }
+ ovpn.mode_set = true;
+ }
+
+ ret = ovpn_new_iface(&ovpn);
+ if (ret < 0) {
+ fprintf(stderr, "Cannot create interface %s: %d\n",
+ ovpn.ifname, ret);
+ return -1;
+ }
+ } else if (!strcmp(argv[1], "del_iface")) {
+ ret = ovpn_del_iface(&ovpn);
+ if (ret < 0) {
+ fprintf(stderr, "Cannot delete interface %s: %d\n",
+ ovpn.ifname, ret);
+ return -1;
+ }
+ } else if (!strcmp(argv[1], "listen")) {
+ char peer_id[10], vpnip[100];
+ int n;
+ FILE *fp;
+
+ if (argc < 4) {
+ usage(argv[0]);
+ return -1;
+ }
+
+ ovpn.lport = strtoul(argv[3], NULL, 10);
+ if (errno == ERANGE || ovpn.lport > 65535) {
+ fprintf(stderr, "lport value out of range\n");
+ return -1;
+ }
+
+ if (argc > 4 && !strcmp(argv[4], "ipv6"))
+ ovpn.sa_family = AF_INET6;
+
+ ret = ovpn_listen(&ovpn, ovpn.sa_family);
+ if (ret < 0) {
+ fprintf(stderr, "cannot listen on TCP socket\n");
+ return ret;
+ }
+
+ fp = fopen(argv[4], "r");
+ if (!fp) {
+ fprintf(stderr, "cannot open file: %s\n", argv[4]);
+ return -1;
+ }
+
+ while ((n = fscanf(fp, "%s %s\n", peer_id, vpnip)) == 2) {
+ struct ovpn_ctx peer_ctx = { 0 };
+
+ peer_ctx.ifindex = ovpn.ifindex;
+ peer_ctx.sa_family = ovpn.sa_family;
+
+ peer_ctx.socket = ovpn_accept(&ovpn);
+ if (peer_ctx.socket < 0) {
+ fprintf(stderr, "cannot accept connection!\n");
+ return -1;
+ }
+
+ /* store the socket of the first peer to test TCP I/O */
+ if (ovpn.cli_socket < 0)
+ ovpn.cli_socket = peer_ctx.socket;
+
+ ret = ovpn_parse_new_peer(&peer_ctx, peer_id, NULL,
+ NULL, vpnip);
+ if (ret < 0) {
+ fprintf(stderr, "error while parsing line\n");
+ return -1;
+ }
+
+ ret = ovpn_new_peer(&peer_ctx, true);
+ if (ret < 0) {
+ fprintf(stderr,
+ "cannot add peer to VPN: %s %s\n",
+ peer_id, vpnip);
+ return ret;
+ }
+ }
+
+ if (ovpn.cli_socket >= 0)
+ ovpn_recv_tcp_data(ovpn.cli_socket);
+ } else if (!strcmp(argv[1], "connect")) {
+ if (argc < 6) {
+ usage(argv[0]);
+ return -1;
+ }
+
+ ovpn.sa_family = AF_INET;
+
+ ret = ovpn_parse_new_peer(&ovpn, argv[3], argv[4], argv[5],
+ argv[6]);
+ if (ret < 0) {
+ fprintf(stderr, "Cannot parse remote peer data\n");
+ return ret;
+ }
+
+ ret = ovpn_connect(&ovpn);
+ if (ret < 0) {
+ fprintf(stderr, "cannot connect TCP socket\n");
+ return ret;
+ }
+
+ ret = ovpn_new_peer(&ovpn, true);
+ if (ret < 0) {
+ fprintf(stderr, "cannot add peer to VPN\n");
+ close(ovpn.socket);
+ return ret;
+ }
+
+ if (argc > 6) {
+ ovpn.key_slot = OVPN_KEY_SLOT_PRIMARY;
+ ovpn.key_id = 0;
+ ovpn.cipher = OVPN_CIPHER_ALG_AES_GCM;
+ ovpn.key_dir = KEY_DIR_OUT;
+
+ ret = ovpn_read_key(argv[7], &ovpn);
+ if (ret)
+ return ret;
+
+ ret = ovpn_new_key(&ovpn);
+ if (ret < 0) {
+ fprintf(stderr, "cannot set key\n");
+ return ret;
+ }
+
+ ovpn_send_tcp_data(ovpn.socket);
+ }
+ } else if (!strcmp(argv[1], "new_peer")) {
+ if (argc < 8) {
+ usage(argv[0]);
+ return -1;
+ }
+
+ ovpn.lport = strtoul(argv[3], NULL, 10);
+ if (errno == ERANGE || ovpn.lport > 65535) {
+ fprintf(stderr, "lport value out of range\n");
+ return -1;
+ }
+
+ ret = ovpn_parse_new_peer(&ovpn, argv[4], argv[5], argv[6],
+ argv[7]);
+ if (ret < 0)
+ return ret;
+
+ ret = ovpn_udp_socket(&ovpn, AF_INET6); //ovpn.sa_family ?
+ if (ret < 0)
+ return ret;
+
+ ret = ovpn_new_peer(&ovpn, false);
+ if (ret < 0) {
+ fprintf(stderr, "cannot add peer to VPN\n");
+ return ret;
+ }
+ } else if (!strcmp(argv[1], "new_multi_peer")) {
+ char peer_id[10], raddr[128], rport[10], vpnip[100];
+ FILE *fp;
+ int n;
+
+ if (argc < 5) {
+ usage(argv[0]);
+ return -1;
+ }
+
+ ovpn.lport = strtoul(argv[3], NULL, 10);
+ if (errno == ERANGE || ovpn.lport > 65535) {
+ fprintf(stderr, "lport value out of range\n");
+ return -1;
+ }
+
+ fp = fopen(argv[4], "r");
+ if (!fp) {
+ fprintf(stderr, "cannot open file: %s\n", argv[4]);
+ return -1;
+ }
+
+ ret = ovpn_udp_socket(&ovpn, AF_INET6);
+ if (ret < 0)
+ return ret;
+
+ while ((n = fscanf(fp, "%s %s %s %s\n", peer_id, raddr, rport,
+ vpnip)) == 4) {
+ struct ovpn_ctx peer_ctx = { 0 };
+
+ peer_ctx.ifindex = ovpn.ifindex;
+ peer_ctx.socket = ovpn.socket;
+ peer_ctx.sa_family = AF_UNSPEC;
+
+ ret = ovpn_parse_new_peer(&peer_ctx, peer_id, raddr,
+ rport, vpnip);
+ if (ret < 0) {
+ fprintf(stderr, "error while parsing line\n");
+ return -1;
+ }
+
+ ret = ovpn_new_peer(&peer_ctx, false);
+ if (ret < 0) {
+ fprintf(stderr,
+ "cannot add peer to VPN: %s %s %s %s\n",
+ peer_id, raddr, rport, vpnip);
+ return ret;
+ }
+ }
+ } else if (!strcmp(argv[1], "set_peer")) {
+ ovpn.peer_id = strtoul(argv[3], NULL, 10);
+ if (errno == ERANGE) {
+ fprintf(stderr, "peer ID value out of range\n");
+ return -1;
+ }
+
+ argv++;
+ argc--;
+
+ ret = ovpn_parse_set_peer(&ovpn, argc, argv);
+ if (ret < 0)
+ return ret;
+
+ ret = ovpn_set_peer(&ovpn);
+ if (ret < 0) {
+ fprintf(stderr, "cannot set peer to VPN\n");
+ return ret;
+ }
+ } else if (!strcmp(argv[1], "del_peer")) {
+ if (argc < 4) {
+ usage(argv[0]);
+ return -1;
+ }
+
+ ovpn.peer_id = strtoul(argv[3], NULL, 10);
+ if (errno == ERANGE) {
+ fprintf(stderr, "peer ID value out of range\n");
+ return -1;
+ }
+
+ ret = ovpn_del_peer(&ovpn);
+ if (ret < 0) {
+ fprintf(stderr, "cannot delete peer to VPN\n");
+ return ret;
+ }
+ } else if (!strcmp(argv[1], "get_peer")) {
+ ovpn.peer_id = PEER_ID_UNDEF;
+ if (argc > 3)
+ ovpn.peer_id = strtoul(argv[3], NULL, 10);
+
+ fprintf(stderr, "List of peers connected to: %s\n",
+ ovpn.ifname);
+
+ ret = ovpn_get_peer(&ovpn);
+ if (ret < 0) {
+ fprintf(stderr, "cannot get peer(s): %d\n", ret);
+ return ret;
+ }
+ } else if (!strcmp(argv[1], "new_key")) {
+ if (argc < 8) {
+ usage(argv[0]);
+ return -1;
+ }
+
+ ovpn.peer_id = strtoul(argv[3], NULL, 10);
+ if (errno == ERANGE) {
+ fprintf(stderr, "peer ID value out of range\n");
+ return -1;
+ }
+
+ int slot = strtoul(argv[4], NULL, 10);
+
+ if (errno == ERANGE || slot < 1 || slot > 2) {
+ fprintf(stderr, "ket slot out of range\n");
+ return -1;
+ }
+
+ switch (slot) {
+ case 1:
+ ovpn.key_slot = OVPN_KEY_SLOT_PRIMARY;
+ break;
+ case 2:
+ ovpn.key_slot = OVPN_KEY_SLOT_SECONDARY;
+ break;
+ }
+
+ ovpn.key_id = strtoul(argv[5], NULL, 10);
+ if (errno == ERANGE || ovpn.key_id > 2) {
+ fprintf(stderr, "ket ID out of range\n");
+ return -1;
+ }
+
+ ret = ovpn_read_cipher(argv[6], &ovpn);
+ if (ret < 0)
+ return ret;
+
+ ret = ovpn_read_key_direction(argv[7], &ovpn);
+ if (ret < 0)
+ return ret;
+
+ ret = ovpn_read_key(argv[8], &ovpn);
+ if (ret)
+ return ret;
+
+ ret = ovpn_new_key(&ovpn);
+ if (ret < 0) {
+ fprintf(stderr, "cannot set key\n");
+ return ret;
+ }
+ } else if (!strcmp(argv[1], "del_key")) {
+ if (argc < 3) {
+ usage(argv[0]);
+ return -1;
+ }
+
+ ovpn.peer_id = strtoul(argv[3], NULL, 10);
+ if (errno == ERANGE) {
+ fprintf(stderr, "peer ID value out of range\n");
+ return -1;
+ }
+
+ argv++;
+ argc--;
+
+ ret = ovpn_del_key(&ovpn);
+ if (ret < 0) {
+ fprintf(stderr, "cannot delete key\n");
+ return ret;
+ }
+ } else if (!strcmp(argv[1], "swap_keys")) {
+ if (argc < 3) {
+ usage(argv[0]);
+ return -1;
+ }
+
+ ovpn.peer_id = strtoul(argv[3], NULL, 10);
+ if (errno == ERANGE) {
+ fprintf(stderr, "peer ID value out of range\n");
+ return -1;
+ }
+
+ argv++;
+ argc--;
+
+ ret = ovpn_swap_keys(&ovpn);
+ if (ret < 0) {
+ fprintf(stderr, "cannot swap keys\n");
+ return ret;
+ }
+ } else if (!strcmp(argv[1], "listen_mcast")) {
+ ovpn_listen_mcast();
+ } else {
+ usage(argv[0]);
+ return -1;
+ }
+
+ return ret;
+}
diff --git a/tools/testing/selftests/net/ovpn/tcp_peers.txt b/tools/testing/selftests/net/ovpn/tcp_peers.txt
new file mode 100644
index 000000000000..3b7f68bb7f64
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/tcp_peers.txt
@@ -0,0 +1 @@
+1 5.5.5.2
diff --git a/tools/testing/selftests/net/ovpn/udp_peers.txt b/tools/testing/selftests/net/ovpn/udp_peers.txt
new file mode 100644
index 000000000000..32f14bd9347a
--- /dev/null
+++ b/tools/testing/selftests/net/ovpn/udp_peers.txt
@@ -0,0 +1,5 @@
+1 10.10.1.2 1 5.5.5.2
+2 10.10.2.2 1 5.5.5.3
+3 10.10.3.2 1 5.5.5.4
+4 10.10.4.2 1 5.5.5.5
+5 10.10.5.2 1 5.5.5.6
--
2.44.2
^ permalink raw reply related [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 24/25] ovpn: add basic ethtool support
2024-06-27 13:08 ` [PATCH net-next v5 24/25] ovpn: add basic ethtool support Antonio Quartulli
@ 2024-06-27 16:25 ` Andrew Lunn
0 siblings, 0 replies; 71+ messages in thread
From: Andrew Lunn @ 2024-06-27 16:25 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, sd
On Thu, Jun 27, 2024 at 03:08:42PM +0200, Antonio Quartulli wrote:
> Implement support for basic ethtool functionality.
>
> Note that ovpn is a virtual device driver, therefore
> various ethtool APIs are just not meaningful and thus
> not implemented.
>
> Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Andrew
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 07/25] ovpn: keep carrier always on
2024-06-27 13:08 ` [PATCH net-next v5 07/25] ovpn: keep carrier always on Antonio Quartulli
@ 2024-06-27 16:25 ` Andrew Lunn
0 siblings, 0 replies; 71+ messages in thread
From: Andrew Lunn @ 2024-06-27 16:25 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, sd
On Thu, Jun 27, 2024 at 03:08:25PM +0200, Antonio Quartulli wrote:
> An ovpn interface will keep carrier always on and let the user
> decide when an interface should be considered disconnected.
>
> This way, even if an ovpn interface is not connected to any peer,
> it can still retain all IPs and routes and thus prevent any data
> leak.
>
> Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Andrew
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 05/25] ovpn: add basic interface creation/destruction/management routines
2024-06-27 13:08 ` [PATCH net-next v5 05/25] ovpn: add basic interface creation/destruction/management routines Antonio Quartulli
@ 2024-06-28 22:11 ` Sabrina Dubroca
2024-07-01 8:48 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-06-28 22:11 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:23 +0200, Antonio Quartulli wrote:
> +static void ovpn_setup(struct net_device *dev)
> +{
> + /* compute the overhead considering AEAD encryption */
> + const int overhead = sizeof(u32) + NONCE_WIRE_SIZE + 16 +
Is that 16 equal to OVPN_MAX_PADDING? Or some other constant that
would help figure out the details of that overhead?
> + sizeof(struct udphdr) +
> + max(sizeof(struct ipv6hdr), sizeof(struct iphdr));
> +
> + netdev_features_t feat = NETIF_F_SG | NETIF_F_LLTX |
[...]
> + dev->features |= feat;
> + dev->hw_features |= feat;
I'm not sure we want NETIF_F_LLTX to be part of hw_features, I think
it's more of a property of the device than something we want to let
users toggle. AFAICT no other virtual driver does that.
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 05/25] ovpn: add basic interface creation/destruction/management routines
2024-06-28 22:11 ` Sabrina Dubroca
@ 2024-07-01 8:48 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-01 8:48 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
Hi,
On 29/06/2024 00:11, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:23 +0200, Antonio Quartulli wrote:
>> +static void ovpn_setup(struct net_device *dev)
>> +{
>> + /* compute the overhead considering AEAD encryption */
>> + const int overhead = sizeof(u32) + NONCE_WIRE_SIZE + 16 +
>
> Is that 16 equal to OVPN_MAX_PADDING? Or some other constant that
> would help figure out the details of that overhead?
That's the size of the auth tag.
Sadly the explanation of the OpenVPN header (with AEAD encryption) is
only described in a comment inside ovpn_aead_encrypt().
Here the comment is much "slimmer"..
In crypto_aead.c it is defined as AUTH_TAG_SIZE.
Maybe I could export that define and reuse it here.
Same goes for the "4" in ovpn_aead_encap_overhead(), which is actually
NONCE_WIRE_SIZE.
>
>> + sizeof(struct udphdr) +
>> + max(sizeof(struct ipv6hdr), sizeof(struct iphdr));
>> +
>> + netdev_features_t feat = NETIF_F_SG | NETIF_F_LLTX |
> [...]
>> + dev->features |= feat;
>> + dev->hw_features |= feat;
>
> I'm not sure we want NETIF_F_LLTX to be part of hw_features, I think
> it's more of a property of the device than something we want to let
> users toggle. AFAICT no other virtual driver does that.
I agree. It should *not* be part of hw_features.
I'll remove it from 'feat' and assign it only to '->features'.
Regards,
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 06/25] ovpn: implement interface creation/destruction via netlink
2024-06-27 13:08 ` [PATCH net-next v5 06/25] ovpn: implement interface creation/destruction via netlink Antonio Quartulli
@ 2024-07-03 21:27 ` Sabrina Dubroca
2024-07-03 21:44 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-03 21:27 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:24 +0200, Antonio Quartulli wrote:
> int ovpn_nl_new_iface_doit(struct sk_buff *skb, struct genl_info *info)
> {
[...]
> + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (!msg)
> + return -ENOMEM;
> +
> + hdr = genlmsg_iput(msg, info);
> + if (!hdr) {
> + nlmsg_free(msg);
> + return -ENOBUFS;
> + }
> +
> + if (nla_put_string(msg, OVPN_A_IFNAME, dev->name)) {
> + genlmsg_cancel(msg, hdr);
> + nlmsg_free(msg);
> + return -EMSGSIZE;
> + }
Maybe the ifindex as well? The notifications in later patches use that
rather than the name, but I don't know how the client handles this reply.
> + genlmsg_end(msg, hdr);
> +
> + return genlmsg_reply(msg, info);
> }
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 08/25] ovpn: introduce the ovpn_peer object
2024-06-27 13:08 ` [PATCH net-next v5 08/25] ovpn: introduce the ovpn_peer object Antonio Quartulli
@ 2024-07-03 21:37 ` Sabrina Dubroca
2024-07-03 22:16 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-03 21:37 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:26 +0200, Antonio Quartulli wrote:
> +/**
> + * struct ovpn_sockaddr - basic transport layer address
> + * @in4: IPv4 address
> + * @in6: IPv6 address
> + */
> +struct ovpn_sockaddr {
> + union {
> + struct sockaddr_in in4;
> + struct sockaddr_in6 in6;
> + };
> +};
nit: wrapping the anonymous union in a struct that contains nothing
else is not that useful.
> +/**
> + * struct ovpn_bind - remote peer binding
> + * @sa: 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 {
> + struct ovpn_sockaddr sa; /* remote sockaddr */
nit: then maybe call it "peer" or "remote" instead of sa?
> + union {
> + struct in_addr ipv4;
> + struct in6_addr ipv6;
> + } local;
> +
> + struct rcu_head rcu;
> +};
> +
[...]
> +struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
> +{
> + struct ovpn_peer *peer;
> + int ret;
> +
> + /* alloc and init peer object */
> + peer = kzalloc(sizeof(*peer), GFP_KERNEL);
> + if (!peer)
> + return ERR_PTR(-ENOMEM);
> +
> + peer->id = id;
> + peer->halt = false;
> + peer->ovpn = ovpn;
> +
> + peer->vpn_addrs.ipv4.s_addr = htonl(INADDR_ANY);
> + peer->vpn_addrs.ipv6 = in6addr_any;
> +
> + RCU_INIT_POINTER(peer->bind, NULL);
> + spin_lock_init(&peer->lock);
> + kref_init(&peer->refcount);
> +
> + ret = dst_cache_init(&peer->dst_cache, GFP_KERNEL);
> + if (ret < 0) {
> + netdev_err(ovpn->dev, "%s: cannot initialize dst cache\n",
> + __func__);
> + kfree(peer);
> + return ERR_PTR(ret);
> + }
> +
> + netdev_hold(ovpn->dev, NULL, GFP_KERNEL);
It would be good to add a tracker to help debug refcount issues.
> +
> + return peer;
> +}
> +
> +#define ovpn_peer_index(_tbl, _key, _key_len) \
> + (jhash(_key, _key_len, 0) % HASH_SIZE(_tbl)) \
nit: not used in this patch, and even removed by patch 16 as you
convert from index to buckets (that conversion should be squashed into
patch 15)
> +/**
> + * 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)
> +{
AFAICT ovpn_peer_transp_match is only called with ss from
ovpn_peer_skb_to_sockaddr, so it's pretty much ovpn_bind_skb_src_match
but using peer->bind. You can probably avoid the code duplication
(ovpn_peer_transp_match and ovpn_bind_skb_src_match are very similar).
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 06/25] ovpn: implement interface creation/destruction via netlink
2024-07-03 21:27 ` Sabrina Dubroca
@ 2024-07-03 21:44 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-03 21:44 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
On 03/07/2024 23:27, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:24 +0200, Antonio Quartulli wrote:
>> int ovpn_nl_new_iface_doit(struct sk_buff *skb, struct genl_info *info)
>> {
> [...]
>> + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
>> + if (!msg)
>> + return -ENOMEM;
>> +
>> + hdr = genlmsg_iput(msg, info);
>> + if (!hdr) {
>> + nlmsg_free(msg);
>> + return -ENOBUFS;
>> + }
>> +
>> + if (nla_put_string(msg, OVPN_A_IFNAME, dev->name)) {
>> + genlmsg_cancel(msg, hdr);
>> + nlmsg_free(msg);
>> + return -EMSGSIZE;
>> + }
>
> Maybe the ifindex as well? The notifications in later patches use that
> rather than the name, but I don't know how the client handles this reply.
Userspace will just throw the name in if_nametoindex().
However passing both doesn't hurt anybody, and actually spares the
conversion in userspace.
will add ifindex too.
Thanks!
>
>> + genlmsg_end(msg, hdr);
>> +
>> + return genlmsg_reply(msg, info);
>> }
>
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 08/25] ovpn: introduce the ovpn_peer object
2024-07-03 21:37 ` Sabrina Dubroca
@ 2024-07-03 22:16 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-03 22:16 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
On 03/07/2024 23:37, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:26 +0200, Antonio Quartulli wrote:
>> +/**
>> + * struct ovpn_sockaddr - basic transport layer address
>> + * @in4: IPv4 address
>> + * @in6: IPv6 address
>> + */
>> +struct ovpn_sockaddr {
>> + union {
>> + struct sockaddr_in in4;
>> + struct sockaddr_in6 in6;
>> + };
>> +};
>
> nit: wrapping the anonymous union in a struct that contains nothing
> else is not that useful.
yeah, I guess I can just turn ovpn_sockaddr in a union.
>
>
>> +/**
>> + * struct ovpn_bind - remote peer binding
>> + * @sa: 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 {
>> + struct ovpn_sockaddr sa; /* remote sockaddr */
>
> nit: then maybe call it "peer" or "remote" instead of sa?
yap, makes sense. I will call it "remote".
>
>> + union {
>> + struct in_addr ipv4;
>> + struct in6_addr ipv6;
>> + } local;
>> +
>> + struct rcu_head rcu;
>> +};
>> +
>
> [...]
>> +struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id)
>> +{
>> + struct ovpn_peer *peer;
>> + int ret;
>> +
>> + /* alloc and init peer object */
>> + peer = kzalloc(sizeof(*peer), GFP_KERNEL);
>> + if (!peer)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + peer->id = id;
>> + peer->halt = false;
>> + peer->ovpn = ovpn;
>> +
>> + peer->vpn_addrs.ipv4.s_addr = htonl(INADDR_ANY);
>> + peer->vpn_addrs.ipv6 = in6addr_any;
>> +
>> + RCU_INIT_POINTER(peer->bind, NULL);
>> + spin_lock_init(&peer->lock);
>> + kref_init(&peer->refcount);
>> +
>> + ret = dst_cache_init(&peer->dst_cache, GFP_KERNEL);
>> + if (ret < 0) {
>> + netdev_err(ovpn->dev, "%s: cannot initialize dst cache\n",
>> + __func__);
>> + kfree(peer);
>> + return ERR_PTR(ret);
>> + }
>> +
>> + netdev_hold(ovpn->dev, NULL, GFP_KERNEL);
>
> It would be good to add a tracker to help debug refcount issues.
Ok, will do!
>
>
>> +
>> + return peer;
>> +}
>> +
>> +#define ovpn_peer_index(_tbl, _key, _key_len) \
>> + (jhash(_key, _key_len, 0) % HASH_SIZE(_tbl)) \
>
> nit: not used in this patch, and even removed by patch 16 as you
> convert from index to buckets (that conversion should be squashed into
> patch 15)
You're correct. Will merge all these pieces in patch 15.
>
>> +/**
>> + * 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)
>> +{
>
> AFAICT ovpn_peer_transp_match is only called with ss from
> ovpn_peer_skb_to_sockaddr, so it's pretty much ovpn_bind_skb_src_match
> but using peer->bind. You can probably avoid the code duplication
> (ovpn_peer_transp_match and ovpn_bind_skb_src_match are very similar).
>
mhh it is not called in ovpn_peer_skb_to_sockaddr, but I guess your
comment still applies: ovpn_peer_transp_match and
ovpn_bind_skb_src_match are very similar.
However in one we have a sockaddr_storage while in the other we have an
skb. How do we combine the two?
The only way I see is to create an ss out of the skb and then always use
ovpn_peer_transp_match. Is this what you were alluding to?
Thanks!
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 11/25] ovpn: implement basic RX path (UDP)
2024-06-27 13:08 ` [PATCH net-next v5 11/25] ovpn: implement basic RX " Antonio Quartulli
@ 2024-07-08 16:11 ` Sabrina Dubroca
2024-07-08 22:09 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-08 16:11 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:29 +0200, Antonio Quartulli wrote:
> +static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb)
> +{
[...]
> + /* cause packet to be "received" by the interface */
> + if (likely(gro_cells_receive(&peer->ovpn->gro_cells,
> + skb) == NET_RX_SUCCESS))
> + /* update RX stats with the size of decrypted packet */
> + dev_sw_netstats_rx_add(peer->ovpn->dev, skb->len);
> + else
> + dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
Not needed AFAICT, gro_cells_receive already does
dev_core_stats_rx_dropped_inc(skb->dev) when it drops the packet.
> +}
> +
> +static void ovpn_decrypt_post(struct sk_buff *skb, int ret)
> +{
> + struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer;
> +
> + if (unlikely(ret < 0))
> + goto drop;
> +
> + ovpn_netdev_write(peer, skb);
> + /* skb is passed to upper layer - don't free it */
> + skb = NULL;
> +drop:
I really find this "common" return code confusing. The only thing the
two cases have in common is dropping the peer reference.
> + if (unlikely(skb))
> + dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
> + kfree_skb(skb);
> + ovpn_peer_put(peer);
> +}
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 11/25] ovpn: implement basic RX path (UDP)
2024-07-08 16:11 ` Sabrina Dubroca
@ 2024-07-08 22:09 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-08 22:09 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
Hi,
On 08/07/2024 18:11, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:29 +0200, Antonio Quartulli wrote:
>> +static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb)
>> +{
> [...]
>> + /* cause packet to be "received" by the interface */
>> + if (likely(gro_cells_receive(&peer->ovpn->gro_cells,
>> + skb) == NET_RX_SUCCESS))
>> + /* update RX stats with the size of decrypted packet */
>> + dev_sw_netstats_rx_add(peer->ovpn->dev, skb->len);
>> + else
>> + dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
>
> Not needed AFAICT, gro_cells_receive already does
> dev_core_stats_rx_dropped_inc(skb->dev) when it drops the packet.
You're right! This should be removed.
>
>> +}
>> +
>> +static void ovpn_decrypt_post(struct sk_buff *skb, int ret)
>> +{
>> + struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer;
>> +
>> + if (unlikely(ret < 0))
>> + goto drop;
>> +
>> + ovpn_netdev_write(peer, skb);
>> + /* skb is passed to upper layer - don't free it */
>> + skb = NULL;
>> +drop:
>
> I really find this "common" return code confusing. The only thing the
> two cases have in common is dropping the peer reference.
I believe it's just a matter of perspective: I read it as "exit path"
and if something went wrong I just do some extra actions.
However, I am perfectly fine splitting success/failure paths if the
common code is little like in this case.
Thanks!
>
>> + if (unlikely(skb))
>> + dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
>> + kfree_skb(skb);
>> + ovpn_peer_put(peer);
>> +}
>
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 12/25] ovpn: implement packet processing
2024-06-27 13:08 ` [PATCH net-next v5 12/25] ovpn: implement packet processing Antonio Quartulli
@ 2024-07-09 8:51 ` Sabrina Dubroca
2024-07-10 11:38 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-09 8:51 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:30 +0200, Antonio Quartulli wrote:
> +/* removes the primary key from the crypto context */
> +void ovpn_crypto_kill_primary(struct ovpn_crypto_state *cs)
> +{
> + struct ovpn_crypto_key_slot *ks;
> +
> + mutex_lock(&cs->mutex);
> + ks = rcu_replace_pointer(cs->primary, NULL,
> + lockdep_is_held(&cs->mutex));
Should there be a check that we're killing the key that has expired
and not some other key? I'm wondering if this could happen:
ovpn_encrypt_one
ovpn_aead_encrypt
ovpn_pktid_xmit_next
seq_num reaches threshold
returns -ERANGE
returns -ERANGE
ovpn_crypto_key_slots_swap
replaces cs->primary with cs->secondary
ovpn_encrypt_post
ret = -ERANGE
ovpn_crypto_kill_primary
kills the freshly installed primary key
> + ovpn_crypto_key_slot_put(ks);
> + mutex_unlock(&cs->mutex);
> +}
> +
[...]
> +static void ovpn_aead_encrypt_done(void *data, int ret)
> +{
> + struct sk_buff *skb = data;
> +
> + aead_request_free(ovpn_skb_cb(skb)->req);
> + ovpn_encrypt_post(skb, ret);
> +}
> +
> +int ovpn_aead_encrypt(struct ovpn_crypto_key_slot *ks, struct sk_buff *skb,
> + u32 peer_id)
> +{
> + const unsigned int tag_size = crypto_aead_authsize(ks->encrypt);
> + const unsigned int head_size = ovpn_aead_encap_overhead(ks);
> + struct scatterlist sg[MAX_SKB_FRAGS + 2];
> + DECLARE_CRYPTO_WAIT(wait);
unused? (also in _decrypt)
[...]
> +
> + req = aead_request_alloc(ks->encrypt, GFP_ATOMIC);
> + if (unlikely(!req))
> + return -ENOMEM;
> +
> + /* setup async crypto operation */
> + aead_request_set_tfm(req, ks->encrypt);
> + aead_request_set_callback(req, 0, ovpn_aead_encrypt_done, NULL);
NULL? That should be skb, ovpn_aead_encrypt_done needs it (same for
decrypt).
I suspect you haven't triggered the async path in testing. For that,
you can use crconf:
git clone https://git.code.sf.net/p/crconf/code
cd code && make
./src/crconf add driver 'pcrypt(generic-gcm-aesni)' type 3 priority 10000
Then all packets encrypted with gcm(aes) should go through the async
code.
> + aead_request_set_crypt(req, sg, sg, skb->len - head_size, iv);
> + aead_request_set_ad(req, OVPN_OP_SIZE_V2 + NONCE_WIRE_SIZE);
> +
> + ovpn_skb_cb(skb)->req = req;
> + ovpn_skb_cb(skb)->ks = ks;
> +
> + /* encrypt it */
> + return crypto_aead_encrypt(req);
> +}
[...]
> @@ -77,14 +133,45 @@ static void ovpn_decrypt_post(struct sk_buff *skb, int ret)
> /* pick next packet from RX queue, decrypt and forward it to the device */
> void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb)
> {
> + struct ovpn_crypto_key_slot *ks;
> + u8 key_id;
> +
> + /* get the key slot matching the key ID in the received packet */
> + key_id = ovpn_key_id_from_skb(skb);
> + ks = ovpn_crypto_key_id_to_slot(&peer->crypto, key_id);
This takes a reference on the keyslot (ovpn_crypto_key_slot_hold), but
I don't see it getting released in ovpn_decrypt_post. In
ovpn_encrypt_post you're adding a ovpn_crypto_key_slot_put (to match
ovpn_crypto_key_slot_primary), but nothing equivalent in
ovpn_decrypt_post?
> + if (unlikely(!ks)) {
> + net_info_ratelimited("%s: no available key for peer %u, key-id: %u\n",
> + peer->ovpn->dev->name, peer->id, key_id);
> + dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
> + kfree_skb(skb);
> + return;
> + }
> +
> ovpn_skb_cb(skb)->peer = peer;
> - ovpn_decrypt_post(skb, 0);
> + ovpn_decrypt_post(skb, ovpn_aead_decrypt(ks, skb));
> }
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 12/25] ovpn: implement packet processing
2024-07-09 8:51 ` Sabrina Dubroca
@ 2024-07-10 11:38 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-10 11:38 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
Hi,
On 09/07/2024 10:51, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:30 +0200, Antonio Quartulli wrote:
>> +/* removes the primary key from the crypto context */
>> +void ovpn_crypto_kill_primary(struct ovpn_crypto_state *cs)
>> +{
>> + struct ovpn_crypto_key_slot *ks;
>> +
>> + mutex_lock(&cs->mutex);
>> + ks = rcu_replace_pointer(cs->primary, NULL,
>> + lockdep_is_held(&cs->mutex));
>
> Should there be a check that we're killing the key that has expired
> and not some other key? I'm wondering if this could happen:
>
> ovpn_encrypt_one
> ovpn_aead_encrypt
> ovpn_pktid_xmit_next
> seq_num reaches threshold
> returns -ERANGE
> returns -ERANGE
>
> ovpn_crypto_key_slots_swap
> replaces cs->primary with cs->secondary
>
> ovpn_encrypt_post
> ret = -ERANGE
> ovpn_crypto_kill_primary
> kills the freshly installed primary key
>
>> + ovpn_crypto_key_slot_put(ks);
>> + mutex_unlock(&cs->mutex);
>> +}
hm I think you're right.
This is theoretically possible...
Userspace might be reneweing the key exactly when the primary key is
expiring..
We need to specify the key_id and kill exactly that key.
>> +
>
> [...]
>> +static void ovpn_aead_encrypt_done(void *data, int ret)
>> +{
>> + struct sk_buff *skb = data;
>> +
>> + aead_request_free(ovpn_skb_cb(skb)->req);
>> + ovpn_encrypt_post(skb, ret);
>> +}
>> +
>> +int ovpn_aead_encrypt(struct ovpn_crypto_key_slot *ks, struct sk_buff *skb,
>> + u32 peer_id)
>> +{
>> + const unsigned int tag_size = crypto_aead_authsize(ks->encrypt);
>> + const unsigned int head_size = ovpn_aead_encap_overhead(ks);
>> + struct scatterlist sg[MAX_SKB_FRAGS + 2];
>> + DECLARE_CRYPTO_WAIT(wait);
>
> unused? (also in _decrypt)
Right. Leftover from the sync crypto approach. Will drop it.
>
> [...]
>> +
>> + req = aead_request_alloc(ks->encrypt, GFP_ATOMIC);
>> + if (unlikely(!req))
>> + return -ENOMEM;
>> +
>> + /* setup async crypto operation */
>> + aead_request_set_tfm(req, ks->encrypt);
>> + aead_request_set_callback(req, 0, ovpn_aead_encrypt_done, NULL);
>
> NULL? That should be skb, ovpn_aead_encrypt_done needs it (same for
> decrypt).
Ouch
>
> I suspect you haven't triggered the async path in testing. For that,
> you can use crconf:
Yeah, I doubt I did, although I tried pumping as much traffic as
possible (but it may not have been enough to trigger the needed conditions).
>
> git clone https://git.code.sf.net/p/crconf/code
> cd code && make
> ./src/crconf add driver 'pcrypt(generic-gcm-aesni)' type 3 priority 10000
>
> Then all packets encrypted with gcm(aes) should go through the async
> code.
Amazing! Thanks! Will give it a go.
>
>> + aead_request_set_crypt(req, sg, sg, skb->len - head_size, iv);
>> + aead_request_set_ad(req, OVPN_OP_SIZE_V2 + NONCE_WIRE_SIZE);
>> +
>> + ovpn_skb_cb(skb)->req = req;
>> + ovpn_skb_cb(skb)->ks = ks;
>> +
>> + /* encrypt it */
>> + return crypto_aead_encrypt(req);
>> +}
>
> [...]
>> @@ -77,14 +133,45 @@ static void ovpn_decrypt_post(struct sk_buff *skb, int ret)
>> /* pick next packet from RX queue, decrypt and forward it to the device */
>> void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb)
>> {
>> + struct ovpn_crypto_key_slot *ks;
>> + u8 key_id;
>> +
>> + /* get the key slot matching the key ID in the received packet */
>> + key_id = ovpn_key_id_from_skb(skb);
>> + ks = ovpn_crypto_key_id_to_slot(&peer->crypto, key_id);
>
> This takes a reference on the keyslot (ovpn_crypto_key_slot_hold), but
> I don't see it getting released in ovpn_decrypt_post. In
> ovpn_encrypt_post you're adding a ovpn_crypto_key_slot_put (to match
> ovpn_crypto_key_slot_primary), but nothing equivalent in
> ovpn_decrypt_post?
good catch! I don't think it triggered any critical side effect (Except
from the leak) hence it went unnoticed.
Will add the ovpn_crypto_key_slot_put in the exit path.
Thanks!
>
>> + if (unlikely(!ks)) {
>> + net_info_ratelimited("%s: no available key for peer %u, key-id: %u\n",
>> + peer->ovpn->dev->name, peer->id, key_id);
>> + dev_core_stats_rx_dropped_inc(peer->ovpn->dev);
>> + kfree_skb(skb);
>> + return;
>> + }
>> +
>> ovpn_skb_cb(skb)->peer = peer;
>> - ovpn_decrypt_post(skb, 0);
>> + ovpn_decrypt_post(skb, ovpn_aead_decrypt(ks, skb));
>> }
>
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 14/25] ovpn: implement TCP transport
2024-06-27 13:08 ` [PATCH net-next v5 14/25] ovpn: implement TCP transport Antonio Quartulli
@ 2024-07-15 9:59 ` Sabrina Dubroca
2024-07-18 10:13 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-15 9:59 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:32 +0200, Antonio Quartulli wrote:
> diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
> index 0475440642dd..764b3df996bc 100644
> --- a/drivers/net/ovpn/io.c
> +++ b/drivers/net/ovpn/io.c
> @@ -21,6 +21,7 @@
> #include "netlink.h"
> #include "proto.h"
> #include "socket.h"
> +#include "tcp.h"
> #include "udp.h"
> #include "skb.h"
>
> @@ -84,8 +85,11 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
> /* PID sits after the op */
> pid = (__force __be32 *)(skb->data + OVPN_OP_SIZE_V2);
> ret = ovpn_pktid_recv(&ks->pid_recv, ntohl(*pid), 0);
> - if (unlikely(ret < 0))
> + if (unlikely(ret < 0)) {
> + net_err_ratelimited("%s: PKT ID RX error: %d\n",
> + peer->ovpn->dev->name, ret);
nit: this should be part of the "packet processing" patch?
> diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
> index dd4d91dfabb5..86d4696b1529 100644
> --- a/drivers/net/ovpn/peer.h
> +++ b/drivers/net/ovpn/peer.h
> @@ -10,8 +10,8 @@
> #ifndef _NET_OVPN_OVPNPEER_H_
> #define _NET_OVPN_OVPNPEER_H_
>
> -#include <linux/ptr_ring.h>
nit: I think you don't need it at all in this version and forgot to
drop it in a previous patch? (I didn't notice when it was introduced)
> +static int ovpn_tcp_to_userspace(struct ovpn_socket *sock, struct sk_buff *skb)
> +{
> + struct sock *sk = sock->sock->sk;
> +
> + skb_set_owner_r(skb, sk);
> + memset(skb->cb, 0, sizeof(skb->cb));
nit: this was just done in ovpn_tcp_rcv
> + skb_queue_tail(&sock->peer->tcp.user_queue, skb);
> + sock->peer->tcp.sk_cb.sk_data_ready(sk);
> +
> + return 0;
> +}
> +
> +static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
> +{
[...]
> + /* DATA_V2 packets are handled in kernel, the rest goes to user space */
> + if (likely(ovpn_opcode_from_skb(skb, 0) == OVPN_DATA_V2)) {
> + /* hold reference to peer as required by ovpn_recv().
> + *
> + * NOTE: in this context we should already be holding a
> + * reference to this peer, therefore ovpn_peer_hold() is
> + * not expected to fail
> + */
> + WARN_ON(!ovpn_peer_hold(peer));
drop the packet if this fails? otherwise I suspect we'll crash later on.
> + ovpn_recv(peer, skb);
> + } else {
> + /* The packet size header must be there when sending the packet
> + * to userspace, therefore we put it back
> + */
> + skb_push(skb, 2);
> + memset(skb->cb, 0, sizeof(skb->cb));
> + if (ovpn_tcp_to_userspace(peer->sock, skb) < 0) {
> + net_warn_ratelimited("%s: cannot send skb to userspace\n",
> + peer->ovpn->dev->name);
> + goto err;
> + }
> + }
[...]
> +void ovpn_tcp_socket_detach(struct socket *sock)
> +{
> + struct ovpn_socket *ovpn_sock;
> + struct ovpn_peer *peer;
> +
> + if (!sock)
> + return;
> +
> + rcu_read_lock();
> + ovpn_sock = rcu_dereference_sk_user_data(sock->sk);
> +
[...]
> + /* cancel any ongoing work. Done after removing the CBs so that these
> + * workers cannot be re-armed
> + */
> + cancel_work_sync(&peer->tcp.tx_work);
I don't think that's ok to call under rcu_read_lock, it seems it can
sleep.
> + strp_done(&peer->tcp.strp);
And same here, since strp_done also calls cancel_work_sync.
> + rcu_read_unlock();
> +}
> +
> +static void ovpn_tcp_send_sock(struct ovpn_peer *peer)
> +{
> + struct sk_buff *skb = peer->tcp.out_msg.skb;
> +
> + if (!skb)
> + return;
> +
> + if (peer->tcp.tx_in_progress)
> + return;
> +
> + peer->tcp.tx_in_progress = true;
I'm not convinced this is safe. ovpn_tcp_send_sock could run
concurrently for the same peer (lock_sock doesn't exclude bh_lock_sock
after the short "grab ownership" phase), so I think both sides could
see tx_in_progress = false and then proceed.
> + do {
> + int ret = skb_send_sock_locked(peer->sock->sock->sk, skb,
> + peer->tcp.out_msg.offset,
> + peer->tcp.out_msg.len);
> + if (unlikely(ret < 0)) {
> + if (ret == -EAGAIN)
> + goto out;
This will silently drop the message? And then in case of a userspace
message, ovpn_tcp_sendmsg will lie to the user (the openvpn client),
claiming that the control message was sent (ret = size just above the
unlock)?
> +
> + net_warn_ratelimited("%s: TCP error to peer %u: %d\n",
> + peer->ovpn->dev->name, peer->id,
> + ret);
> +
> + /* in case of TCP error we can't recover the VPN
> + * stream therefore we abort the connection
> + */
> + ovpn_peer_del(peer,
> + OVPN_DEL_PEER_REASON_TRANSPORT_ERROR);
> + break;
> + }
> +
> + peer->tcp.out_msg.len -= ret;
> + peer->tcp.out_msg.offset += ret;
> + } while (peer->tcp.out_msg.len > 0);
Another thing that worries me: assume the receiver is a bit slow, the
underlying TCP socket gets stuck. skb_send_sock_locked manages to push
some data down the TCP socket, but not everything. We advance by that
amount, and restart this loop. The socket is still stuck, so
skb_send_sock_locked returns -EAGAIN. We have only pushed a partial
message down to the TCP socket, but we drop the rest? Now the stream
is broken, and the next call to ovpn_tcp_send_sock will happily send
its message.
ovpn_tcp_send_sock with msg_len = 1000
iteration 1
skb_send_sock_locked returns 100
advance
iteration 2
skb_send_sock_locked returns -EAGAIN
goto out
So you'd have to keep that partially-sent message around until you can
finish pushing it out on the socket.
[...]
> +static int ovpn_tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
> +{
> + struct ovpn_socket *sock;
> + int ret, linear = PAGE_SIZE;
> + struct ovpn_peer *peer;
> + struct sk_buff *skb;
> +
> + rcu_read_lock();
> + sock = rcu_dereference_sk_user_data(sk);
> + peer = sock->peer;
> + rcu_read_unlock();
What's stopping the peer being freed here?
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 15/25] ovpn: implement multi-peer support
2024-06-27 13:08 ` [PATCH net-next v5 15/25] ovpn: implement multi-peer support Antonio Quartulli
@ 2024-07-15 10:40 ` Sabrina Dubroca
2024-07-17 14:05 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-15 10:40 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:33 +0200, Antonio Quartulli wrote:
> @@ -46,6 +46,17 @@ static int ovpn_struct_init(struct net_device *dev, enum ovpn_mode mode)
> ovpn->mode = mode;
> spin_lock_init(&ovpn->lock);
>
> + if (mode == OVPN_MODE_MP) {
> + /* the peer container is fairly large, therefore we dynamically
> + * allocate it only when needed
> + */
> + ovpn->peers = kzalloc(sizeof(*ovpn->peers), GFP_KERNEL);
> + if (!ovpn->peers)
> + return -ENOMEM;
As we discussed in v3 (it was about the workqueue), I don't think this
will get freed correctly in some of the failure cases. This should go
in ovpn_net_init.
> +
> + spin_lock_init(&ovpn->peers->lock);
> + }
> +
> return 0;
> }
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 16/25] ovpn: implement peer lookup logic
2024-06-27 13:08 ` [PATCH net-next v5 16/25] ovpn: implement peer lookup logic Antonio Quartulli
@ 2024-07-15 13:11 ` Sabrina Dubroca
2024-07-17 14:07 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-15 13:11 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:34 +0200, Antonio Quartulli wrote:
> /**
> * ovpn_peer_check_by_src - check that skb source is routed via peer
> * @ovpn: the openvpn instance to search
> * @skb: the packet to extra source address from
nit, just noticed now but should be fixed in patch 12: s/to extra/to extract/
[...]
> @@ -324,11 +576,11 @@ static int ovpn_peer_add_mp(struct ovpn_struct *ovpn, struct ovpn_peer *peer)
> struct sockaddr_storage sa = { 0 };
> struct sockaddr_in6 *sa6;
> struct sockaddr_in *sa4;
> + struct hlist_head *head;
> struct ovpn_bind *bind;
> struct ovpn_peer *tmp;
> size_t salen;
> int ret = 0;
> - u32 index;
>
> spin_lock_bh(&ovpn->peers->lock);
> /* do not add duplicates */
> @@ -364,30 +616,27 @@ static int ovpn_peer_add_mp(struct ovpn_struct *ovpn, struct ovpn_peer *peer)
> goto unlock;
> }
>
> - index = ovpn_peer_index(ovpn->peers->by_transp_addr, &sa,
> - salen);
> - hlist_add_head_rcu(&peer->hash_entry_transp_addr,
> - &ovpn->peers->by_transp_addr[index]);
> + head = ovpn_get_hash_head(ovpn->peers->by_transp_addr, &sa,
> + salen);
> + hlist_add_head_rcu(&peer->hash_entry_transp_addr, head);
> }
These changes to ovpn_peer_add_mp (and the replacement of
ovpn_peer_index with ovpn_get_hash_head) could be squashed into the
previous patch.
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism
2024-06-27 13:08 ` [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism Antonio Quartulli
@ 2024-07-15 14:44 ` Sabrina Dubroca
2024-07-17 15:30 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-15 14:44 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:35 +0200, Antonio Quartulli wrote:
> +static const unsigned char ovpn_keepalive_message[] = {
> + 0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
> + 0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48
> +};
> +
> +/**
> + * ovpn_is_keepalive - check if skb contains a keepalive message
> + * @skb: packet to check
> + *
> + * Assumes that the first byte of skb->data is defined.
> + *
> + * Return: true if skb contains a keepalive or false otherwise
> + */
> +static bool ovpn_is_keepalive(struct sk_buff *skb)
> +{
> + if (*skb->data != OVPN_KEEPALIVE_FIRST_BYTE)
You could use ovpn_keepalive_message[0], and then you wouldn't need
this extra constant.
> + return false;
> +
> + if (!pskb_may_pull(skb, sizeof(ovpn_keepalive_message)))
> + return false;
> +
> + return !memcmp(skb->data, ovpn_keepalive_message,
> + sizeof(ovpn_keepalive_message));
Is a packet that contains some extra bytes after the exact keepalive
considered a valid keepalive, or does it need to be the correct
length?
> +}
> +
> /* Called after decrypt to write the IP packet to the device.
> * This method is expected to manage/free the skb.
> */
> @@ -91,6 +116,9 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
> goto drop;
> }
>
> + /* note event of authenticated packet received for keepalive */
> + ovpn_peer_keepalive_recv_reset(peer);
> +
> /* point to encapsulated IP packet */
> __skb_pull(skb, ovpn_skb_cb(skb)->payload_offset);
>
> @@ -107,6 +135,12 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
> goto drop;
> }
>
> + if (ovpn_is_keepalive(skb)) {
> + netdev_dbg(peer->ovpn->dev,
> + "ping received from peer %u\n", peer->id);
That should probably be _ratelimited, but it seems we don't have
_ratelimited variants for the netdev_* helpers.
> +/**
> + * ovpn_xmit_special - encrypt and transmit an out-of-band message to peer
> + * @peer: peer to send the message to
> + * @data: message content
> + * @len: message length
> + *
> + * Assumes that caller holds a reference to peer
> + */
> +static void ovpn_xmit_special(struct ovpn_peer *peer, const void *data,
> + const unsigned int len)
> +{
> + struct ovpn_struct *ovpn;
> + struct sk_buff *skb;
> +
> + ovpn = peer->ovpn;
> + if (unlikely(!ovpn))
> + return;
> +
> + skb = alloc_skb(256 + len, GFP_ATOMIC);
Where is that 256 coming from?
> + if (unlikely(!skb))
> + return;
Failure to send a keepalive should probably have a counter, to help
users troubleshoot why their connection dropped.
(can be done later unless someone insists)
> + skb_reserve(skb, 128);
And that 128?
> + skb->priority = TC_PRIO_BESTEFFORT;
> + memcpy(__skb_put(skb, len), data, len);
nit: that's __skb_put_data
> + /* increase reference counter when passing peer to sending queue */
> + if (!ovpn_peer_hold(peer)) {
> + netdev_dbg(ovpn->dev, "%s: cannot hold peer reference for sending special packet\n",
> + __func__);
> + kfree_skb(skb);
> + return;
> + }
> +
> + ovpn_send(ovpn, skb, peer);
> +}
> +
> +/**
> + * ovpn_keepalive_xmit - send keepalive message to peer
> + * @peer: the peer to send the message to
> + */
> +void ovpn_keepalive_xmit(struct ovpn_peer *peer)
> +{
> + ovpn_xmit_special(peer, ovpn_keepalive_message,
> + sizeof(ovpn_keepalive_message));
> +}
I don't see other users of ovpn_xmit_special in this series, if you
don't have more planned in the future you could drop the extra function.
> +/**
> + * ovpn_peer_expire - timer task for incoming keepialive timeout
typo: s/keepialive/keepalive/
> +/**
> + * ovpn_peer_keepalive_set - configure keepalive values for peer
> + * @peer: the peer to configure
> + * @interval: outgoing keepalive interval
> + * @timeout: incoming keepalive timeout
> + */
> +void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout)
> +{
> + u32 delta;
> +
> + netdev_dbg(peer->ovpn->dev,
> + "%s: scheduling keepalive for peer %u: interval=%u timeout=%u\n",
> + __func__, peer->id, interval, timeout);
> +
> + peer->keepalive_interval = interval;
> + if (interval > 0) {
> + delta = msecs_to_jiffies(interval * MSEC_PER_SEC);
> + mod_timer(&peer->keepalive_xmit, jiffies + delta);
Maybe something to consider in the future: this could be resetting a
timer that was just about to go off to a somewhat distant time in the
future. Not sure the peer will be happy about that (and not consider
it a timeout).
> + } else {
> + timer_delete(&peer->keepalive_xmit);
> + }
> +
> + peer->keepalive_timeout = timeout;
> + if (timeout) {
pedantic nit: inconsistent style with the "interval > 0" test just
above
> + delta = msecs_to_jiffies(timeout * MSEC_PER_SEC);
> + mod_timer(&peer->keepalive_recv, jiffies + delta);
> + } else {
> + timer_delete(&peer->keepalive_recv);
> + }
> +}
> +
[...]
> +/**
> + * ovpn_peer_keepalive_recv_reset - reset keepalive timeout
> + * @peer: peer for which the timeout should be reset
> + *
> + * To be invoked upon reception of an authenticated packet from peer in order
> + * to report valid activity and thus reset the keepalive timeout
> + */
> +static inline void ovpn_peer_keepalive_recv_reset(struct ovpn_peer *peer)
> +{
> + u32 delta = msecs_to_jiffies(peer->keepalive_timeout * MSEC_PER_SEC);
> +
> + if (unlikely(!delta))
> + return;
> +
> + mod_timer(&peer->keepalive_recv, jiffies + delta);
This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
each packet. I wonder how well the timer subsystem deals with one
timer getting updated possibly thousands of time per second.
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 20/25] ovpn: implement peer add/dump/delete via netlink
2024-06-27 13:08 ` [PATCH net-next v5 20/25] ovpn: implement peer add/dump/delete via netlink Antonio Quartulli
@ 2024-07-16 13:41 ` Sabrina Dubroca
2024-07-17 14:04 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-16 13:41 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:38 +0200, Antonio Quartulli wrote:
> @@ -29,7 +34,7 @@ MODULE_ALIAS_GENL_FAMILY(OVPN_FAMILY_NAME);
> * Return: the netdevice, if found, or an error otherwise
> */
> static struct net_device *
> -ovpn_get_dev_from_attrs(struct net *net, struct genl_info *info)
> +ovpn_get_dev_from_attrs(struct net *net, const struct genl_info *info)
nit: this should be squashed into "add basic netlink support"
[...]
> int ovpn_nl_set_peer_doit(struct sk_buff *skb, struct genl_info *info)
> {
> - return -EOPNOTSUPP;
> + bool keepalive_set = false, new_peer = false;
> + struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
> + struct ovpn_struct *ovpn = info->user_ptr[0];
> + struct sockaddr_storage *ss = NULL;
> + u32 sockfd, id, interv, timeout;
> + struct socket *sock = NULL;
> + struct sockaddr_in mapped;
> + struct sockaddr_in6 *in6;
> + struct ovpn_peer *peer;
> + u8 *local_ip = NULL;
> + size_t sa_len;
> + int ret;
> +
> + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER))
> + return -EINVAL;
> +
> + ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER],
> + ovpn_peer_nl_policy, info->extack);
> + if (ret)
> + return ret;
> +
> + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs,
> + OVPN_A_PEER_ID))
> + return -EINVAL;
> +
> + id = nla_get_u32(attrs[OVPN_A_PEER_ID]);
> + /* check if the peer exists first, otherwise create a new one */
> + peer = ovpn_peer_get_by_id(ovpn, id);
> + if (!peer) {
> + peer = ovpn_peer_new(ovpn, id);
> + new_peer = true;
> + if (IS_ERR(peer)) {
> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
> + "cannot create new peer object for peer %u (sockaddr=%pIScp): %ld",
> + id, ss, PTR_ERR(peer));
ss hasn't been set yet at this point, including it in the extack
message is not useful.
> + return PTR_ERR(peer);
> + }
> + }
> +
> + if (new_peer && NL_REQ_ATTR_CHECK(info->extack,
> + info->attrs[OVPN_A_PEER], attrs,
> + OVPN_A_PEER_SOCKET)) {
This can be checked at the start of the previous block (!peer), we'd
avoid a pointless peer allocation.
(and the linebreaks in NL_REQ_ATTR_CHECK end up being slightly better
because you don't need the "new_peer &&" test that is wider than the
tab used to indent the !peer block :))
> + ret = -EINVAL;
> + goto peer_release;
> + }
> +
> + if (new_peer && ovpn->mode == OVPN_MODE_MP &&
> + !attrs[OVPN_A_PEER_VPN_IPV4] && !attrs[OVPN_A_PEER_VPN_IPV6]) {
Same for this check.
> + NL_SET_ERR_MSG_MOD(info->extack,
> + "a VPN IP is required when adding a peer in MP mode");
> + ret = -EINVAL;
> + goto peer_release;
> + }
> +
> + if (attrs[OVPN_A_PEER_SOCKET]) {
> + /* lookup the fd in the kernel table and extract the socket
> + * object
> + */
> + sockfd = nla_get_u32(attrs[OVPN_A_PEER_SOCKET]);
> + /* sockfd_lookup() increases sock's refcounter */
> + sock = sockfd_lookup(sockfd, &ret);
> + if (!sock) {
> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
> + "cannot lookup peer socket (fd=%u): %d",
> + sockfd, ret);
> + ret = -ENOTSOCK;
> + goto peer_release;
> + }
> +
> + if (peer->sock)
> + ovpn_socket_put(peer->sock);
> +
> + peer->sock = ovpn_socket_new(sock, peer);
> + if (IS_ERR(peer->sock)) {
> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
> + "cannot encapsulate socket: %ld",
> + PTR_ERR(peer->sock));
> + sockfd_put(sock);
> + peer->sock = NULL;
Is there any value for the client in keeping the old peer->sock
assigned if we fail here?
ie something like:
tmp = ovpn_socket_new(sock, peer);
if (IS_ERR(tmp)) {
...
goto peer_release;
}
if (peer->sock)
ovpn_socket_put(peer->sock);
peer->sock = tmp;
But if it's just going to get rid of the old socket and the whole
association/peer on failure, probably not.
> + ret = -ENOTSOCK;
> + goto peer_release;
> + }
> + }
> +
> + /* Only when using UDP as transport protocol the remote endpoint
> + * can be configured so that ovpn knows where to send packets
> + * to.
> + *
> + * In case of TCP, the socket is connected to the peer and ovpn
> + * will just send bytes over it, without the need to specify a
> + * destination.
(that should also work with UDP "connected" sockets)
> + */
> + if (peer->sock->sock->sk->sk_protocol == IPPROTO_UDP &&
> + attrs[OVPN_A_PEER_SOCKADDR_REMOTE]) {
[...]
> +
> + if (attrs[OVPN_A_PEER_LOCAL_IP]) {
> + local_ip = ovpn_nl_attr_local_ip(info, ovpn,
> + attrs,
> + ss->ss_family);
> + if (IS_ERR(local_ip)) {
> + ret = PTR_ERR(local_ip);
> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
> + "cannot retrieve local IP: %d",
> + ret);
ovpn_nl_attr_local_ip already sets a more specific extack message,
this is unnecessary.
> + goto peer_release;
> + }
> + }
> +
> + /* set peer sockaddr */
> + ret = ovpn_peer_reset_sockaddr(peer, ss, local_ip);
> + if (ret < 0) {
> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
> + "cannot set peer sockaddr: %d",
> + ret);
> + goto peer_release;
> + }
> + }
I would reject OVPN_A_PEER_SOCKADDR_REMOTE for a non-UDP socket.
> + /* VPN IPs cannot be updated, because they are hashed */
Then I think there should be something like
if (!new_peer && (attrs[OVPN_A_PEER_VPN_IPV4] || attrs[OVPN_A_PEER_VPN_IPV6])) {
NL_SET_ERR_MSG_FMT_MOD(... "can't update ip");
ret = -EINVAL;
goto peer_release;
}
(just after getting the peer, before any changes have actually been
made)
And if they are only used in MP mode, I would maybe also reject
requests where mode==P2P and OVPN_A_PEER_VPN_IPV* is provided.
> + if (new_peer && attrs[OVPN_A_PEER_VPN_IPV4])
> + peer->vpn_addrs.ipv4.s_addr =
> + nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]);
> +
> + /* VPN IPs cannot be updated, because they are hashed */
> + if (new_peer && attrs[OVPN_A_PEER_VPN_IPV6])
> + peer->vpn_addrs.ipv6 =
> + nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]);
> +
> + /* when setting the keepalive, both parameters have to be configured */
Then I would also reject a config where only one is set (also before any
changes have been made).
> + if (attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL] &&
> + attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]) {
> + keepalive_set = true;
> + interv = nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]);
> + timeout = nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]);
> + }
> +
> + if (keepalive_set)
> + ovpn_peer_keepalive_set(peer, interv, timeout);
Why not skip the bool and just do this in the previous block?
> + netdev_dbg(ovpn->dev,
> + "%s: %s peer with endpoint=%pIScp/%s id=%u VPN-IPv4=%pI4 VPN-IPv6=%pI6c\n",
> + __func__, (new_peer ? "adding" : "modifying"), ss,
> + peer->sock->sock->sk->sk_prot_creator->name, peer->id,
> + &peer->vpn_addrs.ipv4.s_addr, &peer->vpn_addrs.ipv6);
> +
> + if (new_peer) {
> + ret = ovpn_peer_add(ovpn, peer);
> + if (ret < 0) {
> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
> + "cannot add new peer (id=%u) to hashtable: %d\n",
> + peer->id, ret);
> + goto peer_release;
> + }
> + } else {
> + ovpn_peer_put(peer);
> + }
> +
> + return 0;
> +
> +peer_release:
> + if (new_peer) {
> + /* release right away because peer is not really used in any
> + * context
> + */
> + ovpn_peer_release(peer);
> + kfree(peer);
I don't think that's correct, the new peer was created with
ovpn_peer_new, so it took a reference on the netdevice
(netdev_hold(ovpn->dev, ...)), which isn't released by
ovpn_peer_release. Why not just go through ovpn_peer_put?
> + } else {
> + ovpn_peer_put(peer);
> + }
> +
> + return ret;
> +}
> +
[...]
> int ovpn_nl_get_peer_doit(struct sk_buff *skb, struct genl_info *info)
> {
[...]
> + peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]);
> + peer = ovpn_peer_get_by_id(ovpn, peer_id);
> + if (!peer) {
> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
> + "cannot find peer with id %u", peer_id);
> + return -ENOENT;
> + }
> +
> + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> + if (!msg)
Missing ovpn_peer_put?
> + return -ENOMEM;
> +
> + ret = ovpn_nl_send_peer(msg, info, peer, info->snd_portid,
> + info->snd_seq, 0);
> + if (ret < 0) {
> + nlmsg_free(msg);
> + goto err;
> + }
> +
> + ret = genlmsg_reply(msg, info);
> +err:
> + ovpn_peer_put(peer);
> + return ret;
> }
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 22/25] ovpn: kill key and notify userspace in case of IV exhaustion
2024-06-27 13:08 ` [PATCH net-next v5 22/25] ovpn: kill key and notify userspace in case of IV exhaustion Antonio Quartulli
@ 2024-07-17 10:42 ` Sabrina Dubroca
2024-07-17 11:03 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-17 10:42 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:40 +0200, Antonio Quartulli wrote:
> IV wrap-around is cryptographically dangerous for a number of ciphers,
> therefore kill the key and inform userspace (via netlink) should the
> IV space go exhausted.
>
> Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
> ---
> drivers/net/ovpn/netlink.c | 39 ++++++++++++++++++++++++++++++++++++++
> drivers/net/ovpn/netlink.h | 8 ++++++++
> 2 files changed, 47 insertions(+)
>
> diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
> index 31c58cda6a3d..e43bbc9ad5d2 100644
> --- a/drivers/net/ovpn/netlink.c
> +++ b/drivers/net/ovpn/netlink.c
> @@ -846,6 +846,45 @@ int ovpn_nl_del_key_doit(struct sk_buff *skb, struct genl_info *info)
> return 0;
> }
>
> +int ovpn_nl_notify_swap_keys(struct ovpn_peer *peer)
This is not getting called anywhere in this version. v3 had a change
to ovpn_encrypt_one to handle the -ERANGE coming from ovpn_pktid_xmit_next.
Assuming this was getting called just as the TX key expires (like it
was in v3), I'm a bit unclear on how the client can deal well with
this event.
I don't see any way for userspace to know the current IV state (no
notification for when the packetid gets past some threshold, and
pid_xmit isn't getting dumped via netlink), so no chance for userspace
to swap keys early and avoid running out of IVs. And then, since we
don't have a usable primary key anymore, we will have to drop packets
until userspace tells the kernel to swap the keys (or possibly install
a secondary).
Am I missing something in the kernel/userspace interaction?
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 23/25] ovpn: notify userspace when a peer is deleted
2024-06-27 13:08 ` [PATCH net-next v5 23/25] ovpn: notify userspace when a peer is deleted Antonio Quartulli
@ 2024-07-17 10:54 ` Sabrina Dubroca
2024-07-17 11:16 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-17 10:54 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:41 +0200, Antonio Quartulli wrote:
> diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
> index 2105bcc981fa..23418204fa8e 100644
> --- a/drivers/net/ovpn/peer.c
> +++ b/drivers/net/ovpn/peer.c
> @@ -273,6 +273,7 @@ void ovpn_peer_release_kref(struct kref *kref)
>
> ovpn_peer_release(peer);
> netdev_put(peer->ovpn->dev, NULL);
I don't think you can access peer->ovpn once you release that
reference, if this peer was holding the last reference the netdev (and
the ovpn struct) could be gone while we're accessing it in
ovpn_nl_notify_del_peer.
> + ovpn_nl_notify_del_peer(peer);
> kfree_rcu(peer, rcu);
> }
>
> diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
> index 8d24a8fdd03e..971603a70090 100644
> --- a/drivers/net/ovpn/peer.h
> +++ b/drivers/net/ovpn/peer.h
> @@ -129,6 +129,7 @@ static inline bool ovpn_peer_hold(struct ovpn_peer *peer)
>
> void ovpn_peer_release(struct ovpn_peer *peer);
> void ovpn_peer_release_kref(struct kref *kref);
> +void ovpn_peer_release(struct ovpn_peer *peer);
nit: dupe of 2 lines before
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 22/25] ovpn: kill key and notify userspace in case of IV exhaustion
2024-07-17 10:42 ` Sabrina Dubroca
@ 2024-07-17 11:03 ` Antonio Quartulli
2024-07-17 13:26 ` Sabrina Dubroca
0 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-17 11:03 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
Hi,
On 17/07/2024 12:42, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:40 +0200, Antonio Quartulli wrote:
>> IV wrap-around is cryptographically dangerous for a number of ciphers,
>> therefore kill the key and inform userspace (via netlink) should the
>> IV space go exhausted.
>>
>> Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
>> ---
>> drivers/net/ovpn/netlink.c | 39 ++++++++++++++++++++++++++++++++++++++
>> drivers/net/ovpn/netlink.h | 8 ++++++++
>> 2 files changed, 47 insertions(+)
>>
>> diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c
>> index 31c58cda6a3d..e43bbc9ad5d2 100644
>> --- a/drivers/net/ovpn/netlink.c
>> +++ b/drivers/net/ovpn/netlink.c
>> @@ -846,6 +846,45 @@ int ovpn_nl_del_key_doit(struct sk_buff *skb, struct genl_info *info)
>> return 0;
>> }
>>
>> +int ovpn_nl_notify_swap_keys(struct ovpn_peer *peer)
>
> This is not getting called anywhere in this version. v3 had a change
> to ovpn_encrypt_one to handle the -ERANGE coming from ovpn_pktid_xmit_next.
>
Darn! I must have missed this. This must have been removed by accident
during the n-th rebase/reshuffle. Thanks for pointing it out.
> Assuming this was getting called just as the TX key expires (like it
> was in v3), I'm a bit unclear on how the client can deal well with
> this event.
Correct assumption.
>
> I don't see any way for userspace to know the current IV state (no
> notification for when the packetid gets past some threshold, and
> pid_xmit isn't getting dumped via netlink), so no chance for userspace
> to swap keys early and avoid running out of IVs. And then, since we
> don't have a usable primary key anymore, we will have to drop packets
> until userspace tells the kernel to swap the keys (or possibly install
> a secondary).
>
> Am I missing something in the kernel/userspace interaction?
There are two events triggering userspace to generate a new key:
1) time based
2) packet count based
1) is easy: after X seconds/minutes generate a new key and send it to
the kernel. It's obviously based on guestimate and normally our default
works well.
2) after X packets/bytes generate a new key. Here userspace keeps track
of the amount of traffic by periodically polling GET_PEER and fetching
the VPN/LINK stats.
A future improvement could be to have ovpn proactively notifying
userspace after reaching a certain threshold, but for now this mechanism
does not exist.
I hope it helps.
Cheers,
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 23/25] ovpn: notify userspace when a peer is deleted
2024-07-17 10:54 ` Sabrina Dubroca
@ 2024-07-17 11:16 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-17 11:16 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
Hi,
On 17/07/2024 12:54, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:41 +0200, Antonio Quartulli wrote:
>> diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c
>> index 2105bcc981fa..23418204fa8e 100644
>> --- a/drivers/net/ovpn/peer.c
>> +++ b/drivers/net/ovpn/peer.c
>> @@ -273,6 +273,7 @@ void ovpn_peer_release_kref(struct kref *kref)
>>
>> ovpn_peer_release(peer);
>> netdev_put(peer->ovpn->dev, NULL);
>
> I don't think you can access peer->ovpn once you release that
> reference, if this peer was holding the last reference the netdev (and
> the ovpn struct) could be gone while we're accessing it in
> ovpn_nl_notify_del_peer.
I see what you mean.
I will move notify_del_peer up above, before the release
>
>> + ovpn_nl_notify_del_peer(peer);
>> kfree_rcu(peer, rcu);
>> }
>>
>> diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
>> index 8d24a8fdd03e..971603a70090 100644
>> --- a/drivers/net/ovpn/peer.h
>> +++ b/drivers/net/ovpn/peer.h
>> @@ -129,6 +129,7 @@ static inline bool ovpn_peer_hold(struct ovpn_peer *peer)
>>
>> void ovpn_peer_release(struct ovpn_peer *peer);
>> void ovpn_peer_release_kref(struct kref *kref);
>> +void ovpn_peer_release(struct ovpn_peer *peer);
>
> nit: dupe of 2 lines before
ops
Thanks!
>
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 22/25] ovpn: kill key and notify userspace in case of IV exhaustion
2024-07-17 11:03 ` Antonio Quartulli
@ 2024-07-17 13:26 ` Sabrina Dubroca
2024-07-17 13:38 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-17 13:26 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-07-17, 13:03:11 +0200, Antonio Quartulli wrote:
> On 17/07/2024 12:42, Sabrina Dubroca wrote:
> > I don't see any way for userspace to know the current IV state (no
> > notification for when the packetid gets past some threshold, and
> > pid_xmit isn't getting dumped via netlink), so no chance for userspace
> > to swap keys early and avoid running out of IVs. And then, since we
> > don't have a usable primary key anymore, we will have to drop packets
> > until userspace tells the kernel to swap the keys (or possibly install
> > a secondary).
> >
> > Am I missing something in the kernel/userspace interaction?
>
> There are two events triggering userspace to generate a new key:
> 1) time based
> 2) packet count based
>
> 1) is easy: after X seconds/minutes generate a new key and send it to the
> kernel. It's obviously based on guestimate and normally our default works
> well.
>
> 2) after X packets/bytes generate a new key. Here userspace keeps track of
> the amount of traffic by periodically polling GET_PEER and fetching the
> VPN/LINK stats.
Oh right, that's what I was missing. TX packet count should be
equivalent to packetid. Thanks.
> A future improvement could be to have ovpn proactively notifying userspace
> after reaching a certain threshold, but for now this mechanism does not
> exist.
If it's not there from the start, you won't be able to rely on it
(because the userspace client may run on a kernel that does not
provide the notification), so you would still have to fetch the stats,
unless you have a way to poll for the threshold notification feature
being present.
> I hope it helps.
Yup, thanks. Can you add this explanation to the commit message for
this patch in the next version? Documenting a bit the expectations of
the kernel/userspace interactions would be helpful, also for the
sequencing of key installation/key swap operations. I'm guessing it
goes something like this:
1. client sets up a primary key (key#1) and uses it
2. at some point, it sets up a secondary key (key#2)
3. later, keys are swapped (key#2 is now primary)
4. after some more time, the secondary (key#1) is removed and a new
secondary (key#3) is installed
[steps 3 and 4 keep repeating]
And from reading patch 21, both the TX and RX key seem to be changed
together (swap and delete operate on the whole keyslot, and set
requires both the ENCRYPT_DIR and DECRYPT_DIR attributes).
A rough description of the overall life of a client (opening sockets
and setting up the ovpn device/peers) could also be useful alongside
the code.
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 22/25] ovpn: kill key and notify userspace in case of IV exhaustion
2024-07-17 13:26 ` Sabrina Dubroca
@ 2024-07-17 13:38 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-17 13:38 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
On 17/07/2024 15:26, Sabrina Dubroca wrote:
> 2024-07-17, 13:03:11 +0200, Antonio Quartulli wrote:
>> On 17/07/2024 12:42, Sabrina Dubroca wrote:
>>> I don't see any way for userspace to know the current IV state (no
>>> notification for when the packetid gets past some threshold, and
>>> pid_xmit isn't getting dumped via netlink), so no chance for userspace
>>> to swap keys early and avoid running out of IVs. And then, since we
>>> don't have a usable primary key anymore, we will have to drop packets
>>> until userspace tells the kernel to swap the keys (or possibly install
>>> a secondary).
>>>
>>> Am I missing something in the kernel/userspace interaction?
>>
>> There are two events triggering userspace to generate a new key:
>> 1) time based
>> 2) packet count based
>>
>> 1) is easy: after X seconds/minutes generate a new key and send it to the
>> kernel. It's obviously based on guestimate and normally our default works
>> well.
>>
>> 2) after X packets/bytes generate a new key. Here userspace keeps track of
>> the amount of traffic by periodically polling GET_PEER and fetching the
>> VPN/LINK stats.
>
> Oh right, that's what I was missing. TX packet count should be
> equivalent to packetid. Thanks.
np!
>
>
>> A future improvement could be to have ovpn proactively notifying userspace
>> after reaching a certain threshold, but for now this mechanism does not
>> exist.
>
> If it's not there from the start, you won't be able to rely on it
> (because the userspace client may run on a kernel that does not
> provide the notification), so you would still have to fetch the stats,
> unless you have a way to poll for the threshold notification feature
> being present.
You're right. Then scratch that because userspace is not ready for this.
>
>
>> I hope it helps.
>
> Yup, thanks. Can you add this explanation to the commit message for
> this patch in the next version? Documenting a bit the expectations of
> the kernel/userspace interactions would be helpful, also for the
> sequencing of key installation/key swap operations. I'm guessing it
> goes something like this:
Sure, will document how this works.
>
> 1. client sets up a primary key (key#1) and uses it
> 2. at some point, it sets up a secondary key (key#2)
> 3. later, keys are swapped (key#2 is now primary)
> 4. after some more time, the secondary (key#1) is removed and a new
> secondary (key#3) is installed
> [steps 3 and 4 keep repeating]
>
> And from reading patch 21, both the TX and RX key seem to be changed
> together (swap and delete operate on the whole keyslot, and set
> requires both the ENCRYPT_DIR and DECRYPT_DIR attributes).
You are correct. Encryption and decryption keys are derived from the
same key material that is exchanged/generated upon each "renegotiation".
>
> A rough description of the overall life of a client (opening sockets
> and setting up the ovpn device/peers) could also be useful alongside
> the code.
>
ok, will add it too!
Thanks!
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 20/25] ovpn: implement peer add/dump/delete via netlink
2024-07-16 13:41 ` Sabrina Dubroca
@ 2024-07-17 14:04 ` Antonio Quartulli
2024-07-17 15:37 ` Sabrina Dubroca
0 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-17 14:04 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
On 16/07/2024 15:41, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:38 +0200, Antonio Quartulli wrote:
>> @@ -29,7 +34,7 @@ MODULE_ALIAS_GENL_FAMILY(OVPN_FAMILY_NAME);
>> * Return: the netdevice, if found, or an error otherwise
>> */
>> static struct net_device *
>> -ovpn_get_dev_from_attrs(struct net *net, struct genl_info *info)
>> +ovpn_get_dev_from_attrs(struct net *net, const struct genl_info *info)
>
> nit: this should be squashed into "add basic netlink support"
Right, thanks
>
>
> [...]
>> int ovpn_nl_set_peer_doit(struct sk_buff *skb, struct genl_info *info)
>> {
>> - return -EOPNOTSUPP;
>> + bool keepalive_set = false, new_peer = false;
>> + struct nlattr *attrs[OVPN_A_PEER_MAX + 1];
>> + struct ovpn_struct *ovpn = info->user_ptr[0];
>> + struct sockaddr_storage *ss = NULL;
>> + u32 sockfd, id, interv, timeout;
>> + struct socket *sock = NULL;
>> + struct sockaddr_in mapped;
>> + struct sockaddr_in6 *in6;
>> + struct ovpn_peer *peer;
>> + u8 *local_ip = NULL;
>> + size_t sa_len;
>> + int ret;
>> +
>> + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER))
>> + return -EINVAL;
>> +
>> + ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER],
>> + ovpn_peer_nl_policy, info->extack);
>> + if (ret)
>> + return ret;
>> +
>> + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs,
>> + OVPN_A_PEER_ID))
>> + return -EINVAL;
>> +
>> + id = nla_get_u32(attrs[OVPN_A_PEER_ID]);
>> + /* check if the peer exists first, otherwise create a new one */
>> + peer = ovpn_peer_get_by_id(ovpn, id);
>> + if (!peer) {
>> + peer = ovpn_peer_new(ovpn, id);
>> + new_peer = true;
>> + if (IS_ERR(peer)) {
>> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
>> + "cannot create new peer object for peer %u (sockaddr=%pIScp): %ld",
>> + id, ss, PTR_ERR(peer));
>
> ss hasn't been set yet at this point, including it in the extack
> message is not useful.
argh. you are correct. I'll remove it.
>
>> + return PTR_ERR(peer);
>> + }
>> + }
>> +
>> + if (new_peer && NL_REQ_ATTR_CHECK(info->extack,
>> + info->attrs[OVPN_A_PEER], attrs,
>> + OVPN_A_PEER_SOCKET)) {
>
> This can be checked at the start of the previous block (!peer), we'd
> avoid a pointless peer allocation.
Right - will move it up.
>
> (and the linebreaks in NL_REQ_ATTR_CHECK end up being slightly better
> because you don't need the "new_peer &&" test that is wider than the
> tab used to indent the !peer block :))
good point! :-D
>
>> + ret = -EINVAL;
>> + goto peer_release;
>> + }
>> +
>> + if (new_peer && ovpn->mode == OVPN_MODE_MP &&
>> + !attrs[OVPN_A_PEER_VPN_IPV4] && !attrs[OVPN_A_PEER_VPN_IPV6]) {
>
> Same for this check.
ACK
>
>> + NL_SET_ERR_MSG_MOD(info->extack,
>> + "a VPN IP is required when adding a peer in MP mode");
>> + ret = -EINVAL;
>> + goto peer_release;
>> + }
>> +
>> + if (attrs[OVPN_A_PEER_SOCKET]) {
>> + /* lookup the fd in the kernel table and extract the socket
>> + * object
>> + */
>> + sockfd = nla_get_u32(attrs[OVPN_A_PEER_SOCKET]);
>> + /* sockfd_lookup() increases sock's refcounter */
>> + sock = sockfd_lookup(sockfd, &ret);
>> + if (!sock) {
>> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
>> + "cannot lookup peer socket (fd=%u): %d",
>> + sockfd, ret);
>> + ret = -ENOTSOCK;
>> + goto peer_release;
>> + }
>> +
>> + if (peer->sock)
>> + ovpn_socket_put(peer->sock);
>> +
>> + peer->sock = ovpn_socket_new(sock, peer);
>> + if (IS_ERR(peer->sock)) {
>> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
>> + "cannot encapsulate socket: %ld",
>> + PTR_ERR(peer->sock));
>> + sockfd_put(sock);
>> + peer->sock = NULL;
>
> Is there any value for the client in keeping the old peer->sock
> assigned if we fail here?
>
> ie something like:
>
> tmp = ovpn_socket_new(sock, peer);
> if (IS_ERR(tmp)) {
> ...
> goto peer_release;
> }
> if (peer->sock)
> ovpn_socket_put(peer->sock);
> peer->sock = tmp;
>
>
> But if it's just going to get rid of the old socket and the whole
> association/peer on failure, probably not.
Right. if attaching the new socket fails, we are entering some broken
status which is not worth keeping around.
>
>> + ret = -ENOTSOCK;
>> + goto peer_release;
>> + }
>> + }
>> +
>> + /* Only when using UDP as transport protocol the remote endpoint
>> + * can be configured so that ovpn knows where to send packets
>> + * to.
>> + *
>> + * In case of TCP, the socket is connected to the peer and ovpn
>> + * will just send bytes over it, without the need to specify a
>> + * destination.
>
> (that should also work with UDP "connected" sockets)
True, but those are not used in openvpn. In case of UDP, userspace just
creates one socket and uses it for all peers.
I will add a note about 'connected UDP socket' in the comment, to clear
this out.
>
>
>> + */
>> + if (peer->sock->sock->sk->sk_protocol == IPPROTO_UDP &&
>> + attrs[OVPN_A_PEER_SOCKADDR_REMOTE]) {
> [...]
>> +
>> + if (attrs[OVPN_A_PEER_LOCAL_IP]) {
>> + local_ip = ovpn_nl_attr_local_ip(info, ovpn,
>> + attrs,
>> + ss->ss_family);
>> + if (IS_ERR(local_ip)) {
>> + ret = PTR_ERR(local_ip);
>> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
>> + "cannot retrieve local IP: %d",
>> + ret);
>
> ovpn_nl_attr_local_ip already sets a more specific extack message,
> this is unnecessary.
right.
>
>> + goto peer_release;
>> + }
>> + }
>> +
>> + /* set peer sockaddr */
>> + ret = ovpn_peer_reset_sockaddr(peer, ss, local_ip);
>> + if (ret < 0) {
>> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
>> + "cannot set peer sockaddr: %d",
>> + ret);
>> + goto peer_release;
>> + }
>> + }
>
> I would reject OVPN_A_PEER_SOCKADDR_REMOTE for a non-UDP socket.
judging from the comments below, it seems you prefer to reject unneeded
attributes. OTOH I took the opposite approach (just ignore those).
However, I was actually looking for some preference/indication regarding
this point and I now I got one :-)
I will be strict and return -EINVAL when unneded attributes are present.
>
>
>> + /* VPN IPs cannot be updated, because they are hashed */
>
> Then I think there should be something like
>
> if (!new_peer && (attrs[OVPN_A_PEER_VPN_IPV4] || attrs[OVPN_A_PEER_VPN_IPV6])) {
> NL_SET_ERR_MSG_FMT_MOD(... "can't update ip");
> ret = -EINVAL;
> goto peer_release;
> }
>
> (just after getting the peer, before any changes have actually been
> made)
ACK
>
> And if they are only used in MP mode, I would maybe also reject
> requests where mode==P2P and OVPN_A_PEER_VPN_IPV* is provided.
yup, like I commented above.
>
>
>> + if (new_peer && attrs[OVPN_A_PEER_VPN_IPV4])
>> + peer->vpn_addrs.ipv4.s_addr =
>> + nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]);
>> +
>> + /* VPN IPs cannot be updated, because they are hashed */
>> + if (new_peer && attrs[OVPN_A_PEER_VPN_IPV6])
>> + peer->vpn_addrs.ipv6 =
>> + nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]);
>> +
>> + /* when setting the keepalive, both parameters have to be configured */
>
> Then I would also reject a config where only one is set (also before any
> changes have been made).
ok
>
>> + if (attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL] &&
>> + attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]) {
>> + keepalive_set = true;
>> + interv = nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]);
>> + timeout = nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]);
>> + }
>> +
>> + if (keepalive_set)
>> + ovpn_peer_keepalive_set(peer, interv, timeout);
>
> Why not skip the bool and just do this in the previous block?
I am pretty sure there was a reason...but it may have faded away after
the 95-th rebase hehe. Thanks for spotting this!
>
>> + netdev_dbg(ovpn->dev,
>> + "%s: %s peer with endpoint=%pIScp/%s id=%u VPN-IPv4=%pI4 VPN-IPv6=%pI6c\n",
>> + __func__, (new_peer ? "adding" : "modifying"), ss,
>> + peer->sock->sock->sk->sk_prot_creator->name, peer->id,
>> + &peer->vpn_addrs.ipv4.s_addr, &peer->vpn_addrs.ipv6);
>> +
>> + if (new_peer) {
>> + ret = ovpn_peer_add(ovpn, peer);
>> + if (ret < 0) {
>> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
>> + "cannot add new peer (id=%u) to hashtable: %d\n",
>> + peer->id, ret);
>> + goto peer_release;
>> + }
>> + } else {
>> + ovpn_peer_put(peer);
>> + }
>> +
>> + return 0;
>> +
>> +peer_release:
>> + if (new_peer) {
>> + /* release right away because peer is not really used in any
>> + * context
>> + */
>> + ovpn_peer_release(peer);
>> + kfree(peer);
>
> I don't think that's correct, the new peer was created with
> ovpn_peer_new, so it took a reference on the netdevice
> (netdev_hold(ovpn->dev, ...)), which isn't released by
> ovpn_peer_release. Why not just go through ovpn_peer_put?
Because then we would send the notification to userspace, but it is not
correct to do so, because the new() is just about to return an error.
I presume I should just move netdev_put(peer->ovpn->dev, NULL); to
ovpn_peer_release(). That will take care of this case too.
>
>> + } else {
>> + ovpn_peer_put(peer);
>> + }
>> +
>> + return ret;
>> +}
>> +
>
> [...]
>> int ovpn_nl_get_peer_doit(struct sk_buff *skb, struct genl_info *info)
>> {
> [...]
>> + peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]);
>> + peer = ovpn_peer_get_by_id(ovpn, peer_id);
>> + if (!peer) {
>> + NL_SET_ERR_MSG_FMT_MOD(info->extack,
>> + "cannot find peer with id %u", peer_id);
>> + return -ENOENT;
>> + }
>> +
>> + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
>> + if (!msg)
>
> Missing ovpn_peer_put?
indeed. have to set ret and goto err;
>
>> + return -ENOMEM;
>> +
>> + ret = ovpn_nl_send_peer(msg, info, peer, info->snd_portid,
>> + info->snd_seq, 0);
>> + if (ret < 0) {
>> + nlmsg_free(msg);
>> + goto err;
>> + }
>> +
>> + ret = genlmsg_reply(msg, info);
>> +err:
>> + ovpn_peer_put(peer);
>> + return ret;
>> }
>
Thanks!
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 15/25] ovpn: implement multi-peer support
2024-07-15 10:40 ` Sabrina Dubroca
@ 2024-07-17 14:05 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-17 14:05 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
Hi,
On 15/07/2024 12:40, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:33 +0200, Antonio Quartulli wrote:
>> @@ -46,6 +46,17 @@ static int ovpn_struct_init(struct net_device *dev, enum ovpn_mode mode)
>> ovpn->mode = mode;
>> spin_lock_init(&ovpn->lock);
>>
>> + if (mode == OVPN_MODE_MP) {
>> + /* the peer container is fairly large, therefore we dynamically
>> + * allocate it only when needed
>> + */
>> + ovpn->peers = kzalloc(sizeof(*ovpn->peers), GFP_KERNEL);
>> + if (!ovpn->peers)
>> + return -ENOMEM;
>
> As we discussed in v3 (it was about the workqueue), I don't think this
> will get freed correctly in some of the failure cases. This should go
> in ovpn_net_init.
Argh. Right. I will move it there.
>
>> +
>> + spin_lock_init(&ovpn->peers->lock);
>> + }
>> +
>> return 0;
>> }
>
Thanks!
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 16/25] ovpn: implement peer lookup logic
2024-07-15 13:11 ` Sabrina Dubroca
@ 2024-07-17 14:07 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-17 14:07 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
Hi,
On 15/07/2024 15:11, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:34 +0200, Antonio Quartulli wrote:
>> /**
>> * ovpn_peer_check_by_src - check that skb source is routed via peer
>> * @ovpn: the openvpn instance to search
>> * @skb: the packet to extra source address from
>
> nit, just noticed now but should be fixed in patch 12: s/to extra/to extract/
argh. thanks!
>
> [...]
>> @@ -324,11 +576,11 @@ static int ovpn_peer_add_mp(struct ovpn_struct *ovpn, struct ovpn_peer *peer)
>> struct sockaddr_storage sa = { 0 };
>> struct sockaddr_in6 *sa6;
>> struct sockaddr_in *sa4;
>> + struct hlist_head *head;
>> struct ovpn_bind *bind;
>> struct ovpn_peer *tmp;
>> size_t salen;
>> int ret = 0;
>> - u32 index;
>>
>> spin_lock_bh(&ovpn->peers->lock);
>> /* do not add duplicates */
>> @@ -364,30 +616,27 @@ static int ovpn_peer_add_mp(struct ovpn_struct *ovpn, struct ovpn_peer *peer)
>> goto unlock;
>> }
>>
>> - index = ovpn_peer_index(ovpn->peers->by_transp_addr, &sa,
>> - salen);
>> - hlist_add_head_rcu(&peer->hash_entry_transp_addr,
>> - &ovpn->peers->by_transp_addr[index]);
>> + head = ovpn_get_hash_head(ovpn->peers->by_transp_addr, &sa,
>> + salen);
>> + hlist_add_head_rcu(&peer->hash_entry_transp_addr, head);
>> }
>
> These changes to ovpn_peer_add_mp (and the replacement of
> ovpn_peer_index with ovpn_get_hash_head) could be squashed into the
> previous patch.
hmm true. will do.
Thanks!
>
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism
2024-07-15 14:44 ` Sabrina Dubroca
@ 2024-07-17 15:30 ` Antonio Quartulli
2024-07-17 16:19 ` Eyal Birger
` (2 more replies)
0 siblings, 3 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-17 15:30 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
Hi,
On 15/07/2024 16:44, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:35 +0200, Antonio Quartulli wrote:
>> +static const unsigned char ovpn_keepalive_message[] = {
>> + 0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
>> + 0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48
>> +};
>> +
>> +/**
>> + * ovpn_is_keepalive - check if skb contains a keepalive message
>> + * @skb: packet to check
>> + *
>> + * Assumes that the first byte of skb->data is defined.
>> + *
>> + * Return: true if skb contains a keepalive or false otherwise
>> + */
>> +static bool ovpn_is_keepalive(struct sk_buff *skb)
>> +{
>> + if (*skb->data != OVPN_KEEPALIVE_FIRST_BYTE)
>
> You could use ovpn_keepalive_message[0], and then you wouldn't need
> this extra constant.
Indeed, shame on me, will do as suggested
>
>> + return false;
>> +
>> + if (!pskb_may_pull(skb, sizeof(ovpn_keepalive_message)))
>> + return false;
>> +
>> + return !memcmp(skb->data, ovpn_keepalive_message,
>> + sizeof(ovpn_keepalive_message));
>
> Is a packet that contains some extra bytes after the exact keepalive
> considered a valid keepalive, or does it need to be the correct
> length?
I checked the userspace code and it assumes the length of the received
keepalive message to be the same as the ovpn_keepalive_message array.
So no extra byte expected, otherwise the message is not considered a
keepalive anymore.
This means I must add an extra check before the memcmp to make sure
there is no extra data.
Good catch, thanks!
>
>> +}
>> +
>> /* Called after decrypt to write the IP packet to the device.
>> * This method is expected to manage/free the skb.
>> */
>> @@ -91,6 +116,9 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
>> goto drop;
>> }
>>
>> + /* note event of authenticated packet received for keepalive */
>> + ovpn_peer_keepalive_recv_reset(peer);
>> +
>> /* point to encapsulated IP packet */
>> __skb_pull(skb, ovpn_skb_cb(skb)->payload_offset);
>>
>> @@ -107,6 +135,12 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
>> goto drop;
>> }
>>
>> + if (ovpn_is_keepalive(skb)) {
>> + netdev_dbg(peer->ovpn->dev,
>> + "ping received from peer %u\n", peer->id);
>
> That should probably be _ratelimited, but it seems we don't have
> _ratelimited variants for the netdev_* helpers.
Right.
I have used the net_*_ratelimited() variants when needed.
Too bad we don't have those.
>
>
>
>> +/**
>> + * ovpn_xmit_special - encrypt and transmit an out-of-band message to peer
>> + * @peer: peer to send the message to
>> + * @data: message content
>> + * @len: message length
>> + *
>> + * Assumes that caller holds a reference to peer
>> + */
>> +static void ovpn_xmit_special(struct ovpn_peer *peer, const void *data,
>> + const unsigned int len)
>> +{
>> + struct ovpn_struct *ovpn;
>> + struct sk_buff *skb;
>> +
>> + ovpn = peer->ovpn;
>> + if (unlikely(!ovpn))
>> + return;
>> +
>> + skb = alloc_skb(256 + len, GFP_ATOMIC);
>
> Where is that 256 coming from?
"Reasonable number" which should be enough[tm] to hold the entire packet.
>
>> + if (unlikely(!skb))
>> + return;
>
> Failure to send a keepalive should probably have a counter, to help
> users troubleshoot why their connection dropped.
> (can be done later unless someone insists)
This will be part of a more sophisticated error counting that I will
introduce later on.
>
>
>> + skb_reserve(skb, 128);
>
> And that 128?
same "logic" as 256.
>
>> + skb->priority = TC_PRIO_BESTEFFORT;
>> + memcpy(__skb_put(skb, len), data, len);
>
> nit: that's __skb_put_data
oh cool, thanks!
>
>> + /* increase reference counter when passing peer to sending queue */
>> + if (!ovpn_peer_hold(peer)) {
>> + netdev_dbg(ovpn->dev, "%s: cannot hold peer reference for sending special packet\n",
>> + __func__);
>> + kfree_skb(skb);
>> + return;
>> + }
>> +
>> + ovpn_send(ovpn, skb, peer);
>> +}
>> +
>> +/**
>> + * ovpn_keepalive_xmit - send keepalive message to peer
>> + * @peer: the peer to send the message to
>> + */
>> +void ovpn_keepalive_xmit(struct ovpn_peer *peer)
>> +{
>> + ovpn_xmit_special(peer, ovpn_keepalive_message,
>> + sizeof(ovpn_keepalive_message));
>> +}
>
> I don't see other users of ovpn_xmit_special in this series, if you
> don't have more planned in the future you could drop the extra function.
initially there were plans, but I have always fought back any idea about
adding more unnecessary logic to the kernel side. So for now there is
nothing planned.
I'll remove the extra wrapper.
>
>
>> +/**
>> + * ovpn_peer_expire - timer task for incoming keepialive timeout
>
> typo: s/keepialive/keepalive/
Thanks
>
>
>
>> +/**
>> + * ovpn_peer_keepalive_set - configure keepalive values for peer
>> + * @peer: the peer to configure
>> + * @interval: outgoing keepalive interval
>> + * @timeout: incoming keepalive timeout
>> + */
>> +void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout)
>> +{
>> + u32 delta;
>> +
>> + netdev_dbg(peer->ovpn->dev,
>> + "%s: scheduling keepalive for peer %u: interval=%u timeout=%u\n",
>> + __func__, peer->id, interval, timeout);
>> +
>> + peer->keepalive_interval = interval;
>> + if (interval > 0) {
>> + delta = msecs_to_jiffies(interval * MSEC_PER_SEC);
>> + mod_timer(&peer->keepalive_xmit, jiffies + delta);
>
> Maybe something to consider in the future: this could be resetting a
> timer that was just about to go off to a somewhat distant time in the
> future. Not sure the peer will be happy about that (and not consider
> it a timeout).
Normally this timer is only set upon connection, or maybe upon some
future parameter exchange. In both cases we can assume the connection is
alive, so this case should not scare us.
But thanks for pointing it out
>
>> + } else {
>> + timer_delete(&peer->keepalive_xmit);
>> + }
>> +
>> + peer->keepalive_timeout = timeout;
>> + if (timeout) {
>
> pedantic nit: inconsistent style with the "interval > 0" test just
> above
ACK, will make them uniform.
>
>> + delta = msecs_to_jiffies(timeout * MSEC_PER_SEC);
>> + mod_timer(&peer->keepalive_recv, jiffies + delta);
>> + } else {
>> + timer_delete(&peer->keepalive_recv);
>> + }
>> +}
>> +
>
> [...]
>> +/**
>> + * ovpn_peer_keepalive_recv_reset - reset keepalive timeout
>> + * @peer: peer for which the timeout should be reset
>> + *
>> + * To be invoked upon reception of an authenticated packet from peer in order
>> + * to report valid activity and thus reset the keepalive timeout
>> + */
>> +static inline void ovpn_peer_keepalive_recv_reset(struct ovpn_peer *peer)
>> +{
>> + u32 delta = msecs_to_jiffies(peer->keepalive_timeout * MSEC_PER_SEC);
>> +
>> + if (unlikely(!delta))
>> + return;
>> +
>> + mod_timer(&peer->keepalive_recv, jiffies + delta);
>
> This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
> each packet. I wonder how well the timer subsystem deals with one
> timer getting updated possibly thousands of time per second.
>
May it even introduce some performance penalty?
Maybe we should get rid of the timer object and introduce a periodic
(1s) worker which checks some last_recv timestamp on every known peer?
What do you think?
Thanks!
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 20/25] ovpn: implement peer add/dump/delete via netlink
2024-07-17 14:04 ` Antonio Quartulli
@ 2024-07-17 15:37 ` Sabrina Dubroca
0 siblings, 0 replies; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-17 15:37 UTC (permalink / raw)
To: Antonio Quartulli, kuba, pabeni, edumazet; +Cc: netdev, ryazanov.s.a, andrew
2024-07-17, 16:04:25 +0200, Antonio Quartulli wrote:
> On 16/07/2024 15:41, Sabrina Dubroca wrote:
> > 2024-06-27, 15:08:38 +0200, Antonio Quartulli wrote:
> > > + if (attrs[OVPN_A_PEER_SOCKET]) {
> > > + /* lookup the fd in the kernel table and extract the socket
> > > + * object
> > > + */
> > > + sockfd = nla_get_u32(attrs[OVPN_A_PEER_SOCKET]);
> > > + /* sockfd_lookup() increases sock's refcounter */
> > > + sock = sockfd_lookup(sockfd, &ret);
> > > + if (!sock) {
> > > + NL_SET_ERR_MSG_FMT_MOD(info->extack,
> > > + "cannot lookup peer socket (fd=%u): %d",
> > > + sockfd, ret);
> > > + ret = -ENOTSOCK;
> > > + goto peer_release;
> > > + }
> > > +
> > > + if (peer->sock)
> > > + ovpn_socket_put(peer->sock);
> > > +
> > > + peer->sock = ovpn_socket_new(sock, peer);
> > > + if (IS_ERR(peer->sock)) {
> > > + NL_SET_ERR_MSG_FMT_MOD(info->extack,
> > > + "cannot encapsulate socket: %ld",
> > > + PTR_ERR(peer->sock));
> > > + sockfd_put(sock);
> > > + peer->sock = NULL;
> >
> > Is there any value for the client in keeping the old peer->sock
> > assigned if we fail here?
> >
> > ie something like:
> >
> > tmp = ovpn_socket_new(sock, peer);
> > if (IS_ERR(tmp)) {
> > ...
> > goto peer_release;
> > }
> > if (peer->sock)
> > ovpn_socket_put(peer->sock);
> > peer->sock = tmp;
> >
> >
> > But if it's just going to get rid of the old socket and the whole
> > association/peer on failure, probably not.
>
> Right. if attaching the new socket fails, we are entering some broken status
> which is not worth keeping around.
Ok, then the current code is fine, thanks.
> > > + /* Only when using UDP as transport protocol the remote endpoint
> > > + * can be configured so that ovpn knows where to send packets
> > > + * to.
> > > + *
> > > + * In case of TCP, the socket is connected to the peer and ovpn
> > > + * will just send bytes over it, without the need to specify a
> > > + * destination.
> >
> > (that should also work with UDP "connected" sockets)
>
> True, but those are not used in openvpn. In case of UDP, userspace just
> creates one socket and uses it for all peers.
> I will add a note about 'connected UDP socket' in the comment, to clear this
> out.
If you want. I was being pedantic, I don't think it's really necessary
to mention this.
> > > + goto peer_release;
> > > + }
> > > + }
> > > +
> > > + /* set peer sockaddr */
> > > + ret = ovpn_peer_reset_sockaddr(peer, ss, local_ip);
> > > + if (ret < 0) {
> > > + NL_SET_ERR_MSG_FMT_MOD(info->extack,
> > > + "cannot set peer sockaddr: %d",
> > > + ret);
> > > + goto peer_release;
> > > + }
> > > + }
> >
> > I would reject OVPN_A_PEER_SOCKADDR_REMOTE for a non-UDP socket.
>
> judging from the comments below, it seems you prefer to reject unneeded
> attributes. OTOH I took the opposite approach (just ignore those).
Yes.
> However, I was actually looking for some preference/indication regarding
> this point and I now I got one :-)
I don't think there's an established rule, and a lot of the old code
is very tolerant. That's my preference (in part because I think
refusing bogus combinations allows to enable them in the future with a
new behavior), but maybe the maintainers have a different opinion?
OTOH ignoring those attributes can let a modern client run on an old
kernel (possibly without some features, depending on what the
attribute is).
(leaving a few other examples of stricter validation for context:)
> I will be strict and return -EINVAL when unneded attributes are present.
>
> >
> >
> > > + /* VPN IPs cannot be updated, because they are hashed */
> >
> > Then I think there should be something like
> >
> > if (!new_peer && (attrs[OVPN_A_PEER_VPN_IPV4] || attrs[OVPN_A_PEER_VPN_IPV6])) {
> > NL_SET_ERR_MSG_FMT_MOD(... "can't update ip");
> > ret = -EINVAL;
> > goto peer_release;
> > }
> >
> > (just after getting the peer, before any changes have actually been
> > made)
>
> ACK
>
> >
> > And if they are only used in MP mode, I would maybe also reject
> > requests where mode==P2P and OVPN_A_PEER_VPN_IPV* is provided.
>
> yup, like I commented above.
>
> >
> >
> > > + if (new_peer && attrs[OVPN_A_PEER_VPN_IPV4])
> > > + peer->vpn_addrs.ipv4.s_addr =
> > > + nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]);
> > > +
> > > + /* VPN IPs cannot be updated, because they are hashed */
> > > + if (new_peer && attrs[OVPN_A_PEER_VPN_IPV6])
> > > + peer->vpn_addrs.ipv6 =
> > > + nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]);
> > > +
> > > + /* when setting the keepalive, both parameters have to be configured */
> >
> > Then I would also reject a config where only one is set (also before any
> > changes have been made).
>
> ok
[...]
> > > + if (attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL] &&
> > > + attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]) {
> > > + keepalive_set = true;
> > > + interv = nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]);
> > > + timeout = nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]);
> > > + }
> > > +
> > > + if (keepalive_set)
> > > + ovpn_peer_keepalive_set(peer, interv, timeout);
> >
> > Why not skip the bool and just do this in the previous block?
>
> I am pretty sure there was a reason...but it may have faded away after the
> 95-th rebase hehe. Thanks for spotting this!
:)
>
> >
> > > + netdev_dbg(ovpn->dev,
> > > + "%s: %s peer with endpoint=%pIScp/%s id=%u VPN-IPv4=%pI4 VPN-IPv6=%pI6c\n",
> > > + __func__, (new_peer ? "adding" : "modifying"), ss,
> > > + peer->sock->sock->sk->sk_prot_creator->name, peer->id,
> > > + &peer->vpn_addrs.ipv4.s_addr, &peer->vpn_addrs.ipv6);
> > > +
> > > + if (new_peer) {
> > > + ret = ovpn_peer_add(ovpn, peer);
> > > + if (ret < 0) {
> > > + NL_SET_ERR_MSG_FMT_MOD(info->extack,
> > > + "cannot add new peer (id=%u) to hashtable: %d\n",
> > > + peer->id, ret);
> > > + goto peer_release;
> > > + }
> > > + } else {
> > > + ovpn_peer_put(peer);
> > > + }
> > > +
> > > + return 0;
> > > +
> > > +peer_release:
> > > + if (new_peer) {
> > > + /* release right away because peer is not really used in any
> > > + * context
> > > + */
> > > + ovpn_peer_release(peer);
> > > + kfree(peer);
> >
> > I don't think that's correct, the new peer was created with
> > ovpn_peer_new, so it took a reference on the netdevice
> > (netdev_hold(ovpn->dev, ...)), which isn't released by
> > ovpn_peer_release. Why not just go through ovpn_peer_put?
>
> Because then we would send the notification to userspace, but it is not
> correct to do so, because the new() is just about to return an error.
Oh, right.
> I presume I should just move netdev_put(peer->ovpn->dev, NULL); to
> ovpn_peer_release(). That will take care of this case too.
Ok.
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism
2024-07-17 15:30 ` Antonio Quartulli
@ 2024-07-17 16:19 ` Eyal Birger
2024-07-18 8:20 ` Antonio Quartulli
2024-07-17 20:40 ` Sabrina Dubroca
2024-07-18 2:01 ` Andrew Lunn
2 siblings, 1 reply; 71+ messages in thread
From: Eyal Birger @ 2024-07-17 16:19 UTC (permalink / raw)
To: Antonio Quartulli
Cc: Sabrina Dubroca, netdev, kuba, ryazanov.s.a, pabeni, edumazet,
andrew
Hi,
On Wed, Jul 17, 2024 at 8:29 AM Antonio Quartulli <antonio@openvpn.net> wrote:
>
> On 15/07/2024 16:44, Sabrina Dubroca wrote:
> > This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
> > each packet. I wonder how well the timer subsystem deals with one
> > timer getting updated possibly thousands of time per second.
> >
>
> May it even introduce some performance penalty?
>
> Maybe we should get rid of the timer object and introduce a periodic
> (1s) worker which checks some last_recv timestamp on every known peer?
> What do you think?
FWIW In NATT keepalive for IPsec the first RFC was using timers and
the workqueue
approach was suggested [1], and later implemented [2].
The code is currently in net-next.
Eyal.
[1] https://linux-ipsec.org/pipermail/devel/2023/000283.html
[2] https://patchwork.kernel.org/project/netdevbpf/patch/20240528032914.2551267-1-eyal.birger@gmail.com/
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 19/25] ovpn: add support for peer floating
2024-06-27 13:08 ` [PATCH net-next v5 19/25] ovpn: add support for peer floating Antonio Quartulli
@ 2024-07-17 17:15 ` Sabrina Dubroca
2024-07-18 9:37 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-17 17:15 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:37 +0200, Antonio Quartulli wrote:
> +void ovpn_peer_float(struct ovpn_peer *peer, struct sk_buff *skb)
> +{
> + struct sockaddr_storage ss;
> + const u8 *local_ip = NULL;
> + struct sockaddr_in6 *sa6;
> + struct sockaddr_in *sa;
> + struct ovpn_bind *bind;
> + sa_family_t family;
> + size_t salen;
> +
> + rcu_read_lock();
> + bind = rcu_dereference(peer->bind);
> + if (unlikely(!bind))
> + goto unlock;
Why are you aborting here? ovpn_bind_skb_src_match considers
bind==NULL to be "no match" (reasonable), then we would create a new
bind for the current address.
> +
> + if (likely(ovpn_bind_skb_src_match(bind, skb)))
This could be running in parallel on two CPUs, because ->encap_rcv
isn't protected against that. So the bind could be getting updated in
parallel. I would move spin_lock_bh above this check to make sure it
doesn't happen.
ovpn_peer_update_local_endpoint would also need something like that, I
think.
> + goto unlock;
> +
> + family = skb_protocol_to_family(skb);
> +
> + if (bind->sa.in4.sin_family == family)
> + local_ip = (u8 *)&bind->local;
> +
> + switch (family) {
> + case AF_INET:
> + 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;
> + case AF_INET6:
> + 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);
> + break;
> + default:
> + goto unlock;
> + }
> +
> + netdev_dbg(peer->ovpn->dev, "%s: peer %d floated to %pIScp", __func__,
> + peer->id, &ss);
> + ovpn_peer_reset_sockaddr(peer, (struct sockaddr_storage *)&ss,
> + local_ip);
> +
> + spin_lock_bh(&peer->ovpn->peers->lock);
> + /* remove old hashing */
> + hlist_del_init_rcu(&peer->hash_entry_transp_addr);
> + /* re-add with new transport address */
> + hlist_add_head_rcu(&peer->hash_entry_transp_addr,
> + ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr,
> + &ss, salen));
That could send a concurrent reader onto the wrong hash bucket, if
it's going through peer's old bucket, finds peer before the update,
then continues reading after peer is moved to the new bucket.
This kind of re-hash can be handled with nulls, and re-trying the
lookup if we ended up on the wrong chain. See for example
__inet_lookup_established in net/ipv4/inet_hashtables.c (Thanks to
Paolo for the pointer).
> + spin_unlock_bh(&peer->ovpn->peers->lock);
> +
> +unlock:
> + rcu_read_unlock();
> +}
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 21/25] ovpn: implement key add/del/swap via netlink
2024-06-27 13:08 ` [PATCH net-next v5 21/25] ovpn: implement key add/del/swap " Antonio Quartulli
@ 2024-07-17 17:17 ` Sabrina Dubroca
2024-07-18 8:29 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-17 17:17 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:39 +0200, Antonio Quartulli wrote:
> This change introduces the netlink commands needed to add, delete and
> swap keys for a specific peer.
>
> Userspace is expected to use these commands to create, destroy and
> rotate session keys for a specific peer.
>
> Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
> ---
> Documentation/netlink/specs/ovpn.yaml | 2 +-
> drivers/net/ovpn/netlink-gen.c | 2 +-
> drivers/net/ovpn/netlink.c | 199 +++++++++++++++++++++++++-
> 3 files changed, 198 insertions(+), 5 deletions(-)
>
> diff --git a/Documentation/netlink/specs/ovpn.yaml b/Documentation/netlink/specs/ovpn.yaml
> index 68ed88d03732..21c89f0bdcbb 100644
> --- a/Documentation/netlink/specs/ovpn.yaml
> +++ b/Documentation/netlink/specs/ovpn.yaml
> @@ -153,7 +153,7 @@ attribute-sets:
> decryption
> type: u32
> checks:
> - max: 2
> + max: 7
Looks like this got squashed into the wrong patch.
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism
2024-07-17 15:30 ` Antonio Quartulli
2024-07-17 16:19 ` Eyal Birger
@ 2024-07-17 20:40 ` Sabrina Dubroca
2024-07-18 8:22 ` Antonio Quartulli
2024-07-18 2:01 ` Andrew Lunn
2 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-17 20:40 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-07-17, 17:30:17 +0200, Antonio Quartulli wrote:
> Hi,
>
> On 15/07/2024 16:44, Sabrina Dubroca wrote:
> > 2024-06-27, 15:08:35 +0200, Antonio Quartulli wrote:
> > > +/**
> > > + * ovpn_xmit_special - encrypt and transmit an out-of-band message to peer
> > > + * @peer: peer to send the message to
> > > + * @data: message content
> > > + * @len: message length
> > > + *
> > > + * Assumes that caller holds a reference to peer
> > > + */
> > > +static void ovpn_xmit_special(struct ovpn_peer *peer, const void *data,
> > > + const unsigned int len)
> > > +{
> > > + struct ovpn_struct *ovpn;
> > > + struct sk_buff *skb;
> > > +
> > > + ovpn = peer->ovpn;
> > > + if (unlikely(!ovpn))
> > > + return;
> > > +
> > > + skb = alloc_skb(256 + len, GFP_ATOMIC);
> >
> > Where is that 256 coming from?
>
> "Reasonable number" which should be enough[tm] to hold the entire packet.
Ok, let's go with that for now, unless someone else wants you to
change it.
> > > + if (unlikely(!skb))
> > > + return;
> >
> > Failure to send a keepalive should probably have a counter, to help
> > users troubleshoot why their connection dropped.
> > (can be done later unless someone insists)
>
> This will be part of a more sophisticated error counting that I will
> introduce later on.
Cool, thanks.
> > > +/**
> > > + * ovpn_peer_keepalive_set - configure keepalive values for peer
> > > + * @peer: the peer to configure
> > > + * @interval: outgoing keepalive interval
> > > + * @timeout: incoming keepalive timeout
> > > + */
> > > +void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout)
> > > +{
> > > + u32 delta;
> > > +
> > > + netdev_dbg(peer->ovpn->dev,
> > > + "%s: scheduling keepalive for peer %u: interval=%u timeout=%u\n",
> > > + __func__, peer->id, interval, timeout);
> > > +
> > > + peer->keepalive_interval = interval;
> > > + if (interval > 0) {
> > > + delta = msecs_to_jiffies(interval * MSEC_PER_SEC);
> > > + mod_timer(&peer->keepalive_xmit, jiffies + delta);
> >
> > Maybe something to consider in the future: this could be resetting a
> > timer that was just about to go off to a somewhat distant time in the
> > future. Not sure the peer will be happy about that (and not consider
> > it a timeout).
>
> Normally this timer is only set upon connection, or maybe upon some future
> parameter exchange. In both cases we can assume the connection is alive, so
> this case should not scare us.
>
> But thanks for pointing it out
Ok, I was thinking about updates while the connection is fully
established. If it's only done during setup, it shouldn't be a
problem.
> > > +/**
> > > + * ovpn_peer_keepalive_recv_reset - reset keepalive timeout
> > > + * @peer: peer for which the timeout should be reset
> > > + *
> > > + * To be invoked upon reception of an authenticated packet from peer in order
> > > + * to report valid activity and thus reset the keepalive timeout
> > > + */
> > > +static inline void ovpn_peer_keepalive_recv_reset(struct ovpn_peer *peer)
> > > +{
> > > + u32 delta = msecs_to_jiffies(peer->keepalive_timeout * MSEC_PER_SEC);
> > > +
> > > + if (unlikely(!delta))
> > > + return;
> > > +
> > > + mod_timer(&peer->keepalive_recv, jiffies + delta);
> >
> > This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
> > each packet. I wonder how well the timer subsystem deals with one
> > timer getting updated possibly thousands of time per second.
> >
>
> May it even introduce some performance penalty?
That's what I was worried about, yes.
I asked Paolo, he suggested checking that we're actually doing any
change to the timer:
if (new_timeout_time != old_timeout_time)
mod_timer(...)
This would reduce the update frequency to one per jiffy, which should
be acceptable.
> Maybe we should get rid of the timer object and introduce a periodic (1s)
> worker which checks some last_recv timestamp on every known peer?
> What do you think?
That should work, or the workqueue like Eyal is saying.
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism
2024-07-17 15:30 ` Antonio Quartulli
2024-07-17 16:19 ` Eyal Birger
2024-07-17 20:40 ` Sabrina Dubroca
@ 2024-07-18 2:01 ` Andrew Lunn
2024-07-18 7:46 ` Antonio Quartulli
2 siblings, 1 reply; 71+ messages in thread
From: Andrew Lunn @ 2024-07-18 2:01 UTC (permalink / raw)
To: Antonio Quartulli
Cc: Sabrina Dubroca, netdev, kuba, ryazanov.s.a, pabeni, edumazet
> > > + if (ovpn_is_keepalive(skb)) {
> > > + netdev_dbg(peer->ovpn->dev,
> > > + "ping received from peer %u\n", peer->id);
> >
> > That should probably be _ratelimited, but it seems we don't have
> > _ratelimited variants for the netdev_* helpers.
>
> Right.
> I have used the net_*_ratelimited() variants when needed.
> Too bad we don't have those.
If you think netdev_dbg_ratelimited() would be useful, i don't see why
you cannot add it.
I just did an search and found something interesting in the history:
https://lore.kernel.org/all/20190809002941.15341-1-liuhangbin@gmail.com/T/#u
Maybe limit it to netdev_dbg_ratelimited() to avoid the potential
abuse DaveM was worried about.
Andrew
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism
2024-07-18 2:01 ` Andrew Lunn
@ 2024-07-18 7:46 ` Antonio Quartulli
2024-07-19 3:31 ` Andrew Lunn
0 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-18 7:46 UTC (permalink / raw)
To: Andrew Lunn; +Cc: Sabrina Dubroca, netdev, kuba, ryazanov.s.a, pabeni, edumazet
On 18/07/2024 04:01, Andrew Lunn wrote:
>>>> + if (ovpn_is_keepalive(skb)) {
>>>> + netdev_dbg(peer->ovpn->dev,
>>>> + "ping received from peer %u\n", peer->id);
>>>
>>> That should probably be _ratelimited, but it seems we don't have
>>> _ratelimited variants for the netdev_* helpers.
>>
>> Right.
>> I have used the net_*_ratelimited() variants when needed.
>> Too bad we don't have those.
>
> If you think netdev_dbg_ratelimited() would be useful, i don't see why
> you cannot add it.
>
> I just did an search and found something interesting in the history:
>
> https://lore.kernel.org/all/20190809002941.15341-1-liuhangbin@gmail.com/T/#u
>
> Maybe limit it to netdev_dbg_ratelimited() to avoid the potential
> abuse DaveM was worried about.
I see what Dave says however...
...along the packet processing routine there are several messages (some
are err or warn or info) which require ratelimiting.
Otherwise you end up with a gazilion log entries in case of a long
lasting issue.
Right now I am using net_dbg/warn/err/info_ratelimited(), therefore not
having a netdev counterpart is not really helping with Dave's argument.
I can try to take on this mission after this patchset is in and see what
Dave/Jakub think about it.
Regards,
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism
2024-07-17 16:19 ` Eyal Birger
@ 2024-07-18 8:20 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-18 8:20 UTC (permalink / raw)
To: Eyal Birger
Cc: Sabrina Dubroca, netdev, kuba, ryazanov.s.a, pabeni, edumazet,
andrew
Hi Eyal,
thanks a lot for chiming in.
On 17/07/2024 18:19, Eyal Birger wrote:
> Hi,
>
> On Wed, Jul 17, 2024 at 8:29 AM Antonio Quartulli <antonio@openvpn.net> wrote:
>>
>> On 15/07/2024 16:44, Sabrina Dubroca wrote:
>
>>> This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
>>> each packet. I wonder how well the timer subsystem deals with one
>>> timer getting updated possibly thousands of time per second.
>>>
>>
>> May it even introduce some performance penalty?
>>
>> Maybe we should get rid of the timer object and introduce a periodic
>> (1s) worker which checks some last_recv timestamp on every known peer?
>> What do you think?
>
> FWIW In NATT keepalive for IPsec the first RFC was using timers and
> the workqueue
> approach was suggested [1], and later implemented [2].
> The code is currently in net-next.
>
> Eyal.
>
> [1] https://linux-ipsec.org/pipermail/devel/2023/000283.html
> [2] https://patchwork.kernel.org/project/netdevbpf/patch/20240528032914.2551267-1-eyal.birger@gmail.com/
Thanks for these pointers.
Basically this is pretty much what I had in mind, but rather than
running the worker every second, the next run is scheduled based on the
closest expiration time.
I like it!
Since this worker has no other housekeeping to do, I will go with this
approach as well.
Thanks!
Regards,
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism
2024-07-17 20:40 ` Sabrina Dubroca
@ 2024-07-18 8:22 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-18 8:22 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
On 17/07/2024 22:40, Sabrina Dubroca wrote:
>>>> +/**
>>>> + * ovpn_peer_keepalive_recv_reset - reset keepalive timeout
>>>> + * @peer: peer for which the timeout should be reset
>>>> + *
>>>> + * To be invoked upon reception of an authenticated packet from peer in order
>>>> + * to report valid activity and thus reset the keepalive timeout
>>>> + */
>>>> +static inline void ovpn_peer_keepalive_recv_reset(struct ovpn_peer *peer)
>>>> +{
>>>> + u32 delta = msecs_to_jiffies(peer->keepalive_timeout * MSEC_PER_SEC);
>>>> +
>>>> + if (unlikely(!delta))
>>>> + return;
>>>> +
>>>> + mod_timer(&peer->keepalive_recv, jiffies + delta);
>>>
>>> This (and ovpn_peer_keepalive_xmit_reset) is going to be called for
>>> each packet. I wonder how well the timer subsystem deals with one
>>> timer getting updated possibly thousands of time per second.
>>>
>>
>> May it even introduce some performance penalty?
>
> That's what I was worried about, yes.
>
> I asked Paolo, he suggested checking that we're actually doing any
> change to the timer:
>
> if (new_timeout_time != old_timeout_time)
> mod_timer(...)
>
> This would reduce the update frequency to one per jiffy, which should
> be acceptable.
>
>> Maybe we should get rid of the timer object and introduce a periodic (1s)
>> worker which checks some last_recv timestamp on every known peer?
>> What do you think?
>
> That should work, or the workqueue like Eyal is saying.
I will go with Eyal's approach.
This way we also eliminate any timer related operation from the fast path.
Cheers!
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 21/25] ovpn: implement key add/del/swap via netlink
2024-07-17 17:17 ` Sabrina Dubroca
@ 2024-07-18 8:29 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-18 8:29 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
Hi,
On 17/07/2024 19:17, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:39 +0200, Antonio Quartulli wrote:
>> This change introduces the netlink commands needed to add, delete and
>> swap keys for a specific peer.
>>
>> Userspace is expected to use these commands to create, destroy and
>> rotate session keys for a specific peer.
>>
>> Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
>> ---
>> Documentation/netlink/specs/ovpn.yaml | 2 +-
>> drivers/net/ovpn/netlink-gen.c | 2 +-
>> drivers/net/ovpn/netlink.c | 199 +++++++++++++++++++++++++-
>> 3 files changed, 198 insertions(+), 5 deletions(-)
>>
>> diff --git a/Documentation/netlink/specs/ovpn.yaml b/Documentation/netlink/specs/ovpn.yaml
>> index 68ed88d03732..21c89f0bdcbb 100644
>> --- a/Documentation/netlink/specs/ovpn.yaml
>> +++ b/Documentation/netlink/specs/ovpn.yaml
>> @@ -153,7 +153,7 @@ attribute-sets:
>> decryption
>> type: u32
>> checks:
>> - max: 2
>> + max: 7
>
> Looks like this got squashed into the wrong patch.
Indeed. Thanks for spotting it.
Cheers,
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 19/25] ovpn: add support for peer floating
2024-07-17 17:15 ` Sabrina Dubroca
@ 2024-07-18 9:37 ` Antonio Quartulli
2024-07-18 11:12 ` Sabrina Dubroca
0 siblings, 1 reply; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-18 9:37 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
On 17/07/2024 19:15, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:37 +0200, Antonio Quartulli wrote:
>> +void ovpn_peer_float(struct ovpn_peer *peer, struct sk_buff *skb)
>> +{
>> + struct sockaddr_storage ss;
>> + const u8 *local_ip = NULL;
>> + struct sockaddr_in6 *sa6;
>> + struct sockaddr_in *sa;
>> + struct ovpn_bind *bind;
>> + sa_family_t family;
>> + size_t salen;
>> +
>> + rcu_read_lock();
>> + bind = rcu_dereference(peer->bind);
>> + if (unlikely(!bind))
>> + goto unlock;
>
> Why are you aborting here? ovpn_bind_skb_src_match considers
> bind==NULL to be "no match" (reasonable), then we would create a new
> bind for the current address.
(NOTE: float and the following explanation assume connection via UDP)
peer->bind is assigned right after peer creation in ovpn_nl_set_peer_doit().
ovpn_peer_float() is called while the peer is exchanging traffic.
If we got to this point and bind is NULL, then the peer was being
released, because there is no way we are going to NULLify bind during
the peer life cycle, except upon ovpn_peer_release().
Does it make sense?
>
>> +
>> + if (likely(ovpn_bind_skb_src_match(bind, skb)))
>
> This could be running in parallel on two CPUs, because ->encap_rcv
> isn't protected against that. So the bind could be getting updated in
> parallel. I would move spin_lock_bh above this check to make sure it
> doesn't happen.
hm, I should actually use peer->lock for this, which is currently only
used in ovpn_bind_reset() to avoid multiple concurrent assignments...but
you're right we should include the call to skb_src_check() as well.
>
> ovpn_peer_update_local_endpoint would also need something like that, I
> think.
at least the test-and-set part should be protected, if we can truly
invoke ovpn_peer_update_local_endpoint() multiple times concurrently.
How do I test running encap_rcv in parallel?
This is actually an interesting case that I thought to not be possible
(no specific reason for this..).
>
>> + goto unlock;
>> +
>> + family = skb_protocol_to_family(skb);
>> +
>> + if (bind->sa.in4.sin_family == family)
>> + local_ip = (u8 *)&bind->local;
>> +
>> + switch (family) {
>> + case AF_INET:
>> + 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;
>> + case AF_INET6:
>> + 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);
>> + break;
>> + default:
>> + goto unlock;
>> + }
>> +
>> + netdev_dbg(peer->ovpn->dev, "%s: peer %d floated to %pIScp", __func__,
>> + peer->id, &ss);
>> + ovpn_peer_reset_sockaddr(peer, (struct sockaddr_storage *)&ss,
>> + local_ip);
>> +
>> + spin_lock_bh(&peer->ovpn->peers->lock);
>> + /* remove old hashing */
>> + hlist_del_init_rcu(&peer->hash_entry_transp_addr);
>> + /* re-add with new transport address */
>> + hlist_add_head_rcu(&peer->hash_entry_transp_addr,
>> + ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr,
>> + &ss, salen));
>
> That could send a concurrent reader onto the wrong hash bucket, if
> it's going through peer's old bucket, finds peer before the update,
> then continues reading after peer is moved to the new bucket.
I haven't fully grasped this scenario.
I am imagining we are running ovpn_peer_get_by_transp_addr() in
parallel: reader gets the old bucket and finds peer, because
ovpn_peer_transp_match() will still return true (update wasn't performed
yet), and will return it.
At this point, what do you mean with "continues reading after peer is
moved to the new bucket"?
>
> This kind of re-hash can be handled with nulls, and re-trying the
> lookup if we ended up on the wrong chain. See for example
> __inet_lookup_established in net/ipv4/inet_hashtables.c (Thanks to
> Paolo for the pointer).
>
>> + spin_unlock_bh(&peer->ovpn->peers->lock);
>> +
>> +unlock:
>> + rcu_read_unlock();
>> +}
>
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 10/25] ovpn: implement basic TX path (UDP)
2024-06-27 13:08 ` [PATCH net-next v5 10/25] ovpn: implement basic TX path (UDP) Antonio Quartulli
@ 2024-07-18 10:07 ` Sabrina Dubroca
2024-07-18 10:16 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-18 10:07 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-06-27, 15:08:28 +0200, Antonio Quartulli wrote:
> +static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)
> +{
> + ovpn_skb_cb(skb)->peer = peer;
> +
> + /* take a reference to the peer because the crypto code may run async.
> + * ovpn_encrypt_post() will release it upon completion
> + */
> + DEBUG_NET_WARN_ON_ONCE(!ovpn_peer_hold(peer));
Shouldn't we abort if this fails? This should not really happen, but
if it did, we would proceed (possibly with async crypto) without a ref
on the peer.
> + ovpn_encrypt_post(skb, 0);
> + return true;
> +}
> +
[...]
> diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h
> index aa259be66441..95568671d5ae 100644
> --- a/drivers/net/ovpn/io.h
> +++ b/drivers/net/ovpn/io.h
> @@ -12,4 +12,6 @@
>
> netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev);
>
> +void ovpn_encrypt_work(struct work_struct *work);
leftover from the old implementation I think?
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 14/25] ovpn: implement TCP transport
2024-07-15 9:59 ` Sabrina Dubroca
@ 2024-07-18 10:13 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-18 10:13 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
Hi,
On 15/07/2024 11:59, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:32 +0200, Antonio Quartulli wrote:
>> diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
>> index 0475440642dd..764b3df996bc 100644
>> --- a/drivers/net/ovpn/io.c
>> +++ b/drivers/net/ovpn/io.c
>> @@ -21,6 +21,7 @@
>> #include "netlink.h"
>> #include "proto.h"
>> #include "socket.h"
>> +#include "tcp.h"
>> #include "udp.h"
>> #include "skb.h"
>>
>> @@ -84,8 +85,11 @@ void ovpn_decrypt_post(struct sk_buff *skb, int ret)
>> /* PID sits after the op */
>> pid = (__force __be32 *)(skb->data + OVPN_OP_SIZE_V2);
>> ret = ovpn_pktid_recv(&ks->pid_recv, ntohl(*pid), 0);
>> - if (unlikely(ret < 0))
>> + if (unlikely(ret < 0)) {
>> + net_err_ratelimited("%s: PKT ID RX error: %d\n",
>> + peer->ovpn->dev->name, ret);
>
> nit: this should be part of the "packet processing" patch?
Yap, makes sense.
>
>
>> diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h
>> index dd4d91dfabb5..86d4696b1529 100644
>> --- a/drivers/net/ovpn/peer.h
>> +++ b/drivers/net/ovpn/peer.h
>> @@ -10,8 +10,8 @@
>> #ifndef _NET_OVPN_OVPNPEER_H_
>> #define _NET_OVPN_OVPNPEER_H_
>>
>> -#include <linux/ptr_ring.h>
>
> nit: I think you don't need it at all in this version and forgot to
> drop it in a previous patch? (I didn't notice when it was introduced)
Ouch, you are correct
>
>
>
>> +static int ovpn_tcp_to_userspace(struct ovpn_socket *sock, struct sk_buff *skb)
>> +{
>> + struct sock *sk = sock->sock->sk;
>> +
>> + skb_set_owner_r(skb, sk);
>> + memset(skb->cb, 0, sizeof(skb->cb));
>
> nit: this was just done in ovpn_tcp_rcv
right!
>
>> + skb_queue_tail(&sock->peer->tcp.user_queue, skb);
>> + sock->peer->tcp.sk_cb.sk_data_ready(sk);
>> +
>> + return 0;
>> +}
>> +
>> +static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
>> +{
> [...]
>> + /* DATA_V2 packets are handled in kernel, the rest goes to user space */
>> + if (likely(ovpn_opcode_from_skb(skb, 0) == OVPN_DATA_V2)) {
>> + /* hold reference to peer as required by ovpn_recv().
>> + *
>> + * NOTE: in this context we should already be holding a
>> + * reference to this peer, therefore ovpn_peer_hold() is
>> + * not expected to fail
>> + */
>> + WARN_ON(!ovpn_peer_hold(peer));
>
> drop the packet if this fails? otherwise I suspect we'll crash later on.
yeah, jumping to "err" and dropping everything makes sense.
>
>> + ovpn_recv(peer, skb);
>> + } else {
>> + /* The packet size header must be there when sending the packet
>> + * to userspace, therefore we put it back
>> + */
>> + skb_push(skb, 2);
>> + memset(skb->cb, 0, sizeof(skb->cb));
>> + if (ovpn_tcp_to_userspace(peer->sock, skb) < 0) {
>> + net_warn_ratelimited("%s: cannot send skb to userspace\n",
>> + peer->ovpn->dev->name);
>> + goto err;
>> + }
>> + }
> [...]
>
>
>> +void ovpn_tcp_socket_detach(struct socket *sock)
>> +{
>> + struct ovpn_socket *ovpn_sock;
>> + struct ovpn_peer *peer;
>> +
>> + if (!sock)
>> + return;
>> +
>> + rcu_read_lock();
>> + ovpn_sock = rcu_dereference_sk_user_data(sock->sk);
>> +
> [...]
>> + /* cancel any ongoing work. Done after removing the CBs so that these
>> + * workers cannot be re-armed
>> + */
>> + cancel_work_sync(&peer->tcp.tx_work);
>
> I don't think that's ok to call under rcu_read_lock, it seems it can
> sleep.
>
>> + strp_done(&peer->tcp.strp);
>
> And same here, since strp_done also calls cancel_work_sync.
hm you're right. I'll see how to re-arrange this part..I expect this to
be tricky.
>
>> + rcu_read_unlock();
>> +}
>> +
>> +static void ovpn_tcp_send_sock(struct ovpn_peer *peer)
>> +{
>> + struct sk_buff *skb = peer->tcp.out_msg.skb;
>> +
>> + if (!skb)
>> + return;
>> +
>> + if (peer->tcp.tx_in_progress)
>> + return;
>> +
>> + peer->tcp.tx_in_progress = true;
>
> I'm not convinced this is safe. ovpn_tcp_send_sock could run
> concurrently for the same peer (lock_sock doesn't exclude bh_lock_sock
> after the short "grab ownership" phase), so I think both sides could
> see tx_in_progress = false and then proceed.
I may be missing something here.
I was under the impression that ovpn_tcp_send_sock() is always invoked
with lock_sock() held. Shouldn't that be enough to prevent concurrent
executions for the same peer/sock?
>
>
>> + do {
>> + int ret = skb_send_sock_locked(peer->sock->sock->sk, skb,
>> + peer->tcp.out_msg.offset,
>> + peer->tcp.out_msg.len);
>> + if (unlikely(ret < 0)) {
>> + if (ret == -EAGAIN)
>> + goto out;
>
> This will silently drop the message? And then in case of a userspace
> message, ovpn_tcp_sendmsg will lie to the user (the openvpn client),
> claiming that the control message was sent (ret = size just above the
> unlock)?
why do you think the message will be dropped?
By jumping to 'out' we are keeping the skb in peer->tcp.out_msg.skb,
with peer->tcp.out_msg.offset and peer->tcp.out_msg.len left untouched
and ready for the next attempt triggered by ovpn_tcp_write_space().
>
>> +
>> + net_warn_ratelimited("%s: TCP error to peer %u: %d\n",
>> + peer->ovpn->dev->name, peer->id,
>> + ret);
>> +
>> + /* in case of TCP error we can't recover the VPN
>> + * stream therefore we abort the connection
>> + */
>> + ovpn_peer_del(peer,
>> + OVPN_DEL_PEER_REASON_TRANSPORT_ERROR);
>> + break;
>> + }
>> +
>> + peer->tcp.out_msg.len -= ret;
>> + peer->tcp.out_msg.offset += ret;
>> + } while (peer->tcp.out_msg.len > 0);
>
> Another thing that worries me: assume the receiver is a bit slow, the
> underlying TCP socket gets stuck. skb_send_sock_locked manages to push
> some data down the TCP socket, but not everything. We advance by that
> amount, and restart this loop. The socket is still stuck, so
> skb_send_sock_locked returns -EAGAIN. We have only pushed a partial
> message down to the TCP socket, but we drop the rest? Now the stream
> is broken, and the next call to ovpn_tcp_send_sock will happily send
> its message.
I think this is answered above, where I say that we are actually keeping
the skb (not dropping it) ready for the next sending attempt.
>
> ovpn_tcp_send_sock with msg_len = 1000
> iteration 1
> skb_send_sock_locked returns 100
> advance
> iteration 2
> skb_send_sock_locked returns -EAGAIN
> goto out
>
>
> So you'd have to keep that partially-sent message around until you can
> finish pushing it out on the socket.
yap, see above.
>
>
> [...]
>> +static int ovpn_tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
>> +{
>> + struct ovpn_socket *sock;
>> + int ret, linear = PAGE_SIZE;
>> + struct ovpn_peer *peer;
>> + struct sk_buff *skb;
>> +
>> + rcu_read_lock();
>> + sock = rcu_dereference_sk_user_data(sk);
>> + peer = sock->peer;
>> + rcu_read_unlock();
>
> What's stopping the peer being freed here?
I assumed that while we are in our own sk_cb it should not be possible
for a peer to have refcnt reaching 0.
But after double checking I don't think there is any protection about
this. I Will add a call to ovpn_peer_hold() and abort if that call fails.
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 10/25] ovpn: implement basic TX path (UDP)
2024-07-18 10:07 ` Sabrina Dubroca
@ 2024-07-18 10:16 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-18 10:16 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
On 18/07/2024 12:07, Sabrina Dubroca wrote:
> 2024-06-27, 15:08:28 +0200, Antonio Quartulli wrote:
>> +static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb)
>> +{
>> + ovpn_skb_cb(skb)->peer = peer;
>> +
>> + /* take a reference to the peer because the crypto code may run async.
>> + * ovpn_encrypt_post() will release it upon completion
>> + */
>> + DEBUG_NET_WARN_ON_ONCE(!ovpn_peer_hold(peer));
>
> Shouldn't we abort if this fails? This should not really happen, but
> if it did, we would proceed (possibly with async crypto) without a ref
> on the peer.
Yap, better bail out.
>
>> + ovpn_encrypt_post(skb, 0);
>> + return true;
>> +}
>> +
>
> [...]
>> diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h
>> index aa259be66441..95568671d5ae 100644
>> --- a/drivers/net/ovpn/io.h
>> +++ b/drivers/net/ovpn/io.h
>> @@ -12,4 +12,6 @@
>>
>> netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev);
>>
>> +void ovpn_encrypt_work(struct work_struct *work);
>
> leftover from the old implementation I think?
ops, you're right.
Thanks
>
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 19/25] ovpn: add support for peer floating
2024-07-18 9:37 ` Antonio Quartulli
@ 2024-07-18 11:12 ` Sabrina Dubroca
2024-07-18 13:21 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Sabrina Dubroca @ 2024-07-18 11:12 UTC (permalink / raw)
To: Antonio Quartulli; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
2024-07-18, 11:37:38 +0200, Antonio Quartulli wrote:
> On 17/07/2024 19:15, Sabrina Dubroca wrote:
> > 2024-06-27, 15:08:37 +0200, Antonio Quartulli wrote:
> > > +void ovpn_peer_float(struct ovpn_peer *peer, struct sk_buff *skb)
> > > +{
> > > + struct sockaddr_storage ss;
> > > + const u8 *local_ip = NULL;
> > > + struct sockaddr_in6 *sa6;
> > > + struct sockaddr_in *sa;
> > > + struct ovpn_bind *bind;
> > > + sa_family_t family;
> > > + size_t salen;
> > > +
> > > + rcu_read_lock();
> > > + bind = rcu_dereference(peer->bind);
> > > + if (unlikely(!bind))
> > > + goto unlock;
> >
> > Why are you aborting here? ovpn_bind_skb_src_match considers
> > bind==NULL to be "no match" (reasonable), then we would create a new
> > bind for the current address.
>
> (NOTE: float and the following explanation assume connection via UDP)
>
> peer->bind is assigned right after peer creation in ovpn_nl_set_peer_doit().
>
> ovpn_peer_float() is called while the peer is exchanging traffic.
>
> If we got to this point and bind is NULL, then the peer was being released,
> because there is no way we are going to NULLify bind during the peer life
> cycle, except upon ovpn_peer_release().
>
> Does it make sense?
Alright, thanks, I missed that.
> > > + if (likely(ovpn_bind_skb_src_match(bind, skb)))
> >
> > This could be running in parallel on two CPUs, because ->encap_rcv
> > isn't protected against that. So the bind could be getting updated in
> > parallel. I would move spin_lock_bh above this check to make sure it
> > doesn't happen.
>
> hm, I should actually use peer->lock for this, which is currently only used
> in ovpn_bind_reset() to avoid multiple concurrent assignments...but you're
> right we should include the call to skb_src_check() as well.
Ok, sounds good.
> > ovpn_peer_update_local_endpoint would also need something like that, I
> > think.
>
> at least the test-and-set part should be protected, if we can truly invoke
> ovpn_peer_update_local_endpoint() multiple times concurrently.
Yes.
> How do I test running encap_rcv in parallel?
> This is actually an interesting case that I thought to not be possible (no
> specific reason for this..).
It should happen when the packets come from different source IPs and
the NIC has multiple queues, then they can be spread over different
CPUs. But it's probably not going to be easy to land multiple packets
in ovpn_peer_float at the same time to trigger this issue.
> > > + netdev_dbg(peer->ovpn->dev, "%s: peer %d floated to %pIScp", __func__,
> > > + peer->id, &ss);
> > > + ovpn_peer_reset_sockaddr(peer, (struct sockaddr_storage *)&ss,
> > > + local_ip);
> > > +
> > > + spin_lock_bh(&peer->ovpn->peers->lock);
> > > + /* remove old hashing */
> > > + hlist_del_init_rcu(&peer->hash_entry_transp_addr);
> > > + /* re-add with new transport address */
> > > + hlist_add_head_rcu(&peer->hash_entry_transp_addr,
> > > + ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr,
> > > + &ss, salen));
> >
> > That could send a concurrent reader onto the wrong hash bucket, if
> > it's going through peer's old bucket, finds peer before the update,
> > then continues reading after peer is moved to the new bucket.
>
> I haven't fully grasped this scenario.
> I am imagining we are running ovpn_peer_get_by_transp_addr() in parallel:
> reader gets the old bucket and finds peer, because ovpn_peer_transp_match()
> will still return true (update wasn't performed yet), and will return it.
The other reader isn't necessarily looking for peer, but maybe another
item that landed in the same bucket (though your hashtables are so
large, it would be a bit unlucky).
> At this point, what do you mean with "continues reading after peer is moved
> to the new bucket"?
Continues iterating, in hlist_for_each_entry_rcu inside
ovpn_peer_get_by_transp_addr.
ovpn_peer_float ovpn_peer_get_by_transp_addr
start lookup
head = ovpn_get_hash_head(...)
hlist_for_each_entry_rcu
...
find peer on head
peer moved from head to head2
continue hlist_for_each_entry_rcu with peer->next
but peer->next is now on head2
keep walking ->next on head2 instead of head
--
Sabrina
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 19/25] ovpn: add support for peer floating
2024-07-18 11:12 ` Sabrina Dubroca
@ 2024-07-18 13:21 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-18 13:21 UTC (permalink / raw)
To: Sabrina Dubroca; +Cc: netdev, kuba, ryazanov.s.a, pabeni, edumazet, andrew
On 18/07/2024 13:12, Sabrina Dubroca wrote:
>> How do I test running encap_rcv in parallel?
>> This is actually an interesting case that I thought to not be possible (no
>> specific reason for this..).
>
> It should happen when the packets come from different source IPs and
> the NIC has multiple queues, then they can be spread over different
> CPUs. But it's probably not going to be easy to land multiple packets
> in ovpn_peer_float at the same time to trigger this issue.
I see. Yeah, this is not easy.
>
>
>>>> + netdev_dbg(peer->ovpn->dev, "%s: peer %d floated to %pIScp", __func__,
>>>> + peer->id, &ss);
>>>> + ovpn_peer_reset_sockaddr(peer, (struct sockaddr_storage *)&ss,
>>>> + local_ip);
>>>> +
>>>> + spin_lock_bh(&peer->ovpn->peers->lock);
>>>> + /* remove old hashing */
>>>> + hlist_del_init_rcu(&peer->hash_entry_transp_addr);
>>>> + /* re-add with new transport address */
>>>> + hlist_add_head_rcu(&peer->hash_entry_transp_addr,
>>>> + ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr,
>>>> + &ss, salen));
>>>
>>> That could send a concurrent reader onto the wrong hash bucket, if
>>> it's going through peer's old bucket, finds peer before the update,
>>> then continues reading after peer is moved to the new bucket.
>>
>> I haven't fully grasped this scenario.
>> I am imagining we are running ovpn_peer_get_by_transp_addr() in parallel:
>> reader gets the old bucket and finds peer, because ovpn_peer_transp_match()
>> will still return true (update wasn't performed yet), and will return it.
>
> The other reader isn't necessarily looking for peer, but maybe another
> item that landed in the same bucket (though your hashtables are so
> large, it would be a bit unlucky).
>
>> At this point, what do you mean with "continues reading after peer is moved
>> to the new bucket"?
>
> Continues iterating, in hlist_for_each_entry_rcu inside
> ovpn_peer_get_by_transp_addr.
>
> ovpn_peer_float ovpn_peer_get_by_transp_addr
>
> start lookup
> head = ovpn_get_hash_head(...)
> hlist_for_each_entry_rcu
> ...
> find peer on head
>
> peer moved from head to head2
>
> continue hlist_for_each_entry_rcu with peer->next
> but peer->next is now on head2
> keep walking ->next on head2 instead of head
>
Ok got it.
Basically we might move the reader from a list to another without it
noticing.
Will have a look at the pointer provided by Paolo and modify this code
accordingly.
Thanks!
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism
2024-07-18 7:46 ` Antonio Quartulli
@ 2024-07-19 3:31 ` Andrew Lunn
2024-07-19 8:59 ` Antonio Quartulli
0 siblings, 1 reply; 71+ messages in thread
From: Andrew Lunn @ 2024-07-19 3:31 UTC (permalink / raw)
To: Antonio Quartulli
Cc: Sabrina Dubroca, netdev, kuba, ryazanov.s.a, pabeni, edumazet
On Thu, Jul 18, 2024 at 09:46:00AM +0200, Antonio Quartulli wrote:
> On 18/07/2024 04:01, Andrew Lunn wrote:
> > > > > + if (ovpn_is_keepalive(skb)) {
> > > > > + netdev_dbg(peer->ovpn->dev,
> > > > > + "ping received from peer %u\n", peer->id);
> > > >
> > > > That should probably be _ratelimited, but it seems we don't have
> > > > _ratelimited variants for the netdev_* helpers.
> > >
> > > Right.
> > > I have used the net_*_ratelimited() variants when needed.
> > > Too bad we don't have those.
> >
> > If you think netdev_dbg_ratelimited() would be useful, i don't see why
> > you cannot add it.
> >
> > I just did an search and found something interesting in the history:
> >
> > https://lore.kernel.org/all/20190809002941.15341-1-liuhangbin@gmail.com/T/#u
> >
> > Maybe limit it to netdev_dbg_ratelimited() to avoid the potential
> > abuse DaveM was worried about.
>
> I see what Dave says however...
>
> ...along the packet processing routine there are several messages (some are
> err or warn or info) which require ratelimiting.
> Otherwise you end up with a gazilion log entries in case of a long lasting
> issue.
>
> Right now I am using net_dbg/warn/err/info_ratelimited(), therefore not
> having a netdev counterpart is not really helping with Dave's argument.
Yes, i think Dave' argument is weak because these alternatives
exist. Maybe they did not at the time?
I suspect he was using it as a way to force fixing the real issue. A
driver should not be issues lots of err or info messages. Protocol
errors are part of normal behaviour, just increment a counter and keep
going. Peer devices disappearing is normal behaviour, count it and
keep going. _err is generally reserved for something fatal happened,
and there is no recovery, other than unload the kernel module and
reload it.
Andrew
^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism
2024-07-19 3:31 ` Andrew Lunn
@ 2024-07-19 8:59 ` Antonio Quartulli
0 siblings, 0 replies; 71+ messages in thread
From: Antonio Quartulli @ 2024-07-19 8:59 UTC (permalink / raw)
To: Andrew Lunn; +Cc: Sabrina Dubroca, netdev, kuba, ryazanov.s.a, pabeni, edumazet
On 19/07/2024 05:31, Andrew Lunn wrote:
> On Thu, Jul 18, 2024 at 09:46:00AM +0200, Antonio Quartulli wrote:
>> On 18/07/2024 04:01, Andrew Lunn wrote:
>>>>>> + if (ovpn_is_keepalive(skb)) {
>>>>>> + netdev_dbg(peer->ovpn->dev,
>>>>>> + "ping received from peer %u\n", peer->id);
>>>>>
>>>>> That should probably be _ratelimited, but it seems we don't have
>>>>> _ratelimited variants for the netdev_* helpers.
>>>>
>>>> Right.
>>>> I have used the net_*_ratelimited() variants when needed.
>>>> Too bad we don't have those.
>>>
>>> If you think netdev_dbg_ratelimited() would be useful, i don't see why
>>> you cannot add it.
>>>
>>> I just did an search and found something interesting in the history:
>>>
>>> https://lore.kernel.org/all/20190809002941.15341-1-liuhangbin@gmail.com/T/#u
>>>
>>> Maybe limit it to netdev_dbg_ratelimited() to avoid the potential
>>> abuse DaveM was worried about.
>>
>> I see what Dave says however...
>>
>> ...along the packet processing routine there are several messages (some are
>> err or warn or info) which require ratelimiting.
>> Otherwise you end up with a gazilion log entries in case of a long lasting
>> issue.
>>
>> Right now I am using net_dbg/warn/err/info_ratelimited(), therefore not
>> having a netdev counterpart is not really helping with Dave's argument.
>
> Yes, i think Dave' argument is weak because these alternatives
> exist. Maybe they did not at the time?
They did and it's exactly what Hangbin was introducing:
https://lore.kernel.org/all/20190801090347.8258-1-liuhangbin@gmail.com/
before being suggested by Joe to implement the new macros:
https://lore.kernel.org/all/209f7fe62e2a79cd8c02b104b8e3babdd16bff30.camel@perches.com/
>
> I suspect he was using it as a way to force fixing the real issue. A
> driver should not be issues lots of err or info messages. Protocol
> errors are part of normal behaviour, just increment a counter and keep
> going. Peer devices disappearing is normal behaviour, count it and
> keep going. _err is generally reserved for something fatal happened,
> and there is no recovery, other than unload the kernel module and
> reload it.
Yeah, it looks like it and David pushed them to rethink the error
handling rather than making the error less painful.
But I can still netdev_dbg_ratelimited useful for debugging/development.
Anyway, I will add this to my todo and see if this specific variant can
be accepted.
Regards,
--
Antonio Quartulli
OpenVPN Inc.
^ permalink raw reply [flat|nested] 71+ messages in thread
end of thread, other threads:[~2024-07-19 8:57 UTC | newest]
Thread overview: 71+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-06-27 13:08 [PATCH net-next v5 00/25] Introducing OpenVPN Data Channel Offload Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 01/25] netlink: add NLA_POLICY_MAX_LEN macro Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 02/25] rtnetlink: don't crash on unregister if no dellink exists Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 03/25] net: introduce OpenVPN Data Channel Offload (ovpn) Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 04/25] ovpn: add basic netlink support Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 05/25] ovpn: add basic interface creation/destruction/management routines Antonio Quartulli
2024-06-28 22:11 ` Sabrina Dubroca
2024-07-01 8:48 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 06/25] ovpn: implement interface creation/destruction via netlink Antonio Quartulli
2024-07-03 21:27 ` Sabrina Dubroca
2024-07-03 21:44 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 07/25] ovpn: keep carrier always on Antonio Quartulli
2024-06-27 16:25 ` Andrew Lunn
2024-06-27 13:08 ` [PATCH net-next v5 08/25] ovpn: introduce the ovpn_peer object Antonio Quartulli
2024-07-03 21:37 ` Sabrina Dubroca
2024-07-03 22:16 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 09/25] ovpn: introduce the ovpn_socket object Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 10/25] ovpn: implement basic TX path (UDP) Antonio Quartulli
2024-07-18 10:07 ` Sabrina Dubroca
2024-07-18 10:16 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 11/25] ovpn: implement basic RX " Antonio Quartulli
2024-07-08 16:11 ` Sabrina Dubroca
2024-07-08 22:09 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 12/25] ovpn: implement packet processing Antonio Quartulli
2024-07-09 8:51 ` Sabrina Dubroca
2024-07-10 11:38 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 13/25] ovpn: store tunnel and transport statistics Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 14/25] ovpn: implement TCP transport Antonio Quartulli
2024-07-15 9:59 ` Sabrina Dubroca
2024-07-18 10:13 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 15/25] ovpn: implement multi-peer support Antonio Quartulli
2024-07-15 10:40 ` Sabrina Dubroca
2024-07-17 14:05 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 16/25] ovpn: implement peer lookup logic Antonio Quartulli
2024-07-15 13:11 ` Sabrina Dubroca
2024-07-17 14:07 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 17/25] ovpn: implement keepalive mechanism Antonio Quartulli
2024-07-15 14:44 ` Sabrina Dubroca
2024-07-17 15:30 ` Antonio Quartulli
2024-07-17 16:19 ` Eyal Birger
2024-07-18 8:20 ` Antonio Quartulli
2024-07-17 20:40 ` Sabrina Dubroca
2024-07-18 8:22 ` Antonio Quartulli
2024-07-18 2:01 ` Andrew Lunn
2024-07-18 7:46 ` Antonio Quartulli
2024-07-19 3:31 ` Andrew Lunn
2024-07-19 8:59 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 18/25] ovpn: add support for updating local UDP endpoint Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 19/25] ovpn: add support for peer floating Antonio Quartulli
2024-07-17 17:15 ` Sabrina Dubroca
2024-07-18 9:37 ` Antonio Quartulli
2024-07-18 11:12 ` Sabrina Dubroca
2024-07-18 13:21 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 20/25] ovpn: implement peer add/dump/delete via netlink Antonio Quartulli
2024-07-16 13:41 ` Sabrina Dubroca
2024-07-17 14:04 ` Antonio Quartulli
2024-07-17 15:37 ` Sabrina Dubroca
2024-06-27 13:08 ` [PATCH net-next v5 21/25] ovpn: implement key add/del/swap " Antonio Quartulli
2024-07-17 17:17 ` Sabrina Dubroca
2024-07-18 8:29 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 22/25] ovpn: kill key and notify userspace in case of IV exhaustion Antonio Quartulli
2024-07-17 10:42 ` Sabrina Dubroca
2024-07-17 11:03 ` Antonio Quartulli
2024-07-17 13:26 ` Sabrina Dubroca
2024-07-17 13:38 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 23/25] ovpn: notify userspace when a peer is deleted Antonio Quartulli
2024-07-17 10:54 ` Sabrina Dubroca
2024-07-17 11:16 ` Antonio Quartulli
2024-06-27 13:08 ` [PATCH net-next v5 24/25] ovpn: add basic ethtool support Antonio Quartulli
2024-06-27 16:25 ` Andrew Lunn
2024-06-27 13:08 ` [PATCH net-next v5 25/25] testing/selftest: add test tool and scripts for ovpn module Antonio Quartulli
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).