From patchwork Tue Dec 7 12:11:30 2021
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Openvpn-devel,RFC,1/8] networking: remove duplicate methods from
networking_sitnl.c
X-Patchwork-Submitter: Antonio Quartulli
X-Patchwork-Id: 2111
Message-Id: <20211207121137.3221-2-a@unstable.cc>
To: openvpn-devel@lists.sourceforge.net
Cc: Antonio Quartulli
Date: Tue, 7 Dec 2021 13:11:30 +0100
From: Antonio Quartulli
List-Id:
From: Arne Schwabe
The net_ctx_init/reset/free methods of sitnl are the same dummy
methods that are already defined for non-Linux platforms in the
networking.h header.
Signed-off-by: Arne Schwabe
Signed-off-by: Antonio Quartulli
---
src/openvpn/networking.h | 7 ++++++-
src/openvpn/networking_sitnl.c | 22 ----------------------
2 files changed, 6 insertions(+), 23 deletions(-)
diff --git a/src/openvpn/networking.h b/src/openvpn/networking.h
index d43979f0..ad25fc79 100644
--- a/src/openvpn/networking.h
+++ b/src/openvpn/networking.h
@@ -33,7 +33,12 @@ struct context;
/* define mock types to ensure code builds on any platform */
typedef void *openvpn_net_ctx_t;
typedef void *openvpn_net_iface_t;
+#endif /* ifdef ENABLE_SITNL */
+/* Only the iproute2 backend implements these functions,
+ * the rest can rely on these stubs
+ */
+#if !defined(ENABLE_IPROUTE)
static inline int
net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)
{
@@ -51,7 +56,7 @@ net_ctx_free(openvpn_net_ctx_t *ctx)
{
(void)ctx;
}
-#endif /* ifdef ENABLE_SITNL */
+#endif /* !defined(ENABLE_IPROUTE) */
#if defined(ENABLE_SITNL) || defined(ENABLE_IPROUTE)
diff --git a/src/openvpn/networking_sitnl.c b/src/openvpn/networking_sitnl.c
index 8610e1d2..e0003f5c 100644
--- a/src/openvpn/networking_sitnl.c
+++ b/src/openvpn/networking_sitnl.c
@@ -595,28 +595,6 @@ net_route_v6_best_gw(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
}
#ifdef ENABLE_SITNL
-
-int
-net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)
-{
- (void)c;
- (void)ctx;
-
- return 0;
-}
-
-void
-net_ctx_reset(openvpn_net_ctx_t *ctx)
-{
- (void)ctx;
-}
-
-void
-net_ctx_free(openvpn_net_ctx_t *ctx)
-{
- (void)ctx;
-}
-
int
net_route_v4_best_gw(openvpn_net_ctx_t *ctx, const in_addr_t *dst,
in_addr_t *best_gw, char *best_iface)
From patchwork Tue Dec 7 12:11:31 2021
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Openvpn-devel,RFC,2/8] networking: silence warnings about unused
arguments
X-Patchwork-Submitter: Antonio Quartulli
X-Patchwork-Id: 2112
Message-Id: <20211207121137.3221-3-a@unstable.cc>
To: openvpn-devel@lists.sourceforge.net
Cc: Antonio Quartulli
Date: Tue, 7 Dec 2021 13:11:31 +0100
From: Antonio Quartulli
List-Id:
In the net_ctx_init() stub definition, arguments are not used and
therefore they should be explicitly marked to avoid compiler
warnings.
Signed-off-by: Antonio Quartulli
---
src/openvpn/networking.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/openvpn/networking.h b/src/openvpn/networking.h
index ad25fc79..2f0ee160 100644
--- a/src/openvpn/networking.h
+++ b/src/openvpn/networking.h
@@ -42,6 +42,9 @@ typedef void *openvpn_net_iface_t;
static inline int
net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)
{
+ (void)c;
+ (void)ctx;
+
return 0;
}
From patchwork Tue Dec 7 12:11:32 2021
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Openvpn-devel,RFC,3/8] sitnl: implement net_iface_new and
net_iface_del
X-Patchwork-Submitter: Antonio Quartulli
X-Patchwork-Id: 2113
Message-Id: <20211207121137.3221-4-a@unstable.cc>
To: openvpn-devel@lists.sourceforge.net
Cc: Antonio Quartulli
Date: Tue, 7 Dec 2021 13:11:32 +0100
From: Antonio Quartulli
List-Id:
From: Arne Schwabe
These two new methods can be used to create and delete a tun or an
ovpn-dco interface via RTNL API.
Signed-off-by: Arne Schwabe
Signed-off-by: Antonio Quartulli
---
src/openvpn/networking_sitnl.c | 94 ++++++++++++++++++++++
src/openvpn/networking_sitnl.h | 28 +++++++
tests/unit_tests/openvpn/test_networking.c | 22 ++++-
3 files changed, 143 insertions(+), 1 deletion(-)
diff --git a/src/openvpn/networking_sitnl.c b/src/openvpn/networking_sitnl.c
index e0003f5c..e6ffdb64 100644
--- a/src/openvpn/networking_sitnl.c
+++ b/src/openvpn/networking_sitnl.c
@@ -1312,6 +1312,100 @@ net_route_v6_del(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
table, metric);
}
+
+int
+net_iface_new(const char *iface, const char *type)
+{
+ struct sitnl_link_req req = { };
+ struct rtattr *tail = NULL;
+ int ret = -1;
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
+ req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL ;
+ req.n.nlmsg_type = RTM_NEWLINK;
+
+ if (iface)
+ {
+ SITNL_ADDATTR(&req.n, sizeof(req), IFLA_IFNAME, iface, strlen(iface) + 1);
+ }
+ tail = NLMSG_TAIL(&req.n);
+ SITNL_ADDATTR(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0);
+ SITNL_ADDATTR(&req.n, sizeof(req), IFLA_INFO_KIND, type,
+ strlen(type) + 1);
+ tail->rta_len = (uint8_t *)NLMSG_TAIL(&req.n) - (uint8_t *)tail;
+
+ req.i.ifi_family = AF_PACKET;
+ req.i.ifi_change = 0xFFFFFFFF;
+
+ msg(D_ROUTE, "%s: add %s type %s", __func__, np(iface), type);
+
+ if (iface)
+ {
+ /* if we have an interface name we can use that name to later
+ * lookup what interface index we created */
+ ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+ if (!ret)
+ {
+ req.i.ifi_index = if_nametoindex(iface);
+ }
+
+ }
+ else
+ {
+ req.i.ifi_index = 1194;
+ do
+ {
+ /* for some reason RTM_NEWLINK does not have a reply */
+ /* Therefore we use try using different if indices untiles
+ * we get one that does not exist already */
+ req.i.ifi_index++;
+ ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+ }
+ while (ret == -EEXIST);
+ }
+ if (!ret)
+ {
+ return req.i.ifi_index;
+ }
+
+err:
+ return ret;
+}
+
+int
+net_iface_del_name(const char *iface)
+{
+ int ifindex;
+
+ ifindex = if_nametoindex(iface);
+
+ msg(D_ROUTE,"%s: idel %s", __func__, iface);
+
+ if (ifindex == 0)
+ {
+ msg(D_ROUTE|M_ERRNO, "%s: rtnl: cannot get ifindex for %s:",
+ __func__, iface);
+ return -ENOENT;
+ }
+
+ return net_iface_del_index(ifindex);
+}
+
+int
+net_iface_del_index(int ifindex)
+{
+ struct sitnl_link_req req = { };
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.n.nlmsg_type = RTM_DELLINK;
+
+ req.i.ifi_family = AF_PACKET;
+ req.i.ifi_index = ifindex;
+
+ return sitnl_send(&req.n, 0, 0, NULL, NULL);
+}
+
#endif /* !ENABLE_SITNL */
#endif /* TARGET_LINUX */
diff --git a/src/openvpn/networking_sitnl.h b/src/openvpn/networking_sitnl.h
index f040020e..b88ffd4b 100644
--- a/src/openvpn/networking_sitnl.h
+++ b/src/openvpn/networking_sitnl.h
@@ -25,4 +25,32 @@
typedef char openvpn_net_iface_t;
typedef void *openvpn_net_ctx_t;
+/**
+ * @brief Add new interface (similar to ip link add)
+ *
+ * @param iface interface name
+ * @param type interface link type (for example "ovpn-dco")
+ * @return int 0 on success, negative error code on error
+ */
+int
+net_iface_new(const char *iface, const char *type);
+
+/**
+ * @brief Remove an interface (similar to ip link remove)
+ *
+ * @param iface interface name
+ * @return int 0 on success, negative error code on error
+ */
+int
+net_iface_del_name(const char *iface);
+
+/**
+ * @brief Remove an interface (similar to ip link remove)
+ *
+ * @param ifindex interface index
+ * @return int 0 on success, negative error code on error
+ */
+int
+net_iface_del_index(int ifindex);
+
#endif /* NETWORKING_SITNL_H_ */
diff --git a/tests/unit_tests/openvpn/test_networking.c b/tests/unit_tests/openvpn/test_networking.c
index 9e9744f4..37b97188 100644
--- a/tests/unit_tests/openvpn/test_networking.c
+++ b/tests/unit_tests/openvpn/test_networking.c
@@ -13,6 +13,20 @@ net__iface_up(bool up)
return net_iface_up(NULL, iface, up);
}
+static int
+net__iface_new(const char *name, const char* type)
+{
+ printf("CMD: ip link add %s type %s\n", name, type);
+ return net_iface_new(name, type);
+}
+
+static int
+net__iface_del(const char *name)
+{
+ printf("CMD: ip link del %s\n", name);
+ return net_iface_del_name(name);
+}
+
static int
net__iface_mtu_set(int mtu)
{
@@ -191,7 +205,7 @@ net__route_v6_add_gw(const char *dst_str, int prefixlen, const char *gw_str,
static void
usage(char *name)
{
- printf("Usage: %s <0-7>\n", name);
+ printf("Usage: %s <0-9>\n", name);
}
int
@@ -243,6 +257,12 @@ main(int argc, char *argv[])
case 7:
return net__route_v6_add_gw("2001:cafe:babe::", 48, "2001::2", 600);
+ case 8:
+ return net__iface_new("dummy0815", "dummy");
+
+ case 9:
+ return net__iface_del("dummy0815");
+
default:
printf("invalid test: %d\n", test);
break;
From patchwork Tue Dec 7 12:11:33 2021
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Openvpn-devel,RFC,4/8] ovpn-dco: introduce linux data-channel
offload support
X-Patchwork-Submitter: Antonio Quartulli
X-Patchwork-Id: 2119
Message-Id: <20211207121137.3221-5-a@unstable.cc>
To: openvpn-devel@lists.sourceforge.net
Cc: Antonio Quartulli
Date: Tue, 7 Dec 2021 13:11:33 +0100
From: Antonio Quartulli
List-Id:
From: Arne Schwabe
Implement the data-channel offloading using the ovpn-dco kernel
module. See README.dco.md for more details.
Signed-off-by: Arne Schwabe
Signed-off-by: Antonio Quartulli
---
Changes.rst | 7 +
README.dco.md | 124 +++
configure.ac | 56 ++
doc/man-sections/advanced-options.rst | 13 +
src/openvpn/Makefile.am | 8 +-
src/openvpn/crypto.c | 10 +
src/openvpn/crypto.h | 6 +
src/openvpn/dco.c | 272 +++++++
src/openvpn/dco.h | 118 +++
src/openvpn/errlevel.h | 2 +
src/openvpn/event.h | 3 +
src/openvpn/forward.c | 66 +-
src/openvpn/init.c | 153 +++-
src/openvpn/init.h | 2 +-
src/openvpn/mtcp.c | 61 +-
src/openvpn/mudp.c | 13 +
src/openvpn/multi.c | 278 ++++++-
src/openvpn/multi.h | 6 +-
src/openvpn/networking.h | 1 +
src/openvpn/networking_linuxdco.c | 848 +++++++++++++++++++++
src/openvpn/networking_linuxdco.h | 85 +++
src/openvpn/openvpn.vcxproj | 4 +-
src/openvpn/openvpn.vcxproj.filters | 6 +
src/openvpn/options.c | 143 +++-
src/openvpn/options.h | 26 +
src/openvpn/socket.h | 1 +
src/openvpn/ssl.c | 6 +-
src/openvpn/ssl_common.h | 13 +
src/openvpn/ssl_ncp.c | 2 +-
src/openvpn/tun.c | 13 +-
src/openvpn/tun.h | 4 +
tests/unit_tests/openvpn/test_networking.c | 9 +-
32 files changed, 2305 insertions(+), 54 deletions(-)
create mode 100644 README.dco.md
create mode 100644 src/openvpn/dco.c
create mode 100644 src/openvpn/dco.h
create mode 100644 src/openvpn/networking_linuxdco.c
create mode 100644 src/openvpn/networking_linuxdco.h
diff --git a/Changes.rst b/Changes.rst
index b7d7f205..b613d656 100644
--- a/Changes.rst
+++ b/Changes.rst
@@ -62,6 +62,13 @@ Optional ciphers in ``--data-ciphers``
Ciphers in ``--data-ciphers`` can now be prefixed with a ``?`` to mark
those as optional and only use them if the SSL library supports them.
+Data channel offloading with ovpn-dco
+ 2.6.0+ implements support for data-channel offloading where the data packets
+ are directly processed and forwarded in kernel space thanks to the ovpn-dco
+ kernel module. The userspace openvpn program acts purely as a control plane
+ application.
+
+
Deprecated features
-------------------
``inetd`` has been removed
diff --git a/README.dco.md b/README.dco.md
new file mode 100644
index 00000000..e2500d36
--- /dev/null
+++ b/README.dco.md
@@ -0,0 +1,124 @@
+OpenVPN data channel offload
+============================
+2.6.0+ implements support for data-channel offloading where the data packets
+are directly processed and forwarded in kernel space thanks to the ovpn-dco
+kernel module. The userspace openvpn program acts purely as a control plane
+application.
+
+Overview of current release
+---------------------------
+- See the "Limitations by design" and "Current limitations" sections for
+ features that are not and/or will not be supported by OpenVPN + ovpn-dco
+- The dco branch is based on my working branch and contains a number of
+ other patch sets that are not yet merged into the OpenVPN master branch
+ (e.g. --peer-fingerprint).
+
+
+Getting started (Linux)
+-----------------------
+
+- Use a recent Linux kernel. Ubuntu 20.04 (Linux 5.4.0) and Ubuntu 20.10
+ (Linux 5.8.0) are known to work with ovpn-dco.
+
+Get the ovpn-dco module from one these urls and build it:
+
+* https://gitlab.com/openvpn/ovpn-dco
+* https://github.com/OpenVPN/ovpn-dco
+
+e.g.
+
+ git clone https://github.com/OpenVPN/ovpn-dco
+ cd ovpn-dco
+ make
+ sudo make install
+
+If you want to report bugs please ensure to compile ovpn-dco with
+`make DEBUG=1` and include any debug message being printed by the
+kernel (you can view those messages with `dmesg`).
+
+Clone OpenVPN and build dco branch. For example:
+
+ git clone -b dco https://github.com/schwabe/openvpn.git
+ cd openvpn
+ autoreconf -vi
+ ./configure --enable-dco
+ make
+ make install # Or run just src/openvpn/openvpn
+
+If you start openvpn it should automatically detect DCO support and use the
+kernel module. Add the option `--disable-dco` to disable data channel offload
+support. If the configuration contains an option that is incompatible with
+data channel offloading OpenVPN will automatically disable DCO support and
+warn the user.
+
+Should OpenVPN be configured to use a feature that is not supported by ovpn-dco
+or should the ovpn-dco kernel module not be available on the system, you will
+see a message like
+
+ Note: Kernel support for ovpn-dco missing, disabling data channel offload.
+
+in your log.
+
+
+DCO and P2P mode
+----------------
+DCO is also available when running OpenVPN in P2P mode without --pull/--client option.
+The P2P mode is useful for scenarios when the OpenVPN tunnel should not interfere with
+overall routing and behave more like a "dumb" tunnel like GRE.
+
+However, DCO requires DATA_V2 to be enabled this requires P2P with NCP capability, which
+is only available in OpenVPN 2.6 and later.
+
+OpenVPN prints a diagnostic message for the P2P NCP result when running in P2P mode:
+
+ P2P mode NCP negotiation result: TLS_export=1, DATA_v2=1, peer-id 9484735, cipher=AES-256-GCM
+
+Double check that your have `DATA_v2=1` in your output and a supported AEAD cipher
+(AES-GCM or Chacha20-Poly1305).
+
+Routing with ovpn-dco
+---------------------
+The ovpn-dco kernel module implements a more transparent approach to
+configuring routes to clients (aka 'iroutes') and consults the kernel
+routing tables for forwarding decisions.
+
+- Each client has an IPv4 VPN IP and/or an IPv6 assigned to it
+- Additional IP ranges can be routed to a client by adding a route with
+ a client VPN IP as the gateway/nexthop.
+- No internal routing (iroutes) is available. If you need truly internal
+ routes, this can be achieved either with filtering using `iptables` or
+ using `ip rule`.
+
+
+Limitations by design
+----------------------
+- Layer 3 (dev tun only)
+- only AEAD ciphers are supported and currently only
+ Chacha20-Poly1305 and AES-GCM-128/192/256
+- no support for compression or compression framing
+ - see also `--compress migrate` option to move to a setup with compression
+- various features not implemented since have better replacements
+ - --shaper, use tc instead
+ - packet manipulation, use nftables/iptables instead
+- OpenVPN 2.4.0 is the minimum peer version.
+ - older version are missing support for the AEAD ciphers
+- topology subnet is the only supported `--topology` for servers
+- iroute directives install routes on the host operating system, see also
+ routing with ovpn-dco
+
+Current limitations
+-------------------
+- --persistent-tun not tested/supported
+- fallback to non-dco in client mode missing
+- IPv6 mapped IPv4 addresses need Linux 5.12 to properly work
+- Some incompatible options may not properly fallback to non-dco
+- TCP performance with ovpn-dco can still exhibit bad behaviour and drop to a
+ few megabits per seconds.
+- Not all options that should trigger disabling DCO as they are incompatible
+ are currently identified. Some options that do not trigger disabling DCO
+ are ignored while other might yield unexpected results.
+- ovpn-dco currently does not implement RPF checks and will accept any source
+ IP from any client.
+- If a peer VPN IP is outside the default device subnet, the route needs to be
+ added manually.
+- No per client statistics. Only total statistics available on the interface.
diff --git a/configure.ac b/configure.ac
index e0f9c332..7d05d905 100644
--- a/configure.ac
+++ b/configure.ac
@@ -142,6 +142,13 @@ AC_ARG_ENABLE(
[enable_small="no"]
)
+AC_ARG_ENABLE(
+ [dco],
+ [AS_HELP_STRING([--enable-dco], [enable data channel offload support using ovpn-dco kernel module @<:@default=no@:>@])],
+ ,
+ [enable_dco="no"]
+)
+
AC_ARG_ENABLE(
[iproute2],
[AS_HELP_STRING([--enable-iproute2], [enable support for iproute2 @<:@default=no@:>@])],
@@ -766,6 +773,53 @@ PKG_CHECK_MODULES(
[]
)
+
+
+if test "$enable_dco" = "yes"; then
+dnl
+dnl Configure path for the ovpn-dco kernel module source directory.
+dnl
+dnl This is similar to the core librariy, there is an embedded
+dnl version in this tree which will be used by default. The
+dnl git checkout inside the ovpn-dco/ directory is managed via git
+dnl submodule.
+dnl
+AC_ARG_VAR([DCO_SOURCEDIR], [Alternative ovpn-dco kernel module source directory])
+if test -z "${DCO_SOURCEDIR}"; then
+ DCO_SOURCEDIR="${srcdir}/../ovpn-dco"
+fi
+AC_MSG_NOTICE([Using ovpn-dco source directory: ${DCO_SOURCEDIR}])
+AC_SUBST([DCO_SOURCEDIR])
+
+dnl
+dnl Include generic netlink library used to talk to ovpn-dco
+dnl
+ saved_CFLAGS="${CFLAGS}"
+ PKG_CHECK_MODULES(
+ [LIBNL_GENL],
+ [libnl-genl-3.0 >= 3.2.29],
+ [have_libnl="yes"],
+ [AC_MSG_ERROR([libnl-genl-3.0 package not found or too old. Is the development package and pkg-config installed? Must be version 3.4.0 or newer])]
+ )
+
+ DCO_CFLAGS="-I${DCO_SOURCEDIR}/include/uapi ${LIBNL_GENL_CFLAGS}"
+
+ CFLAGS="${CFLAGS} ${DCO_CFLAGS}"
+ AC_CHECK_HEADERS(
+ [linux/ovpn_dco.h],
+ ,
+ [AC_MSG_ERROR([linux/ovpn_dco.h is missing (use DCO_SOURCE to set path to it, CFLAGS=${CFLAGS})])]
+ )
+ CFLAGS=${saved_CFLAGS}
+ OPTIONAL_DCO_CFLAGS="${DCO_CFLAGS}"
+ OPTIONAL_DCO_LIBS="${LIBNL_GENL_LIBS}"
+
+ AC_DEFINE(ENABLE_LINUXDCO, 1, [Enable linux data channel offload])
+ AC_DEFINE(ENABLE_DCO, 1, [Enable shared data channel offload])
+fi
+AM_CONDITIONAL([ENABLE_OVPNDCO], [test "${enable_dco}" = "yes"])
+
+
if test "${with_crypto_library}" = "openssl"; then
AC_ARG_VAR([OPENSSL_CFLAGS], [C compiler flags for OpenSSL])
AC_ARG_VAR([OPENSSL_LIBS], [linker flags for OpenSSL])
@@ -1331,6 +1385,8 @@ AC_SUBST([OPTIONAL_PKCS11_HELPER_CFLAGS])
AC_SUBST([OPTIONAL_PKCS11_HELPER_LIBS])
AC_SUBST([OPTIONAL_INOTIFY_CFLAGS])
AC_SUBST([OPTIONAL_INOTIFY_LIBS])
+AC_SUBST([OPTIONAL_DCO_CFLAGS])
+AC_SUBST([OPTIONAL_DCO_LIBS])
AC_SUBST([PLUGIN_AUTH_PAM_CFLAGS])
AC_SUBST([PLUGIN_AUTH_PAM_LIBS])
diff --git a/doc/man-sections/advanced-options.rst b/doc/man-sections/advanced-options.rst
index 5157c561..cdec9502 100644
--- a/doc/man-sections/advanced-options.rst
+++ b/doc/man-sections/advanced-options.rst
@@ -91,3 +91,16 @@ used when debugging or testing out special usage scenarios.
*(Linux only)* Set the TX queue length on the TUN/TAP interface.
Currently defaults to operating system default.
+--disable-dco
+ Disables the opportunistic use the data channel offloading if available.
+ Without this option, OpenVPN will opportunistically use DCO mode if
+ the config options and the running kernel supports using DCO.
+
+ Data channel offload currently requires data-ciphers to only contain
+ AEAD ciphers (AES-GCM and Chacha20-Poly1305) and Linux with the
+ ovpn-dco module.
+
+ Note that some options have no effect or not available when
+ DCO mode is enabled.
+
+ A platforms that do not support DCO ``disable-dco`` has no effect.
diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index 5883c291..0cc06155 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -30,6 +30,7 @@ AM_CFLAGS = \
$(OPTIONAL_LZ4_CFLAGS) \
$(OPTIONAL_PKCS11_HELPER_CFLAGS) \
$(OPTIONAL_INOTIFY_CFLAGS) \
+ ${OPTIONAL_DCO_CFLAGS} \
-DPLUGIN_LIBDIR=\"${plugindir}\"
if WIN32
@@ -53,6 +54,7 @@ openvpn_SOURCES = \
crypto.c crypto.h crypto_backend.h \
crypto_openssl.c crypto_openssl.h \
crypto_mbedtls.c crypto_mbedtls.h \
+ dco.c dco.h \
dhcp.c dhcp.h \
env_set.c env_set.h \
errlevel.h \
@@ -85,7 +87,8 @@ openvpn_SOURCES = \
multi.c multi.h \
networking_iproute2.c networking_iproute2.h \
networking_sitnl.c networking_sitnl.h \
- networking.h \
+ networking_linuxdco.c networking_linuxdco.h \
+ networking.h \
ntlm.c ntlm.h \
occ.c occ.h \
openssl_compat.h \
@@ -141,7 +144,8 @@ openvpn_LDADD = \
$(OPTIONAL_SELINUX_LIBS) \
$(OPTIONAL_SYSTEMD_LIBS) \
$(OPTIONAL_DL_LIBS) \
- $(OPTIONAL_INOTIFY_LIBS)
+ $(OPTIONAL_INOTIFY_LIBS) \
+ $(OPTIONAL_DCO_LIBS)
if WIN32
openvpn_SOURCES += openvpn_win32_resources.rc block_dns.c block_dns.h ring_buffer.h
openvpn_LDADD += -lgdi32 -lws2_32 -lwininet -lcrypt32 -liphlpapi -lwinmm -lfwpuclnt -lrpcrt4 -lncrypt -lsetupapi
diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c
index a63a2619..57869c66 100644
--- a/src/openvpn/crypto.c
+++ b/src/openvpn/crypto.c
@@ -826,6 +826,16 @@ init_key_ctx(struct key_ctx *ctx, const struct key *key,
cipher_kt_iv_size(kt->cipher));
warn_insecure_key_type(ciphername, kt->cipher);
}
+
+#if defined(ENABLE_DCO)
+ /* Keep key material for DCO */
+ static_assert(sizeof(key->cipher) == sizeof(ctx->aead_key), "DCO key size mismatch");
+ if (kt->keep_key_data)
+ {
+ memcpy(ctx->aead_key, key->cipher, sizeof(ctx->aead_key));
+ }
+#endif
+
if (kt->digest)
{
ctx->hmac = hmac_ctx_new();
diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h
index 1e2ca3cb..bec6883c 100644
--- a/src/openvpn/crypto.h
+++ b/src/openvpn/crypto.h
@@ -140,6 +140,9 @@ struct key_type
{
const cipher_kt_t *cipher; /**< Cipher static parameters */
const md_kt_t *digest; /**< Message digest static parameters */
+#if defined(ENABLE_DCO)
+ bool keep_key_data;
+#endif
};
/**
@@ -166,6 +169,9 @@ struct key_ctx
uint8_t implicit_iv[OPENVPN_MAX_IV_LENGTH];
/**< The implicit part of the IV */
size_t implicit_iv_len; /**< The length of implicit_iv */
+#if defined(ENABLE_DCO)
+ uint8_t aead_key[MAX_CIPHER_KEY_LENGTH]; /**< Keeps the key data for use with DCO */
+#endif
};
#define KEY_DIRECTION_BIDIRECTIONAL 0 /* same keys for both directions */
diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
new file mode 100644
index 00000000..b04122b6
--- /dev/null
+++ b/src/openvpn/dco.c
@@ -0,0 +1,272 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single TCP/UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2021 Arne Schwabe
+ * Copyright (C) 2021 OpenVPN Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+
+#include "syshead.h"
+
+#include "errlevel.h"
+#include "networking.h"
+
+#include "multi.h"
+#include "dco.h"
+#include "networking_linuxdco.h"
+
+#if defined(ENABLE_DCO)
+#include "ssl_verify.h"
+
+bool
+dco_do_key_dance(struct tuntap *tt, struct tls_multi *multi, const char *ciphername)
+{
+ struct key_state *primary = tls_select_encryption_key(multi);
+ struct key_state *secondary = NULL;
+
+ for (int i = 0; i < KEY_SCAN_SIZE; ++i)
+ {
+ struct key_state *ks = get_key_scan(multi, i);
+ struct key_ctx_bi *key = &ks->crypto_options.key_ctx_bi;
+
+ if (ks == primary)
+ {
+ continue;
+ }
+
+ if (ks->state >= S_GENERATED_KEYS)
+ {
+ ASSERT(ks->authenticated == KS_AUTH_TRUE);
+ ASSERT(key->initialized);
+
+ secondary = ks;
+ }
+ }
+
+ if (!primary)
+ {
+ if (multi->dco_keys_installed >= 1)
+ {
+ msg(D_DCO, "DCO: No encryption key found. Purging data channel keys");
+ dco_del_key(tt, multi->peer_id, OVPN_KEY_SLOT_PRIMARY);
+ if (multi->dco_keys_installed == 2)
+ {
+ dco_del_key(tt, multi->peer_id, OVPN_KEY_SLOT_SECONDARY);
+ }
+ multi->dco_keys_installed = 2;
+ }
+ return false;
+ }
+
+ /* All keys installed as they should */
+ if (primary->dco_status == DCO_INSTALLED_PRIMARY
+ && (!secondary || secondary->dco_status == DCO_INSTALLED_SECONDARY))
+ {
+ /* Check if we have a previously installed secondary key */
+ if (!secondary && multi->dco_keys_installed == 2)
+ {
+ multi->dco_keys_installed = 1;
+ dco_del_key(tt, multi->peer_id, OVPN_KEY_SLOT_SECONDARY);
+ }
+ return true;
+ }
+
+ int pid = primary->key_id;
+ int sid = secondary ? secondary->key_id : -1;
+
+ msg(D_DCO_DEBUG, "Installing DCO data channel keys for peer %d, "
+ "primary key-id: %d, secondary key-id: %d.",
+ multi->peer_id, pid, sid);
+
+ /* General strategy, get primary key installed correctly first. If that is
+ * okay then check if we need to exchange secondary */
+ if (primary->dco_status != DCO_INSTALLED_PRIMARY)
+ {
+ /* ovpn-win-dco does not like to have the install as secondary and then
+ * swap to primary for the first key .... */
+ if (multi->dco_keys_installed == 0)
+ {
+ dco_new_key(tt, multi->peer_id, OVPN_KEY_SLOT_PRIMARY, primary, ciphername);
+ multi->dco_keys_installed = 1;
+ }
+ else
+ {
+ if (primary->dco_status != DCO_INSTALLED_SECONDARY)
+ {
+ dco_new_key(tt, multi->peer_id, OVPN_KEY_SLOT_SECONDARY, primary, ciphername);
+ }
+ dco_swap_keys(tt, multi->peer_id);
+ }
+ primary->dco_status = DCO_INSTALLED_PRIMARY;
+
+ /* if the secondary was installed as primary before the swap demoted
+ * it to secondary */
+ if (secondary && secondary->dco_status == DCO_INSTALLED_PRIMARY)
+ {
+ secondary->dco_status = DCO_INSTALLED_SECONDARY;
+ multi->dco_keys_installed = 2;
+ }
+ }
+
+ /* The primary key is now the correct key but the secondary key might
+ * already a new key that will be later promoted to primary key and we
+ * need to install the key */
+ if (secondary && secondary->dco_status != DCO_INSTALLED_SECONDARY)
+ {
+ dco_new_key(tt, multi->peer_id, OVPN_KEY_SLOT_SECONDARY, secondary, ciphername);
+ secondary->dco_status = DCO_INSTALLED_SECONDARY;
+ multi->dco_keys_installed = 2;
+ }
+ /* delete an expired key */
+ if (!secondary && multi->dco_keys_installed == 2)
+ {
+ dco_del_key(tt, multi->peer_id, OVPN_KEY_SLOT_SECONDARY);
+ multi->dco_keys_installed = 1;
+ }
+
+ /* All keys that we have not installed are set to NOT installed */
+ for (int i = 0; i < KEY_SCAN_SIZE; ++i)
+ {
+ struct key_state *ks = get_key_scan(multi, i);
+ if (ks != primary && ks != secondary)
+ {
+ ks->dco_status = DCO_NOT_INSTALLED;
+ }
+ }
+ return true;
+}
+
+int
+get_dco_cipher(const char *cipher)
+{
+ if (streq(cipher, "AES-256-GCM") || streq(cipher, "AES-128-GCM") ||
+ streq(cipher, "AES-192-GCM"))
+ return OVPN_CIPHER_ALG_AES_GCM;
+ else if (streq(cipher, "CHACHA20-POLY1305"))
+ {
+ return OVPN_CIPHER_ALG_CHACHA20_POLY1305;
+ }
+ else if (strcmp(cipher, "none") == 0)
+ {
+ return OVPN_CIPHER_ALG_NONE;
+ }
+ else
+ {
+ return -ENOTSUP;
+ }
+}
+#endif
+
+/* These methods are currently Linux specified but likely to be used any platform that implements Server side DCO */
+#if defined(ENABLE_LINUXDCO)
+
+void
+dco_install_iroute(struct multi_context *m, struct multi_instance *mi,
+ struct mroute_addr *addr, bool primary)
+{
+ if (!dco_enabled(&m->top.options))
+ {
+ return;
+ }
+
+ if (primary)
+ {
+ /* We do not want to install IP -> IP dev ovpn-dco0 */
+ return;
+ }
+
+ int addrtype = (addr->type & MR_ADDR_MASK);
+
+ /* If we do not have local IP addr to install, skip the route */
+ if ((addrtype == MR_ADDR_IPV6 && !mi->context.c2.push_ifconfig_ipv6_defined)
+ || (addrtype == MR_ADDR_IPV4 && !mi->context.c2.push_ifconfig_defined))
+ {
+ return;
+ }
+
+ struct context *c = &mi->context;
+ const char *dev = c->c1.tuntap->actual_name;
+
+ if (addrtype == MR_ADDR_IPV6)
+ {
+ net_route_v6_add(&m->top.net_ctx, &addr->v6.addr, addr->netbits,
+ &mi->context.c2.push_ifconfig_ipv6_local, dev, 0,
+ DCO_IROUTE_METRIC);
+ }
+ else if (addrtype == MR_ADDR_IPV4)
+ {
+ in_addr_t dest = htonl(addr->v4.addr);
+ net_route_v4_add(&m->top.net_ctx, &dest, addr->netbits,
+ &mi->context.c2.push_ifconfig_local, dev, 0,
+ DCO_IROUTE_METRIC);
+ }
+}
+
+void
+dco_delete_iroutes(struct multi_context *m, struct multi_instance *mi)
+{
+ if (!dco_enabled(&m->top.options))
+ {
+ return;
+ }
+ ASSERT(TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN);
+
+ struct context *c = &mi->context;
+ const char *dev = c->c1.tuntap->actual_name;
+
+ if (mi->context.c2.push_ifconfig_defined)
+ {
+ for (const struct iroute *ir = c->options.iroutes; ir != NULL; ir = ir->next)
+ {
+ net_route_v4_del(&m->top.net_ctx, &ir->network, ir->netbits,
+ &mi->context.c2.push_ifconfig_local, dev,
+ 0, DCO_IROUTE_METRIC);
+ }
+ }
+
+ if (mi->context.c2.push_ifconfig_ipv6_defined)
+ {
+ for (const struct iroute_ipv6 *ir6 = c->options.iroutes_ipv6; ir6 != NULL; ir6 = ir6->next)
+ {
+ net_route_v6_del(&m->top.net_ctx, &ir6->network, ir6->netbits,
+ &mi->context.c2.push_ifconfig_ipv6_local, dev,
+ 0, DCO_IROUTE_METRIC);
+ }
+ }
+}
+#else
+void
+dco_install_iroute(struct multi_context *m, struct multi_instance *mi,
+ struct mroute_addr *addr, bool primary)
+{
+}
+
+void
+dco_delete_iroutes(struct multi_context *m, struct multi_instance *mi)
+{
+}
+#endif
\ No newline at end of file
diff --git a/src/openvpn/dco.h b/src/openvpn/dco.h
new file mode 100644
index 00000000..1cfdc8fb
--- /dev/null
+++ b/src/openvpn/dco.h
@@ -0,0 +1,118 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single TCP/UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2021 Arne Schwabe
+ * Copyright (C) 2021 OpenVPN Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef DCO_H
+#define DCO_H
+
+/* forward declarations, including multi.h leads to nasty include
+ * order problems */
+struct multi_context;
+struct tls_multi;
+struct multi_instance;
+struct mroute_addr;
+struct tuntap;
+
+#if !defined(ENABLE_DCO)
+/* Define dummy type for dco context if DCO is not enabled */
+typedef void *dco_context_t;
+
+static inline void open_tun_dco(struct tuntap *tt, const char* dev) { ASSERT(false); }
+
+static inline void close_tun_dco(struct tuntap *tt) { ASSERT(false); }
+#else
+#include "networking_linuxdco.h"
+#include "crypto.h"
+
+/* forward declarations */
+struct tuntap;
+struct key_state;
+
+void open_tun_dco(struct tuntap *tt, const char* dev);
+
+void close_tun_dco(struct tuntap *tt);
+
+int dco_new_peer(struct tuntap *tt, unsigned int peerid, int sd,
+ struct sockaddr *localaddr, struct sockaddr *remoteaddr,
+ struct in_addr *remote_in4, struct in6_addr *remote_in6);
+
+
+int ovpn_set_peer(struct tuntap *tt, unsigned int peerid,
+ unsigned int keepalive_interval,
+ unsigned int keepalive_timeout);
+
+int ovpn_do_read_dco(struct dco_context *dco);
+int dco_del_peer(struct tuntap *tt, unsigned int peerid);
+
+int
+dco_del_key(struct tuntap *tt, unsigned int peerid, ovpn_key_slot_t slot);
+
+int
+dco_new_key(struct tuntap *tt, unsigned int peerid, ovpn_key_slot_t slot,
+ struct key_state *ks, const char* ciphername);
+
+int dco_swap_keys(struct tuntap *tt, unsigned int peerid);
+
+/**
+ * Does the assertions that the key material is indeed sane.
+ * @param key the key context to be checked
+ */
+static inline void dco_check_key_ctx(const struct key_ctx_bi *key)
+{
+ ASSERT(key->initialized);
+ /* Double check that we do not have empty keys */
+ const uint8_t empty_key[32] = { 0 };
+ ASSERT(memcmp(key->encrypt.aead_key, empty_key, 32));
+ ASSERT(memcmp(key->decrypt.aead_key, empty_key, 32));
+}
+
+#endif
+
+void
+dco_install_iroute(struct multi_context *m, struct multi_instance *mi,
+ struct mroute_addr *addr, bool primary);
+
+void
+dco_delete_iroutes(struct multi_context *m, struct multi_instance *mi);
+
+
+/**
+ * This function will check if the encryption and decryption keys are installed
+ * to the data channel offload and if not do the necessary steps to ensure that
+ * openvpn and data channel are synced again
+ *
+ * @param tt Tun tap device context
+ * @param multi TLS multi instance
+ * @param ciphername Ciphername to use when installing the keys.
+ * @return
+ */
+bool
+dco_do_key_dance(struct tuntap *tt, struct tls_multi *multi, const char *ciphername);
+
+/**
+ * Translates an OpenVPN Cipher string to a supported cipher enum from DCO
+ * @param cipher OpenVPN cipher string
+ * @return constant that defines the cipher or -ENOTSUP if not supported
+ */
+int get_dco_cipher(const char *cipher);
+#endif
\ No newline at end of file
diff --git a/src/openvpn/errlevel.h b/src/openvpn/errlevel.h
index 602e48a8..a7054647 100644
--- a/src/openvpn/errlevel.h
+++ b/src/openvpn/errlevel.h
@@ -91,6 +91,7 @@
#define D_OSBUF LOGLEV(3, 43, 0) /* show socket/tun/tap buffer sizes */
#define D_PS_PROXY LOGLEV(3, 44, 0) /* messages related to --port-share option */
#define D_IFCONFIG LOGLEV(3, 0, 0) /* show ifconfig info (don't mute) */
+#define D_DCO LOGLEV(3, 0, 0) /* show DCO related messages */
#define D_SHOW_PARMS LOGLEV(4, 50, 0) /* show all parameters on program initiation */
#define D_SHOW_OCC LOGLEV(4, 51, 0) /* show options compatibility string */
@@ -113,6 +114,7 @@
#define D_TUN_RW LOGLEV(6, 69, M_DEBUG) /* show TUN/TAP reads/writes */
#define D_TAP_WIN_DEBUG LOGLEV(6, 69, M_DEBUG) /* show TAP-Windows driver debug info */
#define D_CLIENT_NAT LOGLEV(6, 69, M_DEBUG) /* show client NAT debug info */
+#define D_DCO_DEBUG LOGLEV(6, 69, M_DEBUG) /* show DCO related lowlevel debug messages */
#define D_SHOW_KEYS LOGLEV(7, 70, M_DEBUG) /* show data channel encryption keys */
#define D_SHOW_KEY_SOURCE LOGLEV(7, 70, M_DEBUG) /* show data channel key source entropy */
diff --git a/src/openvpn/event.h b/src/openvpn/event.h
index d67d69f6..fc815a59 100644
--- a/src/openvpn/event.h
+++ b/src/openvpn/event.h
@@ -72,6 +72,9 @@
#define MANAGEMENT_WRITE (1 << (MANAGEMENT_SHIFT + WRITE_SHIFT))
#define FILE_SHIFT 8
#define FILE_CLOSED (1 << (FILE_SHIFT + READ_SHIFT))
+#define DCO_SHIFT 10
+#define DCO_READ (1 << (DCO_SHIFT + READ_SHIFT))
+#define DCO_WRITE (1 << (DCO_SHIFT + WRITE_SHIFT))
/*
* Initialization flags passed to event_set_init
diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index 41ef12e3..02aa6659 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -41,6 +41,7 @@
#include "dhcp.h"
#include "common.h"
#include "ssl_verify.h"
+#include "dco.h"
#include "memdbg.h"
@@ -50,7 +51,6 @@ counter_type link_read_bytes_global; /* GLOBAL */
counter_type link_write_bytes_global; /* GLOBAL */
/* show event wait debugging info */
-
#ifdef ENABLE_DEBUG
static const char *
@@ -139,6 +139,22 @@ context_reschedule_sec(struct context *c, int sec)
c->c2.timeval.tv_usec = 0;
}
}
+#if defined(ENABLE_DCO)
+static void
+check_dco_key_status(struct context *c)
+{
+ /* DCO context is not yet initialised or enabled */
+ if ((!c->c2.did_open_tun && !c->c1.tuntap)|| !dco_enabled(&c->options))
+ {
+ return;
+ }
+
+ struct tls_multi *multi = c->c2.tls_multi;
+
+ dco_do_key_dance(c->c1.tuntap, multi, c->options.ciphername);
+}
+#endif
+
/*
* In TLS mode, let TLS level respond to any control-channel
@@ -181,6 +197,13 @@ check_tls(struct context *c)
}
interval_schedule_wakeup(&c->c2.tmp_int, &wakeup);
+#if defined(ENABLE_DCO)
+ /* Our current code has no good hooks in the TLS machinery to install
+ * the keys to DCO. So we check/install keys after the whole TLS
+ * machinery has been completed
+ */
+ check_dco_key_status(c);
+#endif
if (wakeup)
{
@@ -1084,6 +1107,15 @@ process_incoming_link(struct context *c)
perf_pop();
}
+#if defined(ENABLE_LINUXDCO)
+static void
+process_incoming_dco(struct context *c)
+{
+ msg(M_INFO, __func__);
+ ovpn_do_read_dco(&c->c1.tuntap->dco);
+}
+#endif
+
/*
* Output: c->c2.buf
*/
@@ -1605,9 +1637,17 @@ process_outgoing_link(struct context *c)
socks_preprocess_outgoing_link(c, &to_addr, &size_delta);
/* Send packet */
- size = link_socket_write(c->c2.link_socket,
- &c->c2.to_link,
- to_addr);
+#if defined(ENABLE_LINUXDCO)
+ if (c->c2.link_socket->info.dco_installed)
+ {
+ size = ovpn_do_write_dco(&c->c1.tuntap->dco, c->c2.tls_multi->peer_id, &c->c2.to_link);
+ }
+ else
+#endif
+ {
+ size = link_socket_write(c->c2.link_socket, &c->c2.to_link,
+ to_addr);
+ }
/* Undo effect of prepend */
link_socket_write_post_size_adjust(&size, size_delta, &c->c2.to_link);
@@ -1870,6 +1910,9 @@ io_wait_dowork(struct context *c, const unsigned int flags)
#ifdef ENABLE_ASYNC_PUSH
static int file_shift = FILE_SHIFT;
#endif
+#ifdef ENABLE_LINUXDCO
+ static int dco_shift = 10; /* Event from DCO module */
+#endif
/*
* Decide what kind of events we want to wait for.
@@ -1977,6 +2020,12 @@ io_wait_dowork(struct context *c, const unsigned int flags)
*/
socket_set(c->c2.link_socket, c->c2.event_set, socket, (void *)&socket_shift, NULL);
tun_set(c->c1.tuntap, c->c2.event_set, tuntap, (void *)&tun_shift, NULL);
+#if defined(ENABLE_LINUXDCO)
+ if (socket & EVENT_READ && c->c2.did_open_tun)
+ {
+ dco_event_set(&c->c1.tuntap->dco, c->c2.event_set, (void *) &dco_shift);
+ }
+#endif
#ifdef ENABLE_MANAGEMENT
if (management)
@@ -2099,4 +2148,13 @@ process_io(struct context *c)
process_incoming_tun(c);
}
}
+#if defined(ENABLE_LINUXDCO)
+ else if (status & DCO_READ)
+ {
+ if(!IS_SIG(c))
+ {
+ process_incoming_dco(c);
+ }
+ }
+#endif
}
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 4fee7f49..4668fd04 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -53,6 +53,7 @@
#include "tls_crypt.h"
#include "forward.h"
#include "auth_token.h"
+#include "dco.h"
#include "memdbg.h"
@@ -1367,15 +1368,25 @@ do_init_timers(struct context *c, bool deferred)
}
/* initialize pings */
-
- if (c->options.ping_send_timeout)
+#if defined(ENABLE_DCO)
+ if (dco_enabled(&c->options))
{
- event_timeout_init(&c->c2.ping_send_interval, c->options.ping_send_timeout, 0);
+ /* The DCO kernel module will send the pings instead of user space */
+ event_timeout_clear(&c->c2.ping_rec_interval);
+ event_timeout_clear(&c->c2.ping_send_interval);
}
-
- if (c->options.ping_rec_timeout)
+ else
+#endif
{
- event_timeout_init(&c->c2.ping_rec_interval, c->options.ping_rec_timeout, now);
+ if (c->options.ping_send_timeout)
+ {
+ event_timeout_init(&c->c2.ping_send_interval, c->options.ping_send_timeout, 0);
+ }
+
+ if (c->options.ping_rec_timeout)
+ {
+ event_timeout_init(&c->c2.ping_rec_interval, c->options.ping_rec_timeout, now);
+ }
}
if (!deferred)
@@ -2097,6 +2108,7 @@ tun_abort(void)
* Handle delayed tun/tap interface bringup due to --up-delay or --pull
*/
+
/**
* Helper for do_up(). Take two option hashes and return true if they are not
* equal, or either one is all-zeroes.
@@ -2110,6 +2122,85 @@ options_hash_changed_or_zero(const struct sha256_digest *a,
|| !memcmp(a, &zero, sizeof(struct sha256_digest));
}
+#ifdef ENABLE_DCO
+static bool
+p2p_dco_add_new_peer(struct context *c)
+{
+ struct tls_multi *multi = c->c2.tls_multi;
+ struct link_socket *ls = c->c2.link_socket;
+
+ if (!dco_enabled(&c->options))
+ {
+ return true;
+ }
+
+ struct in6_addr remote_ip6 = { 0 };
+ struct in_addr remote_ip4 = { 0 };
+
+ struct in6_addr *remote_addr6 = NULL;
+ struct in_addr *remote_addr4 = NULL;
+
+ const char* gw = NULL;
+ /* In client mode if a P2P style topology is used we assume the
+ * remote-gateway is the IP of the peer */
+ if (c->options.topology == TOP_NET30 || c->options.topology == TOP_P2P)
+ {
+ gw = c->options.ifconfig_remote_netmask;
+ }
+ if (c->options.route_default_gateway)
+ {
+ gw = c->options.route_default_gateway;
+ }
+
+ /* These inet_pton conversion are fatal since options.c already implements
+ * checks to have only valid addresses when setting the options */
+ if (c->options.ifconfig_ipv6_remote)
+ {
+ if (inet_pton(AF_INET6, c->options.ifconfig_ipv6_remote, &remote_ip6) != 1)
+ {
+ msg(M_FATAL,
+ "DCO peer init: problem converting IPv6 ifconfig remote address %s to binary",
+ c->options.ifconfig_ipv6_remote);
+ }
+ remote_addr6 = &remote_ip6;
+ }
+
+ if (gw)
+ {
+ if (inet_pton(AF_INET, gw, &remote_ip4) != 1)
+ {
+ msg(M_FATAL, "DCO peer init: problem converting IPv4 ifconfig gateway address %s to binary", gw);
+ }
+ remote_addr4 = &remote_ip4;
+ }
+ else if (c->options.ifconfig_local)
+ {
+ msg(M_INFO, "DCO peer init: Need a peer VPN addresss to setup IPv4 (set --route-gateway)");
+ }
+
+ if (!c->c2.link_socket->info.dco_installed)
+ {
+ ASSERT(ls->info.connection_established);
+
+ struct sockaddr *remoteaddr = &ls->info.lsa->actual.dest.addr.sa;
+
+ dco_new_peer(c->c1.tuntap, multi->peer_id, c->c2.link_socket->sd, NULL,
+ remoteaddr, remote_addr4, remote_addr6);
+
+ c->c2.tls_multi->dco_peer_added = true;
+ c->c2.link_socket->info.dco_installed = true;
+ }
+
+ if (c->options.ping_send_timeout)
+ {
+ ovpn_set_peer(c->c1.tuntap, multi->peer_id, c->options.ping_send_timeout,
+ c->options.ping_rec_timeout);
+ }
+
+ return true;
+}
+#endif
+
bool
do_up(struct context *c, bool pulled_options, unsigned int option_types_found)
{
@@ -2161,6 +2252,11 @@ do_up(struct context *c, bool pulled_options, unsigned int option_types_found)
if (c->c2.did_open_tun)
{
+ /* If we are in DCO mode we need to set the new peer options now */
+#if defined(ENABLE_DCO)
+ p2p_dco_add_new_peer(c);
+#endif
+
c->c1.pulled_options_digest_save = c->c2.pulled_options_digest;
/* if --route-delay was specified, start timer */
@@ -2288,6 +2384,18 @@ do_deferred_p2p_ncp(struct context *c)
return true;
}
+
+static bool check_dco_pull_options(struct options *o)
+{
+ if (!o->use_peer_id)
+ {
+ msg(D_TLS_ERRORS, "OPTIONS IMPORT: Server did not request DATA_V2 packet "
+ "format required for data channel offloading");
+ return false;
+ }
+ return true;
+}
+
/*
* Handle non-tun-related pulled options.
*/
@@ -2400,8 +2508,17 @@ do_deferred_options(struct context *c, const unsigned int found)
msg(D_TLS_ERRORS, "OPTIONS ERROR: failed to import crypto options");
return false;
}
- }
+ /* Check if the pushed options are compatible with DCO if we have DCO
+ * enabled */
+ if (dco_enabled(&c->options) && !check_dco_pull_options(&c->options))
+ {
+ msg(D_TLS_ERRORS, "OPTIONS ERROR: pushed options are incompatible with "
+ "data channel offloading. Use --disable-dco to connect"
+ "to this server");
+ return false;
+ }
+ }
return true;
}
@@ -4332,6 +4449,24 @@ sig:
close_context(c, -1, flags);
return;
}
+#if defined(ENABLE_LINUXDCO)
+static void remove_dco_peer(struct context *c)
+{
+ if (!dco_enabled(&c->options))
+ {
+ return;
+ }
+ if (c->c1.tuntap && c->c2.tls_multi && c->c2.tls_multi->dco_peer_added)
+ {
+ c->c2.tls_multi->dco_peer_added = false;
+ dco_del_peer(c->c1.tuntap, c->c2.tls_multi->peer_id);
+ }
+}
+#else
+static void remove_dco_peer(struct context *c)
+{
+}
+#endif
/*
* Close a tunnel instance.
@@ -4358,6 +4493,10 @@ close_instance(struct context *c)
/* free buffers */
do_close_free_buf(c);
+ /* close peer for DCO if enabled, needs peer-id so must be done before
+ * closing TLS contexts */
+ remove_dco_peer(c);
+
/* close TLS */
do_close_tls(c);
diff --git a/src/openvpn/init.h b/src/openvpn/init.h
index 52581f8a..5c719117 100644
--- a/src/openvpn/init.h
+++ b/src/openvpn/init.h
@@ -30,7 +30,7 @@
* Baseline maximum number of events
* to wait for.
*/
-#define BASE_N_EVENTS 4
+#define BASE_N_EVENTS 5
void context_clear(struct context *c);
diff --git a/src/openvpn/mtcp.c b/src/openvpn/mtcp.c
index bf9b5190..e335dcef 100644
--- a/src/openvpn/mtcp.c
+++ b/src/openvpn/mtcp.c
@@ -61,6 +61,7 @@
#define MTCP_SIG ((void *)3) /* Only on Windows */
#define MTCP_MANAGEMENT ((void *)4)
#define MTCP_FILE_CLOSE_WRITE ((void *)5)
+#define MTCP_DCO ((void *)6)
#define MTCP_N ((void *)16) /* upper bound on MTCP_x */
@@ -123,6 +124,8 @@ multi_create_instance_tcp(struct multi_context *m)
struct hash *hash = m->hash;
mi = multi_create_instance(m, NULL);
+ multi_assign_peer_id(m, mi);
+
if (mi)
{
struct hash_element *he;
@@ -236,6 +239,7 @@ multi_tcp_dereference_instance(struct multi_tcp *mtcp, struct multi_instance *mi
if (ls && mi->socket_set_called)
{
event_del(mtcp->es, socket_event_handle(ls));
+ mi->socket_set_called = false;
}
mtcp->n_esr = 0;
}
@@ -277,6 +281,9 @@ multi_tcp_wait(const struct context *c,
}
#endif
tun_set(c->c1.tuntap, mtcp->es, EVENT_READ, MTCP_TUN, persistent);
+#if defined(ENABLE_LINUXDCO)
+ dco_event_set(&c->c1.tuntap->dco, mtcp->es, MTCP_DCO);
+#endif
#ifdef ENABLE_MANAGEMENT
if (management)
@@ -393,6 +400,20 @@ multi_tcp_wait_lite(struct multi_context *m, struct multi_instance *mi, const in
tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */
+#if defined(ENABLE_LINUXDCO)
+ if (mi && mi->context.c2.link_socket->info.dco_installed)
+ {
+ /* If we got a socket that has been handed over to the kernel
+ * we must not call the normal socket function to figure out
+ * if it is readable or writable */
+ /* Assert that we only have the DCO exptected flags */
+ ASSERT(action & (TA_SOCKET_READ | TA_SOCKET_WRITE));
+
+ /* We are always ready! */
+ return action;
+ }
+#endif
+
switch (action)
{
case TA_TUN_READ:
@@ -516,7 +537,10 @@ multi_tcp_dispatch(struct multi_context *m, struct multi_instance *mi, const int
case TA_INITIAL:
ASSERT(mi);
- multi_tcp_set_global_rw_flags(m, mi);
+ if (!mi->context.c2.link_socket->info.dco_installed)
+ {
+ multi_tcp_set_global_rw_flags(m, mi);
+ }
multi_process_post(m, mi, mpp_flags);
break;
@@ -566,7 +590,10 @@ multi_tcp_post(struct multi_context *m, struct multi_instance *mi, const int act
}
else
{
- multi_tcp_set_global_rw_flags(m, mi);
+ if (!c->c2.link_socket->info.dco_installed)
+ {
+ multi_tcp_set_global_rw_flags(m, mi);
+ }
}
break;
@@ -623,23 +650,22 @@ multi_tcp_action(struct multi_context *m, struct multi_instance *mi, int action,
/*
* Dispatch the action
*/
- {
- struct multi_instance *touched = multi_tcp_dispatch(m, mi, action);
+ struct multi_instance *touched = multi_tcp_dispatch(m, mi, action);
- /*
- * Signal received or TCP connection
- * reset by peer?
- */
- if (touched && IS_SIG(&touched->context))
+ /*
+ * Signal received or TCP connection
+ * reset by peer?
+ */
+ if (touched && IS_SIG(&touched->context))
+ {
+ if (mi == touched)
{
- if (mi == touched)
- {
- mi = NULL;
- }
- multi_close_instance_on_signal(m, touched);
+ mi = NULL;
}
+ multi_close_instance_on_signal(m, touched);
}
+
/*
* If dispatch produced any pending output
* for a particular instance, point to
@@ -737,6 +763,13 @@ multi_tcp_process_io(struct multi_context *m)
multi_tcp_action(m, mi, TA_INITIAL, false);
}
}
+#if defined(ENABLE_LINUXDCO)
+ /* incoming data on DCO? */
+ else if (e->arg == MTCP_DCO)
+ {
+ multi_process_incoming_dco(m);
+ }
+#endif
/* signal received? */
else if (e->arg == MTCP_SIG)
{
diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index 07e2caae..1fc391fb 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -227,6 +227,19 @@ multi_process_io_udp(struct multi_context *m)
multi_process_file_closed(m, mpp_flags);
}
#endif
+#ifdef ENABLE_LINUXDCO
+ else if (status & DCO_READ)
+ {
+ if(!IS_SIG(&m->top))
+ {
+ bool ret = true;
+ while (ret)
+ {
+ ret = multi_process_incoming_dco(m);
+ }
+ }
+ }
+#endif
}
/*
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 103e882e..650804ea 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -51,6 +51,10 @@
#include "crypto_backend.h"
#include "ssl_util.h"
+#include "dco.h"
+
+static void multi_signal_instance(struct multi_context *m, struct multi_instance *mi, const int sig);
+
/*#define MULTI_DEBUG_EVENT_LOOP*/
@@ -519,6 +523,9 @@ multi_del_iroutes(struct multi_context *m,
{
const struct iroute *ir;
const struct iroute_ipv6 *ir6;
+
+ dco_delete_iroutes(m, mi);
+
if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
{
for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next)
@@ -1224,16 +1231,17 @@ multi_learn_in_addr_t(struct multi_context *m,
addr.netbits = (uint8_t) netbits;
}
- {
- struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);
+ struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);
#ifdef ENABLE_MANAGEMENT
- if (management && owner)
- {
- management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
- }
-#endif
- return owner;
+ if (management && owner)
+ {
+ management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
}
+#endif
+
+ dco_install_iroute(m, mi, &addr, primary);
+
+ return owner;
}
static struct multi_instance *
@@ -1257,16 +1265,20 @@ multi_learn_in6_addr(struct multi_context *m,
mroute_addr_mask_host_bits( &addr );
}
- {
- struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);
+ struct multi_instance *owner = multi_learn_addr(m, mi, &addr, 0);
#ifdef ENABLE_MANAGEMENT
- if (management && owner)
- {
- management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
- }
+ if (management && owner)
+ {
+ management_learn_addr(management, &mi->context.c2.mda_context, &addr, primary);
+ }
#endif
- return owner;
+ if (!primary)
+ {
+ /* We do not want to install IP -> IP dev ovpn-dco0 */
+ dco_install_iroute(m, mi, &addr, primary);
}
+
+ return owner;
}
/*
@@ -1764,6 +1776,15 @@ multi_client_set_protocol_options(struct context *c)
{
tls_multi->use_peer_id = true;
}
+ else if (dco_enabled(o))
+ {
+ msg(M_INFO, "Client does not support DATA_V2. Data channel offloaing "
+ "requires DATA_V2. Dropping client.");
+ auth_set_client_reason(tls_multi, "Data channel negotiation "
+ "failed (missing DATA_V2)");
+ return false;
+ }
+
if (proto & IV_PROTO_REQUEST_PUSH)
{
c->c2.push_request_received = true;
@@ -1775,7 +1796,6 @@ multi_client_set_protocol_options(struct context *c)
o->data_channel_crypto_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT;
}
#endif
-
/* Select cipher if client supports Negotiable Crypto Parameters */
/* if we have already created our key, we cannot *change* our own
@@ -2271,12 +2291,127 @@ cleanup:
return ret;
}
+#if defined(ENABLE_LINUXDCO)
+#include
+
+static struct sockaddr *
+multi_dco_get_localaddr(struct multi_context *m, struct multi_instance *mi,
+ struct gc_arena *gc)
+{
+ struct context *c = &mi->context;
+
+ if ((c->options.sockflags & SF_USE_IP_PKTINFO))
+ {
+ struct link_socket_actual *actual = &c->c2.link_socket_info->lsa->actual;
+
+ switch(actual->dest.addr.sa.sa_family)
+ {
+ case AF_INET:
+ {
+ struct sockaddr_in *sock_in4;
+ ALLOC_OBJ_CLEAR_GC(sock_in4, struct sockaddr_in, gc);
+ sock_in4->sin_addr = actual->pi.in4.ipi_addr;
+ sock_in4->sin_family = AF_INET;
+ return (struct sockaddr *) sock_in4;
+ }
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sock_in6;
+ ALLOC_OBJ_CLEAR_GC(sock_in6, struct sockaddr_in6, gc);
+ sock_in6->sin6_addr = actual->pi.in6.ipi6_addr;
+ sock_in6->sin6_family = AF_INET6;
+ return (struct sockaddr *) sock_in6;
+ }
+ default:
+ ASSERT(0);
+ }
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+static bool
+multi_dco_add_new_peer(struct multi_context *m, struct multi_instance *mi)
+{
+ struct context *c = &mi->context;
+
+ int peer_id = mi->context.c2.tls_multi->peer_id;
+ int sd = c->c2.link_socket->sd;
+ struct sockaddr *remoteaddr;
+ struct sockaddr *local;
+
+ if (c->mode == CM_CHILD_TCP)
+ {
+ /* the remote address will be inferred from the TCP socket endpoint */
+ remoteaddr = NULL;
+ }
+ else
+ {
+ ASSERT(c->c2.link_socket_info->connection_established);
+ remoteaddr = &c->c2.link_socket_info->lsa->actual.dest.addr.sa;
+ }
+
+ struct in_addr remote_ip4 = { 0 };
+ struct in6_addr *remote_addr6 = NULL;
+ struct in_addr *remote_addr4 = NULL;
+ struct gc_arena gc = gc_new();
+
+ /* In server mode we need to fetch the remote addresses from the push config */
+ if (c->c2.push_ifconfig_defined)
+ {
+ remote_ip4.s_addr = htonl(c->c2.push_ifconfig_local);
+ remote_addr4 = &remote_ip4;
+ }
+ if (c->c2.push_ifconfig_ipv6_defined)
+ {
+ remote_addr6 = &c->c2.push_ifconfig_ipv6_local;
+ }
+
+ local = multi_dco_get_localaddr(m, mi, &gc);
+
+ if (dco_new_peer(c->c1.tuntap, peer_id, sd, local, remoteaddr,
+ remote_addr4, remote_addr6) != 0)
+ {
+ gc_free(&gc);
+ return false;
+ }
+
+ c->c2.tls_multi->dco_peer_added = true;
+
+ dco_do_key_dance(c->c1.tuntap, c->c2.tls_multi, c->options.ciphername);
+
+ if (c->options.ping_send_timeout)
+ {
+ ovpn_set_peer(c->c1.tuntap, peer_id, c->options.ping_send_timeout,
+ c->options.ping_rec_timeout);
+ }
+
+ if (c->mode == CM_CHILD_TCP )
+ {
+ multi_tcp_dereference_instance(m->mtcp, mi);
+ if (close(sd))
+ {
+ msg(D_DCO|M_ERRNO, "error closing TCP socket after DCO handover");
+ }
+ c->c2.link_socket->info.dco_installed = true;
+ c->c2.link_socket->sd = SOCKET_UNDEFINED;
+ }
+
+ gc_free(&gc);
+
+ return true;
+}
+#endif
+
/**
* Generates the data channel keys
*/
static bool
-multi_client_generate_tls_keys(struct context *c)
+multi_client_generate_tls_keys(struct multi_context *m, struct multi_instance *mi)
{
+ struct context *c = &mi->context;
struct frame *frame_fragment = NULL;
#ifdef ENABLE_FRAGMENT
if (c->options.ce.fragment)
@@ -2293,6 +2428,13 @@ multi_client_generate_tls_keys(struct context *c)
return false;
}
+#if defined(ENABLE_LINUXDCO)
+ if (dco_enabled(&c->options) && !multi_dco_add_new_peer(m, mi))
+ {
+ return false;
+ }
+#endif
+
return true;
}
@@ -2399,7 +2541,7 @@ multi_client_connect_late_setup(struct multi_context *m,
}
/* Generate data channel keys only if setting protocol options
* has not failed */
- else if (!multi_client_generate_tls_keys(&mi->context))
+ else if (!multi_client_generate_tls_keys(m, mi))
{
mi->context.c2.tls_multi->multi_state = CAS_FAILED;
}
@@ -2659,6 +2801,14 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
(*cur_handler_index)++;
}
+ /* Check if we have forbidding options in the current mode */
+ if (dco_enabled(&mi->context.options)
+ && check_option_conflict_dco(D_MULTI_ERRORS, &mi->context.options))
+ {
+ msg(D_MULTI_ERRORS, "MULTI: client has been rejected due to incompatible options");
+ cc_succeeded = false;
+ }
+
if (cc_succeeded)
{
multi_client_connect_late_setup(m, mi, *option_types_found);
@@ -3077,6 +3227,98 @@ done:
gc_free(&gc);
}
+
+#if defined(ENABLE_LINUXDCO)
+
+static void
+process_incoming_dco_packet(struct multi_context *m, struct multi_instance *mi, dco_context_t *dco)
+{
+ struct buffer orig_buf = mi->context.c2.buf;
+ int peer_id = dco->dco_meesage_peer_id;
+
+ mi->context.c2.buf = dco->dco_packet_in;
+
+ multi_process_incoming_link(m, mi, 0);
+
+ mi->context.c2.buf = orig_buf;
+ if (BLEN(&dco->dco_packet_in) < 1)
+ {
+ msg(D_DCO, "Received too short packet for peer %d" , peer_id);
+ goto done;
+ }
+
+ uint8_t *ptr = BPTR(&dco->dco_packet_in);
+ uint8_t op = ptr[0] >> P_OPCODE_SHIFT;
+ if (op == P_DATA_V2 || op == P_DATA_V2)
+ {
+ msg(D_DCO, "DCO: received data channel packet for peer %d" , peer_id);
+ goto done;
+ }
+ done:
+ buf_init(&dco->dco_packet_in, 0);
+}
+
+static void
+process_incoming_del_peer(struct multi_context *m, struct multi_instance *mi, dco_context_t *dco)
+{
+ const char *reason = "(unknown reason by ovpn-dco)";
+ switch (dco->dco_del_peer_reason)
+ {
+ case OVPN_DEL_PEER_REASON_EXPIRED:
+ reason = "ovpn-dco: ping expired";
+ break;
+ case OVPN_DEL_PEER_REASON_TRANSPORT_ERROR:
+ reason = "ovpn-dco: transport error";
+ break;
+ case OVPN_DEL_PEER_REASON_USERSPACE:
+ /* This very likely ourselves but might be another process, so
+ * still process it */
+ reason = "ovpn-dco: userspace request";
+ break;
+ }
+
+ /* When kernel already deleted the peer, the socket is no longer
+ * installed and we don't need to cleanup the state in the kernel */
+ mi->context.c2.tls_multi->dco_peer_added = false;
+ mi->context.sig->signal_text = reason;
+ multi_signal_instance(m, mi, SIGTERM);
+
+}
+
+bool
+multi_process_incoming_dco(struct multi_context *m)
+{
+ dco_context_t *dco = &m->top.c1.tuntap->dco;
+
+ struct multi_instance *mi = NULL;
+
+ int ret = ovpn_do_read_dco(&m->top.c1.tuntap->dco);
+
+ int peer_id = dco->dco_meesage_peer_id;
+
+ if ((peer_id >= 0) && (peer_id < m->max_clients) && (m->instances[peer_id]))
+ {
+ mi = m->instances[peer_id];
+ if (dco->dco_message_type == OVPN_CMD_PACKET)
+ {
+ process_incoming_dco_packet(m, mi, dco);
+ }
+ else if (dco->dco_message_type == OVPN_CMD_DEL_PEER)
+ {
+ process_incoming_del_peer(m, mi, dco);
+ }
+ }
+ else
+ {
+ msg(D_DCO, "Received packet for peer-id unknown to OpenVPN: %d" , peer_id);
+ }
+
+ dco->dco_message_type = 0;
+ dco->dco_meesage_peer_id = -1;
+ return ret > 0;
+}
+#endif
+
/*
* Process packets in the TCP/UDP socket -> TUN/TAP interface direction,
* i.e. client -> server direction.
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index 6e85c21c..71883ee5 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -98,7 +98,9 @@ struct client_connect_defer_state
* server-mode.
*/
struct multi_instance {
- struct schedule_entry se; /* this must be the first element of the structure */
+ struct schedule_entry se; /* this must be the first element of the structure,
+ * We cast between this and schedule_entry so the
+ * beginning of the struct must be identical */
struct gc_arena gc;
bool halt;
int refcount;
@@ -308,6 +310,8 @@ void multi_process_float(struct multi_context *m, struct multi_instance *mi);
bool multi_process_post(struct multi_context *m, struct multi_instance *mi, const unsigned int flags);
+bool multi_process_incoming_dco(struct multi_context *m);
+
/**************************************************************************/
/**
* Demultiplex and process a packet received over the external network
diff --git a/src/openvpn/networking.h b/src/openvpn/networking.h
index 2f0ee160..cb57802b 100644
--- a/src/openvpn/networking.h
+++ b/src/openvpn/networking.h
@@ -27,6 +27,7 @@ struct context;
#ifdef ENABLE_SITNL
#include "networking_sitnl.h"
+#include "dco.h"
#elif ENABLE_IPROUTE
#include "networking_iproute2.h"
#else
diff --git a/src/openvpn/networking_linuxdco.c b/src/openvpn/networking_linuxdco.c
new file mode 100644
index 00000000..ca6284e7
--- /dev/null
+++ b/src/openvpn/networking_linuxdco.c
@@ -0,0 +1,848 @@
+/*
+ * Interface to linux dco networking code
+ *
+ * Copyright (C) 2020 Antonio Quartulli
+ * Copyright (C) 2020 Arne Schwabe
+ * Copyright (C) 2020 OpenVPN Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#if defined(ENABLE_LINUXDCO)
+
+#include "syshead.h"
+
+#include "errlevel.h"
+#include "buffer.h"
+#include "networking.h"
+
+#include "socket.h"
+#include "tun.h"
+#include "ssl.h"
+#include "fdmisc.h"
+#include "ssl_verify.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+
+/* libnl < 3.5.0 does not set the NLA_F_NESTED on its own, therefore we
+ * have to explicitly do it to prevent the kernel from failing upon
+ * parsing of the message
+ */
+#define nla_nest_start(_msg, _type) \
+ nla_nest_start(_msg, (_type) | NLA_F_NESTED)
+
+static int ovpn_get_mcast_id(dco_context_t *dco);
+
+void dco_check_key_ctx(const struct key_ctx_bi *key);
+
+typedef int (*ovpn_nl_cb)(struct nl_msg *msg, void *arg);
+
+int
+resolve_ovpn_netlink_id(int msglevel)
+{
+ int ret;
+ struct nl_sock* nl_sock = nl_socket_alloc();
+
+ ret = genl_connect(nl_sock);
+ if (ret)
+ {
+ msg(msglevel, "Cannot connect to generic netlink: %s",
+ nl_geterror(ret));
+ goto err_sock;
+ }
+ set_cloexec(nl_socket_get_fd(nl_sock));
+
+ ret = genl_ctrl_resolve(nl_sock, OVPN_NL_NAME);
+ if (ret < 0)
+ {
+ msg(msglevel, "Cannot find ovpn_dco netlink component: %s",
+ nl_geterror(ret));
+ }
+
+err_sock:
+ nl_socket_free(nl_sock);
+ return ret;
+}
+
+static struct nl_msg *ovpn_dco_nlmsg_create(dco_context_t *dco,
+ enum ovpn_nl_commands cmd)
+{
+ struct nl_msg *nl_msg = nlmsg_alloc();
+ if (!nl_msg)
+ {
+ msg(M_ERR, "cannot allocate netlink message");
+ return NULL;
+ }
+
+ genlmsg_put(nl_msg, 0, 0, dco->ovpn_dco_id, 0, 0, cmd, 0);
+ NLA_PUT_U32(nl_msg, OVPN_ATTR_IFINDEX, dco->ifindex);
+
+ return nl_msg;
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ msg(M_INFO, "cannot put into netlink message");
+ return NULL;
+}
+
+static int ovpn_nl_recvmsgs(dco_context_t *dco, const char *prefix)
+{
+ int ret = nl_recvmsgs(dco->nl_sock, dco->nl_cb);
+
+ switch (ret) {
+ case -NLE_INTR:
+ msg(M_WARN, "%s: netlink received interrupt due to signal - ignoring", prefix);
+ break;
+ case -NLE_NOMEM:
+ msg(M_ERR, "%s: netlink out of memory error", prefix);
+ break;
+ case -M_ERR:
+ msg(M_WARN, "%s: netlink reports blocking read - aborting wait", prefix);
+ break;
+ case -NLE_NODEV:
+ msg(M_ERR, "%s: netlink reports device not found:", prefix);
+ break;
+ case -NLE_OBJ_NOTFOUND:
+ msg(M_INFO, "%s: netlink reports object not found, ovpn-dco unloaded?", prefix);
+ break;
+ default:
+ if (ret)
+ {
+ msg(M_NONFATAL|M_ERRNO, "%s: netlink reports error (%d): %s", prefix, ret, nl_geterror(-ret));
+ }
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * Send a preprared netlink message and registers cb as callback if non-null.
+ *
+ * The method will also free nl_msg
+ * @param dco The dco context to use
+ * @param nl_msg the message to use
+ * @param cb An optional callback if the caller expects an answers\
+ * @param prefix A prefix to report in the error message to give the user context
+ * @return status of sending the message
+ */
+static int
+ovpn_nl_msg_send(dco_context_t *dco, struct nl_msg *nl_msg, ovpn_nl_cb cb,
+ const char* prefix)
+{
+ dco->status = 1;
+
+ if (cb)
+ {
+ nl_cb_set(dco->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, cb, dco);
+ }
+ else
+ {
+ nl_cb_set(dco->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, NULL, dco);
+ }
+
+ nl_send_auto(dco->nl_sock, nl_msg);
+
+ while (dco->status == 1)
+ {
+ ovpn_nl_recvmsgs(dco, prefix);
+ }
+
+ if (dco->status < 0)
+ {
+ msg(M_INFO, "%s: failed to send netlink message: %s (%d)",
+ prefix, strerror(-dco->status), dco->status);
+ }
+
+ return dco->status;
+}
+
+struct sockaddr *
+mapped_v4_to_v6(struct sockaddr *sock, struct gc_arena *gc)
+{
+ struct sockaddr_in6 *sock6 = ((struct sockaddr_in6 *)sock);
+ if (sock->sa_family == AF_INET6 &&
+ memcmp(&sock6->sin6_addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", 12)==0)
+ {
+
+ struct sockaddr_in* sock4;
+ ALLOC_OBJ_CLEAR_GC(sock4, struct sockaddr_in, gc);
+ memcpy (&sock4->sin_addr, sock6->sin6_addr.s6_addr +12, 4);
+ sock4->sin_port = sock6->sin6_port;
+ sock4->sin_family = AF_INET;
+ return (struct sockaddr *) sock4;
+ }
+ return sock;
+}
+
+int dco_new_peer(struct tuntap *tt, unsigned int peerid, int sd,
+ struct sockaddr *localaddr, struct sockaddr *remoteaddr,
+ struct in_addr *remote_in4, struct in6_addr *remote_in6)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d, fd %d", __func__, peerid, sd);
+
+ struct gc_arena gc = gc_new();
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_NEW_PEER);
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_NEW_PEER);
+ int ret = -EMSGSIZE;
+
+ NLA_PUT_U32(nl_msg, OVPN_NEW_PEER_ATTR_PEER_ID, peerid);
+ NLA_PUT_U32(nl_msg, OVPN_NEW_PEER_ATTR_SOCKET, sd);
+
+ /* Set the remote endpoint if defined (for UDP) */
+ if (remoteaddr)
+ {
+ remoteaddr = mapped_v4_to_v6(remoteaddr, &gc);
+ int alen = af_addr_size(remoteaddr->sa_family);
+
+ NLA_PUT(nl_msg, OVPN_NEW_PEER_ATTR_SOCKADDR_REMOTE, alen, remoteaddr);
+ }
+
+ if (localaddr)
+ {
+ localaddr = mapped_v4_to_v6(localaddr, &gc);
+ if (localaddr->sa_family == AF_INET)
+ {
+ NLA_PUT(nl_msg, OVPN_NEW_PEER_ATTR_LOCAL_IP, sizeof(struct in_addr),
+ &((struct sockaddr_in *)localaddr)->sin_addr);
+ }
+ else if (localaddr->sa_family == AF_INET6)
+ {
+ NLA_PUT(nl_msg, OVPN_NEW_PEER_ATTR_LOCAL_IP, sizeof(struct in6_addr),
+ &((struct sockaddr_in6 *)localaddr)->sin6_addr);
+ }
+ }
+
+ /* Set the primary VPN IP addresses of the peer */
+ if (remote_in4)
+ {
+ NLA_PUT_U32(nl_msg, OVPN_NEW_PEER_ATTR_IPV4, remote_in4->s_addr);
+ }
+ if (remote_in6)
+ {
+ NLA_PUT(nl_msg, OVPN_NEW_PEER_ATTR_IPV6, sizeof(struct in6_addr),
+ remote_in6);
+ }
+ nla_nest_end(nl_msg, attr);
+
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ gc_free(&gc);
+ return ret;
+}
+
+static int
+ovpn_nl_cb_finish(struct nl_msg (*msg)__attribute__((unused)), void *arg)
+{
+ int *status = arg;
+
+ *status = 0;
+ return NL_SKIP;
+}
+
+static int
+ovpn_nl_cb_error(struct sockaddr_nl (*nla)__attribute__((unused)),
+ struct nlmsgerr *err, void *arg)
+{
+ struct nlmsghdr *nlh = (struct nlmsghdr *)err - 1;
+ struct nlattr *tb_msg[NLMSGERR_ATTR_MAX + 1];
+ int len = nlh->nlmsg_len;
+ struct nlattr *attrs;
+ int *ret = arg;
+ int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh);
+
+ *ret = err->error;
+
+ if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
+ return NL_STOP;
+
+ if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
+ ack_len += err->msg.nlmsg_len - sizeof(*nlh);
+
+ if (len <= ack_len)
+ return NL_STOP;
+
+ attrs = (void *)((unsigned char *)nlh + ack_len);
+ len -= ack_len;
+
+ nla_parse(tb_msg, NLMSGERR_ATTR_MAX, attrs, len, NULL);
+ if (tb_msg[NLMSGERR_ATTR_MSG]) {
+ len = strnlen((char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]),
+ nla_len(tb_msg[NLMSGERR_ATTR_MSG]));
+ msg(M_WARN, "kernel error: %*s\n", len,
+ (char *)nla_data(tb_msg[NLMSGERR_ATTR_MSG]));
+ }
+
+ return NL_STOP;
+}
+
+static void
+ovpn_dco_init_netlink(dco_context_t *dco)
+{
+ dco->ovpn_dco_id = resolve_ovpn_netlink_id(M_ERR);
+
+ dco->nl_sock = nl_socket_alloc();
+
+
+ if (!dco->nl_sock)
+ {
+ msg(M_ERR, "Cannot create netlink socket");
+ }
+
+ /* TODO: Why are we setting this buffer size? */
+ nl_socket_set_buffer_size(dco->nl_sock, 8192, 8192);
+
+ int ret = genl_connect(dco->nl_sock);
+ if (ret)
+ {
+ msg(M_ERR, "Cannot connect to generic netlink: %s",
+ nl_geterror(ret));
+ }
+
+ set_cloexec(nl_socket_get_fd(dco->nl_sock));
+
+ dco->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
+ if (!dco->nl_cb)
+ {
+ msg(M_ERR, "failed to allocate netlink callback");
+ }
+
+ nl_socket_set_cb(dco->nl_sock, dco->nl_cb);
+
+ nl_cb_err(dco->nl_cb, NL_CB_CUSTOM, ovpn_nl_cb_error, &dco->status);
+ nl_cb_set(dco->nl_cb, NL_CB_FINISH, NL_CB_CUSTOM, ovpn_nl_cb_finish,
+ &dco->status);
+ nl_cb_set(dco->nl_cb, NL_CB_ACK, NL_CB_CUSTOM, ovpn_nl_cb_finish,
+ &dco->status);
+
+ /* The async PACKET messages confuse libnl and it will drop them with
+ * wrong sequence numbers (NLE_SEQ_MISMATCH), so disable libnl's sequence
+ * number check */
+ nl_socket_disable_seq_check(dco->nl_sock);
+}
+
+static void
+ovpn_dco_uninit_netlink(dco_context_t *dco)
+{
+ nl_socket_free(dco->nl_sock);
+ dco->nl_sock = NULL;
+
+ /* Decrease reference count */
+ nl_cb_put(dco->nl_cb);
+
+ memset(dco, 0, sizeof(*dco));
+}
+
+static void ovpn_dco_register(dco_context_t *dco)
+{
+ msg(D_DCO_DEBUG, __func__);
+ ovpn_get_mcast_id(dco);
+
+ if (dco->ovpn_dco_mcast_id < 0)
+ {
+ msg(M_ERR, "cannot get mcast group: %s", nl_geterror(dco->ovpn_dco_mcast_id));
+ }
+
+ /* Register for Ovpn dco specific messages */
+ int ret = nl_socket_add_membership(dco->nl_sock, dco->ovpn_dco_mcast_id);
+ if (ret)
+ {
+ msg(M_ERR, "%s: failed to join groups: %d", __func__, ret);
+ }
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_REGISTER_PACKET);
+ if (!nl_msg)
+ {
+ msg(M_ERR, "%s: cannot allocate message to register for control packets",
+ __func__);
+ }
+
+ ret = ovpn_nl_msg_send(dco, nl_msg, NULL, __func__);
+ if (ret)
+ {
+ msg(M_ERR, "%s: failed to register for control packets: %d", __func__,
+ ret);
+ }
+ nlmsg_free(nl_msg);
+}
+
+void
+open_tun_dco(struct tuntap *tt, const char* dev)
+{
+ msg(D_DCO_DEBUG, __func__);
+ ASSERT(tt->type == DEV_TYPE_TUN);
+
+ ovpn_dco_init_netlink(&tt->dco);
+
+ if (!strcmp(dev, "tun"))
+ {
+ /* If no specific name has been requested use an auto-assigned name */
+ dev = NULL;
+ }
+
+ tt->dco.ifindex = net_iface_new(dev, "ovpn-dco");
+
+ char if_name[IFNAMSIZ];
+ if(!if_indextoname(tt->dco.ifindex, if_name))
+ {
+ msg(M_ERR|M_ERRNO, "Cannot resolve interface name for dco interface: ");
+ }
+ tt->actual_name = string_alloc(if_name, NULL);
+ uint8_t *dcobuf = malloc(65536);
+ buf_set_write(&tt->dco.dco_packet_in, dcobuf, 65536);
+ tt->dco.dco_meesage_peer_id = -1;
+
+ ovpn_dco_register(&tt->dco);
+}
+
+void
+close_tun_dco(struct tuntap *tt)
+{
+ msg(D_DCO_DEBUG, __func__);
+
+ net_iface_del_index(tt->dco.ifindex);
+ ovpn_dco_uninit_netlink(&tt->dco);
+ free(tt->dco.dco_packet_in.data);
+}
+
+int dco_swap_keys(struct tuntap *tt, unsigned int peerid)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peerid);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_SWAP_KEYS);
+ if (!nl_msg)
+ return -ENOMEM;
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_SWAP_KEYS);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_SWAP_KEYS_ATTR_PEER_ID, peerid);
+ nla_nest_end(nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+
+int dco_del_peer(struct tuntap *tt, unsigned int peerid)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peerid);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_DEL_PEER);
+ if (!nl_msg)
+ return -ENOMEM;
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_DEL_PEER);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_DEL_PEER_ATTR_PEER_ID, peerid);
+ nla_nest_end(nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+
+int
+dco_del_key(struct tuntap *tt, unsigned int peerid, ovpn_key_slot_t slot)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d, slot %d", __func__, peerid, slot);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_DEL_KEY);
+ if (!nl_msg)
+ return -ENOMEM;
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_DEL_KEY);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_DEL_KEY_ATTR_PEER_ID, peerid);
+ NLA_PUT_U8(nl_msg, OVPN_DEL_KEY_ATTR_KEY_SLOT, slot);
+ nla_nest_end(nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+int
+dco_new_key(struct tuntap *tt, unsigned int peerid, ovpn_key_slot_t slot,
+ struct key_state *ks, const char* ciphername)
+{
+ msg(D_DCO_DEBUG, "%s: slot %d, key-id %d, peer-id %d, cipher %s",
+ __func__, slot, ks->key_id, peerid, ciphername);
+ const int nonce_len = 8;
+
+ struct key_ctx_bi *key = &ks->crypto_options.key_ctx_bi;
+
+ dco_check_key_ctx(key);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_NEW_KEY);
+ if (!nl_msg)
+ return -ENOMEM;
+
+ int dco_cipher = get_dco_cipher(ciphername);
+ ASSERT(dco_cipher >= 0);
+
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_NEW_KEY);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_NEW_KEY_ATTR_PEER_ID, peerid);
+ NLA_PUT_U8(nl_msg, OVPN_NEW_KEY_ATTR_KEY_SLOT, slot);
+ NLA_PUT_U8(nl_msg, OVPN_NEW_KEY_ATTR_KEY_ID, ks->key_id);
+
+ NLA_PUT_U16(nl_msg, OVPN_NEW_KEY_ATTR_CIPHER_ALG, dco_cipher);
+
+ if (dco_cipher != OVPN_CIPHER_ALG_NONE)
+ {
+ size_t key_len = cipher_kt_key_size(cipher_kt_get(ciphername));
+ struct nlattr *key_enc = nla_nest_start(nl_msg, OVPN_NEW_KEY_ATTR_ENCRYPT_KEY);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, key_len, key->encrypt.aead_key);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_NONCE_TAIL, nonce_len,
+ key->encrypt.implicit_iv);
+ nla_nest_end(nl_msg, key_enc);
+
+ struct nlattr *key_dec = nla_nest_start(nl_msg, OVPN_NEW_KEY_ATTR_DECRYPT_KEY);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, key_len, key->decrypt.aead_key);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_NONCE_TAIL, nonce_len,
+ key->decrypt.implicit_iv);
+ nla_nest_end(nl_msg, key_dec);
+ }
+ else
+ {
+ /* Check that --auth is disabled. Normally, we would catch this
+ * inconsistency earlier but since the "none" is only for debug and
+ * requires manual editing of DCO_SUPPORTED_CIPHERS, it should be fine
+ * to abort here */
+ if (key->encrypt.hmac != NULL)
+ {
+ msg(M_FATAL, "FATAL: DCO with cipher none requires --auth none");
+ }
+ /* ovpn-dco needs empty encrypt/decrypt keys with cipher none */
+ struct nlattr *key_enc = nla_nest_start(nl_msg,
+ OVPN_NEW_KEY_ATTR_ENCRYPT_KEY);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, 16,
+ key->encrypt.aead_key);
+ nla_nest_end(nl_msg, key_enc);
+
+ struct nlattr *key_dec = nla_nest_start(nl_msg,
+ OVPN_NEW_KEY_ATTR_DECRYPT_KEY);
+ NLA_PUT(nl_msg, OVPN_KEY_DIR_ATTR_CIPHER_KEY, 16,
+ key->decrypt.aead_key);
+ nla_nest_end(nl_msg, key_dec);
+ }
+ nla_nest_end(nl_msg, attr);
+
+ secure_memzero(key->encrypt.aead_key, sizeof (key->encrypt.aead_key));
+ secure_memzero(key->decrypt.aead_key, sizeof (key->decrypt.aead_key));
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+int ovpn_set_peer(struct tuntap *tt, unsigned int peerid,
+ unsigned int keepalive_interval,
+ unsigned int keepalive_timeout)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d, keepalive %d/%d", __func__, peerid,
+ keepalive_interval, keepalive_timeout);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(&tt->dco, OVPN_CMD_SET_PEER);
+ if (!nl_msg)
+ {
+ return -ENOMEM;
+ }
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_SET_PEER);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_SET_PEER_ATTR_PEER_ID, peerid);
+ NLA_PUT_U32(nl_msg, OVPN_SET_PEER_ATTR_KEEPALIVE_INTERVAL,
+ keepalive_interval);
+ NLA_PUT_U32(nl_msg, OVPN_SET_PEER_ATTR_KEEPALIVE_TIMEOUT,
+ keepalive_timeout);
+ nla_nest_end(nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(&tt->dco, nl_msg, NULL, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+static int mcast_family_handler(struct nl_msg *msg, void *arg)
+{
+ dco_context_t *dco = arg;
+ struct nlattr *tb[CTRL_ATTR_MAX + 1];
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+
+ nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL);
+
+ if (!tb[CTRL_ATTR_MCAST_GROUPS])
+ return NL_SKIP;
+
+ struct nlattr *mcgrp;
+ int rem_mcgrp;
+ nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp)
+ {
+ struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
+
+ nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX,
+ nla_data(mcgrp), nla_len(mcgrp), NULL);
+
+ if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] ||
+ !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
+ {
+ continue;
+ }
+
+ if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]),
+ OVPN_NL_MULTICAST_GROUP_PEERS,
+ nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])) != 0)
+ {
+ continue;
+ }
+ dco->ovpn_dco_mcast_id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
+ break;
+ }
+
+ return NL_SKIP;
+}
+/**
+ * Lookup the multicast id for OpenVPN. This method and its help method currently
+ * hardcode the lookup to OVPN_NL_NAME and OVPN_NL_MULTICAST_GROUP_PEERS but
+ * extended in the future if we need to lookup more than one mcast id.
+ */
+static int
+ovpn_get_mcast_id(dco_context_t *dco)
+{
+ dco->ovpn_dco_mcast_id = -ENOENT;
+
+ /* Even though 'nlctrl' is a constant, there seem to be no library
+ * provided define for it */
+ int ctrlid = genl_ctrl_resolve(dco->nl_sock, "nlctrl");
+
+ struct nl_msg *nl_msg = nlmsg_alloc();
+ if (!nl_msg)
+ {
+ return -ENOMEM;
+ }
+
+ genlmsg_put(nl_msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
+
+ int ret = -EMSGSIZE;
+ NLA_PUT_STRING(nl_msg, CTRL_ATTR_FAMILY_NAME, OVPN_NL_NAME);
+
+ ret = ovpn_nl_msg_send(dco, nl_msg, mcast_family_handler, __func__);
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+static int ovpn_handle_msg(struct nl_msg *msg, void *arg)
+{
+ dco_context_t *dco = arg;
+
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *attrs[OVPN_ATTR_MAX + 1];
+ struct nlmsghdr *nlh = nlmsg_hdr(msg);
+
+ if (!genlmsg_valid_hdr(nlh, 0))
+ {
+ msg(D_DCO, "ovpn-dco: invalid header");
+ return NL_SKIP;
+ }
+
+ if (nla_parse(attrs, OVPN_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL))
+ {
+ msg(D_DCO, "received bogus data from ovpn-dco");
+ return NL_SKIP;
+ }
+
+ if (!attrs[OVPN_ATTR_IFINDEX])
+ {
+ msg(D_DCO, "ovpn-dco: Received message without ifindex");
+ return NL_SKIP;
+ }
+
+ uint32_t ifindex = nla_get_u32(attrs[OVPN_ATTR_IFINDEX]);
+ if (ifindex != dco->ifindex)
+ {
+ msg(D_DCO, "ovpn-dco: received message type %d with mismatched ifindex %d\n",
+ gnlh->cmd, ifindex);
+ return NL_SKIP;
+ }
+
+ switch (gnlh->cmd) {
+ case OVPN_CMD_DEL_PEER:
+ {
+ if (!attrs[OVPN_ATTR_DEL_PEER])
+ {
+ msg(D_DCO, "ovpn-dco: no attributes in OVPN_DEL_PEER message");
+ return NL_SKIP;
+ }
+
+ struct nlattr *dp_attrs[OVPN_DEL_PEER_ATTR_MAX + 1];
+ if (nla_parse_nested(dp_attrs, OVPN_DEL_PEER_ATTR_MAX,
+ attrs[OVPN_ATTR_DEL_PEER], NULL))
+ {
+ msg(D_DCO, "received bogus del peer packet data from ovpn-dco");
+ return NL_SKIP;
+ }
+
+ if (!dp_attrs[OVPN_DEL_PEER_ATTR_REASON])
+ {
+ msg(D_DCO, "ovpn-dco: no reason in DEL_PEER message");
+ return NL_SKIP;
+ }
+ if (!dp_attrs[OVPN_DEL_PEER_ATTR_PEER_ID])
+ {
+ msg(D_DCO, "ovpn-dco: no peer-id in DEL_PEER message");
+ return NL_SKIP;
+ }
+ int reason = nla_get_u8(dp_attrs[OVPN_DEL_PEER_ATTR_REASON]);
+ unsigned int peerid = nla_get_u32(dp_attrs[OVPN_DEL_PEER_ATTR_PEER_ID]);
+
+ msg(D_DCO_DEBUG, "ovpn-dco: received CMD_DEL_PEER, ifindex: %d, peer-id %d, reason: %d",
+ ifindex, peerid, reason);
+ dco->dco_meesage_peer_id = peerid;
+ dco->dco_del_peer_reason = reason;
+ dco->dco_message_type = OVPN_CMD_DEL_PEER;
+
+ break;
+ }
+ case OVPN_CMD_PACKET:
+ {
+ if (!attrs[OVPN_ATTR_PACKET])
+ {
+ msg(D_DCO, "ovpn-dco: no packet in OVPN_CMD_PACKET message");
+ return NL_SKIP;
+ }
+ struct nlattr *pkt_attrs[OVPN_PACKET_ATTR_MAX + 1];
+
+ if (nla_parse_nested(pkt_attrs, OVPN_PACKET_ATTR_MAX,
+ attrs[OVPN_ATTR_PACKET], NULL))
+ {
+ msg(D_DCO, "received bogus cmd packet data from ovpn-dco");
+ return NL_SKIP;
+ }
+ if (!pkt_attrs[OVPN_PACKET_ATTR_PEER_ID])
+ {
+ msg(D_DCO, "ovpn-dco: Received OVPN_CMD_PACKET message without peer id");
+ return NL_SKIP;
+ }
+ if (!pkt_attrs[OVPN_PACKET_ATTR_PACKET])
+ {
+ msg(D_DCO, "ovpn-dco: Received OVPN_CMD_PACKET message without packet");
+ return NL_SKIP;
+ }
+
+ unsigned int peerid = nla_get_u32(pkt_attrs[OVPN_PACKET_ATTR_PEER_ID]);
+
+ uint8_t *data = nla_data(pkt_attrs[OVPN_PACKET_ATTR_PACKET]);
+ int len = nla_len(pkt_attrs[OVPN_PACKET_ATTR_PACKET]);
+
+ msg(D_DCO_DEBUG, "ovpn-dco: received OVPN_PACKET_ATTR_PACKET, ifindex: %d peer-id: %d, len %d",
+ ifindex, peerid, len);
+ if (BLEN(&dco->dco_packet_in) > 0)
+ {
+ msg(D_DCO, "DCO packet buffer still full?!");
+ return NL_SKIP;
+ }
+ buf_init(&dco->dco_packet_in, 0);
+ buf_write(&dco->dco_packet_in, data, len);
+ dco->dco_meesage_peer_id = peerid;
+ dco->dco_message_type = OVPN_CMD_PACKET;
+ break;
+ }
+ default:
+ msg(D_DCO, "ovpn-dco: received unknown command: %d", gnlh->cmd);
+ dco->dco_message_type = 0;
+ return NL_SKIP;
+ }
+
+ return NL_OK;
+}
+
+int
+ovpn_do_read_dco(struct dco_context *dco)
+{
+ msg(D_DCO_DEBUG, __func__);
+ nl_cb_set(dco->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, ovpn_handle_msg, dco);
+
+ return ovpn_nl_recvmsgs(dco, __func__);
+}
+
+int
+ovpn_do_write_dco(dco_context_t *dco, int peer_id, struct buffer *buf)
+{
+ packet_size_type len = BLEN(buf);
+ dmsg(D_STREAM_DEBUG, "DCO: WRITE %d offset=%d", (int)len, buf->offset);
+
+ msg(D_DCO_DEBUG, "%s: peer-id %d, len=%d", __func__, peer_id, len);
+
+ struct nl_msg *nl_msg = ovpn_dco_nlmsg_create(dco, OVPN_CMD_PACKET);
+
+ if (!nl_msg)
+ {
+ return -ENOMEM;
+ }
+
+ struct nlattr *attr = nla_nest_start(nl_msg, OVPN_ATTR_PACKET);
+ int ret = -EMSGSIZE;
+ NLA_PUT_U32(nl_msg, OVPN_PACKET_ATTR_PEER_ID, peer_id);
+ NLA_PUT(nl_msg, OVPN_PACKET_ATTR_PACKET, len, BSTR(buf));
+ nla_nest_end(nl_msg, attr);
+
+ ret = ovpn_nl_msg_send(dco, nl_msg, NULL, __func__);
+ if (ret)
+ {
+ goto nla_put_failure;
+ }
+
+ /* return the length of the written data in case of success */
+ ret = len;
+
+nla_put_failure:
+ nlmsg_free(nl_msg);
+ return ret;
+}
+
+#endif
diff --git a/src/openvpn/networking_linuxdco.h b/src/openvpn/networking_linuxdco.h
new file mode 100644
index 00000000..be39437e
--- /dev/null
+++ b/src/openvpn/networking_linuxdco.h
@@ -0,0 +1,85 @@
+/*
+ * Interface to linux dco networking code
+ *
+ * Copyright (C) 2020 Antonio Quartulli
+ * Copyright (C) 2020 Arne Schwabe
+ * Copyright (C) 2020 OpenVPN Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef NETWORKING_LINUXDCO_H
+#define NETWORKING_LINUXDCO_H
+#if defined(ENABLE_LINUXDCO)
+
+#include
+#include
+#include
+
+typedef enum ovpn_key_slot ovpn_key_slot_t;
+
+#include "event.h"
+
+#define DCO_IROUTE_METRIC 100
+
+#define DCO_SUPPORTED_CIPHERS "AES-128-GCM:AES-256-GCM:AES-192-GCM:CHACHA20-POLY1305"
+
+struct dco_context {
+ struct nl_sock *nl_sock;
+ struct nl_cb *nl_cb;
+ int status;
+
+ int ovpn_dco_id;
+ int ovpn_dco_mcast_id;
+
+ unsigned int ifindex;
+
+ struct buffer dco_packet_in;
+
+ int dco_message_type;
+ int dco_meesage_peer_id;
+ int dco_del_peer_reason;
+
+};
+
+typedef struct dco_context dco_context_t;
+
+
+/**
+ * @brief resolves the netlink ID for ovpn-dco
+ *
+ * This function queries the kernel via a netlink socket
+ * whether the ovpn-dco netlink namespace is available
+ *
+ * This function can be used to determine if the kernel
+ * support DCO offloading.
+ *
+ * @return ID on success, negative error code on error
+ */
+int
+resolve_ovpn_netlink_id(int msglevel);
+
+static inline void
+dco_event_set(struct dco_context *dco,
+ struct event_set *es,
+ void *arg) {
+ if (dco && dco->nl_sock) {
+ event_ctl(es, nl_socket_get_fd(dco->nl_sock), EVENT_READ, arg);
+ }
+}
+
+int ovpn_do_write_dco(dco_context_t *dco, int peer_id, struct buffer *buf);
+
+#endif
+#endif
diff --git a/src/openvpn/openvpn.vcxproj b/src/openvpn/openvpn.vcxproj
index 65ee6839..b394cd4a 100644
--- a/src/openvpn/openvpn.vcxproj
+++ b/src/openvpn/openvpn.vcxproj
@@ -253,8 +253,9 @@
-
+
+
@@ -335,6 +336,7 @@
+
diff --git a/src/openvpn/openvpn.vcxproj.filters b/src/openvpn/openvpn.vcxproj.filters
index f5fdfcd7..abb591e4 100644
--- a/src/openvpn/openvpn.vcxproj.filters
+++ b/src/openvpn/openvpn.vcxproj.filters
@@ -39,6 +39,9 @@
Source Files
+
+ Source Files
+
Source Files
@@ -287,6 +290,9 @@
Header Files
+
+ Header Files
+
Header Files
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index ac13412a..a3014415 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -60,6 +60,7 @@
#include "forward.h"
#include "ssl_verify.h"
#include "platform.h"
+#include "dco.h"
#include
#include "memdbg.h"
@@ -105,6 +106,9 @@ const char title_string[] =
#endif
#endif
" [AEAD]"
+#ifdef ENABLE_LINUXDCO
+ " [DCO]"
+#endif
" built on " __DATE__
;
@@ -3220,6 +3224,133 @@ options_set_backwards_compatible_options(struct options *o)
#endif
}
+#if defined(ENABLE_DCO)
+static bool
+check_option_conflict_dco_ce(const struct connection_entry *ce, int msglevel)
+{
+ if (ce->fragment)
+ {
+ msg(msglevel, "Note: --fragment disables data channel offload.");
+ return true;
+ }
+
+ if (ce->http_proxy_options)
+ {
+ msg(msglevel, "Note: --http-proxy disables data channel offload.");
+ return true;
+ }
+
+ if (ce->socks_proxy_server)
+ {
+ msg(msglevel, "Note --socks-proxy disable data channel offload.");
+ return true;
+ }
+
+ return false;
+}
+
+#if defined(ENABLE_LINUXDCO)
+static bool check_option_conflict_dco_platform(int msglevel, const struct options *o)
+{
+ if (resolve_ovpn_netlink_id(D_TUNTAP_INFO) < 0)
+ {
+ msg(D_TUNTAP_INFO, "Note: Kernel support for ovpn-dco missing, disabling "
+ "data channel offload.");
+ return true;
+ }
+ return false;
+}
+#endif
+
+bool check_option_conflict_dco(int msglevel, const struct options *o)
+{
+ if (o->tuntap_options.disable_dco)
+ {
+ /* already disabled by --disable-dco, no need to print warnings */
+ return true;
+ }
+
+ if (check_option_conflict_dco_platform(msglevel, o))
+ {
+ return true;
+ }
+
+ if (dev_type_enum(o->dev, o->dev_type) != DEV_TYPE_TUN)
+ {
+ msg(msglevel, "Note: dev-type not tun, disabling data channel offload.");
+ return true;
+ }
+
+ /* At this point the ciphers have already been normalised */
+ if (o->enable_ncp_fallback
+ && !tls_item_in_cipher_list(o->ciphername, DCO_SUPPORTED_CIPHERS))
+ {
+ msg(msglevel, "Note: --data-cipher-fallback with cipher '%s' "
+ "disables data channel offload.", o->ciphername);
+ return true;
+ }
+
+ if (o->connection_list)
+ {
+ const struct connection_list *l = o->connection_list;
+ for (int i = 0; i < l->len; ++i)
+ {
+ if (check_option_conflict_dco_ce(l->array[i], msglevel))
+ {
+ return true;
+ }
+ }
+ }
+ else
+ {
+ if (check_option_conflict_dco_ce(&o->ce, msglevel))
+ {
+ return true;
+ }
+ }
+
+ if (o->mode == MODE_SERVER && o->topology != TOP_SUBNET)
+ {
+ msg(msglevel, "Note: NOT using '--topology subnet' disables data channel offload.");
+ return true;
+ }
+
+#ifdef USE_COMP
+ if(o->comp.alg != COMP_ALG_UNDEF)
+ {
+ msg(msglevel, "Note: Using compression disables data channel offload.");
+
+ if (o->mode == MODE_SERVER && !(o->comp.flags & COMP_F_MIGRATE))
+ {
+ /* We can end up here from the multi.c call, only print the
+ * note if it is not already enabled */
+ msg(msglevel, "Consider using the '--compress migrate' option.");
+ }
+ return true;
+ }
+#endif
+
+ struct gc_arena gc = gc_new();
+
+
+ char *tmp_ciphers = string_alloc(o->ncp_ciphers, &gc);
+ const char *token;
+ while ((token = strsep(&tmp_ciphers, ":")))
+ {
+ if (!tls_item_in_cipher_list(token, DCO_SUPPORTED_CIPHERS))
+ {
+ msg(msglevel, "Note: cipher '%s' in --data-ciphers is not supported "
+ "by ovpn-dco, disabling data channel offload.", token);
+ gc_free(&gc);
+ return true;
+ }
+ }
+ gc_free(&gc);
+
+ return false;
+}
+#endif /* if defined(TARGET_LINUX) */
+
static void
options_postprocess_mutate(struct options *o)
{
@@ -3306,7 +3437,11 @@ options_postprocess_mutate(struct options *o)
"option set). ");
o->verify_hash_no_ca = true;
}
-
+#if defined(ENABLE_LINUXDCO)
+ o->tuntap_options.disable_dco = check_option_conflict_dco(D_DCO, o);
+#elif defined(TARGET_LINUX)
+ o->tuntap_options.disable_dco = true;
+#endif
/*
* Save certain parms before modifying options during connect, especially
* when using --pull
@@ -5643,6 +5778,12 @@ add_option(struct options *options,
options->windows_driver = parse_windows_driver(p[1], M_FATAL);
}
#endif
+ else if (streq(p[0], "disable-dco") || streq(p[0], "dco-disable"))
+ {
+#if defined(TARGET_LINUX)
+ options->tuntap_options.disable_dco = true;
+#endif
+ }
else if (streq(p[0], "dev-node") && p[1] && !p[2])
{
VERIFY_PERMISSION(OPT_P_GENERAL);
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index d4f41cd7..0affc71f 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -862,4 +862,30 @@ void options_string_import(struct options *options,
unsigned int *option_types_found,
struct env_set *es);
+/**
+ * Returns whether the current configuration has dco enabled.
+ */
+#ifdef ENABLE_LINUXDCO
+static inline bool
+dco_enabled(struct options *o) { return !o->tuntap_options.disable_dco; }
+
+/**
+ * Checks whether the options struct has any option that is not supported by
+ * our current dco implementation. If so it prints a warning at warning level
+ * for the first conflicting option found and returns false
+ * @param msglevel the msg level to use to print the warnings
+ * @param o the optiions struct that hold the options
+ * @return true if a conflict with dco is detected.
+ */
+bool
+check_option_conflict_dco(int msglevel, const struct options *o);
+#else
+/* Dummy functions to avoid ifdefs in the other code */
+
+static inline bool
+dco_enabled(struct options *o) { return false; }
+
+static inline bool
+check_option_conflict_dco(int msglevel, struct options *o) { return false; }
+#endif
#endif /* ifndef OPTIONS_H */
diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h
index cc1e0c36..57142f4e 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 4a4859c3..3e0be874 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -1880,6 +1880,10 @@ tls_session_update_crypto_params_do_work(struct tls_session *session,
init_key_type(&session->opt->key_type, options->ciphername,
options->authname, true, true);
+#if defined(ENABLE_DCO)
+ session->opt->key_type.keep_key_data = dco_enabled(options);
+#endif
+
bool packet_id_long_form = cipher_kt_mode_ofb_cfb(session->opt->key_type.cipher);
session->opt->crypto_flags &= ~(CO_PACKET_ID_LONG_FORM);
if (packet_id_long_form)
@@ -2236,7 +2240,7 @@ push_peer_info(struct buffer *buf, struct tls_session *session)
{
buf_printf(&out, "IV_HWADDR=%s\n", format_hex_ex(rgi.hwaddr, 6, 0, 1, ":", &gc));
}
- buf_printf(&out, "IV_SSL=%s\n", get_ssl_library_version() );
+ buf_printf(&out, "IV_SSL=%s\n", get_ssl_library_version());
#if defined(_WIN32)
buf_printf(&out, "IV_PLAT_VER=%s\n", win32_version_string(&gc, false));
#endif
diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h
index f851bd2b..0da3c1f2 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
@@ -240,6 +246,8 @@ struct key_state
struct auth_deferred_status plugin_auth;
struct auth_deferred_status script_auth;
+
+ enum dco_key_status dco_status;
};
/** Control channel wrapping (--tls-auth/--tls-crypt) context */
@@ -634,6 +642,11 @@ struct tls_multi
/**< Array of \c tls_session objects
* representing control channel
* sessions with the remote peer. */
+
+ /* Only used when DCO is used to remember how many keys we installed
+ * for this session */
+ int dco_keys_installed;
+ bool dco_peer_added;
};
/** gets an item of \c key_state objects in the
diff --git a/src/openvpn/ssl_ncp.c b/src/openvpn/ssl_ncp.c
index 4b95406e..7f5e6fe8 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 75d5eaf7..fb3c2e8b 100644
--- a/src/openvpn/tun.c
+++ b/src/openvpn/tun.c
@@ -1957,6 +1957,10 @@ open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tun
{
open_null(tt);
}
+ else if (!tt->options.disable_dco)
+ {
+ open_tun_dco(tt, dev);
+ }
else
{
/*
@@ -2206,7 +2210,14 @@ close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
net_ctx_reset(ctx);
}
- close_tun_generic(tt);
+ if (!tt->options.disable_dco)
+ {
+ close_tun_dco(tt);
+ }
+ else
+ {
+ close_tun_generic(tt);
+ }
free(tt);
}
diff --git a/src/openvpn/tun.h b/src/openvpn/tun.h
index aa1e47b5..d5beb11c 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 "networking_linuxdco.h"
#ifdef _WIN32
#define WINTUN_COMPONENT_ID "wintun"
@@ -138,6 +139,7 @@ struct tuntap_options {
struct tuntap_options {
int txqueuelen;
+ bool disable_dco;
};
#else /* if defined(_WIN32) || defined(TARGET_ANDROID) */
@@ -218,6 +220,8 @@ struct tuntap
/* Some TUN/TAP drivers like to be ioctled for mtu
* after open */
int post_open_mtu;
+
+ dco_context_t dco;
};
static inline bool
diff --git a/tests/unit_tests/openvpn/test_networking.c b/tests/unit_tests/openvpn/test_networking.c
index 37b97188..20ac9e94 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";
@@ -16,8 +19,10 @@ net__iface_up(bool up)
static int
net__iface_new(const char *name, const char* type)
{
- printf("CMD: ip link add %s type %s\n", name, type);
- return net_iface_new(name, type);
+ printf("CMD: ip link add type %s\n", type);
+ int ifidx = net_iface_new(NULL, type);
+ printf("ifindex: %d\n", ifidx);
+ return ifidx;
}
static int
From patchwork Tue Dec 7 12:11:34 2021
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Openvpn-devel,RFC,5/8] tun: extract close_tun_handle into its own
fucntion and print correct type
X-Patchwork-Submitter: Antonio Quartulli
X-Patchwork-Id: 2115
Message-Id: <20211207121137.3221-6-a@unstable.cc>
To: openvpn-devel@lists.sourceforge.net
Cc: Antonio Quartulli
Date: Tue, 7 Dec 2021 13:11:34 +0100
From: Antonio Quartulli
List-Id:
From: Arne Schwabe
This moves closing the tun handle into its own function and also prints
the adapter type we are operating on, instead hardcoding it to
tap-windows.
Signed-off-by: Arne Schwabe
Signed-off-by: Antonio Quartulli
---
src/openvpn/tun.c | 75 ++++++++++++++++++++++++++---------------------
1 file changed, 41 insertions(+), 34 deletions(-)
diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c
index fb3c2e8b..d8634ebf 100644
--- a/src/openvpn/tun.c
+++ b/src/openvpn/tun.c
@@ -6780,6 +6780,46 @@ netsh_delete_address_dns(const struct tuntap *tt, bool ipv6, struct gc_arena *gc
argv_free(&argv);
}
+static
+void close_tun_handle(struct tuntap* tt)
+{
+ const char* adaptertype = print_windows_driver(tt->windows_driver);
+ if (tt->hand != NULL)
+ {
+ dmsg(D_WIN32_IO_LOW, "Attempting CancelIO on %s adapter", adaptertype);
+ if (!CancelIo(tt->hand))
+ {
+ msg(M_WARN | M_ERRNO, "Warning: CancelIO failed on %s adapter", adaptertype);
+ }
+ }
+
+ dmsg(D_WIN32_IO_LOW, "Attempting close of overlapped read event on %s adapter", adaptertype);
+ overlapped_io_close(&tt->reads);
+
+ dmsg(D_WIN32_IO_LOW, "Attempting close of overlapped write event on %s adapter", adaptertype);
+ overlapped_io_close(&tt->writes);
+
+ if (tt->hand != NULL)
+ {
+ dmsg(D_WIN32_IO_LOW, "Attempting CloseHandle on %s adapter", adaptertype);
+ if (!CloseHandle(tt->hand))
+ {
+ msg(M_WARN | M_ERRNO, "Warning: CloseHandle failed on %s adapter", adaptertype);
+ }
+ tt->hand = NULL;
+ }
+
+ if (tt->windows_driver == WINDOWS_DRIVER_WINTUN)
+ {
+ CloseHandle(tt->rw_handle.read);
+ CloseHandle(tt->rw_handle.write);
+ UnmapViewOfFile(tt->wintun_send_ring);
+ UnmapViewOfFile(tt->wintun_receive_ring);
+ CloseHandle(tt->wintun_send_ring_handle);
+ CloseHandle(tt->wintun_receive_ring_handle);
+ }
+}
+
void
close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
{
@@ -6849,43 +6889,10 @@ close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
dhcp_release(tt);
- if (tt->hand != NULL)
- {
- dmsg(D_WIN32_IO_LOW, "Attempting CancelIO on TAP-Windows adapter");
- if (!CancelIo(tt->hand))
- {
- msg(M_WARN | M_ERRNO, "Warning: CancelIO failed on TAP-Windows adapter");
- }
- }
-
- dmsg(D_WIN32_IO_LOW, "Attempting close of overlapped read event on TAP-Windows adapter");
- overlapped_io_close(&tt->reads);
-
- dmsg(D_WIN32_IO_LOW, "Attempting close of overlapped write event on TAP-Windows adapter");
- overlapped_io_close(&tt->writes);
-
- if (tt->hand != NULL)
- {
- dmsg(D_WIN32_IO_LOW, "Attempting CloseHandle on TAP-Windows adapter");
- if (!CloseHandle(tt->hand))
- {
- msg(M_WARN | M_ERRNO, "Warning: CloseHandle failed on TAP-Windows adapter");
- }
- }
+ close_tun_handle(tt);
free(tt->actual_name);
- if (tt->windows_driver == WINDOWS_DRIVER_WINTUN)
- {
- CloseHandle(tt->rw_handle.read);
- CloseHandle(tt->rw_handle.write);
- UnmapViewOfFile(tt->wintun_send_ring);
- UnmapViewOfFile(tt->wintun_receive_ring);
- CloseHandle(tt->wintun_send_ring_handle);
- CloseHandle(tt->wintun_receive_ring_handle);
- }
-
-
clear_tuntap(tt);
free(tt);
gc_free(&gc);
From patchwork Tue Dec 7 12:11:35 2021
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Openvpn-devel,RFC,6/8] ovpn-dco-win: introduce windows data-channel
offload support
X-Patchwork-Submitter: Antonio Quartulli
X-Patchwork-Id: 2118
Message-Id: <20211207121137.3221-7-a@unstable.cc>
To: openvpn-devel@lists.sourceforge.net
Cc: Lev Stipakov , Antonio Quartulli
Date: Tue, 7 Dec 2021 13:11:35 +0100
From: Antonio Quartulli
List-Id:
From: Arne Schwabe
Implement the data-channel offloading using the ovpn-dco-win kernel
module. See README.dco.md for more details.
Signed-off-by: Arne Schwabe
Signed-off-by: Lev Stipakov
Signed-off-by: Antonio Quartulli
---
README.dco.md | 8 +
config-msvc.h | 12 +-
configure.ac | 82 +++++---
src/openvpn/Makefile.am | 1 +
src/openvpn/dco.h | 1 +
src/openvpn/init.c | 42 +++-
src/openvpn/networking_windco.c | 301 ++++++++++++++++++++++++++++
src/openvpn/networking_windco.h | 47 +++++
src/openvpn/openvpn.vcxproj | 2 +
src/openvpn/openvpn.vcxproj.filters | 6 +
src/openvpn/options.c | 38 +++-
src/openvpn/options.h | 27 ++-
src/openvpn/socket.c | 125 +++++++++++-
src/openvpn/socket.h | 57 +++++-
src/openvpn/tun.c | 44 +++-
src/openvpn/tun.h | 58 ++++--
16 files changed, 758 insertions(+), 93 deletions(-)
create mode 100644 src/openvpn/networking_windco.c
create mode 100644 src/openvpn/networking_windco.h
diff --git a/README.dco.md b/README.dco.md
index e2500d36..8ecda129 100644
--- a/README.dco.md
+++ b/README.dco.md
@@ -60,6 +60,12 @@ see a message like
in your log.
+Getting started (Windows)
+-------------------------
+Getting started under windows is currently for brave people having experience
+with windows development. You need to compile openvpn yourself and also need
+to get the test driver installed on your system.
+
DCO and P2P mode
----------------
DCO is also available when running OpenVPN in P2P mode without --pull/--client option.
@@ -105,6 +111,8 @@ Limitations by design
- topology subnet is the only supported `--topology` for servers
- iroute directives install routes on the host operating system, see also
routing with ovpn-dco
+- (ovpn-dco-win) client and p2p mode only
+- (ovpn-dco-win) only AES-GCM-128/192/256 cipher support
Current limitations
-------------------
diff --git a/config-msvc.h b/config-msvc.h
index 0ae38482..a1a1d555 100644
--- a/config-msvc.h
+++ b/config-msvc.h
@@ -6,10 +6,10 @@
#define ENABLE_CRYPTO_OPENSSL 1
#define ENABLE_FRAGMENT 1
#define ENABLE_HTTP_PROXY 1
-#define ENABLE_LZO 1
+//#define ENABLE_LZO 1
#define ENABLE_LZ4 1
#define ENABLE_MANAGEMENT 1
-#define ENABLE_PKCS11 1
+//#define ENABLE_PKCS11 0
#define ENABLE_PLUGIN 1
#define ENABLE_PORT_SHARE 1
#define ENABLE_SOCKS 1
@@ -30,8 +30,8 @@
#define HAVE_IO_H 1
#define HAVE_SYS_TYPES_H 1
#define HAVE_SYS_STAT_H 1
-#define HAVE_LZO_LZO1X_H 1
-#define HAVE_LZO_LZOUTIL_H 1
+//#define HAVE_LZO_LZO1X_H 1
+//#define HAVE_LZO_LZOUTIL_H 1
#define HAVE_VERSIONHELPERS_H 1
#define HAVE_ACCESS 1
@@ -86,3 +86,7 @@ typedef uint16_t in_port_t;
#ifdef HAVE_CONFIG_MSVC_LOCAL_H
#include
#endif
+
+#define ENABLE_WINDCO 1
+#define ENABLE_DCO 1
+
diff --git a/configure.ac b/configure.ac
index 7d05d905..b6ecb23a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -784,41 +784,63 @@ dnl version in this tree which will be used by default. The
dnl git checkout inside the ovpn-dco/ directory is managed via git
dnl submodule.
dnl
-AC_ARG_VAR([DCO_SOURCEDIR], [Alternative ovpn-dco kernel module source directory])
-if test -z "${DCO_SOURCEDIR}"; then
- DCO_SOURCEDIR="${srcdir}/../ovpn-dco"
-fi
-AC_MSG_NOTICE([Using ovpn-dco source directory: ${DCO_SOURCEDIR}])
-AC_SUBST([DCO_SOURCEDIR])
+ AC_ARG_VAR([DCO_SOURCEDIR], [Alternative ovpn-dco kernel module source directory])
+ if test -z "${DCO_SOURCEDIR}"; then
+ case "$host" in
+ *-mingw*) DCO_SOURCEDIR="${srcdir}/../ovpn-dco-win";;
+ *) DCO_SOURCEDIR="${srcdir}/../ovpn-dco";;
+ esac
+ fi
+ AC_MSG_NOTICE([Using ovpn-dco source directory: ${DCO_SOURCEDIR}])
+ AC_SUBST([DCO_SOURCEDIR])
+ case "$host" in
+ *-*-linux*)
dnl
dnl Include generic netlink library used to talk to ovpn-dco
dnl
- saved_CFLAGS="${CFLAGS}"
- PKG_CHECK_MODULES(
- [LIBNL_GENL],
- [libnl-genl-3.0 >= 3.2.29],
- [have_libnl="yes"],
- [AC_MSG_ERROR([libnl-genl-3.0 package not found or too old. Is the development package and pkg-config installed? Must be version 3.4.0 or newer])]
- )
-
- DCO_CFLAGS="-I${DCO_SOURCEDIR}/include/uapi ${LIBNL_GENL_CFLAGS}"
-
- CFLAGS="${CFLAGS} ${DCO_CFLAGS}"
- AC_CHECK_HEADERS(
- [linux/ovpn_dco.h],
- ,
- [AC_MSG_ERROR([linux/ovpn_dco.h is missing (use DCO_SOURCE to set path to it, CFLAGS=${CFLAGS})])]
- )
- CFLAGS=${saved_CFLAGS}
- OPTIONAL_DCO_CFLAGS="${DCO_CFLAGS}"
- OPTIONAL_DCO_LIBS="${LIBNL_GENL_LIBS}"
-
- AC_DEFINE(ENABLE_LINUXDCO, 1, [Enable linux data channel offload])
- AC_DEFINE(ENABLE_DCO, 1, [Enable shared data channel offload])
-fi
-AM_CONDITIONAL([ENABLE_OVPNDCO], [test "${enable_dco}" = "yes"])
+ saved_CFLAGS="${CFLAGS}"
+ PKG_CHECK_MODULES(
+ [LIBNL_GENL],
+ [libnl-genl-3.0 >= 3.2.29],
+ [have_libnl="yes"],
+ [AC_MSG_ERROR([libnl-genl-3.0 package not found or too old. Is the development package and pkg-config installed? Must be version 3.4.0 or newer])]
+ )
+ DCO_CFLAGS="-I${DCO_SOURCEDIR}/include/uapi ${LIBNL_GENL_CFLAGS}"
+
+ CFLAGS="${CFLAGS} ${DCO_CFLAGS}"
+ AC_CHECK_HEADERS(
+ [linux/ovpn_dco.h],
+ ,
+ [AC_MSG_ERROR([linux/ovpn_dco.h is missing (use DCO_SOURCE to set path to it, CFLAGS=${CFLAGS})])]
+ )
+ CFLAGS=${saved_CFLAGS}
+
+ OPTIONAL_DCO_LIBS="${LIBNL_GENL_LIBS}"
+
+ AC_DEFINE(ENABLE_LINUXDCO, 1, [Enable linux data channel offload])
+ ;;
+
+ *-mingw*)
+ DCO_CFLAGS="-I${DCO_SOURCEDIR}"
+
+ saved_CFLAGS="${CFLAGS}"
+ CFLAGS="${CFLAGS} ${DCO_CFLAGS}"
+ AC_CHECK_HEADERS(
+ [uapi/ovpn-dco.h],
+ ,
+ [AC_MSG_ERROR([uapi/ovpn-dco.h is missing (use DCO_SOURCEDIR to set path to it, CFLAGS=${CFLAGS})])]
+ )
+ CFLAGS=${saved_CFLAGS}
+
+ AC_DEFINE(ENABLE_WINDCO, 1, [Enable shared data channel offload for Windows])
+ ;;
+ esac
+
+ OPTIONAL_DCO_CFLAGS="${DCO_CFLAGS}"
+ AC_DEFINE(ENABLE_DCO, 1, [Enable shared data channel offload])
+fi
if test "${with_crypto_library}" = "openssl"; then
AC_ARG_VAR([OPENSSL_CFLAGS], [C compiler flags for OpenSSL])
diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index 0cc06155..9645209b 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -88,6 +88,7 @@ openvpn_SOURCES = \
networking_iproute2.c networking_iproute2.h \
networking_sitnl.c networking_sitnl.h \
networking_linuxdco.c networking_linuxdco.h \
+ networking_windco.c networking_windco.h \
networking.h \
ntlm.c ntlm.h \
occ.c occ.h \
diff --git a/src/openvpn/dco.h b/src/openvpn/dco.h
index 1cfdc8fb..f0334536 100644
--- a/src/openvpn/dco.h
+++ b/src/openvpn/dco.h
@@ -41,6 +41,7 @@ static inline void open_tun_dco(struct tuntap *tt, const char* dev) { ASSERT(fal
static inline void close_tun_dco(struct tuntap *tt) { ASSERT(false); }
#else
+#include "networking_windco.h"
#include "networking_linuxdco.h"
#include "crypto.h"
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 4668fd04..6632cb7f 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -1737,7 +1737,8 @@ do_init_tun(struct context *c)
c->c1.link_socket_addr.remote_list,
!c->options.ifconfig_nowarn,
c->c2.es,
- &c->net_ctx);
+ &c->net_ctx,
+ c->c1.tuntap);
#ifdef _WIN32
c->c1.tuntap->windows_driver = c->options.windows_driver;
@@ -1761,7 +1762,15 @@ do_open_tun(struct context *c)
bool ret = false;
#ifndef TARGET_ANDROID
- if (!c->c1.tuntap)
+ if (!c->c1.tuntap
+#ifdef _WIN32
+ || (c->c1.tuntap
+#ifdef ENABLE_WINDCO
+ && !c->c1.tuntap->dco.real_tun_init
+#endif
+ )
+#endif
+ )
{
#endif
@@ -1829,9 +1838,12 @@ 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
- /* open the tun device */
- open_tun(c->options.dev, c->options.dev_type, c->options.dev_node,
- c->c1.tuntap);
+ /* open the tun device. ovpn-dco-win already opend the device for the socket */
+ if (!is_windco(c->c1.tuntap))
+ {
+ open_tun(c->options.dev, c->options.dev_type, c->options.dev_node,
+ c->c1.tuntap);
+ }
/* set the hardware address */
if (c->options.lladdr)
@@ -3646,6 +3658,26 @@ do_close_free_key_schedule(struct context *c, bool free_ssl_ctx)
static void
do_close_link_socket(struct context *c)
{
+#ifdef _WIN32
+ if (c->c2.link_socket && c->c2.link_socket->info.dco_installed && is_windco(c->c1.tuntap))
+ {
+ ASSERT(c->c2.link_socket_owned);
+ ASSERT(c->c1.tuntap);
+
+ /* We rely on the tun close to the handle if also setup
+ * routes etc, since they cannot be delete when the interface
+ * handle has been closed */
+ if (true
+#ifdef ENABLE_WINDCO
+ && !c->c1.tuntap->dco.real_tun_init
+#endif
+ )
+ {
+ do_close_tun_simple(c);
+ }
+ c->c2.link_socket->sd = SOCKET_UNDEFINED;
+ }
+#endif
if (c->c2.link_socket && c->c2.link_socket_owned)
{
link_socket_close(c->c2.link_socket);
diff --git a/src/openvpn/networking_windco.c b/src/openvpn/networking_windco.c
new file mode 100644
index 00000000..3c88b752
--- /dev/null
+++ b/src/openvpn/networking_windco.c
@@ -0,0 +1,301 @@
+/*
+ * Interface to ovpn-win-dco networking code
+ *
+ * Copyright (C) 2020 Arne Schwabe
+ * Copyright (C) 2020 OpenVPN Inc
+ *
+ * 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_WINDCO)
+#include "syshead.h"
+
+#include "networking_windco.h"
+#include "dco.h"
+#include "tun.h"
+#include "crypto.h"
+#include "ssl_common.h"
+
+
+#include
+#include
+
+#if defined(__MINGW32__)
+const IN_ADDR in4addr_any = { 0 };
+#endif
+
+static struct tuntap create_dco_handle(const char* devname, struct gc_arena *gc)
+{
+ struct tuntap tt = { 0 };
+
+ tt.windows_driver = WINDOWS_DRIVER_WINDCO;
+
+ const char* device_guid;
+ tun_open_device(&tt, devname, &device_guid, gc);
+ tt.windows_driver = WINDOWS_DRIVER_WINDCO;
+
+ return tt;
+}
+
+void open_tun_dco(struct tuntap *tt, const char* dev)
+{
+ ASSERT(0);
+}
+
+void dco_start_tun(struct tuntap* tt)
+{
+ msg(D_DCO_DEBUG, "%s", __func__);
+
+ DWORD bytes_returned = 0;
+ if (!DeviceIoControl(tt->hand, OVPN_IOCTL_START_VPN, NULL, 0, NULL, 0,
+ &bytes_returned, NULL))
+ {
+ msg(M_ERR, "DeviceIoControl(OVPN_IOCTL_START_VPN) failed with code %lu", GetLastError());
+ }
+}
+
+int dco_connect_wait(HANDLE handle, OVERLAPPED* ov, int timeout, volatile int* signal_received)
+{
+ while (timeout-- > 0)
+ {
+ DWORD transferred;
+ if (GetOverlappedResultEx(handle, ov, &transferred, 1000, FALSE) != 0)
+ {
+ /* TCP connection established by dco */
+ return 0;
+ }
+
+ DWORD err = GetLastError();
+ if (err != WAIT_TIMEOUT)
+ {
+ /* dco reported connection error */
+ struct gc_arena gc = gc_new();
+ msg(M_NONFATAL, "%s: %s", __func__, strerror_win32(err, &gc));
+ *signal_received = SIGUSR1;
+ gc_free(&gc);
+ return -1;
+ }
+
+ get_signal(signal_received);
+ if (*signal_received)
+ {
+ return -1;
+ }
+
+ management_sleep(0);
+ }
+
+ /* we end up here when timeout occurs in userspace */
+ msg(M_NONFATAL, "%s: dco connect timeout", __func__);
+ *signal_received = SIGUSR1;
+
+ return -1;
+}
+
+struct tuntap
+dco_create_socket(struct addrinfo *remoteaddr, bool bind_local,
+ struct addrinfo *bind, const char* devname,
+ struct gc_arena *gc, int timeout, volatile int* signal_received)
+{
+ msg(D_DCO_DEBUG, "%s", __func__);
+
+ OVPN_NEW_PEER peer = { 0 };
+
+ struct sockaddr *local = NULL;
+ struct sockaddr *remote = remoteaddr->ai_addr;
+
+ if (remoteaddr->ai_protocol == IPPROTO_TCP
+ || remoteaddr->ai_socktype == SOCK_STREAM)
+ {
+ peer.Proto = OVPN_PROTO_TCP;
+ }
+ else
+ {
+ peer.Proto = OVPN_PROTO_UDP;
+ }
+
+ if (bind_local)
+ {
+ /* Use first local address with correct address family */
+ while(bind && !local)
+ {
+ if (bind->ai_family == remote->sa_family)
+ {
+ local = bind->ai_addr;
+ }
+ bind = bind->ai_next;
+ }
+ }
+
+ if (bind_local && !local)
+ {
+ msg(M_FATAL, "DCO: Socket bind failed: Address to bind lacks %s record",
+ addr_family_name(remote->sa_family));
+ }
+
+ if (remote->sa_family == AF_INET6)
+ {
+ peer.Remote.Addr6 = *((SOCKADDR_IN6 *)(remoteaddr->ai_addr));
+ if (local)
+ {
+ peer.Local.Addr6 = *((SOCKADDR_IN6 *)local);
+ }
+ else
+ {
+ peer.Local.Addr6.sin6_addr = in6addr_any;
+ peer.Local.Addr6.sin6_port = 0;
+ peer.Local.Addr6.sin6_family = AF_INET6;
+ }
+ }
+ else if (remote->sa_family == AF_INET)
+ {
+ peer.Remote.Addr4 = *((SOCKADDR_IN *)(remoteaddr->ai_addr));
+ if (local)
+ {
+ peer.Local.Addr4 = *((SOCKADDR_IN *)local);
+ }
+ else
+ {
+ peer.Local.Addr4.sin_addr = in4addr_any;
+ peer.Local.Addr4.sin_port = 0;
+ peer.Local.Addr4.sin_family = AF_INET;
+ }
+ }
+ else
+ {
+ ASSERT(0);
+ }
+
+ struct tuntap tt = create_dco_handle(devname, gc);
+
+ OVERLAPPED ov = { 0 };
+ if (!DeviceIoControl(tt.hand, OVPN_IOCTL_NEW_PEER, &peer, sizeof(peer), NULL, 0, NULL, &ov))
+ {
+ DWORD err = GetLastError();
+ if (err != ERROR_IO_PENDING)
+ {
+ msg(M_ERR, "DeviceIoControl(OVPN_IOCTL_NEW_PEER) failed with code %lu", err);
+ }
+ else
+ {
+ if (dco_connect_wait(tt.hand, &ov, timeout, signal_received))
+ {
+ close_tun_handle(&tt);
+ }
+ }
+ }
+ return tt;
+}
+
+int dco_new_peer(struct tuntap *tt, unsigned int peerid, int sd,
+ struct sockaddr *localaddr, struct sockaddr *remoteaddr,
+ struct in_addr *remote_in4, struct in6_addr *remote_in6)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d, fd %d", __func__, peerid, sd);
+ return 0;
+}
+
+int ovpn_set_peer(struct tuntap *tt, unsigned int peerid,
+ unsigned int keepalive_interval,
+ unsigned int keepalive_timeout)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d, keepalive %d/%d", __func__, peerid,
+ keepalive_interval, keepalive_timeout);
+
+ OVPN_SET_PEER peer;
+
+ peer.KeepaliveInterval = keepalive_interval;
+ peer.KeepaliveTimeout = keepalive_timeout;
+
+ DWORD bytes_returned = 0;
+ if (!DeviceIoControl(tt->hand, OVPN_IOCTL_SET_PEER, &peer, sizeof(peer), NULL, 0, &bytes_returned, NULL))
+ {
+ msg(M_WARN, "DeviceIoControl(OVPN_IOCTL_SET_PEER) failed with code %lu", GetLastError());
+ return -1;
+ }
+ return 0;
+}
+
+int
+dco_new_key(struct tuntap *tt, unsigned int peerid, ovpn_key_slot_t slot,
+ struct key_state *ks, const char* ciphername)
+{
+ msg(D_DCO_DEBUG, "%s: slot %d, key-id %d, peer-id %d, cipher %s",
+ __func__, slot, ks->key_id, peerid, ciphername);
+
+ struct key_ctx_bi *key = &ks->crypto_options.key_ctx_bi;
+ dco_check_key_ctx(key);
+
+ const int nonce_len = 8;
+ size_t key_len = cipher_kt_key_size(cipher_kt_get(ciphername));
+
+ OVPN_CRYPTO_DATA crypto_data;
+ ZeroMemory(&crypto_data, sizeof(crypto_data));
+
+ crypto_data.CipherAlg = get_dco_cipher(ciphername);
+ crypto_data.KeyId = ks->key_id;
+ crypto_data.PeerId = peerid;
+ crypto_data.KeySlot = slot;
+
+ CopyMemory(crypto_data.Encrypt.Key, key->encrypt.aead_key, key_len);
+ crypto_data.Encrypt.KeyLen = (char)key_len;
+ CopyMemory(crypto_data.Encrypt.NonceTail, key->encrypt.implicit_iv, nonce_len);
+
+ CopyMemory(crypto_data.Decrypt.Key, key->decrypt.aead_key, key_len);
+ crypto_data.Decrypt.KeyLen = (char)key_len;
+ CopyMemory(crypto_data.Decrypt.NonceTail, key->decrypt.implicit_iv, nonce_len);
+
+ ASSERT(crypto_data.CipherAlg > 0);
+
+ DWORD bytes_returned = 0;
+
+ secure_memzero(key->encrypt.aead_key, sizeof (key->encrypt.aead_key));
+ secure_memzero(key->decrypt.aead_key, sizeof (key->decrypt.aead_key));
+
+ if (!DeviceIoControl(tt->hand, OVPN_IOCTL_NEW_KEY, &crypto_data, sizeof(crypto_data), NULL, 0,
+ &bytes_returned, NULL))
+ {
+ msg(M_ERR, "DeviceIoControl(OVPN_IOCTL_NEW_KEY) failed with code %lu", GetLastError());
+ return -1;
+ }
+ return 0;
+}
+int
+dco_del_key(struct tuntap *tt, unsigned int peerid, ovpn_key_slot_t slot)
+{
+ msg(D_DCO, "%s: peer-id %d, slot %d called but ignored", __func__, peerid, slot);
+ /* FIXME: Implement in driver first */
+ return 0;
+}
+
+int dco_swap_keys(struct tuntap *tt, unsigned int peer_id)
+{
+ msg(D_DCO_DEBUG, "%s: peer-id %d", __func__, peer_id);
+
+ DWORD bytes_returned = 0;
+ if (!DeviceIoControl(tt->hand, OVPN_IOCTL_SWAP_KEYS, NULL, 0, NULL, 0,
+ &bytes_returned, NULL))
+ {
+ msg(M_ERR, "DeviceIoControl(OVPN_IOCTL_SWAP_KEYS) failed with code %lu", GetLastError());
+ }
+ return 0;
+}
+#endif
\ No newline at end of file
diff --git a/src/openvpn/networking_windco.h b/src/openvpn/networking_windco.h
new file mode 100644
index 00000000..e48aaa6c
--- /dev/null
+++ b/src/openvpn/networking_windco.h
@@ -0,0 +1,47 @@
+/*
+ * Interface to ovpn-win-dco networking code
+ *
+ * Copyright (C) 2020 Arne Schwabe
+ * Copyright (C) 2020 OpenVPN Inc
+ *
+ * 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 OPENVPN_NETWORKING_WINDCO_H
+#define OPENVPN_NETWORKING_WINDCO_H
+#if defined(ENABLE_WINDCO)
+#include "uapi/ovpn-dco.h"
+#include "buffer.h"
+
+
+typedef OVPN_KEY_SLOT ovpn_key_slot_t;
+
+#define DCO_SUPPORTED_CIPHERS "AES-128-GCM:AES-256-GCM:AES-192-GCM"
+
+struct dco_context {
+ bool real_tun_init;
+};
+
+struct tuntap
+dco_create_socket(struct addrinfo *remoteaddr, bool bind_local,
+ struct addrinfo *bind, const char* devname,
+ struct gc_arena *gc, int timeout, volatile int* signal_received);
+
+void dco_start_tun(struct tuntap* tt);
+
+typedef struct dco_context dco_context_t;
+
+#endif
+#endif //OPENVPN_NETWORKING_WINDCO_H
diff --git a/src/openvpn/openvpn.vcxproj b/src/openvpn/openvpn.vcxproj
index b394cd4a..1258b633 100644
--- a/src/openvpn/openvpn.vcxproj
+++ b/src/openvpn/openvpn.vcxproj
@@ -279,6 +279,7 @@
+
@@ -365,6 +366,7 @@
+
diff --git a/src/openvpn/openvpn.vcxproj.filters b/src/openvpn/openvpn.vcxproj.filters
index abb591e4..402999b1 100644
--- a/src/openvpn/openvpn.vcxproj.filters
+++ b/src/openvpn/openvpn.vcxproj.filters
@@ -249,6 +249,9 @@
Source Files
+
+ Source Files
+
@@ -374,6 +377,9 @@
Header Files
+
+ Header Files
+
Header Files
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index a3014415..70c5995e 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -2249,6 +2249,11 @@ options_postprocess_verify_ce(const struct options *options,
{
msg(M_USAGE, "--windows-driver wintun requires --dev tun");
}
+
+ if (options->windows_driver == WINDOWS_DRIVER_WINDCO)
+ {
+ check_option_conflict_dco(M_USAGE, options);
+ }
#endif /* ifdef _WIN32 */
/*
@@ -3001,8 +3006,8 @@ options_postprocess_mutate_invariant(struct options *options)
#ifdef _WIN32
const int dev = dev_type_enum(options->dev, options->dev_type);
- /* when using wintun, kernel doesn't send DHCP requests, so don't use it */
- if (options->windows_driver == WINDOWS_DRIVER_WINTUN
+ /* when using wintun/ovpn-dco-win, kernel doesn't send DHCP requests, so don't use it */
+ if ((options->windows_driver == WINDOWS_DRIVER_WINTUN || options->windows_driver == WINDOWS_DRIVER_WINDCO)
&& (options->tuntap_options.ip_win32_type == IPW32_SET_DHCP_MASQ || options->tuntap_options.ip_win32_type == IPW32_SET_ADAPTIVE))
{
options->tuntap_options.ip_win32_type = IPW32_SET_NETSH;
@@ -3088,7 +3093,7 @@ options_postprocess_setdefault_ncpciphers(struct options *o)
/* custom --data-ciphers set, keep list */
return;
}
- else if (cipher_kt_get("CHACHA20-POLY1305"))
+ else if (cipher_kt_get("CHACHA20-POLY1305") && !dco_win_enabled(o))
{
o->ncp_ciphers = "AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305";
}
@@ -3260,6 +3265,22 @@ static bool check_option_conflict_dco_platform(int msglevel, const struct option
}
return false;
}
+#elif defined(ENABLE_WINDCO)
+static bool check_option_conflict_dco_platform(int msglevel, const struct options *o)
+{
+ if (o->mode == MODE_SERVER)
+ {
+ msg(msglevel, "Only client and p2p data channel offload is supported "
+ "with ovpn-dco-win.");
+ return true;
+ }
+ if (o->persist_tun)
+ {
+ msg(msglevel, "--persist-tun is not supported with ovpn-dco-win.");
+ return true;
+ }
+ return false;
+}
#endif
bool check_option_conflict_dco(int msglevel, const struct options *o)
@@ -4050,7 +4071,8 @@ options_string(const struct options *o,
NULL,
false,
NULL,
- ctx);
+ ctx,
+ NULL);
if (tt)
{
tt_local = true;
@@ -4472,9 +4494,15 @@ parse_windows_driver(const char *str, const int msglevel)
{
return WINDOWS_DRIVER_WINTUN;
}
+
+ else if (streq(str, "ovpn-dco-win"))
+ {
+ return WINDOWS_DRIVER_WINDCO;
+ }
else
{
- msg(msglevel, "--windows-driver must be tap-windows6 or wintun");
+ msg(msglevel, "--windows-driver must be tap-windows6, wintun "
+ "or ovpn-dco-win");
return WINDOWS_DRIVER_UNSPECIFIED;
}
}
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 0affc71f..321a89a5 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -865,10 +865,30 @@ void options_string_import(struct options *options,
/**
* Returns whether the current configuration has dco enabled.
*/
-#ifdef ENABLE_LINUXDCO
+#if defined(ENABLE_LINUXDCO)
static inline bool
dco_enabled(struct options *o) { return !o->tuntap_options.disable_dco; }
+#elif defined(ENABLE_WINDCO)
+static inline bool dco_enabled(struct options *o)
+{
+ return o->windows_driver == WINDOWS_DRIVER_WINDCO;
+}
+#else
+/* Dummy functions to avoid ifdefs in the other code */
+static inline bool
+dco_enabled(struct options *o) { return false; }
+#endif
+
+#if defined(ENABLE_WINDCO)
+static inline bool
+dco_win_enabled(struct options *o) { return dco_enabled(o); }
+#else
+static inline bool
+dco_win_enabled(struct options *o) { return false; }
+#endif
+
+#if defined(ENABLE_DCO)
/**
* Checks whether the options struct has any option that is not supported by
* our current dco implementation. If so it prints a warning at warning level
@@ -880,11 +900,6 @@ dco_enabled(struct options *o) { return !o->tuntap_options.disable_dco; }
bool
check_option_conflict_dco(int msglevel, const struct options *o);
#else
-/* Dummy functions to avoid ifdefs in the other code */
-
-static inline bool
-dco_enabled(struct options *o) { return false; }
-
static inline bool
check_option_conflict_dco(int msglevel, struct options *o) { return false; }
#endif
diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c
index 72062cd0..700fc2aa 100644
--- a/src/openvpn/socket.c
+++ b/src/openvpn/socket.c
@@ -2141,6 +2141,38 @@ phase2_socks_client(struct link_socket *sock, struct signal_info *sig_info)
resolve_remote(sock, 1, NULL, &sig_info->signal_received);
}
+#if defined(ENABLE_WINDCO)
+static void
+create_socket_windco(struct context* c, struct link_socket* sock, volatile int* signal_received)
+{
+ struct tuntap* tt;
+ /* In this case persist-tun is enabled, which we don't support yet */
+ ASSERT(!c->c1.tuntap);
+
+ ALLOC_OBJ(tt, struct tuntap);
+
+ *tt = dco_create_socket(sock->info.lsa->current_remote,
+ sock->bind_local,
+ sock->info.lsa->bind_local,
+ c->options.dev_node,
+ &c->gc,
+ get_server_poll_remaining_time(sock->server_poll_timeout),
+ signal_received);
+ if (*signal_received)
+ {
+ goto done;
+ }
+ c->c1.tuntap = tt;
+ sock->info.dco_installed = true;
+
+ /* Ensure we can "safely" cast the handle to a socket */
+ static_assert(sizeof(sock->sd) == sizeof(tt->hand), "HANDLE and SOCKET size differs");
+ sock->sd = (SOCKET)tt->hand;
+done:
+ ;
+}
+#endif
+
/* finalize socket initialization */
void
link_socket_init_phase2(struct context *c)
@@ -2180,7 +2212,24 @@ link_socket_init_phase2(struct context *c)
/* If a valid remote has been found, create the socket with its addrinfo */
if (sock->info.lsa->current_remote)
{
- create_socket(sock, sock->info.lsa->current_remote);
+#if defined(ENABLE_WINDCO)
+ if (dco_win_enabled(&c->options))
+ {
+ create_socket_windco(c, sock, &sig_info->signal_received);
+ if (sig_info->signal_received)
+ {
+ goto done;
+ }
+
+ linksock_print_addr(sock);
+ goto done;
+ }
+ else
+#endif
+ {
+ create_socket(sock, sock->info.lsa->current_remote);
+ }
+
}
/* If socket has not already been created create it now */
@@ -2243,6 +2292,7 @@ link_socket_init_phase2(struct context *c)
}
phase2_set_socket_flags(sock);
+
linksock_print_addr(sock);
done:
@@ -3199,7 +3249,14 @@ link_socket_read_tcp(struct link_socket *sock,
if (!sock->stream_buf.residual_fully_formed)
{
#ifdef _WIN32
- len = socket_finalize(sock->sd, &sock->reads, buf, NULL);
+ if (sock->info.dco_installed)
+ {
+ len = tun_finalize((HANDLE)sock->sd, &sock->reads, buf);
+ }
+ else
+ {
+ len = socket_finalize(sock->sd, &sock->reads, buf, NULL);
+ }
#else
struct buffer frag;
stream_buf_get_next(&sock->stream_buf, &frag);
@@ -3355,7 +3412,14 @@ link_socket_write_tcp(struct link_socket *sock,
len = htonps(len);
ASSERT(buf_write_prepend(buf, &len, sizeof(len)));
#ifdef _WIN32
- return link_socket_write_win32(sock, buf, to);
+ if (sock->info.dco_installed)
+ {
+ return link_socket_write_win32_dco(sock, buf, to);
+ }
+ else
+ {
+ return link_socket_write_win32(sock, buf, to);
+ }
#else
return link_socket_write_tcp_posix(sock, buf, to);
#endif
@@ -3478,8 +3542,20 @@ socket_recv_queue(struct link_socket *sock, int maxsize)
/* the overlapped read will signal this event on I/O completion */
ASSERT(ResetEvent(sock->reads.overlapped.hEvent));
sock->reads.flags = 0;
-
- if (proto_is_udp(sock->info.proto))
+
+ if (sock->info.dco_installed)
+ {
+ status = ReadFile(
+ (HANDLE) sock->sd,
+ wsabuf[0].buf,
+ wsabuf[0].len,
+ &sock->reads.size,
+ &sock->reads.overlapped
+ );
+ /* Readfile status is inverted from WSARecv */
+ status = !status;
+ }
+ else if (proto_is_udp(sock->info.proto))
{
sock->reads.addr_defined = true;
sock->reads.addrlen = sizeof(sock->reads.addr6);
@@ -3532,7 +3608,14 @@ socket_recv_queue(struct link_socket *sock, int maxsize)
}
else
{
- status = WSAGetLastError();
+ if (sock->info.dco_installed)
+ {
+ status = GetLastError();
+ }
+ else
+ {
+ status = WSAGetLastError();
+ }
if (status == WSA_IO_PENDING) /* operation queued? */
{
sock->reads.iostate = IOSTATE_QUEUED;
@@ -3577,7 +3660,21 @@ socket_send_queue(struct link_socket *sock, struct buffer *buf, const struct lin
ASSERT(ResetEvent(sock->writes.overlapped.hEvent));
sock->writes.flags = 0;
- if (proto_is_udp(sock->info.proto))
+ if (sock->info.dco_installed)
+ {
+ status = WriteFile(
+ (HANDLE)sock->sd,
+ wsabuf[0].buf,
+ wsabuf[0].len,
+ &sock->writes.size,
+ &sock->writes.overlapped
+ );
+
+ /* WriteFile status is inverted from WSASendTo */
+ status = !status;
+
+ }
+ else if (proto_is_udp(sock->info.proto))
{
/* set destination address for UDP writes */
sock->writes.addr_defined = true;
@@ -3638,8 +3735,17 @@ socket_send_queue(struct link_socket *sock, struct buffer *buf, const struct lin
}
else
{
- status = WSAGetLastError();
- if (status == WSA_IO_PENDING) /* operation queued? */
+ if (sock->info.dco_installed)
+ {
+ status = GetLastError();
+ }
+ else
+ {
+ status = WSAGetLastError();
+ }
+
+ /* both status code have the identical value */
+ if (status == WSA_IO_PENDING || status == ERROR_IO_PENDING) /* operation queued? */
{
sock->writes.iostate = IOSTATE_QUEUED;
sock->writes.status = status;
@@ -3664,6 +3770,7 @@ socket_send_queue(struct link_socket *sock, struct buffer *buf, const struct lin
return sock->writes.iostate;
}
+/* Returns the nubmer of bytes successfully read */
int
socket_finalize(SOCKET s,
struct overlapped_io *io,
diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h
index 57142f4e..f9f5faf4 100644
--- a/src/openvpn/socket.h
+++ b/src/openvpn/socket.h
@@ -34,6 +34,7 @@
#include "proxy.h"
#include "socks.h"
#include "misc.h"
+#include "tun.h"
/*
* OpenVPN's default port number as assigned by IANA.
@@ -1021,7 +1022,17 @@ link_socket_read_udp_win32(struct link_socket *sock,
struct buffer *buf,
struct link_socket_actual *from)
{
- return socket_finalize(sock->sd, &sock->reads, buf, from);
+ if (sock->info.dco_installed)
+ {
+ /* from address was set on socket creation and the kernel
+ * checks that it matches for us */
+ addr_copy_sa(&from->dest, &sock->info.lsa->actual.dest);
+ return tun_finalize((HANDLE)sock->sd, &sock->reads, buf);
+ }
+ else
+ {
+ return socket_finalize(sock->sd, &sock->reads, buf, from);
+ }
}
#else /* ifdef _WIN32 */
@@ -1038,7 +1049,10 @@ link_socket_read(struct link_socket *sock,
struct buffer *buf,
struct link_socket_actual *from)
{
- if (proto_is_udp(sock->info.proto)) /* unified UDPv4 and UDPv6 */
+ if (proto_is_udp(sock->info.proto)
+ || sock->info.dco_installed)
+ /* unified UDPv4 and UDPv6, for DCO the kernel
+ * will strip the length header */
{
int res;
@@ -1082,10 +1096,6 @@ link_socket_write_win32(struct link_socket *sock,
if (overlapped_io_active(&sock->writes))
{
status = socket_finalize(sock->sd, &sock->writes, NULL, NULL);
- if (status < 0)
- {
- err = WSAGetLastError();
- }
}
socket_send_queue(sock, buf, to);
if (status < 0)
@@ -1099,6 +1109,29 @@ link_socket_write_win32(struct link_socket *sock,
}
}
+static inline int
+link_socket_write_win32_dco(struct link_socket* sock,
+ struct buffer* buf,
+ struct link_socket_actual* to)
+{
+ int err = 0;
+ int status = 0;
+ if (overlapped_io_active(&sock->writes))
+ {
+ status = tun_finalize((HANDLE)sock->sd, &sock->writes, NULL);
+ }
+ socket_send_queue(sock, buf, to);
+ if (status < 0)
+ {
+ SetLastError(err);
+ return status;
+ }
+ else
+ {
+ return BLEN(buf);
+ }
+}
+
#else /* ifdef _WIN32 */
size_t link_socket_write_udp_posix_sendmsg(struct link_socket *sock,
@@ -1140,7 +1173,14 @@ link_socket_write_udp(struct link_socket *sock,
struct link_socket_actual *to)
{
#ifdef _WIN32
- return link_socket_write_win32(sock, buf, to);
+ if (sock->info.dco_installed)
+ {
+ return link_socket_write_win32_dco(sock, buf, to);
+ }
+ else
+ {
+ return link_socket_write_win32(sock, buf, to);
+ }
#else
return link_socket_write_udp_posix(sock, buf, to);
#endif
@@ -1152,8 +1192,9 @@ link_socket_write(struct link_socket *sock,
struct buffer *buf,
struct link_socket_actual *to)
{
- if (proto_is_udp(sock->info.proto)) /* unified UDPv4 and UDPv6 */
+ if (proto_is_udp(sock->info.proto) || sock->info.dco_installed)
{
+ /* unified UDPv4 and UDPv6 and DCO (kernel adds size header) */
return link_socket_write_udp(sock, buf, to);
}
else if (proto_is_tcp(sock->info.proto)) /* unified TCPv4 and TCPv6 */
diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c
index d8634ebf..459b20f8 100644
--- a/src/openvpn/tun.c
+++ b/src/openvpn/tun.c
@@ -742,13 +742,23 @@ init_tun(const char *dev, /* --dev option */
struct addrinfo *remote_public,
const bool strict_warn,
struct env_set *es,
- openvpn_net_ctx_t *ctx)
+ openvpn_net_ctx_t *ctx,
+ struct tuntap *tt)
{
struct gc_arena gc = gc_new();
- struct tuntap *tt;
- ALLOC_OBJ(tt, struct tuntap);
- clear_tuntap(tt);
+ if (!tt)
+ {
+ ALLOC_OBJ(tt, struct tuntap);
+ clear_tuntap(tt);
+ }
+#ifdef ENABLE_WINDCO
+ else
+ {
+ ASSERT(!tt->dco.real_tun_init);
+ tt->dco.real_tun_init = true;
+ }
+#endif
tt->type = dev_type_enum(dev, dev_type);
tt->topology = topology;
@@ -891,6 +901,13 @@ init_tun_post(struct tuntap *tt,
{
tt->options = *options;
#ifdef _WIN32
+#ifdef ENABLED_WINDCO
+ if (tt->windows_driver == WINDOWS_DRIVER_WINDCO)
+ {
+ dco_start_tun(tt);
+ return;
+ }
+#endif
overlapped_io_init(&tt->reads, frame, FALSE, true);
overlapped_io_init(&tt->writes, frame, TRUE, true);
tt->adapter_index = TUN_ADAPTER_INDEX_INVALID;
@@ -3438,6 +3455,9 @@ print_windows_driver(enum windows_driver_type windows_driver)
case WINDOWS_DRIVER_WINTUN:
return "wintun";
+
+ case WINDOWS_DRIVER_WINDCO:
+ return "ovpn-dco-win";
default:
return "unspecified";
@@ -3878,6 +3898,11 @@ get_tap_reg(struct gc_arena *gc)
{
windows_driver = WINDOWS_DRIVER_WINTUN;
}
+ else if (strcasecmp(component_id, "ovpn-dco") == 0)
+ {
+ windows_driver = WINDOWS_DRIVER_WINDCO;
+ }
+
if (windows_driver != WINDOWS_DRIVER_UNSPECIFIED)
{
@@ -4232,7 +4257,9 @@ at_least_one_tap_win(const struct tap_reg *tap_reg)
{
if (!tap_reg)
{
- msg(M_FATAL, "There are no TAP-Windows nor Wintun adapters on this system. You should be able to create an adapter by using tapctl.exe utility.");
+ msg(M_FATAL, "There are no TAP-Windows, Wintun or ovpn-dco-win adapters "
+ "on this system. You should be able to create an adapter "
+ "by using tapctl.exe utility.");
}
}
@@ -6433,7 +6460,7 @@ tun_try_open_device(struct tuntap *tt, const char *device_guid, const struct dev
const char *path = NULL;
char tuntap_device_path[256];
- if (tt->windows_driver == WINDOWS_DRIVER_WINTUN)
+ if (tt->windows_driver == WINDOWS_DRIVER_WINTUN || tt->windows_driver == WINDOWS_DRIVER_WINDCO)
{
const struct device_instance_id_interface *dev_if;
@@ -6453,7 +6480,7 @@ tun_try_open_device(struct tuntap *tt, const char *device_guid, const struct dev
}
else
{
- /* Open TAP-Windows adapter */
+ /* Open TAP-Windows or dco-win adapter */
openvpn_snprintf(tuntap_device_path, sizeof(tuntap_device_path), "%s%s%s",
USERMODEDEVICEDIR,
device_guid,
@@ -6489,7 +6516,7 @@ tun_try_open_device(struct tuntap *tt, const char *device_guid, const struct dev
return true;
}
-static void
+void
tun_open_device(struct tuntap *tt, const char *dev_node, const char **device_guid, struct gc_arena *gc)
{
const struct tap_reg *tap_reg = get_tap_reg(gc);
@@ -6780,7 +6807,6 @@ netsh_delete_address_dns(const struct tuntap *tt, bool ipv6, struct gc_arena *gc
argv_free(&argv);
}
-static
void close_tun_handle(struct tuntap* tt)
{
const char* adaptertype = print_windows_driver(tt->windows_driver);
diff --git a/src/openvpn/tun.h b/src/openvpn/tun.h
index d5beb11c..8d3bae47 100644
--- a/src/openvpn/tun.h
+++ b/src/openvpn/tun.h
@@ -40,7 +40,7 @@
#include "misc.h"
#include "networking.h"
#include "ring_buffer.h"
-#include "networking_linuxdco.h"
+#include "dco.h"
#ifdef _WIN32
#define WINTUN_COMPONENT_ID "wintun"
@@ -48,7 +48,8 @@
enum windows_driver_type {
WINDOWS_DRIVER_UNSPECIFIED,
WINDOWS_DRIVER_TAP_WINDOWS6,
- WINDOWS_DRIVER_WINTUN
+ WINDOWS_DRIVER_WINTUN,
+ WINDOWS_DRIVER_WINDCO
};
#endif
@@ -64,6 +65,8 @@ struct tuntap_options {
/* --ip-win32 options */
bool ip_win32_defined;
+ bool disable_dco;
+
#define IPW32_SET_MANUAL 0 /* "--ip-win32 manual" */
#define IPW32_SET_NETSH 1 /* "--ip-win32 netsh" */
#define IPW32_SET_IPAPI 2 /* "--ip-win32 ipapi" */
@@ -246,6 +249,10 @@ tuntap_ring_empty(struct tuntap *tt)
{
return tuntap_is_wintun(tt) && (tt->wintun_send_ring->head == tt->wintun_send_ring->tail);
}
+
+/* Low level function to open tun handle, used by DCO to create a handle for DCO*/
+void
+tun_open_device(struct tuntap* tt, const char* dev_node, const char** device_guid, struct gc_arena* gc);
#endif
/*
@@ -257,6 +264,8 @@ void open_tun(const char *dev, const char *dev_type, const char *dev_node,
void close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx);
+void close_tun_handle(struct tuntap* tt);
+
int write_tun(struct tuntap *tt, uint8_t *buf, int len);
int read_tun(struct tuntap *tt, uint8_t *buf, int len);
@@ -283,7 +292,8 @@ struct tuntap *init_tun(const char *dev, /* --dev option */
struct addrinfo *remote_public,
const bool strict_warn,
struct env_set *es,
- openvpn_net_ctx_t *ctx);
+ openvpn_net_ctx_t *ctx,
+ struct tuntap *tt);
void init_tun_post(struct tuntap *tt,
const struct frame *frame,
@@ -659,6 +669,11 @@ write_tun_buffered(struct tuntap *tt, struct buffer *buf)
}
}
+static inline bool is_windco(struct tuntap *tt)
+{
+ return tt->windows_driver == WINDOWS_DRIVER_WINDCO;
+}
+
#else /* ifdef _WIN32 */
static inline bool
@@ -684,6 +699,12 @@ tun_standby(struct tuntap *tt)
return true;
}
+
+static inline bool is_windco(struct tuntap *tt)
+{
+ return false;
+}
+
#endif /* ifdef _WIN32 */
/*
@@ -707,25 +728,28 @@ tun_set(struct tuntap *tt,
void *arg,
unsigned int *persistent)
{
- if (tuntap_defined(tt))
+ if (!tuntap_defined(tt) || is_windco(tt))
{
- /* if persistent is defined, call event_ctl only if rwflags has changed since last call */
- if (!persistent || *persistent != rwflags)
+ return;
+ }
+
+ /* if persistent is defined, call event_ctl only if rwflags has changed since last call */
+ if (!persistent || *persistent != rwflags)
+ {
+ event_ctl(es, tun_event_handle(tt), rwflags, arg);
+ if (persistent)
{
- event_ctl(es, tun_event_handle(tt), rwflags, arg);
- if (persistent)
- {
- *persistent = rwflags;
- }
+ *persistent = rwflags;
}
+ }
#ifdef _WIN32
- if (tt->windows_driver == WINDOWS_DRIVER_TAP_WINDOWS6 && (rwflags & EVENT_READ))
- {
- tun_read_queue(tt, 0);
- }
-#endif
- tt->rwflags_debug = rwflags;
+ if (tt->windows_driver == WINDOWS_DRIVER_TAP_WINDOWS6 && (rwflags & EVENT_READ))
+ {
+ tun_read_queue(tt, 0);
}
+#endif
+ tt->rwflags_debug = rwflags;
+
}
const char *tun_stat(const struct tuntap *tt, unsigned int rwflags, struct gc_arena *gc);
From patchwork Tue Dec 7 12:11:36 2021
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Openvpn-devel,RFC,7/8] ovpn-dco-win: fix mingw i686 build
X-Patchwork-Submitter: Antonio Quartulli
X-Patchwork-Id: 2117
Message-Id: <20211207121137.3221-8-a@unstable.cc>
To: openvpn-devel@lists.sourceforge.net
Cc: Lev Stipakov
Date: Tue, 7 Dec 2021 13:11:36 +0100
From: Antonio Quartulli
List-Id:
From: Lev Stipakov
mingw i686 has GetOverlappedResultEx() in header file
but not in lib file, causing linking error.
To work around this issue, introduce dco_get_overlapped_result
compat macro, which is defined as GetOverlappedResultEx() for
all build configurations except mingw i686. For latter case,
define compat implementation which uses GetOverlappedResult()
and Sleep().
Signed-off-by: Lev Stipakov
Signed-off-by: Arne Schwabe
---
src/compat/Makefile.am | 3 +-
src/compat/compat-dco_get_overlapped_result.c | 44 +++++++++++++++++++
src/compat/compat.h | 6 +++
src/compat/compat.vcxproj | 1 +
src/compat/compat.vcxproj.filters | 3 ++
src/openvpn/networking_windco.c | 11 +++--
6 files changed, 64 insertions(+), 4 deletions(-)
create mode 100644 src/compat/compat-dco_get_overlapped_result.c
diff --git a/src/compat/Makefile.am b/src/compat/Makefile.am
index 95eb04bb..cefc2d81 100644
--- a/src/compat/Makefile.am
+++ b/src/compat/Makefile.am
@@ -28,4 +28,5 @@ libcompat_la_SOURCES = \
compat-gettimeofday.c \
compat-daemon.c \
compat-strsep.c \
- compat-versionhelpers.h
+ compat-versionhelpers.h \
+ compat-dco_get_overlapped_result.c
diff --git a/src/compat/compat-dco_get_overlapped_result.c b/src/compat/compat-dco_get_overlapped_result.c
new file mode 100644
index 00000000..4a52dd46
--- /dev/null
+++ b/src/compat/compat-dco_get_overlapped_result.c
@@ -0,0 +1,44 @@
+/*
+ * OpenVPN -- An application to securely tunnel IP networks
+ * over a single UDP port, with support for SSL/TLS-based
+ * session authentication and key exchange,
+ * packet encryption, packet authentication, and
+ * packet compression.
+ *
+ * Copyright (C) 2021 Lev Stipakov
+ * Copyright (C) 2021 OpenVPN Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program (see the file COPYING included with this
+ * distribution); if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "compat.h"
+
+#if defined(__MINGW32__) && !defined(__MINGW64__)
+BOOL dco_get_overlapped_result(HANDLE handle, OVERLAPPED* ov, DWORD* transferred, DWORD delay_millisec, BOOL unused)
+{
+ BOOL res = GetOverlappedResult(handle, ov, transferred, FALSE);
+ if ((res == 0) && (GetLastError() == ERROR_IO_INCOMPLETE))
+ {
+ Sleep(delay_millisec);
+ }
+ return res;
+}
+#endif
diff --git a/src/compat/compat.h b/src/compat/compat.h
index 026974a8..274febce 100644
--- a/src/compat/compat.h
+++ b/src/compat/compat.h
@@ -62,4 +62,10 @@ char *strsep(char **stringp, const char *delim);
#endif
+#if defined(__MINGW32__) && !defined(__MINGW64__)
+BOOL dco_get_overlapped_result(HANDLE handle, OVERLAPPED* ov, DWORD* transferred, DWORD delay_millisec, BOOL unused);
+#else
+#define dco_get_overlapped_result GetOverlappedResultEx
+#endif
+
#endif /* COMPAT_H */
diff --git a/src/compat/compat.vcxproj b/src/compat/compat.vcxproj
index fe03a51a..1dacb503 100644
--- a/src/compat/compat.vcxproj
+++ b/src/compat/compat.vcxproj
@@ -159,6 +159,7 @@
+
diff --git a/src/compat/compat.vcxproj.filters b/src/compat/compat.vcxproj.filters
index 96ca026a..73fc9f91 100644
--- a/src/compat/compat.vcxproj.filters
+++ b/src/compat/compat.vcxproj.filters
@@ -30,6 +30,9 @@
Source Files
+
+ Source Files
+
diff --git a/src/openvpn/networking_windco.c b/src/openvpn/networking_windco.c
index 3c88b752..f4ec01bc 100644
--- a/src/openvpn/networking_windco.c
+++ b/src/openvpn/networking_windco.c
@@ -74,17 +74,22 @@ void dco_start_tun(struct tuntap* tt)
int dco_connect_wait(HANDLE handle, OVERLAPPED* ov, int timeout, volatile int* signal_received)
{
- while (timeout-- > 0)
+ DWORD timeout_msec = timeout * 1000;
+ const int poll_interval_ms = 50;
+
+ while (timeout_msec > 0)
{
+ timeout_msec -= poll_interval_ms;
+
DWORD transferred;
- if (GetOverlappedResultEx(handle, ov, &transferred, 1000, FALSE) != 0)
+ if (dco_get_overlapped_result(handle, ov, &transferred, poll_interval_ms, FALSE) != 0)
{
/* TCP connection established by dco */
return 0;
}
DWORD err = GetLastError();
- if (err != WAIT_TIMEOUT)
+ if ((err != WAIT_TIMEOUT) && (err != ERROR_IO_INCOMPLETE))
{
/* dco reported connection error */
struct gc_arena gc = gc_new();
From patchwork Tue Dec 7 12:11:37 2021
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Openvpn-devel,RFC,8/8] ovpn-dco: force user to set DCO_INCLUDEDIR
X-Patchwork-Submitter: Antonio Quartulli
X-Patchwork-Id: 2116
Message-Id: <20211207121137.3221-9-a@unstable.cc>
To: openvpn-devel@lists.sourceforge.net
Cc: Antonio Quartulli
Date: Tue, 7 Dec 2021 13:11:37 +0100
From: Antonio Quartulli
List-Id:
Signed-off-by: Antonio Quartulli
---
configure.ac | 32 ++++++++++----------------------
1 file changed, 10 insertions(+), 22 deletions(-)
diff --git a/configure.ac b/configure.ac
index b6ecb23a..1911f24e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -777,29 +777,22 @@ PKG_CHECK_MODULES(
if test "$enable_dco" = "yes"; then
dnl
-dnl Configure path for the ovpn-dco kernel module source directory.
+dnl Configure path for the ovpn-dco header directory.
dnl
-dnl This is similar to the core librariy, there is an embedded
-dnl version in this tree which will be used by default. The
-dnl git checkout inside the ovpn-dco/ directory is managed via git
-dnl submodule.
-dnl
- AC_ARG_VAR([DCO_SOURCEDIR], [Alternative ovpn-dco kernel module source directory])
- if test -z "${DCO_SOURCEDIR}"; then
- case "$host" in
- *-mingw*) DCO_SOURCEDIR="${srcdir}/../ovpn-dco-win";;
- *) DCO_SOURCEDIR="${srcdir}/../ovpn-dco";;
- esac
+ AC_ARG_VAR([DCO_INCLUDEDIR], [ovpn-dco header directory (where to find ovpn-dco.h)])
+ if test -z "${DCO_INCLUDEDIR}"; then
+ AC_MSG_ERROR([You must specify DCO_INCLUDEDIR when using --enable-dco.])
+
fi
- AC_MSG_NOTICE([Using ovpn-dco source directory: ${DCO_SOURCEDIR}])
- AC_SUBST([DCO_SOURCEDIR])
+
+ AC_MSG_NOTICE([Using ovpn-dco header directory: ${DCO_INCLUDEDIR}])
+ DCO_CFLAGS="-I${DCO_INCLUDEDIR}"
case "$host" in
*-*-linux*)
dnl
dnl Include generic netlink library used to talk to ovpn-dco
dnl
- saved_CFLAGS="${CFLAGS}"
PKG_CHECK_MODULES(
[LIBNL_GENL],
[libnl-genl-3.0 >= 3.2.29],
@@ -807,16 +800,12 @@ dnl
[AC_MSG_ERROR([libnl-genl-3.0 package not found or too old. Is the development package and pkg-config installed? Must be version 3.4.0 or newer])]
)
- DCO_CFLAGS="-I${DCO_SOURCEDIR}/include/uapi ${LIBNL_GENL_CFLAGS}"
-
- CFLAGS="${CFLAGS} ${DCO_CFLAGS}"
+ CFLAGS="${CFLAGS} ${DCO_CFLAGS} ${LIBNL_GENL_CFLAGS}"
AC_CHECK_HEADERS(
[linux/ovpn_dco.h],
,
- [AC_MSG_ERROR([linux/ovpn_dco.h is missing (use DCO_SOURCE to set path to it, CFLAGS=${CFLAGS})])]
+ [AC_MSG_ERROR([linux/ovpn_dco.h couldn't be found (use DCO_INCLUDEDIR to set the path to it, CFLAGS=${CFLAGS})])]
)
- CFLAGS=${saved_CFLAGS}
-
OPTIONAL_DCO_LIBS="${LIBNL_GENL_LIBS}"
AC_DEFINE(ENABLE_LINUXDCO, 1, [Enable linux data channel offload])
@@ -1407,7 +1396,6 @@ AC_SUBST([OPTIONAL_PKCS11_HELPER_CFLAGS])
AC_SUBST([OPTIONAL_PKCS11_HELPER_LIBS])
AC_SUBST([OPTIONAL_INOTIFY_CFLAGS])
AC_SUBST([OPTIONAL_INOTIFY_LIBS])
-AC_SUBST([OPTIONAL_DCO_CFLAGS])
AC_SUBST([OPTIONAL_DCO_LIBS])
AC_SUBST([PLUGIN_AUTH_PAM_CFLAGS])