From patchwork Thu Feb 24 05:55:57 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: 2323 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director15.mail.ord1d.rsapps.net ([172.28.255.1]) by backend41.mail.ord1d.rsapps.net with LMTP id sASpAT+/F2L5AQAAqwncew (envelope-from ) for ; Thu, 24 Feb 2022 12:24:15 -0500 Received: from proxy2.mail.ord1c.rsapps.net ([172.28.255.1]) by director15.mail.ord1d.rsapps.net with LMTP id KPKHAz+/F2LbfgAAIcMcQg (envelope-from ) for ; Thu, 24 Feb 2022 12:24:15 -0500 Received: from smtp37.gate.ord1c ([172.28.255.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy2.mail.ord1c.rsapps.net with LMTPS id KFj6Aj+/F2LTUAAA311kuQ (envelope-from ) for ; Thu, 24 Feb 2022 12:24:15 -0500 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: smtp37.gate.ord1c.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: 95e8d438-9596-11ec-b19c-525400e8d833-1-1 Received: from [216.105.38.7] ([216.105.38.7:60972] helo=lists.sourceforge.net) by smtp37.gate.ord1c.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id D9/95-25020-D3FB7126; Thu, 24 Feb 2022 12:24:14 -0500 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.94.2) (envelope-from ) id 1nNHps-0003Qm-1u; Thu, 24 Feb 2022 17:23:22 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1nNHpq-0003Qb-JZ for openvpn-devel@lists.sourceforge.net; Thu, 24 Feb 2022 17:23:21 +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=jOPRk0/iwi1Fj6tHBtUSeSJN8bYtlhjwcb/ozWGDQpo=; b=br3i80U8RB0wqscPN1AmAXHAYn 2ZVNT1OG+MKeg8GbqMwMO148IZF009t9v49VkntXGNgYKhWSQZBFKZ0EaUYnjkaO4POoyKBARdwdG 17Ago33q2a1+PN1FpPV0XkvyCXpsT5uWZYsrMM9kIr8EDBQCq1wtqPoQkKI8WM7Uni8Y=; 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=jOPRk0/iwi1Fj6tHBtUSeSJN8bYtlhjwcb/ozWGDQpo=; b=meZRcQ+uxOOJiWWm72yusBn6DJ AI/xBNL2TFfF4cFs+joTnKo9WVfHmXXW5ahqAar9A4f3vw2K57FVJlrb2dJ8edx6kylA2T8gXWvjA /I7mc+Ss8rMcVhSk4s4y3KFujD9HoR/LhKzlpdMUVGJcCGJkPj65NyG2CVyDb2Yjf1bg=; Received: from mail-wm1-f53.google.com ([209.85.128.53]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.94.2) id 1nNHpk-0006sQ-0z for openvpn-devel@lists.sourceforge.net; Thu, 24 Feb 2022 17:23:20 +0000 Received: by mail-wm1-f53.google.com with SMTP id c18-20020a7bc852000000b003806ce86c6dso214244wml.5 for ; Thu, 24 Feb 2022 09:23:15 -0800 (PST) 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=jOPRk0/iwi1Fj6tHBtUSeSJN8bYtlhjwcb/ozWGDQpo=; b=a/umaNCLRIrn0WWXnWT9fILvPNYKGii7+ifsTEf+XNGDFP6OgnsDVARfMFDlDWbOvX NMIHnjBpluCwMn9Kr7UTTiOkpCw2sc1KPLFT42DR1io2vCcOwBeSCDYLbCCd15wdTu+b OIHj6q3KvnH44HoftNPJEOOk6frtC2aOVIRNc= 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=jOPRk0/iwi1Fj6tHBtUSeSJN8bYtlhjwcb/ozWGDQpo=; b=3+viX/dlo1jeWvLmecr6RONElGvt9oPJn5hZodOCjBjGYaakgAD/SYSyGr3qVlhIkP B1KYDSRDrebZbkE6sKOlxLGq3qlFoHaZ2tOBmPcdgh3o49X6UCr38O99t4UMdp6PuowN imRBxtykZRmzqRkFNUTKgzj8hbgR0oesCC/qAp2Lk+Yf4u0ECkS4zRMXy2GdVnUWbpMv 6DcHasl65YGqnylH2ycGyvHlFJjHy/Kzvfm5uxBLJQ7g3FNVYEJzTcqiWoCySwArbAwj xvJZP5YQOYhNYpgrJiny/nc1z0oBKAsieFJvOOvNir2C/u4IN8t05KN4mikJeBkwlN1j lfSQ== X-Gm-Message-State: AOAM531f3ps9p9v1bjRNCIubbQ5hIhGV0l5tl1B3zEZQZgCd9zrpzgdw sff7t0inZO+biZW6L7KSYDv2cXGM3QhI9//G X-Google-Smtp-Source: ABdhPJztLJecavx4mI0UpSxVbxCGCBeXaeyHhg3u3qG+6X7XQ4JgKeifFY+7gmkvDRlOFjaYzqCHJg== X-Received: by 2002:a05:600c:2101:b0:381:2275:1d71 with SMTP id u1-20020a05600c210100b0038122751d71mr431616wml.90.1645721761822; Thu, 24 Feb 2022 08:56:01 -0800 (PST) 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 d6sm3198308wrs.85.2022.02.24.08.56.01 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 24 Feb 2022 08:56:01 -0800 (PST) To: openvpn-devel Date: Thu, 24 Feb 2022 17:55:57 +0100 Message-Id: <20220224165557.22060-3-kprovost@netgate.com> X-Mailer: git-send-email 2.35.1 In-Reply-To: <20220224165557.22060-1-kprovost@netgate.com> References: <20220224165557.22060-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 | 15 +- src/openvpn/Makefile.am | 1 + src/openvpn/dco_freebsd.c | 559 +++++++++++++++++++++++++++++++++++++ src/openvpn/dco_freeb [...] 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.128.53 listed in list.dnswl.org] -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.128.53 listed in wl.mailspike.net] 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -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.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.0 T_SCC_BODY_TEXT_LINE No description available. X-Headers-End: 1nNHpk-0006sQ-0z Subject: [Openvpn-devel] [PATCH 2/2] 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 | 15 +- src/openvpn/Makefile.am | 1 + src/openvpn/dco_freebsd.c | 559 +++++++++++++++++++++++++++++++++++++ src/openvpn/dco_freebsd.h | 47 ++++ src/openvpn/dco_internal.h | 1 + src/openvpn/forward.c | 6 +- src/openvpn/mtcp.c | 6 +- src/openvpn/multi.c | 32 ++- src/openvpn/options.c | 4 +- src/openvpn/tun.c | 8 +- src/openvpn/tun.h | 6 + 11 files changed, 671 insertions(+), 14 deletions(-) create mode 100644 src/openvpn/dco_freebsd.c create mode 100644 src/openvpn/dco_freebsd.h diff --git a/configure.ac b/configure.ac index cdfb198e..bf1013cc 100644 --- a/configure.ac +++ b/configure.ac @@ -787,7 +787,20 @@ dnl AC_DEFINE(ENABLE_DCO, 1, [Enable data channel offload for Linux]) AC_MSG_NOTICE([Enabled ovpn-dco support for Linux]) ;; - + *-*-freebsd*) + DCO_CFLAGS="-I${DCO_SOURCEDIR}" + saved_CFLAGS="${CFLAGS}" + CFLAGS="${CFLAGS} ${DCO_CFLAGS}" + AC_CHECK_HEADERS( + [if_ovpn.h], + , + [AC_MSG_ERROR([if_ovpn.h is missing (use DCO_SOURCEDIR to set path to it, CFLAGS=${CFLAGS})])] + ) + CFLAGS=${saved_CFLAGS} + 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 b11c3f9e..c4dbf706 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..8e048d74 --- /dev/null +++ b/src/openvpn/dco_freebsd.c @@ -0,0 +1,559 @@ +/* + * 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_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 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); + 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(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)); + + /* Attempt to load the module. Ignore errors, because it might already be + * loaded, or built into the kernel. */ + (void)kldload("if_ovpn"); + + /* 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 +ovpn_set_peer(dco_context_t *dco, unsigned int peerid, + unsigned int keepalive_interval, unsigned int keepalive_timeout) +{ + 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_TIMEOUT; + 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; + + 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 == NULL) + { + msg(D_DCO, "Failed to unpack nvlist"); + return EINVAL; + } + + dco->dco_message_peer_id = nvlist_get_number(nvl, "peerid"); + + pkt = nvlist_get_binary(nvl, "packet", &pktlen); + memcpy(BPTR(&dco->dco_packet_in), pkt, pktlen); + dco->dco_packet_in.len = pktlen; + + 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; + + 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), "ovpn") == 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 == NULL) + { + 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); +} + +#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..e7aee274 --- /dev/null +++ b/src/openvpn/dco_freebsd.h @@ -0,0 +1,47 @@ +/* + * 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 +#include "event.h" + +#define DCO_SUPPORTED_CIPHERS "none:AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305" + +typedef enum ovpn_key_slot dco_key_slot_t; +typedef enum ovpn_key_cipher dco_cipher_t; + +typedef struct dco_context { + int fd; + int pipefd[2]; + + char ifname[IFNAMSIZ]; + + struct buffer dco_packet_in; + + 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 ed1a1cff..80d6821a 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 c16f32fc..b26c8e60 100644 --- a/src/openvpn/forward.c +++ b/src/openvpn/forward.c @@ -1639,7 +1639,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, @@ -1914,7 +1914,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 @@ -2024,7 +2024,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/multi.c b/src/openvpn/multi.c index 5054c4dd..908e5d35 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -3138,7 +3138,8 @@ multi_signal_instance(struct multi_context *m, struct multi_instance *mi, const } #endif -#if defined(ENABLE_DCO) && defined(TARGET_LINUX) +#if defined(ENABLE_DCO) +#if defined(TARGET_LINUX) || defined(TARGET_FREEBSD) static void process_incoming_dco_packet(struct multi_context *m, struct multi_instance *mi, dco_context_t *dco) { @@ -3166,7 +3167,9 @@ process_incoming_dco_packet(struct multi_context *m, struct multi_instance *mi, done: buf_init(&dco->dco_packet_in, 0); } +#endif +#if defined(TARGET_LINUX) static void process_incoming_del_peer(struct multi_context *m, struct multi_instance *mi, dco_context_t *dco) { @@ -3225,6 +3228,33 @@ multi_process_incoming_dco(struct multi_context *m) dco->dco_message_peer_id = -1; return ret > 0; } + +#elif defined(TARGET_FREEBSD) + +bool +multi_process_incoming_dco(struct multi_context *m) +{ + dco_context_t *dco = &m->top.c1.tuntap->dco; + + struct multi_instance *mi = NULL; + + int ret = dco_do_read(&m->top.c1.tuntap->dco); + int peer_id = dco->dco_message_peer_id; + + if ((peer_id >= 0) && (peer_id < m->max_clients) && (m->instances[peer_id])) + { + mi = m->instances[peer_id]; + process_incoming_dco_packet(m, mi, dco); + } + else + { + msg(D_DCO, "Received packet for peer-id unknown to OpenVPN: %d" , peer_id); + } + + dco->dco_message_peer_id = -1; + return ret > 0; +} +#endif #endif /* diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 816d8db8..ab0147b0 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -3343,7 +3343,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 @@ -5668,7 +5668,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/tun.c b/src/openvpn/tun.c index 9d0be713..c89e635c 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) @@ -1829,7 +1829,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) @@ -2009,7 +2009,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); @@ -2265,7 +2265,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 {