From patchwork Wed Oct 2 09:02:15 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819508 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f43.google.com (mail-wr1-f43.google.com [209.85.221.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A12691974F4 for ; Wed, 2 Oct 2024 09:03:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859802; cv=none; b=Bkqf4lvbMMVuFW5TbFXXOXd8zyWxxU3fooL17v7O3teSO829H1fmzZxQjQD5cLaShS0wU4piMqq/aOOYqc56UC0O36DUJzROXMkiC4szWvLwpw1MsoYY4suiH1WaarsAFGHQ5UaYGHzDM3QCH9ZG/NkQ21zudT6FvmsPzyHzM+U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859802; c=relaxed/simple; bh=BlosqxkVcubrmJg/4VB7LReocifRDKBIus4x10zDNg0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=o14ZsyQ2gUPvmsm/Nnt9LiYuds4gblkHsxbJJ618EDRYtXBl2BEM3zzYZf9CaAdCe3OKPLYXOLWMaRgKZHRU8TvVjYrGGJljTtxLWIB5XFvQMb3xVum9KbEv1twHfX5aui++/ikrn0JIZrh1T1Pd3iRAaW/ZWVwaK92eZZng3DE= 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=hSpobwN6; arc=none smtp.client-ip=209.85.221.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="hSpobwN6" Received: by mail-wr1-f43.google.com with SMTP id ffacd0b85a97d-37ce9644daaso2239733f8f.3 for ; Wed, 02 Oct 2024 02:03:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859799; x=1728464599; 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=mnrRM9dH2ACQXMxz9IzXWQR3bhpJdNYqVmlVj8NU+CA=; b=hSpobwN697kWWm/WTGzQw/xuldb9wberrNYzweilVl0J9U8s50DOFMeKYXdl+TTNkW M+bxX1QRfAQ4a2S1owXGv++UtS9Rhl1GpZ7elPLjK0cTAStOgdQ1el3YPXry7Rm90UY8 4p4TyCGd2GYPCtXHWlOyIAYng1e5D2pBUzMO11Uoil1loFCpTOkAYLGzDha/prINTXTi wH6kbZxVGeWlc2jAEzG5xDZAQR/CfXn17/NQwyCLqP+lGM8ydDxantgsYUl4yNaZikg6 cMeqG2JVgjlLHAP5IWEvOyFkdq8zQJGk/Ctz0v/WfjHdPHC6lE8gWSD5t1bDyQbdxfHb LMiw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859799; x=1728464599; 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=mnrRM9dH2ACQXMxz9IzXWQR3bhpJdNYqVmlVj8NU+CA=; b=kpBSyzPCaW+FeLXlooll39NqPMtFn66cXu8rMGbc8jT+q7a44sHP79Hv8yBptPQ3oA BuwcoqbRLJaoYNmqiciKjcK8R3u1GgyyAhZnP7e+zAOujtZ0gsrgiqQmpx7pDEOLNScp epJzc/8VdthIqd4+bOwKQKg4HVr2cy5eTRFZszNrwhwLbOWyFTmLSfsqpD6Cj5DO2BoR 0MNJpV+tCCQkI7HY91ugTs7x9ZOBlwg95NU+2Te0gV/fPLi7xaPn8nc1+yxy4xFLqPbi y7Ujqqg8ePGJYqspxnHbOl7i1VEgsc55wxdOo/kaQO8JT22KWYo89ZXTT0gw8A8F9De7 NYHw== X-Gm-Message-State: AOJu0Ywf+KEKDNI38340kPe0siesDw5Ex3ozBCUSf6AkxowQHyuXE1Bj ltq+r9ldenTLcqBafOJir4D8eWa+a1nqtjo13Uljmq85NJcKywtnoebJj/g9JJw= X-Google-Smtp-Source: AGHT+IHq+1W/hdrmTuCIm42N/nm8hnQR6YetKPtHvBzxj6CavoV/oKNue+L2qRwxN8TkU9S4YAci9w== X-Received: by 2002:a5d:6189:0:b0:374:cbc4:53b1 with SMTP id ffacd0b85a97d-37cfba0a641mr1417496f8f.40.1727859798949; Wed, 02 Oct 2024 02:03:18 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:18 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:15 +0200 Subject: [PATCH net-next v8 01/24] netlink: add NLA_POLICY_MAX_LEN macro Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-1-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=1538; i=antonio@openvpn.net; h=from:subject:message-id; bh=BlosqxkVcubrmJg/4VB7LReocifRDKBIus4x10zDNg0=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxQDUeMpT4CQRRPSNmsg6uA36wdILEsWR2YN XhA5+OTrZaJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUAAKCRALcOU6oDjV hzjaB/9LFbG/DdXZJfVBAml13MngrdL8EDI+wtbFws8hT517/dYRe16eEwq9vtREmMYCglyD4mk OhmXkwuAIIuSGNW14J2F/gdYxSmha49EZo7F9zGHNHwVJ8Krf+h/xFAHyPrU6ZBDGHgz3phIg7m ERvKfA8Arck6jKxv/4yOjsRmdhaAFw/wYnsbUmTSJF6iYyXnfoSRk3isphHcddcKhhhPR37yl8j bZKkumK0OZ2ymnfiLSL8/aLCaszHlxOY1POwp/cMRl+yIy0o4j1kU9/SXfmZfAvF6VoCwtoWx5e QreWQvz3jsoui2V9a7oANKLxvxZmevPGKkc9vL2q+Jz2CYRe X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org Similarly to NLA_POLICY_MIN_LEN, NLA_POLICY_MAX_LEN defines a policy with a maximum length value. The netlink generator for YAML specs has been extended accordingly. Cc: donald.hunter@gmail.com Signed-off-by: Antonio Quartulli --- include/net/netlink.h | 1 + tools/net/ynl/ynl-gen-c.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/include/net/netlink.h b/include/net/netlink.h index db6af207287c839408c58cb28b82408e0548eaca..2dc671c977ff3297975269d236264907009703d3 100644 --- a/include/net/netlink.h +++ b/include/net/netlink.h @@ -469,6 +469,7 @@ struct nla_policy { .max = _len \ } #define NLA_POLICY_MIN_LEN(_len) NLA_POLICY_MIN(NLA_BINARY, _len) +#define NLA_POLICY_MAX_LEN(_len) NLA_POLICY_MAX(NLA_BINARY, _len) /** * struct nl_info - netlink source information diff --git a/tools/net/ynl/ynl-gen-c.py b/tools/net/ynl/ynl-gen-c.py index 717530bc9c52e7cfa897814870b4583c88618a27..3ccbb301be87f80bbcf03da63d60f58c4fedc1c8 100755 --- a/tools/net/ynl/ynl-gen-c.py +++ b/tools/net/ynl/ynl-gen-c.py @@ -466,6 +466,8 @@ class TypeBinary(Type): def _attr_policy(self, policy): if 'exact-len' in self.checks: mem = 'NLA_POLICY_EXACT_LEN(' + str(self.get_limit('exact-len')) + ')' + elif 'max-len' in self.checks: + mem = 'NLA_POLICY_MAX_LEN(' + str(self.get_limit('max-len')) + ')' else: mem = '{ ' if len(self.checks) == 1 and 'min-len' in self.checks: From patchwork Wed Oct 2 09:02:16 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819509 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f43.google.com (mail-wr1-f43.google.com [209.85.221.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E57C5197A83 for ; Wed, 2 Oct 2024 09:03:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859805; cv=none; b=aZ5OqZd6iemyxYuVdy5ZLEqlD4cxbs4dsg4olenEmf5YUc5CxRQiU9Y33ae3obPGSXMfJIROQbiom8IB19mUx4rYz7if2mURb2q7GboBfgROv+E3kbN6Is5PEx/1lZ/Eox8mLRUdk8cbIEk/KJ9U+1z7xxD2+IFIVVNS9zrnN1s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859805; c=relaxed/simple; bh=MB053LV7RqOZWCCPPJ0wd08In1+LTQLKnzeKgRb/zE0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=F8GOUBzmhqKc3yS4DQ8KNXuDcjiNzR54wWtiLERHNH+6gaOGcEMQ8gFlsXOye1TvpUKaGyycpPOdWCDoaA08VCGXeDZOQpDb2gz6RYFueMhmJADC8CKwc1YHRMkvRTzsyFAdX1RYMIXg0NI4DqXm5eliTy7WEez7zRe79FhUU5s= 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=GMPiPyNQ; arc=none smtp.client-ip=209.85.221.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="GMPiPyNQ" Received: by mail-wr1-f43.google.com with SMTP id ffacd0b85a97d-37ccdc0d7f6so3439950f8f.0 for ; Wed, 02 Oct 2024 02:03:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859800; x=1728464600; 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=TM1ZYWXtUr0bo+1jyZNXkB7ZLfkOU8u8FSCLWhl5Xa8=; b=GMPiPyNQ4su3nyzIrMT182iUwK9L3gQCWVZeP7T9nUxFsCgb05tLAYZgUgdwhyDJLh 0j+iD/I4I8yWG4k3FMcIeS+8219UqkYWkh6y2TdK+qiEJ9qJCmACRBSFN+MKcYJdGDZc 998ltUmByApV4GGJYQFALYa5r7Mbos0xp42fXyCNcR/zRtUqJxQWEJHPEEYT+1EoKDg3 zkdhKuf/e3qbb6ftAwrnyykcBQOVgg+cKjlSC3bY9Id2wa1atxRr4jf2HE/tDF4x+KQ0 8WnwJnG2DRmZhtbPLLlwW858+Y/i4FqYLeXbqvHMxGCcs37esHaDjw/dOOKhMrVapoYP +mjg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859800; x=1728464600; 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=TM1ZYWXtUr0bo+1jyZNXkB7ZLfkOU8u8FSCLWhl5Xa8=; b=gvX52y82eMuUid60A/SnyxJXEbPDgQ+RP/SahlnJIloyTNdSXX/iFY61X9QYRHwWqr 4bPBgkBvGAwE824djxJduiJx46KdvWCNIfSxb0BQDAMQ6c2unyzCCUsoZ2n76PY9QrmR U5Je8kQ5RwsjpccxPCRB3pzv45+PdNUI2NoWkjYo3JQbbht3Ek6i5saftdp/M11PWYzr bJmKq8RYYxGBiezrvciv7le21bF1CaUQe/schJGlVtMZ7IPoX5VWdCYdBE7oVkmtTn9K EVe4yG3H/v35po36Z9u+kxrNeyvvuOZAhgxE6a2YYYgZzUTfmBiYecoZ7jsQTK0GnOAp /Kxw== X-Gm-Message-State: AOJu0YywnVoafphdVga0I7WuqA4w/9eExE/frwtV2jeoVwQWOnI8LJAq oxg1zLG9gwCqDcgXtErVGz0j8HjCoffw5zfD/TTfAQBL3j6xX892nVh7xTNTBSY= X-Google-Smtp-Source: AGHT+IEQpeZrR1hILvcRapK11b6gxKEIja/mVld7C3DbYJn7qhMF4nLDhUqFWc0LfzGTe2tehrkcxw== X-Received: by 2002:a5d:598c:0:b0:374:c9f0:752d with SMTP id ffacd0b85a97d-37cfba0468cmr1426258f8f.47.1727859800022; Wed, 02 Oct 2024 02:03:20 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:19 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:16 +0200 Subject: [PATCH net-next v8 02/24] net: introduce OpenVPN Data Channel Offload (ovpn) Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-2-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com, steffen.klassert@secunet.com, antony.antony@secunet.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=11741; i=antonio@openvpn.net; h=from:subject:message-id; bh=MB053LV7RqOZWCCPPJ0wd08In1+LTQLKnzeKgRb/zE0=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxRzEFdnLc1iwyePQXTimSV/n5WJSF1TIVoJ 6OJKvuCzDWJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUQAKCRALcOU6oDjV hyLnCACqhYiFl4T20mFjJ7gJaJCQfY7IkUjS46RcGenLyQ2EJVYxNklw7Fm1TggRtOFrtUd2rDD 3ASy5UYLxeA35QHJwMRR7RwgmRZe/Y2HRKhI4kMLJLTPhGJja2O+nCCSbNrTRjTeBcTWrMPnHxg fE4nnnyVjC2iw1oHBlpGUbEm8Y9MXz7zWdRV32GRM2UIpSklKLB/99W4FH3UBdZQmn+qBbZ+w5M P7WvuLM/SuEx/8ntkw0XXu16q7brc2xUy94kMnB1Rfy8hIV4lIGr/X+gcmNwPAa3TC4O+oBxu4G FBnQKmKVhEfjw+LSxGIxzRWtiC9KqsJ2jYQF2jz9DZuPUU02 X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org OpenVPN is a userspace software existing since around 2005 that allows users to create secure tunnels. So far OpenVPN has implemented all operations in userspace, which implies several back and forth between kernel and user land in order to process packets (encapsulate/decapsulate, encrypt/decrypt, rerouting..). With `ovpn` we intend to move the fast path (data channel) entirely in kernel space and thus improve user measured throughput over the tunnel. `ovpn` is implemented as a simple virtual network device driver, that can be manipulated by means of the standard RTNL APIs. A device of kind `ovpn` allows only IPv4/6 traffic and can be of type: * P2P (peer-to-peer): any packet sent over the interface will be encapsulated and transmitted to the other side (typical OpenVPN client or peer-to-peer behaviour); * P2MP (point-to-multipoint): packets sent over the interface are transmitted to peers based on existing routes (typical OpenVPN server behaviour). After the interface has been created, OpenVPN in userspace can configure it using a new Netlink API. Specifically it is possible to manage peers and their keys. The OpenVPN control channel is multiplexed over the same transport socket by means of OP codes. Anything that is not DATA_V2 (OpenVPN OP code for data traffic) is sent to userspace and handled there. This way the `ovpn` codebase is kept as compact as possible while focusing on handling data traffic only (fast path). Any OpenVPN control feature (like cipher negotiation, TLS handshake, rekeying, etc.) is still fully handled by the userspace process. When userspace establishes a new connection with a peer, it first performs the handshake and then passes the socket to the `ovpn` kernel module, which takes ownership. From this moment on `ovpn` will handle data traffic for the new peer. When control packets are received on the link, they are forwarded to userspace through the same transport socket they were received on, as userspace is still listening to them. Some events (like peer deletion) are sent to a Netlink multicast group. Although it wasn't easy to convince the community, `ovpn` implements only a limited number of the data-channel features supported by the userspace program. Each feature that made it to `ovpn` was attentively vetted to avoid carrying too much legacy along with us (and to give a clear cut to old and probalby-not-so-useful features). Notably, only encryption using AEAD ciphers (specifically ChaCha20Poly1305 and AES-GCM) was implemented. Supporting any other cipher out there was not deemed useful. Both UDP and TCP sockets ae supported. As explained above, in case of P2MP mode, OpenVPN will use the main system routing table to decide which packet goes to which peer. This implies that no routing table was re-implemented in the `ovpn` kernel module. This kernel module can be enabled by selecting the CONFIG_OVPN entry in the networking drivers section. NOTE: this first patch introduces the very basic framework only. Features are then added patch by patch, however, although each patch will compile and possibly not break at runtime, only after having applied the full set it is expected to see the ovpn module fully working. Cc: steffen.klassert@secunet.com Cc: antony.antony@secunet.com Signed-off-by: Antonio Quartulli --- MAINTAINERS | 8 ++++ drivers/net/Kconfig | 14 ++++++ drivers/net/Makefile | 1 + drivers/net/ovpn/Makefile | 11 +++++ drivers/net/ovpn/io.c | 22 ++++++++++ drivers/net/ovpn/io.h | 15 +++++++ drivers/net/ovpn/main.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/main.h | 15 +++++++ include/uapi/linux/udp.h | 1 + 9 files changed, 196 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index e71d066dc919404231446f8adfb291494e392883..0f02c9e1664fddb5ad45232e46b02c42aa27f1b6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17448,6 +17448,14 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.git F: Documentation/filesystems/overlayfs.rst F: fs/overlayfs/ +OPENVPN DATA CHANNEL OFFLOAD +M: Antonio Quartulli +L: openvpn-devel@lists.sourceforge.net (moderated for non-subscribers) +L: netdev@vger.kernel.org +S: Maintained +T: git https://github.com/OpenVPN/linux-kernel-ovpn.git +F: drivers/net/ovpn/ + P54 WIRELESS DRIVER M: Christian Lamparter L: linux-wireless@vger.kernel.org diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 9920b3a68ed1582ab3af1cf9d4f6ae7bfd68ba7a..0055bcd2356c70bd9cfd8a944f549817dab8154f 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -115,6 +115,20 @@ config WIREGUARD_DEBUG Say N here unless you know what you're doing. +config OVPN + tristate "OpenVPN data channel offload" + depends on NET && INET + select NET_UDP_TUNNEL + select DST_CACHE + select CRYPTO + select CRYPTO_AES + select CRYPTO_GCM + select CRYPTO_CHACHA20POLY1305 + select STREAM_PARSER + help + This module enhances the performance of the OpenVPN userspace software + by offloading the data channel processing to kernelspace. + config EQUALIZER tristate "EQL (serial line load balancing) support" help diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 13743d0e83b5fde479e9b30ad736be402d880dee..5152b3330e28da7eaec821018a26c973bb33ce0c 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_IPVLAN) += ipvlan/ obj-$(CONFIG_IPVTAP) += ipvlan/ obj-$(CONFIG_DUMMY) += dummy.o obj-$(CONFIG_WIREGUARD) += wireguard/ +obj-$(CONFIG_OVPN) += ovpn/ obj-$(CONFIG_EQUALIZER) += eql.o obj-$(CONFIG_IFB) += ifb.o obj-$(CONFIG_MACSEC) += macsec.o diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..53fb197027d787d6683e9056d3d341abf6ed38e4 --- /dev/null +++ b/drivers/net/ovpn/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# ovpn -- OpenVPN data channel offload in kernel space +# +# Copyright (C) 2020-2024 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +obj-$(CONFIG_OVPN) := ovpn.o +ovpn-y += main.o +ovpn-y += io.o diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c new file mode 100644 index 0000000000000000000000000000000000000000..ad3813419c33cbdfe7e8ad6f5c8b444a3540a69f --- /dev/null +++ b/drivers/net/ovpn/io.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include + +#include "io.h" + +/* Send user data to the network + */ +netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) +{ + skb_tx_error(skb); + kfree_skb(skb); + return NET_XMIT_DROP; +} diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h new file mode 100644 index 0000000000000000000000000000000000000000..aa259be66441f7b0262f39da12d6c3dce0a9b24c --- /dev/null +++ b/drivers/net/ovpn/io.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPN_H_ +#define _NET_OVPN_OVPN_H_ + +netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev); + +#endif /* _NET_OVPN_OVPN_H_ */ diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c new file mode 100644 index 0000000000000000000000000000000000000000..2a9a8e11c5111eb36fab1fb47d6c4c6dfdf56a78 --- /dev/null +++ b/drivers/net/ovpn/main.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + * James Yonan + */ + +#include +#include +#include +#include + +#include "main.h" +#include "io.h" + +/* Driver info */ +#define DRV_DESCRIPTION "OpenVPN data channel offload (ovpn)" +#define DRV_COPYRIGHT "(C) 2020-2024 OpenVPN, Inc." + +/** + * ovpn_dev_is_valid - check if the netdevice is of type 'ovpn' + * @dev: the interface to check + * + * Return: whether the netdevice is of type 'ovpn' + */ +bool ovpn_dev_is_valid(const struct net_device *dev) +{ + return dev->netdev_ops->ndo_start_xmit == ovpn_net_xmit; +} + +static struct rtnl_link_ops ovpn_link_ops = { + .kind = "ovpn", + .netns_refund = false, + .dellink = unregister_netdevice_queue, +}; + +static int ovpn_netdev_notifier_call(struct notifier_block *nb, + unsigned long state, void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + + if (!ovpn_dev_is_valid(dev)) + return NOTIFY_DONE; + + switch (state) { + case NETDEV_REGISTER: + /* add device to internal list for later destruction upon + * unregistration + */ + break; + case NETDEV_UNREGISTER: + /* can be delivered multiple times, so check registered flag, + * then destroy the interface + */ + break; + case NETDEV_POST_INIT: + case NETDEV_GOING_DOWN: + case NETDEV_DOWN: + case NETDEV_UP: + case NETDEV_PRE_UP: + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static struct notifier_block ovpn_netdev_notifier = { + .notifier_call = ovpn_netdev_notifier_call, +}; + +static int __init ovpn_init(void) +{ + int err = register_netdevice_notifier(&ovpn_netdev_notifier); + + if (err) { + pr_err("ovpn: can't register netdevice notifier: %d\n", err); + return err; + } + + err = rtnl_link_register(&ovpn_link_ops); + if (err) { + pr_err("ovpn: can't register rtnl link ops: %d\n", err); + goto unreg_netdev; + } + + return 0; + +unreg_netdev: + unregister_netdevice_notifier(&ovpn_netdev_notifier); + return err; +} + +static __exit void ovpn_cleanup(void) +{ + rtnl_link_unregister(&ovpn_link_ops); + unregister_netdevice_notifier(&ovpn_netdev_notifier); + + rcu_barrier(); +} + +module_init(ovpn_init); +module_exit(ovpn_cleanup); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_COPYRIGHT); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ovpn/main.h b/drivers/net/ovpn/main.h new file mode 100644 index 0000000000000000000000000000000000000000..a3215316c49bfcdf2496590bac878f145b8b27fd --- /dev/null +++ b/drivers/net/ovpn/main.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2024 OpenVPN, Inc. + * + * Author: James Yonan + * 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/include/uapi/linux/udp.h b/include/uapi/linux/udp.h index 1a0fe8b151fb33543429d1f59167a9021b458e20..f9f8ffddfd0c8b5583bb99b1406c77ddfcbc69d1 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 Wed Oct 2 09:02:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819510 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wm1-f47.google.com (mail-wm1-f47.google.com [209.85.128.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2092815445D for ; Wed, 2 Oct 2024 09:03:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859806; cv=none; b=MjgObNgdYyavvyurwfg43B7tk7lEAGuiNhiOU1cRTCbGkYq4a/Kx7UaoiPOA2N1cWnk0n91stzRlRSitfr1ZJCgFQBBE0gyg6UsDI8tgafm7Y1W34OuQX3vK6AnhuvlS2lsR8pgZGmst8QYPo9bhW8mvnX84ykU4PFNktR9jLYA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859806; c=relaxed/simple; bh=OsaXiXJuPDCgDu9Ty5wxAwW8nJs0U+zkuK7zVb46UPo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=GyaoXz6ne/bFxeJ/dsBBNAXkz3CqDydrcnbQeMJHrLBKAAli87Oc/gX4YeAednQV7fX60NJ+jRjeN2lXd0iJCn1Wt9d75TRwDtdupa4F/yMF/VYXR1uf6tRmXe+DBb1ajVwCpmQC+kck7SQC3qgxNWafXi3Mc8t1SD9WqMonPOw= 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=FXvrgbJq; arc=none smtp.client-ip=209.85.128.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="FXvrgbJq" Received: by mail-wm1-f47.google.com with SMTP id 5b1f17b1804b1-42cde6b5094so57679055e9.3 for ; Wed, 02 Oct 2024 02:03:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859801; x=1728464601; 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=Ff/78j18oy3DUOOL34frBbs0LYYyqaPC/HYWhxroeMU=; b=FXvrgbJqAkaNvA5ONolRtX9aO1RQuwwHO7A371xJh/AVDGNbhb8ayAXNGKNUbjL77W BMHPgoeCHO7g2jkG50QA0zMPwLTAd0OGD7/XKtDXAOS0NMuSiDpM1FlE2HL08HtVUaVV 2ndbgoOsHFs6jULiJV/bdHyxnnD8NoVHr8vmxEpW8oKLt7nI2cBjtcPTe8p9z7p4Z1Pa cjemeBziCrQC++JKj0DMG7REdWOgsmW5JwVNUHr71mfbX3zvlNQCMMbV9kAy1HmEB/HF c2MWJkOiHmX8hc58pqApGD/3E+23vGbEM+dMd1kiKuR8XVC73YrTLa2PMCJYofs782Ud T4Aw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859801; x=1728464601; 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=Ff/78j18oy3DUOOL34frBbs0LYYyqaPC/HYWhxroeMU=; b=aea+rGT/gFUKJBtbG+548SdNP05bMuFnelCr3QKnFSk90rVySsX4a2EWURCtjDmvjp fFybo+Pf2CvOfTujjX9yk7c1GwI5vZ6BzmrKKb+zfEcLUAreSI1Xc/vvMYNfZeI0/lqu twW59A/qT7lFYo0hK2Tc4Nt6Uqc+PQNwdRoWuYg+aiJbJoRX+nozCwYfonBvg4gUq6vh ah4C+uAT1d0y9PZjMp58Mo8fkw8sQXYSSBKiqQVIBQz23TKBGsTEpBIi35wm6UxkXuzp FXpr1kWNUGrLSkOsfKfr+A4S+2j1/5sM8nl8UYuEIeHvvWwZb2VKS1RqqEvPoXJKrScq O4iQ== X-Gm-Message-State: AOJu0YxKyLZCF1iGp8jZW2H6K6ZcWsDI4cbM2O1ZmUI2LaiFDRq3crNR g7KKdnlJy2FQws4/HMSisyiw1WTJB9z1LH1TUQmevV3MzRbTjI5aXZbcZLhtYH8= X-Google-Smtp-Source: AGHT+IGKAi0Iyl2z8fR6jsut7/Bzq7STIHr5pvEm/q50MZB4iY47t5TuuMQDTTDmjkaMYwSZNZ9Eig== X-Received: by 2002:a05:6000:2ac:b0:37c:cf72:f248 with SMTP id ffacd0b85a97d-37cfba183b0mr2137529f8f.50.1727859801288; Wed, 02 Oct 2024 02:03:21 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:20 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:17 +0200 Subject: [PATCH net-next v8 03/24] ovpn: add basic netlink support Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-3-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=31964; i=antonio@openvpn.net; h=from:subject:message-id; bh=OsaXiXJuPDCgDu9Ty5wxAwW8nJs0U+zkuK7zVb46UPo=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxRhZHGINqRpSHdmlXlBObx7W7nrOHvNJSdO cEMcIZGq9iJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUQAKCRALcOU6oDjV h2HfCACocXlHIrjQnSpzzWP78EdAIFTsRGBT6N5Ej8iz+5Wwvl7NndP19xWNXd7yg24QESyupXO zMklKGktlE+G+1Xb4NhFyrF8cIZYmNjDIpHRYBznzfzu1JZbH+cDjvWCrOQYvm2iXxf7YvclcnL kUKXkrklm61vIggAtp8eW9rpgdEtFezUAmqO6x1xnRWzvH8r6Ih6HHi/RMX+xPEqHMD3bLbIBcd N/4YlthcLEYlUTNU7EGIGGCF6rZmxfVk7glDdlhzDw0QEEGVLi9/j5PxnKAxmhgU8JlxrZel064 B5gRlbT7lT6fBaqqwsVja0tzmUEpd4fFItQbWpUyGuAZ554f X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org This commit introduces basic netlink support with family registration/unregistration functionalities and stub pre/post-doit. More importantly it introduces the YAML uAPI description along with its auto-generated files: - include/uapi/linux/ovpn.h - drivers/net/ovpn/netlink-gen.c - drivers/net/ovpn/netlink-gen.h Cc: donald.hunter@gmail.com Signed-off-by: Antonio Quartulli --- Documentation/netlink/specs/ovpn.yaml | 387 ++++++++++++++++++++++++++++++++++ MAINTAINERS | 2 + drivers/net/ovpn/Makefile | 2 + drivers/net/ovpn/main.c | 15 +- drivers/net/ovpn/netlink-gen.c | 224 ++++++++++++++++++++ drivers/net/ovpn/netlink-gen.h | 42 ++++ drivers/net/ovpn/netlink.c | 158 ++++++++++++++ drivers/net/ovpn/netlink.h | 15 ++ drivers/net/ovpn/ovpnstruct.h | 21 ++ include/uapi/linux/ovpn.h | 116 ++++++++++ 10 files changed, 981 insertions(+), 1 deletion(-) diff --git a/Documentation/netlink/specs/ovpn.yaml b/Documentation/netlink/specs/ovpn.yaml new file mode 100644 index 0000000000000000000000000000000000000000..36983f293ecbd59532114808a20737005e91ad72 --- /dev/null +++ b/Documentation/netlink/specs/ovpn.yaml @@ -0,0 +1,387 @@ +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +# +# Author: Antonio Quartulli +# +# Copyright (c) 2024, OpenVPN Inc. +# + +name: ovpn + +protocol: genetlink + +doc: Netlink protocol to control OpenVPN network devices + +definitions: + - + type: const + name: nonce-tail-size + value: 8 + - + type: enum + name: cipher-alg + value-start: 0 + entries: [ none, aes-gcm, chacha20-poly1305 ] + - + type: enum + name: del-peer-reason + value-start: 0 + entries: [ teardown, userspace, expired, transport-error, transport-disconnect ] + - + type: enum + name: key-slot + value-start: 0 + entries: [ primary, secondary ] + - + type: enum + name: mode + value-start: 0 + entries: [ p2p, mp ] + +attribute-sets: + - + name: peer + attributes: + - + name: id + type: u32 + doc: | + The unique ID of the peer. To be used to identify peers during + operations + checks: + max: 0xFFFFFF + - + name: 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: 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: u32 + doc: Number of packets received at the transport level + - + name: link-tx-packets + type: u32 + doc: Number of packets transmitted at the transport level + - + name: keyconf + attributes: + - + name: peer-id + type: u32 + doc: | + The unique ID of the peer. 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. 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 actua cipher IV + checks: + exact-len: nonce-tail-size + - + name: ovpn + attributes: + - + name: ifindex + type: u32 + doc: Index of the ovpn interface to operate on + - + name: ifname + type: string + doc: Name of the ovpn interface that is being created + - + name: mode + type: u32 + enum: mode + doc: | + Oper mode instructing an interface to act as Point2Point or + MultiPoint + - + name: peer + type: nest + doc: | + The peer object containing the attributed of interest for the specific + operation + nested-attributes: peer + - + name: keyconf + type: nest + doc: Peer specific cipher configuration + nested-attributes: keyconf + +operations: + list: + - + name: dev-new + attribute-set: ovpn + flags: [ admin-perm ] + doc: Create a new interface of type ovpn + do: + request: + attributes: + - ifname + - mode + reply: + attributes: + - ifname + - ifindex + - + name: dev-del + attribute-set: ovpn + flags: [ admin-perm ] + doc: Delete existing interface of type ovpn + do: + pre: ovpn-nl-pre-doit + post: ovpn-nl-post-doit + request: + attributes: + - ifindex + - + 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-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-new + 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 0f02c9e1664fddb5ad45232e46b02c42aa27f1b6..f753060d4e2467a786778ddd4f835861a603ce02 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17455,6 +17455,8 @@ L: netdev@vger.kernel.org S: Maintained T: git https://github.com/OpenVPN/linux-kernel-ovpn.git F: drivers/net/ovpn/ +F: include/uapi/linux/ovpn.h +F: Documentation/netlink/spec/ovpn.yaml P54 WIRELESS DRIVER M: Christian Lamparter diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 53fb197027d787d6683e9056d3d341abf6ed38e4..201dc001419f1d99ae95c0ee0f96e68f8a4eac16 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -9,3 +9,5 @@ obj-$(CONFIG_OVPN) := ovpn.o ovpn-y += main.o ovpn-y += io.o +ovpn-y += netlink.o +ovpn-y += netlink-gen.o diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 2a9a8e11c5111eb36fab1fb47d6c4c6dfdf56a78..0274ddddbcd25080d7f85347f79aad0259771508 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -7,12 +7,16 @@ * James Yonan */ +#include #include #include #include #include +#include +#include "ovpnstruct.h" #include "main.h" +#include "netlink.h" #include "io.h" /* Driver info */ @@ -31,7 +35,7 @@ bool ovpn_dev_is_valid(const struct net_device *dev) } static struct rtnl_link_ops ovpn_link_ops = { - .kind = "ovpn", + .kind = OVPN_FAMILY_NAME, .netns_refund = false, .dellink = unregister_netdevice_queue, }; @@ -86,8 +90,16 @@ static int __init ovpn_init(void) goto unreg_netdev; } + err = ovpn_nl_register(); + if (err) { + pr_err("ovpn: can't register netlink family: %d\n", err); + goto unreg_rtnl; + } + return 0; +unreg_rtnl: + rtnl_link_unregister(&ovpn_link_ops); unreg_netdev: unregister_netdevice_notifier(&ovpn_netdev_notifier); return err; @@ -95,6 +107,7 @@ static int __init ovpn_init(void) static __exit void ovpn_cleanup(void) { + ovpn_nl_unregister(); rtnl_link_unregister(&ovpn_link_ops); unregister_netdevice_notifier(&ovpn_netdev_notifier); diff --git a/drivers/net/ovpn/netlink-gen.c b/drivers/net/ovpn/netlink-gen.c new file mode 100644 index 0000000000000000000000000000000000000000..9912706a2d68282e7d483404002cd0eeb1140ba6 --- /dev/null +++ b/drivers/net/ovpn/netlink-gen.c @@ -0,0 +1,224 @@ +// 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_U32, }, + [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_U16, 1), + [OVPN_A_PEER_SOCKET] = { .type = NLA_U32, }, + [OVPN_A_PEER_VPN_IPV4] = { .type = NLA_U32, }, + [OVPN_A_PEER_VPN_IPV6] = NLA_POLICY_EXACT_LEN(16), + [OVPN_A_PEER_LOCAL_IPV4] = { .type = NLA_U32, }, + [OVPN_A_PEER_LOCAL_IPV6] = NLA_POLICY_EXACT_LEN(16), + [OVPN_A_PEER_LOCAL_PORT] = NLA_POLICY_MIN(NLA_U16, 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_U32, }, + [OVPN_A_PEER_LINK_TX_PACKETS] = { .type = NLA_U32, }, +}; + +/* OVPN_CMD_DEV_NEW - do */ +static const struct nla_policy ovpn_dev_new_nl_policy[OVPN_A_MODE + 1] = { + [OVPN_A_IFNAME] = { .type = NLA_NUL_STRING, }, + [OVPN_A_MODE] = NLA_POLICY_MAX(NLA_U32, 1), +}; + +/* OVPN_CMD_DEV_DEL - do */ +static const struct nla_policy ovpn_dev_del_nl_policy[OVPN_A_IFINDEX + 1] = { + [OVPN_A_IFINDEX] = { .type = NLA_U32, }, +}; + +/* 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_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_DEV_NEW, + .doit = ovpn_nl_dev_new_doit, + .policy = ovpn_dev_new_nl_policy, + .maxattr = OVPN_A_MODE, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = OVPN_CMD_DEV_DEL, + .pre_doit = ovpn_nl_pre_doit, + .doit = ovpn_nl_dev_del_doit, + .post_doit = ovpn_nl_post_doit, + .policy = ovpn_dev_del_nl_policy, + .maxattr = OVPN_A_IFINDEX, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .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_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..1b7aeb80af1091d5fe968c886fb019ba6f9a19a8 --- /dev/null +++ b/drivers/net/ovpn/netlink-gen.h @@ -0,0 +1,42 @@ +/* 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_dev_new_doit(struct sk_buff *skb, struct genl_info *info); +int ovpn_nl_dev_del_doit(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_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..7b6b4d03b845eeb8654e37ac3495e8172ac3f291 --- /dev/null +++ b/drivers/net/ovpn/netlink.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#include +#include + +#include + +#include "ovpnstruct.h" +#include "main.h" +#include "io.h" +#include "netlink.h" +#include "netlink-gen.h" + +MODULE_ALIAS_GENL_FAMILY(OVPN_FAMILY_NAME); + +/** + * ovpn_get_dev_from_attrs - retrieve the netdevice a netlink message is + * targeting + * @net: network namespace where to look for the interface + * @info: generic netlink info from the user request + * + * Return: the netdevice, if found, or an error otherwise + */ +static struct net_device * +ovpn_get_dev_from_attrs(struct net *net, const struct genl_info *info) +{ + struct net_device *dev; + int ifindex; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_IFINDEX)) + return ERR_PTR(-EINVAL); + + ifindex = nla_get_u32(info->attrs[OVPN_A_IFINDEX]); + + dev = dev_get_by_index(net, ifindex); + if (!dev) { + NL_SET_ERR_MSG_MOD(info->extack, + "ifindex does not match any interface"); + return ERR_PTR(-ENODEV); + } + + if (!ovpn_dev_is_valid(dev)) + goto err_put_dev; + + return dev; + +err_put_dev: + netdev_put(dev, NULL); + + NL_SET_ERR_MSG_MOD(info->extack, "specified interface is not ovpn"); + NL_SET_BAD_ATTR(info->extack, info->attrs[OVPN_A_IFINDEX]); + + return ERR_PTR(-EINVAL); +} + +int ovpn_nl_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + struct net *net = genl_info_net(info); + struct net_device *dev = ovpn_get_dev_from_attrs(net, info); + + if (IS_ERR(dev)) + return PTR_ERR(dev); + + info->user_ptr[0] = netdev_priv(dev); + + return 0; +} + +void ovpn_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + struct ovpn_struct *ovpn = info->user_ptr[0]; + + if (ovpn) + netdev_put(ovpn->dev, NULL); +} + +int ovpn_nl_dev_new_doit(struct sk_buff *skb, struct genl_info *info) +{ + return -EOPNOTSUPP; +} + +int ovpn_nl_dev_del_doit(struct sk_buff *skb, struct genl_info *info) +{ + return -EOPNOTSUPP; +} + +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_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..9e87cf11d1e9813b7a75ddf3705ab7d5fabe899f --- /dev/null +++ b/drivers/net/ovpn/netlink.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#ifndef _NET_OVPN_NETLINK_H_ +#define _NET_OVPN_NETLINK_H_ + +int ovpn_nl_register(void); +void ovpn_nl_unregister(void); + +#endif /* _NET_OVPN_NETLINK_H_ */ diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h new file mode 100644 index 0000000000000000000000000000000000000000..ff248cad1401d00c5bf0aadf9341e40d0a4aee4b --- /dev/null +++ b/drivers/net/ovpn/ovpnstruct.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNSTRUCT_H_ +#define _NET_OVPN_OVPNSTRUCT_H_ + +/** + * struct ovpn_struct - per ovpn interface state + * @dev: the actual netdev representing the tunnel + */ +struct ovpn_struct { + struct net_device *dev; +}; + +#endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/include/uapi/linux/ovpn.h b/include/uapi/linux/ovpn.h new file mode 100644 index 0000000000000000000000000000000000000000..adf4cafd9624308cc7bf1935bba55fb38bb6ba8c --- /dev/null +++ b/include/uapi/linux/ovpn.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/ovpn.yaml */ +/* YNL-GEN uapi header */ + +#ifndef _UAPI_LINUX_OVPN_H +#define _UAPI_LINUX_OVPN_H + +#define OVPN_FAMILY_NAME "ovpn" +#define OVPN_FAMILY_VERSION 1 + +#define OVPN_NONCE_TAIL_SIZE 8 + +enum ovpn_cipher_alg { + OVPN_CIPHER_ALG_NONE, + OVPN_CIPHER_ALG_AES_GCM, + OVPN_CIPHER_ALG_CHACHA20_POLY1305, +}; + +enum ovpn_del_peer_reason { + OVPN_DEL_PEER_REASON_TEARDOWN, + OVPN_DEL_PEER_REASON_USERSPACE, + OVPN_DEL_PEER_REASON_EXPIRED, + OVPN_DEL_PEER_REASON_TRANSPORT_ERROR, + OVPN_DEL_PEER_REASON_TRANSPORT_DISCONNECT, +}; + +enum ovpn_key_slot { + OVPN_KEY_SLOT_PRIMARY, + OVPN_KEY_SLOT_SECONDARY, +}; + +enum ovpn_mode { + OVPN_MODE_P2P, + OVPN_MODE_MP, +}; + +enum { + OVPN_A_PEER_ID = 1, + OVPN_A_PEER_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_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_IFNAME, + OVPN_A_MODE, + OVPN_A_PEER, + OVPN_A_KEYCONF, + + __OVPN_A_MAX, + OVPN_A_MAX = (__OVPN_A_MAX - 1) +}; + +enum { + OVPN_CMD_DEV_NEW = 1, + OVPN_CMD_DEV_DEL, + OVPN_CMD_PEER_NEW, + OVPN_CMD_PEER_SET, + OVPN_CMD_PEER_GET, + OVPN_CMD_PEER_DEL, + OVPN_CMD_PEER_DEL_NTF, + OVPN_CMD_KEY_NEW, + 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 Wed Oct 2 09:02:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819511 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f45.google.com (mail-wr1-f45.google.com [209.85.221.45]) (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 56BB91990D2 for ; Wed, 2 Oct 2024 09:03:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859806; cv=none; b=SixSsZ69pEVAhIeod3RLeunEnjRLQovb8dPINYeMO2zlGVdiTz0ei3koWPDrEtGmRS60AO9gn6peXRHCiMnSTAtoJipUSq4k6sv97DQJZpRMbffObgQ36DdOd9jLfymv3DseUrSalYBVMTWfxQ5MIjNWCmLdrOwEbC6BDxjdWmk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859806; c=relaxed/simple; bh=DIpuUUDY3GRlEUUrEhbuRpskwknYIwZYdipVwnz546E=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=AKOdYQ+IHD0rGWa1OSROWWCaxp6wRImL5mZeEST3uXOw+9vhzpOPYNA2iZW8vGUV/rRJOirGQcRKgxh2seSEeeqs6XepTJkSSptMpaO5teQB/1C6GOiBpUCPZiL7EyTUb20yhBrX0vUW3HSC6IT5+uzp+T6E3OsXmKVNBK43/uw= 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=anYIKqgD; arc=none smtp.client-ip=209.85.221.45 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="anYIKqgD" Received: by mail-wr1-f45.google.com with SMTP id ffacd0b85a97d-37ce8458ae3so2800991f8f.1 for ; Wed, 02 Oct 2024 02:03:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859803; x=1728464603; 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=3Z8ZpLfimM7LzCfY1ZNyuvgF6DFul/5gZSvdvpf3KJk=; b=anYIKqgDbw3DoeWC9NWZbDeYHZi7fSvRqTiPLm6bPGb3HXqCWVmcKtJ1WSJWkz+ZWL Cw2idP0KV52pnTKnQyZVmEpl5rtdq4rCB7YuX9odxWfMnEHplIpE/3wk5ifEZ29knVxf gjcjXKsRlLM+H2npdI1ojIGZn0PPj+IBJRJ3b0M155TEq9FIVw8WODUfMpvYlFHCx/y2 8A3FI4wQxo2lYWo54Jv3hwYfnmVLPLR78zfaEuKeuUFE6p0bHHzaFnWzWRjhlGsDfzEO iEB+yS9DuSCVu4wYNiw1grUOfY2OLBaz1l/b1DL7UfNC8Y6UeUiMJ9CfNc1cwbSA0S/1 WFYg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859803; x=1728464603; 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=3Z8ZpLfimM7LzCfY1ZNyuvgF6DFul/5gZSvdvpf3KJk=; b=NnUQ68j1TrIqJh4wGSaMnAHZPBFjRw9c2fyncJOjDt8IwYtUYGTyj8JDeRuO0mEwce O6URqDUTwmyss1EVgw5sQ5eaW0DT0v1kaaw6nq3fmVmumgRnPVrE3RmsJI8lxpMqep5Z qzBcRJAmFtWyV66dp9QYdXHOWtqsRMlG/Yr2K8tpZLP1zbvcNbr+Tz3OGAdDBlL1gByE ps0+vkg5PH2JZpj02AcUJ+VFRZZoH4QFb2u38hpyrXBT2YtcLxvp9UysUgBbus2kbCBm lgWvH+31CZ64vKWy2frkbx8uUqx3xDQm7qgUrQIEpw9DZvJnZjMHw3OZnOkc4vr2I36G IyNA== X-Gm-Message-State: AOJu0YzC7AiTm/owqjm9Zos1xVihOa28vW6dQTTfPKueQl23XQl6t3ZL 8TU9uJ/jgHxwbGKEW2KGn/9kgdRiwwM4sHeZcgVfMuQ97VIozxvcQ0NVbM6vRTg= X-Google-Smtp-Source: AGHT+IEOpey/bP6bSL4EQuxf2z/duiQC8NL+7U6+rWg9esJtKpHy98fQnYAapgwc0M/2pCXHTz69Lg== X-Received: by 2002:adf:e712:0:b0:37c:d1c6:7e45 with SMTP id ffacd0b85a97d-37cfba0a614mr1829865f8f.40.1727859802575; Wed, 02 Oct 2024 02:03:22 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:21 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:18 +0200 Subject: [PATCH net-next v8 04/24] ovpn: add basic interface creation/destruction/management routines Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-4-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=8913; i=antonio@openvpn.net; h=from:subject:message-id; bh=DIpuUUDY3GRlEUUrEhbuRpskwknYIwZYdipVwnz546E=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxRPVTa12H/yL2qOmwI1c6KpjsMMs57sm/A6 bIhOxtOIliJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUQAKCRALcOU6oDjV h5zDB/9jFM84v2mA/ZkKXmBfEBTQAadCBkvp/EUdya+6R/lHa59dADFGRrzlEl2Ets74OPIzI+F X93gTaccliJH5SxWQ7Pp9J4qXMh/3NxdwxxyZKXnLeSSkWT2LlyF8sNdai6F/jjSWDz1p6yniV3 IM7iBiYWjLlUqSBd1/DO2/qqHvdEEZsRbep5FfeAL4kz/CzlLh4P0S94ck2s5u4TiXQ6BCKgQcN EDi0O7c5FjbatO7PVMOkjKK06Pbjr2Gpdm/h1bmntxa8HVkmw1dLnH3IbE4SHP7qB3IgfnmcbNh Prm4tfgdAxlELInFZSBPSRi7M5DEhWIrn2CgJITJZGvKTn1o X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org Add basic infrastructure for handling ovpn interfaces. Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/main.c | 155 +++++++++++++++++++++++++++++++++++++++++- drivers/net/ovpn/main.h | 10 +++ drivers/net/ovpn/ovpnstruct.h | 8 +++ drivers/net/ovpn/packet.h | 40 +++++++++++ 4 files changed, 210 insertions(+), 3 deletions(-) diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 0274ddddbcd25080d7f85347f79aad0259771508..12f6258f94c8baef855e2ce90cf70380d5af5ca9 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -10,19 +10,58 @@ #include #include #include +#include #include +#include #include +#include #include #include "ovpnstruct.h" #include "main.h" #include "netlink.h" #include "io.h" +#include "packet.h" /* Driver info */ #define DRV_DESCRIPTION "OpenVPN data channel offload (ovpn)" #define DRV_COPYRIGHT "(C) 2020-2024 OpenVPN, Inc." +/** + * ovpn_struct_init - Initialize the netdevice private area + * @dev: the device to initialize + * @mode: device operation mode (i.e. p2p, mp, ..) + */ +static void ovpn_struct_init(struct net_device *dev, enum ovpn_mode mode) +{ + struct ovpn_struct *ovpn = netdev_priv(dev); + + ovpn->dev = dev; + ovpn->mode = mode; +} + +static void ovpn_struct_free(struct net_device *net) +{ +} + +static int ovpn_net_open(struct net_device *dev) +{ + netif_tx_start_all_queues(dev); + return 0; +} + +static int ovpn_net_stop(struct net_device *dev) +{ + netif_tx_stop_all_queues(dev); + return 0; +} + +static const struct net_device_ops ovpn_netdev_ops = { + .ndo_open = ovpn_net_open, + .ndo_stop = ovpn_net_stop, + .ndo_start_xmit = ovpn_net_xmit, +}; + /** * ovpn_dev_is_valid - check if the netdevice is of type 'ovpn' * @dev: the interface to check @@ -40,30 +79,140 @@ static struct rtnl_link_ops ovpn_link_ops = { .dellink = unregister_netdevice_queue, }; +static void ovpn_setup(struct net_device *dev) +{ + /* compute the overhead considering AEAD encryption */ + const int overhead = sizeof(u32) + NONCE_WIRE_SIZE + 16 + + sizeof(struct udphdr) + + max(sizeof(struct ipv6hdr), sizeof(struct iphdr)); + + netdev_features_t feat = NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_RXCSUM | + NETIF_F_GSO | NETIF_F_GSO_SOFTWARE | + NETIF_F_HIGHDMA; + + dev->needs_free_netdev = true; + + dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS; + + dev->netdev_ops = &ovpn_netdev_ops; + dev->rtnl_link_ops = &ovpn_link_ops; + + dev->priv_destructor = ovpn_struct_free; + + dev->hard_header_len = 0; + dev->addr_len = 0; + dev->mtu = ETH_DATA_LEN - overhead; + dev->min_mtu = IPV4_MIN_MTU; + dev->max_mtu = IP_MAX_MTU - overhead; + + dev->type = ARPHRD_NONE; + dev->flags = IFF_POINTOPOINT | IFF_NOARP; + + dev->lltx = true; + dev->features |= feat; + dev->hw_features |= feat; + dev->hw_enc_features |= feat; + + dev->needed_headroom = OVPN_HEAD_ROOM; + dev->needed_tailroom = OVPN_MAX_PADDING; +} + +/** + * ovpn_iface_create - create and initialize a new 'ovpn' netdevice + * @name: the name of the new device + * @mode: the OpenVPN mode to set this device to + * @net: the netns this device should be created in + * + * A new netdevice is created and registered. + * Its private area is initialized with an empty ovpn_struct object. + * + * Return: a pointer to the new device on success or a negative error code + * otherwise + */ +struct net_device *ovpn_iface_create(const char *name, enum ovpn_mode mode, + struct net *net) +{ + struct net_device *dev; + int ret; + + dev = alloc_netdev(sizeof(struct ovpn_struct), name, NET_NAME_USER, + ovpn_setup); + if (!dev) + return ERR_PTR(-ENOMEM); + + dev_net_set(dev, net); + ovpn_struct_init(dev, mode); + + rtnl_lock(); + ret = register_netdevice(dev); + if (ret < 0) { + netdev_err(dev, "cannot register interface: %d\n", ret); + rtnl_unlock(); + goto err; + } + /* turn carrier explicitly off after registration, this way state is + * clearly defined + */ + netif_carrier_off(dev); + rtnl_unlock(); + + return dev; + +err: + free_netdev(dev); + return ERR_PTR(ret); +} + +/** + * ovpn_iface_destruct - tear down netdevice + * @ovpn: the ovpn instance objected related to the interface to tear down + * + * This function takes care of tearing down an ovpn device and can be invoked + * internally or upon UNREGISTER netdev event + */ +void ovpn_iface_destruct(struct ovpn_struct *ovpn) +{ + ASSERT_RTNL(); + + netif_carrier_off(ovpn->dev); + + ovpn->registered = false; +} + static int ovpn_netdev_notifier_call(struct notifier_block *nb, unsigned long state, void *ptr) { struct net_device *dev = netdev_notifier_info_to_dev(ptr); + struct ovpn_struct *ovpn; if (!ovpn_dev_is_valid(dev)) return NOTIFY_DONE; + ovpn = netdev_priv(dev); + switch (state) { case NETDEV_REGISTER: - /* add device to internal list for later destruction upon - * unregistration - */ + ovpn->registered = true; break; case NETDEV_UNREGISTER: + /* twiddle thumbs on netns device moves */ + if (dev->reg_state != NETREG_UNREGISTERING) + break; + /* can be delivered multiple times, so check registered flag, * then destroy the interface */ + if (!ovpn->registered) + return NOTIFY_DONE; + + ovpn_iface_destruct(ovpn); break; case NETDEV_POST_INIT: case NETDEV_GOING_DOWN: case NETDEV_DOWN: case NETDEV_UP: case NETDEV_PRE_UP: + break; default: return NOTIFY_DONE; } diff --git a/drivers/net/ovpn/main.h b/drivers/net/ovpn/main.h index a3215316c49bfcdf2496590bac878f145b8b27fd..4dfcba9deb590bbf119f51a40dff1517fe227b22 100644 --- a/drivers/net/ovpn/main.h +++ b/drivers/net/ovpn/main.h @@ -10,6 +10,16 @@ #ifndef _NET_OVPN_MAIN_H_ #define _NET_OVPN_MAIN_H_ +struct net_device *ovpn_iface_create(const char *name, enum ovpn_mode mode, + struct net *net); +void ovpn_iface_destruct(struct ovpn_struct *ovpn); bool ovpn_dev_is_valid(const struct net_device *dev); +#define SKB_HEADER_LEN \ + (max(sizeof(struct iphdr), sizeof(struct ipv6hdr)) + \ + sizeof(struct udphdr) + NET_SKB_PAD) + +#define OVPN_HEAD_ROOM ALIGN(16 + SKB_HEADER_LEN, 4) +#define OVPN_MAX_PADDING 16 + #endif /* _NET_OVPN_MAIN_H_ */ diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h index ff248cad1401d00c5bf0aadf9341e40d0a4aee4b..ee05b8a2c61d7c5887e80c7f79e15a1336d99644 100644 --- a/drivers/net/ovpn/ovpnstruct.h +++ b/drivers/net/ovpn/ovpnstruct.h @@ -10,12 +10,20 @@ #ifndef _NET_OVPN_OVPNSTRUCT_H_ #define _NET_OVPN_OVPNSTRUCT_H_ +#include + /** * struct ovpn_struct - per ovpn interface state * @dev: the actual netdev representing the tunnel + * @registered: whether dev is still registered with netdev or not + * @mode: device operation mode (i.e. p2p, mp, ..) + * @dev_list: entry for the module wide device list */ struct ovpn_struct { struct net_device *dev; + bool registered; + enum ovpn_mode mode; + struct list_head dev_list; }; #endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/drivers/net/ovpn/packet.h b/drivers/net/ovpn/packet.h new file mode 100644 index 0000000000000000000000000000000000000000..7ed146f5932a25f448af6da58738a7eae81007fe --- /dev/null +++ b/drivers/net/ovpn/packet.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + * James Yonan + */ + +#ifndef _NET_OVPN_PACKET_H_ +#define _NET_OVPN_PACKET_H_ + +/* When the OpenVPN protocol is ran in AEAD mode, use + * the OpenVPN packet ID as the AEAD nonce: + * + * 00000005 521c3b01 4308c041 + * [seq # ] [ nonce_tail ] + * [ 12-byte full IV ] -> NONCE_SIZE + * [4-bytes -> NONCE_WIRE_SIZE + * on wire] + */ + +/* OpenVPN nonce size */ +#define NONCE_SIZE 12 + +/* OpenVPN nonce size reduced by 8-byte nonce tail -- this is the + * size of the AEAD Associated Data (AD) sent over the wire + * and is normally the head of the IV + */ +#define NONCE_WIRE_SIZE (NONCE_SIZE - sizeof(struct ovpn_nonce_tail)) + +/* Last 8 bytes of AEAD nonce + * Provided by userspace and usually derived from + * key material generated during TLS handshake + */ +struct ovpn_nonce_tail { + u8 u8[OVPN_NONCE_TAIL_SIZE]; +}; + +#endif /* _NET_OVPN_PACKET_H_ */ From patchwork Wed Oct 2 09:02:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819512 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wm1-f49.google.com (mail-wm1-f49.google.com [209.85.128.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2A6EB1991C3 for ; Wed, 2 Oct 2024 09:03:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859807; cv=none; b=LskOgG3kQbsw91vZrqNzhX0khVPvEN0mL5irAUllPpIloM/t5TW9w5/s5Z0aGO7iNya9vdYNHXiBk411xBnAvFq0LQmOcKsmEGTj1S29rtzNBzjPzHVZmlhlcQGRywbhQFvp2NfEh6l4e60pZE2fbrPaJKL5xpM7cezaX0i5XJE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859807; c=relaxed/simple; bh=DM3y7oFYQLCwHm8IBgcaXTmAGiOJkdor1LAmtsasBrQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=bY53Vs8RrKJ9ekIe+EQP933TmfT9MQF3IgkWgbF2o0GvuFKD5RroYSXaB6wB7Z+LLHQNLviSzn6J+o4QtVPOOnVXd5b9FNWcxGoMvJzi8nyLLfeun2C2Dml/jEqzWjp0/XK/psLRZ2Z4ETqfbm9rf13UHICKfDGaGu7QLzqxbvY= 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=JQ01PDxi; arc=none smtp.client-ip=209.85.128.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="JQ01PDxi" Received: by mail-wm1-f49.google.com with SMTP id 5b1f17b1804b1-42cb1e623d1so60260235e9.0 for ; Wed, 02 Oct 2024 02:03:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859803; x=1728464603; 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=6Hmat1snwy24bYlvJyESXk2i6jOGwhPoWPcr27v2+v8=; b=JQ01PDxibAV3680Czi8kml57r8DPeIHaq+Sclt7l/Mm8SNeywFffLPYwxRunuLRxtR LgoYCWgOvVrZ36by179yvOY5bIARq1pz2EH9fmRNlcsZWPnGfGmMZ+Q7DQv/rOE/yfK3 8BK/xQRPyr5WI6yP2W+Lws8CAzLvHxXukNreubkGvFaUNQMFZObfoO5AcQ78qk8rvvVz hrDbUZED5yyptQn8JsQDq8a3r8PwADR1Kcw8PMxK+3Zp0Cy6Ha+5rpwL7YqErbmAiIBx 5DXp0RfP1VAItFZbqrQKYlncTxFGe127nPv3AbG3rA6+vjs9zZWjm5TeAiD0p5+PsJ8F /HJA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859803; x=1728464603; 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=6Hmat1snwy24bYlvJyESXk2i6jOGwhPoWPcr27v2+v8=; b=jnMvuRUnDu3Vqi/z/VoFXuDwQL0OhlYYlZ3RC6TINOOExTgOtbSR4MjacR1h2IIp2S N6JkXeLVtKerSRLOWS55AuUdYKHOaWbmPpRXQAj2rH6C01Bc5adAPGrZJDZoI9jUAQ3M mbS3hLacGMFPBPzsEVBNoZfU8p6PMKPj50GegDetxXBK8bUWZPGLQNl1COF2kKTAExN+ Vrp5/3gBomipB8mRJ99DfeHBBFZETMQW046asxNqgnXSDbq2wj721oDOQ6hLeLTi4Os3 F+Bg3rghObzQV6ZPnLUQgtxg2V5hJv88tN12ySkvBEmrlalVhmLdpUGYBNCQCpR14ZPI /DRQ== X-Gm-Message-State: AOJu0YzuBLFJCB2yQ0uwC82yNhOJWked/ugXQo/bC8PWzvrShJEoRiu3 uUEqh4dAMZ4tq/eQg173v6Rgq54eg0IizyZG9sXYN8ZGeAYxdZosBUnxYNO1nO0= X-Google-Smtp-Source: AGHT+IHGfgHXeajIqz5xpJZ16PbVXjaXdCIAwKIKuj2KQdvb9xIO/VhWAQi5VndxSje2DouimnQQIQ== X-Received: by 2002:a5d:5e05:0:b0:37d:129:e30 with SMTP id ffacd0b85a97d-37d01291038mr487858f8f.2.1727859803560; Wed, 02 Oct 2024 02:03:23 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:23 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:19 +0200 Subject: [PATCH net-next v8 05/24] ovpn: implement interface creation/destruction via netlink Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-5-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=2973; i=antonio@openvpn.net; h=from:subject:message-id; bh=DM3y7oFYQLCwHm8IBgcaXTmAGiOJkdor1LAmtsasBrQ=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxRH1VMATSW6B1xqnv/eu9vR9wYEz8Jjrm5R qpaluPO51KJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUQAKCRALcOU6oDjV h3j3B/0Y6d4sgBenZlLL6ACX9dYwWDNqmoDhw2CTWfpwixqbKb3ytFigN0VahpYrzVHO05Iki3V AqZGg/Jrjt6RlBPNheiP7gZN3WL/2RqhoxjwON6ksiUecEfCt8GiMhia9B2Tv/F6aqhZq/o7PlD YGOVZuuMkwaV3aA/TEv0jNd620jCszrYx9RfiFsFkYplvFMlnljq0G+/IaFYLt4PHmBltIFOvbI 0vrtHoy30/DwcxCqWh6qmn5C/QFPwh2i9Msg1Did1xSFqhRx2/qFtmam9gFqL2n1a5sjR/0bZPs mQVIwP5ag4DexKaIrzqsGuty8bJdhx4Rr6mV0cav2WCO39CE X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org Allow userspace to create and destroy an interface using netlink commands. Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/main.h | 2 ++ drivers/net/ovpn/netlink.c | 59 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/drivers/net/ovpn/main.h b/drivers/net/ovpn/main.h index 4dfcba9deb590bbf119f51a40dff1517fe227b22..c664d9c655734263fcf58dd8f2fa5446565a29cf 100644 --- a/drivers/net/ovpn/main.h +++ b/drivers/net/ovpn/main.h @@ -10,6 +10,8 @@ #ifndef _NET_OVPN_MAIN_H_ #define _NET_OVPN_MAIN_H_ +#define OVPN_DEFAULT_IFNAME "ovpn%d" + struct net_device *ovpn_iface_create(const char *name, enum ovpn_mode mode, struct net *net); void ovpn_iface_destruct(struct ovpn_struct *ovpn); diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 7b6b4d03b845eeb8654e37ac3495e8172ac3f291..6e60591d605dde19c6bbd47ef0e90e522776688c 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -7,6 +7,7 @@ */ #include +#include #include #include @@ -84,12 +85,66 @@ void ovpn_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, int ovpn_nl_dev_new_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + const char *ifname = OVPN_DEFAULT_IFNAME; + enum ovpn_mode mode = OVPN_MODE_P2P; + struct net_device *dev; + struct sk_buff *msg; + void *hdr; + + if (info->attrs[OVPN_A_IFNAME]) + ifname = nla_data(info->attrs[OVPN_A_IFNAME]); + + if (info->attrs[OVPN_A_MODE]) { + mode = nla_get_u32(info->attrs[OVPN_A_MODE]); + pr_debug("ovpn: setting device (%s) mode: %u\n", ifname, mode); + } + + dev = ovpn_iface_create(ifname, mode, genl_info_net(info)); + if (IS_ERR(dev)) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "error while creating interface: %ld", + PTR_ERR(dev)); + return PTR_ERR(dev); + } + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_iput(msg, info); + if (!hdr) { + nlmsg_free(msg); + return -ENOBUFS; + } + + if (nla_put_string(msg, OVPN_A_IFNAME, dev->name)) { + genlmsg_cancel(msg, hdr); + nlmsg_free(msg); + return -EMSGSIZE; + } + + if (nla_put_u32(msg, OVPN_A_IFINDEX, dev->ifindex)) { + genlmsg_cancel(msg, hdr); + nlmsg_free(msg); + return -EMSGSIZE; + } + + genlmsg_end(msg, hdr); + + return genlmsg_reply(msg, info); } int ovpn_nl_dev_del_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct ovpn_struct *ovpn = info->user_ptr[0]; + + rtnl_lock(); + ovpn_iface_destruct(ovpn); + unregister_netdevice(ovpn->dev); + netdev_put(ovpn->dev, NULL); + rtnl_unlock(); + + return 0; } int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) From patchwork Wed Oct 2 09:02:20 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819513 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wm1-f45.google.com (mail-wm1-f45.google.com [209.85.128.45]) (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 592FF195FEF for ; Wed, 2 Oct 2024 09:03:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859808; cv=none; b=kXPHY3nOllcsCW0UKh8Wowj/Bg/lPkfJTABh7YMsZYMDWeAPjHOHxnYtqY3EFIO6mIdh6RnNhybuZer/l1Ic7efHnidL+Cs8NCnWXxfjVp5wZAc89zD97VhgqBacV6FgkG9zqPdu2RPuZleIuUJtg4nQCubKtEhNCa4vIbIQ+dE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859808; c=relaxed/simple; bh=x9G+LQPmQGZYYXuIUN42CrVqwqLTedzZV5YjkHc4IrQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=dJH/p1kEJ+TnnUP6uDqboYNuC0Dyx+1R0dtCYWa2EzpxkuZe2iJviXbzGPdHdNJGECsd49I7oI4XNXSgVcNFB6sfSWPBJTsds1/VCiNFodn3gEdleYT/H7vAy4TKQcr1ZzV/rMFX2DxJAuZM3xcKDE80Jm8viwIYKk7XbU3gmfE= 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=eErUjJbj; arc=none smtp.client-ip=209.85.128.45 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="eErUjJbj" Received: by mail-wm1-f45.google.com with SMTP id 5b1f17b1804b1-42cc8782869so60075835e9.2 for ; Wed, 02 Oct 2024 02:03:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859804; x=1728464604; 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=Tg3GhQ9M4IeLDst9adgxAt09NjdE9INBvd09onQRqV4=; b=eErUjJbjta7riz5ixicsgnAZfLz+rk/7MaA8BOpR+o0C+EPmwD737ADqS5M/iLMZpR W8GN/Lpyv1YJySBY1YzJ8IM/9aTt2/sbZKoLeqG85Rz7/r64/4JpjP/gnsdEJZUSYs23 bfbN4xl2tqcLch1TpshA02qdDNCUMvBoCMBIOBWz4B/2PFzIrvsr2kEvnGL2zcTSxJ3e JGF8IRqdAIeMvlZNBHWgubisFw6on04VaUY1yW8eOWEp6VcZvQbxUAtCSaeXq55gLnbF 7oY/I+JZ5+xYSzBmQRG0eIJEmtTIgixoopVAp+0a1CPUYhjfmXbUdBcaTPVS136F6ykE j5jw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859804; x=1728464604; 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=Tg3GhQ9M4IeLDst9adgxAt09NjdE9INBvd09onQRqV4=; b=Oxs3u6NagQzINER6CMJLb7sFobawRYOGssQtutbMJFXDgVzuNW7iaY5LKwBMGeN+iU MNJktDRq+Tq9WLevf/Cv0yTkWpG+UYwXziCJyp0oQEnbFQvtxPjQhHtDhKi3x5xlHPrI Bd6ClxSVSMqcnwVZzdeSDULfuPGgYF23sBlzKt74ak8fX+635UsqtVpBgOsxFZ/h365h exFJT/jxw9/nK9d7Fwus9CDfjdpXwzwsiXoCeflFF+ui35dpmRfExO0dc45dY+lwWLOX xaYtV9M7I2zu9jrvaqsYEKVIuABl/g7qXVV5q31s8vgVFYiJkpfrGs9gWsOztqZQxdBY AIAw== X-Gm-Message-State: AOJu0YwyZUMCMdoT1xwOvaCprWevtA4uTV01X8lVU1LenZsd/27mpqzz BfbXtPUdLcD8XTOlDCaeQP05IMeYSs//Q3iSy4OdrNaQbPtW7T2jADQ0Zg/DvYI= X-Google-Smtp-Source: AGHT+IFtihufaLNlEccDvXEC3MOXZ1Lwe8AxJAaJJwULIjchFGU9U7BHFhbJDDnaREOsi/jt4YJLdQ== X-Received: by 2002:adf:e3ca:0:b0:37c:cd1d:b87e with SMTP id ffacd0b85a97d-37cfb8cb7a9mr1350404f8f.18.1727859804558; Wed, 02 Oct 2024 02:03:24 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:24 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:20 +0200 Subject: [PATCH net-next v8 06/24] ovpn: keep carrier always on Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-6-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=1171; i=antonio@openvpn.net; h=from:subject:message-id; bh=x9G+LQPmQGZYYXuIUN42CrVqwqLTedzZV5YjkHc4IrQ=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxR+jXN6hJpn0LHlII6NutC7Jtdt4tAgr0H2 5iFVODP4l+JATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUQAKCRALcOU6oDjV h8XLB/9junbiwI77zqocVNhkjjsd1RqrDdh7my9KGyD4qH1RzyTfuLoDMnLqALcQukgpj14FJvr PuoUNIePjF2BwzrOTAM8NR4iy11E0Us5lHFR75nNiL2GpqckrmU5+FJ/YiFD69qt1YSIMnybd// 4crN2QQ/ZChDDOFc+d/PgUy3+ukF4NhN8StmPGFk6578xHFP/Xr4jRLAqPJ+y4Nq7Kscc9P/t8c d6SXS4UB4QIk9BDKyZ/1h3bEBYQ+cei+BQhEK3z+F6Ju+xX47LZYIKN91wJuJ1KoqBB98B/HHdm +ScyNFIh3GLaAnSytWgFGWpVqQHiAr3XMZpH/RZEJFNfSFvX X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org An ovpn interface will keep carrier always on and let the user decide when an interface should be considered disconnected. This way, even if an ovpn interface is not connected to any peer, it can still retain all IPs and routes and thus prevent any data leak. Signed-off-by: Antonio Quartulli Reviewed-by: Andrew Lunn --- drivers/net/ovpn/main.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 12f6258f94c8baef855e2ce90cf70380d5af5ca9..87d49b83107aa9c276c5b200ac919965379c99be 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -46,6 +46,13 @@ static void ovpn_struct_free(struct net_device *net) static int ovpn_net_open(struct net_device *dev) { + /* ovpn keeps the carrier always on to avoid losing IP or route + * configuration upon disconnection. This way it can prevent leaks + * of traffic outside of the VPN tunnel. + * The user may override this behaviour by tearing down the interface + * manually. + */ + netif_carrier_on(dev); netif_tx_start_all_queues(dev); return 0; } From patchwork Wed Oct 2 09:02:21 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819514 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f51.google.com (mail-wr1-f51.google.com [209.85.221.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 626C6199E94 for ; Wed, 2 Oct 2024 09:03:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859810; cv=none; b=TP40CuZV/OuuiWJAIIpa4IXKIbg+F9VuInkoVsHb4CC7VqZtOhO3/V84Texqg701F9cFKTWsmykfu7HBK77ewhxjl5oxKdKG4fi7PbKu0CvDTAmYhJel05TCVq639SN/dxdNgrciKXKLw4rPT8YPhI5+dSI2P3QnonkivuFXNow= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859810; c=relaxed/simple; bh=Gu3ELoUvxUytdBW/KmHoQAlc2H7Ar7o+VA1yPiLFG88=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=KspD9Vrk3L16liAx3Q6/h9Tf9AKnGgV3BEB8hPKJ+IK6oD3NVwsu7qzzWAdVLSlfY0jU/uV+LE5iDqlb0j+WlL3t52WT5/NtNbGqUX1kOStph2EsPdHLdk12gTo8OtGGCC0HeZ2l+bMzGUkUChHyt8rJaZ+Hk5rMRmNwhy2jCiw= 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=S1wD9WtV; arc=none smtp.client-ip=209.85.221.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="S1wD9WtV" Received: by mail-wr1-f51.google.com with SMTP id ffacd0b85a97d-37cd3419937so3312681f8f.0 for ; Wed, 02 Oct 2024 02:03:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859806; x=1728464606; 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=8+nXpvfUj++PdGKmuzvTa3CLwJjHC/iystct+ibzPbk=; b=S1wD9WtV6MI+9cy5VDIqNxBmUVrMFX5hw8yCFu+sPYv5lovAU5uxpbtdFLae0IdUPj qrBDrYcRZRKIxMmdRQyIABGEeFsL7upKX7YiHDWUCsF7rhc4DqgwsPjIDt7zJlYOVzOZ b4wAbQcpNOvdJUtk3b3mR4iu4lhwG/qreze2/iV3pJkMXfaOB0zeWwV2V9iNg/CPAM4B Iv6K70EX7sCFbNHK110o+qxHr7PbOZaduGkzn6Wioje2Ph30K8WyUPES8npGgQ8dl1gO X3+cKjodoB4CI0zuU6JItk7lMY+OZ8Iwc1vQtlgc7GNe9l26JsIQgyZevbxFDtRLTifB mfrw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859806; x=1728464606; 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=8+nXpvfUj++PdGKmuzvTa3CLwJjHC/iystct+ibzPbk=; b=aZoDM9lr6HdY3AfgfiLMPTDzM/nXwL9eeordGSv6Al6hGRVkUTdQRjczP9WDrja4yl 0iep/h8Ngwj+J8ZiXDgnsU3Un3VKWPFcz3ZnFvvOqtO93HX9mtq8WqtbDFYywjSd9VBr jlKz3XFUpievPLgJuABOoes/8rT9UBJrSC1/YDG1894jy42MbmwXS64rkAAB67qKiWTx PRN+TGZvBhaKJK8y/J2V0uVe8nuSIEQMIRD1zq/acwRmEC1ABPUpytAQFaBHiuMDZvFV Byh3ptHSlnrKhoRKXfcHWDlEd1hlp31gAUH67AD4yjVKeUPRSv0+abjJ0qEciANXCPS9 FaVA== X-Gm-Message-State: AOJu0YzgcyfinEL2+H2nDArgPu3672tUS/TMbjSEp14qgU7cgCZhehl5 hlfjYPtmINxHMDxOodIOmMd83pa7XwqAATRRgnTQPt8zlnQfWPT8RFx+Bf0mebo= X-Google-Smtp-Source: AGHT+IGTpfhbWKt7SH/mhNmefN2HhTMqdsgEIms5bMzmhC50E55QSolfMNeQcN5z4nACPtnDdCubpg== X-Received: by 2002:a05:6000:4616:b0:37c:d21a:3d61 with SMTP id ffacd0b85a97d-37cfba0a3e4mr1964248f8f.39.1727859805628; Wed, 02 Oct 2024 02:03:25 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:25 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:21 +0200 Subject: [PATCH net-next v8 07/24] ovpn: introduce the ovpn_peer object Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-7-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=21963; i=antonio@openvpn.net; h=from:subject:message-id; bh=Gu3ELoUvxUytdBW/KmHoQAlc2H7Ar7o+VA1yPiLFG88=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxRDOqDunpfA4ZzT4hBYMCMS2X8QMJAlz/Fk cXMs/fkaeiJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUQAKCRALcOU6oDjV h6s4B/9H7DapensA4wN7ABsXdt6YWwf+MPMZhGHivui3FWXWKWdY/iU35XcIyw5jEgh+CHQ7qZu Lqf1+Bbqi4Lo2tqiGRgHH58n0z1NsLYNMqIjXy47Lox3zwIVr2eLYTMPNvGDVO3kbCh1BZ3tNWv vmfqTUsJ4uc+FHH7OW4zbFqLPM5uMVpcnjUwGeHD7L66Zh+qdTPHjg8o6YXA8sTLaMh2XMGTrlr RFaSDD4cDxF+pHLgikwqCgjan8HoSzllT9ld2RdE80RDaQSKnGsuLNa0ROG5k97pb5ia0tN6IYg M5tbq5FkBiLio1HHDytKKZz33f5KK1WvBTt2WtcX1he2WFTf X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org An ovpn_peer object holds the whole status of a remote peer (regardless whether it is a server or a client). This includes status for crypto, tx/rx buffers, napi, etc. Only support for one peer is introduced (P2P mode). Multi peer support is introduced with a later patch. Along with the ovpn_peer, also the ovpn_bind object is introcued as the two are strictly related. An ovpn_bind object wraps a sockaddr representing the local coordinates being used to talk to a specific peer. Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/Makefile | 2 + drivers/net/ovpn/bind.c | 58 +++++++ drivers/net/ovpn/bind.h | 117 ++++++++++++++ drivers/net/ovpn/main.c | 12 ++ drivers/net/ovpn/main.h | 2 + drivers/net/ovpn/ovpnstruct.h | 7 + drivers/net/ovpn/peer.c | 355 ++++++++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/peer.h | 82 ++++++++++ 8 files changed, 635 insertions(+) diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 201dc001419f1d99ae95c0ee0f96e68f8a4eac16..ce13499b3e1775a7f2a9ce16c6cb0aa088f93685 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -7,7 +7,9 @@ # Author: Antonio Quartulli obj-$(CONFIG_OVPN) := ovpn.o +ovpn-y += bind.o ovpn-y += main.o ovpn-y += io.o ovpn-y += netlink.o ovpn-y += netlink-gen.o +ovpn-y += peer.o diff --git a/drivers/net/ovpn/bind.c b/drivers/net/ovpn/bind.c new file mode 100644 index 0000000000000000000000000000000000000000..b4d2ccec2ceddf43bc445b489cc62a578ef0ad0a --- /dev/null +++ b/drivers/net/ovpn/bind.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2012-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include + +#include "ovpnstruct.h" +#include "bind.h" +#include "peer.h" + +/** + * ovpn_bind_from_sockaddr - retrieve binding matching sockaddr + * @ss: the sockaddr to match + * + * Return: the bind matching the passed sockaddr if found, NULL otherwise + */ +struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *ss) +{ + struct ovpn_bind *bind; + size_t sa_len; + + if (ss->ss_family == AF_INET) + sa_len = sizeof(struct sockaddr_in); + else if (ss->ss_family == AF_INET6) + sa_len = sizeof(struct sockaddr_in6); + else + return ERR_PTR(-EAFNOSUPPORT); + + bind = kzalloc(sizeof(*bind), GFP_ATOMIC); + if (unlikely(!bind)) + return ERR_PTR(-ENOMEM); + + memcpy(&bind->remote, ss, sa_len); + + return bind; +} + +/** + * ovpn_bind_reset - assign new binding to peer + * @peer: the peer whose binding has to be replaced + * @new: the new bind to assign + */ +void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *new) +{ + struct ovpn_bind *old; + + spin_lock_bh(&peer->lock); + old = rcu_replace_pointer(peer->bind, new, true); + spin_unlock_bh(&peer->lock); + + kfree_rcu(old, rcu); +} diff --git a/drivers/net/ovpn/bind.h b/drivers/net/ovpn/bind.h new file mode 100644 index 0000000000000000000000000000000000000000..859213d5040deb36c416eafcf5c6ab31c4d52c7a --- /dev/null +++ b/drivers/net/ovpn/bind.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2012-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNBIND_H_ +#define _NET_OVPN_OVPNBIND_H_ + +#include +#include +#include +#include +#include +#include + +struct ovpn_peer; + +/** + * union ovpn_sockaddr - basic transport layer address + * @in4: IPv4 address + * @in6: IPv6 address + */ +union ovpn_sockaddr { + struct sockaddr_in in4; + struct sockaddr_in6 in6; +}; + +/** + * struct ovpn_bind - remote peer binding + * @remote: the remote peer sockaddress + * @local: local endpoint used to talk to the peer + * @local.ipv4: local IPv4 used to talk to the peer + * @local.ipv6: local IPv6 used to talk to the peer + * @rcu: used to schedule RCU cleanup job + */ +struct ovpn_bind { + union ovpn_sockaddr remote; /* remote sockaddr */ + + union { + struct in_addr ipv4; + struct in6_addr ipv6; + } local; + + struct rcu_head rcu; +}; + +/** + * skb_protocol_to_family - translate skb->protocol to AF_INET or AF_INET6 + * @skb: the packet sk_buff to inspect + * + * Return: AF_INET, AF_INET6 or 0 in case of unknown protocol + */ +static inline unsigned short skb_protocol_to_family(const struct sk_buff *skb) +{ + switch (skb->protocol) { + case htons(ETH_P_IP): + return AF_INET; + case htons(ETH_P_IPV6): + return AF_INET6; + default: + return 0; + } +} + +/** + * ovpn_bind_skb_src_match - match packet source with binding + * @bind: the binding to match + * @skb: the packet to match + * + * Return: true if the packet source matches the remote peer sockaddr + * in the binding + */ +static inline bool ovpn_bind_skb_src_match(const struct ovpn_bind *bind, + const struct sk_buff *skb) +{ + const unsigned short family = skb_protocol_to_family(skb); + const union ovpn_sockaddr *remote; + + if (unlikely(!bind)) + return false; + + remote = &bind->remote; + + if (unlikely(remote->in4.sin_family != family)) + return false; + + switch (family) { + case AF_INET: + if (unlikely(remote->in4.sin_addr.s_addr != ip_hdr(skb)->saddr)) + return false; + + if (unlikely(remote->in4.sin_port != udp_hdr(skb)->source)) + return false; + break; + case AF_INET6: + if (unlikely(!ipv6_addr_equal(&remote->in6.sin6_addr, + &ipv6_hdr(skb)->saddr))) + return false; + + if (unlikely(remote->in6.sin6_port != udp_hdr(skb)->source)) + return false; + break; + default: + return false; + } + + return true; +} + +struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *sa); +void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *bind); + +#endif /* _NET_OVPN_OVPNBIND_H_ */ diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 87d49b83107aa9c276c5b200ac919965379c99be..4522f2f8978614c96d529f807bd2cf32a44f8216 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -11,6 +11,7 @@ #include #include #include +//#include #include #include #include @@ -22,6 +23,7 @@ #include "netlink.h" #include "io.h" #include "packet.h" +#include "peer.h" /* Driver info */ #define DRV_DESCRIPTION "OpenVPN data channel offload (ovpn)" @@ -38,12 +40,18 @@ static void ovpn_struct_init(struct net_device *dev, enum ovpn_mode mode) ovpn->dev = dev; ovpn->mode = mode; + spin_lock_init(&ovpn->lock); } static void ovpn_struct_free(struct net_device *net) { } +static int ovpn_net_init(struct net_device *dev) +{ + return 0; +} + static int ovpn_net_open(struct net_device *dev) { /* ovpn keeps the carrier always on to avoid losing IP or route @@ -64,6 +72,7 @@ static int ovpn_net_stop(struct net_device *dev) } static const struct net_device_ops ovpn_netdev_ops = { + .ndo_init = ovpn_net_init, .ndo_open = ovpn_net_open, .ndo_stop = ovpn_net_stop, .ndo_start_xmit = ovpn_net_xmit, @@ -184,6 +193,9 @@ void ovpn_iface_destruct(struct ovpn_struct *ovpn) netif_carrier_off(ovpn->dev); ovpn->registered = false; + + if (ovpn->mode == OVPN_MODE_P2P) + ovpn_peer_release_p2p(ovpn); } static int ovpn_netdev_notifier_call(struct notifier_block *nb, diff --git a/drivers/net/ovpn/main.h b/drivers/net/ovpn/main.h index c664d9c655734263fcf58dd8f2fa5446565a29cf..7e957248af6ab44ad8555318c74cc94be99844ab 100644 --- a/drivers/net/ovpn/main.h +++ b/drivers/net/ovpn/main.h @@ -24,4 +24,6 @@ bool ovpn_dev_is_valid(const struct net_device *dev); #define OVPN_HEAD_ROOM ALIGN(16 + SKB_HEADER_LEN, 4) #define OVPN_MAX_PADDING 16 +#define OVPN_QUEUE_LEN 1024 + #endif /* _NET_OVPN_MAIN_H_ */ diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h index ee05b8a2c61d7c5887e80c7f79e15a1336d99644..25f4837b798b1a2c9b11763dfb5b44e8ec337e6f 100644 --- a/drivers/net/ovpn/ovpnstruct.h +++ b/drivers/net/ovpn/ovpnstruct.h @@ -10,19 +10,26 @@ #ifndef _NET_OVPN_OVPNSTRUCT_H_ #define _NET_OVPN_OVPNSTRUCT_H_ +#include #include /** * struct ovpn_struct - per ovpn interface state * @dev: the actual netdev representing the tunnel + * @dev_tracker: reference tracker for associated dev * @registered: whether dev is still registered with netdev or not * @mode: device operation mode (i.e. p2p, mp, ..) + * @lock: protect this object + * @peer: in P2P mode, this is the only remote peer * @dev_list: entry for the module wide device list */ struct ovpn_struct { struct net_device *dev; + netdevice_tracker dev_tracker; bool registered; enum ovpn_mode mode; + spinlock_t lock; /* protect writing to the ovpn_struct object */ + struct ovpn_peer __rcu *peer; struct list_head dev_list; }; diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c new file mode 100644 index 0000000000000000000000000000000000000000..23ded79eb7748a10b683d9aaa351b361ae79847e --- /dev/null +++ b/drivers/net/ovpn/peer.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include +#include + +#include "ovpnstruct.h" +#include "bind.h" +#include "io.h" +#include "main.h" +#include "netlink.h" +#include "peer.h" + +/** + * ovpn_peer_new - allocate and initialize a new peer object + * @ovpn: the openvpn instance inside which the peer should be created + * @id: the ID assigned to this peer + * + * Return: a pointer to the new peer on success or an error code otherwise + */ +struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id) +{ + struct ovpn_peer *peer; + int ret; + + /* alloc and init peer object */ + peer = kzalloc(sizeof(*peer), GFP_KERNEL); + if (!peer) + return ERR_PTR(-ENOMEM); + + peer->id = id; + peer->halt = false; + peer->ovpn = ovpn; + + peer->vpn_addrs.ipv4.s_addr = htonl(INADDR_ANY); + peer->vpn_addrs.ipv6 = in6addr_any; + + RCU_INIT_POINTER(peer->bind, NULL); + spin_lock_init(&peer->lock); + kref_init(&peer->refcount); + + ret = dst_cache_init(&peer->dst_cache, GFP_KERNEL); + if (ret < 0) { + netdev_err(ovpn->dev, "%s: cannot initialize dst cache\n", + __func__); + kfree(peer); + return ERR_PTR(ret); + } + + netdev_hold(ovpn->dev, &ovpn->dev_tracker, GFP_KERNEL); + + return peer; +} + +/** + * ovpn_peer_release - release peer private members + * @peer: the peer to release + */ +static void ovpn_peer_release(struct ovpn_peer *peer) +{ + ovpn_bind_reset(peer, NULL); + + dst_cache_destroy(&peer->dst_cache); +} + +/** + * ovpn_peer_release_kref - callback for kref_put + * @kref: the kref object belonging to the peer + */ +void ovpn_peer_release_kref(struct kref *kref) +{ + struct ovpn_peer *peer = container_of(kref, struct ovpn_peer, refcount); + + ovpn_peer_release(peer); + netdev_put(peer->ovpn->dev, &peer->ovpn->dev_tracker); + kfree_rcu(peer, rcu); +} + +/** + * ovpn_peer_skb_to_sockaddr - fill sockaddr with skb source address + * @skb: the packet to extract data from + * @ss: the sockaddr to fill + * + * Return: true on success or false otherwise + */ +static bool ovpn_peer_skb_to_sockaddr(struct sk_buff *skb, + struct sockaddr_storage *ss) +{ + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; + + ss->ss_family = skb_protocol_to_family(skb); + switch (ss->ss_family) { + case AF_INET: + sa4 = (struct sockaddr_in *)ss; + sa4->sin_family = AF_INET; + sa4->sin_addr.s_addr = ip_hdr(skb)->saddr; + sa4->sin_port = udp_hdr(skb)->source; + break; + case AF_INET6: + sa6 = (struct sockaddr_in6 *)ss; + sa6->sin6_family = AF_INET6; + sa6->sin6_addr = ipv6_hdr(skb)->saddr; + sa6->sin6_port = udp_hdr(skb)->source; + break; + default: + return false; + } + + return true; +} + +/** + * ovpn_peer_transp_match - check if sockaddr and peer binding match + * @peer: the peer to get the binding from + * @ss: the sockaddr to match + * + * Return: true if sockaddr and binding match or false otherwise + */ +static bool ovpn_peer_transp_match(const struct ovpn_peer *peer, + const struct sockaddr_storage *ss) +{ + struct ovpn_bind *bind = rcu_dereference(peer->bind); + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; + + if (unlikely(!bind)) + return false; + + if (ss->ss_family != bind->remote.in4.sin_family) + return false; + + switch (ss->ss_family) { + case AF_INET: + sa4 = (struct sockaddr_in *)ss; + if (sa4->sin_addr.s_addr != bind->remote.in4.sin_addr.s_addr) + return false; + if (sa4->sin_port != bind->remote.in4.sin_port) + return false; + break; + case AF_INET6: + sa6 = (struct sockaddr_in6 *)ss; + if (!ipv6_addr_equal(&sa6->sin6_addr, + &bind->remote.in6.sin6_addr)) + return false; + if (sa6->sin6_port != bind->remote.in6.sin6_port) + return false; + break; + default: + return false; + } + + return true; +} + +/** + * ovpn_peer_get_by_transp_addr_p2p - get peer by transport address in a P2P + * instance + * @ovpn: the openvpn instance to search + * @ss: the transport socket address + * + * Return: the peer if found or NULL otherwise + */ +static struct ovpn_peer * +ovpn_peer_get_by_transp_addr_p2p(struct ovpn_struct *ovpn, + struct sockaddr_storage *ss) +{ + struct ovpn_peer *tmp, *peer = NULL; + + rcu_read_lock(); + tmp = rcu_dereference(ovpn->peer); + if (likely(tmp && ovpn_peer_transp_match(tmp, ss) && + ovpn_peer_hold(tmp))) + peer = tmp; + rcu_read_unlock(); + + return peer; +} + +/** + * ovpn_peer_get_by_transp_addr - retrieve peer by transport address + * @ovpn: the openvpn instance to search + * @skb: the skb to retrieve the source transport address from + * + * Return: a pointer to the peer if found or NULL otherwise + */ +struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn, + struct sk_buff *skb) +{ + struct ovpn_peer *peer = NULL; + struct sockaddr_storage ss = { 0 }; + + if (unlikely(!ovpn_peer_skb_to_sockaddr(skb, &ss))) + return NULL; + + if (ovpn->mode == OVPN_MODE_P2P) + peer = ovpn_peer_get_by_transp_addr_p2p(ovpn, &ss); + + return peer; +} + +/** + * ovpn_peer_get_by_id_p2p - get peer by ID in a P2P instance + * @ovpn: the openvpn instance to search + * @peer_id: the ID of the peer to find + * + * Return: the peer if found or NULL otherwise + */ +static struct ovpn_peer *ovpn_peer_get_by_id_p2p(struct ovpn_struct *ovpn, + u32 peer_id) +{ + struct ovpn_peer *tmp, *peer = NULL; + + rcu_read_lock(); + tmp = rcu_dereference(ovpn->peer); + if (likely(tmp && tmp->id == peer_id && ovpn_peer_hold(tmp))) + peer = tmp; + rcu_read_unlock(); + + return peer; +} + +/** + * ovpn_peer_get_by_id - retrieve peer by ID + * @ovpn: the openvpn instance to search + * @peer_id: the unique peer identifier to match + * + * Return: a pointer to the peer if found or NULL otherwise + */ +struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id) +{ + struct ovpn_peer *peer = NULL; + + if (ovpn->mode == OVPN_MODE_P2P) + peer = ovpn_peer_get_by_id_p2p(ovpn, peer_id); + + return peer; +} + +/** + * ovpn_peer_add_p2p - add peer to related tables in a P2P instance + * @ovpn: the instance to add the peer to + * @peer: the peer to add + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_peer_add_p2p(struct ovpn_struct *ovpn, struct ovpn_peer *peer) +{ + struct ovpn_peer *tmp; + + spin_lock_bh(&ovpn->lock); + /* in p2p mode it is possible to have a single peer only, therefore the + * old one is released and substituted by the new one + */ + tmp = rcu_dereference_protected(ovpn->peer, + lockdep_is_held(&ovpn->lock)); + if (tmp) { + tmp->delete_reason = OVPN_DEL_PEER_REASON_TEARDOWN; + ovpn_peer_put(tmp); + } + + rcu_assign_pointer(ovpn->peer, peer); + spin_unlock_bh(&ovpn->lock); + + return 0; +} + +/** + * ovpn_peer_add - add peer to the related tables + * @ovpn: the openvpn instance the peer belongs to + * @peer: the peer object to add + * + * Assume refcounter was increased by caller + * + * Return: 0 on success or a negative error code otherwise + */ +int ovpn_peer_add(struct ovpn_struct *ovpn, struct ovpn_peer *peer) +{ + switch (ovpn->mode) { + case OVPN_MODE_P2P: + return ovpn_peer_add_p2p(ovpn, peer); + default: + return -EOPNOTSUPP; + } +} + +/** + * ovpn_peer_del_p2p - delete peer from related tables in a P2P instance + * @peer: the peer to delete + * @reason: reason why the peer was deleted (sent to userspace) + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_peer_del_p2p(struct ovpn_peer *peer, + enum ovpn_del_peer_reason reason) + __must_hold(&peer->ovpn->lock) +{ + struct ovpn_peer *tmp; + + tmp = rcu_dereference_protected(peer->ovpn->peer, + lockdep_is_held(&peer->ovpn->lock)); + if (tmp != peer) { + DEBUG_NET_WARN_ON_ONCE(1); + if (tmp) + ovpn_peer_put(tmp); + + return -ENOENT; + } + + tmp->delete_reason = reason; + RCU_INIT_POINTER(peer->ovpn->peer, NULL); + ovpn_peer_put(tmp); + + return 0; +} + +/** + * ovpn_peer_release_p2p - release peer upon P2P device teardown + * @ovpn: the instance being torn down + */ +void ovpn_peer_release_p2p(struct ovpn_struct *ovpn) +{ + struct ovpn_peer *tmp; + + spin_lock_bh(&ovpn->lock); + tmp = rcu_dereference_protected(ovpn->peer, + lockdep_is_held(&ovpn->lock)); + if (tmp) + ovpn_peer_del_p2p(tmp, OVPN_DEL_PEER_REASON_TEARDOWN); + spin_unlock_bh(&ovpn->lock); +} + +/** + * ovpn_peer_del - delete peer from related tables + * @peer: the peer object to delete + * @reason: reason for deleting peer (will be sent to userspace) + * + * Return: 0 on success or a negative error code otherwise + */ +int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason) +{ + switch (peer->ovpn->mode) { + case OVPN_MODE_P2P: + return ovpn_peer_del_p2p(peer, reason); + default: + return -EOPNOTSUPP; + } +} diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h new file mode 100644 index 0000000000000000000000000000000000000000..6c51959363c78f22e5bbeecbfe65dc90c67e86cc --- /dev/null +++ b/drivers/net/ovpn/peer.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNPEER_H_ +#define _NET_OVPN_OVPNPEER_H_ + +#include "bind.h" + +#include +#include + +/** + * struct ovpn_peer - the main remote peer object + * @ovpn: main openvpn instance this peer belongs to + * @id: unique identifier + * @vpn_addrs: IP addresses assigned over the tunnel + * @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel + * @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel + * @dst_cache: cache for dst_entry used to send to peer + * @bind: remote peer binding + * @halt: true if ovpn_peer_mark_delete was called + * @delete_reason: why peer was deleted (i.e. timeout, transport error, ..) + * @lock: protects binding to peer (bind) + * @refcount: reference counter + * @rcu: used to free peer in an RCU safe way + * @delete_work: deferred cleanup work, used to notify userspace + */ +struct ovpn_peer { + struct ovpn_struct *ovpn; + u32 id; + struct { + struct in_addr ipv4; + struct in6_addr ipv6; + } vpn_addrs; + struct dst_cache dst_cache; + struct ovpn_bind __rcu *bind; + bool halt; + enum ovpn_del_peer_reason delete_reason; + spinlock_t lock; /* protects bind */ + struct kref refcount; + struct rcu_head rcu; + struct work_struct delete_work; +}; + +/** + * ovpn_peer_hold - increase reference counter + * @peer: the peer whose counter should be increased + * + * Return: true if the counter was increased or false if it was zero already + */ +static inline bool ovpn_peer_hold(struct ovpn_peer *peer) +{ + return kref_get_unless_zero(&peer->refcount); +} + +void ovpn_peer_release_kref(struct kref *kref); + +/** + * ovpn_peer_put - decrease reference counter + * @peer: the peer whose counter should be decreased + */ +static inline void ovpn_peer_put(struct ovpn_peer *peer) +{ + kref_put(&peer->refcount, ovpn_peer_release_kref); +} + +struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id); +int ovpn_peer_add(struct ovpn_struct *ovpn, struct ovpn_peer *peer); +int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason); +void ovpn_peer_release_p2p(struct ovpn_struct *ovpn); + +struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn, + struct sk_buff *skb); +struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id); + +#endif /* _NET_OVPN_OVPNPEER_H_ */ From patchwork Wed Oct 2 09:02:22 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819515 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f52.google.com (mail-wr1-f52.google.com [209.85.221.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4D1EF19CC0E for ; Wed, 2 Oct 2024 09:03:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859811; cv=none; b=fZPzDgSS2VFtbDsWCLrNcYhzZLLkHuo6ZrFUP8r1Mpja0CvBYcS2z5XpUyPLfyrF2WR1N+xOxaPsfbnzQsCJrCj8WqZ4MEZWiu+Qm7U4G3hUNDcGjqrYjhn4HdP+lcvviPaW3rRFvJOhfXzpxNPlCira1KoGlRIoPFFPIpw6j1w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859811; c=relaxed/simple; bh=ggF5K+58gLgiad5OUSjEn5k2AotXeWRMNMLXDkZhNv0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=KjVq/OrzkYX1ILrmlslsDMbj8nETyr7SCIynTAvlTbB9QXjkmgT39dU5mh5iXQJOB7XmwW2TWvRU6ecqYXqk3nKfGEbF60rH74OCC+Q+PG/7oOuzqcWLkOEt0mLmMWtCyH1DDQQhQOl51Og3YfPGKGEBDuOHi4CVNZxoi8//8Kc= 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=PCjtlZjW; arc=none smtp.client-ip=209.85.221.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="PCjtlZjW" Received: by mail-wr1-f52.google.com with SMTP id ffacd0b85a97d-37ccfada422so3500575f8f.2 for ; Wed, 02 Oct 2024 02:03:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859807; x=1728464607; 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=ud6K5GOoq3b4lX4KydaG6wKHFrptq5dDMvYiS7l/dAc=; b=PCjtlZjWIW7vfKXoOw0kGCumUr3gap+9t1bMFrMEkNwFKA1sSQnMWYJ+d0/Kcahy4r MR5VBSdmwAx85cAMOZsvQzu3aCKH3BSyyEspqDNWavBoW06MgSCjN2JUIMKKAr+KLiPI 4IYJIyjL6tcXTInWriX5NF/LfH1xJWiElWStbhOwg/jR26AgD+Ui1WkyY1RWQPKoK1CW 7ZAFRiN9T0pTL+g9Vyni38jCEaymQOtC4U/CVd6O9hahojUcA+8tQ787dhqmRSpJLwoE Fzb3zIv/8zgI35ujPyVcJ3NzH1foov3eBuYd/dwIysDdcKAWSOpkJKM4Ay2XmbezYbaG Ioow== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859807; x=1728464607; 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=ud6K5GOoq3b4lX4KydaG6wKHFrptq5dDMvYiS7l/dAc=; b=D+6Oah9gynQnm5O/wseYybXHAOwvBRCAUfVdAdK51VQOLjF5blDol7R1J/LQnzWFY5 FWFVAk9ehKjkprxvMW8Um+rwJdj4hX0MujCrQyHDT7cycMyq4qRhmqpq28E4lnQM9bEA GQdZts8yK1Z8Hb3MdnTXELqQzZwVkg01Q2Y11dvcNs+h/kThkeoKn7cPCe91HdTZqU+M IPF+bNJCh+q6o0s6Zu8jj/LJJLXwGdJULniGSaueK8Z8SSpEPIyYfnq2hIRMz3B4WJLc yWGhx7itZNLakpIDuhuH0jZJyLHAWfLtw1rqlXIA1/Jhrxa636tDB7sMftxlEJgc1Q3j LbPw== X-Gm-Message-State: AOJu0YxGJSIjAH6JUhUUJGpD6bE/9SgOgHtO4m4YB1oIyXlEGre7MOOE onN/KKC6FMrBG+Zo9THFDOGQ6bPLqk8VyBaJUPhpiXwS8dlJIJCdzOjSyWoPBtQ= X-Google-Smtp-Source: AGHT+IHpAM2XhxaIwZSVpURvWt5PavZQ+eEYh2yvQnfFjayvR6XGXujp+DV5DQzPPTLHo4Rl3Wb1Lw== X-Received: by 2002:a5d:438a:0:b0:37c:ddab:a626 with SMTP id ffacd0b85a97d-37cfb8b5257mr1318662f8f.7.1727859806627; Wed, 02 Oct 2024 02:03:26 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:26 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:22 +0200 Subject: [PATCH net-next v8 08/24] ovpn: introduce the ovpn_socket object Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-8-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=9019; i=antonio@openvpn.net; h=from:subject:message-id; bh=ggF5K+58gLgiad5OUSjEn5k2AotXeWRMNMLXDkZhNv0=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxRKVOwfmu031gZWitqkxwNVxZO62rgGRuqs WYsW48XkfWJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUQAKCRALcOU6oDjV hxnNB/45k5Pb75lJYND2yI7s+Vi8LE7hpWru8jtx6b8f0jMcXToy6da2gzYCKY9zRHc8EPkTIUM 2FcRdCjGeOTPspJBAYbI6sJRQApYZb27tNaSiPW0wkhJXtVdywpCQ6+b8l+CDpr4vEisGkZUImB W9y+DorjKH3RV9/3f4erSG0ROu+DeaYUYJLGN4axDiADU1hsxl4obWM7xUzt7169JhdpIzpdyVc kAMl0sVXRzedmHN9nLWB2tV5Q/ZhdGXEaj6bMBJr2I7JZ/cHPBY5Uwx+mRtwJ/nzf5YQJVoBjN3 n1MUVXOXSvw3V0Krzt7gWOGQotP6Iws31D3MdSzJgYi3gQu8 X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org This specific structure is used in the ovpn kernel module to wrap and carry around a standard kernel socket. ovpn takes ownership of passed sockets and therefore an ovpn specific objects is attached to them for status tracking purposes. Initially only UDP support is introduced. TCP will come in a later patch. Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/Makefile | 2 + drivers/net/ovpn/socket.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/socket.h | 48 +++++++++++++++++++ drivers/net/ovpn/udp.c | 72 ++++++++++++++++++++++++++++ drivers/net/ovpn/udp.h | 17 +++++++ 5 files changed, 259 insertions(+) diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index ce13499b3e1775a7f2a9ce16c6cb0aa088f93685..56bddc9bef83e0befde6af3c3565bb91731d7b22 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -13,3 +13,5 @@ ovpn-y += io.o ovpn-y += netlink.o ovpn-y += netlink-gen.o ovpn-y += peer.o +ovpn-y += socket.o +ovpn-y += udp.o diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c new file mode 100644 index 0000000000000000000000000000000000000000..090a3232ab0ec19702110f1a90f45c7f10889f6f --- /dev/null +++ b/drivers/net/ovpn/socket.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include + +#include "ovpnstruct.h" +#include "main.h" +#include "io.h" +#include "peer.h" +#include "socket.h" +#include "udp.h" + +static void ovpn_socket_detach(struct socket *sock) +{ + if (!sock) + return; + + sockfd_put(sock); +} + +/** + * ovpn_socket_release_kref - kref_put callback + * @kref: the kref object + */ +void ovpn_socket_release_kref(struct kref *kref) +{ + struct ovpn_socket *sock = container_of(kref, struct ovpn_socket, + refcount); + + ovpn_socket_detach(sock->sock); + kfree_rcu(sock, rcu); +} + +static bool ovpn_socket_hold(struct ovpn_socket *sock) +{ + return kref_get_unless_zero(&sock->refcount); +} + +static struct ovpn_socket *ovpn_socket_get(struct socket *sock) +{ + struct ovpn_socket *ovpn_sock; + + rcu_read_lock(); + ovpn_sock = rcu_dereference_sk_user_data(sock->sk); + if (!ovpn_socket_hold(ovpn_sock)) { + pr_warn("%s: found ovpn_socket with ref = 0\n", __func__); + ovpn_sock = NULL; + } + rcu_read_unlock(); + + return ovpn_sock; +} + +static int ovpn_socket_attach(struct socket *sock, struct ovpn_peer *peer) +{ + int ret = -EOPNOTSUPP; + + if (!sock || !peer) + return -EINVAL; + + if (sock->sk->sk_protocol == IPPROTO_UDP) + ret = ovpn_udp_socket_attach(sock, peer->ovpn); + + return ret; +} + +/** + * ovpn_socket_new - create a new socket and initialize it + * @sock: the kernel socket to embed + * @peer: the peer reachable via this socket + * + * Return: an openvpn socket on success or a negative error code otherwise + */ +struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer) +{ + struct ovpn_socket *ovpn_sock; + int ret; + + ret = ovpn_socket_attach(sock, peer); + if (ret < 0 && ret != -EALREADY) + return ERR_PTR(ret); + + /* if this socket is already owned by this interface, just increase the + * refcounter and use it as expected. + * + * Since UDP sockets can be used to talk to multiple remote endpoints, + * openvpn normally instantiates only one socket and shares it among all + * its peers. For this reason, when we find out that a socket is already + * used for some other peer in *this* instance, we can happily increase + * its refcounter and use it normally. + */ + if (ret == -EALREADY) { + /* caller is expected to increase the sock refcounter before + * passing it to this function. For this reason we drop it if + * not needed, like when this socket is already owned. + */ + ovpn_sock = ovpn_socket_get(sock); + sockfd_put(sock); + return ovpn_sock; + } + + ovpn_sock = kzalloc(sizeof(*ovpn_sock), GFP_KERNEL); + if (!ovpn_sock) + return ERR_PTR(-ENOMEM); + + ovpn_sock->ovpn = peer->ovpn; + ovpn_sock->sock = sock; + kref_init(&ovpn_sock->refcount); + + rcu_assign_sk_user_data(sock->sk, ovpn_sock); + + return ovpn_sock; +} diff --git a/drivers/net/ovpn/socket.h b/drivers/net/ovpn/socket.h new file mode 100644 index 0000000000000000000000000000000000000000..5ad9c5073b085482da95ee8ebf40acf20bf2e4b3 --- /dev/null +++ b/drivers/net/ovpn/socket.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_SOCK_H_ +#define _NET_OVPN_SOCK_H_ + +#include +#include +#include + +struct ovpn_struct; +struct ovpn_peer; + +/** + * struct ovpn_socket - a kernel socket referenced in the ovpn code + * @ovpn: ovpn instance owning this socket (UDP only) + * @sock: the low level sock object + * @refcount: amount of contexts currently referencing this object + * @rcu: member used to schedule RCU destructor callback + */ +struct ovpn_socket { + struct ovpn_struct *ovpn; + struct socket *sock; + struct kref refcount; + struct rcu_head rcu; +}; + +void ovpn_socket_release_kref(struct kref *kref); + +/** + * ovpn_socket_put - decrease reference counter + * @sock: the socket whose reference counter should be decreased + */ +static inline void ovpn_socket_put(struct ovpn_socket *sock) +{ + kref_put(&sock->refcount, ovpn_socket_release_kref); +} + +struct ovpn_socket *ovpn_socket_new(struct socket *sock, + struct ovpn_peer *peer); + +#endif /* _NET_OVPN_SOCK_H_ */ diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c new file mode 100644 index 0000000000000000000000000000000000000000..c10474d252e19a0626d17a6f5dd328a5e5811551 --- /dev/null +++ b/drivers/net/ovpn/udp.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#include +#include +#include + +#include "ovpnstruct.h" +#include "main.h" +#include "socket.h" +#include "udp.h" + +/** + * ovpn_udp_socket_attach - set udp-tunnel CBs on socket and link it to ovpn + * @sock: socket to configure + * @ovpn: the openvp instance to link + * + * After invoking this function, the sock will be controlled by ovpn so that + * any incoming packet may be processed by ovpn first. + * + * Return: 0 on success or a negative error code otherwise + */ +int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn) +{ + struct ovpn_socket *old_data; + int ret = 0; + + /* sanity check */ + if (sock->sk->sk_protocol != IPPROTO_UDP) { + DEBUG_NET_WARN_ON_ONCE(1); + return -EINVAL; + } + + /* make sure no pre-existing encapsulation handler exists */ + rcu_read_lock(); + old_data = rcu_dereference_sk_user_data(sock->sk); + if (!old_data) { + /* socket is currently unused - we can take it */ + rcu_read_unlock(); + return 0; + } + + /* socket is in use. We need to understand if it's owned by this ovpn + * instance or by something else. + * In the former case, we can increase the refcounter and happily + * use it, because the same UDP socket is expected to be shared among + * different peers. + * + * Unlikely TCP, a single UDP socket can be used to talk to many remote + * hosts and therefore openvpn instantiates one only for all its peers + */ + if ((READ_ONCE(udp_sk(sock->sk)->encap_type) == UDP_ENCAP_OVPNINUDP) && + old_data->ovpn == ovpn) { + netdev_dbg(ovpn->dev, + "%s: provided socket already owned by this interface\n", + __func__); + ret = -EALREADY; + } else { + netdev_err(ovpn->dev, + "%s: provided socket already taken by other user\n", + __func__); + ret = -EBUSY; + } + rcu_read_unlock(); + + return ret; +} diff --git a/drivers/net/ovpn/udp.h b/drivers/net/ovpn/udp.h new file mode 100644 index 0000000000000000000000000000000000000000..f2507f8f2c71ea9d5e5ac5446801e2d56f86700f --- /dev/null +++ b/drivers/net/ovpn/udp.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#ifndef _NET_OVPN_UDP_H_ +#define _NET_OVPN_UDP_H_ + +struct ovpn_struct; +struct socket; + +int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn); + +#endif /* _NET_OVPN_UDP_H_ */ From patchwork Wed Oct 2 09:02:23 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819516 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f48.google.com (mail-wr1-f48.google.com [209.85.221.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 6D54F19D8A2 for ; Wed, 2 Oct 2024 09:03:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859812; cv=none; b=MArNe+mKR6iUEEDWH64QlCNSxfkfeuPNtuc9LBoOJO6uimkRe4ztST6CuayzHHsBFamwi8O3KMIGwF/vABZ+lVK+LSN++e2PzDTIVDhTvbDsdzISrBpOYb8yXP9fFncLy414qaPsbP5LseUdapM/kPniqGKedhYN1pYp8tmlW+8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859812; c=relaxed/simple; bh=1cPGWwGJVOq3Jt031B/qSyl69Dwk/z9NDKP7HkBFFBU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qJZAOpEalOiLZUBjZVp8f9PxV3PuWgrBxRDjYJ2rAsMDTLmBWJsNdhS9JTJHMNKFJ3lZM0bBPWgj1KxZ7rtf5qE8lEUWcj/vZoAH0XkjIDp/TtpwiD7vk17oqNTveweg2dRp6zTckH3TITyD4JLMqe2WD1rJJfaRkc8MdXyM7pQ= 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=FiHzRuOo; arc=none smtp.client-ip=209.85.221.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="FiHzRuOo" Received: by mail-wr1-f48.google.com with SMTP id ffacd0b85a97d-37cc846fbc4so4577731f8f.2 for ; Wed, 02 Oct 2024 02:03:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859808; x=1728464608; 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=6ygrlPHFuW/58cvvv8yujUAnA0FDrFg/eZxUzNh1xDQ=; b=FiHzRuOoKRM8fNSxs9QOU0p8tN9hIGb7+d7wq+wVlSMZgMhQkys1n+6bx1XO86JzMW wiLgul6Rq8qf84fVXPqJxGYEcgekEG442QoqaUfx08wrT2EznEx9oLNekZ65ArzYNakU UPve12DPvYPFzulC3bf7mTLC4uA+v00hLB72N6WfFEU8NQC98Bc0rCq5eR0gzFdcCY/H hh6Rnnc8rVFrD4cE6sJ970LdebmOqMU3k5pVegnsoclUdAz8cWuia0i6bvSEgeaRTqd1 BPHdyH+Jx3iZ7fxi6IKH3qrrChoS5BhRThwmv6J15KU7EXbG4yX+v61saUzlypWxgEpe hrRg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859808; x=1728464608; 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=6ygrlPHFuW/58cvvv8yujUAnA0FDrFg/eZxUzNh1xDQ=; b=VHrCZPU60oquByEGEgBoyCSeCIT3E2JCfGelk1vnvXM8+tQ/N3QE8H3U1BnDZme9bN ErlWV/6QZTfO9jApmZySmgwZBliKmVNYmuhsWAbHvTI1o2za/D1KxlZ49s7vZm6HEa83 ECoFkkwDy+9tUIk5pWl32JCSJjHY1UYwe7hgg2I9o/rOwX4gNAuG3XlgM542gggoTK8I Sd0T9XF73sYW+yDQtnWrMBmobk0m75g2FwyDd6zr6uuJquBA47s3mGQ8XBRpnycjGW4z WaupTOTvzFJMQiSEQcpsrN4GTCu3KBRrptrJs1GTiN8P/BEeLOhGif/Svwf/o1Fj8UUA kajA== X-Gm-Message-State: AOJu0Yxz/6yiBv//L/kdvyixR6++OC/pLxrd1ZE9fTCcSNw1qQE9Zx4m /3UBy1/Vj/5PjjOsutS+7gG746o6avjJlvX1JM1lV4k7/mDayt96eQEXWHG3y+Y= X-Google-Smtp-Source: AGHT+IEJB4rN25EWkZkk+EmgXwb4xR/aTKL8ZKw8mUmT7i9vAAvWvvtTrzO0CoZHER3hCe0SOAIpZA== X-Received: by 2002:adf:f58f:0:b0:37c:ccd6:7513 with SMTP id ffacd0b85a97d-37cfb8cb5afmr1553980f8f.22.1727859807637; Wed, 02 Oct 2024 02:03:27 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:27 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:23 +0200 Subject: [PATCH net-next v8 09/24] ovpn: implement basic TX path (UDP) Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-9-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=16961; i=antonio@openvpn.net; h=from:subject:message-id; bh=1cPGWwGJVOq3Jt031B/qSyl69Dwk/z9NDKP7HkBFFBU=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxRNTFhmGlHMfvyKdUK+UXSzlJqdUXA8h5TQ D7pedRTmaiJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUQAKCRALcOU6oDjV h+UZB/9AlUThn9/NGyQDrzqqlrUhjw1+Kbu8NHlV5Kj9h0zbYKT25rAM44LsEdMiHaU1uMll41c gS0k6XSAvUdcPpWgYrtYOKznwAvXxAStwM6q9g/XVSUXcLrfauX4MdTqwUVjHQleQ/acut+/Bk3 E48GUmYLKnr5NdHT6CPi2Ba/bTf3xMmYn7J92d0y0zM7Ub8xOVv5wpMhTfGFwJl5C9zaI4L7Tbm ub78NmIO8ohulMeKSgMaQIvIFNFVZLnnx08ODU34ACyFjVpX/pBy2Ax5r+0FFMPMrAosvo7E3wZ GhdlwVb9qBGzC3JLd5Nk+zHXDOgHuBkGXm7PyA3bjDGU1agT X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org Packets sent over the ovpn interface are processed and transmitted to the connected peer, if any. Implementation is UDP only. TCP will be added by a later patch. Note: no crypto/encapsulation exists yet. packets are just captured and sent. Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/io.c | 137 +++++++++++++++++++++++++++- drivers/net/ovpn/peer.c | 37 +++++++- drivers/net/ovpn/peer.h | 5 ++ drivers/net/ovpn/skb.h | 51 +++++++++++ drivers/net/ovpn/udp.c | 232 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/udp.h | 8 ++ 6 files changed, 467 insertions(+), 3 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index ad3813419c33cbdfe7e8ad6f5c8b444a3540a69f..dfd2c90c56842374b7fb1c8bead061a76272441e 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -9,14 +9,149 @@ #include #include +#include #include "io.h" +#include "ovpnstruct.h" +#include "peer.h" +#include "udp.h" +#include "skb.h" + +static void ovpn_encrypt_post(struct sk_buff *skb, int ret) +{ + struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer; + + if (unlikely(ret < 0)) + goto err; + + skb_mark_not_on_list(skb); + + switch (peer->sock->sock->sk->sk_protocol) { + case IPPROTO_UDP: + ovpn_udp_send_skb(peer->ovpn, peer, skb); + break; + default: + /* no transport configured yet */ + goto err; + } + /* skb passed down the stack - don't free it */ + skb = NULL; +err: + if (unlikely(skb)) + dev_core_stats_tx_dropped_inc(peer->ovpn->dev); + ovpn_peer_put(peer); + kfree_skb(skb); +} + +static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb) +{ + ovpn_skb_cb(skb)->peer = peer; + + /* take a reference to the peer because the crypto code may run async. + * ovpn_encrypt_post() will release it upon completion + */ + if (unlikely(!ovpn_peer_hold(peer))) { + DEBUG_NET_WARN_ON_ONCE(1); + return false; + } + + ovpn_encrypt_post(skb, 0); + return true; +} + +/* send skb to connected peer, if any */ +static void ovpn_send(struct ovpn_struct *ovpn, struct sk_buff *skb, + struct ovpn_peer *peer) +{ + struct sk_buff *curr, *next; + + if (likely(!peer)) + /* retrieve peer serving the destination IP of this packet */ + peer = ovpn_peer_get_by_dst(ovpn, skb); + if (unlikely(!peer)) { + net_dbg_ratelimited("%s: no peer to send data to\n", + ovpn->dev->name); + dev_core_stats_tx_dropped_inc(ovpn->dev); + goto drop; + } + + /* this might be a GSO-segmented skb list: process each skb + * independently + */ + skb_list_walk_safe(skb, curr, next) + if (unlikely(!ovpn_encrypt_one(peer, curr))) { + dev_core_stats_tx_dropped_inc(ovpn->dev); + kfree_skb(curr); + } + + /* skb passed over, no need to free */ + skb = NULL; +drop: + if (likely(peer)) + ovpn_peer_put(peer); + kfree_skb_list(skb); +} /* Send user data to the network */ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) { + struct ovpn_struct *ovpn = netdev_priv(dev); + struct sk_buff *segments, *curr, *next; + struct sk_buff_head skb_list; + __be16 proto; + int ret; + + /* reset netfilter state */ + nf_reset_ct(skb); + + /* verify IP header size in network packet */ + proto = ovpn_ip_check_protocol(skb); + if (unlikely(!proto || skb->protocol != proto)) { + net_err_ratelimited("%s: dropping malformed payload packet\n", + dev->name); + dev_core_stats_tx_dropped_inc(ovpn->dev); + goto drop; + } + + if (skb_is_gso(skb)) { + segments = skb_gso_segment(skb, 0); + if (IS_ERR(segments)) { + ret = PTR_ERR(segments); + net_err_ratelimited("%s: cannot segment packet: %d\n", + dev->name, ret); + dev_core_stats_tx_dropped_inc(ovpn->dev); + goto drop; + } + + consume_skb(skb); + skb = segments; + } + + /* from this moment on, "skb" might be a list */ + + __skb_queue_head_init(&skb_list); + skb_list_walk_safe(skb, curr, next) { + skb_mark_not_on_list(curr); + + curr = skb_share_check(curr, GFP_ATOMIC); + if (unlikely(!curr)) { + net_err_ratelimited("%s: skb_share_check failed\n", + dev->name); + dev_core_stats_tx_dropped_inc(ovpn->dev); + continue; + } + + __skb_queue_tail(&skb_list, curr); + } + skb_list.prev->next = NULL; + + ovpn_send(ovpn, skb_list.next, NULL); + + return NETDEV_TX_OK; + +drop: skb_tx_error(skb); - kfree_skb(skb); + kfree_skb_list(skb); return NET_XMIT_DROP; } diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 23ded79eb7748a10b683d9aaa351b361ae79847e..6bde4bd9395b08ea3c25c58cb99e09afdf8ad097 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -9,7 +9,6 @@ #include #include -#include #include "ovpnstruct.h" #include "bind.h" @@ -65,8 +64,10 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id) */ static void ovpn_peer_release(struct ovpn_peer *peer) { - ovpn_bind_reset(peer, NULL); + if (peer->sock) + ovpn_socket_put(peer->sock); + ovpn_bind_reset(peer, NULL); dst_cache_destroy(&peer->dst_cache); } @@ -244,6 +245,38 @@ struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id) return peer; } +/** + * ovpn_peer_get_by_dst - Lookup peer to send skb to + * @ovpn: the private data representing the current VPN session + * @skb: the skb to extract the destination address from + * + * This function takes a tunnel packet and looks up the peer to send it to + * after encapsulation. The skb is expected to be the in-tunnel packet, without + * any OpenVPN related header. + * + * Assume that the IP header is accessible in the skb data. + * + * Return: the peer if found or NULL otherwise. + */ +struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn, + struct sk_buff *skb) +{ + struct ovpn_peer *peer = NULL; + + /* in P2P mode, no matter the destination, packets are always sent to + * the single peer listening on the other side + */ + if (ovpn->mode == OVPN_MODE_P2P) { + rcu_read_lock(); + peer = rcu_dereference(ovpn->peer); + if (unlikely(peer && !ovpn_peer_hold(peer))) + peer = NULL; + rcu_read_unlock(); + } + + return peer; +} + /** * ovpn_peer_add_p2p - add peer to related tables in a P2P instance * @ovpn: the instance to add the peer to diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 6c51959363c78f22e5bbeecbfe65dc90c67e86cc..37de5aff54a8df9adc449d1346b251b1feab0b1c 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -11,6 +11,7 @@ #define _NET_OVPN_OVPNPEER_H_ #include "bind.h" +#include "socket.h" #include #include @@ -22,6 +23,7 @@ * @vpn_addrs: IP addresses assigned over the tunnel * @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel * @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel + * @sock: the socket being used to talk to this peer * @dst_cache: cache for dst_entry used to send to peer * @bind: remote peer binding * @halt: true if ovpn_peer_mark_delete was called @@ -38,6 +40,7 @@ struct ovpn_peer { struct in_addr ipv4; struct in6_addr ipv6; } vpn_addrs; + struct ovpn_socket *sock; struct dst_cache dst_cache; struct ovpn_bind __rcu *bind; bool halt; @@ -78,5 +81,7 @@ void ovpn_peer_release_p2p(struct ovpn_struct *ovpn); struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn, struct sk_buff *skb); struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id); +struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn, + struct sk_buff *skb); #endif /* _NET_OVPN_OVPNPEER_H_ */ diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h new file mode 100644 index 0000000000000000000000000000000000000000..e070fe6f448c0b7a9631394ebef4554f6348ef44 --- /dev/null +++ b/drivers/net/ovpn/skb.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + * James Yonan + */ + +#ifndef _NET_OVPN_SKB_H_ +#define _NET_OVPN_SKB_H_ + +#include +#include +#include +#include +#include +#include + +struct ovpn_cb { + struct ovpn_peer *peer; +}; + +static inline struct ovpn_cb *ovpn_skb_cb(struct sk_buff *skb) +{ + BUILD_BUG_ON(sizeof(struct ovpn_cb) > sizeof(skb->cb)); + return (struct ovpn_cb *)skb->cb; +} + +/* Return IP protocol version from skb header. + * Return 0 if protocol is not IPv4/IPv6 or cannot be read. + */ +static inline __be16 ovpn_ip_check_protocol(struct sk_buff *skb) +{ + __be16 proto = 0; + + /* skb could be non-linear, + * make sure IP header is in non-fragmented part + */ + if (!pskb_network_may_pull(skb, sizeof(struct iphdr))) + return 0; + + if (ip_hdr(skb)->version == 4) + proto = htons(ETH_P_IP); + else if (ip_hdr(skb)->version == 6) + proto = htons(ETH_P_IPV6); + + return proto; +} + +#endif /* _NET_OVPN_SKB_H_ */ diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index c10474d252e19a0626d17a6f5dd328a5e5811551..d26d7566e9c8dfe91fa77f49c34fb179a9fb2239 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -7,14 +7,246 @@ */ #include +#include #include +#include +#include +#include +#include #include +#include #include "ovpnstruct.h" #include "main.h" +#include "bind.h" +#include "io.h" +#include "peer.h" #include "socket.h" #include "udp.h" +/** + * ovpn_udp4_output - send IPv4 packet over udp socket + * @ovpn: the openvpn instance + * @bind: the binding related to the destination peer + * @cache: dst cache + * @sk: the socket to send the packet over + * @skb: the packet to send + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_udp4_output(struct ovpn_struct *ovpn, struct ovpn_bind *bind, + struct dst_cache *cache, struct sock *sk, + struct sk_buff *skb) +{ + struct rtable *rt; + struct flowi4 fl = { + .saddr = bind->local.ipv4.s_addr, + .daddr = bind->remote.in4.sin_addr.s_addr, + .fl4_sport = inet_sk(sk)->inet_sport, + .fl4_dport = bind->remote.in4.sin_port, + .flowi4_proto = sk->sk_protocol, + .flowi4_mark = sk->sk_mark, + }; + int ret; + + local_bh_disable(); + rt = dst_cache_get_ip4(cache, &fl.saddr); + if (rt) + goto transmit; + + if (unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, fl.saddr, + RT_SCOPE_HOST))) { + /* we may end up here when the cached address is not usable + * anymore. In this case we reset address/cache and perform a + * new look up + */ + fl.saddr = 0; + bind->local.ipv4.s_addr = 0; + dst_cache_reset(cache); + } + + rt = ip_route_output_flow(sock_net(sk), &fl, sk); + if (IS_ERR(rt) && PTR_ERR(rt) == -EINVAL) { + fl.saddr = 0; + bind->local.ipv4.s_addr = 0; + dst_cache_reset(cache); + + rt = ip_route_output_flow(sock_net(sk), &fl, sk); + } + + if (IS_ERR(rt)) { + ret = PTR_ERR(rt); + net_dbg_ratelimited("%s: no route to host %pISpc: %d\n", + ovpn->dev->name, &bind->remote.in4, ret); + goto err; + } + dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + +transmit: + udp_tunnel_xmit_skb(rt, sk, skb, fl.saddr, fl.daddr, 0, + ip4_dst_hoplimit(&rt->dst), 0, fl.fl4_sport, + fl.fl4_dport, false, sk->sk_no_check_tx); + ret = 0; +err: + local_bh_enable(); + return ret; +} + +#if IS_ENABLED(CONFIG_IPV6) +/** + * ovpn_udp6_output - send IPv6 packet over udp socket + * @ovpn: the openvpn instance + * @bind: the binding related to the destination peer + * @cache: dst cache + * @sk: the socket to send the packet over + * @skb: the packet to send + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_udp6_output(struct ovpn_struct *ovpn, struct ovpn_bind *bind, + struct dst_cache *cache, struct sock *sk, + struct sk_buff *skb) +{ + struct dst_entry *dst; + int ret; + + struct flowi6 fl = { + .saddr = bind->local.ipv6, + .daddr = bind->remote.in6.sin6_addr, + .fl6_sport = inet_sk(sk)->inet_sport, + .fl6_dport = bind->remote.in6.sin6_port, + .flowi6_proto = sk->sk_protocol, + .flowi6_mark = sk->sk_mark, + .flowi6_oif = bind->remote.in6.sin6_scope_id, + }; + + local_bh_disable(); + dst = dst_cache_get_ip6(cache, &fl.saddr); + if (dst) + goto transmit; + + if (unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) { + /* we may end up here when the cached address is not usable + * anymore. In this case we reset address/cache and perform a + * new look up + */ + fl.saddr = in6addr_any; + bind->local.ipv6 = in6addr_any; + dst_cache_reset(cache); + } + + dst = ipv6_stub->ipv6_dst_lookup_flow(sock_net(sk), sk, &fl, NULL); + if (IS_ERR(dst)) { + ret = PTR_ERR(dst); + net_dbg_ratelimited("%s: no route to host %pISpc: %d\n", + ovpn->dev->name, &bind->remote.in6, ret); + goto err; + } + dst_cache_set_ip6(cache, dst, &fl.saddr); + +transmit: + udp_tunnel6_xmit_skb(dst, sk, skb, skb->dev, &fl.saddr, &fl.daddr, 0, + ip6_dst_hoplimit(dst), 0, fl.fl6_sport, + fl.fl6_dport, udp_get_no_check6_tx(sk)); + ret = 0; +err: + local_bh_enable(); + return ret; +} +#endif + +/** + * ovpn_udp_output - transmit skb using udp-tunnel + * @ovpn: the openvpn instance + * @bind: the binding related to the destination peer + * @cache: dst cache + * @sk: the socket to send the packet over + * @skb: the packet to send + * + * rcu_read_lock should be held on entry. + * On return, the skb is consumed. + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_udp_output(struct ovpn_struct *ovpn, struct ovpn_bind *bind, + struct dst_cache *cache, struct sock *sk, + struct sk_buff *skb) +{ + int ret; + + /* set sk to null if skb is already orphaned */ + if (!skb->destructor) + skb->sk = NULL; + + /* always permit openvpn-created packets to be (outside) fragmented */ + skb->ignore_df = 1; + + switch (bind->remote.in4.sin_family) { + case AF_INET: + ret = ovpn_udp4_output(ovpn, bind, cache, sk, skb); + break; +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: + ret = ovpn_udp6_output(ovpn, bind, cache, sk, skb); + break; +#endif + default: + ret = -EAFNOSUPPORT; + break; + } + + return ret; +} + +/** + * ovpn_udp_send_skb - prepare skb and send it over via UDP + * @ovpn: the openvpn instance + * @peer: the destination peer + * @skb: the packet to send + */ +void ovpn_udp_send_skb(struct ovpn_struct *ovpn, struct ovpn_peer *peer, + struct sk_buff *skb) +{ + struct ovpn_bind *bind; + unsigned int pkt_len; + struct socket *sock; + int ret = -1; + + skb->dev = ovpn->dev; + /* no checksum performed at this layer */ + skb->ip_summed = CHECKSUM_NONE; + + /* get socket info */ + sock = peer->sock->sock; + if (unlikely(!sock)) { + net_warn_ratelimited("%s: no sock for remote peer\n", __func__); + goto out; + } + + rcu_read_lock(); + /* get binding */ + bind = rcu_dereference(peer->bind); + if (unlikely(!bind)) { + net_warn_ratelimited("%s: no bind for remote peer\n", __func__); + goto out_unlock; + } + + /* crypto layer -> transport (UDP) */ + pkt_len = skb->len; + ret = ovpn_udp_output(ovpn, bind, &peer->dst_cache, sock->sk, skb); + +out_unlock: + rcu_read_unlock(); +out: + if (unlikely(ret < 0)) { + dev_core_stats_tx_dropped_inc(ovpn->dev); + kfree_skb(skb); + return; + } + + dev_sw_netstats_tx_add(ovpn->dev, 1, pkt_len); +} + /** * ovpn_udp_socket_attach - set udp-tunnel CBs on socket and link it to ovpn * @sock: socket to configure diff --git a/drivers/net/ovpn/udp.h b/drivers/net/ovpn/udp.h index f2507f8f2c71ea9d5e5ac5446801e2d56f86700f..e60f8cd2b4ac8f910aabcf8ed546af59d6ca4be4 100644 --- a/drivers/net/ovpn/udp.h +++ b/drivers/net/ovpn/udp.h @@ -9,9 +9,17 @@ #ifndef _NET_OVPN_UDP_H_ #define _NET_OVPN_UDP_H_ +#include +#include + +struct ovpn_peer; struct ovpn_struct; +struct sk_buff; struct socket; int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn); +void ovpn_udp_send_skb(struct ovpn_struct *ovpn, struct ovpn_peer *peer, + struct sk_buff *skb); + #endif /* _NET_OVPN_UDP_H_ */ From patchwork Wed Oct 2 09:02:24 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819517 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f47.google.com (mail-wr1-f47.google.com [209.85.221.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B3EC919E96E for ; Wed, 2 Oct 2024 09:03:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859814; cv=none; b=HWLv9wykQyJAtH0C2ZITGtuADYcKx4AWgr2QjJxZJ2WK+KShbIRZT7KmJY26kCMbh4Mxlc/SWmvLxaIOIRwImCkr6OhqvhKFP3GW7iz7W5B/92tazaPl2ZS3n7QETWUjs5zHkNFX3jOi4BcxSupN/QTI8mHgVVwa+XhjOypiIDQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859814; c=relaxed/simple; bh=aOVQzbHAS0m3aoK7vNA3UF7L4WBMhvk/aS/IndQcn5I=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=RTd9N+tRxovsgY2rAeq6s7IcsXZ7TueRbMixh3Jy1X8H4G68p2hRVK6eoqfWgsWDCMPzJF67SRsaCA2hgBzJfKPWPKw38vn1e2YjJklrSmljSRjPKNXg6KKPD2oCI6q+QeNQK5fbI+9GtXk++TFUPb9HQl1U1YUKggjOg722szo= 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=K0nDmTLW; arc=none smtp.client-ip=209.85.221.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="K0nDmTLW" Received: by mail-wr1-f47.google.com with SMTP id ffacd0b85a97d-37ccd50faafso4191575f8f.3 for ; Wed, 02 Oct 2024 02:03:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859809; x=1728464609; 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=hzA/7r2cf9gFl3dmCrLr/KswflOHSEY+WrEWqYFo7PU=; b=K0nDmTLW9ZDSjCvp21PEvMspDHDE9kcrdWY+wi/xZNJLtlt1OOj5J9oBglLCanekjP s2PcMc4CFJI3r5cJLvVuxaZNbGz0Hhnmbx8CZZmxUvjn8vcFCprNvMOBa9Pb4gfESZc6 QGlBqW8aKEAjDKK1IAiamze79ur0RZH5sY2SPmV0wqUQA1oCMxdN+nrekCQfoggHnO/e VbSFSbRozI7mpx5+WjKP8DWqojDNiacvjnC238u/sJwyyoLUndimmW7RYhjIcYB10a9B xcNQ+KePKSgggXcAjZ0JCcbCvoFJOhcmkYwfY1H8OEgSKQ1Rc0ISVWb440HtvB5Dmzxx OVUA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859809; x=1728464609; 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=hzA/7r2cf9gFl3dmCrLr/KswflOHSEY+WrEWqYFo7PU=; b=oKaVC9gQ1B1maXFtBk6NzojA9WIwZMk2uGNnrZca38P9XulBV2Wke7o2DXCjV5Dknz MMAEqL9fFMB1nS7iOGGKqmADLNQZ5kmm62f88yeq7xqGR/Pi/49ZcFevDSMvq76JeIn8 Bv+dxt30V+87ON40+uKS0cb7roxi0EGieQ0UV0lkSfXZ7jvLz6wXtFXtPo0+hoiOWpTt z05QD1rNWNgZ7JCKN785DiLsiUjNQsHDVmdH4yCUEX782GqsiMdEJzp+2ef7ji9WcUkQ zPWagwJ4ohuain76triipXiUmYIIRv4X1F9O8iE8bP0vu9nwEEYQNQKrHEBCPPzRCzPG VMlg== X-Gm-Message-State: AOJu0YySFHz/1ZsL//ZVaB4nK3MG19S8Ao4umFU636cwli06NdHCaNmr g3xgp8R9zCw65AdAi/WcDPHoeed9ee4EJdEVnwRxA5HXgqLEb9h9c8MndDereQw= X-Google-Smtp-Source: AGHT+IHJc5XNcrwFWCbfCKAKOBYKcPGjZR2ZYh1igjMDN3U2v0IDkG8qa94eYabW1nHDeqjDoCR10A== X-Received: by 2002:a05:6000:cd0:b0:374:c287:929b with SMTP id ffacd0b85a97d-37cfb8b1895mr1636870f8f.4.1727859808915; Wed, 02 Oct 2024 02:03:28 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:28 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:24 +0200 Subject: [PATCH net-next v8 10/24] ovpn: implement basic RX path (UDP) Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-10-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=14138; i=antonio@openvpn.net; h=from:subject:message-id; bh=aOVQzbHAS0m3aoK7vNA3UF7L4WBMhvk/aS/IndQcn5I=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxSCf0Xbq1isSmzZjD8Lh7dS4HjHwPqAq5lI s62PfqbgECJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUgAKCRALcOU6oDjV h4cWB/sFqPcKE4+5AGbgPmyKt+8oeYTuzJmW2R6TArCmjoLgmAr+KK9QKHbjwJs+w2KVz/sTEbB 9cXmhGS9CHPpmQ6XFknVPDWgprsu3Rm7iMRLQ6WmkrkhKlSXlVlBPyf0XYbpQEHHjogAoR+nHYY rl/dnKJ6MmZ7hxXKFGlIaygry5UUPMZcB5nSDQDQOtTxKItA4Efve7ydw3GGyd3UV4Lk3Z3vm6F vR4V4djiXtNLqtxQtnu943XM15jkQ1fQjLfL3UvgPFA9EQNv0+RWdkhOSuE9Ht1kleLXqTxeGaP 5ORWaz6DDiMjlF7T5QhbvGcpDZ4yyEaSW9ZU0lvJaXvZaXiy X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org 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 | 66 ++++++++++++++++++++++++++- drivers/net/ovpn/io.h | 2 + drivers/net/ovpn/main.c | 8 +++- drivers/net/ovpn/ovpnstruct.h | 3 ++ drivers/net/ovpn/proto.h | 75 ++++++++++++++++++++++++++++++ drivers/net/ovpn/socket.c | 24 ++++++++++ drivers/net/ovpn/udp.c | 104 +++++++++++++++++++++++++++++++++++++++++- drivers/net/ovpn/udp.h | 3 +- 8 files changed, 281 insertions(+), 4 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index dfd2c90c56842374b7fb1c8bead061a76272441e..78449b52a2f8f710107d2493299c6aca9a2cb2ce 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -9,14 +9,78 @@ #include #include +#include #include -#include "io.h" #include "ovpnstruct.h" #include "peer.h" +#include "io.h" +#include "netlink.h" +#include "proto.h" #include "udp.h" #include "skb.h" +/* Called after decrypt to write the IP packet to the device. + * This method is expected to manage/free the skb. + */ +static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb) +{ + unsigned int pkt_len; + + /* 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_probe_transport_header(skb); + skb_reset_inner_headers(skb); + + memset(skb->cb, 0, sizeof(skb->cb)); + + /* cause packet to be "received" by the interface */ + pkt_len = skb->len; + if (likely(gro_cells_receive(&peer->ovpn->gro_cells, + skb) == NET_RX_SUCCESS)) + /* update RX stats with the size of decrypted packet */ + dev_sw_netstats_rx_add(peer->ovpn->dev, 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_core_stats_rx_dropped_inc(peer->ovpn->dev); + ovpn_peer_put(peer); + kfree_skb(skb); +} + +/* pick next packet from RX queue, decrypt and forward it to the device */ +void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb) +{ + ovpn_skb_cb(skb)->peer = peer; + ovpn_decrypt_post(skb, 0); +} + static void ovpn_encrypt_post(struct sk_buff *skb, int ret) { struct ovpn_peer *peer = ovpn_skb_cb(skb)->peer; diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h index aa259be66441f7b0262f39da12d6c3dce0a9b24c..9667a0a470e0b4b427524fffb5b9b395007e5a2f 100644 --- a/drivers/net/ovpn/io.h +++ b/drivers/net/ovpn/io.h @@ -12,4 +12,6 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev); +void ovpn_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 4522f2f8978614c96d529f807bd2cf32a44f8216..670e66f8f750086de5d48e4708721d102c149a2e 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -13,6 +13,7 @@ #include //#include #include +#include #include #include #include @@ -45,11 +46,16 @@ static void ovpn_struct_init(struct net_device *dev, enum ovpn_mode mode) static void ovpn_struct_free(struct net_device *net) { + struct ovpn_struct *ovpn = netdev_priv(net); + + gro_cells_destroy(&ovpn->gro_cells); } static int ovpn_net_init(struct net_device *dev) { - return 0; + struct ovpn_struct *ovpn = netdev_priv(dev); + + return gro_cells_init(&ovpn->gro_cells, dev); } static int ovpn_net_open(struct net_device *dev) diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h index 25f4837b798b1a2c9b11763dfb5b44e8ec337e6f..65497ce115aa4be719ed03f89135a80b73f9ef9b 100644 --- a/drivers/net/ovpn/ovpnstruct.h +++ b/drivers/net/ovpn/ovpnstruct.h @@ -10,6 +10,7 @@ #ifndef _NET_OVPN_OVPNSTRUCT_H_ #define _NET_OVPN_OVPNSTRUCT_H_ +#include #include #include @@ -22,6 +23,7 @@ * @lock: protect this object * @peer: in P2P mode, this is the only remote peer * @dev_list: entry for the module wide device list + * @gro_cells: pointer to the Generic Receive Offload cell */ struct ovpn_struct { struct net_device *dev; @@ -31,6 +33,7 @@ struct ovpn_struct { spinlock_t lock; /* protect writing to the ovpn_struct object */ struct ovpn_peer __rcu *peer; struct list_head dev_list; + struct gro_cells gro_cells; }; #endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/drivers/net/ovpn/proto.h b/drivers/net/ovpn/proto.h new file mode 100644 index 0000000000000000000000000000000000000000..69604cf26bbf82539ee5cd5a7ac9c23920f555de --- /dev/null +++ b/drivers/net/ovpn/proto.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + * James Yonan + */ + +#ifndef _NET_OVPN_OVPNPROTO_H_ +#define _NET_OVPN_OVPNPROTO_H_ + +#include "main.h" + +#include + +/* Methods for operating on the initial command + * byte of the OpenVPN protocol. + */ + +/* packet opcode (high 5 bits) and key-id (low 3 bits) are combined in + * one byte + */ +#define OVPN_KEY_ID_MASK 0x07 +#define OVPN_OPCODE_SHIFT 3 +#define OVPN_OPCODE_MASK 0x1F +/* upper bounds on opcode and key ID */ +#define OVPN_KEY_ID_MAX (OVPN_KEY_ID_MASK + 1) +#define OVPN_OPCODE_MAX (OVPN_OPCODE_MASK + 1) +/* packet opcodes of interest to us */ +#define OVPN_DATA_V1 6 /* data channel V1 packet */ +#define OVPN_DATA_V2 9 /* data channel V2 packet */ +/* size of initial packet opcode */ +#define OVPN_OP_SIZE_V1 1 +#define OVPN_OP_SIZE_V2 4 +#define OVPN_PEER_ID_MASK 0x00FFFFFF +#define OVPN_PEER_ID_UNDEF 0x00FFFFFF +/* first byte of keepalive message */ +#define OVPN_KEEPALIVE_FIRST_BYTE 0x2a +/* first byte of exit message */ +#define OVPN_EXPLICIT_EXIT_NOTIFY_FIRST_BYTE 0x28 + +/** + * ovpn_opcode_from_skb - extract OP code from skb at specified offset + * @skb: the packet to extract the OP code from + * @offset: the offset in the data buffer where the OP code is located + * + * Note: this function assumes that the skb head was pulled enough + * to access the first byte. + * + * Return: the OP code + */ +static inline u8 ovpn_opcode_from_skb(const struct sk_buff *skb, u16 offset) +{ + u8 byte = *(skb->data + offset); + + return byte >> OVPN_OPCODE_SHIFT; +} + +/** + * ovpn_peer_id_from_skb - extract peer ID from skb at specified offset + * @skb: the packet to extract the OP code from + * @offset: the offset in the data buffer where the OP code is located + * + * Note: this function assumes that the skb head was pulled enough + * to access the first 4 bytes. + * + * Return: the peer ID. + */ +static inline u32 ovpn_peer_id_from_skb(const struct sk_buff *skb, u16 offset) +{ + return ntohl(*(__be32 *)(skb->data + offset)) & OVPN_PEER_ID_MASK; +} + +#endif /* _NET_OVPN_OVPNPROTO_H_ */ diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c index 090a3232ab0ec19702110f1a90f45c7f10889f6f..964b566de69f4132806a969a455cec7f6059a0bd 100644 --- a/drivers/net/ovpn/socket.c +++ b/drivers/net/ovpn/socket.c @@ -22,6 +22,9 @@ static void ovpn_socket_detach(struct socket *sock) if (!sock) return; + if (sock->sk->sk_protocol == IPPROTO_UDP) + ovpn_udp_socket_detach(sock); + sockfd_put(sock); } @@ -71,6 +74,27 @@ static int ovpn_socket_attach(struct socket *sock, struct ovpn_peer *peer) return ret; } +/* Retrieve the corresponding ovpn object from a UDP socket + * rcu_read_lock must be held on entry + */ +struct ovpn_struct *ovpn_from_udp_sock(struct sock *sk) +{ + struct ovpn_socket *ovpn_sock; + + if (unlikely(READ_ONCE(udp_sk(sk)->encap_type) != UDP_ENCAP_OVPNINUDP)) + return NULL; + + ovpn_sock = rcu_dereference_sk_user_data(sk); + if (unlikely(!ovpn_sock)) + return NULL; + + /* make sure that sk matches our stored transport socket */ + if (unlikely(!ovpn_sock->sock || sk != ovpn_sock->sock->sk)) + return NULL; + + return ovpn_sock->ovpn; +} + /** * ovpn_socket_new - create a new socket and initialize it * @sock: the kernel socket to embed diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index d26d7566e9c8dfe91fa77f49c34fb179a9fb2239..d1e88ae83843f02d591e67a7995f2d6868720695 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -21,9 +21,95 @@ #include "bind.h" #include "io.h" #include "peer.h" +#include "proto.h" #include "socket.h" #include "udp.h" +/** + * ovpn_udp_encap_recv - Start processing a received UDP packet. + * @sk: socket over which the packet was received + * @skb: the received packet + * + * If the first byte of the payload is DATA_V2, the packet is further processed, + * otherwise it is forwarded to the UDP stack for delivery to user space. + * + * Return: + * 0 if skb was consumed or dropped + * >0 if skb should be passed up to userspace as UDP (packet not consumed) + * <0 if skb should be resubmitted as proto -N (packet not consumed) + */ +static int ovpn_udp_encap_recv(struct sock *sk, struct sk_buff *skb) +{ + struct ovpn_peer *peer = NULL; + struct ovpn_struct *ovpn; + u32 peer_id; + u8 opcode; + + ovpn = ovpn_from_udp_sock(sk); + if (unlikely(!ovpn)) { + net_err_ratelimited("%s: cannot obtain ovpn object from UDP socket\n", + __func__); + goto drop_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_OP_SIZE_V2))) { + net_dbg_ratelimited("%s: packet too small\n", __func__); + goto drop; + } + + opcode = ovpn_opcode_from_skb(skb, sizeof(struct udphdr)); + if (unlikely(opcode != OVPN_DATA_V2)) { + /* DATA_V1 is not supported */ + if (opcode == OVPN_DATA_V1) + goto drop; + + /* unknown or control packet: let it bubble up to userspace */ + return 1; + } + + peer_id = ovpn_peer_id_from_skb(skb, sizeof(struct udphdr)); + /* some OpenVPN server implementations send data packets with the + * peer-id set to undef. In this case we skip the peer lookup by peer-id + * and we try with the transport address + */ + if (peer_id != OVPN_PEER_ID_UNDEF) { + peer = ovpn_peer_get_by_id(ovpn, peer_id); + if (!peer) { + net_err_ratelimited("%s: received data from unknown peer (id: %d)\n", + __func__, peer_id); + goto drop; + } + } + + if (!peer) { + /* data packet with undef peer-id */ + peer = ovpn_peer_get_by_transp_addr(ovpn, skb); + if (unlikely(!peer)) { + net_dbg_ratelimited("%s: received data with undef peer-id from unknown source\n", + __func__); + goto drop; + } + } + + /* pop off outer UDP header */ + __skb_pull(skb, sizeof(struct udphdr)); + ovpn_recv(peer, skb); + return 0; + +drop: + if (peer) + ovpn_peer_put(peer); + dev_core_stats_rx_dropped_inc(ovpn->dev); +drop_noovpn: + kfree_skb(skb); + return 0; +} + /** * ovpn_udp4_output - send IPv4 packet over udp socket * @ovpn: the openvpn instance @@ -259,8 +345,12 @@ void ovpn_udp_send_skb(struct ovpn_struct *ovpn, struct ovpn_peer *peer, */ int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn) { + struct udp_tunnel_sock_cfg cfg = { + .encap_type = UDP_ENCAP_OVPNINUDP, + .encap_rcv = ovpn_udp_encap_recv, + }; struct ovpn_socket *old_data; - int ret = 0; + int ret; /* sanity check */ if (sock->sk->sk_protocol != IPPROTO_UDP) { @@ -274,6 +364,7 @@ int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn) if (!old_data) { /* socket is currently unused - we can take it */ rcu_read_unlock(); + setup_udp_tunnel_sock(sock_net(sock->sk), sock, &cfg); return 0; } @@ -302,3 +393,14 @@ int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn) return ret; } + +/** + * ovpn_udp_socket_detach - clean udp-tunnel status for this socket + * @sock: the socket to clean + */ +void ovpn_udp_socket_detach(struct socket *sock) +{ + struct udp_tunnel_sock_cfg cfg = { }; + + setup_udp_tunnel_sock(sock_net(sock->sk), sock, &cfg); +} diff --git a/drivers/net/ovpn/udp.h b/drivers/net/ovpn/udp.h index e60f8cd2b4ac8f910aabcf8ed546af59d6ca4be4..fecb68464896bc1228315faf268453f9005e693d 100644 --- a/drivers/net/ovpn/udp.h +++ b/drivers/net/ovpn/udp.h @@ -18,8 +18,9 @@ struct sk_buff; struct socket; int ovpn_udp_socket_attach(struct socket *sock, struct ovpn_struct *ovpn); - +void ovpn_udp_socket_detach(struct socket *sock); void ovpn_udp_send_skb(struct ovpn_struct *ovpn, struct ovpn_peer *peer, struct sk_buff *skb); +struct ovpn_struct *ovpn_from_udp_sock(struct sock *sk); #endif /* _NET_OVPN_UDP_H_ */ From patchwork Wed Oct 2 09:02:25 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819519 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f41.google.com (mail-wr1-f41.google.com [209.85.221.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 230D019E838 for ; Wed, 2 Oct 2024 09:03:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859817; cv=none; b=LbiE8wrgVazaL+LgjsTHSVzQjAzHf52KXigG/vS2f3VIVLH8YRU1UDCon1BtrMjc+FxSlmM7j3OvQdJjl3OpuOts9Fe5wFMwTnx9hbz/JVh3OAUFbI/mhcnaPx6mARF5jT3S5lcRpuTsPeb6Ah4qCoNROkYeI28CjUphLpynkb4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859817; c=relaxed/simple; bh=OUo0EDgoQ0aNfxk4xnRCOXsftOakRkPXy5YPseSIJWA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=FeKBPbjr9+zrISnYYeIG72nvhDdkb5CCHY9+dmqgLDSn1xIqUXOsBRpEmcQvofUI5VGai1neNUAzwYgFwWm6um64YHBnfDp94uJs9OgncNHyKAfuJMlWS/T2vPUnaXs0nXdzFuElrzBJYaAf0ymwP/tyN+OQJhigW3+7BsgOWuU= 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=fLG5gL5w; arc=none smtp.client-ip=209.85.221.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="fLG5gL5w" Received: by mail-wr1-f41.google.com with SMTP id ffacd0b85a97d-37ce14ab7eeso3301297f8f.2 for ; Wed, 02 Oct 2024 02:03:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859810; x=1728464610; 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=FB1GTdHFijJvJ7PJk70u+Gc+jgaMj8AoqGgp+oYMiZA=; b=fLG5gL5wFvl7UqYXNn849dw92fVeH/zbKGaYO+E8IaHgHFzksxab5LvK9iMe/1ZjKz PfGuNktMD5jxTWx+n7seekPG1tEiaghjIf6zZ/XQXUzRcEvkiZ5Ibhhc4J3F1jzeOCrQ r87d2zj77MvQferMWPhf7WJp0dv+AqXfXkXJbQ1zRSz0cryJb5nMXQyzyVuRYsn8pl+O v/lpruY+5Q2EjbgBhoy9aF5SCe52IP8E7PAaXfDm2LXbCztT6QFER1mqMq3BHpmbM3Yh 2tOGfNr7MMAO8n/D4lRpxRU7ng8wij8WRoAFbSyAOQ+a+TW6POBuJVZF1Xi/j4kGN5GA wiiQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859810; x=1728464610; 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=FB1GTdHFijJvJ7PJk70u+Gc+jgaMj8AoqGgp+oYMiZA=; b=CMVBtcaLAiycqjT4rHCx0hqQJ+KXoizq5hpOv5n0zSLdy9OiEXSW2BAKrLnPKef1W9 dTYgTEipVbaVgbFGvEopobAehiO8iuHHhSHtCFoBTwBadzBZepXB9jW6kNXyl1rAM4dq rNE/vALcmqUKF99smZDByTSvwZJ8W8dBJ9fOb127uUVc8gWdLOtlyZStYYCAcm6OSzHV 3CIth4OXjhAyUs+V0YdReyxYWagxizC+shxwxkplZQ1f8GGseJmxy26F8Qw/Urh3RnxH wRFeP3wx95o87Uu5HofwLBZG9Xkio05IB1qRMiGmOVYyL+sonS3OvMBJ0NIlfL8ETAXL sSMQ== X-Gm-Message-State: AOJu0Yw5YoDQMVlpRMcDNffYO5LL6thbAl9i1M8+rzLYajtlTwPgmgWf 1X1nsUTtmW4/vRhgfw8FGvu6vQxWKHwaJEsduIRAnBTYQa+vEosm40/ZWDP0nbA= X-Google-Smtp-Source: AGHT+IGIWSh8HKcs1Mmznu/Vd98gDuwF0fiGg8kOzX4zn8fscGbe0StnFBl/qNT7WWADbeSBuNkkag== X-Received: by 2002:a05:6000:128d:b0:374:c7f8:3d50 with SMTP id ffacd0b85a97d-37cfba19c0fmr1896130f8f.58.1727859810171; Wed, 02 Oct 2024 02:03:30 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:29 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:25 +0200 Subject: [PATCH net-next v8 11/24] ovpn: implement packet processing Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-11-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=40832; i=antonio@openvpn.net; h=from:subject:message-id; bh=OUo0EDgoQ0aNfxk4xnRCOXsftOakRkPXy5YPseSIJWA=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxSTHms3Ru71Eh8zePWpYVA6jOwQozpfVeZv 9p/yo1yLKuJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUgAKCRALcOU6oDjV h4jqB/9mIspPCSz9ZBk2kghOGa6dTdviX/4dMKFp63Rn5svH0vBVn14P1P9lyc1sEMuoZTf4GEe QQsGReTe0pXTQ/XRPKc4/rDS6rYk/98d6UyG+0fgJisyQFZHEmlDEcfuUmpnCA0n8vV9xsARBjL Df5mOTfNiBZ/vXJzPBv4Dmyb4SEnvP2eRlDjJ6zlujG8dbzb3/bz9fOOhs1xLi/ha4JlUx+bt57 oY2sbiPImO00Bv3kvsUgsT+7pk4pTfts/xIdFITXMRoui+ddVvn5pl834OtvruOLMBioXJgSm7J Q+Q8a0QCuKS4rC5CQpk5NutGR0nmQI75NC5wWCAg0Q5f/Oe5 X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org 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/ovpn/Makefile | 3 + drivers/net/ovpn/crypto.c | 153 ++++++++++++++++++ drivers/net/ovpn/crypto.h | 136 ++++++++++++++++ drivers/net/ovpn/crypto_aead.c | 354 +++++++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/crypto_aead.h | 31 ++++ drivers/net/ovpn/io.c | 140 ++++++++++++++-- drivers/net/ovpn/io.h | 3 + drivers/net/ovpn/packet.h | 2 +- drivers/net/ovpn/peer.c | 29 ++++ drivers/net/ovpn/peer.h | 6 + drivers/net/ovpn/pktid.c | 130 +++++++++++++++ drivers/net/ovpn/pktid.h | 87 ++++++++++ drivers/net/ovpn/proto.h | 31 ++++ drivers/net/ovpn/skb.h | 11 +- 14 files changed, 1104 insertions(+), 12 deletions(-) diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 56bddc9bef83e0befde6af3c3565bb91731d7b22..ccdaeced1982c851475657860a005ff2b9dfbd13 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -8,10 +8,13 @@ obj-$(CONFIG_OVPN) := ovpn.o ovpn-y += bind.o +ovpn-y += crypto.o +ovpn-y += crypto_aead.o ovpn-y += main.o ovpn-y += io.o ovpn-y += netlink.o ovpn-y += netlink-gen.o ovpn-y += peer.o +ovpn-y += pktid.o ovpn-y += socket.o ovpn-y += udp.o diff --git a/drivers/net/ovpn/crypto.c b/drivers/net/ovpn/crypto.c new file mode 100644 index 0000000000000000000000000000000000000000..f1f7510e2f735e367f96eb4982ba82c9af3c8bfc --- /dev/null +++ b/drivers/net/ovpn/crypto.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include +#include +#include + +#include "ovpnstruct.h" +#include "main.h" +#include "packet.h" +#include "pktid.h" +#include "crypto_aead.h" +#include "crypto.h" + +static void ovpn_ks_destroy_rcu(struct rcu_head *head) +{ + struct ovpn_crypto_key_slot *ks; + + ks = container_of(head, struct ovpn_crypto_key_slot, rcu); + ovpn_aead_crypto_key_slot_destroy(ks); +} + +void ovpn_crypto_key_slot_release(struct kref *kref) +{ + struct ovpn_crypto_key_slot *ks; + + ks = container_of(kref, struct ovpn_crypto_key_slot, refcount); + call_rcu(&ks->rcu, ovpn_ks_destroy_rcu); +} + +/* can only be invoked when all peer references have been dropped (i.e. RCU + * release routine) + */ +void ovpn_crypto_state_release(struct ovpn_crypto_state *cs) +{ + struct ovpn_crypto_key_slot *ks; + + ks = rcu_access_pointer(cs->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); +} + +/* this swap is not atomic, but there will be a very short time frame where the + * old_secondary key won't be available. This should not be a big deal as most + * likely both peers are already using the new primary at this point. + */ +void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs) +{ + const struct ovpn_crypto_key_slot *old_primary, *old_secondary; + 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 */ + 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..61de855d23649598a6e5a34447a35ed9620dfde1 --- /dev/null +++ b/drivers/net/ovpn/crypto.h @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNCRYPTO_H_ +#define _NET_OVPN_OVPNCRYPTO_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; + struct ovpn_nonce_tail nonce_tail_xmit; + struct ovpn_nonce_tail nonce_tail_recv; + + struct ovpn_pktid_recv pid_recv ____cacheline_aligned_in_smp; + struct ovpn_pktid_xmit pid_xmit ____cacheline_aligned_in_smp; + struct kref refcount; + struct rcu_head rcu; +}; + +struct ovpn_crypto_state { + struct ovpn_crypto_key_slot __rcu *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 = 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 ^ 1]); + 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..97134ac679c67a3d44a0bf49c2ddf058fd7c0e99 --- /dev/null +++ b/drivers/net/ovpn/crypto_aead.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include +#include +#include +#include +#include + +#include "ovpnstruct.h" +#include "main.h" +#include "io.h" +#include "packet.h" +#include "pktid.h" +#include "crypto_aead.h" +#include "crypto.h" +#include "peer.h" +#include "proto.h" +#include "skb.h" + +#define AUTH_TAG_SIZE 16 + +static int ovpn_aead_encap_overhead(const struct ovpn_crypto_key_slot *ks) +{ + return OVPN_OP_SIZE_V2 + /* OP header size */ + 4 + /* Packet ID */ + crypto_aead_authsize(ks->encrypt); /* Auth Tag */ +} + +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); + const unsigned int head_size = ovpn_aead_encap_overhead(ks); + struct aead_request *req; + struct sk_buff *trailer; + struct scatterlist *sg; + u8 iv[NONCE_SIZE]; + int nfrags, ret; + u32 pktid, op; + + /* Sample AEAD header format: + * 48000001 00000005 7e7046bd 444a7e28 cc6387b1 64a4d6c1 380275a... + * [ OP32 ] [seq # ] [ auth tag ] [ payload ... ] + * [4-byte + * IV head] + */ + + /* check that there's enough headroom in the skb for packet + * encapsulation, after adding network header and encryption overhead + */ + if (unlikely(skb_cow_head(skb, OVPN_HEAD_ROOM + head_size))) + return -ENOBUFS; + + /* get number of skb frags and ensure that packet data is writable */ + nfrags = skb_cow_data(skb, 0, &trailer); + if (unlikely(nfrags < 0)) + return nfrags; + + if (unlikely(nfrags + 2 > (MAX_SKB_FRAGS + 2))) + return -ENOSPC; + + ovpn_skb_cb(skb)->ctx = kmalloc(sizeof(*ovpn_skb_cb(skb)->ctx), + GFP_ATOMIC); + if (unlikely(!ovpn_skb_cb(skb)->ctx)) + return -ENOMEM; + + sg = ovpn_skb_cb(skb)->ctx->sg; + + /* sg table: + * 0: op, wire nonce (AD, len=OVPN_OP_SIZE_V2+NONCE_WIRE_SIZE), + * 1, 2, 3, ..., n: payload, + * n+1: auth_tag (len=tag_size) + */ + sg_init_table(sg, nfrags + 2); + + /* build scatterlist to encrypt packet payload */ + ret = skb_to_sgvec_nomark(skb, sg + 1, 0, skb->len); + if (unlikely(nfrags != ret)) + return -EINVAL; + + /* append auth_tag onto scatterlist */ + __skb_push(skb, tag_size); + sg_set_buf(sg + nfrags + 1, skb->data, tag_size); + + /* obtain packet ID, which is used both as a first + * 4 bytes of nonce and last 4 bytes of associated data. + */ + ret = ovpn_pktid_xmit_next(&ks->pid_xmit, &pktid); + if (unlikely(ret < 0)) { + kfree(ovpn_skb_cb(skb)->ctx); + return ret; + } + + /* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes + * nonce + */ + ovpn_pktid_aead_write(pktid, &ks->nonce_tail_xmit, iv); + + /* make space for packet id and push it to the front */ + __skb_push(skb, NONCE_WIRE_SIZE); + memcpy(skb->data, iv, NONCE_WIRE_SIZE); + + /* add packet op as head of additional data */ + op = ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer->id); + __skb_push(skb, OVPN_OP_SIZE_V2); + BUILD_BUG_ON(sizeof(op) != OVPN_OP_SIZE_V2); + *((__force __be32 *)skb->data) = htonl(op); + + /* AEAD Additional data */ + sg_set_buf(sg, skb->data, OVPN_OP_SIZE_V2 + NONCE_WIRE_SIZE); + + req = aead_request_alloc(ks->encrypt, GFP_ATOMIC); + if (unlikely(!req)) { + kfree(ovpn_skb_cb(skb)->ctx); + return -ENOMEM; + } + + /* 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 - head_size, iv); + aead_request_set_ad(req, OVPN_OP_SIZE_V2 + NONCE_WIRE_SIZE); + + ovpn_skb_cb(skb)->ctx->peer = peer; + ovpn_skb_cb(skb)->ctx->req = req; + ovpn_skb_cb(skb)->ctx->ks = ks; + + /* 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; + unsigned int sg_len; + u8 iv[NONCE_SIZE]; + + payload_offset = OVPN_OP_SIZE_V2 + NONCE_WIRE_SIZE + tag_size; + payload_len = skb->len - payload_offset; + + /* sanity check on packet size, payload size must be >= 0 */ + if (unlikely(payload_len < 0)) + return -EINVAL; + + /* Prepare the skb data buffer to be accessed up until the auth tag. + * This is required because this area is directly mapped into the sg + * list. + */ + if (unlikely(!pskb_may_pull(skb, payload_offset))) + return -ENODATA; + + /* get number of skb frags and ensure that packet data is writable */ + nfrags = skb_cow_data(skb, 0, &trailer); + if (unlikely(nfrags < 0)) + return nfrags; + + if (unlikely(nfrags + 2 > (MAX_SKB_FRAGS + 2))) + return -ENOSPC; + + ovpn_skb_cb(skb)->ctx = kmalloc(sizeof(*ovpn_skb_cb(skb)->ctx), + GFP_ATOMIC); + if (unlikely(!ovpn_skb_cb(skb)->ctx)) + return -ENOMEM; + + sg = ovpn_skb_cb(skb)->ctx->sg; + + /* sg table: + * 0: op, wire nonce (AD, len=OVPN_OP_SIZE_V2+NONCE_WIRE_SIZE), + * 1, 2, 3, ..., n: payload, + * n+1: auth_tag (len=tag_size) + */ + sg_init_table(sg, nfrags + 2); + + /* packet op is head of additional data */ + sg_len = OVPN_OP_SIZE_V2 + NONCE_WIRE_SIZE; + sg_set_buf(sg, skb->data, sg_len); + + /* build scatterlist to decrypt packet payload */ + ret = skb_to_sgvec_nomark(skb, sg + 1, payload_offset, payload_len); + if (unlikely(nfrags != ret)) { + kfree(ovpn_skb_cb(skb)->ctx); + return -EINVAL; + } + + /* append auth_tag onto scatterlist */ + sg_set_buf(sg + nfrags + 1, skb->data + sg_len, tag_size); + + /* copy nonce into IV buffer */ + memcpy(iv, skb->data + OVPN_OP_SIZE_V2, NONCE_WIRE_SIZE); + memcpy(iv + NONCE_WIRE_SIZE, ks->nonce_tail_recv.u8, + sizeof(struct ovpn_nonce_tail)); + + req = aead_request_alloc(ks->decrypt, GFP_ATOMIC); + if (unlikely(!req)) { + kfree(ovpn_skb_cb(skb)->ctx); + return -ENOMEM; + } + + /* 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, NONCE_WIRE_SIZE + OVPN_OP_SIZE_V2); + + ovpn_skb_cb(skb)->ctx->payload_offset = payload_offset; + ovpn_skb_cb(skb)->ctx->peer = peer; + ovpn_skb_cb(skb)->ctx->req = req; + ovpn_skb_cb(skb)->ctx->ks = ks; + + /* decrypt it */ + return crypto_aead_decrypt(req); +} + +/* Initialize a struct crypto_aead object */ +struct crypto_aead *ovpn_aead_init(const char *title, const char *alg_name, + const unsigned char *key, + unsigned int keylen) +{ + struct crypto_aead *aead; + int ret; + + aead = crypto_alloc_aead(alg_name, 0, 0); + if (IS_ERR(aead)) { + ret = PTR_ERR(aead); + pr_err("%s crypto_alloc_aead failed, err=%d\n", title, ret); + aead = NULL; + goto error; + } + + ret = crypto_aead_setkey(aead, key, keylen); + if (ret) { + pr_err("%s crypto_aead_setkey size=%u failed, err=%d\n", title, + keylen, ret); + goto error; + } + + ret = crypto_aead_setauthsize(aead, AUTH_TAG_SIZE); + if (ret) { + pr_err("%s crypto_aead_setauthsize failed, err=%d\n", title, + ret); + goto error; + } + + /* basic AEAD assumption */ + if (crypto_aead_ivsize(aead) != NONCE_SIZE) { + pr_err("%s IV size must be %d\n", title, NONCE_SIZE); + ret = -EINVAL; + goto error; + } + + pr_debug("********* Cipher %s (%s)\n", alg_name, title); + pr_debug("*** IV size=%u\n", crypto_aead_ivsize(aead)); + pr_debug("*** req size=%u\n", crypto_aead_reqsize(aead)); + pr_debug("*** block size=%u\n", crypto_aead_blocksize(aead)); + pr_debug("*** auth size=%u\n", crypto_aead_authsize(aead)); + pr_debug("*** alignmask=0x%x\n", crypto_aead_alignmask(aead)); + + return aead; + +error: + crypto_free_aead(aead); + return ERR_PTR(ret); +} + +void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks) +{ + if (!ks) + return; + + crypto_free_aead(ks->encrypt); + crypto_free_aead(ks->decrypt); + kfree(ks); +} + +struct ovpn_crypto_key_slot * +ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc) +{ + struct ovpn_crypto_key_slot *ks = NULL; + const char *alg_name; + int ret; + + /* validate crypto alg */ + switch (kc->cipher_alg) { + case OVPN_CIPHER_ALG_AES_GCM: + alg_name = "gcm(aes)"; + break; + case OVPN_CIPHER_ALG_CHACHA20_POLY1305: + alg_name = "rfc7539(chacha20,poly1305)"; + break; + default: + return ERR_PTR(-EOPNOTSUPP); + } + + if (sizeof(struct ovpn_nonce_tail) != kc->encrypt.nonce_tail_size || + sizeof(struct ovpn_nonce_tail) != kc->decrypt.nonce_tail_size) + return ERR_PTR(-EINVAL); + + /* build the key slot */ + ks = kmalloc(sizeof(*ks), GFP_KERNEL); + if (!ks) + return ERR_PTR(-ENOMEM); + + ks->encrypt = NULL; + ks->decrypt = NULL; + kref_init(&ks->refcount); + ks->key_id = kc->key_id; + + ks->encrypt = ovpn_aead_init("encrypt", alg_name, + kc->encrypt.cipher_key, + kc->encrypt.cipher_key_size); + if (IS_ERR(ks->encrypt)) { + ret = PTR_ERR(ks->encrypt); + ks->encrypt = NULL; + goto destroy_ks; + } + + ks->decrypt = ovpn_aead_init("decrypt", alg_name, + kc->decrypt.cipher_key, + kc->decrypt.cipher_key_size); + if (IS_ERR(ks->decrypt)) { + ret = PTR_ERR(ks->decrypt); + ks->decrypt = NULL; + goto destroy_ks; + } + + memcpy(ks->nonce_tail_xmit.u8, kc->encrypt.nonce_tail, + sizeof(struct ovpn_nonce_tail)); + memcpy(ks->nonce_tail_recv.u8, kc->decrypt.nonce_tail, + sizeof(struct ovpn_nonce_tail)); + + /* init packet ID generation/validation */ + ovpn_pktid_xmit_init(&ks->pid_xmit); + ovpn_pktid_recv_init(&ks->pid_recv); + + return ks; + +destroy_ks: + ovpn_aead_crypto_key_slot_destroy(ks); + return ERR_PTR(ret); +} diff --git a/drivers/net/ovpn/crypto_aead.h b/drivers/net/ovpn/crypto_aead.h new file mode 100644 index 0000000000000000000000000000000000000000..77ee8141599bc06b0dc664c5b0a4dae660a89238 --- /dev/null +++ b/drivers/net/ovpn/crypto_aead.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNAEAD_H_ +#define _NET_OVPN_OVPNAEAD_H_ + +#include "crypto.h" + +#include +#include + +struct crypto_aead *ovpn_aead_init(const char *title, const char *alg_name, + const unsigned char *key, + unsigned int keylen); + +int ovpn_aead_encrypt(struct ovpn_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 78449b52a2f8f710107d2493299c6aca9a2cb2ce..a75aa301b99e6abff04d5cb289b26b5979906ae5 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,8 @@ #include "ovpnstruct.h" #include "peer.h" #include "io.h" +#include "crypto.h" +#include "crypto_aead.h" #include "netlink.h" #include "proto.h" #include "udp.h" @@ -57,33 +60,133 @@ static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb) dev_sw_netstats_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 = NULL; + unsigned int payload_offset = 0; + struct ovpn_peer *peer = NULL; + struct sk_buff *skb = data; + __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; + + /* crypto is done, cleanup skb CB and its members */ + if (likely(ovpn_skb_cb(skb)->ctx)) { + payload_offset = ovpn_skb_cb(skb)->ctx->payload_offset; + ks = ovpn_skb_cb(skb)->ctx->ks; + peer = ovpn_skb_cb(skb)->ctx->peer; + + aead_request_free(ovpn_skb_cb(skb)->ctx->req); + kfree(ovpn_skb_cb(skb)->ctx); + ovpn_skb_cb(skb)->ctx = NULL; + } if (unlikely(ret < 0)) goto drop; + /* PID sits after the op */ + pid = (__force __be32 *)(skb->data + OVPN_OP_SIZE_V2); + ret = ovpn_pktid_recv(&ks->pid_recv, ntohl(*pid), 0); + if (unlikely(ret < 0)) { + net_err_ratelimited("%s: PKT ID RX error: %d\n", + peer->ovpn->dev->name, ret); + goto drop; + } + + /* point to encapsulated IP packet */ + __skb_pull(skb, payload_offset); + + /* check if this is a valid datapacket that has to be delivered to the + * ovpn interface + */ + skb_reset_network_header(skb); + proto = ovpn_ip_check_protocol(skb); + if (unlikely(!proto)) { + /* check if null packet */ + if (unlikely(!pskb_may_pull(skb, 1))) { + net_info_ratelimited("%s: NULL packet received from peer %u\n", + peer->ovpn->dev->name, peer->id); + goto drop; + } + + net_info_ratelimited("%s: unsupported protocol received from peer %u\n", + peer->ovpn->dev->name, peer->id); + goto drop; + } + skb->protocol = proto; + + /* perform Reverse Path Filtering (RPF) */ + if (unlikely(!ovpn_peer_check_by_src(peer->ovpn, skb, peer))) { + if (skb_protocol_to_family(skb) == AF_INET6) + net_dbg_ratelimited("%s: RPF dropped packet from peer %u, src: %pI6c\n", + peer->ovpn->dev->name, peer->id, + &ipv6_hdr(skb)->saddr); + else + net_dbg_ratelimited("%s: RPF dropped packet from peer %u, src: %pI4\n", + peer->ovpn->dev->name, peer->id, + &ip_hdr(skb)->saddr); + goto drop; + } + ovpn_netdev_write(peer, skb); /* skb is passed to upper layer - don't free it */ skb = NULL; drop: if (unlikely(skb)) dev_core_stats_rx_dropped_inc(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); } /* pick next packet from RX queue, decrypt and forward it to the device */ void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb) { - ovpn_skb_cb(skb)->peer = peer; - ovpn_decrypt_post(skb, 0); + struct ovpn_crypto_key_slot *ks; + u8 key_id; + + /* get the key slot matching the key ID in the received packet */ + key_id = ovpn_key_id_from_skb(skb); + ks = ovpn_crypto_key_id_to_slot(&peer->crypto, key_id); + if (unlikely(!ks)) { + net_info_ratelimited("%s: no available key for peer %u, key-id: %u\n", + peer->ovpn->dev->name, peer->id, key_id); + dev_core_stats_rx_dropped_inc(peer->ovpn->dev); + kfree_skb(skb); + return; + } + + ovpn_skb_cb(skb)->ctx = NULL; + 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_peer *peer = NULL; + struct sk_buff *skb = data; + + /* encryption is happening asynchronously. This function will be + * called later by the crypto callback with a proper return value + */ + if (unlikely(ret == -EINPROGRESS)) + return; + + /* crypto is done, cleanup skb CB and its members */ + if (likely(ovpn_skb_cb(skb)->ctx)) { + peer = ovpn_skb_cb(skb)->ctx->peer; + + ovpn_crypto_key_slot_put(ovpn_skb_cb(skb)->ctx->ks); + aead_request_free(ovpn_skb_cb(skb)->ctx->req); + kfree(ovpn_skb_cb(skb)->ctx); + ovpn_skb_cb(skb)->ctx = NULL; + } if (unlikely(ret < 0)) goto err; @@ -103,13 +206,29 @@ static void ovpn_encrypt_post(struct sk_buff *skb, int ret) err: if (unlikely(skb)) dev_core_stats_tx_dropped_inc(peer->ovpn->dev); - ovpn_peer_put(peer); + if (likely(peer)) + ovpn_peer_put(peer); kfree_skb(skb); } static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb) { - ovpn_skb_cb(skb)->peer = peer; + struct ovpn_crypto_key_slot *ks; + + if (unlikely(skb->ip_summed == CHECKSUM_PARTIAL && + skb_checksum_help(skb))) { + net_warn_ratelimited("%s: cannot compute checksum for outgoing packet\n", + peer->ovpn->dev->name); + return false; + } + + /* get primary key to be used for encrypting data */ + ks = ovpn_crypto_key_slot_primary(&peer->crypto); + if (unlikely(!ks)) { + net_warn_ratelimited("%s: error while retrieving primary key slot for peer %u\n", + peer->ovpn->dev->name, peer->id); + return false; + } /* take a reference to the peer because the crypto code may run async. * ovpn_encrypt_post() will release it upon completion @@ -119,7 +238,8 @@ static bool ovpn_encrypt_one(struct ovpn_peer *peer, struct sk_buff *skb) return false; } - ovpn_encrypt_post(skb, 0); + ovpn_skb_cb(skb)->ctx = NULL; + 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 9667a0a470e0b4b427524fffb5b9b395007e5a2f..ad81dd86924689309b3299573575a1705eddaf99 100644 --- a/drivers/net/ovpn/io.h +++ b/drivers/net/ovpn/io.h @@ -14,4 +14,7 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev); void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb); +void ovpn_encrypt_post(void *data, int ret); +void ovpn_decrypt_post(void *data, int ret); + #endif /* _NET_OVPN_OVPN_H_ */ diff --git a/drivers/net/ovpn/packet.h b/drivers/net/ovpn/packet.h index 7ed146f5932a25f448af6da58738a7eae81007fe..e14c9bf464f742e6d27fe3133dd175996970845e 100644 --- a/drivers/net/ovpn/packet.h +++ b/drivers/net/ovpn/packet.h @@ -10,7 +10,7 @@ #ifndef _NET_OVPN_PACKET_H_ #define _NET_OVPN_PACKET_H_ -/* When the OpenVPN protocol is ran in AEAD mode, use +/* When the OpenVPN protocol is run in AEAD mode, use * the OpenVPN packet ID as the AEAD nonce: * * 00000005 521c3b01 4308c041 diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 6bde4bd9395b08ea3c25c58cb99e09afdf8ad097..212d1210bc564ae804899d7c3bf8c2231e732cf1 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -12,6 +12,8 @@ #include "ovpnstruct.h" #include "bind.h" +#include "pktid.h" +#include "crypto.h" #include "io.h" #include "main.h" #include "netlink.h" @@ -42,6 +44,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id) peer->vpn_addrs.ipv6 = in6addr_any; RCU_INIT_POINTER(peer->bind, NULL); + ovpn_crypto_state_init(&peer->crypto); spin_lock_init(&peer->lock); kref_init(&peer->refcount); @@ -67,6 +70,7 @@ static void ovpn_peer_release(struct ovpn_peer *peer) if (peer->sock) ovpn_socket_put(peer->sock); + ovpn_crypto_state_release(&peer->crypto); ovpn_bind_reset(peer, NULL); dst_cache_destroy(&peer->dst_cache); } @@ -277,6 +281,31 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn, return peer; } +/** + * ovpn_peer_check_by_src - check that skb source is routed via peer + * @ovpn: the openvpn instance to search + * @skb: the packet to 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_struct *ovpn, struct sk_buff *skb, + struct ovpn_peer *peer) +{ + bool match = false; + + if (ovpn->mode == OVPN_MODE_P2P) { + /* in P2P mode, no matter the destination, packets are always + * sent to the single peer listening on the other side + */ + rcu_read_lock(); + match = (peer == rcu_dereference(ovpn->peer)); + rcu_read_unlock(); + } + + return match; +} + /** * ovpn_peer_add_p2p - add peer to related tables in a P2P instance * @ovpn: the instance to add the peer to diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 37de5aff54a8df9adc449d1346b251b1feab0b1c..70d92cd5bd6348dd91956593d7d87224061b85d0 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -11,6 +11,8 @@ #define _NET_OVPN_OVPNPEER_H_ #include "bind.h" +#include "pktid.h" +#include "crypto.h" #include "socket.h" #include @@ -24,6 +26,7 @@ * @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel * @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel * @sock: the socket being used to talk to this peer + * @crypto: the crypto configuration (ciphers, keys, etc..) * @dst_cache: cache for dst_entry used to send to peer * @bind: remote peer binding * @halt: true if ovpn_peer_mark_delete was called @@ -41,6 +44,7 @@ struct ovpn_peer { struct in6_addr ipv6; } vpn_addrs; struct ovpn_socket *sock; + struct ovpn_crypto_state crypto; struct dst_cache dst_cache; struct ovpn_bind __rcu *bind; bool halt; @@ -83,5 +87,7 @@ struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn, struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id); struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn, struct sk_buff *skb); +bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb, + struct ovpn_peer *peer); #endif /* _NET_OVPN_OVPNPEER_H_ */ diff --git a/drivers/net/ovpn/pktid.c b/drivers/net/ovpn/pktid.c new file mode 100644 index 0000000000000000000000000000000000000000..96dc876356706eb6e2104cf8291c1487b4441b1f --- /dev/null +++ b/drivers/net/ovpn/pktid.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + * James Yonan + */ + +#include +#include +#include +#include +#include + +#include "ovpnstruct.h" +#include "main.h" +#include "packet.h" +#include "pktid.h" + +void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid) +{ + atomic64_set(&pid->seq_num, 1); +} + +void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr) +{ + memset(pr, 0, sizeof(*pr)); + spin_lock_init(&pr->lock); +} + +/* Packet replay detection. + * Allows ID backtrack of up to REPLAY_WINDOW_SIZE - 1. + */ +int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time) +{ + const unsigned long now = jiffies; + int ret; + + /* ID must not be zero */ + if (unlikely(pkt_id == 0)) + return -EINVAL; + + spin_lock_bh(&pr->lock); + + /* expire backtracks at or below pr->id after PKTID_RECV_EXPIRE time */ + if (unlikely(time_after_eq(now, pr->expire))) + pr->id_floor = pr->id; + + /* time changed? */ + if (unlikely(pkt_time != pr->time)) { + if (pkt_time > pr->time) { + /* time moved forward, accept */ + pr->base = 0; + pr->extent = 0; + pr->id = 0; + pr->time = pkt_time; + pr->id_floor = 0; + } else { + /* time moved backward, reject */ + ret = -ETIME; + goto out; + } + } + + if (likely(pkt_id == pr->id + 1)) { + /* well-formed ID sequence (incremented by 1) */ + pr->base = REPLAY_INDEX(pr->base, -1); + pr->history[pr->base / 8] |= (1 << (pr->base % 8)); + if (pr->extent < REPLAY_WINDOW_SIZE) + ++pr->extent; + pr->id = pkt_id; + } else if (pkt_id > pr->id) { + /* ID jumped forward by more than one */ + const unsigned int delta = pkt_id - pr->id; + + if (delta < REPLAY_WINDOW_SIZE) { + unsigned int i; + + pr->base = REPLAY_INDEX(pr->base, -delta); + pr->history[pr->base / 8] |= (1 << (pr->base % 8)); + pr->extent += delta; + if (pr->extent > REPLAY_WINDOW_SIZE) + pr->extent = REPLAY_WINDOW_SIZE; + for (i = 1; i < delta; ++i) { + unsigned int newb = REPLAY_INDEX(pr->base, i); + + pr->history[newb / 8] &= ~BIT(newb % 8); + } + } else { + pr->base = 0; + pr->extent = REPLAY_WINDOW_SIZE; + memset(pr->history, 0, sizeof(pr->history)); + pr->history[0] = 1; + } + pr->id = pkt_id; + } else { + /* ID backtrack */ + const unsigned int delta = pr->id - pkt_id; + + if (delta > pr->max_backtrack) + pr->max_backtrack = delta; + if (delta < pr->extent) { + if (pkt_id > pr->id_floor) { + const unsigned int ri = REPLAY_INDEX(pr->base, + delta); + u8 *p = &pr->history[ri / 8]; + const u8 mask = (1 << (ri % 8)); + + if (*p & mask) { + ret = -EINVAL; + goto out; + } + *p |= mask; + } else { + ret = -EINVAL; + goto out; + } + } else { + ret = -EINVAL; + goto out; + } + } + + pr->expire = now + PKTID_RECV_EXPIRE; + ret = 0; +out: + spin_unlock_bh(&pr->lock); + return ret; +} diff --git a/drivers/net/ovpn/pktid.h b/drivers/net/ovpn/pktid.h new file mode 100644 index 0000000000000000000000000000000000000000..fe02f0667e1a88a8c866fe4da4e5cebfba9efbcf --- /dev/null +++ b/drivers/net/ovpn/pktid.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + * James Yonan + */ + +#ifndef _NET_OVPN_OVPNPKTID_H_ +#define _NET_OVPN_OVPNPKTID_H_ + +#include "packet.h" + +/* If no packets received for this length of time, set a backtrack floor + * at highest received packet ID thus far. + */ +#define PKTID_RECV_EXPIRE (30 * HZ) + +/* Packet-ID state for transmitter */ +struct ovpn_pktid_xmit { + atomic64_t seq_num; +}; + +/* replay window sizing in bytes = 2^REPLAY_WINDOW_ORDER */ +#define REPLAY_WINDOW_ORDER 8 + +#define REPLAY_WINDOW_BYTES BIT(REPLAY_WINDOW_ORDER) +#define REPLAY_WINDOW_SIZE (REPLAY_WINDOW_BYTES * 8) +#define REPLAY_INDEX(base, i) (((base) + (i)) & (REPLAY_WINDOW_SIZE - 1)) + +/* Packet-ID state for receiver. + * Other than lock member, can be zeroed to initialize. + */ +struct ovpn_pktid_recv { + /* "sliding window" bitmask of recent packet IDs received */ + u8 history[REPLAY_WINDOW_BYTES]; + /* bit position of deque base in history */ + unsigned int base; + /* extent (in bits) of deque in history */ + unsigned int extent; + /* expiration of history in jiffies */ + unsigned long expire; + /* highest sequence number received */ + u32 id; + /* highest time stamp received */ + u32 time; + /* we will only accept backtrack IDs > id_floor */ + u32 id_floor; + unsigned int max_backtrack; + /* protects entire pktd ID state */ + spinlock_t lock; +}; + +/* Get the next packet ID for xmit */ +static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, u32 *pktid) +{ + const s64 seq_num = atomic64_fetch_add_unless(&pid->seq_num, 1, + 0x100000000LL); + /* when the 32bit space is over, we return an error because the packet + * ID is used to create the cipher IV and we do not want to reuse the + * same value more than once + */ + if (unlikely(seq_num == 0x100000000LL)) + return -ERANGE; + + *pktid = (u32)seq_num; + + return 0; +} + +/* Write 12-byte AEAD IV to dest */ +static inline void ovpn_pktid_aead_write(const u32 pktid, + const struct ovpn_nonce_tail *nt, + unsigned char *dest) +{ + *(__force __be32 *)(dest) = htonl(pktid); + BUILD_BUG_ON(4 + sizeof(struct ovpn_nonce_tail) != NONCE_SIZE); + memcpy(dest + 4, nt->u8, sizeof(struct ovpn_nonce_tail)); +} + +void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid); +void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr); + +int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time); + +#endif /* _NET_OVPN_OVPNPKTID_H_ */ diff --git a/drivers/net/ovpn/proto.h b/drivers/net/ovpn/proto.h index 69604cf26bbf82539ee5cd5a7ac9c23920f555de..32af6b8e574381fb719a1b3b9de3ae1071cc4846 100644 --- a/drivers/net/ovpn/proto.h +++ b/drivers/net/ovpn/proto.h @@ -72,4 +72,35 @@ static inline u32 ovpn_peer_id_from_skb(const struct sk_buff *skb, u16 offset) return ntohl(*(__be32 *)(skb->data + offset)) & OVPN_PEER_ID_MASK; } +/** + * ovpn_key_id_from_skb - extract key ID from the skb head + * @skb: the packet to extract the key ID code from + * + * Note: this function assumes that the skb head was pulled enough + * to access the first byte. + * + * Return: the key ID + */ +static inline u8 ovpn_key_id_from_skb(const struct sk_buff *skb) +{ + return *skb->data & OVPN_KEY_ID_MASK; +} + +/** + * ovpn_opcode_compose - combine OP code, key ID and peer ID to wire format + * @opcode: the OP code + * @key_id: the key ID + * @peer_id: the peer ID + * + * Return: a 4 bytes integer obtained combining all input values following the + * OpenVPN wire format. This integer can then be written to the packet header. + */ +static inline u32 ovpn_opcode_compose(u8 opcode, u8 key_id, u32 peer_id) +{ + const u8 op = (opcode << OVPN_OPCODE_SHIFT) | + (key_id & OVPN_KEY_ID_MASK); + + return (op << 24) | (peer_id & OVPN_PEER_ID_MASK); +} + #endif /* _NET_OVPN_OVPNPROTO_H_ */ diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h index e070fe6f448c0b7a9631394ebef4554f6348ef44..3f94efae20fb58772c1a4c47df5bae8bb11ad322 100644 --- a/drivers/net/ovpn/skb.h +++ b/drivers/net/ovpn/skb.h @@ -17,8 +17,17 @@ #include #include -struct ovpn_cb { +struct ovpn_cb_ctx { struct ovpn_peer *peer; + struct sk_buff *skb; + struct ovpn_crypto_key_slot *ks; + struct aead_request *req; + struct scatterlist sg[MAX_SKB_FRAGS + 2]; + unsigned int payload_offset; +}; + +struct ovpn_cb { + struct ovpn_cb_ctx *ctx; }; static inline struct ovpn_cb *ovpn_skb_cb(struct sk_buff *skb) From patchwork Wed Oct 2 09:02:26 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819518 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wm1-f52.google.com (mail-wm1-f52.google.com [209.85.128.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F219619F410 for ; Wed, 2 Oct 2024 09:03:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859815; cv=none; b=NHdHlPFezHARLxyT1Ufrs/zlOnlWt4Z64p1UtLW/D1x5d2OwEz1B+iAL48xjer8dDV5Leb/ZLV3Rawgi2e0wTb4qPWKGCJ3O/yWzgX0odvhQ4TKgMl5CBPu9kbgipElyi3a1bGHcUm4iYvgSBYrYnqZaW/XBjcm8fY18STAdoJU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859815; c=relaxed/simple; bh=MbfNhiX/VC5KXRAabUqnselsAbcgMGYh8QbiPT72LO0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=iUz3ezo4f3oNrJez67LS4s5Cu9YaGE1p0o7DdagcIsiZrNfODBOcnwkp+Pqf2r6EmITw5gaLbPPkIl2hZhC/P9GSoC+iYyYiyH+SwG7JUZDzaNy+NthQA/gNhLolOI75Qe2lCBEvtEnKgGa5vSKi6wuFUNYRhBm/O9aAnT3dSd0= 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=E8LQ5tX3; arc=none smtp.client-ip=209.85.128.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="E8LQ5tX3" Received: by mail-wm1-f52.google.com with SMTP id 5b1f17b1804b1-42cb5b3c57eso59063645e9.2 for ; Wed, 02 Oct 2024 02:03:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859811; x=1728464611; 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=rDdskZURNVON1L2hVJSRcyI2dGmFk6sRhpefpO0czDE=; b=E8LQ5tX3G9dDMskLC6QfO1o0ZzAzZgmRHkMSAZLHhYqx/T+MNEVUF/QHmgPS/TrynK Kr5ImH8Q8lebv0vUV7t/khm6o0p60BEypymMERnCFB0Ik38+NSemh+HCbG72H2EDUMzr ZYyJB4fF83/eMkeXJSpocAqSkcQAOGJGE1hvJDRHJUoDb8Kci13HgtXgWM8YzqTOEbJt sz4GaMx8Y/rVdL3cUS1EKsfdjaFgJpaBgmmrZ6f5EUa5GCC4wQuSzMxt8QiMDrrO33Wc HQJxagKDomVIWAnIUHPeXyFZn3+ivKqh71i8KQZq/EGMmnWxOtcIsxKfpH2qTq7QSKtv v4qw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859811; x=1728464611; 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=rDdskZURNVON1L2hVJSRcyI2dGmFk6sRhpefpO0czDE=; b=TeOwlpBAKDUcxA4LdiCY9+63QH5hIH28KNa6czyGwU7HwO/PkmClirO2hN1LwGXxkp IbfEgFcfmBQfHd2b869BROV+XO91Y36spuKsBkmHQgGrSePJ88fhD3BbFyAjnCWwpuJB YbFYJ8yrbVX24c+nkFwvM1pT/bfi6ek20NP4EbC2aOLYWbXbWOdQRrkgbhKgHfWTSHH5 oHCMorJPzElNXXz7CeJC2HjesXTx3D/4wLSnBeiRmIpR5Tr9GimZwes5G/z55pg4vYK3 nzEiNTerQUndiwkSXQ6vnVymNuA3WALkwkilh/ZnTVD7e9XI0lHM0frRr03qtOAYg7OH JKCA== X-Gm-Message-State: AOJu0Yz+uy1wKJh+psZ7yA3PdX9j+mdq7l6A7+1UP+5GnY5wp3SwAcfq sSsaBd6RugNh6h1Onhmr8uQ/RnPIfGekkLHraXFzbraPEXH07BWwqQeRQdmdhZo= X-Google-Smtp-Source: AGHT+IFaQk2X/pcKsqICPm06ryDSXl3HPbj+9pjaDzM2ckG1DsCU6n5uxZ6txvEAYBmsoKa19RkMHw== X-Received: by 2002:adf:f045:0:b0:374:be10:3eb7 with SMTP id ffacd0b85a97d-37cfba0a7a5mr1574186f8f.38.1727859811157; Wed, 02 Oct 2024 02:03:31 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:30 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:26 +0200 Subject: [PATCH net-next v8 12/24] ovpn: store tunnel and transport statistics Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-12-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=9036; i=antonio@openvpn.net; h=from:subject:message-id; bh=MbfNhiX/VC5KXRAabUqnselsAbcgMGYh8QbiPT72LO0=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxSmFiehZfMVtsZa9b/4zO8X45neXujJirJu x21l4bckJCJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUgAKCRALcOU6oDjV h6f/B/9IGSZ/T/muJKO+acZyYgSeVaJcB1wGsYujsfR4mqUmTFkdbgjd8AK7bZ3IgMJ+GThlsXJ r69oFJg7Fm0EvHaMGxYpYM+eeUspsQncUOgaSwVycT4i94Da9EjsXVN/3tK2e8L4AimwD6s2X/v iOG43DglaaHfuXsjnbEHM5L9AJt9txs3mMliRwkZFpkkHcBCo9A3tKvrORnronjVRZ6rCibXDGZ t7/pmMl3/4qYCnAwJ5g0W47+ogKNSMbRaFZtX91sai64nBFEyMcmyMzshzyZhOr12KazG9EXVfw DU3k09bw8EskW3aQCfK8N7iJK95f0/mQgdQ8/jPvGB9RvZwU X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org Byte/packet counters for in-tunnel and transport streams are now initialized and updated as needed. To be exported via netlink. Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/Makefile | 1 + drivers/net/ovpn/crypto_aead.c | 2 ++ drivers/net/ovpn/io.c | 12 +++++++++++ drivers/net/ovpn/peer.c | 3 +++ drivers/net/ovpn/peer.h | 8 +++++++ drivers/net/ovpn/skb.h | 1 + drivers/net/ovpn/stats.c | 21 +++++++++++++++++++ drivers/net/ovpn/stats.h | 47 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 95 insertions(+) diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index ccdaeced1982c851475657860a005ff2b9dfbd13..d43fda72646bdc7644d9a878b56da0a0e5680c98 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -17,4 +17,5 @@ ovpn-y += netlink-gen.o ovpn-y += peer.o ovpn-y += pktid.o ovpn-y += socket.o +ovpn-y += stats.o ovpn-y += udp.o diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 97134ac679c67a3d44a0bf49c2ddf058fd7c0e99..6599c8550390a60d3b0fb9c144beb8c7871bc320 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -128,6 +128,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, aead_request_set_crypt(req, sg, sg, skb->len - head_size, iv); aead_request_set_ad(req, OVPN_OP_SIZE_V2 + NONCE_WIRE_SIZE); + ovpn_skb_cb(skb)->ctx->orig_len = skb->len; ovpn_skb_cb(skb)->ctx->peer = peer; ovpn_skb_cb(skb)->ctx->req = req; ovpn_skb_cb(skb)->ctx->ks = ks; @@ -216,6 +217,7 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, aead_request_set_ad(req, NONCE_WIRE_SIZE + OVPN_OP_SIZE_V2); + ovpn_skb_cb(skb)->ctx->orig_len = skb->len; ovpn_skb_cb(skb)->ctx->payload_offset = payload_offset; ovpn_skb_cb(skb)->ctx->peer = peer; ovpn_skb_cb(skb)->ctx->req = req; diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index a75aa301b99e6abff04d5cb289b26b5979906ae5..d54af1a0f03a33b03001a041e62554b35a7f5ebc 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "ovpnstruct.h" #include "peer.h" @@ -20,6 +21,7 @@ #include "crypto_aead.h" #include "netlink.h" #include "proto.h" +#include "socket.h" #include "udp.h" #include "skb.h" @@ -66,6 +68,7 @@ void ovpn_decrypt_post(void *data, int ret) unsigned int payload_offset = 0; struct ovpn_peer *peer = NULL; struct sk_buff *skb = data; + unsigned int orig_len = 0; __be16 proto; __be32 *pid; @@ -80,6 +83,7 @@ void ovpn_decrypt_post(void *data, int ret) payload_offset = ovpn_skb_cb(skb)->ctx->payload_offset; ks = ovpn_skb_cb(skb)->ctx->ks; peer = ovpn_skb_cb(skb)->ctx->peer; + orig_len = ovpn_skb_cb(skb)->ctx->orig_len; aead_request_free(ovpn_skb_cb(skb)->ctx->req); kfree(ovpn_skb_cb(skb)->ctx); @@ -133,6 +137,10 @@ void ovpn_decrypt_post(void *data, int ret) goto drop; } + /* increment RX stats */ + ovpn_peer_stats_increment_rx(&peer->vpn_stats, skb->len); + ovpn_peer_stats_increment_rx(&peer->link_stats, orig_len); + ovpn_netdev_write(peer, skb); /* skb is passed to upper layer - don't free it */ skb = NULL; @@ -171,6 +179,7 @@ void ovpn_encrypt_post(void *data, int ret) { struct ovpn_peer *peer = NULL; struct sk_buff *skb = data; + unsigned int orig_len = 0; /* encryption is happening asynchronously. This function will be * called later by the crypto callback with a proper return value @@ -181,6 +190,7 @@ void ovpn_encrypt_post(void *data, int ret) /* crypto is done, cleanup skb CB and its members */ if (likely(ovpn_skb_cb(skb)->ctx)) { peer = ovpn_skb_cb(skb)->ctx->peer; + orig_len = ovpn_skb_cb(skb)->ctx->orig_len; ovpn_crypto_key_slot_put(ovpn_skb_cb(skb)->ctx->ks); aead_request_free(ovpn_skb_cb(skb)->ctx->req); @@ -192,6 +202,8 @@ void ovpn_encrypt_post(void *data, int ret) goto err; skb_mark_not_on_list(skb); + ovpn_peer_stats_increment_tx(&peer->link_stats, skb->len); + ovpn_peer_stats_increment_tx(&peer->vpn_stats, orig_len); switch (peer->sock->sock->sk->sk_protocol) { case IPPROTO_UDP: diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 212d1210bc564ae804899d7c3bf8c2231e732cf1..6d34c56a4a51ce407b20b6175e6140aafc4b4e33 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -18,6 +18,7 @@ #include "main.h" #include "netlink.h" #include "peer.h" +#include "socket.h" /** * ovpn_peer_new - allocate and initialize a new peer object @@ -47,6 +48,8 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id) ovpn_crypto_state_init(&peer->crypto); spin_lock_init(&peer->lock); kref_init(&peer->refcount); + ovpn_peer_stats_init(&peer->vpn_stats); + ovpn_peer_stats_init(&peer->link_stats); ret = dst_cache_init(&peer->dst_cache, GFP_KERNEL); if (ret < 0) { diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 70d92cd5bd6348dd91956593d7d87224061b85d0..89f6face71871e09f8efd533a949f0d9358a1342 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -10,10 +10,14 @@ #ifndef _NET_OVPN_OVPNPEER_H_ #define _NET_OVPN_OVPNPEER_H_ +#include +#include + #include "bind.h" #include "pktid.h" #include "crypto.h" #include "socket.h" +#include "stats.h" #include #include @@ -30,6 +34,8 @@ * @dst_cache: cache for dst_entry used to send to peer * @bind: remote peer binding * @halt: true if ovpn_peer_mark_delete was called + * @vpn_stats: per-peer in-VPN TX/RX stays + * @link_stats: per-peer link/transport TX/RX stats * @delete_reason: why peer was deleted (i.e. timeout, transport error, ..) * @lock: protects binding to peer (bind) * @refcount: reference counter @@ -48,6 +54,8 @@ struct ovpn_peer { struct dst_cache dst_cache; struct ovpn_bind __rcu *bind; bool halt; + struct ovpn_peer_stats vpn_stats; + struct ovpn_peer_stats link_stats; enum ovpn_del_peer_reason delete_reason; spinlock_t lock; /* protects bind */ struct kref refcount; diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h index 3f94efae20fb58772c1a4c47df5bae8bb11ad322..3d9f38b081a6531456a8fba3b15ed3e7cae4240c 100644 --- a/drivers/net/ovpn/skb.h +++ b/drivers/net/ovpn/skb.h @@ -23,6 +23,7 @@ struct ovpn_cb_ctx { struct ovpn_crypto_key_slot *ks; struct aead_request *req; struct scatterlist sg[MAX_SKB_FRAGS + 2]; + size_t orig_len; unsigned int payload_offset; }; diff --git a/drivers/net/ovpn/stats.c b/drivers/net/ovpn/stats.c new file mode 100644 index 0000000000000000000000000000000000000000..a383842c3449b73694c318837b0b92eb9afaec22 --- /dev/null +++ b/drivers/net/ovpn/stats.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + */ + +#include + +#include "stats.h" + +void ovpn_peer_stats_init(struct ovpn_peer_stats *ps) +{ + atomic64_set(&ps->rx.bytes, 0); + atomic64_set(&ps->rx.packets, 0); + + atomic64_set(&ps->tx.bytes, 0); + atomic64_set(&ps->tx.packets, 0); +} diff --git a/drivers/net/ovpn/stats.h b/drivers/net/ovpn/stats.h new file mode 100644 index 0000000000000000000000000000000000000000..868f49d25eaa8fef04a02a61c363d95f9c9ef80a --- /dev/null +++ b/drivers/net/ovpn/stats.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: James Yonan + * Antonio Quartulli + * Lev Stipakov + */ + +#ifndef _NET_OVPN_OVPNSTATS_H_ +#define _NET_OVPN_OVPNSTATS_H_ + +/* one stat */ +struct ovpn_peer_stat { + atomic64_t bytes; + atomic64_t packets; +}; + +/* rx and tx stats combined */ +struct ovpn_peer_stats { + struct ovpn_peer_stat rx; + struct ovpn_peer_stat tx; +}; + +void ovpn_peer_stats_init(struct ovpn_peer_stats *ps); + +static inline void ovpn_peer_stats_increment(struct ovpn_peer_stat *stat, + const unsigned int n) +{ + atomic64_add(n, &stat->bytes); + atomic64_inc(&stat->packets); +} + +static inline void ovpn_peer_stats_increment_rx(struct ovpn_peer_stats *stats, + const unsigned int n) +{ + ovpn_peer_stats_increment(&stats->rx, n); +} + +static inline void ovpn_peer_stats_increment_tx(struct ovpn_peer_stats *stats, + const unsigned int n) +{ + ovpn_peer_stats_increment(&stats->tx, n); +} + +#endif /* _NET_OVPN_OVPNSTATS_H_ */ From patchwork Wed Oct 2 09:02:27 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819520 X-Patchwork-Delegate: kuba@kernel.org 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 0B7EB19F464 for ; Wed, 2 Oct 2024 09:03:33 +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=1727859817; cv=none; b=eUn+buOICcnE6NlTxq5gaNn+EeyWJbKWbUYM1T+21cp9DZsvlCzXm3n9nGpq+9f3kEG7b8k0kqgiMzWcmdrOHPsqq+5B9LlOcQC2PyLEsPk2K4MV0j/wRijcnXDg5IZ9WjLmcYPOdMQ9SM3PTpTrrjpCnHOB8y6VZgB94ZQzw2c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859817; c=relaxed/simple; bh=LsN0kcYSqLCoUfF1EoZ4qP+bVQfuo1AhqIQmIQCdYLU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=GANTyg9iS9Acq5li4R7CFKJGttN6s2RlNsfyaf0rR1Gc21NppP3Z85cdgkrA7IyOMkDeVhKZ0/DoUvPKiXXhnO4nBvCZIdkbUMZNQe3XbWIhK8YYshfBj4zJrVn+MwEfHzaD2XTbxAAWMW0MSGD3x0rdlIogMyz5N8MUpN3L3UI= 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=Vp7ZK0VV; 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="Vp7ZK0VV" Received: by mail-wm1-f48.google.com with SMTP id 5b1f17b1804b1-42cafda818aso62258275e9.2 for ; Wed, 02 Oct 2024 02:03:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859812; x=1728464612; 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=pLhkI0Q6L67BHoCzeNj9D9iPQT1B5QKWnqXp7U4Klcs=; b=Vp7ZK0VVkIuQD/FbaMxV1U4Wayold3+PWbqvREMch/SMroK59wNRo4wpKUWalXjnFv u9m7YsKsCOpFO0OAv/f6qdZpbpS5Exz60CtlCdvRVTw1+IQSwtl4+1Fydo4skNYjqjX/ nxThGK73L9uu3Ye9Jpn4W0dE6k2gZU57ULpT5l8D1DnojLy00KhWKjQgei1WoQTRzpx1 l3ycdD5FrqXxGx9Zf4K/xDXRiyHh0fjRu6UWCaGe457cs4gDPgFMI1O9xSzDJhxFGJDe N4oUMmk9T069S5L4cXAvrA7zUGicmxPGPN+1MSTB/z8mLDo8C96aeM+Xyk6zv8FEm87b 17+g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859812; x=1728464612; 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=pLhkI0Q6L67BHoCzeNj9D9iPQT1B5QKWnqXp7U4Klcs=; b=FtQS6myKBZ/Y/20U5t9ocIYwRAKyHz0H6fnxMNeeJfbbeuDE07TGjyHctRZzZrHsFT llZuAHcgzPJlnU2XWekZ0uDI+uMjdKRpWetXEoRHje4VA/tcrM6PuK0YtTMXCyOIKNSO kFcj/FfFYSYmPOLBbzDOq6iEZpgZ3DCYAokn3NmXql1z+SbPFkX6Jn4MFft1EnTAPnzW kBVgREEwkjUNplxLtHSlfvwiNG1NzE+m8N42aMFGZQonpkJMkB1AdATURGPmCF1qPMBa xcfaoQEolINcw1KgW0ipxOJ9KMnm0G/SfsspxihAH7CcmAxqxw/gB+RDfkRoiAeTy1IK z67Q== X-Gm-Message-State: AOJu0Yxmhguibs5fzndsql4EF1gn3x5ckyEykZJF5rLvEVkUvko/MRdY RKaIZRMQCI5NJIl/QIUUiXYrThzBQjM3QmggSDK7eSDwJxMp7pYgPLRGdOTq7WU= X-Google-Smtp-Source: AGHT+IE0cpuGolf8pT/ttAlbalzFE49a7uPBAN7yBGV+RhtMA3pT1VKsmow82iL4TnWXmUnOtxjHUw== X-Received: by 2002:a05:6000:cd0:b0:374:c287:929b with SMTP id ffacd0b85a97d-37cfb8b1895mr1636980f8f.4.1727859812164; Wed, 02 Oct 2024 02:03:32 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:31 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:27 +0200 Subject: [PATCH net-next v8 13/24] ovpn: implement TCP transport Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-13-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=22637; i=antonio@openvpn.net; h=from:subject:message-id; bh=LsN0kcYSqLCoUfF1EoZ4qP+bVQfuo1AhqIQmIQCdYLU=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxSvG6PW+tvJO7aRvviiPn9G625ppsjro59D HFcZ/sBtACJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUgAKCRALcOU6oDjV h/pXB/9Jyiwsvqy9I6cuPOKk5LOi7DvfOsAvE7GqHismocbqZwxM2pQjgTiwR/vs46aIhXjzB8z XCIJjE2jJzmMUuQ2SJ2ZdJtmM3nLbSSFVx4i0Z4DvPw1FFDRQFNPMscUbxUQekymC4QfkMp3Pp2 ZNPMXBsOR44/KIDeGUOsz2nvB9nPOo1ab5rVAk4+FhzGMT43mN0SAPLBfkDW6JS0Pyaxqk82RvJ hyGK/82hCFfSYMdyjI1RSnY/MCncE488zaZYzWSD9CzPVbiEk2Dv+wjAGSZopFoPycjpWYIAtAF K3AG60MDKNOk6L0ryIHaQLSdfUa3qh9juTsGa2veOBdON6Ia X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org With this change ovpn is allowed to communicate to peers also via TCP. Parsing of incoming messages is implemented through the strparser API. Signed-off-by: Antonio Quartulli --- drivers/net/Kconfig | 1 + drivers/net/ovpn/Makefile | 1 + drivers/net/ovpn/io.c | 4 + drivers/net/ovpn/main.c | 3 + drivers/net/ovpn/peer.h | 37 ++++ drivers/net/ovpn/socket.c | 27 ++- drivers/net/ovpn/socket.h | 7 +- drivers/net/ovpn/tcp.c | 506 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/tcp.h | 43 ++++ 9 files changed, 625 insertions(+), 4 deletions(-) diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 0055bcd2356c70bd9cfd8a944f549817dab8154f..cef1dd4dccad37c7b60a00aa41ab258c8df0807c 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -118,6 +118,7 @@ config WIREGUARD_DEBUG config OVPN tristate "OpenVPN data channel offload" depends on NET && INET + select STREAM_PARSER select NET_UDP_TUNNEL select DST_CACHE select CRYPTO diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index d43fda72646bdc7644d9a878b56da0a0e5680c98..f4d4bd87c851c8dd5b81e357315c4b22de4bd092 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -18,4 +18,5 @@ ovpn-y += peer.o ovpn-y += pktid.o ovpn-y += socket.o ovpn-y += stats.o +ovpn-y += tcp.o ovpn-y += udp.o diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index d54af1a0f03a33b03001a041e62554b35a7f5ebc..985f8bba6f1355b9f164e53f11575fa104133d43 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -22,6 +22,7 @@ #include "netlink.h" #include "proto.h" #include "socket.h" +#include "tcp.h" #include "udp.h" #include "skb.h" @@ -209,6 +210,9 @@ void ovpn_encrypt_post(void *data, int ret) case IPPROTO_UDP: ovpn_udp_send_skb(peer->ovpn, peer, skb); break; + case IPPROTO_TCP: + ovpn_tcp_send_skb(peer, skb); + break; default: /* no transport configured yet */ goto err; diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 670e66f8f750086de5d48e4708721d102c149a2e..cb5cc3d4b5620f5a1e401e06e52e67c32e57aa78 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -25,6 +25,7 @@ #include "io.h" #include "packet.h" #include "peer.h" +#include "tcp.h" /* Driver info */ #define DRV_DESCRIPTION "OpenVPN data channel offload (ovpn)" @@ -270,6 +271,8 @@ static int __init ovpn_init(void) goto unreg_rtnl; } + ovpn_tcp_init(); + return 0; unreg_rtnl: diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 89f6face71871e09f8efd533a949f0d9358a1342..86d4696b1529c45025b63d2973fc48ca81ca8f63 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -11,6 +11,7 @@ #define _NET_OVPN_OVPNPEER_H_ #include +#include #include #include "bind.h" @@ -30,6 +31,18 @@ * @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel * @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel * @sock: the socket being used to talk to this peer + * @tcp: keeps track of TCP specific state + * @tcp.strp: stream parser context (TCP only) + * @tcp.tx_work: work for deferring outgoing packet processing (TCP only) + * @tcp.user_queue: received packets that have to go to userspace (TCP only) + * @tcp.tx_in_progress: true if TX is already ongoing (TCP only) + * @tcp.out_msg.skb: packet scheduled for sending (TCP only) + * @tcp.out_msg.offset: offset where next send should start (TCP only) + * @tcp.out_msg.len: remaining data to send within packet (TCP only) + * @tcp.sk_cb.sk_data_ready: pointer to original cb (TCP only) + * @tcp.sk_cb.sk_write_space: pointer to original cb (TCP only) + * @tcp.sk_cb.prot: pointer to original prot object (TCP only) + * @tcp.sk_cb.ops: pointer to the original prot_ops object (TCP only) * @crypto: the crypto configuration (ciphers, keys, etc..) * @dst_cache: cache for dst_entry used to send to peer * @bind: remote peer binding @@ -50,6 +63,30 @@ struct ovpn_peer { struct in6_addr ipv6; } vpn_addrs; struct ovpn_socket *sock; + + /* state of the TCP reading. Needed to keep track of how much of a + * single packet has already been read from the stream and how much is + * missing + */ + struct { + struct strparser strp; + struct work_struct tx_work; + struct sk_buff_head user_queue; + bool tx_in_progress; + + struct { + struct sk_buff *skb; + int offset; + int len; + } out_msg; + + struct { + void (*sk_data_ready)(struct sock *sk); + void (*sk_write_space)(struct sock *sk); + struct proto *prot; + const struct proto_ops *ops; + } sk_cb; + } tcp; struct ovpn_crypto_state crypto; struct dst_cache dst_cache; struct ovpn_bind __rcu *bind; diff --git a/drivers/net/ovpn/socket.c b/drivers/net/ovpn/socket.c index 964b566de69f4132806a969a455cec7f6059a0bd..8bb3b0cb18ec004fbae396cb2b152793653877d2 100644 --- a/drivers/net/ovpn/socket.c +++ b/drivers/net/ovpn/socket.c @@ -15,6 +15,7 @@ #include "io.h" #include "peer.h" #include "socket.h" +#include "tcp.h" #include "udp.h" static void ovpn_socket_detach(struct socket *sock) @@ -24,6 +25,8 @@ static void ovpn_socket_detach(struct socket *sock) if (sock->sk->sk_protocol == IPPROTO_UDP) ovpn_udp_socket_detach(sock); + else if (sock->sk->sk_protocol == IPPROTO_TCP) + ovpn_tcp_socket_detach(sock); sockfd_put(sock); } @@ -70,6 +73,8 @@ static int ovpn_socket_attach(struct socket *sock, struct ovpn_peer *peer) if (sock->sk->sk_protocol == IPPROTO_UDP) ret = ovpn_udp_socket_attach(sock, peer->ovpn); + else if (sock->sk->sk_protocol == IPPROTO_TCP) + ret = ovpn_tcp_socket_attach(sock, peer); return ret; } @@ -131,14 +136,30 @@ struct ovpn_socket *ovpn_socket_new(struct socket *sock, struct ovpn_peer *peer) } ovpn_sock = kzalloc(sizeof(*ovpn_sock), GFP_KERNEL); - if (!ovpn_sock) - return ERR_PTR(-ENOMEM); + if (!ovpn_sock) { + ret = -ENOMEM; + goto err; + } - ovpn_sock->ovpn = peer->ovpn; ovpn_sock->sock = sock; kref_init(&ovpn_sock->refcount); + /* TCP sockets are per-peer, therefore they are linked to their unique + * peer + */ + if (sock->sk->sk_protocol == IPPROTO_TCP) { + ovpn_sock->peer = peer; + } else { + /* in UDP we only link the ovpn instance since the socket is + * shared among multiple peers + */ + ovpn_sock->ovpn = peer->ovpn; + } + rcu_assign_sk_user_data(sock->sk, ovpn_sock); return ovpn_sock; +err: + ovpn_socket_detach(sock); + return ERR_PTR(ret); } diff --git a/drivers/net/ovpn/socket.h b/drivers/net/ovpn/socket.h index 5ad9c5073b085482da95ee8ebf40acf20bf2e4b3..e9f0035d706eb081f60db0e0d7c62cc27185b8fb 100644 --- a/drivers/net/ovpn/socket.h +++ b/drivers/net/ovpn/socket.h @@ -20,12 +20,17 @@ struct ovpn_peer; /** * struct ovpn_socket - a kernel socket referenced in the ovpn code * @ovpn: ovpn instance owning this socket (UDP only) + * @peer: unique peer transmitting over this socket (TCP only) * @sock: the low level sock object * @refcount: amount of contexts currently referencing this object * @rcu: member used to schedule RCU destructor callback */ struct ovpn_socket { - struct ovpn_struct *ovpn; + union { + struct ovpn_struct *ovpn; + struct ovpn_peer *peer; + }; + struct socket *sock; struct kref refcount; struct rcu_head rcu; diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c new file mode 100644 index 0000000000000000000000000000000000000000..f2f8ad53451405694d9fc1acd7b87c3fa8213f6c --- /dev/null +++ b/drivers/net/ovpn/tcp.c @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#include +#include +#include +#include +#include +#include + +#include "ovpnstruct.h" +#include "main.h" +#include "io.h" +#include "packet.h" +#include "peer.h" +#include "proto.h" +#include "skb.h" +#include "socket.h" +#include "tcp.h" + +static struct proto ovpn_tcp_prot __ro_after_init; +static struct proto_ops ovpn_tcp_ops __ro_after_init; +static struct proto ovpn_tcp6_prot; +static struct proto_ops ovpn_tcp6_ops; +static DEFINE_MUTEX(tcp6_prot_mutex); + +static int ovpn_tcp_parse(struct strparser *strp, struct sk_buff *skb) +{ + struct strp_msg *rxm = strp_msg(skb); + __be16 blen; + u16 len; + int err; + + /* when packets are written to the TCP stream, they are prepended with + * two bytes indicating the actual packet size. + * Here we read those two bytes and move the skb data pointer to the + * beginning of the packet + */ + + if (skb->len < rxm->offset + 2) + return 0; + + err = skb_copy_bits(skb, rxm->offset, &blen, sizeof(blen)); + if (err < 0) + return err; + + len = be16_to_cpu(blen); + if (len < 2) + return -EINVAL; + + return len + 2; +} + +/* queue skb for sending to userspace via recvmsg on the socket */ +static void ovpn_tcp_to_userspace(struct ovpn_socket *sock, struct sk_buff *skb) +{ + struct sock *sk = sock->sock->sk; + + skb_set_owner_r(skb, sk); + memset(skb->cb, 0, sizeof(skb->cb)); + skb_queue_tail(&sock->peer->tcp.user_queue, skb); + sock->peer->tcp.sk_cb.sk_data_ready(sk); +} + +static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb) +{ + struct ovpn_peer *peer = container_of(strp, struct ovpn_peer, tcp.strp); + struct strp_msg *msg = strp_msg(skb); + size_t pkt_len = msg->full_len - 2; + size_t off = msg->offset + 2; + + /* ensure skb->data points to the beginning of the openvpn packet */ + if (!pskb_pull(skb, off)) { + net_warn_ratelimited("%s: packet too small\n", + peer->ovpn->dev->name); + goto err; + } + + /* strparser does not trim the skb for us, therefore we do it now */ + if (pskb_trim(skb, pkt_len) != 0) { + net_warn_ratelimited("%s: trimming skb failed\n", + peer->ovpn->dev->name); + goto err; + } + + /* we need the first byte of data to be accessible + * to extract the opcode and the key ID later on + */ + if (!pskb_may_pull(skb, 1)) { + net_warn_ratelimited("%s: packet too small to fetch opcode\n", + peer->ovpn->dev->name); + goto err; + } + + /* DATA_V2 packets are handled in kernel, the rest goes to user space */ + if (likely(ovpn_opcode_from_skb(skb, 0) == OVPN_DATA_V2)) { + /* hold reference to peer as required by ovpn_recv(). + * + * NOTE: in this context we should already be holding a + * reference to this peer, therefore ovpn_peer_hold() is + * not expected to fail + */ + if (WARN_ON(!ovpn_peer_hold(peer))) + goto err; + + ovpn_recv(peer, skb); + } else { + /* The packet size header must be there when sending the packet + * to userspace, therefore we put it back + */ + skb_push(skb, 2); + ovpn_tcp_to_userspace(peer->sock, skb); + } + + return; +err: + netdev_err(peer->ovpn->dev, + "cannot process incoming TCP data for peer %u\n", peer->id); + dev_core_stats_rx_dropped_inc(peer->ovpn->dev); + kfree_skb(skb); + ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_TRANSPORT_ERROR); +} + +static int ovpn_tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, + int flags, int *addr_len) +{ + int err = 0, off, copied = 0, ret; + struct ovpn_socket *sock; + struct ovpn_peer *peer; + struct sk_buff *skb; + + rcu_read_lock(); + sock = rcu_dereference_sk_user_data(sk); + if (!sock || !sock->peer) { + rcu_read_unlock(); + return -EBADF; + } + /* we take a reference to the peer linked to this TCP socket, because + * in turn the peer holds a reference to the socket itself. + * By doing so we also ensure that the peer stays alive along with + * the socket while executing this function + */ + ovpn_peer_hold(sock->peer); + peer = sock->peer; + rcu_read_unlock(); + + skb = __skb_recv_datagram(sk, &peer->tcp.user_queue, flags, &off, &err); + if (!skb) { + if (err == -EAGAIN && sk->sk_shutdown & RCV_SHUTDOWN) { + ret = 0; + goto out; + } + ret = err; + goto out; + } + + copied = len; + if (copied > skb->len) + copied = skb->len; + else if (copied < skb->len) + msg->msg_flags |= MSG_TRUNC; + + err = skb_copy_datagram_msg(skb, 0, msg, copied); + if (unlikely(err)) { + kfree_skb(skb); + ret = err; + goto out; + } + + if (flags & MSG_TRUNC) + copied = skb->len; + kfree_skb(skb); + ret = copied; +out: + ovpn_peer_put(peer); + return ret; +} + +void ovpn_tcp_socket_detach(struct socket *sock) +{ + struct ovpn_socket *ovpn_sock; + struct ovpn_peer *peer; + + if (!sock) + return; + + rcu_read_lock(); + ovpn_sock = rcu_dereference_sk_user_data(sock->sk); + + if (!ovpn_sock->peer) { + rcu_read_unlock(); + return; + } + + peer = ovpn_sock->peer; + strp_stop(&peer->tcp.strp); + + skb_queue_purge(&peer->tcp.user_queue); + + /* restore CBs that were saved in ovpn_sock_set_tcp_cb() */ + sock->sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready; + sock->sk->sk_write_space = peer->tcp.sk_cb.sk_write_space; + sock->sk->sk_prot = peer->tcp.sk_cb.prot; + sock->sk->sk_socket->ops = peer->tcp.sk_cb.ops; + rcu_assign_sk_user_data(sock->sk, NULL); + + rcu_read_unlock(); + + /* cancel any ongoing work. Done after removing the CBs so that these + * workers cannot be re-armed + */ + cancel_work_sync(&peer->tcp.tx_work); + strp_done(&peer->tcp.strp); +} + +static void ovpn_tcp_send_sock(struct ovpn_peer *peer) +{ + struct sk_buff *skb = peer->tcp.out_msg.skb; + + if (!skb) + return; + + if (peer->tcp.tx_in_progress) + return; + + peer->tcp.tx_in_progress = true; + + do { + int ret = skb_send_sock_locked(peer->sock->sock->sk, skb, + peer->tcp.out_msg.offset, + peer->tcp.out_msg.len); + if (unlikely(ret < 0)) { + if (ret == -EAGAIN) + goto out; + + net_warn_ratelimited("%s: TCP error to peer %u: %d\n", + peer->ovpn->dev->name, peer->id, + ret); + + /* in case of TCP error we can't recover the VPN + * stream therefore we abort the connection + */ + ovpn_peer_del(peer, + OVPN_DEL_PEER_REASON_TRANSPORT_ERROR); + break; + } + + peer->tcp.out_msg.len -= ret; + peer->tcp.out_msg.offset += ret; + } while (peer->tcp.out_msg.len > 0); + + if (!peer->tcp.out_msg.len) + dev_sw_netstats_tx_add(peer->ovpn->dev, 1, skb->len); + + kfree_skb(peer->tcp.out_msg.skb); + peer->tcp.out_msg.skb = NULL; + peer->tcp.out_msg.len = 0; + peer->tcp.out_msg.offset = 0; + +out: + peer->tcp.tx_in_progress = false; +} + +static void ovpn_tcp_tx_work(struct work_struct *work) +{ + struct ovpn_peer *peer; + + peer = container_of(work, struct ovpn_peer, tcp.tx_work); + + lock_sock(peer->sock->sock->sk); + ovpn_tcp_send_sock(peer); + release_sock(peer->sock->sock->sk); +} + +void ovpn_tcp_send_sock_skb(struct ovpn_peer *peer, struct sk_buff *skb) +{ + if (peer->tcp.out_msg.skb) + return; + + peer->tcp.out_msg.skb = skb; + peer->tcp.out_msg.len = skb->len; + peer->tcp.out_msg.offset = 0; + + ovpn_tcp_send_sock(peer); +} + +static int ovpn_tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) +{ + struct ovpn_socket *sock; + int ret, linear = PAGE_SIZE; + struct ovpn_peer *peer; + struct sk_buff *skb; + + rcu_read_lock(); + sock = rcu_dereference_sk_user_data(sk); + peer = sock->peer; + if (unlikely(!ovpn_peer_hold(peer))) { + rcu_read_unlock(); + return -EIO; + } + rcu_read_unlock(); + + if (msg->msg_flags & ~MSG_DONTWAIT) { + ret = -EOPNOTSUPP; + goto peer_free; + } + + lock_sock(sk); + + if (peer->tcp.out_msg.skb) { + ret = -EAGAIN; + goto unlock; + } + + if (size < linear) + linear = size; + + skb = sock_alloc_send_pskb(sk, linear, size - linear, + msg->msg_flags & MSG_DONTWAIT, &ret, 0); + if (!skb) { + net_err_ratelimited("%s: skb alloc failed: %d\n", + sock->peer->ovpn->dev->name, ret); + goto unlock; + } + + skb_put(skb, linear); + skb->len = size; + skb->data_len = size - linear; + + ret = skb_copy_datagram_from_iter(skb, 0, &msg->msg_iter, size); + if (ret) { + kfree_skb(skb); + net_err_ratelimited("%s: skb copy from iter failed: %d\n", + sock->peer->ovpn->dev->name, ret); + goto unlock; + } + + ovpn_tcp_send_sock_skb(sock->peer, skb); + ret = size; +unlock: + release_sock(sk); +peer_free: + ovpn_peer_put(peer); + return ret; +} + +static void ovpn_tcp_data_ready(struct sock *sk) +{ + struct ovpn_socket *sock; + + trace_sk_data_ready(sk); + + rcu_read_lock(); + sock = rcu_dereference_sk_user_data(sk); + strp_data_ready(&sock->peer->tcp.strp); + rcu_read_unlock(); +} + +static void ovpn_tcp_write_space(struct sock *sk) +{ + struct ovpn_socket *sock; + + rcu_read_lock(); + sock = rcu_dereference_sk_user_data(sk); + schedule_work(&sock->peer->tcp.tx_work); + sock->peer->tcp.sk_cb.sk_write_space(sk); + rcu_read_unlock(); +} + +static void ovpn_tcp_build_protos(struct proto *new_prot, + struct proto_ops *new_ops, + const struct proto *orig_prot, + const struct proto_ops *orig_ops); + +/* Set TCP encapsulation callbacks */ +int ovpn_tcp_socket_attach(struct socket *sock, struct ovpn_peer *peer) +{ + struct strp_callbacks cb = { + .rcv_msg = ovpn_tcp_rcv, + .parse_msg = ovpn_tcp_parse, + }; + int ret; + + /* make sure no pre-existing encapsulation handler exists */ + if (sock->sk->sk_user_data) + return -EBUSY; + + /* sanity check */ + if (sock->sk->sk_protocol != IPPROTO_TCP) { + netdev_err(peer->ovpn->dev, + "provided socket is not TCP as expected\n"); + return -EINVAL; + } + + /* only a fully connected socket are expected. Connection should be + * handled in userspace + */ + if (sock->sk->sk_state != TCP_ESTABLISHED) { + netdev_err(peer->ovpn->dev, + "provided TCP socket is not in ESTABLISHED state: %d\n", + sock->sk->sk_state); + return -EINVAL; + } + + lock_sock(sock->sk); + + ret = strp_init(&peer->tcp.strp, sock->sk, &cb); + if (ret < 0) { + DEBUG_NET_WARN_ON_ONCE(1); + release_sock(sock->sk); + return ret; + } + + INIT_WORK(&peer->tcp.tx_work, ovpn_tcp_tx_work); + __sk_dst_reset(sock->sk); + strp_check_rcv(&peer->tcp.strp); + skb_queue_head_init(&peer->tcp.user_queue); + + /* save current CBs so that they can be restored upon socket release */ + peer->tcp.sk_cb.sk_data_ready = sock->sk->sk_data_ready; + peer->tcp.sk_cb.sk_write_space = sock->sk->sk_write_space; + peer->tcp.sk_cb.prot = sock->sk->sk_prot; + peer->tcp.sk_cb.ops = sock->sk->sk_socket->ops; + + /* assign our static CBs and prot/ops */ + sock->sk->sk_data_ready = ovpn_tcp_data_ready; + sock->sk->sk_write_space = ovpn_tcp_write_space; + + if (sock->sk->sk_family == AF_INET) { + sock->sk->sk_prot = &ovpn_tcp_prot; + sock->sk->sk_socket->ops = &ovpn_tcp_ops; + } else { + mutex_lock(&tcp6_prot_mutex); + if (!ovpn_tcp6_prot.recvmsg) + ovpn_tcp_build_protos(&ovpn_tcp6_prot, &ovpn_tcp6_ops, + sock->sk->sk_prot, + sock->sk->sk_socket->ops); + mutex_unlock(&tcp6_prot_mutex); + + sock->sk->sk_prot = &ovpn_tcp6_prot; + sock->sk->sk_socket->ops = &ovpn_tcp6_ops; + } + + /* avoid using task_frag */ + sock->sk->sk_allocation = GFP_ATOMIC; + sock->sk->sk_use_task_frag = false; + + release_sock(sock->sk); + return 0; +} + +static void ovpn_tcp_close(struct sock *sk, long timeout) +{ + struct ovpn_socket *sock; + + rcu_read_lock(); + sock = rcu_dereference_sk_user_data(sk); + + strp_stop(&sock->peer->tcp.strp); + barrier(); + + tcp_close(sk, timeout); + + ovpn_peer_del(sock->peer, OVPN_DEL_PEER_REASON_TRANSPORT_ERROR); + rcu_read_unlock(); +} + +static __poll_t ovpn_tcp_poll(struct file *file, struct socket *sock, + poll_table *wait) +{ + __poll_t mask = datagram_poll(file, sock, wait); + struct ovpn_socket *ovpn_sock; + + rcu_read_lock(); + ovpn_sock = rcu_dereference_sk_user_data(sock->sk); + if (!skb_queue_empty(&ovpn_sock->peer->tcp.user_queue)) + mask |= EPOLLIN | EPOLLRDNORM; + rcu_read_unlock(); + + return mask; +} + +static void ovpn_tcp_build_protos(struct proto *new_prot, + struct proto_ops *new_ops, + const struct proto *orig_prot, + const struct proto_ops *orig_ops) +{ + memcpy(new_prot, orig_prot, sizeof(*new_prot)); + memcpy(new_ops, orig_ops, sizeof(*new_ops)); + new_prot->recvmsg = ovpn_tcp_recvmsg; + new_prot->sendmsg = ovpn_tcp_sendmsg; + new_prot->close = ovpn_tcp_close; + new_ops->poll = ovpn_tcp_poll; +} + +/* Initialize TCP static objects */ +void __init ovpn_tcp_init(void) +{ + ovpn_tcp_build_protos(&ovpn_tcp_prot, &ovpn_tcp_ops, &tcp_prot, + &inet_stream_ops); +} diff --git a/drivers/net/ovpn/tcp.h b/drivers/net/ovpn/tcp.h new file mode 100644 index 0000000000000000000000000000000000000000..77773bb684f55404a4bd2a839d82f417a5401471 --- /dev/null +++ b/drivers/net/ovpn/tcp.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2019-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#ifndef _NET_OVPN_TCP_H_ +#define _NET_OVPN_TCP_H_ + +#include +#include +#include + +#include "peer.h" +#include "skb.h" + +void __init ovpn_tcp_init(void); + +int ovpn_tcp_socket_attach(struct socket *sock, struct ovpn_peer *peer); +void ovpn_tcp_socket_detach(struct socket *sock); +void ovpn_tcp_send_sock_skb(struct ovpn_peer *peer, struct sk_buff *skb); + +/* Prepare skb and enqueue it for sending to peer. + * + * Preparation consist in prepending the skb payload with its size. + * Required by the OpenVPN protocol in order to extract packets from + * the TCP stream on the receiver side. + */ +static inline void ovpn_tcp_send_skb(struct ovpn_peer *peer, + struct sk_buff *skb) +{ + u16 len = skb->len; + + *(__be16 *)__skb_push(skb, sizeof(u16)) = htons(len); + + bh_lock_sock(peer->sock->sock->sk); + ovpn_tcp_send_sock_skb(peer, skb); + bh_unlock_sock(peer->sock->sock->sk); +} + +#endif /* _NET_OVPN_TCP_H_ */ From patchwork Wed Oct 2 09:02:28 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819521 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f52.google.com (mail-wr1-f52.google.com [209.85.221.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3F1E41A01C3 for ; Wed, 2 Oct 2024 09:03:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859818; cv=none; b=UCuRC/Kd9teM+72mTz/CYs0XKz7eMQJELklhPqeqXaVitO4rJQT4nYRHm+0YH+mHkXxbFdTF3mGwyNwJzS8TH45XA/soMPq/X1JnNfsDGBSTNXNXprnPYt3xM7TBr2RIOP21NKot6AT540nDtKoIZa/H7f4ScTXvo0KXgUw8bE8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859818; c=relaxed/simple; bh=/jyZFCitrzzTMegwb3RUVnyedfZN21mXFMy5IfxSzJU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=f5cYx7ocr+WwxAsn+zSEL5FqyfyqJ/e3vXkzPMy5MDyhX3KHrXeMuFxqHO5tq9zb/IQ/foEibs9oh/hIAB9xkO2WjZNPntdmWbm3qzhMMxF8gq9OlqfctOFl1yBvSOEgGPmhPmUX67zYTlP/X+fxCRowylW7aeoTHnFo3pCLm6U= 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=L/YbOIw/; arc=none smtp.client-ip=209.85.221.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="L/YbOIw/" Received: by mail-wr1-f52.google.com with SMTP id ffacd0b85a97d-37ce9644daaso2239853f8f.3 for ; Wed, 02 Oct 2024 02:03:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859813; x=1728464613; 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=vTv3ai7RTqlCKNTR3vLfU8stwy3llALadHqfw0c8P9w=; b=L/YbOIw/IHjeir+JDBezh1+u2u4NuTzpaPupFVRR5csSUzvDgq0L54x0N3/75UiHH7 E9z85zCsCEpjh+PFHftkTyLDusXIv6SMOMq/MAfy2FT+GVHUkg3fdQ2r8ffC18I1kr1l 7+U9qsXI0nDLx3CCMwMReNDzzoMh3sTPTA/6nws1SvvCObfou48W7f3LiXbg5uLFj7Vw +JMctOR0NIA1cQJ+C72AY6QDUm6po8x0AfRe59EG6XI6nFMFDMKU1bFO7/VChwtVBKEi fsXPm+C0ZByKx7O/sKu3+3Uvr0fvi0cN6UT++Zkjhgl9noEF5LvEhQTrpkNHHVbkqmQn fw4w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859813; x=1728464613; 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=vTv3ai7RTqlCKNTR3vLfU8stwy3llALadHqfw0c8P9w=; b=dqzOCcc+F3gJGrrqDhS1KQiunRhr31QDpNSRqDhsHz4PtOGNGIUNtXe81+3IbV7dno 6GdSdLyLDIhyVvMiLRaSIHwHikSjIxg1OadeoYtGXsBUJ6XZ8Yw/EiRID31SSLMpQubI nbuJFH6cGmRUuyBbyDKTy6uAvHvnGWnH4VSIa3pBA8R85NElUEJKFebFc3AjYjVMReBc jAN0loTjtSTINjEoXK5K35D+H/Vek/eTC4ctgrll0cW8twiD0xqw8sDXpCUcqgePlXdt dJ0yPwra15WVG0hx7viyAGSWWcmRDxMpCAOv9AKzuQQZtEpi67avoTkAmYmk8ExCvAPg cegw== X-Gm-Message-State: AOJu0YzYDVBSDCQpazfx8KgI05qA0hGl38gDSalyecn/xe8IP+G7hvNc 8awARRjxpQWI4+ZREjZB9hiCcsYJrdk9FuYratuyZICuixYf3L3iHzIJmBr2lwc= X-Google-Smtp-Source: AGHT+IHEjkOeBEun/Xsvp1QCzEdephCC5mwpKVumRXLB7ffiuz3BMrEIzghL+EVaW2EwA/3DOXKUCg== X-Received: by 2002:a5d:4c87:0:b0:374:c606:df0a with SMTP id ffacd0b85a97d-37cfba0a6c0mr1534988f8f.36.1727859813469; Wed, 02 Oct 2024 02:03:33 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:32 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:28 +0200 Subject: [PATCH net-next v8 14/24] ovpn: implement multi-peer support Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-14-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=13149; i=antonio@openvpn.net; h=from:subject:message-id; bh=/jyZFCitrzzTMegwb3RUVnyedfZN21mXFMy5IfxSzJU=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxSMMpUcsBjjFpYFEvQt63PpGHoH/WNli0Cf Ihh5Ho7IPmJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUgAKCRALcOU6oDjV h2euB/oDijAue0yLnLzz9VFOJs+esn7rFyTxmkRO8C2CmrRZRZ572pzTJ2NN20jyRwjghY23j6e xd/rNlXA9bdThmrpHhTzQlarzrRmVvDV6EvHRqe6P1QJGAOwRyeUdauHNpqUvpAjIk9LEnDn0Jm 5COgeUwl4zfpYLEnK6HFh/SJ3oYtVNOAiqhiPHpplMQGHQblyr09fiBpQIHGhK5HiWFazBX1VYN H0UUjXLEJxqs4vMENTI0srf6n9XwJjI7q5AeeuswD82ejX7YX653A30QYPxiYZmErTpXoKwbdfQ csAXai/srb5g29xr0I8F23Td0fPmyg5Pu302oLvjYyld0NjY X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org 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 | 50 +++++++++++- drivers/net/ovpn/ovpnstruct.h | 19 +++++ drivers/net/ovpn/peer.c | 173 ++++++++++++++++++++++++++++++++++++++++-- drivers/net/ovpn/peer.h | 9 +++ 4 files changed, 241 insertions(+), 10 deletions(-) diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index cb5cc3d4b5620f5a1e401e06e52e67c32e57aa78..7604f0970d3c283f8680f6800a125427999bb174 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -50,13 +50,53 @@ static void ovpn_struct_free(struct net_device *net) struct ovpn_struct *ovpn = netdev_priv(net); gro_cells_destroy(&ovpn->gro_cells); + kfree(ovpn->peers); } static int ovpn_net_init(struct net_device *dev) { struct ovpn_struct *ovpn = netdev_priv(dev); + struct in_device *dev_v4; + int i, err; - return gro_cells_init(&ovpn->gro_cells, dev); + err = gro_cells_init(&ovpn->gro_cells, dev); + if (err) + return err; + + if (ovpn->mode == OVPN_MODE_MP) { + dev_v4 = __in_dev_get_rtnl(dev); + if (dev_v4) { + /* disable redirects as Linux gets confused by ovpn + * handling same-LAN routing. + * This happens because a multipeer interface is used as + * relay point between hosts in the same subnet, while + * in a classic LAN this would not be needed because the + * two hosts would be able to talk directly. + */ + IN_DEV_CONF_SET(dev_v4, SEND_REDIRECTS, false); + IPV4_DEVCONF_ALL(dev_net(dev), SEND_REDIRECTS) = false; + } + + /* the peer container is fairly large, therefore we dynamically + * allocate it only when needed + */ + ovpn->peers = kzalloc(sizeof(*ovpn->peers), GFP_KERNEL); + if (!ovpn->peers) { + gro_cells_destroy(&ovpn->gro_cells); + return -ENOMEM; + } + + spin_lock_init(&ovpn->peers->lock); + + 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_addr[i], i); + INIT_HLIST_NULLS_HEAD(&ovpn->peers->by_transp_addr[i], + i); + } + } + + return 0; } static int ovpn_net_open(struct net_device *dev) @@ -201,8 +241,14 @@ void ovpn_iface_destruct(struct ovpn_struct *ovpn) ovpn->registered = false; - if (ovpn->mode == OVPN_MODE_P2P) + switch (ovpn->mode) { + case OVPN_MODE_P2P: ovpn_peer_release_p2p(ovpn); + break; + default: + ovpn_peers_free(ovpn); + break; + } } static int ovpn_netdev_notifier_call(struct notifier_block *nb, diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h index 65497ce115aa4be719ed03f89135a80b73f9ef9b..4373d1bbc4874dc2f1d1f1697e7b847ec2f486c9 100644 --- a/drivers/net/ovpn/ovpnstruct.h +++ b/drivers/net/ovpn/ovpnstruct.h @@ -14,6 +14,23 @@ #include #include +/** + * struct ovpn_peer_collection - container of peers for MultiPeer mode + * @by_id: table of peers index by ID + * @by_vpn_addr: table of peers indexed by VPN IP 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) + * @lock: protects writes to peer tables + */ +struct ovpn_peer_collection { + DECLARE_HASHTABLE(by_id, 12); + struct hlist_nulls_head by_vpn_addr[1 << 12]; + struct hlist_nulls_head by_transp_addr[1 << 12]; + + spinlock_t lock; /* protects writes to peer tables */ +}; + /** * struct ovpn_struct - per ovpn interface state * @dev: the actual netdev representing the tunnel @@ -21,6 +38,7 @@ * @registered: whether dev is still registered with netdev or not * @mode: device operation mode (i.e. p2p, mp, ..) * @lock: protect this object + * @peers: data structures holding multi-peer references * @peer: in P2P mode, this is the only remote peer * @dev_list: entry for the module wide device list * @gro_cells: pointer to the Generic Receive Offload cell @@ -31,6 +49,7 @@ struct ovpn_struct { bool registered; enum ovpn_mode mode; spinlock_t lock; /* protect writing to the ovpn_struct object */ + struct ovpn_peer_collection *peers; struct ovpn_peer __rcu *peer; struct list_head dev_list; struct gro_cells gro_cells; diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 6d34c56a4a51ce407b20b6175e6140aafc4b4e33..3c6fbf99f696c18d8a2dbe169c7f6f5933fb71ac 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -9,6 +9,7 @@ #include #include +#include #include "ovpnstruct.h" #include "bind.h" @@ -65,13 +66,12 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id) } /** - * ovpn_peer_release - release peer private members - * @peer: the peer to release + * ovpn_peer_release_rcu - release peer private members + * @head: RCU head belonging to peer being released */ -static void ovpn_peer_release(struct ovpn_peer *peer) +static void ovpn_peer_release_rcu(struct rcu_head *head) { - if (peer->sock) - ovpn_socket_put(peer->sock); + struct ovpn_peer *peer = container_of(head, struct ovpn_peer, rcu); ovpn_crypto_state_release(&peer->crypto); ovpn_bind_reset(peer, NULL); @@ -86,9 +86,10 @@ void ovpn_peer_release_kref(struct kref *kref) { struct ovpn_peer *peer = container_of(kref, struct ovpn_peer, refcount); - ovpn_peer_release(peer); + if (peer->sock) + ovpn_socket_put(peer->sock); netdev_put(peer->ovpn->dev, &peer->ovpn->dev_tracker); - kfree_rcu(peer, rcu); + call_rcu(&peer->rcu, ovpn_peer_release_rcu); } /** @@ -309,6 +310,89 @@ bool ovpn_peer_check_by_src(struct ovpn_struct *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_struct *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->peers->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_addr, + &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_addr, + &peer->vpn_addrs.ipv6, + sizeof(peer->vpn_addrs.ipv6)); + hlist_nulls_add_head_rcu(&peer->hash_entry_addr6, nhead); + } +out: + spin_unlock_bh(&ovpn->peers->lock); + return ret; +} + /** * ovpn_peer_add_p2p - add peer to related tables in a P2P instance * @ovpn: the instance to add the peer to @@ -349,6 +433,8 @@ static int ovpn_peer_add_p2p(struct ovpn_struct *ovpn, struct ovpn_peer *peer) int ovpn_peer_add(struct ovpn_struct *ovpn, struct ovpn_peer *peer) { switch (ovpn->mode) { + case OVPN_MODE_MP: + return ovpn_peer_add_mp(ovpn, peer); case OVPN_MODE_P2P: return ovpn_peer_add_p2p(ovpn, peer); default: @@ -356,6 +442,51 @@ int ovpn_peer_add(struct ovpn_struct *ovpn, struct ovpn_peer *peer) } } +/** + * ovpn_peer_unhash - remove peer reference from all hashtables + * @peer: the peer to remove + * @reason: the delete reason to attach to the peer + */ +static void ovpn_peer_unhash(struct ovpn_peer *peer, + enum ovpn_del_peer_reason reason) + __must_hold(&ovpn->peers->lock) +{ + 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); + + ovpn_peer_put(peer); + peer->delete_reason = reason; +} + +/** + * ovpn_peer_del_mp - delete peer from related tables in a MP instance + * @peer: the peer to delete + * @reason: reason why the peer was deleted (sent to userspace) + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_peer_del_mp(struct ovpn_peer *peer, + enum ovpn_del_peer_reason reason) + __must_hold(&peer->ovpn->peers->lock) +{ + struct ovpn_peer *tmp; + int ret = -ENOENT; + + tmp = ovpn_peer_get_by_id(peer->ovpn, peer->id); + if (tmp == peer) { + ovpn_peer_unhash(peer, reason); + ret = 0; + } + + if (tmp) + ovpn_peer_put(tmp); + + return ret; +} + /** * ovpn_peer_del_p2p - delete peer from related tables in a P2P instance * @peer: the peer to delete @@ -411,10 +542,36 @@ void ovpn_peer_release_p2p(struct ovpn_struct *ovpn) */ int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason) { + int ret; + switch (peer->ovpn->mode) { + case OVPN_MODE_MP: + spin_lock_bh(&peer->ovpn->peers->lock); + ret = ovpn_peer_del_mp(peer, reason); + spin_unlock_bh(&peer->ovpn->peers->lock); + return ret; case OVPN_MODE_P2P: - return ovpn_peer_del_p2p(peer, reason); + spin_lock_bh(&peer->ovpn->lock); + ret = ovpn_peer_del_p2p(peer, reason); + spin_unlock_bh(&peer->ovpn->lock); + return ret; default: return -EOPNOTSUPP; } } + +/** + * ovpn_peers_free - free all peers in the instance + * @ovpn: the instance whose peers should be released + */ +void ovpn_peers_free(struct ovpn_struct *ovpn) +{ + struct hlist_node *tmp; + struct ovpn_peer *peer; + int bkt; + + spin_lock_bh(&ovpn->peers->lock); + hash_for_each_safe(ovpn->peers->by_id, bkt, tmp, peer, hash_entry_id) + ovpn_peer_unhash(peer, OVPN_DEL_PEER_REASON_TEARDOWN); + spin_unlock_bh(&ovpn->peers->lock); +} diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 86d4696b1529c45025b63d2973fc48ca81ca8f63..9481eb41742ca2a6c88ee0262dd448fd947614fd 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -30,6 +30,10 @@ * @vpn_addrs: IP addresses assigned over the tunnel * @vpn_addrs.ipv4: IPv4 assigned to peer on the tunnel * @vpn_addrs.ipv6: IPv6 assigned to peer on the tunnel + * @hash_entry_id: entry in the peer ID hashtable + * @hash_entry_addr4: entry in the peer IPv4 hashtable + * @hash_entry_addr6: entry in the peer IPv6 hashtable + * @hash_entry_transp_addr: entry in the peer transport address hashtable * @sock: the socket being used to talk to this peer * @tcp: keeps track of TCP specific state * @tcp.strp: stream parser context (TCP only) @@ -62,6 +66,10 @@ struct ovpn_peer { struct in_addr ipv4; struct in6_addr ipv6; } vpn_addrs; + struct hlist_node hash_entry_id; + struct hlist_nulls_node hash_entry_addr4; + struct hlist_nulls_node hash_entry_addr6; + struct hlist_nulls_node hash_entry_transp_addr; struct ovpn_socket *sock; /* state of the TCP reading. Needed to keep track of how much of a @@ -126,6 +134,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id); int ovpn_peer_add(struct ovpn_struct *ovpn, struct ovpn_peer *peer); int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason); void ovpn_peer_release_p2p(struct ovpn_struct *ovpn); +void ovpn_peers_free(struct ovpn_struct *ovpn); struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn, struct sk_buff *skb); From patchwork Wed Oct 2 09:02:29 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819522 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f47.google.com (mail-wr1-f47.google.com [209.85.221.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 477FA1A08C1 for ; Wed, 2 Oct 2024 09:03:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859819; cv=none; b=r+7EKmPP291meholyNYwU6uW/IY4SSNGP1BqpY8PTvcS7nG1wOsQd5WDlJApkpNflq9bMubXvHpeehvXu2sRQIlqzY3cMiwAp3kr84+Zl8FtRBOpPMBxl3YECelXx1yyqlcuxMsfvEKHOnEVCjVHiMG6s+jciBPmedqXqyVvGb0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859819; c=relaxed/simple; bh=q0PhaZp9wJNYhCUmStZzBiNVB2VevlrBweECXMf1Rpc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=dDGMNzhDo9KysF7Vm/TMrCNZWq67ha0CEo7QAXGV5KrzqkhYyy8yodtQnuQ/gAr2ei6Y865SfVTCQvqHPh77ylIkXsV/rbmLuGhx/eLTh81mxagCl32GqRmo0CXcXc5AQPcRsEcR4dwIH/v7K9kEiKnr1F7UNhpd9OeZmR5Dtl0= 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=RIfCoUd7; arc=none smtp.client-ip=209.85.221.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="RIfCoUd7" Received: by mail-wr1-f47.google.com with SMTP id ffacd0b85a97d-37cea34cb57so1849021f8f.0 for ; Wed, 02 Oct 2024 02:03:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859815; x=1728464615; 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=gHXA82IaKBwNCAyOFeIau6drzsQmyQRGnjGyMzwhyz4=; b=RIfCoUd7zafvlf874SQCxYEROetUC6KJwG6aUEdl4QhZEHMM7kgCflG85rpItznXvt GNy2nnF0efyjAWmYsbThXI6V8rfPv/WFZbBtXGMYX6+vPsOhih4s642eudrRD/txUI9p MzKnqjC1vLATnDbmKxaOCMxcnKlwvv2pRug+TZr5w2uviA9mNaTe1QIxSkQ97tXQ1hOl mazumA0xmi56nkLG0vZVJh71ZGfAvQOdWoBrIHF5DFnRr/cGoeg2Bk8PXNXlM5E2REc6 kVm4xaeKXRydPyfh1RJZGDIPQI42ceMi3jSVe9FF3aWb9EhcTMUGVSLZH9V2EPHtBsYb eqMw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859815; x=1728464615; 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=gHXA82IaKBwNCAyOFeIau6drzsQmyQRGnjGyMzwhyz4=; b=dFUUfkFIYPtfWBki/oQZuLx4NgCr2e11Z8DPgAVzY9b/cqy9nl4UVoVL6gwsYE6B0W /6jScKU3jgf0FNREScTL4r0ALfMF2SkRSd2EbNiiIdSX4YS5V28wWwZHrjzKzEWmYZAE nqmApAToGUnx9s/jrIJYbhsBIwsq3VIyNGThqL0OexOZPtADYqC8fQB8pxoctX+BXkMt ynhYiGkSnsgv1toAVy+DKe0qqxz6UhH8ffl71LBL6HLVSZWR9FWrgkA9fq0+zyRSynms yfBbu5jFrUeGmytdIuIvPNbC8DPbQnBxQv7iPgtYNh3rQ2ERRUpVZsn2mbl0A4JsXPUw PeIg== X-Gm-Message-State: AOJu0Yy4pSlt1rxbLCT8QSeUlXvhRkLaVf5JiIhuLSXMCoKqcrRhqxHu E+qFfe+EzZyGHXHprXL5eC4U7qhMauEHkBdOdPAkPITJYnYorNCxWwgbxkJO64U= X-Google-Smtp-Source: AGHT+IGFkU4ubUMr1Ec6IAhYLJl+TeBbSejZ6TpcKm1lmXuf8R2xaciclbXNIiFxH+RJk/WKitOqoQ== X-Received: by 2002:a05:6000:547:b0:374:c56e:1d44 with SMTP id ffacd0b85a97d-37cfba0f398mr1357871f8f.48.1727859814499; Wed, 02 Oct 2024 02:03:34 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:34 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:29 +0200 Subject: [PATCH net-next v8 15/24] ovpn: implement peer lookup logic Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-15-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=10819; i=antonio@openvpn.net; h=from:subject:message-id; bh=q0PhaZp9wJNYhCUmStZzBiNVB2VevlrBweECXMf1Rpc=; b=owGbwMvMwMHIXfDUaoHF1XbG02pJDGl/eYJkL3s5ah3e8k0hVvvU0cvqtZ5v3RQ9vAVUn8bYT 4pw/HO3k9GYhYGRg0FWTJFl5uo7OT+uCD25F3/gD8wgViaQKQxcnAIwkSB/9r8yF31se96dOtGp Zqj//ZNvYirjqWPsJilJb04w3Bd9nPo7z77gzekoTTPvQ93G2h4P/YssLUIcGeIF6n1VP2w15rh cUaPAJNI6OXaW2nI3r1ab0ndn/h8ztvEOLkpVXeu4NDi2KyakbDWLsGDkvKbGiVsC06L0rq7fM9 /0qIN9h7+JuODHywqF23baa105vDVQ4dg6lVmr66peCV8884ON8691Tsvf+5xLxZs63mp9Ylbb2 /qL84HqVI3SXjUuY7Wrty07Yh9M+DSfi/GQcfLny+tis2Qyrpf2yS5lzhO9PPuUW+yExI/p6kvn FTrndfx/vKXsgVzTzOVZ0pulHX7vT9y7Piv6/3eW4K+bAA== X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org In a multi-peer scenario there are a number of situations when a specific peer needs to be looked up. We may want to lookup a peer by: 1. its ID 2. its VPN destination IP 3. its transport IP/port couple For each of the above, there is a specific routing table referencing all peers for fast look up. Case 2. is a bit special in the sense that an outgoing packet may not be sent to the peer VPN IP directly, but rather to a network behind it. For this reason we first perform a nexthop lookup in the system routing table and then we use the retrieved nexthop as peer search key. Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 272 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 264 insertions(+), 8 deletions(-) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 3c6fbf99f696c18d8a2dbe169c7f6f5933fb71ac..54d0a416f6f91513f42d5ecc1f1f65d688f3e908 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "ovpnstruct.h" #include "bind.h" @@ -126,6 +127,94 @@ static bool ovpn_peer_skb_to_sockaddr(struct sk_buff *skb, return true; } +/** + * ovpn_nexthop_from_skb4 - retrieve IPv4 nexthop for outgoing skb + * @skb: the outgoing packet + * + * Return: the IPv4 of the nexthop + */ +static __be32 ovpn_nexthop_from_skb4(struct sk_buff *skb) +{ + const struct rtable *rt = skb_rtable(skb); + + if (rt && rt->rt_uses_gateway) + return rt->rt_gw4; + + return ip_hdr(skb)->daddr; +} + +/** + * ovpn_nexthop_from_skb6 - retrieve IPv6 nexthop for outgoing skb + * @skb: the outgoing packet + * + * Return: the IPv6 of the nexthop + */ +static struct in6_addr ovpn_nexthop_from_skb6(struct sk_buff *skb) +{ + const struct rt6_info *rt = skb_rt6_info(skb); + + if (!rt || !(rt->rt6i_flags & RTF_GATEWAY)) + return ipv6_hdr(skb)->daddr; + + return rt->rt6i_gateway; +} + +#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \ + typeof(_tbl) *__tbl = &(_tbl); \ + (&(*__tbl)[jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl)]); }) \ + +/** + * ovpn_peer_get_by_vpn_addr4 - retrieve peer by its VPN IPv4 address + * @ovpn: the openvpn instance to search + * @addr: VPN IPv4 to use as search key + * + * Refcounter is not increased for the returned peer. + * + * Return: the peer if found or NULL otherwise + */ +static struct ovpn_peer *ovpn_peer_get_by_vpn_addr4(struct ovpn_struct *ovpn, + __be32 addr) +{ + struct hlist_nulls_head *nhead; + struct hlist_nulls_node *ntmp; + struct ovpn_peer *tmp; + + nhead = ovpn_get_hash_head(ovpn->peers->by_vpn_addr, &addr, + sizeof(addr)); + + hlist_nulls_for_each_entry_rcu(tmp, ntmp, nhead, hash_entry_addr4) + if (addr == tmp->vpn_addrs.ipv4.s_addr) + return tmp; + + return NULL; +} + +/** + * ovpn_peer_get_by_vpn_addr6 - retrieve peer by its VPN IPv6 address + * @ovpn: the openvpn instance to search + * @addr: VPN IPv6 to use as search key + * + * Refcounter is not increased for the returned peer. + * + * Return: the peer if found or NULL otherwise + */ +static struct ovpn_peer *ovpn_peer_get_by_vpn_addr6(struct ovpn_struct *ovpn, + struct in6_addr *addr) +{ + struct hlist_nulls_head *nhead; + struct hlist_nulls_node *ntmp; + struct ovpn_peer *tmp; + + nhead = ovpn_get_hash_head(ovpn->peers->by_vpn_addr, addr, + sizeof(*addr)); + + hlist_nulls_for_each_entry_rcu(tmp, ntmp, nhead, hash_entry_addr6) + if (ipv6_addr_equal(addr, &tmp->vpn_addrs.ipv6)) + return tmp; + + return NULL; +} + /** * ovpn_peer_transp_match - check if sockaddr and peer binding match * @peer: the peer to get the binding from @@ -203,14 +292,44 @@ ovpn_peer_get_by_transp_addr_p2p(struct ovpn_struct *ovpn, struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn, struct sk_buff *skb) { - struct ovpn_peer *peer = NULL; + struct ovpn_peer *tmp, *peer = NULL; struct sockaddr_storage ss = { 0 }; + struct hlist_nulls_head *nhead; + struct hlist_nulls_node *ntmp; + size_t sa_len; if (unlikely(!ovpn_peer_skb_to_sockaddr(skb, &ss))) return NULL; if (ovpn->mode == OVPN_MODE_P2P) - peer = ovpn_peer_get_by_transp_addr_p2p(ovpn, &ss); + return ovpn_peer_get_by_transp_addr_p2p(ovpn, &ss); + + switch (ss.ss_family) { + case AF_INET: + sa_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + sa_len = sizeof(struct sockaddr_in6); + break; + default: + return NULL; + } + + nhead = ovpn_get_hash_head(ovpn->peers->by_transp_addr, &ss, sa_len); + + rcu_read_lock(); + hlist_nulls_for_each_entry_rcu(tmp, ntmp, nhead, + hash_entry_transp_addr) { + if (!ovpn_peer_transp_match(tmp, &ss)) + continue; + + if (!ovpn_peer_hold(tmp)) + continue; + + peer = tmp; + break; + } + rcu_read_unlock(); return peer; } @@ -245,10 +364,27 @@ static struct ovpn_peer *ovpn_peer_get_by_id_p2p(struct ovpn_struct *ovpn, */ struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id) { - struct ovpn_peer *peer = NULL; + struct ovpn_peer *tmp, *peer = NULL; + struct hlist_head *head; if (ovpn->mode == OVPN_MODE_P2P) - peer = ovpn_peer_get_by_id_p2p(ovpn, peer_id); + return ovpn_peer_get_by_id_p2p(ovpn, peer_id); + + head = ovpn_get_hash_head(ovpn->peers->by_id, &peer_id, + sizeof(peer_id)); + + rcu_read_lock(); + hlist_for_each_entry_rcu(tmp, head, hash_entry_id) { + if (tmp->id != peer_id) + continue; + + if (!ovpn_peer_hold(tmp)) + continue; + + peer = tmp; + break; + } + rcu_read_unlock(); return peer; } @@ -270,6 +406,8 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn, struct sk_buff *skb) { struct ovpn_peer *peer = NULL; + struct in6_addr addr6; + __be32 addr4; /* in P2P mode, no matter the destination, packets are always sent to * the single peer listening on the other side @@ -280,11 +418,109 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn, if (unlikely(peer && !ovpn_peer_hold(peer))) peer = NULL; rcu_read_unlock(); + return peer; } + rcu_read_lock(); + switch (skb_protocol_to_family(skb)) { + case AF_INET: + addr4 = ovpn_nexthop_from_skb4(skb); + peer = ovpn_peer_get_by_vpn_addr4(ovpn, addr4); + break; + case AF_INET6: + addr6 = ovpn_nexthop_from_skb6(skb); + peer = ovpn_peer_get_by_vpn_addr6(ovpn, &addr6); + break; + } + + if (unlikely(peer && !ovpn_peer_hold(peer))) + peer = NULL; + rcu_read_unlock(); + return peer; } +/** + * ovpn_nexthop_from_rt4 - look up the IPv4 nexthop for the given destination + * @ovpn: the private data representing the current VPN session + * @dest: the destination to be looked up + * + * Looks up in the IPv4 system routing table the IP of the nexthop to be used + * to reach the destination passed as argument. If no nexthop can be found, the + * destination itself is returned as it probably has to be used as nexthop. + * + * Return: the IP of the next hop if found or dest itself otherwise + */ +static __be32 ovpn_nexthop_from_rt4(struct ovpn_struct *ovpn, __be32 dest) +{ + struct rtable *rt; + struct flowi4 fl = { + .daddr = dest + }; + + rt = ip_route_output_flow(dev_net(ovpn->dev), &fl, NULL); + if (IS_ERR(rt)) { + net_dbg_ratelimited("%s: no route to host %pI4\n", __func__, + &dest); + /* if we end up here this packet is probably going to be + * thrown away later + */ + return dest; + } + + if (!rt->rt_uses_gateway) + goto out; + + dest = rt->rt_gw4; +out: + ip_rt_put(rt); + return dest; +} + +/** + * ovpn_nexthop_from_rt6 - look up the IPv6 nexthop for the given destination + * @ovpn: the private data representing the current VPN session + * @dest: the destination to be looked up + * + * Looks up in the IPv6 system routing table the IP of the nexthop to be used + * to reach the destination passed as argument. If no nexthop can be found, the + * destination itself is returned as it probably has to be used as nexthop. + * + * Return: the IP of the next hop if found or dest itself otherwise + */ +static struct in6_addr ovpn_nexthop_from_rt6(struct ovpn_struct *ovpn, + struct in6_addr dest) +{ +#if IS_ENABLED(CONFIG_IPV6) + struct dst_entry *entry; + struct rt6_info *rt; + struct flowi6 fl = { + .daddr = dest, + }; + + entry = ipv6_stub->ipv6_dst_lookup_flow(dev_net(ovpn->dev), NULL, &fl, + NULL); + if (IS_ERR(entry)) { + net_dbg_ratelimited("%s: no route to host %pI6c\n", __func__, + &dest); + /* if we end up here this packet is probably going to be + * thrown away later + */ + return dest; + } + + rt = dst_rt6_info(entry); + + if (!(rt->rt6i_flags & RTF_GATEWAY)) + goto out; + + dest = rt->rt6i_gateway; +out: + dst_release((struct dst_entry *)rt); +#endif + return dest; +} + /** * ovpn_peer_check_by_src - check that skb source is routed via peer * @ovpn: the openvpn instance to search @@ -297,6 +533,8 @@ bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb, struct ovpn_peer *peer) { bool match = false; + struct in6_addr addr6; + __be32 addr4; if (ovpn->mode == OVPN_MODE_P2P) { /* in P2P mode, no matter the destination, packets are always @@ -305,15 +543,33 @@ bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb, rcu_read_lock(); match = (peer == rcu_dereference(ovpn->peer)); rcu_read_unlock(); + return match; + } + + /* This function performs a reverse path check, therefore we now + * lookup the nexthop we would use if we wanted to route a packet + * to the source IP. If the nexthop matches the sender we know the + * latter is valid and we allow the packet to come in + */ + + switch (skb_protocol_to_family(skb)) { + case AF_INET: + addr4 = ovpn_nexthop_from_rt4(ovpn, ip_hdr(skb)->saddr); + rcu_read_lock(); + match = (peer == ovpn_peer_get_by_vpn_addr4(ovpn, addr4)); + rcu_read_unlock(); + break; + case AF_INET6: + addr6 = ovpn_nexthop_from_rt6(ovpn, ipv6_hdr(skb)->saddr); + rcu_read_lock(); + match = (peer == ovpn_peer_get_by_vpn_addr6(ovpn, &addr6)); + rcu_read_unlock(); + break; } return match; } -#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 From patchwork Wed Oct 2 09:02:30 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819523 X-Patchwork-Delegate: kuba@kernel.org 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 4F7F41B5336 for ; Wed, 2 Oct 2024 09:03:38 +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=1727859821; cv=none; b=jrs/3E09ZxKjsSu2djenzzBTWB7vj6zU0in94gWqSo6ggPIXkjFe4mvOnbNny4+HsiIYk8Ej6fJ5Pcr/HgRCievZHV9zCezjWfLA1wjU0K+CVyi8Y+w2k3dr6dccdHoo6EyuF4XkM1ix4AD1/6YCuF21bOgtBevYpz7iDEZbYuw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859821; c=relaxed/simple; bh=ZD9CYkIE5FSFq5DYQUPg3++Ny0I/XG+G1W/i4+vARdU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=MoJw9MN2bRQ6e1qgHmVyBOVgmBS2iXLEDLK9JuT0XHdeHyDJMRvTt1F11nmdiYGNu75i76rXCYbIVxyC7AU4IjSSLnTH9cXpYWtLPZ58DqN+M9sdbAcFib/8Nn+Qj5X+2fwoph2kdheNOLnCNpsEar9vKgUTgXMZbdDL0dijlxU= 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=eSSGRqp9; 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="eSSGRqp9" Received: by mail-wm1-f44.google.com with SMTP id 5b1f17b1804b1-42cbc22e1c4so48305555e9.2 for ; Wed, 02 Oct 2024 02:03:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859817; x=1728464617; 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=3oEWmzaH16ZJlE/sOepu8X/tC//galY/p3gsmCJfQNo=; b=eSSGRqp9pjpGdJ4ovRfgood5fssaOHCoSXeTto1+VAZTQlTwyiwwxPzLFhfEdPnNmr /PtIce16sLoRBmSG1fxhdLrXcmJdoyKAA5e1NbklttgpNrmez8nVYOqlIPp/58Rb5VfT A8bBfSE8unsqHYn2ZTCb/ApQcVRxb52bcIHfsrkAyQXh5Ufc80eMSEfgWoM3SE6udBgd 58X76qpc9CWK55fayIpv37AX6VQBnvpL+pZmbaF30cLUwLB4a50U8w5lpDoTsMBVp3Qp RB5WX42y5sOhd0Y4nApEl+rcsMloo2F9A3EWU2JxzTOxttilFVkB4+nYXSSviwhm1Wrj R8nw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859817; x=1728464617; 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=3oEWmzaH16ZJlE/sOepu8X/tC//galY/p3gsmCJfQNo=; b=ClAg3TXbowckO0EArhUsT85Ww+Sx9Xmex8llFoA5rNtCuaShZ4pqN+mDDm7EyHNiw3 PSLKaLJ+Vu7u99NGaBy6eT1TRQLUr5PpxCiS1KQklc1TSHYoKM8sAZoVcs+T/FvjxzBU J5vd0h0wfUeONxn1Uh3M35Q1nYK6//iBeWE6Pjo4ScNjuwK48FVXUbEFH+5QKr5X0zkv mWzePRQ/UdrG4FiYvrs5Kg5iADtxqPuZ15Vsrb1VpMyNCnVaYwU95ViGvGIzxupV5zt/ ye++DL2rqcypzDI0WtGu1dGvHVOARCyllXh8AO5qXLlLUjE2U0/i1Tsj3dsVIhytNDDI wo5w== X-Gm-Message-State: AOJu0YxwFge/AuOnVe8V8+mZPdGRFDNbIpHePzSNDT62SeTrGkQkT5Hk 5x+kIv/SUpwt2em7gwcss2AXtsf/oWKU6MABVK6fGZQNTNl8a0m7Ld4tfRERMAk= X-Google-Smtp-Source: AGHT+IF2gQxIzWXOLeJ8H4TU5mP2xuwi12Pf5Wwm7GiGC1CJO23K29iXzrv3zLtW5XdjwhKv8WGUqw== X-Received: by 2002:a05:600c:a02:b0:42c:a8f8:1d58 with SMTP id 5b1f17b1804b1-42f777b5c6amr15395095e9.7.1727859816451; Wed, 02 Oct 2024 02:03:36 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:35 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:30 +0200 Subject: [PATCH net-next v8 16/24] ovpn: implement keepalive mechanism Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-16-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=15105; i=antonio@openvpn.net; h=from:subject:message-id; bh=ZD9CYkIE5FSFq5DYQUPg3++Ny0I/XG+G1W/i4+vARdU=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxSvZyCHcVl29IvPsV/wOlPH+ZJzQdL02OQ1 sVAHkEr5zyJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUgAKCRALcOU6oDjV hwtfB/9BQSSxQv5t71fvXjYJVeIzKIvfmeRiDOZUngNXvT54AZrXlLFDdQF3Tk2gtgKJLjgyYaV XVtLg1zjWMZXtvWCCceHr3rg3waLNzw+J1ZFedPiCG0a0QAgKls9N9JV8lHnS/xuStwiEGatf1H TJ9/U38/Vt4J/bfO5NS+N9vqlMkCpUkBtKD+F3q7vlySygyprjQcdc+ATSN1mQosqCE1yhcNnNW HrwWn7AEI44uOe+Ge/nW28V0JqAxnQhxfxnAGa4Gje24xkzL+y5VOk4WOCH5+wmR8jSUXuJD7k/ 8bRWWkCjC2d8b79LP+NoEKnpSUECNN5XLZcT6PoGqQ+IjM9f X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org OpenVPN supports configuring a periodic keepalive packet. message to allow the remote endpoint detect link failures. This change implements the keepalive sending and timer expiring logic. Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/io.c | 77 +++++++++++++++++ drivers/net/ovpn/io.h | 5 ++ drivers/net/ovpn/main.c | 3 + drivers/net/ovpn/ovpnstruct.h | 2 + drivers/net/ovpn/peer.c | 188 ++++++++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/peer.h | 15 ++++ drivers/net/ovpn/proto.h | 2 - 7 files changed, 290 insertions(+), 2 deletions(-) diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 985f8bba6f1355b9f164e53f11575fa104133d43..4e69f31382d2cb9ce4bc40f06cfbae47add5b5ba 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -26,6 +26,33 @@ #include "udp.h" #include "skb.h" +const unsigned char ovpn_keepalive_message[OVPN_KEEPALIVE_SIZE] = { + 0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb, + 0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48 +}; + +/** + * ovpn_is_keepalive - check if skb contains a keepalive message + * @skb: packet to check + * + * Assumes that the first byte of skb->data is defined. + * + * Return: true if skb contains a keepalive or false otherwise + */ +static bool ovpn_is_keepalive(struct sk_buff *skb) +{ + if (*skb->data != ovpn_keepalive_message[0]) + return false; + + if (skb->len != OVPN_KEEPALIVE_SIZE) + return false; + + if (!pskb_may_pull(skb, OVPN_KEEPALIVE_SIZE)) + return false; + + return !memcmp(skb->data, ovpn_keepalive_message, OVPN_KEEPALIVE_SIZE); +} + /* Called after decrypt to write the IP packet to the device. * This method is expected to manage/free the skb. */ @@ -103,6 +130,9 @@ void ovpn_decrypt_post(void *data, int ret) goto drop; } + /* keep track of last received authenticated packet for keepalive */ + peer->last_recv = ktime_get_real_seconds(); + /* point to encapsulated IP packet */ __skb_pull(skb, payload_offset); @@ -119,6 +149,12 @@ void ovpn_decrypt_post(void *data, int ret) goto drop; } + if (ovpn_is_keepalive(skb)) { + net_dbg_ratelimited("%s: ping received from peer %u\n", + peer->ovpn->dev->name, peer->id); + goto drop; + } + net_info_ratelimited("%s: unsupported protocol received from peer %u\n", peer->ovpn->dev->name, peer->id); goto drop; @@ -217,6 +253,10 @@ void ovpn_encrypt_post(void *data, int ret) /* no transport configured yet */ goto err; } + + /* keep track of last sent packet for keepalive */ + peer->last_sent = ktime_get_real_seconds(); + /* skb passed down the stack - don't free it */ skb = NULL; err: @@ -355,3 +395,40 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev) kfree_skb_list(skb); return NET_XMIT_DROP; } + +/** + * ovpn_xmit_special - encrypt and transmit an out-of-band message to peer + * @peer: peer to send the message to + * @data: message content + * @len: message length + * + * Assumes that caller holds a reference to peer + */ +void ovpn_xmit_special(struct ovpn_peer *peer, const void *data, + const unsigned int len) +{ + struct ovpn_struct *ovpn; + struct sk_buff *skb; + + ovpn = peer->ovpn; + if (unlikely(!ovpn)) + return; + + skb = alloc_skb(256 + len, GFP_ATOMIC); + if (unlikely(!skb)) + return; + + skb_reserve(skb, 128); + skb->priority = TC_PRIO_BESTEFFORT; + __skb_put_data(skb, data, len); + + /* increase reference counter when passing peer to sending queue */ + if (!ovpn_peer_hold(peer)) { + netdev_dbg(ovpn->dev, "%s: cannot hold peer reference for sending special packet\n", + __func__); + kfree_skb(skb); + return; + } + + ovpn_send(ovpn, skb, peer); +} diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h index ad81dd86924689309b3299573575a1705eddaf99..eb224114152c29f42aadf026212e8d278006b490 100644 --- a/drivers/net/ovpn/io.h +++ b/drivers/net/ovpn/io.h @@ -10,9 +10,14 @@ #ifndef _NET_OVPN_OVPN_H_ #define _NET_OVPN_OVPN_H_ +#define OVPN_KEEPALIVE_SIZE 16 +extern const unsigned char ovpn_keepalive_message[OVPN_KEEPALIVE_SIZE]; + netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev); void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb); +void ovpn_xmit_special(struct ovpn_peer *peer, const void *data, + const unsigned int len); void ovpn_encrypt_post(void *data, int ret); void ovpn_decrypt_post(void *data, int ret); diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 7604f0970d3c283f8680f6800a125427999bb174..6048df5890c8fa46f3d91498903b4277a33f06db 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -43,6 +43,7 @@ static void ovpn_struct_init(struct net_device *dev, enum ovpn_mode mode) ovpn->dev = dev; ovpn->mode = mode; spin_lock_init(&ovpn->lock); + INIT_DELAYED_WORK(&ovpn->keepalive_work, ovpn_peer_keepalive_work); } static void ovpn_struct_free(struct net_device *net) @@ -241,6 +242,8 @@ void ovpn_iface_destruct(struct ovpn_struct *ovpn) ovpn->registered = false; + cancel_delayed_work_sync(&ovpn->keepalive_work); + switch (ovpn->mode) { case OVPN_MODE_P2P: ovpn_peer_release_p2p(ovpn); diff --git a/drivers/net/ovpn/ovpnstruct.h b/drivers/net/ovpn/ovpnstruct.h index 4373d1bbc4874dc2f1d1f1697e7b847ec2f486c9..11c6c568246aafe64af32e1a1faf24583afe7ca6 100644 --- a/drivers/net/ovpn/ovpnstruct.h +++ b/drivers/net/ovpn/ovpnstruct.h @@ -42,6 +42,7 @@ struct ovpn_peer_collection { * @peer: in P2P mode, this is the only remote peer * @dev_list: entry for the module wide device list * @gro_cells: pointer to the Generic Receive Offload cell + * @keepalive_work: struct used to schedule keepalive periodic job */ struct ovpn_struct { struct net_device *dev; @@ -53,6 +54,7 @@ struct ovpn_struct { struct ovpn_peer __rcu *peer; struct list_head dev_list; struct gro_cells gro_cells; + struct delayed_work keepalive_work; }; #endif /* _NET_OVPN_OVPNSTRUCT_H_ */ diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index 54d0a416f6f91513f42d5ecc1f1f65d688f3e908..dfaa4c7c012215b3dcd451b9bd1d7ffce9b1291c 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -22,6 +22,34 @@ #include "peer.h" #include "socket.h" +/** + * ovpn_peer_keepalive_set - configure keepalive values for peer + * @peer: the peer to configure + * @interval: outgoing keepalive interval + * @timeout: incoming keepalive timeout + */ +void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout) +{ + time64_t now = ktime_get_real_seconds(); + + netdev_dbg(peer->ovpn->dev, + "%s: scheduling keepalive for peer %u: interval=%u timeout=%u\n", + __func__, peer->id, interval, timeout); + + peer->keepalive_interval = interval; + peer->last_sent = now; + peer->keepalive_xmit_exp = now + interval; + + peer->keepalive_timeout = timeout; + peer->last_recv = now; + peer->keepalive_recv_exp = now + timeout; + + /* now that interval and timeout have been changed, kick + * off the worker so that the next delay can be recomputed + */ + mod_delayed_work(system_wq, &peer->ovpn->keepalive_work, 0); +} + /** * ovpn_peer_new - allocate and initialize a new peer object * @ovpn: the openvpn instance inside which the peer should be created @@ -76,6 +104,7 @@ static void ovpn_peer_release_rcu(struct rcu_head *head) ovpn_crypto_state_release(&peer->crypto); ovpn_bind_reset(peer, NULL); + dst_cache_destroy(&peer->dst_cache); } @@ -816,6 +845,19 @@ int ovpn_peer_del(struct ovpn_peer *peer, enum ovpn_del_peer_reason reason) } } +static int ovpn_peer_del_nolock(struct ovpn_peer *peer, + enum ovpn_del_peer_reason reason) +{ + switch (peer->ovpn->mode) { + case OVPN_MODE_MP: + return ovpn_peer_del_mp(peer, reason); + case OVPN_MODE_P2P: + return ovpn_peer_del_p2p(peer, reason); + default: + return -EOPNOTSUPP; + } +} + /** * ovpn_peers_free - free all peers in the instance * @ovpn: the instance whose peers should be released @@ -831,3 +873,149 @@ void ovpn_peers_free(struct ovpn_struct *ovpn) ovpn_peer_unhash(peer, OVPN_DEL_PEER_REASON_TEARDOWN); spin_unlock_bh(&ovpn->peers->lock); } + +static time64_t ovpn_peer_keepalive_work_single(struct ovpn_peer *peer, + time64_t now) +{ + time64_t next_run1, next_run2, delta; + unsigned long timeout, interval; + bool expired; + + spin_lock_bh(&peer->lock); + /* we expect both timers to be configured at the same time, + * therefore bail out if either is not set + */ + if (!peer->keepalive_timeout || !peer->keepalive_interval) { + spin_unlock_bh(&peer->lock); + return 0; + } + + /* check for peer timeout */ + expired = false; + timeout = peer->keepalive_timeout; + delta = now - peer->last_recv; + if (delta < timeout) { + peer->keepalive_recv_exp = now + timeout - delta; + next_run1 = peer->keepalive_recv_exp; + } else if (peer->keepalive_recv_exp > now) { + next_run1 = peer->keepalive_recv_exp; + } else { + expired = true; + } + + if (expired) { + /* peer is dead -> kill it and move on */ + spin_unlock_bh(&peer->lock); + netdev_dbg(peer->ovpn->dev, "peer %u expired\n", + peer->id); + ovpn_peer_del_nolock(peer, OVPN_DEL_PEER_REASON_EXPIRED); + return 0; + } + + /* check for peer keepalive */ + expired = false; + interval = peer->keepalive_interval; + delta = now - peer->last_sent; + if (delta < interval) { + peer->keepalive_xmit_exp = now + interval - delta; + next_run2 = peer->keepalive_xmit_exp; + } else if (peer->keepalive_xmit_exp > now) { + next_run2 = peer->keepalive_xmit_exp; + } else { + expired = true; + next_run2 = now + interval; + } + spin_unlock_bh(&peer->lock); + + if (expired) { + /* a keepalive packet is required */ + netdev_dbg(peer->ovpn->dev, + "sending keepalive to peer %u\n", + peer->id); + ovpn_xmit_special(peer, ovpn_keepalive_message, + sizeof(ovpn_keepalive_message)); + } + + if (next_run1 < next_run2) + return next_run1; + + return next_run2; +} + +static time64_t ovpn_peer_keepalive_work_mp(struct ovpn_struct *ovpn, + time64_t now) +{ + time64_t tmp_next_run, next_run = 0; + struct hlist_node *tmp; + struct ovpn_peer *peer; + int bkt; + + spin_lock_bh(&ovpn->peers->lock); + hash_for_each_safe(ovpn->peers->by_id, bkt, tmp, peer, hash_entry_id) { + tmp_next_run = ovpn_peer_keepalive_work_single(peer, now); + if (!tmp_next_run) + continue; + + /* the next worker run will be scheduled based on the shortest + * required interval across all peers + */ + if (!next_run || tmp_next_run < next_run) + next_run = tmp_next_run; + } + spin_unlock_bh(&ovpn->peers->lock); + + return next_run; +} + +static time64_t ovpn_peer_keepalive_work_p2p(struct ovpn_struct *ovpn, + time64_t now) +{ + struct ovpn_peer *peer; + time64_t next_run; + + spin_lock_bh(&ovpn->lock); + peer = rcu_dereference_protected(ovpn->peer, + lockdep_is_held(&ovpn->lock)); + next_run = ovpn_peer_keepalive_work_single(peer, now); + spin_unlock_bh(&ovpn->lock); + + return next_run; +} + +/** + * ovpn_peer_keepalive_work - run keepalive logic on each known peer + * @work: pointer to the work member of the related ovpn object + * + * Each peer has two timers (if configured): + * 1. peer timeout: when no data is received for a certain interval, + * the peer is considered dead and it gets killed. + * 2. peer keepalive: when no data is sent to a certain peer for a + * certain interval, a special 'keepalive' packet is explicitly sent. + * + * This function iterates across the whole peer collection while + * checking the timers described above. + */ +void ovpn_peer_keepalive_work(struct work_struct *work) +{ + struct ovpn_struct *ovpn = container_of(work, struct ovpn_struct, + keepalive_work.work); + time64_t next_run = 0, now = ktime_get_real_seconds(); + + switch (ovpn->mode) { + case OVPN_MODE_MP: + next_run = ovpn_peer_keepalive_work_mp(ovpn, now); + break; + case OVPN_MODE_P2P: + next_run = ovpn_peer_keepalive_work_p2p(ovpn, now); + break; + } + + /* prevent rearming if the interface is being destroyed */ + if (next_run > 0 && ovpn->registered) { + netdev_dbg(ovpn->dev, + "scheduling keepalive work: now=%llu next_run=%llu delta=%llu\n", + next_run, now, next_run - now); + schedule_delayed_work(&ovpn->keepalive_work, + (next_run - now) * HZ); + } +} diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 9481eb41742ca2a6c88ee0262dd448fd947614fd..e0d8d1255b837b7ddfdcedc5a56e7e747f13e2c5 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -50,6 +50,12 @@ * @crypto: the crypto configuration (ciphers, keys, etc..) * @dst_cache: cache for dst_entry used to send to peer * @bind: remote peer binding + * @keepalive_interval: seconds after which a new keepalive should be sent + * @keepalive_xmit_exp: future timestamp when next keepalive should be sent + * @last_sent: timestamp of the last successfully sent packet + * @keepalive_timeout: seconds after which an inactive peer is considered dead + * @keepalive_recv_exp: future timestamp when the peer should expire + * @last_recv: timestamp of the last authenticated received packet * @halt: true if ovpn_peer_mark_delete was called * @vpn_stats: per-peer in-VPN TX/RX stays * @link_stats: per-peer link/transport TX/RX stats @@ -98,6 +104,12 @@ struct ovpn_peer { struct ovpn_crypto_state crypto; struct dst_cache dst_cache; struct ovpn_bind __rcu *bind; + unsigned long keepalive_interval; + unsigned long keepalive_xmit_exp; + time64_t last_sent; + unsigned long keepalive_timeout; + unsigned long keepalive_recv_exp; + time64_t last_recv; bool halt; struct ovpn_peer_stats vpn_stats; struct ovpn_peer_stats link_stats; @@ -144,4 +156,7 @@ struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn, bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb, struct ovpn_peer *peer); +void ovpn_peer_keepalive_set(struct ovpn_peer *peer, u32 interval, u32 timeout); +void ovpn_peer_keepalive_work(struct work_struct *work); + #endif /* _NET_OVPN_OVPNPEER_H_ */ diff --git a/drivers/net/ovpn/proto.h b/drivers/net/ovpn/proto.h index 32af6b8e574381fb719a1b3b9de3ae1071cc4846..0de8bafadc89ebb85ce40de95ef394588738a4ad 100644 --- a/drivers/net/ovpn/proto.h +++ b/drivers/net/ovpn/proto.h @@ -35,8 +35,6 @@ #define OVPN_OP_SIZE_V2 4 #define OVPN_PEER_ID_MASK 0x00FFFFFF #define OVPN_PEER_ID_UNDEF 0x00FFFFFF -/* first byte of keepalive message */ -#define OVPN_KEEPALIVE_FIRST_BYTE 0x2a /* first byte of exit message */ #define OVPN_EXPLICIT_EXIT_NOTIFY_FIRST_BYTE 0x28 From patchwork Wed Oct 2 09:02:31 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819524 X-Patchwork-Delegate: kuba@kernel.org 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 43FE91C0DD6 for ; Wed, 2 Oct 2024 09:03:39 +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=1727859822; cv=none; b=W+ZOEK9fTN1X4cVd/FyNEcO3DHMNsDMQati5i6OoajLJ+tEPUor/c26VlZWHmI17+XcbNZg3Lajr+eKpZuCeLaBIbwwfcSUpw3zuyF+QMTYSIgIjFxUx6lZT2CnjVIcpqMgiFTW5Xu8Df3QGN8eyHCju41TGVya/ZhcGN3jJI2U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859822; c=relaxed/simple; bh=em86kif804PfCNBmcFWnuObwa9l8Vt83cqY4INjENwo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ILdCOCGwBDQCnBaEatUp6kAwevJO5bGe61O5OSoivPM+2htjAUJuB0bcq2C0RZ1kEGClrS1bqKc8ZmHrPIOY/De5yYg33N5lEvAJ783GIfIT4GIqDUmfqfeU1DFWeoMv5o47sDVme/jTKFGJ11mfTXP1kZCq6zOVWJyXiCVHzGY= 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=QWl555ky; 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="QWl555ky" Received: by mail-wm1-f51.google.com with SMTP id 5b1f17b1804b1-42cb9a0c300so50792265e9.0 for ; Wed, 02 Oct 2024 02:03:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859817; x=1728464617; 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=M4U2CGV7Muz0s4AKHAl9CLluIYCvToAM/yW8K6lT7kw=; b=QWl555ky6tsmFx1W+LFjVV195OajZjc0dIE3Ha5IaVhZW1rOffRHFofbDelY0mA/+f mF3MSR1ouq84eddhvKB1ARvduG0L1RZxN6pofsBcNFdbQBxKpkH6i20sGIOuRrdSOrZ9 vVZ8p3AGNIIs7pR1Jaz+RxO7aVFwczMFxm/4DB+sKTDYWYWbGBJQdotHV9gsqNRFrcbP HYCIrCNd9SmXw58AaP58q8L5/w/bqnXa0mHH0haeFW/odDE9h6C0rWm/XLAiSULnlm0a TwFTnyN+qn7/baEtQ4nuA3grqsA39RHyDEJjUq3TvmYa7xnqxPT/geArsUCLz4Albim+ hHaw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859817; x=1728464617; 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=M4U2CGV7Muz0s4AKHAl9CLluIYCvToAM/yW8K6lT7kw=; b=S+f1kzjHyyQZOOFPo5VU15wdGNna+kqwVGlPDbee836matqki9hZd3DOFd+JkGuOD5 uTDTlyP5oHSrM2SOcIjNV0J+39d1/e28Db8xhYfBPwp4hyEv/BqyKVTkMichpgnuCx3k bKqzlYAC7DCy8CNx70bZymu0TFrEns9S6Ul60VSkTI5TTZeuWfjh1WiJeEM8kdU4ozoF aqOyx+X9xCUYDET8IkMZFW0AK4uSaonMfL5kRtQgk8iCPvH/sk5VYAQlQsgsy2Ln69dO GfmAxAJ6EGRNW/4EeXB7fpsHvbWPDH72EJkBOZzki+kA9GKawz20aT188NPG7iA5Qlhr 1Ssw== X-Gm-Message-State: AOJu0YzRSQ9Q39sHuzb0Q3+88U5DEIDVXyQh/zSSGLyYNqps0vovMtYJ NdeSrmYOQa2uSvtgIXxmHSC5f5TNHNBdQozuHjODhB7Q1ri3yZs182F98yt4pVc= X-Google-Smtp-Source: AGHT+IH9aZ1vVNBI6bT6zIA82+8YSkypRqTem5OODRt/ezqjNhFnFYCWf59vLNgAuurWVB/PeYOr5g== X-Received: by 2002:a05:600c:1d26:b0:42c:bae0:f05b with SMTP id 5b1f17b1804b1-42f777b0b18mr16920025e9.1.1727859817534; Wed, 02 Oct 2024 02:03:37 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:37 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:31 +0200 Subject: [PATCH net-next v8 17/24] ovpn: add support for updating local UDP endpoint Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-17-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=2783; i=antonio@openvpn.net; h=from:subject:message-id; bh=em86kif804PfCNBmcFWnuObwa9l8Vt83cqY4INjENwo=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxSiIvZ1T7l6cNYuhG2aSBM1H2D74AI73/f7 FoyNbdLNvuJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUgAKCRALcOU6oDjV hxk9B/9/NHMXXWFPR1PVGYzYsGDBAAgpL83X4pLra9JkQ2gXTuMrvCzVc3aqkQx84VXDCHuorFH C+ZSB1qMARRATAba7CrYfEwHi7DRKze4yXYY72NyOMgWD+XhhwOSF/bUPSIPOFprhtyphENHLiR 5evjJD3mRg6RNWJ12kwHYAIq+0xDvW/q9INN1aZcEWtUELz8h5xarg+AhugrtKm4tfxxG2I2O4Z sEYqzSPg8dWhXrbpoD+G837vVYavVgMW3dtxi5lYexcuyLc2cdeSJ5t6He9DeDtbHr3huaN97/Y Hin8iY/qh3/4FnjGBHKXoUmhSXif7v0ikdLFfkWFM8l8FayT X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org In case of UDP links, the local endpoint used to communicate with a given peer may change without a connection restart. Add support for learning the new address in case of change. Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/peer.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/peer.h | 3 +++ 2 files changed, 48 insertions(+) diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index dfaa4c7c012215b3dcd451b9bd1d7ffce9b1291c..f9d8f1d1827fe67dc4b4e0bba41a5b110bb90819 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -418,6 +418,51 @@ struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id) return peer; } +/** + * ovpn_peer_update_local_endpoint - update local endpoint for peer + * @peer: peer to update the endpoint for + * @skb: incoming packet to retrieve the destination address (local) from + */ +void ovpn_peer_update_local_endpoint(struct ovpn_peer *peer, + struct sk_buff *skb) +{ + struct ovpn_bind *bind; + + rcu_read_lock(); + bind = rcu_dereference(peer->bind); + if (unlikely(!bind)) + goto unlock; + + spin_lock_bh(&peer->lock); + switch (skb_protocol_to_family(skb)) { + case AF_INET: + if (unlikely(bind->local.ipv4.s_addr != ip_hdr(skb)->daddr)) { + netdev_dbg(peer->ovpn->dev, + "%s: learning local IPv4 for peer %d (%pI4 -> %pI4)\n", + __func__, peer->id, &bind->local.ipv4.s_addr, + &ip_hdr(skb)->daddr); + bind->local.ipv4.s_addr = ip_hdr(skb)->daddr; + } + break; + case AF_INET6: + if (unlikely(!ipv6_addr_equal(&bind->local.ipv6, + &ipv6_hdr(skb)->daddr))) { + netdev_dbg(peer->ovpn->dev, + "%s: learning local IPv6 for peer %d (%pI6c -> %pI6c\n", + __func__, peer->id, &bind->local.ipv6, + &ipv6_hdr(skb)->daddr); + bind->local.ipv6 = ipv6_hdr(skb)->daddr; + } + break; + default: + break; + } + spin_unlock_bh(&peer->lock); + +unlock: + rcu_read_unlock(); +} + /** * ovpn_peer_get_by_dst - Lookup peer to send skb to * @ovpn: the private data representing the current VPN session diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index e0d8d1255b837b7ddfdcedc5a56e7e747f13e2c5..6b66e169b33510a794f8f43dff757f0357e3e5de 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -159,4 +159,7 @@ bool ovpn_peer_check_by_src(struct ovpn_struct *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_update_local_endpoint(struct ovpn_peer *peer, + struct sk_buff *skb); + #endif /* _NET_OVPN_OVPNPEER_H_ */ From patchwork Wed Oct 2 09:02:32 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819525 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f54.google.com (mail-wr1-f54.google.com [209.85.221.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2F19C1C2DCB for ; Wed, 2 Oct 2024 09:03:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859824; cv=none; b=SyuwtBks6a6kMfCHe1U+OVro9FdE704lOx+H2B+rYCBe+toiKXMVH6XN757aD9szZjGb4SmMWalf9tW74rplRI4QaIgmA9SYO/Ft1lO9YS6WprC1YLZg2bJQPG7s99/uUhb6SP2IIGFDLMTJoSHZPg6ry9IN1FQKTxQBgOAyqdU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859824; c=relaxed/simple; bh=FuAy5kHlJKnyzlxHVywHgmUSOgYpPg1bhUmvEOmeEd4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=AlX4+WW999GaT1udy7FWSGfjE+QA/+0kR1VQS+NrWVR52FCtT8JKJJ2yTjKAbMxR19Zars5CgY7g7YPtmhA+QoI9YVHfGc1jZPKAyV5jsJTMAlUWpMufsTMDqm2T1It9j6WBmoUZtFfe7OGhN7UJIVApUe1OqunXUw4BRxMMlSE= 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=dP7Ll++m; arc=none smtp.client-ip=209.85.221.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="dP7Ll++m" Received: by mail-wr1-f54.google.com with SMTP id ffacd0b85a97d-37cdbdbbb1cso2415024f8f.3 for ; Wed, 02 Oct 2024 02:03:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859818; x=1728464618; 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=V1ge8RFj87ePe1fJxGxvLo18nD2mofhoIzDaa4Vgsqw=; b=dP7Ll++mDjcwMyC+JxZY+ij98f6pRw7GCckOnZ5y29LXIg4phw0Dy7G0mEsRQtv2ZP AOTqsAJBBWXEExYkwYy0BCvkwykBe6r73feyffedURKh3gHor2PSlk+qzoY+4fikHK2p yFPVvlYfSIbhmMVXtA6OU4bndeRadJrPedoZ51m8ovx5vBxsXBBuFt4k7ZNZqSJgDRVa j0yqU5DVkwtr6SBh6QCZGsYOo5ksXLu0kfFa4A0c/ErCkANcdDRa/eYUkFoQbJyib4Ec EFCuxYdq17g2oRMIUk0RNMvqSWtndWboenDS/RKSlPHUCrBKq4YtVXad4MCUF15VN8ax D6eA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859818; x=1728464618; 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=V1ge8RFj87ePe1fJxGxvLo18nD2mofhoIzDaa4Vgsqw=; b=J4uUmZZaFEyFoJvPCMdZPLGVzel10JvDfa0lT3SllRiUJf+ylkbr3GtEaOUDUYKVWq tehkpB07Ez9bSBvrxdGtrSLws2cGfO0AbFnmC0lfeqI4vmr8n4R8iMAw+b1SKjsmMJMA Y84Zudl4bhUibm2Jzrafm8xrhRD0d8nt2dcndBjH+awbPb3vqpfFnLHdOgC2IH0CLZw5 Vl+MqjEcTYpR1V3eEXgmXRuR/HGNQkkp/iOSQA1bIf+d2dL3eWWJZgeOZA0Fcw3OpJuE 8e3wgekvMUiV9NozvVrOF2EbJM6BIYi75j+2M4YhTLzBVsBmxmDtm2CjbU4OTevIoA4W yZUA== X-Gm-Message-State: AOJu0YyxFfo3eHSc6lwJmbEC1bArJhtPAQVJUA7MF85wsqMzk9ZAKcNV xXgeAJSpHu8jMqUz4FzcNNcQM1qowFbHdPLm860qQ5qLTOZeIva8JAJNu6qcdck= X-Google-Smtp-Source: AGHT+IHQn6w8+6VgStUg9D6XQXu4qtWE/udO+C0cwI9N0zr24eLEbJvdFaTMXnX0HhixUEZ3f/RLVg== X-Received: by 2002:adf:e447:0:b0:374:d1dd:1cc1 with SMTP id ffacd0b85a97d-37cfb8a490bmr1491379f8f.2.1727859818484; Wed, 02 Oct 2024 02:03:38 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:38 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:32 +0200 Subject: [PATCH net-next v8 18/24] ovpn: add support for peer floating Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-18-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=7156; i=antonio@openvpn.net; h=from:subject:message-id; bh=FuAy5kHlJKnyzlxHVywHgmUSOgYpPg1bhUmvEOmeEd4=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxTJyusJDPH1TX6vPHfibBEg6qpzzlVxoqZk DD2rnZlWW+JATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUwAKCRALcOU6oDjV h5Y3CACeRnHDDiwuBv8VvGSUkKhhSvCMthRqDSk7Jy5/7OxZata9a94PhS40HFwBr23e722OhuV YnbhrUwQ9JTN3Tt4xbfSYyigYnwFpZQvnBqaoiP8wj5Wup/EM0PGcF+vRrb98XPsGXdvE4Becuw O5i1QZ4PQ2YwrFC9ZGO1Xr6ejH6EZuo2J5ahuO1Tnj9SCy6MY2t3oRNZkQ21KS7LhOW1wg4JYOV PRIQ9s1Vk0a6sXDQVLwp97mfFJCwo0XoKl6iuzSn4vLwr05Y2D4ziPQamj+jE/ffs7pxKpBU49t h5TmWrg5PvXV4i6ZQ/iLA4oBGEco7uJiedkNqTPEizBA6Lfs X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org A peer connected via UDP may change its IP address without reconnecting (float). Add support for detecting and updating the new peer IP/port in case of floating. Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/bind.c | 10 ++-- drivers/net/ovpn/io.c | 9 ++++ drivers/net/ovpn/peer.c | 128 ++++++++++++++++++++++++++++++++++++++++++++++-- drivers/net/ovpn/peer.h | 2 + 4 files changed, 138 insertions(+), 11 deletions(-) diff --git a/drivers/net/ovpn/bind.c b/drivers/net/ovpn/bind.c index b4d2ccec2ceddf43bc445b489cc62a578ef0ad0a..d17d078c5730bf4336dc87f45cdba3f6b8cad770 100644 --- a/drivers/net/ovpn/bind.c +++ b/drivers/net/ovpn/bind.c @@ -47,12 +47,8 @@ struct ovpn_bind *ovpn_bind_from_sockaddr(const struct sockaddr_storage *ss) * @new: the new bind to assign */ void ovpn_bind_reset(struct ovpn_peer *peer, struct ovpn_bind *new) + __must_hold(&peer->lock) { - struct ovpn_bind *old; - - spin_lock_bh(&peer->lock); - old = rcu_replace_pointer(peer->bind, new, true); - spin_unlock_bh(&peer->lock); - - kfree_rcu(old, rcu); + kfree_rcu(rcu_replace_pointer(peer->bind, new, + lockdep_is_held(&peer->lock)), rcu); } diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 4e69f31382d2cb9ce4bc40f06cfbae47add5b5ba..8f2b4a85d20fbdb512de7ec312d391985e96b906 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -133,6 +133,15 @@ void ovpn_decrypt_post(void *data, int ret) /* keep track of last received authenticated packet for keepalive */ peer->last_recv = ktime_get_real_seconds(); + if (peer->sock->sock->sk->sk_protocol == IPPROTO_UDP) { + /* check if this peer changed it's IP address and update + * state + */ + ovpn_peer_float(peer, skb); + /* update source endpoint for this peer */ + ovpn_peer_update_local_endpoint(peer, skb); + } + /* 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 f9d8f1d1827fe67dc4b4e0bba41a5b110bb90819..891cf2fa6a81b46ac764ad4fd50d4456fa7ce5bd 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -94,6 +94,128 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id) return peer; } +/** + * ovpn_peer_reset_sockaddr - recreate binding for peer + * @peer: peer to recreate the binding for + * @ss: sockaddr to use as remote endpoint for the binding + * @local_ip: local IP for the binding + * + * Return: 0 on success or a negative error code otherwise + */ +static int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, + const struct sockaddr_storage *ss, + const u8 *local_ip) + __must_hold(&peer->lock) +{ + struct ovpn_bind *bind; + size_t ip_len; + + /* create new ovpn_bind object */ + bind = ovpn_bind_from_sockaddr(ss); + if (IS_ERR(bind)) + return PTR_ERR(bind); + + if (local_ip) { + if (ss->ss_family == AF_INET) { + ip_len = sizeof(struct in_addr); + } else if (ss->ss_family == AF_INET6) { + ip_len = sizeof(struct in6_addr); + } else { + netdev_dbg(peer->ovpn->dev, "%s: invalid family for remote endpoint\n", + __func__); + kfree(bind); + return -EINVAL; + } + + memcpy(&bind->local, local_ip, ip_len); + } + + /* set binding */ + ovpn_bind_reset(peer, bind); + + return 0; +} + +#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \ + typeof(_tbl) *__tbl = &(_tbl); \ + (&(*__tbl)[jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl)]); }) \ + +/** + * ovpn_peer_float - update remote endpoint for peer + * @peer: peer to update the remote endpoint for + * @skb: incoming packet to retrieve the source address (remote) from + */ +void ovpn_peer_float(struct ovpn_peer *peer, struct sk_buff *skb) +{ + struct hlist_nulls_head *nhead; + struct sockaddr_storage ss; + const u8 *local_ip = NULL; + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa; + struct ovpn_bind *bind; + sa_family_t family; + size_t salen; + + rcu_read_lock(); + bind = rcu_dereference(peer->bind); + if (unlikely(!bind)) { + rcu_read_unlock(); + return; + } + + spin_lock_bh(&peer->lock); + if (likely(ovpn_bind_skb_src_match(bind, skb))) + goto unlock; + + family = skb_protocol_to_family(skb); + + if (bind->remote.in4.sin_family == family) + local_ip = (u8 *)&bind->local; + + switch (family) { + case AF_INET: + sa = (struct sockaddr_in *)&ss; + sa->sin_family = AF_INET; + sa->sin_addr.s_addr = ip_hdr(skb)->saddr; + sa->sin_port = udp_hdr(skb)->source; + salen = sizeof(*sa); + break; + case AF_INET6: + sa6 = (struct sockaddr_in6 *)&ss; + sa6->sin6_family = AF_INET6; + sa6->sin6_addr = ipv6_hdr(skb)->saddr; + sa6->sin6_port = udp_hdr(skb)->source; + sa6->sin6_scope_id = ipv6_iface_scope_id(&ipv6_hdr(skb)->saddr, + skb->skb_iif); + salen = sizeof(*sa6); + break; + default: + goto unlock; + } + + netdev_dbg(peer->ovpn->dev, "%s: peer %d floated to %pIScp", __func__, + peer->id, &ss); + ovpn_peer_reset_sockaddr(peer, (struct sockaddr_storage *)&ss, + local_ip); + + /* 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->peers->lock); + /* 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, + &ss, salen); + hlist_nulls_add_head_rcu(&peer->hash_entry_transp_addr, nhead); + spin_unlock_bh(&peer->ovpn->peers->lock); + } +unlock: + spin_unlock_bh(&peer->lock); + rcu_read_unlock(); +} + /** * ovpn_peer_release_rcu - release peer private members * @head: RCU head belonging to peer being released @@ -103,7 +225,9 @@ static void ovpn_peer_release_rcu(struct rcu_head *head) struct ovpn_peer *peer = container_of(head, struct ovpn_peer, rcu); ovpn_crypto_state_release(&peer->crypto); + spin_lock_bh(&peer->lock); ovpn_bind_reset(peer, NULL); + spin_unlock_bh(&peer->lock); dst_cache_destroy(&peer->dst_cache); } @@ -188,10 +312,6 @@ static struct in6_addr ovpn_nexthop_from_skb6(struct sk_buff *skb) return rt->rt6i_gateway; } -#define ovpn_get_hash_head(_tbl, _key, _key_len) ({ \ - typeof(_tbl) *__tbl = &(_tbl); \ - (&(*__tbl)[jhash(_key, _key_len, 0) % HASH_SIZE(*__tbl)]); }) \ - /** * ovpn_peer_get_by_vpn_addr4 - retrieve peer by its VPN IPv4 address * @ovpn: the openvpn instance to search diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index 6b66e169b33510a794f8f43dff757f0357e3e5de..ea1a014568c64d796bb18447ceb70e801bfaf3f2 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -162,4 +162,6 @@ void ovpn_peer_keepalive_work(struct work_struct *work); void ovpn_peer_update_local_endpoint(struct ovpn_peer *peer, struct sk_buff *skb); +void ovpn_peer_float(struct ovpn_peer *peer, struct sk_buff *skb); + #endif /* _NET_OVPN_OVPNPEER_H_ */ From patchwork Wed Oct 2 09:02:33 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819528 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f48.google.com (mail-wr1-f48.google.com [209.85.221.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 30F64198E70 for ; Wed, 2 Oct 2024 09:03:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859826; cv=none; b=msXNSuaQYPMdEaxisxr1o8Rtbx5iPplfPJ6X6NDTGHo5ZbvWQYKgw5MJ7kOwJDYhw66DeD+I8KY15QTVybRdTi1+EFwj57QlJ626byqxzKL8lpOwEhQWMttx4EloptguGaUF1idQFc6o5TNf5XnJpy6FhjFxMDT4i9jWBOkvsGY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859826; c=relaxed/simple; bh=H4+i114YJm3liai/Cvv0xDa2K61txu3p5IFR9Mn/5x8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=PYKrIsfOauO5Uz8dID5XdJyag3u/DSvnNvuEMw/dnVXgYHBwspp1NcxFhf8O9H4GwaoiyRYJmy/g/RrzADLKDVJeI0h3S/IooCW1UY4jkg7wGssyC03/anU6twqHECwhBFA8+0YCop4+/sWtqbLZw+jYYpGHK0827/qGYISbDTQ= 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=BYJf8Hm1; arc=none smtp.client-ip=209.85.221.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="BYJf8Hm1" Received: by mail-wr1-f48.google.com with SMTP id ffacd0b85a97d-37cd5016d98so3315611f8f.1 for ; Wed, 02 Oct 2024 02:03:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859819; x=1728464619; 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=RB0BwhQTgDngo8VwP9NQd2HqOggg5wOwfXmAbwA3ocM=; b=BYJf8Hm1IUCLE/a8/GHlUw9ej9RoeCuYBGunbUIg3wuhP3s2FY5pu8+NJvl5ZW0Sm8 G8s2CiYbMLLl21VLXxppiJA1Mh+Nl1SbyKO1UJvUKywoSrls+eymxa2M1kDNYdojaDIX 3Z4f9TArVfv654DTQ6X/Vk37rmyKfrWjQCr7uu6VJu7fKxMUqX+/oQaOmfrZ2JCWjTvw s+aEVzR+wt8j5bapk46zORofZTQR17BdS90NYf7bgxlvXHCs88jHHiIqhd8j3bvnn7EU XrXwctdGyludQajMX3GIJ2vVPdbnjOhsQuDfGsyvvlglLObV/LcAWZEakERjtSyWf2t/ /BYA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859819; x=1728464619; 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=RB0BwhQTgDngo8VwP9NQd2HqOggg5wOwfXmAbwA3ocM=; b=ZfFAoCtV+u0ct0mnULXh7x1PNrwXe8Bj87H+yMvKiH6o9BFwDx0hH/4JHGYrcwXX39 RT/ADgkshTZAHOroBY+xApJnnw93DN50cDMLD2hN4BLEXIBJEThjwxjXajgHe4opKIrV a4jx3FtFSjB5uklrPR07658if/+4YOxGeLrT4sXVY/E5PaCbFnukQYrm9mxfX/irMJrN ui57EucqWB2GZs2qFEroYlBZamqqpCAKLRMp9QAqH3vuXpEhpxkhANTptUr/DAtX602I vVc+e4BIaNphZmL5UoRzmWEKavVaGNCy9l5MDwsNG8nylh2lmQbVaQSROQIr6ZWnLMWp sIyQ== X-Gm-Message-State: AOJu0Yx+sRhhTmiUF+3RFdifFx3LISd0YTri/OtupzBseLQHog/Lyn1y V0SA4iIgKD6QEJgYx7uJBb5MpV41p+EYorMnluZsYCvdpjFuV5rnnOxLMSu/ur0= X-Google-Smtp-Source: AGHT+IE7XkUXvaw/pbULrKkqbt8wP/3h8HHjV3r82I7TlayR0PKHJzGbYh0XnCvPsNg5izGD8HYrDA== X-Received: by 2002:adf:e711:0:b0:37c:ccbc:6573 with SMTP id ffacd0b85a97d-37cfb8c7618mr1420598f8f.18.1727859819479; Wed, 02 Oct 2024 02:03:39 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:39 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:33 +0200 Subject: [PATCH net-next v8 19/24] ovpn: implement peer add/dump/delete via netlink Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-19-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=23353; i=antonio@openvpn.net; h=from:subject:message-id; bh=H4+i114YJm3liai/Cvv0xDa2K61txu3p5IFR9Mn/5x8=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxTam3NeBZH1pBHr3g1hh8bz8KWlx7JFIFy0 wlUIzpy4KiJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUwAKCRALcOU6oDjV hxuwB/9p+EL7KdF5OYOyMOrNHwCmwtbAoLFmbT16YtaWJYZR24vubELJRCZRS+qkU+yGukNyT2o A2mswPp8b1jp39NP6RrGCMZG915NKR06LFzt2N40iTPKryH9Li6Q8BarTeMGRX/xLTFS4fVXM1p KRBzg/WButX8WufuEKt6z4gnzJ0Tb8pbHNR8RGX8h+hutpueFzbC4WbPii0s3PACjw4uxM4oih+ j3v6VFJiCezx92pLf2EnrUsPnzsLtmpgXutmE9D2XEMCAoNONK7hfH1zoGtFbBDzR/SI1hpz2YV cp+TCB3GEB8h8QtiPmWMjw9tt58aq6u3uGeHHvAucP7NhDSQ X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org 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 | 582 ++++++++++++++++++++++++++++++++++++++++++++- drivers/net/ovpn/peer.c | 66 +++-- drivers/net/ovpn/peer.h | 5 + 3 files changed, 625 insertions(+), 28 deletions(-) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 6e60591d605dde19c6bbd47ef0e90e522776688c..29ff6023f9135b81a1d5fa42354398c70008aee5 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -17,6 +18,10 @@ #include "io.h" #include "netlink.h" #include "netlink-gen.h" +#include "bind.h" +#include "packet.h" +#include "peer.h" +#include "socket.h" MODULE_ALIAS_GENL_FAMILY(OVPN_FAMILY_NAME); @@ -147,29 +152,596 @@ int ovpn_nl_dev_del_doit(struct sk_buff *skb, struct genl_info *info) return 0; } +static int 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; + int af; + + 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]) { + af = AF_INET; + ss->ss_family = AF_INET; + in = nla_data(attrs[OVPN_A_PEER_REMOTE_IPV4]); + } else if (attrs[OVPN_A_PEER_REMOTE_IPV6]) { + af = AF_INET6; + ss->ss_family = AF_INET6; + in6 = nla_data(attrs[OVPN_A_PEER_REMOTE_IPV6]); + } else { + return AF_UNSPEC; + } + + 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; + } + + /* don't return ss->ss_family as it may have changed in case of + * v4-mapped-v6 address + */ + return af; +} + +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 int ovpn_nl_peer_precheck(struct ovpn_struct *ovpn, + struct genl_info *info, + struct nlattr **attrs) +{ + 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_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; + } + + if (!attrs[OVPN_A_PEER_REMOTE_IPV6] && + 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, + "VPN IP unexpected 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 = {}; + u32 sockfd, interv, timeout; + struct socket *sock = NULL; + u8 *local_ip = NULL; + bool rehash = false; + int ret; + + if (attrs[OVPN_A_PEER_SOCKET]) { + /* lookup the fd in the kernel table and extract the socket + * object + */ + sockfd = nla_get_u32(attrs[OVPN_A_PEER_SOCKET]); + /* sockfd_lookup() increases sock's refcounter */ + sock = sockfd_lookup(sockfd, &ret); + if (!sock) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot lookup peer socket (fd=%u): %d", + sockfd, ret); + return -ENOTSOCK; + } + + /* Only when using UDP as transport protocol the remote endpoint + * can be configured so that ovpn knows where to send packets + * to. + * + * In case of TCP, the socket is connected to the peer and ovpn + * will just send bytes over it, without the need to specify a + * destination. + */ + if (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, + "unexpected remote IP address for non UDP socket"); + sockfd_put(sock); + return -EINVAL; + } + + if (peer->sock) + ovpn_socket_put(peer->sock); + + peer->sock = ovpn_socket_new(sock, peer); + if (IS_ERR(peer->sock)) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot encapsulate socket: %ld", + PTR_ERR(peer->sock)); + sockfd_put(sock); + peer->sock = NULL; + return -ENOTSOCK; + } + } + + if (ovpn_nl_attr_sockaddr_remote(attrs, &ss) != AF_UNSPEC) { + /* 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); + + spin_lock_bh(&peer->lock); + /* 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); + spin_unlock_bh(&peer->lock); + return ret; + } + spin_unlock_bh(&peer->lock); + } + + 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, + "%s: peer id=%u endpoint=%pIScp/%s VPN-IPv4=%pI4 VPN-IPv6=%pI6c\n", + __func__, peer->id, &ss, + peer->sock->sock->sk->sk_prot_creator->name, + &peer->vpn_addrs.ipv4.s_addr, &peer->vpn_addrs.ipv6); + + return rehash ? 1 : 0; +} + 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_struct *ovpn = info->user_ptr[0]; + struct ovpn_peer *peer; + u32 peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER)) + return -EINVAL; + + ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER], + ovpn_peer_nl_policy, info->extack); + if (ret) + return ret; + + 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; + + 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); + } + + ret = ovpn_nl_peer_modify(peer, info, attrs); + if (ret < 0) + goto peer_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\n", + peer->id, ret); + goto peer_release; + } + + return 0; + +peer_release: + /* release right away because peer is not used in any context */ + ovpn_peer_release(peer); + kfree(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_struct *ovpn = info->user_ptr[0]; + struct ovpn_peer *peer; + u32 peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER)) + return -EINVAL; + + ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER], + ovpn_peer_nl_policy, info->extack); + if (ret) + return ret; + + ret = ovpn_nl_peer_precheck(ovpn, info, attrs); + if (ret < 0) + return ret; + + peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]); + peer = ovpn_peer_get_by_id(ovpn, peer_id); + if (!peer) + return -ENOENT; + + ret = ovpn_nl_peer_modify(peer, info, attrs); + if (ret < 0) { + ovpn_peer_put(peer); + return ret; + } + + /* ret == 1 means that VPN IPv4/6 has been modified and rehashing + * is required + */ + if (ret > 0) { + spin_lock_bh(&ovpn->peers->lock); + ovpn_peer_hash_vpn_ip(peer); + spin_unlock_bh(&ovpn->peers->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 nlattr *attr; + void *hdr; + + 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; + + 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, + inet_sk(peer->sock->sock->sk)->inet_sport) || + /* VPN RX stats */ + nla_put_uint(skb, OVPN_A_PEER_VPN_RX_BYTES, + atomic64_read(&peer->vpn_stats.rx.bytes)) || + nla_put_uint(skb, OVPN_A_PEER_VPN_RX_PACKETS, + atomic64_read(&peer->vpn_stats.rx.packets)) || + /* VPN TX stats */ + nla_put_uint(skb, OVPN_A_PEER_VPN_TX_BYTES, + atomic64_read(&peer->vpn_stats.tx.bytes)) || + nla_put_uint(skb, OVPN_A_PEER_VPN_TX_PACKETS, + atomic64_read(&peer->vpn_stats.tx.packets)) || + /* link RX stats */ + nla_put_uint(skb, OVPN_A_PEER_LINK_RX_BYTES, + atomic64_read(&peer->link_stats.rx.bytes)) || + nla_put_uint(skb, OVPN_A_PEER_LINK_RX_PACKETS, + atomic64_read(&peer->link_stats.rx.packets)) || + /* link TX stats */ + nla_put_uint(skb, OVPN_A_PEER_LINK_TX_BYTES, + atomic64_read(&peer->link_stats.tx.bytes)) || + nla_put_uint(skb, OVPN_A_PEER_LINK_TX_PACKETS, + atomic64_read(&peer->link_stats.tx.packets))) + goto err; + + nla_nest_end(skb, attr); + genlmsg_end(skb, hdr); + + return 0; +err_unlock: + rcu_read_unlock(); +err: + genlmsg_cancel(skb, hdr); + return -EMSGSIZE; } int ovpn_nl_peer_get_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct ovpn_struct *ovpn = info->user_ptr[0]; + struct ovpn_peer *peer; + struct sk_buff *msg; + u32 peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER)) + return -EINVAL; + + ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER], + ovpn_peer_nl_policy, info->extack); + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs, + OVPN_A_PEER_ID)) + return -EINVAL; + + peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]); + peer = ovpn_peer_get_by_id(ovpn, peer_id); + if (!peer) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot find peer with id %u", peer_id); + return -ENOENT; + } + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) { + 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); + struct ovpn_struct *ovpn; + struct ovpn_peer *peer; + struct net_device *dev; + int bkt, last_idx = cb->args[1], dumped = 0; + + dev = ovpn_get_dev_from_attrs(sock_net(cb->skb->sk), info); + if (IS_ERR(dev)) + return PTR_ERR(dev); + + ovpn = netdev_priv(dev); + + if (ovpn->mode == OVPN_MODE_P2P) { + /* if we already dumped a peer it means we are done */ + if (last_idx) + goto out; + + rcu_read_lock(); + peer = rcu_dereference(ovpn->peer); + if (peer) { + if (ovpn_nl_send_peer(skb, info, peer, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, + NLM_F_MULTI) == 0) + dumped++; + } + rcu_read_unlock(); + } else { + rcu_read_lock(); + hash_for_each_rcu(ovpn->peers->by_id, bkt, peer, + hash_entry_id) { + /* skip already dumped peers that were dumped by + * previous invocations + */ + if (last_idx > 0) { + last_idx--; + continue; + } + + if (ovpn_nl_send_peer(skb, info, peer, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, + NLM_F_MULTI) < 0) + break; + + /* count peers being dumped during this invocation */ + dumped++; + } + rcu_read_unlock(); + } + +out: + netdev_put(dev, NULL); + + /* sum up peers dumped in this message, so that at the next invocation + * we can continue from where we left + */ + cb->args[1] += dumped; + return skb->len; } int ovpn_nl_peer_del_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct ovpn_struct *ovpn = info->user_ptr[0]; + struct ovpn_peer *peer; + u32 peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_PEER)) + return -EINVAL; + + ret = nla_parse_nested(attrs, OVPN_A_PEER_MAX, info->attrs[OVPN_A_PEER], + ovpn_peer_nl_policy, info->extack); + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_PEER], attrs, + OVPN_A_PEER_ID)) + return -EINVAL; + + peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]); + + peer = ovpn_peer_get_by_id(ovpn, peer_id); + if (!peer) + return -ENOENT; + + netdev_dbg(ovpn->dev, "%s: peer id=%u\n", __func__, peer->id); + ret = ovpn_peer_del(peer, OVPN_DEL_PEER_REASON_USERSPACE); + ovpn_peer_put(peer); + + return ret; } int ovpn_nl_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 891cf2fa6a81b46ac764ad4fd50d4456fa7ce5bd..1ce31f0317cb3593a8edf95c43d03a0bddb0a58f 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -102,9 +102,9 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_struct *ovpn, u32 id) * * Return: 0 on success or a negative error code otherwise */ -static int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, - const struct sockaddr_storage *ss, - const u8 *local_ip) +int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, + const struct sockaddr_storage *ss, + const u8 *local_ip) __must_hold(&peer->lock) { struct ovpn_bind *bind; @@ -216,6 +216,18 @@ void ovpn_peer_float(struct ovpn_peer *peer, struct sk_buff *skb) rcu_read_unlock(); } +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); + + dst_cache_destroy(&peer->dst_cache); + + netdev_put(peer->ovpn->dev, &peer->ovpn->dev_tracker); +} + /** * ovpn_peer_release_rcu - release peer private members * @head: RCU head belonging to peer being released @@ -224,12 +236,7 @@ static void ovpn_peer_release_rcu(struct rcu_head *head) { struct ovpn_peer *peer = container_of(head, struct ovpn_peer, rcu); - ovpn_crypto_state_release(&peer->crypto); - spin_lock_bh(&peer->lock); - ovpn_bind_reset(peer, NULL); - spin_unlock_bh(&peer->lock); - - dst_cache_destroy(&peer->dst_cache); + ovpn_peer_release(peer); } /** @@ -242,7 +249,6 @@ void ovpn_peer_release_kref(struct kref *kref) if (peer->sock) ovpn_socket_put(peer->sock); - netdev_put(peer->ovpn->dev, &peer->ovpn->dev_tracker); call_rcu(&peer->rcu, ovpn_peer_release_rcu); } @@ -764,6 +770,32 @@ bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb, return match; } +void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) + __must_hold(&peer->ovpn->peers->lock) +{ + struct hlist_nulls_head *nhead; + + if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) { + /* remove potential old hashing */ + hlist_nulls_del_init_rcu(&peer->hash_entry_transp_addr); + + nhead = ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr, + &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_transp_addr); + + nhead = ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr, + &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 @@ -825,19 +857,7 @@ static int ovpn_peer_add_mp(struct ovpn_struct *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_addr, - &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_addr, - &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->peers->lock); return ret; diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index ea1a014568c64d796bb18447ceb70e801bfaf3f2..9f2260b21710452a207542e618e5cda917600522 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -131,6 +131,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); /** @@ -153,6 +154,7 @@ struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_struct *ovpn, struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_struct *ovpn, u32 peer_id); struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_struct *ovpn, struct sk_buff *skb); +void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer); bool ovpn_peer_check_by_src(struct ovpn_struct *ovpn, struct sk_buff *skb, struct ovpn_peer *peer); @@ -163,5 +165,8 @@ void ovpn_peer_update_local_endpoint(struct ovpn_peer *peer, struct sk_buff *skb); void ovpn_peer_float(struct ovpn_peer *peer, struct sk_buff *skb); +int ovpn_peer_reset_sockaddr(struct ovpn_peer *peer, + const struct sockaddr_storage *ss, + const u8 *local_ip); #endif /* _NET_OVPN_OVPNPEER_H_ */ From patchwork Wed Oct 2 09:02:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819526 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f47.google.com (mail-wr1-f47.google.com [209.85.221.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3D0901C8FC6 for ; Wed, 2 Oct 2024 09:03:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859825; cv=none; b=n2sjfnkKxtA4RBmSfLOlGop5ItGjdgJskPoT73Hg0EjF/44s7CsXxIKX8aEWe/v5NfHamnafH9iu7h5OWdK1/r9VT3n37hp89hG9gPu5/ctZGNeevE1UGhikeFi0ttJ1W1F5rbpQ3nLxkvfnG1ZNiThk5iRarBdOB+fDzi38+q4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859825; c=relaxed/simple; bh=mceVO3o4Wlc3pK47t0GBHPAOEep2KLBEghv1pZ18o78=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=PjY6mbGoYleVHKCk1kr8sZYLlZu6ZoZij2TUYZpBKzwRElj52Cs7z6UJoQ11gpLnjpzlqQRZN+rqNSW+UWa6BtKkoXm/1k/18I7x4aohFcmNuM91KDu3W5zKqTwPTulj3VDKJhS1jf7aaWMAYYvuKAVUxhXlJ5a3dsYK3CdlZGk= 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=Ee8AEqIp; arc=none smtp.client-ip=209.85.221.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=openvpn.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=openvpn.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=openvpn.net header.i=@openvpn.net header.b="Ee8AEqIp" Received: by mail-wr1-f47.google.com with SMTP id ffacd0b85a97d-37cce5b140bso4151918f8f.3 for ; Wed, 02 Oct 2024 02:03:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859821; x=1728464621; 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=4+f0YYnaXKqWCx+QRho9ClCd3PUHIyO8++QfFS/YDAc=; b=Ee8AEqIpa1dv/+Yr1J7nO7amcUWxw0xBrGCr/sQthZZc+VCMqdlb33sQc44LRt7Ky2 CAHS5Jb7GLub/3oTj318C0aYF00DOE+MdbnsHBcnNKtNT+eLo6xTvYnWXbgVG9F+IC3p UXeZ06EcAqHlcdTkff/36othiKUWucrvCDZr3rmGBBwW8tsIWYydpCpSkX8AKwmglKz0 DVukHEVOkNU75jRRCpe1Lnb/pN2tuGxuweAQ6xgEwIyWvNVEbeO52Tmb1oh/0AEiuBb7 zmsTmw4vmvHzga4cmBLyLSOY/XfWgeykF5C9VwO00KL0JSeuGQtSviiT4xy9CNOraT9t LP/Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859821; x=1728464621; 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=4+f0YYnaXKqWCx+QRho9ClCd3PUHIyO8++QfFS/YDAc=; b=dkmjWcyd3MBfGTjWOBedrGmkPeNtOOzIJoWud5hoPBl5K/ZDZP8ESC3agj2bjiGLXj b9/Y5U3C5WkBqQjdTYCXQEKn+jX0+r7VehygU3Lcq/LiN6bZ8gqnJKmYb2ljDnVgCOzt BemHHLJ4vGM7gz7/Gi86/oJ/Hskoy8TxsYkEla5WWoX7rEAtwOMVopn8oIJ1Vs10Tiv9 4vcEHfKbCD9JLR234mRgH9Y/O8vwEVJxT2fR6XSpS21ZElGdf+t3e287ktlvZI8t9jDu WkRqg0OBpIxzax2I5NbWFlfbqowXQwMdZPWWGW8mGsM/DwW1Ic6xVr9L30s7QpbppYCB e1Ag== X-Gm-Message-State: AOJu0YyO+bzYiZzEKk15FlMX3IRWl9RXBnfXwTueQys/fFEmKDdCnznX wr2zXWhiVSOM75ConDrD/BnGvF2a9m3br9MfYb5kBdqGITqkCzt/V6UY28YYXuU= X-Google-Smtp-Source: AGHT+IF18AUos/8xe/LlHuGVRLW34QDTpsUinhB0BaHX0evz8YQurAmrd7ZPSGvBqJdvXimbyjVvEw== X-Received: by 2002:a5d:5341:0:b0:367:9d05:cf1f with SMTP id ffacd0b85a97d-37cfb8c8634mr1579191f8f.14.1727859820611; Wed, 02 Oct 2024 02:03:40 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:40 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:34 +0200 Subject: [PATCH net-next v8 20/24] ovpn: implement key add/del/swap via netlink Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-20-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=7799; i=antonio@openvpn.net; h=from:subject:message-id; bh=mceVO3o4Wlc3pK47t0GBHPAOEep2KLBEghv1pZ18o78=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxTZR1YCaHaen9hlog8ErJIrRCvNaOgG9MvD 1Fr2SLA99KJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUwAKCRALcOU6oDjV h7SrB/4oyc6DdcKrhqBiGjWnKFLPP2osMDhbaPhY2k2tH0iqwPxf521jeC6kw5eNX6NfnMEhpT3 QCiLamWHhtrOc/Aw2hd2eTGZHwlqX+zn3aTMdxKlF85D9h/eYP1bHCaqeKybn97w0L4M4xajJHB L6rmvHsGLPNyNlu48drSgIkjpW56jduo/L7tuq6kW/vSixmjZBeV8/jqSV5tIdiPw+QqV4+q8Cl YQ48/uJFwTSJS1LzSfWc3mZABZStLdBru66rPjg02cJpNfOE6n5ZR6ps7fl/gZ/uXAKz6ht7IcA VS2P/JP+RVeoyF13JAcAxlDwM170FGBH2typIvHjPipLoNSY X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org This change introduces the netlink commands needed to add, delete and swap keys for a specific peer. Userspace is expected to use these commands to create, destroy and rotate session keys for a specific peer. Signed-off-by: Antonio Quartulli --- drivers/net/ovpn/netlink.c | 210 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 207 insertions(+), 3 deletions(-) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 29ff6023f9135b81a1d5fa42354398c70008aee5..c9cede9025505d1b69ec8eeeb60420d0a1647c82 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -744,19 +744,223 @@ int ovpn_nl_peer_del_doit(struct sk_buff *skb, struct genl_info *info) return ret; } +static int ovpn_nl_get_key_dir(struct genl_info *info, struct nlattr *key, + enum ovpn_cipher_alg cipher, + struct ovpn_key_direction *dir) +{ + struct nlattr *attrs[OVPN_A_KEYDIR_MAX + 1]; + int ret; + + ret = nla_parse_nested(attrs, OVPN_A_KEYDIR_MAX, key, + ovpn_keydir_nl_policy, info->extack); + if (ret) + return ret; + + switch (cipher) { + case OVPN_CIPHER_ALG_AES_GCM: + case OVPN_CIPHER_ALG_CHACHA20_POLY1305: + if (NL_REQ_ATTR_CHECK(info->extack, key, attrs, + OVPN_A_KEYDIR_CIPHER_KEY) || + NL_REQ_ATTR_CHECK(info->extack, key, attrs, + OVPN_A_KEYDIR_NONCE_TAIL)) + return -EINVAL; + + dir->cipher_key = nla_data(attrs[OVPN_A_KEYDIR_CIPHER_KEY]); + dir->cipher_key_size = nla_len(attrs[OVPN_A_KEYDIR_CIPHER_KEY]); + + /* These algorithms require a 96bit nonce, + * Construct it by combining 4-bytes packet id and + * 8-bytes nonce-tail from userspace + */ + dir->nonce_tail = nla_data(attrs[OVPN_A_KEYDIR_NONCE_TAIL]); + dir->nonce_tail_size = nla_len(attrs[OVPN_A_KEYDIR_NONCE_TAIL]); + break; + default: + NL_SET_ERR_MSG_MOD(info->extack, "unsupported cipher"); + return -EINVAL; + } + + return 0; +} + +/** + * ovpn_nl_key_new_doit - configure a new key for the specified peer + * @skb: incoming netlink message + * @info: genetlink metadata + * + * This function allows the user to install a new key in the peer crypto + * state. + * Each peer has two 'slots', namely 'primary' and 'secondary', where + * keys can be installed. The key in the 'primary' slot is used for + * encryption, while both keys can be used for decryption by matching the + * key ID carried in the incoming packet. + * + * The user is responsible for rotating keys when necessary. The user + * may fetch peer traffic statistics via netlink in order to better + * identify the right time to rotate keys. + * The renegotiation follows these steps: + * 1. a new key is computed by the user and is installed in the 'secondary' + * slot + * 2. at user discretion (usually after a predetermined time) 'primary' and + * 'secondary' contents are swapped and the new key starts being used for + * encryption, while the old key is kept around for decryption of late + * packets. + * + * Return: 0 on success or a negative error code otherwise. + */ int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1]; + struct ovpn_struct *ovpn = info->user_ptr[0]; + struct ovpn_peer_key_reset pkr; + struct ovpn_peer *peer; + u32 peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_KEYCONF)) + return -EINVAL; + + ret = nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX, + info->attrs[OVPN_A_KEYCONF], + ovpn_keyconf_nl_policy, info->extack); + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_PEER_ID)) + return -EINVAL; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_SLOT) || + NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_KEY_ID) || + NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_CIPHER_ALG) || + NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_ENCRYPT_DIR) || + NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_DECRYPT_DIR)) + return -EINVAL; + + peer_id = nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]); + pkr.slot = nla_get_u8(attrs[OVPN_A_KEYCONF_SLOT]); + pkr.key.key_id = nla_get_u16(attrs[OVPN_A_KEYCONF_KEY_ID]); + pkr.key.cipher_alg = nla_get_u16(attrs[OVPN_A_KEYCONF_CIPHER_ALG]); + + ret = ovpn_nl_get_key_dir(info, attrs[OVPN_A_KEYCONF_ENCRYPT_DIR], + pkr.key.cipher_alg, &pkr.key.encrypt); + if (ret < 0) + return ret; + + ret = ovpn_nl_get_key_dir(info, attrs[OVPN_A_KEYCONF_DECRYPT_DIR], + pkr.key.cipher_alg, &pkr.key.decrypt); + if (ret < 0) + return ret; + + peer = ovpn_peer_get_by_id(ovpn, peer_id); + if (!peer) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "no peer with id %u to set key for", + peer_id); + return -ENOENT; + } + + ret = ovpn_crypto_state_reset(&peer->crypto, &pkr); + if (ret < 0) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "cannot install new key for peer %u", + peer_id); + goto out; + } + + netdev_dbg(ovpn->dev, "%s: new key installed (id=%u) for peer %u\n", + __func__, pkr.key.key_id, peer_id); +out: + ovpn_peer_put(peer); + return ret; } int ovpn_nl_key_swap_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct ovpn_struct *ovpn = info->user_ptr[0]; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct ovpn_peer *peer; + u32 peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_KEYCONF)) + return -EINVAL; + + ret = nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX, + info->attrs[OVPN_A_KEYCONF], + ovpn_keyconf_nl_policy, info->extack); + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_PEER_ID)) + return -EINVAL; + + peer_id = nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]); + + peer = ovpn_peer_get_by_id(ovpn, peer_id); + if (!peer) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "no peer with id %u to swap keys for", + peer_id); + return -ENOENT; + } + + ovpn_crypto_key_slots_swap(&peer->crypto); + ovpn_peer_put(peer); + + return 0; } int ovpn_nl_key_del_doit(struct sk_buff *skb, struct genl_info *info) { - return -EOPNOTSUPP; + struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1]; + struct ovpn_struct *ovpn = info->user_ptr[0]; + enum ovpn_key_slot slot; + struct ovpn_peer *peer; + u32 peer_id; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, OVPN_A_KEYCONF)) + return -EINVAL; + + ret = nla_parse_nested(attrs, OVPN_A_KEYCONF_MAX, + info->attrs[OVPN_A_KEYCONF], + ovpn_keyconf_nl_policy, info->extack); + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_PEER_ID)) + return -EINVAL; + + if (ret) + return ret; + + if (NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_SLOT)) + return -EINVAL; + + peer_id = nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]); + slot = nla_get_u8(attrs[OVPN_A_KEYCONF_SLOT]); + + peer = ovpn_peer_get_by_id(ovpn, peer_id); + if (!peer) { + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "no peer with id %u to delete key for", + peer_id); + return -ENOENT; + } + + ovpn_crypto_key_slot_delete(&peer->crypto, slot); + ovpn_peer_put(peer); + + return 0; } /** From patchwork Wed Oct 2 09:02:35 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819527 X-Patchwork-Delegate: kuba@kernel.org 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 5B60F1CBE88 for ; Wed, 2 Oct 2024 09:03:43 +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=1727859826; cv=none; b=Okvb02MsY9pMcKSdVIjCnZJzdvAvqkFlXKKVmHzNrAF8qwR2KeqCl3X80dawmlO2qEKy00nIS2+lVQ6FdttOEAhvdgtYv5aYB75+0VL9yFmkttuLm/bLQ/jchRohDGBJiR4HlnBIaflCPf57A3HLXNkUbyw9DtJyDxST6Hwg/t4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859826; c=relaxed/simple; bh=+vnKa4bqLRhmdCMIYEFOcKCf4rJ55QyAdiXnsOoWeHo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=aK5s3+rpGVZmbG16YRoVd9NfYa0SHYSwmSERPprW9k5Am5I/O3DSz7VqjTf7UH0c2PhmCI3PndNjbft/RCdPNgtn5HKlbmSKaVVj8GMrAES0XZ3418qle1omTjXfRIn3LmlhETXj/Nt4CHPJTFN+uAEotvvHuOOFaR6lNrljsHg= 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=Ll7WRys0; 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="Ll7WRys0" Received: by mail-wm1-f48.google.com with SMTP id 5b1f17b1804b1-42cb9a0c300so50792855e9.0 for ; Wed, 02 Oct 2024 02:03:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859822; x=1728464622; 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=KOoCYSq8vNFJ3q8xJBh8u/Lj1omn8cC+c9GbsJCACtU=; b=Ll7WRys0RNVGOV16n65C30T0VJL12RbNyEG5zr9usxROMk5er6qVr97g2ei6DC6HW3 dzuqc8ma7nn+nHWYUQZWpTjxppanrA7IeEDjM3UlUXAppMKO5vJGgol3rn8nnDhuEkYe rEbgGdN2ceSxgQnoUgxS4m+sEPmIe3v3wK4hpFrdgXXob4xfuzkcMcOK06p6Mmk7fFzc j71Sm1hVZdjyecCbwjTGxoipEXtqC/c+eahK+zOFB9r+ZDXfg8t9BGVAdUhDwxgCP6Hg ngGSpE6+a4tUMJxy4+GWSMSc481yZGPXLzvxxnx0UPOwTyVBWbBv3DmW+UmdknW+HELN zi8Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859822; x=1728464622; 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=KOoCYSq8vNFJ3q8xJBh8u/Lj1omn8cC+c9GbsJCACtU=; b=aiX3Y4SSD21LynypbuhZ3G4gtlYIT9RpsoLp7tjOX26YYLYuRrGIhT1WzBCW2r4clu a6mXMUGvGuY8U6tyhSEGBjFR/kc1VOMYlT+YoPDet6bZU3gmZCVvM5NbvGPOP0k54/Q+ IWCCGCdB4hR8l/wO0ePATyUf+Bf2sAqJy/Lj9azK4jbWtNQ3W5zsfzx5SvXtRX23RLs1 Hcrwwbehwn8MlIAVDL5YG2xDqJq025CL6HINhqhL21iXXMS8enFS36jDI8JD2XB+MNk4 M6r24w1ny6aWA9BD0m5QwJZOwKCmgcIWyeZzhWpMZczVOLXgTkonEu6J3ZaSbcQ619an CxjQ== X-Gm-Message-State: AOJu0YwF380NNnWxiP0eXbQveFX+OgBXHg6uGY8S7fpuHRJgqwBFne/o IHFU31+xj81yBs2SbWKSZh6qB9rzZAjX2zPHM1mzJ96hQiK/6TR+bqPsnk+nxXkHFs+SeP1sLg1 R X-Google-Smtp-Source: AGHT+IE1NoG8qkC0Ty09vxBVTJVqx1VgRU9s1bKr4nqnYsiPPH7RmI8HoThZdr2IkFr8e1nCFbT/sQ== X-Received: by 2002:a05:600c:1c05:b0:42c:bb96:340e with SMTP id 5b1f17b1804b1-42f778f34e9mr16157655e9.31.1727859821644; Wed, 02 Oct 2024 02:03:41 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:41 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:35 +0200 Subject: [PATCH net-next v8 21/24] ovpn: kill key and notify userspace in case of IV exhaustion Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-21-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=6543; i=antonio@openvpn.net; h=from:subject:message-id; bh=+vnKa4bqLRhmdCMIYEFOcKCf4rJ55QyAdiXnsOoWeHo=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxTRcfL11iu+0/fx2w7Z4MtzMRWRPlXL6ogh AIwRkNfAFGJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUwAKCRALcOU6oDjV h4UMB/941aAS8jcmPETJHTEcKhq4b7naEU9P6s2hVKEHDu4uicXVpxlHVt85w9B4S9t3JTqKFUF KBpWy6XSb5LUjnZ5b+gReQxlK7SFlK2GfS6qS2Ws5Jgt8WsSc2W2it21g2hT9ZfswWEgubMxpSx nM2zKtDJp/vwLtpn3wXIMYqR5GK7NoQpYo0CUxcSIDnqbm68S/V3Fzyfutiv45jrMFukzt87kbr XMh+ZnuZyMmuKIq8C9HWX6/E1gnldvog8mqcYsLvdbw1jjU8QGWvo/w23qeL/X43LDD2IbczADY yr22bC3px+o46UyTIvJR+GswMKohv6/Q5mtwlDuMrpS/tJIF X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org 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 | 19 ++++++++++++++++ drivers/net/ovpn/crypto.h | 2 ++ drivers/net/ovpn/io.c | 18 ++++++++++++++- drivers/net/ovpn/netlink.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/netlink.h | 2 ++ 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/drivers/net/ovpn/crypto.c b/drivers/net/ovpn/crypto.c index f1f7510e2f735e367f96eb4982ba82c9af3c8bfc..56ae2f5e5c33e13933f26acd91c7786f7c02011a 100644 --- a/drivers/net/ovpn/crypto.c +++ b/drivers/net/ovpn/crypto.c @@ -55,6 +55,25 @@ void ovpn_crypto_state_release(struct ovpn_crypto_state *cs) } } +/* removes the key matching the specified id from the crypto context */ +void 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); +} + /* 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 61de855d23649598a6e5a34447a35ed9620dfde1..48ea6ae9f52114a2f55d71304243d52467ebeef5 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -133,4 +133,6 @@ void ovpn_crypto_state_release(struct ovpn_crypto_state *cs); void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs); +void ovpn_crypto_kill_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 8f2b4a85d20fbdb512de7ec312d391985e96b906..3e82ff882d00b92b67e320f2521269b6bd731083 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -223,6 +223,7 @@ void ovpn_recv(struct ovpn_peer *peer, struct sk_buff *skb) void ovpn_encrypt_post(void *data, int ret) { + struct ovpn_crypto_key_slot *ks = NULL; struct ovpn_peer *peer = NULL; struct sk_buff *skb = data; unsigned int orig_len = 0; @@ -235,15 +236,28 @@ void ovpn_encrypt_post(void *data, int ret) /* crypto is done, cleanup skb CB and its members */ if (likely(ovpn_skb_cb(skb)->ctx)) { + ks = ovpn_skb_cb(skb)->ctx->ks; peer = ovpn_skb_cb(skb)->ctx->peer; orig_len = ovpn_skb_cb(skb)->ctx->orig_len; - ovpn_crypto_key_slot_put(ovpn_skb_cb(skb)->ctx->ks); aead_request_free(ovpn_skb_cb(skb)->ctx->req); kfree(ovpn_skb_cb(skb)->ctx); ovpn_skb_cb(skb)->ctx = NULL; } + 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); + 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; @@ -273,6 +287,8 @@ void ovpn_encrypt_post(void *data, int ret) dev_core_stats_tx_dropped_inc(peer->ovpn->dev); if (likely(peer)) ovpn_peer_put(peer); + if (likely(ks)) + ovpn_crypto_key_slot_put(ks); kfree_skb(skb); } diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index c9cede9025505d1b69ec8eeeb60420d0a1647c82..e9cf847e99026e78eee4d65b62911671e8c2e407 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -963,6 +963,61 @@ 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 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(100, 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); + + genlmsg_multicast_netns(&ovpn_nl_family, dev_net(peer->ovpn->dev), msg, + 0, OVPN_NLGRP_PEERS, GFP_ATOMIC); + + return 0; + +err_cancel_msg: + genlmsg_cancel(msg, hdr); +err_free_msg: + nlmsg_free(msg); + return ret; +} + /** * ovpn_nl_register - perform any needed registration in the NL subsustem * diff --git a/drivers/net/ovpn/netlink.h b/drivers/net/ovpn/netlink.h index 9e87cf11d1e9813b7a75ddf3705ab7d5fabe899f..33390b13c8904d40b629662005a9eb92ff617c3b 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 Wed Oct 2 09:02:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819529 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f45.google.com (mail-wr1-f45.google.com [209.85.221.45]) (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 654011CC881 for ; Wed, 2 Oct 2024 09:03:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859827; cv=none; b=AhbcFV5U5oz+usubOIEhkqHSVAXnxvo3HpAEqRmgd+OGNcP+WZ8761L57enbLMibQxaRaQVoJ6lq6ktaTkMZwc6PB3Xb2uqp+spQGBeVVESfSv1PpX2CqKBSvymH1DMTwWzaMlSccFKS9ibcKMazStQZ8zFP073P9kxejwUhbi0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859827; c=relaxed/simple; bh=poxxnPMFHBlbgk9hT4KAoFDOsyv5Cr0vROL/GXgxBiE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=hEiAxqIxl6FPYI8oFocqMNJLAqDkoH3FfWz5odjyE9iDqmZtwNJdwvuh1M409DcLi6lkle6Av/Qir2WGmYj++hNduWe1BTQYpLy+TSLz2OUoJLpuFfqvBCZQyRUDuBDtb7BF+fYKzo4885hXZI7wY1WsQotSQVM9CesW6E/0xJU= 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=GV/d1h4Q; arc=none smtp.client-ip=209.85.221.45 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="GV/d1h4Q" Received: by mail-wr1-f45.google.com with SMTP id ffacd0b85a97d-37ce14ab7eeso3301453f8f.2 for ; Wed, 02 Oct 2024 02:03:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859823; x=1728464623; 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=Z1g5ErvOWdSDbVBAMFtgccgGsR+CcI40dCMnEOfXG5Q=; b=GV/d1h4Qf1LZEGio6mtJXom7t8AFQtKLKO2ICdv2UFXWgRJoPKFsjhULgSWnbSvXzW uUCfGPlZGLny8OGyb2OGe9/yD6I9+/APep9vktwOTE5wLkTCBREzn8HTFMqef3xxgSTv vO4Sav7dCxIJZYyiCXy2Y3vuWw/QzAv3MhW+AFbxtM2DBUzbKbbtgpHgFkHtkwA6aIoJ fgcw3f3w6LFY8jZAxydgLAymUizxLDmkNsygyngKjhjzQMEyIaNYbfnsiHkOY0BNhvZV DQhdkL97y/HmdCjxHV6ikwRPaElign7+bKrxsp9ya2L0fJkb214SVxmeBjs8OP0HAs5B b2XQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859823; x=1728464623; 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=Z1g5ErvOWdSDbVBAMFtgccgGsR+CcI40dCMnEOfXG5Q=; b=YHpq8qYNi2fwJDcyFt0q41ofwXNH6bgOQXebM2JGuIkh00WqoRMg42+7Yx1GWFKRPe 3xSBDbvcioeJeKptf8KMoIYw0Vkg4B2R8AJDijRBbBPJd+OfnutpreNnVRb+LJOjI8ZE VXD11Nl4W71mznI3EWLnmO/5Dwjmy/dhYBdfnXPXbxUphbZLC/7uW6cx3AUta/F17GkV pObmSJfSsi2V/sKqifJ5wYeZCMi4mUuEm+/tY9w8954rV6HkRi0ByON7Dv972TsztEY6 O/XsPxUAIzTG6hfj649/QenAP5U5jVBKEj+teQKi1Ue7GUlSIkZtQsh2+jjKVkaNPUDN nRCQ== X-Gm-Message-State: AOJu0YzmoJu64EzVfAEntHaGmZB+VjTf+pSjBYbqMWWX82RjpSVzoJDi uEegQS9N5Y24Pi1q7anIjQB+HHRFU79jV1nXcaX7Qhj6gMB64snRKZSlY2T2s28= X-Google-Smtp-Source: AGHT+IH7fw/E/kQ3V24xzbtq3adS/XElxX31aae+kZFgbO0z9PggBosgYGxEoE3ngXbkU7APgvBgPA== X-Received: by 2002:a5d:4108:0:b0:37c:cce6:997d with SMTP id ffacd0b85a97d-37cfb8cf38cmr2076166f8f.20.1727859822608; Wed, 02 Oct 2024 02:03:42 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:42 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:36 +0200 Subject: [PATCH net-next v8 22/24] ovpn: notify userspace when a peer is deleted Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-22-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=3246; i=antonio@openvpn.net; h=from:subject:message-id; bh=poxxnPMFHBlbgk9hT4KAoFDOsyv5Cr0vROL/GXgxBiE=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxTNGlDMBkkc6iZHWRsImvE3HBTYIXK4k31G ZWbXzIQ3rGJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUwAKCRALcOU6oDjV h1tdCAC0zOmuy1ivCPWZ7NSHAKEAslKsZkAfD2e29GZ9yIJ3/eUZzHReWipsVVB4a60CNoWBDS5 lNS9lc2ugXq+YF7h3CSnFLYJ+PsnWFx9TwDvAROujAPWLSRT3+pqtges/3bnS00tjE03wAg8vGd dyk3/LMDb47yHd4BEvvDBZ5MJNLFFgVLUkfdO1MKkRmJzyXNayw6YkoYf0vkjjYKcHnAnzKYR78 wLvN1FPK4t/x+R1aMAXzQLq5sZvet7b/C8viwCeXVgyHV1cMi3LAuZcemDcTausTB2zxU/7dbQ0 AMlxMizMwzwNhbX7+/sUIEDvjHiUJhd8ZeJ4WGDsAOLUrtT4 X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org 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 | 55 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/net/ovpn/netlink.h | 1 + drivers/net/ovpn/peer.c | 1 + 3 files changed, 57 insertions(+) diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index e9cf847e99026e78eee4d65b62911671e8c2e407..b96c84fcc24921b2cd173ec2b6358b59eec72b39 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -963,6 +963,61 @@ 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 sk_buff *msg; + struct nlattr *attr; + int ret = -EMSGSIZE; + void *hdr; + + netdev_info(peer->ovpn->dev, "deleting peer with id %u, reason %d\n", + peer->id, peer->delete_reason); + + msg = nlmsg_new(100, GFP_ATOMIC); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &ovpn_nl_family, 0, OVPN_CMD_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_u8(msg, OVPN_A_PEER_DEL_REASON, peer->delete_reason)) + goto err_cancel_msg; + + if (nla_put_u32(msg, OVPN_A_PEER_ID, peer->id)) + goto err_cancel_msg; + + nla_nest_end(msg, attr); + + genlmsg_end(msg, hdr); + + genlmsg_multicast_netns(&ovpn_nl_family, dev_net(peer->ovpn->dev), msg, + 0, OVPN_NLGRP_PEERS, GFP_ATOMIC); + + return 0; + +err_cancel_msg: + genlmsg_cancel(msg, hdr); +err_free_msg: + nlmsg_free(msg); + return ret; +} + /** * 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 33390b13c8904d40b629662005a9eb92ff617c3b..4ab3abcf23dba11f6b92e3d69e700693adbc671b 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 1ce31f0317cb3593a8edf95c43d03a0bddb0a58f..f201bcddb2d792e4f6b759d5e1fed42234aa1410 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -249,6 +249,7 @@ void ovpn_peer_release_kref(struct kref *kref) if (peer->sock) ovpn_socket_put(peer->sock); + ovpn_nl_peer_del_notify(peer); call_rcu(&peer->rcu, ovpn_peer_release_rcu); } From patchwork Wed Oct 2 09:02:37 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819530 X-Patchwork-Delegate: kuba@kernel.org Received: from mail-wr1-f44.google.com (mail-wr1-f44.google.com [209.85.221.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 795471CCEE5 for ; Wed, 2 Oct 2024 09:03:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859828; cv=none; b=AcBYdWz/kjLPOvytlRc6Bamj6rzepA22USUP0zA5v+ggerRgVJWURGlqEhEncCuw3/Zj5kIr7h3i6lIo+gGfKF6ow+ayln0WSy9RY7cIEvYrwkHphZjVl/cjrCUYvtxhDNrEq6Bsd1MsDblPy1xWfcUOOwrgj1sYXTrxuvglC0w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859828; c=relaxed/simple; bh=0HczZybwcOvyDCXb/Fw0Kq345nQiLm5f4dZXh7KvAg0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=AuYMDHfAga4OdEfshdFTQt+bZkzTuTCiS3EuvSA1ZShcxBHUITGlmiaEKD9OGyOpxAW05PsbbEF1HJbc2EYvkAnKIkQLMx5tqaeCvn1LMInTjiM8Yd1wyURrSCQHuzm0IcwEvfEyMgYiT0JevhHgowurLzQpAd2Gsbm8B+mlmrA= 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=BNRIXeeU; arc=none smtp.client-ip=209.85.221.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="BNRIXeeU" Received: by mail-wr1-f44.google.com with SMTP id ffacd0b85a97d-37cd3419937so3312854f8f.0 for ; Wed, 02 Oct 2024 02:03:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859824; x=1728464624; 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=Jtid/4Ggea81hP8xAarmxZwacDuX1oN1onp/X4GHPpc=; b=BNRIXeeUiKFWw0PuShIhM0vt8kJnlsZHwe24vpmp6S4QViLAXL3tBONMniNN/eEOFZ N6QopwbtJOmlfeuv3EnHUDv5JujinXaS5n/Ipe7VdBs1MBiXaosDQAR8qykFxvOkbwHo 9l1V7b2NZD3JfocF96Jufnajr6ID4VSfEpyLr2XjHon776PAN84yB9mA4/+JdhRF4IH+ 3kTj0IXQOBuYeFbL3vqsKi4krvY0uKSPEC1CcwBdvLtymz8IbdXH7JY8KCM81UOsoLmX VY+OvMd54b09hufruFIMskQBaGoGcZiyw45WjosHUo2B6CDblPuWJYrP1oHR/EUnrKS3 wDcg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859824; x=1728464624; 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=Jtid/4Ggea81hP8xAarmxZwacDuX1oN1onp/X4GHPpc=; b=eQ3edTG3mXYSlu5GM40uIaJgJtX581PgaNwBPPkEJV+em6Nv0BQeae/ZURSKXKfWIM Pxg62JPidwWgzYEDQqzIcfc8NOZkjxp+qHbuELGydcQ/lGkZfx1F1sDV3/yyjW95bL+a VbR3/ybpGDT2LskxyOfs5FVXbTGJfCQ/wJtZZBHdUPJ7vvv1lZ8ggllSN4/tPmBbbBai GlsEid8xL/BQHSesZemz3bsQ8JtbTVfOescee40t9Rk8U59mFzq8bPR9jy9rpTN49NHb hYTFAmocPtsiJVEcYcPtSMU7LXY5XtYHnRDrCSl5whdJsGHp9herd0t77V/Lj3pEr15b 2Oxw== X-Gm-Message-State: AOJu0YxNEPF6ecH6OnDBkelEOTFYXlYLtLIG8ma6AAkRPUAItLPFL9Br VPpmS4/4u42Jsb7kRqqxEEAsDErvWmFPMrHfJ9IoW7EnG4YYdKdqUKPz2rAaRHQ= X-Google-Smtp-Source: AGHT+IHF+moQuogaubmFiV9fQExZlkakNdb2tJX5ZJm8bc81FDHr5npglK+ha3mscDcMX8g5JXpqFQ== X-Received: by 2002:a5d:498e:0:b0:37c:c4b4:3396 with SMTP id ffacd0b85a97d-37cfb9d454cmr1409760f8f.29.1727859823678; Wed, 02 Oct 2024 02:03:43 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:43 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:37 +0200 Subject: [PATCH net-next v8 23/24] ovpn: add basic ethtool support Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-23-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com, Andrew Lunn X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=1696; i=antonio@openvpn.net; h=from:subject:message-id; bh=0HczZybwcOvyDCXb/Fw0Kq345nQiLm5f4dZXh7KvAg0=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxTy21F+BrAC+0BHzA21YyFzTw73Iap1BeA4 QiQ06zZYkaJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUwAKCRALcOU6oDjV hxhlB/0TB9PzRSCIcixOB4y3zYbwFDJCgJpMby3jkPceLt/4GRyPssjFpp1QvRt6sfyxB8Ziu1g 2hTw0pKX8RiWiL2jFFVo5FLFbDhwvwMNMz9eN2MCTfedFMiA5gwsaXC4imQl1Vm5E/pOyTLSTWk uxbDvdTbGs8yYLpw0H9Xk+ZEowVBaV0+T+Vu+FFXhPk7n53ixreCGgtlpLNRa0W9Qr5WauUflbz 2dUFL+63/+z0Hs0UcFJ3B0bn95TYVTEUkp+dCcOh9uvOVZ8R4dnEPkw/b4m08Bq430+ERb0uEOD v3c1k8Ix+/5ZTqpyC/1OUGvmYxFqYPXNS1+sfxe3AZGBkpUL X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org Implement support for basic ethtool functionality. Note that ovpn is a virtual device driver, therefore various ethtool APIs are just not meaningful and thus not implemented. Signed-off-by: Antonio Quartulli Reviewed-by: Andrew Lunn --- drivers/net/ovpn/main.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 6048df5890c8fa46f3d91498903b4277a33f06db..a164b6ca2a4b8d69a76938fcd1b76d1f2e563dd8 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -7,6 +7,7 @@ * James Yonan */ +#include #include #include #include @@ -137,6 +138,19 @@ bool ovpn_dev_is_valid(const struct net_device *dev) return dev->netdev_ops->ndo_start_xmit == ovpn_net_xmit; } +static void ovpn_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strscpy(info->driver, OVPN_FAMILY_NAME, sizeof(info->driver)); + strscpy(info->bus_info, "ovpn", sizeof(info->bus_info)); +} + +static const struct ethtool_ops ovpn_ethtool_ops = { + .get_drvinfo = ovpn_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_ts_info = ethtool_op_get_ts_info, +}; + static struct rtnl_link_ops ovpn_link_ops = { .kind = OVPN_FAMILY_NAME, .netns_refund = false, @@ -158,6 +172,7 @@ static void ovpn_setup(struct net_device *dev) dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS; + dev->ethtool_ops = &ovpn_ethtool_ops; dev->netdev_ops = &ovpn_netdev_ops; dev->rtnl_link_ops = &ovpn_link_ops; From patchwork Wed Oct 2 09:02:38 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 13819531 X-Patchwork-Delegate: kuba@kernel.org 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 BB5D21CF5D1 for ; Wed, 2 Oct 2024 09:03:46 +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=1727859832; cv=none; b=TFneFWwhU/ONw8GIyJu5y8AHaGP6VkyCc6EhekqdyG9hg1chxljVFfuIIonKhdUIYZNtC7w56J3kYtF7BT3oWoGSTWd/RWzRVAa+0Ysr7MDBLvkkqJTsnxoq9gMJJfCfwPpMn7QKsNz0HLNKOWP8wgXLmBAW8I46L8zeuYENom4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727859832; c=relaxed/simple; bh=XY92qSgOIdH22mvRIBRtD0CCzqOqptN+eSBqR7qzpDE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ny2dZdFC3U+Krw3zU1gZSZ3VM2jAKw7XJfDR2IVHdUzxC2F8tR/URhku/ScmO/4w450iz9TqYX18yTs9NMjmlqzfEU33V7bTE2AvX8bXEnM/rfnHORJaTwR4u2+ZU5Vqbk2AgEj/ilk05dGMYvEpZAt49/AMvdbOmLOBxMMmtcU= 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=PCtecr3E; 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="PCtecr3E" Received: by mail-wm1-f41.google.com with SMTP id 5b1f17b1804b1-42cb1758e41so48372045e9.1 for ; Wed, 02 Oct 2024 02:03:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1727859825; x=1728464625; 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=CAZ+fglH7j9NatQp7S1CjF6CSDpmHMs6b4RbuiTSjtk=; b=PCtecr3EGsbbytC6tcvSlepFKedO2kt3yTGcre1nccqFHUiLhUDC3f/E9iTu6b5boK 9tk7x96H6VCx4qraVtPAmwA6yqIvWTyWstVnqCjmi1BU5304yWSDoAoaYl3TcW9IECks tG4Pte0hw179J5n2B9UATCA7qHXRQAYhu8Osj7lbcKIQ//osFfKr/m6I0HOkF7LBk+Hr 34gZJ9zKsk72T4pxjcoqomCkt6W8SR+vKLtq5A/HgOUVydOfhmFMmWt1kul7CJFq5RTY P6lMZN1TJ/pQu421SsjXZvP7QEdlTF3fbKvYQEHjJ7lKuviwoOdXfVTg02rkjj4uByLi bfrw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1727859825; x=1728464625; 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=CAZ+fglH7j9NatQp7S1CjF6CSDpmHMs6b4RbuiTSjtk=; b=B2uEJULK6cw4WpTauousr3Wp9PYQ0l0RV5/GHkAbaPKBdHsuFCQZOs7MzFAJb4G5v3 8dGCTUQiCEYKRis66GW5P4NO9bu3SKeVTbI3lKLO1FqTdyqMQZvTV+IkwUM546HB9x8X On3PS38ijAyBP+74TijAPxhG0gSk/b/As0HLaB280dQOxwP3Lj9FI7T39xo3hs+o+kre VtVHGI2zu7gYSi4AycUUtZ/9m7g0RN87PRoRvEWuR7PFlBeDu/q3k74v5h6Nj+u9Y/mZ 5ps6mgX+AJEP/t4b66013v5TygT6b2IP72WpYHTSvArTpecTpgI5q/JvgbDGMvHSWV4n VqoQ== X-Gm-Message-State: AOJu0Yz544ANRad5GS3UHUP5mfE4fOq0oojbWe3CcQMrKUm+dxezAxJQ 6NGNrwzuKqAadZgQmPLNMo4O1V3v3b5QBk2fuT2qfnJ7fC6zoT0sSgZQpnwRAOQ= X-Google-Smtp-Source: AGHT+IGY1GMDpiIBcjI0eLupe5dPpQlyjULFWV/nyy0zNRTXWjCrI8MZaQz7h4n0XQkNhM+Gr3utbQ== X-Received: by 2002:a05:600c:4714:b0:425:5ec3:570b with SMTP id 5b1f17b1804b1-42f778ffa8cmr13450435e9.35.1727859824715; Wed, 02 Oct 2024 02:03:44 -0700 (PDT) Received: from serenity.mandelbit.com ([2001:67c:2fbc:1:da6e:ecd8:2234:c32e]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-37cd56e8822sm13602320f8f.50.2024.10.02.02.03.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 02 Oct 2024 02:03:44 -0700 (PDT) From: Antonio Quartulli Date: Wed, 02 Oct 2024 11:02:38 +0200 Subject: [PATCH net-next v8 24/24] testing/selftest: add test tool and scripts for ovpn module Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20241002-b4-ovpn-v8-24-37ceffcffbde@openvpn.net> References: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> In-Reply-To: <20241002-b4-ovpn-v8-0-37ceffcffbde@openvpn.net> To: Eric Dumazet , Jakub Kicinski , Paolo Abeni , Donald Hunter , Antonio Quartulli , Shuah Khan Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, sd@queasysnail.net, ryazanov.s.a@gmail.com X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=59505; i=antonio@openvpn.net; h=from:subject:message-id; bh=XY92qSgOIdH22mvRIBRtD0CCzqOqptN+eSBqR7qzpDE=; b=owEBbQGS/pANAwAIAQtw5TqgONWHAcsmYgBm/QxT3xHrgIsBeaGxy/XQ1IJlBpQQljNhIrjMR X8oZ+T70eaJATMEAAEIAB0WIQSZq9xs+NQS5N5fwPwLcOU6oDjVhwUCZv0MUwAKCRALcOU6oDjV h4McB/9ubVry9weXJwjlSvJNhP3CKpJA/gkjMhvVPDwzNktaGxnsTY3Wn2/VI1Jg1SAdoPUefF6 8neXyDA/kFl1Qsidk8OE1uNnQh/ddcQOJgLqW2VSvSc6pvc9QYuaCiBAY+NWq/3igx8t92aXuj+ 3Q+h+4DQWYAQE4xaxvYe0ORg+8YP3wDe9Te4+yzK+JAxxpCD/J3mJG8xEwiqpYzV6RjrIzXK5KB LLxmpQ8vgrXGmGZlgh9QTZCJqUZOHkwzulnt9Fyh+yv7DR4i3PjLMYG7JYWCj35EzoDzv7tlL1j nyesnCkeEiQ0LJHTiUaDUbme1VBhvR3E1bOmjG0fgMehw8gC X-Developer-Key: i=antonio@openvpn.net; a=openpgp; fpr=CABDA1282017C267219885C748F0CCB68F59D14C X-Patchwork-Delegate: kuba@kernel.org The ovpn-cli tool can be compiled and used as selftest for the ovpn kernel module. It implementes the netlink API and can thus be integrated in any script for more automated testing. Along with the tool, 2 scripts are added that perform basic functionality tests by means of network namespaces. The scripts can be performed in sequence by running run.sh Cc: shuah@kernel.org Cc: linux-kselftest@vger.kernel.org Signed-off-by: Antonio Quartulli --- MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + tools/testing/selftests/net/ovpn/.gitignore | 2 + tools/testing/selftests/net/ovpn/Makefile | 18 + tools/testing/selftests/net/ovpn/config | 8 + tools/testing/selftests/net/ovpn/data-test-tcp.sh | 9 + tools/testing/selftests/net/ovpn/data-test.sh | 153 ++ tools/testing/selftests/net/ovpn/data64.key | 5 + tools/testing/selftests/net/ovpn/float-test.sh | 118 ++ tools/testing/selftests/net/ovpn/ovpn-cli.c | 1822 +++++++++++++++++++++ tools/testing/selftests/net/ovpn/tcp_peers.txt | 5 + tools/testing/selftests/net/ovpn/udp_peers.txt | 5 + 12 files changed, 2147 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index f753060d4e2467a786778ddd4f835861a603ce02..ffd997cc6a1f1fbc5bc954b585bc15ef7bf2486a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17457,6 +17457,7 @@ T: git https://github.com/OpenVPN/linux-kernel-ovpn.git F: drivers/net/ovpn/ F: include/uapi/linux/ovpn.h F: Documentation/netlink/spec/ovpn.yaml +F: tools/testing/selftests/net/ovpn/ P54 WIRELESS DRIVER M: Christian Lamparter diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index b38199965f99014f3e2636fe8d705972f2c0d148..3ae2dd6492ca70d5e317c6e5b4e2560b060e3214 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -68,6 +68,7 @@ TARGETS += net/hsr TARGETS += net/mptcp TARGETS += net/netfilter TARGETS += net/openvswitch +TARGETS += net/ovpn TARGETS += net/packetdrill TARGETS += net/rds TARGETS += net/tcp_ao diff --git a/tools/testing/selftests/net/ovpn/.gitignore b/tools/testing/selftests/net/ovpn/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ee44c081ca7c089933659689303c303a9fa9713b --- /dev/null +++ b/tools/testing/selftests/net/ovpn/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0+ +ovpn-cli diff --git a/tools/testing/selftests/net/ovpn/Makefile b/tools/testing/selftests/net/ovpn/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..65e23eb0ba86d31aa365b08a8c3468dc56a0d1a4 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2020-2024 OpenVPN, Inc. +# +CFLAGS = -Wall -I../../../../../usr/include +CFLAGS += $(shell pkg-config --cflags libnl-3.0 libnl-genl-3.0) + +LDFLAGS = -lmbedtls -lmbedcrypto +LDFLAGS += $(shell pkg-config --libs libnl-3.0 libnl-genl-3.0) + +ovpn-cli: ovpn-cli.c + $(CC) -o ovpn-cli ovpn-cli.c $(CFLAGS) $(LDFLAGS) + +TEST_PROGS = data-test.sh \ + data-test-tcp.sh \ + float-test.sh +TEST_GEN_FILES = ovpn-cli + +include ../../lib.mk diff --git a/tools/testing/selftests/net/ovpn/config b/tools/testing/selftests/net/ovpn/config new file mode 100644 index 0000000000000000000000000000000000000000..5ff47de23c1297a310f0fe39a1ebad16ad014d2c --- /dev/null +++ b/tools/testing/selftests/net/ovpn/config @@ -0,0 +1,8 @@ +CONFIG_NET=y +CONFIG_INET=y +CONFIG_NET_UDP_TUNNEL=y +CONFIG_DST_CACHE=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_GCM=y +CONFIG_CRYPTO_CHACHA20POLY1305=y +CONFIG_OVPN=y diff --git a/tools/testing/selftests/net/ovpn/data-test-tcp.sh b/tools/testing/selftests/net/ovpn/data-test-tcp.sh new file mode 100755 index 0000000000000000000000000000000000000000..65f05659b5fd8f05d216f2c22898e7f89a6ea4de --- /dev/null +++ b/tools/testing/selftests/net/ovpn/data-test-tcp.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2024 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +PROTO="TCP" + +source data-test.sh diff --git a/tools/testing/selftests/net/ovpn/data-test.sh b/tools/testing/selftests/net/ovpn/data-test.sh new file mode 100755 index 0000000000000000000000000000000000000000..e3401481440165141993767cc94fd6df7a37a52e --- /dev/null +++ b/tools/testing/selftests/net/ovpn/data-test.sh @@ -0,0 +1,153 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2020-2024 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +#set -x +set -e + +UDP_PEERS_FILE=${UDP_PEERS_FILE:-udp_peers.txt} +TCP_PEERS_FILE=${TCP_PEERS_FILE:-tcp_peers.txt} +OVPN_CLI=${OVPN_CLI:-./ovpn-cli} +ALG=${ALG:-aes} +PROTO=${PROTO:-UDP} + +create_ns() { + ip netns add peer${1} +} + +setup_ns() { + MODE="P2P" + + if [ ${1} -eq 0 ]; then + MODE="MP" + for p in $(seq 1 ${NUM_PEERS}); do + ip link add veth${p} netns peer0 type veth peer name veth${p} netns peer${p} + + ip -n peer0 addr add 10.10.${p}.1/24 dev veth${p} + ip -n peer0 link set veth${p} up + + ip -n peer${p} addr add 10.10.${p}.2/24 dev veth${p} + ip -n peer${p} link set veth${p} up + done + fi + + ip netns exec peer${1} ${OVPN_CLI} new_iface tun${1} $MODE + ip -n peer${1} addr add ${2} dev tun${1} + ip -n peer${1} link set tun${1} up +} + +add_peer() { + if [ "${PROTO}" == "UDP" ]; then + if [ ${1} -eq 0 ]; then + ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 ${UDP_PEERS_FILE} + + for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 ${ALG} 0 \ + data64.key + done + else + ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} 1 ${1} 10.10.${1}.1 1 + ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} ${1} 1 0 ${ALG} 1 + data64.key + fi + else + if [ ${1} -eq 0 ]; then + (ip netns exec peer0 ${OVPN_CLI} listen tun0 1 ${TCP_PEERS_FILE} && { + for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 \ + ${ALG} 0 data64.key + done + }) & + sleep 5 + else + ip netns exec peer${1} ${OVPN_CLI} connect tun${1} ${1} 10.10.${1}.1 1 \ + data64.key + fi + fi +} + +cleanup() { + for p in $(seq 1 10); do + ip -n peer0 link del veth${p} 2>/dev/null || true + done + for p in $(seq 0 10); do + ip netns exec peer${p} ${OVPN_CLI} del_iface tun${p} 2>/dev/null || true + ip netns del peer${p} 2>/dev/null || true + done +} + +if [ "${PROTO}" == "UDP" ]; then + NUM_PEERS=${NUM_PEERS:-$(wc -l ${UDP_PEERS_FILE} | awk '{print $1}')} +else + NUM_PEERS=${NUM_PEERS:-$(wc -l ${TCP_PEERS_FILE} | awk '{print $1}')} +fi + +cleanup + +for p in $(seq 0 ${NUM_PEERS}); do + create_ns ${p} +done + +for p in $(seq 0 ${NUM_PEERS}); do + setup_ns ${p} 5.5.5.$((${p} + 1))/24 +done + +for p in $(seq 0 ${NUM_PEERS}); do + add_peer ${p} +done + +for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 60 120 + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 60 120 +done + +for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer0 ping -qfc 1000 -w 5 5.5.5.$((${p} + 1)) +done + +ip netns exec peer0 iperf3 -1 -s & +sleep 1 +ip netns exec peer1 iperf3 -Z -t 3 -c 5.5.5.1 + +for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 2 1 ${ALG} 0 data64.key + ip netns exec peer${p} ${OVPN_CLI} new_key tun${p} ${p} 2 1 ${ALG} 1 data64.key + ip netns exec peer${p} ${OVPN_CLI} swap_keys tun${p} ${p} +done + +sleep 1 +echo "Querying all peers:" +ip netns exec peer0 ${OVPN_CLI} get_peer tun0 +ip netns exec peer1 ${OVPN_CLI} get_peer tun1 + +echo "Querying peer 1:" +ip netns exec peer0 ${OVPN_CLI} get_peer tun0 1 + +echo "Querying non-existent peer 10:" +ip netns exec peer0 ${OVPN_CLI} get_peer tun0 10 || true + +ip netns exec peer0 ${OVPN_CLI} del_peer tun0 1 +ip netns exec peer1 ${OVPN_CLI} del_peer tun1 1 + +echo "Setting timeout to 10s MP:" +# bring ifaces down to prevent traffic being sent +for p in $(seq 0 ${NUM_PEERS}); do + ip -n peer${p} link set tun${p} down +done +# set short timeout +for p in $(seq 2 ${NUM_PEERS}); do + ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 10 10 || true + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 0 0 +done +# wait for peers to timeout +sleep 15 + +echo "Setting timeout to 10s P2P:" +for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 10 10 +done +sleep 15 + +cleanup diff --git a/tools/testing/selftests/net/ovpn/data64.key b/tools/testing/selftests/net/ovpn/data64.key new file mode 100644 index 0000000000000000000000000000000000000000..a99e88c4e290f58b12f399b857b873f308d9ba09 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/data64.key @@ -0,0 +1,5 @@ +jRqMACN7d7/aFQNT8S7jkrBD8uwrgHbG5OQZP2eu4R1Y7tfpS2bf5RHv06Vi163CGoaIiTX99R3B +ia9ycAH8Wz1+9PWv51dnBLur9jbShlgZ2QHLtUc4a/gfT7zZwULXuuxdLnvR21DDeMBaTbkgbai9 +uvAa7ne1liIgGFzbv+Bas4HDVrygxIxuAnP5Qgc3648IJkZ0QEXPF+O9f0n5+QIvGCxkAUVx+5K6 +KIs+SoeWXnAopELmoGSjUpFtJbagXK82HfdqpuUxT2Tnuef0/14SzVE/vNleBNu2ZbyrSAaah8tE +BofkPJUBFY+YQcfZNM5Dgrw3i+Bpmpq/gpdg5w== diff --git a/tools/testing/selftests/net/ovpn/float-test.sh b/tools/testing/selftests/net/ovpn/float-test.sh new file mode 100755 index 0000000000000000000000000000000000000000..f370dbe8d40bcd40407a795829289c139cd3740a --- /dev/null +++ b/tools/testing/selftests/net/ovpn/float-test.sh @@ -0,0 +1,118 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2020-2024 OpenVPN, Inc. +# +# Author: Antonio Quartulli + +#set -x +set -e + +UDP_PEERS_FILE=${UDP_PEERS_FILE:-udp_peers.txt} +TCP_PEERS_FILE=${TCP_PEERS_FILE:-tcp_peers.txt} +OVPN_CLI=${OVPN_CLI:-./ovpn-cli} +ALG=${ALG:-aes} +PROTO=${PROTO:-UDP} + +create_ns() { + ip netns add peer${1} +} + +setup_ns() { + MODE="P2P" + + if [ ${1} -eq 0 ]; then + MODE="MP" + for p in $(seq 1 ${NUM_PEERS}); do + ip link add veth${p} netns peer0 type veth peer name veth${p} netns peer${p} + + ip -n peer0 addr add 10.10.${p}.1/24 dev veth${p} + ip -n peer0 link set veth${p} up + + ip -n peer${p} addr add 10.10.${p}.2/24 dev veth${p} + ip -n peer${p} link set veth${p} up + done + fi + + ip netns exec peer${1} ${OVPN_CLI} new_iface tun${1} $MODE + ip -n peer${1} addr add ${2} dev tun${1} + ip -n peer${1} link set tun${1} up +} + +add_peer() { + if [ "${PROTO}" == "UDP" ]; then + if [ ${1} -eq 0 ]; then + ip netns exec peer0 ${OVPN_CLI} new_multi_peer tun0 1 ${UDP_PEERS_FILE} + + for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 ${ALG} 0 \ + data64.key + done + else + ip netns exec peer${1} ${OVPN_CLI} new_peer tun${1} 1 ${1} 10.10.${1}.1 1 + ip netns exec peer${1} ${OVPN_CLI} new_key tun${1} ${1} 1 0 ${ALG} 1 \ + data64.key + fi + else + if [ ${1} -eq 0 ]; then + (ip netns exec peer0 ${OVPN_CLI} listen tun0 1 ${TCP_PEERS_FILE} && { + for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer0 ${OVPN_CLI} new_key tun0 ${p} 1 0 \ + ${ALG} 0 data64.key + done + }) & + sleep 5 + else + ip netns exec peer${1} ${OVPN_CLI} connect tun${1} ${1} 10.10.${1}.1 1 \ + 5.5.5.1 data64.key + fi + fi +} + +cleanup() { + for p in $(seq 1 10); do + ip -n peer0 link del veth${p} 2>/dev/null || true + done + for p in $(seq 0 10); do + ip netns exec peer${p} ${OVPN_CLI} del_iface tun${p} 2>/dev/null || true + ip netns del peer${p} 2>/dev/null || true + done +} + +if [ "${PROTO}" == "UDP" ]; then + NUM_PEERS=${NUM_PEERS:-$(wc -l ${UDP_PEERS_FILE} | awk '{print $1}')} +else + NUM_PEERS=${NUM_PEERS:-$(wc -l ${TCP_PEERS_FILE} | awk '{print $1}')} +fi + +cleanup + +for p in $(seq 0 ${NUM_PEERS}); do + create_ns ${p} +done + +for p in $(seq 0 ${NUM_PEERS}); do + setup_ns ${p} 5.5.5.$((${p} + 1))/24 +done + +for p in $(seq 0 ${NUM_PEERS}); do + add_peer ${p} +done + +for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer0 ${OVPN_CLI} set_peer tun0 ${p} 60 120 + ip netns exec peer${p} ${OVPN_CLI} set_peer tun${p} ${p} 60 120 +done + +for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer0 ping -qfc 1000 -w 5 5.5.5.$((${p} + 1)) +done +# make clients float.. +for p in $(seq 1 ${NUM_PEERS}); do + ip -n peer${p} addr del 10.10.${p}.2/24 dev veth${p} + ip -n peer${p} addr add 10.10.${p}.3/24 dev veth${p} +done +for p in $(seq 1 ${NUM_PEERS}); do + ip netns exec peer${p} ping -qfc 1000 -w 5 5.5.5.1 +done + +cleanup diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c new file mode 100644 index 0000000000000000000000000000000000000000..9736e78a92e580ec64db9a96fd45b27c1d457e1c --- /dev/null +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -0,0 +1,1822 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel accelerator + * + * Copyright (C) 2020-2024 OpenVPN, Inc. + * + * Author: Antonio Quartulli + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* we use strscpy to make checkpatch happy */ +#define strscpy strncpy + +/* libnl < 3.5.0 does not set the NLA_F_NESTED on its own, therefore we + * have to explicitly do it to prevent the kernel from failing upon + * parsing of the message + */ +#define nla_nest_start(_msg, _type) \ + nla_nest_start(_msg, (_type) | NLA_F_NESTED) + +uint64_t nla_get_uint(struct nlattr *attr) +{ + if (nla_len(attr) == sizeof(uint32_t)) + return nla_get_u32(attr); + else + return nla_get_u64(attr); +} + +typedef int (*ovpn_nl_cb)(struct nl_msg *msg, void *arg); + +enum ovpn_key_direction { + KEY_DIR_IN = 0, + KEY_DIR_OUT, +}; + +#define KEY_LEN (256 / 8) +#define NONCE_LEN 8 + +#define PEER_ID_UNDEF 0x00FFFFFF + +struct nl_ctx { + struct nl_sock *nl_sock; + struct nl_msg *nl_msg; + struct nl_cb *nl_cb; + + int ovpn_dco_id; +}; + +struct ovpn_ctx { + __u8 key_enc[KEY_LEN]; + __u8 key_dec[KEY_LEN]; + __u8 nonce[NONCE_LEN]; + + enum ovpn_cipher_alg cipher; + + sa_family_t sa_family; + + __u32 peer_id; + __u16 lport; + + union { + struct sockaddr_in in4; + struct sockaddr_in6 in6; + } remote; + + union { + struct sockaddr_in in4; + struct sockaddr_in6 in6; + } peer_ip; + + bool peer_ip_set; + + unsigned int ifindex; + char ifname[IFNAMSIZ]; + enum ovpn_mode mode; + bool mode_set; + + int socket; + int cli_socket; + + __u32 keepalive_interval; + __u32 keepalive_timeout; + + enum ovpn_key_direction key_dir; + enum ovpn_key_slot key_slot; + int key_id; +}; + +static int ovpn_nl_recvmsgs(struct nl_ctx *ctx) +{ + int ret; + + ret = nl_recvmsgs(ctx->nl_sock, ctx->nl_cb); + + switch (ret) { + case -NLE_INTR: + fprintf(stderr, + "netlink received interrupt due to signal - ignoring\n"); + break; + case -NLE_NOMEM: + fprintf(stderr, "netlink out of memory error\n"); + break; + case -NLE_AGAIN: + fprintf(stderr, + "netlink reports blocking read - aborting wait\n"); + break; + default: + if (ret) + fprintf(stderr, "netlink reports error (%d): %s\n", + ret, nl_geterror(-ret)); + break; + } + + return ret; +} + +static struct nl_ctx *nl_ctx_alloc_flags(struct ovpn_ctx *ovpn, int cmd, + int flags) +{ + struct nl_ctx *ctx; + int err, ret; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) + return NULL; + + ctx->nl_sock = nl_socket_alloc(); + if (!ctx->nl_sock) { + fprintf(stderr, "cannot allocate netlink socket\n"); + goto err_free; + } + + nl_socket_set_buffer_size(ctx->nl_sock, 8192, 8192); + + ret = genl_connect(ctx->nl_sock); + if (ret) { + fprintf(stderr, "cannot connect to generic netlink: %s\n", + nl_geterror(ret)); + goto err_sock; + } + + /* enable Extended ACK for detailed error reporting */ + err = 1; + setsockopt(nl_socket_get_fd(ctx->nl_sock), SOL_NETLINK, NETLINK_EXT_ACK, + &err, sizeof(err)); + + ctx->ovpn_dco_id = genl_ctrl_resolve(ctx->nl_sock, OVPN_FAMILY_NAME); + if (ctx->ovpn_dco_id < 0) { + fprintf(stderr, "cannot find ovpn_dco netlink component: %d\n", + ctx->ovpn_dco_id); + goto err_free; + } + + ctx->nl_msg = nlmsg_alloc(); + if (!ctx->nl_msg) { + fprintf(stderr, "cannot allocate netlink message\n"); + goto err_sock; + } + + ctx->nl_cb = nl_cb_alloc(NL_CB_DEFAULT); + if (!ctx->nl_cb) { + fprintf(stderr, "failed to allocate netlink callback\n"); + goto err_msg; + } + + nl_socket_set_cb(ctx->nl_sock, ctx->nl_cb); + + genlmsg_put(ctx->nl_msg, 0, 0, ctx->ovpn_dco_id, 0, flags, cmd, 0); + + if (ovpn->ifindex > 0) + NLA_PUT_U32(ctx->nl_msg, OVPN_A_IFINDEX, ovpn->ifindex); + + return ctx; +nla_put_failure: +err_msg: + nlmsg_free(ctx->nl_msg); +err_sock: + nl_socket_free(ctx->nl_sock); +err_free: + free(ctx); + return NULL; +} + +static struct nl_ctx *nl_ctx_alloc(struct ovpn_ctx *ovpn, int cmd) +{ + return nl_ctx_alloc_flags(ovpn, cmd, 0); +} + +static void nl_ctx_free(struct nl_ctx *ctx) +{ + if (!ctx) + return; + + nl_socket_free(ctx->nl_sock); + nlmsg_free(ctx->nl_msg); + nl_cb_put(ctx->nl_cb); + free(ctx); +} + +static int ovpn_nl_cb_error(struct sockaddr_nl (*nla)__always_unused, + struct nlmsgerr *err, void *arg) +{ + struct nlmsghdr *nlh = (struct nlmsghdr *)err - 1; + struct nlattr *tb_msg[NLMSGERR_ATTR_MAX + 1]; + int len = nlh->nlmsg_len; + struct nlattr *attrs; + int *ret = arg; + int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh); + + *ret = err->error; + + if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS)) + return NL_STOP; + + if (!(nlh->nlmsg_flags & NLM_F_CAPPED)) + ack_len += err->msg.nlmsg_len - sizeof(*nlh); + + if (len <= ack_len) + return NL_STOP; + + attrs = (void *)((unsigned char *)nlh + ack_len); + len -= ack_len; + + nla_parse(tb_msg, NLMSGERR_ATTR_MAX, attrs, len, NULL); + if (tb_msg[NLMSGERR_ATTR_MSG]) { + len = strnlen((char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]), + nla_len(tb_msg[NLMSGERR_ATTR_MSG])); + fprintf(stderr, "kernel error: %*s\n", len, + (char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG])); + } + + if (tb_msg[NLMSGERR_ATTR_MISS_NEST]) { + fprintf(stderr, "missing required nesting type %u\n", + nla_get_u32(tb_msg[NLMSGERR_ATTR_MISS_NEST])); + } + + if (tb_msg[NLMSGERR_ATTR_MISS_TYPE]) { + fprintf(stderr, "missing required attribute type %u\n", + nla_get_u32(tb_msg[NLMSGERR_ATTR_MISS_TYPE])); + } + + return NL_STOP; +} + +static int ovpn_nl_cb_finish(struct nl_msg (*msg)__always_unused, + void *arg) +{ + int *status = arg; + + *status = 0; + return NL_SKIP; +} + +static int ovpn_nl_cb_ack(struct nl_msg (*msg)__always_unused, + void *arg) +{ + int *status = arg; + + *status = 0; + return NL_STOP; +} + +static int ovpn_nl_msg_send(struct nl_ctx *ctx, ovpn_nl_cb cb) +{ + int status = 1; + + nl_cb_err(ctx->nl_cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &status); + nl_cb_set(ctx->nl_cb, NL_CB_FINISH, NL_CB_CUSTOM, ovpn_nl_cb_finish, + &status); + nl_cb_set(ctx->nl_cb, NL_CB_ACK, NL_CB_CUSTOM, ovpn_nl_cb_ack, &status); + + if (cb) + nl_cb_set(ctx->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, cb, ctx); + + nl_send_auto_complete(ctx->nl_sock, ctx->nl_msg); + + while (status == 1) + ovpn_nl_recvmsgs(ctx); + + if (status < 0) + fprintf(stderr, "failed to send netlink message: %s (%d)\n", + strerror(-status), status); + + return status; +} + +static int ovpn_read_key(const char *file, struct ovpn_ctx *ctx) +{ + int idx_enc, idx_dec, ret = -1; + unsigned char *ckey = NULL; + __u8 *bkey = NULL; + size_t olen = 0; + long ckey_len; + FILE *fp; + + fp = fopen(file, "r"); + if (!fp) { + fprintf(stderr, "cannot open: %s\n", file); + return -1; + } + + /* get file size */ + fseek(fp, 0L, SEEK_END); + ckey_len = ftell(fp); + rewind(fp); + + /* if the file is longer, let's just read a portion */ + if (ckey_len > 256) + ckey_len = 256; + + ckey = malloc(ckey_len); + if (!ckey) + goto err; + + ret = fread(ckey, 1, ckey_len, fp); + if (ret != ckey_len) { + fprintf(stderr, + "couldn't read enough data from key file: %dbytes read\n", + ret); + goto err; + } + + olen = 0; + ret = mbedtls_base64_decode(NULL, 0, &olen, ckey, ckey_len); + if (ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) { + char buf[256]; + + mbedtls_strerror(ret, buf, sizeof(buf)); + fprintf(stderr, "unexpected base64 error1: %s (%d)\n", buf, + ret); + + goto err; + } + + bkey = malloc(olen); + if (!bkey) { + fprintf(stderr, "cannot allocate binary key buffer\n"); + goto err; + } + + ret = mbedtls_base64_decode(bkey, olen, &olen, ckey, ckey_len); + if (ret) { + char buf[256]; + + mbedtls_strerror(ret, buf, sizeof(buf)); + fprintf(stderr, "unexpected base64 error2: %s (%d)\n", buf, + ret); + + goto err; + } + + if (olen < 2 * KEY_LEN + NONCE_LEN) { + fprintf(stderr, + "not enough data in key file, found %zdB but needs %dB\n", + olen, 2 * KEY_LEN + NONCE_LEN); + goto err; + } + + switch (ctx->key_dir) { + case KEY_DIR_IN: + idx_enc = 0; + idx_dec = 1; + break; + case KEY_DIR_OUT: + idx_enc = 1; + idx_dec = 0; + break; + } + + memcpy(ctx->key_enc, bkey + KEY_LEN * idx_enc, KEY_LEN); + memcpy(ctx->key_dec, bkey + KEY_LEN * idx_dec, KEY_LEN); + memcpy(ctx->nonce, bkey + 2 * KEY_LEN, NONCE_LEN); + + ret = 0; + +err: + fclose(fp); + free(bkey); + free(ckey); + + return ret; +} + +static int ovpn_read_cipher(const char *cipher, struct ovpn_ctx *ctx) +{ + if (strcmp(cipher, "aes") == 0) + ctx->cipher = OVPN_CIPHER_ALG_AES_GCM; + else if (strcmp(cipher, "chachapoly") == 0) + ctx->cipher = OVPN_CIPHER_ALG_CHACHA20_POLY1305; + else if (strcmp(cipher, "none") == 0) + ctx->cipher = OVPN_CIPHER_ALG_NONE; + else + return -ENOTSUP; + + return 0; +} + +static int ovpn_read_key_direction(const char *dir, struct ovpn_ctx *ctx) +{ + int in_dir; + + in_dir = strtoll(dir, NULL, 10); + switch (in_dir) { + case KEY_DIR_IN: + case KEY_DIR_OUT: + ctx->key_dir = in_dir; + break; + default: + fprintf(stderr, + "invalid key direction provided. Can be 0 or 1 only\n"); + return -1; + } + + return 0; +} + +static int ovpn_socket(struct ovpn_ctx *ctx, sa_family_t family, int proto) +{ + struct sockaddr_storage local_sock; + struct sockaddr_in6 *in6; + struct sockaddr_in *in; + int ret, s, sock_type; + size_t sock_len; + + if (proto == IPPROTO_UDP) + sock_type = SOCK_DGRAM; + else if (proto == IPPROTO_TCP) + sock_type = SOCK_STREAM; + else + return -EINVAL; + + s = socket(family, sock_type, 0); + if (s < 0) { + perror("cannot create socket"); + return -1; + } + + memset((char *)&local_sock, 0, sizeof(local_sock)); + + switch (family) { + case AF_INET: + in = (struct sockaddr_in *)&local_sock; + in->sin_family = family; + in->sin_port = htons(ctx->lport); + in->sin_addr.s_addr = htonl(INADDR_ANY); + sock_len = sizeof(*in); + break; + case AF_INET6: + in6 = (struct sockaddr_in6 *)&local_sock; + in6->sin6_family = family; + in6->sin6_port = htons(ctx->lport); + in6->sin6_addr = in6addr_any; + sock_len = sizeof(*in6); + break; + default: + return -1; + } + + int opt = 1; + + ret = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + if (ret < 0) { + perror("setsockopt for SO_REUSEADDR"); + return ret; + } + + ret = setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)); + if (ret < 0) { + perror("setsockopt for SO_REUSEPORT"); + return ret; + } + + if (family == AF_INET6) { + opt = 0; + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &opt, + sizeof(opt))) { + perror("failed to set IPV6_V6ONLY"); + return -1; + } + } + + ret = bind(s, (struct sockaddr *)&local_sock, sock_len); + if (ret < 0) { + perror("cannot bind socket"); + goto err_socket; + } + + ctx->socket = s; + ctx->sa_family = family; + return 0; + +err_socket: + close(s); + return -1; +} + +static int ovpn_udp_socket(struct ovpn_ctx *ctx, sa_family_t family) +{ + return ovpn_socket(ctx, family, IPPROTO_UDP); +} + +static int ovpn_listen(struct ovpn_ctx *ctx, sa_family_t family) +{ + int ret; + + ret = ovpn_socket(ctx, family, IPPROTO_TCP); + if (ret < 0) + return ret; + + ret = listen(ctx->socket, 10); + if (ret < 0) { + perror("listen"); + close(ctx->socket); + return -1; + } + + return 0; +} + +static int ovpn_accept(struct ovpn_ctx *ctx) +{ + socklen_t socklen; + int ret; + + socklen = sizeof(ctx->remote); + ret = accept(ctx->socket, (struct sockaddr *)&ctx->remote, &socklen); + if (ret < 0) { + perror("accept"); + goto err; + } + + fprintf(stderr, "Connection received!\n"); + + switch (socklen) { + case sizeof(struct sockaddr_in): + case sizeof(struct sockaddr_in6): + break; + default: + fprintf(stderr, "error: expecting IPv4 or IPv6 connection\n"); + close(ret); + ret = -EINVAL; + goto err; + } + + return ret; +err: + close(ctx->socket); + return ret; +} + +static int ovpn_connect(struct ovpn_ctx *ovpn) +{ + socklen_t socklen; + int s, ret; + + s = socket(ovpn->remote.in4.sin_family, SOCK_STREAM, 0); + if (s < 0) { + perror("cannot create socket"); + return -1; + } + + switch (ovpn->remote.in4.sin_family) { + case AF_INET: + socklen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + socklen = sizeof(struct sockaddr_in6); + break; + default: + return -EOPNOTSUPP; + } + + ret = connect(s, (struct sockaddr *)&ovpn->remote, socklen); + if (ret < 0) { + perror("connect"); + goto err; + } + + fprintf(stderr, "connected\n"); + + ovpn->socket = s; + + return 0; +err: + close(s); + return ret; +} + +static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp) +{ + struct nlattr *attr; + struct nl_ctx *ctx; + int ret = -1; + + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_PEER_NEW); + if (!ctx) + return -ENOMEM; + + attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_SOCKET, ovpn->socket); + + if (!is_tcp) { + switch (ovpn->remote.in4.sin_family) { + case AF_INET: + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_REMOTE_IPV4, + ovpn->remote.in4.sin_addr.s_addr); + NLA_PUT_U16(ctx->nl_msg, OVPN_A_PEER_REMOTE_PORT, + ovpn->remote.in4.sin_port); + break; + case AF_INET6: + NLA_PUT(ctx->nl_msg, OVPN_A_PEER_REMOTE_IPV6, + sizeof(ovpn->remote.in6.sin6_addr), + &ovpn->remote.in6.sin6_addr); + NLA_PUT_U32(ctx->nl_msg, + OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID, + ovpn->remote.in6.sin6_scope_id); + NLA_PUT_U16(ctx->nl_msg, OVPN_A_PEER_REMOTE_PORT, + ovpn->remote.in6.sin6_port); + break; + default: + fprintf(stderr, + "Invalid family for remote socket address\n"); + goto nla_put_failure; + } + } + + if (ovpn->peer_ip_set) { + switch (ovpn->peer_ip.in4.sin_family) { + case AF_INET: + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_VPN_IPV4, + ovpn->peer_ip.in4.sin_addr.s_addr); + break; + case AF_INET6: + NLA_PUT(ctx->nl_msg, OVPN_A_PEER_VPN_IPV6, + sizeof(struct in6_addr), + &ovpn->peer_ip.in6.sin6_addr); + break; + default: + fprintf(stderr, "Invalid family for peer address\n"); + goto nla_put_failure; + } + } + + nla_nest_end(ctx->nl_msg, attr); + + ret = ovpn_nl_msg_send(ctx, NULL); +nla_put_failure: + nl_ctx_free(ctx); + return ret; +} + +static int ovpn_set_peer(struct ovpn_ctx *ovpn) +{ + struct nlattr *attr; + struct nl_ctx *ctx; + int ret = -1; + + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_PEER_SET); + if (!ctx) + return -ENOMEM; + + attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_INTERVAL, + ovpn->keepalive_interval); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_TIMEOUT, + ovpn->keepalive_timeout); + nla_nest_end(ctx->nl_msg, attr); + + ret = ovpn_nl_msg_send(ctx, NULL); +nla_put_failure: + nl_ctx_free(ctx); + return ret; +} + +static int ovpn_del_peer(struct ovpn_ctx *ovpn) +{ + struct nlattr *attr; + struct nl_ctx *ctx; + int ret = -1; + + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_PEER_DEL); + if (!ctx) + return -ENOMEM; + + attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id); + nla_nest_end(ctx->nl_msg, attr); + + ret = ovpn_nl_msg_send(ctx, NULL); +nla_put_failure: + nl_ctx_free(ctx); + return ret; +} + +static int ovpn_handle_peer(struct nl_msg *msg, void *arg) +{ + struct nlattr *pattrs[OVPN_A_PEER_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *attrs[OVPN_A_MAX + 1]; + __u16 rport = 0, lport = 0; + + nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!attrs[OVPN_A_PEER]) { + fprintf(stderr, "no packet content in netlink message\n"); + return NL_SKIP; + } + + nla_parse(pattrs, OVPN_A_PEER_MAX, nla_data(attrs[OVPN_A_PEER]), + nla_len(attrs[OVPN_A_PEER]), NULL); + + if (pattrs[OVPN_A_PEER_ID]) + fprintf(stderr, "* Peer %u\n", + nla_get_u32(pattrs[OVPN_A_PEER_ID])); + + if (pattrs[OVPN_A_PEER_VPN_IPV4]) { + char buf[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, nla_data(pattrs[OVPN_A_PEER_VPN_IPV4]), + buf, sizeof(buf)); + fprintf(stderr, "\tVPN IPv4: %s\n", buf); + } + + if (pattrs[OVPN_A_PEER_VPN_IPV6]) { + char buf[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, nla_data(pattrs[OVPN_A_PEER_VPN_IPV6]), + buf, sizeof(buf)); + fprintf(stderr, "\tVPN IPv6: %s\n", buf); + } + + if (pattrs[OVPN_A_PEER_LOCAL_PORT]) + lport = ntohs(nla_get_u16(pattrs[OVPN_A_PEER_LOCAL_PORT])); + + if (pattrs[OVPN_A_PEER_REMOTE_PORT]) + rport = ntohs(nla_get_u16(pattrs[OVPN_A_PEER_REMOTE_PORT])); + + if (pattrs[OVPN_A_PEER_REMOTE_IPV6]) { + void *ip = pattrs[OVPN_A_PEER_REMOTE_IPV6]; + char buf[INET6_ADDRSTRLEN]; + int scope_id = -1; + + if (pattrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]) { + void *p = pattrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]; + + scope_id = nla_get_u32(p); + } + + inet_ntop(AF_INET6, nla_data(ip), buf, sizeof(buf)); + fprintf(stderr, "\tRemote: %s:%hu (scope-id: %u)\n", buf, rport, + scope_id); + + if (pattrs[OVPN_A_PEER_LOCAL_IPV6]) { + void *ip = pattrs[OVPN_A_PEER_LOCAL_IPV6]; + + inet_ntop(AF_INET6, nla_data(ip), buf, sizeof(buf)); + fprintf(stderr, "\tLocal: %s:%hu\n", buf, lport); + } + } + + if (pattrs[OVPN_A_PEER_REMOTE_IPV4]) { + void *ip = pattrs[OVPN_A_PEER_REMOTE_IPV4]; + char buf[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, nla_data(ip), buf, sizeof(buf)); + fprintf(stderr, "\tRemote: %s:%hu\n", buf, rport); + + if (pattrs[OVPN_A_PEER_LOCAL_IPV4]) { + void *p = pattrs[OVPN_A_PEER_LOCAL_IPV4]; + + inet_ntop(AF_INET, nla_data(p), buf, sizeof(buf)); + fprintf(stderr, "\tLocal: %s:%hu\n", buf, lport); + } + } + + if (pattrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]) { + void *p = pattrs[OVPN_A_PEER_KEEPALIVE_INTERVAL]; + + fprintf(stderr, "\tKeepalive interval: %u sec\n", + nla_get_u32(p)); + } + + if (pattrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT]) + fprintf(stderr, "\tKeepalive timeout: %u sec\n", + nla_get_u32(pattrs[OVPN_A_PEER_KEEPALIVE_TIMEOUT])); + + if (pattrs[OVPN_A_PEER_VPN_RX_BYTES]) + fprintf(stderr, "\tVPN RX bytes: %" PRIu64 "\n", + nla_get_uint(pattrs[OVPN_A_PEER_VPN_RX_BYTES])); + + if (pattrs[OVPN_A_PEER_VPN_TX_BYTES]) + fprintf(stderr, "\tVPN TX bytes: %" PRIu64 "\n", + nla_get_uint(pattrs[OVPN_A_PEER_VPN_TX_BYTES])); + + if (pattrs[OVPN_A_PEER_VPN_RX_PACKETS]) + fprintf(stderr, "\tVPN RX packets: %" PRIu64 "\n", + nla_get_uint(pattrs[OVPN_A_PEER_VPN_RX_PACKETS])); + + if (pattrs[OVPN_A_PEER_VPN_TX_PACKETS]) + fprintf(stderr, "\tVPN TX packets: %" PRIu64 "\n", + nla_get_uint(pattrs[OVPN_A_PEER_VPN_TX_PACKETS])); + + if (pattrs[OVPN_A_PEER_LINK_RX_BYTES]) + fprintf(stderr, "\tLINK RX bytes: %" PRIu64 "\n", + nla_get_uint(pattrs[OVPN_A_PEER_LINK_RX_BYTES])); + + if (pattrs[OVPN_A_PEER_LINK_TX_BYTES]) + fprintf(stderr, "\tLINK TX bytes: %" PRIu64 "\n", + nla_get_uint(pattrs[OVPN_A_PEER_LINK_TX_BYTES])); + + if (pattrs[OVPN_A_PEER_LINK_RX_PACKETS]) + fprintf(stderr, "\tLINK RX packets: %" PRIu64 "\n", + nla_get_uint(pattrs[OVPN_A_PEER_LINK_RX_PACKETS])); + + if (pattrs[OVPN_A_PEER_LINK_TX_PACKETS]) + fprintf(stderr, "\tLINK TX packets: %" PRIu64 "\n", + nla_get_uint(pattrs[OVPN_A_PEER_LINK_TX_PACKETS])); + + return NL_SKIP; +} + +static int ovpn_get_peer(struct ovpn_ctx *ovpn) +{ + int flags = 0, ret = -1; + struct nlattr *attr; + struct nl_ctx *ctx; + + if (ovpn->peer_id == PEER_ID_UNDEF) + flags = NLM_F_DUMP; + + ctx = nl_ctx_alloc_flags(ovpn, OVPN_CMD_PEER_GET, flags); + if (!ctx) + return -ENOMEM; + + if (ovpn->peer_id != PEER_ID_UNDEF) { + attr = nla_nest_start(ctx->nl_msg, OVPN_A_PEER); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_ID, ovpn->peer_id); + nla_nest_end(ctx->nl_msg, attr); + } + + ret = ovpn_nl_msg_send(ctx, ovpn_handle_peer); +nla_put_failure: + nl_ctx_free(ctx); + return ret; +} + +static int ovpn_new_key(struct ovpn_ctx *ovpn) +{ + struct nlattr *keyconf, *key_dir; + struct nl_ctx *ctx; + int ret = -1; + + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_KEY_NEW); + if (!ctx) + return -ENOMEM; + + keyconf = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_SLOT, ovpn->key_slot); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_KEY_ID, ovpn->key_id); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_CIPHER_ALG, ovpn->cipher); + + key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_ENCRYPT_DIR); + NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_enc); + NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce); + nla_nest_end(ctx->nl_msg, key_dir); + + key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_DECRYPT_DIR); + NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_dec); + NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce); + nla_nest_end(ctx->nl_msg, key_dir); + + nla_nest_end(ctx->nl_msg, keyconf); + + ret = ovpn_nl_msg_send(ctx, NULL); +nla_put_failure: + nl_ctx_free(ctx); + return ret; +} + +static int ovpn_del_key(struct ovpn_ctx *ovpn) +{ + struct nlattr *keyconf; + struct nl_ctx *ctx; + int ret = -1; + + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_KEY_DEL); + if (!ctx) + return -ENOMEM; + + keyconf = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_SLOT, OVPN_KEY_SLOT_PRIMARY); + nla_nest_end(ctx->nl_msg, keyconf); + + ret = ovpn_nl_msg_send(ctx, NULL); +nla_put_failure: + nl_ctx_free(ctx); + return ret; +} + +static int ovpn_swap_keys(struct ovpn_ctx *ovpn) +{ + struct nl_ctx *ctx; + struct nlattr *kc; + int ret = -1; + + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_KEY_SWAP); + if (!ctx) + return -ENOMEM; + + kc = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF); + NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_PEER_ID, ovpn->peer_id); + nla_nest_end(ctx->nl_msg, kc); + + ret = ovpn_nl_msg_send(ctx, NULL); +nla_put_failure: + nl_ctx_free(ctx); + return ret; +} + +static int ovpn_handle_iface(struct nl_msg *msg, void *arg) +{ + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *attrs[OVPN_A_MAX + 1]; + + nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!attrs[OVPN_A_IFNAME]) { + fprintf(stderr, "no ifname in netlink message\n"); + return NL_SKIP; + } + + fprintf(stderr, "Created ifname: %s\n", + (char *)nla_data(attrs[OVPN_A_IFNAME])); + + return NL_SKIP; +} + +static int ovpn_new_iface(struct ovpn_ctx *ovpn) +{ + struct nl_ctx *ctx; + int ret = -1; + + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_DEV_NEW); + if (!ctx) + return -ENOMEM; + + NLA_PUT(ctx->nl_msg, OVPN_A_IFNAME, strlen(ovpn->ifname) + 1, + ovpn->ifname); + + if (ovpn->mode_set) + NLA_PUT_U32(ctx->nl_msg, OVPN_A_MODE, ovpn->mode); + + fprintf(stdout, "Creating interface %s with mode %u\n", ovpn->ifname, + ovpn->mode); + + ret = ovpn_nl_msg_send(ctx, ovpn_handle_iface); +nla_put_failure: + nl_ctx_free(ctx); + return ret; +} + +static int ovpn_del_iface(struct ovpn_ctx *ovpn) +{ + struct nl_ctx *ctx; + int ret = -1; + + ctx = nl_ctx_alloc(ovpn, OVPN_CMD_DEV_DEL); + if (!ctx) + return -ENOMEM; + + ret = ovpn_nl_msg_send(ctx, NULL); + nl_ctx_free(ctx); + return ret; +} + +static int nl_seq_check(struct nl_msg *msg, void *arg) +{ + return NL_OK; +} + +struct mcast_handler_args { + const char *group; + int id; +}; + +static int mcast_family_handler(struct nl_msg *msg, void *arg) +{ + struct mcast_handler_args *grp = arg; + struct nlattr *tb[CTRL_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *mcgrp; + int rem_mcgrp; + + nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[CTRL_ATTR_MCAST_GROUPS]) + return NL_SKIP; + + nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) { + struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1]; + + nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, + nla_data(mcgrp), nla_len(mcgrp), NULL); + + if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || + !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]) + continue; + if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]), + grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))) + continue; + grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]); + break; + } + + return NL_SKIP; +} + +static int mcast_error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, + void *arg) +{ + int *ret = arg; + + *ret = err->error; + return NL_STOP; +} + +static int mcast_ack_handler(struct nl_msg *msg, void *arg) +{ + int *ret = arg; + + *ret = 0; + return NL_STOP; +} + +static int ovpn_handle_msg(struct nl_msg *msg, void *arg) +{ + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *attrs[OVPN_A_MAX + 1]; + struct nlmsghdr *nlh = nlmsg_hdr(msg); + //enum ovpn_del_peer_reason reason; + char ifname[IF_NAMESIZE]; + __u32 ifindex; + + fprintf(stderr, "received message from ovpn-dco\n"); + + if (!genlmsg_valid_hdr(nlh, 0)) { + fprintf(stderr, "invalid header\n"); + return NL_STOP; + } + + if (nla_parse(attrs, OVPN_A_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL)) { + fprintf(stderr, "received bogus data from ovpn-dco\n"); + return NL_STOP; + } + + if (!attrs[OVPN_A_IFINDEX]) { + fprintf(stderr, "no ifindex in this message\n"); + return NL_STOP; + } + + ifindex = nla_get_u32(attrs[OVPN_A_IFINDEX]); + if (!if_indextoname(ifindex, ifname)) { + fprintf(stderr, "cannot resolve ifname for ifindex: %u\n", + ifindex); + return NL_STOP; + } + + switch (gnlh->cmd) { + case OVPN_CMD_PEER_DEL_NTF: + /*if (!attrs[OVPN_A_DEL_PEER_REASON]) { + * fprintf(stderr, "no reason in DEL_PEER message\n"); + * return NL_STOP; + *} + * + *reason = nla_get_u8(attrs[OVPN_A_DEL_PEER_REASON]); + *fprintf(stderr, + * "received CMD_DEL_PEER, ifname: %s reason: %d\n", + * ifname, reason); + */ + fprintf(stdout, "received CMD_PEER_DEL_NTF\n"); + break; + case OVPN_CMD_KEY_SWAP_NTF: + fprintf(stdout, "received CMD_KEY_SWAP_NTF\n"); + break; + default: + fprintf(stderr, "received unknown command: %d\n", gnlh->cmd); + return NL_STOP; + } + + return NL_OK; +} + +static int ovpn_get_mcast_id(struct nl_sock *sock, const char *family, + const char *group) +{ + struct nl_msg *msg; + struct nl_cb *cb; + int ret, ctrlid; + struct mcast_handler_args grp = { + .group = group, + .id = -ENOENT, + }; + + msg = nlmsg_alloc(); + if (!msg) + return -ENOMEM; + + cb = nl_cb_alloc(NL_CB_DEFAULT); + if (!cb) { + ret = -ENOMEM; + goto out_fail_cb; + } + + ctrlid = genl_ctrl_resolve(sock, "nlctrl"); + + genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0); + + ret = -ENOBUFS; + NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family); + + ret = nl_send_auto_complete(sock, msg); + if (ret < 0) + goto nla_put_failure; + + ret = 1; + + nl_cb_err(cb, NL_CB_CUSTOM, mcast_error_handler, &ret); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, mcast_ack_handler, &ret); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, mcast_family_handler, &grp); + + while (ret > 0) + nl_recvmsgs(sock, cb); + + if (ret == 0) + ret = grp.id; + nla_put_failure: + nl_cb_put(cb); + out_fail_cb: + nlmsg_free(msg); + return ret; +} + +static void ovpn_listen_mcast(void) +{ + struct nl_sock *sock; + struct nl_cb *cb; + int mcid, ret; + + sock = nl_socket_alloc(); + if (!sock) { + fprintf(stderr, "cannot allocate netlink socket\n"); + goto err_free; + } + + nl_socket_set_buffer_size(sock, 8192, 8192); + + ret = genl_connect(sock); + if (ret < 0) { + fprintf(stderr, "cannot connect to generic netlink: %s\n", + nl_geterror(ret)); + goto err_free; + } + + mcid = ovpn_get_mcast_id(sock, OVPN_FAMILY_NAME, OVPN_MCGRP_PEERS); + if (mcid < 0) { + fprintf(stderr, "cannot get mcast group: %s\n", + nl_geterror(mcid)); + goto err_free; + } + + ret = nl_socket_add_membership(sock, mcid); + if (ret) { + fprintf(stderr, "failed to join mcast group: %d\n", ret); + goto err_free; + } + + ret = 0; + cb = nl_cb_alloc(NL_CB_DEFAULT); + nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nl_seq_check, NULL); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, ovpn_handle_msg, &ret); + nl_cb_err(cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &ret); + + while (ret != -EINTR) + ret = nl_recvmsgs(sock, cb); + + nl_cb_put(cb); +err_free: + nl_socket_free(sock); +} + +static void usage(const char *cmd) +{ + fprintf(stderr, "Error: invalid arguments.\n\n"); + fprintf(stderr, + "Usage %s [arguments..]\n", + cmd); + fprintf(stderr, "\tiface: tun interface name\n\n"); + + fprintf(stderr, + "* connect : start connecting peer of TCP-based VPN session\n"); + fprintf(stderr, "\tpeer-id: peer ID of the connecting peer\n"); + fprintf(stderr, "\tremote-addr: peer IP address\n"); + fprintf(stderr, "\tremote-port: peer TCP port\n"); + + fprintf(stderr, + "* listen : listen for incoming peer TCP connections\n"); + fprintf(stderr, "\tlport: src TCP port\n"); + fprintf(stderr, + "\tpeers_file: file containing one peer per line: Line format:\n"); + fprintf(stderr, "\t\t \n\n"); + + fprintf(stderr, + "* new_peer [vpnaddr]: add new peer\n"); + fprintf(stderr, + "\tpeer-id: peer ID to be used in data packets to/from this peer\n"); + fprintf(stderr, "\tlocal-port: local UDP port\n"); + fprintf(stderr, "\tremote-addr: peer IP address\n"); + fprintf(stderr, "\tremote-port: peer UDP port\n"); + fprintf(stderr, "\tvpnaddr: peer VPN IP\n\n"); + + fprintf(stderr, + "* new_multi_peer : add multiple peers as listed in the file\n"); + fprintf(stderr, "\tlport: local UDP port to bind to\n"); + fprintf(stderr, + "\tfile: text file containing one peer per line. Line format:\n"); + fprintf(stderr, "\t\t \n\n"); + + fprintf(stderr, + "* set_peer : set peer attributes\n"); + fprintf(stderr, "\tpeer-id: peer ID of the peer to modify\n"); + fprintf(stderr, + "\tkeepalive_interval: interval for sending ping messages\n"); + fprintf(stderr, + "\tkeepalive_timeout: time after which a peer is timed out\n\n"); + + fprintf(stderr, "* del_peer : delete peer\n"); + fprintf(stderr, "\tpeer-id: peer ID of the peer to delete\n\n"); + + fprintf(stderr, + "* new_key : set data channel key\n"); + fprintf(stderr, + "\tpeer-id: peer ID of the peer to configure the key for\n"); + fprintf(stderr, + "\tcipher: cipher to use, supported: aes (AES-GCM), chachapoly (CHACHA20POLY1305), none\n"); + fprintf(stderr, + "\tkey_dir: key direction, must 0 on one host and 1 on the other\n"); + fprintf(stderr, "\tkey_file: file containing the pre-shared key\n\n"); + + fprintf(stderr, + "* del_key : erase existing data channel key\n"); + fprintf(stderr, "\tpeer-id: peer ID of the peer to modify\n\n"); + + fprintf(stderr, + "* swap_keys : swap primary and seconday key slots\n"); + fprintf(stderr, "\tpeer-id: peer ID of the peer to modify\n\n"); + + fprintf(stderr, + "* listen_mcast: listen to ovpn-dco netlink multicast messages\n"); +} + +static int ovpn_parse_remote(struct ovpn_ctx *ovpn, const char *host, + const char *service, const char *vpnip) +{ + int ret; + struct addrinfo *result; + struct addrinfo hints = { + .ai_family = ovpn->sa_family, + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP + }; + + if (host) { + ret = getaddrinfo(host, service, &hints, &result); + if (ret == EAI_NONAME || ret == EAI_FAIL) + return -1; + + if (!(result->ai_family == AF_INET && + result->ai_addrlen == sizeof(struct sockaddr_in)) && + !(result->ai_family == AF_INET6 && + result->ai_addrlen == sizeof(struct sockaddr_in6))) { + ret = -EINVAL; + goto out; + } + + memcpy(&ovpn->remote, result->ai_addr, result->ai_addrlen); + } + + if (vpnip) { + ret = getaddrinfo(vpnip, NULL, &hints, &result); + if (ret == EAI_NONAME || ret == EAI_FAIL) + return -1; + + if (!(result->ai_family == AF_INET && + result->ai_addrlen == sizeof(struct sockaddr_in)) && + !(result->ai_family == AF_INET6 && + result->ai_addrlen == sizeof(struct sockaddr_in6))) { + ret = -EINVAL; + goto out; + } + + memcpy(&ovpn->peer_ip, result->ai_addr, result->ai_addrlen); + ovpn->sa_family = result->ai_family; + + ovpn->peer_ip_set = true; + } + + ret = 0; +out: + freeaddrinfo(result); + return ret; +} + +static int ovpn_parse_new_peer(struct ovpn_ctx *ovpn, const char *peer_id, + const char *raddr, const char *rport, + const char *vpnip) +{ + ovpn->peer_id = strtoul(peer_id, NULL, 10); + if (errno == ERANGE) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + + return ovpn_parse_remote(ovpn, raddr, rport, vpnip); +} + +static void ovpn_send_tcp_data(int socket) +{ + uint16_t len = htons(1000); + uint8_t buf[1002]; + int ret; + + memcpy(buf, &len, sizeof(len)); + memset(buf + sizeof(len), 0x86, sizeof(buf) - sizeof(len)); + + ret = send(socket, buf, sizeof(buf), 0); + + fprintf(stdout, "Sent %u bytes over TCP socket\n", ret); +} + +static void ovpn_recv_tcp_data(int socket) +{ + uint8_t buf[1002]; + uint16_t len; + int ret; + + ret = recv(socket, buf, sizeof(buf), 0); + + if (ret < 2) { + fprintf(stderr, ">>>> Error while reading TCP data: %d\n", ret); + return; + } + + memcpy(&len, buf, sizeof(len)); + len = ntohs(len); + + fprintf(stdout, ">>>> Received %u bytes over TCP socket, header: %u\n", + ret, len); + +/* int i; + * for (i = 2; i < ret; i++) { + * fprintf(stdout, "0x%.2x ", buf[i]); + * if (i && !((i - 2) % 16)) + * fprintf(stdout, "\n"); + * } + * fprintf(stdout, "\n"); + */ +} + +static int ovpn_parse_set_peer(struct ovpn_ctx *ovpn, int argc, char *argv[]) +{ + if (argc < 5) { + usage(argv[0]); + return -1; + } + + ovpn->keepalive_interval = strtoul(argv[3], NULL, 10); + if (errno == ERANGE) { + fprintf(stderr, "keepalive interval value out of range\n"); + return -1; + } + + ovpn->keepalive_timeout = strtoul(argv[4], NULL, 10); + if (errno == ERANGE) { + fprintf(stderr, "keepalive interval value out of range\n"); + return -1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct ovpn_ctx ovpn; +// struct nl_ctx *ctx; + int ret; + + if (argc < 2) { + usage(argv[0]); + return -1; + } + + memset(&ovpn, 0, sizeof(ovpn)); + ovpn.sa_family = AF_INET; + ovpn.cli_socket = -1; + + if (argc > 2) { + strscpy(ovpn.ifname, argv[2], IFNAMSIZ - 1); + ovpn.ifname[IFNAMSIZ - 1] = '\0'; + } + + /* all commands except new_iface expect a valid ifindex */ + if (strcmp(argv[1], "new_iface")) { + /* in this case a ifname MUST be defined */ + if (argc < 3) { + usage(argv[0]); + return -1; + } + + ovpn.ifindex = if_nametoindex(ovpn.ifname); + if (!ovpn.ifindex) { + fprintf(stderr, "cannot find interface: %s\n", + strerror(errno)); + return -1; + } + } + + if (!strcmp(argv[1], "new_iface")) { + if (argc > 3) { + if (!strcmp(argv[3], "P2P")) { + ovpn.mode = OVPN_MODE_P2P; + } else if (!strcmp(argv[3], "MP")) { + ovpn.mode = OVPN_MODE_MP; + } else { + fprintf(stderr, "Cannot parse iface mode: %s\n", + argv[3]); + return -1; + } + ovpn.mode_set = true; + } + + ret = ovpn_new_iface(&ovpn); + if (ret < 0) { + fprintf(stderr, "Cannot create interface %s: %d\n", + ovpn.ifname, ret); + return -1; + } + } else if (!strcmp(argv[1], "del_iface")) { + ret = ovpn_del_iface(&ovpn); + if (ret < 0) { + fprintf(stderr, "Cannot delete interface %s: %d\n", + ovpn.ifname, ret); + return -1; + } + } else if (!strcmp(argv[1], "listen")) { + char peer_id[10], vpnip[100]; + int n; + FILE *fp; + + if (argc < 4) { + usage(argv[0]); + return -1; + } + + ovpn.lport = strtoul(argv[3], NULL, 10); + if (errno == ERANGE || ovpn.lport > 65535) { + fprintf(stderr, "lport value out of range\n"); + return -1; + } + + if (argc > 4 && !strcmp(argv[4], "ipv6")) + ovpn.sa_family = AF_INET6; + + ret = ovpn_listen(&ovpn, ovpn.sa_family); + if (ret < 0) { + fprintf(stderr, "cannot listen on TCP socket\n"); + return ret; + } + + fp = fopen(argv[4], "r"); + if (!fp) { + fprintf(stderr, "cannot open file: %s\n", argv[4]); + return -1; + } + + while ((n = fscanf(fp, "%s %s\n", peer_id, vpnip)) == 2) { + struct ovpn_ctx peer_ctx = { 0 }; + + peer_ctx.ifindex = ovpn.ifindex; + peer_ctx.sa_family = ovpn.sa_family; + + peer_ctx.socket = ovpn_accept(&ovpn); + if (peer_ctx.socket < 0) { + fprintf(stderr, "cannot accept connection!\n"); + return -1; + } + + /* store the socket of the first peer to test TCP I/O */ + if (ovpn.cli_socket < 0) + ovpn.cli_socket = peer_ctx.socket; + + ret = ovpn_parse_new_peer(&peer_ctx, peer_id, NULL, + NULL, vpnip); + if (ret < 0) { + fprintf(stderr, "error while parsing line\n"); + return -1; + } + + ret = ovpn_new_peer(&peer_ctx, true); + if (ret < 0) { + fprintf(stderr, + "cannot add peer to VPN: %s %s\n", + peer_id, vpnip); + return ret; + } + } + + if (ovpn.cli_socket >= 0) + ovpn_recv_tcp_data(ovpn.cli_socket); + } else if (!strcmp(argv[1], "connect")) { + if (argc < 5) { + usage(argv[0]); + return -1; + } + + ovpn.sa_family = AF_INET; + + ret = ovpn_parse_new_peer(&ovpn, argv[3], argv[4], argv[5], + NULL); + if (ret < 0) { + fprintf(stderr, "Cannot parse remote peer data\n"); + return ret; + } + + ret = ovpn_connect(&ovpn); + if (ret < 0) { + fprintf(stderr, "cannot connect TCP socket\n"); + return ret; + } + + ret = ovpn_new_peer(&ovpn, true); + if (ret < 0) { + fprintf(stderr, "cannot add peer to VPN\n"); + close(ovpn.socket); + return ret; + } + + if (argc > 6) { + ovpn.key_slot = OVPN_KEY_SLOT_PRIMARY; + ovpn.key_id = 0; + ovpn.cipher = OVPN_CIPHER_ALG_AES_GCM; + ovpn.key_dir = KEY_DIR_OUT; + + ret = ovpn_read_key(argv[6], &ovpn); + if (ret) + return ret; + + ret = ovpn_new_key(&ovpn); + if (ret < 0) { + fprintf(stderr, "cannot set key\n"); + return ret; + } + + ovpn_send_tcp_data(ovpn.socket); + } + } else if (!strcmp(argv[1], "new_peer")) { + if (argc < 7) { + usage(argv[0]); + return -1; + } + + ovpn.lport = strtoul(argv[3], NULL, 10); + if (errno == ERANGE || ovpn.lport > 65535) { + fprintf(stderr, "lport value out of range\n"); + return -1; + } + + const char *vpnip = (argc > 7) ? argv[7] : NULL; + + ret = ovpn_parse_new_peer(&ovpn, argv[4], argv[5], argv[6], + vpnip); + if (ret < 0) + return ret; + + ret = ovpn_udp_socket(&ovpn, AF_INET6); //ovpn.sa_family ? + if (ret < 0) + return ret; + + ret = ovpn_new_peer(&ovpn, false); + if (ret < 0) { + fprintf(stderr, "cannot add peer to VPN\n"); + return ret; + } + } else if (!strcmp(argv[1], "new_multi_peer")) { + char peer_id[10], raddr[128], rport[10], vpnip[100]; + FILE *fp; + int n; + + if (argc < 5) { + usage(argv[0]); + return -1; + } + + ovpn.lport = strtoul(argv[3], NULL, 10); + if (errno == ERANGE || ovpn.lport > 65535) { + fprintf(stderr, "lport value out of range\n"); + return -1; + } + + fp = fopen(argv[4], "r"); + if (!fp) { + fprintf(stderr, "cannot open file: %s\n", argv[4]); + return -1; + } + + ret = ovpn_udp_socket(&ovpn, AF_INET6); + if (ret < 0) + return ret; + + while ((n = fscanf(fp, "%s %s %s %s\n", peer_id, raddr, rport, + vpnip)) == 4) { + struct ovpn_ctx peer_ctx = { 0 }; + + peer_ctx.ifindex = ovpn.ifindex; + peer_ctx.socket = ovpn.socket; + peer_ctx.sa_family = AF_UNSPEC; + + ret = ovpn_parse_new_peer(&peer_ctx, peer_id, raddr, + rport, vpnip); + if (ret < 0) { + fprintf(stderr, "error while parsing line\n"); + return -1; + } + + ret = ovpn_new_peer(&peer_ctx, false); + if (ret < 0) { + fprintf(stderr, + "cannot add peer to VPN: %s %s %s %s\n", + peer_id, raddr, rport, vpnip); + return ret; + } + } + } else if (!strcmp(argv[1], "set_peer")) { + ovpn.peer_id = strtoul(argv[3], NULL, 10); + if (errno == ERANGE) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + + argv++; + argc--; + + ret = ovpn_parse_set_peer(&ovpn, argc, argv); + if (ret < 0) + return ret; + + ret = ovpn_set_peer(&ovpn); + if (ret < 0) { + fprintf(stderr, "cannot set peer to VPN\n"); + return ret; + } + } else if (!strcmp(argv[1], "del_peer")) { + if (argc < 4) { + usage(argv[0]); + return -1; + } + + ovpn.peer_id = strtoul(argv[3], NULL, 10); + if (errno == ERANGE) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + + ret = ovpn_del_peer(&ovpn); + if (ret < 0) { + fprintf(stderr, "cannot delete peer to VPN\n"); + return ret; + } + } else if (!strcmp(argv[1], "get_peer")) { + ovpn.peer_id = PEER_ID_UNDEF; + if (argc > 3) + ovpn.peer_id = strtoul(argv[3], NULL, 10); + + fprintf(stderr, "List of peers connected to: %s\n", + ovpn.ifname); + + ret = ovpn_get_peer(&ovpn); + if (ret < 0) { + fprintf(stderr, "cannot get peer(s): %d\n", ret); + return ret; + } + } else if (!strcmp(argv[1], "new_key")) { + if (argc < 8) { + usage(argv[0]); + return -1; + } + + ovpn.peer_id = strtoul(argv[3], NULL, 10); + if (errno == ERANGE) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + + int slot = strtoul(argv[4], NULL, 10); + + if (errno == ERANGE || slot < 1 || slot > 2) { + fprintf(stderr, "ket slot out of range\n"); + return -1; + } + + switch (slot) { + case 1: + ovpn.key_slot = OVPN_KEY_SLOT_PRIMARY; + break; + case 2: + ovpn.key_slot = OVPN_KEY_SLOT_SECONDARY; + break; + } + + ovpn.key_id = strtoul(argv[5], NULL, 10); + if (errno == ERANGE || ovpn.key_id > 2) { + fprintf(stderr, "ket ID out of range\n"); + return -1; + } + + ret = ovpn_read_cipher(argv[6], &ovpn); + if (ret < 0) + return ret; + + ret = ovpn_read_key_direction(argv[7], &ovpn); + if (ret < 0) + return ret; + + ret = ovpn_read_key(argv[8], &ovpn); + if (ret) + return ret; + + ret = ovpn_new_key(&ovpn); + if (ret < 0) { + fprintf(stderr, "cannot set key\n"); + return ret; + } + } else if (!strcmp(argv[1], "del_key")) { + if (argc < 3) { + usage(argv[0]); + return -1; + } + + ovpn.peer_id = strtoul(argv[3], NULL, 10); + if (errno == ERANGE) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + + argv++; + argc--; + + ret = ovpn_del_key(&ovpn); + if (ret < 0) { + fprintf(stderr, "cannot delete key\n"); + return ret; + } + } else if (!strcmp(argv[1], "swap_keys")) { + if (argc < 3) { + usage(argv[0]); + return -1; + } + + ovpn.peer_id = strtoul(argv[3], NULL, 10); + if (errno == ERANGE) { + fprintf(stderr, "peer ID value out of range\n"); + return -1; + } + + argv++; + argc--; + + ret = ovpn_swap_keys(&ovpn); + if (ret < 0) { + fprintf(stderr, "cannot swap keys\n"); + return ret; + } + } else if (!strcmp(argv[1], "listen_mcast")) { + ovpn_listen_mcast(); + } else { + usage(argv[0]); + return -1; + } + + return ret; +} diff --git a/tools/testing/selftests/net/ovpn/tcp_peers.txt b/tools/testing/selftests/net/ovpn/tcp_peers.txt new file mode 100644 index 0000000000000000000000000000000000000000..d753eebe8716ed3588334ad766981e883ed2469a --- /dev/null +++ b/tools/testing/selftests/net/ovpn/tcp_peers.txt @@ -0,0 +1,5 @@ +1 5.5.5.2 +2 5.5.5.3 +3 5.5.5.4 +4 5.5.5.5 +5 5.5.5.6 diff --git a/tools/testing/selftests/net/ovpn/udp_peers.txt b/tools/testing/selftests/net/ovpn/udp_peers.txt new file mode 100644 index 0000000000000000000000000000000000000000..32f14bd9347a63e58438311b6d880b9fef768aa2 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/udp_peers.txt @@ -0,0 +1,5 @@ +1 10.10.1.2 1 5.5.5.2 +2 10.10.2.2 1 5.5.5.3 +3 10.10.3.2 1 5.5.5.4 +4 10.10.4.2 1 5.5.5.5 +5 10.10.5.2 1 5.5.5.6