@@ -62,6 +62,13 @@ Optional ciphers in ``--data-ciphers``
Ciphers in ``--data-ciphers`` can now be prefixed with a ``?`` to mark
those as optional and only use them if the SSL library supports them.
+Data channel offloading with ovpn-dco
+ 2.6.0+ implements support for data-channel offloading where the data packets
+ are directly processed and forwarded in kernel space thanks to the ovpn-dco
+ kernel module. The userspace openvpn program acts purely as a control plane
+ application.
+
+
Deprecated features
-------------------
``inetd`` has been removed
new file mode 100644
@@ -0,0 +1,124 @@
+OpenVPN data channel offload
+============================
+2.6.0+ implements support for data-channel offloading where the data packets
+are directly processed and forwarded in kernel space thanks to the ovpn-dco
+kernel module. The userspace openvpn program acts purely as a control plane
+application.
+
+Overview of current release
+---------------------------
+- See the "Limitations by design" and "Current limitations" sections for
+ features that are not and/or will not be supported by OpenVPN + ovpn-dco
+- The dco branch is based on my working branch and contains a number of
+ other patch sets that are not yet merged into the OpenVPN master branch
+ (e.g. --peer-fingerprint).
+
+
+Getting started (Linux)
+-----------------------
+
+- Use a recent Linux kernel. Ubuntu 20.04 (Linux 5.4.0) and Ubuntu 20.10
+ (Linux 5.8.0) are known to work with ovpn-dco.
+
+Get the ovpn-dco module from one these urls and build it:
+
+* https://gitlab.com/openvpn/ovpn-dco
+* https://github.com/OpenVPN/ovpn-dco
+
+e.g.
+
+ git clone https://github.com/OpenVPN/ovpn-dco
+ cd ovpn-dco
+ make
+ sudo make install
+
+If you want to report bugs please ensure to compile ovpn-dco with
+`make DEBUG=1` and include any debug message being printed by the
+kernel (you can view those messages with `dmesg`).
+
+Clone OpenVPN and build dco branch. For example:
+
+ git clone -b dco https://github.com/schwabe/openvpn.git
+ cd openvpn
+ autoreconf -vi
+ ./configure --enable-dco
+ make
+ make install # Or run just src/openvpn/openvpn
+
+If you start openvpn it should automatically detect DCO support and use the
+kernel module. Add the option `--disable-dco` to disable data channel offload
+support. If the configuration contains an option that is incompatible with
+data channel offloading OpenVPN will automatically disable DCO support and
+warn the user.
+
+Should OpenVPN be configured to use a feature that is not supported by ovpn-dco
+or should the ovpn-dco kernel module not be available on the system, you will
+see a message like
+
+ Note: Kernel support for ovpn-dco missing, disabling data channel offload.
+
+in your log.
+
+
+DCO and P2P mode
+----------------
+DCO is also available when running OpenVPN in P2P mode without --pull/--client option.
+The P2P mode is useful for scenarios when the OpenVPN tunnel should not interfere with
+overall routing and behave more like a "dumb" tunnel like GRE.
+
+However, DCO requires DATA_V2 to be enabled this requires P2P with NCP capability, which
+is only available in OpenVPN 2.6 and later.
+
+OpenVPN prints a diagnostic message for the P2P NCP result when running in P2P mode:
+
+ P2P mode NCP negotiation result: TLS_export=1, DATA_v2=1, peer-id 9484735, cipher=AES-256-GCM
+
+Double check that your have `DATA_v2=1` in your output and a supported AEAD cipher
+(AES-GCM or Chacha20-Poly1305).
+
+Routing with ovpn-dco
+---------------------
+The ovpn-dco kernel module implements a more transparent approach to
+configuring routes to clients (aka 'iroutes') and consults the kernel
+routing tables for forwarding decisions.
+
+- Each client has an IPv4 VPN IP and/or an IPv6 assigned to it
+- Additional IP ranges can be routed to a client by adding a route with
+ a client VPN IP as the gateway/nexthop.
+- No internal routing (iroutes) is available. If you need truly internal
+ routes, this can be achieved either with filtering using `iptables` or
+ using `ip rule`.
+
+
+Limitations by design
+----------------------
+- Layer 3 (dev tun only)
+- only AEAD ciphers are supported and currently only
+ Chacha20-Poly1305 and AES-GCM-128/192/256
+- no support for compression or compression framing
+ - see also `--compress migrate` option to move to a setup with compression
+- various features not implemented since have better replacements
+ - --shaper, use tc instead
+ - packet manipulation, use nftables/iptables instead
+- OpenVPN 2.4.0 is the minimum peer version.
+ - older version are missing support for the AEAD ciphers
+- topology subnet is the only supported `--topology` for servers
+- iroute directives install routes on the host operating system, see also
+ routing with ovpn-dco
+
+Current limitations
+-------------------
+- --persistent-tun not tested/supported
+- fallback to non-dco in client mode missing
+- IPv6 mapped IPv4 addresses need Linux 5.12 to properly work
+- Some incompatible options may not properly fallback to non-dco
+- TCP performance with ovpn-dco can still exhibit bad behaviour and drop to a
+ few megabits per seconds.
+- Not all options that should trigger disabling DCO as they are incompatible
+ are currently identified. Some options that do not trigger disabling DCO
+ are ignored while other might yield unexpected results.
+- ovpn-dco currently does not implement RPF checks and will accept any source
+ IP from any client.
+- If a peer VPN IP is outside the default device subnet, the route needs to be
+ added manually.
+- No per client statistics. Only total statistics available on the interface.
@@ -142,6 +142,13 @@ AC_ARG_ENABLE(
[enable_small="no"]
)
+AC_ARG_ENABLE(
+ [dco],
+ [AS_HELP_STRING([--enable-dco], [enable data channel offload support using ovpn-dco kernel module @<:@default=no@:>@])],
+ ,
+ [enable_dco="no"]
+)
+
AC_ARG_ENABLE(
[iproute2],
[AS_HELP_STRING([--enable-iproute2], [enable support for iproute2 @<:@default=no@:>@])],
@@ -766,6 +773,53 @@ PKG_CHECK_MODULES(
[]
)
+
+
+if test "$enable_dco" = "yes"; then
+dnl
+dnl Configure path for the ovpn-dco kernel module source directory.
+dnl
+dnl This is similar to the core librariy, there is an embedded
+dnl version in this tree which will be used by default. The
+dnl git checkout inside the ovpn-dco/ directory is managed via git
+dnl submodule.
+dnl
+AC_ARG_VAR([DCO_SOURCEDIR], [Alternative ovpn-dco kernel module source directory])
+if test -z "${DCO_SOURCEDIR}"; then
+ DCO_SOURCEDIR="${srcdir}/../ovpn-dco"
+fi
+AC_MSG_NOTICE([Using ovpn-dco source directory: ${DCO_SOURCEDIR}])
+AC_SUBST([DCO_SOURCEDIR])
+
+dnl
+dnl Include generic netlink library used to talk to ovpn-dco
+dnl
+ saved_CFLAGS="${CFLAGS}"
+ PKG_CHECK_MODULES(
+ [LIBNL_GENL],
+ [libnl-genl-3.0 >= 3.2.29],
+ [have_libnl="yes"],
+ [AC_MSG_ERROR([libnl-genl-3.0 package not found or too old. Is the development package and pkg-config installed? Must be version 3.4.0 or newer])]
+ )
+
+ DCO_CFLAGS="-I${DCO_SOURCEDIR}/include/uapi ${LIBNL_GENL_CFLAGS}"
+
+ CFLAGS="${CFLAGS} ${DCO_CFLAGS}"
+ AC_CHECK_HEADERS(
+ [linux/ovpn_dco.h],
+ ,
+ [AC_MSG_ERROR([linux/ovpn_dco.h is missing (use DCO_SOURCE to set path to it, CFLAGS=${CFLAGS})])]
+ )
+ CFLAGS=${saved_CFLAGS}
+ OPTIONAL_DCO_CFLAGS="${DCO_CFLAGS}"
+ OPTIONAL_DCO_LIBS="${LIBNL_GENL_LIBS}"
+
+ AC_DEFINE(ENABLE_LINUXDCO, 1, [Enable linux data channel offload])
+ AC_DEFINE(ENABLE_DCO, 1, [Enable shared data channel offload])
+fi
+AM_CONDITIONAL([ENABLE_OVPNDCO], [test "${enable_dco}" = "yes"])
+
+
if test "${with_crypto_library}" = "openssl"; then
AC_ARG_VAR([OPENSSL_CFLAGS], [C compiler flags for OpenSSL])
AC_ARG_VAR([OPENSSL_LIBS], [linker flags for OpenSSL])
@@ -1331,6 +1385,8 @@ AC_SUBST([OPTIONAL_PKCS11_HELPER_CFLAGS])
AC_SUBST([OPTIONAL_PKCS11_HELPER_LIBS])
AC_SUBST([OPTIONAL_INOTIFY_CFLAGS])
AC_SUBST([OPTIONAL_INOTIFY_LIBS])
+AC_SUBST([OPTIONAL_DCO_CFLAGS])
+AC_SUBST([OPTIONAL_DCO_LIBS])
AC_SUBST([PLUGIN_AUTH_PAM_CFLAGS])
AC_SUBST([PLUGIN_AUTH_PAM_LIBS])
@@ -91,3 +91,16 @@ used when debugging or testing out special usage scenarios.
*(Linux only)* Set the TX queue length on the TUN/TAP interface.
Currently defaults to operating system default.
+--disable-dco
+ Disables the opportunistic use the data channel offloading if available.
+ Without this option, OpenVPN will opportunistically use DCO mode if
+ the config options and the running kernel supports using DCO.
+
+ Data channel offload currently requires data-ciphers to only contain
+ AEAD ciphers (AES-GCM and Chacha20-Poly1305) and Linux with the
+ ovpn-dco module.
+
+ Note that some options have no effect or not available when
+ DCO mode is enabled.
+
+ A platforms that do not support DCO ``disable-dco`` has no effect.
@@ -30,6 +30,7 @@ AM_CFLAGS = \
$(OPTIONAL_LZ4_CFLAGS) \
$(OPTIONAL_PKCS11_HELPER_CFLAGS) \
$(OPTIONAL_INOTIFY_CFLAGS) \
+ ${OPTIONAL_DCO_CFLAGS} \
-DPLUGIN_LIBDIR=\"${plugindir}\"
if WIN32
@@ -53,6 +54,7 @@ openvpn_SOURCES = \
crypto.c crypto.h crypto_backend.h \
crypto_openssl.c crypto_openssl.h \
crypto_mbedtls.c crypto_mbedtls.h \
+ dco.c dco.h \
dhcp.c dhcp.h \
env_set.c env_set.h \
errlevel.h \
@@ -85,7 +87,8 @@ openvpn_SOURCES = \
multi.c multi.h \
networking_iproute2.c networking_iproute2.h \
networking_sitnl.c networking_sitnl.h \
- networking.h \
+ networking_linuxdco.c networking_linuxdco.h \
+ networking.h \
ntlm.c ntlm.h \
occ.c occ.h \
openssl_compat.h \
@@ -141,7 +144,8 @@ openvpn_LDADD = \
$(OPTIONAL_SELINUX_LIBS) \
$(OPTIONAL_SYSTEMD_LIBS) \
$(OPTIONAL_DL_LIBS) \
- $(OPTIONAL_INOTIFY_LIBS)
+ $(OPTIONAL_INOTIFY_LIBS) \
+ $(OPTIONAL_DCO_LIBS)
if WIN32
openvpn_SOURCES += openvpn_win32_resources.rc block_dns.c block_dns.h ring_buffer.h
openvpn_LDADD += -lgdi32 -lws2_32 -lwininet -lcrypt32 -liphlpapi -lwinmm -lfwpuclnt -lrpcrt4 -lncrypt -lsetupapi
@@ -826,6 +826,16 @@ init_key_ctx(struct key_ctx *ctx, const struct key *key,
cipher_kt_iv_size(kt->cipher));
warn_insecure_key_type(ciphername, kt->cipher);
}
+
+#if defined(ENABLE_DCO)
+ /* Keep key material for DCO */
+ static_assert(sizeof(key->cipher) == sizeof(ctx->aead_key), "DCO key size mismatch");
+ if (kt->keep_key_data)
+ {
+ memcpy(ctx->aead_key, key->cipher, sizeof(ctx->aead_key));
+ }
+#endif
+
if (kt->digest)
{
ctx->hmac = hmac_ctx_new();
@@ -140,6 +140,9 @@ struct key_type
{
const cipher_kt_t *cipher; /**< Cipher static parameters */
const md_kt_t *digest; /**< Message digest static parameters */
+#if defined(ENABLE_DCO)
+ bool keep_key_data;
+#endif
};
/**
@@ -166,6 +169,9 @@ struct key_ctx
uint8_t implicit_iv[OPENVPN_MAX_IV_LENGTH];
/**< The implicit part of the IV */
size_t implicit_iv_len; /**< The length of implicit_iv */
+#if defined(ENABLE_DCO)
+ uint8_t aead_key[MAX_CIPHER_KEY_LENGTH]; /**< Keeps the key data for use with DCO */
+#endif
};
#define KEY_DIRECTION_BIDIRECTIONAL 0 /* same keys for both directions */
new file mode 100644
@@ -0,0 +1,272 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single TCP/UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2021 Arne Schwabe <arne@rfc2549.org>
+ * Copyright (C) 2021 OpenVPN Inc <sales@openvpn.net>
+ *
+ * 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
+
+
+#include "syshead.h"
+
+#include "errlevel.h"
+#include "networking.h"
+
+#include "multi.h"
+#include "dco.h"
+#include "networking_linuxdco.h"
+
+#if defined(ENABLE_DCO)
+#include "ssl_verify.h"
+
+bool
+dco_do_key_dance(struct tuntap *tt, struct tls_multi *multi, const char *ciphername)
+{
+ struct key_state *primary = tls_select_encryption_key(multi);
+ struct key_state *secondary = NULL;
+
+ for (int i = 0; i < KEY_SCAN_SIZE; ++i)
+ {
+ struct key_state *ks = get_key_scan(multi, i);
+ struct key_ctx_bi *key = &ks->crypto_options.key_ctx_bi;
+
+ if (ks == primary)
+ {
+ continue;
+ }
+
+ if (ks->state >= S_GENERATED_KEYS)
+ {
+ ASSERT(ks->authenticated == KS_AUTH_TRUE);
+ ASSERT(key->initialized);
+
+ secondary = ks;
+ }
+ }
+
+ if (!primary)
+ {
+ if (multi->dco_keys_installed >= 1)
+ {
+ msg(D_DCO, "DCO: No encryption key found. Purging data channel keys");
+ dco_del_key(tt, multi->peer_id, OVPN_KEY_SLOT_PRIMARY);
+ if (multi->dco_keys_installed == 2)
+ {
+ dco_del_key(tt, multi->peer_id, OVPN_KEY_SLOT_SECONDARY);
+ }
+ multi->dco_keys_installed = 2;
+ }
+ return false;
+ }
+
+ /* All keys installed as they should */
+ if (primary->dco_status == DCO_INSTALLED_PRIMARY
+ && (!secondary || secondary->dco_status == DCO_INSTALLED_SECONDARY))
+ {
+ /* Check if we have a previously installed secondary key */
+ if (!secondary && multi->dco_keys_installed == 2)
+ {
+ multi->dco_keys_installed = 1;
+ dco_del_key(tt, multi->peer_id, OVPN_KEY_SLOT_SECONDARY);
+ }
+ return true;
+ }
+
+ int pid = primary->key_id;
+ int sid = secondary ? secondary->key_id : -1;
+
+ msg(D_DCO_DEBUG, "Installing DCO data channel keys for peer %d, "
+ "primary key-id: %d, secondary key-id: %d.",
+ multi->peer_id, pid, sid);
+
+ /* General strategy, get primary key installed correctly first. If that is
+ * okay then check if we need to exchange secondary */
+ if (primary->dco_status != DCO_INSTALLED_PRIMARY)
+ {
+ /* ovpn-win-dco does not like to have the install as secondary and then
+ * swap to primary for the first key .... */
+ if (multi->dco_keys_installed == 0)
+ {
+ dco_new_key(tt, multi->peer_id, OVPN_KEY_SLOT_PRIMARY, primary, ciphername);
+ multi->dco_keys_installed = 1;
+ }
+ else
+ {
+ if (primary->dco_status != DCO_INSTALLED_SECONDARY)
+ {
+ dco_new_key(tt, multi->peer_id, OVPN_KEY_SLOT_SECONDARY, primary, ciphername);
+ }
+ dco_swap_keys(tt, multi->peer_id);
+ }
+ primary->dco_status = DCO_INSTALLED_PRIMARY;
+
+ /* if the secondary was installed as primary before the swap demoted
+ * it to secondary */
+ if (secondary && secondary->dco_status == DCO_INSTALLED_PRIMARY)
+ {
+ secondary->dco_status = DCO_INSTALLED_SECONDARY;
+ multi->dco_keys_installed = 2;
+ }
+ }
+
+ /* The primary key is now the correct key but the secondary key might
+ * already a new key that will be later promoted to primary key and we
+ * need to install the key */
+ if (secondary && secondary->dco_status != DCO_INSTALLED_SECONDARY)
+ {
+ dco_new_key(tt, multi->peer_id, OVPN_KEY_SLOT_SECONDARY, secondary, ciphername);
+ secondary->dco_status = DCO_INSTALLED_SECONDARY;
+ multi->dco_keys_installed = 2;
+ }
+ /* delete an expired key */
+ if (!secondary && multi->dco_keys_installed == 2)
+ {
+ dco_del_key(tt, multi->peer_id, OVPN_KEY_SLOT_SECONDARY);
+ multi->dco_keys_installed = 1;
+ }
+
+ /* All keys that we have not installed are set to NOT installed */
+ for (int i = 0; i < KEY_SCAN_SIZE; ++i)
+ {
+ struct key_state *ks = get_key_scan(multi, i);
+ if (ks != primary && ks != secondary)
+ {
+ ks->dco_status = DCO_NOT_INSTALLED;
+ }
+ }
+ return true;
+}
+
+int
+get_dco_cipher(const char *cipher)
+{
+ if (streq(cipher, "AES-256-GCM") || streq(cipher, "AES-128-GCM") ||
+ streq(cipher, "AES-192-GCM"))
+ return OVPN_CIPHER_ALG_AES_GCM;
+ else if (streq(cipher, "CHACHA20-POLY1305"))
+ {
+ return OVPN_CIPHER_ALG_CHACHA20_POLY1305;
+ }
+ else if (strcmp(cipher, "none") == 0)
+ {
+ return OVPN_CIPHER_ALG_NONE;
+ }
+ else
+ {
+ return -ENOTSUP;
+ }
+}
+#endif
+
+/* These methods are currently Linux specified but likely to be used any platform that implements Server side DCO */
+#if defined(ENABLE_LINUXDCO)
+
+void
+dco_install_iroute(struct multi_context *m, struct multi_instance *mi,
+ struct mroute_addr *addr, bool primary)
+{
+ if (!dco_enabled(&m->top.options))
+ {
+ return;
+ }
+
+ if (primary)
+ {
+ /* We do not want to install IP -> IP dev ovpn-dco0 */
+ return;
+ }
+
+ int addrtype = (addr->type & MR_ADDR_MASK);
+
+ /* If we do not have local IP addr to install, skip the route */
+ if ((addrtype == MR_ADDR_IPV6 && !mi->context.c2.push_ifconfig_ipv6_defined)
+ || (addrtype == MR_ADDR_IPV4 && !mi->context.c2.push_ifconfig_defined))
+ {
+ return;
+ }
+
+ struct context *c = &mi->context;
+ const char *dev = c->c1.tuntap->actual_name;
+
+ if (addrtype == MR_ADDR_IPV6)
+ {
+ net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, addr->netbits,
+ &mi->context.c2.push_ifconfig_ipv6_local, dev, 0,
+ DCO_IROUTE_METRIC);
+ }
+ else if (addrtype == MR_ADDR_IPV4)
+ {
+ in_addr_t dest = htonl(addr->v4.addr);
+ net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits,
+ &mi->context.c2.push_ifconfig_local, dev, 0,
+ DCO_IROUTE_METRIC);
+ }
+}
+
+void
+dco_delete_iroutes(struct multi_context *m, struct multi_instance *mi)
+{
+ if (!dco_enabled(&m->top.options))
+ {
+ return;
+ }
+ ASSERT(TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN);
+
+ struct context *c = &mi->context;
+ const char *dev = c->c1.tuntap->actual_name;
+
+ if (mi->context.c2.push_ifconfig_defined)
+ {
+ for (const struct iroute *ir = c->options.iroutes; ir != NULL; ir = ir->next)
+ {
+ net_route_v4_del(&m->top.net_ctx, &ir->network, ir->netbits,
+ &mi->context.c2.push_ifconfig_local, dev,
+ 0, DCO_IROUTE_METRIC);
+ }
+ }
+
+ if (mi->context.c2.push_ifconfig_ipv6_defined)
+ {
+ for (const struct iroute_ipv6 *ir6 = c->options.iroutes_ipv6; ir6 != NULL; ir6 = ir6->next)
+ {
+ net_route_v6_del(&m->top.net_ctx, &ir6->network, ir6->netbits,
+ &mi->context.c2.push_ifconfig_ipv6_local, dev,
+ 0, DCO_IROUTE_METRIC);
+ }
+ }
+}
+#else
+void
+dco_install_iroute(struct multi_context *m, struct multi_instance *mi,
+ struct mroute_addr *addr, bool primary)
+{
+}
+
+void
+dco_delete_iroutes(struct multi_context *m, struct multi_instance *mi)
+{
+}
+#endif
\ No newline at end of file
new file mode 100644
@@ -0,0 +1,118 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single TCP/UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2021 Arne Schwabe <arne@rfc2549.org>
+ * Copyright (C) 2021 OpenVPN Inc <sales@openvpn.net>
+ *
+ * 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_H
+#define DCO_H
+
+/* forward declarations, including multi.h leads to nasty include
+ * order problems */
+struct multi_context;
+struct tls_multi;
+struct multi_instance;
+struct mroute_addr;
+struct tuntap;
+
+#if !defined(ENABLE_DCO)
+/* Define dummy type for dco context if DCO is not enabled */
+typedef void *dco_context_t;
+
+static inline void open_tun_dco(struct tuntap *tt, const char* dev) { ASSERT(false); }
+
+static inline void close_tun_dco(struct tuntap *tt) { ASSERT(false); }
+#else
+#include "networking_linuxdco.h"
+#include "crypto.h"
+
+/* forward declarations */
+struct tuntap;
+struct key_state;
+
+void open_tun_dco(struct tuntap *tt, const char* dev);
+
+void close_tun_dco(struct tuntap *tt);
+
+int dco_new_peer(struct tuntap *tt, unsigned int peerid, int sd,
+ struct sockaddr *localaddr, struct sockaddr *remoteaddr,
+ struct in_addr *remote_in4, struct in6_addr *remote_in6);
+
+
+int ovpn_set_peer(struct tuntap *tt, unsigned int peerid,
+ unsigned int keepalive_interval,
+ unsigned int keepalive_timeout);
+
+int ovpn_do_read_dco(struct dco_context *dco);
+int dco_del_peer(struct tuntap *tt, unsigned int peerid);
+
+int
+dco_del_key(struct tuntap *tt, unsigned int peerid, ovpn_key_slot_t slot);
+
+int
+dco_new_key(struct tuntap *tt, unsigned int peerid, ovpn_key_slot_t slot,
+ struct key_state *ks, const char* ciphername);
+
+int dco_swap_keys(struct tuntap *tt, unsigned int peerid);
+
+/**
+ * Does the assertions that the key material is indeed sane.
+ * @param key the key context to be checked
+ */
+static inline void dco_check_key_ctx(const struct key_ctx_bi *key)
+{
+ ASSERT(key->initialized);
+ /* Double check that we do not have empty keys */
+ const uint8_t empty_key[32] = { 0 };
+ ASSERT(memcmp(key->encrypt.aead_key, empty_key, 32));
+ ASSERT(memcmp(key->decrypt.aead_key, empty_key, 32));
+}
+
+#endif
+
+void
+dco_install_iroute(struct multi_context *m, struct multi_instance *mi,
+ struct mroute_addr *addr, bool primary);
+
+void
+dco_delete_iroutes(struct multi_context *m, struct multi_instance *mi);
+
+
+/**
+ * This function will check if the encryption and decryption keys are installed
+ * to the data channel offload and if not do the necessary steps to ensure that
+ * openvpn and data channel are synced again
+ *
+ * @param tt Tun tap device context
+ * @param multi TLS multi instance
+ * @param ciphername Ciphername to use when installing the keys.
+ * @return
+ */
+bool
+dco_do_key_dance(struct tuntap *tt, struct tls_multi *multi, const char *ciphername);
+
+/**
+ * Translates an OpenVPN Cipher string to a supported cipher enum from DCO
+ * @param cipher OpenVPN cipher string
+ * @return constant that defines the cipher or -ENOTSUP if not supported
+ */
+int get_dco_cipher(const char *cipher);
+#endif
\ No newline at end of file
@@ -91,6 +91,7 @@
#define D_OSBUF LOGLEV(3, 43, 0) /* show socket/tun/tap buffer sizes */
#define D_PS_PROXY LOGLEV(3, 44, 0) /* messages related to --port-share option */
#define D_IFCONFIG LOGLEV(3, 0, 0) /* show ifconfig info (don't mute) */
+#define D_DCO LOGLEV(3, 0, 0) /* show DCO related messages */
#define D_SHOW_PARMS LOGLEV(4, 50, 0) /* show all parameters on program initiation */
#define D_SHOW_OCC LOGLEV(4, 51, 0) /* show options compatibility string */
@@ -113,6 +114,7 @@
#define D_TUN_RW LOGLEV(6, 69, M_DEBUG) /* show TUN/TAP reads/writes */
#define D_TAP_WIN_DEBUG LOGLEV(6, 69, M_DEBUG) /* show TAP-Windows driver debug info */
#define D_CLIENT_NAT LOGLEV(6, 69, M_DEBUG) /* show client NAT debug info */
+#define D_DCO_DEBUG LOGLEV(6, 69, M_DEBUG) /* show DCO related lowlevel debug messages */
#define D_SHOW_KEYS LOGLEV(7, 70, M_DEBUG) /* show data channel encryption keys */
#define D_SHOW_KEY_SOURCE LOGLEV(7, 70, M_DEBUG) /* show data channel key source entropy */
@@ -72,6 +72,9 @@
#define MANAGEMENT_WRITE (1 << (MANAGEMENT_SHIFT + WRITE_SHIFT))
#define FILE_SHIFT 8
#define FILE_CLOSED (1 << (FILE_SHIFT + READ_SHIFT))
+#define DCO_SHIFT 10
+#define DCO_READ (1 << (DCO_SHIFT + READ_SHIFT))
+#define DCO_WRITE (1 << (DCO_SHIFT + WRITE_SHIFT))
/*
* Initialization flags passed to event_set_init
@@ -41,6 +41,7 @@
#include "dhcp.h"
#include "common.h"
#include "ssl_verify.h"
+#include "dco.h"
#include "memdbg.h"
@@ -50,7 +51,6 @@ counter_type link_read_bytes_global; /* GLOBAL */
counter_type link_write_bytes_global; /* GLOBAL */
/* show event wait debugging info */
-
#ifdef ENABLE_DEBUG
static const char *
@@ -139,6 +139,22 @@ context_reschedule_sec(struct context *c, int sec)
c->c2.timeval.tv_usec = 0;
}
}
+#if defined(ENABLE_DCO)
+static void
+check_dco_key_status(struct context *c)
+{
+ /* DCO context is not yet initialised or enabled */
+ if ((!c->c2.did_open_tun && !c->c1.tuntap)|| !dco_enabled(&c->options))
+ {
+ return;
+ }
+
+ struct tls_multi *multi = c->c2.tls_multi;
+
+ dco_do_key_dance(c->c1.tuntap, multi, c->options.ciphername);
+}
+#endif
+
/*
* In TLS mode, let TLS level respond to any control-channel
@@ -181,6 +197,13 @@ check_tls(struct context *c)
}
interval_schedule_wakeup(&c->c2.tmp_int, &wakeup);
+#if defined(ENABLE_DCO)
+ /* Our current code has no good hooks in the TLS machinery to install
+ * the keys to DCO. So we check/install keys after the whole TLS
+ * machinery has been completed
+ */
+ check_dco_key_status(c);
+#endif
if (wakeup)
{
@@ -1084,6 +1107,15 @@ process_incoming_link(struct context *c)
perf_pop();
}
+#if defined(ENABLE_LINUXDCO)
+static void
+process_incoming_dco(struct context *c)
+{
+ msg(M_INFO, __func__);
+ ovpn_do_read_dco(&c->c1.tuntap->dco);
+}
+#endif
+
/*
* Output: c->c2.buf
*/
@@ -1605,9 +1637,17 @@ process_outgoing_link(struct context *c)
socks_preprocess_outgoing_link(c, &to_addr, &size_delta);
/* Send packet */
- size = link_socket_write(c->c2.link_socket,
- &c->c2.to_link,
- to_addr);
+#if defined(ENABLE_LINUXDCO)
+ if (c->c2.link_socket->info.dco_installed)
+ {
+ size = ovpn_do_write_dco(&c->c1.tuntap->dco, c->c2.tls_multi->peer_id, &c->c2.to_link);
+ }
+ else
+#endif
+ {
+ size = link_socket_write(c->c2.link_socket, &c->c2.to_link,
+ to_addr);
+ }
/* Undo effect of prepend */
link_socket_write_post_size_adjust(&size, size_delta, &c->c2.to_link);
@@ -1870,6 +1910,9 @@ io_wait_dowork(struct context *c, const unsigned int flags)
#ifdef ENABLE_ASYNC_PUSH
static int file_shift = FILE_SHIFT;
#endif
+#ifdef ENABLE_LINUXDCO
+ static int dco_shift = 10; /* Event from DCO module */
+#endif
/*
* Decide what kind of events we want to wait for.
@@ -1977,6 +2020,12 @@ 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(ENABLE_LINUXDCO)
+ if (socket & EVENT_READ && c->c2.did_open_tun)
+ {
+ dco_event_set(&c->c1.tuntap->dco, c->c2.event_set, (void *) &dco_shift);
+ }
+#endif
#ifdef ENABLE_MANAGEMENT
if (management)
@@ -2099,4 +2148,13 @@ process_io(struct context *c)
process_incoming_tun(c);
}
}
+#if defined(ENABLE_LINUXDCO)
+ else if (status & DCO_READ)
+ {
+ if(!IS_SIG(c))
+ {
+ process_incoming_dco(c);
+ }
+ }
+#endif
}
@@ -53,6 +53,7 @@
#include "tls_crypt.h"
#include "forward.h"
#include "auth_token.h"
+#include "dco.h"
#include "memdbg.h"
@@ -1367,15 +1368,25 @@ do_init_timers(struct context *c, bool deferred)
}
/* initialize pings */
-
- if (c->options.ping_send_timeout)
+#if defined(ENABLE_DCO)
+ if (dco_enabled(&c->options))
{
- event_timeout_init(&c->c2.ping_send_interval, c->options.ping_send_timeout, 0);
+ /* The DCO kernel module will send the pings instead of user space */
+ event_timeout_clear(&c->c2.ping_rec_interval);
+ event_timeout_clear(&c->c2.ping_send_interval);
}
-
- if (c->options.ping_rec_timeout)
+ else
+#endif
{
- event_timeout_init(&c->c2.ping_rec_interval, c->options.ping_rec_timeout, now);
+ if (c->options.ping_send_timeout)
+ {
+ event_timeout_init(&c->c2.ping_send_interval, c->options.ping_send_timeout, 0);
+ }
+
+ if (c->options.ping_rec_timeout)
+ {
+ event_timeout_init(&c->c2.ping_rec_interval, c->options.ping_rec_timeout, now);
+ }
}
if (!deferred)
@@ -2097,6 +2108,7 @@ tun_abort(void)
* Handle delayed tun/tap interface bringup due to --up-delay or --pull
*/
+
/**
* Helper for do_up(). Take two option hashes and return true if they are not
* equal, or either one is all-zeroes.
@@ -2110,6 +2122,85 @@ options_hash_changed_or_zero(const struct sha256_digest *a,
|| !memcmp(a, &zero, sizeof(struct sha256_digest));
}
+#ifdef ENABLE_DCO
+static bool
+p2p_dco_add_new_peer(struct context *c)
+{
+ struct tls_multi *multi = c->c2.tls_multi;
+ struct link_socket *ls = c->c2.link_socket;
+
+ if (!dco_enabled(&c->options))
+ {
+ return true;
+ }
+
+ struct in6_addr remote_ip6 = { 0 };
+ struct in_addr remote_ip4 = { 0 };
+
+ struct in6_addr *remote_addr6 = NULL;
+ struct in_addr *remote_addr4 = NULL;
+
+ const char* gw = NULL;
+ /* In client mode if a P2P style topology is used we assume the
+ * remote-gateway is the IP of the peer */
+ if (c->options.topology == TOP_NET30 || c->options.topology == TOP_P2P)
+ {
+ gw = c->options.ifconfig_remote_netmask;
+ }
+ if (c->options.route_default_gateway)
+ {
+ gw = c->options.route_default_gateway;
+ }
+
+ /* These inet_pton conversion are fatal since options.c already implements
+ * checks to have only valid addresses when setting the options */
+ if (c->options.ifconfig_ipv6_remote)
+ {
+ if (inet_pton(AF_INET6, c->options.ifconfig_ipv6_remote, &remote_ip6) != 1)
+ {
+ msg(M_FATAL,
+ "DCO peer init: problem converting IPv6 ifconfig remote address %s to binary",
+ c->options.ifconfig_ipv6_remote);
+ }
+ remote_addr6 = &remote_ip6;
+ }
+
+ if (gw)
+ {
+ if (inet_pton(AF_INET, gw, &remote_ip4) != 1)
+ {
+ msg(M_FATAL, "DCO peer init: problem converting IPv4 ifconfig gateway address %s to binary", gw);
+ }
+ remote_addr4 = &remote_ip4;
+ }
+ else if (c->options.ifconfig_local)
+ {
+ msg(M_INFO, "DCO peer init: Need a peer VPN addresss to setup IPv4 (set --route-gateway)");
+ }
+
+ if (!c->c2.link_socket->info.dco_installed)
+ {
+ ASSERT(ls->info.connection_established);
+
+ struct sockaddr *remoteaddr = &ls->info.lsa->actual.dest.addr.sa;
+
+ dco_new_peer(c->c1.tuntap, multi->peer_id, c->c2.link_socket->sd, NULL,
+ remoteaddr, remote_addr4, remote_addr6);
+
+ c->c2.tls_multi->dco_peer_added = true;
+ c->c2.link_socket->info.dco_installed = true;
+ }
+
+ if (c->options.ping_send_timeout)
+ {
+ ovpn_set_peer(c->c1.tuntap, multi->peer_id, c->options.ping_send_timeout,
+ c->options.ping_rec_timeout);
+ }
+
+ return true;
+}
+#endif
+
bool
do_up(struct context *c, bool pulled_options, unsigned int option_types_found)
{
@@ -2161,6 +2252,11 @@ do_up(struct context *c, bool pulled_options, unsigned int option_types_found)
if (c->c2.did_open_tun)
{
+ /* If we are in DCO mode we need to set the new peer options now */
+#if defined(ENABLE_DCO)
+ p2p_dco_add_new_peer(c);
+#endif
+
c->c1.pulled_options_digest_save = c->c2.pulled_options_digest;
/* if --route-delay was specified, start timer */
@@ -2288,6 +2384,18 @@ do_deferred_p2p_ncp(struct context *c)
return true;
}
+
+static bool check_dco_pull_options(struct options *o)
+{
+ if (!o->use_peer_id)
+ {
+ msg(D_TLS_ERRORS, "OPTIONS IMPORT: Server did not request DATA_V2 packet "
+ "format required for data channel offloading");
+ return false;
+ }
+ return true;
+}
+
/*
* Handle non-tun-related pulled options.
*/
@@ -2400,8 +2508,17 @@ do_deferred_options(struct context *c, const unsigned int found)
msg(D_TLS_ERRORS, "OPTIONS ERROR: failed to import crypto options");
return false;
}
- }
+ /* Check if the pushed options are compatible with DCO if we have DCO
+ * enabled */
+ if (dco_enabled(&c->options) && !check_dco_pull_options(&c->options))
+ {
+ msg(D_TLS_ERRORS, "OPTIONS ERROR: pushed options are incompatible with "
+ "data channel offloading. Use --disable-dco to connect"
+ "to this server");
+ return false;
+ }
+ }
return true;
}
@@ -4332,6 +4449,24 @@ sig:
close_context(c, -1, flags);
return;
}
+#if defined(ENABLE_LINUXDCO)
+static void remove_dco_peer(struct context *c)
+{
+ if (!dco_enabled(&c->options))
+ {
+ return;
+ }
+ if (c->c1.tuntap && c->c2.tls_multi && c->c2.tls_multi->dco_peer_added)
+ {
+ c->c2.tls_multi->dco_peer_added = false;
+ dco_del_peer(c->c1.tuntap, c->c2.tls_multi->peer_id);
+ }
+}
+#else
+static void remove_dco_peer(struct context *c)
+{
+}
+#endif
/*
* Close a tunnel instance.
@@ -4358,6 +4493,10 @@ close_instance(struct context *c)
/* free buffers */
do_close_free_buf(c);
+ /* close peer for DCO if enabled, needs peer-id so must be done before
+ * closing TLS contexts */
+ remove_dco_peer(c);
+
/* close TLS */
do_close_tls(c);
@@ -30,7 +30,7 @@
* Baseline maximum number of events
* to wait for.
*/
-#define BASE_N_EVENTS 4
+#define BASE_N_EVENTS 5
void context_clear(struct context *c);
@@ -61,6 +61,7 @@
#define MTCP_SIG ((void *)3) /* Only on Windows */
#define MTCP_MANAGEMENT ((void *)4)
#define MTCP_FILE_CLOSE_WRITE ((void *)5)
+#define MTCP_DCO ((void *)6)
#define MTCP_N ((void *)16) /* upper bound on MTCP_x */
@@ -123,6 +124,8 @@ multi_create_instance_tcp(struct multi_context *m)
struct hash *hash = m->hash;
mi = multi_create_instance(m, NULL);
+ multi_assign_peer_id(m, mi);
+
if (mi)
{
struct hash_element *he;
@@ -236,6 +239,7 @@ multi_tcp_dereference_instance(struct multi_tcp *mtcp, struct multi_instance *mi
if (ls && mi->socket_set_called)
{
event_del(mtcp->es, socket_event_handle(ls));
+ mi->socket_set_called = false;
}
mtcp->n_esr = 0;
}
@@ -277,6 +281,9 @@ multi_tcp_wait(const struct context *c,
}
#endif
tun_set(c->c1.tuntap, mtcp->es, EVENT_READ, MTCP_TUN, persistent);
+#if defined(ENABLE_LINUXDCO)
+ dco_event_set(&c->c1.tuntap->dco, mtcp->es, MTCP_DCO);
+#endif
#ifdef ENABLE_MANAGEMENT
if (management)
@@ -393,6 +400,20 @@ multi_tcp_wait_lite(struct multi_context *m, struct multi_instance *mi, const in
tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */
+#if defined(ENABLE_LINUXDCO)
+ if (mi && mi->context.c2.link_socket->info.dco_installed)
+ {
+ /* If we got a socket that has been handed over to the kernel
+ * we must not call the normal socket function to figure out
+ * if it is readable or writable */
+ /* Assert that we only have the DCO exptected flags */
+ ASSERT(action & (TA_SOCKET_READ | TA_SOCKET_WRITE));
+
+ /* We are always ready! */
+ return action;
+ }
+#endif
+
switch (action)
{
case TA_TUN_READ:
@@ -516,7 +537,10 @@ multi_tcp_dispatch(struct multi_context *m, struct multi_instance *mi, const int
case TA_INITIAL:
ASSERT(mi);
- multi_tcp_set_global_rw_flags(m, mi);
+ if (!mi->context.c2.link_socket->info.dco_installed)
+ {
+ multi_tcp_set_global_rw_flags(m, mi);
+ }
multi_process_post(m, mi, mpp_flags);
break;
@@ -566,7 +590,10 @@ multi_tcp_post(struct multi_context *m, struct multi_instance *mi, const int act
}
else
{
- multi_tcp_set_global_rw_flags(m, mi);
+ if (!c->c2.link_socket->info.dco_installed)
+ {
+ multi_tcp_set_global_rw_flags(m, mi);
+ }
}
break;
@@ -623,23 +650,22 @@ multi_tcp_action(struct multi_context *m, struct multi_instance *mi, int action,
/*
* Dispatch the action
*/
- {
- struct multi_instance *touched = multi_tcp_dispatch(m, mi, action);
+ struct multi_instance *touched = multi_tcp_dispatch(m, mi, action);
- /*
- * Signal received or TCP connection
- * reset by peer?
- */
- if (touched && IS_SIG(&touched->context))
+ /*
+ * Signal received or TCP connection
+ * reset by peer?
+ */
+ if (touched && IS_SIG(&touched->context))
+ {
+ if (mi == touched)
{
- if (mi == touched)
- {
- mi = NULL;
- }
- multi_close_instance_on_signal(m, touched);
+ mi = NULL;
}
+ multi_close_instance_on_signal(m, touched);
}
+
/*
* If dispatch produced any pending output
* for a particular instance, point to
@@ -737,6 +763,13 @@ multi_tcp_process_io(struct multi_context *m)
multi_tcp_action(m, mi, TA_INITIAL, false);
}
}
+#if defined(ENABLE_LINUXDCO)
+ /* incoming data on DCO? */
+ else if (e->arg == MTCP_DCO)
+ {
+ multi_process_incoming_dco(m);
+ }
+#endif
/* signal received? */
else if (e->arg == MTCP_SIG)
{
@@ -227,6 +227,19 @@ multi_process_io_udp(struct multi_context *m)
multi_process_file_closed(m, mpp_flags);
}
#endif
+#ifdef ENABLE_LINUXDCO
+ else if (status & DCO_READ)
+ {
+ if(!IS_SIG(&m->top))
+ {
+ bool ret = true;
+ while (ret)
+ {
+ ret = multi_process_incoming_dco(m);
+ }
+ }
+ }
+#endif
}
/*
@@ -51,6 +51,10 @@
#include "crypto_backend.h"
#include "ssl_util.h"
+#include "dco.h"
+
+static void multi_signal_instance(struct multi_context *m, struct multi_instance *mi, const int sig);
+
/*#define MULTI_DEBUG_EVENT_LOOP*/
@@ -519,6 +523,9 @@ multi_del_iroutes(struct multi_context *m,
{
const struct iroute *ir;
const struct iroute_ipv6 *ir6;
+
+ dco_delete_iroutes(m, mi);
+
if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
{
for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next)
@@ -1224,16 +1231,17 @@ multi_learn_in_addr_t(struct multi_context *m,
addr.netbits = (uint8_t) netbits;
}
- {
- struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);
+ struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);
#ifdef ENABLE_MANAGEMENT
- if (management && owner)
- {
- management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
- }
-#endif
- return owner;
+ if (management && owner)
+ {
+ management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
}
+#endif
+
+ dco_install_iroute(m, mi, &addr, primary);
+
+ return owner;
}
static struct multi_instance *
@@ -1257,16 +1265,20 @@ multi_learn_in6_addr(struct multi_context *m,
mroute_addr_mask_host_bits( &addr );
}
- {
- struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);
+ struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);
#ifdef ENABLE_MANAGEMENT
- if (management && owner)
- {
- management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
- }
+ if (management && owner)
+ {
+ management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
+ }
#endif
- return owner;
+ if (!primary)
+ {
+ /* We do not want to install IP -> IP dev ovpn-dco0 */
+ dco_install_iroute(m, mi, &addr, primary);
}
+
+ return owner;
}
/*
@@ -1764,6 +1776,15 @@ multi_client_set_protocol_options(struct context *c)
{
tls_multi->use_peer_id = true;
}
+ else if (dco_enabled(o))
+ {
+ msg(M_INFO, "Client does not support DATA_V2. Data channel offloaing "
+ "requires DATA_V2. Dropping client.");
+ auth_set_client_reason(tls_multi, "Data channel negotiation "
+ "failed (missing DATA_V2)");
+ return false;
+ }
+
if (proto & IV_PROTO_REQUEST_PUSH)
{
c->c2.push_request_received = true;
@@ -1775,7 +1796,6 @@ multi_client_set_protocol_options(struct context *c)
o->data_channel_crypto_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT;
}
#endif
-
/* Select cipher if client supports Negotiable Crypto Parameters */
/* if we have already created our key, we cannot *change* our own
@@ -2271,12 +2291,127 @@ cleanup:
return ret;
}
+#if defined(ENABLE_LINUXDCO)
+#include <linux/ovpn_dco.h>
+
+static struct sockaddr *
+multi_dco_get_localaddr(struct multi_context *m, struct multi_instance *mi,
+ struct gc_arena *gc)
+{
+ struct context *c = &mi->context;
+
+ if ((c->options.sockflags & SF_USE_IP_PKTINFO))
+ {
+ struct link_socket_actual *actual = &c->c2.link_socket_info->lsa->actual;
+
+ switch(actual->dest.addr.sa.sa_family)
+ {
+ case AF_INET:
+ {
+ struct sockaddr_in *sock_in4;
+ ALLOC_OBJ_CLEAR_GC(sock_in4, struct sockaddr_in, gc);
+ sock_in4->sin_addr = actual->pi.in4.ipi_addr;
+ sock_in4->sin_family = AF_INET;
+ return (struct sockaddr *) sock_in4;
+ }
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sock_in6;
+ ALLOC_OBJ_CLEAR_GC(sock_in6, struct sockaddr_in6, gc);
+ sock_in6->sin6_addr = actual->pi.in6.ipi6_addr;
+ sock_in6->sin6_family = AF_INET6;
+ return (struct sockaddr *) sock_in6;
+ }
+ default:
+ ASSERT(0);
+ }
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+static bool
+multi_dco_add_new_peer(struct multi_context *m, struct multi_instance *mi)
+{
+ struct context *c = &mi->context;
+
+ int peer_id = mi->context.c2.tls_multi->peer_id;
+ int sd = c->c2.link_socket->sd;
+ struct sockaddr *remoteaddr;
+ struct sockaddr *local;
+
+ if (c->mode == CM_CHILD_TCP)
+ {
+ /* the remote address will be inferred from the TCP socket endpoint */
+ remoteaddr = NULL;
+ }
+ else
+ {
+ ASSERT(c->c2.link_socket_info->connection_established);
+ remoteaddr = &c->c2.link_socket_info->lsa->actual.dest.addr.sa;
+ }
+
+ struct in_addr remote_ip4 = { 0 };
+ struct in6_addr *remote_addr6 = NULL;
+ struct in_addr *remote_addr4 = NULL;
+ struct gc_arena gc = gc_new();
+
+ /* In server mode we need to fetch the remote addresses from the push config */
+ if (c->c2.push_ifconfig_defined)
+ {
+ remote_ip4.s_addr = htonl(c->c2.push_ifconfig_local);
+ remote_addr4 = &remote_ip4;
+ }
+ if (c->c2.push_ifconfig_ipv6_defined)
+ {
+ remote_addr6 = &c->c2.push_ifconfig_ipv6_local;
+ }
+
+ local = multi_dco_get_localaddr(m, mi, &gc);
+
+ if (dco_new_peer(c->c1.tuntap, peer_id, sd, local, remoteaddr,
+ remote_addr4, remote_addr6) != 0)
+ {
+ gc_free(&gc);
+ return false;
+ }
+
+ c->c2.tls_multi->dco_peer_added = true;
+
+ dco_do_key_dance(c->c1.tuntap, c->c2.tls_multi, c->options.ciphername);
+
+ if (c->options.ping_send_timeout)
+ {
+ ovpn_set_peer(c->c1.tuntap, peer_id, c->options.ping_send_timeout,
+ c->options.ping_rec_timeout);
+ }
+
+ if (c->mode == CM_CHILD_TCP )
+ {
+ multi_tcp_dereference_instance(m->mtcp, mi);
+ if (close(sd))
+ {
+ msg(D_DCO|M_ERRNO, "error closing TCP socket after DCO handover");
+ }
+ c->c2.link_socket->info.dco_installed = true;
+ c->c2.link_socket->sd = SOCKET_UNDEFINED;
+ }
+
+ gc_free(&gc);
+
+ return true;
+}
+#endif
+
/**
* Generates the data channel keys
*/
static bool
-multi_client_generate_tls_keys(struct context *c)
+multi_client_generate_tls_keys(struct multi_context *m, struct multi_instance *mi)
{
+ struct context *c = &mi->context;
struct frame *frame_fragment = NULL;
#ifdef ENABLE_FRAGMENT
if (c->options.ce.fragment)
@@ -2293,6 +2428,13 @@ multi_client_generate_tls_keys(struct context *c)
return false;
}
+#if defined(ENABLE_LINUXDCO)
+ if (dco_enabled(&c->options) && !multi_dco_add_new_peer(m, mi))
+ {
+ return false;
+ }
+#endif
+
return true;
}
@@ -2399,7 +2541,7 @@ multi_client_connect_late_setup(struct multi_context *m,
}
/* Generate data channel keys only if setting protocol options
* has not failed */
- else if (!multi_client_generate_tls_keys(&mi->context))
+ else if (!multi_client_generate_tls_keys(m, mi))
{
mi->context.c2.tls_multi->multi_state = CAS_FAILED;
}
@@ -2659,6 +2801,14 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
(*cur_handler_index)++;
}
+ /* Check if we have forbidding options in the current mode */
+ if (dco_enabled(&mi->context.options)
+ && check_option_conflict_dco(D_MULTI_ERRORS, &mi->context.options))
+ {
+ msg(D_MULTI_ERRORS, "MULTI: client has been rejected due to incompatible options");
+ cc_succeeded = false;
+ }
+
if (cc_succeeded)
{
multi_client_connect_late_setup(m, mi, *option_types_found);
@@ -3077,6 +3227,98 @@ done:
gc_free(&gc);
}
+
+#if defined(ENABLE_LINUXDCO)
+
+static void
+process_incoming_dco_packet(struct multi_context *m, struct multi_instance *mi, dco_context_t *dco)
+{
+ struct buffer orig_buf = mi->context.c2.buf;
+ int peer_id = dco->dco_meesage_peer_id;
+
+ mi->context.c2.buf = dco->dco_packet_in;
+
+ multi_process_incoming_link(m, mi, 0);
+
+ mi->context.c2.buf = orig_buf;
+ if (BLEN(&dco->dco_packet_in) < 1)
+ {
+ msg(D_DCO, "Received too short packet for peer %d" , peer_id);
+ goto done;
+ }
+
+ uint8_t *ptr = BPTR(&dco->dco_packet_in);
+ uint8_t op = ptr[0] >> P_OPCODE_SHIFT;
+ if (op == P_DATA_V2 || op == P_DATA_V2)
+ {
+ msg(D_DCO, "DCO: received data channel packet for peer %d" , peer_id);
+ goto done;
+ }
+ done:
+ buf_init(&dco->dco_packet_in, 0);
+}
+
+static void
+process_incoming_del_peer(struct multi_context *m, struct multi_instance *mi, dco_context_t *dco)
+{
+ const char *reason = "(unknown reason by ovpn-dco)";
+ switch (dco->dco_del_peer_reason)
+ {
+ case OVPN_DEL_PEER_REASON_EXPIRED:
+ reason = "ovpn-dco: ping expired";
+ break;
+ case OVPN_DEL_PEER_REASON_TRANSPORT_ERROR:
+ reason = "ovpn-dco: transport error";
+ break;
+ case OVPN_DEL_PEER_REASON_USERSPACE:
+ /* This very likely ourselves but might be another process, so
+ * still process it */
+ reason = "ovpn-dco: userspace request";
+ break;
+ }
+
+ /* When kernel already deleted the peer, the socket is no longer
+ * installed and we don't need to cleanup the state in the kernel */
+ mi->context.c2.tls_multi->dco_peer_added = false;
+ mi->context.sig->signal_text = reason;
+ multi_signal_instance(m, mi, SIGTERM);
+
+}
+
+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 = ovpn_do_read_dco(&m->top.c1.tuntap->dco);
+
+ int peer_id = dco->dco_meesage_peer_id;
+
+ if ((peer_id >= 0) && (peer_id < m->max_clients) && (m->instances[peer_id]))
+ {
+ mi = m->instances[peer_id];
+ if (dco->dco_message_type == OVPN_CMD_PACKET)
+ {
+ process_incoming_dco_packet(m, mi, dco);
+ }
+ else if (dco->dco_message_type == OVPN_CMD_DEL_PEER)
+ {
+ process_incoming_del_peer(m, mi, dco);
+ }
+ }
+ else
+ {
+ msg(D_DCO, "Received packet for peer-id unknown to OpenVPN: %d" , peer_id);
+ }
+
+ dco->dco_message_type = 0;
+ dco->dco_meesage_peer_id = -1;
+ return ret > 0;
+}
+#endif
+
/*
* Process packets in the TCP/UDP socket -> TUN/TAP interface direction,
* i.e. client -> server direction.
@@ -98,7 +98,9 @@ struct client_connect_defer_state
* server-mode.
*/
struct multi_instance {
- struct schedule_entry se; /* this must be the first element of the structure */
+ struct schedule_entry se; /* this must be the first element of the structure,
+ * We cast between this and schedule_entry so the
+ * beginning of the struct must be identical */
struct gc_arena gc;
bool halt;
int refcount;
@@ -308,6 +310,8 @@ void multi_process_float(struct multi_context *m, struct multi_instance *mi);
bool multi_process_post(struct multi_context *m, struct multi_instance *mi, const unsigned int flags);
+bool multi_process_incoming_dco(struct multi_context *m);
+
/**************************************************************************/
/**
* Demultiplex and process a packet received over the external network
@@ -27,6 +27,7 @@ struct context;
#ifdef ENABLE_SITNL
#include "networking_sitnl.h"
+#include "dco.h"
#elif ENABLE_IPROUTE
#include "networking_iproute2.h"
#else
new file mode 100644
@@ -0,0 +1,848 @@
+/*
+ * Interface to linux dco networking code
+ *
+ * Copyright (C) 2020 Antonio Quartulli <a@unstable.cc>
+ * Copyright (C) 2020 Arne Schwabe <arne@rfc2549.org>
+ * Copyright (C) 2020 OpenVPN Inc <sales@openvpn.net>
+ *
+ * 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_LINUXDCO)
+
+#include "syshead.h"
+
+#include "errlevel.h"
+#include "buffer.h"
+#include "networking.h"
+
+#include "socket.h"
+#include "tun.h"
+#include "ssl.h"
+#include "fdmisc.h"
+#include "ssl_verify.h"
+
+#include <linux/ovpn_dco.h>
+
+#include <netlink/socket.h>
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/family.h>
+#include <netlink/genl/ctrl.h>
+
+
+/* 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)
+
+static int ovpn_get_mcast_id(dco_context_t *dco);
+
+void dco_check_key_ctx(const struct key_ctx_bi *key);
+
+typedef int (*ovpn_nl_cb)(struct nl_msg *msg, void *arg);
+
+int
+resolve_ovpn_netlink_id(int msglevel)
+{
+ int ret;
+ struct nl_sock* nl_sock = nl_socket_alloc();
+
+ ret = genl_connect(nl_sock);
+ if (ret)
+ {
+ msg(msglevel, "Cannot connect to generic netlink: %s",
+ nl_geterror(ret));
+ goto err_sock;
+ }
+ set_cloexec(nl_socket_get_fd(nl_sock));
+
+ ret = genl_ctrl_resolve(nl_sock, OVPN_NL_NAME);
+ if (ret < 0)
+ {
+ msg(msglevel, "Cannot find ovpn_dco netlink component: %s",
+ nl_geterror(ret));
+ }
+
+err_sock:
+ nl_socket_free(nl_sock);
+ return ret;
+}
+
+static struct nl_msg *ovpn_dco_nlmsg_create(dco_context_t *dco,
+ enum ovpn_nl_commands cmd)
+{
+ struct nl_msg *nl_msg = nlmsg_alloc();
+ if (!nl_msg)
+ {
+ msg(M_ERR, "cannot allocate netlink message");
+ return NULL;
+ }
+
+ genlmsg_put(nl_msg, 0, 0, dco->ovpn_dco_id, 0, 0, cmd, 0);
+ NLA_PUT_U32(nl_msg, OVPN_ATTR_IFINDEX, dco->ifindex);
+
+ return nl_msg;
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ msg(M_INFO, "cannot put into netlink message");
+ return NULL;
+}
+
+static int ovpn_nl_recvmsgs(dco_context_t *dco, const char *prefix)
+{
+ int ret = nl_recvmsgs(dco->nl_sock, dco->nl_cb);
+
+ switch (ret) {
+ case -NLE_INTR:
+ msg(M_WARN, "%s: netlink received interrupt due to signal - ignoring", prefix);
+ break;
+ case -NLE_NOMEM:
+ msg(M_ERR, "%s: netlink out of memory error", prefix);
+ break;
+ case -M_ERR:
+ msg(M_WARN, "%s: netlink reports blocking read - aborting wait", prefix);
+ break;
+ case -NLE_NODEV:
+ msg(M_ERR, "%s: netlink reports device not found:", prefix);
+ break;
+ case -NLE_OBJ_NOTFOUND:
+ msg(M_INFO, "%s: netlink reports object not found, ovpn-dco unloaded?", prefix);
+ break;
+ default:
+ if (ret)
+ {
+ msg(M_NONFATAL|M_ERRNO, "%s: netlink reports error (%d): %s", prefix, ret, nl_geterror(-ret));
+ }
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * Send a preprared netlink message and registers cb as callback if non-null.
+ *
+ * The method will also free nl_msg
+ * @param dco The dco context to use
+ * @param nl_msg the message to use
+ * @param cb An optional callback if the caller expects an answers\
+ * @param prefix A prefix to report in the error message to give the user context
+ * @return status of sending the message
+ */
+static int
+ovpn_nl_msg_send(dco_context_t *dco, struct nl_msg *nl_msg, ovpn_nl_cb cb,
+ const char* prefix)
+{
+ dco->status = 1;
+
+ if (cb)
+ {
+ nl_cb_set(dco->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, cb, dco);
+ }
+ else
+ {
+ nl_cb_set(dco->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, NULL, dco);
+ }
+
+ nl_send_auto(dco->nl_sock, nl_msg);
+
+ while (dco->status == 1)
+ {
+ ovpn_nl_recvmsgs(dco, prefix);
+ }
+
+ if (dco->status < 0)
+ {
+ msg(M_INFO, "%s: failed to send netlink message: %s (%d)",
+ prefix, strerror(-dco->status), dco->status);
+ }
+
+ return dco->status;
+}
+
+struct sockaddr *
+mapped_v4_to_v6(struct sockaddr *sock, struct gc_arena *gc)
+{
+ struct sockaddr_in6 *sock6 = ((struct sockaddr_in6 *)sock);
+ if (sock->sa_family == AF_INET6 &&
+ memcmp(&sock6->sin6_addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", 12)==0)
+ {
+
+ struct sockaddr_in* sock4;
+ ALLOC_OBJ_CLEAR_GC(sock4, struct sockaddr_in, gc);
+ memcpy (&sock4->sin_addr, sock6->sin6_addr.s6_addr +12, 4);
+ sock4->sin_port = sock6->sin6_port;
+ sock4->sin_family = AF_INET;
+ return (struct sockaddr *) sock4;
+ }
+ return sock;
+}
+
+int dco_new_peer(struct tuntap *tt, unsigned int peerid, int sd,
+ struct sockaddr *localaddr, struct sockaddr *remoteaddr,
+ struct in_addr *remote_in4, struct in6_addr *remote_in6)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d, fd %d", __func__, peerid, sd);
+
+ struct gc_arena gc = gc_new();
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_NEW_PEER);
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_NEW_PEER);
+ int ret = -EMSGSIZE;
+
+ NLA_PUT_U32(nl_msg, OVPN_NEW_PEER_ATTR_PEER_ID, peerid);
+ NLA_PUT_U32(nl_msg, OVPN_NEW_PEER_ATTR_SOCKET, sd);
+
+ /* Set the remote endpoint if defined (for UDP) */
+ if (remoteaddr)
+ {
+ remoteaddr = mapped_v4_to_v6(remoteaddr, &gc);
+ int alen = af_addr_size(remoteaddr->sa_family);
+
+ NLA_PUT(nl_msg, OVPN_NEW_PEER_ATTR_SOCKADDR_REMOTE, alen, remoteaddr);
+ }
+
+ if (localaddr)
+ {
+ localaddr = mapped_v4_to_v6(localaddr, &gc);
+ if (localaddr->sa_family == AF_INET)
+ {
+ NLA_PUT(nl_msg, OVPN_NEW_PEER_ATTR_LOCAL_IP, sizeof(struct in_addr),
+ &((struct sockaddr_in *)localaddr)->sin_addr);
+ }
+ else if (localaddr->sa_family == AF_INET6)
+ {
+ NLA_PUT(nl_msg, OVPN_NEW_PEER_ATTR_LOCAL_IP, sizeof(struct in6_addr),
+ &((struct sockaddr_in6 *)localaddr)->sin6_addr);
+ }
+ }
+
+ /* Set the primary VPN IP addresses of the peer */
+ if (remote_in4)
+ {
+ NLA_PUT_U32(nl_msg, OVPN_NEW_PEER_ATTR_IPV4, remote_in4->s_addr);
+ }
+ if (remote_in6)
+ {
+ NLA_PUT(nl_msg, OVPN_NEW_PEER_ATTR_IPV6, sizeof(struct in6_addr),
+ remote_in6);
+ }
+ nla_nest_end(nl_msg, attr);
+
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ gc_free(&gc);
+ return ret;
+}
+
+static int
+ovpn_nl_cb_finish(struct nl_msg (*msg)__attribute__((unused)), void *arg)
+{
+ int *status = arg;
+
+ *status = 0;
+ return NL_SKIP;
+}
+
+static int
+ovpn_nl_cb_error(struct sockaddr_nl (*nla)__attribute__((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]));
+ msg(M_WARN, "kernel error: %*s\n", len,
+ (char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]));
+ }
+
+ return NL_STOP;
+}
+
+static void
+ovpn_dco_init_netlink(dco_context_t *dco)
+{
+ dco->ovpn_dco_id = resolve_ovpn_netlink_id(M_ERR);
+
+ dco->nl_sock = nl_socket_alloc();
+
+
+ if (!dco->nl_sock)
+ {
+ msg(M_ERR, "Cannot create netlink socket");
+ }
+
+ /* TODO: Why are we setting this buffer size? */
+ nl_socket_set_buffer_size(dco->nl_sock, 8192, 8192);
+
+ int ret = genl_connect(dco->nl_sock);
+ if (ret)
+ {
+ msg(M_ERR, "Cannot connect to generic netlink: %s",
+ nl_geterror(ret));
+ }
+
+ set_cloexec(nl_socket_get_fd(dco->nl_sock));
+
+ dco->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
+ if (!dco->nl_cb)
+ {
+ msg(M_ERR, "failed to allocate netlink callback");
+ }
+
+ nl_socket_set_cb(dco->nl_sock, dco->nl_cb);
+
+ nl_cb_err(dco->nl_cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &dco->status);
+ nl_cb_set(dco->nl_cb, NL_CB_FINISH, NL_CB_CUSTOM, ovpn_nl_cb_finish,
+ &dco->status);
+ nl_cb_set(dco->nl_cb, NL_CB_ACK, NL_CB_CUSTOM, ovpn_nl_cb_finish,
+ &dco->status);
+
+ /* The async PACKET messages confuse libnl and it will drop them with
+ * wrong sequence numbers (NLE_SEQ_MISMATCH), so disable libnl's sequence
+ * number check */
+ nl_socket_disable_seq_check(dco->nl_sock);
+}
+
+static void
+ovpn_dco_uninit_netlink(dco_context_t *dco)
+{
+ nl_socket_free(dco->nl_sock);
+ dco->nl_sock = NULL;
+
+ /* Decrease reference count */
+ nl_cb_put(dco->nl_cb);
+
+ memset(dco, 0, sizeof(*dco));
+}
+
+static void ovpn_dco_register(dco_context_t *dco)
+{
+ msg(D_DCO_DEBUG, __func__);
+ ovpn_get_mcast_id(dco);
+
+ if (dco->ovpn_dco_mcast_id < 0)
+ {
+ msg(M_ERR, "cannot get mcast group: %s", nl_geterror(dco->ovpn_dco_mcast_id));
+ }
+
+ /* Register for Ovpn dco specific messages */
+ int ret = nl_socket_add_membership(dco->nl_sock, dco->ovpn_dco_mcast_id);
+ if (ret)
+ {
+ msg(M_ERR, "%s: failed to join groups: %d", __func__, ret);
+ }
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_REGISTER_PACKET);
+ if (!nl_msg)
+ {
+ msg(M_ERR, "%s: cannot allocate message to register for control packets",
+ __func__);
+ }
+
+ ret = ovpn_nl_msg_send(dco, nl_msg, NULL, __func__);
+ if (ret)
+ {
+ msg(M_ERR, "%s: failed to register for control packets: %d", __func__,
+ ret);
+ }
+ nlmsg_free(nl_msg);
+}
+
+void
+open_tun_dco(struct tuntap *tt, const char* dev)
+{
+ msg(D_DCO_DEBUG, __func__);
+ ASSERT(tt->type == DEV_TYPE_TUN);
+
+ ovpn_dco_init_netlink(&tt->dco);
+
+ if (!strcmp(dev, "tun"))
+ {
+ /* If no specific name has been requested use an auto-assigned name */
+ dev = NULL;
+ }
+
+ tt->dco.ifindex = net_iface_new(dev, "ovpn-dco");
+
+ char if_name[IFNAMSIZ];
+ if(!if_indextoname(tt->dco.ifindex, if_name))
+ {
+ msg(M_ERR|M_ERRNO, "Cannot resolve interface name for dco interface: ");
+ }
+ tt->actual_name = string_alloc(if_name, NULL);
+ uint8_t *dcobuf = malloc(65536);
+ buf_set_write(&tt->dco.dco_packet_in, dcobuf, 65536);
+ tt->dco.dco_meesage_peer_id = -1;
+
+ ovpn_dco_register(&tt->dco);
+}
+
+void
+close_tun_dco(struct tuntap *tt)
+{
+ msg(D_DCO_DEBUG, __func__);
+
+ net_iface_del_index(tt->dco.ifindex);
+ ovpn_dco_uninit_netlink(&tt->dco);
+ free(tt->dco.dco_packet_in.data);
+}
+
+int dco_swap_keys(struct tuntap *tt, unsigned int peerid)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peerid);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_SWAP_KEYS);
+ if (!nl_msg)
+ return -ENOMEM;
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_SWAP_KEYS);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_SWAP_KEYS_ATTR_PEER_ID, peerid);
+ nla_nest_end(nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+
+int dco_del_peer(struct tuntap *tt, unsigned int peerid)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peerid);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_DEL_PEER);
+ if (!nl_msg)
+ return -ENOMEM;
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_DEL_PEER);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_DEL_PEER_ATTR_PEER_ID, peerid);
+ nla_nest_end(nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+
+int
+dco_del_key(struct tuntap *tt, unsigned int peerid, ovpn_key_slot_t slot)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d, slot %d", __func__, peerid, slot);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_DEL_KEY);
+ if (!nl_msg)
+ return -ENOMEM;
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_DEL_KEY);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_DEL_KEY_ATTR_PEER_ID, peerid);
+ NLA_PUT_U8(nl_msg, OVPN_DEL_KEY_ATTR_KEY_SLOT, slot);
+ nla_nest_end(nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+int
+dco_new_key(struct tuntap *tt, unsigned int peerid, ovpn_key_slot_t slot,
+ struct key_state *ks, const char* ciphername)
+{
+ msg(D_DCO_DEBUG, "%s: slot %d, key-id %d, peer-id %d, cipher %s",
+ __func__, slot, ks->key_id, peerid, ciphername);
+ const int nonce_len = 8;
+
+ struct key_ctx_bi *key = &ks->crypto_options.key_ctx_bi;
+
+ dco_check_key_ctx(key);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_NEW_KEY);
+ if (!nl_msg)
+ return -ENOMEM;
+
+ int dco_cipher = get_dco_cipher(ciphername);
+ ASSERT(dco_cipher >= 0);
+
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_NEW_KEY);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_NEW_KEY_ATTR_PEER_ID, peerid);
+ NLA_PUT_U8(nl_msg, OVPN_NEW_KEY_ATTR_KEY_SLOT, slot);
+ NLA_PUT_U8(nl_msg, OVPN_NEW_KEY_ATTR_KEY_ID, ks->key_id);
+
+ NLA_PUT_U16(nl_msg, OVPN_NEW_KEY_ATTR_CIPHER_ALG, dco_cipher);
+
+ if (dco_cipher != OVPN_CIPHER_ALG_NONE)
+ {
+ size_t key_len = cipher_kt_key_size(cipher_kt_get(ciphername));
+ struct nlattr *key_enc = nla_nest_start(nl_msg, OVPN_NEW_KEY_ATTR_ENCRYPT_KEY);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, key_len, key->encrypt.aead_key);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_NONCE_TAIL, nonce_len,
+ key->encrypt.implicit_iv);
+ nla_nest_end(nl_msg, key_enc);
+
+ struct nlattr *key_dec = nla_nest_start(nl_msg, OVPN_NEW_KEY_ATTR_DECRYPT_KEY);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, key_len, key->decrypt.aead_key);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_NONCE_TAIL, nonce_len,
+ key->decrypt.implicit_iv);
+ nla_nest_end(nl_msg, key_dec);
+ }
+ else
+ {
+ /* Check that --auth is disabled. Normally, we would catch this
+ * inconsistency earlier but since the "none" is only for debug and
+ * requires manual editing of DCO_SUPPORTED_CIPHERS, it should be fine
+ * to abort here */
+ if (key->encrypt.hmac != NULL)
+ {
+ msg(M_FATAL, "FATAL: DCO with cipher none requires --auth none");
+ }
+ /* ovpn-dco needs empty encrypt/decrypt keys with cipher none */
+ struct nlattr *key_enc = nla_nest_start(nl_msg,
+ OVPN_NEW_KEY_ATTR_ENCRYPT_KEY);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, 16,
+ key->encrypt.aead_key);
+ nla_nest_end(nl_msg, key_enc);
+
+ struct nlattr *key_dec = nla_nest_start(nl_msg,
+ OVPN_NEW_KEY_ATTR_DECRYPT_KEY);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, 16,
+ key->decrypt.aead_key);
+ nla_nest_end(nl_msg, key_dec);
+ }
+ nla_nest_end(nl_msg, attr);
+
+ secure_memzero(key->encrypt.aead_key, sizeof (key->encrypt.aead_key));
+ secure_memzero(key->decrypt.aead_key, sizeof (key->decrypt.aead_key));
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+int ovpn_set_peer(struct tuntap *tt, unsigned int peerid,
+ unsigned int keepalive_interval,
+ unsigned int keepalive_timeout)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d, keepalive %d/%d", __func__, peerid,
+ keepalive_interval, keepalive_timeout);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_SET_PEER);
+ if (!nl_msg)
+ {
+ return -ENOMEM;
+ }
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_SET_PEER);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_SET_PEER_ATTR_PEER_ID, peerid);
+ NLA_PUT_U32(nl_msg, OVPN_SET_PEER_ATTR_KEEPALIVE_INTERVAL,
+ keepalive_interval);
+ NLA_PUT_U32(nl_msg, OVPN_SET_PEER_ATTR_KEEPALIVE_TIMEOUT,
+ keepalive_timeout);
+ nla_nest_end(nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+static int mcast_family_handler(struct nl_msg *msg, void *arg)
+{
+ dco_context_t *dco = arg;
+ struct nlattr *tb[CTRL_ATTR_MAX + 1];
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+
+ nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL);
+
+ if (!tb[CTRL_ATTR_MCAST_GROUPS])
+ return NL_SKIP;
+
+ struct nlattr *mcgrp;
+ int rem_mcgrp;
+ 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]),
+ OVPN_NL_MULTICAST_GROUP_PEERS,
+ nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])) != 0)
+ {
+ continue;
+ }
+ dco->ovpn_dco_mcast_id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
+ break;
+ }
+
+ return NL_SKIP;
+}
+/**
+ * Lookup the multicast id for OpenVPN. This method and its help method currently
+ * hardcode the lookup to OVPN_NL_NAME and OVPN_NL_MULTICAST_GROUP_PEERS but
+ * extended in the future if we need to lookup more than one mcast id.
+ */
+static int
+ovpn_get_mcast_id(dco_context_t *dco)
+{
+ dco->ovpn_dco_mcast_id = -ENOENT;
+
+ /* Even though 'nlctrl' is a constant, there seem to be no library
+ * provided define for it */
+ int ctrlid = genl_ctrl_resolve(dco->nl_sock, "nlctrl");
+
+ struct nl_msg *nl_msg = nlmsg_alloc();
+ if (!nl_msg)
+ {
+ return -ENOMEM;
+ }
+
+ genlmsg_put(nl_msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
+
+ int ret = -EMSGSIZE;
+ NLA_PUT_STRING(nl_msg, CTRL_ATTR_FAMILY_NAME, OVPN_NL_NAME);
+
+ ret = ovpn_nl_msg_send(dco, nl_msg, mcast_family_handler, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+static int ovpn_handle_msg(struct nl_msg *msg, void *arg)
+{
+ dco_context_t *dco = arg;
+
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *attrs[OVPN_ATTR_MAX + 1];
+ struct nlmsghdr *nlh = nlmsg_hdr(msg);
+
+ if (!genlmsg_valid_hdr(nlh, 0))
+ {
+ msg(D_DCO, "ovpn-dco: invalid header");
+ return NL_SKIP;
+ }
+
+ if (nla_parse(attrs, OVPN_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL))
+ {
+ msg(D_DCO, "received bogus data from ovpn-dco");
+ return NL_SKIP;
+ }
+
+ if (!attrs[OVPN_ATTR_IFINDEX])
+ {
+ msg(D_DCO, "ovpn-dco: Received message without ifindex");
+ return NL_SKIP;
+ }
+
+ uint32_t ifindex = nla_get_u32(attrs[OVPN_ATTR_IFINDEX]);
+ if (ifindex != dco->ifindex)
+ {
+ msg(D_DCO, "ovpn-dco: received message type %d with mismatched ifindex %d\n",
+ gnlh->cmd, ifindex);
+ return NL_SKIP;
+ }
+
+ switch (gnlh->cmd) {
+ case OVPN_CMD_DEL_PEER:
+ {
+ if (!attrs[OVPN_ATTR_DEL_PEER])
+ {
+ msg(D_DCO, "ovpn-dco: no attributes in OVPN_DEL_PEER message");
+ return NL_SKIP;
+ }
+
+ struct nlattr *dp_attrs[OVPN_DEL_PEER_ATTR_MAX + 1];
+ if (nla_parse_nested(dp_attrs, OVPN_DEL_PEER_ATTR_MAX,
+ attrs[OVPN_ATTR_DEL_PEER], NULL))
+ {
+ msg(D_DCO, "received bogus del peer packet data from ovpn-dco");
+ return NL_SKIP;
+ }
+
+ if (!dp_attrs[OVPN_DEL_PEER_ATTR_REASON])
+ {
+ msg(D_DCO, "ovpn-dco: no reason in DEL_PEER message");
+ return NL_SKIP;
+ }
+ if (!dp_attrs[OVPN_DEL_PEER_ATTR_PEER_ID])
+ {
+ msg(D_DCO, "ovpn-dco: no peer-id in DEL_PEER message");
+ return NL_SKIP;
+ }
+ int reason = nla_get_u8(dp_attrs[OVPN_DEL_PEER_ATTR_REASON]);
+ unsigned int peerid = nla_get_u32(dp_attrs[OVPN_DEL_PEER_ATTR_PEER_ID]);
+
+ msg(D_DCO_DEBUG, "ovpn-dco: received CMD_DEL_PEER, ifindex: %d, peer-id %d, reason: %d",
+ ifindex, peerid, reason);
+ dco->dco_meesage_peer_id = peerid;
+ dco->dco_del_peer_reason = reason;
+ dco->dco_message_type = OVPN_CMD_DEL_PEER;
+
+ break;
+ }
+ case OVPN_CMD_PACKET:
+ {
+ if (!attrs[OVPN_ATTR_PACKET])
+ {
+ msg(D_DCO, "ovpn-dco: no packet in OVPN_CMD_PACKET message");
+ return NL_SKIP;
+ }
+ struct nlattr *pkt_attrs[OVPN_PACKET_ATTR_MAX + 1];
+
+ if (nla_parse_nested(pkt_attrs, OVPN_PACKET_ATTR_MAX,
+ attrs[OVPN_ATTR_PACKET], NULL))
+ {
+ msg(D_DCO, "received bogus cmd packet data from ovpn-dco");
+ return NL_SKIP;
+ }
+ if (!pkt_attrs[OVPN_PACKET_ATTR_PEER_ID])
+ {
+ msg(D_DCO, "ovpn-dco: Received OVPN_CMD_PACKET message without peer id");
+ return NL_SKIP;
+ }
+ if (!pkt_attrs[OVPN_PACKET_ATTR_PACKET])
+ {
+ msg(D_DCO, "ovpn-dco: Received OVPN_CMD_PACKET message without packet");
+ return NL_SKIP;
+ }
+
+ unsigned int peerid = nla_get_u32(pkt_attrs[OVPN_PACKET_ATTR_PEER_ID]);
+
+ uint8_t *data = nla_data(pkt_attrs[OVPN_PACKET_ATTR_PACKET]);
+ int len = nla_len(pkt_attrs[OVPN_PACKET_ATTR_PACKET]);
+
+ msg(D_DCO_DEBUG, "ovpn-dco: received OVPN_PACKET_ATTR_PACKET, ifindex: %d peer-id: %d, len %d",
+ ifindex, peerid, len);
+ if (BLEN(&dco->dco_packet_in) > 0)
+ {
+ msg(D_DCO, "DCO packet buffer still full?!");
+ return NL_SKIP;
+ }
+ buf_init(&dco->dco_packet_in, 0);
+ buf_write(&dco->dco_packet_in, data, len);
+ dco->dco_meesage_peer_id = peerid;
+ dco->dco_message_type = OVPN_CMD_PACKET;
+ break;
+ }
+ default:
+ msg(D_DCO, "ovpn-dco: received unknown command: %d", gnlh->cmd);
+ dco->dco_message_type = 0;
+ return NL_SKIP;
+ }
+
+ return NL_OK;
+}
+
+int
+ovpn_do_read_dco(struct dco_context *dco)
+{
+ msg(D_DCO_DEBUG, __func__);
+ nl_cb_set(dco->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, ovpn_handle_msg, dco);
+
+ return ovpn_nl_recvmsgs(dco, __func__);
+}
+
+int
+ovpn_do_write_dco(dco_context_t *dco, int peer_id, struct buffer *buf)
+{
+ packet_size_type len = BLEN(buf);
+ dmsg(D_STREAM_DEBUG, "DCO: WRITE %d offset=%d", (int)len, buf->offset);
+
+ msg(D_DCO_DEBUG, "%s: peer-id %d, len=%d", __func__, peer_id, len);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_PACKET);
+
+ if (!nl_msg)
+ {
+ return -ENOMEM;
+ }
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_PACKET);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_PACKET_ATTR_PEER_ID, peer_id);
+ NLA_PUT(nl_msg, OVPN_PACKET_ATTR_PACKET, len, BSTR(buf));
+ nla_nest_end(nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(dco, nl_msg, NULL, __func__);
+ if (ret)
+ {
+ goto nla_put_failure;
+ }
+
+ /* return the length of the written data in case of success */
+ ret = len;
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+#endif
new file mode 100644
@@ -0,0 +1,85 @@
+/*
+ * Interface to linux dco networking code
+ *
+ * Copyright (C) 2020 Antonio Quartulli <a@unstable.cc>
+ * Copyright (C) 2020 Arne Schwabe <arne@rfc2549.org>
+ * Copyright (C) 2020 OpenVPN Inc <sales@openvpn.net>
+ *
+ * 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 NETWORKING_LINUXDCO_H
+#define NETWORKING_LINUXDCO_H
+#if defined(ENABLE_LINUXDCO)
+
+#include <netlink/socket.h>
+#include <netlink/netlink.h>
+#include <linux/ovpn_dco.h>
+
+typedef enum ovpn_key_slot ovpn_key_slot_t;
+
+#include "event.h"
+
+#define DCO_IROUTE_METRIC 100
+
+#define DCO_SUPPORTED_CIPHERS "AES-128-GCM:AES-256-GCM:AES-192-GCM:CHACHA20-POLY1305"
+
+struct dco_context {
+ struct nl_sock *nl_sock;
+ struct nl_cb *nl_cb;
+ int status;
+
+ int ovpn_dco_id;
+ int ovpn_dco_mcast_id;
+
+ unsigned int ifindex;
+
+ struct buffer dco_packet_in;
+
+ int dco_message_type;
+ int dco_meesage_peer_id;
+ int dco_del_peer_reason;
+
+};
+
+typedef struct dco_context dco_context_t;
+
+
+/**
+ * @brief resolves the netlink ID for ovpn-dco
+ *
+ * This function queries the kernel via a netlink socket
+ * whether the ovpn-dco netlink namespace is available
+ *
+ * This function can be used to determine if the kernel
+ * support DCO offloading.
+ *
+ * @return ID on success, negative error code on error
+ */
+int
+resolve_ovpn_netlink_id(int msglevel);
+
+static inline void
+dco_event_set(struct dco_context *dco,
+ struct event_set *es,
+ void *arg) {
+ if (dco && dco->nl_sock) {
+ event_ctl(es, nl_socket_get_fd(dco->nl_sock), EVENT_READ, arg);
+ }
+}
+
+int ovpn_do_write_dco(dco_context_t *dco, int peer_id, struct buffer *buf);
+
+#endif
+#endif
@@ -253,8 +253,9 @@
<ClCompile Include="crypto.c" />
<ClCompile Include="crypto_openssl.c" />
<ClCompile Include="cryptoapi.c" />
- <ClCompile Include="env_set.c" />
+ <ClCompile Include="dco.c" />
<ClCompile Include="dhcp.c" />
+ <ClCompile Include="env_set.c" />
<ClCompile Include="error.c" />
<ClCompile Include="event.c" />
<ClCompile Include="fdmisc.c" />
@@ -335,6 +336,7 @@
<ClInclude Include="crypto_backend.h" />
<ClInclude Include="crypto_openssl.h" />
<ClInclude Include="cryptoapi.h" />
+ <ClInclude Include="dco.h" />
<ClInclude Include="dhcp.h" />
<ClInclude Include="env_set.h" />
<ClInclude Include="errlevel.h" />
@@ -39,6 +39,9 @@
<ClCompile Include="dhcp.c">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="dco.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="error.c">
<Filter>Source Files</Filter>
</ClCompile>
@@ -287,6 +290,9 @@
<ClInclude Include="cryptoapi.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="dco.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="dhcp.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -60,6 +60,7 @@
#include "forward.h"
#include "ssl_verify.h"
#include "platform.h"
+#include "dco.h"
#include <ctype.h>
#include "memdbg.h"
@@ -105,6 +106,9 @@ const char title_string[] =
#endif
#endif
" [AEAD]"
+#ifdef ENABLE_LINUXDCO
+ " [DCO]"
+#endif
" built on " __DATE__
;
@@ -3220,6 +3224,133 @@ options_set_backwards_compatible_options(struct options *o)
#endif
}
+#if defined(ENABLE_DCO)
+static bool
+check_option_conflict_dco_ce(const struct connection_entry *ce, int msglevel)
+{
+ if (ce->fragment)
+ {
+ msg(msglevel, "Note: --fragment disables data channel offload.");
+ return true;
+ }
+
+ if (ce->http_proxy_options)
+ {
+ msg(msglevel, "Note: --http-proxy disables data channel offload.");
+ return true;
+ }
+
+ if (ce->socks_proxy_server)
+ {
+ msg(msglevel, "Note --socks-proxy disable data channel offload.");
+ return true;
+ }
+
+ return false;
+}
+
+#if defined(ENABLE_LINUXDCO)
+static bool check_option_conflict_dco_platform(int msglevel, const struct options *o)
+{
+ if (resolve_ovpn_netlink_id(D_TUNTAP_INFO) < 0)
+ {
+ msg(D_TUNTAP_INFO, "Note: Kernel support for ovpn-dco missing, disabling "
+ "data channel offload.");
+ return true;
+ }
+ return false;
+}
+#endif
+
+bool check_option_conflict_dco(int msglevel, const struct options *o)
+{
+ if (o->tuntap_options.disable_dco)
+ {
+ /* already disabled by --disable-dco, no need to print warnings */
+ return true;
+ }
+
+ if (check_option_conflict_dco_platform(msglevel, o))
+ {
+ return true;
+ }
+
+ if (dev_type_enum(o->dev, o->dev_type) != DEV_TYPE_TUN)
+ {
+ msg(msglevel, "Note: dev-type not tun, disabling data channel offload.");
+ return true;
+ }
+
+ /* At this point the ciphers have already been normalised */
+ if (o->enable_ncp_fallback
+ && !tls_item_in_cipher_list(o->ciphername, DCO_SUPPORTED_CIPHERS))
+ {
+ msg(msglevel, "Note: --data-cipher-fallback with cipher '%s' "
+ "disables data channel offload.", o->ciphername);
+ return true;
+ }
+
+ if (o->connection_list)
+ {
+ const struct connection_list *l = o->connection_list;
+ for (int i = 0; i < l->len; ++i)
+ {
+ if (check_option_conflict_dco_ce(l->array[i], msglevel))
+ {
+ return true;
+ }
+ }
+ }
+ else
+ {
+ if (check_option_conflict_dco_ce(&o->ce, msglevel))
+ {
+ return true;
+ }
+ }
+
+ if (o->mode == MODE_SERVER && o->topology != TOP_SUBNET)
+ {
+ msg(msglevel, "Note: NOT using '--topology subnet' disables data channel offload.");
+ return true;
+ }
+
+#ifdef USE_COMP
+ if(o->comp.alg != COMP_ALG_UNDEF)
+ {
+ msg(msglevel, "Note: Using compression disables data channel offload.");
+
+ if (o->mode == MODE_SERVER && !(o->comp.flags & COMP_F_MIGRATE))
+ {
+ /* We can end up here from the multi.c call, only print the
+ * note if it is not already enabled */
+ msg(msglevel, "Consider using the '--compress migrate' option.");
+ }
+ return true;
+ }
+#endif
+
+ struct gc_arena gc = gc_new();
+
+
+ char *tmp_ciphers = string_alloc(o->ncp_ciphers, &gc);
+ const char *token;
+ while ((token = strsep(&tmp_ciphers, ":")))
+ {
+ if (!tls_item_in_cipher_list(token, DCO_SUPPORTED_CIPHERS))
+ {
+ msg(msglevel, "Note: cipher '%s' in --data-ciphers is not supported "
+ "by ovpn-dco, disabling data channel offload.", token);
+ gc_free(&gc);
+ return true;
+ }
+ }
+ gc_free(&gc);
+
+ return false;
+}
+#endif /* if defined(TARGET_LINUX) */
+
static void
options_postprocess_mutate(struct options *o)
{
@@ -3306,7 +3437,11 @@ options_postprocess_mutate(struct options *o)
"option set). ");
o->verify_hash_no_ca = true;
}
-
+#if defined(ENABLE_LINUXDCO)
+ o->tuntap_options.disable_dco = check_option_conflict_dco(D_DCO, o);
+#elif defined(TARGET_LINUX)
+ o->tuntap_options.disable_dco = true;
+#endif
/*
* Save certain parms before modifying options during connect, especially
* when using --pull
@@ -5643,6 +5778,12 @@ add_option(struct options *options,
options->windows_driver = parse_windows_driver(p[1], M_FATAL);
}
#endif
+ else if (streq(p[0], "disable-dco") || streq(p[0], "dco-disable"))
+ {
+#if defined(TARGET_LINUX)
+ options->tuntap_options.disable_dco = true;
+#endif
+ }
else if (streq(p[0], "dev-node") && p[1] && !p[2])
{
VERIFY_PERMISSION(OPT_P_GENERAL);
@@ -862,4 +862,30 @@ void options_string_import(struct options *options,
unsigned int *option_types_found,
struct env_set *es);
+/**
+ * Returns whether the current configuration has dco enabled.
+ */
+#ifdef ENABLE_LINUXDCO
+static inline bool
+dco_enabled(struct options *o) { return !o->tuntap_options.disable_dco; }
+
+/**
+ * Checks whether the options struct has any option that is not supported by
+ * our current dco implementation. If so it prints a warning at warning level
+ * for the first conflicting option found and returns false
+ * @param msglevel the msg level to use to print the warnings
+ * @param o the optiions struct that hold the options
+ * @return true if a conflict with dco is detected.
+ */
+bool
+check_option_conflict_dco(int msglevel, const struct options *o);
+#else
+/* Dummy functions to avoid ifdefs in the other code */
+
+static inline bool
+dco_enabled(struct options *o) { return false; }
+
+static inline bool
+check_option_conflict_dco(int msglevel, struct options *o) { return false; }
+#endif
#endif /* ifndef OPTIONS_H */
@@ -120,6 +120,7 @@ struct link_socket_info
sa_family_t af; /* Address family like AF_INET, AF_INET6 or AF_UNSPEC*/
bool bind_ipv6_only;
int mtu_changed; /* Set to true when mtu value is changed */
+ bool dco_installed;
};
/*
@@ -1880,6 +1880,10 @@ tls_session_update_crypto_params_do_work(struct tls_session *session,
init_key_type(&session->opt->key_type, options->ciphername,
options->authname, true, true);
+#if defined(ENABLE_DCO)
+ session->opt->key_type.keep_key_data = dco_enabled(options);
+#endif
+
bool packet_id_long_form = cipher_kt_mode_ofb_cfb(session->opt->key_type.cipher);
session->opt->crypto_flags &= ~(CO_PACKET_ID_LONG_FORM);
if (packet_id_long_form)
@@ -2236,7 +2240,7 @@ push_peer_info(struct buffer *buf, struct tls_session *session)
{
buf_printf(&out, "IV_HWADDR=%s\n", format_hex_ex(rgi.hwaddr, 6, 0, 1, ":", &gc));
}
- buf_printf(&out, "IV_SSL=%s\n", get_ssl_library_version() );
+ buf_printf(&out, "IV_SSL=%s\n", get_ssl_library_version());
#if defined(_WIN32)
buf_printf(&out, "IV_PLAT_VER=%s\n", win32_version_string(&gc, false));
#endif
@@ -167,6 +167,12 @@ enum auth_deferred_result {
ACF_FAILED /**< deferred auth has failed */
};
+enum dco_key_status {
+ DCO_NOT_INSTALLED,
+ DCO_INSTALLED_PRIMARY,
+ DCO_INSTALLED_SECONDARY
+};
+
/**
* Security parameter state of one TLS and data channel %key session.
* @ingroup control_processor
@@ -240,6 +246,8 @@ struct key_state
struct auth_deferred_status plugin_auth;
struct auth_deferred_status script_auth;
+
+ enum dco_key_status dco_status;
};
/** Control channel wrapping (--tls-auth/--tls-crypt) context */
@@ -634,6 +642,11 @@ struct tls_multi
/**< Array of \c tls_session objects
* representing control channel
* sessions with the remote peer. */
+
+ /* Only used when DCO is used to remember how many keys we installed
+ * for this session */
+ int dco_keys_installed;
+ bool dco_peer_added;
};
/** gets an item of \c key_state objects in the
@@ -489,4 +489,4 @@ p2p_mode_ncp(struct tls_multi *multi, struct tls_session *session)
multi->use_peer_id, multi->peer_id, common_cipher);
gc_free(&gc);
-}
\ No newline at end of file
+}
@@ -1957,6 +1957,10 @@ open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
{
open_null(tt);
}
+ else if (!tt->options.disable_dco)
+ {
+ open_tun_dco(tt, dev);
+ }
else
{
/*
@@ -2206,7 +2210,14 @@ close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
net_ctx_reset(ctx);
}
- close_tun_generic(tt);
+ if (!tt->options.disable_dco)
+ {
+ close_tun_dco(tt);
+ }
+ else
+ {
+ close_tun_generic(tt);
+ }
free(tt);
}
@@ -40,6 +40,7 @@
#include "misc.h"
#include "networking.h"
#include "ring_buffer.h"
+#include "networking_linuxdco.h"
#ifdef _WIN32
#define WINTUN_COMPONENT_ID "wintun"
@@ -138,6 +139,7 @@ struct tuntap_options {
struct tuntap_options {
int txqueuelen;
+ bool disable_dco;
};
#else /* if defined(_WIN32) || defined(TARGET_ANDROID) */
@@ -218,6 +220,8 @@ struct tuntap
/* Some TUN/TAP drivers like to be ioctled for mtu
* after open */
int post_open_mtu;
+
+ dco_context_t dco;
};
static inline bool
@@ -1,7 +1,10 @@
#include "config.h"
#include "syshead.h"
+#include "error.h"
#include "networking.h"
+#include "mock_msg.h"
+
static char *iface = "ovpn-dummy0";
@@ -16,8 +19,10 @@ net__iface_up(bool up)
static int
net__iface_new(const char *name, const char* type)
{
- printf("CMD: ip link add %s type %s\n", name, type);
- return net_iface_new(name, type);
+ printf("CMD: ip link add type %s\n", type);
+ int ifidx = net_iface_new(NULL, type);
+ printf("ifindex: %d\n", ifidx);
+ return ifidx;
}
static int