From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wr1-f51.google.com (mail-wr1-f51.google.com [209.85.221.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B606228F513 for ; Tue, 15 Apr 2025 11:17:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715850; cv=none; b=Yo158xTMqr3xf4s7EUbZxVQW7POZmrj+nFzSmw3UE9wAu1uSsDB0BhQmtmeWymn1LcbxaXanJV4xByceO7/aFPmb/Yz1oiNMT72pbZAfq9fWZdW3EHXxzg2esIDW5zzc8eMQeYmkJ5mOtzJV8yTOp0+d1xSoUMLonaksRETXISw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715850; c=relaxed/simple; bh=+sdUNBYAIaOPKO7/M/Re2Rd/06mEdrQrQD3O5KXjTaI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=rOm/rIvMMmIJ4BRKrUuuV53GMnnPkmf+q7yuL9rQA/vaAUgdVQHGJeojBAW3xrzvNBgPxwvGmM2fZceRLrHf5nje29/4t3vLae0U41KlSZjvEHvic7r54G69ShbMUz4pp8pYxruGq2F5r0sYHXy891mouFKwg94JAHEu0fGNd5c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=Mn79UTy3; arc=none smtp.client-ip=209.85.221.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="Mn79UTy3" Received: by mail-wr1-f51.google.com with SMTP id ffacd0b85a97d-38f2f391864so3099152f8f.3 for ; Tue, 15 Apr 2025 04:17:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715846; x=1745320646; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=f3Mem/u9ngNOMSXPpHg0rOlZxWoz94Gysx025QaiVhE=; b=Mn79UTy3pxKa7/W3gz3RT7az/0IYdAG+9ihv7e0HvRNhagQ1Q/EtzmnCYmU5H3gZyk JTTWGN4/0I7VTdypfpnhCXNi6uIgnHrnR7pCiipZ/SoJRw7j+OS3tyhbrPVcj56QrpeM QZyStu29Bst5L0gpR6/5OwH+dCwsNfgmjXMNgXHMwRMP6NcrOEJVaJui5YRpdTbQE6tc 3rz+fTvf3BcWoROFDcI72RsJBGTg0yNBbdtqCYl4+m3ljKO1bxbf9K485YNYeVVqJmoh ScR7PkT5LfZ3uC4wBz6ScEiE9IGeJcYGZkJl2E/UOxfzS86RB+CLbIySczIH/vvMi4NW fFKw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715846; x=1745320646; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=f3Mem/u9ngNOMSXPpHg0rOlZxWoz94Gysx025QaiVhE=; b=rS8ihSTunkkWeiWLY3sgl76ql3oFjaT7MShUndADmzsfCbOCB0uLf3xK+imvPA/iog TWP83DUsCRuJH0BBB6/S8McS5pO3xfmbG+7drHzzJEgB8DpeEdjtK53hlmwGwsGAeqnB Mo0zdtUpjI6ZO9xEdBkNpKZ9zhfOFzi3VeKct7hE8VIQCBZBNvnVXlG5Udv8kqG3o4+E BnmsZv8ch0FDIb+kZZ2d/6669K38EItqqB7EUxwU04HAYI38vcufMfIjcpQ/Ggg0M+Hi t45PF0itVW6jZPnO1i2OMRy1ECLkUEJbfaZrXySJ0W9bWf8GeKdgmPKgWqZ0kNloNtKi Qk1A== X-Forwarded-Encrypted: i=1; AJvYcCVnX8JYm9uv+RcyHRNzpzZNXe8t9eaOWCrtjvRIl9XViO4+gj81W0d+4DfZh+ZcklcdvFI++Kavdbrm+e8=@vger.kernel.org X-Gm-Message-State: AOJu0YzqK4KHphkKbrGRZEpWzkhM7a9MXggXMiDnBf3AP5OYa7kZ0JZv 6nn4JCiCuRApzi2AJWvPAeZYhOg9cuDsTjhZJqCLmu8014XgWyx+p9bnNmLMZ7nJnvDOAH7h1Iz kXcc2ZuA0AZbcowsRB4A5QIdpsBwUJs5WxmQtbFij4965C+TBaDDbcQg= X-Gm-Gg: ASbGncvQTX3/+k5aKnlJ2L7RYeo+0/FvAwfrP7ne26yFgRmRERuDQ6ERF/jDN33ks4m Z7FDXlWOz059Zzdlp8pwv7lvBj2JfCw+qG0A8phyKem4ElTOmpqeuKHhJdxcoJbrz3nn3uO/zG0 KXwwW2+hatX7QpM3E5FNs4YkDn+tEcWX90ys5z2waKMpuQcR8YoouSh+dAa3q9mOajIZ/nuloog 7ny8g9BbdHJ1BslmzSeO4KWUOS5P2TzkiObV7DW8CMgNqYtA0MyNczxmzpygUCZ2w9MImnkussx BdeIoExUTE+EWGTbbFdKCxDvLDvl1pkNYFiAe0CxCS7AHG34 X-Google-Smtp-Source: AGHT+IEXRW4m6izOB+65hbWIIVmzVu6In3hyug9OS3P5k8LIBFCajnpODOm4PVSbrunAlFdl8Q3Z3w== X-Received: by 2002:a5d:6daa:0:b0:39c:310a:f87e with SMTP id ffacd0b85a97d-39ea51f59eamr13884142f8f.16.1744715845909; Tue, 15 Apr 2025 04:17:25 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:25 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:18 +0200 Subject: [PATCH net-next v26 01/23] net: introduce OpenVPN Data Channel Offload (ovpn) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-1-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang , steffen.klassert@secunet.com, antony.antony@secunet.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=7427; i=antonio@openvpn.net; h=from:subject:message-id; bh=+sdUNBYAIaOPKO7/M/Re2Rd/06mEdrQrQD3O5KXjTaI=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBByUR6q2+HMwb9r98ZMcuzw3sfD3/mJm2z8 ayNee1Gi82JATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQQAKCRALcOU6oDjV h601CACfYxNgV/JgNxiU125KmJ63E6nkaBxJjuXOoMRyi7qw0gDu/0jIja5Ss7/TgForHJ/D5Ur 0gr+kVi9A2Xolu6AGA+5PfUiU5CvXVlyHyunLezPiZczLCTlXZkNSI7yty1YrJ8ZPOwlyCXgk6u 0AOsxtSPv0X96HE3zfT5AUxliPOul2JBlnxq6A80sD/6GuEq/LVwawO/Og25cN783Ps11YEfDI5 o+tIoF8W6iGGHm2JB/iGyx1VRENPkpO94EhAYKRUTRJJbkdcROdjILvUqQqntTjnph71dAxX0LG h7eBu0qPs8TUg/omGhMLyDzmAB4JWJRVaQLQCRQSIbQNnrz3 X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C OpenVPN is a userspace software existing since around 2005 that allows users to create secure tunnels. So far OpenVPN has implemented all operations in userspace, which implies several back and forth between kernel and user land in order to process packets (encapsulate/decapsulate, encrypt/decrypt, rerouting..). With `ovpn` we intend to move the fast path (data channel) entirely in kernel space and thus improve user measured throughput over the tunnel. `ovpn` is implemented as a simple virtual network device driver, that can be manipulated by means of the standard RTNL APIs. A device of kind `ovpn` allows only IPv4/6 traffic and can be of type: * P2P (peer-to-peer): any packet sent over the interface will be encapsulated and transmitted to the other side (typical OpenVPN client or peer-to-peer behaviour); * P2MP (point-to-multipoint): packets sent over the interface are transmitted to peers based on existing routes (typical OpenVPN server behaviour). After the interface has been created, OpenVPN in userspace can configure it using a new Netlink API. Specifically it is possible to manage peers and their keys. The OpenVPN control channel is multiplexed over the same transport socket by means of OP codes. Anything that is not DATA_V2 (OpenVPN OP code for data traffic) is sent to userspace and handled there. This way the `ovpn` codebase is kept as compact as possible while focusing on handling data traffic only (fast path). Any OpenVPN control feature (like cipher negotiation, TLS handshake, rekeying, etc.) is still fully handled by the userspace process. When userspace establishes a new connection with a peer, it first performs the handshake and then passes the socket to the `ovpn` kernel module, which takes ownership. From this moment on `ovpn` will handle data traffic for the new peer. When control packets are received on the link, they are forwarded to userspace through the same transport socket they were received on, as userspace is still listening to them. Some events (like peer deletion) are sent to a Netlink multicast group. Although it wasn't easy to convince the community, `ovpn` implements only a limited number of the data-channel features supported by the userspace program. Each feature that made it to `ovpn` was attentively vetted to avoid carrying too much legacy along with us (and to give a clear cut to old and probalby-not-so-useful features). Notably, only encryption using AEAD ciphers (specifically ChaCha20Poly1305 and AES-GCM) was implemented. Supporting any other cipher out there was not deemed useful. Both UDP and TCP sockets are supported. As explained above, in case of P2MP mode, OpenVPN will use the main system routing table to decide which packet goes to which peer. This implies that no routing table was re-implemented in the `ovpn` kernel module. This kernel module can be enabled by selecting the CONFIG_OVPN entry in the networking drivers section. NOTE: this first patch introduces the very basic framework only. Features are then added patch by patch, however, although each patch will compile and possibly not break at runtime, only after having applied the full set it is expected to see the ovpn module fully working. Cc: steffen.klassert@secunet.com Cc: antony.antony@secunet.com Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- MAINTAINERS | 8 ++++++++ drivers/net/Kconfig | 8 ++++++++ drivers/net/Makefile | 1 + drivers/net/ovpn/Makefile | 10 +++++++++ drivers/net/ovpn/main.c | 52 +++++++++++++++++++++++++++++++++++++++++++= ++++ 5 files changed, 79 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 1248443035f437b903dafa739cf0cea30b71f785..d0d77f43c5af370a895b1056f21= 988daa0389c78 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18125,6 +18125,14 @@ F: arch/openrisc/ F: drivers/irqchip/irq-ompic.c F: drivers/irqchip/irq-or1k-* =20 +OPENVPN DATA CHANNEL OFFLOAD +M: Antonio Quartulli +L: openvpn-devel@lists.sourceforge.net (subscribers-only) +L: netdev@vger.kernel.org +S: Supported +T: git https://github.com/OpenVPN/linux-kernel-ovpn.git +F: drivers/net/ovpn/ + OPENVSWITCH M: Aaron Conole M: Eelco Chaudron diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 271520510b5fc6866bbf4fc6a0d728d110e6b5e4..5fbe25ae1e11e558aa9aaa857cf= 110127e459854 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -115,6 +115,14 @@ config WIREGUARD_DEBUG =20 Say N here unless you know what you're doing. =20 +config OVPN + tristate "OpenVPN data channel offload" + depends on NET && INET + depends on IPV6 || !IPV6 + help + This module enhances the performance of the OpenVPN userspace software + by offloading the data channel processing to kernelspace. + config EQUALIZER tristate "EQL (serial line load balancing) support" help diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 75333251a01a87e18d6c1bc9eec97b96b571b77b..73bc63ecd65ff45c9458a31d7f6= 7447b37e18cdb 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_IPVLAN) +=3D ipvlan/ obj-$(CONFIG_IPVTAP) +=3D ipvlan/ obj-$(CONFIG_DUMMY) +=3D dummy.o obj-$(CONFIG_WIREGUARD) +=3D wireguard/ +obj-$(CONFIG_OVPN) +=3D ovpn/ obj-$(CONFIG_EQUALIZER) +=3D eql.o obj-$(CONFIG_IFB) +=3D ifb.o obj-$(CONFIG_MACSEC) +=3D macsec.o diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..876800ebaa21a5f758ddf60f637= 801710437f70e --- /dev/null +++ b/drivers/net/ovpn/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# ovpn -- OpenVPN data channel offload in kernel space +# +# Copyright (C) 2020-2025 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +obj-$(CONFIG_OVPN) :=3D ovpn.o +ovpn-y +=3D main.o diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c new file mode 100644 index 0000000000000000000000000000000000000000..d5d4ac8da6d78e18960e18cd5f8= d522185900542 --- /dev/null +++ b/drivers/net/ovpn/main.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + * James Yonan + */ + +#include +#include +#include + +static int ovpn_newlink(struct net_device *dev, + struct rtnl_newlink_params *params, + struct netlink_ext_ack *extack) +{ + return -EOPNOTSUPP; +} + +static struct rtnl_link_ops ovpn_link_ops =3D { + .kind =3D "ovpn", + .netns_refund =3D false, + .newlink =3D ovpn_newlink, + .dellink =3D unregister_netdevice_queue, +}; + +static int __init ovpn_init(void) +{ + int err =3D rtnl_link_register(&ovpn_link_ops); + + if (err) { + pr_err("ovpn: can't register rtnl link ops: %d\n", err); + return err; + } + + return 0; +} + +static __exit void ovpn_cleanup(void) +{ + rtnl_link_unregister(&ovpn_link_ops); + + rcu_barrier(); +} + +module_init(ovpn_init); +module_exit(ovpn_cleanup); + +MODULE_DESCRIPTION("OpenVPN data channel offload (ovpn)"); +MODULE_AUTHOR("Antonio Quartulli "); +MODULE_LICENSE("GPL"); --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 88F75291162 for ; Tue, 15 Apr 2025 11:17:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715853; cv=none; b=pgv9Ye6l0HZUVU7TL2EfGmel2P5T3DSG7A7pwoxIk+/FdG4T0Qfsifmor878wNlCpV1pwGNnXuer5t7lo2q+WrU8y/tRy3t+FR/4RYdBQi58234xNElzElmuapbRt8F9NuYXZaq4p9x9cf3XY9/+IzE+orwTl6nuP9c352GUP9g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715853; c=relaxed/simple; bh=OVIqGSvM+rLVF4vDXKLVCpNidkz3I+Wwfh0olcepNjU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=r9NRawZMv/M0bmgZKXQ2GTrK9AaFq+fzLrSZgvaAQcXi+00X+bVu+ohx3jqR5Qh5hfn+D7Ro9+Mokac77F0JNQ9Yr0ypIY8+IFlkQfDkT7Oq++Bn+q6FcMl92qraUuzTzbkjaTi90TkhBAluM3Y6VYNroS339yAH4bXLWnyLgfg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=So4+lB7/; arc=none smtp.client-ip=209.85.128.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="So4+lB7/" Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-43cf680d351so37092105e9.0 for ; Tue, 15 Apr 2025 04:17:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715848; x=1745320648; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=eLwTJ+qQnuQEGlYw6F3SpztxZW+m60tPfbzojmv3kMk=; b=So4+lB7/6tOm/OMuPzb1RIZm86k3N/SpHjvJx0N7tcYF9aVVc7xeN+hKynPdZFH9tF UkxlTORNC7OWVvQdqH9xRpkmDdcxjVLCBBllhGGI09s96oTRxI4mWy8kF69vX6nEdCxy PH6TugkLoW2jdsqwp7JKYE8hX79oP1uTjeimLSWePWv18SFG0kiFN6vYlu5jDFJhkfRK cyfZfjivvLGojsd0vV0UJItv6R9crAsn5V0Nhf6/7woP+tNyw8WIcTgZIT/vbmN9HtH4 jl4nNwpSoldyEEDF9ytUzE5LJTRM7zbkmV4699vbF85o8Hy6tGitTWEczDuxf9hsNvFq 0AiQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715848; x=1745320648; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=eLwTJ+qQnuQEGlYw6F3SpztxZW+m60tPfbzojmv3kMk=; b=dEvDMCcRqWjthwWNJEPawKsLMCpnuSkD6KhSYlgcdJyVhFPjj2UJrKQ+4gL5UoKCtq CeSPBqHii8ma4gB6urhOy89qgldt1Vbt5TRjkOlgQLmK10PGbyG8NWUy7z7jjVL9+qFA VHVVWi+hGfWgtEkqnnGaV9g/K3I0W6S+QPy5DgFECS7BU7iuNn7mJ2bIvyoPCZevSYil kElwxlvd3yDvgy90Im4Ayo2pUbrmkX3XeQbpWLzhvRoWqP+U9BtOduLd1DPTndxFS3nH Z3auTPHmdgUl+3oqc5MX4of1pCNPnKeHSkty2VeHhE6Ai1m/qB3HgcfP4gfNEMNK4i2D U4mw== X-Forwarded-Encrypted: i=1; AJvYcCVrDAXiko4Ch+BgmxPw1k29vqBVYDemPvrPv41twfzC8G9ftyU0U2aniRA2fSf2A874VedikD0u+msg6q0=@vger.kernel.org X-Gm-Message-State: AOJu0YyyT7nce59ta6nSG7Mw5l9FX6nKwbBsBzxrahqwK8SyRdXf82Fz +6qPbN53Lxly+49AyHdnr6RrEgwFKwoV1Us928ueXqAbkchaRtkDxhSfaUbF1mcp9KGGrksSh1/ E/NE4ikC+GTLEP7S21Uf9FcONp2CmVLNSNBVAJKOJK7RWSvwMhMezvcI= X-Gm-Gg: ASbGncvjhr6SN5ZdUj0hMpBbK4uR2SirBuF84QIeBHF14tAoiHu+mCAnpwdWlzPXqnN +SnI5MWivwKgj+wV6FfPk8i+YCfp2qvQbXwCSkNiXD8/x3aQkGPGp8XVxgAUq3F7cNlTHSyghwq tQprHL6SEZ4OZG5DZkQSuQ1AFuAw9XAF/9bhSlLQvc/Jt4CmSjdNJCHsHG7E9P81EV4BmIq4y8H sZuJ2iiy6A1IfpiIWpBVA87LHMUYLSmL1+tSICEHVAwksiOHs8f/sxNzwnbE9KQiOZOu8HXP/Kq 97fQEwhG8oadzZGwj/K8kuMom/EvRRz2ILzv6w== X-Google-Smtp-Source: AGHT+IFiUIeOqiD9iFdIJYUW4gd+Yq9FtWY59rixtYXICKOmxPiBoun3TsVHLTd6hX9vkCzF5YUKYA== X-Received: by 2002:a05:600c:198b:b0:439:873a:1114 with SMTP id 5b1f17b1804b1-43fe2d31b41mr20362195e9.6.1744715847306; Tue, 15 Apr 2025 04:17:27 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:26 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:19 +0200 Subject: [PATCH net-next v26 02/23] ovpn: add basic netlink support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-2-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=32027; i=antonio@openvpn.net; h=from:subject:message-id; bh=OVIqGSvM+rLVF4vDXKLVCpNidkz3I+Wwfh0olcepNjU=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBBi7ANC+01ZbfMeTEZ2a+G9+33TjLT+Bp+K s93JdIdZJaJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQQAKCRALcOU6oDjV h2ipB/96FWH7tYp556CGLkUCnfda6h0AUacOWuBgk9vR3wgjEPf7hhgPY88tC0RuuX52OkQdevo s+AOdS0ay8jyC4dU6bp0U61ikTiC7pGAaIEp19uwnfShcsWBhPDt20rEWNIALEwTd43LGKuaj5U Zd2+QiwB2nTH33TCyREScipThLzxSpNyoSS6JkjqrFEU7TX93ca0GGvQCp36SPi0lrm8obEB+/Z jXE1HGl8l4uLgxau7Q/xF91+/Ms0aAHFCKabEdwEPDQGF2sYUBo37hVHpkQizTGxRfz9or2fIat 7cVTwubGPynsuiU5keuvazM2OS+0qH01gcUSv7Fk9VjU6iUK X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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 Reviewed-by: Donald Hunter Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- Documentation/netlink/specs/ovpn.yaml | 367 ++++++++++++++++++++++++++++++= ++++ MAINTAINERS | 2 + drivers/net/ovpn/Makefile | 2 + drivers/net/ovpn/main.c | 31 +++ drivers/net/ovpn/main.h | 14 ++ drivers/net/ovpn/netlink-gen.c | 213 ++++++++++++++++++++ drivers/net/ovpn/netlink-gen.h | 41 ++++ drivers/net/ovpn/netlink.c | 160 +++++++++++++++ drivers/net/ovpn/netlink.h | 15 ++ drivers/net/ovpn/ovpnpriv.h | 21 ++ include/uapi/linux/ovpn.h | 109 ++++++++++ 11 files changed, 975 insertions(+) diff --git a/Documentation/netlink/specs/ovpn.yaml b/Documentation/netlink/= specs/ovpn.yaml new file mode 100644 index 0000000000000000000000000000000000000000..096c51f0c69a8de777281af35e3= 45dd90f27ecd2 --- /dev/null +++ b/Documentation/netlink/specs/ovpn.yaml @@ -0,0 +1,367 @@ +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Cla= use) +# +# Author: Antonio Quartulli +# +# Copyright (c) 2024-2025, 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 + entries: [ none, aes-gcm, chacha20-poly1305 ] + - + type: enum + name: del-peer-reason + entries: + - teardown + - userspace + - expired + - transport-error + - transport-disconnect + - + type: enum + name: key-slot + entries: [ primary, secondary ] + +attribute-sets: + - + name: peer + attributes: + - + name: id + type: u32 + doc: >- + The unique ID of the peer in the device context. To be used to i= dentify + peers during operations for a specific device + checks: + max: 0xFFFFFF + - + name: remote-ipv4 + type: u32 + doc: The remote IPv4 address of the peer + byte-order: big-endian + display-hint: ipv4 + - + name: remote-ipv6 + type: binary + doc: The remote IPv6 address of the peer + display-hint: ipv6 + checks: + exact-len: 16 + - + name: remote-ipv6-scope-id + type: u32 + doc: The scope id of the remote IPv6 address of the peer (RFC2553) + - + name: remote-port + type: u16 + doc: The remote port of the peer + byte-order: big-endian + checks: + min: 1 + - + name: socket + type: u32 + doc: The socket to be used to communicate with the peer + - + name: socket-netnsid + type: s32 + doc: The ID of the netns the socket assigned to this peer lives in + - + name: vpn-ipv4 + type: u32 + doc: The IPv4 address assigned to the peer by the server + byte-order: big-endian + display-hint: ipv4 + - + name: vpn-ipv6 + type: binary + doc: The IPv6 address assigned to the peer by the server + display-hint: ipv6 + checks: + exact-len: 16 + - + name: local-ipv4 + type: u32 + doc: The local IPv4 to be used to send packets to the peer (UDP on= ly) + byte-order: big-endian + display-hint: ipv4 + - + name: local-ipv6 + type: binary + doc: The local IPv6 to be used to send packets to the peer (UDP on= ly) + display-hint: ipv6 + checks: + exact-len: 16 + - + name: local-port + type: u16 + doc: The local port to be used to send packets to the peer (UDP on= ly) + byte-order: big-endian + checks: + min: 1 + - + name: keepalive-interval + type: u32 + doc: >- + The number of seconds after which a keep alive message is sent t= o the + peer + - + name: keepalive-timeout + type: u32 + doc: >- + The number of seconds from the last activity after which the pee= r is + assumed dead + - + name: del-reason + type: u32 + doc: The reason why a peer was deleted + enum: del-peer-reason + - + 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: uint + doc: Number of packets received at the transport level + - + name: link-tx-packets + type: uint + doc: Number of packets transmitted at the transport level + - + name: keyconf + attributes: + - + name: peer-id + type: u32 + doc: >- + The unique ID of the peer in the device context. To be used to + identify peers during key operations + checks: + max: 0xFFFFFF + - + name: slot + type: u32 + doc: The slot where the key should be stored + enum: key-slot + - + name: key-id + doc: >- + The unique ID of the key in the peer context. Used to fetch the + correct key upon decryption + type: u32 + checks: + max: 7 + - + 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 actual 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: peer + type: nest + doc: >- + The peer object containing the attributed of interest for the sp= ecific + operation + nested-attributes: peer + - + name: keyconf + type: nest + doc: Peer specific cipher configuration + nested-attributes: keyconf + +operations: + list: + - + name: peer-new + attribute-set: ovpn + flags: [ admin-perm ] + doc: Add a remote peer + do: + pre: ovpn-nl-pre-doit + post: ovpn-nl-post-doit + request: + attributes: + - ifindex + - peer + - + name: peer-set + attribute-set: ovpn + flags: [ admin-perm ] + doc: modify a remote peer + do: + pre: ovpn-nl-pre-doit + post: ovpn-nl-post-doit + request: + attributes: + - ifindex + - peer + - + name: peer-get + 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: peer-del + 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: peer-del-ntf + doc: Notification about a peer being deleted + notify: peer-get + mcgrp: peers + + - + name: key-new + attribute-set: ovpn + flags: [ admin-perm ] + doc: Add a cipher key for a specific peer + do: + pre: ovpn-nl-pre-doit + post: ovpn-nl-post-doit + request: + attributes: + - ifindex + - keyconf + - + name: key-get + attribute-set: ovpn + flags: [ admin-perm ] + doc: Retrieve non-sensitive data about peer key and cipher + do: + pre: ovpn-nl-pre-doit + post: ovpn-nl-post-doit + request: + attributes: + - ifindex + - keyconf + reply: + attributes: + - keyconf + - + name: key-swap + 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 + - keyconf + - + name: key-swap-ntf + notify: key-get + doc: >- + Notification about key having exhausted its IV space and requiring + renegotiation + mcgrp: peers + - + name: key-del + 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 + - keyconf + +mcast-groups: + list: + - + name: peers diff --git a/MAINTAINERS b/MAINTAINERS index d0d77f43c5af370a895b1056f21988daa0389c78..c50e87ef728848965f7de80474a= cf9cd4e5bab75 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18131,7 +18131,9 @@ L: openvpn-devel@lists.sourceforge.net (subscribers= -only) L: netdev@vger.kernel.org S: Supported T: git https://github.com/OpenVPN/linux-kernel-ovpn.git +F: Documentation/netlink/specs/ovpn.yaml F: drivers/net/ovpn/ +F: include/uapi/linux/ovpn.h =20 OPENVSWITCH M: Aaron Conole diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 876800ebaa21a5f758ddf60f637801710437f70e..75ac62bba02937bc49cb2a0dec5= ca3cc31a8ee00 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -8,3 +8,5 @@ =20 obj-$(CONFIG_OVPN) :=3D ovpn.o ovpn-y +=3D main.o +ovpn-y +=3D netlink.o +ovpn-y +=3D netlink-gen.o diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index d5d4ac8da6d78e18960e18cd5f8d522185900542..719cac3db6ef4e06b089790afef= b2e66223d43d3 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -7,9 +7,29 @@ * James Yonan */ =20 +#include #include #include #include +#include + +#include "ovpnpriv.h" +#include "main.h" +#include "netlink.h" + +static const struct net_device_ops ovpn_netdev_ops =3D { +}; + +/** + * 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 =3D=3D &ovpn_netdev_ops; +} =20 static int ovpn_newlink(struct net_device *dev, struct rtnl_newlink_params *params, @@ -34,11 +54,22 @@ static int __init ovpn_init(void) return err; } =20 + err =3D 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); + return err; } =20 static __exit void ovpn_cleanup(void) { + ovpn_nl_unregister(); rtnl_link_unregister(&ovpn_link_ops); =20 rcu_barrier(); diff --git a/drivers/net/ovpn/main.h b/drivers/net/ovpn/main.h new file mode 100644 index 0000000000000000000000000000000000000000..017cd0100765900181cb662319c= f38c2d0b7dd2d --- /dev/null +++ b/drivers/net/ovpn/main.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#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/drivers/net/ovpn/netlink-gen.c b/drivers/net/ovpn/netlink-gen.c new file mode 100644 index 0000000000000000000000000000000000000000..58e1a4342378eacc67dec19de34= fa4bb45f16fe2 --- /dev/null +++ b/drivers/net/ovpn/netlink-gen.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Cl= ause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/ovpn.yaml */ +/* YNL-GEN kernel source */ + +#include +#include + +#include "netlink-gen.h" + +#include + +/* Integer value ranges */ +static const struct netlink_range_validation ovpn_a_peer_id_range =3D { + .max =3D 16777215ULL, +}; + +static const struct netlink_range_validation ovpn_a_keyconf_peer_id_range = =3D { + .max =3D 16777215ULL, +}; + +/* Common nested types */ +const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR = + 1] =3D { + [OVPN_A_KEYCONF_PEER_ID] =3D NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_keyco= nf_peer_id_range), + [OVPN_A_KEYCONF_SLOT] =3D NLA_POLICY_MAX(NLA_U32, 1), + [OVPN_A_KEYCONF_KEY_ID] =3D NLA_POLICY_MAX(NLA_U32, 7), + [OVPN_A_KEYCONF_CIPHER_ALG] =3D NLA_POLICY_MAX(NLA_U32, 2), + [OVPN_A_KEYCONF_ENCRYPT_DIR] =3D NLA_POLICY_NESTED(ovpn_keydir_nl_policy), + [OVPN_A_KEYCONF_DECRYPT_DIR] =3D NLA_POLICY_NESTED(ovpn_keydir_nl_policy), +}; + +const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1= ] =3D { + [OVPN_A_KEYDIR_CIPHER_KEY] =3D NLA_POLICY_MAX_LEN(256), + [OVPN_A_KEYDIR_NONCE_TAIL] =3D NLA_POLICY_EXACT_LEN(OVPN_NONCE_TAIL_SIZE), +}; + +const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_LINK_TX_PACKETS + = 1] =3D { + [OVPN_A_PEER_ID] =3D NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range= ), + [OVPN_A_PEER_REMOTE_IPV4] =3D { .type =3D NLA_BE32, }, + [OVPN_A_PEER_REMOTE_IPV6] =3D NLA_POLICY_EXACT_LEN(16), + [OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID] =3D { .type =3D NLA_U32, }, + [OVPN_A_PEER_REMOTE_PORT] =3D NLA_POLICY_MIN(NLA_BE16, 1), + [OVPN_A_PEER_SOCKET] =3D { .type =3D NLA_U32, }, + [OVPN_A_PEER_SOCKET_NETNSID] =3D { .type =3D NLA_S32, }, + [OVPN_A_PEER_VPN_IPV4] =3D { .type =3D NLA_BE32, }, + [OVPN_A_PEER_VPN_IPV6] =3D NLA_POLICY_EXACT_LEN(16), + [OVPN_A_PEER_LOCAL_IPV4] =3D { .type =3D NLA_BE32, }, + [OVPN_A_PEER_LOCAL_IPV6] =3D NLA_POLICY_EXACT_LEN(16), + [OVPN_A_PEER_LOCAL_PORT] =3D NLA_POLICY_MIN(NLA_BE16, 1), + [OVPN_A_PEER_KEEPALIVE_INTERVAL] =3D { .type =3D NLA_U32, }, + [OVPN_A_PEER_KEEPALIVE_TIMEOUT] =3D { .type =3D NLA_U32, }, + [OVPN_A_PEER_DEL_REASON] =3D NLA_POLICY_MAX(NLA_U32, 4), + [OVPN_A_PEER_VPN_RX_BYTES] =3D { .type =3D NLA_UINT, }, + [OVPN_A_PEER_VPN_TX_BYTES] =3D { .type =3D NLA_UINT, }, + [OVPN_A_PEER_VPN_RX_PACKETS] =3D { .type =3D NLA_UINT, }, + [OVPN_A_PEER_VPN_TX_PACKETS] =3D { .type =3D NLA_UINT, }, + [OVPN_A_PEER_LINK_RX_BYTES] =3D { .type =3D NLA_UINT, }, + [OVPN_A_PEER_LINK_TX_BYTES] =3D { .type =3D NLA_UINT, }, + [OVPN_A_PEER_LINK_RX_PACKETS] =3D { .type =3D NLA_UINT, }, + [OVPN_A_PEER_LINK_TX_PACKETS] =3D { .type =3D NLA_UINT, }, +}; + +/* OVPN_CMD_PEER_NEW - do */ +static const struct nla_policy ovpn_peer_new_nl_policy[OVPN_A_PEER + 1] = =3D { + [OVPN_A_IFINDEX] =3D { .type =3D NLA_U32, }, + [OVPN_A_PEER] =3D NLA_POLICY_NESTED(ovpn_peer_nl_policy), +}; + +/* OVPN_CMD_PEER_SET - do */ +static const struct nla_policy ovpn_peer_set_nl_policy[OVPN_A_PEER + 1] = =3D { + [OVPN_A_IFINDEX] =3D { .type =3D NLA_U32, }, + [OVPN_A_PEER] =3D NLA_POLICY_NESTED(ovpn_peer_nl_policy), +}; + +/* OVPN_CMD_PEER_GET - do */ +static const struct nla_policy ovpn_peer_get_do_nl_policy[OVPN_A_PEER + 1]= =3D { + [OVPN_A_IFINDEX] =3D { .type =3D NLA_U32, }, + [OVPN_A_PEER] =3D NLA_POLICY_NESTED(ovpn_peer_nl_policy), +}; + +/* OVPN_CMD_PEER_GET - dump */ +static const struct nla_policy ovpn_peer_get_dump_nl_policy[OVPN_A_IFINDEX= + 1] =3D { + [OVPN_A_IFINDEX] =3D { .type =3D NLA_U32, }, +}; + +/* OVPN_CMD_PEER_DEL - do */ +static const struct nla_policy ovpn_peer_del_nl_policy[OVPN_A_PEER + 1] = =3D { + [OVPN_A_IFINDEX] =3D { .type =3D NLA_U32, }, + [OVPN_A_PEER] =3D NLA_POLICY_NESTED(ovpn_peer_nl_policy), +}; + +/* OVPN_CMD_KEY_NEW - do */ +static const struct nla_policy ovpn_key_new_nl_policy[OVPN_A_KEYCONF + 1] = =3D { + [OVPN_A_IFINDEX] =3D { .type =3D NLA_U32, }, + [OVPN_A_KEYCONF] =3D NLA_POLICY_NESTED(ovpn_keyconf_nl_policy), +}; + +/* OVPN_CMD_KEY_GET - do */ +static const struct nla_policy ovpn_key_get_nl_policy[OVPN_A_KEYCONF + 1] = =3D { + [OVPN_A_IFINDEX] =3D { .type =3D NLA_U32, }, + [OVPN_A_KEYCONF] =3D NLA_POLICY_NESTED(ovpn_keyconf_nl_policy), +}; + +/* OVPN_CMD_KEY_SWAP - do */ +static const struct nla_policy ovpn_key_swap_nl_policy[OVPN_A_KEYCONF + 1]= =3D { + [OVPN_A_IFINDEX] =3D { .type =3D NLA_U32, }, + [OVPN_A_KEYCONF] =3D NLA_POLICY_NESTED(ovpn_keyconf_nl_policy), +}; + +/* OVPN_CMD_KEY_DEL - do */ +static const struct nla_policy ovpn_key_del_nl_policy[OVPN_A_KEYCONF + 1] = =3D { + [OVPN_A_IFINDEX] =3D { .type =3D NLA_U32, }, + [OVPN_A_KEYCONF] =3D NLA_POLICY_NESTED(ovpn_keyconf_nl_policy), +}; + +/* Ops table for ovpn */ +static const struct genl_split_ops ovpn_nl_ops[] =3D { + { + .cmd =3D OVPN_CMD_PEER_NEW, + .pre_doit =3D ovpn_nl_pre_doit, + .doit =3D ovpn_nl_peer_new_doit, + .post_doit =3D ovpn_nl_post_doit, + .policy =3D ovpn_peer_new_nl_policy, + .maxattr =3D OVPN_A_PEER, + .flags =3D GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd =3D OVPN_CMD_PEER_SET, + .pre_doit =3D ovpn_nl_pre_doit, + .doit =3D ovpn_nl_peer_set_doit, + .post_doit =3D ovpn_nl_post_doit, + .policy =3D ovpn_peer_set_nl_policy, + .maxattr =3D OVPN_A_PEER, + .flags =3D GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd =3D OVPN_CMD_PEER_GET, + .pre_doit =3D ovpn_nl_pre_doit, + .doit =3D ovpn_nl_peer_get_doit, + .post_doit =3D ovpn_nl_post_doit, + .policy =3D ovpn_peer_get_do_nl_policy, + .maxattr =3D OVPN_A_PEER, + .flags =3D GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd =3D OVPN_CMD_PEER_GET, + .dumpit =3D ovpn_nl_peer_get_dumpit, + .policy =3D ovpn_peer_get_dump_nl_policy, + .maxattr =3D OVPN_A_IFINDEX, + .flags =3D GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd =3D OVPN_CMD_PEER_DEL, + .pre_doit =3D ovpn_nl_pre_doit, + .doit =3D ovpn_nl_peer_del_doit, + .post_doit =3D ovpn_nl_post_doit, + .policy =3D ovpn_peer_del_nl_policy, + .maxattr =3D OVPN_A_PEER, + .flags =3D GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd =3D OVPN_CMD_KEY_NEW, + .pre_doit =3D ovpn_nl_pre_doit, + .doit =3D ovpn_nl_key_new_doit, + .post_doit =3D ovpn_nl_post_doit, + .policy =3D ovpn_key_new_nl_policy, + .maxattr =3D OVPN_A_KEYCONF, + .flags =3D GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd =3D OVPN_CMD_KEY_GET, + .pre_doit =3D ovpn_nl_pre_doit, + .doit =3D ovpn_nl_key_get_doit, + .post_doit =3D ovpn_nl_post_doit, + .policy =3D ovpn_key_get_nl_policy, + .maxattr =3D OVPN_A_KEYCONF, + .flags =3D GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd =3D OVPN_CMD_KEY_SWAP, + .pre_doit =3D ovpn_nl_pre_doit, + .doit =3D ovpn_nl_key_swap_doit, + .post_doit =3D ovpn_nl_post_doit, + .policy =3D ovpn_key_swap_nl_policy, + .maxattr =3D OVPN_A_KEYCONF, + .flags =3D GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd =3D OVPN_CMD_KEY_DEL, + .pre_doit =3D ovpn_nl_pre_doit, + .doit =3D ovpn_nl_key_del_doit, + .post_doit =3D ovpn_nl_post_doit, + .policy =3D ovpn_key_del_nl_policy, + .maxattr =3D OVPN_A_KEYCONF, + .flags =3D GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, +}; + +static const struct genl_multicast_group ovpn_nl_mcgrps[] =3D { + [OVPN_NLGRP_PEERS] =3D { "peers", }, +}; + +struct genl_family ovpn_nl_family __ro_after_init =3D { + .name =3D OVPN_FAMILY_NAME, + .version =3D OVPN_FAMILY_VERSION, + .netnsok =3D true, + .parallel_ops =3D true, + .module =3D THIS_MODULE, + .split_ops =3D ovpn_nl_ops, + .n_split_ops =3D ARRAY_SIZE(ovpn_nl_ops), + .mcgrps =3D ovpn_nl_mcgrps, + .n_mcgrps =3D 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 0000000000000000000000000000000000000000..66a4e4a0a055b4477b67801ded8= 25e9ec068b0e6 --- /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-Cl= ause) */ +/* 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 +#include + +#include + +/* Common nested types */ +extern const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRY= PT_DIR + 1]; +extern const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_T= AIL + 1]; +extern const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_LINK_TX_PAC= KETS + 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_peer_new_doit(struct sk_buff *skb, struct genl_info *info); +int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info); +int ovpn_nl_peer_get_doit(struct sk_buff *skb, struct genl_info *info); +int ovpn_nl_peer_get_dumpit(struct sk_buff *skb, struct netlink_callback *= cb); +int ovpn_nl_peer_del_doit(struct sk_buff *skb, struct genl_info *info); +int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info); +int ovpn_nl_key_get_doit(struct sk_buff *skb, struct genl_info *info); +int ovpn_nl_key_swap_doit(struct sk_buff *skb, struct genl_info *info); +int ovpn_nl_key_del_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 0000000000000000000000000000000000000000..8d267d4c82283d9b5f989478102= 086ce385195d5 --- /dev/null +++ b/drivers/net/ovpn/netlink.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#include +#include + +#include + +#include "ovpnpriv.h" +#include "main.h" +#include "netlink.h" +#include "netlink-gen.h" + +MODULE_ALIAS_GENL_FAMILY(OVPN_FAMILY_NAME); + +/** + * ovpn_get_dev_from_attrs - retrieve the ovpn private data from the netde= vice + * a netlink message is targeting + * @net: network namespace where to look for the interface + * @info: generic netlink info from the user request + * @tracker: tracker object to be used for the netdev reference acquisition + * + * Return: the ovpn private data, if found, or an error otherwise + */ +static struct ovpn_priv * +ovpn_get_dev_from_attrs(struct net *net, const struct genl_info *info, + netdevice_tracker *tracker) +{ + struct ovpn_priv *ovpn; + struct net_device *dev; + int ifindex; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_IFINDEX)) + return ERR_PTR(-EINVAL); + + ifindex =3D nla_get_u32(info->attrs[OVPN_A_IFINDEX]); + + rcu_read_lock(); + dev =3D dev_get_by_index_rcu(net, ifindex); + if (!dev) { + rcu_read_unlock(); + NL_SET_ERR_MSG_MOD(info->extack, + "ifindex does not match any interface"); + return ERR_PTR(-ENODEV); + } + + if (!ovpn_dev_is_valid(dev)) { + rcu_read_unlock(); + 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); + } + + ovpn =3D netdev_priv(dev); + netdev_hold(dev, tracker, GFP_ATOMIC); + rcu_read_unlock(); + + return ovpn; +} + +int ovpn_nl_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + netdevice_tracker *tracker =3D (netdevice_tracker *)&info->user_ptr[1]; + struct ovpn_priv *ovpn =3D ovpn_get_dev_from_attrs(genl_info_net(info), + info, tracker); + + if (IS_ERR(ovpn)) + return PTR_ERR(ovpn); + + info->user_ptr[0] =3D ovpn; + + return 0; +} + +void ovpn_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *s= kb, + struct genl_info *info) +{ + netdevice_tracker *tracker =3D (netdevice_tracker *)&info->user_ptr[1]; + struct ovpn_priv *ovpn =3D info->user_ptr[0]; + + if (ovpn) + netdev_put(ovpn->dev, tracker); +} + +int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) +{ + return -EOPNOTSUPP; +} + +int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) +{ + return -EOPNOTSUPP; +} + +int ovpn_nl_peer_get_doit(struct sk_buff *skb, struct genl_info *info) +{ + return -EOPNOTSUPP; +} + +int ovpn_nl_peer_get_dumpit(struct sk_buff *skb, struct netlink_callback *= cb) +{ + return -EOPNOTSUPP; +} + +int ovpn_nl_peer_del_doit(struct sk_buff *skb, struct genl_info *info) +{ + return -EOPNOTSUPP; +} + +int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info) +{ + return -EOPNOTSUPP; +} + +int ovpn_nl_key_get_doit(struct sk_buff *skb, struct genl_info *info) +{ + return -EOPNOTSUPP; +} + +int ovpn_nl_key_swap_doit(struct sk_buff *skb, struct genl_info *info) +{ + return -EOPNOTSUPP; +} + +int ovpn_nl_key_del_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 =3D 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 0000000000000000000000000000000000000000..0d6c34e17082cc7c52dd9c5d5ed= 1e964925b3f4b --- /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-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#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/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h new file mode 100644 index 0000000000000000000000000000000000000000..f9322536b06d6baa5524de57cd7= d69f5ecbbd194 --- /dev/null +++ b/drivers/net/ovpn/ovpnpriv.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNSTRUCT_H_ +#define _NET_OVPN_OVPNSTRUCT_H_ + +/** + * struct ovpn_priv - per ovpn interface state + * @dev: the actual netdev representing the tunnel + */ +struct ovpn_priv { + 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 0000000000000000000000000000000000000000..680d1522dc87c222bddc33cbb7b= cef290e2dab9a --- /dev/null +++ b/include/uapi/linux/ovpn.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Cl= ause) */ +/* 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_A_PEER_ID =3D 1, + OVPN_A_PEER_REMOTE_IPV4, + OVPN_A_PEER_REMOTE_IPV6, + OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID, + OVPN_A_PEER_REMOTE_PORT, + OVPN_A_PEER_SOCKET, + OVPN_A_PEER_SOCKET_NETNSID, + OVPN_A_PEER_VPN_IPV4, + OVPN_A_PEER_VPN_IPV6, + OVPN_A_PEER_LOCAL_IPV4, + OVPN_A_PEER_LOCAL_IPV6, + OVPN_A_PEER_LOCAL_PORT, + OVPN_A_PEER_KEEPALIVE_INTERVAL, + OVPN_A_PEER_KEEPALIVE_TIMEOUT, + OVPN_A_PEER_DEL_REASON, + 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 =3D (__OVPN_A_PEER_MAX - 1) +}; + +enum { + OVPN_A_KEYCONF_PEER_ID =3D 1, + OVPN_A_KEYCONF_SLOT, + 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 =3D (__OVPN_A_KEYCONF_MAX - 1) +}; + +enum { + OVPN_A_KEYDIR_CIPHER_KEY =3D 1, + OVPN_A_KEYDIR_NONCE_TAIL, + + __OVPN_A_KEYDIR_MAX, + OVPN_A_KEYDIR_MAX =3D (__OVPN_A_KEYDIR_MAX - 1) +}; + +enum { + OVPN_A_IFINDEX =3D 1, + OVPN_A_PEER, + OVPN_A_KEYCONF, + + __OVPN_A_MAX, + OVPN_A_MAX =3D (__OVPN_A_MAX - 1) +}; + +enum { + OVPN_CMD_PEER_NEW =3D 1, + OVPN_CMD_PEER_SET, + OVPN_CMD_PEER_GET, + OVPN_CMD_PEER_DEL, + OVPN_CMD_PEER_DEL_NTF, + OVPN_CMD_KEY_NEW, + OVPN_CMD_KEY_GET, + OVPN_CMD_KEY_SWAP, + OVPN_CMD_KEY_SWAP_NTF, + OVPN_CMD_KEY_DEL, + + __OVPN_CMD_MAX, + OVPN_CMD_MAX =3D (__OVPN_CMD_MAX - 1) +}; + +#define OVPN_MCGRP_PEERS "peers" + +#endif /* _UAPI_LINUX_OVPN_H */ --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f48.google.com (mail-wm1-f48.google.com [209.85.128.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4230D2918C2 for ; Tue, 15 Apr 2025 11:17:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715854; cv=none; b=Uqe9evwbjOzcWZPlUxqdJSG09tSYxX4vXB3/7F/LI7qps92Gi+uS0u9s3iETstYuJniHT2mYrTaVD7jG+S4D1R8ShSggaiMB9c/X/9X5rpbu8CoEu8ibXPTFrx27clTeLyLfVhHMi/EN52rO2UuT7aev+VZPSyey1lsYxxKMJJU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715854; c=relaxed/simple; bh=6M7VDPb8ZaAkBDxdil55Uh4SFR0vRpCZSKRlt3aKnP4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=OLZxcVvReKDkAhJN9kQyOUoq+ffAUnB8IZ73xRVdXdz1whBeJFZutdzSh05gcXWpA7UWRFpjSfLJBZ630qN5BUDrdvcFk1Ya6sX61nx5gV/yD9DRwQs+/WJVkTaQv3QUZtYNgs/i4ufZtPJIIzYvos6Itw7m0eqlTHyovezhfWg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=IkdlQqZQ; arc=none smtp.client-ip=209.85.128.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="IkdlQqZQ" Received: by mail-wm1-f48.google.com with SMTP id 5b1f17b1804b1-43cf848528aso45050895e9.2 for ; Tue, 15 Apr 2025 04:17:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715848; x=1745320648; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=KnYz6AacJi8VsUnoFC+BvwQkfCfPj0BvzCFwTKyd1FM=; b=IkdlQqZQKb19JuSb/qN8WQCyOr+OJx2GuhQhhVVDuLP5rQTDwHhOEFvS/htjw6ZwRk dkz9oEh/Wcd2piurfYch0Vx5xjM1mIZyP+PR/GQOJcskQ1ttw8iMcMblGe2fIEocXV5t kbf9fzHjEdNkIoof+0/vHbAXL/Jt1xV+wupQ3TzpQFKoY+ls5yHTOBSHET0yqdHtsmrU a+i0VR4YrGFA2kL5ozesgO4jbnkTwv4Ag4gxEQz5niSs6GG55s5dpkKKY87NQEzcMcqs VtRawkpEV2sM6gCE1KDlOmc59fXtXEsM/r3sizf5N1WXJ6udkgySFVQQua/tsV9Yf7r6 DAiQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715848; x=1745320648; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=KnYz6AacJi8VsUnoFC+BvwQkfCfPj0BvzCFwTKyd1FM=; b=LMwRJW4Q7gEJLroPrhD2kzoFhV5RhZin4OZs/QVnvx2Qo9MLFrDyz910zxy0jCK4eL XTA+mfMnLSgQAjQtjOUYQMoWAd/Q+GeZ0Xef6Bc1xoUZb5reG6FkmKuPpqPKqIznNiXc PnP3cP2cNB0P/wGUIECQ+Wpy2zPpxjadiart1E1iVULgewrxQ6+4dRjsIx6IOQw+TrwU 7M8ewRXcCEvvXpSlu/qqUR+OA6wDPrTvXMbcJwBlDiB3C+hrYBF42D1sNipt31pabnW5 9BAw7+2R60i/x0i5BJmKN6d9qYMaRowTpX1lv1fItiUZvsif52Wb8n3InxAPqFkjsOSw 4pXw== X-Forwarded-Encrypted: i=1; AJvYcCUiaHB7sjfayyjuGB5Gr4KAjYRB+8x8BzFuN5xksbxZSRbIwi6kSQHHfXn7K7KXSy+GW1XsjbfEQRyXAy8=@vger.kernel.org X-Gm-Message-State: AOJu0YweuWd+rApfUTT0B9mqVoEl7Q1ATUJfNxXanq3rq3mk0Prqga15 wwuSnC5fUNRw73ILX0o0I3913bafwH5jLN02x/+gkS7qIMPpdXUoS6nhZd/Y/M72FqYDFlu9t3K Qr15cpVr+V1t0A6vBGo3nRBp7rxoPK/Vb/cOYfs2+iL+XaoBdxSsZ4rI= X-Gm-Gg: ASbGncseFnus7U4VKHRle2koPH9q24P3AfyIpsPkp3iDeZtxam09f9w/uG8l3VKbZj6 F80XWOWnUPAqyOpc5xsyFwRMq2HF1mjf7zIrYo+6D2B7qt1cZGbZsbuR8ON6BevRouTMUhmese4 CWEirMNuetZyw+1LzopWQQiUDTkKlDkBXtxTmwKbKLKYF2r8u7LuYVlTG+ueQEvES0d40Yp0Iy4 j86Q4or4ey5btJnMWHyID4nPEiMX/znrhy5vXaRZGZB4+Ft8eP5Snw3X4GIjz2gxryCYetkhAp4 fIok4Lwe02XWtMzaQPdMPlC28tPd2y6ndxtQPWb7RN7eQkyB X-Google-Smtp-Source: AGHT+IE9LEzmJp1QzwcjiF3SEAuGpxqNUVUvkP9pjx7dy2rC+1r0ZI7NB22tcyguysQ6gSySJelrow== X-Received: by 2002:a05:600c:74a:b0:43c:f332:7038 with SMTP id 5b1f17b1804b1-43f3d15dbb8mr84900145e9.21.1744715848568; Tue, 15 Apr 2025 04:17:28 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:28 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:20 +0200 Subject: [PATCH net-next v26 03/23] ovpn: add basic interface creation/destruction/management routines Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-3-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=9683; i=antonio@openvpn.net; h=from:subject:message-id; bh=6M7VDPb8ZaAkBDxdil55Uh4SFR0vRpCZSKRlt3aKnP4=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBBeOmBEO9y7J/WmXrVwFtQlKYnauiXkqwQ4 5fOKSgslQaJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQQAKCRALcOU6oDjV h2yoCACkm8HCocLP6NM4VaB0VnZO1dw2/dAT9FEx+18r1FS32gf5OQzUaitboQpCrA/qviv3reu iVfC9F6iit6OlFZiNiLKQe0xaSpzDnAS3WOVdm9cqst2inucJQQvnQD48vahMXTafwY4CX+4cy5 R0STCXfz2O2x3s7hZrUMPF4qTZVirkPOkjZEAiNcduTDSQSvLf2QmS6UF6R6zGLO0A+E6gwYNIE vHCvymQwyZI+YQGhkAdK7F/MycOQW7O3781xBtaH4ByZ3wsEc9SKR5xbkV8DFJ3hURtx/LchF58 g5dFcV6IV/eRZc8Fm0u7QsYJ3IZL5tGH+C5FdZ3ooF/rAr86 X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C Add basic infrastructure for handling ovpn interfaces. Tested-by: Donald Hunter Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- Documentation/netlink/specs/rt-link.yaml | 16 +++++++ drivers/net/ovpn/Makefile | 1 + drivers/net/ovpn/io.c | 22 +++++++++ drivers/net/ovpn/io.h | 24 ++++++++++ drivers/net/ovpn/main.c | 82 ++++++++++++++++++++++++++++= +++- drivers/net/ovpn/ovpnpriv.h | 5 ++ drivers/net/ovpn/proto.h | 38 +++++++++++++++ include/uapi/linux/if_link.h | 15 ++++++ 8 files changed, 201 insertions(+), 2 deletions(-) diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netli= nk/specs/rt-link.yaml index 31238455f8e9d29531884cad4951391fa47ccfaf..a50d9d7d882e7e4f9de29b2a4e7= acc602972f6b3 100644 --- a/Documentation/netlink/specs/rt-link.yaml +++ b/Documentation/netlink/specs/rt-link.yaml @@ -938,6 +938,12 @@ definitions: entries: - name: none - name: default + - + name: ovpn-mode + type: enum + entries: + - p2p + - mp =20 attribute-sets: - @@ -2272,6 +2278,13 @@ attribute-sets: - name: tailroom type: u16 + - + name: linkinfo-ovpn-attrs + attributes: + - + name: mode + type: u8 + enum: ovpn-mode =20 sub-messages: - @@ -2322,6 +2335,9 @@ sub-messages: - value: netkit attribute-set: linkinfo-netkit-attrs + - + value: ovpn + attribute-set: linkinfo-ovpn-attrs - name: linkinfo-member-data-msg formats: diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 75ac62bba02937bc49cb2a0dec5ca3cc31a8ee00..0e5f686672fb5052cee5a2c2879= 7b70859514a7f 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -8,5 +8,6 @@ =20 obj-$(CONFIG_OVPN) :=3D ovpn.o ovpn-y +=3D main.o +ovpn-y +=3D io.o ovpn-y +=3D netlink.o ovpn-y +=3D netlink-gen.o diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c new file mode 100644 index 0000000000000000000000000000000000000000..4b71c38165d7adbb1a2d1a64d27= a13b7f76cfbfe --- /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-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include + +#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 0000000000000000000000000000000000000000..afea5f81f5628dcb9afda9a7897= 4bbf6f2101c13 --- /dev/null +++ b/drivers/net/ovpn/io.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPN_H_ +#define _NET_OVPN_OVPN_H_ + +/* DATA_V2 header size with AEAD encryption */ +#define OVPN_HEAD_ROOM (OVPN_OPCODE_SIZE + OVPN_NONCE_WIRE_SIZE + \ + 16 /* AEAD TAG length */ + \ + max(sizeof(struct udphdr), sizeof(struct tcphdr)) +\ + max(sizeof(struct ipv6hdr), sizeof(struct iphdr))) + +/* max padding required by encryption */ +#define OVPN_MAX_PADDING 16 + +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 index 719cac3db6ef4e06b089790afefb2e66223d43d3..ea7dad374c0088cab46282c61cd= 8fef65bab0d5c 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -10,14 +10,28 @@ #include #include #include +#include +#include #include -#include +#include =20 #include "ovpnpriv.h" #include "main.h" #include "netlink.h" +#include "io.h" +#include "proto.h" =20 static const struct net_device_ops ovpn_netdev_ops =3D { + .ndo_start_xmit =3D ovpn_net_xmit, +}; + +static const struct device_type ovpn_type =3D { + .name =3D OVPN_FAMILY_NAME, +}; + +static const struct nla_policy ovpn_policy[IFLA_OVPN_MAX + 1] =3D { + [IFLA_OVPN_MODE] =3D NLA_POLICY_RANGE(NLA_U8, OVPN_MODE_P2P, + OVPN_MODE_MP), }; =20 /** @@ -31,18 +45,82 @@ bool ovpn_dev_is_valid(const struct net_device *dev) return dev->netdev_ops =3D=3D &ovpn_netdev_ops; } =20 +static void ovpn_setup(struct net_device *dev) +{ + netdev_features_t feat =3D NETIF_F_SG | NETIF_F_GSO | + NETIF_F_GSO_SOFTWARE | NETIF_F_HIGHDMA; + + dev->needs_free_netdev =3D true; + + dev->pcpu_stat_type =3D NETDEV_PCPU_STAT_DSTATS; + + dev->netdev_ops =3D &ovpn_netdev_ops; + + dev->hard_header_len =3D 0; + dev->addr_len =3D 0; + dev->mtu =3D ETH_DATA_LEN - OVPN_HEAD_ROOM; + dev->min_mtu =3D IPV4_MIN_MTU; + dev->max_mtu =3D IP_MAX_MTU - OVPN_HEAD_ROOM; + + dev->type =3D ARPHRD_NONE; + dev->flags =3D IFF_POINTOPOINT | IFF_NOARP; + dev->priv_flags |=3D IFF_NO_QUEUE; + + dev->lltx =3D true; + dev->features |=3D feat; + dev->hw_features |=3D feat; + dev->hw_enc_features |=3D feat; + + dev->needed_headroom =3D ALIGN(OVPN_HEAD_ROOM, 4); + dev->needed_tailroom =3D OVPN_MAX_PADDING; + + SET_NETDEV_DEVTYPE(dev, &ovpn_type); +} + static int ovpn_newlink(struct net_device *dev, struct rtnl_newlink_params *params, struct netlink_ext_ack *extack) { - return -EOPNOTSUPP; + struct ovpn_priv *ovpn =3D netdev_priv(dev); + struct nlattr **data =3D params->data; + enum ovpn_mode mode =3D OVPN_MODE_P2P; + + if (data && data[IFLA_OVPN_MODE]) { + mode =3D nla_get_u8(data[IFLA_OVPN_MODE]); + netdev_dbg(dev, "setting device mode: %u\n", mode); + } + + ovpn->dev =3D dev; + ovpn->mode =3D mode; + + /* turn carrier explicitly off after registration, this way state is + * clearly defined + */ + netif_carrier_off(dev); + + return register_netdevice(dev); +} + +static int ovpn_fill_info(struct sk_buff *skb, const struct net_device *de= v) +{ + struct ovpn_priv *ovpn =3D netdev_priv(dev); + + if (nla_put_u8(skb, IFLA_OVPN_MODE, ovpn->mode)) + return -EMSGSIZE; + + return 0; } =20 static struct rtnl_link_ops ovpn_link_ops =3D { .kind =3D "ovpn", .netns_refund =3D false, + .priv_size =3D sizeof(struct ovpn_priv), + .setup =3D ovpn_setup, + .policy =3D ovpn_policy, + .maxtype =3D IFLA_OVPN_MAX, .newlink =3D ovpn_newlink, .dellink =3D unregister_netdevice_queue, + .fill_info =3D ovpn_fill_info, }; =20 static int __init ovpn_init(void) diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index f9322536b06d6baa5524de57cd7d69f5ecbbd194..b6b644668d46604ad0f21d59cf4= 79386f4ec1e3e 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -10,12 +10,17 @@ #ifndef _NET_OVPN_OVPNSTRUCT_H_ #define _NET_OVPN_OVPNSTRUCT_H_ =20 +#include +#include + /** * struct ovpn_priv - per ovpn interface state * @dev: the actual netdev representing the tunnel + * @mode: device operation mode (i.e. p2p, mp, ..) */ struct ovpn_priv { struct net_device *dev; + enum ovpn_mode mode; }; =20 #endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/drivers/net/ovpn/proto.h b/drivers/net/ovpn/proto.h new file mode 100644 index 0000000000000000000000000000000000000000..5f95a78bebd3702868ffeeab3ea= 4938e957d568c --- /dev/null +++ b/drivers/net/ovpn/proto.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + * James Yonan + */ + +#ifndef _NET_OVPN_PROTO_H_ +#define _NET_OVPN_PROTO_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 ] -> OVPN_NONCE_SIZE + * [4-bytes -> OVPN_NONCE_WIRE_SIZE + * on wire] + */ + +/* nonce size (96bits) as required by AEAD ciphers */ +#define OVPN_NONCE_SIZE 12 +/* last 8 bytes of AEAD nonce: provided by userspace and usually derived + * from key material generated during TLS handshake + */ +#define OVPN_NONCE_TAIL_SIZE 8 + +/* 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 OVPN_NONCE_WIRE_SIZE (OVPN_NONCE_SIZE - OVPN_NONCE_TAIL_SIZE) + +#define OVPN_OPCODE_SIZE 4 /* DATA_V2 opcode size */ + +#endif /* _NET_OVPN_PROTO_H_ */ diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index 318386cc5b0d19ed6a37734feffb450353dd9440..3ad2d5d9803479a10a6b2cfab2d= f98ce0f823926 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h @@ -1986,4 +1986,19 @@ enum { =20 #define IFLA_DSA_MAX (__IFLA_DSA_MAX - 1) =20 +/* OVPN section */ + +enum ovpn_mode { + OVPN_MODE_P2P, + OVPN_MODE_MP, +}; + +enum { + IFLA_OVPN_UNSPEC, + IFLA_OVPN_MODE, + __IFLA_OVPN_MAX, +}; + +#define IFLA_OVPN_MAX (__IFLA_OVPN_MAX - 1) + #endif /* _UAPI_LINUX_IF_LINK_H */ --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f47.google.com (mail-wm1-f47.google.com [209.85.128.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 801E029291F for ; Tue, 15 Apr 2025 11:17:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715854; cv=none; b=dYx0Ttsa85uy8ua1rPf+uG8FCqpSUzBaijCgeIl92niSR3xJB1+BS1qPM0qny2F3wMR8Y+UC9bi1eBJf2kC2z/FVRr4ioZ9KndbfIuqiQigaWaHVNd1OceneNhLZM+sIDrrNjYfAk+1VuOYAiWjCBuD7CaUMFFRfLFYluGxqba4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715854; c=relaxed/simple; bh=7WU3xSRBMuXGHUG7i/5ocEV9C8YkxglpMrWb+0YdQF8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=fSWVPYh52VFTFVFxpSEgdX/j+kuimbXH5C5LorYsHNXVHbtejcFgOmHMzON+kbQaf36lT029TGSnbLbhO49AdGeOHClHvrPH6eNUc0XQ/pvqE59v9GG2Fe5kP+rixwqoyy0pJITVZ5foDI1anA2xhROPH9N42o5QzpLjbokaV0w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=Bk5dyTrD; arc=none smtp.client-ip=209.85.128.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="Bk5dyTrD" Received: by mail-wm1-f47.google.com with SMTP id 5b1f17b1804b1-43690d4605dso42516335e9.0 for ; Tue, 15 Apr 2025 04:17:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715850; x=1745320650; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=o7JYY+X3NkaUiB+MENoOw1C0gTVvLEJ/wcBfl0EfVo0=; b=Bk5dyTrD7eo2B+3skMZU4ZT7dabAVaBgS3rs64t2itOZZOpxQdmkUCTEDPxAq4rTak I3YWyB/LuTtofUihWa/Y9Wo5XhP290pfvpHMk7kcB/y9BuAZ/Wruk72mBnZVwd3oe8dX grzsT+Ur8h/KI9AwaFueir47zt6VcuDT/EQJ2Cl0+wz0uiOSdHaemWkMh7uq3PVQBMfn bzegGB8Z1ScOp8vs2OMyOTgNPGIm+u+0kJGHipLI4/9yKardzE+Nxv+2JwyDmAsj5OUc EA2UYXDDIUc4Eh3n0fbq9LED0YY7/ymalZ4Bs/vZqrQe1ELoZu/bKg1g6tfdmNkY7ujy Z5kQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715850; x=1745320650; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=o7JYY+X3NkaUiB+MENoOw1C0gTVvLEJ/wcBfl0EfVo0=; b=TbT3FBzmiqu9enUrQFBHLQJskP3ylrCT5+vrltI8QMx4DYUhHgRRRLctfqwlxxPi6n Z0WsT2mIdlwrnXI0VM/Ag4LYI7rcKGLZjjv6rcsh0aunHcElt4wDonCUm0xQJ9fFzjAu lT0LXVoo+XK2qgkj1Sa3FIhHwhhHb/fO2ffU6iFeSPW1oOgLX4FvOj3vuj3V7cxnXXf0 haHusRoNyeW+2ncs0gxFOhA7nesR8TpUbIuq+P5EjO1ftS9J+/1dT+2yYTzl7zhUXktR xFYDhBLS+LY/qEjbaCxlS0Zo6ioht13ORW5AnkzD0qE3fgLC23a6WKzNbUmnFkJCGtse BFJA== X-Forwarded-Encrypted: i=1; AJvYcCUfEHbQMXzz5uYYnkNbvhhYZOsaN5LK1J6EX/gZ3oPx8j2wHM3YXdqZIG9JbqQydW3lFe1L6ML3XoIEJsQ=@vger.kernel.org X-Gm-Message-State: AOJu0YwbjzyL9sxY7s5Bxu/PbJ3go7qTSJX7flmqmdLkvmMX/E4y9scJ 3WqtwQRmAjYJEO9Fn/CKvzG7UWjhNIPUKLJZm+Yx0w5T3nTStIc5J3kBNMp/3CJgTZZGTIlYHKA YKDqEzap/XzF+rAGQNUB+ctu1xIGJ26TQFEWFomEsvmzOSomybtba/cs= X-Gm-Gg: ASbGncuYGZJnQhT6NYkvsCeJuBJ1QuczmCAbDFCa1Jimr4/DUbiuKdgvAlEdPrmTa9Q V9yxnDAqFBKy579BFtAgve2qG3QshzGWXDvar9xR/egn5qXmnEHEEhAbqZH3RKJk0+AhF47Hoof fZ1gJ5jey0tgZFYdR96XK0yGC5Ub1kNJiPQify7fm43tSPbrUq/DJBt7qCzF+jWPYv94Ys1rBJX 3PCogEpXSl0eBYrUyxot+ugoN5nnnqjzozLSJjGU04KXTYvILjWpM6+eL0uuNj+qxr1CVrCtZLs n057sBes1k/UWTptzaprT8bdo1AFJIJzk6D6Pw== X-Google-Smtp-Source: AGHT+IHMLMYFjSVMa8HJnw6tCAUHHCX6kSks+Xt2LOtrqibKe56yD7cKGOKws0MTUTIYWYii15T8qg== X-Received: by 2002:a05:600c:810b:b0:43c:f61e:6ea8 with SMTP id 5b1f17b1804b1-43f3a926c29mr138308855e9.2.1744715849679; Tue, 15 Apr 2025 04:17:29 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:29 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:21 +0200 Subject: [PATCH net-next v26 04/23] ovpn: keep carrier always on for MP interfaces Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-4-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=1339; i=antonio@openvpn.net; h=from:subject:message-id; bh=7WU3xSRBMuXGHUG7i/5ocEV9C8YkxglpMrWb+0YdQF8=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBBifGDIvfJdm1s+alp1EiBkjhaP4U4WT4sy lHHyAsr6sCJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQQAKCRALcOU6oDjV hzFYB/9+kmSw8/ryifu1DvtlDiB3y7rrrRvka0hGy7d8SQR1mvJy8Y1dMMnCsIHEyUq5Vpa4p6X 8EHWQ7BvzMBaOMs6yK3H7D6OFjNMJLy3qqfvG2sCaUqkAju06lE0Z0Mrn8TiK6BqeuCr/MhXPuE iNuUcS/XFic6a3eDrXRgxjIMeUKQ+WZT6Wgc6h1+i5CH+bORRvdow0QbTN73gcwAC0Y1cHsHyki 1PZVz/DfSaFvHoD4irLcBXCw8Q/GkLv3D7+rFCCNYz+IjXlTAEybCvuRH80Sv8vixCI7ton3mod veIo9bznHYc5pT1CYc8YrWTYojVDb2sQosIW7A/BuyCYA1Yq X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C An ovpn interface configured in MP mode will keep carrier always on and let the user decide when to bring it administratively up and down. This way a MP node (i.e. a server) will keep its interface always up and running, even when no peer is connected. Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/main.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index ea7dad374c0088cab46282c61cd8fef65bab0d5c..fa1dd84be24060d6dc749dc9e09= 7f166a7adea26 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -93,10 +93,18 @@ static int ovpn_newlink(struct net_device *dev, ovpn->dev =3D dev; ovpn->mode =3D mode; =20 - /* turn carrier explicitly off after registration, this way state is - * clearly defined + /* Set carrier explicitly after registration, this way state is + * clearly defined. + * + * In case of MP interfaces we keep the carrier always on. + * + * Carrier for P2P interfaces is initially off and it is then + * switched on and off when the remote peer is added or deleted. */ - netif_carrier_off(dev); + if (ovpn->mode =3D=3D OVPN_MODE_MP) + netif_carrier_on(dev); + else + netif_carrier_off(dev); =20 return register_netdevice(dev); } --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wr1-f47.google.com (mail-wr1-f47.google.com [209.85.221.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9CE7E2951C7 for ; Tue, 15 Apr 2025 11:17:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715859; cv=none; b=hGXWCOLjKmCTqhHBNey/DqQbD+rOBKVS9ORM66cMH6MdD+ocfOTZI6P6z2xpodwbuPo9rKZ5FzI0k1PDhSBcvk93zi3G1/YcC3+258eM/YHQ9wxmqa0WjAOihtJdYGV0hpvNmFGE6Cn6mFbog4FufrqGkzaoMqIc9D+K+X5WjJo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715859; c=relaxed/simple; bh=OynMgFdMemAdhPCqB9V4/6xUlIRYf7PzhNDlFf33DZY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=LI2vEPVPa85M1YePxP9VFEioYKAFhavWJ3yZYeGo6k9BvW2WSM9pecHE6rTXC3JGufcOJYxMcTOG1vP28KSIJLdfTQ2cQSyn+dWXSBWIaTkzWFHnFKLEFIJWTbAyVWSuYP8HH2gw15mmL6iQPMfSWHmTiaVmRG8KMLRWrhKRxnM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=CBWFRxto; arc=none smtp.client-ip=209.85.221.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="CBWFRxto" Received: by mail-wr1-f47.google.com with SMTP id ffacd0b85a97d-39ac56756f6so4690629f8f.2 for ; Tue, 15 Apr 2025 04:17:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715854; x=1745320654; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=8nNTItU3nsfj9boXY9j/2t3L2XCYhom564qER2zj9rg=; b=CBWFRxtoOWAlBQSI0RMy96wNj3JgCk0WobcWgFpe8UJ1SBja+P7UI3FE2UDLz7ZbZw H9/VlGKXynKipZ0N8i/SqLWStfdFPV49C7OJXoLdzMB1Jew1QAZikU4hfWVuDilYjSUa SQ6oTDP3YOK/HduV7Fx5eBC6mETydl6z9n5+nwCqksEGiLzcl5UkmVgBtIF4YZAxHDOy VTdFYB/5J4A2V4RIcCQzwykD1d4M9ozoCFJZ75JywVV+NOmKS+39VYoJ+W3hmEQ11iQ4 JJkezOTjHhHprdS+wqkMnfYxtto3PM6/4N0von95Jk4iSnLRd1Fs5Dv5O05eeewK3/JD uHNQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715854; x=1745320654; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=8nNTItU3nsfj9boXY9j/2t3L2XCYhom564qER2zj9rg=; b=hH/HPwS7ZqMkIg+2I1X3guWJsQ+UUm+/MqZTlZveJVYK6h2GqfOkd/EmSNX1rRxXDf JvJcdKjDJhO8iDdWOIOdAFZ4nVA06ltqSKLYoy3MrNKW/cEayVgxVHgGYV8jLAsNJjR6 xSxMXE2nsqzeIibAG3erk9s0jZemP0U4scnglVI0Ybuk+pj5DBmSBnFXHVQz6/n/pB+7 XjAOQ7Zb+6O8bRkgd3pgm+8A09XwlSn0ViC/3sr3omZWsQTngtvsNiIh4IDgyGA/cOCe xgw91AbEIKUmODuUxUP3DjMde/JKm882rqFBYL8Sp8k0d2ptu19EzAJNnUlf3l6FdXwE ulow== X-Forwarded-Encrypted: i=1; AJvYcCWSk9hO3W3WmDTGEsPh029XgsREdZ/1H8+aDsyUzU8NZHJ4ma28AZLwgCO7MEZ6AhQduU0mtb0c5KA4zU4=@vger.kernel.org X-Gm-Message-State: AOJu0YwrcG84NOD9BViTKIO5NiGqmQ38U/eckqRMsnHtK9OA6ftuAtl9 mfVU9fvnjS3ayYwl4K4gNulRjoGc6qotQvHFrnEWKC2Lf84RzEDEpi+RMwF/bilBh73gXI/tpgg gKDG4kv8iqSVlbTGrWNEc6Lk0RUCdl03wV8uOU71k3UsCjV8iiTxd8+8= X-Gm-Gg: ASbGncsryir0zRDkjpS4r/Ia/Fw72X/Scu3u/nzhRnhWFq2DPqQbdU8P5JuOAZ51bsc iss5VbcAmwwIDxtLW9opYw4LyOLBg4zZK+bMjS/6dDHCHsYByQK2Rp7aDreREU1dyoSBQZQKWT1 rfOCviPOz2Cc8gza9c6FpXY9gBtEGX2IMspf0y+ZgmXbfiJ/IdSic4zk3hcW57rIxafEk9d5oON xUilvDRBWdMk+LRflQb16I4FzA+54WAS2UeP41TphbuWvx34lbVBLY33JFtu0t3Lke4GVyObLG0 zXivQ15eYeEBy+Q/8x1GrnBxB3fYPI0u405HWg== X-Google-Smtp-Source: AGHT+IFg0Ph87qXX51dfMOb+Hyq0PFSa7u7iyTUclPQv6T2CZUAujmwXQS/9lHILpefrpe9/NDXoWg== X-Received: by 2002:a5d:6d84:0:b0:39d:8d54:5eac with SMTP id ffacd0b85a97d-39ea51f44ccmr13591723f8f.11.1744715850844; Tue, 15 Apr 2025 04:17:30 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:30 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:22 +0200 Subject: [PATCH net-next v26 05/23] ovpn: introduce the ovpn_peer object Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-5-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=22796; i=antonio@openvpn.net; h=from:subject:message-id; bh=OynMgFdMemAdhPCqB9V4/6xUlIRYf7PzhNDlFf33DZY=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBBYvYFxSI3rh2HVwSY/4asXa/kg7U3GDWyc rMTeDK7AeOJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQQAKCRALcOU6oDjV h5CdB/9kVj1HHiJFvlu1taWqOB/kJTrXWKdf4NfF/H+CPVAB41xAYuqe/qzgIVZs71VWdDgSElL 1hDRcqqu/DoyzQouI6PbOTfomFUlh6yzOE9r0v2QaTQsLzN2x3H+DNdb7xFA8GBlWuPyzCOtyXH PXEjZ6LgeZ1y+uYhey3C/2auRe5hxcLCcZMEDMcQAUYZihcrdz020rmJT2GLLDC87D731OaUuzB A6l693ckh9PsMGogW9yZZE/4szOT2qsNb/y5idquDcEXB/YR/9cst8AX25BI2hWevoyfdLiZE5+ XrZxjX+wqxBM32K6932CKRyn4f/RViWdftCTXPLoHqQCYe3V X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C An ovpn_peer object holds the whole status of a remote peer (regardless whether it is a server or a client). This includes status for crypto, tx/rx buffers, napi, etc. Only support for one peer is introduced (P2P mode). Multi peer support is introduced with a later patch. Along with the ovpn_peer, also the ovpn_bind object is introcued as the two are strictly related. An ovpn_bind object wraps a sockaddr representing the local coordinates being used to talk to a specific peer. Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/Kconfig | 1 + drivers/net/ovpn/Makefile | 2 + drivers/net/ovpn/bind.c | 58 +++++++ drivers/net/ovpn/bind.h | 101 +++++++++++ drivers/net/ovpn/main.c | 14 +- drivers/net/ovpn/ovpnpriv.h | 4 + drivers/net/ovpn/peer.c | 411 ++++++++++++++++++++++++++++++++++++++++= ++++ drivers/net/ovpn/peer.h | 80 +++++++++ 8 files changed, 670 insertions(+), 1 deletion(-) diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 5fbe25ae1e11e558aa9aaa857cf110127e459854..2806fcc22a2dbd9b2985b09dd6e= f65dd1dc4ebc1 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -119,6 +119,7 @@ config OVPN tristate "OpenVPN data channel offload" depends on NET && INET depends on IPV6 || !IPV6 + select DST_CACHE help This module enhances the performance of the OpenVPN userspace software by offloading the data channel processing to kernelspace. diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 0e5f686672fb5052cee5a2c28797b70859514a7f..618328ae338861b9764b42485df= 71ebd0fc1fe90 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -7,7 +7,9 @@ # Author: Antonio Quartulli =20 obj-$(CONFIG_OVPN) :=3D ovpn.o +ovpn-y +=3D bind.o ovpn-y +=3D main.o ovpn-y +=3D io.o ovpn-y +=3D netlink.o ovpn-y +=3D netlink-gen.o +ovpn-y +=3D peer.o diff --git a/drivers/net/ovpn/bind.c b/drivers/net/ovpn/bind.c new file mode 100644 index 0000000000000000000000000000000000000000..d4a1aeed12c99c71eaf5e8e9fc9= c0fe61af6aaac --- /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-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include + +#include "ovpnpriv.h" +#include "bind.h" +#include "peer.h" + +/** + * ovpn_bind_from_sockaddr - retrieve binding matching sockaddr + * @ss: the sockaddr to match + * + * Return: the bind matching the passed sockaddr if found, NULL otherwise + */ +struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *s= s) +{ + struct ovpn_bind *bind; + size_t sa_len; + + if (ss->ss_family =3D=3D AF_INET) + sa_len =3D sizeof(struct sockaddr_in); + else if (ss->ss_family =3D=3D AF_INET6) + sa_len =3D sizeof(struct sockaddr_in6); + else + return ERR_PTR(-EAFNOSUPPORT); + + bind =3D kzalloc(sizeof(*bind), GFP_ATOMIC); + if (unlikely(!bind)) + return ERR_PTR(-ENOMEM); + + memcpy(&bind->remote, ss, sa_len); + + return bind; +} + +/** + * ovpn_bind_reset - assign new binding to peer + * @peer: the peer whose binding has to be replaced + * @new: the new bind to assign + */ +void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *new) +{ + struct ovpn_bind *old; + + spin_lock_bh(&peer->lock); + old =3D rcu_replace_pointer(peer->bind, new, true); + spin_unlock_bh(&peer->lock); + + kfree_rcu(old, rcu); +} diff --git a/drivers/net/ovpn/bind.h b/drivers/net/ovpn/bind.h new file mode 100644 index 0000000000000000000000000000000000000000..4e0b8398bfd9ed60ecb01c777c6= 1a5e6841d7dec --- /dev/null +++ b/drivers/net/ovpn/bind.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2012-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNBIND_H_ +#define _NET_OVPN_OVPNBIND_H_ + +#include +#include +#include +#include +#include +#include + +struct ovpn_peer; + +/** + * union ovpn_sockaddr - basic transport layer address + * @in4: IPv4 address + * @in6: IPv6 address + */ +union ovpn_sockaddr { + struct sockaddr_in in4; + struct sockaddr_in6 in6; +}; + +/** + * struct ovpn_bind - remote peer binding + * @remote: the remote peer sockaddress + * @local: local endpoint used to talk to the peer + * @local.ipv4: local IPv4 used to talk to the peer + * @local.ipv6: local IPv6 used to talk to the peer + * @rcu: used to schedule RCU cleanup job + */ +struct ovpn_bind { + union ovpn_sockaddr remote; /* remote sockaddr */ + + union { + struct in_addr ipv4; + struct in6_addr ipv6; + } local; + + struct rcu_head rcu; +}; + +/** + * ovpn_bind_skb_src_match - match packet source with binding + * @bind: the binding to match + * @skb: the packet to match + * + * Return: true if the packet source matches the remote peer sockaddr + * in the binding + */ +static inline bool ovpn_bind_skb_src_match(const struct ovpn_bind *bind, + const struct sk_buff *skb) +{ + const union ovpn_sockaddr *remote; + + if (unlikely(!bind)) + return false; + + remote =3D &bind->remote; + + switch (skb->protocol) { + case htons(ETH_P_IP): + if (unlikely(remote->in4.sin_family !=3D AF_INET)) + return false; + + if (unlikely(remote->in4.sin_addr.s_addr !=3D ip_hdr(skb)->saddr)) + return false; + + if (unlikely(remote->in4.sin_port !=3D udp_hdr(skb)->source)) + return false; + break; + case htons(ETH_P_IPV6): + if (unlikely(remote->in6.sin6_family !=3D AF_INET6)) + return false; + + if (unlikely(!ipv6_addr_equal(&remote->in6.sin6_addr, + &ipv6_hdr(skb)->saddr))) + return false; + + if (unlikely(remote->in6.sin6_port !=3D udp_hdr(skb)->source)) + return false; + break; + default: + return false; + } + + return true; +} + +struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *s= a); +void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *bind); + +#endif /* _NET_OVPN_OVPNBIND_H_ */ diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index fa1dd84be24060d6dc749dc9e097f166a7adea26..3c2b6a7df27266dcaacc1ea4f8d= 86c686d3105aa 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -19,6 +19,7 @@ #include "main.h" #include "netlink.h" #include "io.h" +#include "peer.h" #include "proto.h" =20 static const struct net_device_ops ovpn_netdev_ops =3D { @@ -92,6 +93,7 @@ static int ovpn_newlink(struct net_device *dev, =20 ovpn->dev =3D dev; ovpn->mode =3D mode; + spin_lock_init(&ovpn->lock); =20 /* Set carrier explicitly after registration, this way state is * clearly defined. @@ -109,6 +111,16 @@ static int ovpn_newlink(struct net_device *dev, return register_netdevice(dev); } =20 +static void ovpn_dellink(struct net_device *dev, struct list_head *head) +{ + struct ovpn_priv *ovpn =3D netdev_priv(dev); + + if (ovpn->mode =3D=3D OVPN_MODE_P2P) + ovpn_peer_release_p2p(ovpn, OVPN_DEL_PEER_REASON_TEARDOWN); + + unregister_netdevice_queue(dev, head); +} + static int ovpn_fill_info(struct sk_buff *skb, const struct net_device *de= v) { struct ovpn_priv *ovpn =3D netdev_priv(dev); @@ -127,7 +139,7 @@ static struct rtnl_link_ops ovpn_link_ops =3D { .policy =3D ovpn_policy, .maxtype =3D IFLA_OVPN_MAX, .newlink =3D ovpn_newlink, - .dellink =3D unregister_netdevice_queue, + .dellink =3D ovpn_dellink, .fill_info =3D ovpn_fill_info, }; =20 diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index b6b644668d46604ad0f21d59cf479386f4ec1e3e..a398f9da2e0958b0be885a50a13= d80b8b5a515eb 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -17,10 +17,14 @@ * struct ovpn_priv - per ovpn interface state * @dev: the actual netdev representing the tunnel * @mode: device operation mode (i.e. p2p, mp, ..) + * @lock: protect this object + * @peer: in P2P mode, this is the only remote peer */ struct ovpn_priv { struct net_device *dev; enum ovpn_mode mode; + spinlock_t lock; /* protect writing to the ovpn_priv object */ + struct ovpn_peer __rcu *peer; }; =20 #endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c new file mode 100644 index 0000000000000000000000000000000000000000..338069c99248f42b0c4aeb44b2b= 9d3a35f8bebeb --- /dev/null +++ b/drivers/net/ovpn/peer.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include + +#include "ovpnpriv.h" +#include "bind.h" +#include "io.h" +#include "main.h" +#include "netlink.h" +#include "peer.h" + +static void unlock_ovpn(struct ovpn_priv *ovpn, + struct llist_head *release_list) + __releases(&ovpn->lock) +{ + struct ovpn_peer *peer; + + spin_unlock_bh(&ovpn->lock); + + llist_for_each_entry(peer, release_list->first, release_entry) + ovpn_peer_put(peer); +} + +/** + * ovpn_peer_new - allocate and initialize a new peer object + * @ovpn: the openvpn instance inside which the peer should be created + * @id: the ID assigned to this peer + * + * Return: a pointer to the new peer on success or an error code otherwise + */ +struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id) +{ + struct ovpn_peer *peer; + int ret; + + /* alloc and init peer object */ + peer =3D kzalloc(sizeof(*peer), GFP_KERNEL); + if (!peer) + return ERR_PTR(-ENOMEM); + + peer->id =3D id; + peer->ovpn =3D ovpn; + + peer->vpn_addrs.ipv4.s_addr =3D htonl(INADDR_ANY); + peer->vpn_addrs.ipv6 =3D in6addr_any; + + RCU_INIT_POINTER(peer->bind, NULL); + spin_lock_init(&peer->lock); + kref_init(&peer->refcount); + + ret =3D dst_cache_init(&peer->dst_cache, GFP_KERNEL); + if (ret < 0) { + netdev_err(ovpn->dev, + "cannot initialize dst cache for peer %u\n", + peer->id); + kfree(peer); + return ERR_PTR(ret); + } + + netdev_hold(ovpn->dev, &peer->dev_tracker, GFP_KERNEL); + + return peer; +} + +/** + * ovpn_peer_release_rcu - RCU callback performing last peer release steps + * @head: RCU member of the ovpn_peer + */ +static void ovpn_peer_release_rcu(struct rcu_head *head) +{ + struct ovpn_peer *peer =3D container_of(head, struct ovpn_peer, rcu); + + /* this call will immediately free the dst_cache, therefore we + * perform it in the RCU callback, when all contexts are done + */ + dst_cache_destroy(&peer->dst_cache); + kfree(peer); +} + +/** + * ovpn_peer_release - release peer private members + * @peer: the peer to release + */ +static void ovpn_peer_release(struct ovpn_peer *peer) +{ + ovpn_bind_reset(peer, NULL); + call_rcu(&peer->rcu, ovpn_peer_release_rcu); + netdev_put(peer->ovpn->dev, &peer->dev_tracker); +} + +/** + * ovpn_peer_release_kref - callback for kref_put + * @kref: the kref object belonging to the peer + */ +void ovpn_peer_release_kref(struct kref *kref) +{ + struct ovpn_peer *peer =3D container_of(kref, struct ovpn_peer, refcount); + + ovpn_peer_release(peer); +} + +/** + * ovpn_peer_skb_to_sockaddr - fill sockaddr with skb source address + * @skb: the packet to extract data from + * @ss: the sockaddr to fill + * + * Return: sockaddr length on success or -1 otherwise + */ +static int ovpn_peer_skb_to_sockaddr(struct sk_buff *skb, + struct sockaddr_storage *ss) +{ + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; + + switch (skb->protocol) { + case htons(ETH_P_IP): + sa4 =3D (struct sockaddr_in *)ss; + sa4->sin_family =3D AF_INET; + sa4->sin_addr.s_addr =3D ip_hdr(skb)->saddr; + sa4->sin_port =3D udp_hdr(skb)->source; + return sizeof(*sa4); + case htons(ETH_P_IPV6): + sa6 =3D (struct sockaddr_in6 *)ss; + sa6->sin6_family =3D AF_INET6; + sa6->sin6_addr =3D ipv6_hdr(skb)->saddr; + sa6->sin6_port =3D udp_hdr(skb)->source; + return sizeof(*sa6); + } + + return -1; +} + +/** + * ovpn_peer_transp_match - check if sockaddr and peer binding match + * @peer: the peer to get the binding from + * @ss: the sockaddr to match + * + * Return: true if sockaddr and binding match or false otherwise + */ +static bool ovpn_peer_transp_match(const struct ovpn_peer *peer, + const struct sockaddr_storage *ss) +{ + struct ovpn_bind *bind =3D rcu_dereference(peer->bind); + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; + + if (unlikely(!bind)) + return false; + + if (ss->ss_family !=3D bind->remote.in4.sin_family) + return false; + + switch (ss->ss_family) { + case AF_INET: + sa4 =3D (struct sockaddr_in *)ss; + if (sa4->sin_addr.s_addr !=3D bind->remote.in4.sin_addr.s_addr) + return false; + if (sa4->sin_port !=3D bind->remote.in4.sin_port) + return false; + break; + case AF_INET6: + sa6 =3D (struct sockaddr_in6 *)ss; + if (!ipv6_addr_equal(&sa6->sin6_addr, + &bind->remote.in6.sin6_addr)) + return false; + if (sa6->sin6_port !=3D bind->remote.in6.sin6_port) + return false; + break; + default: + return false; + } + + return true; +} + +/** + * ovpn_peer_get_by_transp_addr_p2p - get peer by transport address in a P= 2P + * instance + * @ovpn: the openvpn instance to search + * @ss: the transport socket address + * + * Return: the peer if found or NULL otherwise + */ +static struct ovpn_peer * +ovpn_peer_get_by_transp_addr_p2p(struct ovpn_priv *ovpn, + struct sockaddr_storage *ss) +{ + struct ovpn_peer *tmp, *peer =3D NULL; + + rcu_read_lock(); + tmp =3D rcu_dereference(ovpn->peer); + if (likely(tmp && ovpn_peer_transp_match(tmp, ss) && + ovpn_peer_hold(tmp))) + peer =3D tmp; + rcu_read_unlock(); + + return peer; +} + +/** + * ovpn_peer_get_by_transp_addr - retrieve peer by transport address + * @ovpn: the openvpn instance to search + * @skb: the skb to retrieve the source transport address from + * + * Return: a pointer to the peer if found or NULL otherwise + */ +struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, + struct sk_buff *skb) +{ + struct ovpn_peer *peer =3D NULL; + struct sockaddr_storage ss =3D { 0 }; + + if (unlikely(!ovpn_peer_skb_to_sockaddr(skb, &ss))) + return NULL; + + if (ovpn->mode =3D=3D OVPN_MODE_P2P) + peer =3D ovpn_peer_get_by_transp_addr_p2p(ovpn, &ss); + + return peer; +} + +/** + * ovpn_peer_get_by_id_p2p - get peer by ID in a P2P instance + * @ovpn: the openvpn instance to search + * @peer_id: the ID of the peer to find + * + * Return: the peer if found or NULL otherwise + */ +static struct ovpn_peer *ovpn_peer_get_by_id_p2p(struct ovpn_priv *ovpn, + u32 peer_id) +{ + struct ovpn_peer *tmp, *peer =3D NULL; + + rcu_read_lock(); + tmp =3D rcu_dereference(ovpn->peer); + if (likely(tmp && tmp->id =3D=3D peer_id && ovpn_peer_hold(tmp))) + peer =3D tmp; + rcu_read_unlock(); + + return peer; +} + +/** + * ovpn_peer_get_by_id - retrieve peer by ID + * @ovpn: the openvpn instance to search + * @peer_id: the unique peer identifier to match + * + * Return: a pointer to the peer if found or NULL otherwise + */ +struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id) +{ + struct ovpn_peer *peer =3D NULL; + + if (ovpn->mode =3D=3D OVPN_MODE_P2P) + peer =3D ovpn_peer_get_by_id_p2p(ovpn, peer_id); + + return peer; +} + +static void ovpn_peer_remove(struct ovpn_peer *peer, + enum ovpn_del_peer_reason reason, + struct llist_head *release_list) +{ + switch (peer->ovpn->mode) { + case OVPN_MODE_P2P: + /* prevent double remove */ + if (peer !=3D rcu_access_pointer(peer->ovpn->peer)) + return; + + RCU_INIT_POINTER(peer->ovpn->peer, NULL); + /* in P2P mode the carrier is switched off when the peer is + * deleted so that third party protocols can react accordingly + */ + netif_carrier_off(peer->ovpn->dev); + break; + default: + return; + } + + peer->delete_reason =3D reason; + + /* append to provided list for later socket release and ref drop */ + llist_add(&peer->release_entry, release_list); +} + +/** + * ovpn_peer_add_p2p - add peer to related tables in a P2P instance + * @ovpn: the instance to add the peer to + * @peer: the peer to add + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_peer_add_p2p(struct ovpn_priv *ovpn, struct ovpn_peer *pee= r) +{ + LLIST_HEAD(release_list); + struct ovpn_peer *tmp; + + spin_lock_bh(&ovpn->lock); + /* in p2p mode it is possible to have a single peer only, therefore the + * old one is released and substituted by the new one + */ + tmp =3D rcu_dereference_protected(ovpn->peer, + lockdep_is_held(&ovpn->lock)); + if (tmp) + ovpn_peer_remove(tmp, OVPN_DEL_PEER_REASON_TEARDOWN, + &release_list); + + rcu_assign_pointer(ovpn->peer, peer); + /* in P2P mode the carrier is switched on when the peer is added */ + netif_carrier_on(ovpn->dev); + unlock_ovpn(ovpn, &release_list); + + return 0; +} + +/** + * ovpn_peer_add - add peer to the related tables + * @ovpn: the openvpn instance the peer belongs to + * @peer: the peer object to add + * + * Assume refcounter was increased by caller + * + * Return: 0 on success or a negative error code otherwise + */ +int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer) +{ + switch (ovpn->mode) { + case OVPN_MODE_P2P: + return ovpn_peer_add_p2p(ovpn, peer); + default: + return -EOPNOTSUPP; + } +} + +/** + * ovpn_peer_del_p2p - delete peer from related tables in a P2P instance + * @peer: the peer to delete + * @reason: reason why the peer was deleted (sent to userspace) + * @release_list: list where delete peer should be appended + * + * 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 llist_head *release_list) +{ + struct ovpn_peer *tmp; + + lockdep_assert_held(&peer->ovpn->lock); + + tmp =3D rcu_dereference_protected(peer->ovpn->peer, + lockdep_is_held(&peer->ovpn->lock)); + if (tmp !=3D peer) + return -ENOENT; + + ovpn_peer_remove(peer, reason, release_list); + + return 0; +} + +/** + * 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) +{ + LLIST_HEAD(release_list); + int ret =3D -EOPNOTSUPP; + + spin_lock_bh(&peer->ovpn->lock); + switch (peer->ovpn->mode) { + case OVPN_MODE_P2P: + ret =3D ovpn_peer_del_p2p(peer, reason, &release_list); + break; + default: + break; + } + unlock_ovpn(peer->ovpn, &release_list); + + return ret; +} + +/** + * ovpn_peer_release_p2p - release peer upon P2P device teardown + * @ovpn: the instance being torn down + * @reason: the reason for releasing the peer + */ +void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, + enum ovpn_del_peer_reason reason) +{ + LLIST_HEAD(release_list); + struct ovpn_peer *peer; + + spin_lock_bh(&ovpn->lock); + peer =3D rcu_dereference_protected(ovpn->peer, + lockdep_is_held(&ovpn->lock)); + if (peer) + ovpn_peer_remove(peer, reason, &release_list); + unlock_ovpn(ovpn, &release_list); +} diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h new file mode 100644 index 0000000000000000000000000000000000000000..fd2e7625990a73f61bf5bb4c051= 929828d9996bd --- /dev/null +++ b/drivers/net/ovpn/peer.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNPEER_H_ +#define _NET_OVPN_OVPNPEER_H_ + +#include + +/** + * struct ovpn_peer - the main remote peer object + * @ovpn: main openvpn instance this peer belongs to + * @dev_tracker: reference tracker for associated dev + * @id: unique identifier + * @vpn_addrs: IP addresses assigned over the tunnel + * @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel + * @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel + * @dst_cache: cache for dst_entry used to send to peer + * @bind: remote peer binding + * @delete_reason: why peer was deleted (i.e. timeout, transport error, ..) + * @lock: protects binding to peer (bind) + * @refcount: reference counter + * @rcu: used to free peer in an RCU safe way + * @release_entry: entry for the socket release list + */ +struct ovpn_peer { + struct ovpn_priv *ovpn; + netdevice_tracker dev_tracker; + u32 id; + struct { + struct in_addr ipv4; + struct in6_addr ipv6; + } vpn_addrs; + struct dst_cache dst_cache; + struct ovpn_bind __rcu *bind; + enum ovpn_del_peer_reason delete_reason; + spinlock_t lock; /* protects bind */ + struct kref refcount; + struct rcu_head rcu; + struct llist_node release_entry; +}; + +/** + * ovpn_peer_hold - increase reference counter + * @peer: the peer whose counter should be increased + * + * Return: true if the counter was increased or false if it was zero alrea= dy + */ +static inline bool ovpn_peer_hold(struct ovpn_peer *peer) +{ + return kref_get_unless_zero(&peer->refcount); +} + +void ovpn_peer_release_kref(struct kref *kref); + +/** + * ovpn_peer_put - decrease reference counter + * @peer: the peer whose counter should be decreased + */ +static inline void ovpn_peer_put(struct ovpn_peer *peer) +{ + kref_put(&peer->refcount, ovpn_peer_release_kref); +} + +struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id); +int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer); +int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason= ); +void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, + enum ovpn_del_peer_reason reason); + +struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, + struct sk_buff *skb); +struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id); + +#endif /* _NET_OVPN_OVPNPEER_H_ */ --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f54.google.com (mail-wm1-f54.google.com [209.85.128.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F2C08294A0D for ; Tue, 15 Apr 2025 11:17:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715857; cv=none; b=igHmObeHNMrGDVrk500qcLc6W0dIfDOiWeJH7N1QL7dqqD0hVSRB6ruXEI94PCBqFVPfdnVmof6FCaY+lmfg7eJb46JM3YOMaTwJzojplY2sqhSFlWqSycmW9vQA/DreTW8zIcj9N+weQ5L2CqPf+fLSSsZdGPbVrxSPY0Ej/ws= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715857; c=relaxed/simple; bh=VuiGYngCSooNquJEI164XWqHd5fh6XmhJzDN7QNDzCQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=AM/I4I441BUlaGPwzu3Odbjzprdq0t/bruRIn1PQNGWNtZb96m4FR7oeeOS1ouRBPZy6jvM3YxFiWEbSj/Tq4AT5Kk6IjGYiOkyy0POAGoWBMGaBGuZMyzPClCSve7RHFXF3It3uU8q3TgA6RcY20TN4e/mESM0yKb9nPS7QoZ0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=Vq/R36uh; arc=none smtp.client-ip=209.85.128.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="Vq/R36uh" Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-43cebe06e9eso40391095e9.3 for ; Tue, 15 Apr 2025 04:17:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715852; x=1745320652; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=nYX+N/URewVi1nfvAjTnEd0MNFVqeqaSGAihGpWv6NY=; b=Vq/R36uhPPhzHBswzgchxPPQhuVWPrmf48j+BrOnxV3gHyAdIiNzQwl7ajQroHUwYa M8RTQM+tCpXr/lwywySTcqr5ykieVfhwb4G6aI98cTgZzKTPxsnZqGUTcudzObOcnlF8 zUeYwSRkj80O5+dkeeUkOmGxdHi3JUnMy1ajMA239hexo6oRcnPvwq3kxzgIwqGM+6T4 GOhYKcChNOmeZ/GO3VfOYOeM4XkA4r2QodX1inJbR2pY9UknSP29wSk542yGY0ZWl0AW BHKGU2woV3YAZhy7OjKblqSBhkvytiygEMlpT1YFx440LKLZ8RX7dcoA+bPlVRJbJiMQ +Org== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715852; x=1745320652; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=nYX+N/URewVi1nfvAjTnEd0MNFVqeqaSGAihGpWv6NY=; b=qvn6hTNq/Qp9zc2/l47D2ZQLowL1ulxp5Egh3hAqPLUk+MLMyiajAdr7bZjq0pw+q6 4mX5tWX4RPv7Twdpvd5TvqKQ8BPP/qqqiiZzSlARSiTLD/6/d77XML92MQ8s4NtMgj1R OlKy/dG86R+kTF2In1ExH8s6EAWiOGo9nNkVZAOu+IAqSLSNx21Xdq/gYpYnMgjcpFGN ph1siokWKQhSjO5WdJAvGCol9K1LfC3wyNVL2VXsTNXrQUsRSK3X0tfsTPp0zjBESaC6 hdnB+gSf53gWRomXliVcJjscmI5XwRxhI0EiRX34qQLLDyp/KfAe2+7oEsnVj2Z/7/wh NUjw== X-Forwarded-Encrypted: i=1; AJvYcCULZeaGl1oQvX8GUOvpByb/qDNRyQQTQk11HIMtyytHRI5dKCo4vJMKFoyBi94TzaZAROLMs/bNE/yKhqs=@vger.kernel.org X-Gm-Message-State: AOJu0YxLO8OmxaoQ/TmrtmlcIQ7CpRdxhboeOVlwFIlb1P5PXQon3+Jt x8tkxQKbp6n9PO6WtDFMhboe6HXWMvNgT6R9uppmyMkwu5Ssb0s9P7Hkid3/zhDz7/OQf9GALqq D82IF5z5fGCrFOxzbhzYpu3jwAtUWI0NiRAatosw8OAla6nX//Htg1W8= X-Gm-Gg: ASbGncvZCoEmXm0A4MoEWUov5aPGqjl6lOIUNcSqdClOJCo7zei0e4y7Na9wrun7Ukm KmrYGvpfsK1IBbeAHdqVTczOWGjLBW0cNL7OaWXXVBtAQhxRWbmOEWDnO2dJh8xXq552hw74KBp HBs424NdzFpV3J0KfZLoWvtVzLaX00t/gRdKF9HW3VwcebRDE0zyPgq0G5pW94ASfENG6MvNPvH Y/xOopaVwxPkOdMBHpItIu3GICfIM0QUQPO6FMcA27ardWZuoWQhHO5wvSjGi15oXRRaVUVXQo/ htSVAha/JPaYdI864qv+wv2ZYCd3xrXquFkWEg== X-Google-Smtp-Source: AGHT+IHvoeWHFiUmwyX8clnacCgmC0jm8/j0sIhKMJs/Vw9QhvXYMeyvmBKOirrEDzjJKH7YbdLnMg== X-Received: by 2002:a05:600c:b90:b0:43d:fa58:8377 with SMTP id 5b1f17b1804b1-43f3a9afb11mr135251175e9.32.1744715852214; Tue, 15 Apr 2025 04:17:32 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:31 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:23 +0200 Subject: [PATCH net-next v26 06/23] ovpn: introduce the ovpn_socket object Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-6-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang , willemdebruijn.kernel@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=16079; i=antonio@openvpn.net; h=from:subject:message-id; bh=VuiGYngCSooNquJEI164XWqHd5fh6XmhJzDN7QNDzCQ=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBBXOjGpyWm5YqLIOXzMex/An1i7aYUOXCmG KNDWSLUpvKJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQQAKCRALcOU6oDjV h7X2B/0ZYyKUFXV2FatHZArJ21mdY/J5n/9lqz2yZ4rxLEy6thBljj0l8yOn8YW7PgOjYMSRfEX qHg3pSSRQlICRUgl9K/0N9x4ck27XCNsLOrs3AwXr/ZyOOTCo+Fg61spLB6n9/+msU2O1La+MN3 GI+VRB9bOZ2mBB7NnzDS5bz6dPUmnvTICiciKuCuiT1Bymw5yy48+Fa02AmaJHIGSMqtglnHyoD JzEoq8LGGZBYvSVUvWO55QOeeJP6cQtVIoSsDWKV8KIc8qwpHPm9UulG03/tleAbaQVQlGxE+nj mTF82ltUdAlZloB6r+BwNdjEuK2wSuhpjHzkaWWM/KdjZ8RB X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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. Cc: willemdebruijn.kernel@gmail.com Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/Makefile | 2 + drivers/net/ovpn/main.c | 3 +- drivers/net/ovpn/peer.c | 28 +++++-- drivers/net/ovpn/peer.h | 6 +- drivers/net/ovpn/socket.c | 197 ++++++++++++++++++++++++++++++++++++++++++= ++++ drivers/net/ovpn/socket.h | 38 +++++++++ drivers/net/ovpn/udp.c | 75 ++++++++++++++++++ drivers/net/ovpn/udp.h | 19 +++++ include/uapi/linux/udp.h | 1 + 9 files changed, 362 insertions(+), 7 deletions(-) diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 618328ae338861b9764b42485df71ebd0fc1fe90..164f2058ea8e6dc5b9287afb597= 58a268b2f8b56 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -13,3 +13,5 @@ ovpn-y +=3D io.o ovpn-y +=3D netlink.o ovpn-y +=3D netlink-gen.o ovpn-y +=3D peer.o +ovpn-y +=3D socket.o +ovpn-y +=3D udp.o diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 3c2b6a7df27266dcaacc1ea4f8d86c686d3105aa..e9a6dc100d4675353bfb308a519= 5590e7df04b1d 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -116,7 +116,8 @@ static void ovpn_dellink(struct net_device *dev, struct= list_head *head) struct ovpn_priv *ovpn =3D netdev_priv(dev); =20 if (ovpn->mode =3D=3D OVPN_MODE_P2P) - ovpn_peer_release_p2p(ovpn, OVPN_DEL_PEER_REASON_TEARDOWN); + ovpn_peer_release_p2p(ovpn, NULL, + OVPN_DEL_PEER_REASON_TEARDOWN); =20 unregister_netdevice_queue(dev, head); } diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 338069c99248f42b0c4aeb44b2b9d3a35f8bebeb..0bb6c15171848acbc055829a3d2= aefd26c5b810a 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -16,17 +16,20 @@ #include "main.h" #include "netlink.h" #include "peer.h" +#include "socket.h" =20 static void unlock_ovpn(struct ovpn_priv *ovpn, - struct llist_head *release_list) + struct llist_head *release_list) __releases(&ovpn->lock) { struct ovpn_peer *peer; =20 spin_unlock_bh(&ovpn->lock); =20 - llist_for_each_entry(peer, release_list->first, release_entry) + llist_for_each_entry(peer, release_list->first, release_entry) { + ovpn_socket_release(peer); ovpn_peer_put(peer); + } } =20 /** @@ -394,18 +397,33 @@ int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_d= el_peer_reason reason) /** * ovpn_peer_release_p2p - release peer upon P2P device teardown * @ovpn: the instance being torn down + * @sk: if not NULL, release peer only if it's using this specific socket * @reason: the reason for releasing the peer */ -void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, +void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, struct sock *sk, enum ovpn_del_peer_reason reason) { + struct ovpn_socket *ovpn_sock; LLIST_HEAD(release_list); struct ovpn_peer *peer; =20 spin_lock_bh(&ovpn->lock); peer =3D rcu_dereference_protected(ovpn->peer, lockdep_is_held(&ovpn->lock)); - if (peer) - ovpn_peer_remove(peer, reason, &release_list); + if (!peer) { + spin_unlock_bh(&ovpn->lock); + return; + } + + if (sk) { + ovpn_sock =3D rcu_access_pointer(peer->sock); + if (!ovpn_sock || ovpn_sock->sock->sk !=3D sk) { + spin_unlock_bh(&ovpn->lock); + ovpn_peer_put(peer); + return; + } + } + + ovpn_peer_remove(peer, reason, &release_list); unlock_ovpn(ovpn, &release_list); } diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index fd2e7625990a73f61bf5bb4c051929828d9996bd..29c9065cedccb156ec6ca6d9b69= 2372e8fc89a2d 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -12,6 +12,8 @@ =20 #include =20 +#include "socket.h" + /** * struct ovpn_peer - the main remote peer object * @ovpn: main openvpn instance this peer belongs to @@ -20,6 +22,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 * @delete_reason: why peer was deleted (i.e. timeout, transport error, ..) @@ -36,6 +39,7 @@ struct ovpn_peer { struct in_addr ipv4; struct in6_addr ipv6; } vpn_addrs; + struct ovpn_socket __rcu *sock; struct dst_cache dst_cache; struct ovpn_bind __rcu *bind; enum ovpn_del_peer_reason delete_reason; @@ -70,7 +74,7 @@ static inline void ovpn_peer_put(struct ovpn_peer *peer) struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id); int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer); int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason= ); -void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, +void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, struct sock *sk, enum ovpn_del_peer_reason reason); =20 struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c new file mode 100644 index 0000000000000000000000000000000000000000..beec7aee35e15f659873d10318a= 2c7f2a6ecf651 --- /dev/null +++ b/drivers/net/ovpn/socket.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include +#include + +#include "ovpnpriv.h" +#include "main.h" +#include "io.h" +#include "peer.h" +#include "socket.h" +#include "udp.h" + +static void ovpn_socket_release_kref(struct kref *kref) +{ + struct ovpn_socket *sock =3D container_of(kref, struct ovpn_socket, + refcount); + + if (sock->sock->sk->sk_protocol =3D=3D IPPROTO_UDP) + ovpn_udp_socket_detach(sock); + + kfree_rcu(sock, rcu); +} + +/** + * ovpn_socket_put - decrease reference counter + * @peer: peer whose socket reference counter should be decreased + * @sock: the RCU protected peer socket + * + * This function is only used internally. Users willing to release + * references to the ovpn_socket should use ovpn_socket_release() + */ +static void ovpn_socket_put(struct ovpn_peer *peer, struct ovpn_socket *so= ck) +{ + kref_put(&sock->refcount, ovpn_socket_release_kref); +} + +/** + * ovpn_socket_release - release resources owned by socket user + * @peer: peer whose socket should be released + * + * This function should be invoked when the user is shutting + * down and wants to drop its link to the socket. + * + * In case of UDP, the detach routine will drop a reference to the + * ovpn netdev, pointed by the ovpn_socket. + * + * In case of TCP, releasing the socket will cause dropping + * the refcounter for the peer it is linked to, thus allowing the peer + * disappear as well. + * + * This function is expected to be invoked exactly once per peer + * + * NOTE: this function may sleep + */ +void ovpn_socket_release(struct ovpn_peer *peer) +{ + struct ovpn_socket *sock; + + might_sleep(); + + sock =3D rcu_replace_pointer(peer->sock, NULL, true); + /* release may be invoked after socket was detached */ + if (!sock) + return; + + /* sanity check: we should not end up here if the socket + * was already closed + */ + if (!sock->sock->sk) { + DEBUG_NET_WARN_ON_ONCE(1); + return; + } + + /* Drop the reference while holding the sock lock to avoid + * concurrent ovpn_socket_new call to mess up with a partially + * detached socket. + * + * Holding the lock ensures that a socket with refcnt 0 is fully + * detached before it can be picked by a concurrent reader. + */ + lock_sock(sock->sock->sk); + ovpn_socket_put(peer, sock); + release_sock(sock->sock->sk); + + /* align all readers with sk_user_data being NULL */ + synchronize_rcu(); +} + +static bool ovpn_socket_hold(struct ovpn_socket *sock) +{ + return kref_get_unless_zero(&sock->refcount); +} + +static int ovpn_socket_attach(struct ovpn_socket *sock, struct ovpn_peer *= peer) +{ + if (sock->sock->sk->sk_protocol =3D=3D IPPROTO_UDP) + return ovpn_udp_socket_attach(sock, peer->ovpn); + + return -EOPNOTSUPP; +} + +/** + * 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; + + lock_sock(sock->sk); + + /* a TCP socket can only be owned by a single peer, therefore there + * can't be any other user + */ + if (sock->sk->sk_protocol =3D=3D IPPROTO_TCP && sock->sk->sk_user_data) { + ovpn_sock =3D ERR_PTR(-EBUSY); + goto sock_release; + } + + /* a UDP socket can be shared across multiple peers, but we must make + * sure it is not owned by something else + */ + if (sock->sk->sk_protocol =3D=3D IPPROTO_UDP) { + u8 type =3D READ_ONCE(udp_sk(sock->sk)->encap_type); + + /* socket owned by other encapsulation module */ + if (type && type !=3D UDP_ENCAP_OVPNINUDP) { + ovpn_sock =3D ERR_PTR(-EBUSY); + goto sock_release; + } + + rcu_read_lock(); + ovpn_sock =3D rcu_dereference_sk_user_data(sock->sk); + if (ovpn_sock) { + /* socket owned by another ovpn instance, we can't use it */ + if (ovpn_sock->ovpn !=3D peer->ovpn) { + ovpn_sock =3D ERR_PTR(-EBUSY); + rcu_read_unlock(); + goto sock_release; + } + + /* this socket is already owned by this instance, + * therefore we can increase the refcounter and + * use it as expected + */ + if (WARN_ON(!ovpn_socket_hold(ovpn_sock))) { + /* this should never happen because setting + * the refcnt to 0 and detaching the socket + * is expected to be atomic + */ + ovpn_sock =3D ERR_PTR(-EAGAIN); + rcu_read_unlock(); + goto sock_release; + } + + rcu_read_unlock(); + goto sock_release; + } + rcu_read_unlock(); + } + + /* socket is not owned: attach to this ovpn instance */ + + ovpn_sock =3D kzalloc(sizeof(*ovpn_sock), GFP_KERNEL); + if (!ovpn_sock) { + ovpn_sock =3D ERR_PTR(-ENOMEM); + goto sock_release; + } + + ovpn_sock->ovpn =3D peer->ovpn; + ovpn_sock->sock =3D sock; + kref_init(&ovpn_sock->refcount); + + ret =3D ovpn_socket_attach(ovpn_sock, peer); + if (ret < 0) { + kfree(ovpn_sock); + ovpn_sock =3D ERR_PTR(ret); + goto sock_release; + } + + rcu_assign_sk_user_data(sock->sk, ovpn_sock); +sock_release: + release_sock(sock->sk); + return ovpn_sock; +} diff --git a/drivers/net/ovpn/socket.h b/drivers/net/ovpn/socket.h new file mode 100644 index 0000000000000000000000000000000000000000..ade8c94619d7b2f905b5284373d= c73f590188399 --- /dev/null +++ b/drivers/net/ovpn/socket.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_SOCK_H_ +#define _NET_OVPN_SOCK_H_ + +#include +#include +#include + +struct ovpn_priv; +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_priv *ovpn; + struct socket *sock; + struct kref refcount; + struct rcu_head rcu; +}; + +struct ovpn_socket *ovpn_socket_new(struct socket *sock, + struct ovpn_peer *peer); +void ovpn_socket_release(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 0000000000000000000000000000000000000000..91970e66a4340370a96c1fc4232= 1f94574302143 --- /dev/null +++ b/drivers/net/ovpn/udp.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#include +#include +#include +#include + +#include "ovpnpriv.h" +#include "main.h" +#include "socket.h" +#include "udp.h" + +/** + * ovpn_udp_socket_attach - set udp-tunnel CBs on socket and link it to ov= pn + * @ovpn_sock: socket to configure + * @ovpn: the openvp instance to link + * + * After invoking this function, the sock will be controlled by ovpn so th= at + * 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 ovpn_socket *ovpn_sock, + struct ovpn_priv *ovpn) +{ + struct socket *sock =3D ovpn_sock->sock; + struct ovpn_socket *old_data; + int ret =3D 0; + + /* make sure no pre-existing encapsulation handler exists */ + rcu_read_lock(); + old_data =3D 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) =3D=3D UDP_ENCAP_OVPNINUDP) = && + old_data->ovpn =3D=3D ovpn) { + netdev_dbg(ovpn->dev, + "provided socket already owned by this interface\n"); + ret =3D -EALREADY; + } else { + netdev_dbg(ovpn->dev, + "provided socket already taken by other user\n"); + ret =3D -EBUSY; + } + rcu_read_unlock(); + + return ret; +} + +/** + * ovpn_udp_socket_detach - clean udp-tunnel status for this socket + * @ovpn_sock: the socket to clean + */ +void ovpn_udp_socket_detach(struct ovpn_socket *ovpn_sock) +{ +} diff --git a/drivers/net/ovpn/udp.h b/drivers/net/ovpn/udp.h new file mode 100644 index 0000000000000000000000000000000000000000..1c8fb6fe402dc1cfdc10fddc9cf= 5b74d7d6887ce --- /dev/null +++ b/drivers/net/ovpn/udp.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#ifndef _NET_OVPN_UDP_H_ +#define _NET_OVPN_UDP_H_ + +struct ovpn_priv; +struct socket; + +int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, + struct ovpn_priv *ovpn); +void ovpn_udp_socket_detach(struct ovpn_socket *ovpn_sock); + +#endif /* _NET_OVPN_UDP_H_ */ diff --git a/include/uapi/linux/udp.h b/include/uapi/linux/udp.h index d85d671deed3c78f6969189281b9083dcac000c6..edca3e430305a6bffc34e617421= f1f3071582e69 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 */ =20 #endif /* _UAPI_LINUX_UDP_H */ --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f49.google.com (mail-wm1-f49.google.com [209.85.128.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A70CB296D2E for ; Tue, 15 Apr 2025 11:17:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715863; cv=none; b=hLPeR29EEqFX++Fgmm9ZHtAPILj6MykVdwaLfK01cXLfg5MN6vwugYmqaef0M+NwBrWmxGxpAYc0SPTeWaXZQwpe5dN2Yl/zffR8MzA17kRFwwx9iu+SVYaPncXf46hpcJ9Dt3lNum+N6/yX5D5Ei+DdV65fIPnklByMnUVhGwI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715863; c=relaxed/simple; bh=jlCLaGVNxLm1Jsk4tkiNegdEQkq21daqEz9fo+9VcI4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=cTKY3XvZSwDlY3E19ey0alAW4kIbjQe/C5mEs714DXNbiCqQcHftMOOoBGSY9nPeLl1vFN0ew/ei8qeJsOOrhVBGH2dcpBa1Bu3KIN75eQbhFeEUxVucEZDX+VvSE2yPFA4GFTSWtLiCiOeFNoUgRFYwEDi/swkjnPFZGrD5shk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=Wy6w6kQZ; arc=none smtp.client-ip=209.85.128.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="Wy6w6kQZ" Received: by mail-wm1-f49.google.com with SMTP id 5b1f17b1804b1-43cf06eabdaso52000575e9.2 for ; Tue, 15 Apr 2025 04:17:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715858; x=1745320658; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=wcZ4k/k95znNdp6YriGx1xBYvWaYpJ8GxQw+hTFVv8Q=; b=Wy6w6kQZQCv46q7qpWX8ge6o40HTPquQ0TgqoK6A3GDqQV/MRoE+Ny5rLx9sID9l2+ dA1anreZFMDfVzAm8GdaE781FQnUkbTA2ZFubOELIUX3NQqtRbExnCoCa+cJSKo9sFhZ ZR32Pwo7WVHS/WM2GoZDiovC3EWPbvSoNcXv1lHNWh6Z1fp8gQAckcFH6XWI1dsg/Ycy 3v6Q3VEg/lh1OfhHdnaMfJRBw0bR6c/ckOA0F9hwbW4fHVvkNTPsE5JAyYfsrQ6C55tt DNXUHB9/8IgS3bGzJhMTKPXHt60f0gyn/4nx3k51SlyUZAGt3bjYTmcbYEK27MWV6Vkg ISaA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715858; x=1745320658; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=wcZ4k/k95znNdp6YriGx1xBYvWaYpJ8GxQw+hTFVv8Q=; b=gsp2wXBcUegb4tkwxuSvTlRqRZ/PLgk9HdxQ2kfgT1Z/0FCF1RY6vDHBfwjOr8dlJQ VqeUP9ja9gbCWzWk8Jl8nGD883ZzktNUPi+hNSumvfI+pN0aAgluJMLSBmXGrff3i0fv NvXsFS7iMfKQaY+ZUQ1nBityVPKn3+73T0H3uftZoQ6rIKtZxrIegoFoyEDJP5uCefqY tvsBlZDvuVUhN1O44Z/yRQOvQh8H4H8f0/Y9haFzVH2HrKPP3LbG5p24RWZn4y3xyGCD vdmvFX13+ZyqKM4uINrTW9WMSYs07DNta8y60/4jNBm8oLXiXAB+gvll4jvrL8eg+cRE VwHw== X-Forwarded-Encrypted: i=1; AJvYcCUxMkqp6bl75GrnkqWuy9bbnaxpyW9pyDQBhYjhpH9YUwUM4GZbE59JaO8t1xDYIZhsPUSIhJk44OElwIg=@vger.kernel.org X-Gm-Message-State: AOJu0Yz3mpvMpgwubOBRhteCE/qqIGn7a5htU0KnV+SstQ3kG+pBaA+p X5QGdJvyjrkB2z+rNZvQtA0f6h/OLgqSGQ0GE6PQlHGsbPFP+391aslTM8u201DCR5ZQXPx1SqD bQ2QgTlo+1vCVB+dZgeHahlNc08WjTSfDiZFEt3P54jeEUB33Y+Qt8kM= X-Gm-Gg: ASbGncv6I4L9xvyRi6kBmy+ZpRH9hyvjkh2HXTf92M9wm20TwMJze4+SdPWWl2/Sp9T 9BOuURrCBo9C2vdIJ2WAM71SCMs8tV0BadJb0HRd3E+U62U9wJ7lrpLUoZfkaveyIYbZlLE3Knw Z5UC1aSNFI2mv1pmZ6ZOqlG2PGfhZyVXXwrqz2v+rpH4UVYs+AaISXDvWrQss3dVNRqoN1LEHgC W5cghl7Pxicy3bWXePuw6lpD7HytDd/VFRkKi78YbQwUikUswNQ6ZfZA2qVMfCogX715Ul9sFm+ afcksk8A0u6WHyPXP4b9YkaNE4zCy7h2Jn4BjA== X-Google-Smtp-Source: AGHT+IH7Jcw4JCqhPlH3V3HgIBweSBREGujZ9Lk37c2/ZCR87skvltP2Lq6qU5G5KMlbFkbDm9t/MA== X-Received: by 2002:a05:600c:458b:b0:43c:fc0c:7f33 with SMTP id 5b1f17b1804b1-43f3a925f6amr143384645e9.2.1744715853430; Tue, 15 Apr 2025 04:17:33 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:32 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:24 +0200 Subject: [PATCH net-next v26 07/23] ovpn: implement basic TX path (UDP) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-7-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=16589; i=antonio@openvpn.net; h=from:subject:message-id; bh=jlCLaGVNxLm1Jsk4tkiNegdEQkq21daqEz9fo+9VcI4=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBBiasEA7+354YE5uX8/UktWsLwl6Dp8oVeo VQa6de9NJmJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQQAKCRALcOU6oDjV h1gyB/0TpTJRD9FzFK7ngLfY7bfQ2TTz75xoy1PR+zjJWt2tqKjXv6foDzH/yRutjcbWOQ6qBvc rYFtyDw6o4942IhP+7IMwzd+RVhCDhN2GN2hjTJWPMZ9YFKz5L1X6D5gH1c9ey6CVDmwAFPtrVQ +lKt8xkE5bsiG3lH/UNkBIdqhbIV0fwY0n0vzb3YrEW/fcCQEh/5r4XuPxnjNOJw/HNgtTLT4Ic Aw84YaFuzaFSBvmRfyeS+hXMsP3cv08diKK9vfgYjAVo2FwAisNKpWlBv9JQqyQe+UOv24bCjW+ YSJz9a4BjmnI56PP0JDsYoO3G3ulyrG0aVzfcbCTcgxfRSqv X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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 Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/Kconfig | 1 + drivers/net/ovpn/io.c | 137 +++++++++++++++++++++++++++- drivers/net/ovpn/peer.c | 32 +++++++ drivers/net/ovpn/peer.h | 2 + drivers/net/ovpn/skb.h | 55 ++++++++++++ drivers/net/ovpn/udp.c | 233 ++++++++++++++++++++++++++++++++++++++++++++= +++- drivers/net/ovpn/udp.h | 6 ++ 7 files changed, 464 insertions(+), 2 deletions(-) diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 2806fcc22a2dbd9b2985b09dd6ef65dd1dc4ebc1..305f04dd97234c4aa43da782174= 48b914cc7ede0 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -120,6 +120,7 @@ config OVPN depends on NET && INET depends on IPV6 || !IPV6 select DST_CACHE + select NET_UDP_TUNNEL help This module enhances the performance of the OpenVPN userspace software by offloading the data channel processing to kernelspace. diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 4b71c38165d7adbb1a2d1a64d27a13b7f76cfbfe..93b128827b6d683a57297391816= bff3391ef5bec 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -9,14 +9,149 @@ =20 #include #include +#include =20 #include "io.h" +#include "ovpnpriv.h" +#include "peer.h" +#include "udp.h" +#include "skb.h" +#include "socket.h" + +static void ovpn_encrypt_post(struct sk_buff *skb, int ret) +{ + struct ovpn_peer *peer =3D ovpn_skb_cb(skb)->peer; + struct ovpn_socket *sock; + + if (unlikely(ret < 0)) + goto err; + + skb_mark_not_on_list(skb); + + rcu_read_lock(); + sock =3D rcu_dereference(peer->sock); + if (unlikely(!sock)) + goto err_unlock; + + switch (sock->sock->sk->sk_protocol) { + case IPPROTO_UDP: + ovpn_udp_send_skb(peer, sock->sock, skb); + break; + default: + /* no transport configured yet */ + goto err_unlock; + } + /* skb passed down the stack - don't free it */ + skb =3D NULL; +err_unlock: + rcu_read_unlock(); +err: + if (unlikely(skb)) + dev_dstats_tx_dropped(peer->ovpn->dev); + ovpn_peer_put(peer); + kfree_skb(skb); +} + +static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb) +{ + ovpn_skb_cb(skb)->peer =3D peer; + + /* take a reference to the peer because the crypto code may run async. + * ovpn_encrypt_post() will release it upon completion + */ + if (unlikely(!ovpn_peer_hold(peer))) { + DEBUG_NET_WARN_ON_ONCE(1); + return false; + } + + ovpn_encrypt_post(skb, 0); + return true; +} + +/* send skb to connected peer, if any */ +static void ovpn_send(struct ovpn_priv *ovpn, struct sk_buff *skb, + struct ovpn_peer *peer) +{ + struct sk_buff *curr, *next; + + /* 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_dstats_tx_dropped(ovpn->dev); + kfree_skb(curr); + } + } + + ovpn_peer_put(peer); +} =20 /* Send user data to the network */ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) { + struct ovpn_priv *ovpn =3D netdev_priv(dev); + struct sk_buff *segments, *curr, *next; + struct sk_buff_head skb_list; + struct ovpn_peer *peer; + __be16 proto; + int ret; + + /* reset netfilter state */ + nf_reset_ct(skb); + + /* verify IP header size in network packet */ + proto =3D ovpn_ip_check_protocol(skb); + if (unlikely(!proto || skb->protocol !=3D proto)) + goto drop; + + if (skb_is_gso(skb)) { + segments =3D skb_gso_segment(skb, 0); + if (IS_ERR(segments)) { + ret =3D PTR_ERR(segments); + net_err_ratelimited("%s: cannot segment payload packet: %d\n", + netdev_name(dev), ret); + goto drop; + } + + consume_skb(skb); + skb =3D 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); + + curr =3D skb_share_check(curr, GFP_ATOMIC); + if (unlikely(!curr)) { + net_err_ratelimited("%s: skb_share_check failed for payload packet\n", + netdev_name(dev)); + dev_dstats_tx_dropped(ovpn->dev); + continue; + } + + __skb_queue_tail(&skb_list, curr); + } + skb_list.prev->next =3D NULL; + + /* retrieve peer serving the destination IP of this packet */ + peer =3D ovpn_peer_get_by_dst(ovpn, skb); + if (unlikely(!peer)) { + net_dbg_ratelimited("%s: no peer to send data to\n", + netdev_name(ovpn->dev)); + goto drop; + } + + ovpn_send(ovpn, skb_list.next, peer); + + return NETDEV_TX_OK; + +drop: + dev_dstats_tx_dropped(ovpn->dev); skb_tx_error(skb); - kfree_skb(skb); + kfree_skb_list(skb); return NET_XMIT_DROP; } diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 0bb6c15171848acbc055829a3d2aefd26c5b810a..10eabd62ae7237162a36a333b41= c748901a7d888 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -294,6 +294,38 @@ static void ovpn_peer_remove(struct ovpn_peer *peer, llist_add(&peer->release_entry, release_list); } =20 +/** + * 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, wi= thout + * 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_priv *ovpn, + struct sk_buff *skb) +{ + struct ovpn_peer *peer =3D NULL; + + /* in P2P mode, no matter the destination, packets are always sent to + * the single peer listening on the other side + */ + if (ovpn->mode =3D=3D OVPN_MODE_P2P) { + rcu_read_lock(); + peer =3D rcu_dereference(ovpn->peer); + if (unlikely(peer && !ovpn_peer_hold(peer))) + peer =3D 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 29c9065cedccb156ec6ca6d9b692372e8fc89a2d..fef04311c1593db4ccfa3c41748= 7b3d4faaae9d7 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -80,5 +80,7 @@ void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, struct= sock *sk, struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, struct sk_buff *skb); struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id); +struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, + struct sk_buff *skb); =20 #endif /* _NET_OVPN_OVPNPEER_H_ */ diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h new file mode 100644 index 0000000000000000000000000000000000000000..9db7a9adebdb4cc493f162f89fb= 2e9c6301fa213 --- /dev/null +++ b/drivers/net/ovpn/skb.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + * James Yonan + */ + +#ifndef _NET_OVPN_SKB_H_ +#define _NET_OVPN_SKB_H_ + +#include +#include +#include +#include +#include +#include +#include + +struct ovpn_cb { + struct ovpn_peer *peer; +}; + +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 =3D 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 =3D=3D 4) { + proto =3D htons(ETH_P_IP); + } else if (ip_hdr(skb)->version =3D=3D 6) { + if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr))) + return 0; + proto =3D 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 91970e66a4340370a96c1fc42321f94574302143..28307ef7b2672cf1ad1240c1d17= ffdcb9e64b5a5 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -7,15 +7,246 @@ */ =20 #include +#include +#include #include #include +#include +#include +#include +#include #include +#include =20 #include "ovpnpriv.h" #include "main.h" +#include "bind.h" +#include "io.h" +#include "peer.h" #include "socket.h" #include "udp.h" =20 +/** + * ovpn_udp4_output - send IPv4 packet over udp socket + * @peer: the destination peer + * @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_peer *peer, struct ovpn_bind *bind, + struct dst_cache *cache, struct sock *sk, + struct sk_buff *skb) +{ + struct rtable *rt; + struct flowi4 fl =3D { + .saddr =3D bind->local.ipv4.s_addr, + .daddr =3D bind->remote.in4.sin_addr.s_addr, + .fl4_sport =3D inet_sk(sk)->inet_sport, + .fl4_dport =3D bind->remote.in4.sin_port, + .flowi4_proto =3D sk->sk_protocol, + .flowi4_mark =3D sk->sk_mark, + }; + int ret; + + local_bh_disable(); + rt =3D 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 =3D 0; + spin_lock_bh(&peer->lock); + bind->local.ipv4.s_addr =3D 0; + spin_unlock_bh(&peer->lock); + dst_cache_reset(cache); + } + + rt =3D ip_route_output_flow(sock_net(sk), &fl, sk); + if (IS_ERR(rt) && PTR_ERR(rt) =3D=3D -EINVAL) { + fl.saddr =3D 0; + spin_lock_bh(&peer->lock); + bind->local.ipv4.s_addr =3D 0; + spin_unlock_bh(&peer->lock); + dst_cache_reset(cache); + + rt =3D ip_route_output_flow(sock_net(sk), &fl, sk); + } + + if (IS_ERR(rt)) { + ret =3D PTR_ERR(rt); + net_dbg_ratelimited("%s: no route to host %pISpc: %d\n", + netdev_name(peer->ovpn->dev), + &bind->remote.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 =3D 0; +err: + local_bh_enable(); + return ret; +} + +#if IS_ENABLED(CONFIG_IPV6) +/** + * ovpn_udp6_output - send IPv6 packet over udp socket + * @peer: the destination peer + * @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_peer *peer, struct ovpn_bind *bind, + struct dst_cache *cache, struct sock *sk, + struct sk_buff *skb) +{ + struct dst_entry *dst; + int ret; + + struct flowi6 fl =3D { + .saddr =3D bind->local.ipv6, + .daddr =3D bind->remote.in6.sin6_addr, + .fl6_sport =3D inet_sk(sk)->inet_sport, + .fl6_dport =3D bind->remote.in6.sin6_port, + .flowi6_proto =3D sk->sk_protocol, + .flowi6_mark =3D sk->sk_mark, + .flowi6_oif =3D bind->remote.in6.sin6_scope_id, + }; + + local_bh_disable(); + dst =3D 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 =3D in6addr_any; + spin_lock_bh(&peer->lock); + bind->local.ipv6 =3D in6addr_any; + spin_unlock_bh(&peer->lock); + dst_cache_reset(cache); + } + + dst =3D ipv6_stub->ipv6_dst_lookup_flow(sock_net(sk), sk, &fl, NULL); + if (IS_ERR(dst)) { + ret =3D PTR_ERR(dst); + net_dbg_ratelimited("%s: no route to host %pISpc: %d\n", + netdev_name(peer->ovpn->dev), + &bind->remote.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 =3D 0; +err: + local_bh_enable(); + return ret; +} +#endif + +/** + * ovpn_udp_output - transmit skb using udp-tunnel + * @peer: 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_peer *peer, struct dst_cache *cache, + struct sock *sk, struct sk_buff *skb) +{ + struct ovpn_bind *bind; + int ret; + + /* set sk to null if skb is already orphaned */ + if (!skb->destructor) + skb->sk =3D NULL; + + rcu_read_lock(); + bind =3D rcu_dereference(peer->bind); + if (unlikely(!bind)) { + net_warn_ratelimited("%s: no bind for remote peer %u\n", + netdev_name(peer->ovpn->dev), peer->id); + ret =3D -ENODEV; + goto out; + } + + switch (bind->remote.in4.sin_family) { + case AF_INET: + ret =3D ovpn_udp4_output(peer, bind, cache, sk, skb); + break; +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: + ret =3D ovpn_udp6_output(peer, bind, cache, sk, skb); + break; +#endif + default: + ret =3D -EAFNOSUPPORT; + break; + } + +out: + rcu_read_unlock(); + return ret; +} + +/** + * ovpn_udp_send_skb - prepare skb and send it over via UDP + * @peer: the destination peer + * @sock: the RCU protected peer socket + * @skb: the packet to send + */ +void ovpn_udp_send_skb(struct ovpn_peer *peer, struct socket *sock, + struct sk_buff *skb) +{ + int ret =3D -1; + + skb->dev =3D peer->ovpn->dev; + /* no checksum performed at this layer */ + skb->ip_summed =3D CHECKSUM_NONE; + + /* get socket info */ + if (unlikely(!sock)) { + net_warn_ratelimited("%s: no sock for remote peer %u\n", + netdev_name(peer->ovpn->dev), peer->id); + goto out; + } + + /* crypto layer -> transport (UDP) */ + ret =3D ovpn_udp_output(peer, &peer->dst_cache, sock->sk, skb); +out: + if (unlikely(ret < 0)) { + kfree_skb(skb); + return; + } +} + /** * ovpn_udp_socket_attach - set udp-tunnel CBs on socket and link it to ov= pn * @ovpn_sock: socket to configure @@ -31,7 +262,7 @@ int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, { struct socket *sock =3D ovpn_sock->sock; struct ovpn_socket *old_data; - int ret =3D 0; + int ret; =20 /* make sure no pre-existing encapsulation handler exists */ rcu_read_lock(); diff --git a/drivers/net/ovpn/udp.h b/drivers/net/ovpn/udp.h index 1c8fb6fe402dc1cfdc10fddc9cf5b74d7d6887ce..9994eb6e04283247d8ffc729966= 345810f84b22b 100644 --- a/drivers/net/ovpn/udp.h +++ b/drivers/net/ovpn/udp.h @@ -9,6 +9,9 @@ #ifndef _NET_OVPN_UDP_H_ #define _NET_OVPN_UDP_H_ =20 +#include + +struct ovpn_peer; struct ovpn_priv; struct socket; =20 @@ -16,4 +19,7 @@ int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, struct ovpn_priv *ovpn); void ovpn_udp_socket_detach(struct ovpn_socket *ovpn_sock); =20 +void ovpn_udp_send_skb(struct ovpn_peer *peer, struct socket *sock, + struct sk_buff *skb); + #endif /* _NET_OVPN_UDP_H_ */ --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f54.google.com (mail-wm1-f54.google.com [209.85.128.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E30F8296D34 for ; Tue, 15 Apr 2025 11:17:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715863; cv=none; b=NtKb/upSSKD5kslZFtUBhmlxgrfb3LSzzC2qMfGLVw1EKFxHNtNsVBKaOihgVszlZaIa162DdPV7Zuwp/zYkmvtEzSx22b1+Pdl9rL62CeWrHQSDzLFBhNPOvq95UzlK/EBUvP6/P8j+f5ajdrFHcLdkdcBpblroTeX2dNdfJzY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715863; c=relaxed/simple; bh=Ntj32Sgk8Ze7VqGYgoQESMm9GV8t7daC4ug6o221jao=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=I1gCaEd3lgMTI/O9KbYIdXRGwNpsKCUjtFm6vfwkEAJrTwoHiFHKM4HTL4DwHlnBC5XpELiX3kNpmbibXBnU7JUtyUa90ca0vDh2T8qANtIAgkPprzuQ30C7jea9MHySXKhqhZQ7B2R+Eeg7qnPfhuVEaGSVRGmqabB6R8GF+z8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=KFFUaAlY; arc=none smtp.client-ip=209.85.128.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="KFFUaAlY" Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-43edb40f357so45187675e9.0 for ; Tue, 15 Apr 2025 04:17:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715858; x=1745320658; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=+QWCfEnFBAIfzJqhJW913sdv36NsTwBGAT3CLPAjKXo=; b=KFFUaAlYvgKyShJM2gnwZmL4JXEpdbrEHE6iSO4shQ9abqh94k5KG5wEXJ26xitvRg UEm2qrMysx92jPwiDCbzSjq0c+s6ffTjzppEnaWC1Lvk1Vro5Mh2DW7DcBnxzBdbbFMQ 0KgJtXD9xucyrHS6Fto2y61gZrkGlQGbblflNWtOBjcJHU5u0lSz2aTdQ/TI+RM9pITR J9HBQevDtFz31Ic43OOkrlYXPsSehvaHJX808yajTXQfmEn/u4JNeDyMfw/p6iHB9LM1 qzppqzfNqaMelpyAlF2K/s90IT4fhFb19HpoxneN+p9bIefpC0a9BnfkcisZazXVnv+i LSqQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715858; x=1745320658; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=+QWCfEnFBAIfzJqhJW913sdv36NsTwBGAT3CLPAjKXo=; b=T51Z/lt6Uc/Q5WTE8QzcHQFvy7N00CJZB6+CxALKmZ9bxSBPT/F3SSwEXeuQ0o338E 6I6SK0dKYc+S7Ah/0PvlqW6+QoFv7NkfF3XPa8g2QetgpJ70C3a16OQDV56moOJ6OwXd wwEe3MPYwavGW98KrTaC39me/623pOArovSfWgU/spVswtpshcW57JGMt9eBcZDrafA9 fgRRliTCN7tVCKoi7EQyWGUazgq6PJ++RU+n+wcJft9cG7f8rjURk98VQR5olo8JROLE 6FgxZYYCIqBr6rtnyMX6xunm82N51iWtin32g6+QwfpklzDtp16qPWsMfYO2oFtV7deG Z/7Q== X-Forwarded-Encrypted: i=1; AJvYcCUGcewVU7p0UAVYsQrdCoYKuoo9pEylts7H/xYzBepM8cXiJVnfzgJNFYn9HEi8TW3oeZSYuVkDz/YTQmo=@vger.kernel.org X-Gm-Message-State: AOJu0YxIb1VgDolIHRB4CxUWEc3Fk9GIO4njUcHQOt0IfHcNzWeg9S5o jDI094acDdV97n2dm2b/XZbYSZKStUsXXNgvAQObHFnPmxfkh7khBQI6AtIKWspNDkTRrms1wWb 7r/s8iET7Sx1ERWSN/wVPy4Ge+g0NIVZCYyWjaQQd+9RlywwGfIDuWDI= X-Gm-Gg: ASbGncsf5np9ialtq5O9bvlgLPtBmFzvt6gG6iqwL4bxUQORYovU5BziYA75ArGYR8i weQXbXZbrLrzjjGLf6E08by2E2kj4/zOFXJdIo3EOqg37fH7tf/gd934e6j7/cB4gBoM93EqFt2 5+Eb6+XNaHsT580cTz8JLUz5KG9nwFsHtgFPVcgxxxVm69QQi3ZIV5PfzItYfsqliENgroeY3vS 74KUV5ei12KpF0YC/bNS0H2x2ZgA8K/K+PVgIRrTBa2U+uQeVUtp/nUmO32rz/ATbIoDq9ex8pq 81r8urNwNxAYRtCqIHGc3SSJFNIlYEY1AV+NdA== X-Google-Smtp-Source: AGHT+IGuhiKmAwzL6kR5BXZvgjvyyZTb9YO/61Qg0Vh3s8VDop3qhqGiHa/rSbmByrXYdiZ66QxGAw== X-Received: by 2002:a05:600c:1911:b0:43d:526:e0ce with SMTP id 5b1f17b1804b1-43f3a9ab03bmr114771485e9.21.1744715857943; Tue, 15 Apr 2025 04:17:37 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:37 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:25 +0200 Subject: [PATCH net-next v26 08/23] ovpn: implement basic RX path (UDP) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-8-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=15342; i=antonio@openvpn.net; h=from:subject:message-id; bh=Ntj32Sgk8Ze7VqGYgoQESMm9GV8t7daC4ug6o221jao=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBB4q/IR4elhCFqLqay99A/kRHEHgVqxkCRz wWqwRF1i4eJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQQAKCRALcOU6oDjV h9coB/4/MMAvtexOlLbk98ht6o9W75utLDR7jfsvpjXqQHsRXFAkpJkV6JqONLHdY8VI2RaJFKC lwCnfSsHQYMjrGwmeZhu3X2XeHRwgY88KOM+prRjNcTg7zDQgyRjcXvtDZh1itzK20h777KilRc SN7wfLFScI0b8DtZEVuKXAFqHDJeTvBlJiyIQQk7idcbzmtGs5bdPfFVjRbUseSkOEIA6EHRdBZ HGIvPcP9UUU6N0z/PRG3q2AW/bMy6aZ9QioJYI0Dj2F2NXh7t2rzF579+zN5gcPOfqRCPTWILxN 1z6rx10VZpVusK3fjMLVQwt1w+wtXXDBh/s4oN6Qy42CiLWf X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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 Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/io.c | 64 ++++++++++++++++++++- drivers/net/ovpn/io.h | 2 + drivers/net/ovpn/main.c | 18 ++++++ drivers/net/ovpn/ovpnpriv.h | 3 + drivers/net/ovpn/proto.h | 50 +++++++++++++++- drivers/net/ovpn/socket.c | 14 ++++- drivers/net/ovpn/socket.h | 9 ++- drivers/net/ovpn/udp.c | 135 ++++++++++++++++++++++++++++++++++++++++= ++++ 8 files changed, 290 insertions(+), 5 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 93b128827b6d683a57297391816bff3391ef5bec..22fa92b9aeeb55a71fb88503741= 8c643cd34b269 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -9,15 +9,77 @@ =20 #include #include +#include #include =20 -#include "io.h" #include "ovpnpriv.h" #include "peer.h" +#include "io.h" +#include "netlink.h" +#include "proto.h" #include "udp.h" #include "skb.h" #include "socket.h" =20 +/* 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) +{ + unsigned int pkt_len; + int ret; + + /* we can't guarantee the packet wasn't corrupted before entering the + * VPN, therefore we give other layers a chance to check that + */ + skb->ip_summed =3D CHECKSUM_NONE; + + /* 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 =3D 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_reset_inner_headers(skb); + + /* cause packet to be "received" by the interface */ + pkt_len =3D skb->len; + ret =3D gro_cells_receive(&peer->ovpn->gro_cells, skb); + if (likely(ret =3D=3D NET_RX_SUCCESS)) + /* update RX stats with the size of decrypted packet */ + dev_dstats_rx_add(peer->ovpn->dev, pkt_len); +} + +static void ovpn_decrypt_post(struct sk_buff *skb, int ret) +{ + struct ovpn_peer *peer =3D 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 =3D NULL; +drop: + if (unlikely(skb)) + dev_dstats_rx_dropped(peer->ovpn->dev); + ovpn_peer_put(peer); + kfree_skb(skb); +} + +/* RX path entry point: decrypt packet and forward it to the device */ +void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb) +{ + ovpn_skb_cb(skb)->peer =3D peer; + ovpn_decrypt_post(skb, 0); +} + static void ovpn_encrypt_post(struct sk_buff *skb, int ret) { struct ovpn_peer *peer =3D ovpn_skb_cb(skb)->peer; diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h index afea5f81f5628dcb9afda9a78974bbf6f2101c13..1cfa66873a2d4840ce57e337f8b= 4e8143e8b8e79 100644 --- a/drivers/net/ovpn/io.h +++ b/drivers/net/ovpn/io.h @@ -21,4 +21,6 @@ =20 netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev); =20 +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 e9a6dc100d4675353bfb308a5195590e7df04b1d..b356c2235d46fdde500cab4f0b2= 1331cc2fb60c8 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -21,8 +22,25 @@ #include "io.h" #include "peer.h" #include "proto.h" +#include "udp.h" + +static int ovpn_net_init(struct net_device *dev) +{ + struct ovpn_priv *ovpn =3D netdev_priv(dev); + + return gro_cells_init(&ovpn->gro_cells, dev); +} + +static void ovpn_net_uninit(struct net_device *dev) +{ + struct ovpn_priv *ovpn =3D netdev_priv(dev); + + gro_cells_destroy(&ovpn->gro_cells); +} =20 static const struct net_device_ops ovpn_netdev_ops =3D { + .ndo_init =3D ovpn_net_init, + .ndo_uninit =3D ovpn_net_uninit, .ndo_start_xmit =3D ovpn_net_xmit, }; =20 diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index a398f9da2e0958b0be885a50a13d80b8b5a515eb..d31cceee508d7066bf79b71d77d= 05d039dc97f29 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -10,6 +10,7 @@ #ifndef _NET_OVPN_OVPNSTRUCT_H_ #define _NET_OVPN_OVPNSTRUCT_H_ =20 +#include #include #include =20 @@ -19,12 +20,14 @@ * @mode: device operation mode (i.e. p2p, mp, ..) * @lock: protect this object * @peer: in P2P mode, this is the only remote peer + * @gro_cells: pointer to the Generic Receive Offload cell */ struct ovpn_priv { struct net_device *dev; enum ovpn_mode mode; spinlock_t lock; /* protect writing to the ovpn_priv object */ struct ovpn_peer __rcu *peer; + struct gro_cells gro_cells; }; =20 #endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/drivers/net/ovpn/proto.h b/drivers/net/ovpn/proto.h index 5f95a78bebd3702868ffeeab3ea4938e957d568c..591b97a9925fd9b91f996d6d591= fac41b1aa6148 100644 --- a/drivers/net/ovpn/proto.h +++ b/drivers/net/ovpn/proto.h @@ -10,6 +10,11 @@ #ifndef _NET_OVPN_PROTO_H_ #define _NET_OVPN_PROTO_H_ =20 +#include "main.h" + +#include +#include + /* When the OpenVPN protocol is ran in AEAD mode, use * the OpenVPN packet ID as the AEAD nonce: * @@ -34,5 +39,48 @@ #define OVPN_NONCE_WIRE_SIZE (OVPN_NONCE_SIZE - OVPN_NONCE_TAIL_SIZE) =20 #define OVPN_OPCODE_SIZE 4 /* DATA_V2 opcode size */ +#define OVPN_OPCODE_KEYID_MASK 0x07000000 +#define OVPN_OPCODE_PKTTYPE_MASK 0xF8000000 +#define OVPN_OPCODE_PEERID_MASK 0x00FFFFFF + +/* packet opcodes of interest to us */ +#define OVPN_DATA_V1 6 /* data channel v1 packet */ +#define OVPN_DATA_V2 9 /* data channel v2 packet */ + +#define OVPN_PEER_ID_UNDEF 0x00FFFFFF + +/** + * 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 4 bytes. + * + * Return: the OP code + */ +static inline u8 ovpn_opcode_from_skb(const struct sk_buff *skb, u16 offse= t) +{ + u32 opcode =3D be32_to_cpu(*(__be32 *)(skb->data + offset)); + + return FIELD_GET(OVPN_OPCODE_PKTTYPE_MASK, opcode); +} + +/** + * 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 off= set) +{ + u32 opcode =3D be32_to_cpu(*(__be32 *)(skb->data + offset)); + + return FIELD_GET(OVPN_OPCODE_PEERID_MASK, opcode); +} =20 -#endif /* _NET_OVPN_PROTO_H_ */ +#endif /* _NET_OVPN_OVPNPROTO_H_ */ diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c index beec7aee35e15f659873d10318a2c7f2a6ecf651..f1fd98d528451e93a10b872e4d9= b37c3940d339b 100644 --- a/drivers/net/ovpn/socket.c +++ b/drivers/net/ovpn/socket.c @@ -23,8 +23,10 @@ static void ovpn_socket_release_kref(struct kref *kref) struct ovpn_socket *sock =3D container_of(kref, struct ovpn_socket, refcount); =20 - if (sock->sock->sk->sk_protocol =3D=3D IPPROTO_UDP) + if (sock->sock->sk->sk_protocol =3D=3D IPPROTO_UDP) { ovpn_udp_socket_detach(sock); + netdev_put(sock->ovpn->dev, &sock->dev_tracker); + } =20 kfree_rcu(sock, rcu); } @@ -179,7 +181,6 @@ struct ovpn_socket *ovpn_socket_new(struct socket *sock= , struct ovpn_peer *peer) goto sock_release; } =20 - ovpn_sock->ovpn =3D peer->ovpn; ovpn_sock->sock =3D sock; kref_init(&ovpn_sock->refcount); =20 @@ -190,6 +191,15 @@ struct ovpn_socket *ovpn_socket_new(struct socket *soc= k, struct ovpn_peer *peer) goto sock_release; } =20 + if (sock->sk->sk_protocol =3D=3D IPPROTO_UDP) { + /* in UDP we only link the ovpn instance since the socket is + * shared among multiple peers + */ + ovpn_sock->ovpn =3D peer->ovpn; + netdev_hold(peer->ovpn->dev, &ovpn_sock->dev_tracker, + GFP_KERNEL); + } + rcu_assign_sk_user_data(sock->sk, ovpn_sock); sock_release: release_sock(sock->sk); diff --git a/drivers/net/ovpn/socket.h b/drivers/net/ovpn/socket.h index ade8c94619d7b2f905b5284373dc73f590188399..c1697f4616d470f143cba77bb1d= b34de0398a37a 100644 --- a/drivers/net/ovpn/socket.h +++ b/drivers/net/ovpn/socket.h @@ -20,12 +20,19 @@ struct ovpn_peer; /** * struct ovpn_socket - a kernel socket referenced in the ovpn code * @ovpn: ovpn instance owning this socket (UDP only) + * @dev_tracker: reference tracker for associated dev (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_priv *ovpn; + union { + struct { + struct ovpn_priv *ovpn; + netdevice_tracker dev_tracker; + }; + }; + struct socket *sock; struct kref refcount; struct rcu_head rcu; diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index 28307ef7b2672cf1ad1240c1d17ffdcb9e64b5a5..920d71da793ccc88de4cff1c99c= 8e6e8d0a24b71 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include =20 @@ -23,9 +24,114 @@ #include "bind.h" #include "io.h" #include "peer.h" +#include "proto.h" #include "socket.h" #include "udp.h" =20 +/* Retrieve the corresponding ovpn object from a UDP socket + * rcu_read_lock must be held on entry + */ +static struct ovpn_socket *ovpn_socket_from_udp_sock(struct sock *sk) +{ + struct ovpn_socket *ovpn_sock; + + if (unlikely(READ_ONCE(udp_sk(sk)->encap_type) !=3D UDP_ENCAP_OVPNINUDP)) + return NULL; + + ovpn_sock =3D 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 !=3D ovpn_sock->sock->sk)) + return NULL; + + return ovpn_sock; +} + +/** + * 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 accepted for further processing, + * - DATA_V1 the packet is dropped as not supported, + * - anything else the packet 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_socket *ovpn_sock; + struct ovpn_priv *ovpn; + struct ovpn_peer *peer; + u32 peer_id; + u8 opcode; + + ovpn_sock =3D ovpn_socket_from_udp_sock(sk); + if (unlikely(!ovpn_sock)) { + net_err_ratelimited("ovpn: %s invoked on non ovpn socket\n", + __func__); + goto drop_noovpn; + } + + ovpn =3D ovpn_sock->ovpn; + if (unlikely(!ovpn)) { + net_err_ratelimited("ovpn: cannot obtain ovpn object from UDP socket\n"); + goto drop_noovpn; + } + + /* 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_OPCODE_SIZE))) { + net_dbg_ratelimited("%s: packet too small from UDP socket\n", + netdev_name(ovpn->dev)); + goto drop; + } + + opcode =3D ovpn_opcode_from_skb(skb, sizeof(struct udphdr)); + if (unlikely(opcode !=3D OVPN_DATA_V2)) { + /* DATA_V1 is not supported */ + if (opcode =3D=3D OVPN_DATA_V1) + goto drop; + + /* unknown or control packet: let it bubble up to userspace */ + return 1; + } + + peer_id =3D 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 =3D=3D OVPN_PEER_ID_UNDEF) + peer =3D ovpn_peer_get_by_transp_addr(ovpn, skb); + else + peer =3D ovpn_peer_get_by_id(ovpn, peer_id); + + if (unlikely(!peer)) + goto drop; + + /* pop off outer UDP header */ + __skb_pull(skb, sizeof(struct udphdr)); + ovpn_recv(peer, skb); + return 0; + +drop: + dev_dstats_rx_dropped(ovpn->dev); +drop_noovpn: + kfree_skb(skb); + return 0; +} + /** * ovpn_udp4_output - send IPv4 packet over udp socket * @peer: the destination peer @@ -247,6 +353,25 @@ void ovpn_udp_send_skb(struct ovpn_peer *peer, struct = socket *sock, } } =20 +static void ovpn_udp_encap_destroy(struct sock *sk) +{ + struct ovpn_socket *sock; + struct ovpn_priv *ovpn; + + rcu_read_lock(); + sock =3D rcu_dereference_sk_user_data(sk); + if (!sock || !sock->ovpn) { + rcu_read_unlock(); + return; + } + ovpn =3D sock->ovpn; + rcu_read_unlock(); + + if (ovpn->mode =3D=3D OVPN_MODE_P2P) + ovpn_peer_release_p2p(ovpn, sk, + OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT); +} + /** * ovpn_udp_socket_attach - set udp-tunnel CBs on socket and link it to ov= pn * @ovpn_sock: socket to configure @@ -260,6 +385,11 @@ void ovpn_udp_send_skb(struct ovpn_peer *peer, struct = socket *sock, int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, struct ovpn_priv *ovpn) { + struct udp_tunnel_sock_cfg cfg =3D { + .encap_type =3D UDP_ENCAP_OVPNINUDP, + .encap_rcv =3D ovpn_udp_encap_recv, + .encap_destroy =3D ovpn_udp_encap_destroy, + }; struct socket *sock =3D ovpn_sock->sock; struct ovpn_socket *old_data; int ret; @@ -270,6 +400,7 @@ int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_soc= k, 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; } =20 @@ -303,4 +434,8 @@ int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_soc= k, */ void ovpn_udp_socket_detach(struct ovpn_socket *ovpn_sock) { + struct udp_tunnel_sock_cfg cfg =3D { }; + + setup_udp_tunnel_sock(sock_net(ovpn_sock->sock->sk), ovpn_sock->sock, + &cfg); } --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f48.google.com (mail-wm1-f48.google.com [209.85.128.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7AB9B296D1F for ; Tue, 15 Apr 2025 11:17:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715867; cv=none; b=ZtiDoHunR5xdozFf48QQKbePQ52HJB0MFIEUox01qc/ezh745QmW54kf3ziOD07c+SQrHGldhBkJsG0WDvGzijBO3Cdhyzo2RvzuJTkQ5sw9arAvpNNAE/datbuWNOg54L5ubxYRKu8v06dPGdOMD6BtGPK28MCKkdfTaGjMCWs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715867; c=relaxed/simple; bh=k6y8eVqKfSFLPnTifaJD8moQ/nj0fEJVL0725ZdiLd0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=HWY1bEqpw9r39ZSCS6Z1fYlxL0sp5+1J85Av8okL1srHnsrvuD4QK0pWgfdAwNnvf6UFFLWr/noWpaOJ6VFeKfPGCPC+t9COJFICZskqJIzZwWmyQb7dBxmpUrKxRuNyFhx+JHwob34C4sXC1nYbwhK+9XsbjB3zEPNru3zfz7w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=GpIfXqhH; arc=none smtp.client-ip=209.85.128.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="GpIfXqhH" Received: by mail-wm1-f48.google.com with SMTP id 5b1f17b1804b1-43ed8d32a95so45625865e9.3 for ; Tue, 15 Apr 2025 04:17:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715859; x=1745320659; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=WkGKZuZdywRKjFe0IdIcIvTyN6DZk9LmY5thwuv6C/s=; b=GpIfXqhH0ph8eT6cLg/cCNs933N1GiQxfiOpR1oT8U4BBZ7ql/X4kVfimtz25kTvH0 gWrh0ld/6KD4k/dMGnS2ww93k8AmUFuND1bImAnFQnQ9VkvsPPC88wGOjM4bycSPxJ/x AFnzZEWFjf3NJtH/Yu/8DJ1pClITlV7iGfZhiFXahv7Z1akgWSe86QlEXY4mxLHva6z8 4skMXj9ks6Md17GDKKc8RBQ58muiLNYHv0qOiaYNvBLwhb24QMPdUzrXlXaDL8UVhNHJ 3YPmo8nX+/ugjuXSQBVzEYlRGbt/tj17VpvD5COmbH7YQDk+T5u0+vaSWfL0VN6YVTTD POQg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715859; x=1745320659; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=WkGKZuZdywRKjFe0IdIcIvTyN6DZk9LmY5thwuv6C/s=; b=RnwECzYirruL3Gavaj8f/A7sQyScqQgg8RnLQ0jlYDI1N3BEU5zmyC/zjM/vl0LVBX NnXzZ0LXDcb3gfV0ONG8i9zJNgf2Qj+H2KjJfY93PlivBsD7xQ9rzPX53PImZUI6ztOI cqoyixNd3lYhRZM+GCY7y2M2sjlSRE85NiXHOq+/b9K5qgamjzNhToBMUCAR+d8RmbDR fqdPx+2sIy2q7Vj41n2oCqG5aVUZM3ZW8RMFEJPCJBCiQ2E7F0MWo+cSKVnUzh3tZ4vO UGSV22qczqJ/DSVoU/N3iXLsAPfAKDe+VmalogOOVxTx5weOsmDIUbHbYDfge8CipUuM lGYA== X-Forwarded-Encrypted: i=1; AJvYcCU4YKqpwe6L/4ISrB76Amxn0ci7Lma+Tahbe1LWG7ZVNiOa7ZxhyMqGK0JSWFaGlx8WaXnQB1LZf7oA3VQ=@vger.kernel.org X-Gm-Message-State: AOJu0YyUKOJwg9vf0zT88GsXaLXD3Y2Lyrz1qbvehC+Wm5d9wRl/OZZl kw2iYUgTOpwIiL/IaKbch17ibsb/fE1ZwKpC/AUnF40sCrgNlwlMQ0Iy9pe/qSUVR/Y/4IqmH3X 6QV24pDfgc5ShkjAZmQgXwFLfjziXlkga/nYHW5+uffE6j9zvKEsUDDyCz+go0FmT/2LA X-Gm-Gg: ASbGnctH1ZrWmsfixCiY21+Fu1cvyPMw+DKegy7tHQV95B/eXy9BD2a3+oswZ102syG L1HWPpoXlCOAx5PeRuNkM6a8pwc5+2OHPogfEaP4He9fz2yk6fzsRHYtLXkCWy+I6gx2yYiU1aD lSENaUHMKcS+1cBwJio4DBil/YjJJwAy/asbbLSustgDsGELJpHGCdFQ5ZfKoGmu+xxFev6HAeO GX2eiqtUpTRlauM1973O/TDzJ5YLIUm+lHXF+58njGHDQ800QkAoe66LyBOl7tUOGWVZJEbaTXS NvSt1ZD2uTw+qXSRE2N6JRAy+fS8aoPTIqlJSQ== X-Google-Smtp-Source: AGHT+IGaoU63262+s6XvJFkVxulkznW7ahp4wdpWVLLsNojb/NHEYZi8PBe+WSqnmkd5RmgyNSFV4Q== X-Received: by 2002:a05:600c:3501:b0:43c:fe15:41e1 with SMTP id 5b1f17b1804b1-43f3a925a32mr159650705e9.4.1744715859275; Tue, 15 Apr 2025 04:17:39 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:38 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:26 +0200 Subject: [PATCH net-next v26 09/23] ovpn: implement packet processing Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-9-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=41436; i=antonio@openvpn.net; h=from:subject:message-id; bh=k6y8eVqKfSFLPnTifaJD8moQ/nj0fEJVL0725ZdiLd0=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBBbtlr7HPEFGHOgwpMHWrXazwsJq1zpQe0+ xoTsN5xURqJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQQAKCRALcOU6oDjV h3IzB/9sq9/xt+PiQAb3SFsCZxiXVNoVg5P8Azgup6tc24IXGf/M9LlvicqdKV+LWDKQAqHGgfc LXfLp4Em4+kd96cLzyGwfRZfYRZKG17lzG407iRBySfHk0otnbgK4nWnjm1c3PKQv60WCN1d+Zk tnXkpZw3XWqikYGrapoThmoSohtxFMYOWbLwtcS+guXZxSBO8LasQy292nWrHf7er4E0UgZh0zn B8C0r38Ph4PVyfbLWOD/mpSW4TUU5KrOQJ40mEqH53vkJdFTz/wcnM2EBNuJBuEkgOsEoVqm9J9 zm21cSHPqH/FhtDK4tSEMvGCoOIAvR3hmd/JmfGch9TP97GO X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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 Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/Kconfig | 4 + drivers/net/ovpn/Makefile | 3 + drivers/net/ovpn/bind.c | 9 +- drivers/net/ovpn/crypto.c | 148 +++++++++++++++++ drivers/net/ovpn/crypto.h | 139 ++++++++++++++++ drivers/net/ovpn/crypto_aead.c | 366 +++++++++++++++++++++++++++++++++++++= ++++ drivers/net/ovpn/crypto_aead.h | 27 +++ drivers/net/ovpn/io.c | 137 +++++++++++++-- drivers/net/ovpn/io.h | 3 + drivers/net/ovpn/peer.c | 29 ++++ drivers/net/ovpn/peer.h | 5 + drivers/net/ovpn/pktid.c | 129 +++++++++++++++ drivers/net/ovpn/pktid.h | 86 ++++++++++ drivers/net/ovpn/proto.h | 32 ++++ drivers/net/ovpn/skb.h | 5 + 15 files changed, 1105 insertions(+), 17 deletions(-) diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 305f04dd97234c4aa43da78217448b914cc7ede0..a5cee847911b17a3584ab3d9c1c= f7d166d4e1298 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -121,6 +121,10 @@ config OVPN depends on IPV6 || !IPV6 select DST_CACHE select NET_UDP_TUNNEL + 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. diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 164f2058ea8e6dc5b9287afb59758a268b2f8b56..38c9fdca0e2e8e4af3c369ceb39= 71b58ab52d77b 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -8,10 +8,13 @@ =20 obj-$(CONFIG_OVPN) :=3D ovpn.o ovpn-y +=3D bind.o +ovpn-y +=3D crypto.o +ovpn-y +=3D crypto_aead.o ovpn-y +=3D main.o ovpn-y +=3D io.o ovpn-y +=3D netlink.o ovpn-y +=3D netlink-gen.o ovpn-y +=3D peer.o +ovpn-y +=3D pktid.o ovpn-y +=3D socket.o ovpn-y +=3D udp.o diff --git a/drivers/net/ovpn/bind.c b/drivers/net/ovpn/bind.c index d4a1aeed12c99c71eaf5e8e9fc9c0fe61af6aaac..24d2788a277e674bde80b5aac94= 07c6528b108e5 100644 --- a/drivers/net/ovpn/bind.c +++ b/drivers/net/ovpn/bind.c @@ -48,11 +48,8 @@ struct ovpn_bind *ovpn_bind_from_sockaddr(const struct s= ockaddr_storage *ss) */ void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *new) { - struct ovpn_bind *old; + lockdep_assert_held(&peer->lock); =20 - spin_lock_bh(&peer->lock); - old =3D rcu_replace_pointer(peer->bind, new, true); - spin_unlock_bh(&peer->lock); - - kfree_rcu(old, rcu); + kfree_rcu(rcu_replace_pointer(peer->bind, new, + lockdep_is_held(&peer->lock)), rcu); } diff --git a/drivers/net/ovpn/crypto.c b/drivers/net/ovpn/crypto.c new file mode 100644 index 0000000000000000000000000000000000000000..9544255c4588ad0bcccda5bdc83= a4d8729458ff8 --- /dev/null +++ b/drivers/net/ovpn/crypto.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include +#include +#include + +#include "ovpnpriv.h" +#include "main.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 =3D 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 =3D 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 =3D rcu_access_pointer(cs->slots[0]); + if (ks) { + RCU_INIT_POINTER(cs->slots[0], NULL); + ovpn_crypto_key_slot_put(ks); + } + + ks =3D rcu_access_pointer(cs->slots[1]); + if (ks) { + RCU_INIT_POINTER(cs->slots[1], NULL); + ovpn_crypto_key_slot_put(ks); + } +} + +/* 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 =3D NULL, *new; + u8 idx; + + if (pkr->slot !=3D OVPN_KEY_SLOT_PRIMARY && + pkr->slot !=3D OVPN_KEY_SLOT_SECONDARY) + return -EINVAL; + + new =3D ovpn_aead_crypto_key_slot_new(&pkr->key); + if (IS_ERR(new)) + return PTR_ERR(new); + + spin_lock_bh(&cs->lock); + idx =3D cs->primary_idx; + switch (pkr->slot) { + case OVPN_KEY_SLOT_PRIMARY: + old =3D rcu_replace_pointer(cs->slots[idx], new, + lockdep_is_held(&cs->lock)); + break; + case OVPN_KEY_SLOT_SECONDARY: + old =3D rcu_replace_pointer(cs->slots[!idx], new, + lockdep_is_held(&cs->lock)); + break; + } + spin_unlock_bh(&cs->lock); + + 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 =3D NULL; + u8 idx; + + if (slot !=3D OVPN_KEY_SLOT_PRIMARY && + slot !=3D OVPN_KEY_SLOT_SECONDARY) { + pr_warn("Invalid slot to release: %u\n", slot); + return; + } + + spin_lock_bh(&cs->lock); + idx =3D cs->primary_idx; + switch (slot) { + case OVPN_KEY_SLOT_PRIMARY: + ks =3D rcu_replace_pointer(cs->slots[idx], NULL, + lockdep_is_held(&cs->lock)); + break; + case OVPN_KEY_SLOT_SECONDARY: + ks =3D rcu_replace_pointer(cs->slots[!idx], NULL, + lockdep_is_held(&cs->lock)); + break; + } + spin_unlock_bh(&cs->lock); + + if (!ks) { + pr_debug("Key slot already released: %u\n", slot); + return; + } + + pr_debug("deleting key slot %u, key_id=3D%u\n", slot, ks->key_id); + ovpn_crypto_key_slot_put(ks); +} + +void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs) +{ + const struct ovpn_crypto_key_slot *old_primary, *old_secondary; + u8 idx; + + spin_lock_bh(&cs->lock); + idx =3D cs->primary_idx; + old_primary =3D rcu_dereference_protected(cs->slots[idx], + lockdep_is_held(&cs->lock)); + old_secondary =3D rcu_dereference_protected(cs->slots[!idx], + lockdep_is_held(&cs->lock)); + /* perform real swap by switching the index of the primary key */ + WRITE_ONCE(cs->primary_idx, !cs->primary_idx); + + pr_debug("key swapped: (old primary) %d <-> (new primary) %d\n", + old_primary ? old_primary->key_id : -1, + old_secondary ? old_secondary->key_id : -1); + + spin_unlock_bh(&cs->lock); +} diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h new file mode 100644 index 0000000000000000000000000000000000000000..5155791b87df7cccc76a011fa75= 1686180074982 --- /dev/null +++ b/drivers/net/ovpn/crypto.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNCRYPTO_H_ +#define _NET_OVPN_OVPNCRYPTO_H_ + +#include "pktid.h" +#include "proto.h" + +/* 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; + u8 nonce_tail_xmit[OVPN_NONCE_TAIL_SIZE]; + u8 nonce_tail_recv[OVPN_NONCE_TAIL_SIZE]; + + 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 *slots[2]; + u8 primary_idx; + + /* protects primary and secondary slots */ + spinlock_t lock; +}; + +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->slots[0], NULL); + RCU_INIT_POINTER(cs->slots[1], NULL); + cs->primary_idx =3D 0; + spin_lock_init(&cs->lock); +} + +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; + u8 idx; + + if (unlikely(!cs)) + return NULL; + + rcu_read_lock(); + idx =3D READ_ONCE(cs->primary_idx); + ks =3D rcu_dereference(cs->slots[idx]); + if (ks && ks->key_id =3D=3D key_id) { + if (unlikely(!ovpn_crypto_key_slot_hold(ks))) + ks =3D NULL; + goto out; + } + + ks =3D rcu_dereference(cs->slots[!idx]); + if (ks && ks->key_id =3D=3D key_id) { + if (unlikely(!ovpn_crypto_key_slot_hold(ks))) + ks =3D 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 =3D 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 =3D rcu_dereference(cs->slots[cs->primary_idx]); + if (unlikely(ks && !ovpn_crypto_key_slot_hold(ks))) + ks =3D 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 *k= s) +{ + 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); + +#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 0000000000000000000000000000000000000000..83ec18e4b9a4f7960bf789cee95= 2ac11cb77083d --- /dev/null +++ b/drivers/net/ovpn/crypto_aead.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include +#include +#include +#include + +#include "ovpnpriv.h" +#include "main.h" +#include "io.h" +#include "pktid.h" +#include "crypto_aead.h" +#include "crypto.h" +#include "peer.h" +#include "proto.h" +#include "skb.h" + +#define OVPN_AUTH_TAG_SIZE 16 +#define OVPN_AAD_SIZE (OVPN_OPCODE_SIZE + OVPN_NONCE_WIRE_SIZE) + +#define ALG_NAME_AES "gcm(aes)" +#define ALG_NAME_CHACHAPOLY "rfc7539(chacha20,poly1305)" + +static int ovpn_aead_encap_overhead(const struct ovpn_crypto_key_slot *ks) +{ + return OVPN_OPCODE_SIZE + /* OP header size */ + sizeof(u32) + /* Packet ID */ + crypto_aead_authsize(ks->encrypt); /* Auth Tag */ +} + +int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot = *ks, + struct sk_buff *skb) +{ + const unsigned int tag_size =3D crypto_aead_authsize(ks->encrypt); + struct aead_request *req; + struct sk_buff *trailer; + struct scatterlist *sg; + int nfrags, ret; + u32 pktid, op; + u8 *iv; + + ovpn_skb_cb(skb)->peer =3D peer; + ovpn_skb_cb(skb)->ks =3D ks; + + /* 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 + */ + if (unlikely(skb_cow_head(skb, OVPN_HEAD_ROOM))) + return -ENOBUFS; + + /* get number of skb frags and ensure that packet data is writable */ + nfrags =3D skb_cow_data(skb, 0, &trailer); + if (unlikely(nfrags < 0)) + return nfrags; + + if (unlikely(nfrags + 2 > (MAX_SKB_FRAGS + 2))) + return -ENOSPC; + + /* sg may be required by async crypto */ + ovpn_skb_cb(skb)->sg =3D kmalloc(sizeof(*ovpn_skb_cb(skb)->sg) * + (nfrags + 2), GFP_ATOMIC); + if (unlikely(!ovpn_skb_cb(skb)->sg)) + return -ENOMEM; + + sg =3D ovpn_skb_cb(skb)->sg; + + /* sg table: + * 0: op, wire nonce (AD, len=3DOVPN_OP_SIZE_V2+OVPN_NONCE_WIRE_SIZE), + * 1, 2, 3, ..., n: payload, + * n+1: auth_tag (len=3Dtag_size) + */ + sg_init_table(sg, nfrags + 2); + + /* build scatterlist to encrypt packet payload */ + ret =3D skb_to_sgvec_nomark(skb, sg + 1, 0, skb->len); + if (unlikely(nfrags !=3D 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 =3D ovpn_pktid_xmit_next(&ks->pid_xmit, &pktid); + if (unlikely(ret < 0)) + return ret; + + /* iv may be required by async crypto */ + ovpn_skb_cb(skb)->iv =3D kmalloc(OVPN_NONCE_SIZE, GFP_ATOMIC); + if (unlikely(!ovpn_skb_cb(skb)->iv)) + return -ENOMEM; + + iv =3D ovpn_skb_cb(skb)->iv; + + /* 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, OVPN_NONCE_WIRE_SIZE); + memcpy(skb->data, iv, OVPN_NONCE_WIRE_SIZE); + + /* add packet op as head of additional data */ + op =3D ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer->id); + __skb_push(skb, OVPN_OPCODE_SIZE); + BUILD_BUG_ON(sizeof(op) !=3D OVPN_OPCODE_SIZE); + *((__force __be32 *)skb->data) =3D htonl(op); + + /* AEAD Additional data */ + sg_set_buf(sg, skb->data, OVPN_AAD_SIZE); + + req =3D aead_request_alloc(ks->encrypt, GFP_ATOMIC); + if (unlikely(!req)) + return -ENOMEM; + + ovpn_skb_cb(skb)->req =3D req; + + /* setup async crypto operation */ + aead_request_set_tfm(req, ks->encrypt); + aead_request_set_callback(req, 0, ovpn_encrypt_post, skb); + aead_request_set_crypt(req, sg, sg, + skb->len - ovpn_aead_encap_overhead(ks), iv); + aead_request_set_ad(req, OVPN_AAD_SIZE); + + /* encrypt it */ + return crypto_aead_encrypt(req); +} + +int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot = *ks, + struct sk_buff *skb) +{ + const unsigned int tag_size =3D crypto_aead_authsize(ks->decrypt); + int ret, payload_len, nfrags; + unsigned int payload_offset; + struct aead_request *req; + struct sk_buff *trailer; + struct scatterlist *sg; + u8 *iv; + + payload_offset =3D OVPN_AAD_SIZE + tag_size; + payload_len =3D skb->len - payload_offset; + + ovpn_skb_cb(skb)->payload_offset =3D payload_offset; + ovpn_skb_cb(skb)->peer =3D peer; + ovpn_skb_cb(skb)->ks =3D ks; + + /* sanity check on packet size, payload size must be >=3D 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 =3D skb_cow_data(skb, 0, &trailer); + if (unlikely(nfrags < 0)) + return nfrags; + + if (unlikely(nfrags + 2 > (MAX_SKB_FRAGS + 2))) + return -ENOSPC; + + /* sg may be required by async crypto */ + ovpn_skb_cb(skb)->sg =3D kmalloc(sizeof(*ovpn_skb_cb(skb)->sg) * + (nfrags + 2), GFP_ATOMIC); + if (unlikely(!ovpn_skb_cb(skb)->sg)) + return -ENOMEM; + + sg =3D ovpn_skb_cb(skb)->sg; + + /* sg table: + * 0: op, wire nonce (AD, len=3DOVPN_OPCODE_SIZE+OVPN_NONCE_WIRE_SIZE), + * 1, 2, 3, ..., n: payload, + * n+1: auth_tag (len=3Dtag_size) + */ + sg_init_table(sg, nfrags + 2); + + /* packet op is head of additional data */ + sg_set_buf(sg, skb->data, OVPN_AAD_SIZE); + + /* build scatterlist to decrypt packet payload */ + ret =3D skb_to_sgvec_nomark(skb, sg + 1, payload_offset, payload_len); + if (unlikely(nfrags !=3D ret)) + return -EINVAL; + + /* append auth_tag onto scatterlist */ + sg_set_buf(sg + nfrags + 1, skb->data + OVPN_AAD_SIZE, tag_size); + + /* iv may be required by async crypto */ + ovpn_skb_cb(skb)->iv =3D kmalloc(OVPN_NONCE_SIZE, GFP_ATOMIC); + if (unlikely(!ovpn_skb_cb(skb)->iv)) + return -ENOMEM; + + iv =3D ovpn_skb_cb(skb)->iv; + + /* copy nonce into IV buffer */ + memcpy(iv, skb->data + OVPN_OPCODE_SIZE, OVPN_NONCE_WIRE_SIZE); + memcpy(iv + OVPN_NONCE_WIRE_SIZE, ks->nonce_tail_recv, + OVPN_NONCE_TAIL_SIZE); + + req =3D aead_request_alloc(ks->decrypt, GFP_ATOMIC); + if (unlikely(!req)) + return -ENOMEM; + + ovpn_skb_cb(skb)->req =3D req; + + /* setup async crypto operation */ + aead_request_set_tfm(req, ks->decrypt); + aead_request_set_callback(req, 0, ovpn_decrypt_post, skb); + aead_request_set_crypt(req, sg, sg, payload_len + tag_size, iv); + + aead_request_set_ad(req, OVPN_AAD_SIZE); + + /* decrypt it */ + return crypto_aead_decrypt(req); +} + +/* Initialize a struct crypto_aead object */ +static 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 =3D crypto_alloc_aead(alg_name, 0, 0); + if (IS_ERR(aead)) { + ret =3D PTR_ERR(aead); + pr_err("%s crypto_alloc_aead failed, err=3D%d\n", title, ret); + aead =3D NULL; + goto error; + } + + ret =3D crypto_aead_setkey(aead, key, keylen); + if (ret) { + pr_err("%s crypto_aead_setkey size=3D%u failed, err=3D%d\n", title, + keylen, ret); + goto error; + } + + ret =3D crypto_aead_setauthsize(aead, OVPN_AUTH_TAG_SIZE); + if (ret) { + pr_err("%s crypto_aead_setauthsize failed, err=3D%d\n", title, + ret); + goto error; + } + + /* basic AEAD assumption */ + if (crypto_aead_ivsize(aead) !=3D OVPN_NONCE_SIZE) { + pr_err("%s IV size must be %d\n", title, OVPN_NONCE_SIZE); + ret =3D -EINVAL; + goto error; + } + + pr_debug("********* Cipher %s (%s)\n", alg_name, title); + pr_debug("*** IV size=3D%u\n", crypto_aead_ivsize(aead)); + pr_debug("*** req size=3D%u\n", crypto_aead_reqsize(aead)); + pr_debug("*** block size=3D%u\n", crypto_aead_blocksize(aead)); + pr_debug("*** auth size=3D%u\n", crypto_aead_authsize(aead)); + pr_debug("*** alignmask=3D0x%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 =3D NULL; + const char *alg_name; + int ret; + + /* validate crypto alg */ + switch (kc->cipher_alg) { + case OVPN_CIPHER_ALG_AES_GCM: + alg_name =3D ALG_NAME_AES; + break; + case OVPN_CIPHER_ALG_CHACHA20_POLY1305: + alg_name =3D ALG_NAME_CHACHAPOLY; + break; + default: + return ERR_PTR(-EOPNOTSUPP); + } + + if (kc->encrypt.nonce_tail_size !=3D OVPN_NONCE_TAIL_SIZE || + kc->decrypt.nonce_tail_size !=3D OVPN_NONCE_TAIL_SIZE) + return ERR_PTR(-EINVAL); + + /* build the key slot */ + ks =3D kmalloc(sizeof(*ks), GFP_KERNEL); + if (!ks) + return ERR_PTR(-ENOMEM); + + ks->encrypt =3D NULL; + ks->decrypt =3D NULL; + kref_init(&ks->refcount); + ks->key_id =3D kc->key_id; + + ks->encrypt =3D ovpn_aead_init("encrypt", alg_name, + kc->encrypt.cipher_key, + kc->encrypt.cipher_key_size); + if (IS_ERR(ks->encrypt)) { + ret =3D PTR_ERR(ks->encrypt); + ks->encrypt =3D NULL; + goto destroy_ks; + } + + ks->decrypt =3D ovpn_aead_init("decrypt", alg_name, + kc->decrypt.cipher_key, + kc->decrypt.cipher_key_size); + if (IS_ERR(ks->decrypt)) { + ret =3D PTR_ERR(ks->decrypt); + ks->decrypt =3D NULL; + goto destroy_ks; + } + + memcpy(ks->nonce_tail_xmit, kc->encrypt.nonce_tail, + OVPN_NONCE_TAIL_SIZE); + memcpy(ks->nonce_tail_recv, kc->decrypt.nonce_tail, + OVPN_NONCE_TAIL_SIZE); + + /* 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 0000000000000000000000000000000000000000..40c056558add3b9d17fda5c43eb= 858cb44c95945 --- /dev/null +++ b/drivers/net/ovpn/crypto_aead.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNAEAD_H_ +#define _NET_OVPN_OVPNAEAD_H_ + +#include "crypto.h" + +#include +#include + +int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot = *ks, + struct sk_buff *skb); +int ovpn_aead_decrypt(struct ovpn_peer *peer, 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 22fa92b9aeeb55a71fb885037418c643cd34b269..846b6d7d4ec698d0418fa5dd7f7= 0bf84bc62fc99 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -7,6 +7,7 @@ * Antonio Quartulli */ =20 +#include #include #include #include @@ -15,6 +16,9 @@ #include "ovpnpriv.h" #include "peer.h" #include "io.h" +#include "bind.h" +#include "crypto.h" +#include "crypto_aead.h" #include "netlink.h" #include "proto.h" #include "udp.h" @@ -44,7 +48,7 @@ static void ovpn_netdev_write(struct ovpn_peer *peer, str= uct sk_buff *skb) skb_set_queue_mapping(skb, 0); skb_scrub_packet(skb, true); =20 - skb_reset_network_header(skb); + /* network header reset in ovpn_decrypt_post() */ skb_reset_transport_header(skb); skb_reset_inner_headers(skb); =20 @@ -56,34 +60,135 @@ static void ovpn_netdev_write(struct ovpn_peer *peer, = struct sk_buff *skb) dev_dstats_rx_add(peer->ovpn->dev, pkt_len); } =20 -static void ovpn_decrypt_post(struct sk_buff *skb, int ret) +void ovpn_decrypt_post(void *data, int ret) { - struct ovpn_peer *peer =3D ovpn_skb_cb(skb)->peer; + struct ovpn_crypto_key_slot *ks; + unsigned int payload_offset =3D 0; + struct sk_buff *skb =3D data; + struct ovpn_peer *peer; + __be16 proto; + __be32 *pid; + + /* crypto is happening asynchronously. this function will be called + * again later by the crypto callback with a proper return code + */ + if (unlikely(ret =3D=3D -EINPROGRESS)) + return; + + payload_offset =3D ovpn_skb_cb(skb)->payload_offset; + ks =3D ovpn_skb_cb(skb)->ks; + peer =3D ovpn_skb_cb(skb)->peer; + + /* crypto is done, cleanup skb CB and its members */ + kfree(ovpn_skb_cb(skb)->iv); + kfree(ovpn_skb_cb(skb)->sg); + aead_request_free(ovpn_skb_cb(skb)->req); =20 if (unlikely(ret < 0)) goto drop; =20 + /* PID sits after the op */ + pid =3D (__force __be32 *)(skb->data + OVPN_OPCODE_SIZE); + ret =3D ovpn_pktid_recv(&ks->pid_recv, ntohl(*pid), 0); + if (unlikely(ret < 0)) { + net_err_ratelimited("%s: PKT ID RX error for peer %u: %d\n", + netdev_name(peer->ovpn->dev), peer->id, + ret); + goto drop; + } + + /* point to encapsulated IP packet */ + __skb_pull(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 =3D 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", + netdev_name(peer->ovpn->dev), + peer->id); + goto drop; + } + + net_info_ratelimited("%s: unsupported protocol received from peer %u\n", + netdev_name(peer->ovpn->dev), peer->id); + goto drop; + } + skb->protocol =3D proto; + + /* perform Reverse Path Filtering (RPF) */ + if (unlikely(!ovpn_peer_check_by_src(peer->ovpn, skb, peer))) { + if (skb->protocol =3D=3D htons(ETH_P_IPV6)) + net_dbg_ratelimited("%s: RPF dropped packet from peer %u, src: %pI6c\n", + netdev_name(peer->ovpn->dev), + peer->id, &ipv6_hdr(skb)->saddr); + else + net_dbg_ratelimited("%s: RPF dropped packet from peer %u, src: %pI4\n", + netdev_name(peer->ovpn->dev), + peer->id, &ip_hdr(skb)->saddr); + goto drop; + } + ovpn_netdev_write(peer, skb); /* skb is passed to upper layer - don't free it */ skb =3D NULL; drop: if (unlikely(skb)) dev_dstats_rx_dropped(peer->ovpn->dev); - ovpn_peer_put(peer); + if (likely(peer)) + ovpn_peer_put(peer); + if (likely(ks)) + ovpn_crypto_key_slot_put(ks); kfree_skb(skb); } =20 /* RX path entry point: decrypt packet and forward it to the device */ void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb) { - ovpn_skb_cb(skb)->peer =3D peer; - ovpn_decrypt_post(skb, 0); + struct ovpn_crypto_key_slot *ks; + u8 key_id; + + /* get the key slot matching the key ID in the received packet */ + key_id =3D ovpn_key_id_from_skb(skb); + ks =3D 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", + netdev_name(peer->ovpn->dev), peer->id, + key_id); + dev_dstats_rx_dropped(peer->ovpn->dev); + kfree_skb(skb); + ovpn_peer_put(peer); + return; + } + + memset(ovpn_skb_cb(skb), 0, sizeof(struct ovpn_cb)); + ovpn_decrypt_post(skb, ovpn_aead_decrypt(peer, ks, skb)); } =20 -static void ovpn_encrypt_post(struct sk_buff *skb, int ret) +void ovpn_encrypt_post(void *data, int ret) { - struct ovpn_peer *peer =3D ovpn_skb_cb(skb)->peer; + struct ovpn_crypto_key_slot *ks; + struct sk_buff *skb =3D data; struct ovpn_socket *sock; + struct ovpn_peer *peer; + + /* encryption is happening asynchronously. This function will be + * called later by the crypto callback with a proper return value + */ + if (unlikely(ret =3D=3D -EINPROGRESS)) + return; + + ks =3D ovpn_skb_cb(skb)->ks; + peer =3D ovpn_skb_cb(skb)->peer; + + /* crypto is done, cleanup skb CB and its members */ + kfree(ovpn_skb_cb(skb)->iv); + kfree(ovpn_skb_cb(skb)->sg); + aead_request_free(ovpn_skb_cb(skb)->req); =20 if (unlikely(ret < 0)) goto err; @@ -110,23 +215,33 @@ static void ovpn_encrypt_post(struct sk_buff *skb, in= t ret) err: if (unlikely(skb)) dev_dstats_tx_dropped(peer->ovpn->dev); - ovpn_peer_put(peer); + if (likely(peer)) + ovpn_peer_put(peer); + if (likely(ks)) + ovpn_crypto_key_slot_put(ks); kfree_skb(skb); } =20 static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb) { - ovpn_skb_cb(skb)->peer =3D peer; + struct ovpn_crypto_key_slot *ks; + + /* get primary key to be used for encrypting data */ + ks =3D ovpn_crypto_key_slot_primary(&peer->crypto); + if (unlikely(!ks)) + return false; =20 /* take a reference to the peer because the crypto code may run async. * ovpn_encrypt_post() will release it upon completion */ if (unlikely(!ovpn_peer_hold(peer))) { DEBUG_NET_WARN_ON_ONCE(1); + ovpn_crypto_key_slot_put(ks); return false; } =20 - ovpn_encrypt_post(skb, 0); + memset(ovpn_skb_cb(skb), 0, sizeof(struct ovpn_cb)); + ovpn_encrypt_post(skb, ovpn_aead_encrypt(peer, ks, skb)); return true; } =20 diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h index 1cfa66873a2d4840ce57e337f8b4e8143e8b8e79..5143104b2c4b896a030ec4a8c8a= ea7015f40ef02 100644 --- a/drivers/net/ovpn/io.h +++ b/drivers/net/ovpn/io.h @@ -23,4 +23,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net= _device *dev); =20 void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb); =20 +void ovpn_encrypt_post(void *data, int ret); +void ovpn_decrypt_post(void *data, int ret); + #endif /* _NET_OVPN_OVPN_H_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 10eabd62ae7237162a36a333b41c748901a7d888..23eaab1b465b8b88a84cf9f1039= 621923b640b47 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -12,6 +12,8 @@ =20 #include "ovpnpriv.h" #include "bind.h" +#include "pktid.h" +#include "crypto.h" #include "io.h" #include "main.h" #include "netlink.h" @@ -56,6 +58,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u= 32 id) peer->vpn_addrs.ipv6 =3D in6addr_any; =20 RCU_INIT_POINTER(peer->bind, NULL); + ovpn_crypto_state_init(&peer->crypto); spin_lock_init(&peer->lock); kref_init(&peer->refcount); =20 @@ -94,7 +97,10 @@ static void ovpn_peer_release_rcu(struct rcu_head *head) */ static void ovpn_peer_release(struct ovpn_peer *peer) { + ovpn_crypto_state_release(&peer->crypto); + spin_lock_bh(&peer->lock); ovpn_bind_reset(peer, NULL); + spin_unlock_bh(&peer->lock); call_rcu(&peer->rcu, ovpn_peer_release_rcu); netdev_put(peer->ovpn->dev, &peer->dev_tracker); } @@ -326,6 +332,29 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_pri= v *ovpn, return peer; } =20 +/** + * ovpn_peer_check_by_src - check that skb source is routed via peer + * @ovpn: the openvpn instance to search + * @skb: the packet to extract 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_priv *ovpn, struct sk_buff *skb, + struct ovpn_peer *peer) +{ + bool match =3D false; + + if (ovpn->mode =3D=3D OVPN_MODE_P2P) { + /* in P2P mode, no matter the destination, packets are always + * sent to the single peer listening on the other side + */ + match =3D (peer =3D=3D rcu_access_pointer(ovpn->peer)); + } + + 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 fef04311c1593db4ccfa3c417487b3d4faaae9d7..a9113a969f94d66fa17208d563d= 0bbd255c23fa9 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -12,6 +12,7 @@ =20 #include =20 +#include "crypto.h" #include "socket.h" =20 /** @@ -23,6 +24,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 * @delete_reason: why peer was deleted (i.e. timeout, transport error, ..) @@ -40,6 +42,7 @@ struct ovpn_peer { struct in6_addr ipv6; } vpn_addrs; struct ovpn_socket __rcu *sock; + struct ovpn_crypto_state crypto; struct dst_cache dst_cache; struct ovpn_bind __rcu *bind; enum ovpn_del_peer_reason delete_reason; @@ -82,5 +85,7 @@ struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovp= n_priv *ovpn, struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id); struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb); +bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, + struct ovpn_peer *peer); =20 #endif /* _NET_OVPN_OVPNPEER_H_ */ diff --git a/drivers/net/ovpn/pktid.c b/drivers/net/ovpn/pktid.c new file mode 100644 index 0000000000000000000000000000000000000000..2f29049897e396e881e4604adf6= 8219d9f5f56c0 --- /dev/null +++ b/drivers/net/ovpn/pktid.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + * James Yonan + */ + +#include +#include +#include +#include +#include + +#include "ovpnpriv.h" +#include "main.h" +#include "pktid.h" + +void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid) +{ + atomic_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 =3D jiffies; + int ret; + + /* ID must not be zero */ + if (unlikely(pkt_id =3D=3D 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 =3D pr->id; + + /* time changed? */ + if (unlikely(pkt_time !=3D pr->time)) { + if (pkt_time > pr->time) { + /* time moved forward, accept */ + pr->base =3D 0; + pr->extent =3D 0; + pr->id =3D 0; + pr->time =3D pkt_time; + pr->id_floor =3D 0; + } else { + /* time moved backward, reject */ + ret =3D -ETIME; + goto out; + } + } + + if (likely(pkt_id =3D=3D pr->id + 1)) { + /* well-formed ID sequence (incremented by 1) */ + pr->base =3D REPLAY_INDEX(pr->base, -1); + pr->history[pr->base / 8] |=3D (1 << (pr->base % 8)); + if (pr->extent < REPLAY_WINDOW_SIZE) + ++pr->extent; + pr->id =3D pkt_id; + } else if (pkt_id > pr->id) { + /* ID jumped forward by more than one */ + const unsigned int delta =3D pkt_id - pr->id; + + if (delta < REPLAY_WINDOW_SIZE) { + unsigned int i; + + pr->base =3D REPLAY_INDEX(pr->base, -delta); + pr->history[pr->base / 8] |=3D (1 << (pr->base % 8)); + pr->extent +=3D delta; + if (pr->extent > REPLAY_WINDOW_SIZE) + pr->extent =3D REPLAY_WINDOW_SIZE; + for (i =3D 1; i < delta; ++i) { + unsigned int newb =3D REPLAY_INDEX(pr->base, i); + + pr->history[newb / 8] &=3D ~BIT(newb % 8); + } + } else { + pr->base =3D 0; + pr->extent =3D REPLAY_WINDOW_SIZE; + memset(pr->history, 0, sizeof(pr->history)); + pr->history[0] =3D 1; + } + pr->id =3D pkt_id; + } else { + /* ID backtrack */ + const unsigned int delta =3D pr->id - pkt_id; + + if (delta > pr->max_backtrack) + pr->max_backtrack =3D delta; + if (delta < pr->extent) { + if (pkt_id > pr->id_floor) { + const unsigned int ri =3D REPLAY_INDEX(pr->base, + delta); + u8 *p =3D &pr->history[ri / 8]; + const u8 mask =3D (1 << (ri % 8)); + + if (*p & mask) { + ret =3D -EINVAL; + goto out; + } + *p |=3D mask; + } else { + ret =3D -EINVAL; + goto out; + } + } else { + ret =3D -EINVAL; + goto out; + } + } + + pr->expire =3D now + PKTID_RECV_EXPIRE; + ret =3D 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 0000000000000000000000000000000000000000..0262d026d15e203892db05a8b45= c1c53fdb9c7ef --- /dev/null +++ b/drivers/net/ovpn/pktid.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + * James Yonan + */ + +#ifndef _NET_OVPN_OVPNPKTID_H_ +#define _NET_OVPN_OVPNPKTID_H_ + +#include "proto.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 { + atomic_t seq_num; +}; + +/* replay window sizing in bytes =3D 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 *p= ktid) +{ + const u32 seq_num =3D atomic_fetch_add_unless(&pid->seq_num, 1, 0); + /* 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)) + return -ERANGE; + + *pktid =3D seq_num; + + return 0; +} + +/* Write 12-byte AEAD IV to dest */ +static inline void ovpn_pktid_aead_write(const u32 pktid, + const u8 nt[], + unsigned char *dest) +{ + *(__force __be32 *)(dest) =3D htonl(pktid); + BUILD_BUG_ON(4 + OVPN_NONCE_TAIL_SIZE !=3D OVPN_NONCE_SIZE); + memcpy(dest + 4, nt, OVPN_NONCE_TAIL_SIZE); +} + +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 591b97a9925fd9b91f996d6d591fac41b1aa6148..b7d285b4d9c1dd258aee9b55012= 280665626d1aa 100644 --- a/drivers/net/ovpn/proto.h +++ b/drivers/net/ovpn/proto.h @@ -83,4 +83,36 @@ static inline u32 ovpn_peer_id_from_skb(const struct sk_= buff *skb, u16 offset) return FIELD_GET(OVPN_OPCODE_PEERID_MASK, opcode); } =20 +/** + * 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 4 bytes. + * + * Return: the key ID + */ +static inline u8 ovpn_key_id_from_skb(const struct sk_buff *skb) +{ + u32 opcode =3D be32_to_cpu(*(__be32 *)skb->data); + + return FIELD_GET(OVPN_OPCODE_KEYID_MASK, opcode); +} + +/** + * 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 hea= der. + */ +static inline u32 ovpn_opcode_compose(u8 opcode, u8 key_id, u32 peer_id) +{ + return FIELD_PREP(OVPN_OPCODE_PKTTYPE_MASK, opcode) | + FIELD_PREP(OVPN_OPCODE_KEYID_MASK, key_id) | + FIELD_PREP(OVPN_OPCODE_PEERID_MASK, peer_id); +} + #endif /* _NET_OVPN_OVPNPROTO_H_ */ diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h index 9db7a9adebdb4cc493f162f89fb2e9c6301fa213..bd3cbcfc770d2c28d234fcdd081= b4d02e6496ea0 100644 --- a/drivers/net/ovpn/skb.h +++ b/drivers/net/ovpn/skb.h @@ -20,6 +20,11 @@ =20 struct ovpn_cb { struct ovpn_peer *peer; + struct ovpn_crypto_key_slot *ks; + struct aead_request *req; + struct scatterlist *sg; + u8 *iv; + unsigned int payload_offset; }; =20 static inline struct ovpn_cb *ovpn_skb_cb(struct sk_buff *skb) --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 92EE82973BB for ; Tue, 15 Apr 2025 11:17:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715866; cv=none; b=PwOfuXLtLKskvQumA0og4twLIh4uaNR+wumk+94ZghUo/226Fx6uuOsXQpCPf3TTWENsuaptrpTpXeIOf+MomBJY6RwGjXldf6D3HWBnmkW2SwZKd2Zw5IRyRX8HlgNw6LQCulGoiAemfVJzYmMFuLINxZ+4yqOF1ezvLGoscWw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715866; c=relaxed/simple; bh=urcFahA47J1pElkhnEZnA3wXpRcRwV6IPsSmimxSZcQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=e+9dMz/SzIU7RWPzbVn+9rRRpfv9AF2n5uQKaL4qoneOt25S6HW7YjRi67WWGFTcWTNyO59WU5ddwEfA6NszvwZWAHhNTVytK45dCRlHjvG+ewv6wMYli5G0l8gFhSAsmkuHKcKJYZbXKBLyRsVvIDaxIgYqZ78S+ocYDOjzIfg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=F+0eZrv0; arc=none smtp.client-ip=209.85.128.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="F+0eZrv0" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-43cebe06e9eso40391875e9.3 for ; Tue, 15 Apr 2025 04:17:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715861; x=1745320661; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=eAtgxQ1ZXh1tMUFeov5eFpC8kchOzVrDNoGd7hzQ77s=; b=F+0eZrv0JgSv87rBfqQblHZM7cfhUhLOct5gYAxtlOYArKF27r8OfARtbPxokZRTHd dXVlBX8ncZpNhY6q7LiV6Uajn/4TPqR0m9zRaR6Rr7cgKv5IHWE2IS2Bl8vg+Ixgjmf8 sV2fwiqzQDlXs65rDJcpdo5Rv+ONNZdbEmn5ngAAelW2hgoond0Fg1/YZ0kfMcX62YVa 9pSbmbhAcfjRx3/5zshRlBLu3C3lQh4DWOdiy3MO5HNtPWqe2nG+vO5KA9j3B3dV8evr WeoHxUA47zAZVAipj+Ene4gQJJmWEfTJcWqJuvCHksvNmEtBts750nMRN+BZWqKQn/9/ GNeA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715861; x=1745320661; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=eAtgxQ1ZXh1tMUFeov5eFpC8kchOzVrDNoGd7hzQ77s=; b=vWLmkxLcRPPTyoVkAA24B9hoZ2oFa2Oayq0e7JlBvUtQBlDbLRKVBOmqpen0Xw/vqC 9z0uCOk/mSBm76XOx7HQgG0HneO693utb/aimSZYPHusdKaolnqitqGgjpUc6fGjn9aE uENn67lOnsdVFfFV+5q5beBWodwU7nLWf+7ZmrmjRxWCbszAr7+SZfrtWmMsDL9lwBlC sDsvMMYPrgF9WN2/xRVIyQQ3nxNUEyvDUedHIv7D6TXvoCryV9DMXBMcxsiR3pcL97Q6 k/PMAN6cINtUZRiw6ZOM0aiTMaXpKM9U0Qdz1zhmx8fTqbXGIK6b5vafbwXDZo+e+Js9 v6DQ== X-Forwarded-Encrypted: i=1; AJvYcCW8lefldgUNG/axCjDPwjLQ1IPaaITQZc96c064ZHikKKzZBpr7VmhQtJE7ZereZWPYvFSIEEfxKC/K8xM=@vger.kernel.org X-Gm-Message-State: AOJu0YyzWXvWnSSxPk4gHdc5QysvEIfNulL2JBpab91loYIj5TERM9Rw CwniQR4wmmYyimj9BatDuEvfsqh0X5tJf9aBgU7CwoZx/6NIyi/kQXLg2CivnAVm3YXYE5NOCL8 9O9ALnJ6GMplZu6ZAkrl0aQP9OEXXDVywcHLFp6doMSSgx93RPNwxWZI= X-Gm-Gg: ASbGnctGZnZRK7HJeMlf97OgrDeBPQFhSYDyaAaBk+31U43kJHjhkRCTkxicClb114s BqZwLCSVa6PxFmmUFd8ZJgx6vxTpn3qn8Mnw0VX5tkVm0M/q31DzPQ30pvXUQYhCZ6s1+UeIOrE FgEumi5971PkFh2NYDMVEpr+f8lXJaU1h36apdPj4pBkTS9l15awyumWaZ8D+1x57dKNEkIBXIt RtwkKUaptijP7KP9H78W6zE+zy/i+gcML2dynq3KnREa7KLDJcpnd5RSpHdvoy9kewtjHTFEJ0Q t9wEW8VrQksZj9S7fRShryZAqeHy55aUFiwlJA== X-Google-Smtp-Source: AGHT+IEsKIOzgcEHTHD4ixmstMrBe2jywdP4jH+InbBGGKb852aGFnH9pIv1g8STLsl08D0n73bjjg== X-Received: by 2002:a5d:584f:0:b0:390:e853:85bd with SMTP id ffacd0b85a97d-39eaaecaa9dmr13803707f8f.48.1744715860840; Tue, 15 Apr 2025 04:17:40 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:40 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:27 +0200 Subject: [PATCH net-next v26 10/23] ovpn: store tunnel and transport statistics Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-10-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=7104; i=antonio@openvpn.net; h=from:subject:message-id; bh=urcFahA47J1pElkhnEZnA3wXpRcRwV6IPsSmimxSZcQ=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBBh0COy9QEw8A3e6QLItiOMImqupeOuPfdM eSnuOHNddqJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQQAKCRALcOU6oDjV h16SB/wJUA7VdYNWzCbSdlXlx/vR9DHz8hbH2k0+J5Zgp0HYzjyRbGI0/wJ+B6A382CmeF0Q2/V BTvBQOKfXPbfpANZrpp04snhjvV17Kbb34yKXfuyGqP0gSUYWjF+7fUAhXppEoDE8Hlb+QUxafx SJwUoI+3J+uXvUfnWl8eprR0flkY7ehY4x1/1JqOWV/t0KfZYryY6Ku/1iBM3Ak9hejIP5fc5lm huldhRcrg+Jhmqq445mDwzBQbbFbAu1LOckb2zuDStSe+x6/MIuAjDuj+giPBtD7QIhk8/aCuC8 p5Ksfg2mc4shLkyPeTDy78Zi1UPUEIctfgNSh1WMgiiPe0bL X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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 Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/Makefile | 1 + drivers/net/ovpn/io.c | 12 +++++++++++- drivers/net/ovpn/peer.c | 2 ++ drivers/net/ovpn/peer.h | 5 +++++ drivers/net/ovpn/stats.c | 21 +++++++++++++++++++++ drivers/net/ovpn/stats.h | 47 +++++++++++++++++++++++++++++++++++++++++++= ++++ 6 files changed, 87 insertions(+), 1 deletion(-) diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 38c9fdca0e2e8e4af3c369ceb3971b58ab52d77b..04c3345807c5d759daf65cc80a2= 90f784dbf5588 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -17,4 +17,5 @@ ovpn-y +=3D netlink-gen.o ovpn-y +=3D peer.o ovpn-y +=3D pktid.o ovpn-y +=3D socket.o +ovpn-y +=3D stats.o ovpn-y +=3D udp.o diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 846b6d7d4ec698d0418fa5dd7f70bf84bc62fc99..bf4d471e6d149c4c031d989833f= 654404e4f0458 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -12,6 +12,7 @@ #include #include #include +#include =20 #include "ovpnpriv.h" #include "peer.h" @@ -55,9 +56,11 @@ static void ovpn_netdev_write(struct ovpn_peer *peer, st= ruct sk_buff *skb) /* cause packet to be "received" by the interface */ pkt_len =3D skb->len; ret =3D gro_cells_receive(&peer->ovpn->gro_cells, skb); - if (likely(ret =3D=3D NET_RX_SUCCESS)) + if (likely(ret =3D=3D NET_RX_SUCCESS)) { /* update RX stats with the size of decrypted packet */ + ovpn_peer_stats_increment_rx(&peer->vpn_stats, pkt_len); dev_dstats_rx_add(peer->ovpn->dev, pkt_len); + } } =20 void ovpn_decrypt_post(void *data, int ret) @@ -152,6 +155,8 @@ void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *= skb) struct ovpn_crypto_key_slot *ks; u8 key_id; =20 + ovpn_peer_stats_increment_rx(&peer->link_stats, skb->len); + /* get the key slot matching the key ID in the received packet */ key_id =3D ovpn_key_id_from_skb(skb); ks =3D ovpn_crypto_key_id_to_slot(&peer->crypto, key_id); @@ -175,6 +180,7 @@ void ovpn_encrypt_post(void *data, int ret) struct sk_buff *skb =3D data; struct ovpn_socket *sock; struct ovpn_peer *peer; + unsigned int orig_len; =20 /* encryption is happening asynchronously. This function will be * called later by the crypto callback with a proper return value @@ -194,6 +200,7 @@ void ovpn_encrypt_post(void *data, int ret) goto err; =20 skb_mark_not_on_list(skb); + orig_len =3D skb->len; =20 rcu_read_lock(); sock =3D rcu_dereference(peer->sock); @@ -208,6 +215,8 @@ void ovpn_encrypt_post(void *data, int ret) /* no transport configured yet */ goto err_unlock; } + + ovpn_peer_stats_increment_tx(&peer->link_stats, orig_len); /* skb passed down the stack - don't free it */ skb =3D NULL; err_unlock: @@ -322,6 +331,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct n= et_device *dev) goto drop; } =20 + ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb->len); ovpn_send(ovpn, skb_list.next, peer); =20 return NETDEV_TX_OK; diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 23eaab1b465b8b88a84cf9f1039621923b640b47..0fe5333c6b8104913526dacc4d7= d2260b97f62aa 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -61,6 +61,8 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u= 32 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); =20 ret =3D 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 a9113a969f94d66fa17208d563d0bbd255c23fa9..2453d39ce327c6d174cfb35fe54= 30865b32c2efe 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -14,6 +14,7 @@ =20 #include "crypto.h" #include "socket.h" +#include "stats.h" =20 /** * struct ovpn_peer - the main remote peer object @@ -27,6 +28,8 @@ * @crypto: the crypto configuration (ciphers, keys, etc..) * @dst_cache: cache for dst_entry used to send to peer * @bind: remote peer binding + * @vpn_stats: per-peer in-VPN TX/RX stats + * @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 @@ -45,6 +48,8 @@ struct ovpn_peer { struct ovpn_crypto_state crypto; struct dst_cache dst_cache; struct ovpn_bind __rcu *bind; + 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/stats.c b/drivers/net/ovpn/stats.c new file mode 100644 index 0000000000000000000000000000000000000000..d637143473bb913647c79832fd9= eb3ebfd9efb59 --- /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-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include + +#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 0000000000000000000000000000000000000000..53433d8b6c33160845de2ae1ca3= 8e85cf31950b7 --- /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-2025 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + * Lev Stipakov + */ + +#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 *st= ats, + const unsigned int n) +{ + ovpn_peer_stats_increment(&stats->rx, n); +} + +static inline void ovpn_peer_stats_increment_tx(struct ovpn_peer_stats *st= ats, + const unsigned int n) +{ + ovpn_peer_stats_increment(&stats->tx, n); +} + +#endif /* _NET_OVPN_OVPNSTATS_H_ */ --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f54.google.com (mail-wm1-f54.google.com [209.85.128.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 49C472973D2 for ; Tue, 15 Apr 2025 11:17:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715868; cv=none; b=Pu1GC1xR6Wl1RUS2hB58MwLYmGj37rF8acBlD5I2LQ0v8Udufiuell5zgANKGudTKPXfHoUrvAufXhwUULMOnaUNcPeBnQo7HdfZ6fSLvKU0/TkdQ+IgPPKLzbudIhnUFkF4twbgvUcM2RT2SaJA+vbk4tyfJ10Q6CeC4YmAhG0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715868; c=relaxed/simple; bh=XbP75wctmK97NPrTEJd2MUWHWGcAPXimwn145zTqe1w=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Kpwgy1bDoCw16bupib6qCREA22zJs0D8vSwiOEX9Ieyh8Tnl8YZRPFvmmwVppz+hzHncjcgCrqOrG+tGpfRnY4WUug134luK955gu+JeldZ7QWlIwFif59qKeaXwuSjeMNT//dcppRApNTWD9vZslttxIlW7rkixc79UA+0ouS0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=d0jMTQ8I; arc=none smtp.client-ip=209.85.128.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="d0jMTQ8I" Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-43cf848528aso45052665e9.2 for ; Tue, 15 Apr 2025 04:17:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715862; x=1745320662; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=Gk+TZwQe/ecmyh3sRSh/udwMxeQHlubMSjupJQd01y4=; b=d0jMTQ8IVX4A31Qx51+mV5PBaMiL2lHx5jt5HnpfbSmVEE83UefqvOa2BLnCzyDSXr vcTqkNnmLpO+1WHKVHkDX7fzoxgxp0oKb7rkMBhNGO2zVHtyYNWg662uotf5snxKp2KC WyLep2YBetdgPozs2mJN23AsXW7B25eMQp1kfY+jrFHoVdn24+0TCX6lNQFN32te1dz5 zc88JaT03oX3y1OdMp47GTB/1g6VRMEga6vTowjUJn+s///hbQdHtnAEBVFCbdOvrkkJ F8Ut3eXjvCnoveeqCwkNIyqeVAqwOEcjoFkZd6u2ys3sdF+AbepxQcIUdqhrZU2dZKHv TWQA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715862; x=1745320662; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Gk+TZwQe/ecmyh3sRSh/udwMxeQHlubMSjupJQd01y4=; b=wqEp/ML8XYBp5ef/vnqNzHrEiXMQStK6usdCTe/8Dqtci+ivOSVv6qiJKqD0UjSrE+ B4v2jDiQeEKYXK4mveN3EL+Ey0uFcDLBe1oAx9jbL8Ot2DxsSPuy1xIlfPEOYL+9FTZ0 uKuC6ibaFBJ9B+Wknx58168lhlIs2YaXnCnT7ieVBcYNJJfoCk8JlNoJCYq4e5irSWHM JaHyCuraImWfBH4WQSQFgrnky6RhEiFH5ZXCTHZ3L/o+3cJSMaDHnfkK0MCEqYItThB8 qo3jItBKYN3kC2Mppl7WbJtD0VaiMU5oUMMUBZe2zLpkkVyN53e24CtC9GZlmqQnJj0Q wO4w== X-Forwarded-Encrypted: i=1; AJvYcCX+MtnqQyEYB51sbpBS7mfpukf32qd7Zi2FvT1wa5yFq4i/YZicnmBxBAe95rALLti1+ZUmQc1ZvlGmyLM=@vger.kernel.org X-Gm-Message-State: AOJu0Yz2gKzIt4x7ezfguRQtwyQYLR4tpRTOID0UX9BZ/O+IPq5eIAcE UKxyUuopmLTR15a1p54X85o5q0yXh2pXqyYJtnJJ2lck9UOKStDIfhggxt9NO8VenyboguOM6kp kGVhXIN4ciO7nttkVoJc6RofVWkISuEMBGVXgy5/yD537kZ2dQZwx3A8= X-Gm-Gg: ASbGncuNpqNutJ7HFyN2NAgz4x2AoinC/ZE2edMCe9p93tv/yCfszpoVBR+y3jp9Oqr SImlcK0yzLlbAbKXNli992GslY+5lp8abDdp39CXOu5Ofsa6oM6sJMFwoRWyz5HURCAdO+Xy772 H2BdDDoHFNUjkFM6goCD1urIl3tBjStOHA1SBufcduDfG/scD4hiBt+YyJutwDfHoWA967cclMG LnPlELUv4nXPgTcU/1qN7wnb4BpLZrc5CmVZMl/8MQeSmcdGdDBHOqB+HRXcsDOssVy7Y7Nr7ed NXKMnmEjHJUz5BUcwepOOfTYxi9B3Ba+rID/3bE9tT0TKykD X-Google-Smtp-Source: AGHT+IGCKpB+Jl3F8ccEz+PybJGDJ05c2a+Ryydnqc6aKBfNqmIElmSR+89NdgkNJRB+BXCV7a7CqA== X-Received: by 2002:a05:600c:3b19:b0:439:9e13:2dd7 with SMTP id 5b1f17b1804b1-43f3a92568amr163933925e9.2.1744715862163; Tue, 15 Apr 2025 04:17:42 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:41 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:28 +0200 Subject: [PATCH net-next v26 11/23] ovpn: implement TCP transport Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-11-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang , David Ahern X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=28738; i=antonio@openvpn.net; h=from:subject:message-id; bh=XbP75wctmK97NPrTEJd2MUWHWGcAPXimwn145zTqe1w=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBCCMVPgHRrHP6XO7aPi47yr5ekmB4D5ca/R gV7yJoASt2JATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQgAKCRALcOU6oDjV h7IoB/4+0twpyq3BAGtzHzGyVHusB7vUofawEuPIbx1QPZC8PbaWEzY6JqA0kvpS7+D9ZpFXW66 fMYnrM2E8RkQgCXic5jm0Yt8F2nPazUlYNQ0p3xgvK6Fq5Ynl6QHkCZMoYP0qPuJ5ArbQ0C8cKd ThPnIEbsRXdnZ2uglzuSAPXOYxb2zuICoz97fJ7oouTil3caSaD5TJvtZfGxVmgL7RqRVx1Emqg qyMfweOOkf+EKQw3z7/RKSZxsScXQVmhb65TivMbJsBaBuj5JrzYie24vEE6rdaH9zuRUu+B8/7 PHrZxphbg3tb13rLypS5lTD1FH3K2GocUO//DP8KB0MGD3Fz X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C With this change ovpn is allowed to communicate to peers also via TCP. Parsing of incoming messages is implemented through the strparser API. Note that ovpn redefines sk_prot and sk_socket->ops for the TCP socket used to communicate with the peer. For this reason it needs to access inet6_stream_ops, which is declared as extern in the IPv6 module, but it is not fully exported. Therefore this patch is also adding EXPORT_SYMBOL_GPL(inet6_stream_ops) to net/ipv6/af_inet6.c. Cc: David Ahern Cc: Eric Dumazet Cc: Jakub Kicinski Cc: Paolo Abeni Cc: Simon Horman Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/Kconfig | 1 + drivers/net/ovpn/Makefile | 1 + drivers/net/ovpn/io.c | 4 + drivers/net/ovpn/main.c | 3 + drivers/net/ovpn/ovpnpriv.h | 1 + drivers/net/ovpn/peer.h | 35 +++ drivers/net/ovpn/socket.c | 44 +++- drivers/net/ovpn/socket.h | 8 +- drivers/net/ovpn/tcp.c | 594 ++++++++++++++++++++++++++++++++++++++++= ++++ drivers/net/ovpn/tcp.h | 36 +++ net/ipv6/af_inet6.c | 1 + 11 files changed, 717 insertions(+), 11 deletions(-) diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index a5cee847911b17a3584ab3d9c1cf7d166d4e1298..b29628d46be9b3c3b0158c239a7= d7ddc6e0b9f1e 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -125,6 +125,7 @@ config OVPN select CRYPTO_AES select CRYPTO_GCM select CRYPTO_CHACHA20POLY1305 + select STREAM_PARSER help This module enhances the performance of the OpenVPN userspace software by offloading the data channel processing to kernelspace. diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 04c3345807c5d759daf65cc80a290f784dbf5588..229be66167e1fe8e5e4c8f47534= 3b544d7ee694e 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -18,4 +18,5 @@ ovpn-y +=3D peer.o ovpn-y +=3D pktid.o ovpn-y +=3D socket.o ovpn-y +=3D stats.o +ovpn-y +=3D tcp.o ovpn-y +=3D udp.o diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index bf4d471e6d149c4c031d989833f654404e4f0458..5479b40f84a4d7207a7fdd4f6a8= ea72e6b6a73a0 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -22,6 +22,7 @@ #include "crypto_aead.h" #include "netlink.h" #include "proto.h" +#include "tcp.h" #include "udp.h" #include "skb.h" #include "socket.h" @@ -211,6 +212,9 @@ void ovpn_encrypt_post(void *data, int ret) case IPPROTO_UDP: ovpn_udp_send_skb(peer, sock->sock, skb); break; + case IPPROTO_TCP: + ovpn_tcp_send_skb(peer, sock->sock, skb); + break; default: /* no transport configured yet */ goto err_unlock; diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index b356c2235d46fdde500cab4f0b21331cc2fb60c8..89075502c5af9168a2096e8426b= 1b5e8558665f5 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -22,6 +22,7 @@ #include "io.h" #include "peer.h" #include "proto.h" +#include "tcp.h" #include "udp.h" =20 static int ovpn_net_init(struct net_device *dev) @@ -177,6 +178,8 @@ static int __init ovpn_init(void) goto unreg_rtnl; } =20 + ovpn_tcp_init(); + return 0; =20 unreg_rtnl: diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index d31cceee508d7066bf79b71d77d05d039dc97f29..bb345954b8d9e78fcb4190bb0f6= e3b262ca60410 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -10,6 +10,7 @@ #ifndef _NET_OVPN_OVPNSTRUCT_H_ #define _NET_OVPN_OVPNSTRUCT_H_ =20 +#include #include #include #include diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 2453d39ce327c6d174cfb35fe5430865b32c2efe..5ef00ba6523d7bf6608adb86fa7= c7029f559ce2a 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -11,6 +11,7 @@ #define _NET_OVPN_OVPNPEER_H_ =20 #include +#include =20 #include "crypto.h" #include "socket.h" @@ -25,6 +26,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.user_queue: received packets that have to go to userspace (TCP onl= y) + * @tcp.out_queue: packets on hold while socket is taken by user (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 @@ -45,6 +58,28 @@ struct ovpn_peer { struct in6_addr ipv6; } vpn_addrs; struct ovpn_socket __rcu *sock; + + struct { + struct strparser strp; + struct sk_buff_head user_queue; + struct sk_buff_head out_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; + + struct work_struct defer_del_work; + } 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 f1fd98d528451e93a10b872e4d9b37c3940d339b..3a18fbdd3721628c0b4b0d23c9a= ef77332addfe6 100644 --- a/drivers/net/ovpn/socket.c +++ b/drivers/net/ovpn/socket.c @@ -16,6 +16,7 @@ #include "io.h" #include "peer.h" #include "socket.h" +#include "tcp.h" #include "udp.h" =20 static void ovpn_socket_release_kref(struct kref *kref) @@ -23,12 +24,10 @@ static void ovpn_socket_release_kref(struct kref *kref) struct ovpn_socket *sock =3D container_of(kref, struct ovpn_socket, refcount); =20 - if (sock->sock->sk->sk_protocol =3D=3D IPPROTO_UDP) { + if (sock->sock->sk->sk_protocol =3D=3D IPPROTO_UDP) ovpn_udp_socket_detach(sock); - netdev_put(sock->ovpn->dev, &sock->dev_tracker); - } - - kfree_rcu(sock, rcu); + else if (sock->sock->sk->sk_protocol =3D=3D IPPROTO_TCP) + ovpn_tcp_socket_detach(sock); } =20 /** @@ -38,10 +37,12 @@ static void ovpn_socket_release_kref(struct kref *kref) * * This function is only used internally. Users willing to release * references to the ovpn_socket should use ovpn_socket_release() + * + * Return: true if the socket was released, false otherwise */ -static void ovpn_socket_put(struct ovpn_peer *peer, struct ovpn_socket *so= ck) +static bool ovpn_socket_put(struct ovpn_peer *peer, struct ovpn_socket *so= ck) { - kref_put(&sock->refcount, ovpn_socket_release_kref); + return kref_put(&sock->refcount, ovpn_socket_release_kref); } =20 /** @@ -65,6 +66,7 @@ static void ovpn_socket_put(struct ovpn_peer *peer, struc= t ovpn_socket *sock) void ovpn_socket_release(struct ovpn_peer *peer) { struct ovpn_socket *sock; + bool released; =20 might_sleep(); =20 @@ -89,11 +91,26 @@ void ovpn_socket_release(struct ovpn_peer *peer) * detached before it can be picked by a concurrent reader. */ lock_sock(sock->sock->sk); - ovpn_socket_put(peer, sock); + released =3D ovpn_socket_put(peer, sock); release_sock(sock->sock->sk); =20 /* align all readers with sk_user_data being NULL */ synchronize_rcu(); + + /* following cleanup should happen with lock released */ + if (released) { + if (sock->sock->sk->sk_protocol =3D=3D IPPROTO_UDP) { + netdev_put(sock->ovpn->dev, &sock->dev_tracker); + } else if (sock->sock->sk->sk_protocol =3D=3D IPPROTO_TCP) { + /* wait for TCP jobs to terminate */ + ovpn_tcp_socket_wait_finish(sock); + ovpn_peer_put(sock->peer); + } + /* we can call plain kfree() because we already waited one RCU + * period due to synchronize_rcu() + */ + kfree(sock); + } } =20 static bool ovpn_socket_hold(struct ovpn_socket *sock) @@ -105,6 +122,8 @@ static int ovpn_socket_attach(struct ovpn_socket *sock,= struct ovpn_peer *peer) { if (sock->sock->sk->sk_protocol =3D=3D IPPROTO_UDP) return ovpn_udp_socket_attach(sock, peer->ovpn); + else if (sock->sock->sk->sk_protocol =3D=3D IPPROTO_TCP) + return ovpn_tcp_socket_attach(sock, peer); =20 return -EOPNOTSUPP; } @@ -191,7 +210,14 @@ struct ovpn_socket *ovpn_socket_new(struct socket *soc= k, struct ovpn_peer *peer) goto sock_release; } =20 - if (sock->sk->sk_protocol =3D=3D IPPROTO_UDP) { + /* TCP sockets are per-peer, therefore they are linked to their unique + * peer + */ + if (sock->sk->sk_protocol =3D=3D IPPROTO_TCP) { + INIT_WORK(&ovpn_sock->tcp_tx_work, ovpn_tcp_tx_work); + ovpn_sock->peer =3D peer; + ovpn_peer_hold(peer); + } else if (sock->sk->sk_protocol =3D=3D IPPROTO_UDP) { /* in UDP we only link the ovpn instance since the socket is * shared among multiple peers */ diff --git a/drivers/net/ovpn/socket.h b/drivers/net/ovpn/socket.h index c1697f4616d470f143cba77bb1db34de0398a37a..00d856b1a5d884ffeca7cde7f69= 3cad599746c10 100644 --- a/drivers/net/ovpn/socket.h +++ b/drivers/net/ovpn/socket.h @@ -21,9 +21,11 @@ struct ovpn_peer; * struct ovpn_socket - a kernel socket referenced in the ovpn code * @ovpn: ovpn instance owning this socket (UDP only) * @dev_tracker: reference tracker for associated dev (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 + * @work: member used to schedule release routine (it may block) + * @tcp_tx_work: work for deferring outgoing packet processing (TCP only) */ struct ovpn_socket { union { @@ -31,11 +33,13 @@ struct ovpn_socket { struct ovpn_priv *ovpn; netdevice_tracker dev_tracker; }; + struct ovpn_peer *peer; }; =20 struct socket *sock; struct kref refcount; - struct rcu_head rcu; + struct work_struct work; + struct work_struct tcp_tx_work; }; =20 struct ovpn_socket *ovpn_socket_new(struct socket *sock, diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c new file mode 100644 index 0000000000000000000000000000000000000000..588ff6b0440103f6620837a75ea= 2f1029d91b8a3 --- /dev/null +++ b/drivers/net/ovpn/tcp.c @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ovpnpriv.h" +#include "main.h" +#include "io.h" +#include "peer.h" +#include "proto.h" +#include "skb.h" +#include "tcp.h" + +#define OVPN_TCP_DEPTH_NESTING 2 +#if OVPN_TCP_DEPTH_NESTING =3D=3D SINGLE_DEPTH_NESTING +#error "OVPN TCP requires its own lockdep subclass" +#endif + +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 __ro_after_init; +static struct proto_ops ovpn_tcp6_ops __ro_after_init; + +static int ovpn_tcp_parse(struct strparser *strp, struct sk_buff *skb) +{ + struct strp_msg *rxm =3D 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. + * Parse accordingly and return the actual size (including the size + * header) + */ + + if (skb->len < rxm->offset + 2) + return 0; + + err =3D skb_copy_bits(skb, rxm->offset, &blen, sizeof(blen)); + if (err < 0) + return err; + + len =3D be16_to_cpu(blen); + if (len < 2) + return -EINVAL; + + return len + 2; +} + +/* queue skb for sending to userspace via recvmsg on the socket */ +static void ovpn_tcp_to_userspace(struct ovpn_peer *peer, struct sock *sk, + struct sk_buff *skb) +{ + skb_set_owner_r(skb, sk); + memset(skb->cb, 0, sizeof(skb->cb)); + skb_queue_tail(&peer->tcp.user_queue, skb); + peer->tcp.sk_cb.sk_data_ready(sk); +} + +static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb) +{ + struct ovpn_peer *peer =3D container_of(strp, struct ovpn_peer, tcp.strp); + struct strp_msg *msg =3D strp_msg(skb); + size_t pkt_len =3D msg->full_len - 2; + size_t off =3D msg->offset + 2; + u8 opcode; + + /* ensure skb->data points to the beginning of the openvpn packet */ + if (!pskb_pull(skb, off)) { + net_warn_ratelimited("%s: packet too small for peer %u\n", + netdev_name(peer->ovpn->dev), peer->id); + goto err; + } + + /* strparser does not trim the skb for us, therefore we do it now */ + if (pskb_trim(skb, pkt_len) !=3D 0) { + net_warn_ratelimited("%s: trimming skb failed for peer %u\n", + netdev_name(peer->ovpn->dev), peer->id); + goto err; + } + + /* we need the first 4 bytes of data to be accessible + * to extract the opcode and the key ID later on + */ + if (!pskb_may_pull(skb, OVPN_OPCODE_SIZE)) { + net_warn_ratelimited("%s: packet too small to fetch opcode for peer %u\n= ", + netdev_name(peer->ovpn->dev), peer->id); + goto err; + } + + /* DATA_V2 packets are handled in kernel, the rest goes to user space */ + opcode =3D ovpn_opcode_from_skb(skb, 0); + if (unlikely(opcode !=3D OVPN_DATA_V2)) { + if (opcode =3D=3D OVPN_DATA_V1) { + net_warn_ratelimited("%s: DATA_V1 detected on the TCP stream\n", + netdev_name(peer->ovpn->dev)); + goto err; + } + + /* The packet size header must be there when sending the packet + * to userspace, therefore we put it back + */ + skb_push(skb, 2); + ovpn_tcp_to_userspace(peer, strp->sk, skb); + return; + } + + /* 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 + */ + if (WARN_ON(!ovpn_peer_hold(peer))) + goto err; + + ovpn_recv(peer, skb); + return; +err: + dev_dstats_rx_dropped(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 le= n, + int flags, int *addr_len) +{ + int err =3D 0, off, copied =3D 0, ret; + struct ovpn_socket *sock; + struct ovpn_peer *peer; + struct sk_buff *skb; + + rcu_read_lock(); + sock =3D rcu_dereference_sk_user_data(sk); + if (unlikely(!sock || !sock->peer || !ovpn_peer_hold(sock->peer))) { + rcu_read_unlock(); + return -EBADF; + } + peer =3D sock->peer; + rcu_read_unlock(); + + skb =3D __skb_recv_datagram(sk, &peer->tcp.user_queue, flags, &off, &err); + if (!skb) { + if (err =3D=3D -EAGAIN && sk->sk_shutdown & RCV_SHUTDOWN) { + ret =3D 0; + goto out; + } + ret =3D err; + goto out; + } + + copied =3D len; + if (copied > skb->len) + copied =3D skb->len; + else if (copied < skb->len) + msg->msg_flags |=3D MSG_TRUNC; + + err =3D skb_copy_datagram_msg(skb, 0, msg, copied); + if (unlikely(err)) { + kfree_skb(skb); + ret =3D err; + goto out; + } + + if (flags & MSG_TRUNC) + copied =3D skb->len; + kfree_skb(skb); + ret =3D copied; +out: + ovpn_peer_put(peer); + return ret; +} + +void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock) +{ + struct ovpn_peer *peer =3D ovpn_sock->peer; + struct socket *sock =3D ovpn_sock->sock; + + 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 =3D peer->tcp.sk_cb.sk_data_ready; + sock->sk->sk_write_space =3D peer->tcp.sk_cb.sk_write_space; + sock->sk->sk_prot =3D peer->tcp.sk_cb.prot; + sock->sk->sk_socket->ops =3D peer->tcp.sk_cb.ops; + + rcu_assign_sk_user_data(sock->sk, NULL); +} + +void ovpn_tcp_socket_wait_finish(struct ovpn_socket *sock) +{ + struct ovpn_peer *peer =3D sock->peer; + + /* NOTE: we don't wait for peer->tcp.defer_del_work to finish: + * either the worker is not running or this function + * was invoked by that worker. + */ + + cancel_work_sync(&sock->tcp_tx_work); + strp_done(&peer->tcp.strp); + + skb_queue_purge(&peer->tcp.out_queue); + kfree_skb(peer->tcp.out_msg.skb); + peer->tcp.out_msg.skb =3D NULL; +} + +static void ovpn_tcp_send_sock(struct ovpn_peer *peer, struct sock *sk) +{ + struct sk_buff *skb =3D peer->tcp.out_msg.skb; + + if (!skb) + return; + + if (peer->tcp.tx_in_progress) + return; + + peer->tcp.tx_in_progress =3D true; + + do { + int ret =3D skb_send_sock_locked(sk, skb, + peer->tcp.out_msg.offset, + peer->tcp.out_msg.len); + if (unlikely(ret < 0)) { + if (ret =3D=3D -EAGAIN) + goto out; + + net_warn_ratelimited("%s: TCP error to peer %u: %d\n", + netdev_name(peer->ovpn->dev), + peer->id, ret); + + /* in case of TCP error we can't recover the VPN + * stream therefore we abort the connection + */ + ovpn_peer_hold(peer); + schedule_work(&peer->tcp.defer_del_work); + + /* we bail out immediately and keep tx_in_progress set + * to true. This way we prevent more TX attempts + * which would lead to more invocations of + * schedule_work() + */ + return; + } + + peer->tcp.out_msg.len -=3D ret; + peer->tcp.out_msg.offset +=3D ret; + } while (peer->tcp.out_msg.len > 0); + + if (!peer->tcp.out_msg.len) { + preempt_disable(); + dev_dstats_tx_add(peer->ovpn->dev, skb->len); + preempt_enable(); + } + + kfree_skb(peer->tcp.out_msg.skb); + peer->tcp.out_msg.skb =3D NULL; + peer->tcp.out_msg.len =3D 0; + peer->tcp.out_msg.offset =3D 0; + +out: + peer->tcp.tx_in_progress =3D false; +} + +void ovpn_tcp_tx_work(struct work_struct *work) +{ + struct ovpn_socket *sock; + + sock =3D container_of(work, struct ovpn_socket, tcp_tx_work); + + lock_sock(sock->sock->sk); + if (sock->peer) + ovpn_tcp_send_sock(sock->peer, sock->sock->sk); + release_sock(sock->sock->sk); +} + +static void ovpn_tcp_send_sock_skb(struct ovpn_peer *peer, struct sock *sk, + struct sk_buff *skb) +{ + if (peer->tcp.out_msg.skb) + ovpn_tcp_send_sock(peer, sk); + + if (peer->tcp.out_msg.skb) { + dev_dstats_tx_dropped(peer->ovpn->dev); + kfree_skb(skb); + return; + } + + peer->tcp.out_msg.skb =3D skb; + peer->tcp.out_msg.len =3D skb->len; + peer->tcp.out_msg.offset =3D 0; + ovpn_tcp_send_sock(peer, sk); +} + +void ovpn_tcp_send_skb(struct ovpn_peer *peer, struct socket *sock, + struct sk_buff *skb) +{ + u16 len =3D skb->len; + + *(__be16 *)__skb_push(skb, sizeof(u16)) =3D htons(len); + + spin_lock_nested(&sock->sk->sk_lock.slock, OVPN_TCP_DEPTH_NESTING); + if (sock_owned_by_user(sock->sk)) { + if (skb_queue_len(&peer->tcp.out_queue) >=3D + READ_ONCE(net_hotdata.max_backlog)) { + dev_dstats_tx_dropped(peer->ovpn->dev); + kfree_skb(skb); + goto unlock; + } + __skb_queue_tail(&peer->tcp.out_queue, skb); + } else { + ovpn_tcp_send_sock_skb(peer, sock->sk, skb); + } +unlock: + spin_unlock(&sock->sk->sk_lock.slock); +} + +static void ovpn_tcp_release(struct sock *sk) +{ + struct sk_buff_head queue; + struct ovpn_socket *sock; + struct ovpn_peer *peer; + struct sk_buff *skb; + + rcu_read_lock(); + sock =3D rcu_dereference_sk_user_data(sk); + if (!sock) { + rcu_read_unlock(); + return; + } + + peer =3D sock->peer; + + /* during initialization this function is called before + * assigning sock->peer + */ + if (unlikely(!peer || !ovpn_peer_hold(peer))) { + rcu_read_unlock(); + return; + } + rcu_read_unlock(); + + __skb_queue_head_init(&queue); + skb_queue_splice_init(&peer->tcp.out_queue, &queue); + + while ((skb =3D __skb_dequeue(&queue))) + ovpn_tcp_send_sock_skb(peer, sk, skb); + + peer->tcp.sk_cb.prot->release_cb(sk); + ovpn_peer_put(peer); +} + +static int ovpn_tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t si= ze) +{ + struct ovpn_socket *sock; + int ret, linear =3D PAGE_SIZE; + struct ovpn_peer *peer; + struct sk_buff *skb; + + lock_sock(sk); + rcu_read_lock(); + sock =3D rcu_dereference_sk_user_data(sk); + if (unlikely(!sock || !sock->peer || !ovpn_peer_hold(sock->peer))) { + rcu_read_unlock(); + release_sock(sk); + return -EIO; + } + rcu_read_unlock(); + peer =3D sock->peer; + + if (msg->msg_flags & ~MSG_DONTWAIT) { + ret =3D -EOPNOTSUPP; + goto peer_free; + } + + if (peer->tcp.out_msg.skb) { + ret =3D -EAGAIN; + goto peer_free; + } + + if (size < linear) + linear =3D size; + + skb =3D 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", + netdev_name(peer->ovpn->dev), ret); + goto peer_free; + } + + skb_put(skb, linear); + skb->len =3D size; + skb->data_len =3D size - linear; + + ret =3D 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", + netdev_name(peer->ovpn->dev), ret); + goto peer_free; + } + + ovpn_tcp_send_sock_skb(peer, sk, skb); + ret =3D size; +peer_free: + release_sock(sk); + ovpn_peer_put(peer); + return ret; +} + +static int ovpn_tcp_disconnect(struct sock *sk, int flags) +{ + return -EBUSY; +} + +static void ovpn_tcp_data_ready(struct sock *sk) +{ + struct ovpn_socket *sock; + + trace_sk_data_ready(sk); + + rcu_read_lock(); + sock =3D rcu_dereference_sk_user_data(sk); + if (likely(sock && sock->peer)) + 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 =3D rcu_dereference_sk_user_data(sk); + if (likely(sock && sock->peer)) { + schedule_work(&sock->tcp_tx_work); + sock->peer->tcp.sk_cb.sk_write_space(sk); + } + rcu_read_unlock(); +} + +static void ovpn_tcp_build_protos(struct proto *new_prot, + struct proto_ops *new_ops, + const struct proto *orig_prot, + const struct proto_ops *orig_ops); + +static void ovpn_tcp_peer_del_work(struct work_struct *work) +{ + struct ovpn_peer *peer =3D container_of(work, struct ovpn_peer, + tcp.defer_del_work); + + ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_ERROR); + ovpn_peer_put(peer); +} + +/* Set TCP encapsulation callbacks */ +int ovpn_tcp_socket_attach(struct ovpn_socket *ovpn_sock, + struct ovpn_peer *peer) +{ + struct socket *sock =3D ovpn_sock->sock; + struct strp_callbacks cb =3D { + .rcv_msg =3D ovpn_tcp_rcv, + .parse_msg =3D ovpn_tcp_parse, + }; + int ret; + + /* make sure no pre-existing encapsulation handler exists */ + if (sock->sk->sk_user_data) + return -EBUSY; + + /* only a fully connected socket is expected. Connection should be + * handled in userspace + */ + if (sock->sk->sk_state !=3D TCP_ESTABLISHED) { + net_err_ratelimited("%s: provided TCP socket is not in ESTABLISHED state= : %d\n", + netdev_name(peer->ovpn->dev), + sock->sk->sk_state); + return -EINVAL; + } + + ret =3D strp_init(&peer->tcp.strp, sock->sk, &cb); + if (ret < 0) { + DEBUG_NET_WARN_ON_ONCE(1); + return ret; + } + + INIT_WORK(&peer->tcp.defer_del_work, ovpn_tcp_peer_del_work); + + __sk_dst_reset(sock->sk); + skb_queue_head_init(&peer->tcp.user_queue); + skb_queue_head_init(&peer->tcp.out_queue); + + /* save current CBs so that they can be restored upon socket release */ + peer->tcp.sk_cb.sk_data_ready =3D sock->sk->sk_data_ready; + peer->tcp.sk_cb.sk_write_space =3D sock->sk->sk_write_space; + peer->tcp.sk_cb.prot =3D sock->sk->sk_prot; + peer->tcp.sk_cb.ops =3D sock->sk->sk_socket->ops; + + /* assign our static CBs and prot/ops */ + sock->sk->sk_data_ready =3D ovpn_tcp_data_ready; + sock->sk->sk_write_space =3D ovpn_tcp_write_space; + + if (sock->sk->sk_family =3D=3D AF_INET) { + sock->sk->sk_prot =3D &ovpn_tcp_prot; + sock->sk->sk_socket->ops =3D &ovpn_tcp_ops; + } else { + sock->sk->sk_prot =3D &ovpn_tcp6_prot; + sock->sk->sk_socket->ops =3D &ovpn_tcp6_ops; + } + + /* avoid using task_frag */ + sock->sk->sk_allocation =3D GFP_ATOMIC; + sock->sk->sk_use_task_frag =3D false; + + /* enqueue the RX worker */ + strp_check_rcv(&peer->tcp.strp); + + return 0; +} + +static void ovpn_tcp_close(struct sock *sk, long timeout) +{ + struct ovpn_socket *sock; + struct ovpn_peer *peer; + + rcu_read_lock(); + sock =3D rcu_dereference_sk_user_data(sk); + if (!sock || !sock->peer || !ovpn_peer_hold(sock->peer)) { + rcu_read_unlock(); + return; + } + peer =3D sock->peer; + rcu_read_unlock(); + + ovpn_peer_del(sock->peer, OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT); + peer->tcp.sk_cb.prot->close(sk, timeout); + ovpn_peer_put(peer); +} + +static __poll_t ovpn_tcp_poll(struct file *file, struct socket *sock, + poll_table *wait) +{ + __poll_t mask =3D datagram_poll(file, sock, wait); + struct ovpn_socket *ovpn_sock; + + rcu_read_lock(); + ovpn_sock =3D rcu_dereference_sk_user_data(sock->sk); + if (ovpn_sock && ovpn_sock->peer && + !skb_queue_empty(&ovpn_sock->peer->tcp.user_queue)) + mask |=3D EPOLLIN | EPOLLRDNORM; + rcu_read_unlock(); + + return mask; +} + +static void ovpn_tcp_build_protos(struct proto *new_prot, + struct proto_ops *new_ops, + const struct proto *orig_prot, + const struct proto_ops *orig_ops) +{ + memcpy(new_prot, orig_prot, sizeof(*new_prot)); + memcpy(new_ops, orig_ops, sizeof(*new_ops)); + new_prot->recvmsg =3D ovpn_tcp_recvmsg; + new_prot->sendmsg =3D ovpn_tcp_sendmsg; + new_prot->disconnect =3D ovpn_tcp_disconnect; + new_prot->close =3D ovpn_tcp_close; + new_prot->release_cb =3D ovpn_tcp_release; + new_ops->poll =3D 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); + +#if IS_ENABLED(CONFIG_IPV6) + ovpn_tcp_build_protos(&ovpn_tcp6_prot, &ovpn_tcp6_ops, &tcpv6_prot, + &inet6_stream_ops); +#endif +} diff --git a/drivers/net/ovpn/tcp.h b/drivers/net/ovpn/tcp.h new file mode 100644 index 0000000000000000000000000000000000000000..10aefa834cf358f39f4fc250063= d6ef13e0353b0 --- /dev/null +++ b/drivers/net/ovpn/tcp.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#ifndef _NET_OVPN_TCP_H_ +#define _NET_OVPN_TCP_H_ + +#include +#include +#include + +#include "peer.h" +#include "skb.h" +#include "socket.h" + +void __init ovpn_tcp_init(void); + +int ovpn_tcp_socket_attach(struct ovpn_socket *ovpn_sock, + struct ovpn_peer *peer); +void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock); +void ovpn_tcp_socket_wait_finish(struct ovpn_socket *sock); + +/* 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. + */ +void ovpn_tcp_send_skb(struct ovpn_peer *peer, struct socket *sock, struct= sk_buff *skb); +void ovpn_tcp_tx_work(struct work_struct *work); + +#endif /* _NET_OVPN_TCP_H_ */ diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c index 85bf681d427b8175cd035e2e0b1d744ac4cdccac..acaff129678353d84efad48b5e3= 8693d03e6034e 100644 --- a/net/ipv6/af_inet6.c +++ b/net/ipv6/af_inet6.c @@ -715,6 +715,7 @@ const struct proto_ops inet6_stream_ops =3D { #endif .set_rcvlowat =3D tcp_set_rcvlowat, }; +EXPORT_SYMBOL_GPL(inet6_stream_ops); =20 const struct proto_ops inet6_dgram_ops =3D { .family =3D PF_INET6, --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wr1-f54.google.com (mail-wr1-f54.google.com [209.85.221.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5CA6128F52E for ; Tue, 15 Apr 2025 11:17:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715868; cv=none; b=iV2E9QJprv4iVw8z8m3jvmg5FzPr8KGYL+Wtayxvjo9Gy7FzjFcmp+hqxX6R+RXuJ1IPQtnriQX/FY4toK9Tlh7TcetP1t9E8LQizWXXVauW3Xd32Zj7gNsUDWLrkEqOjChhDDjwsYAblK5bPv+xt1V9SDqA5mt3sylIkDuaZho= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715868; c=relaxed/simple; bh=f2L/6MV3WoY+r58AHmJzHD1uZB1yXOJNNlPf35u+BQk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=FdOZSCu2iICUIaL3kk9opMV5dn3cbvo3Lc2cYFPQ6Igyiq/K/Fo3WRNOWqIPmoOxsX6hag1ju/r1qHwh56D/aSjlmnDSm0652mHId8EFAo15Yjf6Hputxxu67ao35hbYbuV0y/eJeo/zPVCz+8DoQdAflqNbV08/N1JM6OKdS94= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=a3p/Jopz; arc=none smtp.client-ip=209.85.221.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="a3p/Jopz" Received: by mail-wr1-f54.google.com with SMTP id ffacd0b85a97d-39c1efc457bso3598380f8f.2 for ; Tue, 15 Apr 2025 04:17:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715863; x=1745320663; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=9WYY/11xBcIOkdKUznP/08VpnWtjoWEZk7RzwrODJkE=; b=a3p/Jopzb2Ad1EWqHIHFnpxP6QRwW40Jgxp1CxXRbxi2GpgWvSkzo1OzMKk7Mr3zXr WG8CkPSfJGWtnwP/e6rWBWosxd+XXdhToMLfON/oLJasww4iqcgBcLxRPDvJQfmOl8gi adI9xBwXLiDPOPSfZCKFTtcRpiXnJK+9jZw6sF1RSMdfYj9IBk7cJXdYf8M15vLmfhMT EGzGV/jcPUtr7Kcsq/MAm4P8Xs59rD6+cU12RyXCd1LC+BOfC6aR4CS9FqvdAlxaDsH/ E+iSFg6+e0dD5IGnc+lvOE7J+/mKY3u3nW7UfFPIlBpHNBAtrEoyJAemLNCZ0Ti37i5H qPoQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715863; x=1745320663; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=9WYY/11xBcIOkdKUznP/08VpnWtjoWEZk7RzwrODJkE=; b=SFSQy2yNx110Bxu+KwxRXxFfbTCUHgWIijCkDcavHmHEc4iprl9hG+Tq1yddrZbWRN r1o06RhwIULEq8qObuo3oipzciNmh0lcVrH/vXMbewh/ihnq88oUY9Cad6o8D3z8/wzn 0G+/SIYTOPuWEH7uDW+97Z1i8oebyxs7+PEpqfR9uLazdAtDbNaNY/qCkPZYljOubw+r 8gb9Jrr6azMMb5EzLt6bq182S9B0kTon73O1MzENrVcsSNyDQu/1XxV+13VC66SP3htY HJm9xCYs0eai/mvjl2lUrdH/JpGLfNzj/Z4L5PjiOiCJXss26VYnhNpq9efatyCsKzj5 zIJA== X-Forwarded-Encrypted: i=1; AJvYcCXIFfuyw5WNzjJE13P/DQqE9tjObfA8BVJ1ZpzoxZrNKx/Nq0kA1fbpPwrPIcThH7fLG0S1dAHG1sSuuzA=@vger.kernel.org X-Gm-Message-State: AOJu0YwP0zO3e3Bfe8bNCS8Zu4exx9u0rJwakEnFqNGQdl0kmNn5ZDw2 SYcabMXKxs9qUtqBqLLzus8h2z0t5acEb+ZzzzP1NYJ7ZRBYPgeOY81632H72/vHhC0kOleQO0G MHNIf67KEGcS1FTv2eZNuiC13B8ctlZGXRM5wc7AburloRWTSaPs8xk8= X-Gm-Gg: ASbGncvJSRkuaaoITN9XdkSIFjCOkdjKgxwiA3Kdj0ybscdzuhrjSgqXA9Id40htTDn c+KHoJtHnKNiQB0cqhbImBXKs8pLCcf3RgnsVZv1ouE2r8esJrtBPCDTWMglAwK+T2YuZoIVEkl f0Ivy59jEMW5ucNJ10lFkU0Akbh/03AGS6XZ1nmOetvoPsPHFTdmY8zmGyU24ksn5szgMQxjeN/ kdY2gUrAomCt3PAr7vfJIVAQ7Grg4frUb+4y8B1IRfr88mh7qqMcUyZcmbvMrn5UZ97XM7e7v8a miaWa6GsWm/XzuUGR2JiG9j4khb7MV3OeG236nDIYxVVmZ7d X-Google-Smtp-Source: AGHT+IGxnmTpIEZoPgKS6KSxnbaUdq8NgL5AA63J7oNVT7dDt++Ud3v8hl4tc0WEstdoOJvr+/JphA== X-Received: by 2002:a05:6000:2401:b0:391:20ef:62d6 with SMTP id ffacd0b85a97d-39ea51ee240mr13529343f8f.11.1744715863426; Tue, 15 Apr 2025 04:17:43 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:42 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:29 +0200 Subject: [PATCH net-next v26 12/23] skb: implement skb_send_sock_locked_with_flags() Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-12-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=3763; i=antonio@openvpn.net; h=from:subject:message-id; bh=f2L/6MV3WoY+r58AHmJzHD1uZB1yXOJNNlPf35u+BQk=; b=owGbwMvMwMHIXfDUaoHF1XbG02pJDOn/HJyiXoau2Hdf+eVp3mXTziazBBS8/NLn2bQxboJrq 2X0d9faTkZjFgZGDgZZMUWWmavv5Py4IvTkXvyBPzCDWJlApjBwcQrAREQcORjWJM4wtUyWduXv dA3dLR3LVOOqOXM3h1Yzx4H9d+NyY1zPqWfGLdkmIHnNrNTVJO2fjglvpFCIqJ5DQ3i41/PM1+8 Y16dGNP2+eLLuafQe7xuSS9P7DfWmfOU2ilPwKOLWLCzUM+WPP2Ux9diXzUd2/oh3zr/m5X4leL G+2Nr4RV1c//e+NWKwlJt8Q2P1amnNu3uS4iavYXGrfpcWM+t16x053+Vrz2VzGE1NSmlttZu43 fHAZX3Wn1UlewLnsZ9JiC+aeO1YqE6d76LDi6QuVJYFnc0q6s3X6yreXW90bEOg0IzX0nb3bsy+ cvyLoV5asMaNSGZZNesvyQzqLeeuWUf8z0tyzn+e+7wIAA== X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C When sending an skb over a socket using skb_send_sock_locked(), it is currently not possible to specify any flag to be set in msghdr->msg_flags. However, we may want to pass flags the user may have specified, like MSG_NOSIGNAL. Extend __skb_send_sock() with a new argument 'flags' and add a new interface named skb_send_sock_locked_with_flags(). Cc: Eric Dumazet Cc: Jakub Kicinski Cc: Paolo Abeni Cc: Simon Horman Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- include/linux/skbuff.h | 2 ++ net/core/skbuff.c | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index f1381aff0f896220b2b6bc706aaca17b8f28fd8b..beb084ee4f4d2a83165e08d3a59= 18d0bc9bfc069 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -4145,6 +4145,8 @@ int skb_splice_bits(struct sk_buff *skb, struct sock = *sk, unsigned int offset, unsigned int flags); int skb_send_sock_locked(struct sock *sk, struct sk_buff *skb, int offset, int len); +int skb_send_sock_locked_with_flags(struct sock *sk, struct sk_buff *skb, + int offset, int len, int flags); int skb_send_sock(struct sock *sk, struct sk_buff *skb, int offset, int le= n); void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to); unsigned int skb_zerocopy_headlen(const struct sk_buff *from); diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 74a2d886a35b518d55b6d3cafcb8442212f9beee..d73ad79fe739d1c412615ed01f0= 850dbf4c9e09e 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -3227,7 +3227,7 @@ static int sendmsg_unlocked(struct sock *sk, struct m= sghdr *msg) =20 typedef int (*sendmsg_func)(struct sock *sk, struct msghdr *msg); static int __skb_send_sock(struct sock *sk, struct sk_buff *skb, int offse= t, - int len, sendmsg_func sendmsg) + int len, sendmsg_func sendmsg, int flags) { unsigned int orig_len =3D len; struct sk_buff *head =3D skb; @@ -3245,7 +3245,7 @@ static int __skb_send_sock(struct sock *sk, struct sk= _buff *skb, int offset, kv.iov_base =3D skb->data + offset; kv.iov_len =3D slen; memset(&msg, 0, sizeof(msg)); - msg.msg_flags =3D MSG_DONTWAIT; + msg.msg_flags =3D MSG_DONTWAIT | flags; =20 iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &kv, 1, slen); ret =3D INDIRECT_CALL_2(sendmsg, sendmsg_locked, @@ -3282,7 +3282,8 @@ static int __skb_send_sock(struct sock *sk, struct sk= _buff *skb, int offset, while (slen) { struct bio_vec bvec; struct msghdr msg =3D { - .msg_flags =3D MSG_SPLICE_PAGES | MSG_DONTWAIT, + .msg_flags =3D MSG_SPLICE_PAGES | MSG_DONTWAIT | + flags, }; =20 bvec_set_page(&bvec, skb_frag_page(frag), slen, @@ -3328,14 +3329,21 @@ static int __skb_send_sock(struct sock *sk, struct = sk_buff *skb, int offset, int skb_send_sock_locked(struct sock *sk, struct sk_buff *skb, int offset, int len) { - return __skb_send_sock(sk, skb, offset, len, sendmsg_locked); + return __skb_send_sock(sk, skb, offset, len, sendmsg_locked, 0); } EXPORT_SYMBOL_GPL(skb_send_sock_locked); =20 +int skb_send_sock_locked_with_flags(struct sock *sk, struct sk_buff *skb, + int offset, int len, int flags) +{ + return __skb_send_sock(sk, skb, offset, len, sendmsg_locked, flags); +} +EXPORT_SYMBOL_GPL(skb_send_sock_locked_with_flags); + /* Send skb data on a socket. Socket must be unlocked. */ int skb_send_sock(struct sock *sk, struct sk_buff *skb, int offset, int le= n) { - return __skb_send_sock(sk, skb, offset, len, sendmsg_unlocked); + return __skb_send_sock(sk, skb, offset, len, sendmsg_unlocked, 0); } =20 /** --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f50.google.com (mail-wm1-f50.google.com [209.85.128.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7F84B297A63 for ; Tue, 15 Apr 2025 11:17:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715870; cv=none; b=otv7txxHQggrt50eo+1U094/0KtxhzkA8kML0wc+hleuL9/BHs1ph5r4M689xP6QEFxxMuHa9VPRfipceqjh0nEA8YAwMZjbedyczowqcb/NwRy2wLb+DuPqf+4E1xbi5uoGUQzbzVUEHtEQcCbG0rPgDcieFLh0YqoeRB7DckQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715870; c=relaxed/simple; bh=+Unom1yykFNqbE9773wCLW/thNu13gqRTUvNb4z4izU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=CaesKp/zRX4+NCgGUdWVAX4AhML5QEQzImbdXsPR3VW8UlT8uT/cq/11XcjlN/YrzrnNJ+pmCaQ2oPXNTJ64srHXX4ThbErbkFtw1ZTl8Aj0JoPHHScg62UBLdwZKAlhvT9D0wk/pZ5XU+W+uzN681ryu3++SDkViR6/ef4VdcY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=Km3SQY+g; arc=none smtp.client-ip=209.85.128.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="Km3SQY+g" Received: by mail-wm1-f50.google.com with SMTP id 5b1f17b1804b1-43edecbfb94so57076815e9.1 for ; Tue, 15 Apr 2025 04:17:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715864; x=1745320664; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=LTV89+C/IAgzFB5m5xPEkC+pORUgcF9jMRUpcGRiTts=; b=Km3SQY+g68dlL5WDJfC9Wox3eGYLBFS2chRqnlwomuKB2kuzSEES130Oy+FHQQ5bu+ myJtOB+U//Tcy0CjVyTfP5kBUA52GLq7PwPu+DfrHXPeNheDDXb93LSwXUAnR8uL2DPr Dmd0KmtfdB3xhE9AEwDThk2DQeD230CzX0uy+HICxT3OnDcLiV6ylXK2XZabjUNm+sjA Drk+ReGt3IE8q3cQ+sCc2wRdoCj2rEtWpoyQAUL3XkBmNNb01RfxD78YEZi7h0aV7yDm zer7EsnuVPYnAy6P+nSmtcWLfbT/7UISDrIpFatKYKMPprMnmrqKfsMVXqVVK13Q0pdu fX5g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715864; x=1745320664; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=LTV89+C/IAgzFB5m5xPEkC+pORUgcF9jMRUpcGRiTts=; b=u+C+Qahymspq2hr22BIhbK9N9rt4MKY1lGbX4dAPVd4tMNJS5IYayHpRkrxZYL3iEr u7sgCBjikbWSdUskGF2S1gTApNm3d7M3wlviYjrK2mRaM167eVNuCzvZYMjpALdG2EUV Ztxdjl+G07Rmn94+i0XXyLxhTdbwI5XPwN7g5+xrBEqBI+xr7ls7iCZBhQj7EUSMFBsx lh4KnJa/WYRQpPtUYXd77YTEoQr8NrCPcv9xTiISYq2p48Xnfh+xu8WXmkAhJ16Y/nhE Pl/JTuwx5Fzm1qw/qCKlAGqaYpx//UJMac4oiYF4eoTstMk8kuj8J225ZF92bPGs646G I1HQ== X-Forwarded-Encrypted: i=1; AJvYcCUuqm6k2O+VEwifMkni5823VwEZEc9hcplWh4Jl12lPglryMe0faP0/r/osSp5LH7AUt/zSeTu+Y+pIRW8=@vger.kernel.org X-Gm-Message-State: AOJu0YxztRHvR6zqFNKZnSS8BMEIkaX8bud+cU5nfnxVvG2uZ5AHheDg rby4cYA02jPIgk1Vu8hqyWxe7NQxmLN1IIJfzXUGLRMLOQ1GR9xghVptRfhDtkpYT965MsqCQ2x B+YkSk+hcYCkN1mKNLjlVwWD70zhgmB6MNzmkOhd4IfqlhAq04SaB6zU= X-Gm-Gg: ASbGncsJYenBIxdRTUZnCgHZC4OhTi3iRBrD9RweFMbN9jTE+esYBZ7YeEiKoMIELP0 4HAz7+E0JkcFL82G5HWuy11BUmADF9zLvJdCXhOhrgAyqmfeD82tUUZ9y6MA/B3wAPbwJjzMAN/ IN5xE6sPiaUtZOao32OQjZFQLsEJne8O8WoIuAH8mpfb0eAlZgVzWpWskA+PjYHcmAv6OfFXgM0 lSWdq092LJFDv8cNozMC/17ljg0ba6bXYPrryRenbUXkaV0pidkvvJkPG7j0eTLjqzWO/8+BAku Hb7yAaRD+leXrt1SBEsmxUjwemQNcfX6QJmGwQ== X-Google-Smtp-Source: AGHT+IFMQlof59fOSg2M7Eq+m/RtbhU+97yysT9+PCWJfN8QfdC4S+dr1wKcgeULLpXXtIHsL2RW3A== X-Received: by 2002:a05:600c:4fc1:b0:43d:47b7:b32d with SMTP id 5b1f17b1804b1-43f3a9afc1emr114850195e9.25.1744715864556; Tue, 15 Apr 2025 04:17:44 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:44 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:30 +0200 Subject: [PATCH net-next v26 13/23] ovpn: add support for MSG_NOSIGNAL in tcp_sendmsg Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-13-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=2479; i=antonio@openvpn.net; h=from:subject:message-id; bh=+Unom1yykFNqbE9773wCLW/thNu13gqRTUvNb4z4izU=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBC5GCVEXr1yIn8QuqochbJhgC073YBScniF aNqKp9N6/qJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQgAKCRALcOU6oDjV h0skCACVDtxscS+U1pTczUQRXmdOPUnVYdiNZu9j/IbBs6wPQNrsNWG68KmhumIbaHSuJ3KhE2j o0r8LcyNbe/eAQcsnJcmiWxD6lXq0Nj3Ksh8fvo3jtAlN78gdkCCJkacixK0AbAAZL8j7/FySOk snWdSK2+2gc746sqw1LA+7x+BWFTaxf3GV31em7bTGo+WIFQ4Dy2YDolNlN9kLghzUXj84iXJOY K30JWMryR49joNOZLiOQQ5GjqfkF1MxiqTAdtixCs5qi9LL8njRQx54g0tJCT/UzezZccB5WxcU zUaehnb687l5liknaruBdGyY3ooLvo4a5e9VmfDKkm+wkrgx X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C Userspace may want to pass the MSG_NOSIGNAL flag to tcp_sendmsg() in order to avoid generating a SIGPIPE. To pass this flag down the TCP stack a new skb sending API accepting a flags argument is introduced. Cc: Eric Dumazet Cc: Paolo Abeni Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/skb.h | 1 + drivers/net/ovpn/tcp.c | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h index bd3cbcfc770d2c28d234fcdd081b4d02e6496ea0..64430880f1dae33a41f698d713c= f151be5b38577 100644 --- a/drivers/net/ovpn/skb.h +++ b/drivers/net/ovpn/skb.h @@ -25,6 +25,7 @@ struct ovpn_cb { struct scatterlist *sg; u8 *iv; unsigned int payload_offset; + bool nosignal; }; =20 static inline struct ovpn_cb *ovpn_skb_cb(struct sk_buff *skb) diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c index 588ff6b0440103f6620837a75ea2f1029d91b8a3..7c42d84987ad362289dbf5e7992= 403c76e910ed9 100644 --- a/drivers/net/ovpn/tcp.c +++ b/drivers/net/ovpn/tcp.c @@ -220,6 +220,7 @@ void ovpn_tcp_socket_wait_finish(struct ovpn_socket *so= ck) static void ovpn_tcp_send_sock(struct ovpn_peer *peer, struct sock *sk) { struct sk_buff *skb =3D peer->tcp.out_msg.skb; + int ret, flags; =20 if (!skb) return; @@ -230,9 +231,11 @@ static void ovpn_tcp_send_sock(struct ovpn_peer *peer,= struct sock *sk) peer->tcp.tx_in_progress =3D true; =20 do { - int ret =3D skb_send_sock_locked(sk, skb, - peer->tcp.out_msg.offset, - peer->tcp.out_msg.len); + flags =3D ovpn_skb_cb(skb)->nosignal ? MSG_NOSIGNAL : 0; + ret =3D skb_send_sock_locked_with_flags(sk, skb, + peer->tcp.out_msg.offset, + peer->tcp.out_msg.len, + flags); if (unlikely(ret < 0)) { if (ret =3D=3D -EAGAIN) goto out; @@ -380,7 +383,7 @@ static int ovpn_tcp_sendmsg(struct sock *sk, struct msg= hdr *msg, size_t size) rcu_read_unlock(); peer =3D sock->peer; =20 - if (msg->msg_flags & ~MSG_DONTWAIT) { + if (msg->msg_flags & ~(MSG_DONTWAIT | MSG_NOSIGNAL)) { ret =3D -EOPNOTSUPP; goto peer_free; } @@ -413,6 +416,7 @@ static int ovpn_tcp_sendmsg(struct sock *sk, struct msg= hdr *msg, size_t size) goto peer_free; } =20 + ovpn_skb_cb(skb)->nosignal =3D msg->msg_flags & MSG_NOSIGNAL; ovpn_tcp_send_sock_skb(peer, sk, skb); ret =3D size; peer_free: --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f44.google.com (mail-wm1-f44.google.com [209.85.128.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 80249297A69 for ; Tue, 15 Apr 2025 11:17:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715871; cv=none; b=ISqAhL88XtnfzuKCn0qn4xu45a9CW5kz3H7VI5zYnd5BzIx+nEYgcZ5zZ3mOr9KuI1JujhNwJgEVOvInU7QD36E0XOSJGxxHmEtIOia0mcPGZufoyvzPmDSrt+YvfoPN3vLydf90MPTxxHQfT+9oAh/3cPfkTme2r+8cPT6DCe0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715871; c=relaxed/simple; bh=IBVqLNp9FcpUwB9MrBtN8l0O7e5CtjbqcksNbQASLkc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=S3lQoTj7bVxrcu0qM7JCwE57fLu1PkLOoFU5YinIAwSs1dJ6bSUxzcT9GrJHJcZmxCvcqsWzu6J6ZZ946e9YhD91hMZHvsq9G8+xXCZzpy+gVqmq/wCRDfz4WAnWD35YYhUTV9LW4a40hfhXnMmGe51+RVsaZuvr2TVqU+0fK1A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=eRxmUgVu; arc=none smtp.client-ip=209.85.128.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="eRxmUgVu" Received: by mail-wm1-f44.google.com with SMTP id 5b1f17b1804b1-43ea40a6e98so47179775e9.1 for ; Tue, 15 Apr 2025 04:17:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715866; x=1745320666; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=9oRn0iPr1G3+mbCYzVlCplhrnR+ZeoD38QJ/LwB8cmQ=; b=eRxmUgVuJ0rjny9ep6GpyyXaiBwGqrAW6D1JhZMu+9Fy7K6V5cpmk5BmO9H5yo+TIF S4uXYmDg4O9ejPgOLNhYvqro5F1hzcnPkAgE1+NnjhYISQDEXmQL9cjKjjkdiVzcaAqM EPxKoSSGZXHVK8w3G2iz2SLqss/n8LTSwq4LyHn6EFujgnuOsyVogMk0kTIcEsMxvKMy uaatAK6ceOamFvy3Icz4lHogC13M7LuidqKVArnarw5WAYot43a1pvMWPctozlcN1hu8 Ad85SaFWplRXJJ+L0GZ+MYCJHvSTJUcTQx5iiwMlWkF7UWj7awGIiQCFh2WvA6oTiOXc 3S0Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715866; x=1745320666; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=9oRn0iPr1G3+mbCYzVlCplhrnR+ZeoD38QJ/LwB8cmQ=; b=QOS8/Tvv83Wy7mD0Cq9KOl4PfcLPOCSxwkeQa8rR4EfRRnrhwyZimye42+hyfwfgLH +KAmJ5oChtaLiLuuRK6H1p4Yw6xAYipi3sZd4D4Z9b89S7kdf68gg3aan5V3pspvRy0T bGpbbd4GB5ZKeHbWe4PwNiw/X9zUSIafIK6SMQmcT9QC11E57iQf0B23q+yat/NDy29j iUCJwAM/HJYFXyVassNyOwMhivvYSNx0i/Bk+3VS4fqaK4xHV0gZ9EEeGP0F9Q0wasJ2 GfNKvryd+gqXSxpzMWe7Fv8a4wDnLKjRFAvL73++pjhnpwIh3qGCSUVwOHvXYJJz+QQU 6c3w== X-Forwarded-Encrypted: i=1; AJvYcCU2H8JD3hWrPyNzFT59gLT8USx1LEMXtJR2q2Yrn1cnJtsTtG49IFv7kbpD+vwUI72TH3Qi/H+a+8LjplU=@vger.kernel.org X-Gm-Message-State: AOJu0YzbE7hiOQeTK2nCZ/ffPplJRGbmXgr3kuSo9j18j9j+5QS8Hf46 lN/RQ1x3OoIhBQvuLc2FNFFT1cHdcEMles46t1MTSMPSJ0VmH+gJc0zvzpJdeSc6Us/fSEyOb/5 CnjE6ShUi3g5IBSNQ6BVwbJd+xxdprcFnvmzxLMnQhaa3Zjtd7CMgmIQ= X-Gm-Gg: ASbGncsiRZfFL0p2mLT6pLW6iABhdXpZHLKENJNUbenAK0NFXAT+5874xPBU7vAheUi T1tYbfoNOuT14pjNbSXSE0tRSlW0mBKMzmjGV/q+ls4739/IKWrFP3FZhbIrCR93RGZONoi5w+O kBCV/3Pq1SWa2aj6GZS0p9sjTbbvBq4kIMXYnMKMbN9ZH6bNAnDu42m9LDNwnlBtnGWg+KX0hvH lsMzWNwy0ngOonZ8D9jR5i4rcUdFTPypWJQgWYE98LZ5PFjUxqwLTIjfmESIFextrQOJTOLeA1a kEGRDAWnkzgp++XSJoOFJUbQZQGyikhrMP5kog== X-Google-Smtp-Source: AGHT+IFwb2Ca2XgYMCVdVSI5X9AhY+t2knsByZD9E2prmn9n0hY3VdndcVUgpLdMD1M4vHeyFUJyKA== X-Received: by 2002:a05:6000:4285:b0:39a:c80b:8288 with SMTP id ffacd0b85a97d-39ea5211c47mr11854221f8f.33.1744715865729; Tue, 15 Apr 2025 04:17:45 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:45 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:31 +0200 Subject: [PATCH net-next v26 14/23] ovpn: implement multi-peer support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-14-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=14774; i=antonio@openvpn.net; h=from:subject:message-id; bh=IBVqLNp9FcpUwB9MrBtN8l0O7e5CtjbqcksNbQASLkc=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBCi+ktdLKb9qycbptbw9ogoTa8MiLf7ZeIF 2WxR7O+6TuJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQgAKCRALcOU6oDjV h7xzB/0T0+0VOS3TldrAREWxLKPG//QYFdKrOVzGLED1oy8b2uYj0du2rhKxtCXJuKoPksBSBq3 +Om6eIHqiPq8qKPyZORyv84M2QaQNtSXBblBJXzhE6trSRZBn58iO24lTzpf2JVQ4qWip1tMkcC rLNSUG+kTcrOLb47TT4lvMXbONdXYOes8biuynv2bD0CumMWjAbKPN4XNvN9f/PZ/9e3Ms+fOZQ fUSVxGUNhjfn1QUz0x/tqZeoA4agTl5SbM5+/1jVi7Ba4onzCVQYe3Mlay1pdIScaDTUnsO6yl+ akaBvyp5A4Cv4Es+a3M+jYvK8lGvp7rvpPfIpQ5JbebZl8CS X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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 Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/main.c | 64 +++++++++++++-- drivers/net/ovpn/ovpnpriv.h | 19 +++++ drivers/net/ovpn/peer.c | 189 ++++++++++++++++++++++++++++++++++++++++= ++-- drivers/net/ovpn/peer.h | 12 ++- drivers/net/ovpn/udp.c | 4 +- 5 files changed, 272 insertions(+), 16 deletions(-) diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 89075502c5af9168a2096e8426b1b5e8558665f5..889c4585f06bc8cbf0a22872a5f= 82512bc717c5e 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -25,11 +25,66 @@ #include "tcp.h" #include "udp.h" =20 +static void ovpn_priv_free(struct net_device *net) +{ + struct ovpn_priv *ovpn =3D netdev_priv(net); + + kfree(ovpn->peers); +} + +static int ovpn_mp_alloc(struct ovpn_priv *ovpn) +{ + struct in_device *dev_v4; + int i; + + if (ovpn->mode !=3D OVPN_MODE_MP) + return 0; + + dev_v4 =3D __in_dev_get_rtnl(ovpn->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(ovpn->dev), SEND_REDIRECTS) =3D false; + } + + /* the peer container is fairly large, therefore we allocate it only in + * MP mode + */ + ovpn->peers =3D kzalloc(sizeof(*ovpn->peers), GFP_KERNEL); + if (!ovpn->peers) + return -ENOMEM; + + for (i =3D 0; i < ARRAY_SIZE(ovpn->peers->by_id); i++) { + INIT_HLIST_HEAD(&ovpn->peers->by_id[i]); + INIT_HLIST_NULLS_HEAD(&ovpn->peers->by_vpn_addr4[i], i); + INIT_HLIST_NULLS_HEAD(&ovpn->peers->by_vpn_addr6[i], i); + INIT_HLIST_NULLS_HEAD(&ovpn->peers->by_transp_addr[i], i); + } + + return 0; +} + static int ovpn_net_init(struct net_device *dev) { struct ovpn_priv *ovpn =3D netdev_priv(dev); + int err =3D gro_cells_init(&ovpn->gro_cells, dev); + + if (err < 0) + return err; + + err =3D ovpn_mp_alloc(ovpn); + if (err < 0) { + gro_cells_destroy(&ovpn->gro_cells); + return err; + } =20 - return gro_cells_init(&ovpn->gro_cells, dev); + return 0; } =20 static void ovpn_net_uninit(struct net_device *dev) @@ -76,6 +131,8 @@ static void ovpn_setup(struct net_device *dev) =20 dev->netdev_ops =3D &ovpn_netdev_ops; =20 + dev->priv_destructor =3D ovpn_priv_free; + dev->hard_header_len =3D 0; dev->addr_len =3D 0; dev->mtu =3D ETH_DATA_LEN - OVPN_HEAD_ROOM; @@ -134,10 +191,7 @@ static void ovpn_dellink(struct net_device *dev, struc= t list_head *head) { struct ovpn_priv *ovpn =3D netdev_priv(dev); =20 - if (ovpn->mode =3D=3D OVPN_MODE_P2P) - ovpn_peer_release_p2p(ovpn, NULL, - OVPN_DEL_PEER_REASON_TEARDOWN); - + ovpn_peers_free(ovpn, NULL, OVPN_DEL_PEER_REASON_TEARDOWN); unregister_netdevice_queue(dev, head); } =20 diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index bb345954b8d9e78fcb4190bb0f6e3b262ca60410..5ba3c3e446e4d067675541435bc= c6aa282aee75c 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -15,11 +15,29 @@ #include #include =20 +/** + * struct ovpn_peer_collection - container of peers for MultiPeer mode + * @by_id: table of peers index by ID + * @by_vpn_addr4: table of peers indexed by VPN IPv4 address (items can be + * rehashed on the fly due to peer IP change) + * @by_vpn_addr6: table of peers indexed by VPN IPv6 address (items can be + * rehashed on the fly due to peer IP change) + * @by_transp_addr: table of peers indexed by transport address (items can= be + * rehashed on the fly due to peer IP change) + */ +struct ovpn_peer_collection { + DECLARE_HASHTABLE(by_id, 12); + struct hlist_nulls_head by_vpn_addr4[1 << 12]; + struct hlist_nulls_head by_vpn_addr6[1 << 12]; + struct hlist_nulls_head by_transp_addr[1 << 12]; +}; + /** * struct ovpn_priv - per ovpn interface state * @dev: the actual netdev representing the tunnel * @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 * @gro_cells: pointer to the Generic Receive Offload cell */ @@ -27,6 +45,7 @@ struct ovpn_priv { struct net_device *dev; enum ovpn_mode mode; spinlock_t lock; /* protect writing to the ovpn_priv object */ + struct ovpn_peer_collection *peers; struct ovpn_peer __rcu *peer; struct gro_cells gro_cells; }; diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 0fe5333c6b8104913526dacc4d7d2260b97f62aa..bed2e591c000c4efecdcd92db48= 4e590f97f9f7f 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -9,6 +9,7 @@ =20 #include #include +#include =20 #include "ovpnpriv.h" #include "bind.h" @@ -280,7 +281,19 @@ static void ovpn_peer_remove(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason, struct llist_head *release_list) { + lockdep_assert_held(&peer->ovpn->lock); + switch (peer->ovpn->mode) { + case OVPN_MODE_MP: + /* prevent double remove */ + if (hlist_unhashed(&peer->hash_entry_id)) + return; + + hlist_del_init_rcu(&peer->hash_entry_id); + hlist_nulls_del_init_rcu(&peer->hash_entry_addr4); + hlist_nulls_del_init_rcu(&peer->hash_entry_addr6); + hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr); + break; case OVPN_MODE_P2P: /* prevent double remove */ if (peer !=3D rcu_access_pointer(peer->ovpn->peer)) @@ -292,8 +305,6 @@ static void ovpn_peer_remove(struct ovpn_peer *peer, */ netif_carrier_off(peer->ovpn->dev); break; - default: - return; } =20 peer->delete_reason =3D reason; @@ -357,6 +368,89 @@ bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, st= ruct sk_buff *skb, return match; } =20 +#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \ + typeof(_tbl) *__tbl =3D &(_tbl); \ + (&(*__tbl)[jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl)]); }) \ + +/** + * 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_priv *ovpn, struct ovpn_peer *peer) +{ + struct sockaddr_storage sa =3D { 0 }; + struct hlist_nulls_head *nhead; + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; + struct ovpn_bind *bind; + struct ovpn_peer *tmp; + size_t salen; + int ret =3D 0; + + spin_lock_bh(&ovpn->lock); + /* do not add duplicates */ + tmp =3D ovpn_peer_get_by_id(ovpn, peer->id); + if (tmp) { + ovpn_peer_put(tmp); + ret =3D -EEXIST; + goto out; + } + + bind =3D rcu_dereference_protected(peer->bind, true); + /* peers connected via TCP have bind =3D=3D NULL */ + if (bind) { + switch (bind->remote.in4.sin_family) { + case AF_INET: + sa4 =3D (struct sockaddr_in *)&sa; + + sa4->sin_family =3D AF_INET; + sa4->sin_addr.s_addr =3D bind->remote.in4.sin_addr.s_addr; + sa4->sin_port =3D bind->remote.in4.sin_port; + salen =3D sizeof(*sa4); + break; + case AF_INET6: + sa6 =3D (struct sockaddr_in6 *)&sa; + + sa6->sin6_family =3D AF_INET6; + sa6->sin6_addr =3D bind->remote.in6.sin6_addr; + sa6->sin6_port =3D bind->remote.in6.sin6_port; + salen =3D sizeof(*sa6); + break; + default: + ret =3D -EPROTONOSUPPORT; + goto out; + } + + nhead =3D ovpn_get_hash_head(ovpn->peers->by_transp_addr, &sa, + salen); + hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead); + } + + 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 !=3D htonl(INADDR_ANY)) { + nhead =3D ovpn_get_hash_head(ovpn->peers->by_vpn_addr4, + &peer->vpn_addrs.ipv4, + sizeof(peer->vpn_addrs.ipv4)); + hlist_nulls_add_head_rcu(&peer->hash_entry_addr4, nhead); + } + + if (!ipv6_addr_any(&peer->vpn_addrs.ipv6)) { + nhead =3D ovpn_get_hash_head(ovpn->peers->by_vpn_addr6, + &peer->vpn_addrs.ipv6, + sizeof(peer->vpn_addrs.ipv6)); + hlist_nulls_add_head_rcu(&peer->hash_entry_addr6, nhead); + } +out: + spin_unlock_bh(&ovpn->lock); + return ret; +} + /** * ovpn_peer_add_p2p - add peer to related tables in a P2P instance * @ovpn: the instance to add the peer to @@ -399,11 +493,42 @@ static int ovpn_peer_add_p2p(struct ovpn_priv *ovpn, = struct ovpn_peer *peer) int ovpn_peer_add(struct ovpn_priv *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: - return -EOPNOTSUPP; } + + return -EOPNOTSUPP; +} + +/** + * 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) + * @release_list: list where delete peer should be appended + * + * 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 llist_head *release_list) +{ + struct ovpn_peer *tmp; + int ret =3D -ENOENT; + + lockdep_assert_held(&peer->ovpn->lock); + + tmp =3D ovpn_peer_get_by_id(peer->ovpn, peer->id); + if (tmp =3D=3D peer) { + ovpn_peer_remove(peer, reason, release_list); + ret =3D 0; + } + + if (tmp) + ovpn_peer_put(tmp); + + return ret; } =20 /** @@ -446,6 +571,9 @@ int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del= _peer_reason reason) =20 spin_lock_bh(&peer->ovpn->lock); switch (peer->ovpn->mode) { + case OVPN_MODE_MP: + ret =3D ovpn_peer_del_mp(peer, reason, &release_list); + break; case OVPN_MODE_P2P: ret =3D ovpn_peer_del_p2p(peer, reason, &release_list); break; @@ -463,8 +591,8 @@ int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del= _peer_reason reason) * @sk: if not NULL, release peer only if it's using this specific socket * @reason: the reason for releasing the peer */ -void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, struct sock *sk, - enum ovpn_del_peer_reason reason) +static void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, struct sock *sk, + enum ovpn_del_peer_reason reason) { struct ovpn_socket *ovpn_sock; LLIST_HEAD(release_list); @@ -490,3 +618,52 @@ void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, str= uct sock *sk, ovpn_peer_remove(peer, reason, &release_list); unlock_ovpn(ovpn, &release_list); } + +static void ovpn_peers_release_mp(struct ovpn_priv *ovpn, struct sock *sk, + enum ovpn_del_peer_reason reason) +{ + struct ovpn_socket *ovpn_sock; + LLIST_HEAD(release_list); + struct ovpn_peer *peer; + struct hlist_node *tmp; + int bkt; + + spin_lock_bh(&ovpn->lock); + hash_for_each_safe(ovpn->peers->by_id, bkt, tmp, peer, hash_entry_id) { + bool remove =3D true; + + /* if a socket was passed as argument, skip all peers except + * those using it + */ + if (sk) { + rcu_read_lock(); + ovpn_sock =3D rcu_dereference(peer->sock); + remove =3D ovpn_sock && ovpn_sock->sock->sk =3D=3D sk; + rcu_read_unlock(); + } + + if (remove) + ovpn_peer_remove(peer, reason, &release_list); + } + unlock_ovpn(ovpn, &release_list); +} + +/** + * ovpn_peers_free - free all peers in the instance + * @ovpn: the instance whose peers should be released + * @sk: if not NULL, only peers using this socket are removed and the sock= et + * is released immediately + * @reason: the reason for releasing all peers + */ +void ovpn_peers_free(struct ovpn_priv *ovpn, struct sock *sk, + enum ovpn_del_peer_reason reason) +{ + switch (ovpn->mode) { + case OVPN_MODE_P2P: + ovpn_peer_release_p2p(ovpn, sk, reason); + break; + case OVPN_MODE_MP: + ovpn_peers_release_mp(ovpn, sk, reason); + break; + } +} diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 5ef00ba6523d7bf6608adb86fa7c7029f559ce2a..2a3b1031f58dd73925a9ed74aed= 7ac4cb7b8c4c5 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -25,6 +25,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) @@ -57,6 +61,10 @@ struct ovpn_peer { struct in_addr ipv4; struct in6_addr ipv6; } vpn_addrs; + struct hlist_node hash_entry_id; + struct hlist_nulls_node hash_entry_addr4; + struct hlist_nulls_node hash_entry_addr6; + struct hlist_nulls_node hash_entry_transp_addr; struct ovpn_socket __rcu *sock; =20 struct { @@ -117,8 +125,8 @@ static inline void ovpn_peer_put(struct ovpn_peer *peer) struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id); int ovpn_peer_add(struct ovpn_priv *ovpn, struct ovpn_peer *peer); int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason= ); -void ovpn_peer_release_p2p(struct ovpn_priv *ovpn, struct sock *sk, - enum ovpn_del_peer_reason reason); +void ovpn_peers_free(struct ovpn_priv *ovpn, struct sock *sock, + enum ovpn_del_peer_reason reason); =20 struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, struct sk_buff *skb); diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index 920d71da793ccc88de4cff1c99c8e6e8d0a24b71..c9e189056f3367beb08784576d0= 9b9a161dfa697 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -367,9 +367,7 @@ static void ovpn_udp_encap_destroy(struct sock *sk) ovpn =3D sock->ovpn; rcu_read_unlock(); =20 - if (ovpn->mode =3D=3D OVPN_MODE_P2P) - ovpn_peer_release_p2p(ovpn, sk, - OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT); + ovpn_peers_free(ovpn, sk, OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT); } =20 /** --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wr1-f46.google.com (mail-wr1-f46.google.com [209.85.221.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AC3AE29A3C0 for ; Tue, 15 Apr 2025 11:17:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715872; cv=none; b=FeAi2PkT+JDHaFqLT8nfUxlm6MBrgiDSf4SCOr1plKxoEyBRW33dFcGPb/cWCpK7PxbTfrgMFbwT4KVxIfeMCDs6T0EEJoO08kXhYIf98zc3wggSL1iXl24ABRwRGYYMHe/DSc81rVLqqJthmdZE7FTwRzQysc4S44Uhpzse8r4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715872; c=relaxed/simple; bh=Qz1wwbBezNZv1FnAKam5tfxVwWdQfiQrT7NGpNzQOdE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=OR4ciQenXh5u3PWahNoE3dD1vxF9qhV1gc0WF1w1jQKS8smIiyC2MTPPE5K3cPUMCungoJhiZRqYIULdvNdZsJLEMQ6ZjvD+sDBPB3cEgQmEgDfk1mP3clU24T+ouZazbfrjuSQdEcFOsNqKdZB+eEyefNddwrJEOvFpbjo9VXU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=Uag6v6Xd; arc=none smtp.client-ip=209.85.221.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="Uag6v6Xd" Received: by mail-wr1-f46.google.com with SMTP id ffacd0b85a97d-39ac56756f6so4690813f8f.2 for ; Tue, 15 Apr 2025 04:17:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715867; x=1745320667; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=rwUZuVAaNoiHUHlyIfXYJQLeFNxbSXbeyVs9czXZA+0=; b=Uag6v6XdeI0+rX2icfhH1MFbikgd7VXeF5OW7Rumxh4D50PEG2FGPrGN5CeNBehJB/ uwpS3PrAKFS313x+40uYU16pN3jYQBhA12UehXzlOjQLl1dU2TAqd0OizHUIwtzDw5// u8bOCf9VJp2XH2nUdAil+2MIj5WtfTKv8XXnOTq07f1TwA3RJ0z7B8LMyz1qjJKOFn1J ZLoZkFQTzXZFRax/EMcURgrugPqp0yiELN+KFHFTNRgemOubhPL3x30sWFs0g9ZexPSh DwGSdlpn3vOr4NljU/MdRugl6HCOTXvcAdIyrTDGqvVXciHGv+BYVi8+BE4yEAvjvrsp /Glw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715867; x=1745320667; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=rwUZuVAaNoiHUHlyIfXYJQLeFNxbSXbeyVs9czXZA+0=; b=a2UboDUxnKLpbs6kwdhTLx+eh7XTfXdx7REq5nSfBstu6+PZ+yK+kxz/c1v3OKD5Cm 9zyqQ3Dl+zza6EnHrpcB6WJ0lvEKPiXruYgyLMRtx7D8gXsjmzn6Gt1edeibh81PBNCZ vktn3ohP9Cs1Bafpe9fSQQFf+cbDia3IfVotrP373ByKdIJo1ZjhMyZRESUruRW0Dted kDOeVSH1SbdcjjhIppx63JoPbo3oknVxok4tRog9RFuGVy71QXuewDutZM1KSB5+Pe1z mAYtdk5bkw4RZjK3K1TP7PgOLk92QoKONVUfL2RP6kitu2pOT6pCimfjgF29/lWm/XjV f3LA== X-Forwarded-Encrypted: i=1; AJvYcCW0bgBxDIAgafhloJj4zSzf34+AQE+Z6jYh5FA3nbI67STMJg1rZd/XRHzcLd5dF8hDAm5TUArtAdF99OE=@vger.kernel.org X-Gm-Message-State: AOJu0Yw9uPzMPWnx74idr2/QxCTx5MoWcBcZsxccFU9nlw6HlpZYqNIs 86rzaCx567CTVO8OnIFOQlIAOLOgOUy8l7wks4wHzQMXE4sK7YdEvg3HFpqxRucmF49UhV2yafx ln4n2DoQoWv2oWvt3dL0lOjUKLBvk4/3dCpiNrgOg8LMQ/YloFC5m0Gc= X-Gm-Gg: ASbGnctO3ZNvW38agx+bWk0PJQAHg01Dlay5szQrxjZ8yYA7UWUMuZ5+lLo6vowdFx6 +aoW+PpY9TBTz1/zu8TS5623QSXpm4l39yCHKdmJjlLjGdRMgADc8I3C7Syc2aJSCXyfCf8WFjI 1a838Cskdy1biwD/Zwaxf9C9195qKEiBIHUF4ZAUVkAuYVXXISy+CIWrqPT1ooqnPC6H33WpQpG IRoRfASoNevxCoq/ujxwMk61lZLBx02quKWi8i7vtZjnR40uWqv+Kv+bXEeSozVeAOxge9pPsY8 EfnCsczEsOaQk9PrMGnhnwlBFErSHLs0Sz96tg== X-Google-Smtp-Source: AGHT+IEZwOuJRYqQv+4pwcnYZIYkfSBMrFpZbDIdxKyZPJuf/MqnrOaOI32eFRsWXN50vGz7AfrlGQ== X-Received: by 2002:a5d:64a9:0:b0:39e:dd1e:f325 with SMTP id ffacd0b85a97d-39edd1ef401mr1778010f8f.31.1744715866934; Tue, 15 Apr 2025 04:17:46 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:46 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:32 +0200 Subject: [PATCH net-next v26 15/23] ovpn: implement peer lookup logic Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-15-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=11637; i=antonio@openvpn.net; h=from:subject:message-id; bh=Qz1wwbBezNZv1FnAKam5tfxVwWdQfiQrT7NGpNzQOdE=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBCQs8bWPMKI9fCg27dtdt0ocYT4mU0ON6aw oZ0ikJ/ZNqJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQgAKCRALcOU6oDjV h3NdCACo1r32h9fePzE7jpJepg7G8lUj9pm2hmJFhZadPNsHwBT0t9bzZJMoivmu9zYac5Anx/f javGfoVxU1iZcZqvSCRBzTwROLPl9vGefbfUjquTAYFJp5MewZ2b60YwTs+fHZjuapKs1NVQjz/ +uuN6Trt/YtouaNAHhujTR1Zmd911zirAszOjJf/W+HOGENFkzJ39wueB+7hBqOF7t0U0bPkSdl XtyvT8klF343tOrfoGsGr7dwF4fzixKgE7yFtXgGyT8+bkw1JBt3ak1G8ZvEw9Gu4Z2rMD4gyyv s66ZVLRnNSgHGQAeIMLDISBrM74XFrKSAsU5EDyk6+PB2xFg X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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 Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/peer.c | 301 ++++++++++++++++++++++++++++++++++++++++++++= ++-- 1 file changed, 291 insertions(+), 10 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index bed2e591c000c4efecdcd92db484e590f97f9f7f..2d6cecc28c5d1f7d5516f7f89bc= 0ba274c72d5e1 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -10,6 +10,7 @@ #include #include #include +#include =20 #include "ovpnpriv.h" #include "bind.h" @@ -150,6 +151,121 @@ static int ovpn_peer_skb_to_sockaddr(struct sk_buff *= skb, return -1; } =20 +/** + * 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 =3D 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 =3D skb_rt6_info(skb); + + if (!rt || !(rt->rt6i_flags & RTF_GATEWAY)) + return ipv6_hdr(skb)->daddr; + + return rt->rt6i_gateway; +} + +/* variable name __tbl2 needs to be different from __tbl1 + * in the macro below to avoid confusing clang + */ +#define ovpn_get_hash_slot(_tbl, _key, _key_len) ({ \ + typeof(_tbl) *__tbl2 =3D &(_tbl); \ + jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl2); \ +}) + +#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \ + typeof(_tbl) *__tbl1 =3D &(_tbl); \ + &(*__tbl1)[ovpn_get_hash_slot(*__tbl1, _key, _key_len)];\ +}) + +/** + * ovpn_peer_get_by_vpn_addr4 - retrieve peer by its VPN IPv4 address + * @ovpn: the openvpn instance to search + * @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_priv *ovpn, + __be32 addr) +{ + struct hlist_nulls_head *nhead; + struct hlist_nulls_node *ntmp; + struct ovpn_peer *tmp; + unsigned int slot; + +begin: + slot =3D ovpn_get_hash_slot(ovpn->peers->by_vpn_addr4, &addr, + sizeof(addr)); + nhead =3D &ovpn->peers->by_vpn_addr4[slot]; + + hlist_nulls_for_each_entry_rcu(tmp, ntmp, nhead, hash_entry_addr4) + if (addr =3D=3D tmp->vpn_addrs.ipv4.s_addr) + return tmp; + + /* item may have moved during lookup - check nulls and restart + * if that's the case + */ + if (get_nulls_value(ntmp) !=3D slot) + goto begin; + + 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_priv *ovpn, + struct in6_addr *addr) +{ + struct hlist_nulls_head *nhead; + struct hlist_nulls_node *ntmp; + struct ovpn_peer *tmp; + unsigned int slot; + +begin: + slot =3D ovpn_get_hash_slot(ovpn->peers->by_vpn_addr6, addr, + sizeof(*addr)); + nhead =3D &ovpn->peers->by_vpn_addr6[slot]; + + hlist_nulls_for_each_entry_rcu(tmp, ntmp, nhead, hash_entry_addr6) + if (ipv6_addr_equal(addr, &tmp->vpn_addrs.ipv6)) + return tmp; + + /* item may have moved during lookup - check nulls and restart + * if that's the case + */ + if (get_nulls_value(ntmp) !=3D slot) + goto begin; + + return NULL; +} + /** * ovpn_peer_transp_match - check if sockaddr and peer binding match * @peer: the peer to get the binding from @@ -227,14 +343,43 @@ ovpn_peer_get_by_transp_addr_p2p(struct ovpn_priv *ov= pn, struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, struct sk_buff *skb) { - struct ovpn_peer *peer =3D NULL; + struct ovpn_peer *tmp, *peer =3D NULL; struct sockaddr_storage ss =3D { 0 }; + struct hlist_nulls_head *nhead; + struct hlist_nulls_node *ntmp; + unsigned int slot; + ssize_t sa_len; =20 - if (unlikely(!ovpn_peer_skb_to_sockaddr(skb, &ss))) + sa_len =3D ovpn_peer_skb_to_sockaddr(skb, &ss); + if (unlikely(sa_len < 0)) return NULL; =20 if (ovpn->mode =3D=3D OVPN_MODE_P2P) - peer =3D ovpn_peer_get_by_transp_addr_p2p(ovpn, &ss); + return ovpn_peer_get_by_transp_addr_p2p(ovpn, &ss); + + rcu_read_lock(); +begin: + slot =3D ovpn_get_hash_slot(ovpn->peers->by_transp_addr, &ss, sa_len); + nhead =3D &ovpn->peers->by_transp_addr[slot]; + + hlist_nulls_for_each_entry_rcu(tmp, ntmp, nhead, + hash_entry_transp_addr) { + if (!ovpn_peer_transp_match(tmp, &ss)) + continue; + + if (!ovpn_peer_hold(tmp)) + continue; + + peer =3D tmp; + break; + } + + /* item may have moved during lookup - check nulls and restart + * if that's the case + */ + if (!peer && get_nulls_value(ntmp) !=3D slot) + goto begin; + rcu_read_unlock(); =20 return peer; } @@ -269,10 +414,27 @@ static struct ovpn_peer *ovpn_peer_get_by_id_p2p(stru= ct ovpn_priv *ovpn, */ struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id) { - struct ovpn_peer *peer =3D NULL; + struct ovpn_peer *tmp, *peer =3D NULL; + struct hlist_head *head; =20 if (ovpn->mode =3D=3D OVPN_MODE_P2P) - peer =3D ovpn_peer_get_by_id_p2p(ovpn, peer_id); + return ovpn_peer_get_by_id_p2p(ovpn, peer_id); + + head =3D 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 !=3D peer_id) + continue; + + if (!ovpn_peer_hold(tmp)) + continue; + + peer =3D tmp; + break; + } + rcu_read_unlock(); =20 return peer; } @@ -330,6 +492,8 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv= *ovpn, struct sk_buff *skb) { struct ovpn_peer *peer =3D NULL; + struct in6_addr addr6; + __be32 addr4; =20 /* in P2P mode, no matter the destination, packets are always sent to * the single peer listening on the other side @@ -340,11 +504,109 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_p= riv *ovpn, if (unlikely(peer && !ovpn_peer_hold(peer))) peer =3D NULL; rcu_read_unlock(); + return peer; } =20 + rcu_read_lock(); + switch (skb->protocol) { + case htons(ETH_P_IP): + addr4 =3D ovpn_nexthop_from_skb4(skb); + peer =3D ovpn_peer_get_by_vpn_addr4(ovpn, addr4); + break; + case htons(ETH_P_IPV6): + addr6 =3D ovpn_nexthop_from_skb6(skb); + peer =3D ovpn_peer_get_by_vpn_addr6(ovpn, &addr6); + break; + } + + if (unlikely(peer && !ovpn_peer_hold(peer))) + peer =3D NULL; + rcu_read_unlock(); + return peer; } =20 +/** + * ovpn_nexthop_from_rt4 - look up the IPv4 nexthop for the given destinat= ion + * @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 u= sed + * 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_priv *ovpn, __be32 dest) +{ + struct rtable *rt; + struct flowi4 fl =3D { + .daddr =3D dest + }; + + rt =3D ip_route_output_flow(dev_net(ovpn->dev), &fl, NULL); + if (IS_ERR(rt)) { + net_dbg_ratelimited("%s: no route to host %pI4\n", + netdev_name(ovpn->dev), &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 =3D rt->rt_gw4; +out: + ip_rt_put(rt); + return dest; +} + +/** + * ovpn_nexthop_from_rt6 - look up the IPv6 nexthop for the given destinat= ion + * @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 u= sed + * 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_priv *ovpn, + struct in6_addr dest) +{ +#if IS_ENABLED(CONFIG_IPV6) + struct dst_entry *entry; + struct rt6_info *rt; + struct flowi6 fl =3D { + .daddr =3D dest, + }; + + entry =3D 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", + netdev_name(ovpn->dev), &dest); + /* if we end up here this packet is probably going to be + * thrown away later + */ + return dest; + } + + rt =3D dst_rt6_info(entry); + + if (!(rt->rt6i_flags & RTF_GATEWAY)) + goto out; + + dest =3D 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 @@ -357,21 +619,40 @@ bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, s= truct sk_buff *skb, struct ovpn_peer *peer) { bool match =3D false; + struct in6_addr addr6; + __be32 addr4; =20 if (ovpn->mode =3D=3D OVPN_MODE_P2P) { /* in P2P mode, no matter the destination, packets are always * sent to the single peer listening on the other side */ - match =3D (peer =3D=3D rcu_access_pointer(ovpn->peer)); + return peer =3D=3D rcu_access_pointer(ovpn->peer); + } + + /* 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) { + case htons(ETH_P_IP): + addr4 =3D ovpn_nexthop_from_rt4(ovpn, ip_hdr(skb)->saddr); + rcu_read_lock(); + match =3D (peer =3D=3D ovpn_peer_get_by_vpn_addr4(ovpn, addr4)); + rcu_read_unlock(); + break; + case htons(ETH_P_IPV6): + addr6 =3D ovpn_nexthop_from_rt6(ovpn, ipv6_hdr(skb)->saddr); + rcu_read_lock(); + match =3D (peer =3D=3D ovpn_peer_get_by_vpn_addr6(ovpn, &addr6)); + rcu_read_unlock(); + break; } =20 return match; } =20 -#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \ - typeof(_tbl) *__tbl =3D &(_tbl); \ - (&(*__tbl)[jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl)]); }) \ - /** * ovpn_peer_add_mp - add peer to related tables in a MP instance * @ovpn: the instance to add the peer to --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wr1-f52.google.com (mail-wr1-f52.google.com [209.85.221.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4D04E2949FF for ; Tue, 15 Apr 2025 11:17:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715874; cv=none; b=dqQH3YM6r3ObSmZ4D4o7ByTd1lE8u+zv99Co6pWdXAjRPhnTJXUmYQZxdssU5SjbwlEA8EWQhHIRnDEv4yTKtMPC3Kv25fRXdG3j297mj2r8GUDbypDAS/DZaZa4ZXkbAXZqUqJtFFqjBtRKM8bBIxjhjVJfwaAXdgMekKPJifI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715874; c=relaxed/simple; bh=uzyYWPpTZHE5FL3YlqpdiE+FXNdsTgwuDNppQQvcMjA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=sB1AiOCqMwzluDhoUv3TnntdUJ1lHhsnd3l8sBu+TcGyMLMeZrkUmoPyBTs9+0/Dy4obyrB/WUatxA+rYLULaL7AGw9gUCsKqm1G6gu+L4gLBAFeAPqQ0EUh0tsHWFPTAoM2JaAaAOCaglVMtQfNXoO3dgyuTcx0B17++G/GEl8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=AVWmVu8w; arc=none smtp.client-ip=209.85.221.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="AVWmVu8w" Received: by mail-wr1-f52.google.com with SMTP id ffacd0b85a97d-39129fc51f8so4697800f8f.0 for ; Tue, 15 Apr 2025 04:17:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715868; x=1745320668; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=KYO/uR/uD3BECo6VPbRNZtFHuf/aMCen8FhNH0TDmGY=; b=AVWmVu8wJhqG90Hx7/Mvr/ONWfNllFWkYtWemwXZtGQnGLG/8hO1x/GJzNfOmw0Vgu s03dCUS7TerYok1zFKbW6QRVzrA/FhxeVpWmrOeZPNDcOmnc0M/HlmBZ17nrNBhq6Ajd nt8V8v1mFPELlYNMFfv77YDZi0ud+9d5T9EiFrdzHMnuKb79VPnanfJuvu5VdoBQG56Z ZSFo3BObuT09sk4cGdt0pPwR+cI11SPOm+o0GJcyLFEKbIHBgqF8nxdgccHdTu+y0Iw2 EZNm5ljUL+J3Nyh5PKMP02WBTlS8aO4NyDcDo2IPHaw7hoiKcg22eG42pvkCLWTkjcGy NgOg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715868; x=1745320668; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=KYO/uR/uD3BECo6VPbRNZtFHuf/aMCen8FhNH0TDmGY=; b=ZYk8ecJpOaa6Tv4XdOPwI+OjnZyeIv4PQmG/CMhoLVloehWi+fLMGAO3Gf09/YieiE ua9wbNlyffJr84Z3ce+cGeSBiEk194gxo1ZHttSYuM7eXTO3Xse1WEQ7YcLzw3EeYtag 6USwvghd2pp9XGs7vplR9ljhm2MRtm+qqXXygKuKl7zLMVg02/YdOJh0NIC3o8Kt3OR/ ExTDQUiplg+Yl9s+RiT6agTHfGw/v7+UJANGdsO5KStIHCyxij9/goMkRyctqA1ck08J aXKwoDgcDGCpIJ5z/Pvd+VJ0k54oRbW/b7RPWaJ7pyfRBctzQEiPmWwZzPce2iVsyuqp oKDw== X-Forwarded-Encrypted: i=1; AJvYcCXwARnOZWhCG3aUbe3PpoKkSMCZFDfR1HvnY8LpKADDmWbelywOKbnnIS4JCPaZAfVu1FMufLrUxogei0A=@vger.kernel.org X-Gm-Message-State: AOJu0Yy8kM3jDu1pYEKpaDv8mKe+o+f7WB6JhVcUFIrnlYq7KKDb/48i r0xw/wITC9mP4xE6BOSl67qwrjHoXBf1PnM7tr1hHYbDvqF3vsCk9mqyO1BNDDsinBfF9e0y0fv Ko7uwmB2xidTuYY0jnWjCXMZtRolLzOoEmz+9JKD2sZkI6l/aPJ9RQsk= X-Gm-Gg: ASbGncu/kwYowHgA1BAhbaLkmetBE52/N3JpRJL7vOpmaWqyk04aZhPxURd/FadVaYc JbV3Ce+7/QNc7lXOnoQieCwp51p+TVJnbRzm9bC7IqN+e2yCnT6F/1EotTKweXQ1BRwPmVvYioP Woq0hctSP7XD1IVEhNrh9Ahy1VJSgS4SeQpSIDtrHG8dvYuzpCyie0hW+LrPhM5ejgWcKJ+dARw 48j3blBn4w+Z/vQOB6FAvdPT4EAu+Vo6m0wf2w9E6TVQUfsZJ2mooulqsqBRMidFqvhs8Bn7kkr FLHoOhC37AzrMb7eo7gKF47X2gfD2UGpbNCZU2jvJP+dKC1p X-Google-Smtp-Source: AGHT+IEmyVTamFKQu7pd234SU+CUwekT4Rx1T0TWQn8Ts+dN84RRHKlDF9nTN7ebh42IbeOMNV+rxQ== X-Received: by 2002:a5d:584a:0:b0:399:7f43:b3a4 with SMTP id ffacd0b85a97d-39ea521505emr13698551f8f.24.1744715868204; Tue, 15 Apr 2025 04:17:48 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:47 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:33 +0200 Subject: [PATCH net-next v26 16/23] ovpn: implement keepalive mechanism Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-16-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=16146; i=antonio@openvpn.net; h=from:subject:message-id; bh=uzyYWPpTZHE5FL3YlqpdiE+FXNdsTgwuDNppQQvcMjA=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBCig6U/6AGtVX2Hx9gKRX0KtdCHCZ6kEZwl O5DVCEjf4+JATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQgAKCRALcOU6oDjV h8s1CACNEKv8TJQq3WUvbna4tOZcYmJG/Z8ov2TVNUxRA+6Tt5IOJg5lrUMNgBEh9YojLBB1O/p Nc7qLce+KZa9RKX/2JH2y+4+BZOg3xAqW96KU86teOYp5nKTaUSTDiCOcGW0ALZN1xH7zT0pRoY Rn6Iln7J33I9wB4P3I7tQ7UZorBdUlq1096A8oeWKRYyKomBHRT/1qUHkyVPKRYtJyvlqgi+naj lAkEftsonJD2Yy0cRpY66WoKsw0YGODqf0RoInqqm2dGTrE2OLZc+Iuic2J7Ner2vVqK5kc+ba8 H7fmoYQY4LMqyZWAAFGXbOW2rOWXwB21GALsqjmpcZVxh7jF X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C OpenVPN supports configuring a periodic keepalive packet. message to allow the remote endpoint detect link failures. This change implements the keepalive sending and timer expiring logic. Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/io.c | 78 ++++++++++++++++- drivers/net/ovpn/io.h | 5 ++ drivers/net/ovpn/main.c | 2 + drivers/net/ovpn/ovpnpriv.h | 2 + drivers/net/ovpn/peer.c | 206 ++++++++++++++++++++++++++++++++++++++++= ++++ drivers/net/ovpn/peer.h | 21 ++++- 6 files changed, 311 insertions(+), 3 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 5479b40f84a4d7207a7fdd4f6a8ea72e6b6a73a0..a1816f72634bb32f5146ba0f5dc= c5d1440b83792 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -27,6 +27,33 @@ #include "skb.h" #include "socket.h" =20 +const unsigned char ovpn_keepalive_message[OVPN_KEEPALIVE_SIZE] =3D { + 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 !=3D ovpn_keepalive_message[0]) + return false; + + if (skb->len !=3D OVPN_KEEPALIVE_SIZE) + return false; + + if (!pskb_may_pull(skb, OVPN_KEEPALIVE_SIZE)) + return false; + + return !memcmp(skb->data, ovpn_keepalive_message, OVPN_KEEPALIVE_SIZE); +} + /* Called after decrypt to write the IP packet to the device. * This method is expected to manage/free the skb. */ @@ -101,6 +128,9 @@ void ovpn_decrypt_post(void *data, int ret) goto drop; } =20 + /* keep track of last received authenticated packet for keepalive */ + WRITE_ONCE(peer->last_recv, ktime_get_real_seconds()); + /* point to encapsulated IP packet */ __skb_pull(skb, payload_offset); =20 @@ -118,6 +148,15 @@ void ovpn_decrypt_post(void *data, int ret) goto drop; } =20 + if (ovpn_is_keepalive(skb)) { + net_dbg_ratelimited("%s: ping received from peer %u\n", + netdev_name(peer->ovpn->dev), + peer->id); + /* we drop the packet, but this is not a failure */ + consume_skb(skb); + goto drop_nocount; + } + net_info_ratelimited("%s: unsupported protocol received from peer %u\n", netdev_name(peer->ovpn->dev), peer->id); goto drop; @@ -143,11 +182,12 @@ void ovpn_decrypt_post(void *data, int ret) drop: if (unlikely(skb)) dev_dstats_rx_dropped(peer->ovpn->dev); + kfree_skb(skb); +drop_nocount: if (likely(peer)) ovpn_peer_put(peer); if (likely(ks)) ovpn_crypto_key_slot_put(ks); - kfree_skb(skb); } =20 /* RX path entry point: decrypt packet and forward it to the device */ @@ -221,6 +261,8 @@ void ovpn_encrypt_post(void *data, int ret) } =20 ovpn_peer_stats_increment_tx(&peer->link_stats, orig_len); + /* keep track of last sent packet for keepalive */ + WRITE_ONCE(peer->last_sent, ktime_get_real_seconds()); /* skb passed down the stack - don't free it */ skb =3D NULL; err_unlock: @@ -346,3 +388,37 @@ 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, which will be + * passed to ovpn_send() + */ +void ovpn_xmit_special(struct ovpn_peer *peer, const void *data, + const unsigned int len) +{ + struct ovpn_priv *ovpn; + struct sk_buff *skb; + + ovpn =3D peer->ovpn; + if (unlikely(!ovpn)) { + ovpn_peer_put(peer); + return; + } + + skb =3D alloc_skb(256 + len, GFP_ATOMIC); + if (unlikely(!skb)) { + ovpn_peer_put(peer); + return; + } + + skb_reserve(skb, 128); + skb->priority =3D TC_PRIO_BESTEFFORT; + __skb_put_data(skb, data, len); + + ovpn_send(ovpn, skb, peer); +} diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h index 5143104b2c4b896a030ec4a8c8aea7015f40ef02..db9e10f9077c4738ee79e5723e2= a4bf5ef72f633 100644 --- a/drivers/net/ovpn/io.h +++ b/drivers/net/ovpn/io.h @@ -19,9 +19,14 @@ /* max padding required by encryption */ #define OVPN_MAX_PADDING 16 =20 +#define OVPN_KEEPALIVE_SIZE 16 +extern const unsigned char ovpn_keepalive_message[OVPN_KEEPALIVE_SIZE]; + netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev); =20 void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb); +void ovpn_xmit_special(struct ovpn_peer *peer, const void *data, + const unsigned int len); =20 void ovpn_encrypt_post(void *data, int ret); void ovpn_decrypt_post(void *data, int ret); diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 889c4585f06bc8cbf0a22872a5f82512bc717c5e..232eeb08e929230afca909bca53= 9ef97892837f5 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -170,6 +170,7 @@ static int ovpn_newlink(struct net_device *dev, ovpn->dev =3D dev; ovpn->mode =3D mode; spin_lock_init(&ovpn->lock); + INIT_DELAYED_WORK(&ovpn->keepalive_work, ovpn_peer_keepalive_work); =20 /* Set carrier explicitly after registration, this way state is * clearly defined. @@ -191,6 +192,7 @@ static void ovpn_dellink(struct net_device *dev, struct= list_head *head) { struct ovpn_priv *ovpn =3D netdev_priv(dev); =20 + cancel_delayed_work_sync(&ovpn->keepalive_work); ovpn_peers_free(ovpn, NULL, OVPN_DEL_PEER_REASON_TEARDOWN); unregister_netdevice_queue(dev, head); } diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index 5ba3c3e446e4d067675541435bcc6aa282aee75c..5898f6adada7f57abcbfa4f37ca= 653f4f1e506a9 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -40,6 +40,7 @@ struct ovpn_peer_collection { * @peers: data structures holding multi-peer references * @peer: in P2P mode, this is the only remote peer * @gro_cells: pointer to the Generic Receive Offload cell + * @keepalive_work: struct used to schedule keepalive periodic job */ struct ovpn_priv { struct net_device *dev; @@ -48,6 +49,7 @@ struct ovpn_priv { struct ovpn_peer_collection *peers; struct ovpn_peer __rcu *peer; struct gro_cells gro_cells; + struct delayed_work keepalive_work; }; =20 #endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 2d6cecc28c5d1f7d5516f7f89bc0ba274c72d5e1..50514736a4327c96d381805f087= 0d89fe1193ab1 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -36,6 +36,52 @@ static void unlock_ovpn(struct ovpn_priv *ovpn, } } =20 +/** + * 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 tim= eout) +{ + time64_t now =3D ktime_get_real_seconds(); + + netdev_dbg(peer->ovpn->dev, + "scheduling keepalive for peer %u: interval=3D%u timeout=3D%u\n", + peer->id, interval, timeout); + + peer->keepalive_interval =3D interval; + WRITE_ONCE(peer->last_sent, now); + peer->keepalive_xmit_exp =3D now + interval; + + peer->keepalive_timeout =3D timeout; + WRITE_ONCE(peer->last_recv, now); + peer->keepalive_recv_exp =3D now + timeout; + + /* now that interval and timeout have been changed, kick + * off the worker so that the next delay can be recomputed + */ + mod_delayed_work(system_wq, &peer->ovpn->keepalive_work, 0); +} + +/** + * ovpn_peer_keepalive_send - periodic worker sending keepalive packets + * @work: pointer to the work member of the related peer object + * + * NOTE: the reference to peer is not dropped because it gets inherited + * by ovpn_xmit_special() + */ +static void ovpn_peer_keepalive_send(struct work_struct *work) +{ + struct ovpn_peer *peer =3D container_of(work, struct ovpn_peer, + keepalive_work); + + local_bh_disable(); + ovpn_xmit_special(peer, ovpn_keepalive_message, + sizeof(ovpn_keepalive_message)); + local_bh_enable(); +} + /** * ovpn_peer_new - allocate and initialize a new peer object * @ovpn: the openvpn instance inside which the peer should be created @@ -65,6 +111,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, = u32 id) kref_init(&peer->refcount); ovpn_peer_stats_init(&peer->vpn_stats); ovpn_peer_stats_init(&peer->link_stats); + INIT_WORK(&peer->keepalive_work, ovpn_peer_keepalive_send); =20 ret =3D dst_cache_init(&peer->dst_cache, GFP_KERNEL); if (ret < 0) { @@ -948,3 +995,162 @@ void ovpn_peers_free(struct ovpn_priv *ovpn, struct s= ock *sk, break; } } + +static time64_t ovpn_peer_keepalive_work_single(struct ovpn_peer *peer, + time64_t now, + struct llist_head *release_list) +{ + time64_t last_recv, last_sent, next_run1, next_run2; + unsigned long timeout, interval; + bool expired; + + spin_lock_bh(&peer->lock); + /* we expect both timers to be configured at the same time, + * therefore bail out if either is not set + */ + if (!peer->keepalive_timeout || !peer->keepalive_interval) { + spin_unlock_bh(&peer->lock); + return 0; + } + + /* check for peer timeout */ + expired =3D false; + timeout =3D peer->keepalive_timeout; + last_recv =3D READ_ONCE(peer->last_recv); + if (now < last_recv + timeout) { + peer->keepalive_recv_exp =3D last_recv + timeout; + next_run1 =3D peer->keepalive_recv_exp; + } else if (peer->keepalive_recv_exp > now) { + next_run1 =3D peer->keepalive_recv_exp; + } else { + expired =3D true; + } + + if (expired) { + /* peer is dead -> kill it and move on */ + spin_unlock_bh(&peer->lock); + netdev_dbg(peer->ovpn->dev, "peer %u expired\n", + peer->id); + ovpn_peer_remove(peer, OVPN_DEL_PEER_REASON_EXPIRED, + release_list); + return 0; + } + + /* check for peer keepalive */ + expired =3D false; + interval =3D peer->keepalive_interval; + last_sent =3D READ_ONCE(peer->last_sent); + if (now < last_sent + interval) { + peer->keepalive_xmit_exp =3D last_sent + interval; + next_run2 =3D peer->keepalive_xmit_exp; + } else if (peer->keepalive_xmit_exp > now) { + next_run2 =3D peer->keepalive_xmit_exp; + } else { + expired =3D true; + next_run2 =3D now + interval; + } + spin_unlock_bh(&peer->lock); + + if (expired) { + /* a keepalive packet is required */ + netdev_dbg(peer->ovpn->dev, + "sending keepalive to peer %u\n", + peer->id); + if (schedule_work(&peer->keepalive_work)) + ovpn_peer_hold(peer); + } + + if (next_run1 < next_run2) + return next_run1; + + return next_run2; +} + +static time64_t ovpn_peer_keepalive_work_mp(struct ovpn_priv *ovpn, + time64_t now, + struct llist_head *release_list) +{ + time64_t tmp_next_run, next_run =3D 0; + struct hlist_node *tmp; + struct ovpn_peer *peer; + int bkt; + + lockdep_assert_held(&ovpn->lock); + + hash_for_each_safe(ovpn->peers->by_id, bkt, tmp, peer, hash_entry_id) { + tmp_next_run =3D ovpn_peer_keepalive_work_single(peer, now, + release_list); + if (!tmp_next_run) + continue; + + /* the next worker run will be scheduled based on the shortest + * required interval across all peers + */ + if (!next_run || tmp_next_run < next_run) + next_run =3D tmp_next_run; + } + + return next_run; +} + +static time64_t ovpn_peer_keepalive_work_p2p(struct ovpn_priv *ovpn, + time64_t now, + struct llist_head *release_list) +{ + struct ovpn_peer *peer; + time64_t next_run =3D 0; + + lockdep_assert_held(&ovpn->lock); + + peer =3D rcu_dereference_protected(ovpn->peer, + lockdep_is_held(&ovpn->lock)); + if (peer) + next_run =3D ovpn_peer_keepalive_work_single(peer, now, + release_list); + + return next_run; +} + +/** + * ovpn_peer_keepalive_work - run keepalive logic on each known peer + * @work: pointer to the work member of the related ovpn object + * + * Each peer has two timers (if configured): + * 1. peer timeout: when no data is received for a certain interval, + * the peer is considered dead and it gets killed. + * 2. peer keepalive: when no data is sent to a certain peer for a + * certain interval, a special 'keepalive' packet is explicitly sent. + * + * This function iterates across the whole peer collection while + * checking the timers described above. + */ +void ovpn_peer_keepalive_work(struct work_struct *work) +{ + struct ovpn_priv *ovpn =3D container_of(work, struct ovpn_priv, + keepalive_work.work); + time64_t next_run =3D 0, now =3D ktime_get_real_seconds(); + LLIST_HEAD(release_list); + + spin_lock_bh(&ovpn->lock); + switch (ovpn->mode) { + case OVPN_MODE_MP: + next_run =3D ovpn_peer_keepalive_work_mp(ovpn, now, + &release_list); + break; + case OVPN_MODE_P2P: + next_run =3D ovpn_peer_keepalive_work_p2p(ovpn, now, + &release_list); + break; + } + + /* prevent rearming if the interface is being destroyed */ + if (next_run > 0 && + READ_ONCE(ovpn->dev->reg_state) =3D=3D NETREG_REGISTERED) { + netdev_dbg(ovpn->dev, + "scheduling keepalive work: now=3D%llu next_run=3D%llu delta=3D%llu\= n", + next_run, now, next_run - now); + schedule_delayed_work(&ovpn->keepalive_work, + (next_run - now) * HZ); + } + unlock_ovpn(ovpn, &release_list); +} diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 2a3b1031f58dd73925a9ed74aed7ac4cb7b8c4c5..e747c4b210642db990222986a80= bb37c9a0413fe 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -45,13 +45,20 @@ * @crypto: the crypto configuration (ciphers, keys, etc..) * @dst_cache: cache for dst_entry used to send to peer * @bind: remote peer binding + * @keepalive_interval: seconds after which a new keepalive should be sent + * @keepalive_xmit_exp: future timestamp when next keepalive should be sent + * @last_sent: timestamp of the last successfully sent packet + * @keepalive_timeout: seconds after which an inactive peer is considered = dead + * @keepalive_recv_exp: future timestamp when the peer should expire + * @last_recv: timestamp of the last authenticated received packet * @vpn_stats: per-peer in-VPN TX/RX stats * @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) + * @lock: protects binding to peer (bind) and keepalive* fields * @refcount: reference counter * @rcu: used to free peer in an RCU safe way * @release_entry: entry for the socket release list + * @keepalive_work: used to schedule keepalive sending */ struct ovpn_peer { struct ovpn_priv *ovpn; @@ -91,13 +98,20 @@ struct ovpn_peer { struct ovpn_crypto_state crypto; struct dst_cache dst_cache; struct ovpn_bind __rcu *bind; + unsigned long keepalive_interval; + unsigned long keepalive_xmit_exp; + time64_t last_sent; + unsigned long keepalive_timeout; + unsigned long keepalive_recv_exp; + time64_t last_recv; struct ovpn_peer_stats vpn_stats; struct ovpn_peer_stats link_stats; enum ovpn_del_peer_reason delete_reason; - spinlock_t lock; /* protects bind */ + spinlock_t lock; /* protects bind and keepalive* */ struct kref refcount; struct rcu_head rcu; struct llist_node release_entry; + struct work_struct keepalive_work; }; =20 /** @@ -136,4 +150,7 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv= *ovpn, bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, struct ovpn_peer *peer); =20 +void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 tim= eout); +void ovpn_peer_keepalive_work(struct work_struct *work); + #endif /* _NET_OVPN_OVPNPEER_H_ */ --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f49.google.com (mail-wm1-f49.google.com [209.85.128.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 73E8729B76B for ; Tue, 15 Apr 2025 11:17:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715875; cv=none; b=U9y3UizJ0OCbCv2lk3e89kStLTVQUGqF4uOd8CFjUzqRpw+UC0ReQoThC2MGzETDUPpguUxCxtOWxL9oH0bGQmCqrPZvglyXUtWLI3yJv8Gj4/ZOQ6IeRCfqYnbzaLh+YM5JwHDKcUvw8EKl+Bj+Y6Vj/XT0JW1zcPSniRHM84s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715875; c=relaxed/simple; bh=CbV/xZ4dPpOMFH1hmxEh/UbBsnp5CnSGduceaTrcwxY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Gl12Y4TdLGvR17ZQFTOerdxY9J7YBLuLv0ZCLAew6/EdeixMaXLFjZP35hhUrqpmZ8+CiGIoPBYirkygvOUL/9lOCJNsgdw3bHCRT0pS0jC/3f2GZyVjVooFF9mfZBDjXoVyuXd8fJaZOgYRBLUOO3qW+Yj3wdXb8pTi4buc4Lk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=EGIvjegM; arc=none smtp.client-ip=209.85.128.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="EGIvjegM" Received: by mail-wm1-f49.google.com with SMTP id 5b1f17b1804b1-43cf05f0c3eso38208755e9.0 for ; Tue, 15 Apr 2025 04:17:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715869; x=1745320669; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=5RapZUB36/dZTZq+EGc+NWJQg4ONmoLPBkwfzJPQWOE=; b=EGIvjegMb5amUCCc9U+06OmXlhzV8uegYgqw2o0frdZNSU+G89LURwgRi4bUp+NRs0 EpPRUWjLHu1OR74VvSLZXUhLWxSGN/dG9RuNflbD4KokucFJ35E5gxZDTYhg4GDZ+EIB Jo+VuvXyMBI8vaL7vXtR4ij94uA4iJc6kttCGJtkJEv5Eb04c/k2mSlDKKJFbYGRIF+P dnkSCqwXstDUWlCsuFbesG9s07n8DvHYF5g9Aniv/JeAFQZQQLz28M27V/pORsgSFRDA ZOFFBeEjATBubtrE1gDhemtNkuGQA2JCBWhWsCrKk0/MqhagvTp5PbzAYB4XYYCGBiTQ GQtQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715869; x=1745320669; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=5RapZUB36/dZTZq+EGc+NWJQg4ONmoLPBkwfzJPQWOE=; b=Wo/TxdVFfVO/VjcAPUfsV1k/f0ONM/GSV2zJ5Tm20Vr9crfUPRDfCbKj3JDXG6jHzB BtbNTdtNIK2EcS+l8j4suDETLAABlqTffWArtkhcpgecPNvNHcuzBkRWaq7ev7irVHFM 71y+wpQJJEYQCQtCMtr3Z6ThaVEBb+JpLVqo7DjQXOmaCfMtKbcrlOJnmmP/yGWIj53h 1MpjqKYnUiz22/qDGrnaKMBqEZPLJj3M2UNgfJWKK8T8e5tQnDiN7VO/SZjeQ5SYE3Ve GoX6QQS6s7f1VVEH3990U/648X7UOm/2rZpIkOLfZ+SqQbNjzl8pqNBEhrKqwkqSYNkS ooOA== X-Forwarded-Encrypted: i=1; AJvYcCVEUJQCYdrQUy00H/ipaqtJlIr6SU2rU8PU8TzmNcbeA6Qz/Oc3MLOvk4Chc4p3YLJOfAdfUbbiiVpxogs=@vger.kernel.org X-Gm-Message-State: AOJu0YyNPPgNf2vpPA5dYk56Re7UM0Nprob1TSQwvtnpJRgpoINsOE0d 6nctxmyH4RIx+vhW5HEtYvRGy74H0ZtLoT+Z2po7yGHltqGR1Z6AldUIZsaNT5L6XR6diHxdRuJ +F35XD+qLXY9Y6qfEI2wPCKpqyUBcvD0mAx0pZHmbTcGQrBoPZEs8EYs= X-Gm-Gg: ASbGncvAOVtQ4BfR7LjNRh98bOJiR2cmfYjlpVhfyc8dVH2m5Lw4W6IjSZYN4t7KeLk u+jZGDnkg00UmQbbSGYms/tDwUwtkhnj5eVwf1wVzZrX2YeAdGHeAkDSlx4bubZ6cU3sCgDqscU gLgvhtSlJwBdSu/pStJ83ajpFxqb2V9a78Y0MiS3XZGVAqmBpCd5sZJZtGhy8Ppgn8W/ZmIxMpL ETpa6UXgjd1MVMC4Fe1WrHQodDvO1TsTJz2A6YHHUW/8hox4KpYVfCjzeLAycwky/pTX27Jbkee H5SMTLj54QYUtxoS5BbkuAVecBrI5HwEA9OiQw== X-Google-Smtp-Source: AGHT+IHaZp/PexTrYRJfrN//iXIw+OEPmcI8CE7swUXMn6yPkR11sUk0Xv9P3U97cm1YdPvGivJEdA== X-Received: by 2002:a05:600c:49a4:b0:43d:77c5:9c1a with SMTP id 5b1f17b1804b1-43f42e0599emr87202445e9.4.1744715869381; Tue, 15 Apr 2025 04:17:49 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:48 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:34 +0200 Subject: [PATCH net-next v26 17/23] ovpn: add support for updating local or remote UDP endpoint Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-17-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=9204; i=antonio@openvpn.net; h=from:subject:message-id; bh=CbV/xZ4dPpOMFH1hmxEh/UbBsnp5CnSGduceaTrcwxY=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBCvivMAFBFB2iA6kl4Yy4WpuBaAszuSi1zk 7PhYz1koa+JATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQgAKCRALcOU6oDjV h5jBB/45IGlZyWK7spkVpt3TAETuK2aORexnPAbmdHleSTYlvoCzIzdeh4yYUjZ1YX7LZiTnDOG SlBfc6xc5i2oGpsrVZ1usxk2YiJbb4CCAoNZz/h2YIQGT/oiTMUu9V8lYCXJQdvtsadXMrC9Y0d P24XhDzFk6eoJxD+wKbZkqoHBiUDy1Q8vkDmoerEU1R2Te8UThYkMYyCF7XzhtoltXKbbSU1Y8w fPOEDwBd9muJlag3HRZqWcKo5Bp9axd+Q35fCoSCDjIJU5oh56TGC83cN66whLpjUDEKsmdqxtr 64uJv8hLuz8xKFCQzygA44ngpOmRXUoEfNodTt8G+/4XaP5q X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C In case of UDP links, the local or remote endpoint used to communicate with a given peer may change without a connection restart. Add support for learning the new address in case of change. Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/io.c | 8 ++ drivers/net/ovpn/peer.c | 213 ++++++++++++++++++++++++++++++++++++++++++++= +--- drivers/net/ovpn/peer.h | 2 + 3 files changed, 210 insertions(+), 13 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index a1816f72634bb32f5146ba0f5dcc5d1440b83792..fd8820c7d133707b7ba52462218= c428fdeca24d5 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -96,6 +96,7 @@ void ovpn_decrypt_post(void *data, int ret) struct ovpn_crypto_key_slot *ks; unsigned int payload_offset =3D 0; struct sk_buff *skb =3D data; + struct ovpn_socket *sock; struct ovpn_peer *peer; __be16 proto; __be32 *pid; @@ -131,6 +132,13 @@ void ovpn_decrypt_post(void *data, int ret) /* keep track of last received authenticated packet for keepalive */ WRITE_ONCE(peer->last_recv, ktime_get_real_seconds()); =20 + rcu_read_lock(); + sock =3D rcu_dereference(peer->sock); + if (sock && sock->sock->sk->sk_protocol =3D=3D IPPROTO_UDP) + /* check if this peer changed local or remote endpoint */ + ovpn_peer_endpoints_update(peer, skb); + rcu_read_unlock(); + /* point to encapsulated IP packet */ __skb_pull(skb, payload_offset); =20 diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 50514736a4327c96d381805f0870d89fe1193ab1..10a6fd3e6ecd0306072053d51a5= a195543069223 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -127,6 +127,206 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovp= n, u32 id) return peer; } =20 +/** + * ovpn_peer_reset_sockaddr - recreate binding for peer + * @peer: peer to recreate the binding for + * @ss: sockaddr to use as remote endpoint for the binding + * @local_ip: local IP for the binding + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, + const struct sockaddr_storage *ss, + const void *local_ip) +{ + struct ovpn_bind *bind; + size_t ip_len; + + lockdep_assert_held(&peer->lock); + + /* create new ovpn_bind object */ + bind =3D ovpn_bind_from_sockaddr(ss); + if (IS_ERR(bind)) + return PTR_ERR(bind); + + if (ss->ss_family =3D=3D AF_INET) { + ip_len =3D sizeof(struct in_addr); + } else if (ss->ss_family =3D=3D AF_INET6) { + ip_len =3D sizeof(struct in6_addr); + } else { + net_dbg_ratelimited("%s: invalid family %u for remote endpoint for peer = %u\n", + netdev_name(peer->ovpn->dev), + ss->ss_family, peer->id); + kfree(bind); + return -EINVAL; + } + + memcpy(&bind->local, local_ip, ip_len); + + /* set binding */ + ovpn_bind_reset(peer, bind); + + return 0; +} + +/* variable name __tbl2 needs to be different from __tbl1 + * in the macro below to avoid confusing clang + */ +#define ovpn_get_hash_slot(_tbl, _key, _key_len) ({ \ + typeof(_tbl) *__tbl2 =3D &(_tbl); \ + jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl2); \ +}) + +#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \ + typeof(_tbl) *__tbl1 =3D &(_tbl); \ + &(*__tbl1)[ovpn_get_hash_slot(*__tbl1, _key, _key_len)];\ +}) + +/** + * ovpn_peer_endpoints_update - update remote or local endpoint for peer + * @peer: peer to update the remote endpoint for + * @skb: incoming packet to retrieve the source/destination address from + */ +void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *sk= b) +{ + struct hlist_nulls_head *nhead; + struct sockaddr_storage ss; + struct sockaddr_in6 *sa6; + bool reset_cache =3D false; + struct sockaddr_in *sa; + struct ovpn_bind *bind; + const void *local_ip; + size_t salen =3D 0; + + spin_lock_bh(&peer->lock); + bind =3D rcu_dereference_protected(peer->bind, + lockdep_is_held(&peer->lock)); + if (unlikely(!bind)) + goto unlock; + + switch (skb->protocol) { + case htons(ETH_P_IP): + /* float check */ + if (unlikely(!ovpn_bind_skb_src_match(bind, skb))) { + /* unconditionally save local endpoint in case + * of float, as it may have changed as well + */ + local_ip =3D &ip_hdr(skb)->daddr; + sa =3D (struct sockaddr_in *)&ss; + sa->sin_family =3D AF_INET; + sa->sin_addr.s_addr =3D ip_hdr(skb)->saddr; + sa->sin_port =3D udp_hdr(skb)->source; + salen =3D sizeof(*sa); + reset_cache =3D true; + break; + } + + /* if no float happened, let's double check if the local endpoint + * has changed + */ + if (unlikely(bind->local.ipv4.s_addr !=3D ip_hdr(skb)->daddr)) { + net_dbg_ratelimited("%s: learning local IPv4 for peer %d (%pI4 -> %pI4)= \n", + netdev_name(peer->ovpn->dev), + peer->id, &bind->local.ipv4.s_addr, + &ip_hdr(skb)->daddr); + bind->local.ipv4.s_addr =3D ip_hdr(skb)->daddr; + reset_cache =3D true; + } + break; + case htons(ETH_P_IPV6): + /* float check */ + if (unlikely(!ovpn_bind_skb_src_match(bind, skb))) { + /* unconditionally save local endpoint in case + * of float, as it may have changed as well + */ + local_ip =3D &ipv6_hdr(skb)->daddr; + sa6 =3D (struct sockaddr_in6 *)&ss; + sa6->sin6_family =3D AF_INET6; + sa6->sin6_addr =3D ipv6_hdr(skb)->saddr; + sa6->sin6_port =3D udp_hdr(skb)->source; + sa6->sin6_scope_id =3D ipv6_iface_scope_id(&ipv6_hdr(skb)->saddr, + skb->skb_iif); + salen =3D sizeof(*sa6); + reset_cache =3D true; + break; + } + + /* if no float happened, let's double check if the local endpoint + * has changed + */ + if (unlikely(!ipv6_addr_equal(&bind->local.ipv6, + &ipv6_hdr(skb)->daddr))) { + net_dbg_ratelimited("%s: learning local IPv6 for peer %d (%pI6c -> %pI6= c\n", + netdev_name(peer->ovpn->dev), + peer->id, &bind->local.ipv6, + &ipv6_hdr(skb)->daddr); + bind->local.ipv6 =3D ipv6_hdr(skb)->daddr; + reset_cache =3D true; + } + break; + default: + goto unlock; + } + + if (unlikely(reset_cache)) + dst_cache_reset(&peer->dst_cache); + + /* if the peer did not float, we can bail out now */ + if (likely(!salen)) + goto unlock; + + if (unlikely(ovpn_peer_reset_sockaddr(peer, + (struct sockaddr_storage *)&ss, + local_ip) < 0)) + goto unlock; + + net_dbg_ratelimited("%s: peer %d floated to %pIScp", + netdev_name(peer->ovpn->dev), peer->id, &ss); + + spin_unlock_bh(&peer->lock); + + /* rehashing is required only in MP mode as P2P has one peer + * only and thus there is no hashtable + */ + if (peer->ovpn->mode =3D=3D OVPN_MODE_MP) { + spin_lock_bh(&peer->ovpn->lock); + spin_lock_bh(&peer->lock); + bind =3D rcu_dereference_protected(peer->bind, + lockdep_is_held(&peer->lock)); + if (unlikely(!bind)) { + spin_unlock_bh(&peer->lock); + spin_unlock_bh(&peer->ovpn->lock); + return; + } + + /* This function may be invoked concurrently, therefore another + * float may have happened in parallel: perform rehashing + * using the peer->bind->remote directly as key + */ + + switch (bind->remote.in4.sin_family) { + case AF_INET: + salen =3D sizeof(*sa); + break; + case AF_INET6: + salen =3D sizeof(*sa6); + break; + } + + /* remove old hashing */ + hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr); + /* re-add with new transport address */ + nhead =3D ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr, + &bind->remote, salen); + hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead); + spin_unlock_bh(&peer->lock); + spin_unlock_bh(&peer->ovpn->lock); + } + return; +unlock: + spin_unlock_bh(&peer->lock); +} + /** * ovpn_peer_release_rcu - RCU callback performing last peer release steps * @head: RCU member of the ovpn_peer @@ -230,19 +430,6 @@ static struct in6_addr ovpn_nexthop_from_skb6(struct s= k_buff *skb) return rt->rt6i_gateway; } =20 -/* variable name __tbl2 needs to be different from __tbl1 - * in the macro below to avoid confusing clang - */ -#define ovpn_get_hash_slot(_tbl, _key, _key_len) ({ \ - typeof(_tbl) *__tbl2 =3D &(_tbl); \ - jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl2); \ -}) - -#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \ - typeof(_tbl) *__tbl1 =3D &(_tbl); \ - &(*__tbl1)[ovpn_get_hash_slot(*__tbl1, _key, _key_len)];\ -}) - /** * ovpn_peer_get_by_vpn_addr4 - retrieve peer by its VPN IPv4 address * @ovpn: the openvpn instance to search diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index e747c4b210642db990222986a80bb37c9a0413fe..f1288734ff100ee76b0c41ebb6d= c71725ea33261 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -153,4 +153,6 @@ bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, str= uct sk_buff *skb, void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 tim= eout); void ovpn_peer_keepalive_work(struct work_struct *work); =20 +void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *sk= b); + #endif /* _NET_OVPN_OVPNPEER_H_ */ --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AC9EE29B795 for ; Tue, 15 Apr 2025 11:17:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715878; cv=none; b=mogaUXbJvav4e4IpgDNz4dzSNerwAHb+n3HLwqdX8ltf9FRNJSsnPafi70zTAg+1pSx2ISrdcxggkpq9uIuIOiEY1rO3lM4LXDDqdiCDcvTrZsuQd9kgO4teP/R8DtB6upjrhUYmur8JH/uc7MY5VviYVIs36+UsXDdLBKIbeuQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715878; c=relaxed/simple; bh=FMh55rBun0mQDo3vsCYfcGp6JsEV2a+N+XLBGQAlCaQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Q1RPPMf84CrUtnb1K6SZyIYB5NY8HmCB9+yy9hWoWUgcHSmb1uR8x3ZNOIv3BLTAhWrJgZkkKbeo+rZOyZNDDzedvBvWOgkDLf48rWsIFONtim4sLNSaI0J7BGNLiDZaGpqYeDBIOSosy3QDWkLH+KX5yF7b3a6b99ZJ31YnFnI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=D9wGoCmc; arc=none smtp.client-ip=209.85.128.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="D9wGoCmc" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-43cfe574976so37113125e9.1 for ; Tue, 15 Apr 2025 04:17:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715871; x=1745320671; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=pj8nUplhYIj9I7Taqpyu2yhpVZ0f+jGbbXyib7HYCmQ=; b=D9wGoCmcjkzitVV2hlvjFLUXPP7kixKrMMSId3o0pPI+6ZxMLQj5B5rCg0ueEJ1zFR PxEvGskz7yVC0TsZ6BifohiSSkWVH7A8p7meQy2s+gK4Pko03jOaiKLFud1l8kL8WRVd XunMGL1JzNmMcaW64u5tKPbo5CfssbT1EM/p5c/14OUxzZrY3fTlgWWG6SjJXVMEHU8r ne56Dk2y0x65ejK0SxtEs+qNil2NnSld0LrUvXDA+6GvPLZLxh67IKujbr/y7UNW1Ga/ nGB2k9Es9EpB6kkEOAs5NOik8Jet8HiOQIZYsK2p1Fiw4V1gLUGN3fYn2k3Hed2NjfEx DWzA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715871; x=1745320671; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=pj8nUplhYIj9I7Taqpyu2yhpVZ0f+jGbbXyib7HYCmQ=; b=pmYOLt+A6VUZCYlAoTTiwQ93u8U6fMQ8xXqIp+aUBWVQKy2jfOy5rs1E+ImOIbP0hk 06KVMWWmnZOtGRPo1ErQROM71oFhs4O21hqHwq0qVlkveEZqYWwzJrbXBFOHDpPz/zdY ehK1WCNxVbUW2RVS414WzmJdoekIm/7qlKjSs8sqPykl5jKXVgd3aVw3zYbc596CMh9f nB0fU/g9BIUY7GMhFbvqt/zgi9SKiK+UjPDtbh6+cySP9QNwxPu8HevOnY5orX1TYta8 uM1bV8M/CaBW2SE+MeKt9My+YGXwfmbgTFXs+RNK3ZDLJ+VwR13uUYPUldqKhYAgnVh+ pqzA== X-Forwarded-Encrypted: i=1; AJvYcCW+DstKcAbcMJMAVZ6DGlqALFRmLxadycVMX1Mh+NjKnqlKjR8crJYJqmHQoowWItZu8+XB/58xExQOYqQ=@vger.kernel.org X-Gm-Message-State: AOJu0YySbv0BFje5kgjW45EJ/9p1R1R7iK9qS5/jKVh1lCEXXnKT/9zJ +3A+a3xy4vGaPapxR7Gfj/ThgxGocxzZQlg+Y8N75yzNIyZPcKscZVLNlrj8EgGJopz6gyj4xCh +ihSGA6Ttw5rp4LBalkQXUplQIL2qupXsbzqDaT+R6r5aCi5c8mPuaEI= X-Gm-Gg: ASbGncsR2BYGWhlZw74C5kvA8w8Yknoxmz97OARfsTiIa/iyUn0B7V5qMEH5iSob/6/ 7yE8YaShikFO/KEZl5gtndXbxZ2E8N7vJRoTsb11my/OQU5q9f5FDSOZ8YfSuRIXM4DbFqDa6K9 F8BLiKDOlTrYICA408FNMh/wu0gjwIqBg/8BRJopp3fkw5JYQgm7o04ICjggpDktoyb4QLPUpvo uRXGgxN4q8hSrNcXkAlvQ/7zOA6CsBxVzeFGvzJUW/fI0JUVhPvktkS/GnHs3AcbZ8bvjwHh+gb lo8rU81cBDxNOmnyOUMQOT8uYm3D9Q5MUEqLDw== X-Google-Smtp-Source: AGHT+IE28SkgmY2tRziujtF++BH3UyqpBQrMnPvGUHPstnAGLNrwr/SES6EoSEFPbc5LfXXXClHgrw== X-Received: by 2002:a05:600c:294:b0:43b:c0fa:f9dd with SMTP id 5b1f17b1804b1-43f3d15d62fmr84121065e9.25.1744715870653; Tue, 15 Apr 2025 04:17:50 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:50 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:35 +0200 Subject: [PATCH net-next v26 18/23] ovpn: implement peer add/get/dump/delete via netlink Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-18-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=27886; i=antonio@openvpn.net; h=from:subject:message-id; bh=FMh55rBun0mQDo3vsCYfcGp6JsEV2a+N+XLBGQAlCaQ=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBCEySK2Ru+c01KlVsZSH0B+A3yDHab+q/8G /gGAbRHV4SJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQgAKCRALcOU6oDjV h0zGB/sFG4vtM/1eHDs5gQplh7yoGZn4dMQb7g6mXq/hCHb+bETNwzrbx7RATKqGBEdFAbmnrRO hh9KaCnYpqV2U+HSoigN2I5aovTtgmQr29IyZOgu5o2/HmAWvNJ6N4L8u2U3bLaPGKaewOYrvYc +gMPEV+auuy/wjctNOSVp8In1ugBIToPUxu8Vq2oiYN4qyP8rPJC3qqYu6KvKT+U0A6LRgpppok PB9Ln/sBWO3xA1d1UxZ3Mv6cA48kFi0HDlNRAOYuv8MNyz3iqCD+ngkmP/W0fltStKNRTNQM5wo XFaWqsrBmSCRHZxcAPcm5dXoyp37A9cDxzWuZH37JDcCqYQO X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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 Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/netlink.c | 686 +++++++++++++++++++++++++++++++++++++++++= +++- drivers/net/ovpn/peer.c | 79 ++++-- drivers/net/ovpn/peer.h | 5 + drivers/net/ovpn/socket.c | 4 +- 4 files changed, 738 insertions(+), 36 deletions(-) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 8d267d4c82283d9b5f989478102086ce385195d5..5653a588a47c6c94ff10e042f4f= 6d32bd8205e58 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -7,6 +7,7 @@ */ =20 #include +#include #include =20 #include @@ -15,6 +16,9 @@ #include "main.h" #include "netlink.h" #include "netlink-gen.h" +#include "bind.h" +#include "peer.h" +#include "socket.h" =20 MODULE_ALIAS_GENL_FAMILY(OVPN_FAMILY_NAME); =20 @@ -89,29 +93,701 @@ void ovpn_nl_post_doit(const struct genl_split_ops *op= s, struct sk_buff *skb, netdev_put(ovpn->dev, tracker); } =20 +static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs, + struct sockaddr_storage *ss) +{ + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + struct in6_addr *in6; + __be16 port =3D 0; + __be32 *in; + + ss->ss_family =3D AF_UNSPEC; + + if (attrs[OVPN_A_PEER_REMOTE_PORT]) + port =3D nla_get_be16(attrs[OVPN_A_PEER_REMOTE_PORT]); + + if (attrs[OVPN_A_PEER_REMOTE_IPV4]) { + ss->ss_family =3D AF_INET; + in =3D nla_data(attrs[OVPN_A_PEER_REMOTE_IPV4]); + } else if (attrs[OVPN_A_PEER_REMOTE_IPV6]) { + ss->ss_family =3D AF_INET6; + in6 =3D nla_data(attrs[OVPN_A_PEER_REMOTE_IPV6]); + } else { + return false; + } + + switch (ss->ss_family) { + case AF_INET6: + /* If this is a regular IPv6 just break and move on, + * otherwise switch to AF_INET and extract the IPv4 accordingly + */ + if (!ipv6_addr_v4mapped(in6)) { + sin6 =3D (struct sockaddr_in6 *)ss; + sin6->sin6_port =3D port; + memcpy(&sin6->sin6_addr, in6, sizeof(*in6)); + break; + } + + /* v4-mapped-v6 address */ + ss->ss_family =3D AF_INET; + in =3D &in6->s6_addr32[3]; + fallthrough; + case AF_INET: + sin =3D (struct sockaddr_in *)ss; + sin->sin_port =3D port; + sin->sin_addr.s_addr =3D *in; + break; + } + + return true; +} + +static u8 *ovpn_nl_attr_local_ip(struct nlattr **attrs) +{ + u8 *addr6; + + if (!attrs[OVPN_A_PEER_LOCAL_IPV4] && !attrs[OVPN_A_PEER_LOCAL_IPV6]) + return NULL; + + if (attrs[OVPN_A_PEER_LOCAL_IPV4]) + return nla_data(attrs[OVPN_A_PEER_LOCAL_IPV4]); + + addr6 =3D nla_data(attrs[OVPN_A_PEER_LOCAL_IPV6]); + /* this is an IPv4-mapped IPv6 address, therefore extract the actual + * v4 address from the last 4 bytes + */ + if (ipv6_addr_v4mapped((struct in6_addr *)addr6)) + return addr6 + 12; + + return addr6; +} + +static sa_family_t ovpn_nl_family_get(struct nlattr *addr4, + struct nlattr *addr6) +{ + if (addr4) + return AF_INET; + + if (addr6) { + if (ipv6_addr_v4mapped((struct in6_addr *)nla_data(addr6))) + return AF_INET; + return AF_INET6; + } + + return AF_UNSPEC; +} + +static int ovpn_nl_peer_precheck(struct ovpn_priv *ovpn, + struct genl_info *info, + struct nlattr **attrs) +{ + sa_family_t local_fam, remote_fam; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs, + OVPN_A_PEER_ID)) + return -EINVAL; + + if (attrs[OVPN_A_PEER_REMOTE_IPV4] && attrs[OVPN_A_PEER_REMOTE_IPV6]) { + NL_SET_ERR_MSG_MOD(info->extack, + "cannot specify both remote IPv4 or IPv6 address"); + return -EINVAL; + } + + if (!attrs[OVPN_A_PEER_REMOTE_IPV4] && + !attrs[OVPN_A_PEER_REMOTE_IPV6] && attrs[OVPN_A_PEER_REMOTE_PORT]) { + NL_SET_ERR_MSG_MOD(info->extack, + "cannot specify remote port without IP address"); + return -EINVAL; + } + + if ((attrs[OVPN_A_PEER_REMOTE_IPV4] || + attrs[OVPN_A_PEER_REMOTE_IPV6]) && + !attrs[OVPN_A_PEER_REMOTE_PORT]) { + NL_SET_ERR_MSG_MOD(info->extack, + "cannot specify remote IP address without port"); + return -EINVAL; + } + + if (!attrs[OVPN_A_PEER_REMOTE_IPV4] && + attrs[OVPN_A_PEER_LOCAL_IPV4]) { + NL_SET_ERR_MSG_MOD(info->extack, + "cannot specify local IPv4 address without remote"); + return -EINVAL; + } + + if (!attrs[OVPN_A_PEER_REMOTE_IPV6] && + attrs[OVPN_A_PEER_LOCAL_IPV6]) { + NL_SET_ERR_MSG_MOD(info->extack, + "cannot specify local IPV6 address without remote"); + return -EINVAL; + } + + /* check that local and remote address families are the same even + * after parsing v4mapped IPv6 addresses. + * (if addresses are not provided, family will be AF_UNSPEC and + * the check is skipped) + */ + local_fam =3D ovpn_nl_family_get(attrs[OVPN_A_PEER_LOCAL_IPV4], + attrs[OVPN_A_PEER_LOCAL_IPV6]); + remote_fam =3D ovpn_nl_family_get(attrs[OVPN_A_PEER_REMOTE_IPV4], + attrs[OVPN_A_PEER_REMOTE_IPV6]); + if (local_fam !=3D AF_UNSPEC && remote_fam !=3D AF_UNSPEC && + local_fam !=3D remote_fam) { + NL_SET_ERR_MSG_MOD(info->extack, + "mismatching local and remote address families"); + return -EINVAL; + } + + if (remote_fam !=3D AF_INET6 && attrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]) { + NL_SET_ERR_MSG_MOD(info->extack, + "cannot specify scope id without remote IPv6 address"); + return -EINVAL; + } + + /* VPN IPs are needed only in MP mode for selecting the right peer */ + if (ovpn->mode =3D=3D OVPN_MODE_P2P && (attrs[OVPN_A_PEER_VPN_IPV4] || + attrs[OVPN_A_PEER_VPN_IPV6])) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "unexpected VPN IP in P2P mode"); + return -EINVAL; + } + + if ((attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL] && + !attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]) || + (!attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL] && + attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT])) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "keepalive interval and timeout are required together"); + return -EINVAL; + } + + return 0; +} + +/** + * ovpn_nl_peer_modify - modify the peer attributes according to the incom= ing msg + * @peer: the peer to modify + * @info: generic netlink info from the user request + * @attrs: the attributes from the user request + * + * Return: a negative error code in case of failure, 0 on success or 1 on + * success and the VPN IPs have been modified (requires rehashing in MP + * mode) + */ +static int ovpn_nl_peer_modify(struct ovpn_peer *peer, struct genl_info *i= nfo, + struct nlattr **attrs) +{ + struct sockaddr_storage ss =3D {}; + void *local_ip =3D NULL; + u32 interv, timeout; + bool rehash =3D false; + int ret; + + spin_lock_bh(&peer->lock); + + if (ovpn_nl_attr_sockaddr_remote(attrs, &ss)) { + /* we carry the local IP in a generic container. + * ovpn_peer_reset_sockaddr() will properly interpret it + * based on ss.ss_family + */ + local_ip =3D ovpn_nl_attr_local_ip(attrs); + + /* set peer sockaddr */ + ret =3D 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 err_unlock; + } + dst_cache_reset(&peer->dst_cache); + } + + if (attrs[OVPN_A_PEER_VPN_IPV4]) { + rehash =3D true; + peer->vpn_addrs.ipv4.s_addr =3D + nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]); + } + + if (attrs[OVPN_A_PEER_VPN_IPV6]) { + rehash =3D true; + peer->vpn_addrs.ipv6 =3D + 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]) { + interv =3D nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]); + timeout =3D nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]); + ovpn_peer_keepalive_set(peer, interv, timeout); + } + + netdev_dbg(peer->ovpn->dev, + "modify peer id=3D%u endpoint=3D%pIScp VPN-IPv4=3D%pI4 VPN-IPv6=3D%pI= 6c\n", + peer->id, &ss, + &peer->vpn_addrs.ipv4.s_addr, &peer->vpn_addrs.ipv6); + + spin_unlock_bh(&peer->lock); + + return rehash ? 1 : 0; +err_unlock: + spin_unlock_bh(&peer->lock); + return ret; +} + int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct ovpn_priv *ovpn =3D info->user_ptr[0]; + struct ovpn_socket *ovpn_sock; + struct socket *sock =3D NULL; + struct ovpn_peer *peer; + u32 sockfd, peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER)) + return -EINVAL; + + ret =3D nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER], + ovpn_peer_nl_policy, info->extack); + if (ret) + return ret; + + ret =3D ovpn_nl_peer_precheck(ovpn, info, attrs); + if (ret < 0) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs, + OVPN_A_PEER_SOCKET)) + return -EINVAL; + + /* in MP mode VPN IPs are required for selecting the right peer */ + if (ovpn->mode =3D=3D OVPN_MODE_MP && !attrs[OVPN_A_PEER_VPN_IPV4] && + !attrs[OVPN_A_PEER_VPN_IPV6]) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "VPN IP must be provided in MP mode"); + return -EINVAL; + } + + peer_id =3D nla_get_u32(attrs[OVPN_A_PEER_ID]); + peer =3D ovpn_peer_new(ovpn, peer_id); + if (IS_ERR(peer)) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot create new peer object for peer %u: %ld", + peer_id, PTR_ERR(peer)); + return PTR_ERR(peer); + } + + /* lookup the fd in the kernel table and extract the socket object */ + sockfd =3D nla_get_u32(attrs[OVPN_A_PEER_SOCKET]); + /* sockfd_lookup() increases sock's refcounter */ + sock =3D sockfd_lookup(sockfd, &ret); + if (!sock) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot lookup peer socket (fd=3D%u): %d", + sockfd, ret); + ret =3D -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. + */ + if (sock->sk->sk_protocol =3D=3D IPPROTO_UDP && + !attrs[OVPN_A_PEER_REMOTE_IPV4] && + !attrs[OVPN_A_PEER_REMOTE_IPV6]) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "missing remote IP address for UDP socket"); + sockfd_put(sock); + ret =3D -EINVAL; + goto peer_release; + } + + /* 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 (sock->sk->sk_protocol =3D=3D IPPROTO_TCP && + (attrs[OVPN_A_PEER_REMOTE_IPV4] || + attrs[OVPN_A_PEER_REMOTE_IPV6])) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "unexpected remote IP address with TCP socket"); + sockfd_put(sock); + ret =3D -EINVAL; + goto peer_release; + } + + ovpn_sock =3D ovpn_socket_new(sock, peer); + /* at this point we unconditionally drop the reference to the socket: + * - in case of error, the socket has to be dropped + * - if case of success, the socket is configured and let + * userspace own the reference, so that the latter can + * trigger the final close() + */ + sockfd_put(sock); + if (IS_ERR(ovpn_sock)) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot encapsulate socket: %ld", + PTR_ERR(ovpn_sock)); + ret =3D -ENOTSOCK; + goto peer_release; + } + + rcu_assign_pointer(peer->sock, ovpn_sock); + + ret =3D ovpn_nl_peer_modify(peer, info, attrs); + if (ret < 0) + goto sock_release; + + ret =3D ovpn_peer_add(ovpn, peer); + if (ret < 0) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot add new peer (id=3D%u) to hashtable: %d", + peer->id, ret); + goto sock_release; + } + + return 0; + +sock_release: + ovpn_socket_release(peer); +peer_release: + /* release right away because peer was not yet hashed, thus it is not + * used in any context + */ + ovpn_peer_release(peer); + + return ret; } =20 int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct ovpn_priv *ovpn =3D info->user_ptr[0]; + struct ovpn_socket *sock; + struct ovpn_peer *peer; + u32 peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER)) + return -EINVAL; + + ret =3D nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER], + ovpn_peer_nl_policy, info->extack); + if (ret) + return ret; + + ret =3D ovpn_nl_peer_precheck(ovpn, info, attrs); + if (ret < 0) + return ret; + + if (attrs[OVPN_A_PEER_SOCKET]) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "socket cannot be modified"); + return -EINVAL; + } + + peer_id =3D nla_get_u32(attrs[OVPN_A_PEER_ID]); + peer =3D 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; + } + + /* when using a TCP socket the remote IP is not expected */ + rcu_read_lock(); + sock =3D rcu_dereference(peer->sock); + if (sock && sock->sock->sk->sk_protocol =3D=3D IPPROTO_TCP && + (attrs[OVPN_A_PEER_REMOTE_IPV4] || + attrs[OVPN_A_PEER_REMOTE_IPV6])) { + rcu_read_unlock(); + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "unexpected remote IP address with TCP socket"); + ovpn_peer_put(peer); + return -EINVAL; + } + rcu_read_unlock(); + + spin_lock_bh(&ovpn->lock); + ret =3D ovpn_nl_peer_modify(peer, info, attrs); + if (ret < 0) { + spin_unlock_bh(&ovpn->lock); + ovpn_peer_put(peer); + return ret; + } + + /* ret =3D=3D 1 means that VPN IPv4/6 has been modified and rehashing + * is required + */ + if (ret > 0) + ovpn_peer_hash_vpn_ip(peer); + spin_unlock_bh(&ovpn->lock); + ovpn_peer_put(peer); + + return 0; +} + +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 ovpn_socket *sock; + int ret =3D -EMSGSIZE; + struct nlattr *attr; + __be16 local_port; + void *hdr; + int id; + + hdr =3D genlmsg_put(skb, portid, seq, &ovpn_nl_family, flags, + OVPN_CMD_PEER_GET); + if (!hdr) + return -ENOBUFS; + + attr =3D nla_nest_start(skb, OVPN_A_PEER); + if (!attr) + goto err; + + rcu_read_lock(); + sock =3D rcu_dereference(peer->sock); + if (!sock) { + ret =3D -EINVAL; + goto err_unlock; + } + + if (!net_eq(genl_info_net(info), sock_net(sock->sock->sk))) { + id =3D peernet2id_alloc(genl_info_net(info), + sock_net(sock->sock->sk), + GFP_ATOMIC); + if (nla_put_s32(skb, OVPN_A_PEER_SOCKET_NETNSID, id)) + goto err_unlock; + } + local_port =3D inet_sk(sock->sock->sk)->inet_sport; + rcu_read_unlock(); + + if (nla_put_u32(skb, OVPN_A_PEER_ID, peer->id)) + goto err; + + if (peer->vpn_addrs.ipv4.s_addr !=3D 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 =3D rcu_dereference(peer->bind); + if (bind) { + if (bind->remote.in4.sin_family =3D=3D AF_INET) { + if (nla_put_in_addr(skb, OVPN_A_PEER_REMOTE_IPV4, + bind->remote.in4.sin_addr.s_addr) || + nla_put_net16(skb, OVPN_A_PEER_REMOTE_PORT, + bind->remote.in4.sin_port) || + nla_put_in_addr(skb, OVPN_A_PEER_LOCAL_IPV4, + bind->local.ipv4.s_addr)) + goto err_unlock; + } else if (bind->remote.in4.sin_family =3D=3D AF_INET6) { + if (nla_put_in6_addr(skb, OVPN_A_PEER_REMOTE_IPV6, + &bind->remote.in6.sin6_addr) || + nla_put_u32(skb, OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID, + bind->remote.in6.sin6_scope_id) || + nla_put_net16(skb, OVPN_A_PEER_REMOTE_PORT, + bind->remote.in6.sin6_port) || + nla_put_in6_addr(skb, OVPN_A_PEER_LOCAL_IPV6, + &bind->local.ipv6)) + goto err_unlock; + } + } + rcu_read_unlock(); + + if (nla_put_net16(skb, OVPN_A_PEER_LOCAL_PORT, local_port) || + /* 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 ret; } =20 int ovpn_nl_peer_get_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct ovpn_priv *ovpn =3D 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 =3D 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 =3D nla_get_u32(attrs[OVPN_A_PEER_ID]); + peer =3D 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 =3D nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) { + ret =3D -ENOMEM; + goto err; + } + + ret =3D ovpn_nl_send_peer(msg, info, peer, info->snd_portid, + info->snd_seq, 0); + if (ret < 0) { + nlmsg_free(msg); + goto err; + } + + ret =3D genlmsg_reply(msg, info); +err: + ovpn_peer_put(peer); + return ret; } =20 int ovpn_nl_peer_get_dumpit(struct sk_buff *skb, struct netlink_callback *= cb) { - return -EOPNOTSUPP; + const struct genl_info *info =3D genl_info_dump(cb); + int bkt, last_idx =3D cb->args[1], dumped =3D 0; + netdevice_tracker tracker; + struct ovpn_priv *ovpn; + struct ovpn_peer *peer; + + ovpn =3D ovpn_get_dev_from_attrs(sock_net(cb->skb->sk), info, &tracker); + if (IS_ERR(ovpn)) + return PTR_ERR(ovpn); + + if (ovpn->mode =3D=3D OVPN_MODE_P2P) { + /* if we already dumped a peer it means we are done */ + if (last_idx) + goto out; + + rcu_read_lock(); + peer =3D 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) =3D=3D 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(ovpn->dev, &tracker); + + /* sum up peers dumped in this message, so that at the next invocation + * we can continue from where we left + */ + cb->args[1] +=3D dumped; + return skb->len; } =20 int ovpn_nl_peer_del_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct ovpn_priv *ovpn =3D 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 =3D 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 =3D nla_get_u32(attrs[OVPN_A_PEER_ID]); + peer =3D 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; + } + + netdev_dbg(ovpn->dev, "del peer %u\n", peer->id); + ret =3D ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_USERSPACE); + ovpn_peer_put(peer); + + return ret; } =20 int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 10a6fd3e6ecd0306072053d51a5a195543069223..0469afd6da238d795b63b9e68c8= c728fa324de98 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -135,9 +135,9 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *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 void *local_ip) +int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, + const struct sockaddr_storage *ss, + const void *local_ip) { struct ovpn_bind *bind; size_t ip_len; @@ -149,19 +149,21 @@ static int ovpn_peer_reset_sockaddr(struct ovpn_peer = *peer, if (IS_ERR(bind)) return PTR_ERR(bind); =20 - if (ss->ss_family =3D=3D AF_INET) { - ip_len =3D sizeof(struct in_addr); - } else if (ss->ss_family =3D=3D AF_INET6) { - ip_len =3D sizeof(struct in6_addr); - } else { - net_dbg_ratelimited("%s: invalid family %u for remote endpoint for peer = %u\n", - netdev_name(peer->ovpn->dev), - ss->ss_family, peer->id); - kfree(bind); - return -EINVAL; - } + if (local_ip) { + if (ss->ss_family =3D=3D AF_INET) { + ip_len =3D sizeof(struct in_addr); + } else if (ss->ss_family =3D=3D AF_INET6) { + ip_len =3D sizeof(struct in6_addr); + } else { + net_dbg_ratelimited("%s: invalid family %u for remote endpoint for peer= %u\n", + netdev_name(peer->ovpn->dev), + ss->ss_family, peer->id); + kfree(bind); + return -EINVAL; + } =20 - memcpy(&bind->local, local_ip, ip_len); + memcpy(&bind->local, local_ip, ip_len); + } =20 /* set binding */ ovpn_bind_reset(peer, bind); @@ -346,7 +348,7 @@ static void ovpn_peer_release_rcu(struct rcu_head *head) * 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) { ovpn_crypto_state_release(&peer->crypto); spin_lock_bh(&peer->lock); @@ -887,6 +889,37 @@ bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, st= ruct sk_buff *skb, return match; } =20 +void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) +{ + struct hlist_nulls_head *nhead; + + lockdep_assert_held(&peer->ovpn->lock); + + /* rehashing makes sense only in multipeer mode */ + if (peer->ovpn->mode !=3D OVPN_MODE_MP) + return; + + if (peer->vpn_addrs.ipv4.s_addr !=3D htonl(INADDR_ANY)) { + /* remove potential old hashing */ + hlist_nulls_del_init_rcu(&peer->hash_entry_addr4); + + nhead =3D ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr4, + &peer->vpn_addrs.ipv4, + sizeof(peer->vpn_addrs.ipv4)); + hlist_nulls_add_head_rcu(&peer->hash_entry_addr4, nhead); + } + + if (!ipv6_addr_any(&peer->vpn_addrs.ipv6)) { + /* remove potential old hashing */ + hlist_nulls_del_init_rcu(&peer->hash_entry_addr6); + + nhead =3D ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr6, + &peer->vpn_addrs.ipv6, + sizeof(peer->vpn_addrs.ipv6)); + hlist_nulls_add_head_rcu(&peer->hash_entry_addr6, nhead); + } +} + /** * ovpn_peer_add_mp - add peer to related tables in a MP instance * @ovpn: the instance to add the peer to @@ -948,19 +981,7 @@ static int ovpn_peer_add_mp(struct ovpn_priv *ovpn, st= ruct ovpn_peer *peer) ovpn_get_hash_head(ovpn->peers->by_id, &peer->id, sizeof(peer->id))); =20 - if (peer->vpn_addrs.ipv4.s_addr !=3D htonl(INADDR_ANY)) { - nhead =3D ovpn_get_hash_head(ovpn->peers->by_vpn_addr4, - &peer->vpn_addrs.ipv4, - sizeof(peer->vpn_addrs.ipv4)); - hlist_nulls_add_head_rcu(&peer->hash_entry_addr4, nhead); - } - - if (!ipv6_addr_any(&peer->vpn_addrs.ipv6)) { - nhead =3D ovpn_get_hash_head(ovpn->peers->by_vpn_addr6, - &peer->vpn_addrs.ipv6, - sizeof(peer->vpn_addrs.ipv6)); - hlist_nulls_add_head_rcu(&peer->hash_entry_addr6, nhead); - } + ovpn_peer_hash_vpn_ip(peer); out: spin_unlock_bh(&ovpn->lock); return ret; diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index f1288734ff100ee76b0c41ebb6dc71725ea33261..a1423f2b09e066d9c2392685d37= 65a2ec29a82ba 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -125,6 +125,7 @@ static inline bool ovpn_peer_hold(struct ovpn_peer *pee= r) return kref_get_unless_zero(&peer->refcount); } =20 +void ovpn_peer_release(struct ovpn_peer *peer); void ovpn_peer_release_kref(struct kref *kref); =20 /** @@ -147,6 +148,7 @@ struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct o= vpn_priv *ovpn, struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id); struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb); +void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer); bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, struct ovpn_peer *peer); =20 @@ -154,5 +156,8 @@ void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u3= 2 interval, u32 timeout); void ovpn_peer_keepalive_work(struct work_struct *work); =20 void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *sk= b); +int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, + const struct sockaddr_storage *ss, + const void *local_ip); =20 #endif /* _NET_OVPN_OVPNPEER_H_ */ diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c index 3a18fbdd3721628c0b4b0d23c9aef77332addfe6..a83cbab7259109f6cb476d6cc67= 0d16180c12761 100644 --- a/drivers/net/ovpn/socket.c +++ b/drivers/net/ovpn/socket.c @@ -49,8 +49,8 @@ static bool ovpn_socket_put(struct ovpn_peer *peer, struc= t ovpn_socket *sock) * ovpn_socket_release - release resources owned by socket user * @peer: peer whose socket should be released * - * This function should be invoked when the user is shutting - * down and wants to drop its link to the socket. + * This function should be invoked when the peer is being removed + * and wants to drop its link to the socket. * * In case of UDP, the detach routine will drop a reference to the * ovpn netdev, pointed by the ovpn_socket. --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f46.google.com (mail-wm1-f46.google.com [209.85.128.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DFE8729E042 for ; Tue, 15 Apr 2025 11:17:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715878; cv=none; b=CUGCSA6lUsWxm0qcXBMR1bqRY5+cjWTJpO2mL8EXPJdfH8JvbDGamWrQ80gpN1c+OAcZuRsmR3yPBi2tUTUWcgxCPi0Fb6NhmxagrQuletzE7yXt7PkmLZZnscvf9bmU3bCamp4o61iBzZjUzjG7b4WvTkD/PzA7Nh3SuesKbrI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715878; c=relaxed/simple; bh=SP9tKzz/2n8MbZ6ttiX409MBf+90esNv6MaFX8tCCDo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Cr1B13VjvLwgChsoP6ABrs7ZJeP3QbpaoBntUVmOzfCnodVOebkHoBvwE8q/cDXID+Bo5SsAjWgrYiPHT6rm5XtqiJEMkCMDd2o6DztOFXa1A4ILnCOx6uL/7Tz91j8km7fh2KuFUSLxUPjtCpFOTwJYz31aBBXyqFbe1Ysl9Yg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=EhaBT3b9; arc=none smtp.client-ip=209.85.128.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="EhaBT3b9" Received: by mail-wm1-f46.google.com with SMTP id 5b1f17b1804b1-43cef035a3bso38600575e9.1 for ; Tue, 15 Apr 2025 04:17:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715872; x=1745320672; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=dguwmznsu3oF510rWUYpOvHArpKkT0QOLCMzhXkpcXo=; b=EhaBT3b9fHvo6L/9GOEKqmlArI1AbIo3jw3sbqUBkZDMTaM+rEn7CrvPoLj7kBj2LD xGJYkoFuZipQeuIPmGcfDZ8otZpayzukq/BhQa2qfIT1uKJMTilrqKfdZQhA2ZxKxlfM m29vJoyxFQlPK0G2rXEaW5WhGDIQBhljJN9DxD9jwCO6e9WU60+fkfGTP4HXI1hY2Gz2 pxtUnrka8tLMyDm5kLVHWX54M5KyHDYoiDpZxUJfLLccvTqsnDs9JbX4CWpZfd7CKqCS 28uS25cAG06TIiR7rKiOtBX5RrolAXXYDaQXPiHaxnwoC8ZcEEgrni18/hbA3vM+Ptq8 ZfPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715872; x=1745320672; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=dguwmznsu3oF510rWUYpOvHArpKkT0QOLCMzhXkpcXo=; b=tzR8lCNQWFuruQNRJ2ZCtQdhOe3x4fZX8wI58Nervq0lS8Dz468RrNGUW6SzlhcFQK 8pNklaOkmb1S7LOvpmxS9wEkNL8lFc7Yf7OFPSZ/KeKcC+GBADV/xVSGGnSKDaX0fi9X CcG4H85D00VQ46484aLYVG4EDCrGVYxAJJBGTIVhwH7BeNC8JTusC33wfCjnWY4uLrww hR21hvl/1nDtrOxpDCmMX3pyezaMnbjTyFcTpFnHDM9PmEVb00iU5+s+D2if8fgI0QUT 1PtxOH18x/DzxL3hpQh6VBvQqDWqm0ypauzLHNKO363ka9geDredKYQ2QGOk6z0kLlDc pwiw== X-Forwarded-Encrypted: i=1; AJvYcCWUCRf9UF5mHwJ/JKKbzQ4hHmCj9cN83CyDSlc1uyR6t2al2peh2p9xtTRFU5nVtWZdD40M/0Vg+TmW39k=@vger.kernel.org X-Gm-Message-State: AOJu0YxHORZ9exl1zl3vacMiaDaczTZ5OJJZSbAtb1OFlofKFRlwCgg3 hM1BL+Jf3EXWZW2i/WGKSJ9g3ImGvIrCNJBhS56+BlyMOQELhsqoYhwSX4c71GgKN9DABY5vD+D cL0JKyMfebeNSV2YZD7eMO/jqOTZvz3NRRwLarXIsB4E80f+JvN/+vpc= X-Gm-Gg: ASbGncvpjVL4L2b050sX90er3HtEIBtm3b0UOCyqS9mC3lWnSTHQH/jCFL9qprzWOKJ +XwcFWZreLZ0HPo3sJmB/ZprREfNaLOjKgeDppkkesQpPoQt313GR3rS+mw+SnW/93P9siZUHS+ j7vPXH3EU8dX6sveJvUPFasPrAiWQk4XvvotZuL63tLgABQbnQXwT7oNBiZHtYlPpIfMoulnwGT piWzEfoXeM0PD/XwrODEVzcaRgfL5AjYK6gZuRN10FKf8YTJ29LzkznjsxdJvc36hLqWxGTLAU8 1aa+v9KXygLe4GE0Jx4U/vY5pQDBZwhIz35qKw== X-Google-Smtp-Source: AGHT+IHg7ZRvaPfiaQYWTVfb5k0SNFDvgtfoDGkEI4Ju6tCfNshFLi+h+kdAVtff4IGRPe04HkT2OA== X-Received: by 2002:a05:600c:3baa:b0:43c:efed:732c with SMTP id 5b1f17b1804b1-43f3a9b0285mr122942755e9.28.1744715871885; Tue, 15 Apr 2025 04:17:51 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:51 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:36 +0200 Subject: [PATCH net-next v26 19/23] ovpn: implement key add/get/del/swap via netlink Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-19-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=13973; i=antonio@openvpn.net; h=from:subject:message-id; bh=SP9tKzz/2n8MbZ6ttiX409MBf+90esNv6MaFX8tCCDo=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBC/5UkxcRUxGexOYrFXLj1jUerzGhXroZm3 vIUCd7rGgmJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQgAKCRALcOU6oDjV h3KbB/4tiL4Fh71TKMnTFFrvbKTgHyPH501UVe7UCq3LDL7nx3laqG9Eoti9d+WjDXNWWeMKz3u BqeOWsXlhKko3LOGccPG+y4hBsNi0bj0Hj1Aqb5rroNVdxMic9CtCaOtmB5tAA3w9m1fuqyO1qT r8TnJJPLbdP/2oaurof7r1zGBt0qisi4w1ncJyvvAOoMbyd808+6QnxmdIfSo6orgmFJjX0q94R VPZZhjYFEu8qa0JPWN7luoDsJ+fTtqHDIbDCotJC8It5JenjoFVCu66zkZOhzQT/wePFUvnAtSa lulqZ1JO9fNRoxhSf8ZS2dzpSZKHWcw5p6R2IZS467YSrHoS X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C This change introduces the netlink commands needed to add, get, delete and swap keys for a specific peer. Userspace is expected to use these commands to create, inspect (non sensitive data only), destroy and rotate session keys for a specific peer. Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/crypto.c | 40 ++++++ drivers/net/ovpn/crypto.h | 4 + drivers/net/ovpn/crypto_aead.c | 17 +++ drivers/net/ovpn/crypto_aead.h | 2 + drivers/net/ovpn/netlink.c | 301 +++++++++++++++++++++++++++++++++++++= +++- 5 files changed, 360 insertions(+), 4 deletions(-) diff --git a/drivers/net/ovpn/crypto.c b/drivers/net/ovpn/crypto.c index 9544255c4588ad0bcccda5bdc83a4d8729458ff8..deeefffc098162b17ea53eb7a5d= e6b0f19a38022 100644 --- a/drivers/net/ovpn/crypto.c +++ b/drivers/net/ovpn/crypto.c @@ -146,3 +146,43 @@ void ovpn_crypto_key_slots_swap(struct ovpn_crypto_sta= te *cs) =20 spin_unlock_bh(&cs->lock); } + +/** + * ovpn_crypto_config_get - populate keyconf object with non-sensible key = data + * @cs: the crypto state to extract the key data from + * @slot: the specific slot to inspect + * @keyconf: the output object to populate + * + * Return: 0 on success or a negative error code otherwise + */ +int ovpn_crypto_config_get(struct ovpn_crypto_state *cs, + enum ovpn_key_slot slot, + struct ovpn_key_config *keyconf) +{ + struct ovpn_crypto_key_slot *ks; + int idx; + + switch (slot) { + case OVPN_KEY_SLOT_PRIMARY: + idx =3D cs->primary_idx; + break; + case OVPN_KEY_SLOT_SECONDARY: + idx =3D !cs->primary_idx; + break; + default: + return -EINVAL; + } + + rcu_read_lock(); + ks =3D rcu_dereference(cs->slots[idx]); + if (!ks) { + rcu_read_unlock(); + return -ENOENT; + } + + keyconf->cipher_alg =3D ovpn_aead_crypto_alg(ks); + keyconf->key_id =3D ks->key_id; + rcu_read_unlock(); + + return 0; +} diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 5155791b87df7cccc76a011fa751686180074982..487d24a7d26635c9ca0fd66c757= 17502f60e7a0c 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -136,4 +136,8 @@ void ovpn_crypto_state_release(struct ovpn_crypto_state= *cs); =20 void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs); =20 +int ovpn_crypto_config_get(struct ovpn_crypto_state *cs, + enum ovpn_key_slot slot, + struct ovpn_key_config *keyconf); + #endif /* _NET_OVPN_OVPNCRYPTO_H_ */ diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 83ec18e4b9a4f7960bf789cee952ac11cb77083d..74ee639ac86880da9e22b88f182= f5e0851cb2746 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -364,3 +364,20 @@ ovpn_aead_crypto_key_slot_new(const struct ovpn_key_co= nfig *kc) ovpn_aead_crypto_key_slot_destroy(ks); return ERR_PTR(ret); } + +enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks) +{ + const char *alg_name; + + if (!ks->encrypt) + return OVPN_CIPHER_ALG_NONE; + + alg_name =3D crypto_tfm_alg_name(crypto_aead_tfm(ks->encrypt)); + + if (!strcmp(alg_name, ALG_NAME_AES)) + return OVPN_CIPHER_ALG_AES_GCM; + else if (!strcmp(alg_name, ALG_NAME_CHACHAPOLY)) + return OVPN_CIPHER_ALG_CHACHA20_POLY1305; + else + return OVPN_CIPHER_ALG_NONE; +} diff --git a/drivers/net/ovpn/crypto_aead.h b/drivers/net/ovpn/crypto_aead.h index 40c056558add3b9d17fda5c43eb858cb44c95945..65a2ff30789862bf52bbda389a9= 95f8edff48e7e 100644 --- a/drivers/net/ovpn/crypto_aead.h +++ b/drivers/net/ovpn/crypto_aead.h @@ -24,4 +24,6 @@ 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); =20 +enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks); + #endif /* _NET_OVPN_OVPNAEAD_H_ */ diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 5653a588a47c6c94ff10e042f4f6d32bd8205e58..1f4220021df3a6e74d6e8946a58= 882bf5d66e444 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -17,6 +17,7 @@ #include "netlink.h" #include "netlink-gen.h" #include "bind.h" +#include "crypto.h" #include "peer.h" #include "socket.h" =20 @@ -790,24 +791,316 @@ int ovpn_nl_peer_del_doit(struct sk_buff *skb, struc= t genl_info *info) return ret; } =20 +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 =3D 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 =3D nla_data(attrs[OVPN_A_KEYDIR_CIPHER_KEY]); + dir->cipher_key_size =3D 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 =3D nla_data(attrs[OVPN_A_KEYDIR_NONCE_TAIL]); + dir->nonce_tail_size =3D nla_len(attrs[OVPN_A_KEYDIR_NONCE_TAIL]); + break; + default: + NL_SET_ERR_MSG_MOD(info->extack, "unsupported cipher"); + return -EINVAL; + } + + return 0; +} + +/** + * ovpn_nl_key_new_doit - configure a new key for the specified peer + * @skb: incoming netlink message + * @info: genetlink metadata + * + * This function allows the user to install a new key in the peer crypto + * state. + * Each peer has two 'slots', namely 'primary' and 'secondary', where + * keys can be installed. The key in the 'primary' slot is used for + * encryption, while both keys can be used for decryption by matching the + * key ID carried in the incoming packet. + * + * The user is responsible for rotating keys when necessary. The user + * may fetch peer traffic statistics via netlink in order to better + * identify the right time to rotate keys. + * The renegotiation follows these steps: + * 1. a new key is computed by the user and is installed in the 'secondary' + * slot + * 2. at user discretion (usually after a predetermined time) 'primary' and + * 'secondary' contents are swapped and the new key starts being used f= or + * encryption, while the old key is kept around for decryption of late + * packets. + * + * Return: 0 on success or a negative error code otherwise. + */ int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1]; + struct ovpn_priv *ovpn =3D 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_KEYCONF)) + return -EINVAL; + + ret =3D nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX, + info->attrs[OVPN_A_KEYCONF], + ovpn_keyconf_nl_policy, info->extack); + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_PEER_ID)) + return -EINVAL; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_SLOT) || + NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_KEY_ID) || + NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_CIPHER_ALG) || + NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_ENCRYPT_DIR) || + NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_DECRYPT_DIR)) + return -EINVAL; + + pkr.slot =3D nla_get_u32(attrs[OVPN_A_KEYCONF_SLOT]); + pkr.key.key_id =3D nla_get_u32(attrs[OVPN_A_KEYCONF_KEY_ID]); + pkr.key.cipher_alg =3D nla_get_u32(attrs[OVPN_A_KEYCONF_CIPHER_ALG]); + + ret =3D 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 =3D 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_id =3D nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]); + peer =3D 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 =3D 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, "new key installed (id=3D%u) for peer %u\n", + pkr.key.key_id, peer_id); +out: + ovpn_peer_put(peer); + return ret; +} + +static int ovpn_nl_send_key(struct sk_buff *skb, const struct genl_info *i= nfo, + u32 peer_id, enum ovpn_key_slot slot, + const struct ovpn_key_config *keyconf) +{ + struct nlattr *attr; + void *hdr; + + hdr =3D genlmsg_put(skb, info->snd_portid, info->snd_seq, &ovpn_nl_family, + 0, OVPN_CMD_KEY_GET); + if (!hdr) + return -ENOBUFS; + + attr =3D nla_nest_start(skb, OVPN_A_KEYCONF); + if (!attr) + goto err; + + if (nla_put_u32(skb, OVPN_A_KEYCONF_PEER_ID, peer_id)) + goto err; + + if (nla_put_u32(skb, OVPN_A_KEYCONF_SLOT, slot) || + nla_put_u32(skb, OVPN_A_KEYCONF_KEY_ID, keyconf->key_id) || + nla_put_u32(skb, OVPN_A_KEYCONF_CIPHER_ALG, keyconf->cipher_alg)) + goto err; + + nla_nest_end(skb, attr); + genlmsg_end(skb, hdr); + + return 0; +err: + genlmsg_cancel(skb, hdr); + return -EMSGSIZE; } =20 int ovpn_nl_key_get_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1]; + struct ovpn_priv *ovpn =3D info->user_ptr[0]; + struct ovpn_key_config keyconf =3D { 0 }; + enum ovpn_key_slot slot; + struct ovpn_peer *peer; + struct sk_buff *msg; + u32 peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_KEYCONF)) + return -EINVAL; + + ret =3D nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX, + info->attrs[OVPN_A_KEYCONF], + ovpn_keyconf_nl_policy, info->extack); + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_PEER_ID)) + return -EINVAL; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_SLOT)) + return -EINVAL; + + peer_id =3D nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]); + peer =3D 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; + } + + slot =3D nla_get_u32(attrs[OVPN_A_KEYCONF_SLOT]); + + ret =3D ovpn_crypto_config_get(&peer->crypto, slot, &keyconf); + if (ret < 0) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot extract key from slot %u for peer %u", + slot, peer_id); + goto err; + } + + msg =3D nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) { + ret =3D -ENOMEM; + goto err; + } + + ret =3D ovpn_nl_send_key(msg, info, peer->id, slot, &keyconf); + if (ret < 0) { + nlmsg_free(msg); + goto err; + } + + ret =3D genlmsg_reply(msg, info); +err: + ovpn_peer_put(peer); + return ret; } =20 int ovpn_nl_key_swap_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct ovpn_priv *ovpn =3D 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_KEYCONF)) + return -EINVAL; + + ret =3D nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX, + info->attrs[OVPN_A_KEYCONF], + ovpn_keyconf_nl_policy, info->extack); + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_PEER_ID)) + return -EINVAL; + + peer_id =3D nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]); + peer =3D 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; } =20 int ovpn_nl_key_del_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1]; + struct ovpn_priv *ovpn =3D 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_KEYCONF)) + return -EINVAL; + + ret =3D nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX, + info->attrs[OVPN_A_KEYCONF], + ovpn_keyconf_nl_policy, info->extack); + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_PEER_ID)) + return -EINVAL; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_SLOT)) + return -EINVAL; + + peer_id =3D nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]); + slot =3D nla_get_u32(attrs[OVPN_A_KEYCONF_SLOT]); + + peer =3D 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; } =20 /** --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f48.google.com (mail-wm1-f48.google.com [209.85.128.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1B47A29E060 for ; Tue, 15 Apr 2025 11:17:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715879; cv=none; b=Jg+bONqOJww6LncKOLa93o5mu7QPVwQdkboUF+b1GGhU4YNXvnEssQ5DDlz0lNUApIrSPMOp09x3OVNCgyQqUoTxqJZN7H4G4l+XOmrhXgBvA6w8rKA6mamRx4/anKCo/Hypgar9E/n5jcHE/0iVx+xBlUQhsM7z1PURl3Jj/78= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715879; c=relaxed/simple; bh=tkMTNHYy1IP8criuCtWawbeRzr/0TEcKezDOnHfMX10=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=nzM1l9Y0iuEj6TUS28nIS6jM0uQCs6mk/Y1/qt1/aP1p1+6KTv0bD55XGroVuBDBHpUwsF2XBR91N3MeBk03ufLEvv+rMnveDQQaeYe61bgKri903Pd12Db0h2wTDrLzSdnVsS5kmPnk8KTjmgjrn37R+ST+g70j+aV29s66v/U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=JfDCDolV; arc=none smtp.client-ip=209.85.128.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="JfDCDolV" Received: by mail-wm1-f48.google.com with SMTP id 5b1f17b1804b1-43cf848528aso45054285e9.2 for ; Tue, 15 Apr 2025 04:17:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715873; x=1745320673; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=Iip5t9IVRYddkbwxUsheGgszhhgYtHmqVexEl563WV0=; b=JfDCDolVlGTjbKvIOxYQdL0C5xvQ+VnChXVNw9Ii4fR8E8CEAV8jOXWD9e1t5TzhNC 7atD/2ejpfpElRSve6orw213qXvPz2lhbq4bpJkKBAtY4hoHA1B9uhRN3wMOyLSjtxEU DWLPS7vHZNS3BWon16cZpbSO2UinRWg7utROOKMAcCVU4z3f83l2d93LvWda/SrGDH1R DyBvSEKYhxouUYBBzffkL6VzutE1rCuyvurS812cKpuwQcOnsmfFvs0ek8VWVxQKd5Ka X8l7O638bm0XCuG1D/cysekp8O1pN6DuY258CNQZXqc0ZHWhn+kbBWypBwdOmrCpSsVR mTlQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715873; x=1745320673; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Iip5t9IVRYddkbwxUsheGgszhhgYtHmqVexEl563WV0=; b=O/Fs493H+NOPDdIJjCLuT4vS/0uGWVQIc1qKuC8c3HKk91hWG1ItixeSElwoX+DN76 /e2tjlFCLW7xnF0zjB2PGFR61RdvHJ+Sft4tYZbZZ3iSoZSFwbkgQC731zZ17jOpclrD NysfrF44jgzhwxMKFo5wg5KvYp5kh0nTilzd7xxLcOMI1S3BJUkbiFTrSIoxtGFZLkFN WbzOmkYJqXs6Dge4AmcpdsRf55Jj8Q27/wi0Kg/udQv7uNobNA2wNWm8tIVbyB4I1eZ4 fVV5p87v67AnlvzvO1y/Y7rVRlhqZRYao/CCfxQV9ikknKyzFVi/AUMUZ8IC2JXAYTEr txHA== X-Forwarded-Encrypted: i=1; AJvYcCVXRdLyGRkQPr+7inbfn0Jal/02RDLin02ExZ6C3CftCbinm8x3zh+FIS/Neqn5J8333kdIekUGEvDQC+E=@vger.kernel.org X-Gm-Message-State: AOJu0YyEzSAVaUYunl4dUnXsgETC2gXH+Y/iKRCvgfDKtUQzkpj/rt0R yccsl9pqHVmVLx4cLTeUM02PWB5nWHJRvjxO3BsV3lbTWqrjE/iNU0jMAr7bnutqdaNW4f2lrUm uAjCSspcp94HoUqXIklkcO9xvQf4bm+3jyZCF56S2TWd15fitOo3TjGA= X-Gm-Gg: ASbGncufnX3nGBh3mXAvdkd0YyqLuRcWe+dFdKjF9l4hSUxkLgt2nwo9W5uSG0dgfYa /IZWmoX/rH5RY3lZbLYbosdcT8KrtZKN3C0sxaALXGb9rSx8cHV2meBMpZPQRn8H3Lg3LwLCIjy iH/7oa/kQ4qsfP6X2OsLfBVbZr5l9QXxBImwj58vmjHO+dGy8q5HLXpujpeInx3dpVeiz7Gvc98 jF7cpsiaN0oVW5hdNpgOoMuPyEx/6FuPd5iu4MKGf5rD5lEpPVl68HHQaNOWmcIsEk1CF21sLa9 zM/0dxWsg5T9iwlODfNfVRqg2xmjaE6AMmYqWw== X-Google-Smtp-Source: AGHT+IEyNCYbaCGeX6svbgDCPw199qHQY6Kdr9kL5mqdxQjun3z+89Goflp3fK31rR59d20VxlQMlQ== X-Received: by 2002:a05:600c:4fc8:b0:43d:745a:5a50 with SMTP id 5b1f17b1804b1-43f3a959ed2mr140655835e9.19.1744715873164; Tue, 15 Apr 2025 04:17:53 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:52 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:37 +0200 Subject: [PATCH net-next v26 20/23] ovpn: kill key and notify userspace in case of IV exhaustion Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-20-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=5986; i=antonio@openvpn.net; h=from:subject:message-id; bh=tkMTNHYy1IP8criuCtWawbeRzr/0TEcKezDOnHfMX10=; b=kA0DAAgBC3DlOqA41YcByyZiAGf+QEKjHEM4Xs2/C/31hukq4mjln7Yur8hHDKB5gUvSJuZSI IkBMwQAAQgAHRYhBJmr3Gz41BLk3l/A/Atw5TqgONWHBQJn/kBCAAoJEAtw5TqgONWHySEH/jHJ 93PWaWVa1Zbu8Rah6u+lckNfJLR4HGhDBwQ11aCExWF7RV1Amu3FVQihhX+dfVdTppaJp+uU4fH pyHjtHG2uzd3AphAEoywHRwNwzWU2oCuUVxDpHFA90MHp6FrXWrUNRbGjDPgiEZeYsNRF2FeQRI L2xIQSWLhoY3ZBKJNyoum8CZE//N5VzlBMLDb7XOWSRaSRCv+iR3zdPh0Fu8XsiOuBV/Jx8n45i 5Rbm/cnXuR09dp7GYadJzQVpgRakXQ3IL9VrS/JgD3E/mEm9YKgmwS8hmr3M4H5weI2J/IbMcQB oqzzW2Wa7i6YOhvJrEKAJiJhA9t0fE0wn5qt9RU= X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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. Userspace has two ways of deciding when the key has to be renewed before exhausting the IV space: 1) time based approach: after X seconds/minutes userspace generates a new key and sends it to the kernel. This is based on guestimate and normally default timer value works well. 2) packet count based approach: after X packets/bytes userspace generates a new key and sends it to the kernel. Userspace keeps track of the amount of traffic by periodically polling GET_PEER and fetching the VPN/LINK stats. Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/crypto.c | 22 ++++++++++++++++ drivers/net/ovpn/crypto.h | 2 ++ drivers/net/ovpn/io.c | 14 ++++++++++ drivers/net/ovpn/netlink.c | 64 ++++++++++++++++++++++++++++++++++++++++++= ++++ drivers/net/ovpn/netlink.h | 2 ++ 5 files changed, 104 insertions(+) diff --git a/drivers/net/ovpn/crypto.c b/drivers/net/ovpn/crypto.c index deeefffc098162b17ea53eb7a5de6b0f19a38022..90580e32052fb56c646a6df7816= 872366133bc75 100644 --- a/drivers/net/ovpn/crypto.c +++ b/drivers/net/ovpn/crypto.c @@ -54,6 +54,28 @@ void ovpn_crypto_state_release(struct ovpn_crypto_state = *cs) } } =20 +/* removes the key matching the specified id from the crypto context */ +bool ovpn_crypto_kill_key(struct ovpn_crypto_state *cs, u8 key_id) +{ + struct ovpn_crypto_key_slot *ks =3D NULL; + + spin_lock_bh(&cs->lock); + if (rcu_access_pointer(cs->slots[0])->key_id =3D=3D key_id) { + ks =3D rcu_replace_pointer(cs->slots[0], NULL, + lockdep_is_held(&cs->lock)); + } else if (rcu_access_pointer(cs->slots[1])->key_id =3D=3D key_id) { + ks =3D rcu_replace_pointer(cs->slots[1], NULL, + lockdep_is_held(&cs->lock)); + } + spin_unlock_bh(&cs->lock); + + if (ks) + ovpn_crypto_key_slot_put(ks); + + /* let the caller know if a key was actually killed */ + return ks; +} + /* Reset the ovpn_crypto_state object in a way that is atomic * to RCU readers. */ diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 487d24a7d26635c9ca0fd66c75717502f60e7a0c..0e284fec3a75a0a5933978ea9d1= 36f87a2e5c57a 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -140,4 +140,6 @@ int ovpn_crypto_config_get(struct ovpn_crypto_state *cs, enum ovpn_key_slot slot, struct ovpn_key_config *keyconf); =20 +bool ovpn_crypto_kill_key(struct ovpn_crypto_state *cs, u8 key_id); + #endif /* _NET_OVPN_OVPNCRYPTO_H_ */ diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index fd8820c7d133707b7ba52462218c428fdeca24d5..dd8a8055d9676e4b57cf9b5dc0f= f1d082d231850 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -245,6 +245,20 @@ void ovpn_encrypt_post(void *data, int ret) kfree(ovpn_skb_cb(skb)->sg); aead_request_free(ovpn_skb_cb(skb)->req); =20 + if (unlikely(ret =3D=3D -ERANGE)) { + /* we ran out of IVs and we must kill the key as it can't be + * use anymore + */ + netdev_warn(peer->ovpn->dev, + "killing key %u for peer %u\n", ks->key_id, + peer->id); + if (ovpn_crypto_kill_key(&peer->crypto, ks->key_id)) + /* let userspace know so that a new key must be negotiated */ + ovpn_nl_key_swap_notify(peer, ks->key_id); + + goto err; + } + if (unlikely(ret < 0)) goto err; =20 diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 1f4220021df3a6e74d6e8946a58882bf5d66e444..f0b5716059364a1deee1c7d4da1= d5341b53dffca 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -1103,6 +1103,70 @@ int ovpn_nl_key_del_doit(struct sk_buff *skb, struct= genl_info *info) return 0; } =20 +/** + * ovpn_nl_key_swap_notify - notify userspace peer's key must be renewed + * @peer: the peer whose key needs to be renewed + * @key_id: the ID of the key that needs to be renewed + * + * Return: 0 on success or a negative error code otherwise + */ +int ovpn_nl_key_swap_notify(struct ovpn_peer *peer, u8 key_id) +{ + struct ovpn_socket *sock; + struct nlattr *k_attr; + struct sk_buff *msg; + int ret =3D -EMSGSIZE; + void *hdr; + + netdev_info(peer->ovpn->dev, "peer with id %u must rekey - primary key un= usable.\n", + peer->id); + + msg =3D nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + hdr =3D genlmsg_put(msg, 0, 0, &ovpn_nl_family, 0, OVPN_CMD_KEY_SWAP_NTF); + if (!hdr) { + ret =3D -ENOBUFS; + goto err_free_msg; + } + + if (nla_put_u32(msg, OVPN_A_IFINDEX, peer->ovpn->dev->ifindex)) + goto err_cancel_msg; + + k_attr =3D nla_nest_start(msg, OVPN_A_KEYCONF); + if (!k_attr) + goto err_cancel_msg; + + if (nla_put_u32(msg, OVPN_A_KEYCONF_PEER_ID, peer->id)) + goto err_cancel_msg; + + if (nla_put_u16(msg, OVPN_A_KEYCONF_KEY_ID, key_id)) + goto err_cancel_msg; + + nla_nest_end(msg, k_attr); + genlmsg_end(msg, hdr); + + rcu_read_lock(); + sock =3D rcu_dereference(peer->sock); + if (!sock) { + ret =3D -EINVAL; + goto err_unlock; + } + genlmsg_multicast_netns(&ovpn_nl_family, sock_net(sock->sock->sk), + msg, 0, OVPN_NLGRP_PEERS, GFP_ATOMIC); + rcu_read_unlock(); + + return 0; +err_unlock: + rcu_read_unlock(); +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 0d6c34e17082cc7c52dd9c5d5ed1e964925b3f4b..5dc84c8e5e803014053faa0d892= fc3a7259d40e5 100644 --- a/drivers/net/ovpn/netlink.h +++ b/drivers/net/ovpn/netlink.h @@ -12,4 +12,6 @@ int ovpn_nl_register(void); void ovpn_nl_unregister(void); =20 +int ovpn_nl_key_swap_notify(struct ovpn_peer *peer, u8 key_id); + #endif /* _NET_OVPN_NETLINK_H_ */ --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f54.google.com (mail-wm1-f54.google.com [209.85.128.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8E2A929E079 for ; Tue, 15 Apr 2025 11:17:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715881; cv=none; b=HtZrg0rn2PSaM4WxrzfgI5JEPPERIbDSojHYPdR5wpR2DOc1JPB4o2WjDsC2pvoYxBvMDB4Of2osDSNjnX00+JpRE9dX1Yg9KHZiLkViEjyRETbczQckC9nt6/wH9QtJermgmVrmQOb5UFhmn+NKYso14OL9WfFaXY9Pm0Pt1sw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715881; c=relaxed/simple; bh=msH2j+Q6ALu9sPaWC8c9A15IHOtUvKpWFZuqoXCVm1E=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=nNMpsDpn3jW5YokCkn4NKzAzkmpLmEzYPD4AQArIdIAY+P3lo06ht60o97euoxVCHPRlzCLyBgis9gHMY2ZL9f6/ItZFAqw+TZStapc6DO+z8zNBaDNySvEAQaPVoZY+f7IvW3D2zPF6JflsChnBRaQ/m/55WpVFAw4zF4v0QNQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=WZq1u/x+; arc=none smtp.client-ip=209.85.128.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="WZq1u/x+" Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-43cfa7e7f54so34400275e9.1 for ; Tue, 15 Apr 2025 04:17:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715874; x=1745320674; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=DObrEelohmbhDtacq4AF90RWgYCcWXl9R0gtuBJxDJI=; b=WZq1u/x+s4paJ9CTh4SIeJFodMQcJd5tIKU+cLCiYqw5ds/yX/1bxgtbNxW7dq4XTu C8tLIYiRcdr1XbC9G4NQI04U4DJ9RUd3Io3KWU1EqaqasJ7L0OmF9wdDOHuYV+D6R/p7 UfIBEFXZpk2AWh8RVstqTOVIpAZvMXUJ8priwqeiZUSxqoz/LAEQD0lyrr8CAjDOTfwe on1/Z659wshxslWY0XEo+rB1xmThnq0qr0Ym2vrrNxSnrv4U2KmDowDVs2TS26y7iCdB 15mjYLKROSa5LRnNOQVtFcuFP9yb/NLaGqkE92a5zwxX2BXYyMXWDdyky0hmqKeHvRwD qfAg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715874; x=1745320674; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=DObrEelohmbhDtacq4AF90RWgYCcWXl9R0gtuBJxDJI=; b=TpnQ0iGrh8tQGzlV1t+3Z0vi1lWamOl116Ru2fdG0g3waJPuErjKHVz4vStYTEgUxT 9bsEvBiZ9069YUf5ii7z9eesuVPTRMqJ/zhkmxv0yHdsZ6VacW2GSb89e2Cau17hNDCj VFF5+vsXDLsP38Nrlb0cADCGShbrQONOTJsT1+0JphVBIHbXo5tDl3JB6ahtTRKbNKnq TTQwDu4o3SG7IDo95KTL9gpFsx1vwWlcYDZlj4tOA9yYnVVUKonzI20QM5neTRLvvr0j uED+Wn97KyWZF1EkBvP4754bFVGOuErHswb3Bsg02/3EwvNY7pX4WXG6lz4KiT7r1CNa 8a/w== X-Forwarded-Encrypted: i=1; AJvYcCVs7tQ9s5mXyhTJuEHou14rDAlZbHYdsk89tw99LNuIPViBxVizSjtLmV6RseYVxnelqQwaJWm/xwHQpH0=@vger.kernel.org X-Gm-Message-State: AOJu0Yxh3jDf7sGWPF+kInysK9A7q4QyeDVzoIP+xq5ojrCzRH1PLjUT 2vFtFSWoTA0CGhbzWVHFakouWNg4edmzkcFlWsngwpwlLcayFwdEzi/fW+gZQM2DRKIyirfWRm+ VLxVCD5RWG9AfkJbD86RK3HtzdrOb65MkfuFkPiBcrzvhyoMi+j9YR3M= X-Gm-Gg: ASbGncvoaWs19a+IGdfJYIGN0zOjy8LtRBnjXyrE3Q2z8O/2y6aCFUyhLY2pH0vvPYh O6IGD56K9SQMXHflBGPc1pBxf34wygdc/EqepePwvhGSHkUHkUDs3G5GX2Wra9K/x0PEj1QCemy FEVXatjUrXF+FKBQg5ecaT9FASJwGA1t7aw/p8nBXELMV176Vvx1lW49/4z6q/92Gabohn0bMvK suXNyftfo+IU7wV3qAuvT9YXWj6j2S+C9dKCDlifpgiJrgLIbFYzysQHFfGTgdP13LddvkhG1Um 1qwO8K3E8+Kl2rBa3DD+U3uzEhoHBnzdH3MugA== X-Google-Smtp-Source: AGHT+IEVtyPWfeEm0iAB1SWN457EOBw6FOEMlM/OCAQt4uzO7AOZNHLuBgdFeFZZEcrRfbgsh3KthQ== X-Received: by 2002:a05:600c:35c5:b0:439:4b23:9e8e with SMTP id 5b1f17b1804b1-43f99889ad0mr22670395e9.3.1744715874331; Tue, 15 Apr 2025 04:17:54 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:53 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:38 +0200 Subject: [PATCH net-next v26 21/23] ovpn: notify userspace when a peer is deleted Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-21-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=3532; i=antonio@openvpn.net; h=from:subject:message-id; bh=msH2j+Q6ALu9sPaWC8c9A15IHOtUvKpWFZuqoXCVm1E=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBC251cpPT2Ir32maxXT+1GseMs0GX5F3HpU izTyIMkSiyJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQgAKCRALcOU6oDjV h43yB/oCyuzwiHH/C2i4SGt96BFO8DmgMOKrjPMmw6zYgOTTb6RFgTOLtYKTY3L9orFc3eiWphX Z4bBwgHqlyDU7b0ApH1oeCg3Xzr1Ef2x7d9m6k7NK9V2mLsDxk+PIDwAG5m//IkP8TxbGb/Twfo e+7HtRxIPW/8vyx6OTazLFNWHMhS5HMjPcKwltX/YTBdTQT+Q2uawJ8wQmsBoF61+keCPaWclQk rizH+J9ZnTg/KydSKaqWEuf4BpEfPjLTTHKl5MExeOJQprLU/+iob4aL1lVUegrlmn9en+VJtoF aOyJK8WOkQzAwRV3k7G8qRjen5MUNsxDjzYyRRicigiz0qpb X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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 Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- drivers/net/ovpn/netlink.c | 65 ++++++++++++++++++++++++++++++++++++++++++= ++++ drivers/net/ovpn/netlink.h | 1 + drivers/net/ovpn/peer.c | 1 + 3 files changed, 67 insertions(+) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index f0b5716059364a1deee1c7d4da1d5341b53dffca..bea03913bfb1e1948d57bd613d2= bc6241c76fc06 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -1103,6 +1103,71 @@ int ovpn_nl_key_del_doit(struct sk_buff *skb, struct= genl_info *info) return 0; } =20 +/** + * ovpn_nl_peer_del_notify - notify userspace about peer being deleted + * @peer: the peer being deleted + * + * Return: 0 on success or a negative error code otherwise + */ +int ovpn_nl_peer_del_notify(struct ovpn_peer *peer) +{ + struct ovpn_socket *sock; + struct sk_buff *msg; + struct nlattr *attr; + int ret =3D -EMSGSIZE; + void *hdr; + + netdev_info(peer->ovpn->dev, "deleting peer with id %u, reason %d\n", + peer->id, peer->delete_reason); + + msg =3D nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + hdr =3D genlmsg_put(msg, 0, 0, &ovpn_nl_family, 0, OVPN_CMD_PEER_DEL_NTF); + if (!hdr) { + ret =3D -ENOBUFS; + goto err_free_msg; + } + + if (nla_put_u32(msg, OVPN_A_IFINDEX, peer->ovpn->dev->ifindex)) + goto err_cancel_msg; + + attr =3D nla_nest_start(msg, OVPN_A_PEER); + if (!attr) + goto err_cancel_msg; + + if (nla_put_u32(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); + + rcu_read_lock(); + sock =3D rcu_dereference(peer->sock); + if (!sock) { + ret =3D -EINVAL; + goto err_unlock; + } + genlmsg_multicast_netns(&ovpn_nl_family, sock_net(sock->sock->sk), + msg, 0, OVPN_NLGRP_PEERS, GFP_ATOMIC); + rcu_read_unlock(); + + return 0; + +err_unlock: + rcu_read_unlock(); +err_cancel_msg: + genlmsg_cancel(msg, hdr); +err_free_msg: + nlmsg_free(msg); + return ret; +} + /** * ovpn_nl_key_swap_notify - notify userspace peer's key must be renewed * @peer: the peer whose key needs to be renewed diff --git a/drivers/net/ovpn/netlink.h b/drivers/net/ovpn/netlink.h index 5dc84c8e5e803014053faa0d892fc3a7259d40e5..8615dfc3c4720a2a550b5cd1a84= 54ccc58a3c6ba 100644 --- a/drivers/net/ovpn/netlink.h +++ b/drivers/net/ovpn/netlink.h @@ -12,6 +12,7 @@ int ovpn_nl_register(void); void ovpn_nl_unregister(void); =20 +int ovpn_nl_peer_del_notify(struct ovpn_peer *peer); int ovpn_nl_key_swap_notify(struct ovpn_peer *peer, u8 key_id); =20 #endif /* _NET_OVPN_NETLINK_H_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 0469afd6da238d795b63b9e68c8c728fa324de98..a37f89fffb02efbb6468d54c932= 47b170b5d11f6 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -706,6 +706,7 @@ static void ovpn_peer_remove(struct ovpn_peer *peer, } =20 peer->delete_reason =3D reason; + ovpn_nl_peer_del_notify(peer); =20 /* append to provided list for later socket release and ref drop */ llist_add(&peer->release_entry, release_list); --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f46.google.com (mail-wm1-f46.google.com [209.85.128.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DB2962BCF58 for ; Tue, 15 Apr 2025 11:17:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715881; cv=none; b=eyqMLNvlkpnXcni6MYGt7Mgi9acIAfWFM+hwMvVFHl7Xm34f3aAk/cz+/ITAr46o3K4ZuEEgpWRCaJDyIE2ejt4qdzRsbdrH9iDnV+BfROvI2oMOkPst962KcZBBsFr7xA1yY7cJBxrdq+ExxK+ohuJZaf8i7nG1cdDbXGYMPFQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715881; c=relaxed/simple; bh=+m3tlswA/C58CSW303WbNzD55RlTq5TdK2GIiXBLu2s=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=cah2zKW6MYjRwgdFz5TBEF8wDuFa/zEL1R1qJpAtYdGN+NeQlmFn0NNaQLDouGvGGIoQsSktfph/wr6yQ5oeC7mZdLurhwPw0FqPqXumlPA6fhYGQdcxlwcyFAW80kbV8Wm2eybQOaU6T42LjW2xLMk43zc9Y4sKdHuUvglav2E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=CCYm+HvM; arc=none smtp.client-ip=209.85.128.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="CCYm+HvM" Received: by mail-wm1-f46.google.com with SMTP id 5b1f17b1804b1-43cf848528aso45054505e9.2 for ; Tue, 15 Apr 2025 04:17:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715875; x=1745320675; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=sDo4E58Z4GiyjgViKH7H1rsh8W7CNiDIluEBrKsBDtU=; b=CCYm+HvMTAFEbZD51Zuy9Z8v7PsY6iOdytPQBwYBzs804TOwkFytb6IWdTLd+D/cc5 vKKbUVH0mM15D8ELgKs3VTa1zAT/ezaJ8ViXrJYADcO/J3KnDV8ipnxTSCdtBuKVvX/+ /CAEmosPXaIGcWwfMdHEtTiqKd0Q2y3n2N8xNxXYBqpnIcy563sxKnKCqgnnfvwwFFAz JDRZMgzNX/pFBqNprt85FqSTnfklWoTd/xTZ1l02IIsAylRsbqdNrK0T/tTj9fpmPW0y 5vOnoDYC8JGjMBFhTceazynYUeZabHeJi7eKjlh8GXGSOOSb4VMfGdMq8pYWFdgc/NkN H7/Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715875; x=1745320675; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=sDo4E58Z4GiyjgViKH7H1rsh8W7CNiDIluEBrKsBDtU=; b=RqYgVLXrPgOgDAfXkvtJd9n59DVmQPQkdeyvJorGSFkjY9BOqQWw03+oV8o0+4Vs/9 iLpu/bhe0OJgYPFxie6ETC19LUc6EnHR8H+nz957gWrdTjCXbBGK4MEuFasr2QOuO4iD pXDWwffk4fH+n+avsucpLLYyfNufG3AJQSA5+5hi8LA2yBDJTVbcvkfFCLxlRVe+spIP BLPWU7BduhHixq9pz48GhExWttaWbHV5qKRai4q4hcYRtf8JbwXrfj/ZyUR3sA+lmHyj BQTEGox7KScI+nDBSNsoZ3Dq9jS2ZjwpPyhJJkcXiEZQWto+2+OCJZBz7+XDpZRCoAGQ sbOg== X-Forwarded-Encrypted: i=1; AJvYcCWaLuMzltFa2F9CnCkINH29WjyXWD2FGSCRsHvTsohIP1fth2FhabChVqqAgV+BUI6gUDiBlpxZOiQVAaY=@vger.kernel.org X-Gm-Message-State: AOJu0YwSCx2YoDzrbzMjgTLSUeocBbpQ9FiQ4jIGULSlnuDCQ/L/M4RQ plvlnMwN7xd9KVtPxkq0eDqOGfzItvuWYyxQCsKWmWXl5/+BOsIuava2Vl8BScsW8+T7ycOKDWK Kl9On6h7OSqJ2sV68LpjP/OyCx4E2VzUSFLGuwSRSTQ0j0XS0eiMftgw= X-Gm-Gg: ASbGnctBgWeILyd0FRgQd37/Y1UKkP5a2ZljO2d4ACpJ/MLltHz9oBYdxvBP2MTw9r0 mMI+cNNZixGncHdfn78ikbYcr/2TEMlod+DQKkq2ixrATtmiCxekyQNFQst/8DEvtP4qt/M8/7y hTbW+uaYGYb5YvEtrxPkU4SZBbUioZpEu1eJ5dRq+qopwAxxi8BPerUTuDYmWqr0U1fSakty40S DiZ8D5u8L6hfAu3mWwbBMEoEnH7nq8ZQqV7kl6mtfX+bseTbESV/J0pc8IR4mWgxGKmcvoiJsAZ 8O1qJ3Gbxo4DN7qUH1Mqw40FMIG9CB6ETTqt+g== X-Google-Smtp-Source: AGHT+IEFxLDPrCvz+qbpmpr2fHmj5St/mAmYl3ctWBeGLo/g0haXN6SiP/ICM1A8PSpby+n1t8YiXg== X-Received: by 2002:a05:600c:3483:b0:43c:f8fc:f6a6 with SMTP id 5b1f17b1804b1-43f3a93c7c7mr153344635e9.9.1744715875528; Tue, 15 Apr 2025 04:17:55 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:55 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:39 +0200 Subject: [PATCH net-next v26 22/23] ovpn: add basic ethtool support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-22-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang , Andrew Lunn X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=1680; i=antonio@openvpn.net; h=from:subject:message-id; bh=+m3tlswA/C58CSW303WbNzD55RlTq5TdK2GIiXBLu2s=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBCJAXJ5ieI9UdG7ZdBzCyyuDy+NnVdZLyz1 zyACUGimESJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQgAKCRALcOU6oDjV h5jlB/4h54npijhaPkX4d3rH9jsOt18HydP/dplr6UDnxyMs6R9XhkvR8bgYIywNJVCV4VJSNrj BAnwm2DWMmUsCM74MKo5/S18nmKpfTkzmInqqPmDp6IOVy8JkzYMYsDxdueu2S7ivLNug3fqkwV uRJaoUoRTXz1cqOdL4EMVvqpJd8tAD7YPIurmewpnw8BWVrt7BCr5/Xtd+WWNS//6OALl/sMHMS xiUtoZXrQ9yhS4AQXHqI3Ie8BxXCFup9qSCWs2ranIcJbbMZ3OSt198DgjDELg25HAoqx+YMpg8 e/CuCa5MGagvmrJaY3ymRqLh7gMTZT5rSSvF8lsJ689JRs8+ X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C 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 Reviewed-by: Andrew Lunn Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- 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 232eeb08e929230afca909bca539ef97892837f5..0acb0934c1bea49c2277aeebefe= 68dde660f2042 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -7,6 +7,7 @@ * James Yonan */ =20 +#include #include #include #include @@ -120,6 +121,19 @@ bool ovpn_dev_is_valid(const struct net_device *dev) return dev->netdev_ops =3D=3D &ovpn_netdev_ops; } =20 +static void ovpn_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strscpy(info->driver, "ovpn", sizeof(info->driver)); + strscpy(info->bus_info, "ovpn", sizeof(info->bus_info)); +} + +static const struct ethtool_ops ovpn_ethtool_ops =3D { + .get_drvinfo =3D ovpn_get_drvinfo, + .get_link =3D ethtool_op_get_link, + .get_ts_info =3D ethtool_op_get_ts_info, +}; + static void ovpn_setup(struct net_device *dev) { netdev_features_t feat =3D NETIF_F_SG | NETIF_F_GSO | @@ -129,6 +143,7 @@ static void ovpn_setup(struct net_device *dev) =20 dev->pcpu_stat_type =3D NETDEV_PCPU_STAT_DSTATS; =20 + dev->ethtool_ops =3D &ovpn_ethtool_ops; dev->netdev_ops =3D &ovpn_netdev_ops; =20 dev->priv_destructor =3D ovpn_priv_free; --=20 2.49.0 From nobody Tue Dec 16 21:11:19 2025 Received: from mail-wm1-f41.google.com (mail-wm1-f41.google.com [209.85.128.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 617D02BD5A6 for ; Tue, 15 Apr 2025 11:17:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715887; cv=none; b=uKD+G+1ellGfPlhb788Xs8u+o8T/nuYGbCvuao/hVSpyhBVWR08V3gVirtJJcE4rkrjwRojiiTbLpHHiR6jYcDfzeZeqY9PGHdPXCNaHpclOrbng4kDbwz55oFsb9yxGcQwPvkdTuu8Q1rmGRBDtA6uP3U7iFTAeGBNtsCN2jlE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715887; c=relaxed/simple; bh=1M8NBN62WSf6TaMhjAdRCYIzL6QYKlmn8AntG1py+is=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=R4VjQHboDDA4uuBznGdSO4T9AonozpTGWiNQVJ/JYQcer7L9TAy22He8B5g7fMD37TAiiD98dyECmXPUPqHohlLl1LI/cbp/TiFDtna6KljloAoxzAJDGmWV5P5oPGbgo/l1SWwulUp9qQOXfA4J/I/VUZpcZ3k78ndVMyIADCM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net; spf=pass smtp.mailfrom=openvpn.com; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b=VEmoALZ1; arc=none smtp.client-ip=209.85.128.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="VEmoALZ1" Received: by mail-wm1-f41.google.com with SMTP id 5b1f17b1804b1-43ce71582e9so43962035e9.1 for ; Tue, 15 Apr 2025 04:17:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1744715877; x=1745320677; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=EiQONZyOTlgpextT9JySbdHQj4mVJ7XL5JcG9LQi/vY=; b=VEmoALZ1w7i1v57TGVQa1uzgqTPOsbd22sOpn29vbnlgTXwToqVnWBh/nwtfTGX47B pqyD8t8ALlY7F2ikGivnI+x8EwuM1b7JVXx9q6MTMdh09uavKEhGK5M3Is427CgR0hJz V0Ax/D//8ArxsJhzpo19WAHeibK31mza0h8Petwb665lfANGuviIfr1YV5lckEnD8V/x 4DA+H5PG4dW4JI/+83GS7Nd6ix8vV0uaFwfk/+NVzmz0HC6HmvT2hKLt8oCW88mOfijT livZeNsVluT07ElnUwO+jcsRZ9gjhPKWEUo7cfV6fKNenLCMl73q65ebL+8EvXHHM5Xw 5vVQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1744715877; x=1745320677; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=EiQONZyOTlgpextT9JySbdHQj4mVJ7XL5JcG9LQi/vY=; b=H0nSvBxoIN2WIvEDni17+ILRsLRgQW2HqoKpzcV9VmcxLBld1m8kaQ/qHzfNVmWeF9 jP49FiatM3YXp/hSTpGpCTPfnUWQTuU2FXaz+dc87DlPEWA3a4jsQaXwDBqTZ12feQR2 9mjotfl0O8/V6JiTUQwXF1j2nZojluSBHlDgL7++8LFYzGYQ500zRf2wOkbd5xoHhmfR 0qN/wHfnnmVXafLC0pnDg0tidUu6iOzbZxl3Eyt/PK3oAFB0ZKBTxkRtT3m85AKOf0VN BQxgkM2ouQPc3QWj/VSkONLM1rmBzlaMf1Dqv0Tnzx5DFvbL0NXoyWHqTUFr6Z6fJPZ5 Uq1Q== X-Forwarded-Encrypted: i=1; AJvYcCX56KJwgjqeUSsnxvCXogs0dzCb8Ktshswja+9VolHiwKMzj93r+86jhuhGEvFwrRwsAdi9cPWeHoISNSY=@vger.kernel.org X-Gm-Message-State: AOJu0YwzPPXP9O2nj4daDLV9vAGARuAaEAeA+bv3snJhCJCnrOdWRkKL +kn5KF2nEfx2/v36oVggAwTAKeGZBwatquz6JfGhMRvYNXH6Xf/9RW99/xhwMjfhkzLfpX4G/7i UikwwJXe1O+m4+BVouWOabLjdtKRXdehGitI90nxuFkxrw/9h9qc04cw= X-Gm-Gg: ASbGncskOOS2JZCHONsBy78BekEFJEavasWpNrna2r0GWNerYSDTV5t6H92CBCUhAwA f8m43lLqnUQ22uOyOGqO1RQXYSMmnejBGffTdhEoYf/t7SBsoQGKclr/p1FZnoPmIUTS2BPxCrf 6McW0Gsb7uRTdbOglfou98dWDUpTjutdFj/6N0qSAOjL/TowKH5I3I62cpjAdCh8UKhqOvMlp7P uFK9MNKxZ1CLkgVpTzvLSmi37PN0zYVHrFU+g9vTxRmkqeCVObb4YxNufmEPmNgBU2PG6tGjQ4j yxntoaZ2kvrApFJmp5UhZW6EqxMzCqqIxrsi5w== X-Google-Smtp-Source: AGHT+IEZA8mKSN5Ys0KleMUC0oIWq0bcwbhb7e9zIiUZE8W/3fzHxwU1CWCcTouqsPVEu01S0CpEYw== X-Received: by 2002:a05:6000:1a88:b0:391:3cb7:d441 with SMTP id ffacd0b85a97d-39ea5212b03mr13858075f8f.25.1744715876933; Tue, 15 Apr 2025 04:17:56 -0700 (PDT) Received: from [127.0.0.1] ([2001:67c:2fbc:1:83ac:73b4:720f:dc99]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-43f2066d26bsm210836255e9.22.2025.04.15.04.17.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Apr 2025 04:17:56 -0700 (PDT) From: Antonio Quartulli Date: Tue, 15 Apr 2025 13:17:40 +0200 Subject: [PATCH net-next v26 23/23] testing/selftests: add test tool and scripts for ovpn module Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250415-b4-ovpn-v26-23-577f6097b964@openvpn.net> References: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> In-Reply-To: <20250415-b4-ovpn-v26-0-577f6097b964@openvpn.net> To: netdev@vger.kernel.org, Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan , sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn Cc: Simon Horman , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Xiao Liang , Shuah Khan X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=76041; i=antonio@openvpn.net; h=from:subject:message-id; bh=1M8NBN62WSf6TaMhjAdRCYIzL6QYKlmn8AntG1py+is=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBn/kBD7I0WmMFd8Dqb6yiADEe6JVKqn7bSL0EvR zRqlR7UefOJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZ/5AQwAKCRALcOU6oDjV hxk4CACjxZ9NNFcbyvZdEM5MSnc1RTARho4JbSrUErsFI7lgQA70aSOyrguAWSxcHYqJU8BlLbr CXhm4dsGtWD8fvbi/wWYgG7U5Y2QzoM2/eFfJ4IoUISAVF4li+Bvxvrj8qw75nzDPLQSBSkf2dX GbsvkkRTEZgNxiigMo7M8flphJ4uo4qywofK67g+h2wzDgyhprszzAjgs+9fGarFBIQoBmbBnDF nm9te+j+rVtb9OSM2Tg62gu3wD1u0xqQcMnkRKJ3QTyH9MQJRxjlQe10HLMR9zsaAiCJ71bYGN4 hg5Z3EvfOJSEQexnVuo1NCYq718f4L41gMinoDZ3n5t7VHfF X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C The ovpn-cli tool can be compiled and used as selftest for the ovpn kernel module. [NOTE: it depends on libmedtls for decoding base64-encoded keys] ovpn-cli implements the netlink and RTNL APIs and can thus be integrated in any script for more automated testing. Along with the tool, a bunch of scripts are provided that perform basic functionality tests by means of network namespaces. These scripts take part to the kselftest automation. The output of the scripts, which will appear in the kselftest reports, is a list of steps performed by the scripts plus some output coming from the execution of `ping`, `iperf` and `ovpn-cli` itself. In general it is useful only in case of failure, in order to understand which step has failed and why. Please note: since peer sockets are tied to the userspace process that created them (i.e. exiting the process will result in closing the socket), every run of ovpn-cli that created one will go to background and enter pause(), waiting for the signal which will allow it to terminate. Termination is accomplished at the end of each script by issuing a killall command. Cc: linux-kselftest@vger.kernel.org Cc: Shuah Khan Signed-off-by: Antonio Quartulli Reviewed-by: Sabrina Dubroca Tested-by: Oleksandr Natalenko --- MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + tools/testing/selftests/net/ovpn/.gitignore | 2 + tools/testing/selftests/net/ovpn/Makefile | 31 + tools/testing/selftests/net/ovpn/common.sh | 92 + tools/testing/selftests/net/ovpn/config | 10 + tools/testing/selftests/net/ovpn/data64.key | 5 + tools/testing/selftests/net/ovpn/ovpn-cli.c | 2376 ++++++++++++++++= ++++ tools/testing/selftests/net/ovpn/tcp_peers.txt | 5 + .../testing/selftests/net/ovpn/test-chachapoly.sh | 9 + .../selftests/net/ovpn/test-close-socket-tcp.sh | 9 + .../selftests/net/ovpn/test-close-socket.sh | 45 + tools/testing/selftests/net/ovpn/test-float.sh | 9 + tools/testing/selftests/net/ovpn/test-tcp.sh | 9 + tools/testing/selftests/net/ovpn/test.sh | 113 + tools/testing/selftests/net/ovpn/udp_peers.txt | 5 + 16 files changed, 2722 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index c50e87ef728848965f7de80474acf9cd4e5bab75..350009769173c7a37ff1188ec09= 1a3fd269bc636 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18134,6 +18134,7 @@ T: git https://github.com/OpenVPN/linux-kernel-ovpn= .git F: Documentation/netlink/specs/ovpn.yaml F: drivers/net/ovpn/ F: include/uapi/linux/ovpn.h +F: tools/testing/selftests/net/ovpn/ =20 OPENVSWITCH M: Aaron Conole diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Mak= efile index c77c8c8e3d9bdd8047c9cb7722c3830447e504e5..61bb8bf1b5074fa8fd56874f9f0= f5aa182effbdb 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -71,6 +71,7 @@ TARGETS +=3D net/hsr TARGETS +=3D net/mptcp TARGETS +=3D net/netfilter TARGETS +=3D net/openvswitch +TARGETS +=3D net/ovpn TARGETS +=3D net/packetdrill TARGETS +=3D net/rds TARGETS +=3D net/tcp_ao diff --git a/tools/testing/selftests/net/ovpn/.gitignore b/tools/testing/se= lftests/net/ovpn/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ee44c081ca7c089933659689303= c303a9fa9713b --- /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/self= tests/net/ovpn/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..2d102878cb6ddf3dc7ac8e18306= 8633ec72e5b95 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/Makefile @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2020-2025 OpenVPN, Inc. +# +CFLAGS =3D -pedantic -Wextra -Wall -Wl,--no-as-needed -g -O0 -ggdb $(KHDR_= INCLUDES) +VAR_CFLAGS =3D $(shell pkg-config --cflags libnl-3.0 libnl-genl-3.0 2>/dev= /null) +ifeq ($(VAR_CFLAGS),) +VAR_CFLAGS =3D -I/usr/include/libnl3 +endif +CFLAGS +=3D $(VAR_CFLAGS) + + +LDLIBS =3D -lmbedtls -lmbedcrypto +VAR_LDLIBS =3D $(shell pkg-config --libs libnl-3.0 libnl-genl-3.0 2>/dev/n= ull) +ifeq ($(VAR_LDLIBS),) +VAR_LDLIBS =3D -lnl-genl-3 -lnl-3 +endif +LDLIBS +=3D $(VAR_LDLIBS) + + +TEST_FILES =3D common.sh + +TEST_PROGS =3D test.sh \ + test-chachapoly.sh \ + test-tcp.sh \ + test-float.sh \ + test-close-socket.sh \ + test-close-socket-tcp.sh + +TEST_GEN_FILES :=3D ovpn-cli + +include ../../lib.mk diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/sel= ftests/net/ovpn/common.sh new file mode 100644 index 0000000000000000000000000000000000000000..7502292a1ee037f8ff433bd4b46= 8595acf1a81b3 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2020-2025 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +UDP_PEERS_FILE=3D${UDP_PEERS_FILE:-udp_peers.txt} +TCP_PEERS_FILE=3D${TCP_PEERS_FILE:-tcp_peers.txt} +OVPN_CLI=3D${OVPN_CLI:-./ovpn-cli} +ALG=3D${ALG:-aes} +PROTO=3D${PROTO:-UDP} +FLOAT=3D${FLOAT:-0} + +create_ns() { + ip netns add peer${1} +} + +setup_ns() { + MODE=3D"P2P" + + if [ ${1} -eq 0 ]; then + MODE=3D"MP" + for p in $(seq 1 ${NUM_PEERS}); do + ip link add veth${p} netns peer0 type veth peer name veth${p} netns pee= r${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}" =3D=3D "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_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 + 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 \ + data64.key + fi + fi +} + +cleanup() { + # some ovpn-cli processes sleep in background so they need manual poking + killall $(basename ${OVPN_CLI}) 2>/dev/null || true + + # netns peer0 is deleted without erasing ifaces first + for p in $(seq 1 10); do + ip -n peer${p} link set tun${p} down 2>/dev/null || true + ip netns exec peer${p} ${OVPN_CLI} del_iface tun${p} 2>/dev/null || true + done + 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 del peer${p} 2>/dev/null || true + done +} + +if [ "${PROTO}" =3D=3D "UDP" ]; then + NUM_PEERS=3D${NUM_PEERS:-$(wc -l ${UDP_PEERS_FILE} | awk '{print $1}')} +else + NUM_PEERS=3D${NUM_PEERS:-$(wc -l ${TCP_PEERS_FILE} | awk '{print $1}')} +fi + + diff --git a/tools/testing/selftests/net/ovpn/config b/tools/testing/selfte= sts/net/ovpn/config new file mode 100644 index 0000000000000000000000000000000000000000..71946ba9fa175c191725e369eb9= b973503d9d9c4 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/config @@ -0,0 +1,10 @@ +CONFIG_NET=3Dy +CONFIG_INET=3Dy +CONFIG_STREAM_PARSER=3Dy +CONFIG_NET_UDP_TUNNEL=3Dy +CONFIG_DST_CACHE=3Dy +CONFIG_CRYPTO=3Dy +CONFIG_CRYPTO_AES=3Dy +CONFIG_CRYPTO_GCM=3Dy +CONFIG_CRYPTO_CHACHA20POLY1305=3Dy +CONFIG_OVPN=3Dm diff --git a/tools/testing/selftests/net/ovpn/data64.key b/tools/testing/se= lftests/net/ovpn/data64.key new file mode 100644 index 0000000000000000000000000000000000000000..a99e88c4e290f58b12f399b857b= 873f308d9ba09 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/data64.key @@ -0,0 +1,5 @@ +jRqMACN7d7/aFQNT8S7jkrBD8uwrgHbG5OQZP2eu4R1Y7tfpS2bf5RHv06Vi163CGoaIiTX99R= 3B +ia9ycAH8Wz1+9PWv51dnBLur9jbShlgZ2QHLtUc4a/gfT7zZwULXuuxdLnvR21DDeMBaTbkgba= i9 +uvAa7ne1liIgGFzbv+Bas4HDVrygxIxuAnP5Qgc3648IJkZ0QEXPF+O9f0n5+QIvGCxkAUVx+5= K6 +KIs+SoeWXnAopELmoGSjUpFtJbagXK82HfdqpuUxT2Tnuef0/14SzVE/vNleBNu2ZbyrSAaah8= tE +BofkPJUBFY+YQcfZNM5Dgrw3i+Bpmpq/gpdg5w=3D=3D diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/se= lftests/net/ovpn/ovpn-cli.c new file mode 100644 index 0000000000000000000000000000000000000000..69e41fc07fbcaf7bf60df6584a9= 7d097b268f808 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -0,0 +1,2376 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel accelerator + * + * Copyright (C) 2020-2025 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* defines to make checkpatch happy */ +#define strscpy strncpy +#define __always_unused __attribute__((__unused__)) + +/* 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) + +/* libnl < 3.11.0 does not implement nla_get_uint() */ +uint64_t ovpn_nla_get_uint(struct nlattr *attr) +{ + if (nla_len(attr) =3D=3D 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 =3D 0, + KEY_DIR_OUT, +}; + +#define KEY_LEN (256 / 8) +#define NONCE_LEN 8 + +#define PEER_ID_UNDEF 0x00FFFFFF +#define MAX_PEERS 10 + +struct nl_ctx { + struct nl_sock *nl_sock; + struct nl_msg *nl_msg; + struct nl_cb *nl_cb; + + int ovpn_dco_id; +}; + +enum ovpn_cmd { + CMD_INVALID, + CMD_NEW_IFACE, + CMD_DEL_IFACE, + CMD_LISTEN, + CMD_CONNECT, + CMD_NEW_PEER, + CMD_NEW_MULTI_PEER, + CMD_SET_PEER, + CMD_DEL_PEER, + CMD_GET_PEER, + CMD_NEW_KEY, + CMD_DEL_KEY, + CMD_GET_KEY, + CMD_SWAP_KEYS, + CMD_LISTEN_MCAST, +}; + +struct ovpn_ctx { + enum ovpn_cmd cmd; + + __u8 key_enc[KEY_LEN]; + __u8 key_dec[KEY_LEN]; + __u8 nonce[NONCE_LEN]; + + enum ovpn_cipher_alg cipher; + + sa_family_t sa_family; + + unsigned long peer_id; + unsigned long lport; + + union { + struct sockaddr_in in4; + struct sockaddr_in6 in6; + } remote; + + union { + struct sockaddr_in in4; + struct sockaddr_in6 in6; + } peer_ip; + + bool peer_ip_set; + + unsigned int ifindex; + char ifname[IFNAMSIZ]; + enum ovpn_mode mode; + bool mode_set; + + int socket; + int cli_sockets[MAX_PEERS]; + + __u32 keepalive_interval; + __u32 keepalive_timeout; + + enum ovpn_key_direction key_dir; + enum ovpn_key_slot key_slot; + int key_id; + + const char *peers_file; +}; + +static int ovpn_nl_recvmsgs(struct nl_ctx *ctx) +{ + int ret; + + ret =3D 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 err, ret; + + ctx =3D calloc(1, sizeof(*ctx)); + if (!ctx) + return NULL; + + ctx->nl_sock =3D 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 =3D genl_connect(ctx->nl_sock); + if (ret) { + fprintf(stderr, "cannot connect to generic netlink: %s\n", + nl_geterror(ret)); + goto err_sock; + } + + /* enable Extended ACK for detailed error reporting */ + err =3D 1; + setsockopt(nl_socket_get_fd(ctx->nl_sock), SOL_NETLINK, NETLINK_EXT_ACK, + &err, sizeof(err)); + + ctx->ovpn_dco_id =3D 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 =3D nlmsg_alloc(); + if (!ctx->nl_msg) { + fprintf(stderr, "cannot allocate netlink message\n"); + goto err_sock; + } + + ctx->nl_cb =3D 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)__always_unused, + struct nlmsgerr *err, void *arg) +{ + struct nlmsghdr *nlh =3D (struct nlmsghdr *)err - 1; + struct nlattr *tb_msg[NLMSGERR_ATTR_MAX + 1]; + int len =3D nlh->nlmsg_len; + struct nlattr *attrs; + int *ret =3D arg; + int ack_len =3D sizeof(*nlh) + sizeof(int) + sizeof(*nlh); + + *ret =3D err->error; + + if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS)) + return NL_STOP; + + if (!(nlh->nlmsg_flags & NLM_F_CAPPED)) + ack_len +=3D err->msg.nlmsg_len - sizeof(*nlh); + + if (len <=3D ack_len) + return NL_STOP; + + attrs =3D (void *)((uint8_t *)nlh + ack_len); + len -=3D ack_len; + + nla_parse(tb_msg, NLMSGERR_ATTR_MAX, attrs, len, NULL); + if (tb_msg[NLMSGERR_ATTR_MSG]) { + len =3D 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])); + } + + if (tb_msg[NLMSGERR_ATTR_MISS_NEST]) { + fprintf(stderr, "missing required nesting type %u\n", + nla_get_u32(tb_msg[NLMSGERR_ATTR_MISS_NEST])); + } + + if (tb_msg[NLMSGERR_ATTR_MISS_TYPE]) { + fprintf(stderr, "missing required attribute type %u\n", + nla_get_u32(tb_msg[NLMSGERR_ATTR_MISS_TYPE])); + } + + return NL_STOP; +} + +static int ovpn_nl_cb_finish(struct nl_msg (*msg)__always_unused, + void *arg) +{ + int *status =3D arg; + + *status =3D 0; + return NL_SKIP; +} + +static int ovpn_nl_cb_ack(struct nl_msg (*msg)__always_unused, + void *arg) +{ + int *status =3D arg; + + *status =3D 0; + return NL_STOP; +} + +static int ovpn_nl_msg_send(struct nl_ctx *ctx, ovpn_nl_cb cb) +{ + int status =3D 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_ack, &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 =3D=3D 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_parse_key(const char *file, struct ovpn_ctx *ctx) +{ + int idx_enc, idx_dec, ret =3D -1; + unsigned char *ckey =3D NULL; + __u8 *bkey =3D NULL; + size_t olen =3D 0; + long ckey_len; + FILE *fp; + + fp =3D fopen(file, "r"); + if (!fp) { + fprintf(stderr, "cannot open: %s\n", file); + return -1; + } + + /* get file size */ + fseek(fp, 0L, SEEK_END); + ckey_len =3D ftell(fp); + rewind(fp); + + /* if the file is longer, let's just read a portion */ + if (ckey_len > 256) + ckey_len =3D 256; + + ckey =3D malloc(ckey_len); + if (!ckey) + goto err; + + ret =3D fread(ckey, 1, ckey_len, fp); + if (ret !=3D ckey_len) { + fprintf(stderr, + "couldn't read enough data from key file: %dbytes read\n", + ret); + goto err; + } + + olen =3D 0; + ret =3D mbedtls_base64_decode(NULL, 0, &olen, ckey, ckey_len); + if (ret !=3D 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 =3D malloc(olen); + if (!bkey) { + fprintf(stderr, "cannot allocate binary key buffer\n"); + goto err; + } + + ret =3D 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 =3D 0; + idx_dec =3D 1; + break; + case KEY_DIR_OUT: + idx_enc =3D 1; + idx_dec =3D 0; + break; + default: + goto err; + } + + 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 =3D 0; + +err: + fclose(fp); + free(bkey); + free(ckey); + + return ret; +} + +static int ovpn_parse_cipher(const char *cipher, struct ovpn_ctx *ctx) +{ + if (strcmp(cipher, "aes") =3D=3D 0) + ctx->cipher =3D OVPN_CIPHER_ALG_AES_GCM; + else if (strcmp(cipher, "chachapoly") =3D=3D 0) + ctx->cipher =3D OVPN_CIPHER_ALG_CHACHA20_POLY1305; + else if (strcmp(cipher, "none") =3D=3D 0) + ctx->cipher =3D OVPN_CIPHER_ALG_NONE; + else + return -ENOTSUP; + + return 0; +} + +static int ovpn_parse_key_direction(const char *dir, struct ovpn_ctx *ctx) +{ + int in_dir; + + in_dir =3D strtoll(dir, NULL, 10); + switch (in_dir) { + case KEY_DIR_IN: + case KEY_DIR_OUT: + ctx->key_dir =3D 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 =3D { 0 }; + struct sockaddr_in6 *in6; + struct sockaddr_in *in; + int ret, s, sock_type; + size_t sock_len; + + if (proto =3D=3D IPPROTO_UDP) + sock_type =3D SOCK_DGRAM; + else if (proto =3D=3D IPPROTO_TCP) + sock_type =3D SOCK_STREAM; + else + return -EINVAL; + + s =3D socket(family, sock_type, 0); + if (s < 0) { + perror("cannot create socket"); + return -1; + } + + switch (family) { + case AF_INET: + in =3D (struct sockaddr_in *)&local_sock; + in->sin_family =3D family; + in->sin_port =3D htons(ctx->lport); + in->sin_addr.s_addr =3D htonl(INADDR_ANY); + sock_len =3D sizeof(*in); + break; + case AF_INET6: + in6 =3D (struct sockaddr_in6 *)&local_sock; + in6->sin6_family =3D family; + in6->sin6_port =3D htons(ctx->lport); + in6->sin6_addr =3D in6addr_any; + sock_len =3D sizeof(*in6); + break; + default: + return -1; + } + + int opt =3D 1; + + ret =3D setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + if (ret < 0) { + perror("setsockopt for SO_REUSEADDR"); + return ret; + } + + ret =3D setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)); + if (ret < 0) { + perror("setsockopt for SO_REUSEPORT"); + return ret; + } + + if (family =3D=3D AF_INET6) { + opt =3D 0; + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &opt, + sizeof(opt))) { + perror("failed to set IPV6_V6ONLY"); + return -1; + } + } + + ret =3D bind(s, (struct sockaddr *)&local_sock, sock_len); + if (ret < 0) { + perror("cannot bind socket"); + goto err_socket; + } + + ctx->socket =3D s; + ctx->sa_family =3D 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 =3D ovpn_socket(ctx, family, IPPROTO_TCP); + if (ret < 0) + return ret; + + ret =3D 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 =3D sizeof(ctx->remote); + ret =3D 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 =3D -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 =3D 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 =3D sizeof(struct sockaddr_in); + break; + case AF_INET6: + socklen =3D sizeof(struct sockaddr_in6); + break; + default: + return -EOPNOTSUPP; + } + + ret =3D connect(s, (struct sockaddr *)&ovpn->remote, socklen); + if (ret < 0) { + perror("connect"); + goto err; + } + + fprintf(stderr, "connected\n"); + + ovpn->socket =3D 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; + int ret =3D -1; + + ctx =3D nl_ctx_alloc(ovpn, OVPN_CMD_PEER_NEW); + if (!ctx) + return -ENOMEM; + + attr =3D 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: + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_REMOTE_IPV4, + ovpn->remote.in4.sin_addr.s_addr); + NLA_PUT_U16(ctx->nl_msg, OVPN_A_PEER_REMOTE_PORT, + ovpn->remote.in4.sin_port); + break; + case AF_INET6: + NLA_PUT(ctx->nl_msg, OVPN_A_PEER_REMOTE_IPV6, + sizeof(ovpn->remote.in6.sin6_addr), + &ovpn->remote.in6.sin6_addr); + NLA_PUT_U32(ctx->nl_msg, + OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID, + ovpn->remote.in6.sin6_scope_id); + NLA_PUT_U16(ctx->nl_msg, OVPN_A_PEER_REMOTE_PORT, + ovpn->remote.in6.sin6_port); + break; + default: + fprintf(stderr, + "Invalid family for remote socket address\n"); + goto nla_put_failure; + } + } + + if (ovpn->peer_ip_set) { + 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 =3D 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 =3D -1; + + ctx =3D nl_ctx_alloc(ovpn, OVPN_CMD_PEER_SET); + if (!ctx) + return -ENOMEM; + + attr =3D 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 =3D 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 =3D -1; + + ctx =3D nl_ctx_alloc(ovpn, OVPN_CMD_PEER_DEL); + if (!ctx) + return -ENOMEM; + + attr =3D 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 =3D 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)__always_unused) +{ + struct nlattr *pattrs[OVPN_A_PEER_MAX + 1]; + struct genlmsghdr *gnlh =3D nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *attrs[OVPN_A_MAX + 1]; + __u16 rport =3D 0, lport =3D 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(pattrs, OVPN_A_PEER_MAX, nla_data(attrs[OVPN_A_PEER]), + nla_len(attrs[OVPN_A_PEER]), NULL); + + if (pattrs[OVPN_A_PEER_ID]) + fprintf(stderr, "* Peer %u\n", + nla_get_u32(pattrs[OVPN_A_PEER_ID])); + + if (pattrs[OVPN_A_PEER_SOCKET_NETNSID]) + fprintf(stderr, "\tsocket NetNS ID: %d\n", + nla_get_s32(pattrs[OVPN_A_PEER_SOCKET_NETNSID])); + + if (pattrs[OVPN_A_PEER_VPN_IPV4]) { + char buf[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, nla_data(pattrs[OVPN_A_PEER_VPN_IPV4]), + buf, sizeof(buf)); + fprintf(stderr, "\tVPN IPv4: %s\n", buf); + } + + if (pattrs[OVPN_A_PEER_VPN_IPV6]) { + char buf[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, nla_data(pattrs[OVPN_A_PEER_VPN_IPV6]), + buf, sizeof(buf)); + fprintf(stderr, "\tVPN IPv6: %s\n", buf); + } + + if (pattrs[OVPN_A_PEER_LOCAL_PORT]) + lport =3D ntohs(nla_get_u16(pattrs[OVPN_A_PEER_LOCAL_PORT])); + + if (pattrs[OVPN_A_PEER_REMOTE_PORT]) + rport =3D ntohs(nla_get_u16(pattrs[OVPN_A_PEER_REMOTE_PORT])); + + if (pattrs[OVPN_A_PEER_REMOTE_IPV6]) { + void *ip =3D pattrs[OVPN_A_PEER_REMOTE_IPV6]; + char buf[INET6_ADDRSTRLEN]; + int scope_id =3D -1; + + if (pattrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]) { + void *p =3D pattrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]; + + scope_id =3D nla_get_u32(p); + } + + inet_ntop(AF_INET6, nla_data(ip), buf, sizeof(buf)); + fprintf(stderr, "\tRemote: %s:%hu (scope-id: %u)\n", buf, rport, + scope_id); + + if (pattrs[OVPN_A_PEER_LOCAL_IPV6]) { + void *ip =3D pattrs[OVPN_A_PEER_LOCAL_IPV6]; + + inet_ntop(AF_INET6, nla_data(ip), buf, sizeof(buf)); + fprintf(stderr, "\tLocal: %s:%hu\n", buf, lport); + } + } + + if (pattrs[OVPN_A_PEER_REMOTE_IPV4]) { + void *ip =3D pattrs[OVPN_A_PEER_REMOTE_IPV4]; + char buf[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, nla_data(ip), buf, sizeof(buf)); + fprintf(stderr, "\tRemote: %s:%hu\n", buf, rport); + + if (pattrs[OVPN_A_PEER_LOCAL_IPV4]) { + void *p =3D pattrs[OVPN_A_PEER_LOCAL_IPV4]; + + inet_ntop(AF_INET, nla_data(p), buf, sizeof(buf)); + fprintf(stderr, "\tLocal: %s:%hu\n", buf, lport); + } + } + + if (pattrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]) { + void *p =3D pattrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]; + + fprintf(stderr, "\tKeepalive interval: %u sec\n", + nla_get_u32(p)); + } + + if (pattrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]) + fprintf(stderr, "\tKeepalive timeout: %u sec\n", + nla_get_u32(pattrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT])); + + if (pattrs[OVPN_A_PEER_VPN_RX_BYTES]) + fprintf(stderr, "\tVPN RX bytes: %" PRIu64 "\n", + ovpn_nla_get_uint(pattrs[OVPN_A_PEER_VPN_RX_BYTES])); + + if (pattrs[OVPN_A_PEER_VPN_TX_BYTES]) + fprintf(stderr, "\tVPN TX bytes: %" PRIu64 "\n", + ovpn_nla_get_uint(pattrs[OVPN_A_PEER_VPN_TX_BYTES])); + + if (pattrs[OVPN_A_PEER_VPN_RX_PACKETS]) + fprintf(stderr, "\tVPN RX packets: %" PRIu64 "\n", + ovpn_nla_get_uint(pattrs[OVPN_A_PEER_VPN_RX_PACKETS])); + + if (pattrs[OVPN_A_PEER_VPN_TX_PACKETS]) + fprintf(stderr, "\tVPN TX packets: %" PRIu64 "\n", + ovpn_nla_get_uint(pattrs[OVPN_A_PEER_VPN_TX_PACKETS])); + + if (pattrs[OVPN_A_PEER_LINK_RX_BYTES]) + fprintf(stderr, "\tLINK RX bytes: %" PRIu64 "\n", + ovpn_nla_get_uint(pattrs[OVPN_A_PEER_LINK_RX_BYTES])); + + if (pattrs[OVPN_A_PEER_LINK_TX_BYTES]) + fprintf(stderr, "\tLINK TX bytes: %" PRIu64 "\n", + ovpn_nla_get_uint(pattrs[OVPN_A_PEER_LINK_TX_BYTES])); + + if (pattrs[OVPN_A_PEER_LINK_RX_PACKETS]) + fprintf(stderr, "\tLINK RX packets: %" PRIu64 "\n", + ovpn_nla_get_uint(pattrs[OVPN_A_PEER_LINK_RX_PACKETS])); + + if (pattrs[OVPN_A_PEER_LINK_TX_PACKETS]) + fprintf(stderr, "\tLINK TX packets: %" PRIu64 "\n", + ovpn_nla_get_uint(pattrs[OVPN_A_PEER_LINK_TX_PACKETS])); + + return NL_SKIP; +} + +static int ovpn_get_peer(struct ovpn_ctx *ovpn) +{ + int flags =3D 0, ret =3D -1; + struct nlattr *attr; + struct nl_ctx *ctx; + + if (ovpn->peer_id =3D=3D PEER_ID_UNDEF) + flags =3D NLM_F_DUMP; + + ctx =3D nl_ctx_alloc_flags(ovpn, OVPN_CMD_PEER_GET, flags); + if (!ctx) + return -ENOMEM; + + if (ovpn->peer_id !=3D PEER_ID_UNDEF) { + attr =3D 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 =3D 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 *keyconf, *key_dir; + struct nl_ctx *ctx; + int ret =3D -1; + + ctx =3D nl_ctx_alloc(ovpn, OVPN_CMD_KEY_NEW); + if (!ctx) + return -ENOMEM; + + keyconf =3D nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id); + 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 =3D 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 =3D 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); + + ret =3D 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 *keyconf; + struct nl_ctx *ctx; + int ret =3D -1; + + ctx =3D nl_ctx_alloc(ovpn, OVPN_CMD_KEY_DEL); + if (!ctx) + return -ENOMEM; + + keyconf =3D nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_SLOT, ovpn->key_slot); + nla_nest_end(ctx->nl_msg, keyconf); + + ret =3D ovpn_nl_msg_send(ctx, NULL); +nla_put_failure: + nl_ctx_free(ctx); + return ret; +} + +static int ovpn_handle_key(struct nl_msg *msg, void (*arg)__always_unused) +{ + struct nlattr *kattrs[OVPN_A_KEYCONF_MAX + 1]; + struct genlmsghdr *gnlh =3D 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_KEYCONF]) { + fprintf(stderr, "no packet content in netlink message\n"); + return NL_SKIP; + } + + nla_parse(kattrs, OVPN_A_KEYCONF_MAX, nla_data(attrs[OVPN_A_KEYCONF]), + nla_len(attrs[OVPN_A_KEYCONF]), NULL); + + if (kattrs[OVPN_A_KEYCONF_PEER_ID]) + fprintf(stderr, "* Peer %u\n", + nla_get_u32(kattrs[OVPN_A_KEYCONF_PEER_ID])); + if (kattrs[OVPN_A_KEYCONF_SLOT]) { + fprintf(stderr, "\t- Slot: "); + switch (nla_get_u32(kattrs[OVPN_A_KEYCONF_SLOT])) { + case OVPN_KEY_SLOT_PRIMARY: + fprintf(stderr, "primary\n"); + break; + case OVPN_KEY_SLOT_SECONDARY: + fprintf(stderr, "secondary\n"); + break; + default: + fprintf(stderr, "invalid (%u)\n", + nla_get_u32(kattrs[OVPN_A_KEYCONF_SLOT])); + break; + } + } + if (kattrs[OVPN_A_KEYCONF_KEY_ID]) + fprintf(stderr, "\t- Key ID: %u\n", + nla_get_u32(kattrs[OVPN_A_KEYCONF_KEY_ID])); + if (kattrs[OVPN_A_KEYCONF_CIPHER_ALG]) { + fprintf(stderr, "\t- Cipher: "); + switch (nla_get_u32(kattrs[OVPN_A_KEYCONF_CIPHER_ALG])) { + case OVPN_CIPHER_ALG_NONE: + fprintf(stderr, "none\n"); + break; + case OVPN_CIPHER_ALG_AES_GCM: + fprintf(stderr, "aes-gcm\n"); + break; + case OVPN_CIPHER_ALG_CHACHA20_POLY1305: + fprintf(stderr, "chacha20poly1305\n"); + break; + default: + fprintf(stderr, "invalid (%u)\n", + nla_get_u32(kattrs[OVPN_A_KEYCONF_CIPHER_ALG])); + break; + } + } + + return NL_SKIP; +} + +static int ovpn_get_key(struct ovpn_ctx *ovpn) +{ + struct nlattr *keyconf; + struct nl_ctx *ctx; + int ret =3D -1; + + ctx =3D nl_ctx_alloc(ovpn, OVPN_CMD_KEY_GET); + if (!ctx) + return -ENOMEM; + + keyconf =3D nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_SLOT, ovpn->key_slot); + nla_nest_end(ctx->nl_msg, keyconf); + + ret =3D ovpn_nl_msg_send(ctx, ovpn_handle_key); +nla_put_failure: + nl_ctx_free(ctx); + return ret; +} + +static int ovpn_swap_keys(struct ovpn_ctx *ovpn) +{ + struct nl_ctx *ctx; + struct nlattr *kc; + int ret =3D -1; + + ctx =3D nl_ctx_alloc(ovpn, OVPN_CMD_KEY_SWAP); + if (!ctx) + return -ENOMEM; + + kc =3D nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id); + nla_nest_end(ctx->nl_msg, kc); + + ret =3D ovpn_nl_msg_send(ctx, NULL); +nla_put_failure: + nl_ctx_free(ctx); + return ret; +} + +/* Helper function used to easily add attributes to a rtnl message */ +static int ovpn_addattr(struct nlmsghdr *n, int maxlen, int type, + const void *data, int alen) +{ + int len =3D RTA_LENGTH(alen); + struct rtattr *rta; + + if ((int)(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen) { + fprintf(stderr, "%s: rtnl: message exceeded bound of %d\n", + __func__, maxlen); + return -EMSGSIZE; + } + + rta =3D nlmsg_tail(n); + rta->rta_type =3D type; + rta->rta_len =3D len; + + if (!data) + memset(RTA_DATA(rta), 0, alen); + else + memcpy(RTA_DATA(rta), data, alen); + + n->nlmsg_len =3D NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + + return 0; +} + +static struct rtattr *ovpn_nest_start(struct nlmsghdr *msg, size_t max_siz= e, + int attr) +{ + struct rtattr *nest =3D nlmsg_tail(msg); + + if (ovpn_addattr(msg, max_size, attr, NULL, 0) < 0) + return NULL; + + return nest; +} + +static void ovpn_nest_end(struct nlmsghdr *msg, struct rtattr *nest) +{ + nest->rta_len =3D (uint8_t *)nlmsg_tail(msg) - (uint8_t *)nest; +} + +#define RT_SNDBUF_SIZE (1024 * 2) +#define RT_RCVBUF_SIZE (1024 * 4) + +/* Open RTNL socket */ +static int ovpn_rt_socket(void) +{ + int sndbuf =3D RT_SNDBUF_SIZE, rcvbuf =3D RT_RCVBUF_SIZE, fd; + + fd =3D socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { + fprintf(stderr, "%s: cannot open netlink socket\n", __func__); + return fd; + } + + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, + sizeof(sndbuf)) < 0) { + fprintf(stderr, "%s: SO_SNDBUF\n", __func__); + close(fd); + return -1; + } + + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, + sizeof(rcvbuf)) < 0) { + fprintf(stderr, "%s: SO_RCVBUF\n", __func__); + close(fd); + return -1; + } + + return fd; +} + +/* Bind socket to Netlink subsystem */ +static int ovpn_rt_bind(int fd, uint32_t groups) +{ + struct sockaddr_nl local =3D { 0 }; + socklen_t addr_len; + + local.nl_family =3D AF_NETLINK; + local.nl_groups =3D groups; + + if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0) { + fprintf(stderr, "%s: cannot bind netlink socket: %d\n", + __func__, errno); + return -errno; + } + + addr_len =3D sizeof(local); + if (getsockname(fd, (struct sockaddr *)&local, &addr_len) < 0) { + fprintf(stderr, "%s: cannot getsockname: %d\n", __func__, + errno); + return -errno; + } + + if (addr_len !=3D sizeof(local)) { + fprintf(stderr, "%s: wrong address length %d\n", __func__, + addr_len); + return -EINVAL; + } + + if (local.nl_family !=3D AF_NETLINK) { + fprintf(stderr, "%s: wrong address family %d\n", __func__, + local.nl_family); + return -EINVAL; + } + + return 0; +} + +typedef int (*ovpn_parse_reply_cb)(struct nlmsghdr *msg, void *arg); + +/* Send Netlink message and run callback on reply (if specified) */ +static int ovpn_rt_send(struct nlmsghdr *payload, pid_t peer, + unsigned int groups, ovpn_parse_reply_cb cb, + void *arg_cb) +{ + int len, rem_len, fd, ret, rcv_len; + struct sockaddr_nl nladdr =3D { 0 }; + struct nlmsgerr *err; + struct nlmsghdr *h; + char buf[1024 * 16]; + struct iovec iov =3D { + .iov_base =3D payload, + .iov_len =3D payload->nlmsg_len, + }; + struct msghdr nlmsg =3D { + .msg_name =3D &nladdr, + .msg_namelen =3D sizeof(nladdr), + .msg_iov =3D &iov, + .msg_iovlen =3D 1, + }; + + nladdr.nl_family =3D AF_NETLINK; + nladdr.nl_pid =3D peer; + nladdr.nl_groups =3D groups; + + payload->nlmsg_seq =3D time(NULL); + + /* no need to send reply */ + if (!cb) + payload->nlmsg_flags |=3D NLM_F_ACK; + + fd =3D ovpn_rt_socket(); + if (fd < 0) { + fprintf(stderr, "%s: can't open rtnl socket\n", __func__); + return -errno; + } + + ret =3D ovpn_rt_bind(fd, 0); + if (ret < 0) { + fprintf(stderr, "%s: can't bind rtnl socket\n", __func__); + ret =3D -errno; + goto out; + } + + ret =3D sendmsg(fd, &nlmsg, 0); + if (ret < 0) { + fprintf(stderr, "%s: rtnl: error on sendmsg()\n", __func__); + ret =3D -errno; + goto out; + } + + /* prepare buffer to store RTNL replies */ + memset(buf, 0, sizeof(buf)); + iov.iov_base =3D buf; + + while (1) { + /* + * iov_len is modified by recvmsg(), therefore has to be initialized bef= ore + * using it again + */ + iov.iov_len =3D sizeof(buf); + rcv_len =3D recvmsg(fd, &nlmsg, 0); + if (rcv_len < 0) { + if (errno =3D=3D EINTR || errno =3D=3D EAGAIN) { + fprintf(stderr, "%s: interrupted call\n", + __func__); + continue; + } + fprintf(stderr, "%s: rtnl: error on recvmsg()\n", + __func__); + ret =3D -errno; + goto out; + } + + if (rcv_len =3D=3D 0) { + fprintf(stderr, + "%s: rtnl: socket reached unexpected EOF\n", + __func__); + ret =3D -EIO; + goto out; + } + + if (nlmsg.msg_namelen !=3D sizeof(nladdr)) { + fprintf(stderr, + "%s: sender address length: %u (expected %zu)\n", + __func__, nlmsg.msg_namelen, sizeof(nladdr)); + ret =3D -EIO; + goto out; + } + + h =3D (struct nlmsghdr *)buf; + while (rcv_len >=3D (int)sizeof(*h)) { + len =3D h->nlmsg_len; + rem_len =3D len - sizeof(*h); + + if (rem_len < 0 || len > rcv_len) { + if (nlmsg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "%s: truncated message\n", + __func__); + ret =3D -EIO; + goto out; + } + fprintf(stderr, "%s: malformed message: len=3D%d\n", + __func__, len); + ret =3D -EIO; + goto out; + } + + if (h->nlmsg_type =3D=3D NLMSG_DONE) { + ret =3D 0; + goto out; + } + + if (h->nlmsg_type =3D=3D NLMSG_ERROR) { + err =3D (struct nlmsgerr *)NLMSG_DATA(h); + if (rem_len < (int)sizeof(struct nlmsgerr)) { + fprintf(stderr, "%s: ERROR truncated\n", + __func__); + ret =3D -EIO; + goto out; + } + + if (err->error) { + fprintf(stderr, "%s: (%d) %s\n", + __func__, err->error, + strerror(-err->error)); + ret =3D err->error; + goto out; + } + + ret =3D 0; + if (cb) { + int r =3D cb(h, arg_cb); + + if (r <=3D 0) + ret =3D r; + } + goto out; + } + + if (cb) { + int r =3D cb(h, arg_cb); + + if (r <=3D 0) { + ret =3D r; + goto out; + } + } else { + fprintf(stderr, "%s: RTNL: unexpected reply\n", + __func__); + } + + rcv_len -=3D NLMSG_ALIGN(len); + h =3D (struct nlmsghdr *)((uint8_t *)h + + NLMSG_ALIGN(len)); + } + + if (nlmsg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "%s: message truncated\n", __func__); + continue; + } + + if (rcv_len) { + fprintf(stderr, "%s: rtnl: %d not parsed bytes\n", + __func__, rcv_len); + ret =3D -1; + goto out; + } + } +out: + close(fd); + + return ret; +} + +struct ovpn_link_req { + struct nlmsghdr n; + struct ifinfomsg i; + char buf[256]; +}; + +static int ovpn_new_iface(struct ovpn_ctx *ovpn) +{ + struct rtattr *linkinfo, *data; + struct ovpn_link_req req =3D { 0 }; + int ret =3D -1; + + fprintf(stdout, "Creating interface %s with mode %u\n", ovpn->ifname, + ovpn->mode); + + req.n.nlmsg_len =3D NLMSG_LENGTH(sizeof(req.i)); + req.n.nlmsg_flags =3D NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; + req.n.nlmsg_type =3D RTM_NEWLINK; + + if (ovpn_addattr(&req.n, sizeof(req), IFLA_IFNAME, ovpn->ifname, + strlen(ovpn->ifname) + 1) < 0) + goto err; + + linkinfo =3D ovpn_nest_start(&req.n, sizeof(req), IFLA_LINKINFO); + if (!linkinfo) + goto err; + + if (ovpn_addattr(&req.n, sizeof(req), IFLA_INFO_KIND, OVPN_FAMILY_NAME, + strlen(OVPN_FAMILY_NAME) + 1) < 0) + goto err; + + if (ovpn->mode_set) { + data =3D ovpn_nest_start(&req.n, sizeof(req), IFLA_INFO_DATA); + if (!data) + goto err; + + if (ovpn_addattr(&req.n, sizeof(req), IFLA_OVPN_MODE, + &ovpn->mode, sizeof(uint8_t)) < 0) + goto err; + + ovpn_nest_end(&req.n, data); + } + + ovpn_nest_end(&req.n, linkinfo); + + req.i.ifi_family =3D AF_PACKET; + + ret =3D ovpn_rt_send(&req.n, 0, 0, NULL, NULL); +err: + return ret; +} + +static int ovpn_del_iface(struct ovpn_ctx *ovpn) +{ + struct ovpn_link_req req =3D { 0 }; + + fprintf(stdout, "Deleting interface %s ifindex %u\n", ovpn->ifname, + ovpn->ifindex); + + req.n.nlmsg_len =3D NLMSG_LENGTH(sizeof(req.i)); + req.n.nlmsg_flags =3D NLM_F_REQUEST; + req.n.nlmsg_type =3D RTM_DELLINK; + + req.i.ifi_family =3D AF_PACKET; + req.i.ifi_index =3D ovpn->ifindex; + + return ovpn_rt_send(&req.n, 0, 0, NULL, NULL); +} + +static int nl_seq_check(struct nl_msg (*msg)__always_unused, + void (*arg)__always_unused) +{ + 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 =3D arg; + struct nlattr *tb[CTRL_ATTR_MAX + 1]; + struct genlmsghdr *gnlh =3D 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 =3D nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]); + break; + } + + return NL_SKIP; +} + +static int mcast_error_handler(struct sockaddr_nl (*nla)__always_unused, + struct nlmsgerr *err, void *arg) +{ + int *ret =3D arg; + + *ret =3D err->error; + return NL_STOP; +} + +static int mcast_ack_handler(struct nl_msg (*msg)__always_unused, void *ar= g) +{ + int *ret =3D arg; + + *ret =3D 0; + return NL_STOP; +} + +static int ovpn_handle_msg(struct nl_msg *msg, void *arg) +{ + struct genlmsghdr *gnlh =3D nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *attrs[OVPN_A_MAX + 1]; + struct nlmsghdr *nlh =3D nlmsg_hdr(msg); + char ifname[IF_NAMESIZE]; + int *ret =3D arg; + __u32 ifindex; + + fprintf(stderr, "received message from ovpn-dco\n"); + + *ret =3D -1; + + 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 =3D 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_PEER_DEL_NTF: + fprintf(stdout, "received CMD_PEER_DEL_NTF\n"); + break; + case OVPN_CMD_KEY_SWAP_NTF: + fprintf(stdout, "received CMD_KEY_SWAP_NTF\n"); + break; + default: + fprintf(stderr, "received unknown command: %d\n", gnlh->cmd); + return NL_STOP; + } + + *ret =3D 0; + 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 =3D { + .group =3D group, + .id =3D -ENOENT, + }; + + msg =3D nlmsg_alloc(); + if (!msg) + return -ENOMEM; + + cb =3D nl_cb_alloc(NL_CB_DEFAULT); + if (!cb) { + ret =3D -ENOMEM; + goto out_fail_cb; + } + + ctrlid =3D genl_ctrl_resolve(sock, "nlctrl"); + + genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0); + + ret =3D -ENOBUFS; + NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family); + + ret =3D nl_send_auto_complete(sock, msg); + if (ret < 0) + goto nla_put_failure; + + ret =3D 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 =3D=3D 0) + ret =3D grp.id; + nla_put_failure: + nl_cb_put(cb); + out_fail_cb: + nlmsg_free(msg); + return ret; +} + +static int ovpn_listen_mcast(void) +{ + struct nl_sock *sock; + struct nl_cb *cb; + int mcid, ret; + + sock =3D nl_socket_alloc(); + if (!sock) { + fprintf(stderr, "cannot allocate netlink socket\n"); + goto err_free; + } + + nl_socket_set_buffer_size(sock, 8192, 8192); + + ret =3D genl_connect(sock); + if (ret < 0) { + fprintf(stderr, "cannot connect to generic netlink: %s\n", + nl_geterror(ret)); + goto err_free; + } + + mcid =3D 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 =3D nl_socket_add_membership(sock, mcid); + if (ret) { + fprintf(stderr, "failed to join mcast group: %d\n", ret); + goto err_free; + } + + ret =3D 1; + cb =3D 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 =3D=3D 1) { + int err =3D nl_recvmsgs(sock, cb); + + if (err < 0) { + fprintf(stderr, + "cannot receive netlink message: (%d) %s\n", + err, nl_geterror(-err)); + ret =3D -1; + break; + } + } + + nl_cb_put(cb); +err_free: + nl_socket_free(sock); + return ret; +} + +static void usage(const char *cmd) +{ + fprintf(stderr, + "Usage %s [arguments..]\n", + cmd); + fprintf(stderr, "where can be one of the following\n\n"); + + fprintf(stderr, "* new_iface [mode]: create new ovpn interface\n"= ); + fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, "\tmode:\n"); + fprintf(stderr, "\t\t- P2P for peer-to-peer mode (i.e. client)\n"); + fprintf(stderr, "\t\t- MP for multi-peer mode (i.e. server)\n"); + + fprintf(stderr, "* del_iface : delete ovpn interface\n"); + fprintf(stderr, "\tiface: ovpn interface name\n"); + + fprintf(stderr, + "* listen [ipv6]: listen for incoming peer = TCP connections\n"); + fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, "\tlport: TCP port to listen to\n"); + fprintf(stderr, + "\tpeers_file: file containing one peer per line: Line format:\n"); + fprintf(stderr, "\t\t \n"); + fprintf(stderr, + "\tipv6: whether the socket should listen to the IPv6 wildcard address\n= "); + + fprintf(stderr, + "* connect [key_file]: start connectin= g peer of TCP-based VPN session\n"); + fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, "\tpeer_id: peer ID of the connecting peer\n"); + fprintf(stderr, "\traddr: peer IP address to connect to\n"); + fprintf(stderr, "\trport: peer TCP port to connect to\n"); + fprintf(stderr, + "\tkey_file: file containing the symmetric key for encryption\n"); + + fprintf(stderr, + "* new_peer [vpnaddr]: add new= peer\n"); + fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, "\tlport: local UDP port to bind to\n"); + fprintf(stderr, + "\tpeer_id: peer ID to be used in data packets to/from this peer\n"); + fprintf(stderr, "\traddr: peer IP address\n"); + fprintf(stderr, "\trport: peer UDP port\n"); + fprintf(stderr, "\tvpnaddr: peer VPN IP\n"); + + fprintf(stderr, + "* new_multi_peer : add multiple peers as li= sted in the file\n"); + fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, "\tlport: local UDP port to bind to\n"); + fprintf(stderr, + "\tpeers_file: text file containing one peer per line. Line format:\n"); + fprintf(stderr, "\t\t \n"); + + fprintf(stderr, + "* set_peer : = set peer attributes\n"); + fprintf(stderr, "\tiface: ovpn interface name\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"); + + fprintf(stderr, "* del_peer : delete peer\n"); + fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, "\tpeer_id: peer ID of the peer to delete\n"); + + fprintf(stderr, "* get_peer [peer_id]: retrieve peer(s) status\n"= ); + fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, + "\tpeer_id: peer ID of the peer to query. All peers are returned if omit= ted\n"); + + fprintf(stderr, + "* new_key : set data channel key\n"); + fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, + "\tpeer_id: peer ID of the peer to configure the key for\n"); + fprintf(stderr, "\tslot: either 1 (primary) or 2 (secondary)\n"); + fprintf(stderr, "\tkey_id: an ID from 0 to 7\n"); + fprintf(stderr, + "\tcipher: cipher to use, supported: aes (AES-GCM), chachapoly (CHACHA20= POLY1305)\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"); + + fprintf(stderr, + "* del_key [slot]: erase existing data channel key\n"); + fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, "\tpeer_id: peer ID of the peer to modify\n"); + fprintf(stderr, "\tslot: slot to erase. PRIMARY if omitted\n"); + + fprintf(stderr, + "* get_key : retrieve non sensible key data\n"); + fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, "\tpeer_id: peer ID of the peer to query\n"); + fprintf(stderr, "\tslot: either 1 (primary) or 2 (secondary)\n"); + + fprintf(stderr, + "* swap_keys : swap content of primary and secondary ke= y slots\n"); + fprintf(stderr, "\tiface: ovpn interface name\n"); + fprintf(stderr, "\tpeer_id: peer ID of the peer to modify\n"); + + fprintf(stderr, + "* listen_mcast: listen to ovpn netlink multicast messages\n"); +} + +static int ovpn_parse_remote(struct ovpn_ctx *ovpn, const char *host, + const char *service, const char *vpnip) +{ + int ret; + struct addrinfo *result; + struct addrinfo hints =3D { + .ai_family =3D ovpn->sa_family, + .ai_socktype =3D SOCK_DGRAM, + .ai_protocol =3D IPPROTO_UDP + }; + + if (host) { + ret =3D getaddrinfo(host, service, &hints, &result); + if (ret =3D=3D EAI_NONAME || ret =3D=3D EAI_FAIL) + return -1; + + if (!(result->ai_family =3D=3D AF_INET && + result->ai_addrlen =3D=3D sizeof(struct sockaddr_in)) && + !(result->ai_family =3D=3D AF_INET6 && + result->ai_addrlen =3D=3D sizeof(struct sockaddr_in6))) { + ret =3D -EINVAL; + goto out; + } + + memcpy(&ovpn->remote, result->ai_addr, result->ai_addrlen); + } + + if (vpnip) { + ret =3D getaddrinfo(vpnip, NULL, &hints, &result); + if (ret =3D=3D EAI_NONAME || ret =3D=3D EAI_FAIL) + return -1; + + if (!(result->ai_family =3D=3D AF_INET && + result->ai_addrlen =3D=3D sizeof(struct sockaddr_in)) && + !(result->ai_family =3D=3D AF_INET6 && + result->ai_addrlen =3D=3D sizeof(struct sockaddr_in6))) { + ret =3D -EINVAL; + goto out; + } + + memcpy(&ovpn->peer_ip, result->ai_addr, result->ai_addrlen); + ovpn->sa_family =3D result->ai_family; + + ovpn->peer_ip_set =3D true; + } + + ret =3D 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 =3D strtoul(peer_id, NULL, 10); + if (errno =3D=3D ERANGE || ovpn->peer_id > PEER_ID_UNDEF) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + + return ovpn_parse_remote(ovpn, raddr, rport, vpnip); +} + +static int ovpn_parse_key_slot(const char *arg, struct ovpn_ctx *ovpn) +{ + int slot =3D strtoul(arg, NULL, 10); + + if (errno =3D=3D ERANGE || slot < 1 || slot > 2) { + fprintf(stderr, "key slot out of range\n"); + return -1; + } + + switch (slot) { + case 1: + ovpn->key_slot =3D OVPN_KEY_SLOT_PRIMARY; + break; + case 2: + ovpn->key_slot =3D OVPN_KEY_SLOT_SECONDARY; + break; + } + + return 0; +} + +static int ovpn_send_tcp_data(int socket) +{ + uint16_t len =3D htons(1000); + uint8_t buf[1002]; + int ret; + + memcpy(buf, &len, sizeof(len)); + memset(buf + sizeof(len), 0x86, sizeof(buf) - sizeof(len)); + + ret =3D send(socket, buf, sizeof(buf), MSG_NOSIGNAL); + + fprintf(stdout, "Sent %u bytes over TCP socket\n", ret); + + return ret > 0 ? 0 : ret; +} + +static int ovpn_recv_tcp_data(int socket) +{ + uint8_t buf[1002]; + uint16_t len; + int ret; + + ret =3D recv(socket, buf, sizeof(buf), MSG_NOSIGNAL); + + if (ret < 2) { + fprintf(stderr, ">>>> Error while reading TCP data: %d\n", ret); + return ret; + } + + memcpy(&len, buf, sizeof(len)); + len =3D ntohs(len); + + fprintf(stdout, ">>>> Received %u bytes over TCP socket, header: %u\n", + ret, len); + + return 0; +} + +static enum ovpn_cmd ovpn_parse_cmd(const char *cmd) +{ + if (!strcmp(cmd, "new_iface")) + return CMD_NEW_IFACE; + + if (!strcmp(cmd, "del_iface")) + return CMD_DEL_IFACE; + + if (!strcmp(cmd, "listen")) + return CMD_LISTEN; + + if (!strcmp(cmd, "connect")) + return CMD_CONNECT; + + if (!strcmp(cmd, "new_peer")) + return CMD_NEW_PEER; + + if (!strcmp(cmd, "new_multi_peer")) + return CMD_NEW_MULTI_PEER; + + if (!strcmp(cmd, "set_peer")) + return CMD_SET_PEER; + + if (!strcmp(cmd, "del_peer")) + return CMD_DEL_PEER; + + if (!strcmp(cmd, "get_peer")) + return CMD_GET_PEER; + + if (!strcmp(cmd, "new_key")) + return CMD_NEW_KEY; + + if (!strcmp(cmd, "del_key")) + return CMD_DEL_KEY; + + if (!strcmp(cmd, "get_key")) + return CMD_GET_KEY; + + if (!strcmp(cmd, "swap_keys")) + return CMD_SWAP_KEYS; + + if (!strcmp(cmd, "listen_mcast")) + return CMD_LISTEN_MCAST; + + return CMD_INVALID; +} + +/* Send process to background and waits for signal. + * + * This helper is called at the end of commands + * creating sockets, so that the latter stay alive + * along with the process that created them. + * + * A signal is expected to be delivered in order to + * terminate the waiting processes + */ +static void ovpn_waitbg(void) +{ + daemon(1, 1); + pause(); +} + +static int ovpn_run_cmd(struct ovpn_ctx *ovpn) +{ + char peer_id[10], vpnip[INET6_ADDRSTRLEN], raddr[128], rport[10]; + int n, ret; + FILE *fp; + + switch (ovpn->cmd) { + case CMD_NEW_IFACE: + ret =3D ovpn_new_iface(ovpn); + break; + case CMD_DEL_IFACE: + ret =3D ovpn_del_iface(ovpn); + break; + case CMD_LISTEN: + ret =3D ovpn_listen(ovpn, ovpn->sa_family); + if (ret < 0) { + fprintf(stderr, "cannot listen on TCP socket\n"); + return ret; + } + + fp =3D fopen(ovpn->peers_file, "r"); + if (!fp) { + fprintf(stderr, "cannot open file: %s\n", + ovpn->peers_file); + return -1; + } + + int num_peers =3D 0; + + while ((n =3D fscanf(fp, "%s %s\n", peer_id, vpnip)) =3D=3D 2) { + struct ovpn_ctx peer_ctx =3D { 0 }; + + if (num_peers =3D=3D MAX_PEERS) { + fprintf(stderr, "max peers reached!\n"); + return -E2BIG; + } + + peer_ctx.ifindex =3D ovpn->ifindex; + peer_ctx.sa_family =3D ovpn->sa_family; + + peer_ctx.socket =3D ovpn_accept(ovpn); + if (peer_ctx.socket < 0) { + fprintf(stderr, "cannot accept connection!\n"); + return -1; + } + + /* store peer sockets to test TCP I/O */ + ovpn->cli_sockets[num_peers] =3D peer_ctx.socket; + + ret =3D ovpn_parse_new_peer(&peer_ctx, peer_id, NULL, + NULL, vpnip); + if (ret < 0) { + fprintf(stderr, "error while parsing line\n"); + return -1; + } + + ret =3D ovpn_new_peer(&peer_ctx, true); + if (ret < 0) { + fprintf(stderr, + "cannot add peer to VPN: %s %s\n", + peer_id, vpnip); + return ret; + } + num_peers++; + } + + for (int i =3D 0; i < num_peers; i++) { + ret =3D ovpn_recv_tcp_data(ovpn->cli_sockets[i]); + if (ret < 0) + break; + } + ovpn_waitbg(); + break; + case CMD_CONNECT: + ret =3D ovpn_connect(ovpn); + if (ret < 0) { + fprintf(stderr, "cannot connect TCP socket\n"); + return ret; + } + + ret =3D ovpn_new_peer(ovpn, true); + if (ret < 0) { + fprintf(stderr, "cannot add peer to VPN\n"); + close(ovpn->socket); + return ret; + } + + if (ovpn->cipher !=3D OVPN_CIPHER_ALG_NONE) { + ret =3D ovpn_new_key(ovpn); + if (ret < 0) { + fprintf(stderr, "cannot set key\n"); + return ret; + } + } + + ret =3D ovpn_send_tcp_data(ovpn->socket); + ovpn_waitbg(); + break; + case CMD_NEW_PEER: + ret =3D ovpn_udp_socket(ovpn, AF_INET6); + if (ret < 0) + return ret; + + ret =3D ovpn_new_peer(ovpn, false); + ovpn_waitbg(); + break; + case CMD_NEW_MULTI_PEER: + ret =3D ovpn_udp_socket(ovpn, AF_INET6); + if (ret < 0) + return ret; + + fp =3D fopen(ovpn->peers_file, "r"); + if (!fp) { + fprintf(stderr, "cannot open file: %s\n", + ovpn->peers_file); + return -1; + } + + while ((n =3D fscanf(fp, "%s %s %s %s\n", peer_id, raddr, rport, + vpnip)) =3D=3D 4) { + struct ovpn_ctx peer_ctx =3D { 0 }; + + peer_ctx.ifindex =3D ovpn->ifindex; + peer_ctx.socket =3D ovpn->socket; + peer_ctx.sa_family =3D AF_UNSPEC; + + ret =3D ovpn_parse_new_peer(&peer_ctx, peer_id, raddr, + rport, vpnip); + if (ret < 0) { + fprintf(stderr, "error while parsing line\n"); + return -1; + } + + ret =3D 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; + } + } + ovpn_waitbg(); + break; + case CMD_SET_PEER: + ret =3D ovpn_set_peer(ovpn); + break; + case CMD_DEL_PEER: + ret =3D ovpn_del_peer(ovpn); + break; + case CMD_GET_PEER: + if (ovpn->peer_id =3D=3D PEER_ID_UNDEF) + fprintf(stderr, "List of peers connected to: %s\n", + ovpn->ifname); + + ret =3D ovpn_get_peer(ovpn); + break; + case CMD_NEW_KEY: + ret =3D ovpn_new_key(ovpn); + break; + case CMD_DEL_KEY: + ret =3D ovpn_del_key(ovpn); + break; + case CMD_GET_KEY: + ret =3D ovpn_get_key(ovpn); + break; + case CMD_SWAP_KEYS: + ret =3D ovpn_swap_keys(ovpn); + break; + case CMD_LISTEN_MCAST: + ret =3D ovpn_listen_mcast(); + break; + case CMD_INVALID: + break; + } + + return ret; +} + +static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv= []) +{ + int ret; + + /* no args required for LISTEN_MCAST */ + if (ovpn->cmd =3D=3D CMD_LISTEN_MCAST) + return 0; + + /* all commands need an ifname */ + if (argc < 3) + return -EINVAL; + + strscpy(ovpn->ifname, argv[2], IFNAMSIZ - 1); + ovpn->ifname[IFNAMSIZ - 1] =3D '\0'; + + /* all commands, except NEW_IFNAME, needs an ifindex */ + if (ovpn->cmd !=3D CMD_NEW_IFACE) { + ovpn->ifindex =3D if_nametoindex(ovpn->ifname); + if (!ovpn->ifindex) { + fprintf(stderr, "cannot find interface: %s\n", + strerror(errno)); + return -1; + } + } + + switch (ovpn->cmd) { + case CMD_NEW_IFACE: + if (argc < 4) + break; + + if (!strcmp(argv[3], "P2P")) { + ovpn->mode =3D OVPN_MODE_P2P; + } else if (!strcmp(argv[3], "MP")) { + ovpn->mode =3D OVPN_MODE_MP; + } else { + fprintf(stderr, "Cannot parse iface mode: %s\n", + argv[3]); + return -1; + } + ovpn->mode_set =3D true; + break; + case CMD_DEL_IFACE: + break; + case CMD_LISTEN: + if (argc < 5) + return -EINVAL; + + ovpn->lport =3D strtoul(argv[3], NULL, 10); + if (errno =3D=3D ERANGE || ovpn->lport > 65535) { + fprintf(stderr, "lport value out of range\n"); + return -1; + } + + ovpn->peers_file =3D argv[4]; + + if (argc > 5 && !strcmp(argv[5], "ipv6")) + ovpn->sa_family =3D AF_INET6; + break; + case CMD_CONNECT: + if (argc < 6) + return -EINVAL; + + ovpn->sa_family =3D AF_INET; + + ret =3D ovpn_parse_new_peer(ovpn, argv[3], argv[4], argv[5], + NULL); + if (ret < 0) { + fprintf(stderr, "Cannot parse remote peer data\n"); + return -1; + } + + if (argc > 6) { + ovpn->key_slot =3D OVPN_KEY_SLOT_PRIMARY; + ovpn->key_id =3D 0; + ovpn->cipher =3D OVPN_CIPHER_ALG_AES_GCM; + ovpn->key_dir =3D KEY_DIR_OUT; + + ret =3D ovpn_parse_key(argv[6], ovpn); + if (ret) + return -1; + } + break; + case CMD_NEW_PEER: + if (argc < 7) + return -EINVAL; + + ovpn->lport =3D strtoul(argv[4], NULL, 10); + if (errno =3D=3D ERANGE || ovpn->lport > 65535) { + fprintf(stderr, "lport value out of range\n"); + return -1; + } + + const char *vpnip =3D (argc > 7) ? argv[7] : NULL; + + ret =3D ovpn_parse_new_peer(ovpn, argv[3], argv[5], argv[6], + vpnip); + if (ret < 0) + return -1; + break; + case CMD_NEW_MULTI_PEER: + if (argc < 5) + return -EINVAL; + + ovpn->lport =3D strtoul(argv[3], NULL, 10); + if (errno =3D=3D ERANGE || ovpn->lport > 65535) { + fprintf(stderr, "lport value out of range\n"); + return -1; + } + + ovpn->peers_file =3D argv[4]; + break; + case CMD_SET_PEER: + if (argc < 6) + return -EINVAL; + + ovpn->peer_id =3D strtoul(argv[3], NULL, 10); + if (errno =3D=3D ERANGE || ovpn->peer_id > PEER_ID_UNDEF) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + + ovpn->keepalive_interval =3D strtoul(argv[4], NULL, 10); + if (errno =3D=3D ERANGE) { + fprintf(stderr, + "keepalive interval value out of range\n"); + return -1; + } + + ovpn->keepalive_timeout =3D strtoul(argv[5], NULL, 10); + if (errno =3D=3D ERANGE) { + fprintf(stderr, + "keepalive interval value out of range\n"); + return -1; + } + break; + case CMD_DEL_PEER: + if (argc < 4) + return -EINVAL; + + ovpn->peer_id =3D strtoul(argv[3], NULL, 10); + if (errno =3D=3D ERANGE || ovpn->peer_id > PEER_ID_UNDEF) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + break; + case CMD_GET_PEER: + ovpn->peer_id =3D PEER_ID_UNDEF; + if (argc > 3) { + ovpn->peer_id =3D strtoul(argv[3], NULL, 10); + if (errno =3D=3D ERANGE || ovpn->peer_id > PEER_ID_UNDEF) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + } + break; + case CMD_NEW_KEY: + if (argc < 9) + return -EINVAL; + + ovpn->peer_id =3D strtoul(argv[3], NULL, 10); + if (errno =3D=3D ERANGE) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + + ret =3D ovpn_parse_key_slot(argv[4], ovpn); + if (ret) + return -1; + + ovpn->key_id =3D strtoul(argv[5], NULL, 10); + if (errno =3D=3D ERANGE || ovpn->key_id > 2) { + fprintf(stderr, "key ID out of range\n"); + return -1; + } + + ret =3D ovpn_parse_cipher(argv[6], ovpn); + if (ret < 0) + return -1; + + ret =3D ovpn_parse_key_direction(argv[7], ovpn); + if (ret < 0) + return -1; + + ret =3D ovpn_parse_key(argv[8], ovpn); + if (ret) + return -1; + break; + case CMD_DEL_KEY: + if (argc < 4) + return -EINVAL; + + ovpn->peer_id =3D strtoul(argv[3], NULL, 10); + if (errno =3D=3D ERANGE) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + + ret =3D ovpn_parse_key_slot(argv[4], ovpn); + if (ret) + return ret; + break; + case CMD_GET_KEY: + if (argc < 5) + return -EINVAL; + + ovpn->peer_id =3D strtoul(argv[3], NULL, 10); + if (errno =3D=3D ERANGE) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + + ret =3D ovpn_parse_key_slot(argv[4], ovpn); + if (ret) + return ret; + break; + case CMD_SWAP_KEYS: + if (argc < 4) + return -EINVAL; + + ovpn->peer_id =3D strtoul(argv[3], NULL, 10); + if (errno =3D=3D ERANGE) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + break; + case CMD_LISTEN_MCAST: + break; + case CMD_INVALID: + break; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct ovpn_ctx ovpn; + int ret; + + if (argc < 2) { + usage(argv[0]); + return -1; + } + + memset(&ovpn, 0, sizeof(ovpn)); + ovpn.sa_family =3D AF_INET; + ovpn.cipher =3D OVPN_CIPHER_ALG_NONE; + + ovpn.cmd =3D ovpn_parse_cmd(argv[1]); + if (ovpn.cmd =3D=3D CMD_INVALID) { + fprintf(stderr, "Error: unknown command.\n\n"); + usage(argv[0]); + return -1; + } + + ret =3D ovpn_parse_cmd_args(&ovpn, argc, argv); + if (ret < 0) { + fprintf(stderr, "Error: invalid arguments.\n\n"); + if (ret =3D=3D -EINVAL) + usage(argv[0]); + return ret; + } + + ret =3D ovpn_run_cmd(&ovpn); + if (ret) + fprintf(stderr, "Cannot execute command: %s (%d)\n", + strerror(-ret), ret); + + 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 0000000000000000000000000000000000000000..d753eebe8716ed3588334ad7669= 81e883ed2469a --- /dev/null +++ b/tools/testing/selftests/net/ovpn/tcp_peers.txt @@ -0,0 +1,5 @@ +1 5.5.5.2 +2 5.5.5.3 +3 5.5.5.4 +4 5.5.5.5 +5 5.5.5.6 diff --git a/tools/testing/selftests/net/ovpn/test-chachapoly.sh b/tools/te= sting/selftests/net/ovpn/test-chachapoly.sh new file mode 100755 index 0000000000000000000000000000000000000000..32504079a2b894c3b7005387724= 93a5c74139cd6 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-chachapoly.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2025 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +ALG=3D"chachapoly" + +source test.sh diff --git a/tools/testing/selftests/net/ovpn/test-close-socket-tcp.sh b/to= ols/testing/selftests/net/ovpn/test-close-socket-tcp.sh new file mode 100755 index 0000000000000000000000000000000000000000..093d44772ffdf5aa8d0aa5505c2= 1cb2c77a70aa0 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-close-socket-tcp.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2025 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +PROTO=3D"TCP" + +source test-close-socket.sh diff --git a/tools/testing/selftests/net/ovpn/test-close-socket.sh b/tools/= testing/selftests/net/ovpn/test-close-socket.sh new file mode 100755 index 0000000000000000000000000000000000000000..5e48a8b67928770daecc7b7d7f8= 77a542a97743e --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-close-socket.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2020-2025 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +#set -x +set -e + +source ./common.sh + +cleanup + +modprobe -q ovpn || true + +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 + +sleep 1 + +for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer0 ping -qfc 500 -w 3 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 + +cleanup + +modprobe -r ovpn || true diff --git a/tools/testing/selftests/net/ovpn/test-float.sh b/tools/testing= /selftests/net/ovpn/test-float.sh new file mode 100755 index 0000000000000000000000000000000000000000..ba5d725e18b074ac4cac6d160cc= e4e64fa4f6867 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-float.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2025 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +FLOAT=3D"1" + +source test.sh diff --git a/tools/testing/selftests/net/ovpn/test-tcp.sh b/tools/testing/s= elftests/net/ovpn/test-tcp.sh new file mode 100755 index 0000000000000000000000000000000000000000..ba3f1f315a349cf6f20c9035cf6= 9816b590f91ab --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-tcp.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2025 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +PROTO=3D"TCP" + +source test.sh diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selft= ests/net/ovpn/test.sh new file mode 100755 index 0000000000000000000000000000000000000000..7b62897b024084a52e18e67be3b= 9ec1a736316e4 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2020-2025 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +#set -x +set -e + +source ./common.sh + +cleanup + +modprobe -q ovpn || true + +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 + +sleep 1 + +for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer0 ping -qfc 500 -w 3 5.5.5.$((${p} + 1)) +done + +if [ "$FLOAT" =3D=3D "1" ]; then + # 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 500 -w 3 5.5.5.1 + done +fi + +ip netns exec peer0 iperf3 -1 -s & +sleep 1 +ip netns exec peer1 iperf3 -Z -t 3 -c 5.5.5.1 + +echo "Adding secondary key and then swap:" +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 data= 64.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 + +echo "Deleting peer 1:" +ip netns exec peer0 ${OVPN_CLI} del_peer tun0 1 +ip netns exec peer1 ${OVPN_CLI} del_peer tun1 1 + +echo "Querying keys:" +for p in $(seq 2 ${NUM_PEERS}); do + ip netns exec peer${p} ${OVPN_CLI} get_key tun${p} ${p} 1 + ip netns exec peer${p} ${OVPN_CLI} get_key tun${p} ${p} 2 +done + +echo "Deleting peer while sending traffic:" +(ip netns exec peer2 ping -qf -w 4 5.5.5.1)& +sleep 2 +ip netns exec peer0 ${OVPN_CLI} del_peer tun0 2 +# following command fails in TCP mode +# (both ends get conn reset when one peer disconnects) +ip netns exec peer2 ${OVPN_CLI} del_peer tun2 2 || true + +echo "Deleting keys:" +for p in $(seq 3 ${NUM_PEERS}); do + ip netns exec peer${p} ${OVPN_CLI} del_key tun${p} ${p} 1 + ip netns exec peer${p} ${OVPN_CLI} del_key tun${p} ${p} 2 +done + +echo "Setting timeout to 3s MP:" +for p in $(seq 3 ${NUM_PEERS}); do + ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 3 3 || true + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 0 0 +done +# wait for peers to timeout +sleep 5 + +echo "Setting timeout to 3s P2P:" +for p in $(seq 3 ${NUM_PEERS}); do + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 3 3 +done +sleep 5 + +cleanup + +modprobe -r ovpn || true 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 0000000000000000000000000000000000000000..32f14bd9347a63e58438311b6d8= 80b9fef768aa2 --- /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 --=20 2.49.0