Implement the data-channel offloading using the ovpn-dco kernel module. See README.dco.md for more details.
Comments and bugfixes provided also by Kristof Provost <kprov...@netgate.com> Signed-off-by: Arne Schwabe <a...@rfc2549.org> Signed-off-by: Antonio Quartulli <a...@unstable.cc> --- Changes from v2 (highlights): * added more doc to the netlink code in dco_linux.c * use IN6_IS_ADDR_V4MAPPED() * simplify dco_p2p_add_new_peer/dco_installed logic * invert call to dco_del_peer and setting peer_added to false * change dco_status assignment to ternary if * remove some double new lines * make DCO_DEFAULT_METRIC define unique across platforms * in ovpn_nl_msg_send just set nl_cb once with no if * rework do_up based on kp's suggestion * set timeout for the p2p case * properly set dynamic_name (from kp) Changes.rst | 9 + README.dco.md | 123 +++ configure.ac | 28 + dev-tools/special-files.lst | 1 + doc/man-sections/advanced-options.rst | 13 + doc/man-sections/server-options.rst | 6 + src/openvpn/Makefile.am | 2 + src/openvpn/dco.c | 600 +++++++++++++ src/openvpn/dco.h | 301 +++++++ src/openvpn/dco_internal.h | 83 ++ src/openvpn/dco_linux.c | 933 +++++++++++++++++++++ src/openvpn/dco_linux.h | 62 ++ src/openvpn/errlevel.h | 2 + src/openvpn/event.h | 3 + src/openvpn/forward.c | 85 +- src/openvpn/init.c | 191 ++++- src/openvpn/init.h | 4 +- src/openvpn/misc.h | 3 +- src/openvpn/mtcp.c | 62 +- src/openvpn/mudp.c | 13 + src/openvpn/multi.c | 217 ++++- src/openvpn/multi.h | 6 +- src/openvpn/networking_sitnl.c | 11 + src/openvpn/openvpn.vcxproj | 4 +- src/openvpn/openvpn.vcxproj.filters | 6 + src/openvpn/options.c | 29 + src/openvpn/options.h | 20 + src/openvpn/ovpn_dco_linux.h | 265 ++++++ src/openvpn/socket.h | 1 + src/openvpn/ssl.c | 76 +- src/openvpn/ssl.h | 7 +- src/openvpn/ssl_common.h | 23 + src/openvpn/ssl_ncp.c | 2 +- src/openvpn/tun.c | 130 ++- src/openvpn/tun.h | 6 +- tests/unit_tests/openvpn/test_networking.c | 3 + 36 files changed, 3170 insertions(+), 160 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/dco_internal.h create mode 100644 src/openvpn/dco_linux.c create mode 100644 src/openvpn/dco_linux.h create mode 100644 src/openvpn/ovpn_dco_linux.h diff --git a/Changes.rst b/Changes.rst index 67a23c79..275f8d64 100644 --- a/Changes.rst +++ b/Changes.rst @@ -79,6 +79,15 @@ Cookie based handshake for UDP server shake. The tls-crypt-v2 option allows controlling if older clients are accepted. +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. Note that DCO will use DATA_V2 packets in P2P mode, therefore, + this implies that peers must be running 2.6.0+ in order to have P2P-NCP + which brings DATA_V2 packet support. + + Deprecated features ------------------- ``inetd`` has been removed diff --git a/README.dco.md b/README.dco.md new file mode 100644 index 00000000..e73e0fc2 --- /dev/null +++ b/README.dco.md @@ -0,0 +1,123 @@ +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 + + +Getting started (Linux) +----------------------- + +- Use a recent Linux kernel. Linux 5.4.0 and newer 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/openvpn/openvpn.git + cd openvpn + autoreconf -vi + ./configure --enable-dco + make + sudo 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-XXX-GCM or CHACHA20POLY1305). + + +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 and/or an IPv6 VPN IP 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 (i.e. ip route add a.b.c.d/24 via $VPNIP). +- Due to the point above, there is no real need to add a companion --route for + each --iroute directive, unless you want to blackhole traffic when the specific + client is not connected. +- No internal routing is available. If you need truly internal routes, this can be + achieved either with filtering using `iptables` or using `ip rule`. +- client-to-client behaviour, as implemented in userspace, does not exist: packets + always reach the tunnel interface and are then re-routed to the destination peer + based on the system routing table. + + +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 without compression +- various features not implemented since they have better replacements + - --shaper, use tc instead + - packet manipulation, use nftables/iptables instead +- OpenVPN 2.4.0 is the minimum peer version. + - older versions 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 implementation limitations +------------------- +- --persistent-tun not tested/supported +- fallback to non-dco in client mode missing +- IPv6 mapped IPv4 addresses need Linux 5.4.189+/5.10.110+/5.12+ to 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 incompatible options are currently identified +- No per client statistics. Only total statistics available on the interface diff --git a/configure.ac b/configure.ac index 9c898718..c5e09fb8 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@:>@])], @@ -760,6 +767,26 @@ PKG_CHECK_MODULES( [] ) + + +if test "$enable_dco" = "yes"; then +dnl +dnl Include generic netlink library used to talk to ovpn-dco +dnl + + PKG_CHECK_MODULES([LIBNL_GENL], + [libnl-genl-3.0 >= 3.4.0], + [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])] + ) + + CFLAGS="${CFLAGS} ${LIBNL_GENL_CFLAGS}" + LIBS="${LIBS} ${LIBNL_GENL_LIBS}" + + AC_DEFINE(ENABLE_DCO, 1, [Enable shared data channel offload]) + AC_MSG_NOTICE([Enabled ovpn-dco support for Linux]) +fi + 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]) @@ -1196,6 +1223,7 @@ fi AM_CONDITIONAL([HAVE_SITNL], [false]) if test "${enable_iproute2}" = "yes"; then + test "${enable_dco}" = "yes" && AC_MSG_ERROR([iproute2 support cannot be enabled when using DCO]) test -z "${IPROUTE}" && AC_MSG_ERROR([ip utility is required but missing]) AC_DEFINE([ENABLE_IPROUTE], [1], [enable iproute2 support]) else if test "${have_sitnl}" = "yes"; then diff --git a/dev-tools/special-files.lst b/dev-tools/special-files.lst index 64ee9e1a..33e830d7 100644 --- a/dev-tools/special-files.lst +++ b/dev-tools/special-files.lst @@ -1,3 +1,4 @@ E:doc/doxygen/doc_key_generation.h # @verbatim section gets mistreated, exclude it E:src/compat/compat-lz4.c # Preserve LZ4 upstream formatting E:src/compat/compat-lz4.h # Preserve LZ4 upstream formatting +E:src/openvpn/ovpn_dco_linux.h # Preserve ovpn-dco upstream formatting diff --git a/doc/man-sections/advanced-options.rst b/doc/man-sections/advanced-options.rst index 5157c561..d5a6b4f2 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 of 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 cannot be used when DCO mode + is enabled. + + On platforms that do not support DCO ``disable-dco`` has no effect. diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst index 08ee7bd3..31992732 100644 --- a/doc/man-sections/server-options.rst +++ b/doc/man-sections/server-options.rst @@ -321,6 +321,12 @@ fast hardware. SSL/TLS authentication must be used in this mode. from the kernel to OpenVPN. Once in OpenVPN, the ``--iroute`` directive routes to the specific client. + However, when using DCO, the ``--iroute`` directive is usually enough + for DCO to fully configure the routing table. The extra ``--route`` + directive is required only if the expected behaviour is to route the + traffic for a specific network to the VPN interface also when the + responsible client is not connected (traffic will then be dropped). + This option must be specified either in a client instance config file using ``--client-config-dir`` or dynamically generated using a ``--client-connect`` script. diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 57729480..2eb627cc 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -53,6 +53,8 @@ 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 dco_internal.h \ + dco_linux.c dco_linux.h \ dhcp.c dhcp.h \ dns.c dns.h \ env_set.c env_set.h \ diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c new file mode 100644 index 00000000..1272f011 --- /dev/null +++ b/src/openvpn/dco.c @@ -0,0 +1,600 @@ +/* + * 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-2022 Arne Schwabe <a...@rfc2549.org> + * Copyright (C) 2021-2022 Antonio Quartulli <a...@unstable.cc> + * Copyright (C) 2021-2022 OpenVPN Inc <sa...@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_DCO) + +#include "syshead.h" +#include "errlevel.h" +#include "networking.h" +#include "multi.h" +#include "ssl_verify.h" +#include "ssl_ncp.h" +#include "dco.h" + +static bool +dco_multi_get_localaddr(struct multi_context *m, struct multi_instance *mi, + struct sockaddr_storage *local) +{ +#if ENABLE_IP_PKTINFO + struct context *c = &mi->context; + + if (!(c->options.sockflags & SF_USE_IP_PKTINFO)) + { + return false; + } + + 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 = (struct sockaddr_in *)local; +#if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST) + sock_in4->sin_addr = actual->pi.in4.ipi_addr; +#elif defined(IP_RECVDSTADDR) + sock_in4->sin_addr = actual->pi.in4; +#else + /* source IP not available on this platform */ + return false; +#endif + sock_in4->sin_family = AF_INET; + break; + } + + case AF_INET6: + { + struct sockaddr_in6 *sock_in6 = (struct sockaddr_in6 *)local; + sock_in6->sin6_addr = actual->pi.in6.ipi6_addr; + sock_in6->sin6_family = AF_INET6; + break; + } + + default: + ASSERT(false); + } + + return true; +#else /* if ENABLE_IP_PKTINFO */ + return false; +#endif /* if ENABLE_IP_PKTINFO */ +} + +int +dco_multi_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; + struct sockaddr *remoteaddr, *localaddr = NULL; + struct sockaddr_storage local = { 0 }; + int sd = c->c2.link_socket->sd; + + 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; + + /* 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; + } + + if (dco_multi_get_localaddr(m, mi, &local)) + { + localaddr = (struct sockaddr *)&local; + } + + int ret = dco_new_peer(&c->c1.tuntap->dco, peer_id, sd, localaddr, + remoteaddr, remote_addr4, remote_addr6); + if (ret < 0) + { + return ret; + } + + c->c2.tls_multi->dco_peer_added = true; + + 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; + } + + return 0; +} + +int +dco_p2p_add_new_peer(struct context *c) +{ + if (!dco_enabled(&c->options)) + { + return 0; + } + + + struct tls_multi *multi = c->c2.tls_multi; + struct link_socket *ls = c->c2.link_socket; + + 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; + + ASSERT(ls->info.connection_established); + + /* 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)"); + } + + struct sockaddr *remoteaddr = &ls->info.lsa->actual.dest.addr.sa; + + int ret = dco_new_peer(&c->c1.tuntap->dco, multi->peer_id, + c->c2.link_socket->sd, NULL, remoteaddr, + remote_addr4, remote_addr6); + if (ret < 0) + { + return ret; + } + + c->c2.tls_multi->dco_peer_added = true; + c->c2.link_socket->info.dco_installed = true; + + return 0; +} + +void +dco_remove_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) + { + dco_del_peer(&c->c1.tuntap->dco, c->c2.tls_multi->peer_id); + c->c2.tls_multi->dco_peer_added = false; + } +} + +/** + * Find a usable key that is not the primary (i.e. the secondary key) + * + * @param multi The TLS struct to retrieve keys from + * @param primary The primary key that should be skipped during the scan + * + * @return The secondary key or NULL if none could be found + */ +static struct key_state * +dco_get_secondary_key(struct tls_multi *multi, const struct key_state *primary) +{ + 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 && ks->authenticated == KS_AUTH_TRUE) + { + ASSERT(key->initialized); + return ks; + } + } + + return NULL; +} + +void +dco_update_keys(dco_context_t *dco, struct tls_multi *multi) +{ + msg(D_DCO_DEBUG, "%s: peer_id=%d", __func__, multi->peer_id); + + /* this function checks if keys have to be swapped or erased, therefore it + * can't do much if we don't have any key installed + */ + if (multi->dco_keys_installed == 0) + { + return; + } + + struct key_state *primary = tls_select_encryption_key(multi); + ASSERT(!primary || primary->dco_status != DCO_NOT_INSTALLED); + + /* no primary key available -> no usable key exists, therefore we should + * tell DCO to simply wipe all keys + */ + if (!primary) + { + msg(D_DCO, "No encryption key found. Purging data channel keys"); + + dco_del_key(dco, multi->peer_id, OVPN_KEY_SLOT_PRIMARY); + dco_del_key(dco, multi->peer_id, OVPN_KEY_SLOT_SECONDARY); + multi->dco_keys_installed = 0; + return; + } + + struct key_state *secondary = dco_get_secondary_key(multi, primary); + ASSERT(!secondary || secondary->dco_status != DCO_NOT_INSTALLED); + + /* the current primary key was installed as secondary in DCO, this means + * that userspace has promoted it and we should tell DCO to swap keys + */ + if (primary->dco_status == DCO_INSTALLED_SECONDARY) + { + msg(D_DCO_DEBUG, "Swapping primary and secondary keys, now: id1=%d id2=%d", + primary->key_id, secondary ? secondary->key_id : -1); + + dco_swap_keys(dco, multi->peer_id); + primary->dco_status = DCO_INSTALLED_PRIMARY; + if (secondary) + { + secondary->dco_status = DCO_INSTALLED_SECONDARY; + } + } + + /* if we have no secondary key anymore, inform DCO about it */ + if (!secondary && multi->dco_keys_installed == 2) + { + dco_del_key(dco, multi->peer_id, OVPN_KEY_SLOT_SECONDARY); + multi->dco_keys_installed = 1; + } + + /* all keys that are 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; + } + } +} + +static int +dco_install_key(struct tls_multi *multi, struct key_state *ks, + const uint8_t *encrypt_key, const uint8_t *encrypt_iv, + const uint8_t *decrypt_key, const uint8_t *decrypt_iv, + const char *ciphername) + +{ + msg(D_DCO_DEBUG, "%s: peer_id=%d keyid=%d", __func__, multi->peer_id, + ks->key_id); + + /* Install a key in the PRIMARY slot only when no other key exist. + * From that moment on, any new key will be installed in the SECONDARY + * slot and will be promoted to PRIMARY when userspace says so (a swap + * will be performed in that case) + */ + dco_key_slot_t slot = OVPN_KEY_SLOT_PRIMARY; + if (multi->dco_keys_installed > 0) + { + slot = OVPN_KEY_SLOT_SECONDARY; + } + + int ret = dco_new_key(multi->dco, multi->peer_id, ks->key_id, slot, + encrypt_key, encrypt_iv, + decrypt_key, decrypt_iv, + ciphername); + if ((ret == 0) && (multi->dco_keys_installed < 2)) + { + multi->dco_keys_installed++; + ks->dco_status = (slot == OVPN_KEY_SLOT_PRIMARY) ? DCO_INSTALLED_PRIMARY : + DCO_INSTALLED_SECONDARY; + } + + return ret; +} + +int +init_key_dco_bi(struct tls_multi *multi, struct key_state *ks, + const struct key2 *key2, int key_direction, + const char *ciphername, bool server) +{ + struct key_direction_state kds; + key_direction_state_init(&kds, key_direction); + + return dco_install_key(multi, ks, + key2->keys[kds.out_key].cipher, + key2->keys[(int)server].hmac, + key2->keys[kds.in_key].cipher, + key2->keys[1 - (int)server].hmac, + ciphername); +} + +static bool +dco_check_option_conflict_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 disables data channel offload."); + return true; + } + + return false; +} + +bool +dco_check_option_conflict(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 (!dco_available(msglevel)) + { + 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 (dco_check_option_conflict_ce(l->array[i], msglevel)) + { + return true; + } + } + } + else + { + if (dco_check_option_conflict_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; +} + +/* These methods are currently Linux specific but likely to be used any + * platform that implements Server side DCO + */ + +void +dco_install_iroute(struct multi_context *m, struct multi_instance *mi, + struct mroute_addr *addr) +{ +#if defined(TARGET_LINUX) + if (!dco_enabled(&m->top.options)) + { + 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) + { + int netbits = 128; + if (addr->type & MR_WITH_NETBITS) + { + netbits = addr->netbits; + } + + net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, netbits, + &mi->context.c2.push_ifconfig_ipv6_local, dev, 0, + DCO_IROUTE_METRIC); + } + else if (addrtype == MR_ADDR_IPV4) + { + int netbits = 32; + if (addr->type & MR_WITH_NETBITS) + { + netbits = addr->netbits; + } + + in_addr_t dest = htonl(addr->v4.addr); + net_route_v4_add(&m->top.net_ctx, &dest, netbits, + &mi->context.c2.push_ifconfig_local, dev, 0, + DCO_IROUTE_METRIC); + } +#endif /* if defined(TARGET_LINUX) */ +} + +void +dco_delete_iroutes(struct multi_context *m, struct multi_instance *mi) +{ +#if defined(TARGET_LINUX) + 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; + 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; + 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); + } + } +#endif /* if defined(TARGET_LINUX) */ +} + +#endif /* defined(ENABLE_DCO) */ diff --git a/src/openvpn/dco.h b/src/openvpn/dco.h new file mode 100644 index 00000000..217b6b10 --- /dev/null +++ b/src/openvpn/dco.h @@ -0,0 +1,301 @@ +/* + * 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-2022 Arne Schwabe <a...@rfc2549.org> + * Copyright (C) 2021-2022 Antonio Quartulli <a...@unstable.cc> + * Copyright (C) 2021-2022 OpenVPN Inc <sa...@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 + +#include "crypto.h" +#include "networking.h" +#include "dco_internal.h" + +/* forward declarations, including multi.h leads to nasty include + * order problems */ +struct context; +struct key_state; +struct multi_context; +struct multi_instance; +struct mroute_addr; +struct tls_multi; +struct tuntap; +struct event_set; + +#define DCO_DEFAULT_METRIC 200 + +#if defined(ENABLE_DCO) + +/** + * Check whether the options struct has any option that is not supported by + * our current dco implementation. If so print a warning at warning level + * for the first conflicting option found and return true. + * + * @param msglevel the msg level to use to print the warnings + * @param o the options struct that hold the options + * @return true if a conflict was detected, false otherwise + */ +bool dco_check_option_conflict(int msglevel, const struct options *o); + +/** + * Initialize the DCO context + * + * @param mode the instance operating mode (P2P or multi-peer) + * @param dco the context to initialize + * @return true on success, false otherwise + */ +bool ovpn_dco_init(int mode, dco_context_t *dco); + +/** + * Open/create a DCO interface + * + * @param tt the tuntap context + * @param ctx the networking API context + * @param dev the name of the interface to create + * @return 0 on success or a negative error code otherwise + */ +int open_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev); + +/** + * Close/destroy a DCO interface + * + * @param tt the tuntap context + * @param ctx the networking API context + */ +void close_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx); + +/** + * Install a new peer in DCO - to be called by a SERVER instance + * + * @param m the server context + * @param mi the client instance + * @return 0 on success or a negative error code otherwise + */ +int dco_multi_add_new_peer(struct multi_context *m, struct multi_instance *mi); + +/** + * Install a new peer in DCO - to be called by a CLIENT (or P2P) instance + * + * @param c the main instance context + * @return 0 on success or a negative error code otherwise + */ +int dco_p2p_add_new_peer(struct context *c); + +/** + * Remove a peer from DCO + * + * @param c the main instance context of the peer to remove + */ +void dco_remove_peer(struct context *c); + +/** + * Read data from the DCO communication channel (i.e. a control packet) + * + * @param dco the DCO context + * @return 0 on success or a negative error code otherwise + */ +int dco_do_read(dco_context_t *dco); + +/** + * Write data to the DCO communication channel (control packet expected) + * + * @param dco the DCO context + * @param peer_id the ID of the peer to send the data to + * @param buf the buffer containing the data to send + */ +int dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf); + +/** + * Install an iroute in DCO, which means adding a route to the system routing + * table. To be called by a SERVER instance only. + * + * @param m the server context + * @param mi the client instance acting as nexthop for the route + * @param addr the route to add + */ +void dco_install_iroute(struct multi_context *m, struct multi_instance *mi, + struct mroute_addr *addr); + +/** + * Remove all routes added through the specified client + * + * @param m the server context + * @param mi the client instance for which routes have to be removed + */ +void dco_delete_iroutes(struct multi_context *m, struct multi_instance *mi); + +/** + * Install the key material in DCO for the specified peer, at the specified slot + * + * @param multi the TLS context of the current instance + * @param ks the state of the key being installed + * @param key2 the container for the raw key material + * @param key_direction the key direction to be used to extract the material + * @param ciphername the name of the cipher to use the key with + * @param server whether we are running on a server instance or not + * + * @return 0 on success or a negative error code otherwise + */ +int init_key_dco_bi(struct tls_multi *multi, struct key_state *ks, + const struct key2 *key2, int key_direction, + const char *ciphername, bool server); + +/** + * Possibly swap or wipe keys from DCO + * + * @param dco DCO device context + * @param multi TLS multi instance + */ +void dco_update_keys(dco_context_t *dco, struct tls_multi *multi); + +/** + * Check whether ovpn-dco is available on this platform (i.e. kernel support is + * there) + * + * @param msglevel level to print messages to + * @return true if ovpn-dco is available, false otherwise + */ +bool dco_available(int msglevel); + +/** + * Install a DCO in the main event loop + */ +void dco_event_set(dco_context_t *dco, struct event_set *es, void *arg); + +/** + * Modify DCO peer options. Special values are 0 (disable) + * and -1 (do not touch). + * + * @param dco DCO device context + * @param peer_id the ID of the peer to be modified + * @param keepalive_interval keepalive interval in seconds + * @param keepalive_timeout keepalive timeout in seconds + * @param mss TCP MSS value + * + * @return 0 on success or a negative error code otherwise + */ +int dco_set_peer(dco_context_t *dco, unsigned int peerid, + int keepalive_interval, int keepalive_timeout, int mss); + +#else /* if defined(ENABLE_DCO) */ + +typedef void *dco_context_t; + +static inline bool +dco_check_option_conflict(int msglevel, const struct options *o) +{ + return true; +} + +static inline bool +ovpn_dco_init(int mode, dco_context_t *dco) +{ + return true; +} + +static inline void +dco_install_iroute(struct multi_context *m, struct multi_instance *mi, + struct mroute_addr *addr) +{ +} + +static inline void +dco_delete_iroutes(struct multi_context *m, struct multi_instance *mi) +{ +} + +static inline int +open_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev) +{ + return 0; +} + +static inline void +close_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx) +{ +} + +static inline int +init_key_dco_bi(struct tls_multi *multi, struct key_state *ks, + const struct key2 *key2, int key_direction, + const char *ciphername, bool server) +{ + ASSERT(false); +} + +static inline void +dco_update_keys(dco_context_t *dco, struct tls_multi *multi) +{ + ASSERT(false); +} + +static inline bool +dco_multi_add_new_peer(struct multi_context *m, struct multi_instance *mi) +{ + return true; +} + +static inline bool +dco_p2p_add_new_peer(struct context *c) +{ + return true; +} + +static inline int +dco_set_peer(dco_context_t *dco, unsigned int peerid, + int keepalive_interval, int keepalive_timeout, int mss) +{ + return 0; +} + +static inline void +dco_remove_peer(struct context *c) +{ +} + +static inline int +dco_do_read(dco_context_t *dco) +{ + ASSERT(false); + return 0; +} + +static inline int +dco_do_write(dco_context_t *dco, int peer_id, struct buffer *buf) +{ + ASSERT(false); + return 0; +} + +static inline bool +dco_available(int msglevel) +{ + return false; +} + +static inline void +dco_event_set(dco_context_t *dco, struct event_set *es, void *arg) +{ +} + +#endif /* defined(ENABLE_DCO) */ +#endif /* ifndef DCO_H */ diff --git a/src/openvpn/dco_internal.h b/src/openvpn/dco_internal.h new file mode 100644 index 00000000..c40c29ca --- /dev/null +++ b/src/openvpn/dco_internal.h @@ -0,0 +1,83 @@ +/* + * 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) 2022 Antonio Quartulli <a...@unstable.cc> + * Copyright (C) 2022 OpenVPN Inc <sa...@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_INTERNAL_H +#define DCO_INTERNAL_H + +#if defined(ENABLE_DCO) + +#include "dco_linux.h" + +/** + * This file contains the internal DCO API definition. + * It is expected that this file is included only in dco.h after including the + * platform specific DCO header (i.e. dco_linux.h or dco_win.h). + * + * The OpenVPN code should never directly include this file + */ + +static inline dco_cipher_t +dco_get_cipher(const char *cipher) +{ + if (strcmp(cipher, "AES-256-GCM") == 0 || strcmp(cipher, "AES-128-GCM") == 0 + || strcmp(cipher, "AES-192-GCM") == 0) + { + return OVPN_CIPHER_ALG_AES_GCM; + } + else if (strcmp(cipher, "CHACHA20-POLY1305") == 0) + { + return OVPN_CIPHER_ALG_CHACHA20_POLY1305; + } + else if (strcmp(cipher, "none") == 0) + { + return OVPN_CIPHER_ALG_NONE; + } + else + { + msg(M_FATAL, "DCO: provided unsupported cipher: %s", cipher); + } +} + +/** + * The following are the DCO APIs used to control the driver. + * They are implemented by either dco_linux.c or dco_win.c + */ +int dco_new_peer(dco_context_t *dco, unsigned int peerid, int sd, + struct sockaddr *localaddr, struct sockaddr *remoteaddr, + struct in_addr *remote_in4, struct in6_addr *remote_in6); +int dco_del_peer(dco_context_t *dco, unsigned int peerid); + +int dco_new_key(dco_context_t *dco, unsigned int peerid, int keyid, + dco_key_slot_t slot, + const uint8_t *encrypt_key, const uint8_t *encrypt_iv, + const uint8_t *decrypt_key, const uint8_t *decrypt_iv, + const char *ciphername); + +int dco_del_key(dco_context_t *dco, unsigned int peerid, + dco_key_slot_t slot); + +int dco_swap_keys(dco_context_t *dco, unsigned int peerid); + +#endif /* defined(ENABLE_DCO) */ +#endif /* ifndef DCO_INTERNAL_H */ diff --git a/src/openvpn/dco_linux.c b/src/openvpn/dco_linux.c new file mode 100644 index 00000000..1795bd39 --- /dev/null +++ b/src/openvpn/dco_linux.c @@ -0,0 +1,933 @@ +/* + * Interface to linux dco networking code + * + * Copyright (C) 2020-2022 Antonio Quartulli <a...@unstable.cc> + * Copyright (C) 2020-2022 Arne Schwabe <a...@rfc2549.org> + * Copyright (C) 2020-2022 OpenVPN Inc <sa...@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_DCO) && defined(TARGET_LINUX) + +#include "syshead.h" + +#include "errlevel.h" +#include "buffer.h" +#include "networking.h" +#include "openvpn.h" + +#include "socket.h" +#include "tun.h" +#include "ssl.h" +#include "fdmisc.h" +#include "ssl_verify.h" + +#include "ovpn_dco_linux.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); + +/** + * @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 + * supports DCO offloading. + * + * @return ID on success, negative error code on error + */ +static 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 prepared 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 answer + * @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; + + nl_cb_set(dco->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, cb, 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 && IN6_IS_ADDR_V4MAPPED(&sock6->sin6_addr)) + { + + 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(dco_context_t *dco, unsigned int peerid, int sd, + struct sockaddr *localaddr, struct sockaddr *remoteaddr, + struct in_addr *remote_in4, struct in6_addr *remote_in6) +{ + 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(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(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; +} + +/* This function is used as error callback on the netlink socket. + * When something goes wrong and the kernel returns an error, this function is + * invoked. + * + * We pass the error code to the user by means of a variable pointed by *arg + * (supplied by the user when setting this callback) and we parse the kernel + * reply to see if it contains a human readable error. If found, it is printed. + */ +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); +} + +bool +ovpn_dco_init(int mode, dco_context_t *dco) +{ + switch (mode) + { + case CM_TOP: + dco->ifmode = OVPN_MODE_MP; + break; + + case CM_P2P: + dco->ifmode = OVPN_MODE_P2P; + break; + + default: + ASSERT(false); + } + + ovpn_dco_init_netlink(dco); + return true; +} + +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); + + CLEAR(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 multicast messages that the kernel may + * send + */ + 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); + } + + /* Register for non-data packets that ovpn-dco may receive. They will be + * forwarded to userspace + */ + 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); +} + +int +open_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev) +{ + msg(D_DCO_DEBUG, "%s: %s", __func__, dev); + ASSERT(tt->type == DEV_TYPE_TUN); + + int ret = net_iface_new(ctx, dev, "ovpn-dco", &tt->dco); + if (ret < 0) + { + msg(D_DCO_DEBUG, "Cannot create DCO interface %s: %d", dev, ret); + return ret; + } + + tt->dco.ifindex = if_nametoindex(dev); + if (!tt->dco.ifindex) + { + msg(M_FATAL, "DCO: cannot retrieve ifindex for interface %s", dev); + } + + tt->actual_name = string_alloc(dev, NULL); + uint8_t *dcobuf = malloc(65536); + buf_set_write(&tt->dco.dco_packet_in, dcobuf, 65536); + tt->dco.dco_message_peer_id = -1; + + ovpn_dco_register(&tt->dco); + + return 0; +} + +void +close_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx) +{ + msg(D_DCO_DEBUG, __func__); + + net_iface_del(ctx, tt->actual_name); + ovpn_dco_uninit_netlink(&tt->dco); + free(tt->dco.dco_packet_in.data); +} + +int +dco_swap_keys(dco_context_t *dco, unsigned int peerid) +{ + msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peerid); + + struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(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(dco, nl_msg, NULL, __func__); + +nla_put_failure: + nlmsg_free(nl_msg); + return ret; +} + + +int +dco_del_peer(dco_context_t *dco, unsigned int peerid) +{ + msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peerid); + + struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(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(dco, nl_msg, NULL, __func__); + +nla_put_failure: + nlmsg_free(nl_msg); + return ret; +} + + +int +dco_del_key(dco_context_t *dco, unsigned int peerid, + dco_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(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(dco, nl_msg, NULL, __func__); + +nla_put_failure: + nlmsg_free(nl_msg); + return ret; +} + +int +dco_new_key(dco_context_t *dco, unsigned int peerid, int keyid, + dco_key_slot_t slot, + const uint8_t *encrypt_key, const uint8_t *encrypt_iv, + const uint8_t *decrypt_key, const uint8_t *decrypt_iv, + const char *ciphername) +{ + msg(D_DCO_DEBUG, "%s: slot %d, key-id %d, peer-id %d, cipher %s", + __func__, slot, keyid, peerid, ciphername); + + const size_t key_len = cipher_kt_key_size(ciphername); + const int nonce_tail_len = 8; + + struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_NEW_KEY); + if (!nl_msg) + { + return -ENOMEM; + } + + dco_cipher_t dco_cipher = dco_get_cipher(ciphername); + + int ret = -EMSGSIZE; + struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_NEW_KEY); + 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, keyid); + NLA_PUT_U16(nl_msg, OVPN_NEW_KEY_ATTR_CIPHER_ALG, dco_cipher); + + struct nlattr *key_enc = nla_nest_start(nl_msg, + OVPN_NEW_KEY_ATTR_ENCRYPT_KEY); + if (dco_cipher != OVPN_CIPHER_ALG_NONE) + { + NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, key_len, encrypt_key); + NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_NONCE_TAIL, nonce_tail_len, + encrypt_iv); + } + nla_nest_end(nl_msg, key_enc); + + struct nlattr *key_dec = nla_nest_start(nl_msg, + OVPN_NEW_KEY_ATTR_DECRYPT_KEY); + if (dco_cipher != OVPN_CIPHER_ALG_NONE) + { + NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, key_len, decrypt_key); + NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_NONCE_TAIL, nonce_tail_len, + decrypt_iv); + } + nla_nest_end(nl_msg, key_dec); + + nla_nest_end(nl_msg, attr); + + ret = ovpn_nl_msg_send(dco, nl_msg, NULL, __func__); + +nla_put_failure: + nlmsg_free(nl_msg); + return ret; +} + +int +dco_set_peer(dco_context_t *dco, unsigned int peerid, + int keepalive_interval, int keepalive_timeout, int mss) +{ + msg(D_DCO_DEBUG, "%s: peer-id %d, keepalive %d/%d, mss %d", __func__, + peerid, keepalive_interval, keepalive_timeout, mss); + + struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(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(dco, nl_msg, NULL, __func__); + +nla_put_failure: + nlmsg_free(nl_msg); + return ret; +} + +/* This function parses the reply provided by the kernel to the CTRL_CMD_GETFAMILY + * message. We parse the reply and we retrieve the multicast group ID associated + * with the "ovpn-dco" netlink family. + * + * The ID is later used to subscribe to the multicast group and be notified + * about any multicast message sent by the ovpn-dco kernel module. + */ +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; +} + +/* This function parses any netlink message sent by ovpn-dco to userspace */ +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; + } + + /* we must know which interface this message is referring to in order to + * avoid mixing messages for other instances + */ + 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; + } + + /* based on the message type, we parse the subobject contained in the + * message, that stores the type-specific attributes. + * + * the "dco" object is then filled accordingly with the information + * retrieved from the message, so that the rest of the OpenVPN code can + * react as need be. + */ + 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_message_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_message_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 +dco_do_read(dco_context_t *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 +dco_do_write(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; +} + +bool +dco_available(int msglevel) +{ + if (resolve_ovpn_netlink_id(msglevel) < 0) + { + msg(msglevel, + "Note: Kernel support for ovpn-dco missing, disabling data channel offload."); + return false; + } + return true; +} + +void +dco_event_set(dco_context_t *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); + } +} + +#endif /* defined(ENABLE_DCO) && defined(TARGET_LINUX) */ diff --git a/src/openvpn/dco_linux.h b/src/openvpn/dco_linux.h new file mode 100644 index 00000000..039761ae --- /dev/null +++ b/src/openvpn/dco_linux.h @@ -0,0 +1,62 @@ +/* + * Interface to linux dco networking code + * + * Copyright (C) 2020-2022 Antonio Quartulli <a...@unstable.cc> + * Copyright (C) 2020-2022 Arne Schwabe <a...@rfc2549.org> + * Copyright (C) 2020-2022 OpenVPN Inc <sa...@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_LINUX_H +#define DCO_LINUX_H + +#if defined(ENABLE_DCO) && defined(TARGET_LINUX) + +#include "event.h" + +#include "ovpn_dco_linux.h" + +#include <netlink/socket.h> +#include <netlink/netlink.h> + +typedef enum ovpn_key_slot dco_key_slot_t; +typedef enum ovpn_cipher_alg dco_cipher_t; + +#define DCO_IROUTE_METRIC 100 +#define DCO_DEFAULT_METRIC 200 +#define DCO_SUPPORTED_CIPHERS "AES-128-GCM:AES-256-GCM:AES-192-GCM:CHACHA20-POLY1305" + +typedef struct +{ + struct nl_sock *nl_sock; + struct nl_cb *nl_cb; + int status; + + enum ovpn_mode ifmode; + + int ovpn_dco_id; + int ovpn_dco_mcast_id; + + unsigned int ifindex; + + struct buffer dco_packet_in; + + int dco_message_type; + int dco_message_peer_id; + int dco_del_peer_reason; +} dco_context_t; + +#endif /* defined(ENABLE_DCO) && defined(TARGET_LINUX) */ +#endif /* ifndef DCO_LINUX_H */ diff --git a/src/openvpn/errlevel.h b/src/openvpn/errlevel.h index e616a496..5bb1e65e 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 */ @@ -114,6 +115,7 @@ #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_XKEY LOGLEV(6, 69, M_DEBUG) /* show xkey-provider 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 a472afbe..f2438f97 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 6afe152b..b345395a 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 * @@ -140,6 +140,18 @@ context_reschedule_sec(struct context *c, int sec) } } +void +check_dco_key_status(struct context *c) +{ + /* DCO context is not yet initialised or enabled */ + if (!dco_enabled(&c->options)) + { + return; + } + + dco_update_keys(&c->c1.tuntap->dco, c->c2.tls_multi); +} + /* * In TLS mode, let TLS level respond to any control-channel * packets which were received, or prepare any packets for @@ -182,6 +194,12 @@ check_tls(struct context *c) interval_schedule_wakeup(&c->c2.tmp_int, &wakeup); + /* 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); + if (wakeup) { context_reschedule_sec(c, wakeup); @@ -1084,6 +1102,39 @@ process_incoming_link(struct context *c) perf_pop(); } +static void +process_incoming_dco(struct context *c) +{ +#if defined(ENABLE_DCO) && defined(TARGET_LINUX) + struct link_socket_info *lsi = get_link_socket_info(c); + dco_context_t *dco = &c->c1.tuntap->dco; + + dco_do_read(dco); + + if (dco->dco_message_type == OVPN_CMD_DEL_PEER) + { + trigger_ping_timeout_signal(c); + return; + } + + if (dco->dco_message_type != OVPN_CMD_PACKET) + { + msg(D_DCO_DEBUG, "%s: received message of type %u - ignoring", __func__, + dco->dco_message_type); + return; + } + + struct buffer orig_buff = c->c2.buf; + c->c2.buf = dco->dco_packet_in; + c->c2.from = lsi->lsa->actual; + + process_incoming_link(c); + + c->c2.buf = orig_buff; + buf_init(&dco->dco_packet_in, 0); +#endif /* if defined(ENABLE_DCO) && defined(TARGET_LINUX) */ +} + /* * Output: c->c2.buf */ @@ -1607,9 +1658,19 @@ 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); +#ifdef TARGET_LINUX + if (c->c2.link_socket->info.dco_installed) + { + size = dco_do_write(&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); @@ -1879,6 +1940,9 @@ io_wait_dowork(struct context *c, const unsigned int flags) #ifdef ENABLE_ASYNC_PUSH static int file_shift = FILE_SHIFT; #endif +#ifdef TARGET_LINUX + static int dco_shift = DCO_SHIFT; /* Event from DCO linux kernel module */ +#endif /* * Decide what kind of events we want to wait for. @@ -1986,6 +2050,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(TARGET_LINUX) + 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) @@ -2108,4 +2178,11 @@ process_io(struct context *c) process_incoming_tun(c); } } + else if (status & DCO_READ) + { + if (!IS_SIG(c)) + { + process_incoming_dco(c); + } + } } diff --git a/src/openvpn/init.c b/src/openvpn/init.c index b0c62a85..172585d7 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -55,6 +55,7 @@ #include "auth_token.h" #include "mss.h" #include "mudp.h" +#include "dco.h" #include "memdbg.h" @@ -1295,15 +1296,23 @@ do_init_timers(struct context *c, bool deferred) } /* initialize pings */ - - if (c->options.ping_send_timeout) + 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 { - 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) @@ -1385,6 +1394,11 @@ do_init_route_list(const struct options *options, int dev = dev_type_enum(options->dev, options->dev_type); int metric = 0; + if (dco_enabled(options)) + { + metric = DCO_DEFAULT_METRIC; + } + if (dev == DEV_TYPE_TUN && (options->topology == TOP_NET30 || options->topology == TOP_P2P)) { gw = options->ifconfig_remote_netmask; @@ -1421,6 +1435,11 @@ do_init_route_ipv6_list(const struct options *options, const char *gw = NULL; int metric = -1; /* no metric set */ + if (dco_enabled(options)) + { + metric = DCO_DEFAULT_METRIC; + } + gw = options->ifconfig_ipv6_remote; /* default GW = remote end */ if (options->route_ipv6_default_gateway) { @@ -1698,6 +1717,12 @@ do_open_tun(struct context *c) /* initialize (but do not open) tun/tap object */ do_init_tun(c); + /* inherit the dco context from the tuntap object */ + if (c->c2.tls_multi) + { + c->c2.tls_multi->dco = &c->c1.tuntap->dco; + } + #ifdef _WIN32 /* store (hide) interactive service handle in tuntap_options */ c->c1.tuntap->options.msg_channel = c->options.msg_channel; @@ -1746,9 +1771,14 @@ do_open_tun(struct context *c) /* Store the old fd inside the fd so open_tun can use it */ c->c1.tuntap->fd = oldtunfd; #endif + if (dco_enabled(&c->options)) + { + ovpn_dco_init(c->mode, &c->c1.tuntap->dco); + } + /* open the tun device */ open_tun(c->options.dev, c->options.dev_type, c->options.dev_node, - c->c1.tuntap); + c->c1.tuntap, &c->net_ctx); /* set the hardware address */ if (c->options.lladdr) @@ -2030,23 +2060,6 @@ do_up(struct context *c, bool pulled_options, unsigned int option_types_found) { reset_coarse_timers(c); - if (pulled_options) - { - if (!do_deferred_options(c, option_types_found)) - { - msg(D_PUSH_ERRORS, "ERROR: Failed to apply push options"); - return false; - } - } - else if (c->mode == MODE_POINT_TO_POINT) - { - if (!do_deferred_p2p_ncp(c)) - { - msg(D_TLS_ERRORS, "ERROR: Failed to apply P2P negotiated protocol options"); - return false; - } - } - /* if --up-delay specified, open tun, do ifconfig, and run up script now */ if (c->options.up_delay || PULL_DEFINED(&c->options)) { @@ -2072,6 +2085,44 @@ do_up(struct context *c, bool pulled_options, unsigned int option_types_found) } } + if (pulled_options) + { + if (!do_deferred_options(c, option_types_found)) + { + msg(D_PUSH_ERRORS, "ERROR: Failed to apply push options"); + return false; + } + } + + if (c->mode == MODE_POINT_TO_POINT) + { + /* ovpn-dco requires adding the peer now, before any option can be set, + * but *after* having parsed the pushed peer-id + */ + int ret = dco_p2p_add_new_peer(c); + if (ret < 0) + { + msg(D_DCO, "Cannot add peer to DCO: %s", strerror(-ret)); + return false; + } + } + + if (!pulled_options && c->mode == MODE_POINT_TO_POINT) + { + if (!do_deferred_p2p_ncp(c)) + { + msg(D_TLS_ERRORS, "ERROR: Failed to apply P2P negotiated protocol options"); + return false; + } + } + + + if (!finish_options(c)) + { + msg(D_TLS_ERRORS, "ERROR: Failed to finish option processing"); + return false; + } + if (c->c2.did_open_tun) { c->c1.pulled_options_digest_save = c->c2.pulled_options_digest; @@ -2169,8 +2220,9 @@ do_deferred_p2p_ncp(struct context *c) } #endif - if (!tls_session_update_crypto_params(session, &c->options, &c->c2.frame, - frame_fragment, get_link_socket_info(c))) + if (!tls_session_update_crypto_params(c->c2.tls_multi, session, &c->options, + &c->c2.frame, frame_fragment, + get_link_socket_info(c))) { msg(D_TLS_ERRORS, "ERROR: failed to set crypto cipher"); return false; @@ -2178,6 +2230,19 @@ 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 offload"); + return false; + } + return true; +} + /* * Handle non-tun-related pulled options. */ @@ -2274,19 +2339,54 @@ do_deferred_options(struct context *c, const unsigned int found) { return false; } - struct frame *frame_fragment = NULL; + } + + return true; +} + +bool +finish_options(struct context *c) +{ + struct frame *frame_fragment = NULL; #ifdef ENABLE_FRAGMENT - if (c->options.ce.fragment) - { - frame_fragment = &c->c2.frame_fragment; - } + if (c->options.ce.fragment) + { + frame_fragment = &c->c2.frame_fragment; + } #endif - struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE]; - if (!tls_session_update_crypto_params(session, &c->options, &c->c2.frame, - frame_fragment, get_link_socket_info(c))) + struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE]; + if (!tls_session_update_crypto_params(c->c2.tls_multi, session, + &c->options, &c->c2.frame, + frame_fragment, + get_link_socket_info(c))) + { + 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 offload. Use --disable-dco to connect" + "to this server"); + return false; + } + + if (dco_enabled(&c->options) + && (c->options.ping_send_timeout || c->c2.frame.mss_fix)) + { + int ret = dco_set_peer(&c->c1.tuntap->dco, + c->c2.tls_multi->peer_id, + c->options.ping_send_timeout, + c->options.ping_rec_timeout, + c->c2.frame.mss_fix); + if (ret < 0) { - msg(D_TLS_ERRORS, "OPTIONS ERROR: failed to import crypto options"); + msg(D_DCO, "Cannot set parameters for DCO peer (id=%u): %s", + c->c2.tls_multi->peer_id, strerror(-ret)); return false; } } @@ -2967,12 +3067,20 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) } } + /* let the TLS engine know if keys have to be installed in DCO or not */ + to.disable_dco = !dco_enabled(options); + /* * Initialize OpenVPN's master TLS-mode object. */ if (flags & CF_INIT_TLS_MULTI) { c->c2.tls_multi = tls_multi_init(&to); + /* inherit the dco context from the tuntap object */ + if (c->c1.tuntap) + { + c->c2.tls_multi->dco = &c->c1.tuntap->dco; + } } if (flags & CF_INIT_TLS_AUTH_STANDALONE) @@ -4256,6 +4364,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 */ + dco_remove_peer(c); + /* close TLS */ do_close_tls(c); @@ -4353,15 +4465,18 @@ inherit_context_child(struct context *dest, #endif /* context init */ + + /* inherit tun/tap interface object now as it may be required + * to initialize the DCO context in init_instance() + */ + dest->c1.tuntap = src->c1.tuntap; + init_instance(dest, src->c2.es, CC_NO_CLOSE | CC_USR1_TO_HUP); if (IS_SIG(dest)) { return; } - /* inherit tun/tap interface object */ - dest->c1.tuntap = src->c1.tuntap; - /* UDP inherits some extra things which TCP does not */ if (dest->mode == CM_CHILD_UDP) { diff --git a/src/openvpn/init.h b/src/openvpn/init.h index 2b8c2dcc..5cc2a990 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); @@ -97,6 +97,8 @@ void reset_coarse_timers(struct context *c); bool do_deferred_options(struct context *c, const unsigned int found); +bool finish_options(struct context *c); + void inherit_context_child(struct context *dest, const struct context *src); diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h index 2a6c0b8b..11ec122a 100644 --- a/src/openvpn/misc.h +++ b/src/openvpn/misc.h @@ -32,10 +32,11 @@ #include "buffer.h" #include "platform.h" +#include <stddef.h> + /* forward declarations */ struct plugin_list; - /* Set standard file descriptors to /dev/null */ void set_std_files_to_null(bool stdin_only); diff --git a/src/openvpn/mtcp.c b/src/openvpn/mtcp.c index b725bebb..414a5676 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,12 +124,15 @@ multi_create_instance_tcp(struct multi_context *m) struct hash *hash = m->hash; mi = multi_create_instance(m, NULL); + if (mi) { struct hash_element *he; const uint32_t hv = hash_value(hash, &mi->real); struct hash_bucket *bucket = hash_bucket(hash, hv); + multi_assign_peer_id(m, mi); + he = hash_lookup_fast(hash, bucket, &mi->real, hv); if (he) @@ -236,6 +240,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 +282,9 @@ multi_tcp_wait(const struct context *c, } #endif tun_set(c->c1.tuntap, mtcp->es, EVENT_READ, MTCP_TUN, persistent); +#if defined(TARGET_LINUX) + dco_event_set(&c->c1.tuntap->dco, mtcp->es, MTCP_DCO); +#endif #ifdef ENABLE_MANAGEMENT if (management) @@ -393,6 +401,20 @@ multi_tcp_wait_lite(struct multi_context *m, struct multi_instance *mi, const in tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */ +#if defined(TARGET_LINUX) + if (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 +538,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 +591,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 +651,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 +764,13 @@ multi_tcp_process_io(struct multi_context *m) multi_tcp_action(m, mi, TA_INITIAL, false); } } +#if defined(ENABLE_DCO) && defined(TARGET_LINUX) + /* 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 0810fada..14aa7236 100644 --- a/src/openvpn/mudp.c +++ b/src/openvpn/mudp.c @@ -380,6 +380,19 @@ multi_process_io_udp(struct multi_context *m) multi_process_file_closed(m, mpp_flags); } #endif +#if defined(ENABLE_DCO) && defined(TARGET_LINUX) + 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 ba2f6d58..ef5cb07a 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -51,6 +51,7 @@ #include "crypto_backend.h" #include "ssl_util.h" +#include "dco.h" /*#define MULTI_DEBUG_EVENT_LOOP*/ @@ -519,6 +520,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 +1228,20 @@ 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); - } + 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); } + + 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); } + + return owner; } /* @@ -1765,6 +1777,15 @@ multi_client_set_protocol_options(struct context *c) tls_multi->use_peer_id = true; o->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; @@ -1776,7 +1797,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 @@ -2276,8 +2296,9 @@ cleanup: * 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) @@ -2285,8 +2306,19 @@ multi_client_generate_tls_keys(struct context *c) frame_fragment = &c->c2.frame_fragment; } #endif + + if (dco_enabled(&c->options)) + { + int ret = dco_multi_add_new_peer(m, mi); + if (ret < 0) + { + msg(D_DCO, "Cannot add peer to DCO: %s", strerror(-ret)); + return false; + } + } + struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE]; - if (!tls_session_update_crypto_params(session, &c->options, + if (!tls_session_update_crypto_params(c->c2.tls_multi, session, &c->options, &c->c2.frame, frame_fragment, get_link_socket_info(c))) { @@ -2401,11 +2433,13 @@ 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; } + finish_options(&mi->context); + /* send push reply if ready */ if (mi->context.c2.push_request_received) { @@ -2661,6 +2695,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) + && dco_check_option_conflict(D_MULTI_ERRORS, &mi->context.options)) + { + msg(D_MULTI_ERRORS, "MULTI: client has been rejected due to incompatible DCO options"); + cc_succeeded = false; + } + if (cc_succeeded) { multi_client_connect_late_setup(m, mi, *option_types_found); @@ -3079,6 +3121,120 @@ done: gc_free(&gc); } +/* + * Called when an instance should be closed due to the + * reception of a soft signal. + */ +void +multi_close_instance_on_signal(struct multi_context *m, struct multi_instance *mi) +{ + remap_signal(&mi->context); + set_prefix(mi); + print_signal(mi->context.sig, "client-instance", D_MULTI_LOW); + clear_prefix(); + multi_close_instance(m, mi, false); +} + +#if (defined(ENABLE_DCO) && defined(TARGET_LINUX)) || defined(ENABLE_MANAGEMENT) +static void +multi_signal_instance(struct multi_context *m, struct multi_instance *mi, const int sig) +{ + mi->context.sig->signal_received = sig; + multi_close_instance_on_signal(m, mi); +} +#endif + +#if defined(ENABLE_DCO) && defined(TARGET_LINUX) +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_message_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 = dco_do_read(&m->top.c1.tuntap->dco); + + int peer_id = dco->dco_message_peer_id; + + if ((peer_id >= 0) && (peer_id < m->max_clients) && (m->instances[peer_id])) + { + mi = m->instances[peer_id]; + 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_message_peer_id = -1; + return ret > 0; +} +#endif /* if defined(ENABLE_DCO) && defined(TARGET_LINUX) */ + /* * Process packets in the TCP/UDP socket -> TUN/TAP interface direction, * i.e. client -> server direction. @@ -3640,32 +3796,11 @@ multi_process_signal(struct multi_context *m) return true; } -/* - * Called when an instance should be closed due to the - * reception of a soft signal. - */ -void -multi_close_instance_on_signal(struct multi_context *m, struct multi_instance *mi) -{ - remap_signal(&mi->context); - set_prefix(mi); - print_signal(mi->context.sig, "client-instance", D_MULTI_LOW); - clear_prefix(); - multi_close_instance(m, mi, false); -} - /* * Management subsystem callbacks */ #ifdef ENABLE_MANAGEMENT -static void -multi_signal_instance(struct multi_context *m, struct multi_instance *mi, const int sig) -{ - mi->context.sig->signal_received = sig; - multi_close_instance_on_signal(m, mi); -} - static void management_callback_status(void *arg, const int version, struct status_output *so) { @@ -3755,10 +3890,6 @@ management_delete_event(void *arg, event_t event) } } -#endif /* ifdef ENABLE_MANAGEMENT */ - -#ifdef ENABLE_MANAGEMENT - static struct multi_instance * lookup_by_cid(struct multi_context *m, const unsigned long cid) { diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index f1e9ab91..12d73126 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; @@ -311,6 +313,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_sitnl.c b/src/openvpn/networking_sitnl.c index e6328090..7ffbeb95 100644 --- a/src/openvpn/networking_sitnl.c +++ b/src/openvpn/networking_sitnl.c @@ -28,6 +28,7 @@ #include "syshead.h" +#include "dco.h" #include "errlevel.h" #include "buffer.h" #include "misc.h" @@ -1344,6 +1345,16 @@ net_iface_new(openvpn_net_ctx_t *ctx, const char *iface, const char *type, struct rtattr *linkinfo = SITNL_NEST(&req.n, sizeof(req), IFLA_LINKINFO); SITNL_ADDATTR(&req.n, sizeof(req), IFLA_INFO_KIND, type, strlen(type) + 1); +#if defined(ENABLE_DCO) + if (arg && (strcmp(type, "ovpn-dco") == 0)) + { + dco_context_t *dco = arg; + struct rtattr *data = SITNL_NEST(&req.n, sizeof(req), IFLA_INFO_DATA); + SITNL_ADDATTR(&req.n, sizeof(req), IFLA_OVPN_MODE, &dco->ifmode, + sizeof(uint8_t)); + SITNL_NEST_END(&req.n, data); + } +#endif SITNL_NEST_END(&req.n, linkinfo); req.i.ifi_family = AF_PACKET; diff --git a/src/openvpn/openvpn.vcxproj b/src/openvpn/openvpn.vcxproj index 860ef892..4213a1c7 100644 --- a/src/openvpn/openvpn.vcxproj +++ b/src/openvpn/openvpn.vcxproj @@ -276,9 +276,10 @@ <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="dns.c" /> + <ClCompile Include="env_set.c" /> <ClCompile Include="error.c" /> <ClCompile Include="event.c" /> <ClCompile Include="fdmisc.c" /> @@ -362,6 +363,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="dns.h" /> <ClInclude Include="env_set.h" /> diff --git a/src/openvpn/openvpn.vcxproj.filters b/src/openvpn/openvpn.vcxproj.filters index f76e5923..9121bba4 100644 --- a/src/openvpn/openvpn.vcxproj.filters +++ b/src/openvpn/openvpn.vcxproj.filters @@ -42,6 +42,9 @@ <ClCompile Include="dns.c"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="dco.c"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="error.c"> <Filter>Source Files</Filter> </ClCompile> @@ -299,6 +302,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> diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 9ff384d0..d63cd535 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -61,6 +61,7 @@ #include "ssl_verify.h" #include "platform.h" #include "xkey_common.h" +#include "dco.h" #include <ctype.h> #include "memdbg.h" @@ -106,6 +107,9 @@ const char title_string[] = #endif #endif " [AEAD]" +#ifdef ENABLE_DCO + " [DCO]" +#endif " built on " __DATE__ ; @@ -177,6 +181,9 @@ static const char usage_message[] = " does not begin with \"tun\" or \"tap\".\n" "--dev-node node : Explicitly set the device node rather than using\n" " /dev/net/tun, /dev/tun, /dev/tap, etc.\n" +#if defined(ENABLE_DCO) && defined(TARGET_LINUX) + "--disable-dco : Do not attempt using Data Channel Offload.\n" +#endif "--lladdr hw : Set the link layer address of the tap device.\n" "--topology t : Set --dev tun topology: 'net30', 'p2p', or 'subnet'.\n" #ifdef ENABLE_IPROUTE @@ -1673,6 +1680,9 @@ show_settings(const struct options *o) SHOW_STR(dev); SHOW_STR(dev_type); SHOW_STR(dev_node); +#if defined(ENABLE_DCO) && defined(TARGET_LINUX) + SHOW_BOOL(tuntap_options.disable_dco); +#endif SHOW_STR(lladdr); SHOW_INT(topology); SHOW_STR(ifconfig_local); @@ -3172,6 +3182,14 @@ options_postprocess_verify(const struct options *o) } dns_options_verify(M_FATAL, &o->dns_options); + + if (dco_enabled(o) && o->enable_c2c) + { + msg(M_WARN, "Note: --client-to-client has no effect when using data " + "channel offload: packets are always sent to the VPN " + "interface and then routed based on the system routing " + "table"); + } } /** @@ -3416,6 +3434,11 @@ options_postprocess_mutate(struct options *o) o->verify_hash_no_ca = true; } + /* check if any option should force disabling DCO */ +#if defined(TARGET_LINUX) + o->tuntap_options.disable_dco = dco_check_option_conflict(D_DCO, o); +#endif + /* * Save certain parms before modifying options during connect, especially * when using --pull @@ -5766,6 +5789,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); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index c2937dc3..4d82538c 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -879,4 +879,24 @@ void options_string_import(struct options *options, bool key_is_external(const struct options *options); +#if defined(ENABLE_DCO) + +/** + * Returns whether the current configuration has dco enabled. + */ +static inline bool +dco_enabled(const struct options *o) +{ + return !o->tuntap_options.disable_dco; +} + +#else /* if defined(ENABLE_DCO) */ + +static inline bool +dco_enabled(const struct options *o) +{ + return false; +} + +#endif #endif /* ifndef OPTIONS_H */ diff --git a/src/openvpn/ovpn_dco_linux.h b/src/openvpn/ovpn_dco_linux.h new file mode 100644 index 00000000..beca1beb --- /dev/null +++ b/src/openvpn/ovpn_dco_linux.h @@ -0,0 +1,265 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * OpenVPN data channel accelerator + * + * Copyright (C) 2019-2021 OpenVPN, Inc. + * + * Author: James Yonan <ja...@openvpn.net> + * Antonio Quartulli <anto...@openvpn.net> + */ + +#ifndef _UAPI_LINUX_OVPN_DCO_H_ +#define _UAPI_LINUX_OVPN_DCO_H_ + +#define OVPN_NL_NAME "ovpn-dco" + +#define OVPN_NL_MULTICAST_GROUP_PEERS "peers" + +/** + * enum ovpn_nl_commands - supported netlink commands + */ +enum ovpn_nl_commands { + /** + * @OVPN_CMD_UNSPEC: unspecified command to catch errors + */ + OVPN_CMD_UNSPEC = 0, + + /** + * @OVPN_CMD_NEW_PEER: Configure peer with its crypto keys + */ + OVPN_CMD_NEW_PEER, + + /** + * @OVPN_CMD_SET_PEER: Tweak parameters for an existing peer + */ + OVPN_CMD_SET_PEER, + + /** + * @OVPN_CMD_DEL_PEER: Remove peer from internal table + */ + OVPN_CMD_DEL_PEER, + + OVPN_CMD_NEW_KEY, + + OVPN_CMD_SWAP_KEYS, + + OVPN_CMD_DEL_KEY, + + /** + * @OVPN_CMD_REGISTER_PACKET: Register for specific packet types to be + * forwarded to userspace + */ + OVPN_CMD_REGISTER_PACKET, + + /** + * @OVPN_CMD_PACKET: Send a packet from userspace to kernelspace. Also + * used to send to userspace packets for which a process had registered + * with OVPN_CMD_REGISTER_PACKET + */ + OVPN_CMD_PACKET, + + /** + * @OVPN_CMD_GET_PEER: Retrieve the status of a peer or all peers + */ + OVPN_CMD_GET_PEER, +}; + +enum ovpn_cipher_alg { + /** + * @OVPN_CIPHER_ALG_NONE: No encryption - reserved for debugging only + */ + OVPN_CIPHER_ALG_NONE = 0, + /** + * @OVPN_CIPHER_ALG_AES_GCM: AES-GCM AEAD cipher with any allowed key size + */ + OVPN_CIPHER_ALG_AES_GCM, + /** + * @OVPN_CIPHER_ALG_CHACHA20_POLY1305: ChaCha20Poly1305 AEAD cipher + */ + OVPN_CIPHER_ALG_CHACHA20_POLY1305, +}; + +enum ovpn_del_peer_reason { + __OVPN_DEL_PEER_REASON_FIRST, + OVPN_DEL_PEER_REASON_TEARDOWN = __OVPN_DEL_PEER_REASON_FIRST, + OVPN_DEL_PEER_REASON_USERSPACE, + OVPN_DEL_PEER_REASON_EXPIRED, + OVPN_DEL_PEER_REASON_TRANSPORT_ERROR, + __OVPN_DEL_PEER_REASON_AFTER_LAST +}; + +enum ovpn_key_slot { + __OVPN_KEY_SLOT_FIRST, + OVPN_KEY_SLOT_PRIMARY = __OVPN_KEY_SLOT_FIRST, + OVPN_KEY_SLOT_SECONDARY, + __OVPN_KEY_SLOT_AFTER_LAST, +}; + +enum ovpn_netlink_attrs { + OVPN_ATTR_UNSPEC = 0, + OVPN_ATTR_IFINDEX, + OVPN_ATTR_NEW_PEER, + OVPN_ATTR_SET_PEER, + OVPN_ATTR_DEL_PEER, + OVPN_ATTR_NEW_KEY, + OVPN_ATTR_SWAP_KEYS, + OVPN_ATTR_DEL_KEY, + OVPN_ATTR_PACKET, + OVPN_ATTR_GET_PEER, + + __OVPN_ATTR_AFTER_LAST, + OVPN_ATTR_MAX = __OVPN_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_netlink_key_dir_attrs { + OVPN_KEY_DIR_ATTR_UNSPEC = 0, + OVPN_KEY_DIR_ATTR_CIPHER_KEY, + OVPN_KEY_DIR_ATTR_NONCE_TAIL, + + __OVPN_KEY_DIR_ATTR_AFTER_LAST, + OVPN_KEY_DIR_ATTR_MAX = __OVPN_KEY_DIR_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_netlink_new_key_attrs { + OVPN_NEW_KEY_ATTR_UNSPEC = 0, + OVPN_NEW_KEY_ATTR_PEER_ID, + OVPN_NEW_KEY_ATTR_KEY_SLOT, + OVPN_NEW_KEY_ATTR_KEY_ID, + OVPN_NEW_KEY_ATTR_CIPHER_ALG, + OVPN_NEW_KEY_ATTR_ENCRYPT_KEY, + OVPN_NEW_KEY_ATTR_DECRYPT_KEY, + + __OVPN_NEW_KEY_ATTR_AFTER_LAST, + OVPN_NEW_KEY_ATTR_MAX = __OVPN_NEW_KEY_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_netlink_del_key_attrs { + OVPN_DEL_KEY_ATTR_UNSPEC = 0, + OVPN_DEL_KEY_ATTR_PEER_ID, + OVPN_DEL_KEY_ATTR_KEY_SLOT, + + __OVPN_DEL_KEY_ATTR_AFTER_LAST, + OVPN_DEL_KEY_ATTR_MAX = __OVPN_DEL_KEY_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_netlink_swap_keys_attrs { + OVPN_SWAP_KEYS_ATTR_UNSPEC = 0, + OVPN_SWAP_KEYS_ATTR_PEER_ID, + + __OVPN_SWAP_KEYS_ATTR_AFTER_LAST, + OVPN_SWAP_KEYS_ATTR_MAX = __OVPN_SWAP_KEYS_ATTR_AFTER_LAST - 1, + +}; + +enum ovpn_netlink_new_peer_attrs { + OVPN_NEW_PEER_ATTR_UNSPEC = 0, + OVPN_NEW_PEER_ATTR_PEER_ID, + OVPN_NEW_PEER_ATTR_SOCKADDR_REMOTE, + OVPN_NEW_PEER_ATTR_SOCKET, + OVPN_NEW_PEER_ATTR_IPV4, + OVPN_NEW_PEER_ATTR_IPV6, + OVPN_NEW_PEER_ATTR_LOCAL_IP, + + __OVPN_NEW_PEER_ATTR_AFTER_LAST, + OVPN_NEW_PEER_ATTR_MAX = __OVPN_NEW_PEER_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_netlink_set_peer_attrs { + OVPN_SET_PEER_ATTR_UNSPEC = 0, + OVPN_SET_PEER_ATTR_PEER_ID, + OVPN_SET_PEER_ATTR_KEEPALIVE_INTERVAL, + OVPN_SET_PEER_ATTR_KEEPALIVE_TIMEOUT, + + __OVPN_SET_PEER_ATTR_AFTER_LAST, + OVPN_SET_PEER_ATTR_MAX = __OVPN_SET_PEER_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_netlink_del_peer_attrs { + OVPN_DEL_PEER_ATTR_UNSPEC = 0, + OVPN_DEL_PEER_ATTR_REASON, + OVPN_DEL_PEER_ATTR_PEER_ID, + + __OVPN_DEL_PEER_ATTR_AFTER_LAST, + OVPN_DEL_PEER_ATTR_MAX = __OVPN_DEL_PEER_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_netlink_get_peer_attrs { + OVPN_GET_PEER_ATTR_UNSPEC = 0, + OVPN_GET_PEER_ATTR_PEER_ID, + + __OVPN_GET_PEER_ATTR_AFTER_LAST, + OVPN_GET_PEER_ATTR_MAX = __OVPN_GET_PEER_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_netlink_get_peer_response_attrs { + OVPN_GET_PEER_RESP_ATTR_UNSPEC = 0, + OVPN_GET_PEER_RESP_ATTR_PEER_ID, + OVPN_GET_PEER_RESP_ATTR_SOCKADDR_REMOTE, + OVPN_GET_PEER_RESP_ATTR_IPV4, + OVPN_GET_PEER_RESP_ATTR_IPV6, + OVPN_GET_PEER_RESP_ATTR_LOCAL_IP, + OVPN_GET_PEER_RESP_ATTR_LOCAL_PORT, + OVPN_GET_PEER_RESP_ATTR_KEEPALIVE_INTERVAL, + OVPN_GET_PEER_RESP_ATTR_KEEPALIVE_TIMEOUT, + OVPN_GET_PEER_RESP_ATTR_RX_BYTES, + OVPN_GET_PEER_RESP_ATTR_TX_BYTES, + OVPN_GET_PEER_RESP_ATTR_RX_PACKETS, + OVPN_GET_PEER_RESP_ATTR_TX_PACKETS, + + __OVPN_GET_PEER_RESP_ATTR_AFTER_LAST, + OVPN_GET_PEER_RESP_ATTR_MAX = __OVPN_GET_PEER_RESP_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_netlink_peer_stats_attrs { + OVPN_PEER_STATS_ATTR_UNSPEC = 0, + OVPN_PEER_STATS_BYTES, + OVPN_PEER_STATS_PACKETS, + + __OVPN_PEER_STATS_ATTR_AFTER_LAST, + OVPN_PEER_STATS_ATTR_MAX = __OVPN_PEER_STATS_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_netlink_peer_attrs { + OVPN_PEER_ATTR_UNSPEC = 0, + OVPN_PEER_ATTR_PEER_ID, + OVPN_PEER_ATTR_SOCKADDR_REMOTE, + OVPN_PEER_ATTR_IPV4, + OVPN_PEER_ATTR_IPV6, + OVPN_PEER_ATTR_LOCAL_IP, + OVPN_PEER_ATTR_KEEPALIVE_INTERVAL, + OVPN_PEER_ATTR_KEEPALIVE_TIMEOUT, + OVPN_PEER_ATTR_ENCRYPT_KEY, + OVPN_PEER_ATTR_DECRYPT_KEY, + OVPN_PEER_ATTR_RX_STATS, + OVPN_PEER_ATTR_TX_STATS, + + __OVPN_PEER_ATTR_AFTER_LAST, + OVPN_PEER_ATTR_MAX = __OVPN_PEER_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_netlink_packet_attrs { + OVPN_PACKET_ATTR_UNSPEC = 0, + OVPN_PACKET_ATTR_PACKET, + OVPN_PACKET_ATTR_PEER_ID, + + __OVPN_PACKET_ATTR_AFTER_LAST, + OVPN_PACKET_ATTR_MAX = __OVPN_PACKET_ATTR_AFTER_LAST - 1, +}; + +enum ovpn_ifla_attrs { + IFLA_OVPN_UNSPEC = 0, + IFLA_OVPN_MODE, + + __IFLA_OVPN_AFTER_LAST, + IFLA_OVPN_MAX = __IFLA_OVPN_AFTER_LAST - 1, +}; + +enum ovpn_mode { + __OVPN_MODE_FIRST = 0, + OVPN_MODE_P2P = __OVPN_MODE_FIRST, + OVPN_MODE_MP, + + __OVPN_MODE_AFTER_LAST, +}; + +#endif /* _UAPI_LINUX_OVPN_DCO_H_ */ diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h index 270a829f..b478f3a8 100644 --- a/src/openvpn/socket.h +++ b/src/openvpn/socket.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; }; /* diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 61dea996..a01a4729 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -63,6 +63,7 @@ #include "ssl_util.h" #include "auth_token.h" #include "mss.h" +#include "dco.h" #include "memdbg.h" @@ -1429,21 +1430,49 @@ openvpn_PRF(const uint8_t *secret, } static void -init_key_contexts(struct key_ctx_bi *key, +init_key_contexts(struct key_state *ks, + struct tls_multi *multi, const struct key_type *key_type, bool server, - struct key2 *key2) + struct key2 *key2, + bool dco_disabled) { + struct key_ctx_bi *key = &ks->crypto_options.key_ctx_bi; + /* Initialize key contexts */ int key_direction = server ? KEY_DIRECTION_INVERSE : KEY_DIRECTION_NORMAL; - init_key_ctx_bi(key, key2, key_direction, key_type, "Data Channel"); - /* Initialize implicit IVs */ - key_ctx_update_implicit_iv(&key->encrypt, key2->keys[(int)server].hmac, - MAX_HMAC_KEY_LENGTH); - key_ctx_update_implicit_iv(&key->decrypt, key2->keys[1 - (int)server].hmac, - MAX_HMAC_KEY_LENGTH); + if (dco_disabled) + { + init_key_ctx_bi(key, key2, key_direction, key_type, "Data Channel"); + /* Initialize implicit IVs */ + key_ctx_update_implicit_iv(&key->encrypt, key2->keys[(int)server].hmac, + MAX_HMAC_KEY_LENGTH); + key_ctx_update_implicit_iv(&key->decrypt, + key2->keys[1 - (int)server].hmac, + MAX_HMAC_KEY_LENGTH); + } + + if (!dco_disabled) + { + if (key->encrypt.hmac) + { + msg(M_FATAL, "FATAL: DCO does not support --auth"); + } + + int ret = init_key_dco_bi(multi, ks, key2, key_direction, + key_type->cipher, server); + if (ret < 0) + { + msg(M_FATAL, "Impossible to install key material in DCO: %s", + strerror(-ret)); + } + /* encrypt/decrypt context are unused with DCO */ + CLEAR(key->encrypt); + CLEAR(key->decrypt); + key->initialized = true; + } } static bool @@ -1519,9 +1548,10 @@ generate_key_expansion_openvpn_prf(const struct tls_session *session, struct key * master key. */ static bool -generate_key_expansion(struct key_ctx_bi *key, +generate_key_expansion(struct tls_multi *multi, struct key_state *ks, struct tls_session *session) { + struct key_ctx_bi *key = &ks->crypto_options.key_ctx_bi; bool ret = false; struct key2 key2; @@ -1562,7 +1592,9 @@ generate_key_expansion(struct key_ctx_bi *key, goto exit; } } - init_key_contexts(key, &session->opt->key_type, server, &key2); + + init_key_contexts(ks, multi, &session->opt->key_type, server, &key2, + session->opt->disable_dco); ret = true; exit: @@ -1594,7 +1626,8 @@ key_ctx_update_implicit_iv(struct key_ctx *ctx, uint8_t *key, size_t key_len) * can thus be called only once per session. */ bool -tls_session_generate_data_channel_keys(struct tls_session *session) +tls_session_generate_data_channel_keys(struct tls_multi *multi, + struct tls_session *session) { bool ret = false; struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */ @@ -1607,7 +1640,7 @@ tls_session_generate_data_channel_keys(struct tls_session *session) ks->crypto_options.flags = session->opt->crypto_flags; - if (!generate_key_expansion(&ks->crypto_options.key_ctx_bi, session)) + if (!generate_key_expansion(multi, ks, session)) { msg(D_TLS_ERRORS, "TLS Error: generate_key_expansion failed"); goto cleanup; @@ -1625,8 +1658,10 @@ cleanup: } bool -tls_session_update_crypto_params_do_work(struct tls_session *session, - struct options *options, struct frame *frame, +tls_session_update_crypto_params_do_work(struct tls_multi *multi, + struct tls_session *session, + struct options *options, + struct frame *frame, struct frame *frame_fragment, struct link_socket_info *lsi) { @@ -1669,11 +1704,12 @@ tls_session_update_crypto_params_do_work(struct tls_session *session, frame_print(frame_fragment, D_MTU_INFO, "Fragmentation MTU parms"); } - return tls_session_generate_data_channel_keys(session); + return tls_session_generate_data_channel_keys(multi, session); } bool -tls_session_update_crypto_params(struct tls_session *session, +tls_session_update_crypto_params(struct tls_multi *multi, + struct tls_session *session, struct options *options, struct frame *frame, struct frame *frame_fragment, struct link_socket_info *lsi) @@ -1695,8 +1731,8 @@ tls_session_update_crypto_params(struct tls_session *session, /* Import crypto settings that might be set by pull/push */ session->opt->crypto_flags |= options->data_channel_crypto_flags; - return tls_session_update_crypto_params_do_work(session, options, frame, - frame_fragment, lsi); + return tls_session_update_crypto_params_do_work(multi, session, options, + frame, frame_fragment, lsi); } @@ -1991,7 +2027,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 @@ -3089,7 +3125,7 @@ tls_multi_process(struct tls_multi *multi, /* Session is now fully authenticated. * tls_session_generate_data_channel_keys will move ks->state * from S_ACTIVE to S_GENERATED_KEYS */ - if (!tls_session_generate_data_channel_keys(session)) + if (!tls_session_generate_data_channel_keys(multi, session)) { msg(D_TLS_ERRORS, "TLS Error: generate_key_expansion failed"); ks->authenticated = KS_AUTH_FALSE; diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index 0ba86d3e..ba271971 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -423,6 +423,7 @@ void tls_update_remote_addr(struct tls_multi *multi, * channel keys based on the supplied options. Does nothing if keys are already * generated. * + * @param multi The TLS object for this instance. * @param session The TLS session to update. * @param options The options to use when updating session. * @param frame The frame options for this session (frame overhead is @@ -433,7 +434,8 @@ void tls_update_remote_addr(struct tls_multi *multi, * * @return true if updating succeeded or keys are already generated, false otherwise. */ -bool tls_session_update_crypto_params(struct tls_session *session, +bool tls_session_update_crypto_params(struct tls_multi *multi, + struct tls_session *session, struct options *options, struct frame *frame, struct frame *frame_fragment, @@ -548,7 +550,8 @@ show_available_tls_ciphers(const char *cipher_list, * can thus be called only once per session. */ bool -tls_session_generate_data_channel_keys(struct tls_session *session); +tls_session_generate_data_channel_keys(struct tls_multi *multi, + struct tls_session *session); /** * Load ovpn.xkey provider used for external key signing diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index cef2611b..9eb81fe9 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -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 @@ -197,6 +203,12 @@ struct key_state */ int key_id; + /** + * Key id for this key_state, inherited from struct tls_session. + * @see tls_multi::peer_id. + */ + uint32_t peer_id; + struct key_state_ssl ks_ssl; /* contains SSL object and BIOs for the control channel */ time_t initial; /* when we created this session */ @@ -241,6 +253,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 */ @@ -404,6 +418,8 @@ struct tls_options const char *ekm_label; size_t ekm_label_size; size_t ekm_size; + + bool disable_dco; /**< Whether keys have to be installed in DCO or not */ }; /** @addtogroup control_processor @@ -636,6 +652,13 @@ 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; + + dco_context_t *dco; }; /** gets an item of \c key_state objects in the diff --git a/src/openvpn/ssl_ncp.c b/src/openvpn/ssl_ncp.c index 5d7e6dd3..0ed0fa13 100644 --- a/src/openvpn/ssl_ncp.c +++ b/src/openvpn/ssl_ncp.c @@ -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 +} diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c index e12f0369..e4b27a13 100644 --- a/src/openvpn/tun.c +++ b/src/openvpn/tun.c @@ -1718,10 +1718,10 @@ read_tun_header(struct tuntap *tt, uint8_t *buf, int len) #endif /* if defined (TARGET_OPENBSD) || (defined(TARGET_DARWIN) && HAVE_NET_IF_UTUN_H) */ -#if !(defined(_WIN32) || defined(TARGET_LINUX)) +#if !defined(_WIN32) static void open_tun_generic(const char *dev, const char *dev_type, const char *dev_node, - bool dynamic, struct tuntap *tt) + bool dynamic, struct tuntap *tt, openvpn_net_ctx_t *ctx) { char tunname[256]; char dynamic_name[256]; @@ -1739,6 +1739,7 @@ open_tun_generic(const char *dev, const char *dev_type, const char *dev_node, if (dev_node) { openvpn_snprintf(tunname, sizeof(tunname), "%s", dev_node); + strncpynt(dynamic_name, dev, sizeof(dynamic_name)); } else { @@ -1780,6 +1781,19 @@ open_tun_generic(const char *dev, const char *dev_type, const char *dev_node, "/dev/%s%d", dev, i); openvpn_snprintf(dynamic_name, sizeof(dynamic_name), "%s%d", dev, i); +#ifdef TARGET_LINUX + if (!tt->options.disable_dco) + { + if (open_tun_dco(tt, ctx, dynamic_name) == 0) + { + dynamic_opened = true; + strncpynt(tunname, dynamic_name, + sizeof(dynamic_name)); + break; + } + } + else +#endif if ((tt->fd = open(tunname, O_RDWR)) > 0) { dynamic_opened = true; @@ -1798,26 +1812,49 @@ open_tun_generic(const char *dev, const char *dev_type, const char *dev_node, else { openvpn_snprintf(tunname, sizeof(tunname), "/dev/%s", dev); + strncpynt(dynamic_name, dev, sizeof(dynamic_name)); } } - if (!dynamic_opened) +#ifdef TARGET_LINUX + if (!tt->options.disable_dco) { - /* has named device existed before? if so, don't destroy at end */ - if (if_nametoindex( dev ) > 0) + if (!dynamic_opened) { - msg(M_INFO, "TUN/TAP device %s exists previously, keep at program end", dev ); - tt->persistent_if = true; + int ret = open_tun_dco(tt, ctx, dynamic_name); + if (ret == -EEXIST) + { + msg(M_INFO, "TUN/TAP device %s exists previously, keep at program end", + dynamic_name); + tt->persistent_if = true; + } + else if (ret < 0) + { + msg(M_ERR, "Cannot open TUN/TAP dev %s: %d", dynamic_name, ret); + } } - - if ((tt->fd = open(tunname, O_RDWR)) < 0) + } + else +#endif + { + if (!dynamic_opened) { - msg(M_ERR, "Cannot open TUN/TAP dev %s", tunname); + /* has named device existed before? if so, don't destroy at end */ + if (if_nametoindex( dev ) > 0) + { + msg(M_INFO, "TUN/TAP device %s exists previously, keep at program end", dev ); + tt->persistent_if = true; + } + + if ((tt->fd = open(tunname, O_RDWR)) < 0) + { + msg(M_ERR, "Cannot open TUN/TAP dev %s", tunname); + } } + set_nonblock(tt->fd); + set_cloexec(tt->fd); /* don't pass fd to scripts */ } - set_nonblock(tt->fd); - set_cloexec(tt->fd); /* don't pass fd to scripts */ msg(M_INFO, "TUN/TAP device %s opened", tunname); /* tt->actual_name is passed to up and down scripts and used as the ifconfig dev name */ @@ -1842,7 +1879,8 @@ close_tun_generic(struct tuntap *tt) #if defined (TARGET_ANDROID) void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { #define ANDROID_TUNNAME "vpnservice-tun" struct user_pass up; @@ -1939,7 +1977,8 @@ read_tun(struct tuntap *tt, uint8_t *buf, int len) #if !PEDANTIC void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { struct ifreq ifr; @@ -1950,6 +1989,12 @@ open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun { open_null(tt); } +#if defined(TARGET_LINUX) + else if (!tt->options.disable_dco) + { + open_tun_generic(dev, dev_type, NULL, true, tt, ctx); + } +#endif else { /* @@ -2056,7 +2101,8 @@ open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun #else /* if !PEDANTIC */ void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { ASSERT(0); } @@ -2081,7 +2127,7 @@ tuncfg(const char *dev, const char *dev_type, const char *dev_node, clear_tuntap(tt); tt->type = dev_type_enum(dev, dev_type); tt->options = *options; - open_tun(dev, dev_type, dev_node, tt); + open_tun(dev, dev_type, dev_node, tt, ctx); if (ioctl(tt->fd, TUNSETPERSIST, persist_mode) < 0) { msg(M_ERR, "Cannot ioctl TUNSETPERSIST(%d) %s", persist_mode, dev); @@ -2199,7 +2245,16 @@ close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx) net_ctx_reset(ctx); } - close_tun_generic(tt); +#ifdef TARGET_LINUX + if (!tt->options.disable_dco) + { + close_tun_dco(tt, ctx); + } + else +#endif + { + close_tun_generic(tt); + } free(tt); } @@ -2222,7 +2277,8 @@ read_tun(struct tuntap *tt, uint8_t *buf, int len) #endif void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { int if_fd, ip_muxid, arp_muxid, arp_fd, ppa = -1; struct lifreq ifr; @@ -2574,9 +2630,10 @@ read_tun(struct tuntap *tt, uint8_t *buf, int len) #elif defined(TARGET_OPENBSD) void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { - open_tun_generic(dev, dev_type, dev_node, true, tt); + open_tun_generic(dev, dev_type, dev_node, true, tt, ctx); /* Enable multicast on the interface */ if (tt->fd >= 0) @@ -2668,9 +2725,10 @@ read_tun(struct tuntap *tt, uint8_t *buf, int len) */ void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { - open_tun_generic(dev, dev_type, dev_node, true, tt); + open_tun_generic(dev, dev_type, dev_node, true, tt, ctx); if (tt->fd >= 0) { @@ -2808,9 +2866,10 @@ freebsd_modify_read_write_return(int len) } void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { - open_tun_generic(dev, dev_type, dev_node, true, tt); + open_tun_generic(dev, dev_type, dev_node, true, tt, ctx); if (tt->fd >= 0 && tt->type == DEV_TYPE_TUN) { @@ -2936,9 +2995,10 @@ dragonfly_modify_read_write_return(int len) } void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { - open_tun_generic(dev, dev_type, dev_node, true, tt); + open_tun_generic(dev, dev_type, dev_node, true, tt, ctx); if (tt->fd >= 0) { @@ -3164,7 +3224,8 @@ open_darwin_utun(const char *dev, const char *dev_type, const char *dev_node, st #endif /* ifdef HAVE_NET_IF_UTUN_H */ void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { #ifdef HAVE_NET_IF_UTUN_H /* If dev_node does not start start with utun assume regular tun/tap */ @@ -3190,7 +3251,7 @@ open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun { /* No explicit utun and utun failed, try the generic way) */ msg(M_INFO, "Failed to open utun device. Falling back to /dev/tun device"); - open_tun_generic(dev, dev_type, NULL, true, tt); + open_tun_generic(dev, dev_type, NULL, true, tt, ctx); } else { @@ -3213,7 +3274,7 @@ open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun dev_node = NULL; } - open_tun_generic(dev, dev_type, dev_node, true, tt); + open_tun_generic(dev, dev_type, dev_node, true, tt, ctx); } } @@ -3271,7 +3332,8 @@ read_tun(struct tuntap *tt, uint8_t *buf, int len) #elif defined(TARGET_AIX) void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { char tunname[256]; char dynamic_name[20]; @@ -6580,7 +6642,8 @@ tuntap_post_open(struct tuntap *tt, const char *device_guid) } void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { const char *device_guid = NULL; @@ -6881,9 +6944,10 @@ ipset2ascii_all(struct gc_arena *gc) #else /* generic */ void -open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt, + openvpn_net_ctx_t *ctx) { - open_tun_generic(dev, dev_type, dev_node, true, tt); + open_tun_generic(dev, dev_type, dev_node, true, tt, ctx); } void diff --git a/src/openvpn/tun.h b/src/openvpn/tun.h index 4bc35916..cf02bf43 100644 --- a/src/openvpn/tun.h +++ b/src/openvpn/tun.h @@ -40,6 +40,7 @@ #include "misc.h" #include "networking.h" #include "ring_buffer.h" +#include "dco.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) */ @@ -214,6 +216,8 @@ struct tuntap #endif /* used for printing status info only */ unsigned int rwflags_debug; + + dco_context_t dco; }; static inline bool @@ -245,7 +249,7 @@ tuntap_ring_empty(struct tuntap *tt) */ void open_tun(const char *dev, const char *dev_type, const char *dev_node, - struct tuntap *tt); + struct tuntap *tt, openvpn_net_ctx_t *ctx); void close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx); diff --git a/tests/unit_tests/openvpn/test_networking.c b/tests/unit_tests/openvpn/test_networking.c index 10ed2cb5..befbb546 100644 --- a/tests/unit_tests/openvpn/test_networking.c +++ b/tests/unit_tests/openvpn/test_networking.c @@ -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"; -- 2.35.1 _______________________________________________ Openvpn-devel mailing list Openvpn-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openvpn-devel