|
From: Antonio Q. <a...@un...> - 2021-12-07 12:12:17
|
From: Arne Schwabe <ar...@rf...>
Implement the data-channel offloading using the ovpn-dco kernel
module. See README.dco.md for more details.
Signed-off-by: Arne Schwabe <ar...@rf...>
Signed-off-by: Antonio Quartulli <a...@un...>
---
Changes.rst | 7 +
README.dco.md | 124 +++
configure.ac | 56 ++
doc/man-sections/advanced-options.rst | 13 +
src/openvpn/Makefile.am | 8 +-
src/openvpn/crypto.c | 10 +
src/openvpn/crypto.h | 6 +
src/openvpn/dco.c | 272 +++++++
src/openvpn/dco.h | 118 +++
src/openvpn/errlevel.h | 2 +
src/openvpn/event.h | 3 +
src/openvpn/forward.c | 66 +-
src/openvpn/init.c | 153 +++-
src/openvpn/init.h | 2 +-
src/openvpn/mtcp.c | 61 +-
src/openvpn/mudp.c | 13 +
src/openvpn/multi.c | 278 ++++++-
src/openvpn/multi.h | 6 +-
src/openvpn/networking.h | 1 +
src/openvpn/networking_linuxdco.c | 848 +++++++++++++++++++++
src/openvpn/networking_linuxdco.h | 85 +++
src/openvpn/openvpn.vcxproj | 4 +-
src/openvpn/openvpn.vcxproj.filters | 6 +
src/openvpn/options.c | 143 +++-
src/openvpn/options.h | 26 +
src/openvpn/socket.h | 1 +
src/openvpn/ssl.c | 6 +-
src/openvpn/ssl_common.h | 13 +
src/openvpn/ssl_ncp.c | 2 +-
src/openvpn/tun.c | 13 +-
src/openvpn/tun.h | 4 +
tests/unit_tests/openvpn/test_networking.c | 9 +-
32 files changed, 2305 insertions(+), 54 deletions(-)
create mode 100644 README.dco.md
create mode 100644 src/openvpn/dco.c
create mode 100644 src/openvpn/dco.h
create mode 100644 src/openvpn/networking_linuxdco.c
create mode 100644 src/openvpn/networking_linuxdco.h
diff --git a/Changes.rst b/Changes.rst
index b7d7f205..b613d656 100644
--- a/Changes.rst
+++ b/Changes.rst
@@ -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
diff --git a/README.dco.md b/README.dco.md
new file mode 100644
index 00000000..e2500d36
--- /dev/null
+++ b/README.dco.md
@@ -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.
diff --git a/configure.ac b/configure.ac
index e0f9c332..7d05d905 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])
diff --git a/doc/man-sections/advanced-options.rst b/doc/man-sections/advanced-options.rst
index 5157c561..cdec9502 100644
--- a/doc/man-sections/advanced-options.rst
+++ b/doc/man-sections/advanced-options.rst
@@ -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.
diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index 5883c291..0cc06155 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -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
diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c
index a63a2619..57869c66 100644
--- a/src/openvpn/crypto.c
+++ b/src/openvpn/crypto.c
@@ -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();
diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h
index 1e2ca3cb..bec6883c 100644
--- a/src/openvpn/crypto.h
+++ b/src/openvpn/crypto.h
@@ -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 */
diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
new file mode 100644
index 00000000..b04122b6
--- /dev/null
+++ b/src/openvpn/dco.c
@@ -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 <ar...@rf...>
+ * Copyright (C) 2021 OpenVPN Inc <sa...@op...>
+ *
+ * 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
diff --git a/src/openvpn/dco.h b/src/openvpn/dco.h
new file mode 100644
index 00000000..1cfdc8fb
--- /dev/null
+++ b/src/openvpn/dco.h
@@ -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 <ar...@rf...>
+ * Copyright (C) 2021 OpenVPN Inc <sa...@op...>
+ *
+ * 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
diff --git a/src/openvpn/errlevel.h b/src/openvpn/errlevel.h
index 602e48a8..a7054647 100644
--- a/src/openvpn/errlevel.h
+++ b/src/openvpn/errlevel.h
@@ -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 */
diff --git a/src/openvpn/event.h b/src/openvpn/event.h
index d67d69f6..fc815a59 100644
--- a/src/openvpn/event.h
+++ b/src/openvpn/event.h
@@ -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
diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index 41ef12e3..02aa6659 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -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
}
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 4fee7f49..4668fd04 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -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);
diff --git a/src/openvpn/init.h b/src/openvpn/init.h
index 52581f8a..5c719117 100644
--- a/src/openvpn/init.h
+++ b/src/openvpn/init.h
@@ -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);
diff --git a/src/openvpn/mtcp.c b/src/openvpn/mtcp.c
index bf9b5190..e335dcef 100644
--- a/src/openvpn/mtcp.c
+++ b/src/openvpn/mtcp.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)
{
diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index 07e2caae..1fc391fb 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -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
}
/*
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 103e882e..650804ea 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -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.
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 6e85c21c..71883ee5 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -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
diff --git a/src/openvpn/networking.h b/src/openvpn/networking.h
index 2f0ee160..cb57802b 100644
--- a/src/openvpn/networking.h
+++ b/src/openvpn/networking.h
@@ -27,6 +27,7 @@ struct context;
#ifdef ENABLE_SITNL
#include "networking_sitnl.h"
+#include "dco.h"
#elif ENABLE_IPROUTE
#include "networking_iproute2.h"
#else
diff --git a/src/openvpn/networking_linuxdco.c b/src/openvpn/networking_linuxdco.c
new file mode 100644
index 00000000..ca6284e7
--- /dev/null
+++ b/src/openvpn/networking_linuxdco.c
@@ -0,0 +1,848 @@
+/*
+ * Interface to linux dco networking code
+ *
+ * Copyright (C) 2020 Antonio Quartulli <a...@un...>
+ * Copyright (C) 2020 Arne Schwabe <ar...@rf...>
+ * Copyright (C) 2020 OpenVPN Inc <sa...@op...>
+ *
+ * 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 -...
[truncated message content] |