From patchwork Tue Apr 15 11:17:19 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 881520 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 6BDBC29115E for ; Tue, 15 Apr 2025 11:17:29 +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=1744715853; cv=none; b=JFFIV/dXHulKgRuAh4he0Rfy++iJjYUoavy+YV8EGq0Z+O7ItX3tfF6E1wQZjeFRa28BHTSZf3XVEQt1MDwBwPG50ykEyrVi79LqrUtU7O3WP7UQw8Zxv97dYOmFBwxTw8NPz4hGaOgOCwRCZUY/AX2lKS/6HjxhXEcM3LuefBg= 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.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="So4+lB7/" Received: by mail-wm1-f44.google.com with SMTP id 5b1f17b1804b1-43cf680d351so37092095e9.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=RkvgUdwenviv2JgWkaX4craCPIh2JIpm/D2hu0FydHVik1j2pK9s0NTtn3pRLPhFAD E8y+MPGNfQDsL/u6zFFdF0Sqz92jPQvk39mFkjU9vu6ELgf535838t7g9tUApdrl/vSj KNzocKXsZe7cldr8T6ArTobr/a1SIDz6kDBLjvH5sUDCAsyVL451Xnxp7U0RJA8I92xc Pu+b8XrGtFypGhkPIx15QYwzE9JtrZN/YSnmwCAeTmSN+H4yow454cpt5HV1EWn6/y7u iMYRdccLp3dDnehkPq4rkiunB4brxBx3ensvXlfgUZT13exgEkv7a5avUJQGtIfXpQQQ EgfA== X-Forwarded-Encrypted: i=1; AJvYcCXPc+fw+FKLz+KktvZbnHqS51jam3rIYIarOIkt4ENKlg8BGRg2DEfy0PCdCm/HZJZppCG/oqsX4L2GSmWr+Rk=@vger.kernel.org X-Gm-Message-State: AOJu0YylWii7VACrrsWs8mw70IeUmZByPpLTnL1mQT+wUd5SMHIerBLM vAbr0xENrpwAE3KnscMOOqqaoS8S31jFEHHbg6yEMVa6KDas0z0eb3OdtMJv6L8IDJ7rxsXhtWU JqTpEhUCUIqtBR5hnqWTC2Om92hx1qbIAQv3RjyZMRrUjY57MslVtyeHr4kU= X-Gm-Gg: ASbGncst2bZmcUeWzQ2QQ7yjGTH/Rf3IRSEff7CReZ6z+JTSXDz06tuD41o0coQDMkH rb87bRj+2v1A/gGua59mbfHXw8+jJHlTwoD6MWuR5K9g+X48HpYUCKwcw4iMBBB/IdFDlzMgkTz 9GCNgo2fo/NC2w3EZfgiTtxmg1b4KMf9BEpDSq7D7ne/Xdt45uRO3gYpsF0L0NCcOQb+5OMDdrQ fYC/49Zmf+qI9bY1eCJf7oE1o0vnKiIHTcNKs+RSMghHMuVHzNJyYv5eevL+pfr7xLxW18VGhav Ex9G6sihwK0rFDiZJw22/k+D/A/320dcUYRDWw== 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-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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..096c51f0c69a8de777281af35e345dd90f27ecd2 --- /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-Clause) +# +# 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 identify + 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 only) + 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 only) + 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 only) + 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 to the + peer + - + name: keepalive-timeout + type: u32 + doc: >- + The number of seconds from the last activity after which the peer is + assumed dead + - + name: del-reason + type: u32 + doc: The reason why a peer was deleted + enum: del-peer-reason + - + name: 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 specific + 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..c50e87ef728848965f7de80474acf9cd4e5bab75 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 OPENVSWITCH M: Aaron Conole diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 876800ebaa21a5f758ddf60f637801710437f70e..75ac62bba02937bc49cb2a0dec5ca3cc31a8ee00 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -8,3 +8,5 @@ obj-$(CONFIG_OVPN) := ovpn.o ovpn-y += main.o +ovpn-y += netlink.o +ovpn-y += netlink-gen.o diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index d5d4ac8da6d78e18960e18cd5f8d522185900542..719cac3db6ef4e06b089790afefb2e66223d43d3 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -7,9 +7,29 @@ * James Yonan */ +#include #include #include #include +#include + +#include "ovpnpriv.h" +#include "main.h" +#include "netlink.h" + +static const struct net_device_ops ovpn_netdev_ops = { +}; + +/** + * ovpn_dev_is_valid - check if the netdevice is of type 'ovpn' + * @dev: the interface to check + * + * Return: whether the netdevice is of type 'ovpn' + */ +bool ovpn_dev_is_valid(const struct net_device *dev) +{ + return dev->netdev_ops == &ovpn_netdev_ops; +} static int ovpn_newlink(struct net_device *dev, struct rtnl_newlink_params *params, @@ -34,11 +54,22 @@ static int __init ovpn_init(void) return err; } + err = ovpn_nl_register(); + if (err) { + pr_err("ovpn: can't register netlink family: %d\n", err); + goto unreg_rtnl; + } + return 0; + +unreg_rtnl: + rtnl_link_unregister(&ovpn_link_ops); + return err; } static __exit void ovpn_cleanup(void) { + ovpn_nl_unregister(); rtnl_link_unregister(&ovpn_link_ops); rcu_barrier(); diff --git a/drivers/net/ovpn/main.h b/drivers/net/ovpn/main.h new file mode 100644 index 0000000000000000000000000000000000000000..017cd0100765900181cb662319cf38c2d0b7dd2d --- /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..58e1a4342378eacc67dec19de34fa4bb45f16fe2 --- /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-Clause) +/* 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 = { + .max = 16777215ULL, +}; + +static const struct netlink_range_validation ovpn_a_keyconf_peer_id_range = { + .max = 16777215ULL, +}; + +/* Common nested types */ +const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR + 1] = { + [OVPN_A_KEYCONF_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_keyconf_peer_id_range), + [OVPN_A_KEYCONF_SLOT] = NLA_POLICY_MAX(NLA_U32, 1), + [OVPN_A_KEYCONF_KEY_ID] = NLA_POLICY_MAX(NLA_U32, 7), + [OVPN_A_KEYCONF_CIPHER_ALG] = NLA_POLICY_MAX(NLA_U32, 2), + [OVPN_A_KEYCONF_ENCRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy), + [OVPN_A_KEYCONF_DECRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy), +}; + +const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1] = { + [OVPN_A_KEYDIR_CIPHER_KEY] = NLA_POLICY_MAX_LEN(256), + [OVPN_A_KEYDIR_NONCE_TAIL] = NLA_POLICY_EXACT_LEN(OVPN_NONCE_TAIL_SIZE), +}; + +const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_LINK_TX_PACKETS + 1] = { + [OVPN_A_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_peer_id_range), + [OVPN_A_PEER_REMOTE_IPV4] = { .type = NLA_BE32, }, + [OVPN_A_PEER_REMOTE_IPV6] = NLA_POLICY_EXACT_LEN(16), + [OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID] = { .type = NLA_U32, }, + [OVPN_A_PEER_REMOTE_PORT] = NLA_POLICY_MIN(NLA_BE16, 1), + [OVPN_A_PEER_SOCKET] = { .type = NLA_U32, }, + [OVPN_A_PEER_SOCKET_NETNSID] = { .type = NLA_S32, }, + [OVPN_A_PEER_VPN_IPV4] = { .type = NLA_BE32, }, + [OVPN_A_PEER_VPN_IPV6] = NLA_POLICY_EXACT_LEN(16), + [OVPN_A_PEER_LOCAL_IPV4] = { .type = NLA_BE32, }, + [OVPN_A_PEER_LOCAL_IPV6] = NLA_POLICY_EXACT_LEN(16), + [OVPN_A_PEER_LOCAL_PORT] = NLA_POLICY_MIN(NLA_BE16, 1), + [OVPN_A_PEER_KEEPALIVE_INTERVAL] = { .type = NLA_U32, }, + [OVPN_A_PEER_KEEPALIVE_TIMEOUT] = { .type = NLA_U32, }, + [OVPN_A_PEER_DEL_REASON] = NLA_POLICY_MAX(NLA_U32, 4), + [OVPN_A_PEER_VPN_RX_BYTES] = { .type = NLA_UINT, }, + [OVPN_A_PEER_VPN_TX_BYTES] = { .type = NLA_UINT, }, + [OVPN_A_PEER_VPN_RX_PACKETS] = { .type = NLA_UINT, }, + [OVPN_A_PEER_VPN_TX_PACKETS] = { .type = NLA_UINT, }, + [OVPN_A_PEER_LINK_RX_BYTES] = { .type = NLA_UINT, }, + [OVPN_A_PEER_LINK_TX_BYTES] = { .type = NLA_UINT, }, + [OVPN_A_PEER_LINK_RX_PACKETS] = { .type = NLA_UINT, }, + [OVPN_A_PEER_LINK_TX_PACKETS] = { .type = NLA_UINT, }, +}; + +/* OVPN_CMD_PEER_NEW - do */ +static const struct nla_policy ovpn_peer_new_nl_policy[OVPN_A_PEER + 1] = { + [OVPN_A_IFINDEX] = { .type = NLA_U32, }, + [OVPN_A_PEER] = 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] = { + [OVPN_A_IFINDEX] = { .type = NLA_U32, }, + [OVPN_A_PEER] = 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] = { + [OVPN_A_IFINDEX] = { .type = NLA_U32, }, + [OVPN_A_PEER] = 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] = { + [OVPN_A_IFINDEX] = { .type = NLA_U32, }, +}; + +/* OVPN_CMD_PEER_DEL - do */ +static const struct nla_policy ovpn_peer_del_nl_policy[OVPN_A_PEER + 1] = { + [OVPN_A_IFINDEX] = { .type = NLA_U32, }, + [OVPN_A_PEER] = 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] = { + [OVPN_A_IFINDEX] = { .type = NLA_U32, }, + [OVPN_A_KEYCONF] = 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] = { + [OVPN_A_IFINDEX] = { .type = NLA_U32, }, + [OVPN_A_KEYCONF] = 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] = { + [OVPN_A_IFINDEX] = { .type = NLA_U32, }, + [OVPN_A_KEYCONF] = 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] = { + [OVPN_A_IFINDEX] = { .type = NLA_U32, }, + [OVPN_A_KEYCONF] = NLA_POLICY_NESTED(ovpn_keyconf_nl_policy), +}; + +/* Ops table for ovpn */ +static const struct genl_split_ops ovpn_nl_ops[] = { + { + .cmd = OVPN_CMD_PEER_NEW, + .pre_doit = ovpn_nl_pre_doit, + .doit = ovpn_nl_peer_new_doit, + .post_doit = ovpn_nl_post_doit, + .policy = ovpn_peer_new_nl_policy, + .maxattr = OVPN_A_PEER, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = OVPN_CMD_PEER_SET, + .pre_doit = ovpn_nl_pre_doit, + .doit = ovpn_nl_peer_set_doit, + .post_doit = ovpn_nl_post_doit, + .policy = ovpn_peer_set_nl_policy, + .maxattr = OVPN_A_PEER, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = OVPN_CMD_PEER_GET, + .pre_doit = ovpn_nl_pre_doit, + .doit = ovpn_nl_peer_get_doit, + .post_doit = ovpn_nl_post_doit, + .policy = ovpn_peer_get_do_nl_policy, + .maxattr = OVPN_A_PEER, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = OVPN_CMD_PEER_GET, + .dumpit = ovpn_nl_peer_get_dumpit, + .policy = ovpn_peer_get_dump_nl_policy, + .maxattr = OVPN_A_IFINDEX, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = OVPN_CMD_PEER_DEL, + .pre_doit = ovpn_nl_pre_doit, + .doit = ovpn_nl_peer_del_doit, + .post_doit = ovpn_nl_post_doit, + .policy = ovpn_peer_del_nl_policy, + .maxattr = OVPN_A_PEER, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = OVPN_CMD_KEY_NEW, + .pre_doit = ovpn_nl_pre_doit, + .doit = ovpn_nl_key_new_doit, + .post_doit = ovpn_nl_post_doit, + .policy = ovpn_key_new_nl_policy, + .maxattr = OVPN_A_KEYCONF, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = OVPN_CMD_KEY_GET, + .pre_doit = ovpn_nl_pre_doit, + .doit = ovpn_nl_key_get_doit, + .post_doit = ovpn_nl_post_doit, + .policy = ovpn_key_get_nl_policy, + .maxattr = OVPN_A_KEYCONF, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = OVPN_CMD_KEY_SWAP, + .pre_doit = ovpn_nl_pre_doit, + .doit = ovpn_nl_key_swap_doit, + .post_doit = ovpn_nl_post_doit, + .policy = ovpn_key_swap_nl_policy, + .maxattr = OVPN_A_KEYCONF, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = OVPN_CMD_KEY_DEL, + .pre_doit = ovpn_nl_pre_doit, + .doit = ovpn_nl_key_del_doit, + .post_doit = ovpn_nl_post_doit, + .policy = ovpn_key_del_nl_policy, + .maxattr = OVPN_A_KEYCONF, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, +}; + +static const struct genl_multicast_group ovpn_nl_mcgrps[] = { + [OVPN_NLGRP_PEERS] = { "peers", }, +}; + +struct genl_family ovpn_nl_family __ro_after_init = { + .name = OVPN_FAMILY_NAME, + .version = OVPN_FAMILY_VERSION, + .netnsok = true, + .parallel_ops = true, + .module = THIS_MODULE, + .split_ops = ovpn_nl_ops, + .n_split_ops = ARRAY_SIZE(ovpn_nl_ops), + .mcgrps = ovpn_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(ovpn_nl_mcgrps), +}; diff --git a/drivers/net/ovpn/netlink-gen.h b/drivers/net/ovpn/netlink-gen.h new file mode 100644 index 0000000000000000000000000000000000000000..66a4e4a0a055b4477b67801ded825e9ec068b0e6 --- /dev/null +++ b/drivers/net/ovpn/netlink-gen.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/ovpn.yaml */ +/* YNL-GEN kernel header */ + +#ifndef _LINUX_OVPN_GEN_H +#define _LINUX_OVPN_GEN_H + +#include +#include + +#include + +/* Common nested types */ +extern const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR + 1]; +extern const struct nla_policy ovpn_keydir_nl_policy[OVPN_A_KEYDIR_NONCE_TAIL + 1]; +extern const struct nla_policy ovpn_peer_nl_policy[OVPN_A_PEER_LINK_TX_PACKETS + 1]; + +int ovpn_nl_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); +void +ovpn_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); + +int ovpn_nl_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..8d267d4c82283d9b5f989478102086ce385195d5 --- /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 netdevice + * 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 = nla_get_u32(info->attrs[OVPN_A_IFINDEX]); + + rcu_read_lock(); + dev = 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 = 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 = (netdevice_tracker *)&info->user_ptr[1]; + struct ovpn_priv *ovpn = ovpn_get_dev_from_attrs(genl_info_net(info), + info, tracker); + + if (IS_ERR(ovpn)) + return PTR_ERR(ovpn); + + info->user_ptr[0] = ovpn; + + return 0; +} + +void ovpn_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + netdevice_tracker *tracker = (netdevice_tracker *)&info->user_ptr[1]; + struct ovpn_priv *ovpn = 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 = 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..0d6c34e17082cc7c52dd9c5d5ed1e964925b3f4b --- /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..f9322536b06d6baa5524de57cd7d69f5ecbbd194 --- /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..680d1522dc87c222bddc33cbb7bcef290e2dab9a --- /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-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/ovpn.yaml */ +/* YNL-GEN uapi header */ + +#ifndef _UAPI_LINUX_OVPN_H +#define _UAPI_LINUX_OVPN_H + +#define OVPN_FAMILY_NAME "ovpn" +#define OVPN_FAMILY_VERSION 1 + +#define OVPN_NONCE_TAIL_SIZE 8 + +enum ovpn_cipher_alg { + OVPN_CIPHER_ALG_NONE, + OVPN_CIPHER_ALG_AES_GCM, + OVPN_CIPHER_ALG_CHACHA20_POLY1305, +}; + +enum ovpn_del_peer_reason { + OVPN_DEL_PEER_REASON_TEARDOWN, + OVPN_DEL_PEER_REASON_USERSPACE, + OVPN_DEL_PEER_REASON_EXPIRED, + OVPN_DEL_PEER_REASON_TRANSPORT_ERROR, + OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT, +}; + +enum ovpn_key_slot { + OVPN_KEY_SLOT_PRIMARY, + OVPN_KEY_SLOT_SECONDARY, +}; + +enum { + OVPN_A_PEER_ID = 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 = (__OVPN_A_PEER_MAX - 1) +}; + +enum { + OVPN_A_KEYCONF_PEER_ID = 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 = (__OVPN_A_KEYCONF_MAX - 1) +}; + +enum { + OVPN_A_KEYDIR_CIPHER_KEY = 1, + OVPN_A_KEYDIR_NONCE_TAIL, + + __OVPN_A_KEYDIR_MAX, + OVPN_A_KEYDIR_MAX = (__OVPN_A_KEYDIR_MAX - 1) +}; + +enum { + OVPN_A_IFINDEX = 1, + OVPN_A_PEER, + OVPN_A_KEYCONF, + + __OVPN_A_MAX, + OVPN_A_MAX = (__OVPN_A_MAX - 1) +}; + +enum { + OVPN_CMD_PEER_NEW = 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 = (__OVPN_CMD_MAX - 1) +}; + +#define OVPN_MCGRP_PEERS "peers" + +#endif /* _UAPI_LINUX_OVPN_H */ From patchwork Tue Apr 15 11:17:21 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 881519 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 79A9F29291B for ; Tue, 15 Apr 2025 11:17:31 +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=1744715854; cv=none; b=QT6lvOViPMUfFMQaCelkwj9jMUmJjzzIUd8o5zNuvEhkYLRU50K6UoxbQUGWDqze5oHymYqjjXMjEUdF7HxRHHhiktCA2xEYypOxrWGfuzIZ5hdQktJz0/vELMOTPmmQmskvY2tTiqxLqm4ydTYEy0YeFvOxYiYcp6znfYwsLVE= 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.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="Bk5dyTrD" Received: by mail-wm1-f46.google.com with SMTP id 5b1f17b1804b1-43690d4605dso42516325e9.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=WonU8U3lpuPiSslIPnkuupzusuBVQlglb1XVjpltrQkDp3FiTlADAUuDUw4QRbPzL0 glvEIAE1TVE9K2id8xkwPWy0Y8fcdEPROL69Z0t4zWNF+He2n9kNCrWwEBfe0Y6pI7t/ cbE0bTM+2iEudZGKoQhAQsJwGpfIW5VqjBXNwTah0/qOFT9hmCszjhqacBPRAgRPesEo jedVCwOBgUcQsjLxROV2fWm/g85kWS67qY9nqB9Z0bPSrOSvmoclMo5tlZAxgXOBAtdT 9Hf1mVcuWyzBR+B1zUjR/DXR7BVEx2lPpST/KJt+C+ozpfwzhflXuo7g/rXofw5wYvoe mQ2A== X-Forwarded-Encrypted: i=1; AJvYcCXTHZ8nuM39yWaEitkZG3Ys+RaOEBb0YIq5EFCgwgYH7X0s2j+foXe7TKxTQH/aGaI2vUP1qX9bCwwUSXyi/LA=@vger.kernel.org X-Gm-Message-State: AOJu0YwjMIoqwgOmCI1UNpmWwvWKKVd0KSGLRLAQlqIMozITUzcmM57u TkWs18v48+zmAAKMoAAHzJJ0NREBXUDXK1+WBNo4FpJ5X8pCj039+0Aemkxcs042/2RDp9LFVG3 aao+/2oDAFQkL5N7bY2b3eGBs9a//Hyn94+8Wyp61RUtDfgt8h1DJnoVeosM= X-Gm-Gg: ASbGncukGgwhsy2U/WkKowwuoVZvk6PkIjYRa9ACnBHyo9hTS/UAq8GGndpM3RE6BlV 2DEEJ520PJ6jS4c69vTBZAWYUR4BkOuYU2nux9UOyEt1zTSYGC8+d+yrvy4iRrNsnE+0gfFsCAx UUJVTWgCEkWBotMVFGld8rScMVwAx426JFdds7xH5yczxiFNbEy7g9+P0rmUzMTl5jzHxD5Nxbs guLFwKvhelt7m0phV9n7a9suHs9ZxTZNbyhiTi+hO+YPiK8hUGLFRutbIjGbfEKXcnsJjNXtF+F di+dakdMM/l4vsGJ4gBFTnNwwsKnSuwhZrA6lA== 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-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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..fa1dd84be24060d6dc749dc9e097f166a7adea26 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 = dev; ovpn->mode = mode; - /* 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 == OVPN_MODE_MP) + netif_carrier_on(dev); + else + netif_carrier_off(dev); return register_netdevice(dev); } From patchwork Tue Apr 15 11:17:23 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 881518 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 42FD72951A3 for ; Tue, 15 Apr 2025 11:17:33 +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=1744715857; cv=none; b=LiijgnoOkb5/DdTl9XE5aSujhLRkILcOUBo2aNVx6vzYJG8G5kIsnWurkPLDYXlFu9JiqKaC3CawWoT7QwAVQ+F9B8osnhMrtraqhAErWc0ItYkCGClCibXX6xO8ky+mUG1SpwB4nqJok/qznEJgSsUPAeL2sLA+XCatnyNnP78= 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.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="Vq/R36uh" Received: by mail-wm1-f41.google.com with SMTP id 5b1f17b1804b1-43cebe06e9eso40391115e9.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=UvZPpK69YDifOpAufo41x3QtjRYm93EfG384tQ7NoPn4Ms3ae8CjPtQplO7eSqAOyu IyceoXrA+fd/GgOLF02aaqkT9E42G9qZfRDXrvkFsosF/S3p+WZjT/9xoChn59uOI45x Clu3kGZ3WNaoqGo2x3zlrnaI2d+OhrElTUI3MLOIXy7Xpj1SRwKAovYVbKX5XfVyuWhH dJMZjZTXaZMIsxPsCc+PQ0vMNMVSLkXNqa1wwJhV5jXjkPi8EW9ytP44j83zIHIByNop yhuu10jqzvnd360sDaNIUbMAHQuHS1ZOKy+IW8mrWxYz2hCt3UBS5g3MpxI85sqFaLOc vlrg== X-Forwarded-Encrypted: i=1; AJvYcCUBfLn+SHIamB7wE5QSS/N1TuIwoARGheNMqkbBDsu/Rbx8Qax/WH1fbkZmRLVRQgB6psdGZq+ZxHWY6m9OwMc=@vger.kernel.org X-Gm-Message-State: AOJu0Ywv3V4HYjXQpGo+DsAclTSSOWe7IskYLHz0l/zm8i+RqLXjV4dF W7ikEzPH/oQBuDhJRHhbuTf02h67rsXmGE2aUYq/K1eVg9vqaMeNgqWY5fQYcJkTPoNNdjkG6fg BoJe8efJKaSg/wPAVtNJQcIGJIimx4EktLvp92yLf2OSP5Enl8S1lqGMp2D0= X-Gm-Gg: ASbGncvCBI0Kr+Mm3FsOEQWy9ZlebBG3YB0psL4mmDSkDjUyek9MtaViOwFZm29ypWL nLCONbYGs0Cl2zvPDwRpL+GlYcIAA7bSxfNuMDybcSvjX+5IuXMK8oI9R2MznhmxDTcFbBDsjy9 IHvQNPj6isbLXh84AzXdRll1sXsSV8xDTrOCgtvYgNdcAnPEVuVYksxSJicraXF4mQ/6NHPHaFz u3sURaK3DnRaE8lzMWFA5BjGznVwO3vXrk6rJKoV3QPXWAVzkZXuvWUaQmZ7ztOUGGu0Wbvfkc+ xlFcGEC1h0jJcNpUOlYNDaaUtD1bY9fLRbeb0g== 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-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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..164f2058ea8e6dc5b9287afb59758a268b2f8b56 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -13,3 +13,5 @@ ovpn-y += io.o ovpn-y += netlink.o ovpn-y += netlink-gen.o ovpn-y += peer.o +ovpn-y += socket.o +ovpn-y += udp.o diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 3c2b6a7df27266dcaacc1ea4f8d86c686d3105aa..e9a6dc100d4675353bfb308a5195590e7df04b1d 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 = netdev_priv(dev); if (ovpn->mode == OVPN_MODE_P2P) - ovpn_peer_release_p2p(ovpn, OVPN_DEL_PEER_REASON_TEARDOWN); + ovpn_peer_release_p2p(ovpn, NULL, + OVPN_DEL_PEER_REASON_TEARDOWN); unregister_netdevice_queue(dev, head); } diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 338069c99248f42b0c4aeb44b2b9d3a35f8bebeb..0bb6c15171848acbc055829a3d2aefd26c5b810a 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" 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; spin_unlock_bh(&ovpn->lock); - 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); + } } /** @@ -394,18 +397,33 @@ int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_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; spin_lock_bh(&ovpn->lock); peer = 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 = rcu_access_pointer(peer->sock); + if (!ovpn_sock || ovpn_sock->sock->sk != 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..29c9065cedccb156ec6ca6d9b692372e8fc89a2d 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -12,6 +12,8 @@ #include +#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); 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..beec7aee35e15f659873d10318a2c7f2a6ecf651 --- /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 = container_of(kref, struct ovpn_socket, + refcount); + + if (sock->sock->sk->sk_protocol == 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 *sock) +{ + 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 = 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 == 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 == IPPROTO_TCP && sock->sk->sk_user_data) { + ovpn_sock = 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 == IPPROTO_UDP) { + u8 type = READ_ONCE(udp_sk(sock->sk)->encap_type); + + /* socket owned by other encapsulation module */ + if (type && type != UDP_ENCAP_OVPNINUDP) { + ovpn_sock = ERR_PTR(-EBUSY); + goto sock_release; + } + + rcu_read_lock(); + ovpn_sock = 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 != peer->ovpn) { + ovpn_sock = 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 = 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 = kzalloc(sizeof(*ovpn_sock), GFP_KERNEL); + if (!ovpn_sock) { + ovpn_sock = ERR_PTR(-ENOMEM); + goto sock_release; + } + + ovpn_sock->ovpn = peer->ovpn; + ovpn_sock->sock = sock; + kref_init(&ovpn_sock->refcount); + + ret = ovpn_socket_attach(ovpn_sock, peer); + if (ret < 0) { + kfree(ovpn_sock); + ovpn_sock = 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..ade8c94619d7b2f905b5284373dc73f590188399 --- /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..91970e66a4340370a96c1fc42321f94574302143 --- /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 ovpn + * @ovpn_sock: socket to configure + * @ovpn: the openvp instance to link + * + * After invoking this function, the sock will be controlled by ovpn so that + * any incoming packet may be processed by ovpn first. + * + * Return: 0 on success or a negative error code otherwise + */ +int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, + struct ovpn_priv *ovpn) +{ + struct socket *sock = ovpn_sock->sock; + struct ovpn_socket *old_data; + int ret = 0; + + /* make sure no pre-existing encapsulation handler exists */ + rcu_read_lock(); + old_data = rcu_dereference_sk_user_data(sock->sk); + if (!old_data) { + /* socket is currently unused - we can take it */ + rcu_read_unlock(); + return 0; + } + + /* socket is in use. We need to understand if it's owned by this ovpn + * instance or by something else. + * In the former case, we can increase the refcounter and happily + * use it, because the same UDP socket is expected to be shared among + * different peers. + * + * Unlikely TCP, a single UDP socket can be used to talk to many remote + * hosts and therefore openvpn instantiates one only for all its peers + */ + if ((READ_ONCE(udp_sk(sock->sk)->encap_type) == UDP_ENCAP_OVPNINUDP) && + old_data->ovpn == ovpn) { + netdev_dbg(ovpn->dev, + "provided socket already owned by this interface\n"); + ret = -EALREADY; + } else { + netdev_dbg(ovpn->dev, + "provided socket already taken by other user\n"); + ret = -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..1c8fb6fe402dc1cfdc10fddc9cf5b74d7d6887ce --- /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..edca3e430305a6bffc34e617421f1f3071582e69 100644 --- a/include/uapi/linux/udp.h +++ b/include/uapi/linux/udp.h @@ -43,5 +43,6 @@ struct udphdr { #define UDP_ENCAP_GTP1U 5 /* 3GPP TS 29.060 */ #define UDP_ENCAP_RXRPC 6 #define TCP_ENCAP_ESPINTCP 7 /* Yikes, this is really xfrm encap types. */ +#define UDP_ENCAP_OVPNINUDP 8 /* OpenVPN traffic */ #endif /* _UAPI_LINUX_UDP_H */ From patchwork Tue Apr 15 11:17:25 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 881517 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 D381A2957CF for ; Tue, 15 Apr 2025 11:17:39 +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=1744715863; cv=none; b=tWoPmMGgIxpK0kbKc88hjRKyugAV/lGwEzGhbpOcn39SYflQGBtFN5FZ2ozIJCkSYjDgGJKE9lL9yUsLJsU14kMjNPs69WIUwF/Ug9IQSxQ8Dlj4WSz6TZoHOTv0ppJ5DOLeQeZFuqNECs9WZv8o8t3hxHSMKsXssadFNl54xfs= 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.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="KFFUaAlY" Received: by mail-wm1-f41.google.com with SMTP id 5b1f17b1804b1-43cec5cd73bso36770075e9.3 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=t8u9GsfFpHTiwvjnuJ8mXeINxpir4OA9PstCfwHsYBfhy65/OTl5VttO2G6DzuuzV/ n+lFIQjHvL62Hl+DdLMaxsl4lN4Um3tjefEUfBdefSzXJdjQ0NpeYQOM4N3XgIjx7R7F e9CyYThOQPFUWDdM/mlzKSDcJU87OkkIjFGJ4Jos7zf3zSeoyh34DoGclQ5yTtmSVzE0 XkQZ4QJQDEubd7/PbOwp+FiHs461Z1dYICNLg2gAJtVqaVJIXKiyUxJwBHIXmQPn0IZr INKen62iaGSg6SvikbuHaKTlsMxK5JV1mPA4lITLMlj8GZnzKJcMEo8e922pzZ25DBcA fSPA== X-Forwarded-Encrypted: i=1; AJvYcCUBTiYw3yG4rlgRM2GAX4OTMqfKqrg1YXjoZLAOqGvNkpAqsxfEU5JYCEd139NSnmJYuusFy+BiokChpSszU10=@vger.kernel.org X-Gm-Message-State: AOJu0YwDZXMWMdjfJ/8lQGpeGvPUR+m6U5c2xPsWQsweFVlCfMS+RgCj SOxPvl38IA4p2ZugiQ6e1q5u/iWpZiB+7b07l/kk3qbSrrimyL3UjSscglhggqkvDMpt6SQqY11 Ge1UkNydaMBUBezixMgp/UJLJVu1OM8272ObOcCHwooE4n9abLIMLoqrrA84= X-Gm-Gg: ASbGncu/+3qTHAja61CuGB841ej+BqU8LMZEmSpUIUqV2GYEQBi37PEHCKxU1GbUViE HVzMWLiRuCYsDNfnCgbcow2yWGuUDQ/P0zV2czuaUmS351yJTlUbrGcpn5qcEdmQ4Yqx5RBIlL5 BFTL0L4cAmPYKsoYTu67V7JXRiJEHqvIYw4k/Eu1GxSKcpdr9ocuCERsMo5KYLECDrXKBsoJTyX hrtA1ZY+wOrqtluitJ297i+baUvorz8QxpIHXEnRPZDPmdJO7LwzHHeLvMxGKZ5i3g8yWmqleHA HxsEXKiJ7VgPoFqeRR5NOVF3newP4nkVMbqJiA== 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-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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..22fa92b9aeeb55a71fb885037418c643cd34b269 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -9,15 +9,77 @@ #include #include +#include #include -#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" +/* 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 = 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 = 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 = skb->len; + ret = gro_cells_receive(&peer->ovpn->gro_cells, skb); + if (likely(ret == 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 = ovpn_skb_cb(skb)->peer; + + if (unlikely(ret < 0)) + goto drop; + + ovpn_netdev_write(peer, skb); + /* skb is passed to upper layer - don't free it */ + skb = NULL; +drop: + if (unlikely(skb)) + dev_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 = peer; + ovpn_decrypt_post(skb, 0); +} + static void ovpn_encrypt_post(struct sk_buff *skb, int ret) { struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer; diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h index afea5f81f5628dcb9afda9a78974bbf6f2101c13..1cfa66873a2d4840ce57e337f8b4e8143e8b8e79 100644 --- a/drivers/net/ovpn/io.h +++ b/drivers/net/ovpn/io.h @@ -21,4 +21,6 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev); +void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb); + #endif /* _NET_OVPN_OVPN_H_ */ diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index e9a6dc100d4675353bfb308a5195590e7df04b1d..b356c2235d46fdde500cab4f0b21331cc2fb60c8 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 = netdev_priv(dev); + + return gro_cells_init(&ovpn->gro_cells, dev); +} + +static void ovpn_net_uninit(struct net_device *dev) +{ + struct ovpn_priv *ovpn = netdev_priv(dev); + + gro_cells_destroy(&ovpn->gro_cells); +} static const struct net_device_ops ovpn_netdev_ops = { + .ndo_init = ovpn_net_init, + .ndo_uninit = ovpn_net_uninit, .ndo_start_xmit = ovpn_net_xmit, }; diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index a398f9da2e0958b0be885a50a13d80b8b5a515eb..d31cceee508d7066bf79b71d77d05d039dc97f29 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_ +#include #include #include @@ -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; }; #endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/drivers/net/ovpn/proto.h b/drivers/net/ovpn/proto.h index 5f95a78bebd3702868ffeeab3ea4938e957d568c..591b97a9925fd9b91f996d6d591fac41b1aa6148 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_ +#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) #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 offset) +{ + u32 opcode = 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 offset) +{ + u32 opcode = be32_to_cpu(*(__be32 *)(skb->data + offset)); + + return FIELD_GET(OVPN_OPCODE_PEERID_MASK, opcode); +} -#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..f1fd98d528451e93a10b872e4d9b37c3940d339b 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 = container_of(kref, struct ovpn_socket, refcount); - if (sock->sock->sk->sk_protocol == IPPROTO_UDP) + if (sock->sock->sk->sk_protocol == IPPROTO_UDP) { ovpn_udp_socket_detach(sock); + netdev_put(sock->ovpn->dev, &sock->dev_tracker); + } kfree_rcu(sock, rcu); } @@ -179,7 +181,6 @@ struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer) goto sock_release; } - ovpn_sock->ovpn = peer->ovpn; ovpn_sock->sock = sock; kref_init(&ovpn_sock->refcount); @@ -190,6 +191,15 @@ struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer) goto sock_release; } + if (sock->sk->sk_protocol == IPPROTO_UDP) { + /* in UDP we only link the ovpn instance since the socket is + * shared among multiple peers + */ + ovpn_sock->ovpn = 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..c1697f4616d470f143cba77bb1db34de0398a37a 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..920d71da793ccc88de4cff1c99c8e6e8d0a24b71 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -23,9 +24,114 @@ #include "bind.h" #include "io.h" #include "peer.h" +#include "proto.h" #include "socket.h" #include "udp.h" +/* 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) != UDP_ENCAP_OVPNINUDP)) + return NULL; + + ovpn_sock = rcu_dereference_sk_user_data(sk); + if (unlikely(!ovpn_sock)) + return NULL; + + /* make sure that sk matches our stored transport socket */ + if (unlikely(!ovpn_sock->sock || sk != ovpn_sock->sock->sk)) + return NULL; + + return ovpn_sock; +} + +/** + * ovpn_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 = 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 = 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 = ovpn_opcode_from_skb(skb, sizeof(struct udphdr)); + if (unlikely(opcode != OVPN_DATA_V2)) { + /* DATA_V1 is not supported */ + if (opcode == OVPN_DATA_V1) + goto drop; + + /* unknown or control packet: let it bubble up to userspace */ + return 1; + } + + peer_id = ovpn_peer_id_from_skb(skb, sizeof(struct udphdr)); + /* some OpenVPN server implementations send data packets with the + * peer-id set to UNDEF. In this case we skip the peer lookup by peer-id + * and we try with the transport address + */ + if (peer_id == OVPN_PEER_ID_UNDEF) + peer = ovpn_peer_get_by_transp_addr(ovpn, skb); + else + peer = 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, } } +static void ovpn_udp_encap_destroy(struct sock *sk) +{ + struct ovpn_socket *sock; + struct ovpn_priv *ovpn; + + rcu_read_lock(); + sock = rcu_dereference_sk_user_data(sk); + if (!sock || !sock->ovpn) { + rcu_read_unlock(); + return; + } + ovpn = sock->ovpn; + rcu_read_unlock(); + + if (ovpn->mode == 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 ovpn * @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 = { + .encap_type = UDP_ENCAP_OVPNINUDP, + .encap_rcv = ovpn_udp_encap_recv, + .encap_destroy = ovpn_udp_encap_destroy, + }; struct socket *sock = ovpn_sock->sock; struct ovpn_socket *old_data; int ret; @@ -270,6 +400,7 @@ int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, 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; } @@ -303,4 +434,8 @@ int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, */ void ovpn_udp_socket_detach(struct ovpn_socket *ovpn_sock) { + struct udp_tunnel_sock_cfg cfg = { }; + + setup_udp_tunnel_sock(sock_net(ovpn_sock->sock->sk), ovpn_sock->sock, + &cfg); } From patchwork Tue Apr 15 11:17:26 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 881516 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (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 40C4D2973B6 for ; Tue, 15 Apr 2025 11:17:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715867; cv=none; b=JiJgyKegD1Lz5jXVqrvMp1dS6+m1RAkeTMQtjbUCwCzuh007SFQU7CpV4xcrH83wT/79oIEywUNfkcboX12KT4UkzF6c/VRoYDyPveeB+P+hMnK4SrlYOcSDkHMaiKpCsfqklqIV0m52jGYZrey1mlOJt5wCZhhgMCgBaRP7qlA= 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.42 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-f42.google.com with SMTP id 5b1f17b1804b1-43cfebc343dso41164305e9.2 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=Mp43pA1PocVcDGGUgrHMLC0FlUicCi25gj0Agqrh3cWQDozxksCDkqYVOf8QnxIy8/ SexUqEFcH7OkGShsEJnQK5+wXALLOuocUCiJ5/IIKRu4sfmg+5RtVUpVeGqrloVtGNvc SU+nI5KR6ha8+KcuzV7ZBk+2NahhXJ7gYdv3kRRTUd6VNEiijqWqbVGNUJyY1Yh8zOTm acAbHqJbHV9a5RHSStckKFH079Uxt5o+DLCTotCDsZ5zABYHgnecDzbZ+UtZn3jxqDtY pv+he6MmGO9LJlR0Dd+rvSKVyemvIaYrwzsAVeViK/nrFb5qg/EVvYsuzaAuergZ5MDY wHAg== X-Forwarded-Encrypted: i=1; AJvYcCXELK/PpoXIhvHdH/0b6OPn0v8EywLLXnN6gZ709CGU3h1/JPPfvwFZByzHYkqkxvS+W0joU0rXN7I21DWoh+4=@vger.kernel.org X-Gm-Message-State: AOJu0YwdoHmpXJOkDGxUVT/VHKNWhjk8v6Sq1jbrgq1fWB0bl5mn5/tN 8ryVSrMs6RaWLxAMHmobiFs52CjZLxzwg1/lkmygsui5FOl/Dc+OuWFGL5tNoF9/dEqshqf0sFq Fz8xwH5gGq1n+f0ToA6S20fgpQvyefBUjeDCFoP422QKUVTIPW4ei3dvY93U= X-Gm-Gg: ASbGncsti9cJgTRrXjqKXNYqPA1kBvTDwWVZJ3ONl45YHTXijoX01ZOZJJtt/fMw3f8 QruoU4GbxhFbQAGxk+SOOd0ps0qSvlPRcgXLI1IyQwqJRpGlEeweIObAktB8fpty/ETj8eroYz5 njriEoVigwOZHLzmVrHWHm8VYDb3LkpblL/xc6Y/qq9fmc7tlOGnUlWcI49aoaMJxxOHFoSrUp5 tJavOizsOZl72qU+GeJdZCpQ+KQoW9Ifu7unReE5OCsZQJ3NNSJ38pemKgLCP1/NQyIEfwrQagv adN8/xNAj1AiuX3YGOZXU/ZwDvZdGD25rlH1Rg== 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-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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..a5cee847911b17a3584ab3d9c1cf7d166d4e1298 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..38c9fdca0e2e8e4af3c369ceb3971b58ab52d77b 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -8,10 +8,13 @@ obj-$(CONFIG_OVPN) := ovpn.o ovpn-y += bind.o +ovpn-y += crypto.o +ovpn-y += crypto_aead.o ovpn-y += main.o ovpn-y += io.o ovpn-y += netlink.o ovpn-y += netlink-gen.o ovpn-y += peer.o +ovpn-y += pktid.o ovpn-y += socket.o ovpn-y += udp.o diff --git a/drivers/net/ovpn/bind.c b/drivers/net/ovpn/bind.c index d4a1aeed12c99c71eaf5e8e9fc9c0fe61af6aaac..24d2788a277e674bde80b5aac9407c6528b108e5 100644 --- a/drivers/net/ovpn/bind.c +++ b/drivers/net/ovpn/bind.c @@ -48,11 +48,8 @@ struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *ss) */ void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *new) { - struct ovpn_bind *old; + lockdep_assert_held(&peer->lock); - spin_lock_bh(&peer->lock); - old = rcu_replace_pointer(peer->bind, new, true); - spin_unlock_bh(&peer->lock); - - kfree_rcu(old, rcu); + kfree_rcu(rcu_replace_pointer(peer->bind, new, + lockdep_is_held(&peer->lock)), rcu); } diff --git a/drivers/net/ovpn/crypto.c b/drivers/net/ovpn/crypto.c new file mode 100644 index 0000000000000000000000000000000000000000..9544255c4588ad0bcccda5bdc83a4d8729458ff8 --- /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 = container_of(head, struct ovpn_crypto_key_slot, rcu); + ovpn_aead_crypto_key_slot_destroy(ks); +} + +void ovpn_crypto_key_slot_release(struct kref *kref) +{ + struct ovpn_crypto_key_slot *ks; + + ks = container_of(kref, struct ovpn_crypto_key_slot, refcount); + call_rcu(&ks->rcu, ovpn_ks_destroy_rcu); +} + +/* can only be invoked when all peer references have been dropped (i.e. RCU + * release routine) + */ +void ovpn_crypto_state_release(struct ovpn_crypto_state *cs) +{ + struct ovpn_crypto_key_slot *ks; + + ks = rcu_access_pointer(cs->slots[0]); + if (ks) { + RCU_INIT_POINTER(cs->slots[0], NULL); + ovpn_crypto_key_slot_put(ks); + } + + ks = 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 = NULL, *new; + u8 idx; + + if (pkr->slot != OVPN_KEY_SLOT_PRIMARY && + pkr->slot != OVPN_KEY_SLOT_SECONDARY) + return -EINVAL; + + new = ovpn_aead_crypto_key_slot_new(&pkr->key); + if (IS_ERR(new)) + return PTR_ERR(new); + + spin_lock_bh(&cs->lock); + idx = cs->primary_idx; + switch (pkr->slot) { + case OVPN_KEY_SLOT_PRIMARY: + old = rcu_replace_pointer(cs->slots[idx], new, + lockdep_is_held(&cs->lock)); + break; + case OVPN_KEY_SLOT_SECONDARY: + old = 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 = NULL; + u8 idx; + + if (slot != OVPN_KEY_SLOT_PRIMARY && + slot != OVPN_KEY_SLOT_SECONDARY) { + pr_warn("Invalid slot to release: %u\n", slot); + return; + } + + spin_lock_bh(&cs->lock); + idx = cs->primary_idx; + switch (slot) { + case OVPN_KEY_SLOT_PRIMARY: + ks = rcu_replace_pointer(cs->slots[idx], NULL, + lockdep_is_held(&cs->lock)); + break; + case OVPN_KEY_SLOT_SECONDARY: + ks = 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=%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 = cs->primary_idx; + old_primary = rcu_dereference_protected(cs->slots[idx], + lockdep_is_held(&cs->lock)); + old_secondary = 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..5155791b87df7cccc76a011fa751686180074982 --- /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 = 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 = READ_ONCE(cs->primary_idx); + ks = rcu_dereference(cs->slots[idx]); + if (ks && ks->key_id == key_id) { + if (unlikely(!ovpn_crypto_key_slot_hold(ks))) + ks = NULL; + goto out; + } + + ks = rcu_dereference(cs->slots[!idx]); + if (ks && ks->key_id == key_id) { + if (unlikely(!ovpn_crypto_key_slot_hold(ks))) + ks = NULL; + goto out; + } + + /* when both key slots are occupied but no matching key ID is found, ks + * has to be reset to NULL to avoid carrying a stale pointer + */ + ks = NULL; +out: + rcu_read_unlock(); + + return ks; +} + +static inline struct ovpn_crypto_key_slot * +ovpn_crypto_key_slot_primary(const struct ovpn_crypto_state *cs) +{ + struct ovpn_crypto_key_slot *ks; + + rcu_read_lock(); + ks = rcu_dereference(cs->slots[cs->primary_idx]); + if (unlikely(ks && !ovpn_crypto_key_slot_hold(ks))) + ks = NULL; + rcu_read_unlock(); + + return ks; +} + +void ovpn_crypto_key_slot_release(struct kref *kref); + +static inline void ovpn_crypto_key_slot_put(struct ovpn_crypto_key_slot *ks) +{ + kref_put(&ks->refcount, ovpn_crypto_key_slot_release); +} + +int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs, + const struct ovpn_peer_key_reset *pkr); + +void ovpn_crypto_key_slot_delete(struct ovpn_crypto_state *cs, + enum ovpn_key_slot slot); + +void ovpn_crypto_state_release(struct ovpn_crypto_state *cs); + +void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs); + +#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..83ec18e4b9a4f7960bf789cee952ac11cb77083d --- /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 = 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 = peer; + ovpn_skb_cb(skb)->ks = 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 = 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 = kmalloc(sizeof(*ovpn_skb_cb(skb)->sg) * + (nfrags + 2), GFP_ATOMIC); + if (unlikely(!ovpn_skb_cb(skb)->sg)) + return -ENOMEM; + + sg = ovpn_skb_cb(skb)->sg; + + /* sg table: + * 0: op, wire nonce (AD, len=OVPN_OP_SIZE_V2+OVPN_NONCE_WIRE_SIZE), + * 1, 2, 3, ..., n: payload, + * n+1: auth_tag (len=tag_size) + */ + sg_init_table(sg, nfrags + 2); + + /* build scatterlist to encrypt packet payload */ + ret = skb_to_sgvec_nomark(skb, sg + 1, 0, skb->len); + if (unlikely(nfrags != ret)) + return -EINVAL; + + /* append auth_tag onto scatterlist */ + __skb_push(skb, tag_size); + sg_set_buf(sg + nfrags + 1, skb->data, tag_size); + + /* obtain packet ID, which is used both as a first + * 4 bytes of nonce and last 4 bytes of associated data. + */ + ret = ovpn_pktid_xmit_next(&ks->pid_xmit, &pktid); + if (unlikely(ret < 0)) + return ret; + + /* iv may be required by async crypto */ + ovpn_skb_cb(skb)->iv = kmalloc(OVPN_NONCE_SIZE, GFP_ATOMIC); + if (unlikely(!ovpn_skb_cb(skb)->iv)) + return -ENOMEM; + + iv = 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 = ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer->id); + __skb_push(skb, OVPN_OPCODE_SIZE); + BUILD_BUG_ON(sizeof(op) != OVPN_OPCODE_SIZE); + *((__force __be32 *)skb->data) = htonl(op); + + /* AEAD Additional data */ + sg_set_buf(sg, skb->data, OVPN_AAD_SIZE); + + req = aead_request_alloc(ks->encrypt, GFP_ATOMIC); + if (unlikely(!req)) + return -ENOMEM; + + ovpn_skb_cb(skb)->req = 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 = 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 = OVPN_AAD_SIZE + tag_size; + payload_len = skb->len - payload_offset; + + ovpn_skb_cb(skb)->payload_offset = payload_offset; + ovpn_skb_cb(skb)->peer = peer; + ovpn_skb_cb(skb)->ks = ks; + + /* sanity check on packet size, payload size must be >= 0 */ + if (unlikely(payload_len < 0)) + return -EINVAL; + + /* Prepare the skb data buffer to be accessed up until the auth tag. + * This is required because this area is directly mapped into the sg + * list. + */ + if (unlikely(!pskb_may_pull(skb, payload_offset))) + return -ENODATA; + + /* get number of skb frags and ensure that packet data is writable */ + nfrags = skb_cow_data(skb, 0, &trailer); + if (unlikely(nfrags < 0)) + return nfrags; + + if (unlikely(nfrags + 2 > (MAX_SKB_FRAGS + 2))) + return -ENOSPC; + + /* sg may be required by async crypto */ + ovpn_skb_cb(skb)->sg = kmalloc(sizeof(*ovpn_skb_cb(skb)->sg) * + (nfrags + 2), GFP_ATOMIC); + if (unlikely(!ovpn_skb_cb(skb)->sg)) + return -ENOMEM; + + sg = ovpn_skb_cb(skb)->sg; + + /* sg table: + * 0: op, wire nonce (AD, len=OVPN_OPCODE_SIZE+OVPN_NONCE_WIRE_SIZE), + * 1, 2, 3, ..., n: payload, + * n+1: auth_tag (len=tag_size) + */ + sg_init_table(sg, nfrags + 2); + + /* packet op is head of additional data */ + sg_set_buf(sg, skb->data, OVPN_AAD_SIZE); + + /* build scatterlist to decrypt packet payload */ + ret = skb_to_sgvec_nomark(skb, sg + 1, payload_offset, payload_len); + if (unlikely(nfrags != ret)) + return -EINVAL; + + /* append auth_tag onto scatterlist */ + sg_set_buf(sg + nfrags + 1, skb->data + OVPN_AAD_SIZE, tag_size); + + /* iv may be required by async crypto */ + ovpn_skb_cb(skb)->iv = kmalloc(OVPN_NONCE_SIZE, GFP_ATOMIC); + if (unlikely(!ovpn_skb_cb(skb)->iv)) + return -ENOMEM; + + iv = 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 = aead_request_alloc(ks->decrypt, GFP_ATOMIC); + if (unlikely(!req)) + return -ENOMEM; + + ovpn_skb_cb(skb)->req = 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 = crypto_alloc_aead(alg_name, 0, 0); + if (IS_ERR(aead)) { + ret = PTR_ERR(aead); + pr_err("%s crypto_alloc_aead failed, err=%d\n", title, ret); + aead = NULL; + goto error; + } + + ret = crypto_aead_setkey(aead, key, keylen); + if (ret) { + pr_err("%s crypto_aead_setkey size=%u failed, err=%d\n", title, + keylen, ret); + goto error; + } + + ret = crypto_aead_setauthsize(aead, OVPN_AUTH_TAG_SIZE); + if (ret) { + pr_err("%s crypto_aead_setauthsize failed, err=%d\n", title, + ret); + goto error; + } + + /* basic AEAD assumption */ + if (crypto_aead_ivsize(aead) != OVPN_NONCE_SIZE) { + pr_err("%s IV size must be %d\n", title, OVPN_NONCE_SIZE); + ret = -EINVAL; + goto error; + } + + pr_debug("********* Cipher %s (%s)\n", alg_name, title); + pr_debug("*** IV size=%u\n", crypto_aead_ivsize(aead)); + pr_debug("*** req size=%u\n", crypto_aead_reqsize(aead)); + pr_debug("*** block size=%u\n", crypto_aead_blocksize(aead)); + pr_debug("*** auth size=%u\n", crypto_aead_authsize(aead)); + pr_debug("*** alignmask=0x%x\n", crypto_aead_alignmask(aead)); + + return aead; + +error: + crypto_free_aead(aead); + return ERR_PTR(ret); +} + +void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks) +{ + if (!ks) + return; + + crypto_free_aead(ks->encrypt); + crypto_free_aead(ks->decrypt); + kfree(ks); +} + +struct ovpn_crypto_key_slot * +ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc) +{ + struct ovpn_crypto_key_slot *ks = NULL; + const char *alg_name; + int ret; + + /* validate crypto alg */ + switch (kc->cipher_alg) { + case OVPN_CIPHER_ALG_AES_GCM: + alg_name = ALG_NAME_AES; + break; + case OVPN_CIPHER_ALG_CHACHA20_POLY1305: + alg_name = ALG_NAME_CHACHAPOLY; + break; + default: + return ERR_PTR(-EOPNOTSUPP); + } + + if (kc->encrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE || + kc->decrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE) + return ERR_PTR(-EINVAL); + + /* build the key slot */ + ks = kmalloc(sizeof(*ks), GFP_KERNEL); + if (!ks) + return ERR_PTR(-ENOMEM); + + ks->encrypt = NULL; + ks->decrypt = NULL; + kref_init(&ks->refcount); + ks->key_id = kc->key_id; + + ks->encrypt = ovpn_aead_init("encrypt", alg_name, + kc->encrypt.cipher_key, + kc->encrypt.cipher_key_size); + if (IS_ERR(ks->encrypt)) { + ret = PTR_ERR(ks->encrypt); + ks->encrypt = NULL; + goto destroy_ks; + } + + ks->decrypt = ovpn_aead_init("decrypt", alg_name, + kc->decrypt.cipher_key, + kc->decrypt.cipher_key_size); + if (IS_ERR(ks->decrypt)) { + ret = PTR_ERR(ks->decrypt); + ks->decrypt = NULL; + goto destroy_ks; + } + + memcpy(ks->nonce_tail_xmit, 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..40c056558add3b9d17fda5c43eb858cb44c95945 --- /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..846b6d7d4ec698d0418fa5dd7f70bf84bc62fc99 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -7,6 +7,7 @@ * Antonio Quartulli */ +#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, struct sk_buff *skb) skb_set_queue_mapping(skb, 0); skb_scrub_packet(skb, true); - skb_reset_network_header(skb); + /* network header reset in ovpn_decrypt_post() */ skb_reset_transport_header(skb); skb_reset_inner_headers(skb); @@ -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); } -static void ovpn_decrypt_post(struct sk_buff *skb, int ret) +void ovpn_decrypt_post(void *data, int ret) { - struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer; + struct ovpn_crypto_key_slot *ks; + unsigned int payload_offset = 0; + struct sk_buff *skb = 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 == -EINPROGRESS)) + return; + + payload_offset = ovpn_skb_cb(skb)->payload_offset; + ks = ovpn_skb_cb(skb)->ks; + peer = 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); if (unlikely(ret < 0)) goto drop; + /* PID sits after the op */ + pid = (__force __be32 *)(skb->data + OVPN_OPCODE_SIZE); + ret = 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 = 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 = proto; + + /* perform Reverse Path Filtering (RPF) */ + if (unlikely(!ovpn_peer_check_by_src(peer->ovpn, skb, peer))) { + if (skb->protocol == 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 = 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); } /* 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 = 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 = ovpn_key_id_from_skb(skb); + ks = ovpn_crypto_key_id_to_slot(&peer->crypto, key_id); + if (unlikely(!ks)) { + net_info_ratelimited("%s: no available key for peer %u, key-id: %u\n", + 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)); } -static void ovpn_encrypt_post(struct sk_buff *skb, int ret) +void ovpn_encrypt_post(void *data, int ret) { - struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer; + struct ovpn_crypto_key_slot *ks; + struct sk_buff *skb = 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 == -EINPROGRESS)) + return; + + ks = ovpn_skb_cb(skb)->ks; + peer = 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); if (unlikely(ret < 0)) goto err; @@ -110,23 +215,33 @@ static void ovpn_encrypt_post(struct sk_buff *skb, int 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); } static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb) { - ovpn_skb_cb(skb)->peer = peer; + struct ovpn_crypto_key_slot *ks; + + /* get primary key to be used for encrypting data */ + ks = ovpn_crypto_key_slot_primary(&peer->crypto); + if (unlikely(!ks)) + return false; /* 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; } - 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; } diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h index 1cfa66873a2d4840ce57e337f8b4e8143e8b8e79..5143104b2c4b896a030ec4a8c8aea7015f40ef02 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); void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb); +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..23eaab1b465b8b88a84cf9f1039621923b640b47 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -12,6 +12,8 @@ #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, u32 id) peer->vpn_addrs.ipv6 = in6addr_any; RCU_INIT_POINTER(peer->bind, NULL); + ovpn_crypto_state_init(&peer->crypto); spin_lock_init(&peer->lock); kref_init(&peer->refcount); @@ -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_priv *ovpn, return peer; } +/** + * ovpn_peer_check_by_src - check that skb source is routed via peer + * @ovpn: the openvpn instance to search + * @skb: the packet to 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 = false; + + if (ovpn->mode == OVPN_MODE_P2P) { + /* in P2P mode, no matter the destination, packets are always + * sent to the single peer listening on the other side + */ + match = (peer == 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..a9113a969f94d66fa17208d563d0bbd255c23fa9 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -12,6 +12,7 @@ #include +#include "crypto.h" #include "socket.h" /** @@ -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 ovpn_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); #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..2f29049897e396e881e4604adf68219d9f5f56c0 --- /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 = jiffies; + int ret; + + /* ID must not be zero */ + if (unlikely(pkt_id == 0)) + return -EINVAL; + + spin_lock_bh(&pr->lock); + + /* expire backtracks at or below pr->id after PKTID_RECV_EXPIRE time */ + if (unlikely(time_after_eq(now, pr->expire))) + pr->id_floor = pr->id; + + /* time changed? */ + if (unlikely(pkt_time != pr->time)) { + if (pkt_time > pr->time) { + /* time moved forward, accept */ + pr->base = 0; + pr->extent = 0; + pr->id = 0; + pr->time = pkt_time; + pr->id_floor = 0; + } else { + /* time moved backward, reject */ + ret = -ETIME; + goto out; + } + } + + if (likely(pkt_id == pr->id + 1)) { + /* well-formed ID sequence (incremented by 1) */ + pr->base = REPLAY_INDEX(pr->base, -1); + pr->history[pr->base / 8] |= (1 << (pr->base % 8)); + if (pr->extent < REPLAY_WINDOW_SIZE) + ++pr->extent; + pr->id = pkt_id; + } else if (pkt_id > pr->id) { + /* ID jumped forward by more than one */ + const unsigned int delta = pkt_id - pr->id; + + if (delta < REPLAY_WINDOW_SIZE) { + unsigned int i; + + pr->base = REPLAY_INDEX(pr->base, -delta); + pr->history[pr->base / 8] |= (1 << (pr->base % 8)); + pr->extent += delta; + if (pr->extent > REPLAY_WINDOW_SIZE) + pr->extent = REPLAY_WINDOW_SIZE; + for (i = 1; i < delta; ++i) { + unsigned int newb = REPLAY_INDEX(pr->base, i); + + pr->history[newb / 8] &= ~BIT(newb % 8); + } + } else { + pr->base = 0; + pr->extent = REPLAY_WINDOW_SIZE; + memset(pr->history, 0, sizeof(pr->history)); + pr->history[0] = 1; + } + pr->id = pkt_id; + } else { + /* ID backtrack */ + const unsigned int delta = pr->id - pkt_id; + + if (delta > pr->max_backtrack) + pr->max_backtrack = delta; + if (delta < pr->extent) { + if (pkt_id > pr->id_floor) { + const unsigned int ri = REPLAY_INDEX(pr->base, + delta); + u8 *p = &pr->history[ri / 8]; + const u8 mask = (1 << (ri % 8)); + + if (*p & mask) { + ret = -EINVAL; + goto out; + } + *p |= mask; + } else { + ret = -EINVAL; + goto out; + } + } else { + ret = -EINVAL; + goto out; + } + } + + pr->expire = now + PKTID_RECV_EXPIRE; + ret = 0; +out: + spin_unlock_bh(&pr->lock); + return ret; +} diff --git a/drivers/net/ovpn/pktid.h b/drivers/net/ovpn/pktid.h new file mode 100644 index 0000000000000000000000000000000000000000..0262d026d15e203892db05a8b45c1c53fdb9c7ef --- /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 = 2^REPLAY_WINDOW_ORDER */ +#define REPLAY_WINDOW_ORDER 8 + +#define REPLAY_WINDOW_BYTES BIT(REPLAY_WINDOW_ORDER) +#define REPLAY_WINDOW_SIZE (REPLAY_WINDOW_BYTES * 8) +#define REPLAY_INDEX(base, i) (((base) + (i)) & (REPLAY_WINDOW_SIZE - 1)) + +/* Packet-ID state for receiver. + * Other than lock member, can be zeroed to initialize. + */ +struct ovpn_pktid_recv { + /* "sliding window" bitmask of recent packet IDs received */ + u8 history[REPLAY_WINDOW_BYTES]; + /* bit position of deque base in history */ + unsigned int base; + /* extent (in bits) of deque in history */ + unsigned int extent; + /* expiration of history in jiffies */ + unsigned long expire; + /* highest sequence number received */ + u32 id; + /* highest time stamp received */ + u32 time; + /* we will only accept backtrack IDs > id_floor */ + u32 id_floor; + unsigned int max_backtrack; + /* protects entire pktd ID state */ + spinlock_t lock; +}; + +/* Get the next packet ID for xmit */ +static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, u32 *pktid) +{ + const u32 seq_num = 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 = 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) = htonl(pktid); + BUILD_BUG_ON(4 + OVPN_NONCE_TAIL_SIZE != 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..b7d285b4d9c1dd258aee9b55012280665626d1aa 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); } +/** + * 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 = 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 header. + */ +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..bd3cbcfc770d2c28d234fcdd081b4d02e6496ea0 100644 --- a/drivers/net/ovpn/skb.h +++ b/drivers/net/ovpn/skb.h @@ -20,6 +20,11 @@ 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; }; static inline struct ovpn_cb *ovpn_skb_cb(struct sk_buff *skb) From patchwork Tue Apr 15 11:17:30 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 881515 Received: from mail-wm1-f42.google.com (mail-wm1-f42.google.com [209.85.128.42]) (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 5A1C3297A5A for ; Tue, 15 Apr 2025 11:17:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715869; cv=none; b=mZXq19u1Lz6XEputf6ZQV5SBzrbSqO+Buh1TJF/Zf89hyj7O61X8DzsRN2KjRii3C3pacjuj4i9rIZyG1gJtlR4rU+7G+L/3PgZlkmJpyu3pQzrwo8zlMVYqfWTWTM/GfBGLf+xuDPJPik5gx9JFpeXAgzqzPJeat4tXLpMsiRE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715869; c=relaxed/simple; bh=+Unom1yykFNqbE9773wCLW/thNu13gqRTUvNb4z4izU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Bj5RW8g5I/HV27hKvCaJAs7x9IGOpnLKKWLC9/ITd6+mdd9u6TYI0AyZNFdsnxdluQ84n+qTBF8TT8XezNpS62DWd5581t8nnDoLZIqge68Aav/VQmA6TI7pgfOpBF1bdFPgXAsSIaIUsJkoyUS9W1qT2E8Fe7yGCOfiCfj/0/0= 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.42 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-f42.google.com with SMTP id 5b1f17b1804b1-43cfecdd8b2so43924035e9.2 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=N75LogsOsEomcGIrgfm5UaBd2OG9cWExigBGJX55rP3TFycg+LdAEpn0hYpDrhK1HZ 5HnbHQ7ZizPAXE1Pwkmn7UiR9jwj5aM+wGskVErNXAEE0yu6nEZlgvR3GijyPodfwJoq Y9UIHU/khARbwuPDlLX8klyny4yi3eRtWq9DN6GhDNVCQE2R0p5WMf7Cb3RoeuXfMyyJ WL070Ap8hZp2ILFrvjY1LFhkTTEhPI4kaX3QFtUF9xoYXZbK3LO2SKLvBJoFbLnCa9zy HqvORAZYTu/X31i06+I93PwZuZTkY08aS2NuuK2XCdy7utolz+egxjNEMx/6qnlOvdKa WjgA== X-Forwarded-Encrypted: i=1; AJvYcCX91v4XdTwYJ1LGVa/iI1dC1ob/hL2wqlO5dfdgUSfdV80NHHAOEaFg73vqG33AIf/XkQ7jyJSNHA5VVjhLEm0=@vger.kernel.org X-Gm-Message-State: AOJu0YwrF1DL0Z3wDZmlR4V7/eog4ZROLn75+oZw9E0PwKZr08YZlTyI CPfMp9WT805MuBi5BwNIhUPtAgfTexPndeP6XO98UvGBNVBTz7M0nfm7eq6aSDXX+J0JIjb8Zi5 aiu1IAkEYVmhz/D/k4/HDy69YQupeS2t7ctekwpRwq5au1VFOw21PcfE4exA= X-Gm-Gg: ASbGncswBwsJ6lHXcysb7epKMW21GLY3c2UPzNJ/PrRAShQLXD4Yoot72ZrH4XtpUmb i7UeC8cyH+NdmVtd+DHeTHx+1Mk8u5+NAdMp/MehuSn1D6HfC7GhmxIgVzLPUSW46b+s13nm9Ic viqirByc9AJYtWlCNQ0S4mecUf4HqSJyZ/BgVehMBNJtswn+Jop7Z1KufpUnqirOejBi0mrSMXa O2o6sdPJf6XqAqBB/7QSeE0UdGl3AzM5f5UDaK8Mi+yUa6htdZQrNgTtCeSDr6ppWDRq5F2CP8i YhQtJk6RsFEfz/TnjIrLU9iD51wRv+6LkqLd5w== 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-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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..64430880f1dae33a41f698d713cf151be5b38577 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; }; 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..7c42d84987ad362289dbf5e7992403c76e910ed9 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 *sock) static void ovpn_tcp_send_sock(struct ovpn_peer *peer, struct sock *sk) { struct sk_buff *skb = peer->tcp.out_msg.skb; + int ret, flags; 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 = true; do { - int ret = skb_send_sock_locked(sk, skb, - peer->tcp.out_msg.offset, - peer->tcp.out_msg.len); + flags = ovpn_skb_cb(skb)->nosignal ? MSG_NOSIGNAL : 0; + ret = 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 == -EAGAIN) goto out; @@ -380,7 +383,7 @@ static int ovpn_tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) rcu_read_unlock(); peer = sock->peer; - if (msg->msg_flags & ~MSG_DONTWAIT) { + if (msg->msg_flags & ~(MSG_DONTWAIT | MSG_NOSIGNAL)) { ret = -EOPNOTSUPP; goto peer_free; } @@ -413,6 +416,7 @@ static int ovpn_tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) goto peer_free; } + ovpn_skb_cb(skb)->nosignal = msg->msg_flags & MSG_NOSIGNAL; ovpn_tcp_send_sock_skb(peer, sk, skb); ret = size; peer_free: From patchwork Tue Apr 15 11:17:31 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 881514 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 9842E297A70 for ; Tue, 15 Apr 2025 11:17:47 +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=1744715872; cv=none; b=VnlO+tAF2CbjhuxHRiWcL3CwvrYjtiF0RZJDQvi/U7c6oWLoPPhdfaRgQAfSMJiizCPLCpZ4dwPBm9F0UBD2njcOv3NxClPOqUSiN+DoZpCEj9BPNn9RNykHCKtQPjW1h7XNyTECMPgpZzFE66xAt0Sv+Ec4+nbRK0afIwg6xA8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715872; c=relaxed/simple; bh=IBVqLNp9FcpUwB9MrBtN8l0O7e5CtjbqcksNbQASLkc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=QMX6MZxtjE6vGc9jMow0kZ6q5szXi0EyxKZqK7GsWRIREDZ7Xn2x1dUVpvz3s9hH4lInw8CHj0yK1tMhfaCi8z/LEbT15/+OA4Sao7f7TB1mWOfYygg0igQE9BmIh8aGhaxOU5+JsxLc/bCwE5xYTEmzcnsVgM2KYgCXyQWeE1s= 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.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="eRxmUgVu" Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-4394a823036so53841485e9.0 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=YwffSpsbSRfYYasbTHTUwzzUhc6AiQbk54wt049vt44uSFTf5015WCLT+j/DJvefQD XJ31qqcvzQT1uaRJVaxysBbbrTMG2A2/inucWqBq7a5zVWEJIMFQxdCWrY+na+lB9Arb WGZmn2QbkKV4s+CNX8nbf1bIJdepeh8cJ37nGm+mv9l7DHAXhqLMKIo3vLns8RAP76nq 6SMotHHZvRIjLCUjtLJo0sx1FYAZkhvFc6CJAtsHQz+OeeV4cM0ksWXPsARUeuKfIirA 9DGbQhvDgPbz8edr13IIUF3xneDLkH7mI+coaY1ZFDD9nCuoN8XxqB72HH1ryN1EVa0K //eQ== X-Forwarded-Encrypted: i=1; AJvYcCXEWQVPiyrRVOC17YKyPRkxrjqlel15Bkt4Fe/QusjEytNxZikuA8VYnWrY0+eDQYFrMTkqfkGxaV8qw+D7vdU=@vger.kernel.org X-Gm-Message-State: AOJu0YyPaqdMSDdMVavro9fw+G7hpD243YOoPU0pII26vDQF2j3t85Pd LSNuWt9qD01g1z2j0I2i78W7y8nWIPeOdNnzkQRtDd8BjnMr8Nz8d93WqQEF+/CLXCfOYcYC9xG +AQUWKpovl38glSq+0yn04Ki8qPp1s5lD+oqm6IXQ6kRW8sSH+2dwj8+OMnY= X-Gm-Gg: ASbGncvODj4YWhs2iilOej/N3b+8UUVK9UZXRxwryFPKvJz+kSDtV2FEWlXK/eHLb4f LjDvGerqRR0Z6JhcVx27uQTBBAFShPpipekZRH8bqDyk5aMxOndVq370NS3gJi+gPHIsqmoi+g0 /+3vHdNKy8l/Mye/iyLps4UfPiVr2uM+pE74zZGdq0QqBzxj3pQSQ8XG05e+J+tTLeUmuZLhn88 pDUaUqE86YUaZ0GdjD3guZzlW8R4cv+uGlAX3mFo5ehQry8PdKxDFHmdj0h1KY+oRw619mFL8JE 05tQ++9UHkH5oxMKtaI62Iim37U3yTFrLcnFwg== 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-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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..889c4585f06bc8cbf0a22872a5f82512bc717c5e 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -25,11 +25,66 @@ #include "tcp.h" #include "udp.h" +static void ovpn_priv_free(struct net_device *net) +{ + struct ovpn_priv *ovpn = 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 != OVPN_MODE_MP) + return 0; + + dev_v4 = __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) = false; + } + + /* the peer container is fairly large, therefore we allocate it only in + * MP mode + */ + ovpn->peers = kzalloc(sizeof(*ovpn->peers), GFP_KERNEL); + if (!ovpn->peers) + return -ENOMEM; + + for (i = 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 = netdev_priv(dev); + int err = gro_cells_init(&ovpn->gro_cells, dev); + + if (err < 0) + return err; + + err = ovpn_mp_alloc(ovpn); + if (err < 0) { + gro_cells_destroy(&ovpn->gro_cells); + return err; + } - return gro_cells_init(&ovpn->gro_cells, dev); + return 0; } static void ovpn_net_uninit(struct net_device *dev) @@ -76,6 +131,8 @@ static void ovpn_setup(struct net_device *dev) dev->netdev_ops = &ovpn_netdev_ops; + dev->priv_destructor = ovpn_priv_free; + dev->hard_header_len = 0; dev->addr_len = 0; dev->mtu = ETH_DATA_LEN - OVPN_HEAD_ROOM; @@ -134,10 +191,7 @@ static void ovpn_dellink(struct net_device *dev, struct list_head *head) { struct ovpn_priv *ovpn = netdev_priv(dev); - if (ovpn->mode == 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); } diff --git a/drivers/net/ovpn/ovpnpriv.h b/drivers/net/ovpn/ovpnpriv.h index bb345954b8d9e78fcb4190bb0f6e3b262ca60410..5ba3c3e446e4d067675541435bcc6aa282aee75c 100644 --- a/drivers/net/ovpn/ovpnpriv.h +++ b/drivers/net/ovpn/ovpnpriv.h @@ -15,11 +15,29 @@ #include #include +/** + * 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..bed2e591c000c4efecdcd92db484e590f97f9f7f 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -9,6 +9,7 @@ #include #include +#include #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 != 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; } peer->delete_reason = reason; @@ -357,6 +368,89 @@ bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, return match; } +#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \ + typeof(_tbl) *__tbl = &(_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 = { 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 = 0; + + spin_lock_bh(&ovpn->lock); + /* do not add duplicates */ + tmp = ovpn_peer_get_by_id(ovpn, peer->id); + if (tmp) { + ovpn_peer_put(tmp); + ret = -EEXIST; + goto out; + } + + bind = rcu_dereference_protected(peer->bind, true); + /* peers connected via TCP have bind == NULL */ + if (bind) { + switch (bind->remote.in4.sin_family) { + case AF_INET: + sa4 = (struct sockaddr_in *)&sa; + + sa4->sin_family = AF_INET; + sa4->sin_addr.s_addr = bind->remote.in4.sin_addr.s_addr; + sa4->sin_port = bind->remote.in4.sin_port; + salen = sizeof(*sa4); + break; + case AF_INET6: + sa6 = (struct sockaddr_in6 *)&sa; + + sa6->sin6_family = AF_INET6; + sa6->sin6_addr = bind->remote.in6.sin6_addr; + sa6->sin6_port = bind->remote.in6.sin6_port; + salen = sizeof(*sa6); + break; + default: + ret = -EPROTONOSUPPORT; + goto out; + } + + nhead = 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 != htonl(INADDR_ANY)) { + nhead = 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 = 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 = -ENOENT; + + lockdep_assert_held(&peer->ovpn->lock); + + tmp = ovpn_peer_get_by_id(peer->ovpn, peer->id); + if (tmp == peer) { + ovpn_peer_remove(peer, reason, release_list); + ret = 0; + } + + if (tmp) + ovpn_peer_put(tmp); + + return ret; } /** @@ -446,6 +571,9 @@ int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason) spin_lock_bh(&peer->ovpn->lock); switch (peer->ovpn->mode) { + case OVPN_MODE_MP: + ret = ovpn_peer_del_mp(peer, reason, &release_list); + break; case OVPN_MODE_P2P: ret = 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, struct 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 = true; + + /* if a socket was passed as argument, skip all peers except + * those using it + */ + if (sk) { + rcu_read_lock(); + ovpn_sock = rcu_dereference(peer->sock); + remove = ovpn_sock && ovpn_sock->sock->sk == 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 socket + * 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..2a3b1031f58dd73925a9ed74aed7ac4cb7b8c4c5 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; 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); 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..c9e189056f3367beb08784576d09b9a161dfa697 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 = sock->ovpn; rcu_read_unlock(); - if (ovpn->mode == 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); } /** From patchwork Tue Apr 15 11:17:34 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 881513 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 5A3FB29B766 for ; Tue, 15 Apr 2025 11:17:51 +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=1744715875; cv=none; b=koiBfxIKEEUG5QgapSPAUFTVD3xw8n4jVN0lPQ7fugwdHDwCUtTIpN57aKQCxppF+VSenPgqDbmplxs9MFMzYQZXCvXEHBH4RaRPo4inM716BRBoajqwAAk7CnSrsg34hNGRc2m2h7ero7WsdNzC9trsb0E6XZNALuCYlSh3M8w= 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.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="EGIvjegM" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-43ce71582e9so43961045e9.1 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=KTAfbAoAmOc0n7NM31s66tnvBa35sofJVQ20xfu+e4Kf/vtA1ruf+tjKKjFe+YCYqA Q1U4AgA84B/IsbvQhe67RB4mANkCDSwqlS/CnObZjNbfBh2QA0gnJWDulfg1x+GiBZZt /5E8AU50+JnXMG5STX8uFbvah6UwuXplFpCvR9PI5xnW3o1hEauBtKyI2A/vCiNw51yb LdO++e7nWMugo5yOOPgy4BNgQc8sitk7iCc2KlXJ7/iyquPG4/ZJi8uvQISmTdwIHPNp +itB+7Qtv4FE9nK4KUn+BSRg2/D8X6wnYw60K5GThqD8OoX7e2z6VQb5yCveiZVx7q6k zIyQ== X-Forwarded-Encrypted: i=1; AJvYcCUbprSJyAktwSqmdMeyw6SzNPhTRZmOu0Ka3L54QMGrT4/IWRkkRSUwgj111FXyLWZdWb2dX6fSfqFrxzNqgZQ=@vger.kernel.org X-Gm-Message-State: AOJu0YwsWLiZb4TNfOheN6lnoDz2qye0s++L7dQUy5Ja/aXIi5SWEjYE ueeh3oDQNMeXeO1fcPFadViAJ+tJwlnurmk0iDOEzslR3l88IkoKcdMcMjNWU6MNj0FDVOLYdDI upN4+RVNFmt5By7zv7nR/mNykBDPv0QvHv6Tfpl/FAihHqZ4lV/a47y8PMYo= X-Gm-Gg: ASbGncuoM94VrAP9ygrUziGUykZi6S+HyoCREn5aQH3u/a1laXHy9dFPiR0SvrnhuOP 8aH22oIeoGptxcu/h8KVYeUXfXw8r3jftsgb587OZh5Xh/N/mbQAU5K9o1IeozTNSYCv17k1Ib9 AAldDbgW0MCMLkgFGhmLe4wFjJQ7uULyCApbZovFvAtjnYrixJ3dz71Tdws6Lg4gJ0gUnyvMAYW HUsosEf1T8VRm6BMTm51kSKBCjzS7WzxTjRRxrccTBVciXKsRDk2MQaWG7dnJG4vlvAK6U8e27A +A3N23NiZd3dkazQnabkXp3/IhP9J4/sBroZRA== 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-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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..fd8820c7d133707b7ba52462218c428fdeca24d5 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 = 0; struct sk_buff *skb = 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()); + rcu_read_lock(); + sock = rcu_dereference(peer->sock); + if (sock && sock->sock->sk->sk_protocol == 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); diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 50514736a4327c96d381805f0870d89fe1193ab1..10a6fd3e6ecd0306072053d51a5a195543069223 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 *ovpn, u32 id) return peer; } +/** + * ovpn_peer_reset_sockaddr - recreate binding for peer + * @peer: peer to recreate the binding for + * @ss: sockaddr to use as remote endpoint for the binding + * @local_ip: local IP for the binding + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, + const struct sockaddr_storage *ss, + const void *local_ip) +{ + struct ovpn_bind *bind; + size_t ip_len; + + lockdep_assert_held(&peer->lock); + + /* create new ovpn_bind object */ + bind = ovpn_bind_from_sockaddr(ss); + if (IS_ERR(bind)) + return PTR_ERR(bind); + + if (ss->ss_family == AF_INET) { + ip_len = sizeof(struct in_addr); + } else if (ss->ss_family == AF_INET6) { + ip_len = sizeof(struct in6_addr); + } else { + net_dbg_ratelimited("%s: invalid family %u for remote endpoint for peer %u\n", + netdev_name(peer->ovpn->dev), + ss->ss_family, peer->id); + kfree(bind); + return -EINVAL; + } + + memcpy(&bind->local, local_ip, ip_len); + + /* set binding */ + ovpn_bind_reset(peer, bind); + + return 0; +} + +/* 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 = &(_tbl); \ + jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl2); \ +}) + +#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \ + typeof(_tbl) *__tbl1 = &(_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 *skb) +{ + struct hlist_nulls_head *nhead; + struct sockaddr_storage ss; + struct sockaddr_in6 *sa6; + bool reset_cache = false; + struct sockaddr_in *sa; + struct ovpn_bind *bind; + const void *local_ip; + size_t salen = 0; + + spin_lock_bh(&peer->lock); + bind = rcu_dereference_protected(peer->bind, + lockdep_is_held(&peer->lock)); + if (unlikely(!bind)) + goto unlock; + + switch (skb->protocol) { + case htons(ETH_P_IP): + /* float check */ + if (unlikely(!ovpn_bind_skb_src_match(bind, skb))) { + /* unconditionally save local endpoint in case + * of float, as it may have changed as well + */ + local_ip = &ip_hdr(skb)->daddr; + sa = (struct sockaddr_in *)&ss; + sa->sin_family = AF_INET; + sa->sin_addr.s_addr = ip_hdr(skb)->saddr; + sa->sin_port = udp_hdr(skb)->source; + salen = sizeof(*sa); + reset_cache = true; + break; + } + + /* if no float happened, let's double check if the local endpoint + * has changed + */ + if (unlikely(bind->local.ipv4.s_addr != ip_hdr(skb)->daddr)) { + net_dbg_ratelimited("%s: learning local IPv4 for peer %d (%pI4 -> %pI4)\n", + netdev_name(peer->ovpn->dev), + peer->id, &bind->local.ipv4.s_addr, + &ip_hdr(skb)->daddr); + bind->local.ipv4.s_addr = ip_hdr(skb)->daddr; + reset_cache = 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 = &ipv6_hdr(skb)->daddr; + sa6 = (struct sockaddr_in6 *)&ss; + sa6->sin6_family = AF_INET6; + sa6->sin6_addr = ipv6_hdr(skb)->saddr; + sa6->sin6_port = udp_hdr(skb)->source; + sa6->sin6_scope_id = ipv6_iface_scope_id(&ipv6_hdr(skb)->saddr, + skb->skb_iif); + salen = sizeof(*sa6); + reset_cache = 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 -> %pI6c\n", + netdev_name(peer->ovpn->dev), + peer->id, &bind->local.ipv6, + &ipv6_hdr(skb)->daddr); + bind->local.ipv6 = ipv6_hdr(skb)->daddr; + reset_cache = 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 == OVPN_MODE_MP) { + spin_lock_bh(&peer->ovpn->lock); + spin_lock_bh(&peer->lock); + bind = rcu_dereference_protected(peer->bind, + lockdep_is_held(&peer->lock)); + if (unlikely(!bind)) { + spin_unlock_bh(&peer->lock); + spin_unlock_bh(&peer->ovpn->lock); + return; + } + + /* 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 = sizeof(*sa); + break; + case AF_INET6: + salen = sizeof(*sa6); + break; + } + + /* remove old hashing */ + hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr); + /* re-add with new transport address */ + nhead = ovpn_get_hash_head(peer->ovpn->peers->by_transp_addr, + &bind->remote, salen); + hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead); + spin_unlock_bh(&peer->lock); + spin_unlock_bh(&peer->ovpn->lock); + } + return; +unlock: + spin_unlock_bh(&peer->lock); +} + /** * ovpn_peer_release_rcu - RCU callback performing last peer release steps * @head: RCU member of the ovpn_peer @@ -230,19 +430,6 @@ static struct in6_addr ovpn_nexthop_from_skb6(struct sk_buff *skb) 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 = &(_tbl); \ - jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl2); \ -}) - -#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \ - typeof(_tbl) *__tbl1 = &(_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..f1288734ff100ee76b0c41ebb6dc71725ea33261 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, struct sk_buff *skb, void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout); void ovpn_peer_keepalive_work(struct work_struct *work); +void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb); + #endif /* _NET_OVPN_OVPNPEER_H_ */ From patchwork Tue Apr 15 11:17:35 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 881512 Received: from mail-wm1-f51.google.com (mail-wm1-f51.google.com [209.85.128.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 DEA4629A3EC for ; Tue, 15 Apr 2025 11:17:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715878; cv=none; b=DZOuykpbawlWEHXsF/J0WpO6tNnDTpD7/33jO2MD3WMqAxIxKsc/8Za6YghqnLdkb9QKiY2zgAecK61EAAgomv+lHlXuhUyqfTPHErmI1jOe9au2yOY/0KrBxwvQ1V4U782v5asKdTqg/0fg4oJXXdoRklxGVzx9sc3VWvUuIfw= 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.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="D9wGoCmc" Received: by mail-wm1-f51.google.com with SMTP id 5b1f17b1804b1-43cf58eea0fso27515115e9.0 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=OdachcT/Okc0wqbbCttYXOeUKtWTwfP/Ydc2hQF+ifGZsAG6U9nIjH/vv9cKwxkYQK W0x7gZIvSuSwAMQKOQDlEjFEIeRFQ50Djodd57Z399Ti+zKWxlZ9OhtU6DWj1pyNnV/y rzJU0q2bID/k35nTC30Ta4OPgp1YmEmcNXK3X08pps2RKwweciL51J/vc3s+AZ7W5VD0 nbJmKTpZqEq7KLZR/SIURWX96OwyZ0eHdkv9r4lfT3iqV0GfEN6VJUDUkurJ3ZQIM7QI vsQAEsARSHLYjBXnTlYZTgPSkss1nPhMB/a/OtYs0Pi6TyxTkLpT0IeuC8Tj2EWUDxvT YKQQ== X-Forwarded-Encrypted: i=1; AJvYcCWfBmOhfr0PCFsUkGl9bcu+22gx//wMeEPsEuEtAg70bcubSdla1c3HYn6frbDcFBiQXRURWIqmnDK8J1D7pYo=@vger.kernel.org X-Gm-Message-State: AOJu0Yy7nd50CO9eGUe+nLfszCgUcst3yt5H7EiRIJSEJ+7S9YMqpOhY QYoiGA34bidl2aOyW0LuCN7O0TcKWHgRf+iIC41yEB5MG+F/HutBEpF8ix38GJQiyHOjNU5DK3i bh3KLWjoiIUYQ+WiYmyyOL06DAigcgzGu7VreaJdKtgXqbQYborrdj199RYI= X-Gm-Gg: ASbGncsXL/eMgXC6qyngznIl1CoG+oukNst2Sasfn6wvOH2wSpgZ7RSsJcYccw0maui E5YQi/h0bxBB6wh1I7NDqGc862zjhKk0N/AJ3l28cGiU/gOb4JsDs9Yg6n0mA3fGC9rkhcidkK2 dDWk4Nt7P+iw6uVqFgnORQqoTWfpsYMEd2lWF6ublyDoM3rEzCdO2P5L54DNVtr91h5/UT795/a TikzuiojEPdqGROmc8/cv8SQKebzPWIFKwqGwF1zaLmrd/B0QC56Z/OKA3efRQu/5TW4oYjcpz9 QNFDqLLwOjLpMa6K4YtumhCWdzXc/++TnUk/rw== 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-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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..5653a588a47c6c94ff10e042f4f6d32bd8205e58 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -7,6 +7,7 @@ */ #include +#include #include #include @@ -15,6 +16,9 @@ #include "main.h" #include "netlink.h" #include "netlink-gen.h" +#include "bind.h" +#include "peer.h" +#include "socket.h" MODULE_ALIAS_GENL_FAMILY(OVPN_FAMILY_NAME); @@ -89,29 +93,701 @@ void ovpn_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, netdev_put(ovpn->dev, tracker); } +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 = 0; + __be32 *in; + + ss->ss_family = AF_UNSPEC; + + if (attrs[OVPN_A_PEER_REMOTE_PORT]) + port = nla_get_be16(attrs[OVPN_A_PEER_REMOTE_PORT]); + + if (attrs[OVPN_A_PEER_REMOTE_IPV4]) { + ss->ss_family = AF_INET; + in = nla_data(attrs[OVPN_A_PEER_REMOTE_IPV4]); + } else if (attrs[OVPN_A_PEER_REMOTE_IPV6]) { + ss->ss_family = AF_INET6; + in6 = 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 = (struct sockaddr_in6 *)ss; + sin6->sin6_port = port; + memcpy(&sin6->sin6_addr, in6, sizeof(*in6)); + break; + } + + /* v4-mapped-v6 address */ + ss->ss_family = AF_INET; + in = &in6->s6_addr32[3]; + fallthrough; + case AF_INET: + sin = (struct sockaddr_in *)ss; + sin->sin_port = port; + sin->sin_addr.s_addr = *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 = 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 = ovpn_nl_family_get(attrs[OVPN_A_PEER_LOCAL_IPV4], + attrs[OVPN_A_PEER_LOCAL_IPV6]); + remote_fam = ovpn_nl_family_get(attrs[OVPN_A_PEER_REMOTE_IPV4], + attrs[OVPN_A_PEER_REMOTE_IPV6]); + if (local_fam != AF_UNSPEC && remote_fam != AF_UNSPEC && + local_fam != remote_fam) { + NL_SET_ERR_MSG_MOD(info->extack, + "mismatching local and remote address families"); + return -EINVAL; + } + + if (remote_fam != 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 == 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 incoming 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 *info, + struct nlattr **attrs) +{ + struct sockaddr_storage ss = {}; + void *local_ip = NULL; + u32 interv, timeout; + bool rehash = 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 = ovpn_nl_attr_local_ip(attrs); + + /* set peer sockaddr */ + ret = ovpn_peer_reset_sockaddr(peer, &ss, local_ip); + if (ret < 0) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot set peer sockaddr: %d", + ret); + goto err_unlock; + } + dst_cache_reset(&peer->dst_cache); + } + + if (attrs[OVPN_A_PEER_VPN_IPV4]) { + rehash = true; + peer->vpn_addrs.ipv4.s_addr = + nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]); + } + + if (attrs[OVPN_A_PEER_VPN_IPV6]) { + rehash = true; + peer->vpn_addrs.ipv6 = + nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]); + } + + /* when setting the keepalive, both parameters have to be configured */ + if (attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL] && + attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]) { + interv = nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]); + timeout = nla_get_u32(attrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]); + ovpn_peer_keepalive_set(peer, interv, timeout); + } + + netdev_dbg(peer->ovpn->dev, + "modify peer id=%u endpoint=%pIScp VPN-IPv4=%pI4 VPN-IPv6=%pI6c\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 = info->user_ptr[0]; + struct ovpn_socket *ovpn_sock; + struct socket *sock = NULL; + struct ovpn_peer *peer; + u32 sockfd, peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER)) + return -EINVAL; + + ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER], + ovpn_peer_nl_policy, info->extack); + if (ret) + return ret; + + ret = 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 == 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 = nla_get_u32(attrs[OVPN_A_PEER_ID]); + peer = 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 = nla_get_u32(attrs[OVPN_A_PEER_SOCKET]); + /* sockfd_lookup() increases sock's refcounter */ + sock = sockfd_lookup(sockfd, &ret); + if (!sock) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot lookup peer socket (fd=%u): %d", + sockfd, ret); + ret = -ENOTSOCK; + goto peer_release; + } + + /* 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 == 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 = -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 == 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 = -EINVAL; + goto peer_release; + } + + ovpn_sock = 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 = -ENOTSOCK; + goto peer_release; + } + + rcu_assign_pointer(peer->sock, ovpn_sock); + + ret = ovpn_nl_peer_modify(peer, info, attrs); + if (ret < 0) + goto sock_release; + + ret = ovpn_peer_add(ovpn, peer); + if (ret < 0) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot add new peer (id=%u) to hashtable: %d", + 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; } 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 = 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 = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER], + ovpn_peer_nl_policy, info->extack); + if (ret) + return ret; + + ret = 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 = nla_get_u32(attrs[OVPN_A_PEER_ID]); + peer = ovpn_peer_get_by_id(ovpn, peer_id); + if (!peer) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot find peer with id %u", peer_id); + return -ENOENT; + } + + /* when using a TCP socket the remote IP is not expected */ + rcu_read_lock(); + sock = rcu_dereference(peer->sock); + if (sock && sock->sock->sk->sk_protocol == 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 = ovpn_nl_peer_modify(peer, info, attrs); + if (ret < 0) { + spin_unlock_bh(&ovpn->lock); + ovpn_peer_put(peer); + return ret; + } + + /* ret == 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 = -EMSGSIZE; + struct nlattr *attr; + __be16 local_port; + void *hdr; + int id; + + hdr = genlmsg_put(skb, portid, seq, &ovpn_nl_family, flags, + OVPN_CMD_PEER_GET); + if (!hdr) + return -ENOBUFS; + + attr = nla_nest_start(skb, OVPN_A_PEER); + if (!attr) + goto err; + + rcu_read_lock(); + sock = rcu_dereference(peer->sock); + if (!sock) { + ret = -EINVAL; + goto err_unlock; + } + + if (!net_eq(genl_info_net(info), sock_net(sock->sock->sk))) { + id = 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 = 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 != htonl(INADDR_ANY)) + if (nla_put_in_addr(skb, OVPN_A_PEER_VPN_IPV4, + peer->vpn_addrs.ipv4.s_addr)) + goto err; + + if (!ipv6_addr_equal(&peer->vpn_addrs.ipv6, &in6addr_any)) + if (nla_put_in6_addr(skb, OVPN_A_PEER_VPN_IPV6, + &peer->vpn_addrs.ipv6)) + goto err; + + if (nla_put_u32(skb, OVPN_A_PEER_KEEPALIVE_INTERVAL, + peer->keepalive_interval) || + nla_put_u32(skb, OVPN_A_PEER_KEEPALIVE_TIMEOUT, + peer->keepalive_timeout)) + goto err; + + rcu_read_lock(); + bind = rcu_dereference(peer->bind); + if (bind) { + if (bind->remote.in4.sin_family == 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 == 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; } 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 = info->user_ptr[0]; + struct ovpn_peer *peer; + struct sk_buff *msg; + u32 peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER)) + return -EINVAL; + + ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER], + ovpn_peer_nl_policy, info->extack); + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs, + OVPN_A_PEER_ID)) + return -EINVAL; + + peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]); + peer = ovpn_peer_get_by_id(ovpn, peer_id); + if (!peer) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot find peer with id %u", peer_id); + return -ENOENT; + } + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) { + ret = -ENOMEM; + goto err; + } + + ret = ovpn_nl_send_peer(msg, info, peer, info->snd_portid, + info->snd_seq, 0); + if (ret < 0) { + nlmsg_free(msg); + goto err; + } + + ret = genlmsg_reply(msg, info); +err: + ovpn_peer_put(peer); + return ret; } int ovpn_nl_peer_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) { - return -EOPNOTSUPP; + const struct genl_info *info = genl_info_dump(cb); + int bkt, last_idx = cb->args[1], dumped = 0; + netdevice_tracker tracker; + struct ovpn_priv *ovpn; + struct ovpn_peer *peer; + + ovpn = ovpn_get_dev_from_attrs(sock_net(cb->skb->sk), info, &tracker); + if (IS_ERR(ovpn)) + return PTR_ERR(ovpn); + + if (ovpn->mode == OVPN_MODE_P2P) { + /* if we already dumped a peer it means we are done */ + if (last_idx) + goto out; + + rcu_read_lock(); + peer = rcu_dereference(ovpn->peer); + if (peer) { + if (ovpn_nl_send_peer(skb, info, peer, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, + NLM_F_MULTI) == 0) + dumped++; + } + rcu_read_unlock(); + } else { + rcu_read_lock(); + hash_for_each_rcu(ovpn->peers->by_id, bkt, peer, + hash_entry_id) { + /* skip already dumped peers that were dumped by + * previous invocations + */ + if (last_idx > 0) { + last_idx--; + continue; + } + + if (ovpn_nl_send_peer(skb, info, peer, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, + NLM_F_MULTI) < 0) + break; + + /* count peers being dumped during this invocation */ + dumped++; + } + rcu_read_unlock(); + } + +out: + netdev_put(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] += dumped; + return skb->len; } 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 = info->user_ptr[0]; + struct ovpn_peer *peer; + u32 peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER)) + return -EINVAL; + + ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER], + ovpn_peer_nl_policy, info->extack); + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs, + OVPN_A_PEER_ID)) + return -EINVAL; + + peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]); + peer = ovpn_peer_get_by_id(ovpn, peer_id); + if (!peer) { + 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 = ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_USERSPACE); + ovpn_peer_put(peer); + + return ret; } 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..0469afd6da238d795b63b9e68c8c728fa324de98 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); - if (ss->ss_family == AF_INET) { - ip_len = sizeof(struct in_addr); - } else if (ss->ss_family == AF_INET6) { - ip_len = sizeof(struct in6_addr); - } else { - net_dbg_ratelimited("%s: invalid family %u for remote endpoint for peer %u\n", - netdev_name(peer->ovpn->dev), - ss->ss_family, peer->id); - kfree(bind); - return -EINVAL; - } + if (local_ip) { + if (ss->ss_family == AF_INET) { + ip_len = sizeof(struct in_addr); + } else if (ss->ss_family == AF_INET6) { + ip_len = sizeof(struct in6_addr); + } else { + net_dbg_ratelimited("%s: invalid family %u for remote endpoint for peer %u\n", + netdev_name(peer->ovpn->dev), + ss->ss_family, peer->id); + kfree(bind); + return -EINVAL; + } - memcpy(&bind->local, local_ip, ip_len); + memcpy(&bind->local, local_ip, ip_len); + } /* 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, struct sk_buff *skb, return match; } +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 != OVPN_MODE_MP) + return; + + if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) { + /* remove potential old hashing */ + hlist_nulls_del_init_rcu(&peer->hash_entry_addr4); + + nhead = 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 = 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, struct ovpn_peer *peer) ovpn_get_hash_head(ovpn->peers->by_id, &peer->id, sizeof(peer->id))); - if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) { - nhead = 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 = 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..a1423f2b09e066d9c2392685d3765a2ec29a82ba 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 *peer) return kref_get_unless_zero(&peer->refcount); } +void ovpn_peer_release(struct ovpn_peer *peer); void ovpn_peer_release_kref(struct kref *kref); /** @@ -147,6 +148,7 @@ struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_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); @@ -154,5 +156,8 @@ void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout); void ovpn_peer_keepalive_work(struct work_struct *work); void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb); +int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, + const struct sockaddr_storage *ss, + const void *local_ip); #endif /* _NET_OVPN_OVPNPEER_H_ */ diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c index 3a18fbdd3721628c0b4b0d23c9aef77332addfe6..a83cbab7259109f6cb476d6cc670d16180c12761 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, struct 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. From patchwork Tue Apr 15 11:17:37 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 881511 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 F189F29E057 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=1744715878; cv=none; b=gpKfcPbGsQcGlneC4h1IHW2SIqNVCOdEKk6DBa2E1bTVGP4Dpdq0+/rtnzrpOFxjwTF0cO5nOoeR/JSTCdmN66pxfyfa0jRYERdoRrR7SpE/SULfQJb39UN1gDpXW0+dLbIFH72njNJoDGc0MSL2qwfLg0OWhIxoHAvbcFbbCa4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1744715878; c=relaxed/simple; bh=tkMTNHYy1IP8criuCtWawbeRzr/0TEcKezDOnHfMX10=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=sbRTaLBpjWMrMsfwQmIIV6Diwz44FFSiU/Sr1tiF23yqwlUfBdKYpPscmyYsp872bTUrbJtqHhaygvmlCLJDMS4LDjST5tL4f3chtl70iTBG6C9OQA/LFxIsBoU090T927sLKRS2yS1InDxrL5dX0TzGlb88A17Kn+nx8hY4woQ= 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-43cf034d4abso59023695e9.3 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=ITq50wiq7v9IYOlqQDt6dNFDD8pFU+zmw+Jtc8LHbLN8YDlG7HxdpNkAlf0TrCF+Wy 1YfG1sMjIoyJ2qnaQFq1ZNWv1Zt86Oo8r2lrY2HYgRHgSqDaHHxfUrWmWuM2bRRz8w3I sgdCcHjNEbdE4RyXy4uScsE4BLI9FzVD/0OyPawFFo3kcDiot/mFuMI0cnvgUQcakOsW H8D31v/sLHf2UbFpAoLMUOUNtXxk63AozXMLvUB1nvP3wIoMmqFP/8UhIx6eam+T/Rz1 T/Ro/xB/nQR6qZ1LKgpcfijw6gBeICpdo8I00oC0FUkw2QGI8QILev6VXusLuSFYo619 dsnQ== X-Forwarded-Encrypted: i=1; AJvYcCXCWcixyojS2Mr6pH1Ig2nPzQ0JaNLEorhBM2DDyWwJYibjmx6G0b1L/1/oznVes8MyupwD6A2nI8lxbvweHEY=@vger.kernel.org X-Gm-Message-State: AOJu0YwUy2dvayimYomOw/p9hl8fma7rPZO0xm8Y9xb1xuNFZkCURNHg QdaWFH50RcyLZtmza3GKnwioqaWRkYA3kvhIVL/C5R8Ug0xNUwOL02CAFNcT5IhcsLHMAdNNGHr bUgKmskt6HaObaO5IJqGMljhj8hEXTb0YzUkuJo6HTw5UOP6J76I+lyDdoNg= X-Gm-Gg: ASbGncvIWPnakSqoLLNUa59rlQPs6e4ommF3XmBG3Hzh/6i5ZjyCJHJYIfXJ7QaQqXH BAu7cCm69v3ig1hy2I7h6XFvaD8sJCJwuQfFUequUgmsNf5FkRF+EV5doL+9vuegRpNf3YD5+M6 f/9pqOE2BeobxVxnHXmN7YNbuz68kLybrGmt5H1TAYBcYlLstzTQiyB7gaSgghrYv63QXOzifrL B8unDgyF3D1zB8hk7o5UwDk4atlT04yHX43Mel9JfBTZweo8vQmk/Xm7jzD+yNGLH2feY7c3J7M 2Rf87qmflVt73RFuWpqQl8lXUg0ClDHJAN4ipQ== 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-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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..90580e32052fb56c646a6df7816872366133bc75 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) } } +/* 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 = NULL; + + spin_lock_bh(&cs->lock); + if (rcu_access_pointer(cs->slots[0])->key_id == key_id) { + ks = rcu_replace_pointer(cs->slots[0], NULL, + lockdep_is_held(&cs->lock)); + } else if (rcu_access_pointer(cs->slots[1])->key_id == key_id) { + ks = 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..0e284fec3a75a0a5933978ea9d136f87a2e5c57a 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); +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..dd8a8055d9676e4b57cf9b5dc0ff1d082d231850 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); + if (unlikely(ret == -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; diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 1f4220021df3a6e74d6e8946a58882bf5d66e444..f0b5716059364a1deee1c7d4da1d5341b53dffca 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; } +/** + * 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 = -EMSGSIZE; + void *hdr; + + netdev_info(peer->ovpn->dev, "peer with id %u must rekey - primary key unusable.\n", + peer->id); + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &ovpn_nl_family, 0, OVPN_CMD_KEY_SWAP_NTF); + if (!hdr) { + ret = -ENOBUFS; + goto err_free_msg; + } + + if (nla_put_u32(msg, OVPN_A_IFINDEX, peer->ovpn->dev->ifindex)) + goto err_cancel_msg; + + k_attr = 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 = rcu_dereference(peer->sock); + if (!sock) { + ret = -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..5dc84c8e5e803014053faa0d892fc3a7259d40e5 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); +int ovpn_nl_key_swap_notify(struct ovpn_peer *peer, u8 key_id); + #endif /* _NET_OVPN_NETLINK_H_ */ From patchwork Tue Apr 15 11:17:38 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 881510 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 9276729E07B for ; Tue, 15 Apr 2025 11:17:56 +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=1744715881; cv=none; b=bqBdnwto6M5H8G8cRUsnOwX3Kg7LrfSOEcnooBcSU3226JhUHeoRrFOtP5+2uvQE6DE/s4IuoE62da/lL3cIdEN5P7bF37GVNt3cRiFi9e1k6rZ+9Q/okKZVS3vl8phVikgeXgf/ULmpqhRWT2b//xliZqCL6QUoPxq4KoFt09c= 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.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="WZq1u/x+" Received: by mail-wm1-f41.google.com with SMTP id 5b1f17b1804b1-43d0359b1fcso36382345e9.0 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=rbS2y25oqLuqjdXQx+aOeH6ZL/p/XtdP3olPcxBh2YsoSFG8yIxn20msb/SD3KeB3F /ao9akGQdc7QHHdEpg+X+IapSHimYN+uqykr28XywGFN1IgJRZQCzAIJujKyBV11+MAc /XO3s++0KEsljoNvzMs0pQ5z2JGH9K0YdZTGvepo6Ep2MS3+CRF/cXWk1MVLF0eNgClO iqVzXbnfcmvTBv0CrHbJwc3a0CRX2C/rsmOLf17MbjHg8j7xR58h+hK9aUh5jG1FScfV qDHvOiNTvxLkjA5OqF9Bil8D8NKE6EvPOrt5gc4ElVwzCtfQnqAr8mDVRMbKzP7R2y36 yCDw== X-Forwarded-Encrypted: i=1; AJvYcCUlKmA0METVK88fkqWXf0raka85tqcUI2nvCaUFRnTcVza2plhFPYKoXt4LKLFy0pQEBaP9AYTLzarjqHY5oW8=@vger.kernel.org X-Gm-Message-State: AOJu0YzqTOSJVPK9EMLAQ9VhZPmpknff6e3xVLMoLwnoPrlpwf9XGJHe 8rZ1Yy+nMw30zObFu9ZD0kHELnKyTlDsq/w1eOR1n3PaVj1s1AjeSLmj1YRECz2TTjnuDId/C0g 4nGfng5xBLDUdGIYWb0fBIIwd5q7bmIhvoWSAl7WNsM/Kh2hJgbEBiCuGdAE= X-Gm-Gg: ASbGncsGPI+P5mdMiJ6BvdOte7fC4mOkmODYKMvtnsW2vvOgjvmwV9d1ebWYT+ru1O5 Fn7ByYUwPucVUy9dxCLhKFLxYrPYxJNOZUqtQlLBdpbMSXTts2J8C+G29YolrNxd4d1dQYkeTWD wI0gTOPB9qna/38ZcHtTOCjq5oOkr7GzFOfKqshO0OJTRm8fYpkXwIa/rnvYFRej8eAiPEu5+y1 jlDoPNaydx7RU7Qzan+UTKjiRpOeUW3Ka5B52w4zFXUMjdo4cldy1+vyp8ZJ9wpf3QZYon4pcsy 2jMvS0sWnA1t4aV4oNiAPUz/oPUgAIx2Lo/hbw== 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-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 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 --- 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..bea03913bfb1e1948d57bd613d2bc6241c76fc06 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; } +/** + * 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 = -EMSGSIZE; + void *hdr; + + netdev_info(peer->ovpn->dev, "deleting peer with id %u, reason %d\n", + peer->id, peer->delete_reason); + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &ovpn_nl_family, 0, OVPN_CMD_PEER_DEL_NTF); + if (!hdr) { + ret = -ENOBUFS; + goto err_free_msg; + } + + if (nla_put_u32(msg, OVPN_A_IFINDEX, peer->ovpn->dev->ifindex)) + goto err_cancel_msg; + + attr = nla_nest_start(msg, OVPN_A_PEER); + if (!attr) + goto err_cancel_msg; + + if (nla_put_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 = rcu_dereference(peer->sock); + if (!sock) { + ret = -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..8615dfc3c4720a2a550b5cd1a8454ccc58a3c6ba 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); +int ovpn_nl_peer_del_notify(struct ovpn_peer *peer); int ovpn_nl_key_swap_notify(struct ovpn_peer *peer, u8 key_id); #endif /* _NET_OVPN_NETLINK_H_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 0469afd6da238d795b63b9e68c8c728fa324de98..a37f89fffb02efbb6468d54c93247b170b5d11f6 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, } peer->delete_reason = reason; + ovpn_nl_peer_del_notify(peer); /* append to provided list for later socket release and ref drop */ llist_add(&peer->release_entry, release_list);