From patchwork Thu Mar 31 21:57:36 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kristof Provost via Openvpn-devel X-Patchwork-Id: 2357 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director12.mail.ord1d.rsapps.net ([172.27.255.9]) by backend41.mail.ord1d.rsapps.net with LMTP id aLRuKdW+RmIILQAAqwncew (envelope-from ) for ; Fri, 01 Apr 2022 04:59:01 -0400 Received: from proxy20.mail.iad3a.rsapps.net ([172.27.255.9]) by director12.mail.ord1d.rsapps.net with LMTP id CJ2LBta+RmLmNgAAIasKDg (envelope-from ) for ; Fri, 01 Apr 2022 04:59:02 -0400 Received: from smtp17.gate.iad3a ([172.27.255.9]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy20.mail.iad3a.rsapps.net with LMTPS id 0DQxAda+RmKpdgAAtfLT2w (envelope-from ) for ; Fri, 01 Apr 2022 04:59:02 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp17.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=netgate.com; dmarc=pass (p=none; dis=none) header.from=lists.sourceforge.net X-Suspicious-Flag: YES X-Classification-ID: f906d7da-b199-11ec-b78c-525400723ca9-1-1 Received: from [216.105.38.7] ([216.105.38.7:51900] helo=lists.sourceforge.net) by smtp17.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id AA/EE-10973-5DEB6426; Fri, 01 Apr 2022 04:59:01 -0400 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.94.2) (envelope-from ) id 1naD6Q-0008JK-TE; Fri, 01 Apr 2022 08:57:53 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1naD6Q-0008JE-AO for openvpn-devel@lists.sourceforge.net; Fri, 01 Apr 2022 08:57:52 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=m3stfv6UPMPuRL5dN0yjERZ85JA/WoEW/zfvTOquXcE=; b=h0VbVWaU+etbXE1EmHm1GAnmb8 1RmwS1tjbNA+k3vW5onmlni7H5tv/qnf9pBcX+RVt3FRMOIvBWAlD5Z7IE7q7WartgIH8BbVlNuBC DnsTG1CfjpPkthz0jePgGBWELlWbJV5Dv2+PjFnLGHxcX0RnfIWIbCIyfAd5+UBx7vEc=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=m3stfv6UPMPuRL5dN0yjERZ85JA/WoEW/zfvTOquXcE=; b=O7UVcQ/mMtRaR2qYIznXdpl5eX 2qYHM4qRVccThxyU0jjtMerfMuCktzyPoQyoNexm6q8izm5qvHcyrR72RS81WFd13YjksrC36TTx0 4SoBaZbFADoBX/giOCFqMO/pHLPAA6E8/z+KX5RCPzlhKL6HzYCzB/EU/7bH12m0PBr8=; Received: from mail-wr1-f53.google.com ([209.85.221.53]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.94.2) id 1naD6K-001fWl-0j for openvpn-devel@lists.sourceforge.net; Fri, 01 Apr 2022 08:57:52 +0000 Received: by mail-wr1-f53.google.com with SMTP id r13so3185966wrr.9 for ; Fri, 01 Apr 2022 01:57:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=netgate.com; s=google; h=from:to:subject:date:message-id:in-reply-to:references:mime-version :content-transfer-encoding; bh=m3stfv6UPMPuRL5dN0yjERZ85JA/WoEW/zfvTOquXcE=; b=Kk5fbvgwoVqbC79gdDU/kAIeJdlGtBS5Uo3XhTU+0vGra7Bq6XEZmvJCL8P3b8FZLF NhBl3/YR9+FEIAzezFqI3dG/u4Wh3KrJMbASg85aiesaqbxE4O5CuLHc1s5eKCen+FMg fQ/dLXCXjfGoQh/krEeD9Mg+2ZmLEU4y0EzWY= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=m3stfv6UPMPuRL5dN0yjERZ85JA/WoEW/zfvTOquXcE=; b=eoJlWKWUpI7dKzQH63Dg/j3SmAaK0xnIR0Hwvud63arsd1ZRxRcIzGYj2kBzLbouSP vdwRoWNupXjq0Bek467N5dt/BcUtzObgAmnRAG1tEyns6p9Mn3i51KUt7K0J2NB89fgM kuOfXtIb4RnH3rMcc9/9x7hhDKinoyFMGJFUOpExPu4sWZ57ZRprDr/zXB6zjc8yX+7b qqCyC+XC/Jfd/JVsjaCqLFZxLXObWtpmjq3M72SFivre5E3pdgEVVckOZzxhSXU7OQe9 z8criCS7oLVkqmocFJ77wHFzXxEWpn2fzJNo2KkFqiwA2/3KaOVzLyN9gDSId+gcCHyY UBuQ== X-Gm-Message-State: AOAM531uaSMYvreqGF2ndObUIx9oK3h3brHHDTbw9ajZ9OKQfuOF3/Sk 0SxhspaE9BmiUIhgukiqIBYS9LiSSgEDdF56 X-Google-Smtp-Source: ABdhPJyA0KeR2bnOwMIZ+EAjgH+gTTiLwlw8U201z9UYBwpqeaodbT+GomAzVzw4v9EN+GSoUitW6A== X-Received: by 2002:a05:6000:1e06:b0:205:8a7f:c08d with SMTP id bj6-20020a0560001e0600b002058a7fc08dmr6943028wrb.559.1648803460986; Fri, 01 Apr 2022 01:57:40 -0700 (PDT) Received: from nut.jupiter.sigsegv.be (ptr-8rgvk5277arubwggeqg.18120a2.ip6.access.telenet.be. [2a02:1811:240d:2900:f602:70ff:feae:6e98]) by smtp.googlemail.com with ESMTPSA id o5-20020a5d4a85000000b00205a8bb9c0dsm2032719wrq.90.2022.04.01.01.57.40 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 01 Apr 2022 01:57:40 -0700 (PDT) To: openvpn-devel Date: Fri, 1 Apr 2022 10:57:36 +0200 Message-Id: <20220401085736.5727-2-kprovost@netgate.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220401085736.5727-1-kprovost@netgate.com> References: <20220401085736.5727-1-kprovost@netgate.com> MIME-Version: 1.0 X-Spam-Report: Spam detection software, running on the system "util-spamd-2.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: From: Kristof Provost Implement data-channel offload for FreeBSD. The implementation and flow is very similar to that of the Linux DCO support. Signed-off-by: Kristof Provost --- configure.ac | 6 +- src/openvpn/Makefile.am | 1 + src/openvpn/dco_freebsd.c | 594 +++++++++++++++++++++++++++++++++ src/openvpn/dco_freebsd.h [...] Content analysis details: (-0.2 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [209.85.221.53 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.221.53 listed in wl.mailspike.net] -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.0 T_SCC_BODY_TEXT_LINE No description available. X-Headers-End: 1naD6K-001fWl-0j Subject: [Openvpn-devel] [PATCH] ovpn-dco: introduce FreeBSD data-channel offload support X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Kristof Provost via Openvpn-devel From: Kristof Provost via Openvpn-devel Reply-To: Kristof Provost Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Kristof Provost Implement data-channel offload for FreeBSD. The implementation and flow is very similar to that of the Linux DCO support. Signed-off-by: Kristof Provost --- configure.ac | 6 +- src/openvpn/Makefile.am | 1 + src/openvpn/dco_freebsd.c | 594 +++++++++++++++++++++++++++++++++ src/openvpn/dco_freebsd.h | 58 ++++ src/openvpn/dco_internal.h | 1 + src/openvpn/forward.c | 8 +- src/openvpn/mtcp.c | 6 +- src/openvpn/mudp.c | 2 +- src/openvpn/multi.c | 2 +- src/openvpn/options.c | 8 +- src/openvpn/ovpn_dco_freebsd.h | 64 ++++ src/openvpn/tun.c | 8 +- src/openvpn/tun.h | 6 + 13 files changed, 746 insertions(+), 18 deletions(-) create mode 100644 src/openvpn/dco_freebsd.c create mode 100644 src/openvpn/dco_freebsd.h create mode 100644 src/openvpn/ovpn_dco_freebsd.h diff --git a/configure.ac b/configure.ac index 7199483a..d3ebc932 100644 --- a/configure.ac +++ b/configure.ac @@ -787,7 +787,11 @@ dnl AC_DEFINE(ENABLE_DCO, 1, [Enable data channel offload for Linux]) AC_MSG_NOTICE([Enabled ovpn-dco support for Linux]) ;; - + *-*-freebsd*) + LIBS="${LIBS} -lnv" + AC_DEFINE(ENABLE_DCO, 1, [Enable data channel offload for FreeBSD]) + AC_MSG_NOTICE([Enabled ovpn-dco support for FreeBSD]) + ;; *-mingw*) AC_MSG_NOTICE([NOTE: --enable-dco ignored on Windows because it's always enabled]) ;; diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 84fd3202..4515935e 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -54,6 +54,7 @@ openvpn_SOURCES = \ crypto_openssl.c crypto_openssl.h \ crypto_mbedtls.c crypto_mbedtls.h \ dco.c dco.h dco_internal.h \ + dco_freebsd.c dco_freebsd.h \ dco_linux.c dco_linux.h \ dco_win.c dco_win.h \ dhcp.c dhcp.h \ diff --git a/src/openvpn/dco_freebsd.c b/src/openvpn/dco_freebsd.c new file mode 100644 index 00000000..508137e0 --- /dev/null +++ b/src/openvpn/dco_freebsd.c @@ -0,0 +1,594 @@ +/* + * Interface to FreeBSD dco networking code + * + * Copyright (C) 2022 Rubicon Communications, LLC (Netgate). All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#if defined(ENABLE_DCO) && defined(TARGET_FREEBSD) + +#include "syshead.h" + +#include +#include +#include +#include + +#include "dco_freebsd.h" +#include "dco.h" +#include "tun.h" +#include "crypto.h" +#include "ssl_common.h" + +static nvlist_t * +sockaddr_to_nvlist(const struct sockaddr *sa) +{ + nvlist_t *nvl = nvlist_create(0); + + nvlist_add_number(nvl, "af", sa->sa_family); + + switch (sa->sa_family) { + case AF_INET: + { + const struct sockaddr_in *in = (const struct sockaddr_in*)sa; + nvlist_add_binary(nvl, "address", &in->sin_addr, sizeof(in->sin_addr)); + nvlist_add_number(nvl, "port", in->sin_port); + break; + } + case AF_INET6: + { + const struct sockaddr_in6 *in6 = (const struct sockaddr_in6*)sa; + nvlist_add_binary(nvl, "address", &in6->sin6_addr, sizeof(in6->sin6_addr)); + nvlist_add_number(nvl, "port", in6->sin6_port); + break; + } + default: + abort(); + } + + return (nvl); +} + +int +dco_new_peer(dco_context_t *dco, unsigned int peerid, int sd, + struct sockaddr *localaddr, struct sockaddr *remoteaddr, + struct in_addr *remote_in4, struct in6_addr *remote_in6) +{ + struct ifdrv drv; + nvlist_t *nvl; + int ret; + + nvl = nvlist_create(0); + + msg(D_DCO_DEBUG, "%s: peer-id %d, fd %d", __func__, peerid, sd); + + if (localaddr) + { + nvlist_add_nvlist(nvl, "local", sockaddr_to_nvlist(localaddr)); + } + + if (remoteaddr) + { + nvlist_add_nvlist(nvl, "remote", sockaddr_to_nvlist(remoteaddr)); + } + + nvlist_add_number(nvl, "fd", sd); + + bzero(&drv, sizeof(drv)); + snprintf(drv.ifd_name, IFNAMSIZ, "%s", dco->ifname); + drv.ifd_cmd = OVPN_NEW_PEER; + drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len); + + ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv); + free(drv.ifd_data); + nvlist_destroy(nvl); + if (ret) + { + msg(D_DCO, "Failed to create new peer %d", errno); + return ret; + } + + return 0; +} + +static int +open_fd(dco_context_t *dco) +{ + int ret; + + ret = pipe2(dco->pipefd, O_CLOEXEC | O_NONBLOCK); + if (ret != 0) + { + return -1; + } + + dco->fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + dco->dco_packet_in = alloc_buf(PAGE_SIZE); + + return dco->fd; +} + +static void +close_fd(dco_context_t *dco) +{ + close(dco->pipefd[0]); + close(dco->pipefd[1]); + close(dco->fd); +} + +bool +ovpn_dco_init(int mode, dco_context_t *dco) +{ + if (open_fd(dco) < 0) + { + msg(D_DCO, "Failed to open socket"); + return false; + } + return true; +} + +static int +create_interface(struct tuntap *tt, const char *dev) +{ + int ret; + struct ifreq ifr; + + bzero(&ifr, sizeof(ifr)); + + /* Create ovpnx first, then rename it. */ + snprintf(ifr.ifr_name, IFNAMSIZ, "ovpn"); + ret = ioctl(tt->dco.fd, SIOCIFCREATE2, &ifr); + if (ret) + { + msg(D_DCO, "Failed to create interface %s: %d", ifr.ifr_name, errno); + return ret; + } + + /* Rename */ + if (!strcmp(dev, "tun")) + { + ifr.ifr_data = "ovpn"; + } + else + { + ifr.ifr_data = dev; + } + ret = ioctl(tt->dco.fd, SIOCSIFNAME, &ifr); + if (ret) + { + /* Delete the created interface again. */ + (void)ioctl(tt->dco.fd, SIOCIFDESTROY, &ifr); + msg(D_DCO, "Failed to create interface %s: %d", ifr.ifr_data, errno); + return ret; + } + + snprintf(tt->dco.ifname, IFNAMSIZ, "%s", ifr.ifr_data); + tt->actual_name = string_alloc(tt->dco.ifname, NULL); + + return 0; +} + +static int +remove_interface(struct tuntap *tt) +{ + int ret; + struct ifreq ifr; + + bzero(&ifr, sizeof(ifr)); + snprintf(ifr.ifr_name, IFNAMSIZ, "%s", tt->dco.ifname); + + ret = ioctl(tt->dco.fd, SIOCIFDESTROY, &ifr); + if (ret) + { + msg(D_DCO, "Failed to remove interface %s: %d", ifr.ifr_name, errno); + return ret; + } + + tt->dco.ifname[0] = 0; + + return 0; +} + +int +open_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev) +{ + int ret; + + ret = create_interface(tt, dev); + + if (ret < 0) + { + msg(D_DCO, "Failed to create interface"); + } + + return ret; +} + +void +close_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx) +{ + remove_interface(tt); + close_fd(&tt->dco); +} + +int +dco_swap_keys(dco_context_t *dco, unsigned int peerid) +{ + struct ifdrv drv; + int ret; + + msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peerid); + + bzero(&drv, sizeof(drv)); + snprintf(drv.ifd_name, IFNAMSIZ, "%s", dco->ifname); + drv.ifd_cmd = OVPN_SWAP_KEYS; + + ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv); + if (ret) + { + msg(D_DCO, "Failed to swap keys %d", errno); + return ret; + } + + return 0; +} + +int +dco_del_peer(dco_context_t *dco, unsigned int peerid) +{ + struct ifdrv drv; + int ret; + + bzero(&drv, sizeof(drv)); + snprintf(drv.ifd_name, IFNAMSIZ, "%s", dco->ifname); + drv.ifd_cmd = OVPN_DEL_PEER; + + ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv); + if (ret) + { + msg(D_DCO, "Failed to delete peer %d", errno); + return ret; + } + + return 0; +} + +int +dco_del_key(dco_context_t *dco, unsigned int peerid, + dco_key_slot_t slot) +{ + struct ifdrv drv; + nvlist_t *nvl; + int ret; + + msg(D_DCO, "%s: peer-id %d, slot %d", __func__, peerid, slot); + + nvl = nvlist_create(0); + nvlist_add_number(nvl, "slot", slot); + nvlist_add_number(nvl, "peerid", peerid); + + bzero(&drv, sizeof(drv)); + snprintf(drv.ifd_name, IFNAMSIZ, "%s", dco->ifname); + drv.ifd_cmd = OVPN_DEL_KEY; + drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len); + + ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv); + free(drv.ifd_data); + nvlist_destroy(nvl); + if (ret) + { + msg(D_DCO, "Failed to delete key %d", errno); + return ret; + } + + return 0; +} + +static nvlist_t * +key_to_nvlist(const uint8_t *key, const uint8_t *implicit_iv, const char *ciphername) +{ + nvlist_t *nvl; + size_t key_len; + + nvl = nvlist_create(0); + + nvlist_add_string(nvl, "cipher", ciphername); + + if (strcmp(ciphername, "none") != 0) + { + key_len = cipher_kt_key_size(ciphername); + + nvlist_add_binary(nvl, "key", key, key_len); + nvlist_add_binary(nvl, "iv", implicit_iv, 8); + } + + return (nvl); +} + +static int +start_tun(dco_context_t *dco) +{ + struct ifdrv drv; + int ret; + + bzero(&drv, sizeof(drv)); + snprintf(drv.ifd_name, IFNAMSIZ, "%s", dco->ifname); + drv.ifd_cmd = OVPN_START_VPN; + + ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv); + if (ret) + { + msg(D_DCO, "Failed to start vpn %d", errno); + return ret; + } + + return 0; +} + +int +dco_new_key(dco_context_t *dco, unsigned int peerid, int keyid, + dco_key_slot_t slot, + const uint8_t *encrypt_key, const uint8_t *encrypt_iv, + const uint8_t *decrypt_key, const uint8_t *decrypt_iv, + const char *ciphername) +{ + struct ifdrv drv; + nvlist_t *nvl; + int ret; + + msg(D_DCO_DEBUG, "%s: slot %d, key-id %d, peer-id %d, cipher %s", + __func__, slot, keyid, peerid, ciphername); + + nvl = nvlist_create(0); + + nvlist_add_number(nvl, "slot", slot); + nvlist_add_number(nvl, "keyid", keyid); + nvlist_add_number(nvl, "peerid", peerid); + + nvlist_add_nvlist(nvl, "encrypt", + key_to_nvlist(encrypt_key, encrypt_iv, ciphername)); + nvlist_add_nvlist(nvl, "decrypt", + key_to_nvlist(decrypt_key, decrypt_iv, ciphername)); + + bzero(&drv, sizeof(drv)); + snprintf(drv.ifd_name, IFNAMSIZ, "%s", dco->ifname); + drv.ifd_cmd = OVPN_NEW_KEY; + drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len); + + ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv); + free(drv.ifd_data); + nvlist_destroy(nvl); + if (ret) + { + msg(D_DCO, "Failed to set key %d", errno); + return ret; + } + + return start_tun(dco); +} + +int +dco_set_peer(dco_context_t *dco, unsigned int peerid, + int keepalive_interval, int keepalive_timeout, + int mss) +{ + struct ifdrv drv; + nvlist_t *nvl; + int ret; + + nvl = nvlist_create(0); + nvlist_add_number(nvl, "interval", keepalive_interval); + nvlist_add_number(nvl, "timeout", keepalive_timeout); + + bzero(&drv, sizeof(drv)); + snprintf(drv.ifd_name, IFNAMSIZ, "%s", dco->ifname); + drv.ifd_cmd = OVPN_SET_PEER; + drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len); + + ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv); + free(drv.ifd_data); + nvlist_destroy(nvl); + if (ret) + { + msg(D_DCO, "Failed to set keepalive %d", errno); + return ret; + } + + return 0; +} + +int +dco_do_read(dco_context_t *dco) +{ + struct ifdrv drv; + uint8_t buf[4096]; + nvlist_t *nvl; + const uint8_t *pkt; + size_t pktlen; + int ret; + + /* Flush any pending data from the pipe. */ + (void)read(dco->pipefd[1], buf, sizeof(buf)); + + bzero(&drv, sizeof(drv)); + snprintf(drv.ifd_name, IFNAMSIZ, "%s", dco->ifname); + drv.ifd_cmd = OVPN_GET_PKT; + drv.ifd_data = buf; + drv.ifd_len = sizeof(buf); + + ret = ioctl(dco->fd, SIOCGDRVSPEC, &drv); + if (ret) + { + msg(D_DCO, "Failed to read control packet %d", errno); + return errno; + } + + nvl = nvlist_unpack(buf, drv.ifd_len, 0); + if (!nvl) + { + msg(D_DCO, "Failed to unpack nvlist"); + return EINVAL; + } + + dco->dco_message_peer_id = nvlist_get_number(nvl, "peerid"); + + if (nvlist_exists_binary(nvl, "packet")) { + pkt = nvlist_get_binary(nvl, "packet", &pktlen); + memcpy(BPTR(&dco->dco_packet_in), pkt, pktlen); + dco->dco_packet_in.len = pktlen; + dco->dco_message_type = OVPN_CMD_PACKET; + } else { + dco->dco_del_peer_reason = OVPN_DEL_PEER_REASON_EXPIRED; + dco->dco_message_type = OVPN_CMD_DEL_PEER; + } + + nvlist_destroy(nvl); + + return 0; +} + +int +dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf) +{ + struct ifdrv drv; + nvlist_t *nvl; + int ret; + + nvl = nvlist_create(0); + + nvlist_add_binary(nvl, "packet", BSTR(buf), BLEN(buf)); + nvlist_add_number(nvl, "peerid", peer_id); + + bzero(&drv, sizeof(drv)); + snprintf(drv.ifd_name, IFNAMSIZ, "%s", dco->ifname); + drv.ifd_cmd = OVPN_SEND_PKT; + drv.ifd_data = nvlist_pack(nvl, &drv.ifd_len); + + ret = ioctl(dco->fd, SIOCSDRVSPEC, &drv); + free(drv.ifd_data); + nvlist_destroy(nvl); + if (ret) + { + msg(D_DCO, "Failed to send control packet %d", errno); + return ret; + } + + return BLEN(buf); +} + +bool +dco_available(int msglevel) +{ + struct if_clonereq ifcr; + char *buf = NULL; + int fd; + int ret; + bool available = false; + + /* Attempt to load the module. Ignore errors, because it might already be + * loaded, or built into the kernel. */ + (void)kldload("if_ovpn"); + + fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) + { + return false; + } + + bzero(&ifcr, sizeof(ifcr)); + + ret = ioctl(fd, SIOCIFGCLONERS, &ifcr); + if (ret != 0) + { + goto out; + } + + buf = malloc(ifcr.ifcr_total * IFNAMSIZ); + + ifcr.ifcr_count = ifcr.ifcr_total; + ifcr.ifcr_buffer = buf; + ret = ioctl(fd, SIOCIFGCLONERS, &ifcr); + if (ret != 0) + { + goto out; + } + + for (int i = 0; i < ifcr.ifcr_total; i++) + { + if (strcmp(buf + (i * IFNAMSIZ), "openvpn") == 0) + { + available = true; + goto out; + } + } + +out: + free(buf); + close(fd); + + return available; +} + +void +dco_event_set(dco_context_t *dco, struct event_set *es, void *arg) +{ + struct ifdrv drv; + nvlist_t *nvl; + uint8_t buf[128]; + int ret; + + bzero(&drv, sizeof(drv)); + snprintf(drv.ifd_name, IFNAMSIZ, "%s", dco->ifname); + drv.ifd_cmd = OVPN_POLL_PKT; + drv.ifd_len = sizeof(buf); + drv.ifd_data = buf; + + ret = ioctl(dco->fd, SIOCGDRVSPEC, &drv); + if (ret) + { + msg(D_DCO, "Failed to poll for packets %d", errno); + return; + } + + nvl = nvlist_unpack(buf, drv.ifd_len, 0); + if (!nvl) + { + msg(D_DCO, "Failed to unpack nvlist"); + return; + } + + if (nvlist_get_number(nvl, "pending") > 0) + { + (void)write(dco->pipefd[0], " ", 1); + event_ctl(es, dco->pipefd[1], EVENT_READ, arg); + } + + nvlist_destroy(nvl); +} + +const char * +dco_get_supported_ciphers() +{ + return "none:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305"; +} + +#endif /* defined(ENABLE_DCO) && defined(TARGET_FREEBSD) */ diff --git a/src/openvpn/dco_freebsd.h b/src/openvpn/dco_freebsd.h new file mode 100644 index 00000000..48ceac1d --- /dev/null +++ b/src/openvpn/dco_freebsd.h @@ -0,0 +1,58 @@ +/* + * Interface to FreeBSD dco networking code + * + * Copyright (C) 2022 Rubicon Communications, LLC (Netgate). All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef DCO_FREEBSD_H +#define DCO_FREEBSD_H + +#if defined(ENABLE_DCO) && defined(TARGET_FREEBSD) + +#include +#include "event.h" + +#include "ovpn_dco_freebsd.h" + +typedef enum ovpn_key_slot dco_key_slot_t; +typedef enum ovpn_key_cipher dco_cipher_t; + +enum ovpn_message_type_t { + OVPN_CMD_DEL_PEER, + OVPN_CMD_PACKET, +}; + +enum ovpn_del_reason_t { + OVPN_DEL_PEER_REASON_EXPIRED, + OVPN_DEL_PEER_REASON_TRANSPORT_ERROR, + OVPN_DEL_PEER_REASON_USERSPACE, +}; + +typedef struct dco_context { + int fd; + int pipefd[2]; + + char ifname[IFNAMSIZ]; + + struct buffer dco_packet_in; + + int dco_message_type; + int dco_message_peer_id; + int dco_del_peer_reason; +} dco_context_t; + +#endif /* defined(ENABLE_DCO) && defined(TARGET_FREEBSD) */ +#endif /* ifndef DCO_FREEBSD_H */ diff --git a/src/openvpn/dco_internal.h b/src/openvpn/dco_internal.h index 821aae49..f39c8d17 100644 --- a/src/openvpn/dco_internal.h +++ b/src/openvpn/dco_internal.h @@ -27,6 +27,7 @@ #if defined(ENABLE_DCO) +#include "dco_freebsd.h" #include "dco_linux.h" #include "dco_win.h" diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c index a54ce040..86eb3d98 100644 --- a/src/openvpn/forward.c +++ b/src/openvpn/forward.c @@ -1104,7 +1104,7 @@ process_incoming_link(struct context *c) static void process_incoming_dco(struct context *c) { -#if defined(ENABLE_DCO) && defined(TARGET_LINUX) +#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD)) struct link_socket_info *lsi = get_link_socket_info(c); dco_context_t *dco = &c->c1.tuntap->dco; @@ -1651,7 +1651,7 @@ process_outgoing_link(struct context *c) socks_preprocess_outgoing_link(c, &to_addr, &size_delta); /* Send packet */ -#ifdef TARGET_LINUX +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) if (c->c2.link_socket->info.dco_installed) { size = dco_do_write(&c->c1.tuntap->dco, @@ -1926,7 +1926,7 @@ io_wait_dowork(struct context *c, const unsigned int flags) #ifdef ENABLE_ASYNC_PUSH static int file_shift = FILE_SHIFT; #endif -#ifdef TARGET_LINUX +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) static int dco_shift = DCO_SHIFT; /* Event from DCO linux kernel module */ #endif @@ -2036,7 +2036,7 @@ io_wait_dowork(struct context *c, const unsigned int flags) */ socket_set(c->c2.link_socket, c->c2.event_set, socket, (void *)&socket_shift, NULL); tun_set(c->c1.tuntap, c->c2.event_set, tuntap, (void *)&tun_shift, NULL); -#if defined(TARGET_LINUX) +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) if (socket & EVENT_READ && c->c2.did_open_tun) { dco_event_set(&c->c1.tuntap->dco, c->c2.event_set, (void *)&dco_shift); diff --git a/src/openvpn/mtcp.c b/src/openvpn/mtcp.c index b4445dbe..43573e5b 100644 --- a/src/openvpn/mtcp.c +++ b/src/openvpn/mtcp.c @@ -281,7 +281,7 @@ multi_tcp_wait(const struct context *c, } #endif tun_set(c->c1.tuntap, mtcp->es, EVENT_READ, MTCP_TUN, persistent); -#if defined(TARGET_LINUX) +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) dco_event_set(&c->c1.tuntap->dco, mtcp->es, MTCP_DCO); #endif @@ -400,7 +400,7 @@ multi_tcp_wait_lite(struct multi_context *m, struct multi_instance *mi, const in tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */ -#if defined(TARGET_LINUX) +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) if (mi && mi->context.c2.link_socket->info.dco_installed) { /* If we got a socket that has been handed over to the kernel @@ -763,7 +763,7 @@ multi_tcp_process_io(struct multi_context *m) multi_tcp_action(m, mi, TA_INITIAL, false); } } -#if defined(ENABLE_DCO) && defined(TARGET_LINUX) +#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD)) /* incoming data on DCO? */ else if (e->arg == MTCP_DCO) { diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c index cfc8c975..d4c24526 100644 --- a/src/openvpn/mudp.c +++ b/src/openvpn/mudp.c @@ -227,7 +227,7 @@ multi_process_io_udp(struct multi_context *m) multi_process_file_closed(m, mpp_flags); } #endif -#if defined(ENABLE_DCO) && defined(TARGET_LINUX) +#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD)) else if (status & DCO_READ) { if(!IS_SIG(&m->top)) diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 6cccb24b..84fb0f8d 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -3156,7 +3156,7 @@ multi_signal_instance(struct multi_context *m, struct multi_instance *mi, const } #endif -#if defined(ENABLE_DCO) && defined(TARGET_LINUX) +#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD)) static void process_incoming_dco_packet(struct multi_context *m, struct multi_instance *mi, dco_context_t *dco) { diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 6603f485..ac13dfc1 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -181,7 +181,7 @@ static const char usage_message[] = " does not begin with \"tun\" or \"tap\".\n" "--dev-node node : Explicitly set the device node rather than using\n" " /dev/net/tun, /dev/tun, /dev/tap, etc.\n" -#if defined(ENABLE_DCO) && defined(TARGET_LINUX) +#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD)) "--disable-dco : Do not attempt using Data Channel Offload.\n" #endif "--lladdr hw : Set the link layer address of the tap device.\n" @@ -1604,7 +1604,7 @@ show_settings(const struct options *o) SHOW_STR(dev); SHOW_STR(dev_type); SHOW_STR(dev_node); -#if defined(ENABLE_DCO) && defined(TARGET_LINUX) +#if defined(ENABLE_DCO) && (defined(TARGET_LINUX) || defined(TARGET_FREEBSD)) SHOW_BOOL(tuntap_options.disable_dco); #endif SHOW_STR(lladdr); @@ -3357,7 +3357,7 @@ options_postprocess_mutate(struct options *o) } /* check if any option should force disabling DCO */ -#if defined(TARGET_LINUX) +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) o->tuntap_options.disable_dco = dco_check_option_conflict(D_DCO, o); #endif @@ -5682,7 +5682,7 @@ add_option(struct options *options, #endif else if (streq(p[0], "disable-dco") || streq(p[0], "dco-disable")) { -#if defined(TARGET_LINUX) +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) options->tuntap_options.disable_dco = true; #endif } diff --git a/src/openvpn/ovpn_dco_freebsd.h b/src/openvpn/ovpn_dco_freebsd.h new file mode 100644 index 00000000..abebbb78 --- /dev/null +++ b/src/openvpn/ovpn_dco_freebsd.h @@ -0,0 +1,64 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Rubicon Communications, LLC (Netgate) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _NET_IF_OVPN_H_ +#define _NET_IF_OVPN_H_ + +#include +#include + +/* Maximum size of an ioctl request. */ +#define OVPN_MAX_REQUEST_SIZE 4096 + +enum ovpn_notif_type { + OVPN_NOTIF_DEL_PEER, +}; + +enum ovpn_key_slot { + OVPN_KEY_SLOT_PRIMARY = 0, + OVPN_KEY_SLOT_SECONDARY = 1 +}; + +enum ovpn_key_cipher { + OVPN_CIPHER_ALG_NONE = 0, + OVPN_CIPHER_ALG_AES_GCM = 1, + OVPN_CIPHER_ALG_CHACHA20_POLY1305 = 2 +}; + +#define OVPN_NEW_PEER _IO ('D', 1) +#define OVPN_DEL_PEER _IO ('D', 2) +#define OVPN_GET_STATS _IO ('D', 3) +#define OVPN_NEW_KEY _IO ('D', 4) +#define OVPN_SWAP_KEYS _IO ('D', 5) +#define OVPN_DEL_KEY _IO ('D', 6) +#define OVPN_SET_PEER _IO ('D', 7) +#define OVPN_START_VPN _IO ('D', 8) +#define OVPN_SEND_PKT _IO ('D', 9) +#define OVPN_POLL_PKT _IO ('D', 10) +#define OVPN_GET_PKT _IO ('D', 11) + +#endif diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c index 79ddcec1..0f73a691 100644 --- a/src/openvpn/tun.c +++ b/src/openvpn/tun.c @@ -1796,7 +1796,7 @@ open_tun_generic(const char *dev, const char *dev_type, const char *dev_node, "/dev/%s%d", dev, i); openvpn_snprintf(dynamic_name, sizeof(dynamic_name), "%s%d", dev, i); -#ifdef TARGET_LINUX +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) if (!tt->options.disable_dco) { if (open_tun_dco(tt, ctx, dynamic_name) == 0) @@ -1831,7 +1831,7 @@ open_tun_generic(const char *dev, const char *dev_type, const char *dev_node, } } -#ifdef TARGET_LINUX +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) if (!tt->options.disable_dco) { if (!dynamic_opened) @@ -2011,7 +2011,7 @@ open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun { open_null(tt); } -#if defined(TARGET_LINUX) +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) else if (!tt->options.disable_dco) { open_tun_generic(dev, dev_type, NULL, true, tt, ctx); @@ -2267,7 +2267,7 @@ close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx) net_ctx_reset(ctx); } -#ifdef TARGET_LINUX +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) if (!tt->options.disable_dco) { close_tun_dco(tt, ctx); diff --git a/src/openvpn/tun.h b/src/openvpn/tun.h index fee2c61c..4490ae9a 100644 --- a/src/openvpn/tun.h +++ b/src/openvpn/tun.h @@ -145,6 +145,12 @@ struct tuntap_options { bool disable_dco; }; +#elif defined(TARGET_FREEBSD) + +struct tuntap_options { + bool disable_dco; +}; + #else /* if defined(_WIN32) || defined(TARGET_ANDROID) */ struct tuntap_options {