Internal change

PiperOrigin-RevId: 374242514
Change-Id: Ia58df878d4572a3e9ad1aae3a7c411f07d4ab31d
diff --git a/INSTALL.openvpn b/INSTALL.openvpn
index a5936b3..9db5b64 100644
--- a/INSTALL.openvpn
+++ b/INSTALL.openvpn
@@ -71,12 +71,13 @@
   (1) TUN and/or TAP driver to allow user-space programs to control
       a virtual point-to-point IP or Ethernet device.  See
       TUN/TAP Driver Configuration section below for more info.
-
-OPTIONAL (but recommended):
-  (1) OpenSSL library, necessary for encryption, version 0.9.8 or higher
+  (2) OpenSSL library, necessary for encryption, version 1.0.2 or higher
       required, available from http://www.openssl.org/
-  (2) mbed TLS library, an alternative for encryption, version 2.0 or higher
+      or
+  (3) mbed TLS library, an alternative for encryption, version 2.0 or higher
       required, available from https://tls.mbed.org/
+
+OPTIONAL:
   (3) LZO real-time compression library, required for link compression,
       available from http://www.oberhumer.com/opensource/lzo/
       OpenBSD users can use ports or packages to install lzo, but remember
@@ -145,7 +146,7 @@
 
 Test Crypto:
 
-./openvpn --genkey --secret key
+./openvpn --genkey secret key
 ./openvpn --test-crypto --secret key
 
 Test SSL/TLS negotiations (runs for 2 minutes):
@@ -156,6 +157,20 @@
 For more thorough client-server tests you can configure your own, private test
 environment. See tests/t_client.rc-sample for details.
 
+To do the C unit tests, you need to have the "cmocka" test framework
+installed on your system.  More recent distributions already ship this
+as part of their packages/ports.  If your system does not have it,
+you can install cmocka with these commands:
+
+  $ git clone https://git.cryptomilk.org/projects/cmocka.git
+  $ cd cmocka
+  $ mkdir build
+  $ cd build
+  $ cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Debug ..
+  $ make
+  $ sudo make install
+
+
 *************************************************************************
 
 OPTIONS for ./configure:
@@ -213,7 +228,6 @@
   ROUTE       full path to route utility
   IPROUTE     full path to ip utility
   NETSTAT     path to netstat utility
-  MAN2HTML    path to man2html utility
   GIT         path to git utility
   SYSTEMD_ASK_PASSWORD
               path to systemd-ask-password utility
@@ -221,6 +235,8 @@
               Path of systemd unit directory [default=LIBDIR/systemd/system]
   TMPFILES_DIR
               Path of tmpfiles directory [default=LIBDIR/tmpfiles.d]
+  RST2MAN     Path to rst2man utility
+  RST2HTML    Path to rst2html utility
 
 ENVIRONMENT variables adjusting parameters related to dependencies
 
diff --git a/Makefile.am b/Makefile.am
index 753c526..d1c10fc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -23,9 +23,6 @@
 #  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 #
 
-# This option prevents autoreconf from overriding our COPYING and
-# INSTALL targets:
-AUTOMAKE_OPTIONS = foreign 1.9
 ACLOCAL_AMFLAGS = -I m4
 
 MAINTAINERCLEANFILES = \
@@ -46,21 +43,33 @@
 	contrib \
 	debug
 
-.PHONY: config-version.h
+.PHONY: config-version.h doxygen
 
 if GIT_CHECKOUT
 BUILT_SOURCES = \
 	config-version.h
 endif
 
-SUBDIRS = include src
+SUBDIRS = build distro include src sample doc tests
 
 dist_doc_DATA = \
-	README.IPv6
-	LICENSE
+	README \
+	README.IPv6 \
+	README.mbedtls \
+	Changes.rst \
+	COPYRIGHT.GPL \
+	COPYING
 
 dist_noinst_DATA = \
-	README.IPv6
+	.gitignore \
+	.gitattributes \
+	PORTS \
+	README.IPv6 TODO.IPv6 \
+	README.mbedtls \
+	openvpn.sln \
+	msvc-env.bat \
+	msvc-dev.bat \
+	msvc-build.bat
 
 dist_noinst_HEADERS = \
 	config-msvc.h \
@@ -84,3 +93,6 @@
 	else \
 		rm -f config-version.h.tmp; \
 	fi
+
+doxygen:
+	$(MAKE) -C doc/doxygen doxygen
diff --git a/README.ec b/README.ec
index 3293801..61f23b2 100644
--- a/README.ec
+++ b/README.ec
@@ -12,14 +12,15 @@
 used for authentication, the curve used for the server certificate will be used
 for ECDH too. When autodetection fails (e.g. when using RSA certificates)
 OpenVPN lets the crypto library decide if possible, or falls back to the
-secp384r1 curve.
+secp384r1 curve. The list of groups/curves that the crypto library will choose
+from can be set with the --tls-groups <grouplist> option.
 
 An administrator can force an OpenVPN/OpenSSL server to use a specific curve
 using the --ecdh-curve <curvename> option with one of the curves listed as
-available by the --show-curves option. Clients will use the same curve as
+available by the --show-groups option. Clients will use the same curve as
 selected by the server.
 
-Note that not all curves listed by --show-curves are available for use with TLS;
+Note that not all curves listed by --show-groups are available for use with TLS;
 in that case connecting will fail with a 'no shared cipher' TLS error.
 
 Authentication (ECDSA)
diff --git a/config-msvc.h b/config-msvc.h
index 0bb153d..f199bb2 100644
--- a/config-msvc.h
+++ b/config-msvc.h
@@ -4,7 +4,6 @@
 
 #define ENABLE_DEF_AUTH 1
 #define ENABLE_PF 1
-#define ENABLE_CRYPTO 1
 #define ENABLE_CRYPTO_OPENSSL 1
 #define ENABLE_DEBUG 1
 #define ENABLE_EUREPHIA 1
@@ -76,6 +75,44 @@
 #define HAVE_POLL 1
 
 #define HAVE_OPENSSL_ENGINE 1
+/* hardcode usage of OpenSSL 1.1.x */
+#define HAVE_EVP_MD_CTX_RESET 1
+#define HAVE_EVP_MD_CTX_FREE 1
+#define HAVE_EVP_MD_CTX_NEW 1
+#define HAVE_HMAC_CTX_RESET 1
+#define HAVE_HMAC_CTX_FREE 1
+#define HAVE_HMAC_CTX_NEW 1
+#define HAVE_SSL_CTX_GET_DEFAULT_PASSWD_CB_USERDATA 1
+#define HAVE_SSL_CTX_GET_DEFAULT_PASSWD_CB 1
+#define HAVE_X509_GET0_PUBKEY 1
+#define HAVE_X509_STORE_GET0_OBJECTS 1
+#define HAVE_X509_OBJECT_FREE 1
+#define HAVE_X509_OBJECT_GET_TYPE 1
+#define HAVE_EVP_PKEY_GET0_RSA 1
+#define HAVE_EVP_PKEY_GET0_EC_KEY 1
+#define HAVE_EVP_PKEY_ID 1
+#define HAVE_EVP_PKEY_GET0_DSA 1
+#define HAVE_RSA_SET_FLAGS 1
+#define HAVE_RSA_GET0_KEY 1
+#define HAVE_RSA_SET0_KEY 1
+#define HAVE_RSA_BITS 1
+#define HAVE_DSA_GET0_PQG 1
+#define HAVE_DSA_BITS 1
+#define HAVE_RSA_METH_NEW 1
+#define HAVE_RSA_METH_FREE 1
+#define HAVE_RSA_METH_SET_PUB_ENC 1
+#define HAVE_RSA_METH_SET_PUB_DEC 1
+#define HAVE_RSA_METH_SET_PRIV_ENC 1
+#define HAVE_RSA_METH_SET_PRIV_DEC 1
+#define HAVE_RSA_METH_SET_INIT 1
+#define HAVE_RSA_METH_SET_SIGN 1
+#define HAVE_RSA_METH_SET_FINISH 1
+#define HAVE_RSA_METH_SET0_APP_DATA 1
+#define HAVE_RSA_METH_GET0_APP_DATA 1
+#define HAVE_EC_GROUP_ORDER_BITS 1
+#define OPENSSL_NO_EC 1
+#define HAVE_EVP_CIPHER_CTX_RESET 1
+#define HAVE_DIINSTALLDEVICE 1
 
 #define PATH_SEPARATOR     '\\'
 #define PATH_SEPARATOR_STR "\\"
diff --git a/config.h b/config.h
index 14ccd59..2170e28 100644
--- a/config.h
+++ b/config.h
@@ -2,7 +2,7 @@
 /* config.h.in.  Generated from configure.ac by autoheader.  */
 
 /* Configuration settings */
-#define CONFIGURE_DEFINES "enable_async_push=no enable_comp_stub=no enable_crypto=yes enable_crypto_ofb_cfb=yes enable_debug=yes enable_def_auth=yes enable_dlopen=unknown enable_dlopen_self=unknown enable_dlopen_self_static=unknown enable_fast_install=needless enable_fragment=yes enable_iproute2=no enable_libtool_lock=yes enable_lz4=no enable_lzo=yes enable_management=yes enable_multihome=yes enable_pam_dlopen=no enable_pedantic=no enable_pf=yes enable_pkcs11=no enable_plugin_auth_pam=no enable_plugin_down_root=yes enable_plugins=yes enable_port_share=yes enable_selinux=no enable_server=yes enable_shared=yes enable_shared_with_static_runtimes=no enable_small=no enable_static=yes enable_strict=no enable_strict_options=no enable_systemd=no enable_werror=no enable_win32_dll=yes enable_x509_alt_username=no with_aix_soname=aix with_crypto_library=openssl with_gnu_ld=yes with_mem_check=no with_sysroot=no"
+#define CONFIGURE_DEFINES "enable_async_push=no enable_comp_stub=no enable_crypto_ofb_cfb=yes enable_debug=yes enable_def_auth=yes enable_dlopen=unknown enable_dlopen_self=unknown enable_dlopen_self_static=unknown enable_fast_install=needless enable_fragment=yes enable_iproute2=no enable_libtool_lock=yes enable_lz4=yes enable_lzo=yes enable_management=yes enable_multihome=yes enable_pam_dlopen=no enable_pedantic=no enable_pf=yes enable_pkcs11=no enable_plugin_auth_pam=yes enable_plugin_down_root=yes enable_plugins=yes enable_port_share=yes enable_selinux=no enable_shared=yes enable_shared_with_static_runtimes=no enable_small=no enable_static=yes enable_strict=no enable_strict_options=no enable_systemd=no enable_werror=no enable_win32_dll=yes enable_x509_alt_username=no with_aix_soname=aix with_crypto_library=openssl with_gnu_ld=yes with_mem_check=no with_sysroot=no"
 
 /* special build string */
 /* #undef CONFIGURE_SPECIAL_BUILD */
@@ -22,15 +22,9 @@
 /* Enable async push */
 /* #undef ENABLE_ASYNC_PUSH */
 
-/* Enable client capability only */
-/* #undef ENABLE_CLIENT_ONLY */
-
 /* Enable compression stub capability */
 /* #undef ENABLE_COMP_STUB */
 
-/* Enable crypto library */
-#define ENABLE_CRYPTO 1
-
 /* Use mbed TLS library */
 /* #undef ENABLE_CRYPTO_MBEDTLS */
 
@@ -82,6 +76,9 @@
 /* SELinux support */
 /* #undef ENABLE_SELINUX */
 
+/* enable sitnl support */
+#define ENABLE_SITNL 1
+
 /* Enable smaller executable size */
 /* #undef ENABLE_SMALL */
 
@@ -100,9 +97,6 @@
 /* Define to 1 if you have the `access' function. */
 #define HAVE_ACCESS 1
 
-/* Use crypto library */
-#define HAVE_AEAD_CIPHER_MODES 1
-
 /* Compiler supports anonymous unions */
 #define HAVE_ANONYMOUS_UNION_SUPPORT /**/
 
@@ -254,6 +248,9 @@
 /* Define to 1 if you have the `execve' function. */
 #define HAVE_EXECVE 1
 
+/* Crypto library supports keying material exporter */
+#define HAVE_EXPORT_KEYING_MATERIAL 1
+
 /* Define to 1 if you have the <fcntl.h> header file. */
 #define HAVE_FCNTL_H 1
 
@@ -431,9 +428,15 @@
 /* OpenSSL engine support available */
 /* #undef HAVE_OPENSSL_ENGINE */
 
+/* Define to 1 if you have the `OpenSSL_version' function. */
+#define HAVE_OPENSSL_VERSION 1
+
 /* Define to 1 if you have the `poll' function. */
 #define HAVE_POLL 1
 
+/* Define to 1 if you have the <poll.h> header file. */
+#define HAVE_POLL_H 1
+
 /* Define to 1 if you have the `putenv' function. */
 #define HAVE_PUTENV 1
 
@@ -552,6 +555,9 @@
 /* Define to 1 if you have the `SSL_CTX_new' function. */
 #define HAVE_SSL_CTX_NEW 1
 
+/* Define to 1 if you have the `SSL_CTX_set1_groups' function. */
+/* #undef HAVE_SSL_CTX_SET1_GROUPS */
+
 /* Define to 1 if you have the `SSL_CTX_set_security_level' function. */
 #define HAVE_SSL_CTX_SET_SECURITY_LEVEL 1
 
@@ -582,6 +588,9 @@
 /* Define to 1 if you have the <stropts.h> header file. */
 /* #undef HAVE_STROPTS_H */
 
+/* Define to 1 if you have the `strsep' function. */
+#define HAVE_STRSEP 1
+
 /* Define to 1 if you have the `syslog' function. */
 #define HAVE_SYSLOG 1
 
@@ -612,9 +621,6 @@
 /* Define to 1 if you have the <sys/mman.h> header file. */
 #define HAVE_SYS_MMAN_H 1
 
-/* Define to 1 if you have the <sys/poll.h> header file. */
-#define HAVE_SYS_POLL_H 1
-
 /* Define to 1 if you have the <sys/socket.h> header file. */
 #define HAVE_SYS_SOCKET_H 1
 
@@ -724,13 +730,13 @@
 #define OPENVPN_VERSION_MAJOR 2
 
 /* OpenVPN minor version - integer */
-#define OPENVPN_VERSION_MINOR 4
+#define OPENVPN_VERSION_MINOR 5
 
 /* OpenVPN patch level - may be a string or integer */
-#define OPENVPN_VERSION_PATCH ".9"
+#define OPENVPN_VERSION_PATCH ".2"
 
 /* Version in windows resource format */
-#define OPENVPN_VERSION_RESOURCE 2,4,9,0
+#define OPENVPN_VERSION_RESOURCE 2,5,2,0
 
 /* Name of package */
 #define PACKAGE "openvpn"
@@ -742,7 +748,7 @@
 #define PACKAGE_NAME "OpenVPN"
 
 /* Define to the full name and version of this package. */
-#define PACKAGE_STRING "OpenVPN 2.4.9"
+#define PACKAGE_STRING "OpenVPN 2.5.2"
 
 /* Define to the one symbol short name of this package. */
 #define PACKAGE_TARNAME "openvpn"
@@ -751,7 +757,7 @@
 #define PACKAGE_URL ""
 
 /* Define to the version of this package. */
-#define PACKAGE_VERSION "2.4.9"
+#define PACKAGE_VERSION "2.5.2"
 
 /* Path separator */
 #define PATH_SEPARATOR '/'
@@ -869,7 +875,7 @@
 /* #undef USE_VALGRIND */
 
 /* Version number of package */
-#define VERSION "2.4.9"
+#define VERSION "2.5.2"
 
 /* Define to 1 if on MINIX. */
 /* #undef _MINIX */
diff --git a/configure.ac b/configure.ac
index f61255b..ebb3220 100644
--- a/configure.ac
+++ b/configure.ac
@@ -54,7 +54,9 @@
                 awk '{split ($NF,a,"."); if (a[1] == 1 && a[2] >= 12) { print "serial-tests" }}'
     ])
 ])
-AM_INIT_AUTOMAKE(foreign serial_tests) dnl NB: Do not [quote] this parameter.
+# This foreign option prevents autoreconf from overriding our COPYING and
+# INSTALL targets:
+AM_INIT_AUTOMAKE(foreign serial_tests 1.9) dnl NB: Do not [quote] this parameter.
 AC_CANONICAL_HOST
 AC_USE_SYSTEM_EXTENSIONS
 
@@ -78,13 +80,6 @@
 )
 
 AC_ARG_ENABLE(
-	[crypto],
-	[AS_HELP_STRING([--disable-crypto], [disable crypto support @<:@default=yes@:>@])],
-	,
-	[enable_crypto="yes"]
-)
-
-AC_ARG_ENABLE(
 	[ofb-cfb],
 	[AS_HELP_STRING([--disable-ofb-cfb], [disable support for OFB and CFB cipher modes @<:@default=yes@:>@])],
 	,
@@ -99,13 +94,6 @@
 )
 
 AC_ARG_ENABLE(
-	[server],
-	[AS_HELP_STRING([--disable-server], [disable server support only (but retain client support) @<:@default=yes@:>@])],
-	,
-	[enable_server="yes"]
-)
-
-AC_ARG_ENABLE(
 	[plugins],
 	[AS_HELP_STRING([--disable-plugins], [disable plug-in support @<:@default=yes@:>@])],
 	,
@@ -251,7 +239,7 @@
 
 AC_ARG_ENABLE(
 	[systemd],
-	[AS_HELP_STRING([--enable-systemd], [enable systemd suppport @<:@default=no@:>@])],
+	[AS_HELP_STRING([--enable-systemd], [enable systemd support @<:@default=no@:>@])],
 	,
 	[enable_systemd="no"]
 )
@@ -301,15 +289,19 @@
 fi
 
 AC_DEFINE_UNQUOTED([TARGET_ALIAS], ["${host}"], [A string representing our host])
+AM_CONDITIONAL([TARGET_LINUX], [false])
 case "$host" in
 	*-*-linux*)
 		AC_DEFINE([TARGET_LINUX], [1], [Are we running on Linux?])
+		AM_CONDITIONAL([TARGET_LINUX], [true])
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["L"], [Target prefix])
+		have_sitnl="yes"
 		;;
 	*-*-solaris*)
 		AC_DEFINE([TARGET_SOLARIS], [1], [Are we running on Solaris?])
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["S"], [Target prefix])
 		CPPFLAGS="$CPPFLAGS -D_XPG4_2"
+		test -x /bin/bash && SHELL="/bin/bash"
 		;;
 	*-*-openbsd*)
 		AC_DEFINE([TARGET_OPENBSD], [1], [Are we running on OpenBSD?])
@@ -364,7 +356,6 @@
 AC_ARG_VAR([ROUTE], [full path to route utility])
 AC_ARG_VAR([IPROUTE], [full path to ip utility])
 AC_ARG_VAR([NETSTAT], [path to netstat utility]) # tests
-AC_ARG_VAR([MAN2HTML], [path to man2html utility])
 AC_ARG_VAR([GIT], [path to git utility])
 AC_ARG_VAR([SYSTEMD_ASK_PASSWORD], [path to systemd-ask-password utility])
 AC_ARG_VAR([SYSTEMD_UNIT_DIR], [Path of systemd unit directory @<:@default=LIBDIR/systemd/system@:>@])
@@ -374,13 +365,21 @@
 AC_PATH_PROGS([IPROUTE], [ip],, [$PATH:/usr/local/sbin:/usr/sbin:/sbin])
 AC_PATH_PROGS([SYSTEMD_ASK_PASSWORD], [systemd-ask-password],, [$PATH:/usr/local/bin:/usr/bin:/bin])
 AC_CHECK_PROGS([NETSTAT], [netstat], [netstat], [$PATH:/usr/local/sbin:/usr/sbin:/sbin:/etc]) # tests
-AC_CHECK_PROGS([MAN2HTML], [man2html])
 AC_CHECK_PROGS([GIT], [git]) # optional
 AC_DEFINE_UNQUOTED([IFCONFIG_PATH], ["$IFCONFIG"], [Path to ifconfig tool])
 AC_DEFINE_UNQUOTED([IPROUTE_PATH], ["$IPROUTE"], [Path to iproute tool])
 AC_DEFINE_UNQUOTED([ROUTE_PATH], ["$ROUTE"], [Path to route tool])
 AC_DEFINE_UNQUOTED([SYSTEMD_ASK_PASSWORD_PATH], ["$SYSTEMD_ASK_PASSWORD"], [Path to systemd-ask-password tool])
 
+#
+#  man page generation - based on python-docutils
+#
+AC_ARG_VAR([RST2MAN], [path to rst2man utility])
+AC_ARG_VAR([RST2HTML], [path to rst2html utility])
+AC_CHECK_PROGS([RST2MAN], [rst2man])
+AC_CHECK_PROGS([RST2HTML], [rst2html])
+AM_CONDITIONAL([HAVE_PYDOCUTILS], [test "${RST2MAN}" -a "${RST2HTML}"])
+
 # Set -std=c99 unless user already specified a -std=
 case "${CFLAGS}" in
   *-std=*) ;;
@@ -441,7 +440,7 @@
 	unistd.h signal.h libgen.h stropts.h \
 	syslog.h pwd.h grp.h \
 	sys/sockio.h sys/uio.h linux/sockios.h \
-	linux/types.h sys/poll.h sys/epoll.h err.h \
+	linux/types.h poll.h sys/epoll.h err.h \
 ])
 
 SOCKET_INCLUDES="
@@ -658,7 +657,7 @@
 	ctime memset vsnprintf strdup \
 	setsid chdir putenv getpeername unlink \
 	chsize ftruncate execve getpeereid umask basename dirname access \
-	epoll_create \
+	epoll_create strsep \
 ])
 
 AC_CHECK_LIB(
@@ -841,7 +840,7 @@
 	[]
 )
 
-if test "${enable_crypto}" = "yes" -a "${with_crypto_library}" = "openssl"; then
+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])
 
@@ -849,11 +848,10 @@
 		# if the user did not explicitly specify flags, try to autodetect
 		PKG_CHECK_MODULES(
 			[OPENSSL],
-			[libcrypto >= 0.9.8, libssl >= 0.9.8],
-	        [have_openssl="yes"],
-			[have_openssl="no"] # Provide if-not-found to prevent erroring out
+			[openssl >= 1.0.2],
+			[have_openssl="yes"],
+			[] # If this fails, we will do another test next
 		)
-
 		OPENSSL_LIBS=${OPENSSL_LIBS:--lssl -lcrypto}
 	fi
 
@@ -862,6 +860,27 @@
 	CFLAGS="${CFLAGS} ${OPENSSL_CFLAGS}"
 	LIBS="${LIBS} ${OPENSSL_LIBS}"
 
+	# If pkgconfig check failed or OPENSSL_CFLAGS/OPENSSL_LIBS env vars
+	# are used, check the version directly in the OpenSSL include file
+	if test "${have_openssl}" != "yes"; then
+		AC_MSG_CHECKING([additionally if OpenSSL is available and version >= 1.0.2])
+		AC_COMPILE_IFELSE(
+			[AC_LANG_PROGRAM(
+				[[
+#include <openssl/opensslv.h>
+				]],
+				[[
+/*	     Version encoding: MNNFFPPS - see opensslv.h for details */
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+#error OpenSSL too old
+#endif
+				]]
+			)],
+			[AC_MSG_RESULT([ok])],
+			[AC_MSG_ERROR([OpenSSL version too old])]
+		)
+	fi
+
 	AC_CHECK_FUNCS([SSL_CTX_new EVP_CIPHER_CTX_set_key_length],
 				   ,
 				   [AC_MSG_ERROR([openssl check failed])]
@@ -888,13 +907,16 @@
 		AC_DEFINE([HAVE_OPENSSL_ENGINE], [1], [OpenSSL engine support available])
 	fi
 
-	have_crypto_aead_modes="yes"
-	AC_CHECK_FUNCS(
+	AC_CHECK_FUNC(
 		[EVP_aes_256_gcm],
 		,
-		[have_crypto_aead_modes="no"; break]
+		[AC_MSG_ERROR([OpenSSL check for AES-256-GCM support failed])]
 	)
 
+	# All supported OpenSSL version (>= 1.0.2)
+	# have this feature
+	have_export_keying_material="yes"
+
 	AC_CHECK_FUNCS(
 		[ \
 			HMAC_CTX_new \
@@ -904,8 +926,10 @@
 			EVP_MD_CTX_free \
 			EVP_MD_CTX_reset \
 			EVP_CIPHER_CTX_reset \
+			OpenSSL_version \
 			SSL_CTX_get_default_passwd_cb \
 			SSL_CTX_get_default_passwd_cb_userdata \
+			SSL_CTX_set1_groups \
 			SSL_CTX_set_security_level \
 			X509_get0_notBefore \
 			X509_get0_notAfter \
@@ -913,7 +937,6 @@
 			X509_STORE_get0_objects \
 			X509_OBJECT_free \
 			X509_OBJECT_get_type \
-			EVP_PKEY_id \
 			EVP_PKEY_get0_RSA \
 			EVP_PKEY_get0_DSA \
 			EVP_PKEY_get0_EC_KEY \
@@ -941,11 +964,10 @@
 	CFLAGS="${saved_CFLAGS}"
 	LIBS="${saved_LIBS}"
 
-	have_crypto="yes"
 	AC_DEFINE([ENABLE_CRYPTO_OPENSSL], [1], [Use OpenSSL library])
 	CRYPTO_CFLAGS="${OPENSSL_CFLAGS}"
 	CRYPTO_LIBS="${OPENSSL_LIBS}"
-elif test "${enable_crypto}" = "yes" -a "${with_crypto_library}" = "mbedtls"; then
+elif test "${with_crypto_library}" = "mbedtls"; then
 	AC_ARG_VAR([MBEDTLS_CFLAGS], [C compiler flags for mbedtls])
 	AC_ARG_VAR([MBEDTLS_LIBS], [linker flags for mbedtls])
 
@@ -983,52 +1005,28 @@
 		[AC_MSG_ERROR([mbed TLS 2.y.z required])]
 	)
 
-	mbedtls_with_pkcs11="no"
-	AC_COMPILE_IFELSE(
-		[AC_LANG_PROGRAM(
-			[[
-#include <mbedtls/config.h>
-			]],
-			[[
-#ifndef MBEDTLS_PKCS11_C
-#error pkcs11 wrapper missing
-#endif
-			]]
-		)],
-		mbedtls_with_pkcs11="yes")
-
-	AC_MSG_CHECKING([mbedtls pkcs11 support])
-	if test "${enable_pkcs11}" = "yes"; then
-		if test "${mbedtls_with_pkcs11}" = "yes"; then
-			AC_MSG_RESULT([ok])
-		else
-			AC_MSG_ERROR([mbedtls has no pkcs11 wrapper compiled in])
-		fi
-	else
-		if test "${mbedtls_with_pkcs11}" != "yes"; then
-			AC_MSG_RESULT([ok])
-		else
-			AC_MSG_ERROR([mbed TLS compiled with PKCS11, while OpenVPN is not])
-		fi
-	fi
-
-	have_crypto_aead_modes="yes"
 	AC_CHECK_FUNCS(
 		[ \
 			mbedtls_cipher_write_tag \
 			mbedtls_cipher_check_tag \
 		],
 		,
-		[have_crypto_aead_modes="no"; break]
+		[AC_MSG_ERROR([mbed TLS check for AEAD support failed])]
+	)
+
+	have_export_keying_material="yes"
+	AC_CHECK_FUNC(
+		[mbedtls_ssl_conf_export_keys_ext_cb],
+		,
+		[have_export_keying_material="no"]
 	)
 
 	CFLAGS="${saved_CFLAGS}"
 	LIBS="${saved_LIBS}"
-	have_crypto="yes"
 	AC_DEFINE([ENABLE_CRYPTO_MBEDTLS], [1], [Use mbed TLS library])
 	CRYPTO_CFLAGS="${MBEDTLS_CFLAGS}"
 	CRYPTO_LIBS="${MBEDTLS_LIBS}"
-elif test "${enable_crypto}" = "yes"; then
+else
 	AC_MSG_ERROR([Invalid crypto library: ${with_crypto_library}])
 fi
 
@@ -1217,7 +1215,6 @@
 fi
 
 test "${ac_cv_header_sys_uio_h}" = "yes" && AC_DEFINE([HAVE_IOVEC], [1], [struct iovec needed for IPv6 support])
-test "${enable_server}" = "no" && AC_DEFINE([ENABLE_CLIENT_ONLY], [1], [Enable client capability only])
 test "${enable_management}" = "yes" && AC_DEFINE([ENABLE_MANAGEMENT], [1], [Enable management server capability])
 test "${enable_multihome}" = "yes" && AC_DEFINE([ENABLE_MULTIHOME], [1], [Enable multi-homed UDP server capability])
 test "${enable_debug}" = "yes" && AC_DEFINE([ENABLE_DEBUG], [1], [Enable debugging support])
@@ -1228,14 +1225,15 @@
 test "${enable_pf}" = "yes" && AC_DEFINE([ENABLE_PF], [1], [Enable internal packet filter])
 test "${enable_strict_options}" = "yes" && AC_DEFINE([ENABLE_STRICT_OPTIONS_CHECK], [1], [Enable strict options check between peers])
 
-if test "${enable_crypto}" = "yes"; then
-	test "${have_crypto}" != "yes" && AC_MSG_ERROR([${with_crypto_library} crypto is required but missing])
-	test "${enable_crypto_ofb_cfb}" = "yes" && AC_DEFINE([ENABLE_OFB_CFB_MODE], [1], [Enable OFB and CFB cipher modes])
-	test "${have_crypto_aead_modes}" = "yes" && AC_DEFINE([HAVE_AEAD_CIPHER_MODES], [1], [Use crypto library])
-	OPTIONAL_CRYPTO_CFLAGS="${OPTIONAL_CRYPTO_CFLAGS} ${CRYPTO_CFLAGS}"
-	OPTIONAL_CRYPTO_LIBS="${OPTIONAL_CRYPTO_LIBS} ${CRYPTO_LIBS}"
-	AC_DEFINE([ENABLE_CRYPTO], [1], [Enable crypto library])
+test "${enable_crypto_ofb_cfb}" = "yes" && AC_DEFINE([ENABLE_OFB_CFB_MODE], [1], [Enable OFB and CFB cipher modes])
+if test "${have_export_keying_material}" = "yes"; then
+	AC_DEFINE(
+		[HAVE_EXPORT_KEYING_MATERIAL], [1],
+		[Crypto library supports keying material exporter]
+	)
 fi
+OPTIONAL_CRYPTO_CFLAGS="${OPTIONAL_CRYPTO_CFLAGS} ${CRYPTO_CFLAGS}"
+OPTIONAL_CRYPTO_LIBS="${OPTIONAL_CRYPTO_LIBS} ${CRYPTO_LIBS}"
 
 if test "${enable_plugins}" = "yes"; then
 	OPTIONAL_DL_LIBS="${DL_LIBS}"
@@ -1245,14 +1243,19 @@
 	enable_plugin_down_root="no"
 fi
 
+AM_CONDITIONAL([HAVE_SITNL], [false])
+
 if test "${enable_iproute2}" = "yes"; then
 	test -z "${IPROUTE}" && AC_MSG_ERROR([ip utility is required but missing])
 	AC_DEFINE([ENABLE_IPROUTE], [1], [enable iproute2 support])
-else
-	if test "${WIN32}" != "yes"; then
-		test -z "${ROUTE}" && AC_MSG_ERROR([route utility is required but missing])
-		test -z "${IFCONFIG}" && AC_MSG_ERROR([ifconfig utility is required but missing])
-	fi
+else if test "${have_sitnl}" = "yes"; then
+	AC_DEFINE([ENABLE_SITNL], [1], [enable sitnl support])
+	AM_CONDITIONAL([HAVE_SITNL], [true])
+else if test "${WIN32}" != "yes" -a "${have_sitnl}" != "yes"; then
+	test -z "${ROUTE}" && AC_MSG_ERROR([route utility is required but missing])
+	test -z "${IFCONFIG}" && AC_MSG_ERROR([ifconfig utility is required but missing])
+fi
+fi
 fi
 
 if test "${enable_selinux}" = "yes"; then
@@ -1275,7 +1278,6 @@
 
 if test "${enable_pkcs11}" = "yes"; then
 	test "${have_pkcs11_helper}" != "yes" && AC_MSG_ERROR([PKCS11 enabled but libpkcs11-helper is missing])
-	test "${enable_crypto}" != "yes" && AC_MSG_ERROR([PKCS11 can be enabled only if crypto is enabled])
 	OPTIONAL_PKCS11_HELPER_CFLAGS="${PKCS11_HELPER_CFLAGS}"
 	OPTIONAL_PKCS11_HELPER_LIBS="${PKCS11_HELPER_LIBS}"
 	AC_DEFINE([ENABLE_PKCS11], [1], [Enable PKCS11])
@@ -1288,14 +1290,18 @@
 	)
 fi
 
+# When testing a compiler option, we add -Werror to force
+# an error when the option is unsupported. This is not
+# required for gcc, but some compilers such as clang needs it.
 AC_DEFUN([ACL_CHECK_ADD_COMPILE_FLAGS], [
     old_cflags="$CFLAGS"
-    CFLAGS="$1 $CFLAGS"
-    AC_MSG_CHECKING([whether the compiler acceppts $1])
-    AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], [AC_MSG_RESULT([yes])],
+    CFLAGS="$1 -Werror $CFLAGS"
+    AC_MSG_CHECKING([whether the compiler accepts $1])
+    AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], [AC_MSG_RESULT([yes])]; CFLAGS="$1 $old_cflags",
         [AC_MSG_RESULT([no]); CFLAGS="$old_cflags"])]
 )
 
+ACL_CHECK_ADD_COMPILE_FLAGS([-Wno-stringop-truncation])
 ACL_CHECK_ADD_COMPILE_FLAGS([-Wno-unused-function])
 ACL_CHECK_ADD_COMPILE_FLAGS([-Wno-unused-parameter])
 ACL_CHECK_ADD_COMPILE_FLAGS([-Wall])
@@ -1379,8 +1385,8 @@
 AM_CONDITIONAL([GIT_CHECKOUT], [test "${GIT_CHECKOUT}" = "yes"])
 AM_CONDITIONAL([ENABLE_PLUGIN_AUTH_PAM], [test "${enable_plugin_auth_pam}" = "yes"])
 AM_CONDITIONAL([ENABLE_PLUGIN_DOWN_ROOT], [test "${enable_plugin_down_root}" = "yes"])
-AM_CONDITIONAL([ENABLE_CRYPTO], [test "${enable_crypto}" = "yes"])
 AM_CONDITIONAL([HAVE_LD_WRAP_SUPPORT], [test "${have_ld_wrap_support}" = "yes"])
+AM_CONDITIONAL([OPENSSL_ENGINE], [test "${have_openssl_engine}" = "yes"])
 
 sampledir="\$(docdir)/sample"
 AC_SUBST([plugindir])
@@ -1389,36 +1395,62 @@
 AC_SUBST([systemdunitdir])
 AC_SUBST([tmpfilesdir])
 
-TEST_LDFLAGS="${OPTIONAL_CRYPTO_LIBS} ${OPTIONAL_PKCS11_HELPER_LIBS} -lcmocka -L\$(top_builddir)/vendor/dist/lib -Wl,-rpath,\$(top_builddir)/vendor/dist/lib"
-TEST_CFLAGS="${OPTIONAL_CRYPTO_CFLAGS} ${OPTIONAL_PKCS11_HELPER_CFLAGS} -I\$(top_srcdir)/include -I\$(top_builddir)/vendor/dist/include"
+AC_ARG_ENABLE(
+     [unit-tests],
+     [AS_HELP_STRING([--disable-unit-tests],
+                     [Disables building and running the unit tests suite])],
+     [],
+     [enable_unit_tests="yes"]
+)
+
+# Check if cmocka is available - needed for unit testing
+PKG_CHECK_MODULES(
+	[CMOCKA], [cmocka],
+	[have_cmocka="yes"],
+	[AC_MSG_WARN([cmocka.pc not found on the system.  Unit tests disabled])]
+)
+AM_CONDITIONAL([ENABLE_UNITTESTS], [test "${enable_unit_tests}" = "yes" -a "${have_cmocka}" = "yes" ])
+AC_SUBST([ENABLE_UNITTESTS])
+
+TEST_LDFLAGS="${OPTIONAL_CRYPTO_LIBS} ${OPTIONAL_PKCS11_HELPER_LIBS}"
+TEST_LDFLAGS="${TEST_LDFLAGS} ${OPTIONAL_LZO_LIBS} ${CMOCKA_LIBS}"
+TEST_CFLAGS="${OPTIONAL_CRYPTO_CFLAGS} ${OPTIONAL_PKCS11_HELPER_CFLAGS}"
+TEST_CFLAGS="${TEST_CFLAGS} ${OPTIONAL_LZO_CFLAGS}"
+TEST_CFLAGS="${TEST_CFLAGS} -I\$(top_srcdir)/include ${CMOCKA_CFLAGS}"
 
 AC_SUBST([TEST_LDFLAGS])
 AC_SUBST([TEST_CFLAGS])
 
-# Check if cmake is available and cmocka git submodule is initialized,
-# needed for unit testing
-AC_CHECK_PROGS([CMAKE], [cmake])
-if test -n "${CMAKE}"; then
-   if test -f "${srcdir}/vendor/cmocka/CMakeLists.txt"; then
-      AM_CONDITIONAL([CMOCKA_INITIALIZED], [true])
-   else
-      AM_CONDITIONAL([CMOCKA_INITIALIZED], [false])
-      AC_MSG_RESULT([!! WARNING !! The cmoka git submodule has not been initialized or updated.  Unit testing cannot be performed.])
-   fi
-else
-   AC_MSG_RESULT([!! WARNING !! CMake is NOT available.  Unit testing cannot be performed.])
-   AM_CONDITIONAL([CMOCKA_INITIALIZED], [false])
-fi
-
-
 AC_CONFIG_FILES([
 	version.sh
 	Makefile
+	build/Makefile
+	build/msvc/Makefile
+	build/msvc/msvc-generate/Makefile
+	distro/Makefile
+	distro/systemd/Makefile
+	doc/Makefile
+	doc/doxygen/Makefile
+	doc/doxygen/openvpn.doxyfile
 	include/Makefile
+	sample/sample-plugins/Makefile
 	src/Makefile
 	src/compat/Makefile
 	src/openvpn/Makefile
+	src/openvpnmsica/Makefile
+	src/openvpnserv/Makefile
 	src/plugins/Makefile
+	src/plugins/auth-pam/Makefile
 	src/plugins/down-root/Makefile
+	src/tapctl/Makefile
+	tests/Makefile
+        tests/unit_tests/Makefile
+        tests/unit_tests/example_test/Makefile
+        tests/unit_tests/openvpn/Makefile
+        tests/unit_tests/plugins/Makefile
+        tests/unit_tests/plugins/auth-pam/Makefile
+	tests/unit_tests/engine-key/Makefile
+	sample/Makefile
 ])
+AC_CONFIG_FILES([tests/t_client.sh], [chmod +x tests/t_client.sh])
 AC_OUTPUT
diff --git a/include/openvpn-msg.h b/include/openvpn-msg.h
index 66177a2..a4789e3 100644
--- a/include/openvpn-msg.h
+++ b/include/openvpn-msg.h
@@ -39,6 +39,8 @@
     msg_del_block_dns,
     msg_register_dns,
     msg_enable_dhcp,
+    msg_register_ring_buffers,
+    msg_set_mtu
 } message_type_t;
 
 typedef struct {
@@ -117,4 +119,20 @@
     interface_t iface;
 } enable_dhcp_message_t;
 
+typedef struct {
+    message_header_t header;
+    HANDLE device;
+    HANDLE send_ring_handle;
+    HANDLE receive_ring_handle;
+    HANDLE send_tail_moved;
+    HANDLE receive_tail_moved;
+} register_ring_buffers_message_t;
+
+typedef struct {
+    message_header_t header;
+    interface_t iface;
+    short family;
+    int mtu;
+} set_mtu_message_t;
+
 #endif /* ifndef OPENVPN_MSG_H_ */
diff --git a/include/openvpn-plugin.h b/include/openvpn-plugin.h
index 0a9f3fe..89c3cea 100644
--- a/include/openvpn-plugin.h
+++ b/include/openvpn-plugin.h
@@ -26,7 +26,6 @@
 
 #define OPENVPN_PLUGIN_VERSION 3
 
-#ifdef ENABLE_CRYPTO
 #ifdef ENABLE_CRYPTO_MBEDTLS
 #include <mbedtls/x509_crt.h>
 #ifndef __OPENVPN_X509_CERT_T_DECLARED
@@ -40,7 +39,6 @@
 typedef X509 openvpn_x509_cert_t;
 #endif
 #endif
-#endif
 
 #include <stdarg.h>
 #include <stddef.h>
@@ -111,20 +109,22 @@
  * FUNC: openvpn_plugin_client_destructor_v1 (top-level "generic" client)
  * FUNC: openvpn_plugin_close_v1
  */
-#define OPENVPN_PLUGIN_UP                    0
-#define OPENVPN_PLUGIN_DOWN                  1
-#define OPENVPN_PLUGIN_ROUTE_UP              2
-#define OPENVPN_PLUGIN_IPCHANGE              3
-#define OPENVPN_PLUGIN_TLS_VERIFY            4
-#define OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY 5
-#define OPENVPN_PLUGIN_CLIENT_CONNECT        6
-#define OPENVPN_PLUGIN_CLIENT_DISCONNECT     7
-#define OPENVPN_PLUGIN_LEARN_ADDRESS         8
-#define OPENVPN_PLUGIN_CLIENT_CONNECT_V2     9
-#define OPENVPN_PLUGIN_TLS_FINAL             10
-#define OPENVPN_PLUGIN_ENABLE_PF             11
-#define OPENVPN_PLUGIN_ROUTE_PREDOWN         12
-#define OPENVPN_PLUGIN_N                     13
+#define OPENVPN_PLUGIN_UP                        0
+#define OPENVPN_PLUGIN_DOWN                      1
+#define OPENVPN_PLUGIN_ROUTE_UP                  2
+#define OPENVPN_PLUGIN_IPCHANGE                  3
+#define OPENVPN_PLUGIN_TLS_VERIFY                4
+#define OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY     5
+#define OPENVPN_PLUGIN_CLIENT_CONNECT            6
+#define OPENVPN_PLUGIN_CLIENT_DISCONNECT         7
+#define OPENVPN_PLUGIN_LEARN_ADDRESS             8
+#define OPENVPN_PLUGIN_CLIENT_CONNECT_V2         9
+#define OPENVPN_PLUGIN_TLS_FINAL                10
+#define OPENVPN_PLUGIN_ENABLE_PF                11
+#define OPENVPN_PLUGIN_ROUTE_PREDOWN            12
+#define OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER     13
+#define OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2  14
+#define OPENVPN_PLUGIN_N                        15
 
 /*
  * Build a mask out of a set of plug-in types.
@@ -416,9 +416,9 @@
  * per_client_context : the per-client context pointer which was returned by
  *        openvpn_plugin_client_constructor_v1, if defined.
  *
- * current_cert_depth : Certificate depth of the certificate being passed over (only if compiled with ENABLE_CRYPTO defined)
+ * current_cert_depth : Certificate depth of the certificate being passed over
  *
- * *current_cert : X509 Certificate object received from the client (only if compiled with ENABLE_CRYPTO defined)
+ * *current_cert : X509 Certificate object received from the client
  *
  */
 struct openvpn_plugin_args_func_in
@@ -428,13 +428,8 @@
     const char **const envp;
     openvpn_plugin_handle_t handle;
     void *per_client_context;
-#ifdef ENABLE_CRYPTO
     int current_cert_depth;
     openvpn_x509_cert_t *current_cert;
-#else
-    int __current_cert_depth_disabled; /* Unused, for compatibility purposes only */
-    void *__current_cert_disabled; /* Unused, for compatibility purposes only */
-#endif
 };
 
 
@@ -555,12 +550,21 @@
  * OPENVPN_PLUGIN_FUNC_SUCCESS on success, OPENVPN_PLUGIN_FUNC_ERROR on failure
  *
  * In addition, OPENVPN_PLUGIN_FUNC_DEFERRED may be returned by
- * OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY.  This enables asynchronous
- * authentication where the plugin (or one of its agents) may indicate
- * authentication success/failure some number of seconds after the return
- * of the OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY handler by writing a single
- * char to the file named by auth_control_file in the environmental variable
- * list (envp).
+ * OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, OPENVPN_PLUGIN_CLIENT_CONNECT and
+ * OPENVPN_PLUGIN_CLIENT_CONNECT_V2. This enables asynchronous
+ * authentication or client connect  where the plugin (or one of its agents)
+ * may indicate authentication success/failure or client configuration some
+ * number of seconds after the return of the function handler.
+ * For OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY and OPENVPN_PLUGIN_CLIENT_CONNECT
+ * this is done by writing a single char to the file named by
+ * auth_control_file/client_connect_deferred_file
+ * in the environmental variable list (envp).
+ *
+ * In addition the OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER and
+ * OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2 are called when OpenVPN tries to
+ * get the deferred result. For a V2 call implementing this function is
+ * required as information is not passed by files. For the normal version
+ * the call is optional.
  *
  * first char of auth_control_file:
  * '0' -- indicates auth failure
@@ -659,12 +663,12 @@
  * ARGUMENTS
  *
  * version : fixed value, defines the API version of the OpenVPN plug-in API.  The plug-in
- *           should validate that this value is matching the OPENVPN_PLUGIN_VERSION value.
+ *           should validate that this value is matching the OPENVPN_PLUGINv3_STRUCTVER
+ *           value.
  *
- * handle : the openvpn_plugin_handle_t value which was returned by
- *          openvpn_plugin_open.
+ * arguments : Structure with all arguments available to the plug-in.
  *
- * return_list : used to return data back to OpenVPN.
+ * retptr :    used to return data back to OpenVPN.
  *
  * RETURN VALUE
  *
@@ -725,8 +729,8 @@
  * A given client or subnet rule applies to both incoming and outgoing
  * packets.
  *
- * See plugin/defer/simple.c for an example on using asynchronous
- * authentication and client-specific packet filtering.
+ * See sample/sample-plugins/defer/simple.c for an example on using
+ * asynchronous authentication and client-specific packet filtering.
  */
 OPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_func_v3)
     (const int version,
diff --git a/include/openvpn-plugin.h.in b/include/openvpn-plugin.h.in
index a604f1c..b73b745 100644
--- a/include/openvpn-plugin.h.in
+++ b/include/openvpn-plugin.h.in
@@ -26,7 +26,6 @@
 
 #define OPENVPN_PLUGIN_VERSION 3
 
-#ifdef ENABLE_CRYPTO
 #ifdef ENABLE_CRYPTO_MBEDTLS
 #include <mbedtls/x509_crt.h>
 #ifndef __OPENVPN_X509_CERT_T_DECLARED
@@ -40,7 +39,6 @@
 typedef X509 openvpn_x509_cert_t;
 #endif
 #endif
-#endif
 
 #include <stdarg.h>
 #include <stddef.h>
@@ -118,20 +116,22 @@
  * FUNC: openvpn_plugin_client_destructor_v1 (top-level "generic" client)
  * FUNC: openvpn_plugin_close_v1
  */
-#define OPENVPN_PLUGIN_UP                    0
-#define OPENVPN_PLUGIN_DOWN                  1
-#define OPENVPN_PLUGIN_ROUTE_UP              2
-#define OPENVPN_PLUGIN_IPCHANGE              3
-#define OPENVPN_PLUGIN_TLS_VERIFY            4
-#define OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY 5
-#define OPENVPN_PLUGIN_CLIENT_CONNECT        6
-#define OPENVPN_PLUGIN_CLIENT_DISCONNECT     7
-#define OPENVPN_PLUGIN_LEARN_ADDRESS         8
-#define OPENVPN_PLUGIN_CLIENT_CONNECT_V2     9
-#define OPENVPN_PLUGIN_TLS_FINAL             10
-#define OPENVPN_PLUGIN_ENABLE_PF             11
-#define OPENVPN_PLUGIN_ROUTE_PREDOWN         12
-#define OPENVPN_PLUGIN_N                     13
+#define OPENVPN_PLUGIN_UP                        0
+#define OPENVPN_PLUGIN_DOWN                      1
+#define OPENVPN_PLUGIN_ROUTE_UP                  2
+#define OPENVPN_PLUGIN_IPCHANGE                  3
+#define OPENVPN_PLUGIN_TLS_VERIFY                4
+#define OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY     5
+#define OPENVPN_PLUGIN_CLIENT_CONNECT            6
+#define OPENVPN_PLUGIN_CLIENT_DISCONNECT         7
+#define OPENVPN_PLUGIN_LEARN_ADDRESS             8
+#define OPENVPN_PLUGIN_CLIENT_CONNECT_V2         9
+#define OPENVPN_PLUGIN_TLS_FINAL                10
+#define OPENVPN_PLUGIN_ENABLE_PF                11
+#define OPENVPN_PLUGIN_ROUTE_PREDOWN            12
+#define OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER     13
+#define OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2  14
+#define OPENVPN_PLUGIN_N                        15
 
 /*
  * Build a mask out of a set of plug-in types.
@@ -423,9 +423,9 @@
  * per_client_context : the per-client context pointer which was returned by
  *        openvpn_plugin_client_constructor_v1, if defined.
  *
- * current_cert_depth : Certificate depth of the certificate being passed over (only if compiled with ENABLE_CRYPTO defined)
+ * current_cert_depth : Certificate depth of the certificate being passed over
  *
- * *current_cert : X509 Certificate object received from the client (only if compiled with ENABLE_CRYPTO defined)
+ * *current_cert : X509 Certificate object received from the client
  *
  */
 struct openvpn_plugin_args_func_in
@@ -435,13 +435,8 @@
     const char **const envp;
     openvpn_plugin_handle_t handle;
     void *per_client_context;
-#ifdef ENABLE_CRYPTO
     int current_cert_depth;
     openvpn_x509_cert_t *current_cert;
-#else
-    int __current_cert_depth_disabled; /* Unused, for compatibility purposes only */
-    void *__current_cert_disabled; /* Unused, for compatibility purposes only */
-#endif
 };
 
 
@@ -562,12 +557,21 @@
  * OPENVPN_PLUGIN_FUNC_SUCCESS on success, OPENVPN_PLUGIN_FUNC_ERROR on failure
  *
  * In addition, OPENVPN_PLUGIN_FUNC_DEFERRED may be returned by
- * OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY.  This enables asynchronous
- * authentication where the plugin (or one of its agents) may indicate
- * authentication success/failure some number of seconds after the return
- * of the OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY handler by writing a single
- * char to the file named by auth_control_file in the environmental variable
- * list (envp).
+ * OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, OPENVPN_PLUGIN_CLIENT_CONNECT and
+ * OPENVPN_PLUGIN_CLIENT_CONNECT_V2. This enables asynchronous
+ * authentication or client connect  where the plugin (or one of its agents)
+ * may indicate authentication success/failure or client configuration some
+ * number of seconds after the return of the function handler.
+ * For OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY and OPENVPN_PLUGIN_CLIENT_CONNECT
+ * this is done by writing a single char to the file named by
+ * auth_control_file/client_connect_deferred_file
+ * in the environmental variable list (envp).
+ *
+ * In addition the OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER and
+ * OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2 are called when OpenVPN tries to
+ * get the deferred result. For a V2 call implementing this function is
+ * required as information is not passed by files. For the normal version
+ * the call is optional.
  *
  * first char of auth_control_file:
  * '0' -- indicates auth failure
@@ -666,12 +670,12 @@
  * ARGUMENTS
  *
  * version : fixed value, defines the API version of the OpenVPN plug-in API.  The plug-in
- *           should validate that this value is matching the OPENVPN_PLUGIN_VERSION value.
+ *           should validate that this value is matching the OPENVPN_PLUGINv3_STRUCTVER
+ *           value.
  *
- * handle : the openvpn_plugin_handle_t value which was returned by
- *          openvpn_plugin_open.
+ * arguments : Structure with all arguments available to the plug-in.
  *
- * return_list : used to return data back to OpenVPN.
+ * retptr :    used to return data back to OpenVPN.
  *
  * RETURN VALUE
  *
@@ -732,8 +736,8 @@
  * A given client or subnet rule applies to both incoming and outgoing
  * packets.
  *
- * See plugin/defer/simple.c for an example on using asynchronous
- * authentication and client-specific packet filtering.
+ * See sample/sample-plugins/defer/simple.c for an example on using
+ * asynchronous authentication and client-specific packet filtering.
  */
 OPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_func_v3)
     (const int version,
diff --git a/m4/pkg.m4 b/m4/pkg.m4
index 12d2a58..cca47a7 100644
--- a/m4/pkg.m4
+++ b/m4/pkg.m4
@@ -53,7 +53,7 @@
 # to PKG_CHECK_MODULES(), but does not set variables or print errors.
 #
 # Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG])
-# only at the first occurence in configure.ac, so if the first place
+# only at the first occurrence in configure.ac, so if the first place
 # it's called might be skipped (such as if it is within an "if", you
 # have to call PKG_CHECK_EXISTS manually
 # --------------------------------------------------------------
diff --git a/patches/series b/patches/series
index 73fef3e..f7b5bd2 100644
--- a/patches/series
+++ b/patches/series
@@ -1,4 +1,3 @@
 remove_autoconf_vars.patch
 add_missing_licenses.patch
-cleanup_makefiles.patch
 fix_long_password.patch
diff --git a/src/Makefile.am b/src/Makefile.am
index 14bca42..313d289 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -12,4 +12,4 @@
 MAINTAINERCLEANFILES = \
 	$(srcdir)/Makefile.in
 
-SUBDIRS = compat openvpn plugins
+SUBDIRS = compat openvpn openvpnmsica openvpnserv plugins tapctl
diff --git a/src/compat/Debug.props b/src/compat/Debug.props
new file mode 100644
index 0000000..31bb9d9
--- /dev/null
+++ b/src/compat/Debug.props
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets">
+    <Import Project="PropertySheet.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup>
+    <_PropertySheetDisplayName>compat-Debug</_PropertySheetDisplayName>
+    <LinkIncremental>true</LinkIncremental>
+  </PropertyGroup>
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+      <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemGroup />
+</Project>
\ No newline at end of file
diff --git a/src/compat/Makefile.am b/src/compat/Makefile.am
index b4c3a4a..2e94e94 100644
--- a/src/compat/Makefile.am
+++ b/src/compat/Makefile.am
@@ -14,7 +14,10 @@
 
 EXTRA_DIST = \
 	compat.vcxproj \
-	compat.vcxproj.filters
+	compat.vcxproj.filters \
+	PropertySheet.props \
+	Debug.props \
+	Release.props
 
 noinst_LTLIBRARIES = libcompat.la
 
@@ -27,4 +30,5 @@
 	compat-inet_ntop.c \
 	compat-inet_pton.c \
 	compat-lz4.c compat-lz4.h \
+	compat-strsep.c \
 	compat-versionhelpers.h
diff --git a/src/compat/PropertySheet.props b/src/compat/PropertySheet.props
new file mode 100644
index 0000000..4f94b97
--- /dev/null
+++ b/src/compat/PropertySheet.props
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets" />
+  <PropertyGroup Label="UserMacros">
+    <SOURCEBASE>$(SolutionDir)</SOURCEBASE>
+    <OPENVPN_DEPROOT>$(SOURCEBASE)\..\openvpn-build\msvc\image$(PlatformArchitecture)</OPENVPN_DEPROOT>
+    <OPENSSL_HOME>$(OPENVPN_DEPROOT)</OPENSSL_HOME>
+    <TAP_WINDOWS_HOME>$(OPENVPN_DEPROOT)</TAP_WINDOWS_HOME>
+    <LZO_HOME>$(OPENVPN_DEPROOT)</LZO_HOME>
+    <PKCS11H_HOME>$(OPENVPN_DEPROOT)</PKCS11H_HOME>
+  </PropertyGroup>
+  <PropertyGroup>
+    <OutDir>$(SolutionDir)$(Platform)-Output\$(Configuration)\</OutDir>
+    <_PropertySheetDisplayName>compat</_PropertySheetDisplayName>
+  </PropertyGroup>
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PreprocessorDefinitions>WIN32;$(CPPFLAGS);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>$(SOURCEBASE);$(SOURCEBASE)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+    <ResourceCompile>
+      <AdditionalIncludeDirectories>$(SOURCEBASE);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ResourceCompile>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <BuildMacro Include="SOURCEBASE">
+      <Value>$(SOURCEBASE)</Value>
+      <EnvironmentVariable>true</EnvironmentVariable>
+    </BuildMacro>
+    <BuildMacro Include="OPENVPN_DEPROOT">
+      <Value>$(OPENVPN_DEPROOT)</Value>
+      <EnvironmentVariable>true</EnvironmentVariable>
+    </BuildMacro>
+    <BuildMacro Include="OPENSSL_HOME">
+      <Value>$(OPENSSL_HOME)</Value>
+      <EnvironmentVariable>true</EnvironmentVariable>
+    </BuildMacro>
+    <BuildMacro Include="TAP_WINDOWS_HOME">
+      <Value>$(TAP_WINDOWS_HOME)</Value>
+      <EnvironmentVariable>true</EnvironmentVariable>
+    </BuildMacro>
+    <BuildMacro Include="LZO_HOME">
+      <Value>$(LZO_HOME)</Value>
+      <EnvironmentVariable>true</EnvironmentVariable>
+    </BuildMacro>
+    <BuildMacro Include="PKCS11H_HOME">
+      <Value>$(PKCS11H_HOME)</Value>
+      <EnvironmentVariable>true</EnvironmentVariable>
+    </BuildMacro>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/compat/Release.props b/src/compat/Release.props
new file mode 100644
index 0000000..63828b7
--- /dev/null
+++ b/src/compat/Release.props
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets">
+    <Import Project="PropertySheet.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup>
+    <_PropertySheetDisplayName>compat-Release</_PropertySheetDisplayName>
+    <LinkIncremental>false</LinkIncremental>
+  </PropertyGroup>
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+    <Link>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup />
+</Project>
\ No newline at end of file
diff --git a/src/compat/compat-lz4.c b/src/compat/compat-lz4.c
index 723157d..26a3980 100644
--- a/src/compat/compat-lz4.c
+++ b/src/compat/compat-lz4.c
@@ -1,5 +1,5 @@
 /* This file has been backported by dev-tools/lz4-rebaser.sh
- * from upstream lz4 commit 7bb64ff2b69a9f8367de (v1.7.5)
+ * from upstream lz4 commit fdf2ef5809ca875c4545 (v1.9.2)
  */
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -10,7 +10,7 @@
 #ifdef NEED_COMPAT_LZ4
 /*
    LZ4 - Fast LZ compression algorithm
-   Copyright (C) 2011-2016, Yann Collet.
+   Copyright (C) 2011-present, Yann Collet.
 
    BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
 
@@ -42,17 +42,16 @@
     - LZ4 source repository : https://github.com/lz4/lz4
 */
 
-
 /*-************************************
 *  Tuning parameters
 **************************************/
 /*
- * HEAPMODE :
+ * LZ4_HEAPMODE :
  * Select how default compression functions will allocate memory for their hash table,
  * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()).
  */
-#ifndef HEAPMODE
-#  define HEAPMODE 0
+#ifndef LZ4_HEAPMODE
+#  define LZ4_HEAPMODE 0
 #endif
 
 /*
@@ -73,16 +72,17 @@
  * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable).
  *            This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`.
  * Method 2 : direct access. This method is portable but violate C standard.
- *            It can generate buggy code on targets which generate assembly depending on alignment.
+ *            It can generate buggy code on targets which assembly generation depends on alignment.
  *            But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6)
  * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details.
  * Prefer these methods in priority order (0 > 1 > 2)
  */
-#ifndef LZ4_FORCE_MEMORY_ACCESS   /* can be defined externally, on command line for example */
-#  if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) )
+#ifndef LZ4_FORCE_MEMORY_ACCESS   /* can be defined externally */
+#  if defined(__GNUC__) && \
+  ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \
+  || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) )
 #    define LZ4_FORCE_MEMORY_ACCESS 2
-#  elif defined(__INTEL_COMPILER) || \
-  (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) ))
+#  elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__)
 #    define LZ4_FORCE_MEMORY_ACCESS 1
 #  endif
 #endif
@@ -91,14 +91,32 @@
  * LZ4_FORCE_SW_BITCOUNT
  * Define this parameter if your target system or compiler does not support hardware bit count
  */
-#if defined(_MSC_VER) && defined(_WIN32_WCE)   /* Visual Studio for Windows CE does not support Hardware bit count */
+#if defined(_MSC_VER) && defined(_WIN32_WCE)   /* Visual Studio for WinCE doesn't support Hardware bit count */
 #  define LZ4_FORCE_SW_BITCOUNT
 #endif
 
 
+
 /*-************************************
 *  Dependency
 **************************************/
+/*
+ * LZ4_SRC_INCLUDED:
+ * Amalgamation flag, whether lz4.c is included
+ */
+#ifndef LZ4_SRC_INCLUDED
+#  define LZ4_SRC_INCLUDED 1
+#endif
+
+#ifndef LZ4_STATIC_LINKING_ONLY
+#define LZ4_STATIC_LINKING_ONLY
+#endif
+
+#ifndef LZ4_DISABLE_DEPRECATE_WARNINGS
+#define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */
+#endif
+
+#define LZ4_STATIC_LINKING_ONLY  /* LZ4_DISTANCE_MAX */
 #include "compat-lz4.h"
 /* see also "memory routines" below */
 
@@ -107,42 +125,130 @@
 *  Compiler Options
 **************************************/
 #ifdef _MSC_VER    /* Visual Studio */
-#  define FORCE_INLINE static __forceinline
 #  include <intrin.h>
 #  pragma warning(disable : 4127)        /* disable: C4127: conditional expression is constant */
 #  pragma warning(disable : 4293)        /* disable: C4293: too large shift (32-bits) */
-#else
-#  if defined(__GNUC__) || defined(__clang__)
-#    define FORCE_INLINE static inline __attribute__((always_inline))
-#  elif defined(__cplusplus) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
-#    define FORCE_INLINE static inline
-#  else
-#    define FORCE_INLINE static
-#  endif
 #endif  /* _MSC_VER */
 
+#ifndef LZ4_FORCE_INLINE
+#  ifdef _MSC_VER    /* Visual Studio */
+#    define LZ4_FORCE_INLINE static __forceinline
+#  else
+#    if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L   /* C99 */
+#      ifdef __GNUC__
+#        define LZ4_FORCE_INLINE static inline __attribute__((always_inline))
+#      else
+#        define LZ4_FORCE_INLINE static inline
+#      endif
+#    else
+#      define LZ4_FORCE_INLINE static
+#    endif /* __STDC_VERSION__ */
+#  endif  /* _MSC_VER */
+#endif /* LZ4_FORCE_INLINE */
+
+/* LZ4_FORCE_O2_GCC_PPC64LE and LZ4_FORCE_O2_INLINE_GCC_PPC64LE
+ * gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy8,
+ * together with a simple 8-byte copy loop as a fall-back path.
+ * However, this optimization hurts the decompression speed by >30%,
+ * because the execution does not go to the optimized loop
+ * for typical compressible data, and all of the preamble checks
+ * before going to the fall-back path become useless overhead.
+ * This optimization happens only with the -O3 flag, and -O2 generates
+ * a simple 8-byte copy loop.
+ * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy8
+ * functions are annotated with __attribute__((optimize("O2"))),
+ * and also LZ4_wildCopy8 is forcibly inlined, so that the O2 attribute
+ * of LZ4_wildCopy8 does not affect the compression speed.
+ */
+#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) && !defined(__clang__)
+#  define LZ4_FORCE_O2_GCC_PPC64LE __attribute__((optimize("O2")))
+#  define LZ4_FORCE_O2_INLINE_GCC_PPC64LE __attribute__((optimize("O2"))) LZ4_FORCE_INLINE
+#else
+#  define LZ4_FORCE_O2_GCC_PPC64LE
+#  define LZ4_FORCE_O2_INLINE_GCC_PPC64LE static
+#endif
+
 #if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__)
 #  define expect(expr,value)    (__builtin_expect ((expr),(value)) )
 #else
 #  define expect(expr,value)    (expr)
 #endif
 
+#ifndef likely
 #define likely(expr)     expect((expr) != 0, 1)
+#endif
+#ifndef unlikely
 #define unlikely(expr)   expect((expr) != 0, 0)
+#endif
 
 
 /*-************************************
 *  Memory routines
 **************************************/
 #include <stdlib.h>   /* malloc, calloc, free */
-#define ALLOCATOR(n,s) calloc(n,s)
-#define FREEMEM        free
+#define ALLOC(s)          malloc(s)
+#define ALLOC_AND_ZERO(s) calloc(1,s)
+#define FREEMEM(p)        free(p)
 #include <string.h>   /* memset, memcpy */
-#define MEM_INIT       memset
+#define MEM_INIT(p,v,s)   memset((p),(v),(s))
 
 
 /*-************************************
-*  Basic Types
+*  Common Constants
+**************************************/
+#define MINMATCH 4
+
+#define WILDCOPYLENGTH 8
+#define LASTLITERALS   5   /* see ../doc/lz4_Block_format.md#parsing-restrictions */
+#define MFLIMIT       12   /* see ../doc/lz4_Block_format.md#parsing-restrictions */
+#define MATCH_SAFEGUARD_DISTANCE  ((2*WILDCOPYLENGTH) - MINMATCH)   /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */
+#define FASTLOOP_SAFE_DISTANCE 64
+static const int LZ4_minLength = (MFLIMIT+1);
+
+#define KB *(1 <<10)
+#define MB *(1 <<20)
+#define GB *(1U<<30)
+
+#define LZ4_DISTANCE_ABSOLUTE_MAX 65535
+#if (LZ4_DISTANCE_MAX > LZ4_DISTANCE_ABSOLUTE_MAX)   /* max supported by LZ4 format */
+#  error "LZ4_DISTANCE_MAX is too big : must be <= 65535"
+#endif
+
+#define ML_BITS  4
+#define ML_MASK  ((1U<<ML_BITS)-1)
+#define RUN_BITS (8-ML_BITS)
+#define RUN_MASK ((1U<<RUN_BITS)-1)
+
+
+/*-************************************
+*  Error detection
+**************************************/
+#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=1)
+#  include <assert.h>
+#else
+#  ifndef assert
+#    define assert(condition) ((void)0)
+#  endif
+#endif
+
+#define LZ4_STATIC_ASSERT(c)   { enum { LZ4_static_assert = 1/(int)(!!(c)) }; }   /* use after variable declarations */
+
+#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2)
+#  include <stdio.h>
+static int g_debuglog_enable = 1;
+#  define DEBUGLOG(l, ...) {                                  \
+                if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) {  \
+                    fprintf(stderr, __FILE__ ": ");           \
+                    fprintf(stderr, __VA_ARGS__);             \
+                    fprintf(stderr, " \n");                   \
+            }   }
+#else
+#  define DEBUGLOG(l, ...)      {}    /* disabled */
+#endif
+
+
+/*-************************************
+*  Types
 **************************************/
 #if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
 # include <stdint.h>
@@ -167,6 +273,13 @@
   typedef size_t reg_t;   /* 32-bits in x32 mode */
 #endif
 
+typedef enum {
+    notLimited = 0,
+    limitedOutput = 1,
+    fillOutput = 2
+} limitedOutput_directive;
+
+
 /*-************************************
 *  Reading and writing into memory
 **************************************/
@@ -200,7 +313,7 @@
 static void LZ4_write16(void* memPtr, U16 value) { ((unalign*)memPtr)->u16 = value; }
 static void LZ4_write32(void* memPtr, U32 value) { ((unalign*)memPtr)->u32 = value; }
 
-#else  /* safe and portable access through memcpy() */
+#else  /* safe and portable access using memcpy() */
 
 static U16 LZ4_read16(const void* memPtr)
 {
@@ -251,55 +364,113 @@
     }
 }
 
-static void LZ4_copy8(void* dst, const void* src)
-{
-    memcpy(dst,src,8);
-}
-
 /* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */
-static void LZ4_wildCopy(void* dstPtr, const void* srcPtr, void* dstEnd)
+LZ4_FORCE_O2_INLINE_GCC_PPC64LE
+void LZ4_wildCopy8(void* dstPtr, const void* srcPtr, void* dstEnd)
 {
     BYTE* d = (BYTE*)dstPtr;
     const BYTE* s = (const BYTE*)srcPtr;
     BYTE* const e = (BYTE*)dstEnd;
 
-    do { LZ4_copy8(d,s); d+=8; s+=8; } while (d<e);
+    do { memcpy(d,s,8); d+=8; s+=8; } while (d<e);
 }
 
-
-/*-************************************
-*  Common Constants
-**************************************/
-#define MINMATCH 4
-
-#define WILDCOPYLENGTH 8
-#define LASTLITERALS 5
-#define MFLIMIT (WILDCOPYLENGTH+MINMATCH)
-static const int LZ4_minLength = (MFLIMIT+1);
-
-#define KB *(1 <<10)
-#define MB *(1 <<20)
-#define GB *(1U<<30)
-
-#define MAXD_LOG 16
-#define MAX_DISTANCE ((1 << MAXD_LOG) - 1)
-
-#define ML_BITS  4
-#define ML_MASK  ((1U<<ML_BITS)-1)
-#define RUN_BITS (8-ML_BITS)
-#define RUN_MASK ((1U<<RUN_BITS)-1)
+static const unsigned inc32table[8] = {0, 1, 2,  1,  0,  4, 4, 4};
+static const int      dec64table[8] = {0, 0, 0, -1, -4,  1, 2, 3};
 
 
-/*-************************************
-*  Common Utils
-**************************************/
-#define LZ4_STATIC_ASSERT(c)    { enum { LZ4_static_assert = 1/(int)(!!(c)) }; }   /* use only *after* variable declarations */
+#ifndef LZ4_FAST_DEC_LOOP
+#  if defined(__i386__) || defined(__x86_64__)
+#    define LZ4_FAST_DEC_LOOP 1
+#  elif defined(__aarch64__) && !defined(__clang__)
+     /* On aarch64, we disable this optimization for clang because on certain
+      * mobile chipsets and clang, it reduces performance. For more information
+      * refer to https://github.com/lz4/lz4/pull/707. */
+#    define LZ4_FAST_DEC_LOOP 1
+#  else
+#    define LZ4_FAST_DEC_LOOP 0
+#  endif
+#endif
+
+#if LZ4_FAST_DEC_LOOP
+
+LZ4_FORCE_O2_INLINE_GCC_PPC64LE void
+LZ4_memcpy_using_offset_base(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset)
+{
+    if (offset < 8) {
+        dstPtr[0] = srcPtr[0];
+        dstPtr[1] = srcPtr[1];
+        dstPtr[2] = srcPtr[2];
+        dstPtr[3] = srcPtr[3];
+        srcPtr += inc32table[offset];
+        memcpy(dstPtr+4, srcPtr, 4);
+        srcPtr -= dec64table[offset];
+        dstPtr += 8;
+    } else {
+        memcpy(dstPtr, srcPtr, 8);
+        dstPtr += 8;
+        srcPtr += 8;
+    }
+
+    LZ4_wildCopy8(dstPtr, srcPtr, dstEnd);
+}
+
+/* customized variant of memcpy, which can overwrite up to 32 bytes beyond dstEnd
+ * this version copies two times 16 bytes (instead of one time 32 bytes)
+ * because it must be compatible with offsets >= 16. */
+LZ4_FORCE_O2_INLINE_GCC_PPC64LE void
+LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd)
+{
+    BYTE* d = (BYTE*)dstPtr;
+    const BYTE* s = (const BYTE*)srcPtr;
+    BYTE* const e = (BYTE*)dstEnd;
+
+    do { memcpy(d,s,16); memcpy(d+16,s+16,16); d+=32; s+=32; } while (d<e);
+}
+
+/* LZ4_memcpy_using_offset()  presumes :
+ * - dstEnd >= dstPtr + MINMATCH
+ * - there is at least 8 bytes available to write after dstEnd */
+LZ4_FORCE_O2_INLINE_GCC_PPC64LE void
+LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset)
+{
+    BYTE v[8];
+
+    assert(dstEnd >= dstPtr + MINMATCH);
+    LZ4_write32(dstPtr, 0);   /* silence an msan warning when offset==0 */
+
+    switch(offset) {
+    case 1:
+        memset(v, *srcPtr, 8);
+        break;
+    case 2:
+        memcpy(v, srcPtr, 2);
+        memcpy(&v[2], srcPtr, 2);
+        memcpy(&v[4], &v[0], 4);
+        break;
+    case 4:
+        memcpy(v, srcPtr, 4);
+        memcpy(&v[4], srcPtr, 4);
+        break;
+    default:
+        LZ4_memcpy_using_offset_base(dstPtr, srcPtr, dstEnd, offset);
+        return;
+    }
+
+    memcpy(dstPtr, v, 8);
+    dstPtr += 8;
+    while (dstPtr < dstEnd) {
+        memcpy(dstPtr, v, 8);
+        dstPtr += 8;
+    }
+}
+#endif
 
 
 /*-************************************
 *  Common functions
 **************************************/
-static unsigned LZ4_NbCommonBytes (register reg_t val)
+static unsigned LZ4_NbCommonBytes (reg_t val)
 {
     if (LZ4_isLittleEndian()) {
         if (sizeof(val)==8) {
@@ -308,9 +479,16 @@
             _BitScanForward64( &r, (U64)val );
             return (int)(r>>3);
 #       elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT)
-            return (__builtin_ctzll((U64)val) >> 3);
+            return (unsigned)__builtin_ctzll((U64)val) >> 3;
 #       else
-            static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, 0, 3, 1, 3, 1, 4, 2, 7, 0, 2, 3, 6, 1, 5, 3, 5, 1, 3, 4, 4, 2, 5, 6, 7, 7, 0, 1, 2, 3, 3, 4, 6, 2, 6, 5, 5, 3, 4, 5, 6, 7, 1, 2, 4, 6, 4, 4, 5, 7, 2, 6, 5, 7, 6, 7, 7 };
+            static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2,
+                                                     0, 3, 1, 3, 1, 4, 2, 7,
+                                                     0, 2, 3, 6, 1, 5, 3, 5,
+                                                     1, 3, 4, 4, 2, 5, 6, 7,
+                                                     7, 0, 1, 2, 3, 3, 4, 6,
+                                                     2, 6, 5, 5, 3, 4, 5, 6,
+                                                     7, 1, 2, 4, 6, 4, 4, 5,
+                                                     7, 2, 6, 5, 7, 6, 7, 7 };
             return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58];
 #       endif
         } else /* 32 bits */ {
@@ -319,23 +497,29 @@
             _BitScanForward( &r, (U32)val );
             return (int)(r>>3);
 #       elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT)
-            return (__builtin_ctz((U32)val) >> 3);
+            return (unsigned)__builtin_ctz((U32)val) >> 3;
 #       else
-            static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, 3, 2, 2, 1, 3, 2, 0, 1, 3, 3, 1, 2, 2, 2, 2, 0, 3, 1, 2, 0, 1, 0, 1, 1 };
+            static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0,
+                                                     3, 2, 2, 1, 3, 2, 0, 1,
+                                                     3, 3, 1, 2, 2, 2, 2, 0,
+                                                     3, 1, 2, 0, 1, 0, 1, 1 };
             return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27];
 #       endif
         }
     } else   /* Big Endian CPU */ {
-        if (sizeof(val)==8) {
+        if (sizeof(val)==8) {   /* 64-bits */
 #       if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT)
             unsigned long r = 0;
             _BitScanReverse64( &r, val );
             return (unsigned)(r>>3);
 #       elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT)
-            return (__builtin_clzll((U64)val) >> 3);
+            return (unsigned)__builtin_clzll((U64)val) >> 3;
 #       else
+            static const U32 by32 = sizeof(val)*4;  /* 32 on 64 bits (goal), 16 on 32 bits.
+                Just to avoid some static analyzer complaining about shift by 32 on 32-bits target.
+                Note that this code path is never triggered in 32-bits mode. */
             unsigned r;
-            if (!(val>>32)) { r=4; } else { r=0; val>>=32; }
+            if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; }
             if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; }
             r += (!val);
             return r;
@@ -346,7 +530,7 @@
             _BitScanReverse( &r, (unsigned long)val );
             return (unsigned)(r>>3);
 #       elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT)
-            return (__builtin_clz((U32)val) >> 3);
+            return (unsigned)__builtin_clz((U32)val) >> 3;
 #       else
             unsigned r;
             if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; }
@@ -358,11 +542,20 @@
 }
 
 #define STEPSIZE sizeof(reg_t)
-static unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit)
+LZ4_FORCE_INLINE
+unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit)
 {
     const BYTE* const pStart = pIn;
 
-    while (likely(pIn<pInLimit-(STEPSIZE-1))) {
+    if (likely(pIn < pInLimit-(STEPSIZE-1))) {
+        reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn);
+        if (!diff) {
+            pIn+=STEPSIZE; pMatch+=STEPSIZE;
+        } else {
+            return LZ4_NbCommonBytes(diff);
+    }   }
+
+    while (likely(pIn < pInLimit-(STEPSIZE-1))) {
         reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn);
         if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; }
         pIn += LZ4_NbCommonBytes(diff);
@@ -387,15 +580,34 @@
 /*-************************************
 *  Local Structures and types
 **************************************/
-typedef enum { notLimited = 0, limitedOutput = 1 } limitedOutput_directive;
-typedef enum { byPtr, byU32, byU16 } tableType_t;
+typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t;
 
-typedef enum { noDict = 0, withPrefix64k, usingExtDict } dict_directive;
+/**
+ * This enum distinguishes several different modes of accessing previous
+ * content in the stream.
+ *
+ * - noDict        : There is no preceding content.
+ * - withPrefix64k : Table entries up to ctx->dictSize before the current blob
+ *                   blob being compressed are valid and refer to the preceding
+ *                   content (of length ctx->dictSize), which is available
+ *                   contiguously preceding in memory the content currently
+ *                   being compressed.
+ * - usingExtDict  : Like withPrefix64k, but the preceding content is somewhere
+ *                   else in memory, starting at ctx->dictionary with length
+ *                   ctx->dictSize.
+ * - usingDictCtx  : Like usingExtDict, but everything concerning the preceding
+ *                   content is in a separate context, pointed to by
+ *                   ctx->dictCtx. ctx->dictionary, ctx->dictSize, and table
+ *                   entries in the current context that refer to positions
+ *                   preceding the beginning of the current compression are
+ *                   ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx
+ *                   ->dictSize describe the location and size of the preceding
+ *                   content, and matches are found by looking in the ctx
+ *                   ->dictCtx->hashTable.
+ */
+typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive;
 typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive;
 
-typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive;
-typedef enum { full = 0, partial = 1 } earlyEnd_directive;
-
 
 /*-************************************
 *  Local Utils
@@ -406,6 +618,23 @@
 int LZ4_sizeofState() { return LZ4_STREAMSIZE; }
 
 
+/*-************************************
+*  Internal Definitions used in Tests
+**************************************/
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize);
+
+int LZ4_decompress_safe_forceExtDict(const char* source, char* dest,
+                                     int compressedSize, int maxOutputSize,
+                                     const void* dictStart, size_t dictSize);
+
+#if defined (__cplusplus)
+}
+#endif
+
 /*-******************************
 *  Compression functions
 ********************************/
@@ -419,102 +648,225 @@
 
 static U32 LZ4_hash5(U64 sequence, tableType_t const tableType)
 {
-    static const U64 prime5bytes = 889523592379ULL;
-    static const U64 prime8bytes = 11400714785074694791ULL;
     const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG;
-    if (LZ4_isLittleEndian())
+    if (LZ4_isLittleEndian()) {
+        const U64 prime5bytes = 889523592379ULL;
         return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog));
-    else
+    } else {
+        const U64 prime8bytes = 11400714785074694791ULL;
         return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog));
+    }
 }
 
-FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType)
+LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType)
 {
     if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType);
     return LZ4_hash4(LZ4_read32(p), tableType);
 }
 
-static void LZ4_putPositionOnHash(const BYTE* p, U32 h, void* tableBase, tableType_t const tableType, const BYTE* srcBase)
+static void LZ4_clearHash(U32 h, void* tableBase, tableType_t const tableType)
 {
     switch (tableType)
     {
+    default: /* fallthrough */
+    case clearedTable: { /* illegal! */ assert(0); return; }
+    case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = NULL; return; }
+    case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = 0; return; }
+    case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = 0; return; }
+    }
+}
+
+static void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType)
+{
+    switch (tableType)
+    {
+    default: /* fallthrough */
+    case clearedTable: /* fallthrough */
+    case byPtr: { /* illegal! */ assert(0); return; }
+    case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; }
+    case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; }
+    }
+}
+
+static void LZ4_putPositionOnHash(const BYTE* p, U32 h,
+                                  void* tableBase, tableType_t const tableType,
+                            const BYTE* srcBase)
+{
+    switch (tableType)
+    {
+    case clearedTable: { /* illegal! */ assert(0); return; }
     case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; }
     case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; }
     case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; }
     }
 }
 
-FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase)
+LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase)
 {
     U32 const h = LZ4_hashPosition(p, tableType);
     LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase);
 }
 
-static const BYTE* LZ4_getPositionOnHash(U32 h, void* tableBase, tableType_t tableType, const BYTE* srcBase)
+/* LZ4_getIndexOnHash() :
+ * Index of match position registered in hash table.
+ * hash position must be calculated by using base+index, or dictBase+index.
+ * Assumption 1 : only valid if tableType == byU32 or byU16.
+ * Assumption 2 : h is presumed valid (within limits of hash table)
+ */
+static U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType)
 {
-    if (tableType == byPtr) { const BYTE** hashTable = (const BYTE**) tableBase; return hashTable[h]; }
-    if (tableType == byU32) { const U32* const hashTable = (U32*) tableBase; return hashTable[h] + srcBase; }
-    { const U16* const hashTable = (U16*) tableBase; return hashTable[h] + srcBase; }   /* default, to ensure a return */
+    LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2);
+    if (tableType == byU32) {
+        const U32* const hashTable = (const U32*) tableBase;
+        assert(h < (1U << (LZ4_MEMORY_USAGE-2)));
+        return hashTable[h];
+    }
+    if (tableType == byU16) {
+        const U16* const hashTable = (const U16*) tableBase;
+        assert(h < (1U << (LZ4_MEMORY_USAGE-1)));
+        return hashTable[h];
+    }
+    assert(0); return 0;  /* forbidden case */
 }
 
-FORCE_INLINE const BYTE* LZ4_getPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase)
+static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType, const BYTE* srcBase)
+{
+    if (tableType == byPtr) { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; }
+    if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; return hashTable[h] + srcBase; }
+    { const U16* const hashTable = (const U16*) tableBase; return hashTable[h] + srcBase; }   /* default, to ensure a return */
+}
+
+LZ4_FORCE_INLINE const BYTE*
+LZ4_getPosition(const BYTE* p,
+                const void* tableBase, tableType_t tableType,
+                const BYTE* srcBase)
 {
     U32 const h = LZ4_hashPosition(p, tableType);
     return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase);
 }
 
+LZ4_FORCE_INLINE void
+LZ4_prepareTable(LZ4_stream_t_internal* const cctx,
+           const int inputSize,
+           const tableType_t tableType) {
+    /* If compression failed during the previous step, then the context
+     * is marked as dirty, therefore, it has to be fully reset.
+     */
+    if (cctx->dirty) {
+        DEBUGLOG(5, "LZ4_prepareTable: Full reset for %p", cctx);
+        MEM_INIT(cctx, 0, sizeof(LZ4_stream_t_internal));
+        return;
+    }
+
+    /* If the table hasn't been used, it's guaranteed to be zeroed out, and is
+     * therefore safe to use no matter what mode we're in. Otherwise, we figure
+     * out if it's safe to leave as is or whether it needs to be reset.
+     */
+    if (cctx->tableType != clearedTable) {
+        assert(inputSize >= 0);
+        if (cctx->tableType != tableType
+          || ((tableType == byU16) && cctx->currentOffset + (unsigned)inputSize >= 0xFFFFU)
+          || ((tableType == byU32) && cctx->currentOffset > 1 GB)
+          || tableType == byPtr
+          || inputSize >= 4 KB)
+        {
+            DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", cctx);
+            MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE);
+            cctx->currentOffset = 0;
+            cctx->tableType = clearedTable;
+        } else {
+            DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)");
+        }
+    }
+
+    /* Adding a gap, so all previous entries are > LZ4_DISTANCE_MAX back, is faster
+     * than compressing without a gap. However, compressing with
+     * currentOffset == 0 is faster still, so we preserve that case.
+     */
+    if (cctx->currentOffset != 0 && tableType == byU32) {
+        DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset");
+        cctx->currentOffset += 64 KB;
+    }
+
+    /* Finally, clear history */
+    cctx->dictCtx = NULL;
+    cctx->dictionary = NULL;
+    cctx->dictSize = 0;
+}
 
 /** LZ4_compress_generic() :
     inlined, to ensure branches are decided at compilation time */
-FORCE_INLINE int LZ4_compress_generic(
+LZ4_FORCE_INLINE int LZ4_compress_generic(
                  LZ4_stream_t_internal* const cctx,
                  const char* const source,
                  char* const dest,
                  const int inputSize,
+                 int *inputConsumed, /* only written when outputDirective == fillOutput */
                  const int maxOutputSize,
-                 const limitedOutput_directive outputLimited,
+                 const limitedOutput_directive outputDirective,
                  const tableType_t tableType,
-                 const dict_directive dict,
+                 const dict_directive dictDirective,
                  const dictIssue_directive dictIssue,
-                 const U32 acceleration)
+                 const int acceleration)
 {
+    int result;
     const BYTE* ip = (const BYTE*) source;
-    const BYTE* base;
+
+    U32 const startIndex = cctx->currentOffset;
+    const BYTE* base = (const BYTE*) source - startIndex;
     const BYTE* lowLimit;
-    const BYTE* const lowRefLimit = ip - cctx->dictSize;
-    const BYTE* const dictionary = cctx->dictionary;
-    const BYTE* const dictEnd = dictionary + cctx->dictSize;
-    const ptrdiff_t dictDelta = dictEnd - (const BYTE*)source;
+
+    const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx;
+    const BYTE* const dictionary =
+        dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary;
+    const U32 dictSize =
+        dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize;
+    const U32 dictDelta = (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0;   /* make indexes in dictCtx comparable with index in current context */
+
+    int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx);
+    U32 const prefixIdxLimit = startIndex - dictSize;   /* used when dictDirective == dictSmall */
+    const BYTE* const dictEnd = dictionary + dictSize;
     const BYTE* anchor = (const BYTE*) source;
     const BYTE* const iend = ip + inputSize;
-    const BYTE* const mflimit = iend - MFLIMIT;
+    const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1;
     const BYTE* const matchlimit = iend - LASTLITERALS;
 
+    /* the dictCtx currentOffset is indexed on the start of the dictionary,
+     * while a dictionary in the current context precedes the currentOffset */
+    const BYTE* dictBase = (dictDirective == usingDictCtx) ?
+                            dictionary + dictSize - dictCtx->currentOffset :
+                            dictionary + dictSize - startIndex;
+
     BYTE* op = (BYTE*) dest;
     BYTE* const olimit = op + maxOutputSize;
 
+    U32 offset = 0;
     U32 forwardH;
 
-    /* Init conditions */
-    if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) return 0;   /* Unsupported inputSize, too large (or negative) */
-    switch(dict)
-    {
-    case noDict:
-    default:
-        base = (const BYTE*)source;
-        lowLimit = (const BYTE*)source;
-        break;
-    case withPrefix64k:
-        base = (const BYTE*)source - cctx->currentOffset;
-        lowLimit = (const BYTE*)source - cctx->dictSize;
-        break;
-    case usingExtDict:
-        base = (const BYTE*)source - cctx->currentOffset;
-        lowLimit = (const BYTE*)source;
-        break;
+    DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, tableType=%u", inputSize, tableType);
+    /* If init conditions are not met, we don't have to mark stream
+     * as having dirty context, since no action was taken yet */
+    if (outputDirective == fillOutput && maxOutputSize < 1) { return 0; } /* Impossible to store anything */
+    if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) { return 0; }           /* Unsupported inputSize, too large (or negative) */
+    if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) { return 0; }  /* Size too large (not within 64K limit) */
+    if (tableType==byPtr) assert(dictDirective==noDict);      /* only supported use case with byPtr */
+    assert(acceleration >= 1);
+
+    lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0);
+
+    /* Update context state */
+    if (dictDirective == usingDictCtx) {
+        /* Subsequent linked blocks can't use the dictionary. */
+        /* Instead, they use the block we just compressed. */
+        cctx->dictCtx = NULL;
+        cctx->dictSize = (U32)inputSize;
+    } else {
+        cctx->dictSize += (U32)inputSize;
     }
-    if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) return 0;   /* Size too large (not within 64K limit) */
-    if (inputSize<LZ4_minLength) goto _last_literals;                  /* Input too small, no compression (all literals) */
+    cctx->currentOffset += (U32)inputSize;
+    cctx->tableType = (U16)tableType;
+
+    if (inputSize<LZ4_minLength) goto _last_literals;        /* Input too small, no compression (all literals) */
 
     /* First Byte */
     LZ4_putPosition(ip, cctx->hashTable, tableType, base);
@@ -522,50 +874,112 @@
 
     /* Main Loop */
     for ( ; ; ) {
-        ptrdiff_t refDelta = 0;
         const BYTE* match;
         BYTE* token;
+        const BYTE* filledIp;
 
         /* Find a match */
-        {   const BYTE* forwardIp = ip;
-            unsigned step = 1;
-            unsigned searchMatchNb = acceleration << LZ4_skipTrigger;
+        if (tableType == byPtr) {
+            const BYTE* forwardIp = ip;
+            int step = 1;
+            int searchMatchNb = acceleration << LZ4_skipTrigger;
             do {
                 U32 const h = forwardH;
                 ip = forwardIp;
                 forwardIp += step;
                 step = (searchMatchNb++ >> LZ4_skipTrigger);
 
-                if (unlikely(forwardIp > mflimit)) goto _last_literals;
+                if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals;
+                assert(ip < mflimitPlusOne);
 
                 match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base);
-                if (dict==usingExtDict) {
-                    if (match < (const BYTE*)source) {
-                        refDelta = dictDelta;
-                        lowLimit = dictionary;
-                    } else {
-                        refDelta = 0;
-                        lowLimit = (const BYTE*)source;
-                }   }
                 forwardH = LZ4_hashPosition(forwardIp, tableType);
                 LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base);
 
-            } while ( ((dictIssue==dictSmall) ? (match < lowRefLimit) : 0)
-                || ((tableType==byU16) ? 0 : (match + MAX_DISTANCE < ip))
-                || (LZ4_read32(match+refDelta) != LZ4_read32(ip)) );
+            } while ( (match+LZ4_DISTANCE_MAX < ip)
+                   || (LZ4_read32(match) != LZ4_read32(ip)) );
+
+        } else {   /* byU32, byU16 */
+
+            const BYTE* forwardIp = ip;
+            int step = 1;
+            int searchMatchNb = acceleration << LZ4_skipTrigger;
+            do {
+                U32 const h = forwardH;
+                U32 const current = (U32)(forwardIp - base);
+                U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType);
+                assert(matchIndex <= current);
+                assert(forwardIp - base < (ptrdiff_t)(2 GB - 1));
+                ip = forwardIp;
+                forwardIp += step;
+                step = (searchMatchNb++ >> LZ4_skipTrigger);
+
+                if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals;
+                assert(ip < mflimitPlusOne);
+
+                if (dictDirective == usingDictCtx) {
+                    if (matchIndex < startIndex) {
+                        /* there was no match, try the dictionary */
+                        assert(tableType == byU32);
+                        matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32);
+                        match = dictBase + matchIndex;
+                        matchIndex += dictDelta;   /* make dictCtx index comparable with current context */
+                        lowLimit = dictionary;
+                    } else {
+                        match = base + matchIndex;
+                        lowLimit = (const BYTE*)source;
+                    }
+                } else if (dictDirective==usingExtDict) {
+                    if (matchIndex < startIndex) {
+                        DEBUGLOG(7, "extDict candidate: matchIndex=%5u  <  startIndex=%5u", matchIndex, startIndex);
+                        assert(startIndex - matchIndex >= MINMATCH);
+                        match = dictBase + matchIndex;
+                        lowLimit = dictionary;
+                    } else {
+                        match = base + matchIndex;
+                        lowLimit = (const BYTE*)source;
+                    }
+                } else {   /* single continuous memory segment */
+                    match = base + matchIndex;
+                }
+                forwardH = LZ4_hashPosition(forwardIp, tableType);
+                LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType);
+
+                DEBUGLOG(7, "candidate at pos=%u  (offset=%u \n", matchIndex, current - matchIndex);
+                if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) { continue; }    /* match outside of valid area */
+                assert(matchIndex < current);
+                if ( ((tableType != byU16) || (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX))
+                  && (matchIndex+LZ4_DISTANCE_MAX < current)) {
+                    continue;
+                } /* too far */
+                assert((current - matchIndex) <= LZ4_DISTANCE_MAX);  /* match now expected within distance */
+
+                if (LZ4_read32(match) == LZ4_read32(ip)) {
+                    if (maybe_extMem) offset = current - matchIndex;
+                    break;   /* match found */
+                }
+
+            } while(1);
         }
 
         /* Catch up */
-        while (((ip>anchor) & (match+refDelta > lowLimit)) && (unlikely(ip[-1]==match[refDelta-1]))) { ip--; match--; }
+        filledIp = ip;
+        while (((ip>anchor) & (match > lowLimit)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; }
 
         /* Encode Literals */
         {   unsigned const litLength = (unsigned)(ip - anchor);
             token = op++;
-            if ((outputLimited) &&  /* Check output buffer overflow */
-                (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)))
-                return 0;
+            if ((outputDirective == limitedOutput) &&  /* Check output buffer overflow */
+                (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)) ) {
+                return 0;   /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */
+            }
+            if ((outputDirective == fillOutput) &&
+                (unlikely(op + (litLength+240)/255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) {
+                op--;
+                goto _last_literals;
+            }
             if (litLength >= RUN_MASK) {
-                int len = (int)litLength-RUN_MASK;
+                int len = (int)(litLength - RUN_MASK);
                 *token = (RUN_MASK<<ML_BITS);
                 for(; len >= 255 ; len-=255) *op++ = 255;
                 *op++ = (BYTE)len;
@@ -573,82 +987,183 @@
             else *token = (BYTE)(litLength<<ML_BITS);
 
             /* Copy Literals */
-            LZ4_wildCopy(op, anchor, op+litLength);
+            LZ4_wildCopy8(op, anchor, op+litLength);
             op+=litLength;
+            DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i",
+                        (int)(anchor-(const BYTE*)source), litLength, (int)(ip-(const BYTE*)source));
         }
 
 _next_match:
+        /* at this stage, the following variables must be correctly set :
+         * - ip : at start of LZ operation
+         * - match : at start of previous pattern occurence; can be within current prefix, or within extDict
+         * - offset : if maybe_ext_memSegment==1 (constant)
+         * - lowLimit : must be == dictionary to mean "match is within extDict"; must be == source otherwise
+         * - token and *token : position to write 4-bits for match length; higher 4-bits for literal length supposed already written
+         */
+
+        if ((outputDirective == fillOutput) &&
+            (op + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit)) {
+            /* the match was too close to the end, rewind and go to last literals */
+            op = token;
+            goto _last_literals;
+        }
+
         /* Encode Offset */
-        LZ4_writeLE16(op, (U16)(ip-match)); op+=2;
+        if (maybe_extMem) {   /* static test */
+            DEBUGLOG(6, "             with offset=%u  (ext if > %i)", offset, (int)(ip - (const BYTE*)source));
+            assert(offset <= LZ4_DISTANCE_MAX && offset > 0);
+            LZ4_writeLE16(op, (U16)offset); op+=2;
+        } else  {
+            DEBUGLOG(6, "             with offset=%u  (same segment)", (U32)(ip - match));
+            assert(ip-match <= LZ4_DISTANCE_MAX);
+            LZ4_writeLE16(op, (U16)(ip - match)); op+=2;
+        }
 
         /* Encode MatchLength */
         {   unsigned matchCode;
 
-            if ((dict==usingExtDict) && (lowLimit==dictionary)) {
-                const BYTE* limit;
-                match += refDelta;
-                limit = ip + (dictEnd-match);
+            if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx)
+              && (lowLimit==dictionary) /* match within extDict */ ) {
+                const BYTE* limit = ip + (dictEnd-match);
+                assert(dictEnd > match);
                 if (limit > matchlimit) limit = matchlimit;
                 matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit);
-                ip += MINMATCH + matchCode;
+                ip += (size_t)matchCode + MINMATCH;
                 if (ip==limit) {
-                    unsigned const more = LZ4_count(ip, (const BYTE*)source, matchlimit);
+                    unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit);
                     matchCode += more;
                     ip += more;
                 }
+                DEBUGLOG(6, "             with matchLength=%u starting in extDict", matchCode+MINMATCH);
             } else {
                 matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit);
-                ip += MINMATCH + matchCode;
+                ip += (size_t)matchCode + MINMATCH;
+                DEBUGLOG(6, "             with matchLength=%u", matchCode+MINMATCH);
             }
 
-            if ( outputLimited &&    /* Check output buffer overflow */
-                (unlikely(op + (1 + LASTLITERALS) + (matchCode>>8) > olimit)) )
-                return 0;
+            if ((outputDirective) &&    /* Check output buffer overflow */
+                (unlikely(op + (1 + LASTLITERALS) + (matchCode+240)/255 > olimit)) ) {
+                if (outputDirective == fillOutput) {
+                    /* Match description too long : reduce it */
+                    U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 1 - LASTLITERALS) * 255;
+                    ip -= matchCode - newMatchCode;
+                    assert(newMatchCode < matchCode);
+                    matchCode = newMatchCode;
+                    if (unlikely(ip <= filledIp)) {
+                        /* We have already filled up to filledIp so if ip ends up less than filledIp
+                         * we have positions in the hash table beyond the current position. This is
+                         * a problem if we reuse the hash table. So we have to remove these positions
+                         * from the hash table.
+                         */
+                        const BYTE* ptr;
+                        DEBUGLOG(5, "Clearing %u positions", (U32)(filledIp - ip));
+                        for (ptr = ip; ptr <= filledIp; ++ptr) {
+                            U32 const h = LZ4_hashPosition(ptr, tableType);
+                            LZ4_clearHash(h, cctx->hashTable, tableType);
+                        }
+                    }
+                } else {
+                    assert(outputDirective == limitedOutput);
+                    return 0;   /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */
+                }
+            }
             if (matchCode >= ML_MASK) {
                 *token += ML_MASK;
                 matchCode -= ML_MASK;
                 LZ4_write32(op, 0xFFFFFFFF);
-                while (matchCode >= 4*255) op+=4, LZ4_write32(op, 0xFFFFFFFF), matchCode -= 4*255;
+                while (matchCode >= 4*255) {
+                    op+=4;
+                    LZ4_write32(op, 0xFFFFFFFF);
+                    matchCode -= 4*255;
+                }
                 op += matchCode / 255;
                 *op++ = (BYTE)(matchCode % 255);
             } else
                 *token += (BYTE)(matchCode);
         }
+        /* Ensure we have enough space for the last literals. */
+        assert(!(outputDirective == fillOutput && op + 1 + LASTLITERALS > olimit));
 
         anchor = ip;
 
         /* Test end of chunk */
-        if (ip > mflimit) break;
+        if (ip >= mflimitPlusOne) break;
 
         /* Fill table */
         LZ4_putPosition(ip-2, cctx->hashTable, tableType, base);
 
         /* Test next position */
-        match = LZ4_getPosition(ip, cctx->hashTable, tableType, base);
-        if (dict==usingExtDict) {
-            if (match < (const BYTE*)source) {
-                refDelta = dictDelta;
-                lowLimit = dictionary;
-            } else {
-                refDelta = 0;
-                lowLimit = (const BYTE*)source;
-        }   }
-        LZ4_putPosition(ip, cctx->hashTable, tableType, base);
-        if ( ((dictIssue==dictSmall) ? (match>=lowRefLimit) : 1)
-            && (match+MAX_DISTANCE>=ip)
-            && (LZ4_read32(match+refDelta)==LZ4_read32(ip)) )
-        { token=op++; *token=0; goto _next_match; }
+        if (tableType == byPtr) {
+
+            match = LZ4_getPosition(ip, cctx->hashTable, tableType, base);
+            LZ4_putPosition(ip, cctx->hashTable, tableType, base);
+            if ( (match+LZ4_DISTANCE_MAX >= ip)
+              && (LZ4_read32(match) == LZ4_read32(ip)) )
+            { token=op++; *token=0; goto _next_match; }
+
+        } else {   /* byU32, byU16 */
+
+            U32 const h = LZ4_hashPosition(ip, tableType);
+            U32 const current = (U32)(ip-base);
+            U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType);
+            assert(matchIndex < current);
+            if (dictDirective == usingDictCtx) {
+                if (matchIndex < startIndex) {
+                    /* there was no match, try the dictionary */
+                    matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32);
+                    match = dictBase + matchIndex;
+                    lowLimit = dictionary;   /* required for match length counter */
+                    matchIndex += dictDelta;
+                } else {
+                    match = base + matchIndex;
+                    lowLimit = (const BYTE*)source;  /* required for match length counter */
+                }
+            } else if (dictDirective==usingExtDict) {
+                if (matchIndex < startIndex) {
+                    match = dictBase + matchIndex;
+                    lowLimit = dictionary;   /* required for match length counter */
+                } else {
+                    match = base + matchIndex;
+                    lowLimit = (const BYTE*)source;   /* required for match length counter */
+                }
+            } else {   /* single memory segment */
+                match = base + matchIndex;
+            }
+            LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType);
+            assert(matchIndex < current);
+            if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1)
+              && (((tableType==byU16) && (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ? 1 : (matchIndex+LZ4_DISTANCE_MAX >= current))
+              && (LZ4_read32(match) == LZ4_read32(ip)) ) {
+                token=op++;
+                *token=0;
+                if (maybe_extMem) offset = current - matchIndex;
+                DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i",
+                            (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source));
+                goto _next_match;
+            }
+        }
 
         /* Prepare next loop */
         forwardH = LZ4_hashPosition(++ip, tableType);
+
     }
 
 _last_literals:
     /* Encode Last Literals */
-    {   size_t const lastRun = (size_t)(iend - anchor);
-        if ( (outputLimited) &&  /* Check output buffer overflow */
-            ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > (U32)maxOutputSize) )
-            return 0;
+    {   size_t lastRun = (size_t)(iend - anchor);
+        if ( (outputDirective) &&  /* Check output buffer overflow */
+            (op + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > olimit)) {
+            if (outputDirective == fillOutput) {
+                /* adapt lastRun to fill 'dst' */
+                assert(olimit >= op);
+                lastRun  = (size_t)(olimit-op) - 1;
+                lastRun -= (lastRun+240)/255;
+            } else {
+                assert(outputDirective == limitedOutput);
+                return 0;   /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */
+            }
+        }
         if (lastRun >= RUN_MASK) {
             size_t accumulator = lastRun - RUN_MASK;
             *op++ = RUN_MASK << ML_BITS;
@@ -658,251 +1173,154 @@
             *op++ = (BYTE)(lastRun<<ML_BITS);
         }
         memcpy(op, anchor, lastRun);
+        ip = anchor + lastRun;
         op += lastRun;
     }
 
-    /* End */
-    return (int) (((char*)op)-dest);
+    if (outputDirective == fillOutput) {
+        *inputConsumed = (int) (((const char*)ip)-source);
+    }
+    DEBUGLOG(5, "LZ4_compress_generic: compressed %i bytes into %i bytes", inputSize, (int)(((char*)op) - dest));
+    result = (int)(((char*)op) - dest);
+    assert(result > 0);
+    return result;
 }
 
 
 int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration)
 {
+    LZ4_stream_t_internal* const ctx = & LZ4_initStream(state, sizeof(LZ4_stream_t)) -> internal_donotuse;
+    assert(ctx != NULL);
+    if (acceleration < 1) acceleration = ACCELERATION_DEFAULT;
+    if (maxOutputSize >= LZ4_compressBound(inputSize)) {
+        if (inputSize < LZ4_64Klimit) {
+            return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration);
+        } else {
+            const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
+            return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration);
+        }
+    } else {
+        if (inputSize < LZ4_64Klimit) {
+            return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration);
+        } else {
+            const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
+            return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration);
+        }
+    }
+}
+
+/**
+ * LZ4_compress_fast_extState_fastReset() :
+ * A variant of LZ4_compress_fast_extState().
+ *
+ * Using this variant avoids an expensive initialization step. It is only safe
+ * to call if the state buffer is known to be correctly initialized already
+ * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of
+ * "correctly initialized").
+ */
+int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration)
+{
     LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse;
-    LZ4_resetStream((LZ4_stream_t*)state);
     if (acceleration < 1) acceleration = ACCELERATION_DEFAULT;
 
-    if (maxOutputSize >= LZ4_compressBound(inputSize)) {
-        if (inputSize < LZ4_64Klimit)
-            return LZ4_compress_generic(ctx, source, dest, inputSize,             0,    notLimited,                        byU16, noDict, noDictIssue, acceleration);
-        else
-            return LZ4_compress_generic(ctx, source, dest, inputSize,             0,    notLimited, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration);
+    if (dstCapacity >= LZ4_compressBound(srcSize)) {
+        if (srcSize < LZ4_64Klimit) {
+            const tableType_t tableType = byU16;
+            LZ4_prepareTable(ctx, srcSize, tableType);
+            if (ctx->currentOffset) {
+                return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, dictSmall, acceleration);
+            } else {
+                return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration);
+            }
+        } else {
+            const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
+            LZ4_prepareTable(ctx, srcSize, tableType);
+            return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration);
+        }
     } else {
-        if (inputSize < LZ4_64Klimit)
-            return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput,                        byU16, noDict, noDictIssue, acceleration);
-        else
-            return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration);
+        if (srcSize < LZ4_64Klimit) {
+            const tableType_t tableType = byU16;
+            LZ4_prepareTable(ctx, srcSize, tableType);
+            if (ctx->currentOffset) {
+                return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration);
+            } else {
+                return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration);
+            }
+        } else {
+            const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
+            LZ4_prepareTable(ctx, srcSize, tableType);
+            return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration);
+        }
     }
 }
 
 
 int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration)
 {
-#if (HEAPMODE)
-    void* ctxPtr = ALLOCATOR(1, sizeof(LZ4_stream_t));   /* malloc-calloc always properly aligned */
+    int result;
+#if (LZ4_HEAPMODE)
+    LZ4_stream_t* ctxPtr = ALLOC(sizeof(LZ4_stream_t));   /* malloc-calloc always properly aligned */
+    if (ctxPtr == NULL) return 0;
 #else
     LZ4_stream_t ctx;
-    void* const ctxPtr = &ctx;
+    LZ4_stream_t* const ctxPtr = &ctx;
 #endif
+    result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration);
 
-    int const result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration);
-
-#if (HEAPMODE)
+#if (LZ4_HEAPMODE)
     FREEMEM(ctxPtr);
 #endif
     return result;
 }
 
 
-int LZ4_compress_default(const char* source, char* dest, int inputSize, int maxOutputSize)
+int LZ4_compress_default(const char* src, char* dst, int srcSize, int maxOutputSize)
 {
-    return LZ4_compress_fast(source, dest, inputSize, maxOutputSize, 1);
+    return LZ4_compress_fast(src, dst, srcSize, maxOutputSize, 1);
 }
 
 
 /* hidden debug function */
 /* strangely enough, gcc generates faster code when this function is uncommented, even if unused */
-int LZ4_compress_fast_force(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration)
+int LZ4_compress_fast_force(const char* src, char* dst, int srcSize, int dstCapacity, int acceleration)
 {
     LZ4_stream_t ctx;
-    LZ4_resetStream(&ctx);
+    LZ4_initStream(&ctx, sizeof(ctx));
 
-    if (inputSize < LZ4_64Klimit)
-        return LZ4_compress_generic(&ctx.internal_donotuse, source, dest, inputSize, maxOutputSize, limitedOutput, byU16,                        noDict, noDictIssue, acceleration);
-    else
-        return LZ4_compress_generic(&ctx.internal_donotuse, source, dest, inputSize, maxOutputSize, limitedOutput, sizeof(void*)==8 ? byU32 : byPtr, noDict, noDictIssue, acceleration);
+    if (srcSize < LZ4_64Klimit) {
+        return LZ4_compress_generic(&ctx.internal_donotuse, src, dst, srcSize, NULL, dstCapacity, limitedOutput, byU16,    noDict, noDictIssue, acceleration);
+    } else {
+        tableType_t const addrMode = (sizeof(void*) > 4) ? byU32 : byPtr;
+        return LZ4_compress_generic(&ctx.internal_donotuse, src, dst, srcSize, NULL, dstCapacity, limitedOutput, addrMode, noDict, noDictIssue, acceleration);
+    }
 }
 
 
-/*-******************************
-*  *_destSize() variant
-********************************/
-
-static int LZ4_compress_destSize_generic(
-                       LZ4_stream_t_internal* const ctx,
-                 const char* const src,
-                       char* const dst,
-                       int*  const srcSizePtr,
-                 const int targetDstSize,
-                 const tableType_t tableType)
-{
-    const BYTE* ip = (const BYTE*) src;
-    const BYTE* base = (const BYTE*) src;
-    const BYTE* lowLimit = (const BYTE*) src;
-    const BYTE* anchor = ip;
-    const BYTE* const iend = ip + *srcSizePtr;
-    const BYTE* const mflimit = iend - MFLIMIT;
-    const BYTE* const matchlimit = iend - LASTLITERALS;
-
-    BYTE* op = (BYTE*) dst;
-    BYTE* const oend = op + targetDstSize;
-    BYTE* const oMaxLit = op + targetDstSize - 2 /* offset */ - 8 /* because 8+MINMATCH==MFLIMIT */ - 1 /* token */;
-    BYTE* const oMaxMatch = op + targetDstSize - (LASTLITERALS + 1 /* token */);
-    BYTE* const oMaxSeq = oMaxLit - 1 /* token */;
-
-    U32 forwardH;
-
-
-    /* Init conditions */
-    if (targetDstSize < 1) return 0;                                     /* Impossible to store anything */
-    if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0;            /* Unsupported input size, too large (or negative) */
-    if ((tableType == byU16) && (*srcSizePtr>=LZ4_64Klimit)) return 0;   /* Size too large (not within 64K limit) */
-    if (*srcSizePtr<LZ4_minLength) goto _last_literals;                  /* Input too small, no compression (all literals) */
-
-    /* First Byte */
-    *srcSizePtr = 0;
-    LZ4_putPosition(ip, ctx->hashTable, tableType, base);
-    ip++; forwardH = LZ4_hashPosition(ip, tableType);
-
-    /* Main Loop */
-    for ( ; ; ) {
-        const BYTE* match;
-        BYTE* token;
-
-        /* Find a match */
-        {   const BYTE* forwardIp = ip;
-            unsigned step = 1;
-            unsigned searchMatchNb = 1 << LZ4_skipTrigger;
-
-            do {
-                U32 h = forwardH;
-                ip = forwardIp;
-                forwardIp += step;
-                step = (searchMatchNb++ >> LZ4_skipTrigger);
-
-                if (unlikely(forwardIp > mflimit)) goto _last_literals;
-
-                match = LZ4_getPositionOnHash(h, ctx->hashTable, tableType, base);
-                forwardH = LZ4_hashPosition(forwardIp, tableType);
-                LZ4_putPositionOnHash(ip, h, ctx->hashTable, tableType, base);
-
-            } while ( ((tableType==byU16) ? 0 : (match + MAX_DISTANCE < ip))
-                || (LZ4_read32(match) != LZ4_read32(ip)) );
-        }
-
-        /* Catch up */
-        while ((ip>anchor) && (match > lowLimit) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; }
-
-        /* Encode Literal length */
-        {   unsigned litLength = (unsigned)(ip - anchor);
-            token = op++;
-            if (op + ((litLength+240)/255) + litLength > oMaxLit) {
-                /* Not enough space for a last match */
-                op--;
-                goto _last_literals;
-            }
-            if (litLength>=RUN_MASK) {
-                unsigned len = litLength - RUN_MASK;
-                *token=(RUN_MASK<<ML_BITS);
-                for(; len >= 255 ; len-=255) *op++ = 255;
-                *op++ = (BYTE)len;
-            }
-            else *token = (BYTE)(litLength<<ML_BITS);
-
-            /* Copy Literals */
-            LZ4_wildCopy(op, anchor, op+litLength);
-            op += litLength;
-        }
-
-_next_match:
-        /* Encode Offset */
-        LZ4_writeLE16(op, (U16)(ip-match)); op+=2;
-
-        /* Encode MatchLength */
-        {   size_t matchLength = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit);
-
-            if (op + ((matchLength+240)/255) > oMaxMatch) {
-                /* Match description too long : reduce it */
-                matchLength = (15-1) + (oMaxMatch-op) * 255;
-            }
-            ip += MINMATCH + matchLength;
-
-            if (matchLength>=ML_MASK) {
-                *token += ML_MASK;
-                matchLength -= ML_MASK;
-                while (matchLength >= 255) { matchLength-=255; *op++ = 255; }
-                *op++ = (BYTE)matchLength;
-            }
-            else *token += (BYTE)(matchLength);
-        }
-
-        anchor = ip;
-
-        /* Test end of block */
-        if (ip > mflimit) break;
-        if (op > oMaxSeq) break;
-
-        /* Fill table */
-        LZ4_putPosition(ip-2, ctx->hashTable, tableType, base);
-
-        /* Test next position */
-        match = LZ4_getPosition(ip, ctx->hashTable, tableType, base);
-        LZ4_putPosition(ip, ctx->hashTable, tableType, base);
-        if ( (match+MAX_DISTANCE>=ip)
-            && (LZ4_read32(match)==LZ4_read32(ip)) )
-        { token=op++; *token=0; goto _next_match; }
-
-        /* Prepare next loop */
-        forwardH = LZ4_hashPosition(++ip, tableType);
-    }
-
-_last_literals:
-    /* Encode Last Literals */
-    {   size_t lastRunSize = (size_t)(iend - anchor);
-        if (op + 1 /* token */ + ((lastRunSize+240)/255) /* litLength */ + lastRunSize /* literals */ > oend) {
-            /* adapt lastRunSize to fill 'dst' */
-            lastRunSize  = (oend-op) - 1;
-            lastRunSize -= (lastRunSize+240)/255;
-        }
-        ip = anchor + lastRunSize;
-
-        if (lastRunSize >= RUN_MASK) {
-            size_t accumulator = lastRunSize - RUN_MASK;
-            *op++ = RUN_MASK << ML_BITS;
-            for(; accumulator >= 255 ; accumulator-=255) *op++ = 255;
-            *op++ = (BYTE) accumulator;
-        } else {
-            *op++ = (BYTE)(lastRunSize<<ML_BITS);
-        }
-        memcpy(op, anchor, lastRunSize);
-        op += lastRunSize;
-    }
-
-    /* End */
-    *srcSizePtr = (int) (((const char*)ip)-src);
-    return (int) (((char*)op)-dst);
-}
-
-
+/* Note!: This function leaves the stream in an unclean/broken state!
+ * It is not safe to subsequently use the same state with a _fastReset() or
+ * _continue() call without resetting it. */
 static int LZ4_compress_destSize_extState (LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize)
 {
-    LZ4_resetStream(state);
+    void* const s = LZ4_initStream(state, sizeof (*state));
+    assert(s != NULL); (void)s;
 
     if (targetDstSize >= LZ4_compressBound(*srcSizePtr)) {  /* compression success is guaranteed */
         return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1);
     } else {
-        if (*srcSizePtr < LZ4_64Klimit)
-            return LZ4_compress_destSize_generic(&state->internal_donotuse, src, dst, srcSizePtr, targetDstSize, byU16);
-        else
-            return LZ4_compress_destSize_generic(&state->internal_donotuse, src, dst, srcSizePtr, targetDstSize, sizeof(void*)==8 ? byU32 : byPtr);
-    }
+        if (*srcSizePtr < LZ4_64Klimit) {
+            return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, 1);
+        } else {
+            tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32;
+            return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, 1);
+    }   }
 }
 
 
 int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize)
 {
-#if (HEAPMODE)
-    LZ4_stream_t* ctx = (LZ4_stream_t*)ALLOCATOR(1, sizeof(LZ4_stream_t));   /* malloc-calloc always properly aligned */
+#if (LZ4_HEAPMODE)
+    LZ4_stream_t* ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t));   /* malloc-calloc always properly aligned */
+    if (ctx == NULL) return 0;
 #else
     LZ4_stream_t ctxBody;
     LZ4_stream_t* ctx = &ctxBody;
@@ -910,7 +1328,7 @@
 
     int result = LZ4_compress_destSize_extState(ctx, src, dst, srcSizePtr, targetDstSize);
 
-#if (HEAPMODE)
+#if (LZ4_HEAPMODE)
     FREEMEM(ctx);
 #endif
     return result;
@@ -924,19 +1342,54 @@
 
 LZ4_stream_t* LZ4_createStream(void)
 {
-    LZ4_stream_t* lz4s = (LZ4_stream_t*)ALLOCATOR(8, LZ4_STREAMSIZE_U64);
+    LZ4_stream_t* const lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t));
     LZ4_STATIC_ASSERT(LZ4_STREAMSIZE >= sizeof(LZ4_stream_t_internal));    /* A compilation error here means LZ4_STREAMSIZE is not large enough */
-    LZ4_resetStream(lz4s);
+    DEBUGLOG(4, "LZ4_createStream %p", lz4s);
+    if (lz4s == NULL) return NULL;
+    LZ4_initStream(lz4s, sizeof(*lz4s));
     return lz4s;
 }
 
+#ifndef _MSC_VER  /* for some reason, Visual fails the aligment test on 32-bit x86 :
+                     it reports an aligment of 8-bytes,
+                     while actually aligning LZ4_stream_t on 4 bytes. */
+static size_t LZ4_stream_t_alignment(void)
+{
+    struct { char c; LZ4_stream_t t; } t_a;
+    return sizeof(t_a) - sizeof(t_a.t);
+}
+#endif
+
+LZ4_stream_t* LZ4_initStream (void* buffer, size_t size)
+{
+    DEBUGLOG(5, "LZ4_initStream");
+    if (buffer == NULL) { return NULL; }
+    if (size < sizeof(LZ4_stream_t)) { return NULL; }
+#ifndef _MSC_VER  /* for some reason, Visual fails the aligment test on 32-bit x86 :
+                     it reports an aligment of 8-bytes,
+                     while actually aligning LZ4_stream_t on 4 bytes. */
+    if (((size_t)buffer) & (LZ4_stream_t_alignment() - 1)) { return NULL; } /* alignment check */
+#endif
+    MEM_INIT(buffer, 0, sizeof(LZ4_stream_t));
+    return (LZ4_stream_t*)buffer;
+}
+
+/* resetStream is now deprecated,
+ * prefer initStream() which is more general */
 void LZ4_resetStream (LZ4_stream_t* LZ4_stream)
 {
+    DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream);
     MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t));
 }
 
+void LZ4_resetStream_fast(LZ4_stream_t* ctx) {
+    LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32);
+}
+
 int LZ4_freeStream (LZ4_stream_t* LZ4_stream)
 {
+    if (!LZ4_stream) return 0;   /* support free on NULL */
+    DEBUGLOG(5, "LZ4_freeStream %p", LZ4_stream);
     FREEMEM(LZ4_stream);
     return (0);
 }
@@ -946,43 +1399,88 @@
 int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize)
 {
     LZ4_stream_t_internal* dict = &LZ4_dict->internal_donotuse;
+    const tableType_t tableType = byU32;
     const BYTE* p = (const BYTE*)dictionary;
     const BYTE* const dictEnd = p + dictSize;
     const BYTE* base;
 
-    if ((dict->initCheck) || (dict->currentOffset > 1 GB))  /* Uninitialized structure, or reuse overflow */
-        LZ4_resetStream(LZ4_dict);
+    DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict);
+
+    /* It's necessary to reset the context,
+     * and not just continue it with prepareTable()
+     * to avoid any risk of generating overflowing matchIndex
+     * when compressing using this dictionary */
+    LZ4_resetStream(LZ4_dict);
+
+    /* We always increment the offset by 64 KB, since, if the dict is longer,
+     * we truncate it to the last 64k, and if it's shorter, we still want to
+     * advance by a whole window length so we can provide the guarantee that
+     * there are only valid offsets in the window, which allows an optimization
+     * in LZ4_compress_fast_continue() where it uses noDictIssue even when the
+     * dictionary isn't a full 64k. */
+    dict->currentOffset += 64 KB;
 
     if (dictSize < (int)HASH_UNIT) {
-        dict->dictionary = NULL;
-        dict->dictSize = 0;
         return 0;
     }
 
     if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB;
-    dict->currentOffset += 64 KB;
-    base = p - dict->currentOffset;
+    base = dictEnd - dict->currentOffset;
     dict->dictionary = p;
     dict->dictSize = (U32)(dictEnd - p);
-    dict->currentOffset += dict->dictSize;
+    dict->tableType = tableType;
 
     while (p <= dictEnd-HASH_UNIT) {
-        LZ4_putPosition(p, dict->hashTable, byU32, base);
+        LZ4_putPosition(p, dict->hashTable, tableType, base);
         p+=3;
     }
 
-    return dict->dictSize;
+    return (int)dict->dictSize;
+}
+
+void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream) {
+    const LZ4_stream_t_internal* dictCtx = dictionaryStream == NULL ? NULL :
+        &(dictionaryStream->internal_donotuse);
+
+    DEBUGLOG(4, "LZ4_attach_dictionary (%p, %p, size %u)",
+             workingStream, dictionaryStream,
+             dictCtx != NULL ? dictCtx->dictSize : 0);
+
+    /* Calling LZ4_resetStream_fast() here makes sure that changes will not be
+     * erased by subsequent calls to LZ4_resetStream_fast() in case stream was
+     * marked as having dirty context, e.g. requiring full reset.
+     */
+    LZ4_resetStream_fast(workingStream);
+
+    if (dictCtx != NULL) {
+        /* If the current offset is zero, we will never look in the
+         * external dictionary context, since there is no value a table
+         * entry can take that indicate a miss. In that case, we need
+         * to bump the offset to something non-zero.
+         */
+        if (workingStream->internal_donotuse.currentOffset == 0) {
+            workingStream->internal_donotuse.currentOffset = 64 KB;
+        }
+
+        /* Don't actually attach an empty dictionary.
+         */
+        if (dictCtx->dictSize == 0) {
+            dictCtx = NULL;
+        }
+    }
+    workingStream->internal_donotuse.dictCtx = dictCtx;
 }
 
 
-static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, const BYTE* src)
+static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize)
 {
-    if ((LZ4_dict->currentOffset > 0x80000000) ||
-        ((uptrval)LZ4_dict->currentOffset > (uptrval)src)) {   /* address space overflow */
+    assert(nextSize >= 0);
+    if (LZ4_dict->currentOffset + (unsigned)nextSize > 0x80000000) {   /* potential ptrdiff_t overflow (32-bits mode) */
         /* rescale hash table */
         U32 const delta = LZ4_dict->currentOffset - 64 KB;
         const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize;
         int i;
+        DEBUGLOG(4, "LZ4_renormDictT");
         for (i=0; i<LZ4_HASH_SIZE_U32; i++) {
             if (LZ4_dict->hashTable[i] < delta) LZ4_dict->hashTable[i]=0;
             else LZ4_dict->hashTable[i] -= delta;
@@ -994,17 +1492,30 @@
 }
 
 
-int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration)
+int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream,
+                                const char* source, char* dest,
+                                int inputSize, int maxOutputSize,
+                                int acceleration)
 {
+    const tableType_t tableType = byU32;
     LZ4_stream_t_internal* streamPtr = &LZ4_stream->internal_donotuse;
-    const BYTE* const dictEnd = streamPtr->dictionary + streamPtr->dictSize;
+    const BYTE* dictEnd = streamPtr->dictionary + streamPtr->dictSize;
 
-    const BYTE* smallest = (const BYTE*) source;
-    if (streamPtr->initCheck) return 0;   /* Uninitialized structure detected */
-    if ((streamPtr->dictSize>0) && (smallest>dictEnd)) smallest = dictEnd;
-    LZ4_renormDictT(streamPtr, smallest);
+    DEBUGLOG(5, "LZ4_compress_fast_continue (inputSize=%i)", inputSize);
+
+    if (streamPtr->dirty) { return 0; } /* Uninitialized structure detected */
+    LZ4_renormDictT(streamPtr, inputSize);   /* avoid index overflow */
     if (acceleration < 1) acceleration = ACCELERATION_DEFAULT;
 
+    /* invalidate tiny dictionaries */
+    if ( (streamPtr->dictSize-1 < 4-1)   /* intentional underflow */
+      && (dictEnd != (const BYTE*)source) ) {
+        DEBUGLOG(5, "LZ4_compress_fast_continue: dictSize(%u) at addr:%p is too small", streamPtr->dictSize, streamPtr->dictionary);
+        streamPtr->dictSize = 0;
+        streamPtr->dictionary = (const BYTE*)source;
+        dictEnd = (const BYTE*)source;
+    }
+
     /* Check overlapping input/dictionary space */
     {   const BYTE* sourceEnd = (const BYTE*) source + inputSize;
         if ((sourceEnd > streamPtr->dictionary) && (sourceEnd < dictEnd)) {
@@ -1017,46 +1528,61 @@
 
     /* prefix mode : source data follows dictionary */
     if (dictEnd == (const BYTE*)source) {
-        int result;
         if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset))
-            result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, withPrefix64k, dictSmall, acceleration);
+            return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration);
         else
-            result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, withPrefix64k, noDictIssue, acceleration);
-        streamPtr->dictSize += (U32)inputSize;
-        streamPtr->currentOffset += (U32)inputSize;
-        return result;
+            return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration);
     }
 
     /* external dictionary mode */
     {   int result;
-        if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset))
-            result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, usingExtDict, dictSmall, acceleration);
-        else
-            result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, byU32, usingExtDict, noDictIssue, acceleration);
+        if (streamPtr->dictCtx) {
+            /* We depend here on the fact that dictCtx'es (produced by
+             * LZ4_loadDict) guarantee that their tables contain no references
+             * to offsets between dictCtx->currentOffset - 64 KB and
+             * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe
+             * to use noDictIssue even when the dict isn't a full 64 KB.
+             */
+            if (inputSize > 4 KB) {
+                /* For compressing large blobs, it is faster to pay the setup
+                 * cost to copy the dictionary's tables into the active context,
+                 * so that the compression loop is only looking into one table.
+                 */
+                memcpy(streamPtr, streamPtr->dictCtx, sizeof(LZ4_stream_t));
+                result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration);
+            } else {
+                result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration);
+            }
+        } else {
+            if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) {
+                result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration);
+            } else {
+                result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration);
+            }
+        }
         streamPtr->dictionary = (const BYTE*)source;
         streamPtr->dictSize = (U32)inputSize;
-        streamPtr->currentOffset += (U32)inputSize;
         return result;
     }
 }
 
 
-/* Hidden debug function, to force external dictionary mode */
-int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int inputSize)
+/* Hidden debug function, to force-test external dictionary mode */
+int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize)
 {
     LZ4_stream_t_internal* streamPtr = &LZ4_dict->internal_donotuse;
     int result;
-    const BYTE* const dictEnd = streamPtr->dictionary + streamPtr->dictSize;
 
-    const BYTE* smallest = dictEnd;
-    if (smallest > (const BYTE*) source) smallest = (const BYTE*) source;
-    LZ4_renormDictT(streamPtr, smallest);
+    LZ4_renormDictT(streamPtr, srcSize);
 
-    result = LZ4_compress_generic(streamPtr, source, dest, inputSize, 0, notLimited, byU32, usingExtDict, noDictIssue, 1);
+    if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) {
+        result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, dictSmall, 1);
+    } else {
+        result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, noDictIssue, 1);
+    }
 
     streamPtr->dictionary = (const BYTE*)source;
-    streamPtr->dictSize = (U32)inputSize;
-    streamPtr->currentOffset += (U32)inputSize;
+    streamPtr->dictSize = (U32)srcSize;
 
     return result;
 }
@@ -1074,8 +1600,8 @@
     LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse;
     const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize;
 
-    if ((U32)dictSize > 64 KB) dictSize = 64 KB;   /* useless to define a dictionary > 64 KB */
-    if ((U32)dictSize > dict->dictSize) dictSize = dict->dictSize;
+    if ((U32)dictSize > 64 KB) { dictSize = 64 KB; } /* useless to define a dictionary > 64 KB */
+    if ((U32)dictSize > dict->dictSize) { dictSize = (int)dict->dictSize; }
 
     memmove(safeBuffer, previousDictEnd - dictSize, dictSize);
 
@@ -1087,218 +1613,587 @@
 
 
 
-/*-*****************************
-*  Decompression functions
-*******************************/
+/*-*******************************
+ *  Decompression functions
+ ********************************/
+
+typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive;
+typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive;
+
+#undef MIN
+#define MIN(a,b)    ( (a) < (b) ? (a) : (b) )
+
+/* Read the variable-length literal or match length.
+ *
+ * ip - pointer to use as input.
+ * lencheck - end ip.  Return an error if ip advances >= lencheck.
+ * loop_check - check ip >= lencheck in body of loop.  Returns loop_error if so.
+ * initial_check - check ip >= lencheck before start of loop.  Returns initial_error if so.
+ * error (output) - error code.  Should be set to 0 before call.
+ */
+typedef enum { loop_error = -2, initial_error = -1, ok = 0 } variable_length_error;
+LZ4_FORCE_INLINE unsigned
+read_variable_length(const BYTE**ip, const BYTE* lencheck, int loop_check, int initial_check, variable_length_error* error)
+{
+  unsigned length = 0;
+  unsigned s;
+  if (initial_check && unlikely((*ip) >= lencheck)) {    /* overflow detection */
+    *error = initial_error;
+    return length;
+  }
+  do {
+    s = **ip;
+    (*ip)++;
+    length += s;
+    if (loop_check && unlikely((*ip) >= lencheck)) {    /* overflow detection */
+      *error = loop_error;
+      return length;
+    }
+  } while (s==255);
+
+  return length;
+}
+
 /*! LZ4_decompress_generic() :
- *  This generic decompression function cover all use cases.
- *  It shall be instantiated several times, using different sets of directives
- *  Note that it is important this generic function is really inlined,
+ *  This generic decompression function covers all use cases.
+ *  It shall be instantiated several times, using different sets of directives.
+ *  Note that it is important for performance that this function really get inlined,
  *  in order to remove useless branches during compilation optimization.
  */
-FORCE_INLINE int LZ4_decompress_generic(
-                 const char* const source,
-                 char* const dest,
-                 int inputSize,
-                 int outputSize,         /* If endOnInput==endOnInputSize, this value is the max size of Output Buffer. */
+LZ4_FORCE_INLINE int
+LZ4_decompress_generic(
+                 const char* const src,
+                 char* const dst,
+                 int srcSize,
+                 int outputSize,         /* If endOnInput==endOnInputSize, this value is `dstCapacity` */
 
-                 int endOnInput,         /* endOnOutputSize, endOnInputSize */
-                 int partialDecoding,    /* full, partial */
-                 int targetOutputSize,   /* only used if partialDecoding==partial */
-                 int dict,               /* noDict, withPrefix64k, usingExtDict */
-                 const BYTE* const lowPrefix,  /* == dest when no prefix */
+                 endCondition_directive endOnInput,   /* endOnOutputSize, endOnInputSize */
+                 earlyEnd_directive partialDecoding,  /* full, partial */
+                 dict_directive dict,                 /* noDict, withPrefix64k, usingExtDict */
+                 const BYTE* const lowPrefix,  /* always <= dst, == dst when no prefix */
                  const BYTE* const dictStart,  /* only if dict==usingExtDict */
                  const size_t dictSize         /* note : = 0 if noDict */
                  )
 {
-    /* Local Variables */
-    const BYTE* ip = (const BYTE*) source;
-    const BYTE* const iend = ip + inputSize;
+    if (src == NULL) { return -1; }
 
-    BYTE* op = (BYTE*) dest;
-    BYTE* const oend = op + outputSize;
-    BYTE* cpy;
-    BYTE* oexit = op + targetOutputSize;
-    const BYTE* const lowLimit = lowPrefix - dictSize;
+    {   const BYTE* ip = (const BYTE*) src;
+        const BYTE* const iend = ip + srcSize;
 
-    const BYTE* const dictEnd = (const BYTE*)dictStart + dictSize;
-    const unsigned dec32table[] = {0, 1, 2, 1, 4, 4, 4, 4};
-    const int dec64table[] = {0, 0, 0, -1, 0, 1, 2, 3};
+        BYTE* op = (BYTE*) dst;
+        BYTE* const oend = op + outputSize;
+        BYTE* cpy;
 
-    const int safeDecode = (endOnInput==endOnInputSize);
-    const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB)));
+        const BYTE* const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize;
+
+        const int safeDecode = (endOnInput==endOnInputSize);
+        const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB)));
 
 
-    /* Special cases */
-    if ((partialDecoding) && (oexit > oend-MFLIMIT)) oexit = oend-MFLIMIT;                        /* targetOutputSize too high => decode everything */
-    if ((endOnInput) && (unlikely(outputSize==0))) return ((inputSize==1) && (*ip==0)) ? 0 : -1;  /* Empty output buffer */
-    if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0?1:-1);
+        /* Set up the "end" pointers for the shortcut. */
+        const BYTE* const shortiend = iend - (endOnInput ? 14 : 8) /*maxLL*/ - 2 /*offset*/;
+        const BYTE* const shortoend = oend - (endOnInput ? 14 : 8) /*maxLL*/ - 18 /*maxML*/;
 
-    /* Main Loop : decode sequences */
-    while (1) {
-        size_t length;
         const BYTE* match;
         size_t offset;
+        unsigned token;
+        size_t length;
 
-        /* get literal length */
-        unsigned const token = *ip++;
-        if ((length=(token>>ML_BITS)) == RUN_MASK) {
-            unsigned s;
-            do {
-                s = *ip++;
-                length += s;
-            } while ( likely(endOnInput ? ip<iend-RUN_MASK : 1) & (s==255) );
-            if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)(op))) goto _output_error;   /* overflow detection */
-            if ((safeDecode) && unlikely((uptrval)(ip)+length<(uptrval)(ip))) goto _output_error;   /* overflow detection */
+
+        DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize);
+
+        /* Special cases */
+        assert(lowPrefix <= op);
+        if ((endOnInput) && (unlikely(outputSize==0))) {
+            /* Empty output buffer */
+            if (partialDecoding) return 0;
+            return ((srcSize==1) && (*ip==0)) ? 0 : -1;
+        }
+        if ((!endOnInput) && (unlikely(outputSize==0))) { return (*ip==0 ? 1 : -1); }
+        if ((endOnInput) && unlikely(srcSize==0)) { return -1; }
+
+	/* Currently the fast loop shows a regression on qualcomm arm chips. */
+#if LZ4_FAST_DEC_LOOP
+        if ((oend - op) < FASTLOOP_SAFE_DISTANCE) {
+            DEBUGLOG(6, "skip fast decode loop");
+            goto safe_decode;
         }
 
-        /* copy literals */
-        cpy = op+length;
-        if ( ((endOnInput) && ((cpy>(partialDecoding?oexit:oend-MFLIMIT)) || (ip+length>iend-(2+1+LASTLITERALS))) )
-            || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) )
-        {
-            if (partialDecoding) {
-                if (cpy > oend) goto _output_error;                           /* Error : write attempt beyond end of output buffer */
-                if ((endOnInput) && (ip+length > iend)) goto _output_error;   /* Error : read attempt beyond end of input buffer */
+        /* Fast loop : decode sequences as long as output < iend-FASTLOOP_SAFE_DISTANCE */
+        while (1) {
+            /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */
+            assert(oend - op >= FASTLOOP_SAFE_DISTANCE);
+            if (endOnInput) { assert(ip < iend); }
+            token = *ip++;
+            length = token >> ML_BITS;  /* literal length */
+
+            assert(!endOnInput || ip <= iend); /* ip < iend before the increment */
+
+            /* decode literal length */
+            if (length == RUN_MASK) {
+                variable_length_error error = ok;
+                length += read_variable_length(&ip, iend-RUN_MASK, endOnInput, endOnInput, &error);
+                if (error == initial_error) { goto _output_error; }
+                if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */
+                if ((safeDecode) && unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */
+
+                /* copy literals */
+                cpy = op+length;
+                LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH);
+                if (endOnInput) {  /* LZ4_decompress_safe() */
+                    if ((cpy>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; }
+                    LZ4_wildCopy32(op, ip, cpy);
+                } else {   /* LZ4_decompress_fast() */
+                    if (cpy>oend-8) { goto safe_literal_copy; }
+                    LZ4_wildCopy8(op, ip, cpy); /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time :
+                                                 * it doesn't know input length, and only relies on end-of-block properties */
+                }
+                ip += length; op = cpy;
             } else {
-                if ((!endOnInput) && (cpy != oend)) goto _output_error;       /* Error : block decoding must stop exactly there */
-                if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) goto _output_error;   /* Error : input must be consumed */
+                cpy = op+length;
+                if (endOnInput) {  /* LZ4_decompress_safe() */
+                    DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length);
+                    /* We don't need to check oend, since we check it once for each loop below */
+                    if (ip > iend-(16 + 1/*max lit + offset + nextToken*/)) { goto safe_literal_copy; }
+                    /* Literals can only be 14, but hope compilers optimize if we copy by a register size */
+                    memcpy(op, ip, 16);
+                } else {  /* LZ4_decompress_fast() */
+                    /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time :
+                     * it doesn't know input length, and relies on end-of-block properties */
+                    memcpy(op, ip, 8);
+                    if (length > 8) { memcpy(op+8, ip+8, 8); }
+                }
+                ip += length; op = cpy;
             }
-            memcpy(op, ip, length);
-            ip += length;
-            op += length;
-            break;     /* Necessarily EOF, due to parsing restrictions */
-        }
-        LZ4_wildCopy(op, ip, cpy);
-        ip += length; op = cpy;
 
-        /* get offset */
-        offset = LZ4_readLE16(ip); ip+=2;
-        match = op - offset;
-        if ((checkOffset) && (unlikely(match < lowLimit))) goto _output_error;   /* Error : offset outside buffers */
-        LZ4_write32(op, (U32)offset);   /* costs ~1%; silence an msan warning when offset==0 */
+            /* get offset */
+            offset = LZ4_readLE16(ip); ip+=2;
+            match = op - offset;
+            assert(match <= op);
 
-        /* get matchlength */
-        length = token & ML_MASK;
-        if (length == ML_MASK) {
-            unsigned s;
-            do {
-                s = *ip++;
-                if ((endOnInput) && (ip > iend-LASTLITERALS)) goto _output_error;
-                length += s;
-            } while (s==255);
-            if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error;   /* overflow detection */
-        }
-        length += MINMATCH;
+            /* get matchlength */
+            length = token & ML_MASK;
 
-        /* check external dictionary */
-        if ((dict==usingExtDict) && (match < lowPrefix)) {
-            if (unlikely(op+length > oend-LASTLITERALS)) goto _output_error;   /* doesn't respect parsing restriction */
-
-            if (length <= (size_t)(lowPrefix-match)) {
-                /* match can be copied as a single segment from external dictionary */
-                memmove(op, dictEnd - (lowPrefix-match), length);
-                op += length;
+            if (length == ML_MASK) {
+              variable_length_error error = ok;
+              if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */
+              length += read_variable_length(&ip, iend - LASTLITERALS + 1, endOnInput, 0, &error);
+              if (error != ok) { goto _output_error; }
+                if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) { goto _output_error; } /* overflow detection */
+                length += MINMATCH;
+                if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) {
+                    goto safe_match_copy;
+                }
             } else {
-                /* match encompass external dictionary and current block */
-                size_t const copySize = (size_t)(lowPrefix-match);
-                size_t const restSize = length - copySize;
-                memcpy(op, dictEnd - copySize, copySize);
-                op += copySize;
-                if (restSize > (size_t)(op-lowPrefix)) {  /* overlap copy */
-                    BYTE* const endOfMatch = op + restSize;
-                    const BYTE* copyFrom = lowPrefix;
-                    while (op < endOfMatch) *op++ = *copyFrom++;
+                length += MINMATCH;
+                if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) {
+                    goto safe_match_copy;
+                }
+
+                /* Fastpath check: Avoids a branch in LZ4_wildCopy32 if true */
+                if ((dict == withPrefix64k) || (match >= lowPrefix)) {
+                    if (offset >= 8) {
+                        assert(match >= lowPrefix);
+                        assert(match <= op);
+                        assert(op + 18 <= oend);
+
+                        memcpy(op, match, 8);
+                        memcpy(op+8, match+8, 8);
+                        memcpy(op+16, match+16, 2);
+                        op += length;
+                        continue;
+            }   }   }
+
+            if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */
+            /* match starting within external dictionary */
+            if ((dict==usingExtDict) && (match < lowPrefix)) {
+                if (unlikely(op+length > oend-LASTLITERALS)) {
+                    if (partialDecoding) {
+                        length = MIN(length, (size_t)(oend-op));  /* reach end of buffer */
+                    } else {
+                        goto _output_error;  /* end-of-block condition violated */
+                }   }
+
+                if (length <= (size_t)(lowPrefix-match)) {
+                    /* match fits entirely within external dictionary : just copy */
+                    memmove(op, dictEnd - (lowPrefix-match), length);
+                    op += length;
                 } else {
-                    memcpy(op, lowPrefix, restSize);
-                    op += restSize;
-            }   }
-            continue;
-        }
-
-        /* copy match within block */
-        cpy = op + length;
-        if (unlikely(offset<8)) {
-            const int dec64 = dec64table[offset];
-            op[0] = match[0];
-            op[1] = match[1];
-            op[2] = match[2];
-            op[3] = match[3];
-            match += dec32table[offset];
-            memcpy(op+4, match, 4);
-            match -= dec64;
-        } else { LZ4_copy8(op, match); match+=8; }
-        op += 8;
-
-        if (unlikely(cpy>oend-12)) {
-            BYTE* const oCopyLimit = oend-(WILDCOPYLENGTH-1);
-            if (cpy > oend-LASTLITERALS) goto _output_error;    /* Error : last LASTLITERALS bytes must be literals (uncompressed) */
-            if (op < oCopyLimit) {
-                LZ4_wildCopy(op, match, oCopyLimit);
-                match += oCopyLimit - op;
-                op = oCopyLimit;
+                    /* match stretches into both external dictionary and current block */
+                    size_t const copySize = (size_t)(lowPrefix - match);
+                    size_t const restSize = length - copySize;
+                    memcpy(op, dictEnd - copySize, copySize);
+                    op += copySize;
+                    if (restSize > (size_t)(op - lowPrefix)) {  /* overlap copy */
+                        BYTE* const endOfMatch = op + restSize;
+                        const BYTE* copyFrom = lowPrefix;
+                        while (op < endOfMatch) { *op++ = *copyFrom++; }
+                    } else {
+                        memcpy(op, lowPrefix, restSize);
+                        op += restSize;
+                }   }
+                continue;
             }
-            while (op<cpy) *op++ = *match++;
-        } else {
-            LZ4_copy8(op, match);
-            if (length>16) LZ4_wildCopy(op+8, match+8, cpy);
+
+            /* copy match within block */
+            cpy = op + length;
+
+            assert((op <= oend) && (oend-op >= 32));
+            if (unlikely(offset<16)) {
+                LZ4_memcpy_using_offset(op, match, cpy, offset);
+            } else {
+                LZ4_wildCopy32(op, match, cpy);
+            }
+
+            op = cpy;   /* wildcopy correction */
         }
-        op=cpy;   /* correction */
+    safe_decode:
+#endif
+
+        /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */
+        while (1) {
+            token = *ip++;
+            length = token >> ML_BITS;  /* literal length */
+
+            assert(!endOnInput || ip <= iend); /* ip < iend before the increment */
+
+            /* A two-stage shortcut for the most common case:
+             * 1) If the literal length is 0..14, and there is enough space,
+             * enter the shortcut and copy 16 bytes on behalf of the literals
+             * (in the fast mode, only 8 bytes can be safely copied this way).
+             * 2) Further if the match length is 4..18, copy 18 bytes in a similar
+             * manner; but we ensure that there's enough space in the output for
+             * those 18 bytes earlier, upon entering the shortcut (in other words,
+             * there is a combined check for both stages).
+             */
+            if ( (endOnInput ? length != RUN_MASK : length <= 8)
+                /* strictly "less than" on input, to re-enter the loop with at least one byte */
+              && likely((endOnInput ? ip < shortiend : 1) & (op <= shortoend)) ) {
+                /* Copy the literals */
+                memcpy(op, ip, endOnInput ? 16 : 8);
+                op += length; ip += length;
+
+                /* The second stage: prepare for match copying, decode full info.
+                 * If it doesn't work out, the info won't be wasted. */
+                length = token & ML_MASK; /* match length */
+                offset = LZ4_readLE16(ip); ip += 2;
+                match = op - offset;
+                assert(match <= op); /* check overflow */
+
+                /* Do not deal with overlapping matches. */
+                if ( (length != ML_MASK)
+                  && (offset >= 8)
+                  && (dict==withPrefix64k || match >= lowPrefix) ) {
+                    /* Copy the match. */
+                    memcpy(op + 0, match + 0, 8);
+                    memcpy(op + 8, match + 8, 8);
+                    memcpy(op +16, match +16, 2);
+                    op += length + MINMATCH;
+                    /* Both stages worked, load the next token. */
+                    continue;
+                }
+
+                /* The second stage didn't work out, but the info is ready.
+                 * Propel it right to the point of match copying. */
+                goto _copy_match;
+            }
+
+            /* decode literal length */
+            if (length == RUN_MASK) {
+                variable_length_error error = ok;
+                length += read_variable_length(&ip, iend-RUN_MASK, endOnInput, endOnInput, &error);
+                if (error == initial_error) { goto _output_error; }
+                if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */
+                if ((safeDecode) && unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */
+            }
+
+            /* copy literals */
+            cpy = op+length;
+#if LZ4_FAST_DEC_LOOP
+        safe_literal_copy:
+#endif
+            LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH);
+            if ( ((endOnInput) && ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) )
+              || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) )
+            {
+                /* We've either hit the input parsing restriction or the output parsing restriction.
+                 * If we've hit the input parsing condition then this must be the last sequence.
+                 * If we've hit the output parsing condition then we are either using partialDecoding
+                 * or we've hit the output parsing condition.
+                 */
+                if (partialDecoding) {
+                    /* Since we are partial decoding we may be in this block because of the output parsing
+                     * restriction, which is not valid since the output buffer is allowed to be undersized.
+                     */
+                    assert(endOnInput);
+                    /* If we're in this block because of the input parsing condition, then we must be on the
+                     * last sequence (or invalid), so we must check that we exactly consume the input.
+                     */
+                    if ((ip+length>iend-(2+1+LASTLITERALS)) && (ip+length != iend)) { goto _output_error; }
+                    assert(ip+length <= iend);
+                    /* We are finishing in the middle of a literals segment.
+                     * Break after the copy.
+                     */
+                    if (cpy > oend) {
+                        cpy = oend;
+                        assert(op<=oend);
+                        length = (size_t)(oend-op);
+                    }
+                    assert(ip+length <= iend);
+                } else {
+                    /* We must be on the last sequence because of the parsing limitations so check
+                     * that we exactly regenerate the original size (must be exact when !endOnInput).
+                     */
+                    if ((!endOnInput) && (cpy != oend)) { goto _output_error; }
+                     /* We must be on the last sequence (or invalid) because of the parsing limitations
+                      * so check that we exactly consume the input and don't overrun the output buffer.
+                      */
+                    if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) { goto _output_error; }
+                }
+                memmove(op, ip, length);  /* supports overlapping memory regions, which only matters for in-place decompression scenarios */
+                ip += length;
+                op += length;
+                /* Necessarily EOF when !partialDecoding. When partialDecoding
+                 * it is EOF if we've either filled the output buffer or hit
+                 * the input parsing restriction.
+                 */
+                if (!partialDecoding || (cpy == oend) || (ip == iend)) {
+                    break;
+                }
+            } else {
+                LZ4_wildCopy8(op, ip, cpy);   /* may overwrite up to WILDCOPYLENGTH beyond cpy */
+                ip += length; op = cpy;
+            }
+
+            /* get offset */
+            offset = LZ4_readLE16(ip); ip+=2;
+            match = op - offset;
+
+            /* get matchlength */
+            length = token & ML_MASK;
+
+    _copy_match:
+            if (length == ML_MASK) {
+              variable_length_error error = ok;
+              length += read_variable_length(&ip, iend - LASTLITERALS + 1, endOnInput, 0, &error);
+              if (error != ok) goto _output_error;
+                if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error;   /* overflow detection */
+            }
+            length += MINMATCH;
+
+#if LZ4_FAST_DEC_LOOP
+        safe_match_copy:
+#endif
+            if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error;   /* Error : offset outside buffers */
+            /* match starting within external dictionary */
+            if ((dict==usingExtDict) && (match < lowPrefix)) {
+                if (unlikely(op+length > oend-LASTLITERALS)) {
+                    if (partialDecoding) length = MIN(length, (size_t)(oend-op));
+                    else goto _output_error;   /* doesn't respect parsing restriction */
+                }
+
+                if (length <= (size_t)(lowPrefix-match)) {
+                    /* match fits entirely within external dictionary : just copy */
+                    memmove(op, dictEnd - (lowPrefix-match), length);
+                    op += length;
+                } else {
+                    /* match stretches into both external dictionary and current block */
+                    size_t const copySize = (size_t)(lowPrefix - match);
+                    size_t const restSize = length - copySize;
+                    memcpy(op, dictEnd - copySize, copySize);
+                    op += copySize;
+                    if (restSize > (size_t)(op - lowPrefix)) {  /* overlap copy */
+                        BYTE* const endOfMatch = op + restSize;
+                        const BYTE* copyFrom = lowPrefix;
+                        while (op < endOfMatch) *op++ = *copyFrom++;
+                    } else {
+                        memcpy(op, lowPrefix, restSize);
+                        op += restSize;
+                }   }
+                continue;
+            }
+            assert(match >= lowPrefix);
+
+            /* copy match within block */
+            cpy = op + length;
+
+            /* partialDecoding : may end anywhere within the block */
+            assert(op<=oend);
+            if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) {
+                size_t const mlen = MIN(length, (size_t)(oend-op));
+                const BYTE* const matchEnd = match + mlen;
+                BYTE* const copyEnd = op + mlen;
+                if (matchEnd > op) {   /* overlap copy */
+                    while (op < copyEnd) { *op++ = *match++; }
+                } else {
+                    memcpy(op, match, mlen);
+                }
+                op = copyEnd;
+                if (op == oend) { break; }
+                continue;
+            }
+
+            if (unlikely(offset<8)) {
+                LZ4_write32(op, 0);   /* silence msan warning when offset==0 */
+                op[0] = match[0];
+                op[1] = match[1];
+                op[2] = match[2];
+                op[3] = match[3];
+                match += inc32table[offset];
+                memcpy(op+4, match, 4);
+                match -= dec64table[offset];
+            } else {
+                memcpy(op, match, 8);
+                match += 8;
+            }
+            op += 8;
+
+            if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) {
+                BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1);
+                if (cpy > oend-LASTLITERALS) { goto _output_error; } /* Error : last LASTLITERALS bytes must be literals (uncompressed) */
+                if (op < oCopyLimit) {
+                    LZ4_wildCopy8(op, match, oCopyLimit);
+                    match += oCopyLimit - op;
+                    op = oCopyLimit;
+                }
+                while (op < cpy) { *op++ = *match++; }
+            } else {
+                memcpy(op, match, 8);
+                if (length > 16)  { LZ4_wildCopy8(op+8, match+8, cpy); }
+            }
+            op = cpy;   /* wildcopy correction */
+        }
+
+        /* end of decoding */
+        if (endOnInput) {
+           return (int) (((char*)op)-dst);     /* Nb of output bytes decoded */
+       } else {
+           return (int) (((const char*)ip)-src);   /* Nb of input bytes read */
+       }
+
+        /* Overflow error detected */
+    _output_error:
+        return (int) (-(((const char*)ip)-src))-1;
     }
-
-    /* end of decoding */
-    if (endOnInput)
-       return (int) (((char*)op)-dest);     /* Nb of output bytes decoded */
-    else
-       return (int) (((const char*)ip)-source);   /* Nb of input bytes read */
-
-    /* Overflow error detected */
-_output_error:
-    return (int) (-(((const char*)ip)-source))-1;
 }
 
 
+/*===== Instantiate the API decoding functions. =====*/
+
+LZ4_FORCE_O2_GCC_PPC64LE
 int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize)
 {
-    return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, endOnInputSize, full, 0, noDict, (BYTE*)dest, NULL, 0);
+    return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize,
+                                  endOnInputSize, decode_full_block, noDict,
+                                  (BYTE*)dest, NULL, 0);
 }
 
-int LZ4_decompress_safe_partial(const char* source, char* dest, int compressedSize, int targetOutputSize, int maxDecompressedSize)
+LZ4_FORCE_O2_GCC_PPC64LE
+int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity)
 {
-    return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, endOnInputSize, partial, targetOutputSize, noDict, (BYTE*)dest, NULL, 0);
+    dstCapacity = MIN(targetOutputSize, dstCapacity);
+    return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity,
+                                  endOnInputSize, partial_decode,
+                                  noDict, (BYTE*)dst, NULL, 0);
 }
 
+LZ4_FORCE_O2_GCC_PPC64LE
 int LZ4_decompress_fast(const char* source, char* dest, int originalSize)
 {
-    return LZ4_decompress_generic(source, dest, 0, originalSize, endOnOutputSize, full, 0, withPrefix64k, (BYTE*)(dest - 64 KB), NULL, 64 KB);
+    return LZ4_decompress_generic(source, dest, 0, originalSize,
+                                  endOnOutputSize, decode_full_block, withPrefix64k,
+                                  (BYTE*)dest - 64 KB, NULL, 0);
 }
 
+/*===== Instantiate a few more decoding cases, used more than once. =====*/
+
+LZ4_FORCE_O2_GCC_PPC64LE /* Exported, an obsolete API function. */
+int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize)
+{
+    return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
+                                  endOnInputSize, decode_full_block, withPrefix64k,
+                                  (BYTE*)dest - 64 KB, NULL, 0);
+}
+
+/* Another obsolete API function, paired with the previous one. */
+int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize)
+{
+    /* LZ4_decompress_fast doesn't validate match offsets,
+     * and thus serves well with any prefixed dictionary. */
+    return LZ4_decompress_fast(source, dest, originalSize);
+}
+
+LZ4_FORCE_O2_GCC_PPC64LE
+static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, int compressedSize, int maxOutputSize,
+                                               size_t prefixSize)
+{
+    return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
+                                  endOnInputSize, decode_full_block, noDict,
+                                  (BYTE*)dest-prefixSize, NULL, 0);
+}
+
+LZ4_FORCE_O2_GCC_PPC64LE
+int LZ4_decompress_safe_forceExtDict(const char* source, char* dest,
+                                     int compressedSize, int maxOutputSize,
+                                     const void* dictStart, size_t dictSize)
+{
+    return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
+                                  endOnInputSize, decode_full_block, usingExtDict,
+                                  (BYTE*)dest, (const BYTE*)dictStart, dictSize);
+}
+
+LZ4_FORCE_O2_GCC_PPC64LE
+static int LZ4_decompress_fast_extDict(const char* source, char* dest, int originalSize,
+                                       const void* dictStart, size_t dictSize)
+{
+    return LZ4_decompress_generic(source, dest, 0, originalSize,
+                                  endOnOutputSize, decode_full_block, usingExtDict,
+                                  (BYTE*)dest, (const BYTE*)dictStart, dictSize);
+}
+
+/* The "double dictionary" mode, for use with e.g. ring buffers: the first part
+ * of the dictionary is passed as prefix, and the second via dictStart + dictSize.
+ * These routines are used only once, in LZ4_decompress_*_continue().
+ */
+LZ4_FORCE_INLINE
+int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compressedSize, int maxOutputSize,
+                                   size_t prefixSize, const void* dictStart, size_t dictSize)
+{
+    return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
+                                  endOnInputSize, decode_full_block, usingExtDict,
+                                  (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize);
+}
+
+LZ4_FORCE_INLINE
+int LZ4_decompress_fast_doubleDict(const char* source, char* dest, int originalSize,
+                                   size_t prefixSize, const void* dictStart, size_t dictSize)
+{
+    return LZ4_decompress_generic(source, dest, 0, originalSize,
+                                  endOnOutputSize, decode_full_block, usingExtDict,
+                                  (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize);
+}
 
 /*===== streaming decompression functions =====*/
 
-/*
- * If you prefer dynamic allocation methods,
- * LZ4_createStreamDecode()
- * provides a pointer (void*) towards an initialized LZ4_streamDecode_t structure.
- */
 LZ4_streamDecode_t* LZ4_createStreamDecode(void)
 {
-    LZ4_streamDecode_t* lz4s = (LZ4_streamDecode_t*) ALLOCATOR(1, sizeof(LZ4_streamDecode_t));
+    LZ4_streamDecode_t* lz4s = (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t));
+    LZ4_STATIC_ASSERT(LZ4_STREAMDECODESIZE >= sizeof(LZ4_streamDecode_t_internal));    /* A compilation error here means LZ4_STREAMDECODESIZE is not large enough */
     return lz4s;
 }
 
 int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream)
 {
+    if (LZ4_stream == NULL) { return 0; }  /* support free on NULL */
     FREEMEM(LZ4_stream);
     return 0;
 }
 
-/*!
- * LZ4_setStreamDecode() :
- * Use this function to instruct where to find the dictionary.
- * This function is not necessary if previous data is still available where it was decoded.
- * Loading a size of 0 is allowed (same effect as no dictionary).
- * Return : 1 if OK, 0 if error
+/*! LZ4_setStreamDecode() :
+ *  Use this function to instruct where to find the dictionary.
+ *  This function is not necessary if previous data is still available where it was decoded.
+ *  Loading a size of 0 is allowed (same effect as no dictionary).
+ * @return : 1 if OK, 0 if error
  */
 int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize)
 {
@@ -1310,6 +2205,25 @@
     return 1;
 }
 
+/*! LZ4_decoderRingBufferSize() :
+ *  when setting a ring buffer for streaming decompression (optional scenario),
+ *  provides the minimum size of this ring buffer
+ *  to be compatible with any source respecting maxBlockSize condition.
+ *  Note : in a ring buffer scenario,
+ *  blocks are presumed decompressed next to each other.
+ *  When not enough space remains for next block (remainingSize < maxBlockSize),
+ *  decoding resumes from beginning of ring buffer.
+ * @return : minimum ring buffer size,
+ *           or 0 if there is an error (invalid maxBlockSize).
+ */
+int LZ4_decoderRingBufferSize(int maxBlockSize)
+{
+    if (maxBlockSize < 0) return 0;
+    if (maxBlockSize > LZ4_MAX_INPUT_SIZE) return 0;
+    if (maxBlockSize < 16) maxBlockSize = 16;
+    return LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize);
+}
+
 /*
 *_continue() :
     These decoding functions allow decompression of multiple blocks in "streaming" mode.
@@ -1317,52 +2231,75 @@
     If it's not possible, save the relevant part of decoded data into a safe buffer,
     and indicate where it stands using LZ4_setStreamDecode()
 */
+LZ4_FORCE_O2_GCC_PPC64LE
 int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize)
 {
     LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse;
     int result;
 
-    if (lz4sd->prefixEnd == (BYTE*)dest) {
-        result = LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
-                                        endOnInputSize, full, 0,
-                                        usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize);
+    if (lz4sd->prefixSize == 0) {
+        /* The first call, no dictionary yet. */
+        assert(lz4sd->extDictSize == 0);
+        result = LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize);
         if (result <= 0) return result;
-        lz4sd->prefixSize += result;
+        lz4sd->prefixSize = (size_t)result;
+        lz4sd->prefixEnd = (BYTE*)dest + result;
+    } else if (lz4sd->prefixEnd == (BYTE*)dest) {
+        /* They're rolling the current segment. */
+        if (lz4sd->prefixSize >= 64 KB - 1)
+            result = LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize);
+        else if (lz4sd->extDictSize == 0)
+            result = LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize,
+                                                         lz4sd->prefixSize);
+        else
+            result = LZ4_decompress_safe_doubleDict(source, dest, compressedSize, maxOutputSize,
+                                                    lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize);
+        if (result <= 0) return result;
+        lz4sd->prefixSize += (size_t)result;
         lz4sd->prefixEnd  += result;
     } else {
+        /* The buffer wraps around, or they're switching to another buffer. */
         lz4sd->extDictSize = lz4sd->prefixSize;
         lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize;
-        result = LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize,
-                                        endOnInputSize, full, 0,
-                                        usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize);
+        result = LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize,
+                                                  lz4sd->externalDict, lz4sd->extDictSize);
         if (result <= 0) return result;
-        lz4sd->prefixSize = result;
+        lz4sd->prefixSize = (size_t)result;
         lz4sd->prefixEnd  = (BYTE*)dest + result;
     }
 
     return result;
 }
 
+LZ4_FORCE_O2_GCC_PPC64LE
 int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize)
 {
     LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse;
     int result;
+    assert(originalSize >= 0);
 
-    if (lz4sd->prefixEnd == (BYTE*)dest) {
-        result = LZ4_decompress_generic(source, dest, 0, originalSize,
-                                        endOnOutputSize, full, 0,
-                                        usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize);
+    if (lz4sd->prefixSize == 0) {
+        assert(lz4sd->extDictSize == 0);
+        result = LZ4_decompress_fast(source, dest, originalSize);
         if (result <= 0) return result;
-        lz4sd->prefixSize += originalSize;
+        lz4sd->prefixSize = (size_t)originalSize;
+        lz4sd->prefixEnd = (BYTE*)dest + originalSize;
+    } else if (lz4sd->prefixEnd == (BYTE*)dest) {
+        if (lz4sd->prefixSize >= 64 KB - 1 || lz4sd->extDictSize == 0)
+            result = LZ4_decompress_fast(source, dest, originalSize);
+        else
+            result = LZ4_decompress_fast_doubleDict(source, dest, originalSize,
+                                                    lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize);
+        if (result <= 0) return result;
+        lz4sd->prefixSize += (size_t)originalSize;
         lz4sd->prefixEnd  += originalSize;
     } else {
         lz4sd->extDictSize = lz4sd->prefixSize;
         lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize;
-        result = LZ4_decompress_generic(source, dest, 0, originalSize,
-                                        endOnOutputSize, full, 0,
-                                        usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize);
+        result = LZ4_decompress_fast_extDict(source, dest, originalSize,
+                                             lz4sd->externalDict, lz4sd->extDictSize);
         if (result <= 0) return result;
-        lz4sd->prefixSize = originalSize;
+        lz4sd->prefixSize = (size_t)originalSize;
         lz4sd->prefixEnd  = (BYTE*)dest + originalSize;
     }
 
@@ -1377,32 +2314,27 @@
     the dictionary must be explicitly provided within parameters
 */
 
-FORCE_INLINE int LZ4_decompress_usingDict_generic(const char* source, char* dest, int compressedSize, int maxOutputSize, int safe, const char* dictStart, int dictSize)
-{
-    if (dictSize==0)
-        return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest, NULL, 0);
-    if (dictStart+dictSize == dest) {
-        if (dictSize >= (int)(64 KB - 1))
-            return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, withPrefix64k, (BYTE*)dest-64 KB, NULL, 0);
-        return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest-dictSize, NULL, 0);
-    }
-    return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize);
-}
-
 int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize)
 {
-    return LZ4_decompress_usingDict_generic(source, dest, compressedSize, maxOutputSize, 1, dictStart, dictSize);
+    if (dictSize==0)
+        return LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize);
+    if (dictStart+dictSize == dest) {
+        if (dictSize >= 64 KB - 1) {
+            return LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize);
+        }
+        assert(dictSize >= 0);
+        return LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, (size_t)dictSize);
+    }
+    assert(dictSize >= 0);
+    return LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, dictStart, (size_t)dictSize);
 }
 
 int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize)
 {
-    return LZ4_decompress_usingDict_generic(source, dest, 0, originalSize, 0, dictStart, dictSize);
-}
-
-/* debug function */
-int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize)
-{
-    return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize);
+    if (dictSize==0 || dictStart+dictSize == dest)
+        return LZ4_decompress_fast(source, dest, originalSize);
+    assert(dictSize >= 0);
+    return LZ4_decompress_fast_extDict(source, dest, originalSize, dictStart, (size_t)dictSize);
 }
 
 
@@ -1410,64 +2342,67 @@
 *  Obsolete Functions
 ***************************************************/
 /* obsolete compression functions */
-int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) { return LZ4_compress_default(source, dest, inputSize, maxOutputSize); }
-int LZ4_compress(const char* source, char* dest, int inputSize) { return LZ4_compress_default(source, dest, inputSize, LZ4_compressBound(inputSize)); }
-int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) { return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); }
-int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) { return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); }
-int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, maxDstSize, 1); }
-int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) { return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); }
+int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize)
+{
+    return LZ4_compress_default(source, dest, inputSize, maxOutputSize);
+}
+int LZ4_compress(const char* src, char* dest, int srcSize)
+{
+    return LZ4_compress_default(src, dest, srcSize, LZ4_compressBound(srcSize));
+}
+int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize)
+{
+    return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1);
+}
+int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize)
+{
+    return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1);
+}
+int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int dstCapacity)
+{
+    return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, dstCapacity, 1);
+}
+int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize)
+{
+    return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1);
+}
 
 /*
-These function names are deprecated and should no longer be used.
+These decompression functions are deprecated and should no longer be used.
 They are only provided here for compatibility with older user programs.
 - LZ4_uncompress is totally equivalent to LZ4_decompress_fast
 - LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe
 */
-int LZ4_uncompress (const char* source, char* dest, int outputSize) { return LZ4_decompress_fast(source, dest, outputSize); }
-int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) { return LZ4_decompress_safe(source, dest, isize, maxOutputSize); }
-
+int LZ4_uncompress (const char* source, char* dest, int outputSize)
+{
+    return LZ4_decompress_fast(source, dest, outputSize);
+}
+int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize)
+{
+    return LZ4_decompress_safe(source, dest, isize, maxOutputSize);
+}
 
 /* Obsolete Streaming functions */
 
 int LZ4_sizeofStreamState() { return LZ4_STREAMSIZE; }
 
-static void LZ4_init(LZ4_stream_t* lz4ds, BYTE* base)
-{
-    MEM_INIT(lz4ds, 0, sizeof(LZ4_stream_t));
-    lz4ds->internal_donotuse.bufferStart = base;
-}
-
 int LZ4_resetStreamState(void* state, char* inputBuffer)
 {
-    if ((((uptrval)state) & 3) != 0) return 1;   /* Error : pointer is not aligned on 4-bytes boundary */
-    LZ4_init((LZ4_stream_t*)state, (BYTE*)inputBuffer);
+    (void)inputBuffer;
+    LZ4_resetStream((LZ4_stream_t*)state);
     return 0;
 }
 
 void* LZ4_create (char* inputBuffer)
 {
-    LZ4_stream_t* lz4ds = (LZ4_stream_t*)ALLOCATOR(8, sizeof(LZ4_stream_t));
-    LZ4_init (lz4ds, (BYTE*)inputBuffer);
-    return lz4ds;
+    (void)inputBuffer;
+    return LZ4_createStream();
 }
 
-char* LZ4_slideInputBuffer (void* LZ4_Data)
+char* LZ4_slideInputBuffer (void* state)
 {
-    LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)LZ4_Data)->internal_donotuse;
-    int dictSize = LZ4_saveDict((LZ4_stream_t*)LZ4_Data, (char*)ctx->bufferStart, 64 KB);
-    return (char*)(ctx->bufferStart + dictSize);
-}
-
-/* Obsolete streaming decompression functions */
-
-int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize)
-{
-    return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB);
-}
-
-int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize)
-{
-    return LZ4_decompress_generic(source, dest, 0, originalSize, endOnOutputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB);
+    /* avoid const char * -> char * conversion warning */
+    return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary;
 }
 
 #endif   /* LZ4_COMMONDEFS_ONLY */
diff --git a/src/compat/compat-lz4.h b/src/compat/compat-lz4.h
index 0aae19c..32108e2 100644
--- a/src/compat/compat-lz4.h
+++ b/src/compat/compat-lz4.h
@@ -1,7 +1,7 @@
 /*
  *  LZ4 - Fast LZ compression algorithm
  *  Header File
- *  Copyright (C) 2011-2016, Yann Collet.
+ *  Copyright (C) 2011-present, Yann Collet.
 
    BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
 
@@ -32,13 +32,13 @@
     - LZ4 homepage : http://www.lz4.org
     - LZ4 source repository : https://github.com/lz4/lz4
 */
-#ifndef LZ4_H_2983827168210
-#define LZ4_H_2983827168210
-
 #if defined (__cplusplus)
 extern "C" {
 #endif
 
+#ifndef LZ4_H_2983827168210
+#define LZ4_H_2983827168210
+
 /* --- Dependency --- */
 #include <stddef.h>   /* size_t */
 
@@ -46,24 +46,31 @@
 /**
   Introduction
 
-  LZ4 is lossless compression algorithm, providing compression speed at 400 MB/s per core,
+  LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core,
   scalable with multi-cores CPU. It features an extremely fast decoder, with speed in
   multiple GB/s per core, typically reaching RAM speed limits on multi-core systems.
 
   The LZ4 compression library provides in-memory compression and decompression functions.
+  It gives full buffer control to user.
   Compression can be done in:
     - a single step (described as Simple Functions)
     - a single step, reusing a context (described in Advanced Functions)
     - unbounded multiple steps (described as Streaming compression)
 
-  lz4.h provides block compression functions. It gives full buffer control to user.
-  Decompressing an lz4-compressed block also requires metadata (such as compressed size).
-  Each application is free to encode such metadata in whichever way it wants.
+  lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md).
+  Decompressing such a compressed block requires additional metadata.
+  Exact metadata depends on exact decompression function.
+  For the typical case of LZ4_decompress_safe(),
+  metadata includes block's compressed size, and maximum bound of decompressed size.
+  Each application is free to encode and pass such metadata in whichever way it wants.
 
-  An additional format, called LZ4 frame specification (doc/lz4_Frame_format.md),
-  take care of encoding standard metadata alongside LZ4-compressed blocks.
-  If your application requires interoperability, it's recommended to use it.
-  A library is provided to take care of it, see lz4frame.h.
+  lz4.h only handle blocks, it can not generate Frames.
+
+  Blocks are different from Frames (doc/lz4_Frame_format.md).
+  Frames bundle both blocks and metadata in a specified manner.
+  Embedding metadata is required for compressed data to be self-contained and portable.
+  Frame format is delivered through a companion API, declared in lz4frame.h.
+  The `lz4` CLI can only manage frames.
 */
 
 /*^***************************************************************
@@ -72,20 +79,28 @@
 /*
 *  LZ4_DLL_EXPORT :
 *  Enable exporting of functions when building a Windows DLL
+*  LZ4LIB_VISIBILITY :
+*  Control library symbols visibility.
 */
+#ifndef LZ4LIB_VISIBILITY
+#  if defined(__GNUC__) && (__GNUC__ >= 4)
+#    define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default")))
+#  else
+#    define LZ4LIB_VISIBILITY
+#  endif
+#endif
 #if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1)
-#  define LZ4LIB_API __declspec(dllexport)
+#  define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY
 #elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1)
-#  define LZ4LIB_API __declspec(dllimport) /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
+#  define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
 #else
-#  define LZ4LIB_API
+#  define LZ4LIB_API LZ4LIB_VISIBILITY
 #endif
 
-
-/*========== Version =========== */
+/*------   Version   ------*/
 #define LZ4_VERSION_MAJOR    1    /* for breaking interface changes  */
-#define LZ4_VERSION_MINOR    7    /* for new (non-breaking) interface capabilities */
-#define LZ4_VERSION_RELEASE  5    /* for tweaks, bug-fixes, or development */
+#define LZ4_VERSION_MINOR    9    /* for new (non-breaking) interface capabilities */
+#define LZ4_VERSION_RELEASE  2    /* for tweaks, bug-fixes, or development */
 
 #define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE)
 
@@ -94,8 +109,8 @@
 #define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str)
 #define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION)
 
-LZ4LIB_API int LZ4_versionNumber (void);
-LZ4LIB_API const char* LZ4_versionString (void);
+LZ4LIB_API int LZ4_versionNumber (void);  /**< library version number; useful to check dll version */
+LZ4LIB_API const char* LZ4_versionString (void);   /**< library version string; useful to check dll version */
 
 
 /*-************************************
@@ -104,41 +119,49 @@
 /*!
  * LZ4_MEMORY_USAGE :
  * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.)
- * Increasing memory usage improves compression ratio
- * Reduced memory usage can improve speed, due to cache effect
+ * Increasing memory usage improves compression ratio.
+ * Reduced memory usage may improve speed, thanks to better cache locality.
  * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache
  */
-#define LZ4_MEMORY_USAGE 14
+#ifndef LZ4_MEMORY_USAGE
+# define LZ4_MEMORY_USAGE 14
+#endif
 
 
 /*-************************************
 *  Simple Functions
 **************************************/
 /*! LZ4_compress_default() :
-    Compresses 'sourceSize' bytes from buffer 'source'
-    into already allocated 'dest' buffer of size 'maxDestSize'.
-    Compression is guaranteed to succeed if 'maxDestSize' >= LZ4_compressBound(sourceSize).
-    It also runs faster, so it's a recommended setting.
-    If the function cannot compress 'source' into a more limited 'dest' budget,
-    compression stops *immediately*, and the function result is zero.
-    As a consequence, 'dest' content is not valid.
-    This function never writes outside 'dest' buffer, nor read outside 'source' buffer.
-        sourceSize  : Max supported value is LZ4_MAX_INPUT_VALUE
-        maxDestSize : full or partial size of buffer 'dest' (which must be already allocated)
-        return : the number of bytes written into buffer 'dest' (necessarily <= maxOutputSize)
-              or 0 if compression fails */
-LZ4LIB_API int LZ4_compress_default(const char* source, char* dest, int sourceSize, int maxDestSize);
+ *  Compresses 'srcSize' bytes from buffer 'src'
+ *  into already allocated 'dst' buffer of size 'dstCapacity'.
+ *  Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize).
+ *  It also runs faster, so it's a recommended setting.
+ *  If the function cannot compress 'src' into a more limited 'dst' budget,
+ *  compression stops *immediately*, and the function result is zero.
+ *  In which case, 'dst' content is undefined (invalid).
+ *      srcSize : max supported value is LZ4_MAX_INPUT_SIZE.
+ *      dstCapacity : size of buffer 'dst' (which must be already allocated)
+ *     @return  : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity)
+ *                or 0 if compression fails
+ * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer).
+ */
+LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity);
 
 /*! LZ4_decompress_safe() :
-    compressedSize : is the precise full size of the compressed block.
-    maxDecompressedSize : is the size of destination buffer, which must be already allocated.
-    return : the number of bytes decompressed into destination buffer (necessarily <= maxDecompressedSize)
-             If destination buffer is not large enough, decoding will stop and output an error code (<0).
-             If the source stream is detected malformed, the function will stop decoding and return a negative result.
-             This function is protected against buffer overflow exploits, including malicious data packets.
-             It never writes outside output buffer, nor reads outside input buffer.
-*/
-LZ4LIB_API int LZ4_decompress_safe (const char* source, char* dest, int compressedSize, int maxDecompressedSize);
+ *  compressedSize : is the exact complete size of the compressed block.
+ *  dstCapacity : is the size of destination buffer (which must be already allocated), presumed an upper bound of decompressed size.
+ * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity)
+ *           If destination buffer is not large enough, decoding will stop and output an error code (negative value).
+ *           If the source stream is detected malformed, the function will stop decoding and return a negative result.
+ * Note 1 : This function is protected against malicious data packets :
+ *          it will never writes outside 'dst' buffer, nor read outside 'source' buffer,
+ *          even if the compressed block is maliciously modified to order the decoder to do these actions.
+ *          In such case, the decoder stops immediately, and considers the compressed block malformed.
+ * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them.
+ *          The implementation is free to send / store / derive this information in whichever way is most beneficial.
+ *          If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead.
+ */
+LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity);
 
 
 /*-************************************
@@ -147,184 +170,389 @@
 #define LZ4_MAX_INPUT_SIZE        0x7E000000   /* 2 113 929 216 bytes */
 #define LZ4_COMPRESSBOUND(isize)  ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16)
 
-/*!
-LZ4_compressBound() :
+/*! LZ4_compressBound() :
     Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible)
     This function is primarily useful for memory allocation purposes (destination buffer size).
     Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example).
-    Note that LZ4_compress_default() compress faster when dest buffer size is >= LZ4_compressBound(srcSize)
+    Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize)
         inputSize  : max supported value is LZ4_MAX_INPUT_SIZE
         return : maximum output size in a "worst case" scenario
-              or 0, if input size is too large ( > LZ4_MAX_INPUT_SIZE)
+              or 0, if input size is incorrect (too large or negative)
 */
 LZ4LIB_API int LZ4_compressBound(int inputSize);
 
-/*!
-LZ4_compress_fast() :
-    Same as LZ4_compress_default(), but allows to select an "acceleration" factor.
+/*! LZ4_compress_fast() :
+    Same as LZ4_compress_default(), but allows selection of "acceleration" factor.
     The larger the acceleration value, the faster the algorithm, but also the lesser the compression.
     It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed.
     An acceleration value of "1" is the same as regular LZ4_compress_default()
-    Values <= 0 will be replaced by ACCELERATION_DEFAULT (see lz4.c), which is 1.
+    Values <= 0 will be replaced by ACCELERATION_DEFAULT (currently == 1, see lz4.c).
 */
-LZ4LIB_API int LZ4_compress_fast (const char* source, char* dest, int sourceSize, int maxDestSize, int acceleration);
+LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
 
 
-/*!
-LZ4_compress_fast_extState() :
-    Same compression function, just using an externally allocated memory space to store compression state.
-    Use LZ4_sizeofState() to know how much memory must be allocated,
-    and allocate it on 8-bytes boundaries (using malloc() typically).
-    Then, provide it as 'void* state' to compression function.
-*/
+/*! LZ4_compress_fast_extState() :
+ *  Same as LZ4_compress_fast(), using an externally allocated memory space for its state.
+ *  Use LZ4_sizeofState() to know how much memory must be allocated,
+ *  and allocate it on 8-bytes boundaries (using `malloc()` typically).
+ *  Then, provide this buffer as `void* state` to compression function.
+ */
 LZ4LIB_API int LZ4_sizeofState(void);
-LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* source, char* dest, int inputSize, int maxDestSize, int acceleration);
+LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
 
 
-/*!
-LZ4_compress_destSize() :
-    Reverse the logic, by compressing as much data as possible from 'source' buffer
-    into already allocated buffer 'dest' of size 'targetDestSize'.
-    This function either compresses the entire 'source' content into 'dest' if it's large enough,
-    or fill 'dest' buffer completely with as much data as possible from 'source'.
-        *sourceSizePtr : will be modified to indicate how many bytes where read from 'source' to fill 'dest'.
-                         New value is necessarily <= old value.
-        return : Nb bytes written into 'dest' (necessarily <= targetDestSize)
-              or 0 if compression fails
+/*! LZ4_compress_destSize() :
+ *  Reverse the logic : compresses as much data as possible from 'src' buffer
+ *  into already allocated buffer 'dst', of size >= 'targetDestSize'.
+ *  This function either compresses the entire 'src' content into 'dst' if it's large enough,
+ *  or fill 'dst' buffer completely with as much data as possible from 'src'.
+ *  note: acceleration parameter is fixed to "default".
+ *
+ * *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'.
+ *               New value is necessarily <= input value.
+ * @return : Nb bytes written into 'dst' (necessarily <= targetDestSize)
+ *           or 0 if compression fails.
 */
-LZ4LIB_API int LZ4_compress_destSize (const char* source, char* dest, int* sourceSizePtr, int targetDestSize);
+LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize);
 
 
-/*!
-LZ4_decompress_fast() :
-    originalSize : is the original and therefore uncompressed size
-    return : the number of bytes read from the source buffer (in other words, the compressed size)
-             If the source stream is detected malformed, the function will stop decoding and return a negative result.
-             Destination buffer must be already allocated. Its size must be a minimum of 'originalSize' bytes.
-    note : This function fully respect memory boundaries for properly formed compressed data.
-           It is a bit faster than LZ4_decompress_safe().
-           However, it does not provide any protection against intentionally modified data stream (malicious input).
-           Use this function in trusted environment only (data to decode comes from a trusted source).
-*/
-LZ4LIB_API int LZ4_decompress_fast (const char* source, char* dest, int originalSize);
-
-/*!
-LZ4_decompress_safe_partial() :
-    This function decompress a compressed block of size 'compressedSize' at position 'source'
-    into destination buffer 'dest' of size 'maxDecompressedSize'.
-    The function tries to stop decompressing operation as soon as 'targetOutputSize' has been reached,
-    reducing decompression time.
-    return : the number of bytes decoded in the destination buffer (necessarily <= maxDecompressedSize)
-       Note : this number can be < 'targetOutputSize' should the compressed block to decode be smaller.
-             Always control how many bytes were decoded.
-             If the source stream is detected malformed, the function will stop decoding and return a negative result.
-             This function never writes outside of output buffer, and never reads outside of input buffer. It is therefore protected against malicious data packets
-*/
-LZ4LIB_API int LZ4_decompress_safe_partial (const char* source, char* dest, int compressedSize, int targetOutputSize, int maxDecompressedSize);
+/*! LZ4_decompress_safe_partial() :
+ *  Decompress an LZ4 compressed block, of size 'srcSize' at position 'src',
+ *  into destination buffer 'dst' of size 'dstCapacity'.
+ *  Up to 'targetOutputSize' bytes will be decoded.
+ *  The function stops decoding on reaching this objective,
+ *  which can boost performance when only the beginning of a block is required.
+ *
+ * @return : the number of bytes decoded in `dst` (necessarily <= dstCapacity)
+ *           If source stream is detected malformed, function returns a negative result.
+ *
+ *  Note : @return can be < targetOutputSize, if compressed block contains less data.
+ *
+ *  Note 2 : this function features 2 parameters, targetOutputSize and dstCapacity,
+ *           and expects targetOutputSize <= dstCapacity.
+ *           It effectively stops decoding on reaching targetOutputSize,
+ *           so dstCapacity is kind of redundant.
+ *           This is because in a previous version of this function,
+ *           decoding operation would not "break" a sequence in the middle.
+ *           As a consequence, there was no guarantee that decoding would stop at exactly targetOutputSize,
+ *           it could write more bytes, though only up to dstCapacity.
+ *           Some "margin" used to be required for this operation to work properly.
+ *           This is no longer necessary.
+ *           The function nonetheless keeps its signature, in an effort to not break API.
+ */
+LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity);
 
 
 /*-*********************************************
 *  Streaming Compression Functions
 ***********************************************/
-typedef union LZ4_stream_u LZ4_stream_t;   /* incomplete type (defined later) */
+typedef union LZ4_stream_u LZ4_stream_t;  /* incomplete type (defined later) */
 
-/*! LZ4_createStream() and LZ4_freeStream() :
- *  LZ4_createStream() will allocate and initialize an `LZ4_stream_t` structure.
- *  LZ4_freeStream() releases its memory.
- */
 LZ4LIB_API LZ4_stream_t* LZ4_createStream(void);
 LZ4LIB_API int           LZ4_freeStream (LZ4_stream_t* streamPtr);
 
-/*! LZ4_resetStream() :
- *  An LZ4_stream_t structure can be allocated once and re-used multiple times.
- *  Use this function to init an allocated `LZ4_stream_t` structure and start a new compression.
+/*! LZ4_resetStream_fast() : v1.9.0+
+ *  Use this to prepare an LZ4_stream_t for a new chain of dependent blocks
+ *  (e.g., LZ4_compress_fast_continue()).
+ *
+ *  An LZ4_stream_t must be initialized once before usage.
+ *  This is automatically done when created by LZ4_createStream().
+ *  However, should the LZ4_stream_t be simply declared on stack (for example),
+ *  it's necessary to initialize it first, using LZ4_initStream().
+ *
+ *  After init, start any new stream with LZ4_resetStream_fast().
+ *  A same LZ4_stream_t can be re-used multiple times consecutively
+ *  and compress multiple streams,
+ *  provided that it starts each new stream with LZ4_resetStream_fast().
+ *
+ *  LZ4_resetStream_fast() is much faster than LZ4_initStream(),
+ *  but is not compatible with memory regions containing garbage data.
+ *
+ *  Note: it's only useful to call LZ4_resetStream_fast()
+ *        in the context of streaming compression.
+ *        The *extState* functions perform their own resets.
+ *        Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive.
  */
-LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr);
+LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr);
 
 /*! LZ4_loadDict() :
- *  Use this function to load a static dictionary into LZ4_stream.
- *  Any previous data will be forgotten, only 'dictionary' will remain in memory.
- *  Loading a size of 0 is allowed.
- *  Return : dictionary size, in bytes (necessarily <= 64 KB)
+ *  Use this function to reference a static dictionary into LZ4_stream_t.
+ *  The dictionary must remain available during compression.
+ *  LZ4_loadDict() triggers a reset, so any previous data will be forgotten.
+ *  The same dictionary will have to be loaded on decompression side for successful decoding.
+ *  Dictionary are useful for better compression of small data (KB range).
+ *  While LZ4 accept any input as dictionary,
+ *  results are generally better when using Zstandard's Dictionary Builder.
+ *  Loading a size of 0 is allowed, and is the same as reset.
+ * @return : loaded dictionary size, in bytes (necessarily <= 64 KB)
  */
 LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize);
 
 /*! LZ4_compress_fast_continue() :
- *  Compress buffer content 'src', using data from previously compressed blocks as dictionary to improve compression ratio.
- *  Important : Previous data blocks are assumed to still be present and unmodified !
- *  'dst' buffer must be already allocated.
- *  If maxDstSize >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster.
- *  If not, and if compressed data cannot fit into 'dst' buffer size, compression stops, and function returns a zero.
+ *  Compress 'src' content using data from previously compressed blocks, for better compression ratio.
+ * 'dst' buffer must be already allocated.
+ *  If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster.
+ *
+ * @return : size of compressed block
+ *           or 0 if there is an error (typically, cannot fit into 'dst').
+ *
+ *  Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block.
+ *           Each block has precise boundaries.
+ *           Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata.
+ *           It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together.
+ *
+ *  Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory !
+ *
+ *  Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB.
+ *           Make sure that buffers are separated, by at least one byte.
+ *           This construction ensures that each block only depends on previous block.
+ *
+ *  Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB.
+ *
+ *  Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed.
  */
-LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int maxDstSize, int acceleration);
+LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
 
 /*! LZ4_saveDict() :
- *  If previously compressed data block is not guaranteed to remain available at its memory location,
+ *  If last 64KB data cannot be guaranteed to remain available at its current memory location,
  *  save it into a safer place (char* safeBuffer).
- *  Note : you don't need to call LZ4_loadDict() afterwards,
- *         dictionary is immediately usable, you can therefore call LZ4_compress_fast_continue().
- *  Return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error.
+ *  This is schematically equivalent to a memcpy() followed by LZ4_loadDict(),
+ *  but is much faster, because LZ4_saveDict() doesn't need to rebuild tables.
+ * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error.
  */
-LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int dictSize);
+LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize);
 
 
 /*-**********************************************
 *  Streaming Decompression Functions
 *  Bufferless synchronous API
 ************************************************/
-typedef union LZ4_streamDecode_u LZ4_streamDecode_t;   /* incomplete type (defined later) */
+typedef union LZ4_streamDecode_u LZ4_streamDecode_t;   /* tracking context */
 
-/* creation / destruction of streaming decompression tracking structure */
+/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() :
+ *  creation / destruction of streaming decompression tracking context.
+ *  A tracking context can be re-used multiple times.
+ */
 LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void);
 LZ4LIB_API int                 LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream);
 
 /*! LZ4_setStreamDecode() :
- *  Use this function to instruct where to find the dictionary.
- *  Setting a size of 0 is allowed (same effect as reset).
- *  @return : 1 if OK, 0 if error
+ *  An LZ4_streamDecode_t context can be allocated once and re-used multiple times.
+ *  Use this function to start decompression of a new stream of blocks.
+ *  A dictionary can optionally be set. Use NULL or size 0 for a reset order.
+ *  Dictionary is presumed stable : it must remain accessible and unmodified during next decompression.
+ * @return : 1 if OK, 0 if error
  */
 LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize);
 
-/*!
-LZ4_decompress_*_continue() :
-    These decoding functions allow decompression of multiple blocks in "streaming" mode.
-    Previously decoded blocks *must* remain available at the memory position where they were decoded (up to 64 KB)
-    In the case of a ring buffers, decoding buffer must be either :
-    - Exactly same size as encoding buffer, with same update rule (block boundaries at same positions)
-      In which case, the decoding & encoding ring buffer can have any size, including very small ones ( < 64 KB).
-    - Larger than encoding buffer, by a minimum of maxBlockSize more bytes.
-      maxBlockSize is implementation dependent. It's the maximum size you intend to compress into a single block.
-      In which case, encoding and decoding buffers do not need to be synchronized,
-      and encoding ring buffer can have any size, including small ones ( < 64 KB).
-    - _At least_ 64 KB + 8 bytes + maxBlockSize.
-      In which case, encoding and decoding buffers do not need to be synchronized,
-      and encoding ring buffer can have any size, including larger than decoding buffer.
-    Whenever these conditions are not possible, save the last 64KB of decoded data into a safe buffer,
-    and indicate where it is saved using LZ4_setStreamDecode()
+/*! LZ4_decoderRingBufferSize() : v1.8.2+
+ *  Note : in a ring buffer scenario (optional),
+ *  blocks are presumed decompressed next to each other
+ *  up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize),
+ *  at which stage it resumes from beginning of ring buffer.
+ *  When setting such a ring buffer for streaming decompression,
+ *  provides the minimum size of this ring buffer
+ *  to be compatible with any source respecting maxBlockSize condition.
+ * @return : minimum ring buffer size,
+ *           or 0 if there is an error (invalid maxBlockSize).
+ */
+LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize);
+#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize))  /* for static allocation; maxBlockSize presumed valid */
+
+/*! LZ4_decompress_*_continue() :
+ *  These decoding functions allow decompression of consecutive blocks in "streaming" mode.
+ *  A block is an unsplittable entity, it must be presented entirely to a decompression function.
+ *  Decompression functions only accepts one block at a time.
+ *  The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded.
+ *  If less than 64KB of data has been decoded, all the data must be present.
+ *
+ *  Special : if decompression side sets a ring buffer, it must respect one of the following conditions :
+ *  - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize).
+ *    maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes.
+ *    In which case, encoding and decoding buffers do not need to be synchronized.
+ *    Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize.
+ *  - Synchronized mode :
+ *    Decompression buffer size is _exactly_ the same as compression buffer size,
+ *    and follows exactly same update rule (block boundaries at same positions),
+ *    and decoding function is provided with exact decompressed size of each block (exception for last block of the stream),
+ *    _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB).
+ *  - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes.
+ *    In which case, encoding and decoding buffers do not need to be synchronized,
+ *    and encoding ring buffer can have any size, including small ones ( < 64 KB).
+ *
+ *  Whenever these conditions are not possible,
+ *  save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression,
+ *  then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block.
 */
-LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxDecompressedSize);
-LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize);
+LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity);
 
 
 /*! LZ4_decompress_*_usingDict() :
  *  These decoding functions work the same as
  *  a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue()
  *  They are stand-alone, and don't need an LZ4_streamDecode_t structure.
+ *  Dictionary is presumed stable : it must remain accessible and unmodified during decompression.
+ *  Performance tip : Decompression speed can be substantially increased
+ *                    when dst == dictStart + dictSize.
  */
-LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* source, char* dest, int compressedSize, int maxDecompressedSize, const char* dictStart, int dictSize);
-LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* source, char* dest, int originalSize, const char* dictStart, int dictSize);
+LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize);
+
+#endif /* LZ4_H_2983827168210 */
 
 
-/*^**********************************************
+/*^*************************************
  * !!!!!!   STATIC LINKING ONLY   !!!!!!
- ***********************************************/
-/*-************************************
- *  Private definitions
- **************************************
- * Do not use these definitions.
- * They are exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`.
- * Using these definitions will expose code to API and/or ABI break in future versions of the library.
- **************************************/
+ ***************************************/
+
+/*-****************************************************************************
+ * Experimental section
+ *
+ * Symbols declared in this section must be considered unstable. Their
+ * signatures or semantics may change, or they may be removed altogether in the
+ * future. They are therefore only safe to depend on when the caller is
+ * statically linked against the library.
+ *
+ * To protect against unsafe usage, not only are the declarations guarded,
+ * the definitions are hidden by default
+ * when building LZ4 as a shared/dynamic library.
+ *
+ * In order to access these declarations,
+ * define LZ4_STATIC_LINKING_ONLY in your application
+ * before including LZ4's headers.
+ *
+ * In order to make their implementations accessible dynamically, you must
+ * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library.
+ ******************************************************************************/
+
+#ifdef LZ4_STATIC_LINKING_ONLY
+
+#ifndef LZ4_STATIC_3504398509
+#define LZ4_STATIC_3504398509
+
+#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS
+#define LZ4LIB_STATIC_API LZ4LIB_API
+#else
+#define LZ4LIB_STATIC_API
+#endif
+
+
+/*! LZ4_compress_fast_extState_fastReset() :
+ *  A variant of LZ4_compress_fast_extState().
+ *
+ *  Using this variant avoids an expensive initialization step.
+ *  It is only safe to call if the state buffer is known to be correctly initialized already
+ *  (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized").
+ *  From a high level, the difference is that
+ *  this function initializes the provided state with a call to something like LZ4_resetStream_fast()
+ *  while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream().
+ */
+LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration);
+
+/*! LZ4_attach_dictionary() :
+ *  This is an experimental API that allows
+ *  efficient use of a static dictionary many times.
+ *
+ *  Rather than re-loading the dictionary buffer into a working context before
+ *  each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a
+ *  working LZ4_stream_t, this function introduces a no-copy setup mechanism,
+ *  in which the working stream references the dictionary stream in-place.
+ *
+ *  Several assumptions are made about the state of the dictionary stream.
+ *  Currently, only streams which have been prepared by LZ4_loadDict() should
+ *  be expected to work.
+ *
+ *  Alternatively, the provided dictionaryStream may be NULL,
+ *  in which case any existing dictionary stream is unset.
+ *
+ *  If a dictionary is provided, it replaces any pre-existing stream history.
+ *  The dictionary contents are the only history that can be referenced and
+ *  logically immediately precede the data compressed in the first subsequent
+ *  compression call.
+ *
+ *  The dictionary will only remain attached to the working stream through the
+ *  first compression call, at the end of which it is cleared. The dictionary
+ *  stream (and source buffer) must remain in-place / accessible / unchanged
+ *  through the completion of the first compression call on the stream.
+ */
+LZ4LIB_STATIC_API void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream);
+
+
+/*! In-place compression and decompression
+ *
+ * It's possible to have input and output sharing the same buffer,
+ * for highly contrained memory environments.
+ * In both cases, it requires input to lay at the end of the buffer,
+ * and decompression to start at beginning of the buffer.
+ * Buffer size must feature some margin, hence be larger than final size.
+ *
+ * |<------------------------buffer--------------------------------->|
+ *                             |<-----------compressed data--------->|
+ * |<-----------decompressed size------------------>|
+ *                                                  |<----margin---->|
+ *
+ * This technique is more useful for decompression,
+ * since decompressed size is typically larger,
+ * and margin is short.
+ *
+ * In-place decompression will work inside any buffer
+ * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize).
+ * This presumes that decompressedSize > compressedSize.
+ * Otherwise, it means compression actually expanded data,
+ * and it would be more efficient to store such data with a flag indicating it's not compressed.
+ * This can happen when data is not compressible (already compressed, or encrypted).
+ *
+ * For in-place compression, margin is larger, as it must be able to cope with both
+ * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX,
+ * and data expansion, which can happen when input is not compressible.
+ * As a consequence, buffer size requirements are much higher,
+ * and memory savings offered by in-place compression are more limited.
+ *
+ * There are ways to limit this cost for compression :
+ * - Reduce history size, by modifying LZ4_DISTANCE_MAX.
+ *   Note that it is a compile-time constant, so all compressions will apply this limit.
+ *   Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX,
+ *   so it's a reasonable trick when inputs are known to be small.
+ * - Require the compressor to deliver a "maximum compressed size".
+ *   This is the `dstCapacity` parameter in `LZ4_compress*()`.
+ *   When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail,
+ *   in which case, the return code will be 0 (zero).
+ *   The caller must be ready for these cases to happen,
+ *   and typically design a backup scheme to send data uncompressed.
+ * The combination of both techniques can significantly reduce
+ * the amount of margin required for in-place compression.
+ *
+ * In-place compression can work in any buffer
+ * which size is >= (maxCompressedSize)
+ * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success.
+ * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX,
+ * so it's possible to reduce memory requirements by playing with them.
+ */
+
+#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize)          (((compressedSize) >> 8) + 32)
+#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize)   ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize))  /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */
+
+#ifndef LZ4_DISTANCE_MAX   /* history window size; can be user-defined at compile time */
+#  define LZ4_DISTANCE_MAX 65535   /* set to maximum value by default */
+#endif
+
+#define LZ4_COMPRESS_INPLACE_MARGIN                           (LZ4_DISTANCE_MAX + 32)   /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */
+#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize)   ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN)  /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */
+
+#endif   /* LZ4_STATIC_3504398509 */
+#endif   /* LZ4_STATIC_LINKING_ONLY */
+
+
+
+#ifndef LZ4_H_98237428734687
+#define LZ4_H_98237428734687
+
+/*-************************************************************
+ *  PRIVATE DEFINITIONS
+ **************************************************************
+ * Do not use these definitions directly.
+ * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`.
+ * Accessing members will expose code to API and/or ABI break in future versions of the library.
+ **************************************************************/
 #define LZ4_HASHLOG   (LZ4_MEMORY_USAGE-2)
 #define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE)
 #define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG)       /* required as macro for static allocation */
@@ -332,14 +560,16 @@
 #if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
 #include <stdint.h>
 
-typedef struct {
+typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
+struct LZ4_stream_t_internal {
     uint32_t hashTable[LZ4_HASH_SIZE_U32];
     uint32_t currentOffset;
-    uint32_t initCheck;
+    uint16_t dirty;
+    uint16_t tableType;
     const uint8_t* dictionary;
-    uint8_t* bufferStart;   /* obsolete, used for slideInputBuffer */
+    const LZ4_stream_t_internal* dictCtx;
     uint32_t dictSize;
-} LZ4_stream_t_internal;
+};
 
 typedef struct {
     const uint8_t* externalDict;
@@ -350,49 +580,67 @@
 
 #else
 
-typedef struct {
+typedef struct LZ4_stream_t_internal LZ4_stream_t_internal;
+struct LZ4_stream_t_internal {
     unsigned int hashTable[LZ4_HASH_SIZE_U32];
     unsigned int currentOffset;
-    unsigned int initCheck;
+    unsigned short dirty;
+    unsigned short tableType;
     const unsigned char* dictionary;
-    unsigned char* bufferStart;   /* obsolete, used for slideInputBuffer */
+    const LZ4_stream_t_internal* dictCtx;
     unsigned int dictSize;
-} LZ4_stream_t_internal;
+};
 
 typedef struct {
     const unsigned char* externalDict;
-    size_t extDictSize;
     const unsigned char* prefixEnd;
+    size_t extDictSize;
     size_t prefixSize;
 } LZ4_streamDecode_t_internal;
 
 #endif
 
-/*!
- * LZ4_stream_t :
- * information structure to track an LZ4 stream.
- * init this structure before first use.
- * note : only use in association with static linking !
- *        this definition is not API/ABI safe,
- *        and may change in a future version !
+/*! LZ4_stream_t :
+ *  information structure to track an LZ4 stream.
+ *  LZ4_stream_t can also be created using LZ4_createStream(), which is recommended.
+ *  The structure definition can be convenient for static allocation
+ *  (on stack, or as part of larger structure).
+ *  Init this structure with LZ4_initStream() before first use.
+ *  note : only use this definition in association with static linking !
+ *    this definition is not API/ABI safe, and may change in a future version.
  */
-#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4)
+#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4 + ((sizeof(void*)==16) ? 4 : 0) /*AS-400*/ )
 #define LZ4_STREAMSIZE     (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long))
 union LZ4_stream_u {
     unsigned long long table[LZ4_STREAMSIZE_U64];
     LZ4_stream_t_internal internal_donotuse;
 } ;  /* previously typedef'd to LZ4_stream_t */
 
-
-/*!
- * LZ4_streamDecode_t :
- * information structure to track an LZ4 stream during decompression.
- * init this structure  using LZ4_setStreamDecode (or memset()) before first use
- * note : only use in association with static linking !
- *        this definition is not API/ABI safe,
- *        and may change in a future version !
+/*! LZ4_initStream() : v1.9.0+
+ *  An LZ4_stream_t structure must be initialized at least once.
+ *  This is automatically done when invoking LZ4_createStream(),
+ *  but it's not when the structure is simply declared on stack (for example).
+ *
+ *  Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t.
+ *  It can also initialize any arbitrary buffer of sufficient size,
+ *  and will @return a pointer of proper type upon initialization.
+ *
+ *  Note : initialization fails if size and alignment conditions are not respected.
+ *         In which case, the function will @return NULL.
+ *  Note2: An LZ4_stream_t structure guarantees correct alignment and size.
+ *  Note3: Before v1.9.0, use LZ4_resetStream() instead
  */
-#define LZ4_STREAMDECODESIZE_U64  4
+LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* buffer, size_t size);
+
+
+/*! LZ4_streamDecode_t :
+ *  information structure to track an LZ4 stream during decompression.
+ *  init this structure  using LZ4_setStreamDecode() before first use.
+ *  note : only use in association with static linking !
+ *         this definition is not API/ABI safe,
+ *         and may change in a future version !
+ */
+#define LZ4_STREAMDECODESIZE_U64 (4 + ((sizeof(void*)==16) ? 2 : 0) /*AS-400*/ )
 #define LZ4_STREAMDECODESIZE     (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long))
 union LZ4_streamDecode_u {
     unsigned long long table[LZ4_STREAMDECODESIZE_U64];
@@ -400,15 +648,22 @@
 } ;   /* previously typedef'd to LZ4_streamDecode_t */
 
 
-/*=************************************
+
+/*-************************************
 *  Obsolete Functions
 **************************************/
-/* Deprecation warnings */
-/* Should these warnings be a problem,
-   it is generally possible to disable them,
-   typically with -Wno-deprecated-declarations for gcc
-   or _CRT_SECURE_NO_WARNINGS in Visual.
-   Otherwise, it's also possible to define LZ4_DISABLE_DEPRECATE_WARNINGS */
+
+/*! Deprecation warnings
+ *
+ *  Deprecated functions make the compiler generate a warning when invoked.
+ *  This is meant to invite users to update their source code.
+ *  Should deprecation warnings be a problem, it is generally possible to disable them,
+ *  typically with -Wno-deprecated-declarations for gcc
+ *  or _CRT_SECURE_NO_WARNINGS in Visual.
+ *
+ *  Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS
+ *  before including the header file.
+ */
 #ifdef LZ4_DISABLE_DEPRECATE_WARNINGS
 #  define LZ4_DEPRECATED(message)   /* disable deprecation warnings */
 #else
@@ -428,36 +683,82 @@
 #endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */
 
 /* Obsolete compression functions */
-LZ4_DEPRECATED("use LZ4_compress_default() instead") int LZ4_compress               (const char* source, char* dest, int sourceSize);
-LZ4_DEPRECATED("use LZ4_compress_default() instead") int LZ4_compress_limitedOutput (const char* source, char* dest, int sourceSize, int maxOutputSize);
-LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") int LZ4_compress_withState               (void* state, const char* source, char* dest, int inputSize);
-LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize);
-LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") int LZ4_compress_continue                (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize);
-LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") int LZ4_compress_limitedOutput_continue  (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize);
+LZ4_DEPRECATED("use LZ4_compress_default() instead")       LZ4LIB_API int LZ4_compress               (const char* src, char* dest, int srcSize);
+LZ4_DEPRECATED("use LZ4_compress_default() instead")       LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize);
+LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState               (void* state, const char* source, char* dest, int inputSize);
+LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize);
+LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue                (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize);
+LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue  (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize);
 
 /* Obsolete decompression functions */
-/* These function names are completely deprecated and must no longer be used.
-   They are only provided in lz4.c for compatibility with older programs.
-    - LZ4_uncompress is the same as LZ4_decompress_fast
-    - LZ4_uncompress_unknownOutputSize is the same as LZ4_decompress_safe
-   These function prototypes are now disabled; uncomment them only if you really need them.
-   It is highly recommended to stop using these prototypes and migrate to maintained ones */
-/* int LZ4_uncompress (const char* source, char* dest, int outputSize); */
-/* int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); */
+LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize);
+LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize);
 
-/* Obsolete streaming functions; use new streaming interface whenever possible */
-LZ4_DEPRECATED("use LZ4_createStream() instead") void* LZ4_create (char* inputBuffer);
-LZ4_DEPRECATED("use LZ4_createStream() instead") int   LZ4_sizeofStreamState(void);
-LZ4_DEPRECATED("use LZ4_resetStream() instead")  int   LZ4_resetStreamState(void* state, char* inputBuffer);
-LZ4_DEPRECATED("use LZ4_saveDict() instead")     char* LZ4_slideInputBuffer (void* state);
+/* Obsolete streaming functions; degraded functionality; do not use!
+ *
+ * In order to perform streaming compression, these functions depended on data
+ * that is no longer tracked in the state. They have been preserved as well as
+ * possible: using them will still produce a correct output. However, they don't
+ * actually retain any history between compression calls. The compression ratio
+ * achieved will therefore be no better than compressing each chunk
+ * independently.
+ */
+LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer);
+LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int   LZ4_sizeofStreamState(void);
+LZ4_DEPRECATED("Use LZ4_resetStream() instead")  LZ4LIB_API int   LZ4_resetStreamState(void* state, char* inputBuffer);
+LZ4_DEPRECATED("Use LZ4_saveDict() instead")     LZ4LIB_API char* LZ4_slideInputBuffer (void* state);
 
 /* Obsolete streaming decoding functions */
-LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize);
-LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize);
+LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize);
+LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize);
+
+/*! LZ4_decompress_fast() : **unsafe!**
+ *  These functions used to be faster than LZ4_decompress_safe(),
+ *  but it has changed, and they are now slower than LZ4_decompress_safe().
+ *  This is because LZ4_decompress_fast() doesn't know the input size,
+ *  and therefore must progress more cautiously in the input buffer to not read beyond the end of block.
+ *  On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability.
+ *  As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated.
+ *
+ *  The last remaining LZ4_decompress_fast() specificity is that
+ *  it can decompress a block without knowing its compressed size.
+ *  Such functionality could be achieved in a more secure manner,
+ *  by also providing the maximum size of input buffer,
+ *  but it would require new prototypes, and adaptation of the implementation to this new use case.
+ *
+ *  Parameters:
+ *  originalSize : is the uncompressed size to regenerate.
+ *                 `dst` must be already allocated, its size must be >= 'originalSize' bytes.
+ * @return : number of bytes read from source buffer (== compressed size).
+ *           The function expects to finish at block's end exactly.
+ *           If the source stream is detected malformed, the function stops decoding and returns a negative result.
+ *  note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer.
+ *         However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds.
+ *         Also, since match offsets are not validated, match reads from 'src' may underflow too.
+ *         These issues never happen if input (compressed) data is correct.
+ *         But they may happen if input data is invalid (error or intentional tampering).
+ *         As a consequence, use these functions in trusted environments with trusted data **only**.
+ */
+
+LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead")
+LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize);
+LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead")
+LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize);
+LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_usingDict() instead")
+LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize);
+
+/*! LZ4_resetStream() :
+ *  An LZ4_stream_t structure must be initialized at least once.
+ *  This is done with LZ4_initStream(), or LZ4_resetStream().
+ *  Consider switching to LZ4_initStream(),
+ *  invoking LZ4_resetStream() will trigger deprecation warnings in the future.
+ */
+LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr);
+
+
+#endif /* LZ4_H_98237428734687 */
 
 
 #if defined (__cplusplus)
 }
 #endif
-
-#endif /* LZ4_H_2983827168210 */
diff --git a/src/compat/compat-strsep.c b/src/compat/compat-strsep.c
new file mode 100644
index 0000000..e6518db
--- /dev/null
+++ b/src/compat/compat-strsep.c
@@ -0,0 +1,61 @@
+/*
+ *  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) 2019 Arne Schwabe <arne@rfc2549.org>
+ *  Copyright (C) 1992-2019 Free Software Foundation, 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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#ifndef HAVE_STRSEP
+#include <string.h>
+
+/*
+ * Modified version based on the glibc
+ */
+char *
+strsep(char **stringp, const char *delim)
+{
+    char *begin, *end;
+    begin = *stringp;
+    if (begin == NULL)
+    {
+        return NULL;
+    }
+    /* Find the end of the token.  */
+    end = begin + strcspn(begin, delim);
+    if (*end)
+    {
+        /* Terminate the token and set *STRINGP past NUL character.  */
+        *end++ = '\0';
+        *stringp = end;
+    }
+    else
+    {
+        /* No more delimiters; this is the last token.  */
+        *stringp = NULL;
+    }
+    return begin;
+}
+#endif /* ifndef HAVE_STRSEP */
diff --git a/src/compat/compat-versionhelpers.h b/src/compat/compat-versionhelpers.h
index 251fb04..9e25470 100644
--- a/src/compat/compat-versionhelpers.h
+++ b/src/compat/compat-versionhelpers.h
@@ -18,6 +18,10 @@
 
 #define _WIN32_WINNT_WINBLUE    0x0603
 
+#ifndef _WIN32_WINNT_WINTHRESHOLD
+#define _WIN32_WINNT_WINTHRESHOLD    0x0A00 // Windows 10
+#endif
+
 VERSIONHELPERAPI
 IsWindowsVersionOrGreater(WORD major, WORD minor, WORD servpack)
 {
@@ -96,6 +100,12 @@
 }
 
 VERSIONHELPERAPI
+IsWindows10OrGreater()
+{
+    return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINTHRESHOLD), LOBYTE(_WIN32_WINNT_WINTHRESHOLD), 0);
+}
+
+VERSIONHELPERAPI
 IsWindowsServer(void)
 {
     OSVERSIONINFOEXW vi = {sizeof(vi),0,0,0,0,{0},0,0,0,VER_NT_WORKSTATION};
diff --git a/src/compat/compat.h b/src/compat/compat.h
index d522898..a66a423 100644
--- a/src/compat/compat.h
+++ b/src/compat/compat.h
@@ -70,4 +70,9 @@
 
 #endif
 
+#ifndef HAVE_STRSEP
+char *strsep(char **stringp, const char *delim);
+
+#endif
+
 #endif /* COMPAT_H */
diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index 1f91e59..37b002c 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -9,14 +9,15 @@
 #  Copyright (C) 2006-2012 Alon Bar-Lev <alon.barlev@gmail.com>
 #
 
-include $(top_srcdir)/ltrc.inc
+include $(top_srcdir)/build/ltrc.inc
 
 MAINTAINERCLEANFILES = \
 	$(srcdir)/Makefile.in
 
 EXTRA_DIST = \
 	openvpn.vcxproj \
-	openvpn.vcxproj.filters
+	openvpn.vcxproj.filters \
+	openvpn.manifest
 
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/include \
@@ -40,6 +41,7 @@
 
 openvpn_SOURCES = \
 	argv.c argv.h \
+	auth_token.c auth_token.h \
 	base64.c base64.h \
 	basic.h \
 	buffer.c buffer.h \
@@ -52,11 +54,12 @@
 	crypto_openssl.c crypto_openssl.h \
 	crypto_mbedtls.c crypto_mbedtls.h \
 	dhcp.c dhcp.h \
+	env_set.c env_set.h \
 	errlevel.h \
 	error.c error.h \
 	event.c event.h \
 	fdmisc.c fdmisc.h \
-	forward.c forward.h forward-inline.h \
+	forward.c forward.h \
 	fragment.c fragment.h \
 	gremlin.c gremlin.h \
 	helper.c helper.h \
@@ -80,8 +83,11 @@
 	mtu.c mtu.h \
 	mudp.c mudp.h \
 	multi.c multi.h \
+	networking_iproute2.c networking_iproute2.h \
+	networking_sitnl.c networking_sitnl.h \
+	networking.h \
 	ntlm.c ntlm.h \
-	occ.c occ.h occ-inline.h \
+	occ.c occ.h \
 	openssl_compat.h \
 	pkcs11.c pkcs11.h pkcs11_backend.h \
 	pkcs11_openssl.c \
@@ -91,8 +97,8 @@
 	otime.c otime.h \
 	packet_id.c packet_id.h \
 	perf.c perf.h \
-	pf.c pf.h pf-inline.h \
-	ping.c ping.h ping-inline.h \
+	pf.c pf.h \
+	ping.c ping.h \
 	plugin.c plugin.h \
 	pool.c pool.h \
 	proto.c proto.h \
@@ -102,6 +108,7 @@
 	pushlist.h \
 	reliable.c reliable.h \
 	route.c route.h \
+	run_command.c run_command.h \
 	schedule.c schedule.h \
 	session_id.c session_id.h \
 	shaper.c shaper.h \
@@ -111,6 +118,7 @@
 	ssl.c ssl.h  ssl_backend.h \
 	ssl_openssl.c ssl_openssl.h \
 	ssl_mbedtls.c ssl_mbedtls.h \
+	ssl_ncp.c ssl_ncp.h \
 	ssl_common.h \
 	ssl_verify.c ssl_verify.h ssl_verify_backend.h \
 	ssl_verify_openssl.c ssl_verify_openssl.h \
@@ -119,6 +127,7 @@
 	syshead.h \
 	tls_crypt.c tls_crypt.h \
 	tun.c tun.h \
+	vlan.c vlan.h \
 	win32.h win32.c \
 	cryptoapi.h cryptoapi.c
 openvpn_LDADD = \
@@ -133,6 +142,6 @@
 	$(OPTIONAL_DL_LIBS) \
 	$(OPTIONAL_INOTIFY_LIBS)
 if WIN32
-openvpn_SOURCES += openvpn_win32_resources.rc block_dns.c block_dns.h
-openvpn_LDADD += -lgdi32 -lws2_32 -lwininet -lcrypt32 -liphlpapi -lwinmm -lfwpuclnt -lrpcrt4 -lncrypt
+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
 endif
diff --git a/src/openvpn/argv.c b/src/openvpn/argv.c
index 7d06951..b799c97 100644
--- a/src/openvpn/argv.c
+++ b/src/openvpn/argv.c
@@ -37,16 +37,55 @@
 
 #include "argv.h"
 #include "integer.h"
+#include "env_set.h"
 #include "options.h"
 
+/**
+ *  Resizes the list of arguments struct argv can carry.  This resize
+ *  operation will only increase the size, never decrease the size.
+ *
+ *  @param *a      Valid pointer to a struct argv to resize
+ *  @param newcap  size_t with the new size of the argument list.
+ */
+static void
+argv_extend(struct argv *a, const size_t newcap)
+{
+    if (newcap > a->capacity)
+    {
+        char **newargv;
+        size_t i;
+        ALLOC_ARRAY_CLEAR_GC(newargv, char *, newcap, &a->gc);
+        for (i = 0; i < a->argc; ++i)
+        {
+            newargv[i] = a->argv[i];
+        }
+        a->argv = newargv;
+        a->capacity = newcap;
+    }
+}
+
+/**
+ *  Initialise an already allocated struct argv.
+ *  It is expected that the input argument is a valid pointer.
+ *
+ *  @param *a  Pointer to a struct argv to initialise
+ */
 static void
 argv_init(struct argv *a)
 {
     a->capacity = 0;
     a->argc = 0;
     a->argv = NULL;
+    a->gc = gc_new();
+    argv_extend(a, 8);
 }
 
+/**
+ *  Allocates a new struct argv and ensures it is initialised.
+ *  Note that it does not return a pointer, but a struct argv directly.
+ *
+ *  @returns Returns an initialised and empty struct argv.
+ */
 struct argv
 argv_new(void)
 {
@@ -55,36 +94,51 @@
     return ret;
 }
 
+/**
+ *  Frees all memory allocations allocated by the struct argv
+ *  related functions.
+ *
+ *  @param *a  Valid pointer to a struct argv to release memory from
+ */
 void
+argv_free(struct argv *a)
+{
+    gc_free(&a->gc);
+}
+
+/**
+ *  Resets the struct argv to an initial state.  No memory buffers
+ *  will be released by this call.
+ *
+ *  @param *a      Valid pointer to a struct argv to resize
+ */
+static void
 argv_reset(struct argv *a)
 {
-    size_t i;
-    for (i = 0; i < a->argc; ++i)
+    if (a->argc)
     {
-        free(a->argv[i]);
-    }
-    free(a->argv);
-    argv_init(a);
-}
-
-static void
-argv_extend(struct argv *a, const size_t newcap)
-{
-    if (newcap > a->capacity)
-    {
-        char **newargv;
         size_t i;
-        ALLOC_ARRAY_CLEAR(newargv, char *, newcap);
         for (i = 0; i < a->argc; ++i)
         {
-            newargv[i] = a->argv[i];
+            a->argv[i] = NULL;
         }
-        free(a->argv);
-        a->argv = newargv;
-        a->capacity = newcap;
+        a->argc = 0;
     }
 }
 
+/**
+ *  Extends an existing struct argv to carry minimum 'add' number
+ *  of new arguments.  This builds on argv_extend(), which ensures the
+ *  new size will only be higher than the current capacity.
+ *
+ *  The new size is also calculated based on the result of adjust_power_of_2().
+ *  This approach ensures that the list does grow bulks and only when the
+ *  current limit is reached.
+ *
+ *  @param *a   Valid pointer to the struct argv to extend
+ *  @param add  size_t with the number of elements to add.
+ *
+ */
 static void
 argv_grow(struct argv *a, const size_t add)
 {
@@ -93,114 +147,100 @@
     argv_extend(a, adjust_power_of_2(newargc));
 }
 
+/**
+ *  Appends a string to to the list of arguments stored in a struct argv
+ *  This will ensure the list size in struct argv has the needed capacity to
+ *  store the value.
+ *
+ *  @param *a    struct argv where to append the new string value
+ *  @param *str  Pointer to string to append.  The provided string *MUST* have
+ *               been malloc()ed or NULL.
+ */
 static void
-argv_append(struct argv *a, char *str)  /* str must have been malloced or be NULL */
+argv_append(struct argv *a, char *str)
 {
     argv_grow(a, 1);
     a->argv[a->argc++] = str;
 }
 
+/**
+ *  Clones a struct argv with all the contents to a new allocated struct argv.
+ *  If 'headroom' is larger than 0, it will create a head-room in front of the
+ *  values being copied from the source input.
+ *
+ *
+ *  @param *source   Valid pointer to the source struct argv to clone.  It may
+ *                   be NULL.
+ *  @param headroom  Number of slots to leave empty in front of the slots
+ *                   copied from the source.
+ *
+ *  @returns Returns a new struct argv containing a copy of the source
+ *           struct argv, with the given headroom in front of the copy.
+ *
+ */
 static struct argv
-argv_clone(const struct argv *a, const size_t headroom)
+argv_clone(const struct argv *source, const size_t headroom)
 {
     struct argv r;
-    size_t i;
-
     argv_init(&r);
-    for (i = 0; i < headroom; ++i)
+
+    for (size_t i = 0; i < headroom; ++i)
     {
         argv_append(&r, NULL);
     }
-    if (a)
+    if (source)
     {
-        for (i = 0; i < a->argc; ++i)
+        for (size_t i = 0; i < source->argc; ++i)
         {
-            argv_append(&r, string_alloc(a->argv[i], NULL));
+            argv_append(&r, string_alloc(source->argv[i], &r.gc));
         }
     }
     return r;
 }
 
+/**
+ *  Inserts an argument string in front of all other argument slots.
+ *
+ *  @param  *a     Valid pointer to the struct argv to insert the argument into
+ *  @param  *head  Pointer to the char * string with the argument to insert
+ *
+ *  @returns Returns a new struct argv with the inserted argument in front
+ */
 struct argv
 argv_insert_head(const struct argv *a, const char *head)
 {
     struct argv r;
     r = argv_clone(a, 1);
-    r.argv[0] = string_alloc(head, NULL);
+    r.argv[0] = string_alloc(head, &r.gc);
     return r;
 }
 
-static char *
-argv_term(const char **f)
-{
-    const char *p = *f;
-    const char *term = NULL;
-    size_t termlen = 0;
-
-    if (*p == '\0')
-    {
-        return NULL;
-    }
-
-    while (true)
-    {
-        const int c = *p;
-        if (c == '\0')
-        {
-            break;
-        }
-        if (term)
-        {
-            if (!isspace(c))
-            {
-                ++termlen;
-            }
-            else
-            {
-                break;
-            }
-        }
-        else
-        {
-            if (!isspace(c))
-            {
-                term = p;
-                termlen = 1;
-            }
-        }
-        ++p;
-    }
-    *f = p;
-
-    if (term)
-    {
-        char *ret;
-        ASSERT(termlen > 0);
-        ret = malloc(termlen + 1);
-        check_malloc_return(ret);
-        memcpy(ret, term, termlen);
-        ret[termlen] = '\0';
-        return ret;
-    }
-    else
-    {
-        return NULL;
-    }
-}
-
+/**
+ *  Generate a single string with all the arguments in a struct argv
+ *  concatenated.
+ *
+ *  @param *a    Valid pointer to the struct argv with the arguments to list
+ *  @param *gc   Pointer to a struct gc_arena managed buffer
+ *  @param flags Flags passed to the print_argv() function.
+ *
+ *  @returns Returns a string generated by print_argv() with all the arguments
+ *           concatenated.  If the argument count is 0, it will return an empty
+ *           string.  The return string is allocated in the gc_arena managed
+ *           buffer.  If the gc_arena pointer is NULL, the returned string
+ *           must be free()d explicitly to avoid memory leaks.
+ */
 const char *
 argv_str(const struct argv *a, struct gc_arena *gc, const unsigned int flags)
 {
-    if (a->argv)
-    {
-        return print_argv((const char **)a->argv, gc, flags);
-    }
-    else
-    {
-        return "";
-    }
+    return print_argv((const char **)a->argv, gc, flags);
 }
 
+/**
+ *  Write the arguments stored in a struct argv via the msg() command.
+ *
+ *  @param msglev  Integer with the message level used by msg().
+ *  @param *a      Valid pointer to the struct argv with the arguments to write.
+ */
 void
 argv_msg(const int msglev, const struct argv *a)
 {
@@ -209,6 +249,15 @@
     gc_free(&gc);
 }
 
+/**
+ *  Similar to argv_msg() but prefixes the messages being written with a
+ *  given string.
+ *
+ *  @param msglev   Integer with the message level used by msg().
+ *  @param *a       Valid pointer to the struct argv with the arguments to write
+ *  @param *prefix  Valid char * pointer to the prefix string
+ *
+ */
 void
 argv_msg_prefix(const int msglev, const struct argv *a, const char *prefix)
 {
@@ -217,144 +266,239 @@
     gc_free(&gc);
 }
 
-static void
-argv_printf_arglist(struct argv *a, const char *format, va_list arglist)
+/**
+ *  Prepares argv format string for further processing
+ *
+ *  Individual argument must be separated by space. Ignores leading and
+ *  trailing spaces.  Consecutive spaces count as one. Returns prepared
+ *  format string, with space replaced by delim and adds the number of
+ *  arguments to the count parameter.
+ *
+ *  @param *format  Pointer to a the format string to process
+ *  @param delim    Char with the delimiter to use
+ *  @param *count   size_t pointer used to return the number of
+ *                  tokens (argument slots) found in the format string.
+ *  @param *gc      Pointer to a gc_arena managed buffer.
+ *
+ *  @returns Returns a parsed format string (char *), together with the
+ *           number of tokens parts found (via *count).  The result string
+ *           is allocated within the gc_arena managed buffer.  If the
+ *           gc_arena pointer is NULL, the returned string must be explicitly
+ *           free()d to avoid memory leaks.
+ */
+static char *
+argv_prep_format(const char *format, const char delim, size_t *count,
+                 struct gc_arena *gc)
 {
-    char *term;
-    const char *f = format;
-
-    argv_extend(a, 1); /* ensure trailing NULL */
-
-    while ((term = argv_term(&f)) != NULL)
+    if (format == NULL)
     {
-        if (term[0] == '%')
-        {
-            if (!strcmp(term, "%s"))
-            {
-                char *s = va_arg(arglist, char *);
-                if (!s)
-                {
-                    s = "";
-                }
-                argv_append(a, string_alloc(s, NULL));
-            }
-            else if (!strcmp(term, "%d"))
-            {
-                char numstr[64];
-                openvpn_snprintf(numstr, sizeof(numstr), "%d", va_arg(arglist, int));
-                argv_append(a, string_alloc(numstr, NULL));
-            }
-            else if (!strcmp(term, "%u"))
-            {
-                char numstr[64];
-                openvpn_snprintf(numstr, sizeof(numstr), "%u", va_arg(arglist, unsigned int));
-                argv_append(a, string_alloc(numstr, NULL));
-            }
-            else if (!strcmp(term, "%lu"))
-            {
-                char numstr[64];
-                openvpn_snprintf(numstr, sizeof(numstr), "%lu",
-                                 va_arg(arglist, unsigned long));
-                argv_append(a, string_alloc(numstr, NULL));
-            }
-            else if (!strcmp(term, "%s/%d"))
-            {
-                char numstr[64];
-                char *s = va_arg(arglist, char *);
-
-                if (!s)
-                {
-                    s = "";
-                }
-
-                openvpn_snprintf(numstr, sizeof(numstr), "%d", va_arg(arglist, int));
-
-                {
-                    const size_t len = strlen(s) + strlen(numstr) + 2;
-                    char *combined = (char *) malloc(len);
-                    check_malloc_return(combined);
-
-                    strcpy(combined, s);
-                    strcat(combined, "/");
-                    strcat(combined, numstr);
-                    argv_append(a, combined);
-                }
-            }
-            else if (!strcmp(term, "%s%sc"))
-            {
-                char *s1 = va_arg(arglist, char *);
-                char *s2 = va_arg(arglist, char *);
-                char *combined;
-
-                if (!s1)
-                {
-                    s1 = "";
-                }
-                if (!s2)
-                {
-                    s2 = "";
-                }
-                combined = (char *) malloc(strlen(s1) + strlen(s2) + 1);
-                check_malloc_return(combined);
-                strcpy(combined, s1);
-                strcat(combined, s2);
-                argv_append(a, combined);
-            }
-            else
-            {
-                ASSERT(0);
-            }
-            free(term);
-        }
-        else
-        {
-            argv_append(a, term);
-        }
+        return NULL;
     }
+
+    bool in_token = false;
+    char *f = gc_malloc(strlen(format) + 1, true, gc);
+    for (int i = 0, j = 0; i < strlen(format); i++)
+    {
+        if (format[i] == ' ')
+        {
+            in_token = false;
+            continue;
+        }
+
+        if (!in_token)
+        {
+            (*count)++;
+
+            /*
+             * We don't add any delimiter to the output string if
+             * the string is empty; the resulting format string
+             * will never start with a delimiter.
+             */
+            if (j > 0)  /* Has anything been written to the output string? */
+            {
+                f[j++] = delim;
+            }
+        }
+
+        f[j++] = format[i];
+        in_token = true;
+    }
+
+    return f;
 }
 
-void
-argv_printf(struct argv *a, const char *format, ...)
+/**
+ *  Create a struct argv based on a format string
+ *
+ *  Instead of parsing the format string ourselves place delimiters via
+ *  argv_prep_format() before we let libc's printf() do the parsing.
+ *  Then split the resulting string at the injected delimiters.
+ *
+ *  @param *argres  Valid pointer to a struct argv where the resulting parsed
+ *                  arguments, based on the format string.
+ *  @param *format  Char* string with a printf() compliant format string
+ *  @param arglist  A va_list with the arguments to be consumed by the format
+ *                  string
+ *
+ *  @returns Returns true if the parsing and processing was successfully.  If
+ *           the resulting number of arguments does not match the expected
+ *           number of arguments (based on the format string), it is
+ *           considered a failure, which returns false.  This can happen if
+ *           the ASCII Group Separator (GS - 0x1D) is put into the arguments
+ *           list or format string.
+ */
+static bool
+argv_printf_arglist(struct argv *argres, const char *format, va_list arglist)
 {
-    va_list arglist;
-    argv_reset(a);
-    va_start(arglist, format);
-    argv_printf_arglist(a, format, arglist);
-    va_end(arglist);
+    const char delim = 0x1D;  /* ASCII Group Separator (GS) */
+    bool res = false;
+
+    /*
+     * Prepare a format string which will be used by vsnprintf() later on.
+     *
+     * This means all space separators in the input format string will be
+     * replaced by the GS (0x1D), so we can split this up again after the
+     * the vsnprintf() call into individual arguments again which will be
+     * saved in the struct argv.
+     *
+     */
+    size_t argc = argres->argc;
+    char *f = argv_prep_format(format, delim, &argc, &argres->gc);
+    if (f == NULL)
+    {
+        goto out;
+    }
+
+    /*
+     * Determine minimum buffer size.
+     *
+     * With C99, vsnprintf(NULL, 0, ...) will return the number of bytes
+     * it would have written, had the buffer been large enough.
+     */
+    va_list tmplist;
+    va_copy(tmplist, arglist);
+    int len = vsnprintf(NULL, 0, f, tmplist);
+    va_end(tmplist);
+    if (len < 0)
+    {
+        goto out;
+    }
+
+    /*
+     *  Do the actual vsnprintf() operation, which expands the format
+     *  string with the provided arguments.
+     */
+    size_t size = len + 1;
+    char *buf = gc_malloc(size, false, &argres->gc);
+    len = vsnprintf(buf, size, f, arglist);
+    if (len < 0 || len >= size)
+    {
+        goto out;
+    }
+
+    /*
+     * Split the string at the GS (0x1D) delimiters and put each elemen
+     * into the struct argv being returned to the caller.
+     */
+    char *end = strchr(buf, delim);
+    while (end)
+    {
+        *end = '\0';
+        argv_append(argres, buf);
+        buf = end + 1;
+        end = strchr(buf, delim);
+    }
+    argv_append(argres, buf);
+
+    if (argres->argc != argc)
+    {
+        /* Someone snuck in a GS (0x1D), fail gracefully */
+        argv_reset(argres);
+        goto out;
+    }
+    res = true;
+
+out:
+    return res;
 }
 
-void
-argv_printf_cat(struct argv *a, const char *format, ...)
+/**
+ *  printf() variant which populates a struct argv.  It processes the
+ *  format string with the provided arguments.  For each space separator found
+ *  in the format string, a new argument will be added to the resulting
+ *  struct argv.
+ *
+ *  This will always reset and ensure the result is based on a pristine
+ *  struct argv.
+ *
+ *  @param *argres  Valid pointer to a struct argv where the result will be put.
+ *  @param *format  printf() compliant (char *) format string.
+ *
+ *  @returns Returns true if the parsing was successful.  See
+ *           argv_printf_arglist() for more details.  The parsed result will
+ *           be put into argres.
+ */
+bool
+argv_printf(struct argv *argres, const char *format, ...)
 {
     va_list arglist;
     va_start(arglist, format);
-    argv_printf_arglist(a, format, arglist);
+
+    argv_reset(argres);
+    bool res = argv_printf_arglist(argres, format, arglist);
     va_end(arglist);
+    return res;
 }
 
-void
-argv_parse_cmd(struct argv *a, const char *s)
+/**
+ *  printf() inspired argv concatenation.  Adds arguments to an existing
+ *  struct argv and populets the argument slots based on the printf() based
+ *  format string.
+ *
+ *  @param *argres  Valid pointer to a struct argv where the result will be put.
+ *  @param *format  printf() compliant (char *) format string.
+ *
+ *  @returns Returns true if the parsing was successful.  See
+ *           argv_printf_arglist() for more details.  The parsed result will
+ *           be put into argres.
+ */
+bool
+argv_printf_cat(struct argv *argres, const char *format, ...)
 {
-    int nparms;
-    char *parms[MAX_PARMS + 1];
-    struct gc_arena gc = gc_new();
+    va_list arglist;
+    va_start(arglist, format);
+    bool res = argv_printf_arglist(argres, format, arglist);
+    va_end(arglist);
+    return res;
+}
 
-    argv_reset(a);
-    argv_extend(a, 1); /* ensure trailing NULL */
+/**
+ *  Parses a command string, tokenizes it and puts each element into a separate
+ *  struct argv argument slot.
+ *
+ *  @params *argres  Valid pointer to a struct argv where the parsed result
+ *                   will be found.
+ *  @params *cmdstr  Char * based string to parse
+ *
+ */
+void
+argv_parse_cmd(struct argv *argres, const char *cmdstr)
+{
+    argv_reset(argres);
 
-    nparms = parse_line(s, parms, MAX_PARMS, "SCRIPT-ARGV", 0, D_ARGV_PARSE_CMD, &gc);
+    char *parms[MAX_PARMS + 1] = { 0 };
+    int nparms = parse_line(cmdstr, parms, MAX_PARMS, "SCRIPT-ARGV", 0,
+                            D_ARGV_PARSE_CMD, &argres->gc);
     if (nparms)
     {
         int i;
         for (i = 0; i < nparms; ++i)
         {
-            argv_append(a, string_alloc(parms[i], NULL));
+            argv_append(argres, parms[i]);
         }
     }
     else
     {
-        argv_append(a, string_alloc(s, NULL));
+        argv_append(argres, string_alloc(cmdstr, &argres->gc));
     }
-
-    gc_free(&gc);
 }
diff --git a/src/openvpn/argv.h b/src/openvpn/argv.h
index 9d9f387..943c78e 100644
--- a/src/openvpn/argv.h
+++ b/src/openvpn/argv.h
@@ -33,6 +33,7 @@
 #include "buffer.h"
 
 struct argv {
+    struct gc_arena gc;
     size_t capacity;
     size_t argc;
     char **argv;
@@ -40,7 +41,7 @@
 
 struct argv argv_new(void);
 
-void argv_reset(struct argv *a);
+void argv_free(struct argv *a);
 
 const char *argv_str(const struct argv *a, struct gc_arena *gc, const unsigned int flags);
 
@@ -52,7 +53,7 @@
 
 void argv_parse_cmd(struct argv *a, const char *s);
 
-void argv_printf(struct argv *a, const char *format, ...)
+bool argv_printf(struct argv *a, const char *format, ...)
 #ifdef __GNUC__
 #if __USE_MINGW_ANSI_STDIO
 __attribute__ ((format(gnu_printf, 2, 3)))
@@ -62,7 +63,7 @@
 #endif
 ;
 
-void argv_printf_cat(struct argv *a, const char *format, ...)
+bool argv_printf_cat(struct argv *a, const char *format, ...)
 #ifdef __GNUC__
 #if __USE_MINGW_ANSI_STDIO
 __attribute__ ((format(gnu_printf, 2, 3)))
diff --git a/src/openvpn/auth_token.c b/src/openvpn/auth_token.c
new file mode 100644
index 0000000..0ea6d18
--- /dev/null
+++ b/src/openvpn/auth_token.c
@@ -0,0 +1,410 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#include "base64.h"
+#include "buffer.h"
+#include "crypto.h"
+#include "openvpn.h"
+#include "ssl_common.h"
+#include "auth_token.h"
+#include "push.h"
+#include "integer.h"
+#include "ssl.h"
+#include "ssl_verify.h"
+#include <inttypes.h>
+
+const char *auth_token_pem_name = "OpenVPN auth-token server key";
+
+#define AUTH_TOKEN_SESSION_ID_LEN 12
+#if AUTH_TOKEN_SESSION_ID_LEN % 3
+#error AUTH_TOKEN_SESSION_ID_LEN needs to be multiple a 3
+#endif
+
+/* Size of the data of the token (not b64 encoded and without prefix) */
+#define TOKEN_DATA_LEN (2 * sizeof(int64_t) + AUTH_TOKEN_SESSION_ID_LEN + 32)
+
+static struct key_type
+auth_token_kt(void)
+{
+    struct key_type kt = { 0 };
+    /* We do not encrypt our session tokens */
+    kt.cipher = NULL;
+    kt.digest = md_kt_get("SHA256");
+
+    if (!kt.digest)
+    {
+        msg(M_WARN, "ERROR: --tls-crypt requires HMAC-SHA-256 support.");
+        return (struct key_type) { 0 };
+    }
+
+    kt.hmac_length = md_kt_size(kt.digest);
+
+    return kt;
+}
+
+
+void
+add_session_token_env(struct tls_session *session, struct tls_multi *multi,
+                      const struct user_pass *up)
+{
+    if (!multi->opt.auth_token_generate)
+    {
+        return;
+    }
+
+    int auth_token_state_flags = session->key[KS_PRIMARY].auth_token_state_flags;
+
+    const char *state;
+
+    if (!is_auth_token(up->password))
+    {
+        state = "Initial";
+    }
+    else if (auth_token_state_flags & AUTH_TOKEN_HMAC_OK)
+    {
+        switch (auth_token_state_flags & (AUTH_TOKEN_VALID_EMPTYUSER|AUTH_TOKEN_EXPIRED))
+        {
+            case 0:
+                state = "Authenticated";
+                break;
+
+            case AUTH_TOKEN_EXPIRED:
+                state = "Expired";
+                break;
+
+            case AUTH_TOKEN_VALID_EMPTYUSER:
+                state = "AuthenticatedEmptyUser";
+                break;
+
+            case AUTH_TOKEN_VALID_EMPTYUSER | AUTH_TOKEN_EXPIRED:
+                state = "ExpiredEmptyUser";
+                break;
+
+            default:
+                /* Silence compiler warning, all four possible combinations are covered */
+                ASSERT(0);
+        }
+    }
+    else
+    {
+        state = "Invalid";
+    }
+
+    setenv_str(session->opt->es, "session_state", state);
+
+    /* We had a valid session id before */
+    const char *session_id_source;
+    if (auth_token_state_flags & AUTH_TOKEN_HMAC_OK
+        && !(auth_token_state_flags & AUTH_TOKEN_EXPIRED))
+    {
+        session_id_source = up->password;
+    }
+    else
+    {
+        /*
+         * No session before, generate a new session token for the new session
+         */
+        if (!multi->auth_token)
+        {
+            generate_auth_token(up, multi);
+        }
+        session_id_source = multi->auth_token;
+    }
+    /*
+     * In the auth-token the auth token is already base64 encoded
+     * and being a multiple of 4 ensure that it a multiple of bytes
+     * in the encoding
+     */
+
+    char session_id[AUTH_TOKEN_SESSION_ID_LEN*2] = {0};
+    memcpy(session_id, session_id_source + strlen(SESSION_ID_PREFIX),
+           AUTH_TOKEN_SESSION_ID_LEN*8/6);
+
+    setenv_str(session->opt->es, "session_id", session_id);
+}
+
+void
+auth_token_write_server_key_file(const char *filename)
+{
+    write_pem_key_file(filename, auth_token_pem_name);
+}
+
+void
+auth_token_init_secret(struct key_ctx *key_ctx, const char *key_file,
+                       bool key_inline)
+{
+    struct key_type kt = auth_token_kt();
+
+    struct buffer server_secret_key = alloc_buf(2048);
+
+    bool key_loaded = false;
+    if (key_file)
+    {
+        key_loaded = read_pem_key_file(&server_secret_key,
+                                       auth_token_pem_name,
+                                       key_file, key_inline);
+    }
+    else
+    {
+        key_loaded = generate_ephemeral_key(&server_secret_key,
+                                            auth_token_pem_name);
+    }
+
+    if (!key_loaded)
+    {
+        msg(M_FATAL, "ERROR: Cannot load auth-token secret");
+    }
+
+    struct key key;
+
+    if (!buf_read(&server_secret_key, &key, sizeof(key)))
+    {
+        msg(M_FATAL, "ERROR: not enough data in auth-token secret");
+    }
+    init_key_ctx(key_ctx, &key, &kt, false, "auth-token secret");
+
+    free_buf(&server_secret_key);
+}
+
+void
+generate_auth_token(const struct user_pass *up, struct tls_multi *multi)
+{
+    struct gc_arena gc = gc_new();
+
+    int64_t timestamp = htonll((uint64_t)now);
+    int64_t initial_timestamp = timestamp;
+
+    hmac_ctx_t *ctx = multi->opt.auth_token_key.hmac;
+    ASSERT(hmac_ctx_size(ctx) == 256/8);
+
+    uint8_t sessid[AUTH_TOKEN_SESSION_ID_LEN];
+
+    if (multi->auth_token)
+    {
+        /* Just enough space to fit 8 bytes+ 1 extra to decode a non padded
+         * base64 string (multiple of 3 bytes). 9 bytes => 12 bytes base64
+         * bytes
+         */
+        char old_tstamp_decode[9];
+
+        /*
+         * reuse the same session id and timestamp and null terminate it at
+         * for base64 decode it only decodes the session id part of it
+         */
+        char *old_sessid = multi->auth_token + strlen(SESSION_ID_PREFIX);
+        char *old_tsamp_initial = old_sessid + AUTH_TOKEN_SESSION_ID_LEN*8/6;
+
+        old_tsamp_initial[12] = '\0';
+        ASSERT(openvpn_base64_decode(old_tsamp_initial, old_tstamp_decode, 9) == 9);
+
+        /*
+         * Avoid old gcc (4.8.x) complaining about strict aliasing
+         * by using a temporary variable instead of doing it in one
+         * line
+         */
+        uint64_t *tstamp_ptr = (uint64_t *) old_tstamp_decode;
+        initial_timestamp = *tstamp_ptr;
+
+        old_tsamp_initial[0] = '\0';
+        ASSERT(openvpn_base64_decode(old_sessid, sessid, AUTH_TOKEN_SESSION_ID_LEN)==AUTH_TOKEN_SESSION_ID_LEN);
+
+
+        /* free the auth-token, we will replace it with a new one */
+        free(multi->auth_token);
+    }
+    else if (!rand_bytes(sessid, AUTH_TOKEN_SESSION_ID_LEN))
+    {
+        msg( M_FATAL, "Failed to get enough randomness for "
+             "authentication token");
+    }
+
+    /* Calculate the HMAC */
+    /* We enforce up->username to be \0 terminated in ssl.c.. Allowing username
+     * with \0 in them is asking for troubles in so many ways anyway that we
+     * ignore that corner case here
+     */
+    uint8_t hmac_output[256/8];
+
+    hmac_ctx_reset(ctx);
+
+    /*
+     * If the token was only valid for the empty user, also generate
+     * a new token with the empty username since we do not want to loose
+     * the information that the username cannot be trusted
+     */
+    struct key_state *ks = &multi->session[TM_ACTIVE].key[KS_PRIMARY];
+    if (ks->auth_token_state_flags & AUTH_TOKEN_VALID_EMPTYUSER)
+    {
+        hmac_ctx_update(ctx, (const uint8_t *) "", 0);
+    }
+    else
+    {
+        hmac_ctx_update(ctx, (uint8_t *) up->username, (int) strlen(up->username));
+    }
+    hmac_ctx_update(ctx, sessid, AUTH_TOKEN_SESSION_ID_LEN);
+    hmac_ctx_update(ctx, (uint8_t *) &initial_timestamp, sizeof(initial_timestamp));
+    hmac_ctx_update(ctx, (uint8_t *) &timestamp, sizeof(timestamp));
+    hmac_ctx_final(ctx, hmac_output);
+
+    /* Construct the unencoded session token */
+    struct buffer token = alloc_buf_gc(
+        2*sizeof(uint64_t) + AUTH_TOKEN_SESSION_ID_LEN + 256/8, &gc);
+
+    ASSERT(buf_write(&token, sessid, sizeof(sessid)));
+    ASSERT(buf_write(&token, &initial_timestamp, sizeof(initial_timestamp)));
+    ASSERT(buf_write(&token, &timestamp, sizeof(timestamp)));
+    ASSERT(buf_write(&token, hmac_output, sizeof(hmac_output)));
+
+    char *b64output;
+    openvpn_base64_encode(BPTR(&token), BLEN(&token), &b64output);
+
+    struct buffer session_token = alloc_buf_gc(
+        strlen(SESSION_ID_PREFIX) + strlen(b64output) + 1, &gc);
+
+    ASSERT(buf_write(&session_token, SESSION_ID_PREFIX, strlen(SESSION_ID_PREFIX)));
+    ASSERT(buf_write(&session_token, b64output, (int)strlen(b64output)));
+    ASSERT(buf_write_u8(&session_token, 0));
+
+    free(b64output);
+
+    multi->auth_token = strdup((char *)BPTR(&session_token));
+
+    dmsg(D_SHOW_KEYS, "Generated token for client: %s (%s)",
+         multi->auth_token, up->username);
+
+    gc_free(&gc);
+}
+
+
+static bool
+check_hmac_token(hmac_ctx_t *ctx, const uint8_t *b64decoded, const char *username)
+{
+    ASSERT(hmac_ctx_size(ctx) == 256/8);
+
+    uint8_t hmac_output[256/8];
+
+    hmac_ctx_reset(ctx);
+    hmac_ctx_update(ctx, (uint8_t *) username, (int)strlen(username));
+    hmac_ctx_update(ctx, b64decoded, TOKEN_DATA_LEN - 256/8);
+    hmac_ctx_final(ctx, hmac_output);
+
+    const uint8_t *hmac = b64decoded + TOKEN_DATA_LEN - 256/8;
+    return memcmp_constant_time(&hmac_output, hmac, 32) == 0;
+}
+
+unsigned int
+verify_auth_token(struct user_pass *up, struct tls_multi *multi,
+                  struct tls_session *session)
+{
+    /*
+     * Base64 is <= input and input is < USER_PASS_LEN, so using USER_PASS_LEN
+     * is safe here but a bit overkill
+     */
+    uint8_t b64decoded[USER_PASS_LEN];
+    int decoded_len = openvpn_base64_decode(up->password + strlen(SESSION_ID_PREFIX),
+                                            b64decoded, USER_PASS_LEN);
+
+    /*
+     * Ensure that the decoded data is the size of the
+     * timestamp + hmac + session id
+     */
+    if (decoded_len != TOKEN_DATA_LEN)
+    {
+        msg(M_WARN, "ERROR: --auth-token wrong size (%d!=%d)",
+            decoded_len, (int) TOKEN_DATA_LEN);
+        return 0;
+    }
+
+    unsigned int ret = 0;
+
+    const uint8_t *sessid = b64decoded;
+    const uint8_t *tstamp_initial = sessid + AUTH_TOKEN_SESSION_ID_LEN;
+    const uint8_t *tstamp = tstamp_initial + sizeof(int64_t);
+
+    uint64_t timestamp = ntohll(*((uint64_t *) (tstamp)));
+    uint64_t timestamp_initial = ntohll(*((uint64_t *) (tstamp_initial)));
+
+    hmac_ctx_t *ctx = multi->opt.auth_token_key.hmac;
+    if (check_hmac_token(ctx, b64decoded, up->username))
+    {
+        ret |= AUTH_TOKEN_HMAC_OK;
+    }
+    else if (check_hmac_token(ctx, b64decoded, ""))
+    {
+        ret |= AUTH_TOKEN_HMAC_OK;
+        ret |= AUTH_TOKEN_VALID_EMPTYUSER;
+        /* overwrite the username of the client with the empty one */
+        strcpy(up->username, "");
+    }
+    else
+    {
+        msg(M_WARN, "--auth-token-gen: HMAC on token from client failed (%s)",
+            up->username);
+        return 0;
+    }
+
+    /* Accept session tokens that not expired are in the acceptable range
+     * for renogiations */
+    bool in_renog_time = now >= timestamp
+                         && now < timestamp + 2 * session->opt->renegotiate_seconds;
+
+    /* We could still have a client that does not update
+     * its auth-token, so also allow the initial auth-token */
+    bool initialtoken = multi->auth_token_initial
+                        && memcmp_constant_time(up->password, multi->auth_token_initial,
+                                                strlen(multi->auth_token_initial)) == 0;
+
+    if (!in_renog_time && !initialtoken)
+    {
+        ret |= AUTH_TOKEN_EXPIRED;
+    }
+
+    /* Sanity check the initial timestamp */
+    if (timestamp < timestamp_initial)
+    {
+        msg(M_WARN, "Initial timestamp (%" PRIu64 " in token from client earlier than "
+            "current timestamp %" PRIu64 ". Broken/unsynchronised clock?",
+            timestamp_initial, timestamp);
+        ret |= AUTH_TOKEN_EXPIRED;
+    }
+
+    if (multi->opt.auth_token_lifetime
+        && now > timestamp_initial + multi->opt.auth_token_lifetime)
+    {
+        ret |= AUTH_TOKEN_EXPIRED;
+    }
+
+    if (ret & AUTH_TOKEN_EXPIRED)
+    {
+        /* Tell client that the session token is expired */
+        auth_set_client_reason(multi, "SESSION: token expired");
+        msg(M_INFO, "--auth-token-gen: auth-token from client expired");
+    }
+    return ret;
+}
+
+void
+wipe_auth_token(struct tls_multi *multi)
+{
+    if (multi)
+    {
+        if (multi->auth_token)
+        {
+            secure_memzero(multi->auth_token, strlen(multi->auth_token));
+            free(multi->auth_token);
+        }
+        if (multi->auth_token_initial)
+        {
+            secure_memzero(multi->auth_token_initial,
+                           strlen(multi->auth_token_initial));
+            free(multi->auth_token_initial);
+        }
+        multi->auth_token = NULL;
+        multi->auth_token_initial = NULL;
+    }
+}
diff --git a/src/openvpn/auth_token.h b/src/openvpn/auth_token.h
new file mode 100644
index 0000000..fe07945
--- /dev/null
+++ b/src/openvpn/auth_token.h
@@ -0,0 +1,132 @@
+/*
+ *  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) 2002-2018 OpenVPN Inc <sales@openvpn.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef AUTH_TOKEN_H
+#define AUTH_TOKEN_H
+
+/**
+ * Generate an auth token based on username and timestamp
+ *
+ * The idea of auth token is to be stateless, so that we can verify use it
+ * even after we have forgotten about it or server has been restarted.
+ *
+ * To achieve this even though we cannot trust the client we use HMAC
+ * to be able to verify the information.
+ *
+ * Format of the auth-token (before base64 encode)
+ *
+ * session id(12 bytes)|uint64 timestamp (8 bytes)|
+ * uint64 timestamp (8 bytes)|sha256-hmac(32 bytes)
+ *
+ * The first timestamp is the time the token was initially created and is used to
+ * determine the maximum renewable time of the token. We always include this even
+ * if tokens do not expire (this value is not used) to keep the code cleaner.
+ *
+ * The second timestamp is the time the token was renewed/regenerated and is used
+ * to determine if this token has been renewed in the acceptable time range
+ * (2 * renogiation timeout)
+ *
+ * The session id is a random string of 12 byte (or 16 in base64) that is not
+ * used by OpenVPN itself but kept intact so that external logging/managment
+ * can track the session multiple reconnects/servers. It is delibrately chosen
+ * be a multiple of 3 bytes to have a base64 encoding without padding.
+ *
+ * The hmac is calculated over the username contactinated with the
+ * raw auth-token bytes to include authentication of the username in the token
+ *
+ * We encode the auth-token with base64 and then prepend "SESS_ID_" before
+ * sending it to the client.
+ *
+ * This function will free() an existing multi->auth_token and keep the
+ * existing initial timestamp and session id contained in that token.
+ */
+void
+generate_auth_token(const struct user_pass *up, struct tls_multi *multi);
+
+/**
+ * Verifies the auth token to be in the format that generate_auth_token
+ * create and checks if the token is valid.
+ *
+ */
+unsigned
+verify_auth_token(struct user_pass *up, struct tls_multi *multi,
+                  struct tls_session *session);
+
+
+
+/**
+ * Loads an HMAC secret from a file or if no file is present generates a
+ * epheremal secret for the run time of the server and stores it into ctx
+ */
+void
+auth_token_init_secret(struct key_ctx *key_ctx, const char *key_file,
+                       bool key_inline);
+
+
+/**
+ * Generate a auth-token server secret key, and write to file.
+ *
+ * @param filename          Filename of the server key file to create.
+ */
+void auth_token_write_server_key_file(const char *filename);
+
+
+/**
+ * Put the session id, and auth token status into the environment
+ * if auth-token is enabled
+ *
+ */
+void add_session_token_env(struct tls_session *session, struct tls_multi *multi,
+                           const struct user_pass *up);
+
+/**
+ * Wipes the authentication token out of the memory, frees and cleans up
+ * related buffers and flags
+ *
+ *  @param multi  Pointer to a multi object holding the auth_token variables
+ */
+void wipe_auth_token(struct tls_multi *multi);
+
+/**
+ * The prefix given to auth tokens start with, this prefix is special
+ * cased to not show up in log files in OpenVPN 2 and 3
+ *
+ * We also prefix this with _AT_ to only act on auth token generated by us.
+ */
+#define SESSION_ID_PREFIX "SESS_ID_AT_"
+
+/**
+ * Return if the password string has the format of a password.
+ *
+ * This fuction will always read as many bytes as SESSION_ID_PREFIX is longer
+ * the caller needs ensure that password memory is at least that long (true for
+ * calling with struct user_pass)
+ * @param password
+ * @return whether the password string starts with the session token prefix
+ */
+static inline bool
+is_auth_token(const char *password)
+{
+    return (memcmp_constant_time(SESSION_ID_PREFIX, password,
+                                 strlen(SESSION_ID_PREFIX)) == 0);
+}
+#endif /* AUTH_TOKEN_H */
diff --git a/src/openvpn/base64.h b/src/openvpn/base64.h
index 5679bc9..f49860f 100644
--- a/src/openvpn/base64.h
+++ b/src/openvpn/base64.h
@@ -34,6 +34,10 @@
 #ifndef _BASE64_H_
 #define _BASE64_H_
 
+/** Compute resulting base64 length.  6 bits per byte, padded to 4 bytes. */
+#define OPENVPN_BASE64_LENGTH(binary_length) \
+    ((((8 * binary_length) / 6) + 3) & ~3)
+
 int openvpn_base64_encode(const void *data, int size, char **str);
 
 int openvpn_base64_decode(const char *str, void *data, int size);
diff --git a/src/openvpn/block_dns.c b/src/openvpn/block_dns.c
index 889d6bb..f4718fc 100644
--- a/src/openvpn/block_dns.c
+++ b/src/openvpn/block_dns.c
@@ -109,9 +109,6 @@
 
 static WCHAR *FIREWALL_NAME = L"OpenVPN";
 
-VOID NETIOAPI_API_
-InitializeIpInterfaceEntry(PMIB_IPINTERFACE_ROW Row);
-
 /*
  * Default msg handler does nothing
  */
diff --git a/src/openvpn/block_dns.h b/src/openvpn/block_dns.h
index 50b383f..f9b1e5d 100644
--- a/src/openvpn/block_dns.h
+++ b/src/openvpn/block_dns.h
@@ -65,5 +65,5 @@
 set_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family,
                      const ULONG metric);
 
-#endif
-#endif
+#endif /* ifndef OPENVPN_BLOCK_DNS_H */
+#endif /* ifdef _WIN32 */
diff --git a/src/openvpn/buffer.c b/src/openvpn/buffer.c
index f9c76b1..890f31a 100644
--- a/src/openvpn/buffer.c
+++ b/src/openvpn/buffer.c
@@ -37,6 +37,8 @@
 
 #include "memdbg.h"
 
+#include <wchar.h>
+
 size_t
 array_mult_safe(const size_t m1, const size_t m2, const size_t extra)
 {
@@ -44,7 +46,7 @@
     unsigned long long res = (unsigned long long)m1 * (unsigned long long)m2 + (unsigned long long)extra;
     if (unlikely(m1 > limit) || unlikely(m2 > limit) || unlikely(extra > limit) || unlikely(res > (unsigned long long)limit))
     {
-        msg(M_FATAL, "attemped allocation of excessively large array");
+        msg(M_FATAL, "attempted allocation of excessively large array");
     }
     return (size_t) res;
 }
@@ -179,14 +181,6 @@
     return buf_write(dest, BPTR(src), BLEN(src));
 }
 
-struct buffer
-clear_buf(void)
-{
-    struct buffer buf;
-    CLEAR(buf);
-    return buf;
-}
-
 void
 free_buf(struct buffer *buf)
 {
@@ -197,6 +191,34 @@
     CLEAR(*buf);
 }
 
+static void
+free_buf_gc(struct buffer *buf, struct gc_arena *gc)
+{
+    if (gc)
+    {
+        struct gc_entry **e = &gc->list;
+
+        while (*e)
+        {
+            /* check if this object is the one we want to delete */
+            if ((uint8_t *)(*e + 1) == buf->data)
+            {
+                struct gc_entry *to_delete = *e;
+
+                /* remove element from linked list and free it */
+                *e = (*e)->next;
+                free(to_delete);
+
+                break;
+            }
+
+            e = &(*e)->next;
+        }
+    }
+
+    CLEAR(*buf);
+}
+
 /*
  * Return a buffer for write that is a subset of another buffer
  */
@@ -289,6 +311,29 @@
 }
 
 /*
+ * openvpn_swprintf() is currently only used by Windows code paths
+ * and when enabled for all platforms it will currently break older
+ * OpenBSD versions lacking vswprintf(3) support in their libc.
+ */
+
+#ifdef _WIN32
+bool
+openvpn_swprintf(wchar_t *const str, const size_t size, const wchar_t *const format, ...)
+{
+    va_list arglist;
+    int len = -1;
+    if (size > 0)
+    {
+        va_start(arglist, format);
+        len = vswprintf(str, size, format, arglist);
+        va_end(arglist);
+        str[size - 1] = L'\0';
+    }
+    return (len >= 0 && len < size);
+}
+#endif
+
+/*
  * write a string to the end of a buffer that was
  * truncated by buf_printf
  */
@@ -323,16 +368,33 @@
     }
 }
 
-/* NOTE: requires that string be null terminated */
-void
-buf_write_string_file(const struct buffer *buf, const char *filename, int fd)
+bool
+buffer_write_file(const char *filename, const struct buffer *buf)
 {
-    const int len = strlen((char *) BPTR(buf));
-    const int size = write(fd, BPTR(buf), len);
-    if (size != len)
+    bool ret = false;
+    int fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY,
+                           S_IRUSR | S_IWUSR);
+    if (fd == -1)
     {
-        msg(M_ERR, "Write error on file '%s'", filename);
+        msg(M_ERRNO, "Cannot open file '%s' for write", filename);
+        return false;
     }
+
+    const int size = write(fd, BPTR(buf), BLEN(buf));
+    if (size != BLEN(buf))
+    {
+        msg(M_ERRNO, "Write error on file '%s'", filename);
+        goto cleanup;
+    }
+
+    ret = true;
+cleanup:
+    if (close(fd) < 0)
+    {
+        msg(M_ERRNO, "Close error on file %s", filename);
+        ret = false;
+    }
+    return ret;
 }
 
 /*
@@ -412,7 +474,7 @@
 }
 
 void
-gc_addspecial(void *addr, void(free_function)(void *), struct gc_arena *a)
+gc_addspecial(void *addr, void (*free_function)(void *), struct gc_arena *a)
 {
     ASSERT(a);
     struct gc_entry_special *e;
@@ -647,7 +709,6 @@
              */
 #ifdef DMALLOC
             ret = openvpn_dmalloc(file, line, n);
-            memset(ret, 0, n);
 #else
             ret = calloc(1, n);
 #endif
@@ -1335,3 +1396,36 @@
     }
     return bl;
 }
+
+struct buffer
+buffer_read_from_file(const char *filename, struct gc_arena *gc)
+{
+    struct buffer ret = { 0 };
+
+    platform_stat_t file_stat = {0};
+    if (platform_stat(filename, &file_stat) < 0)
+    {
+        return ret;
+    }
+
+    FILE *fp = platform_fopen(filename, "r");
+    if (!fp)
+    {
+        return ret;
+    }
+
+    const size_t size = file_stat.st_size;
+    ret = alloc_buf_gc(size + 1, gc); /* space for trailing \0 */
+    ssize_t read_size = fread(BPTR(&ret), 1, size, fp);
+    if (read_size < 0)
+    {
+        free_buf_gc(&ret, gc);
+        goto cleanup;
+    }
+    ASSERT(buf_inc_len(&ret, read_size));
+    buf_null_terminate(&ret);
+
+cleanup:
+    fclose(fp);
+    return ret;
+}
diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h
index c510c00..1722ffd 100644
--- a/src/openvpn/buffer.h
+++ b/src/openvpn/buffer.h
@@ -131,8 +131,6 @@
 
 void buf_clear(struct buffer *buf);
 
-struct buffer clear_buf(void);
-
 void free_buf(struct buffer *buf);
 
 bool buf_assign(struct buffer *dest, const struct buffer *src);
@@ -206,6 +204,13 @@
     freeaddrinfo((struct addrinfo *) addr);
 }
 
+/** Return an empty struct buffer */
+static inline struct buffer
+clear_buf(void)
+{
+    return (struct buffer) { 0 };
+}
+
 static inline bool
 buf_defined(const struct buffer *buf)
 {
@@ -342,9 +347,9 @@
 static inline void
 strncpynt(char *dest, const char *src, size_t maxlen)
 {
-    strncpy(dest, src, maxlen);
     if (maxlen > 0)
     {
+        strncpy(dest, src, maxlen-1);
         dest[maxlen - 1] = 0;
     }
 }
@@ -443,6 +448,23 @@
 #endif
 ;
 
+
+#ifdef _WIN32
+/*
+ * Like swprintf but guarantees null termination for size > 0
+ *
+ * This is under #ifdef because only Windows-specific code in tun.c
+ * uses this function and its implementation breaks OpenBSD <= 4.9
+ */
+bool
+openvpn_swprintf(wchar_t *const str, const size_t size, const wchar_t *const format, ...);
+
+/*
+ * Unlike in openvpn_snprintf, we cannot use format attributes since
+ * GCC doesn't support wprintf as archetype.
+ */
+#endif
+
 /*
  * remove/add trailing characters
  */
@@ -464,11 +486,15 @@
 
 void string_null_terminate(char *str, int len, int capacity);
 
-/*
- * Write string in buf to file descriptor fd.
- * NOTE: requires that string be null terminated.
+/**
+ * Write buffer contents to file.
+ *
+ * @param filename  The filename to write the buffer to.
+ * @param buf       The buffer to write to the file.
+ *
+ * @return true on success, false otherwise.
  */
-void buf_write_string_file(const struct buffer *buf, const char *filename, int fd);
+bool buffer_write_file(const char *filename, const struct buffer *buf);
 
 /*
  * write a string to the end of a buffer that was
@@ -828,6 +854,13 @@
     }
 }
 
+/** Return true if buffer contents are equal */
+static inline bool
+buf_equal(const struct buffer *a, const struct buffer *b)
+{
+    return BLEN(a) == BLEN(b) && 0 == memcmp(BPTR(a), BPTR(b), BLEN(a));
+}
+
 /**
  * Compare src buffer contents with match.
  * *NOT* constant time. Do not use when comparing HMACs.
@@ -1174,4 +1207,16 @@
 
 struct buffer_list *buffer_list_file(const char *fn, int max_line_len);
 
+/**
+ * buffer_read_from_file - copy the content of a file into a buffer
+ *
+ * @param file      path to the file to read
+ * @param gc        the garbage collector to use when allocating the buffer. It
+ *                  is passed to alloc_buf_gc() and therefore can be NULL.
+ *
+ * @return the buffer storing the file content or an invalid buffer in case of
+ * error
+ */
+struct buffer buffer_read_from_file(const char *filename, struct gc_arena *gc);
+
 #endif /* BUFFER_H */
diff --git a/src/openvpn/common.h b/src/openvpn/common.h
index 0f73200..623b3e0 100644
--- a/src/openvpn/common.h
+++ b/src/openvpn/common.h
@@ -57,12 +57,10 @@
 #else
 #define ptr_format              "0x%08lx"
 #endif
-#define time_format             "%lu"
 #define fragment_header_format  "0x%08x"
 
 /* these are used to cast the arguments
  * and MUST match the formats above */
-typedef unsigned long time_type;
 #ifdef _WIN64
 typedef unsigned long long ptr_type;
 #else
@@ -91,12 +89,6 @@
 #define PUSH_REQUEST_INTERVAL 5
 
 /*
- * A sort of pseudo-filename for data provided inline within
- * the configuration file.
- */
-#define INLINE_FILE_TAG "[[INLINE]]"
-
-/*
  * Script security warning
  */
 #define SCRIPT_SECURITY_WARNING "WARNING: External program may not be called unless '--script-security 2' or higher is enabled. See --help text or man page for detailed info."
diff --git a/src/openvpn/comp-lz4.c b/src/openvpn/comp-lz4.c
index f2916bd..30e6da9 100644
--- a/src/openvpn/comp-lz4.c
+++ b/src/openvpn/comp-lz4.c
@@ -35,7 +35,7 @@
 #if defined(NEED_COMPAT_LZ4)
 #include "compat-lz4.h"
 #else
-#include "lz4.h"
+#include <lz4.h>
 #endif
 
 #include "comp.h"
@@ -70,8 +70,9 @@
 {
     /*
      * In order to attempt compression, length must be at least COMPRESS_THRESHOLD.
+     * and asymmetric compression must be disabled
      */
-    if (buf->len >= COMPRESS_THRESHOLD)
+    if (buf->len >= COMPRESS_THRESHOLD && (compctx->flags & COMP_F_ALLOW_COMPRESS))
     {
         const size_t ps = PAYLOAD_SIZE(frame);
         int zlen_max = ps + COMP_EXTRA_BUFFER(ps);
diff --git a/src/openvpn/comp.c b/src/openvpn/comp.c
index a945913..9b13113 100644
--- a/src/openvpn/comp.c
+++ b/src/openvpn/comp.c
@@ -127,7 +127,7 @@
 comp_add_to_extra_buffer(struct frame *frame)
 {
     /* Leave room for compression buffer to expand in worst case scenario
-     * where data is totally uncompressible */
+     * where data is totally incompressible */
     frame_add_to_extra_buffer(frame, COMP_EXTRA_BUFFER(EXPANDED_SIZE(frame)));
 }
 
diff --git a/src/openvpn/comp.h b/src/openvpn/comp.h
index 0dadd1e..5c0322c 100644
--- a/src/openvpn/comp.h
+++ b/src/openvpn/comp.h
@@ -52,10 +52,12 @@
  */
 
 /* Compression flags */
-#define COMP_F_ADAPTIVE   (1<<0) /* COMP_ALG_LZO only */
-#define COMP_F_ASYM       (1<<1) /* only downlink is compressed, not uplink */
-#define COMP_F_SWAP       (1<<2) /* initial command byte is swapped with last byte in buffer to preserve payload alignment */
+#define COMP_F_ADAPTIVE             (1<<0) /* COMP_ALG_LZO only */
+#define COMP_F_ALLOW_COMPRESS       (1<<1) /* not only downlink is compressed but also uplink */
+#define COMP_F_SWAP                 (1<<2) /* initial command byte is swapped with last byte in buffer to preserve payload alignment */
 #define COMP_F_ADVERTISE_STUBS_ONLY (1<<3) /* tell server that we only support compression stubs */
+#define COMP_F_ALLOW_STUB_ONLY      (1<<4) /* Only accept stub compression, even with COMP_F_ADVERTISE_STUBS_ONLY
+                                            * we still accept other compressions to be pushed */
 
 
 /*
@@ -189,6 +191,14 @@
 }
 
 static inline bool
+comp_non_stub_enabled(const struct compress_options *info)
+{
+    return info->alg != COMP_ALGV2_UNCOMPRESSED
+           && info->alg != COMP_ALG_STUB
+           && info->alg != COMP_ALG_UNDEF;
+}
+
+static inline bool
 comp_unswapped_prefix(const struct compress_options *info)
 {
     return !(info->flags & COMP_F_SWAP);
diff --git a/src/openvpn/console.h b/src/openvpn/console.h
index 5a70e5f..f948168 100644
--- a/src/openvpn/console.h
+++ b/src/openvpn/console.h
@@ -21,7 +21,7 @@
  *  You should have received a copy of the GNU General Public License along
  *  with this program; if not, write to the Free Software Foundation, Inc.,
  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
+ */
 
 #ifndef CONSOLE_H
 #define CONSOLE_H
@@ -33,9 +33,9 @@
  */
 struct _query_user {
     char *prompt;             /**< Prompt to present to the user */
-    size_t prompt_len;        /**< Lenght of the prompt string */
+    size_t prompt_len;        /**< Length of the prompt string */
     char *response;           /**< The user's response */
-    size_t response_len;      /**< Lenght the of the user reposone */
+    size_t response_len;      /**< Length the of the user response */
     bool echo;                /**< True: The user should see what is being typed, otherwise mask it */
 };
 
@@ -55,7 +55,7 @@
  * @param prompt     Prompt to display to the user
  * @param prompt_len Length of the prompt string
  * @param resp       String containing the user response
- * @param resp_len   Lenght of the response string
+ * @param resp_len   Length of the response string
  * @param echo       Should the user input be echoed to the user?  If False, input will be masked
  *
  */
diff --git a/src/openvpn/console_systemd.c b/src/openvpn/console_systemd.c
index e7a72ae..c7cf1ad 100644
--- a/src/openvpn/console_systemd.c
+++ b/src/openvpn/console_systemd.c
@@ -33,6 +33,7 @@
 #include "syshead.h"
 #include "console.h"
 #include "misc.h"
+#include "run_command.h"
 
 #include <systemd/sd-daemon.h>
 
@@ -84,7 +85,7 @@
     }
     close(std_out);
 
-    argv_reset(&argv);
+    argv_free(&argv);
 
     return ret;
 }
diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c
index 7e7dead..3a0bfbe 100644
--- a/src/openvpn/crypto.c
+++ b/src/openvpn/crypto.c
@@ -30,8 +30,6 @@
 
 #include "syshead.h"
 
-#ifdef ENABLE_CRYPTO
-
 #include "crypto.h"
 #include "error.h"
 #include "integer.h"
@@ -66,7 +64,6 @@
 openvpn_encrypt_aead(struct buffer *buf, struct buffer work,
                      struct crypto_options *opt)
 {
-#ifdef HAVE_AEAD_CIPHER_MODES
     struct gc_arena gc;
     int outlen = 0;
     const struct key_ctx *ctx = &opt->key_ctx_bi.encrypt;
@@ -77,7 +74,6 @@
     /* IV, packet-ID and implicit IV required for this mode. */
     ASSERT(ctx->cipher);
     ASSERT(cipher_kt_mode_aead(cipher_kt));
-    ASSERT(opt->flags & CO_USE_IV);
     ASSERT(packet_id_initialized(&opt->packet_id));
 
     gc_init(&gc);
@@ -155,9 +151,6 @@
     buf->len = 0;
     gc_free(&gc);
     return;
-#else /* HAVE_AEAD_CIPHER_MODES */
-    ASSERT(0);
-#endif /* ifdef HAVE_AEAD_CIPHER_MODES */
 }
 
 static void
@@ -192,10 +185,7 @@
             if (cipher_kt_mode_cbc(cipher_kt))
             {
                 /* generate pseudo-random IV */
-                if (opt->flags & CO_USE_IV)
-                {
-                    prng_bytes(iv_buf, iv_size);
-                }
+                prng_bytes(iv_buf, iv_size);
 
                 /* Put packet ID in plaintext buffer */
                 if (packet_id_initialized(&opt->packet_id)
@@ -211,8 +201,7 @@
             {
                 struct buffer b;
 
-                /* IV and packet-ID required for this mode. */
-                ASSERT(opt->flags & CO_USE_IV);
+                /* packet-ID required for this mode. */
                 ASSERT(packet_id_initialized(&opt->packet_id));
 
                 buf_set_write(&b, iv_buf, iv_size);
@@ -224,11 +213,8 @@
             }
 
             /* set the IV pseudo-randomly */
-            if (opt->flags & CO_USE_IV)
-            {
-                ASSERT(buf_write(&work, iv_buf, iv_size));
-                dmsg(D_PACKET_CONTENT, "ENCRYPT IV: %s", format_hex(iv_buf, iv_size, 0, &gc));
-            }
+            ASSERT(buf_write(&work, iv_buf, iv_size));
+            dmsg(D_PACKET_CONTENT, "ENCRYPT IV: %s", format_hex(iv_buf, iv_size, 0, &gc));
 
             dmsg(D_PACKET_CONTENT, "ENCRYPT FROM: %s",
                  format_hex(BPTR(buf), BLEN(buf), 80, &gc));
@@ -358,20 +344,19 @@
     return ret;
 }
 
-/*
- * If (opt->flags & CO_USE_IV) is not NULL, we will read an IV from the packet.
+/**
+ * Unwrap (authenticate, decrypt and check replay protection) AEAD-mode data
+ * channel packets.
  *
  * Set buf->len to 0 and return false on decrypt error.
  *
- * On success, buf is set to point to plaintext, true
- * is returned.
+ * On success, buf is set to point to plaintext, true is returned.
  */
 static bool
 openvpn_decrypt_aead(struct buffer *buf, struct buffer work,
                      struct crypto_options *opt, const struct frame *frame,
                      const uint8_t *ad_start)
 {
-#ifdef HAVE_AEAD_CIPHER_MODES
     static const char error_prefix[] = "AEAD Decrypt error";
     struct packet_id_net pin = { 0 };
     const struct key_ctx *ctx = &opt->key_ctx_bi.decrypt;
@@ -398,7 +383,6 @@
 
     /* IV and Packet ID required for this mode */
     ASSERT(packet_id_initialized(&opt->packet_id));
-    ASSERT(opt->flags & CO_USE_IV);
 
     /* Combine IV from explicit part from packet and implicit part from context */
     {
@@ -439,13 +423,6 @@
     tag_ptr = BPTR(buf);
     ASSERT(buf_advance(buf, tag_size));
     dmsg(D_PACKET_CONTENT, "DECRYPT MAC: %s", format_hex(tag_ptr, tag_size, 0, &gc));
-#if defined(ENABLE_CRYPTO_OPENSSL) && OPENSSL_VERSION_NUMBER < 0x10001040L
-    /* OpenSSL <= 1.0.1c bug requires set tag before processing ciphertext */
-    if (!EVP_CIPHER_CTX_ctrl(ctx->cipher, EVP_CTRL_GCM_SET_TAG, tag_size, tag_ptr))
-    {
-        CRYPT_ERROR("setting tag failed");
-    }
-#endif
 
     if (buf->len < 1)
     {
@@ -500,19 +477,15 @@
     buf->len = 0;
     gc_free(&gc);
     return false;
-#else /* HAVE_AEAD_CIPHER_MODES */
-    ASSERT(0);
-    return false;
-#endif /* ifdef HAVE_AEAD_CIPHER_MODES */
 }
 
 /*
- * If (opt->flags & CO_USE_IV) is not NULL, we will read an IV from the packet.
+ * Unwrap (authenticate, decrypt and check replay protection) CBC, OFB or CFB
+ * mode data channel packets.
  *
  * Set buf->len to 0 and return false on decrypt error.
  *
- * On success, buf is set to point to plaintext, true
- * is returned.
+ * On success, buf is set to point to plaintext, true is returned.
  */
 static bool
 openvpn_decrypt_v1(struct buffer *buf, struct buffer work,
@@ -572,22 +545,14 @@
             /* initialize work buffer with FRAME_HEADROOM bytes of prepend capacity */
             ASSERT(buf_init(&work, FRAME_HEADROOM_ADJ(frame, FRAME_HEADROOM_MARKER_DECRYPT)));
 
-            /* use IV if user requested it */
-            if (opt->flags & CO_USE_IV)
+            /* read the IV from the packet */
+            if (buf->len < iv_size)
             {
-                if (buf->len < iv_size)
-                {
-                    CRYPT_ERROR("missing IV info");
-                }
-                memcpy(iv_buf, BPTR(buf), iv_size);
-                ASSERT(buf_advance(buf, iv_size));
+                CRYPT_ERROR("missing IV info");
             }
-
-            /* show the IV's initial state */
-            if (opt->flags & CO_USE_IV)
-            {
-                dmsg(D_PACKET_CONTENT, "DECRYPT IV: %s", format_hex(iv_buf, iv_size, 0, &gc));
-            }
+            memcpy(iv_buf, BPTR(buf), iv_size);
+            ASSERT(buf_advance(buf, iv_size));
+            dmsg(D_PACKET_CONTENT, "DECRYPT IV: %s", format_hex(iv_buf, iv_size, 0, &gc));
 
             if (buf->len < 1)
             {
@@ -640,8 +605,7 @@
                 {
                     struct buffer b;
 
-                    /* IV and packet-ID required for this mode. */
-                    ASSERT(opt->flags & CO_USE_IV);
+                    /* packet-ID required for this mode. */
                     ASSERT(packet_id_initialized(&opt->packet_id));
 
                     buf_set_read(&b, iv_buf, iv_size);
@@ -717,7 +681,6 @@
 void
 crypto_adjust_frame_parameters(struct frame *frame,
                                const struct key_type *kt,
-                               bool use_iv,
                                bool packet_id,
                                bool packet_id_long_form)
 {
@@ -730,10 +693,7 @@
 
     if (kt->cipher)
     {
-        if (use_iv)
-        {
-            crypto_overhead += cipher_kt_iv_size(kt->cipher);
-        }
+        crypto_overhead += cipher_kt_iv_size(kt->cipher);
 
         if (cipher_kt_mode_aead(kt->cipher))
         {
@@ -760,6 +720,20 @@
            +max_int(OPENVPN_MAX_HMAC_SIZE, OPENVPN_AEAD_TAG_LENGTH);
 }
 
+static void
+warn_insecure_key_type(const char *ciphername, const cipher_kt_t *cipher)
+{
+    if (cipher_kt_insecure(cipher))
+    {
+        msg(M_WARN, "WARNING: INSECURE cipher (%s) with block size less than 128"
+            " bit (%d bit).  This allows attacks like SWEET32.  Mitigate by "
+            "using a --cipher with a larger block size (e.g. AES-256-CBC). "
+            "Support for these insecure ciphers will be removed in "
+            "OpenVPN 2.6.",
+            ciphername, cipher_kt_block_size(cipher)*8);
+    }
+}
+
 /*
  * Build a struct key_type.
  */
@@ -775,7 +749,7 @@
     CLEAR(*kt);
     if (strcmp(ciphername, "none") != 0)
     {
-        kt->cipher = cipher_kt_get(translate_cipher_name_from_openvpn(ciphername));
+        kt->cipher = cipher_kt_get(ciphername);
         if (!kt->cipher)
         {
             msg(M_FATAL, "Cipher %s not supported", ciphername);
@@ -803,6 +777,10 @@
         {
             msg(M_FATAL, "Cipher '%s' not allowed: block size too big.", ciphername);
         }
+        if (warn)
+        {
+            warn_insecure_key_type(ciphername, kt->cipher);
+        }
     }
     else
     {
@@ -855,9 +833,10 @@
         cipher_ctx_init(ctx->cipher, key->cipher, kt->cipher_length,
                         kt->cipher, enc);
 
+        const char *ciphername = cipher_kt_name(kt->cipher);
         msg(D_HANDSHAKE, "%s: Cipher '%s' initialized with %d bit key",
             prefix,
-            translate_cipher_name_to_openvpn(cipher_kt_name(kt->cipher)),
+            ciphername,
             kt->cipher_length *8);
 
         dmsg(D_SHOW_KEYS, "%s: CIPHER KEY: %s", prefix,
@@ -865,13 +844,7 @@
         dmsg(D_CRYPTO_DEBUG, "%s: CIPHER block_size=%d iv_size=%d",
              prefix, cipher_kt_block_size(kt->cipher),
              cipher_kt_iv_size(kt->cipher));
-        if (cipher_kt_block_size(kt->cipher) < 128/8)
-        {
-            msg(M_WARN, "WARNING: INSECURE cipher with block size less than 128"
-                " bit (%d bit).  This allows attacks like SWEET32.  Mitigate by "
-                "using a --cipher with a larger block size (e.g. AES-256-CBC).",
-                cipher_kt_block_size(kt->cipher)*8);
-        }
+        warn_insecure_key_type(ciphername, kt->cipher);
     }
     if (kt->digest && kt->hmac_length > 0)
     {
@@ -943,10 +916,12 @@
 {
     int i;
     for (i = 0; i < kt->cipher_length; ++i)
+    {
         if (key->cipher[i])
         {
             return false;
         }
+    }
     msg(D_CRYPT_ERRORS, "CRYPTO INFO: WARNING: zero key detected");
     return true;
 }
@@ -1025,15 +1000,14 @@
 }
 
 void
-check_replay_iv_consistency(const struct key_type *kt, bool packet_id, bool use_iv)
+check_replay_consistency(const struct key_type *kt, bool packet_id)
 {
     ASSERT(kt);
 
-    if (!(packet_id && use_iv) && (cipher_kt_mode_ofb_cfb(kt->cipher)
-                                   || cipher_kt_mode_aead(kt->cipher)))
+    if (!packet_id && (cipher_kt_mode_ofb_cfb(kt->cipher)
+                       || cipher_kt_mode_aead(kt->cipher)))
     {
-        msg(M_FATAL, "--no-replay or --no-iv cannot be used with a CFB, OFB or "
-            "AEAD mode cipher");
+        msg(M_FATAL, "--no-replay cannot be used with a CFB, OFB or AEAD mode cipher");
     }
 }
 
@@ -1123,7 +1097,6 @@
     /* init work */
     ASSERT(buf_init(&work, FRAME_HEADROOM(frame)));
 
-#ifdef HAVE_AEAD_CIPHER_MODES
     /* init implicit IV */
     {
         const cipher_kt_t *cipher =
@@ -1145,7 +1118,6 @@
             co->key_ctx_bi.decrypt.implicit_iv_len = impl_iv_len;
         }
     }
-#endif /* ifdef HAVE_AEAD_CIPHER_MODES */
 
     msg(M_INFO, "Entering " PACKAGE_NAME " crypto self-test mode.");
     for (i = 1; i <= TUN_MTU_SIZE(frame); ++i)
@@ -1196,27 +1168,38 @@
     gc_free(&gc);
 }
 
+const char *
+print_key_filename(const char *str, bool is_inline)
+{
+    if (is_inline)
+    {
+        return "[[INLINE]]";
+    }
+
+    return np(str);
+}
+
 void
 crypto_read_openvpn_key(const struct key_type *key_type,
-                        struct key_ctx_bi *ctx, const char *key_file, const char *key_inline,
-                        const int key_direction, const char *key_name, const char *opt_name)
+                        struct key_ctx_bi *ctx, const char *key_file,
+                        bool key_inline, const int key_direction,
+                        const char *key_name, const char *opt_name)
 {
     struct key2 key2;
     struct key_direction_state kds;
+    unsigned int flags = RKF_MUST_SUCCEED;
 
     if (key_inline)
     {
-        read_key_file(&key2, key_inline, RKF_MUST_SUCCEED|RKF_INLINE);
+        flags |= RKF_INLINE;
     }
-    else
-    {
-        read_key_file(&key2, key_file, RKF_MUST_SUCCEED);
-    }
+    read_key_file(&key2, key_file, flags);
 
     if (key2.n != 2)
     {
         msg(M_ERR, "File '%s' does not have OpenVPN Static Key format.  Using "
-            "free-form passphrase file is not supported anymore.", key_file);
+            "free-form passphrase file is not supported anymore.",
+            print_key_filename(key_file, key_inline));
     }
 
     /* check for and fix highly unlikely key problems */
@@ -1248,9 +1231,8 @@
 {
     struct gc_arena gc = gc_new();
     struct buffer in;
-    int fd, size;
+    int size;
     uint8_t hex_byte[3] = {0, 0, 0};
-    const char *error_filename = file;
 
     /* parse info */
     const unsigned char *cp;
@@ -1288,26 +1270,16 @@
     {
         size = strlen(file) + 1;
         buf_set_read(&in, (const uint8_t *)file, size);
-        error_filename = INLINE_FILE_TAG;
     }
     else /* 'file' is a filename which refers to a file containing the ascii key */
     {
-        in = alloc_buf_gc(2048, &gc);
-        fd = platform_open(file, O_RDONLY, 0);
-        if (fd == -1)
-        {
-            msg(M_ERR, "Cannot open key file '%s'", file);
-        }
-        size = read(fd, in.data, in.capacity);
-        if (size < 0)
+        in = buffer_read_from_file(file, &gc);
+        if (!buf_valid(&in))
         {
             msg(M_FATAL, "Read error on key file ('%s')", file);
         }
-        if (size == in.capacity)
-        {
-            msg(M_FATAL, "Key file ('%s') can be a maximum of %d bytes", file, (int)in.capacity);
-        }
-        close(fd);
+
+        size = in.len;
     }
 
     cp = (unsigned char *)in.data;
@@ -1393,7 +1365,9 @@
                 {
                     msg(M_FATAL,
                         (isprint(c) ? printable_char_fmt : unprintable_char_fmt),
-                        c, line_num, error_filename, count, onekeylen, keylen);
+                        c, line_num,
+                        print_key_filename(file, flags & RKF_INLINE), count,
+                        onekeylen, keylen);
                 }
             }
             ++line_index;
@@ -1414,13 +1388,15 @@
         if (!key2->n)
         {
             msg(M_FATAL, "Insufficient key material or header text not found in file '%s' (%d/%d/%d bytes found/min/max)",
-                error_filename, count, onekeylen, keylen);
+                print_key_filename(file, flags & RKF_INLINE), count, onekeylen,
+                keylen);
         }
 
         if (state != PARSE_FINISHED)
         {
             msg(M_FATAL, "Footer text not found in file '%s' (%d/%d/%d bytes found/min/max)",
-                error_filename, count, onekeylen, keylen);
+                print_key_filename(file, flags & RKF_INLINE), count, onekeylen,
+                keylen);
         }
     }
 
@@ -1453,36 +1429,24 @@
     gc_free(&gc);
 }
 
-/*
- * Write key to file, return number of random bits
- * written.
- */
 int
 write_key_file(const int nkeys, const char *filename)
 {
     struct gc_arena gc = gc_new();
 
-    int fd, i;
-    int nbits = 0;
+    int nbits = nkeys * sizeof(struct key) * 8;
 
     /* must be large enough to hold full key file */
     struct buffer out = alloc_buf_gc(2048, &gc);
-    struct buffer nbits_head_text = alloc_buf_gc(128, &gc);
 
     /* how to format the ascii file representation of key */
     const int bytes_per_line = 16;
 
-    /* open key file */
-    fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);
-
-    if (fd == -1)
-    {
-        msg(M_ERR, "Cannot open shared secret file '%s' for write", filename);
-    }
-
+    /* write header */
+    buf_printf(&out, "#\n# %d bit OpenVPN static key\n#\n", nbits);
     buf_printf(&out, "%s\n", static_key_head);
 
-    for (i = 0; i < nkeys; ++i)
+    for (int i = 0; i < nkeys; ++i)
     {
         struct key key;
         char *fmt;
@@ -1498,9 +1462,6 @@
                             "\n",
                             &gc);
 
-        /* increment random bits counter */
-        nbits += sizeof(key) * 8;
-
         /* write to holding buffer */
         buf_printf(&out, "%s\n", fmt);
 
@@ -1511,16 +1472,15 @@
 
     buf_printf(&out, "%s\n", static_key_foot);
 
-    /* write number of bits */
-    buf_printf(&nbits_head_text, "#\n# %d bit OpenVPN static key\n#\n", nbits);
-    buf_write_string_file(&nbits_head_text, filename, fd);
-
-    /* write key file, now formatted in out, to file */
-    buf_write_string_file(&out, filename, fd);
-
-    if (close(fd))
+    /* write key file to stdout if no filename given */
+    if (!filename || strcmp(filename, "")==0)
     {
-        msg(M_ERR, "Close error on shared secret file %s", filename);
+        printf("%.*s\n", BLEN(&out), BPTR(&out));
+    }
+    /* write key file, now formatted in out, to file */
+    else if (!buffer_write_file(filename, &out))
+    {
+        nbits = -1;
     }
 
     /* zero memory which held file content (memory will be freed by GC) */
@@ -1540,7 +1500,7 @@
 #ifdef ENABLE_SMALL
         msg(M_FATAL, "Key file '%s' used in --%s contains insufficient key material [keys found=%d required=%d]", filename, option, key2->n, n);
 #else
-        msg(M_FATAL, "Key file '%s' used in --%s contains insufficient key material [keys found=%d required=%d] -- try generating a new key file with '" PACKAGE " --genkey --secret [file]', or use the existing key file in bidirectional mode by specifying --%s without a key direction parameter", filename, option, key2->n, n, option);
+        msg(M_FATAL, "Key file '%s' used in --%s contains insufficient key material [keys found=%d required=%d] -- try generating a new key file with '" PACKAGE " --genkey secret [file]', or use the existing key file in bidirectional mode by specifying --%s without a key direction parameter", filename, option, key2->n, n, option);
 #endif
     }
 }
@@ -1748,7 +1708,9 @@
     {
         int i;
         for (i = 0; i < size; ++i)
+        {
             nonce_data[i] = (uint8_t) i;
+        }
     }
 #endif
 }
@@ -1825,6 +1787,33 @@
     return l;
 }
 
+void
+print_cipher(const cipher_kt_t *cipher)
+{
+    const char *var_key_size = cipher_kt_var_key_size(cipher) ?
+                               " by default" : "";
+
+    printf("%s  (%d bit key%s, ",
+           cipher_kt_name(cipher),
+           cipher_kt_key_size(cipher) * 8, var_key_size);
+
+    if (cipher_kt_block_size(cipher) == 1)
+    {
+        printf("stream cipher");
+    }
+    else
+    {
+        printf("%d bit block", cipher_kt_block_size(cipher) * 8);
+    }
+
+    if (!cipher_kt_mode_cbc(cipher))
+    {
+        printf(", TLS client/server mode only");
+    }
+
+    printf(")\n");
+}
+
 static const cipher_name_pair *
 get_cipher_name_pair(const char *cipher_name)
 {
@@ -1872,4 +1861,97 @@
     return pair->openvpn_name;
 }
 
-#endif /* ENABLE_CRYPTO */
+void
+write_pem_key_file(const char *filename, const char *pem_name)
+{
+    struct gc_arena gc = gc_new();
+    struct key server_key = { 0 };
+    struct buffer server_key_buf = clear_buf();
+    struct buffer server_key_pem = clear_buf();
+
+    if (!rand_bytes((void *)&server_key, sizeof(server_key)))
+    {
+        msg(M_NONFATAL, "ERROR: could not generate random key");
+        goto cleanup;
+    }
+    buf_set_read(&server_key_buf, (void *)&server_key, sizeof(server_key));
+    if (!crypto_pem_encode(pem_name, &server_key_pem,
+                           &server_key_buf, &gc))
+    {
+        msg(M_WARN, "ERROR: could not PEM-encode key");
+        goto cleanup;
+    }
+
+    if (!filename || strcmp(filename, "")==0)
+    {
+        printf("%.*s", BLEN(&server_key_pem), BPTR(&server_key_pem));
+    }
+    else if (!buffer_write_file(filename, &server_key_pem))
+    {
+        msg(M_ERR, "ERROR: could not write key file");
+        goto cleanup;
+    }
+
+cleanup:
+    secure_memzero(&server_key, sizeof(server_key));
+    buf_clear(&server_key_pem);
+    gc_free(&gc);
+    return;
+}
+
+bool
+generate_ephemeral_key(struct buffer *key, const char *key_name)
+{
+    const int len = BCAP(key);
+
+    msg(M_INFO, "Using random %s.", key_name);
+
+    if (!rand_bytes(BEND(key), len))
+    {
+        msg(M_WARN, "ERROR: could not generate random key");
+        return false;
+    }
+
+    buf_inc_len(key, len);
+
+    return true;
+}
+
+bool
+read_pem_key_file(struct buffer *key, const char *pem_name,
+                  const char *key_file, bool key_inline)
+{
+    bool ret = false;
+    struct buffer key_pem = { 0 };
+    struct gc_arena gc = gc_new();
+
+    if (!key_inline)
+    {
+        key_pem = buffer_read_from_file(key_file, &gc);
+        if (!buf_valid(&key_pem))
+        {
+            msg(M_WARN, "ERROR: failed to read %s file (%s)",
+                pem_name, key_file);
+            goto cleanup;
+        }
+    }
+    else
+    {
+        buf_set_read(&key_pem, (const void *)key_file, strlen(key_file) + 1);
+    }
+
+    if (!crypto_pem_decode(pem_name, key, &key_pem))
+    {
+        msg(M_WARN, "ERROR: %s pem decode failed", pem_name);
+        goto cleanup;
+    }
+
+    ret = true;
+cleanup:
+    if (!key_inline)
+    {
+        buf_clear(&key_pem);
+    }
+    gc_free(&gc);
+    return ret;
+}
diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h
index 185bfd3..999f643 100644
--- a/src/openvpn/crypto.h
+++ b/src/openvpn/crypto.h
@@ -38,8 +38,7 @@
  *  - \b HMAC, covering the ciphertext IV + ciphertext. The HMAC size depends
  *    on the \c \-\-auth option. If \c \-\-auth \c none is specified, there is no
  *    HMAC at all.
- *  - \b Ciphertext \b IV, if not disabled by \c \-\-no-iv. The IV size depends on
- *    the \c \-\-cipher option.
+ *  - \b Ciphertext \b IV. The IV size depends on the \c \-\-cipher option.
  *  - \b Packet \b ID, a 32-bit incrementing packet counter that provides replay
  *    protection (if not disabled by \c \-\-no-replay).
  *  - \b Timestamp, a 32-bit timestamp of the current time.
@@ -123,8 +122,6 @@
 #ifndef CRYPTO_H
 #define CRYPTO_H
 
-#ifdef ENABLE_CRYPTO
-
 #include "crypto_backend.h"
 #include "basic.h"
 #include "buffer.h"
@@ -248,17 +245,13 @@
 #define CO_PACKET_ID_LONG_FORM  (1<<0)
     /**< Bit-flag indicating whether to use
     *   OpenVPN's long packet ID format. */
-#define CO_USE_IV               (1<<1)
-    /**< Bit-flag indicating whether to
-     *   generate a pseudo-random IV for each
-     *   packet being encrypted. */
-#define CO_IGNORE_PACKET_ID     (1<<2)
+#define CO_IGNORE_PACKET_ID     (1<<1)
     /**< Bit-flag indicating whether to ignore
      *   the packet ID of a received packet.
      *   This flag is used during processing
      *   of the first packet received from a
      *   client. */
-#define CO_MUTE_REPLAY_WARNINGS (1<<3)
+#define CO_MUTE_REPLAY_WARNINGS (1<<2)
     /**< Bit-flag indicating not to display
      *   replay warnings. */
     unsigned int flags;         /**< Bit-flags determining behavior of
@@ -278,16 +271,16 @@
 #define RKF_INLINE       (1<<1)
 void read_key_file(struct key2 *key2, const char *file, const unsigned int flags);
 
+/**
+ * Write nkeys 1024-bits keys to file.
+ *
+ * @returns number of random bits written, or -1 on failure.
+ */
 int write_key_file(const int nkeys, const char *filename);
 
-int read_passphrase_hash(const char *passphrase_file,
-                         const md_kt_t *digest,
-                         uint8_t *output,
-                         int len);
-
 void generate_key_random(struct key *key, const struct key_type *kt);
 
-void check_replay_iv_consistency(const struct key_type *kt, bool packet_id, bool use_iv);
+void check_replay_consistency(const struct key_type *kt, bool packet_id);
 
 bool check_key(struct key *key, const struct key_type *kt);
 
@@ -306,7 +299,7 @@
  * @param authname    The name of the HMAC digest to use
  * @param keysize     The length of the cipher key to use, in bytes.  Only valid
  *                    for ciphers that support variable length keys.
- * @param tls_mode    Specifies wether we are running in TLS mode, which allows
+ * @param tls_mode    Specifies whether we are running in TLS mode, which allows
  *                    more ciphers than static key mode.
  * @param warn        Print warnings when null cipher / auth is used.
  */
@@ -325,7 +318,7 @@
 
 void init_key_ctx_bi(struct key_ctx_bi *ctx, const struct key2 *key2,
                      int key_direction, const struct key_type *kt,
-		     const char *name);
+                     const char *name);
 
 void free_key_ctx_bi(struct key_ctx_bi *ctx);
 
@@ -421,13 +414,46 @@
 /** Calculate crypto overhead and adjust frame to account for that */
 void crypto_adjust_frame_parameters(struct frame *frame,
                                     const struct key_type *kt,
-                                    bool use_iv,
                                     bool packet_id,
                                     bool packet_id_long_form);
 
 /** Return the worst-case OpenVPN crypto overhead (in bytes) */
 unsigned int crypto_max_overhead(void);
 
+/**
+ * Generate a server key with enough randomness to fill a key struct
+ * and write to file.
+ *
+ * @param filename          Filename of the server key file to create.
+ * @param pem_name          The name to use in the PEM header/footer.
+ */
+void
+write_pem_key_file(const char *filename, const char *key_name);
+
+/**
+ * Generate ephermal key material into the key structure
+ *
+ * @param key           the key structure that will hold the key material
+ * @param pem_name      the name used for logging
+ * @return              true if key generation was successful
+ */
+bool
+generate_ephemeral_key(struct buffer *key, const char *pem_name);
+
+/**
+ * Read key material from a PEM encoded files into the key structure
+ * @param key           the key structure that will hold the key material
+ * @param pem_name      the name used in the pem encoding start/end lines
+ * @param key_file      name of the file to read or the key itself if
+ *                      key_inline is true
+ * @param key_inline    True if key_file contains an inline key, False
+ *                      otherwise.
+ * @return              true if reading into key was successful
+ */
+bool
+read_pem_key_file(struct buffer *key, const char *pem_name,
+                  const char *key_file, bool key_inline);
+
 /* Minimum length of the nonce used by the PRNG */
 #define NONCE_SECRET_LEN_MIN 16
 
@@ -465,6 +491,12 @@
 
 void prng_uninit(void);
 
+/* an analogue to the random() function, but use prng_bytes */
+long int get_random(void);
+
+/** Print a cipher list entry */
+void print_cipher(const cipher_kt_t *cipher);
+
 void test_crypto(struct crypto_options *co, struct frame *f);
 
 
@@ -487,8 +519,9 @@
                 const char *prefix1);
 
 void crypto_read_openvpn_key(const struct key_type *key_type,
-                             struct key_ctx_bi *ctx, const char *key_file, const char *key_inline,
-                             const int key_direction, const char *key_name, const char *opt_name);
+                             struct key_ctx_bi *ctx, const char *key_file,
+                             bool key_inline, const int key_direction,
+                             const char *key_name, const char *opt_name);
 
 /*
  * Inline functions
@@ -498,20 +531,7 @@
  * As memcmp(), but constant-time.
  * Returns 0 when data is equal, non-zero otherwise.
  */
-static inline int
-memcmp_constant_time(const void *a, const void *b, size_t size)
-{
-    const uint8_t *a1 = a;
-    const uint8_t *b1 = b;
-    int ret = 0;
-    size_t i;
-
-    for (i = 0; i < size; i++) {
-        ret |= *a1++ ^ *b1++;
-    }
-
-    return ret;
-}
+int memcmp_constant_time(const void *a, const void *b, size_t size);
 
 static inline bool
 key_ctx_bi_defined(const struct key_ctx_bi *key)
@@ -519,6 +539,16 @@
     return key->encrypt.cipher || key->encrypt.hmac || key->decrypt.cipher || key->decrypt.hmac;
 }
 
+/**
+ * To be used when printing a string that may contain inline data.
+ *
+ * If "is_inline" is true, return the inline tag.
+ * If "is_inline" is false and "str" is not NULL, return "str".
+ * Return the constant string "[NULL]" otherwise.
+ *
+ * @param str       the original string to return when is_inline is false
+ * @param is_inline true when str contains an inline data of some sort
+ */
+const char *print_key_filename(const char *str, bool is_inline);
 
-#endif /* ENABLE_CRYPTO */
 #endif /* CRYPTO_H */
diff --git a/src/openvpn/crypto_backend.h b/src/openvpn/crypto_backend.h
index b3db925..85cb084 100644
--- a/src/openvpn/crypto_backend.h
+++ b/src/openvpn/crypto_backend.h
@@ -36,6 +36,7 @@
 #include "crypto_mbedtls.h"
 #endif
 #include "basic.h"
+#include "buffer.h"
 
 /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */
 #define OPENVPN_AEAD_TAG_LENGTH 16
@@ -50,7 +51,7 @@
 typedef enum {
     MD_SHA1,
     MD_SHA256
-} hash_algo_type ;
+} hash_algo_type;
 
 /** Struct used in cipher name translation table */
 typedef struct {
@@ -105,6 +106,34 @@
 
 void show_available_engines(void);
 
+/**
+ * Encode binary data as PEM.
+ *
+ * @param name      The name to use in the PEM header/footer.
+ * @param dst       Destination buffer for PEM-encoded data.  Must be a valid
+ *                  pointer to an uninitialized buffer structure.  Iff this
+ *                  function returns true, the buffer will contain memory
+ *                  allocated through the supplied gc.
+ * @param src       Source buffer.
+ * @param gc        The garbage collector to use when allocating memory for dst.
+ *
+ * @return true iff PEM encode succeeded.
+ */
+bool crypto_pem_encode(const char *name, struct buffer *dst,
+                       const struct buffer *src, struct gc_arena *gc);
+
+/**
+ * Decode a PEM buffer to binary data.
+ *
+ * @param name      The name expected in the PEM header/footer.
+ * @param dst       Destination buffer for decoded data.
+ * @param src       Source buffer (PEM data).
+ *
+ * @return true iff PEM decode succeeded.
+ */
+bool crypto_pem_decode(const char *name, struct buffer *dst,
+                       const struct buffer *src);
+
 /*
  *
  * Random number functions, used in cases where we want
@@ -198,7 +227,8 @@
  * initialise encryption/decryption.
  *
  * @param ciphername    Name of the cipher to retrieve parameters for (e.g.
- *                      \c AES-128-CBC).
+ *                      \c AES-128-CBC). Will be translated to the library name
+ *                      from the openvpn config name if needed.
  *
  * @return              A statically allocated structure containing parameters
  *                      for the given cipher, or NULL if no matching parameters
@@ -208,6 +238,8 @@
 
 /**
  * Retrieve a string describing the cipher (e.g. \c AES-128-CBC).
+ * The returned name is normalised to the OpenVPN config name in case the
+ * name differs from the name used by the crypto library.
  *
  * @param cipher_kt     Static cipher parameters
  *
@@ -256,6 +288,11 @@
 int cipher_kt_tag_size(const cipher_kt_t *cipher_kt);
 
 /**
+ * Returns true if we consider this cipher to be insecure.
+ */
+bool cipher_kt_insecure(const cipher_kt_t *cipher);
+
+/**
  * Returns the mode that the cipher runs in.
  *
  * @param cipher_kt     Static cipher parameters. May not be NULL.
@@ -384,7 +421,7 @@
  *
  * @return              \c 0 on failure, \c 1 on success.
  */
-int cipher_ctx_reset(cipher_ctx_t *ctx, uint8_t *iv_buf);
+int cipher_ctx_reset(cipher_ctx_t *ctx, const uint8_t *iv_buf);
 
 /**
  * Updates the given cipher context, providing additional data (AD) for
@@ -492,7 +529,7 @@
  *
  * @return              Message digest size, in bytes, or 0 if ctx was NULL.
  */
-int md_kt_size(const md_kt_t *kt);
+unsigned char md_kt_size(const md_kt_t *kt);
 
 
 /*
@@ -593,7 +630,7 @@
  * Initialises the given HMAC context, using the given digest
  * and key.
  *
- * @param ctx           HMAC context to intialise
+ * @param ctx           HMAC context to initialise
  * @param key           The key to use for the HMAC
  * @param key_len       The key length to use
  * @param kt            Static message digest parameters
diff --git a/src/openvpn/crypto_mbedtls.c b/src/openvpn/crypto_mbedtls.c
index 748043e..fbb1f12 100644
--- a/src/openvpn/crypto_mbedtls.c
+++ b/src/openvpn/crypto_mbedtls.c
@@ -34,21 +34,24 @@
 
 #include "syshead.h"
 
-#if defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_MBEDTLS)
+#if defined(ENABLE_CRYPTO_MBEDTLS)
 
 #include "errlevel.h"
 #include "basic.h"
 #include "buffer.h"
+#include "crypto.h"
 #include "integer.h"
 #include "crypto_backend.h"
 #include "otime.h"
 #include "misc.h"
 
+#include <mbedtls/base64.h>
 #include <mbedtls/des.h>
 #include <mbedtls/error.h>
 #include <mbedtls/md5.h>
 #include <mbedtls/cipher.h>
 #include <mbedtls/havege.h>
+#include <mbedtls/pem.h>
 
 #include <mbedtls/entropy.h>
 
@@ -138,26 +141,6 @@
 const size_t cipher_name_translation_table_count =
     sizeof(cipher_name_translation_table) / sizeof(*cipher_name_translation_table);
 
-static void
-print_cipher(const cipher_kt_t *info)
-{
-    if (info && (cipher_kt_mode_cbc(info)
-#ifdef HAVE_AEAD_CIPHER_MODES
-                 || cipher_kt_mode_aead(info)
-#endif
-                 ))
-    {
-        const char *ssl_only = cipher_kt_mode_cbc(info) ?
-                               "" : ", TLS client/server mode only";
-        const char *var_key_size = info->flags & MBEDTLS_CIPHER_VARIABLE_KEY_LEN ?
-                                   " by default" : "";
-
-        printf("%s  (%d bit key%s, %d bit block%s)\n",
-               cipher_kt_name(info), cipher_kt_key_size(info) * 8, var_key_size,
-               cipher_kt_block_size(info) * 8, ssl_only);
-    }
-}
-
 void
 show_available_ciphers(void)
 {
@@ -166,14 +149,16 @@
 #ifndef ENABLE_SMALL
     printf("The following ciphers and cipher modes are available for use\n"
            "with " PACKAGE_NAME ".  Each cipher shown below may be used as a\n"
-           "parameter to the --cipher option.  Using a CBC or GCM mode is\n"
-           "recommended.  In static key mode only CBC mode is allowed.\n\n");
+           "parameter to the --data-ciphers (or --cipher) option.  Using a\n"
+           "GCM or CBC mode is recommended.  In static key mode only CBC\n"
+           "mode is allowed.\n\n");
 #endif
 
     while (*ciphers != 0)
     {
         const cipher_kt_t *info = mbedtls_cipher_info_from_type(*ciphers);
-        if (info && cipher_kt_block_size(info) >= 128/8)
+        if (info && !cipher_kt_insecure(info)
+            && (cipher_kt_mode_aead(info) || cipher_kt_mode_cbc(info)))
         {
             print_cipher(info);
         }
@@ -186,7 +171,8 @@
     while (*ciphers != 0)
     {
         const cipher_kt_t *info = mbedtls_cipher_info_from_type(*ciphers);
-        if (info && cipher_kt_block_size(info) < 128/8)
+        if (info && cipher_kt_insecure(info)
+            && (cipher_kt_mode_aead(info) || cipher_kt_mode_cbc(info)))
         {
             print_cipher(info);
         }
@@ -229,6 +215,84 @@
            "available\n");
 }
 
+bool
+crypto_pem_encode(const char *name, struct buffer *dst,
+                  const struct buffer *src, struct gc_arena *gc)
+{
+    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */
+    char header[1000+1] = { 0 };
+    char footer[1000+1] = { 0 };
+
+    if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----\n", name))
+    {
+        return false;
+    }
+    if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----\n", name))
+    {
+        return false;
+    }
+
+    size_t out_len = 0;
+    if (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL !=
+        mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src),
+                                 NULL, 0, &out_len))
+    {
+        return false;
+    }
+
+    /* We set the size buf to out_len-1 to NOT include the 0 byte that
+     * mbedtls_pem_write_buffer in its length calculation */
+    *dst = alloc_buf_gc(out_len, gc);
+    if (!mbed_ok(mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src),
+                                          BPTR(dst), BCAP(dst), &out_len))
+        || !buf_inc_len(dst, out_len-1))
+    {
+        CLEAR(*dst);
+        return false;
+    }
+
+    return true;
+}
+
+bool
+crypto_pem_decode(const char *name, struct buffer *dst,
+                  const struct buffer *src)
+{
+    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */
+    char header[1000+1] = { 0 };
+    char footer[1000+1] = { 0 };
+
+    if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----", name))
+    {
+        return false;
+    }
+    if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----", name))
+    {
+        return false;
+    }
+
+    /* mbed TLS requires the src to be null-terminated */
+    /* allocate a new buffer to avoid modifying the src buffer */
+    struct gc_arena gc = gc_new();
+    struct buffer input = alloc_buf_gc(BLEN(src) + 1, &gc);
+    buf_copy(&input, src);
+    buf_null_terminate(&input);
+
+    size_t use_len = 0;
+    mbedtls_pem_context ctx = { 0 };
+    bool ret = mbed_ok(mbedtls_pem_read_buffer(&ctx, header, footer, BPTR(&input),
+                                               NULL, 0, &use_len));
+    if (ret && !buf_write(dst, ctx.buf, ctx.buflen))
+    {
+        ret = false;
+        msg(M_WARN, "PEM decode error: destination buffer too small");
+    }
+
+    mbedtls_pem_free(&ctx);
+    gc_free(&gc);
+    return ret;
+}
+
 /*
  *
  * Random number functions, used in cases where we want
@@ -402,6 +466,7 @@
 
     ASSERT(ciphername);
 
+    ciphername = translate_cipher_name_from_openvpn(ciphername);
     cipher = mbedtls_cipher_info_from_string(ciphername);
 
     if (NULL == cipher)
@@ -466,15 +531,23 @@
 int
 cipher_kt_tag_size(const mbedtls_cipher_info_t *cipher_kt)
 {
-#ifdef HAVE_AEAD_CIPHER_MODES
     if (cipher_kt && cipher_kt_mode_aead(cipher_kt))
     {
         return OPENVPN_AEAD_TAG_LENGTH;
     }
-#endif
     return 0;
 }
 
+bool
+cipher_kt_insecure(const mbedtls_cipher_info_t *cipher_kt)
+{
+    return !(cipher_kt_block_size(cipher_kt) >= 128 / 8
+#ifdef MBEDTLS_CHACHAPOLY_C
+             || cipher_kt->type == MBEDTLS_CIPHER_CHACHA20_POLY1305
+#endif
+             );
+}
+
 int
 cipher_kt_mode(const mbedtls_cipher_info_t *cipher_kt)
 {
@@ -498,7 +571,11 @@
 bool
 cipher_kt_mode_aead(const cipher_kt_t *cipher)
 {
-    return cipher && cipher_kt_mode(cipher) == OPENVPN_MODE_GCM;
+    return cipher && (cipher_kt_mode(cipher) == OPENVPN_MODE_GCM
+#ifdef MBEDTLS_CHACHAPOLY_C
+                      || cipher_kt_mode(cipher) == MBEDTLS_MODE_CHACHAPOLY
+#endif
+                      );
 }
 
 
@@ -554,7 +631,6 @@
 int
 cipher_ctx_get_tag(cipher_ctx_t *ctx, uint8_t *tag, int tag_len)
 {
-#ifdef HAVE_AEAD_CIPHER_MODES
     if (tag_len > SIZE_MAX)
     {
         return 0;
@@ -566,9 +642,6 @@
     }
 
     return 1;
-#else  /* ifdef HAVE_AEAD_CIPHER_MODES */
-    ASSERT(0);
-#endif /* HAVE_AEAD_CIPHER_MODES */
 }
 
 int
@@ -592,7 +665,7 @@
 }
 
 int
-cipher_ctx_reset(mbedtls_cipher_context_t *ctx, uint8_t *iv_buf)
+cipher_ctx_reset(mbedtls_cipher_context_t *ctx, const uint8_t *iv_buf)
 {
     if (!mbed_ok(mbedtls_cipher_reset(ctx)))
     {
@@ -610,7 +683,6 @@
 int
 cipher_ctx_update_ad(cipher_ctx_t *ctx, const uint8_t *src, int src_len)
 {
-#ifdef HAVE_AEAD_CIPHER_MODES
     if (src_len > SIZE_MAX)
     {
         return 0;
@@ -622,9 +694,6 @@
     }
 
     return 1;
-#else  /* ifdef HAVE_AEAD_CIPHER_MODES */
-    ASSERT(0);
-#endif /* HAVE_AEAD_CIPHER_MODES */
 }
 
 int
@@ -663,7 +732,6 @@
 cipher_ctx_final_check_tag(mbedtls_cipher_context_t *ctx, uint8_t *dst,
                            int *dst_len, uint8_t *tag, size_t tag_len)
 {
-#ifdef HAVE_AEAD_CIPHER_MODES
     size_t olen = 0;
 
     if (MBEDTLS_DECRYPT != ctx->operation)
@@ -695,9 +763,6 @@
     }
 
     return 1;
-#else  /* ifdef HAVE_AEAD_CIPHER_MODES */
-    ASSERT(0);
-#endif /* HAVE_AEAD_CIPHER_MODES */
 }
 
 void
@@ -751,7 +816,7 @@
     return mbedtls_md_get_name(kt);
 }
 
-int
+unsigned char
 md_kt_size(const mbedtls_md_info_t *kt)
 {
     if (NULL == kt)
@@ -781,7 +846,8 @@
     return ctx;
 }
 
-void md_ctx_free(mbedtls_md_context_t *ctx)
+void
+md_ctx_free(mbedtls_md_context_t *ctx)
 {
     free(ctx);
 }
@@ -899,4 +965,23 @@
     ASSERT(0 == mbedtls_md_hmac_finish(ctx, dst));
 }
 
-#endif /* ENABLE_CRYPTO && ENABLE_CRYPTO_MBEDTLS */
+int
+memcmp_constant_time(const void *a, const void *b, size_t size)
+{
+    /* mbed TLS has a no const time memcmp function that it exposes
+     * via its APIs like OpenSSL does with CRYPTO_memcmp
+     * Adapt the function that mbedtls itself uses in
+     * mbedtls_safer_memcmp as it considers that to be safe */
+    volatile const unsigned char *A = (volatile const unsigned char *) a;
+    volatile const unsigned char *B = (volatile const unsigned char *) b;
+    volatile unsigned char diff = 0;
+
+    for (size_t i = 0; i < size; i++)
+    {
+        unsigned char x = A[i], y = B[i];
+        diff |= x ^ y;
+    }
+
+    return diff;
+}
+#endif /* ENABLE_CRYPTO_MBEDTLS */
diff --git a/src/openvpn/crypto_mbedtls.h b/src/openvpn/crypto_mbedtls.h
index 452b06e..c4b13b7 100644
--- a/src/openvpn/crypto_mbedtls.h
+++ b/src/openvpn/crypto_mbedtls.h
@@ -146,5 +146,10 @@
 #define mbed_ok(errval) \
     mbed_log_func_line_lite(D_CRYPT_ERRORS, errval, __func__, __LINE__)
 
+static inline bool
+cipher_kt_var_key_size(const cipher_kt_t *cipher)
+{
+    return cipher->flags & MBEDTLS_CIPHER_VARIABLE_KEY_LEN;
+}
 
 #endif /* CRYPTO_MBEDTLS_H_ */
diff --git a/src/openvpn/crypto_openssl.c b/src/openvpn/crypto_openssl.c
index 3abcc99..c60d4a5 100644
--- a/src/openvpn/crypto_openssl.c
+++ b/src/openvpn/crypto_openssl.c
@@ -34,7 +34,7 @@
 
 #include "syshead.h"
 
-#if defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_OPENSSL)
+#if defined(ENABLE_CRYPTO_OPENSSL)
 
 #include "basic.h"
 #include "buffer.h"
@@ -43,6 +43,7 @@
 #include "crypto_backend.h"
 #include "openssl_compat.h"
 
+#include <openssl/conf.h>
 #include <openssl/des.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
@@ -63,6 +64,7 @@
 #endif
 
 #if HAVE_OPENSSL_ENGINE
+#include <openssl/ui.h>
 #include <openssl/engine.h>
 
 static bool engine_initialized = false; /* GLOBAL */
@@ -148,6 +150,11 @@
 void
 crypto_init_lib(void)
 {
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL);
+#else
+    OPENSSL_config(NULL);
+#endif
     /*
      * If you build the OpenSSL library and OpenVPN with
      * CRYPTO_MDEBUG, you will get a listing of OpenSSL
@@ -202,12 +209,12 @@
         else if (ERR_GET_REASON(err) == SSL_R_UNSUPPORTED_PROTOCOL)
         {
             msg(D_CRYPT_ERRORS, "TLS error: Unsupported protocol. This typically "
-                 "indicates that client and server have no common TLS version enabled. "
-                 "This can be caused by mismatched tls-version-min and tls-version-max "
-                 "options on client and server. "
-                 "If your OpenVPN client is between v2.3.6 and v2.3.2 try adding "
-                 "tls-version-min 1.0 to the client configuration to use TLS 1.0+ "
-                 "instead of TLS 1.0 only");
+                "indicates that client and server have no common TLS version enabled. "
+                "This can be caused by mismatched tls-version-min and tls-version-max "
+                "options on client and server. "
+                "If your OpenVPN client is between v2.3.6 and v2.3.2 try adding "
+                "tls-version-min 1.0 to the client configuration to use TLS 1.0+ "
+                "instead of TLS 1.0 only");
         }
         msg(flags, "OpenSSL: %s", ERR_error_string(err, NULL));
     }
@@ -254,6 +261,7 @@
     { "AES-128-GCM", "id-aes128-GCM" },
     { "AES-192-GCM", "id-aes192-GCM" },
     { "AES-256-GCM", "id-aes256-GCM" },
+    { "CHACHA20-POLY1305", "ChaCha20-Poly1305" },
 };
 const size_t cipher_name_translation_table_count =
     sizeof(cipher_name_translation_table) / sizeof(*cipher_name_translation_table);
@@ -265,27 +273,7 @@
     const EVP_CIPHER *const *cipher_a = a;
     const EVP_CIPHER *const *cipher_b = b;
 
-    const char *cipher_name_a =
-        translate_cipher_name_to_openvpn(EVP_CIPHER_name(*cipher_a));
-    const char *cipher_name_b =
-        translate_cipher_name_to_openvpn(EVP_CIPHER_name(*cipher_b));
-
-    return strcmp(cipher_name_a, cipher_name_b);
-}
-
-static void
-print_cipher(const EVP_CIPHER *cipher)
-{
-    const char *var_key_size =
-        (EVP_CIPHER_flags(cipher) & EVP_CIPH_VARIABLE_LENGTH) ?
-        " by default" : "";
-    const char *ssl_only = cipher_kt_mode_cbc(cipher) ?
-                           "" : ", TLS client/server mode only";
-
-    printf("%s  (%d bit key%s, %d bit block%s)\n",
-           translate_cipher_name_to_openvpn(EVP_CIPHER_name(cipher)),
-           EVP_CIPHER_key_length(cipher) * 8, var_key_size,
-           cipher_kt_block_size(cipher) * 8, ssl_only);
+    return strcmp(cipher_kt_name(*cipher_a), cipher_kt_name(*cipher_b));
 }
 
 void
@@ -299,11 +287,11 @@
     size_t num_ciphers = 0;
 #ifndef ENABLE_SMALL
     printf("The following ciphers and cipher modes are available for use\n"
-           "with " PACKAGE_NAME ".  Each cipher shown below may be use as a\n"
-           "parameter to the --cipher option.  The default key size is\n"
-           "shown as well as whether or not it can be changed with the\n"
-           "--keysize directive.  Using a CBC or GCM mode is recommended.\n"
-           "In static key mode only CBC mode is allowed.\n\n");
+           "with " PACKAGE_NAME ".  Each cipher shown below may be used as a\n"
+           "parameter to the --data-ciphers (or --cipher) option.  The\n"
+           "default key size is shown as well as whether or not it can be\n"
+           "changed with the --keysize directive.  Using a GCM or CBC mode\n"
+           "is recommended.  In static key mode only CBC mode is allowed.\n\n");
 #endif
 
     for (nid = 0; nid < 10000; ++nid)
@@ -313,9 +301,7 @@
 #ifdef ENABLE_OFB_CFB_MODE
                        || cipher_kt_mode_ofb_cfb(cipher)
 #endif
-#ifdef HAVE_AEAD_CIPHER_MODES
                        || cipher_kt_mode_aead(cipher)
-#endif
                        ))
         {
             cipher_list[num_ciphers++] = cipher;
@@ -327,10 +313,12 @@
         }
     }
 
-    qsort(cipher_list, num_ciphers, sizeof(*cipher_list), cipher_name_cmp);
+    /* cast to non-const to prevent warning */
+    qsort((EVP_CIPHER *)cipher_list, num_ciphers, sizeof(*cipher_list), cipher_name_cmp);
 
-    for (i = 0; i < num_ciphers; i++) {
-        if (cipher_kt_block_size(cipher_list[i]) >= 128/8)
+    for (i = 0; i < num_ciphers; i++)
+    {
+        if (!cipher_kt_insecure(cipher_list[i]))
         {
             print_cipher(cipher_list[i]);
         }
@@ -338,8 +326,9 @@
 
     printf("\nThe following ciphers have a block size of less than 128 bits, \n"
            "and are therefore deprecated.  Do not use unless you have to.\n\n");
-    for (i = 0; i < num_ciphers; i++) {
-        if (cipher_kt_block_size(cipher_list[i]) < 128/8)
+    for (i = 0; i < num_ciphers; i++)
+    {
+        if (cipher_kt_insecure(cipher_list[i]))
         {
             print_cipher(cipher_list[i]);
         }
@@ -396,6 +385,88 @@
 #endif
 }
 
+
+bool
+crypto_pem_encode(const char *name, struct buffer *dst,
+                  const struct buffer *src, struct gc_arena *gc)
+{
+    bool ret = false;
+    BIO *bio = BIO_new(BIO_s_mem());
+    if (!bio || !PEM_write_bio(bio, name, "", BPTR(src), BLEN(src)))
+    {
+        ret = false;
+        goto cleanup;
+    }
+
+    BUF_MEM *bptr;
+    BIO_get_mem_ptr(bio, &bptr);
+
+    *dst = alloc_buf_gc(bptr->length, gc);
+    ASSERT(buf_write(dst, bptr->data, bptr->length));
+
+    ret = true;
+cleanup:
+    if (!BIO_free(bio))
+    {
+        ret = false;
+    }
+
+    return ret;
+}
+
+bool
+crypto_pem_decode(const char *name, struct buffer *dst,
+                  const struct buffer *src)
+{
+    bool ret = false;
+
+    BIO *bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src));
+    if (!bio)
+    {
+        crypto_msg(M_FATAL, "Cannot open memory BIO for PEM decode");
+    }
+
+    char *name_read = NULL;
+    char *header_read = NULL;
+    uint8_t *data_read = NULL;
+    long data_read_len = 0;
+    if (!PEM_read_bio(bio, &name_read, &header_read, &data_read,
+                      &data_read_len))
+    {
+        dmsg(D_CRYPT_ERRORS, "%s: PEM decode failed", __func__);
+        goto cleanup;
+    }
+
+    if (strcmp(name, name_read))
+    {
+        dmsg(D_CRYPT_ERRORS,
+             "%s: unexpected PEM name (got '%s', expected '%s')",
+             __func__, name_read, name);
+        goto cleanup;
+    }
+
+    uint8_t *dst_data = buf_write_alloc(dst, data_read_len);
+    if (!dst_data)
+    {
+        dmsg(D_CRYPT_ERRORS, "%s: dst too small (%i, needs %li)", __func__,
+             BCAP(dst), data_read_len);
+        goto cleanup;
+    }
+    memcpy(dst_data, data_read, data_read_len);
+
+    ret = true;
+cleanup:
+    OPENSSL_free(name_read);
+    OPENSSL_free(header_read);
+    OPENSSL_free(data_read);
+    if (!BIO_free(bio))
+    {
+        ret = false;
+    }
+
+    return ret;
+}
+
 /*
  *
  * Random number functions, used in cases where we want
@@ -515,6 +586,7 @@
 
     ASSERT(ciphername);
 
+    ciphername = translate_cipher_name_from_openvpn(ciphername);
     cipher = EVP_get_cipherbyname(ciphername);
 
     if (NULL == cipher)
@@ -543,7 +615,9 @@
     {
         return "[null-cipher]";
     }
-    return EVP_CIPHER_name(cipher_kt);
+
+    const char *name = EVP_CIPHER_name(cipher_kt);
+    return translate_cipher_name_to_openvpn(name);
 }
 
 int
@@ -574,7 +648,7 @@
 
     int block_size = EVP_CIPHER_block_size(cipher);
 
-    orig_name = cipher_kt_name(cipher);
+    orig_name = EVP_CIPHER_name(cipher);
     if (!orig_name)
     {
         goto cleanup;
@@ -613,6 +687,16 @@
     }
 }
 
+bool
+cipher_kt_insecure(const EVP_CIPHER *cipher)
+{
+    return !(cipher_kt_block_size(cipher) >= 128 / 8
+#ifdef NID_chacha20_poly1305
+             || EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305
+#endif
+             );
+}
+
 int
 cipher_kt_mode(const EVP_CIPHER *cipher_kt)
 {
@@ -624,11 +708,8 @@
 cipher_kt_mode_cbc(const cipher_kt_t *cipher)
 {
     return cipher && cipher_kt_mode(cipher) == OPENVPN_MODE_CBC
-#ifdef EVP_CIPH_FLAG_AEAD_CIPHER
            /* Exclude AEAD cipher modes, they require a different API */
-           && !(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_AEAD_CIPHER)
-#endif
-    ;
+           && !(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_AEAD_CIPHER);
 }
 
 bool
@@ -636,21 +717,28 @@
 {
     return cipher && (cipher_kt_mode(cipher) == OPENVPN_MODE_OFB
                       || cipher_kt_mode(cipher) == OPENVPN_MODE_CFB)
-#ifdef EVP_CIPH_FLAG_AEAD_CIPHER
            /* Exclude AEAD cipher modes, they require a different API */
-           && !(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_AEAD_CIPHER)
-#endif
-    ;
+           && !(EVP_CIPHER_flags(cipher) & EVP_CIPH_FLAG_AEAD_CIPHER);
 }
 
 bool
 cipher_kt_mode_aead(const cipher_kt_t *cipher)
 {
-#ifdef HAVE_AEAD_CIPHER_MODES
-    return cipher && (cipher_kt_mode(cipher) == OPENVPN_MODE_GCM);
-#else
-    return false;
+    if (cipher)
+    {
+        switch (EVP_CIPHER_nid(cipher))
+        {
+            case NID_aes_128_gcm:
+            case NID_aes_192_gcm:
+            case NID_aes_256_gcm:
+#ifdef NID_chacha20_poly1305
+            case NID_chacha20_poly1305:
 #endif
+                return true;
+        }
+    }
+
+    return false;
 }
 
 /*
@@ -708,11 +796,7 @@
 int
 cipher_ctx_get_tag(EVP_CIPHER_CTX *ctx, uint8_t *tag_buf, int tag_size)
 {
-#ifdef HAVE_AEAD_CIPHER_MODES
     return EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, tag_size, tag_buf);
-#else
-    ASSERT(0);
-#endif
 }
 
 int
@@ -735,7 +819,7 @@
 
 
 int
-cipher_ctx_reset(EVP_CIPHER_CTX *ctx, uint8_t *iv_buf)
+cipher_ctx_reset(EVP_CIPHER_CTX *ctx, const uint8_t *iv_buf)
 {
     return EVP_CipherInit_ex(ctx, NULL, NULL, NULL, iv_buf, -1);
 }
@@ -743,16 +827,12 @@
 int
 cipher_ctx_update_ad(EVP_CIPHER_CTX *ctx, const uint8_t *src, int src_len)
 {
-#ifdef HAVE_AEAD_CIPHER_MODES
     int len;
     if (!EVP_CipherUpdate(ctx, NULL, &len, src, src_len))
     {
         crypto_msg(M_FATAL, "%s: EVP_CipherUpdate() failed", __func__);
     }
     return 1;
-#else  /* ifdef HAVE_AEAD_CIPHER_MODES */
-    ASSERT(0);
-#endif
 }
 
 int
@@ -776,7 +856,6 @@
 cipher_ctx_final_check_tag(EVP_CIPHER_CTX *ctx, uint8_t *dst, int *dst_len,
                            uint8_t *tag, size_t tag_len)
 {
-#ifdef HAVE_AEAD_CIPHER_MODES
     ASSERT(tag_len < SIZE_MAX);
     if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tag_len, tag))
     {
@@ -784,9 +863,6 @@
     }
 
     return cipher_ctx_final(ctx, dst, dst_len);
-#else  /* ifdef HAVE_AEAD_CIPHER_MODES */
-    ASSERT(0);
-#endif
 }
 
 void
@@ -837,10 +913,10 @@
     return EVP_MD_name(kt);
 }
 
-int
+unsigned char
 md_kt_size(const EVP_MD *kt)
 {
-    return EVP_MD_size(kt);
+    return (unsigned char)EVP_MD_size(kt);
 }
 
 
@@ -866,7 +942,8 @@
     return ctx;
 }
 
-void md_ctx_free(EVP_MD_CTX *ctx)
+void
+md_ctx_free(EVP_MD_CTX *ctx)
 {
     EVP_MD_CTX_free(ctx);
 }
@@ -972,4 +1049,70 @@
     HMAC_Final(ctx, dst, &in_hmac_len);
 }
 
-#endif /* ENABLE_CRYPTO && ENABLE_CRYPTO_OPENSSL */
+int
+memcmp_constant_time(const void *a, const void *b, size_t size)
+{
+    return CRYPTO_memcmp(a, b, size);
+}
+
+#if HAVE_OPENSSL_ENGINE
+static int
+ui_reader(UI *ui, UI_STRING *uis)
+{
+    SSL_CTX *ctx = UI_get0_user_data(ui);
+
+    if (UI_get_string_type(uis) == UIT_PROMPT)
+    {
+        pem_password_cb *cb = SSL_CTX_get_default_passwd_cb(ctx);
+        void *d = SSL_CTX_get_default_passwd_cb_userdata(ctx);
+        char password[64];
+
+        cb(password, sizeof(password), 0, d);
+        UI_set_result(ui, uis, password);
+
+        return 1;
+    }
+    return 0;
+}
+#endif
+
+EVP_PKEY *
+engine_load_key(const char *file, SSL_CTX *ctx)
+{
+#if HAVE_OPENSSL_ENGINE
+    UI_METHOD *ui;
+    EVP_PKEY *pkey;
+
+    if (!engine_persist)
+    {
+        return NULL;
+    }
+
+    /* this will print out the error from BIO_read */
+    crypto_msg(M_INFO, "PEM_read_bio failed, now trying engine method to load private key");
+
+    ui = UI_create_method("openvpn");
+    if (!ui)
+    {
+        crypto_msg(M_FATAL, "Engine UI creation failed");
+        return NULL;
+    }
+
+    UI_method_set_reader(ui, ui_reader);
+
+    ENGINE_init(engine_persist);
+    pkey = ENGINE_load_private_key(engine_persist, file, ui, ctx);
+    ENGINE_finish(engine_persist);
+    if (!pkey)
+    {
+        crypto_msg(M_FATAL, "Engine could not load key file");
+    }
+
+    UI_destroy_method(ui);
+    return pkey;
+#else  /* if HAVE_OPENSSL_ENGINE */
+    return NULL;
+#endif /* if HAVE_OPENSSL_ENGINE */
+}
+
+#endif /* ENABLE_CRYPTO_OPENSSL */
diff --git a/src/openvpn/crypto_openssl.h b/src/openvpn/crypto_openssl.h
index 0a41370..e6f8f53 100644
--- a/src/openvpn/crypto_openssl.h
+++ b/src/openvpn/crypto_openssl.h
@@ -61,13 +61,9 @@
 /** Cipher is in CFB mode */
 #define OPENVPN_MODE_CFB        EVP_CIPH_CFB_MODE
 
-#ifdef HAVE_AEAD_CIPHER_MODES
-
 /** Cipher is in GCM mode */
 #define OPENVPN_MODE_GCM        EVP_CIPH_GCM_MODE
 
-#endif /* HAVE_AEAD_CIPHER_MODES */
-
 /** Cipher should encrypt */
 #define OPENVPN_OP_ENCRYPT      1
 
@@ -101,5 +97,22 @@
         msg((flags), __VA_ARGS__); \
     } while (false)
 
+static inline bool
+cipher_kt_var_key_size(const cipher_kt_t *cipher)
+{
+    return EVP_CIPHER_flags(cipher) & EVP_CIPH_VARIABLE_LENGTH;
+}
+
+/**
+ * Load a key file from an engine
+ *
+ * @param file  The engine file to load
+ * @param ui    The UI method for the password prompt
+ * @param data  The data to pass to the UI method
+ *
+ * @return      The private key if successful or NULL if not
+ */
+EVP_PKEY *
+engine_load_key(const char *file, SSL_CTX *ctx);
 
 #endif /* CRYPTO_OPENSSL_H_ */
diff --git a/src/openvpn/cryptoapi.c b/src/openvpn/cryptoapi.c
index 0f95d00..6c4df9e 100644
--- a/src/openvpn/cryptoapi.c
+++ b/src/openvpn/cryptoapi.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2004 Peter 'Luna' Runestig <peter@runestig.com>
+ * Copyright (c) 2018 Selva Nair <selva.nair@gmail.com>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without modifi-
@@ -103,6 +104,9 @@
     { 0, NULL }
 };
 
+/* index for storing external data in EC_KEY: < 0 means uninitialized */
+static int ec_data_idx = -1;
+
 /* Global EVP_PKEY_METHOD used to override the sign operation */
 static EVP_PKEY_METHOD *pmethod;
 static int (*default_pkey_sign_init) (EVP_PKEY_CTX *ctx);
@@ -114,10 +118,10 @@
     HCRYPTPROV_OR_NCRYPT_KEY_HANDLE crypt_prov;
     DWORD key_spec;
     BOOL free_crypt_prov;
+    int ref_count;
 } CAPI_DATA;
 
-/**
- * Translate OpenSSL padding type to CNG padding type
+/* Translate OpenSSL padding type to CNG padding type
  * Returns 0 for unknown/unsupported padding.
  */
 static DWORD
@@ -128,7 +132,6 @@
     switch (padding)
     {
         case RSA_NO_PADDING:
-            pad = BCRYPT_PAD_NONE;
             break;
 
         case RSA_PKCS1_PADDING:
@@ -147,7 +150,7 @@
     return pad;
 }
 
-/**
+/*
  * Translate OpenSSL hash OID to CNG algorithm name. Returns
  * "UNKNOWN" for unsupported algorithms and NULL for MD5+SHA1
  * mixed hash used in TLS 1.1 and earlier.
@@ -190,6 +193,31 @@
     return alg;
 }
 
+static void
+CAPI_DATA_free(CAPI_DATA *cd)
+{
+    if (!cd || cd->ref_count-- > 0)
+    {
+        return;
+    }
+    if (cd->free_crypt_prov && cd->crypt_prov)
+    {
+        if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
+        {
+            NCryptFreeObject(cd->crypt_prov);
+        }
+        else
+        {
+            CryptReleaseContext(cd->crypt_prov, 0);
+        }
+    }
+    if (cd->cert_context)
+    {
+        CertFreeCertificateContext(cd->cert_context);
+    }
+    free(cd);
+}
+
 static char *
 ms_error_text(DWORD ms_err)
 {
@@ -211,7 +239,8 @@
         /* trim to the left */
         if (rv)
         {
-            for (p = rv + strlen(rv) - 1; p >= rv; p--) {
+            for (p = rv + strlen(rv) - 1; p >= rv; p--)
+            {
                 if (isspace(*p))
                 {
                     *p = '\0';
@@ -250,7 +279,8 @@
     }
     /* since MS error codes are 32 bit, and the ones in the ERR_... system is
      * only 12, we must have a mapping table between them.  */
-    for (i = 0; i < ERR_MAP_SZ; i++) {
+    for (i = 0; i < ERR_MAP_SZ; i++)
+    {
         if (err_map[i].ms_err == ms_err)
         {
             ERR_PUT_error(ERR_LIB_CRYPTOAPI, func, err_map[i].err, file, line);
@@ -299,7 +329,7 @@
  * Sign the hash in 'from' using NCryptSignHash(). This requires an NCRYPT
  * key handle in cd->crypt_prov. On return the signature is in 'to'. Returns
  * the length of the signature or 0 on error.
- * Only RSA is supported and padding should be BCRYPT_PAD_PKCS1 or
+ * This is used only for RSA and padding should be BCRYPT_PAD_PKCS1 or
  * BCRYPT_PAD_PSS.
  * If the hash_algo is not NULL, PKCS #1 DigestInfo header gets added
  * to |from|, else it is signed as is. Use NULL for MD5 + SHA1 hash used
@@ -363,12 +393,6 @@
         return 0;
     }
 
-    if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
-    {
-        return priv_enc_CNG(cd, NULL, from, flen, to, RSA_size(rsa),
-                            cng_padding_type(padding), 0);
-    }
-
     if (padding != RSA_PKCS1_PADDING)
     {
         /* AFAICS, CryptSignHash() *always* uses PKCS1 padding. */
@@ -376,6 +400,12 @@
         return 0;
     }
 
+    if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
+    {
+        return priv_enc_CNG(cd, NULL, from, flen, to, RSA_size(rsa),
+                            cng_padding_type(padding), 0);
+    }
+
     /* Unfortunately, there is no "CryptSign()" function in CryptoAPI, that would
      * be way to straightforward for M$, I guess... So we have to do it this
      * tricky way instead, by creating a "Hash", and load the already-made hash
@@ -447,7 +477,7 @@
  */
 static int
 rsa_sign_CNG(int type, const unsigned char *m, unsigned int m_len,
-              unsigned char *sig, unsigned int *siglen, const RSA *rsa)
+             unsigned char *sig, unsigned int *siglen, const RSA *rsa)
 {
     CAPI_DATA *cd = (CAPI_DATA *) RSA_meth_get0_app_data(RSA_get_method(rsa));
     const wchar_t *alg = NULL;
@@ -502,26 +532,206 @@
     {
         return 0;
     }
-    if (cd->crypt_prov && cd->free_crypt_prov)
-    {
-        if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
-        {
-            NCryptFreeObject(cd->crypt_prov);
-        }
-        else
-        {
-            CryptReleaseContext(cd->crypt_prov, 0);
-        }
-    }
-    if (cd->cert_context)
-    {
-        CertFreeCertificateContext(cd->cert_context);
-    }
-    free(cd);
-    RSA_meth_free((RSA_METHOD*) rsa_meth);
+    CAPI_DATA_free(cd);
+    RSA_meth_free((RSA_METHOD *) rsa_meth);
     return 1;
 }
 
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(OPENSSL_NO_EC)
+
+static EC_KEY_METHOD *ec_method = NULL;
+
+/** EC_KEY_METHOD callback: called when the key is freed */
+static void
+ec_finish(EC_KEY *ec)
+{
+    EC_KEY_METHOD_free(ec_method);
+    ec_method = NULL;
+    CAPI_DATA *cd = EC_KEY_get_ex_data(ec, ec_data_idx);
+    CAPI_DATA_free(cd);
+    EC_KEY_set_ex_data(ec, ec_data_idx, NULL);
+}
+
+/** EC_KEY_METHOD callback sign_setup(): we do nothing here */
+static int
+ecdsa_sign_setup(EC_KEY *eckey, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp)
+{
+    return 1;
+}
+
+/**
+ * Helper to convert ECDSA signature returned by NCryptSignHash
+ * to an ECDSA_SIG structure.
+ * On entry 'buf[]' of length len contains r and s concatenated.
+ * Returns a newly allocated ECDSA_SIG or NULL (on error).
+ */
+static ECDSA_SIG *
+ecdsa_bin2sig(unsigned char *buf, int len)
+{
+    ECDSA_SIG *ecsig = NULL;
+    DWORD rlen = len/2;
+    BIGNUM *r = BN_bin2bn(buf, rlen, NULL);
+    BIGNUM *s = BN_bin2bn(buf+rlen, rlen, NULL);
+    if (!r || !s)
+    {
+        goto err;
+    }
+    ecsig = ECDSA_SIG_new(); /* in openssl 1.1 this does not allocate r, s */
+    if (!ecsig)
+    {
+        goto err;
+    }
+    if (!ECDSA_SIG_set0(ecsig, r, s)) /* ecsig takes ownership of r and s */
+    {
+        ECDSA_SIG_free(ecsig);
+        goto err;
+    }
+    return ecsig;
+err:
+    BN_free(r); /* it is ok to free NULL BN */
+    BN_free(s);
+    return NULL;
+}
+
+/** EC_KEY_METHOD callback sign_sig(): sign and return an ECDSA_SIG pointer. */
+static ECDSA_SIG *
+ecdsa_sign_sig(const unsigned char *dgst, int dgstlen,
+               const BIGNUM *in_kinv, const BIGNUM *in_r, EC_KEY *ec)
+{
+    ECDSA_SIG *ecsig = NULL;
+    CAPI_DATA *cd = (CAPI_DATA *)EC_KEY_get_ex_data(ec, ec_data_idx);
+
+    ASSERT(cd->key_spec == CERT_NCRYPT_KEY_SPEC);
+
+    NCRYPT_KEY_HANDLE hkey = cd->crypt_prov;
+    BYTE buf[512]; /* large enough buffer for signature to avoid malloc */
+    DWORD len = _countof(buf);
+
+    msg(D_LOW, "Cryptoapi: signing hash using EC key: data size = %d", dgstlen);
+
+    DWORD status = NCryptSignHash(hkey, NULL, (BYTE *)dgst, dgstlen, (BYTE *)buf, len, &len, 0);
+    if (status != ERROR_SUCCESS)
+    {
+        SetLastError(status);
+        CRYPTOAPIerr(CRYPTOAPI_F_NCRYPT_SIGN_HASH);
+    }
+    else
+    {
+        /* NCryptSignHash returns r, s concatenated in buf[] */
+        ecsig = ecdsa_bin2sig(buf, len);
+    }
+    return ecsig;
+}
+
+/** EC_KEY_METHOD callback sign(): sign and return a DER encoded signature */
+static int
+ecdsa_sign(int type, const unsigned char *dgst, int dgstlen, unsigned char *sig,
+           unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *ec)
+{
+    ECDSA_SIG *s;
+
+    *siglen = 0;
+    s = ecdsa_sign_sig(dgst, dgstlen, NULL, NULL, ec);
+    if (s == NULL)
+    {
+        return 0;
+    }
+
+    /* convert internal signature structure 's' to DER encoded byte array in sig */
+    int len = i2d_ECDSA_SIG(s, NULL);
+    if (len > ECDSA_size(ec))
+    {
+        ECDSA_SIG_free(s);
+        msg(M_NONFATAL,"Error: DER encoded ECDSA signature is too long (%d bytes)", len);
+        return 0;
+    }
+    *siglen = i2d_ECDSA_SIG(s, &sig);
+    ECDSA_SIG_free(s);
+
+    return 1;
+}
+
+static int
+ssl_ctx_set_eckey(SSL_CTX *ssl_ctx, CAPI_DATA *cd, EVP_PKEY *pkey)
+{
+    EC_KEY *ec = NULL;
+    EVP_PKEY *privkey = NULL;
+
+    if (cd->key_spec != CERT_NCRYPT_KEY_SPEC)
+    {
+        msg(M_NONFATAL, "ERROR: cryptoapicert with only legacy private key handle available."
+            " EC certificate not supported.");
+        goto err;
+    }
+    /* create a method struct with default callbacks filled in */
+    ec_method = EC_KEY_METHOD_new(EC_KEY_OpenSSL());
+    if (!ec_method)
+    {
+        goto err;
+    }
+
+    /* We only need to set finish among init methods, and sign methods */
+    EC_KEY_METHOD_set_init(ec_method, NULL, ec_finish, NULL, NULL, NULL, NULL);
+    EC_KEY_METHOD_set_sign(ec_method, ecdsa_sign, ecdsa_sign_setup, ecdsa_sign_sig);
+
+    ec = EC_KEY_dup(EVP_PKEY_get0_EC_KEY(pkey));
+    if (!ec)
+    {
+        goto err;
+    }
+    if (!EC_KEY_set_method(ec, ec_method))
+    {
+        goto err;
+    }
+
+    /* get an index to store cd as external data */
+    if (ec_data_idx < 0)
+    {
+        ec_data_idx = EC_KEY_get_ex_new_index(0, "cryptapicert ec key", NULL, NULL, NULL);
+        if (ec_data_idx < 0)
+        {
+            goto err;
+        }
+    }
+    EC_KEY_set_ex_data(ec, ec_data_idx, cd);
+
+    /* cd assigned to ec as ex_data, increase its refcount */
+    cd->ref_count++;
+
+    privkey = EVP_PKEY_new();
+    if (!EVP_PKEY_assign_EC_KEY(privkey, ec))
+    {
+        EC_KEY_free(ec);
+        goto err;
+    }
+    /* from here on ec will get freed with privkey */
+
+    if (!SSL_CTX_use_PrivateKey(ssl_ctx, privkey))
+    {
+        goto err;
+    }
+    EVP_PKEY_free(privkey); /* this will dn_ref or free ec as well */
+    return 1;
+
+err:
+    if (privkey)
+    {
+        EVP_PKEY_free(privkey);
+    }
+    else if (ec)
+    {
+        EC_KEY_free(ec);
+    }
+    if (ec_method) /* do always set ec_method = NULL after freeing it */
+    {
+        EC_KEY_METHOD_free(ec_method);
+        ec_method = NULL;
+    }
+    return 0;
+}
+
+#endif /* OPENSSL_VERSION_NUMBER >= 1.1.0 */
+
 static const CERT_CONTEXT *
 find_certificate_in_store(const char *cert_prop, HCERTSTORE cert_store)
 {
@@ -599,7 +809,7 @@
         goto out;
     }
 
-    while(true)
+    while (true)
     {
         int validity = 1;
         /* this frees previous rv, if not NULL */
@@ -643,6 +853,8 @@
 static int
 pkey_rsa_sign_init(EVP_PKEY_CTX *ctx)
 {
+    msg(D_LOW, "cryptoapicert: enter pkey_rsa_sign_init");
+
     EVP_PKEY *pkey = EVP_PKEY_CTX_get0_pkey(ctx);
 
     if (pkey && retrieve_capi_data(pkey))
@@ -660,7 +872,7 @@
  * Implementation of EVP_PKEY_sign() using CNG: sign the digest in |tbs|
  * and save the the signature in |sig| and its size in |*siglen|.
  * If |sig| is NULL the required buffer size is returned in |*siglen|.
- * Returns 1 on success, 0 or a negative integer on error.
+ * Returns value is 1 on success, 0 or a negative integer on error.
  */
 static int
 pkey_rsa_sign(EVP_PKEY_CTX *ctx, unsigned char *sig, size_t *siglen,
@@ -671,9 +883,9 @@
     EVP_MD *md = NULL;
     const wchar_t *alg = NULL;
 
-    int padding;
-    int hashlen;
-    int saltlen;
+    int padding = 0;
+    int hashlen = 0;
+    int saltlen = 0;
 
     pkey = EVP_PKEY_CTX_get0_pkey(ctx);
     if (pkey)
@@ -752,7 +964,7 @@
 
         if (!EVP_PKEY_CTX_get_rsa_pss_saltlen(ctx, &saltlen))
         {
-            msg(M_WARN|M_INFO, "cryptoapicert: unable to get the salt length from context."
+            msg(M_WARN, "cryptoapicert: unable to get the salt length from context."
                 " Using the default value.");
             saltlen = -1;
         }
@@ -784,6 +996,7 @@
         msg(D_LOW, "cryptoapicert: PSS padding using saltlen = %d", saltlen);
     }
 
+    msg(D_LOW, "cryptoapicert: calling priv_enc_CNG with alg = %ls", alg);
     *siglen = priv_enc_CNG(cd, alg, tbs, (int)tbslen, sig, *siglen,
                            cng_padding_type(padding), (DWORD)saltlen);
 
@@ -792,14 +1005,131 @@
 
 #endif /* OPENSSL_VERSION >= 1.1.0 */
 
+static int
+ssl_ctx_set_rsakey(SSL_CTX *ssl_ctx, CAPI_DATA *cd, EVP_PKEY *pkey)
+{
+    RSA *rsa = NULL, *pub_rsa;
+    RSA_METHOD *my_rsa_method = NULL;
+    bool rsa_method_set = false;
+
+    my_rsa_method = RSA_meth_new("Microsoft Cryptography API RSA Method",
+                                 RSA_METHOD_FLAG_NO_CHECK);
+    check_malloc_return(my_rsa_method);
+    RSA_meth_set_pub_enc(my_rsa_method, rsa_pub_enc);
+    RSA_meth_set_pub_dec(my_rsa_method, rsa_pub_dec);
+    RSA_meth_set_priv_enc(my_rsa_method, rsa_priv_enc);
+    RSA_meth_set_priv_dec(my_rsa_method, rsa_priv_dec);
+    RSA_meth_set_init(my_rsa_method, NULL);
+    RSA_meth_set_finish(my_rsa_method, finish);
+    RSA_meth_set0_app_data(my_rsa_method, cd);
+
+    /*
+     * For CNG, set the RSA_sign method which gets priority over priv_enc().
+     * This method is called with the raw hash without the digestinfo
+     * header and works better when using NCryptSignHash() with some tokens.
+     * However, if PSS padding is in use, openssl does not call this
+     * function but adds the padding and then calls rsa_priv_enc()
+     * with padding set to NONE which is not supported by CNG.
+     * So, when posisble (OpenSSL 1.1.0 and up), we hook on to the sign
+     * operation in EVP_PKEY_METHOD struct.
+     */
+    if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
+    {
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
+        RSA_meth_set_sign(my_rsa_method, rsa_sign_CNG);
+#else
+        /* pmethod is global -- initialize only if NULL */
+        if (!pmethod)
+        {
+            pmethod = EVP_PKEY_meth_new(EVP_PKEY_RSA, 0);
+            if (!pmethod)
+            {
+                SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE);
+                goto err;
+            }
+            const EVP_PKEY_METHOD *default_pmethod = EVP_PKEY_meth_find(EVP_PKEY_RSA);
+            EVP_PKEY_meth_copy(pmethod, default_pmethod);
+
+            /* We want to override only sign_init() and sign() */
+            EVP_PKEY_meth_set_sign(pmethod, pkey_rsa_sign_init, pkey_rsa_sign);
+            EVP_PKEY_meth_add0(pmethod);
+
+            /* Keep a copy of the default sign and sign_init methods */
+
+#if (OPENSSL_VERSION_NUMBER < 0x1010009fL)   /* > version 1.1.0i */
+            /* The function signature is not const-correct in these versions */
+            EVP_PKEY_meth_get_sign((EVP_PKEY_METHOD *)default_pmethod, &default_pkey_sign_init,
+                                   &default_pkey_sign);
+#else
+            EVP_PKEY_meth_get_sign(default_pmethod, &default_pkey_sign_init,
+                                   &default_pkey_sign);
+
+#endif
+        }
+#endif /* (OPENSSL_VERSION_NUMBER < 0x10100000L) */
+    }
+
+    rsa = RSA_new();
+    if (rsa == NULL)
+    {
+        SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE);
+        goto err;
+    }
+
+    pub_rsa = EVP_PKEY_get0_RSA(pkey);
+    if (!pub_rsa)
+    {
+        goto err;
+    }
+
+    /* Our private key is external, so we fill in only n and e from the public key */
+    const BIGNUM *n = NULL;
+    const BIGNUM *e = NULL;
+    RSA_get0_key(pub_rsa, &n, &e, NULL);
+    BIGNUM *rsa_n = BN_dup(n);
+    BIGNUM *rsa_e = BN_dup(e);
+    if (!rsa_n || !rsa_e || !RSA_set0_key(rsa, rsa_n, rsa_e, NULL))
+    {
+        BN_free(rsa_n); /* ok to free even if NULL */
+        BN_free(rsa_e);
+        msg(M_NONFATAL, "ERROR: %s: out of memory", __func__);
+        goto err;
+    }
+    RSA_set_flags(rsa, RSA_flags(rsa) | RSA_FLAG_EXT_PKEY);
+    if (!RSA_set_method(rsa, my_rsa_method))
+    {
+        goto err;
+    }
+    rsa_method_set = true; /* flag that method pointer will get freed with the key */
+    cd->ref_count++;       /* with method, cd gets assigned to the key as well */
+
+    if (!SSL_CTX_use_RSAPrivateKey(ssl_ctx, rsa))
+    {
+        goto err;
+    }
+    /* SSL_CTX_use_RSAPrivateKey() increased the reference count in 'rsa', so
+    * we decrease it here with RSA_free(), or it will never be cleaned up. */
+    RSA_free(rsa);
+    return 1;
+
+err:
+    if (rsa)
+    {
+        RSA_free(rsa);
+    }
+    if (my_rsa_method && !rsa_method_set)
+    {
+        RSA_meth_free(my_rsa_method);
+    }
+    return 0;
+}
+
 int
 SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
 {
     HCERTSTORE cs;
     X509 *cert = NULL;
-    RSA *rsa = NULL, *pub_rsa;
     CAPI_DATA *cd = calloc(1, sizeof(*cd));
-    RSA_METHOD *my_rsa_method = NULL;
 
     if (cd == NULL)
     {
@@ -848,7 +1178,7 @@
     DWORD flags = CRYPT_ACQUIRE_COMPARE_KEY_FLAG
                   | CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG;
     if (!CryptAcquireCertificatePrivateKey(cd->cert_context, flags, NULL,
-                    &cd->crypt_prov, &cd->key_spec, &cd->free_crypt_prov))
+                                           &cd->crypt_prov, &cd->key_spec, &cd->free_crypt_prov))
     {
         /* if we don't have a smart card reader here, and we try to access a
          * smart card certificate, we get:
@@ -880,74 +1210,13 @@
         }
     }
 
-    my_rsa_method = RSA_meth_new("Microsoft Cryptography API RSA Method",
-                                  RSA_METHOD_FLAG_NO_CHECK);
-    check_malloc_return(my_rsa_method);
-    RSA_meth_set_pub_enc(my_rsa_method, rsa_pub_enc);
-    RSA_meth_set_pub_dec(my_rsa_method, rsa_pub_dec);
-    RSA_meth_set_priv_enc(my_rsa_method, rsa_priv_enc);
-    RSA_meth_set_priv_dec(my_rsa_method, rsa_priv_dec);
-    RSA_meth_set_init(my_rsa_method, NULL);
-    RSA_meth_set_finish(my_rsa_method, finish);
-    RSA_meth_set0_app_data(my_rsa_method, cd);
-
-    /* For CNG, set the RSA_sign method which gets priority over priv_enc().
-     * This method is called with the raw hash without the digestinfo
-     * header and works better when using NCryptSignHash() with some tokens.
-     * However, if PSS padding is in use, openssl does not call this
-     * function but adds the padding and then calls rsa_priv_enc()
-     * with padding set to NONE which is not supported by CNG.
-     * So, when posisble (OpenSSL 1.1.0 and up), we hook on to the sign
-     * operation in EVP_PKEY_METHOD struct.
-     */
-    if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
-    {
-#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
-        RSA_meth_set_sign(my_rsa_method, rsa_sign_CNG);
-#else
-        /* pmethod is global -- initialize only if NULL */
-        if (!pmethod)
-        {
-            pmethod = EVP_PKEY_meth_new(EVP_PKEY_RSA, 0);
-            if (!pmethod)
-            {
-                SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE);
-                goto err;
-            }
-            const EVP_PKEY_METHOD *default_pmethod = EVP_PKEY_meth_find(EVP_PKEY_RSA);
-            EVP_PKEY_meth_copy(pmethod, default_pmethod);
-
-            /* We want to override only sign_init() and sign() */
-            EVP_PKEY_meth_set_sign(pmethod, pkey_rsa_sign_init, pkey_rsa_sign);
-            EVP_PKEY_meth_add0(pmethod);
-
-            /* Keep a copy of the default sign and sign_init methods */
-
-#if (OPENSSL_VERSION_NUMBER < 0x1010009fL)   /* < version 1.1.0i */
-            /* The function signature is not const-correct in these versions */
-            EVP_PKEY_meth_get_sign((EVP_PKEY_METHOD *)default_pmethod, &default_pkey_sign_init,
-                                   &default_pkey_sign);
-#else
-            EVP_PKEY_meth_get_sign(default_pmethod, &default_pkey_sign_init,
-                                   &default_pkey_sign);
-#endif
-        }
-#endif /* (OPENSSL_VERSION_NUMBER < 0x10100000L) */
-    }
-
-    rsa = RSA_new();
-    if (rsa == NULL)
-    {
-        SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE);
-        goto err;
-    }
-
     /* Public key in cert is NULL until we call SSL_CTX_use_certificate(),
      * so we do it here then...  */
     if (!SSL_CTX_use_certificate(ssl_ctx, cert))
     {
         goto err;
     }
+
     /* the public key */
     EVP_PKEY *pkey = X509_get0_pubkey(cert);
 
@@ -956,70 +1225,32 @@
     X509_free(cert);
     cert = NULL;
 
-    if (!(pub_rsa = EVP_PKEY_get0_RSA(pkey)))
+    if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA)
     {
-        msg(M_WARN, "cryptoapicert requires an RSA certificate");
+        if (!ssl_ctx_set_rsakey(ssl_ctx, cd, pkey))
+        {
+            goto err;
+        }
+    }
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(OPENSSL_NO_EC)
+    else if (EVP_PKEY_id(pkey) == EVP_PKEY_EC)
+    {
+        if (!ssl_ctx_set_eckey(ssl_ctx, cd, pkey))
+        {
+            goto err;
+        }
+    }
+#endif /* OPENSSL_VERSION_NUMBER >= 1.1.0 */
+    else
+    {
+        msg(M_WARN, "WARNING: cryptoapicert: certificate type not supported");
         goto err;
     }
-
-    /* Our private key is external, so we fill in only n and e from the public key */
-    const BIGNUM *n = NULL;
-    const BIGNUM *e = NULL;
-    RSA_get0_key(pub_rsa, &n, &e, NULL);
-    if (!RSA_set0_key(rsa, BN_dup(n), BN_dup(e), NULL))
-    {
-        goto err;
-    }
-    RSA_set_flags(rsa, RSA_flags(rsa) | RSA_FLAG_EXT_PKEY);
-    if (!RSA_set_method(rsa, my_rsa_method))
-    {
-        goto err;
-    }
-
-    if (!SSL_CTX_use_RSAPrivateKey(ssl_ctx, rsa))
-    {
-        goto err;
-    }
-    /* SSL_CTX_use_RSAPrivateKey() increased the reference count in 'rsa', so
-    * we decrease it here with RSA_free(), or it will never be cleaned up. */
-    RSA_free(rsa);
+    CAPI_DATA_free(cd); /* this will do a ref_count-- */
     return 1;
 
 err:
-    if (cert)
-    {
-        X509_free(cert);
-    }
-    if (rsa)
-    {
-        RSA_free(rsa);
-    }
-    else
-    {
-        if (my_rsa_method)
-        {
-            free(my_rsa_method);
-        }
-        if (cd)
-        {
-            if (cd->free_crypt_prov && cd->crypt_prov)
-            {
-                if (cd->key_spec == CERT_NCRYPT_KEY_SPEC)
-                {
-                    NCryptFreeObject(cd->crypt_prov);
-                }
-                else
-                {
-                    CryptReleaseContext(cd->crypt_prov, 0);
-                }
-            }
-            if (cd->cert_context)
-            {
-                CertFreeCertificateContext(cd->cert_context);
-            }
-            free(cd);
-        }
-    }
+    CAPI_DATA_free(cd);
     return 0;
 }
 
diff --git a/src/openvpn/dhcp.c b/src/openvpn/dhcp.c
index fb28b27..c19370e 100644
--- a/src/openvpn/dhcp.c
+++ b/src/openvpn/dhcp.c
@@ -147,49 +147,6 @@
     return ret;
 }
 
-static uint16_t
-udp_checksum(const uint8_t *buf,
-             const int len_udp,
-             const uint8_t *src_addr,
-             const uint8_t *dest_addr)
-{
-    uint16_t word16;
-    uint32_t sum = 0;
-    int i;
-
-    /* make 16 bit words out of every two adjacent 8 bit words and  */
-    /* calculate the sum of all 16 bit words */
-    for (i = 0; i < len_udp; i += 2)
-    {
-        word16 = ((buf[i] << 8) & 0xFF00) + ((i + 1 < len_udp) ? (buf[i+1] & 0xFF) : 0);
-        sum += word16;
-    }
-
-    /* add the UDP pseudo header which contains the IP source and destination addresses */
-    for (i = 0; i < 4; i += 2)
-    {
-        word16 = ((src_addr[i] << 8) & 0xFF00) + (src_addr[i+1] & 0xFF);
-        sum += word16;
-    }
-    for (i = 0; i < 4; i += 2)
-    {
-        word16 = ((dest_addr[i] << 8) & 0xFF00) + (dest_addr[i+1] & 0xFF);
-        sum += word16;
-    }
-
-    /* the protocol number and the length of the UDP packet */
-    sum += (uint16_t) OPENVPN_IPPROTO_UDP + (uint16_t) len_udp;
-
-    /* keep only the last 16 bits of the 32 bit calculated sum and add the carries */
-    while (sum >> 16)
-    {
-        sum = (sum & 0xFFFF) + (sum >> 16);
-    }
-
-    /* Take the one's complement of sum */
-    return ((uint16_t) ~sum);
-}
-
 in_addr_t
 dhcp_extract_router_msg(struct buffer *ipbuf)
 {
@@ -210,10 +167,10 @@
 
             /* recompute the UDP checksum */
             df->udp.check = 0;
-            df->udp.check = htons(udp_checksum((uint8_t *) &df->udp,
-                                               sizeof(struct openvpn_udphdr) + sizeof(struct dhcp) + optlen,
-                                               (uint8_t *)&df->ip.saddr,
-                                               (uint8_t *)&df->ip.daddr));
+            df->udp.check = htons(ip_checksum(AF_INET, (uint8_t *)&df->udp,
+                                              sizeof(struct openvpn_udphdr) + sizeof(struct dhcp) + optlen,
+                                              (uint8_t *)&df->ip.saddr, (uint8_t *)&df->ip.daddr,
+                                              OPENVPN_IPPROTO_UDP));
 
             /* only return the extracted Router address if DHCPACK */
             if (message_type == DHCPACK)
diff --git a/src/openvpn/env_set.c b/src/openvpn/env_set.c
new file mode 100644
index 0000000..0ab0262
--- /dev/null
+++ b/src/openvpn/env_set.c
@@ -0,0 +1,459 @@
+/*
+ *  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) 2002-2017 OpenVPN Technologies, Inc. <sales@openvpn.net>
+ *  Copyright (C) 2014-2015 David Sommerseth <davids@redhat.com>
+ *  Copyright (C) 2016-2017 David Sommerseth <davids@openvpn.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program (see the file COPYING included with this
+ *  distribution); if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#include "env_set.h"
+
+#include "run_command.h"
+
+/*
+ * Set environmental variable (int or string).
+ *
+ * On Posix, we use putenv for portability,
+ * and put up with its painful semantics
+ * that require all the support code below.
+ */
+
+/* General-purpose environmental variable set functions */
+
+static char *
+construct_name_value(const char *name, const char *value, struct gc_arena *gc)
+{
+    struct buffer out;
+
+    ASSERT(name);
+    if (!value)
+    {
+        value = "";
+    }
+    out = alloc_buf_gc(strlen(name) + strlen(value) + 2, gc);
+    buf_printf(&out, "%s=%s", name, value);
+    return BSTR(&out);
+}
+
+static bool
+env_string_equal(const char *s1, const char *s2)
+{
+    int c1, c2;
+    ASSERT(s1);
+    ASSERT(s2);
+
+    while (true)
+    {
+        c1 = *s1++;
+        c2 = *s2++;
+        if (c1 == '=')
+        {
+            c1 = 0;
+        }
+        if (c2 == '=')
+        {
+            c2 = 0;
+        }
+        if (!c1 && !c2)
+        {
+            return true;
+        }
+        if (c1 != c2)
+        {
+            break;
+        }
+    }
+    return false;
+}
+
+static bool
+remove_env_item(const char *str, const bool do_free, struct env_item **list)
+{
+    struct env_item *current, *prev;
+
+    ASSERT(str);
+    ASSERT(list);
+
+    for (current = *list, prev = NULL; current != NULL; current = current->next)
+    {
+        if (env_string_equal(current->string, str))
+        {
+            if (prev)
+            {
+                prev->next = current->next;
+            }
+            else
+            {
+                *list = current->next;
+            }
+            if (do_free)
+            {
+                secure_memzero(current->string, strlen(current->string));
+                free(current->string);
+                free(current);
+            }
+            return true;
+        }
+        prev = current;
+    }
+    return false;
+}
+
+static void
+add_env_item(char *str, const bool do_alloc, struct env_item **list, struct gc_arena *gc)
+{
+    struct env_item *item;
+
+    ASSERT(str);
+    ASSERT(list);
+
+    ALLOC_OBJ_GC(item, struct env_item, gc);
+    item->string = do_alloc ? string_alloc(str, gc) : str;
+    item->next = *list;
+    *list = item;
+}
+
+/* struct env_set functions */
+
+static bool
+env_set_del_nolock(struct env_set *es, const char *str)
+{
+    return remove_env_item(str, es->gc == NULL, &es->list);
+}
+
+static void
+env_set_add_nolock(struct env_set *es, const char *str)
+{
+    remove_env_item(str, es->gc == NULL, &es->list);
+    add_env_item((char *)str, true, &es->list, es->gc);
+}
+
+struct env_set *
+env_set_create(struct gc_arena *gc)
+{
+    struct env_set *es;
+    ALLOC_OBJ_CLEAR_GC(es, struct env_set, gc);
+    es->list = NULL;
+    es->gc = gc;
+    return es;
+}
+
+void
+env_set_destroy(struct env_set *es)
+{
+    if (es && es->gc == NULL)
+    {
+        struct env_item *e = es->list;
+        while (e)
+        {
+            struct env_item *next = e->next;
+            free(e->string);
+            free(e);
+            e = next;
+        }
+        free(es);
+    }
+}
+
+bool
+env_set_del(struct env_set *es, const char *str)
+{
+    bool ret;
+    ASSERT(es);
+    ASSERT(str);
+    ret = env_set_del_nolock(es, str);
+    return ret;
+}
+
+void
+env_set_add(struct env_set *es, const char *str)
+{
+    ASSERT(es);
+    ASSERT(str);
+    env_set_add_nolock(es, str);
+}
+
+const char *
+env_set_get(const struct env_set *es, const char *name)
+{
+    const struct env_item *item = es->list;
+    while (item && !env_string_equal(item->string, name))
+    {
+        item = item->next;
+    }
+    return item ? item->string : NULL;
+}
+
+void
+env_set_print(int msglevel, const struct env_set *es)
+{
+    if (check_debug_level(msglevel))
+    {
+        const struct env_item *e;
+        int i;
+
+        if (es)
+        {
+            e = es->list;
+            i = 0;
+
+            while (e)
+            {
+                if (env_safe_to_print(e->string))
+                {
+                    msg(msglevel, "ENV [%d] '%s'", i, e->string);
+                }
+                ++i;
+                e = e->next;
+            }
+        }
+    }
+}
+
+void
+env_set_inherit(struct env_set *es, const struct env_set *src)
+{
+    const struct env_item *e;
+
+    ASSERT(es);
+
+    if (src)
+    {
+        e = src->list;
+        while (e)
+        {
+            env_set_add_nolock(es, e->string);
+            e = e->next;
+        }
+    }
+}
+
+
+/* add/modify/delete environmental strings */
+
+void
+setenv_counter(struct env_set *es, const char *name, counter_type value)
+{
+    char buf[64];
+    openvpn_snprintf(buf, sizeof(buf), counter_format, value);
+    setenv_str(es, name, buf);
+}
+
+void
+setenv_int(struct env_set *es, const char *name, int value)
+{
+    char buf[64];
+    openvpn_snprintf(buf, sizeof(buf), "%d", value);
+    setenv_str(es, name, buf);
+}
+
+void
+setenv_long_long(struct env_set *es, const char *name, long long value)
+{
+    char buf[64];
+    openvpn_snprintf(buf, sizeof(buf), "%" PRIi64, (int64_t)value);
+    setenv_str(es, name, buf);
+}
+
+void
+setenv_str(struct env_set *es, const char *name, const char *value)
+{
+    setenv_str_ex(es, name, value, CC_NAME, 0, 0, CC_PRINT, 0, 0);
+}
+
+void
+setenv_str_safe(struct env_set *es, const char *name, const char *value)
+{
+    uint8_t b[64];
+    struct buffer buf;
+    buf_set_write(&buf, b, sizeof(b));
+    if (buf_printf(&buf, "OPENVPN_%s", name))
+    {
+        setenv_str(es, BSTR(&buf), value);
+    }
+    else
+    {
+        msg(M_WARN, "setenv_str_safe: name overflow");
+    }
+}
+
+void
+setenv_str_incr(struct env_set *es, const char *name, const char *value)
+{
+    unsigned int counter = 1;
+    const size_t tmpname_len = strlen(name) + 5; /* 3 digits counter max */
+    char *tmpname = gc_malloc(tmpname_len, true, NULL);
+    strcpy(tmpname, name);
+    while (NULL != env_set_get(es, tmpname) && counter < 1000)
+    {
+        ASSERT(openvpn_snprintf(tmpname, tmpname_len, "%s_%u", name, counter));
+        counter++;
+    }
+    if (counter < 1000)
+    {
+        setenv_str(es, tmpname, value);
+    }
+    else
+    {
+        msg(D_TLS_DEBUG_MED, "Too many same-name env variables, ignoring: %s", name);
+    }
+    free(tmpname);
+}
+
+void
+setenv_del(struct env_set *es, const char *name)
+{
+    ASSERT(name);
+    setenv_str(es, name, NULL);
+}
+
+void
+setenv_str_ex(struct env_set *es,
+              const char *name,
+              const char *value,
+              const unsigned int name_include,
+              const unsigned int name_exclude,
+              const char name_replace,
+              const unsigned int value_include,
+              const unsigned int value_exclude,
+              const char value_replace)
+{
+    struct gc_arena gc = gc_new();
+    const char *name_tmp;
+    const char *val_tmp = NULL;
+
+    ASSERT(name && strlen(name) > 1);
+
+    name_tmp = string_mod_const(name, name_include, name_exclude, name_replace, &gc);
+
+    if (value)
+    {
+        val_tmp = string_mod_const(value, value_include, value_exclude, value_replace, &gc);
+    }
+
+    ASSERT(es);
+
+    if (val_tmp)
+    {
+        const char *str = construct_name_value(name_tmp, val_tmp, &gc);
+        env_set_add(es, str);
+#if DEBUG_VERBOSE_SETENV
+        msg(M_INFO, "SETENV_ES '%s'", str);
+#endif
+    }
+    else
+    {
+        env_set_del(es, name_tmp);
+    }
+
+    gc_free(&gc);
+}
+
+/*
+ * Setenv functions that append an integer index to the name
+ */
+static const char *
+setenv_format_indexed_name(const char *name, const int i, struct gc_arena *gc)
+{
+    struct buffer out = alloc_buf_gc(strlen(name) + 16, gc);
+    if (i >= 0)
+    {
+        buf_printf(&out, "%s_%d", name, i);
+    }
+    else
+    {
+        buf_printf(&out, "%s", name);
+    }
+    return BSTR(&out);
+}
+
+void
+setenv_int_i(struct env_set *es, const char *name, const int value, const int i)
+{
+    struct gc_arena gc = gc_new();
+    const char *name_str = setenv_format_indexed_name(name, i, &gc);
+    setenv_int(es, name_str, value);
+    gc_free(&gc);
+}
+
+void
+setenv_str_i(struct env_set *es, const char *name, const char *value, const int i)
+{
+    struct gc_arena gc = gc_new();
+    const char *name_str = setenv_format_indexed_name(name, i, &gc);
+    setenv_str(es, name_str, value);
+    gc_free(&gc);
+}
+
+bool
+env_allowed(const char *str)
+{
+    return (script_security() >= SSEC_PW_ENV || !is_password_env_var(str));
+}
+
+/* Make arrays of strings */
+
+const char **
+make_env_array(const struct env_set *es,
+               const bool check_allowed,
+               struct gc_arena *gc)
+{
+    char **ret = NULL;
+    struct env_item *e = NULL;
+    int i = 0, n = 0;
+
+    /* figure length of es */
+    if (es)
+    {
+        for (e = es->list; e != NULL; e = e->next)
+        {
+            ++n;
+        }
+    }
+
+    /* alloc return array */
+    ALLOC_ARRAY_CLEAR_GC(ret, char *, n+1, gc);
+
+    /* fill return array */
+    if (es)
+    {
+        i = 0;
+        for (e = es->list; e != NULL; e = e->next)
+        {
+            if (!check_allowed || env_allowed(e->string))
+            {
+                ASSERT(i < n);
+                ret[i++] = e->string;
+            }
+        }
+    }
+
+    ret[i] = NULL;
+    return (const char **)ret;
+}
diff --git a/src/openvpn/env_set.h b/src/openvpn/env_set.h
new file mode 100644
index 0000000..cf8415c
--- /dev/null
+++ b/src/openvpn/env_set.h
@@ -0,0 +1,123 @@
+/*
+ *  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) 2002-2017 OpenVPN Technologies, Inc. <sales@openvpn.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program (see the file COPYING included with this
+ *  distribution); if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef ENV_SET_H
+#define ENV_SET_H
+
+#include "argv.h"
+#include "basic.h"
+#include "buffer.h"
+#include "common.h"
+
+/*
+ * Handle environmental variable lists
+ */
+
+struct env_item {
+    char *string;
+    struct env_item *next;
+};
+
+struct env_set {
+    struct gc_arena *gc;
+    struct env_item *list;
+};
+
+/* set/delete environmental variable */
+void setenv_str_ex(struct env_set *es,
+                   const char *name,
+                   const char *value,
+                   const unsigned int name_include,
+                   const unsigned int name_exclude,
+                   const char name_replace,
+                   const unsigned int value_include,
+                   const unsigned int value_exclude,
+                   const char value_replace);
+
+void setenv_counter(struct env_set *es, const char *name, counter_type value);
+
+void setenv_int(struct env_set *es, const char *name, int value);
+
+void setenv_long_long(struct env_set *es, const char *name, long long value);
+
+void setenv_str(struct env_set *es, const char *name, const char *value);
+
+void setenv_str_safe(struct env_set *es, const char *name, const char *value);
+
+void setenv_del(struct env_set *es, const char *name);
+
+/**
+ * Store the supplied name value pair in the env_set.  If the variable with the
+ * supplied name  already exists, append _N to the name, starting at N=1.
+ */
+void setenv_str_incr(struct env_set *es, const char *name, const char *value);
+
+void setenv_int_i(struct env_set *es, const char *name, const int value, const int i);
+
+void setenv_str_i(struct env_set *es, const char *name, const char *value, const int i);
+
+/* struct env_set functions */
+
+struct env_set *env_set_create(struct gc_arena *gc);
+
+void env_set_destroy(struct env_set *es);
+
+bool env_set_del(struct env_set *es, const char *str);
+
+void env_set_add(struct env_set *es, const char *str);
+
+const char *env_set_get(const struct env_set *es, const char *name);
+
+void env_set_print(int msglevel, const struct env_set *es);
+
+void env_set_inherit(struct env_set *es, const struct env_set *src);
+
+/* returns true if environmental variable name starts with 'password' */
+static inline bool
+is_password_env_var(const char *str)
+{
+    return (strncmp(str, "password", 8) == 0);
+}
+
+/* returns true if environmental variable safe to print to log */
+static inline bool
+env_safe_to_print(const char *str)
+{
+#ifndef UNSAFE_DEBUG
+    if (is_password_env_var(str))
+    {
+        return false;
+    }
+#endif
+    return true;
+}
+
+/* returns true if environmental variable may be passed to an external program */
+bool env_allowed(const char *str);
+
+const char **make_env_array(const struct env_set *es,
+                            const bool check_allowed,
+                            struct gc_arena *gc);
+
+#endif /* ifndef ENV_SET_H */
diff --git a/src/openvpn/errlevel.h b/src/openvpn/errlevel.h
index 5ca4fa8..5663f84 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_PF_INFO            LOGLEV(3, 45, 0)        /* packet filter informational messages */
+#define D_IFCONFIG           LOGLEV(3, 0,  0)        /* show ifconfig info (don't mute) */
 
 #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 */
@@ -109,6 +110,7 @@
 
 #define D_LOG_RW             LOGLEV(5, 0,  0)        /* Print 'R' or 'W' to stdout for read/write */
 
+#define D_RTNL               LOGLEV(6, 68, M_DEBUG)  /* show RTNL low level operations */
 #define D_LINK_RW            LOGLEV(6, 69, M_DEBUG)  /* show TCP/UDP reads/writes (terse) */
 #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 */
@@ -139,7 +141,6 @@
 #define D_PACKET_TRUNC_DEBUG LOGLEV(7, 70, M_DEBUG)  /* PACKET_TRUNCATION_CHECK verbose */
 #define D_PING               LOGLEV(7, 70, M_DEBUG)  /* PING send/receive messages */
 #define D_PS_PROXY_DEBUG     LOGLEV(7, 70, M_DEBUG)  /* port share proxy debug */
-#define D_AUTO_USERID        LOGLEV(7, 70, M_DEBUG)  /* AUTO_USERID debugging */
 #define D_TLS_KEYSELECT      LOGLEV(7, 70, M_DEBUG)  /* show information on key selection for data channel */
 #define D_ARGV_PARSE_CMD     LOGLEV(7, 70, M_DEBUG)  /* show parse_line() errors in argv_parse_cmd */
 #define D_CRYPTO_DEBUG       LOGLEV(7, 70, M_DEBUG)  /* show detailed info from crypto.c routines */
@@ -148,6 +149,8 @@
 #define D_PF_DEBUG           LOGLEV(7, 72, M_DEBUG)  /* packet filter debugging, must also define PF_DEBUG in pf.h */
 #define D_PUSH_DEBUG         LOGLEV(7, 73, M_DEBUG)  /* show push/pull debugging info */
 
+#define D_VLAN_DEBUG         LOGLEV(7, 74, M_DEBUG)  /* show VLAN tagging/untagging debug info */
+
 #define D_HANDSHAKE_VERBOSE  LOGLEV(8, 70, M_DEBUG)  /* show detailed description of each handshake */
 #define D_TLS_DEBUG_MED      LOGLEV(8, 70, M_DEBUG)  /* limited info from tls_session routines */
 #define D_INTERVAL           LOGLEV(8, 70, M_DEBUG)  /* show interval.h debugging info */
diff --git a/src/openvpn/error.c b/src/openvpn/error.c
index bc14e8c..d6247fe 100644
--- a/src/openvpn/error.c
+++ b/src/openvpn/error.c
@@ -31,6 +31,7 @@
 
 #include "error.h"
 #include "buffer.h"
+#include "init.h"
 #include "misc.h"
 #include "win32.h"
 #include "socket.h"
@@ -342,9 +343,9 @@
                 struct timeval tv;
                 gettimeofday(&tv, NULL);
 
-                fprintf(fp, "%"PRIi64".%06lu %x %s%s%s%s",
+                fprintf(fp, "%" PRIi64 ".%06ld %x %s%s%s%s",
                         (int64_t)tv.tv_sec,
-                        (unsigned long)tv.tv_usec,
+                        (long)tv.tv_usec,
                         flags,
                         prefix,
                         prefix_sep,
@@ -687,7 +688,10 @@
         }
 #elif defined(_WIN32)
         /* get possible driver error from TAP-Windows driver */
-        extended_msg = tap_win_getinfo(tt, &gc);
+        if (tuntap_defined(tt))
+        {
+            extended_msg = tap_win_getinfo(tt, &gc);
+        }
 #endif
         if (!ignore_sys_error(my_errno))
         {
@@ -734,18 +738,12 @@
 {
     if (!forked)
     {
-        void tun_abort();
-
-#ifdef ENABLE_PLUGIN
-        void plugin_abort(void);
-
-#endif
-
         tun_abort();
 
 #ifdef _WIN32
         uninit_win32();
 #endif
+        remove_pid_file();
 
         close_syslog();
 
diff --git a/src/openvpn/event.c b/src/openvpn/event.c
index b22741f..49dfa86 100644
--- a/src/openvpn/event.c
+++ b/src/openvpn/event.c
@@ -1041,10 +1041,10 @@
     struct timeval tv_tmp = *tv;
     int stat;
 
-    dmsg(D_EVENT_WAIT, "SE_WAIT_FAST maxfd=%d tv=%d/%d",
+    dmsg(D_EVENT_WAIT, "SE_WAIT_FAST maxfd=%d tv=%" PRIi64 "/%ld",
          ses->maxfd,
-         (int)tv_tmp.tv_sec,
-         (int)tv_tmp.tv_usec);
+         (int64_t)tv_tmp.tv_sec,
+         (long)tv_tmp.tv_usec);
 
     stat = select(ses->maxfd + 1, &ses->readfds, &ses->writefds, NULL, &tv_tmp);
 
@@ -1065,8 +1065,8 @@
     fd_set write = ses->writefds;
     int stat;
 
-    dmsg(D_EVENT_WAIT, "SE_WAIT_SCALEABLE maxfd=%d tv=%d/%d",
-         ses->maxfd, (int)tv_tmp.tv_sec, (int)tv_tmp.tv_usec);
+    dmsg(D_EVENT_WAIT, "SE_WAIT_SCALEABLE maxfd=%d tv=%" PRIi64 "/%ld",
+         ses->maxfd, (int64_t)tv_tmp.tv_sec, (long)tv_tmp.tv_usec);
 
     stat = select(ses->maxfd + 1, &read, &write, NULL, &tv_tmp);
 
diff --git a/src/openvpn/forward-inline.h b/src/openvpn/forward-inline.h
deleted file mode 100644
index 7d06b4e..0000000
--- a/src/openvpn/forward-inline.h
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- *  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) 2002-2018 OpenVPN Inc <sales@openvpn.net>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License version 2
- *  as published by the Free Software Foundation.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License along
- *  with this program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef FORWARD_INLINE_H
-#define FORWARD_INLINE_H
-
-/*
- * Inline functions
- */
-
-/*
- * Does TLS session need service?
- */
-static inline void
-check_tls(struct context *c)
-{
-#if defined(ENABLE_CRYPTO)
-    void check_tls_dowork(struct context *c);
-
-    if (c->c2.tls_multi)
-    {
-        check_tls_dowork(c);
-    }
-#endif
-}
-
-/*
- * TLS errors are fatal in TCP mode.
- * Also check for --tls-exit trigger.
- */
-static inline void
-check_tls_errors(struct context *c)
-{
-#if defined(ENABLE_CRYPTO)
-    void check_tls_errors_co(struct context *c);
-
-    void check_tls_errors_nco(struct context *c);
-
-    if (c->c2.tls_multi && c->c2.tls_exit_signal)
-    {
-        if (link_socket_connection_oriented(c->c2.link_socket))
-        {
-            if (c->c2.tls_multi->n_soft_errors)
-            {
-                check_tls_errors_co(c);
-            }
-        }
-        else
-        {
-            if (c->c2.tls_multi->n_hard_errors)
-            {
-                check_tls_errors_nco(c);
-            }
-        }
-    }
-#endif /* if defined(ENABLE_CRYPTO) */
-}
-
-/*
- * Check for possible incoming configuration
- * messages on the control channel.
- */
-static inline void
-check_incoming_control_channel(struct context *c)
-{
-#if P2MP
-    void check_incoming_control_channel_dowork(struct context *c);
-
-    if (tls_test_payload_len(c->c2.tls_multi) > 0)
-    {
-        check_incoming_control_channel_dowork(c);
-    }
-#endif
-}
-
-/*
- * Options like --up-delay need to be triggered by this function which
- * checks for connection establishment.
- */
-static inline void
-check_connection_established(struct context *c)
-{
-    void check_connection_established_dowork(struct context *c);
-
-    if (event_timeout_defined(&c->c2.wait_for_connect))
-    {
-        check_connection_established_dowork(c);
-    }
-}
-
-/*
- * Should we add routes?
- */
-static inline void
-check_add_routes(struct context *c)
-{
-    void check_add_routes_dowork(struct context *c);
-
-    if (event_timeout_trigger(&c->c2.route_wakeup, &c->c2.timeval, ETT_DEFAULT))
-    {
-        check_add_routes_dowork(c);
-    }
-}
-
-/*
- * Should we exit due to inactivity timeout?
- */
-static inline void
-check_inactivity_timeout(struct context *c)
-{
-    void check_inactivity_timeout_dowork(struct context *c);
-
-    if (c->options.inactivity_timeout
-        && event_timeout_trigger(&c->c2.inactivity_interval, &c->c2.timeval, ETT_DEFAULT))
-    {
-        check_inactivity_timeout_dowork(c);
-    }
-}
-
-#if P2MP
-
-static inline void
-check_server_poll_timeout(struct context *c)
-{
-    void check_server_poll_timeout_dowork(struct context *c);
-
-    if (c->options.ce.connect_timeout
-        && event_timeout_trigger(&c->c2.server_poll_interval, &c->c2.timeval, ETT_DEFAULT))
-    {
-        check_server_poll_timeout_dowork(c);
-    }
-}
-
-/*
- * Scheduled exit?
- */
-static inline void
-check_scheduled_exit(struct context *c)
-{
-    void check_scheduled_exit_dowork(struct context *c);
-
-    if (event_timeout_defined(&c->c2.scheduled_exit))
-    {
-        if (event_timeout_trigger(&c->c2.scheduled_exit, &c->c2.timeval, ETT_DEFAULT))
-        {
-            check_scheduled_exit_dowork(c);
-        }
-    }
-}
-#endif /* if P2MP */
-
-/*
- * Should we write timer-triggered status file.
- */
-static inline void
-check_status_file(struct context *c)
-{
-    void check_status_file_dowork(struct context *c);
-
-    if (c->c1.status_output)
-    {
-        if (status_trigger_tv(c->c1.status_output, &c->c2.timeval))
-        {
-            check_status_file_dowork(c);
-        }
-    }
-}
-
-#ifdef ENABLE_FRAGMENT
-/*
- * Should we deliver a datagram fragment to remote?
- */
-static inline void
-check_fragment(struct context *c)
-{
-    void check_fragment_dowork(struct context *c);
-
-    if (c->c2.fragment)
-    {
-        check_fragment_dowork(c);
-    }
-}
-#endif
-
-#if P2MP
-
-/*
- * see if we should send a push_request in response to --pull
- */
-static inline void
-check_push_request(struct context *c)
-{
-    void check_push_request_dowork(struct context *c);
-
-    if (event_timeout_trigger(&c->c2.push_request_interval, &c->c2.timeval, ETT_DEFAULT))
-    {
-        check_push_request_dowork(c);
-    }
-}
-
-#endif
-
-#ifdef ENABLE_CRYPTO
-/*
- * Should we persist our anti-replay packet ID state to disk?
- */
-static inline void
-check_packet_id_persist_flush(struct context *c)
-{
-    if (packet_id_persist_enabled(&c->c1.pid_persist)
-        && event_timeout_trigger(&c->c2.packet_id_persist_interval, &c->c2.timeval, ETT_DEFAULT))
-    {
-        packet_id_persist_save(&c->c1.pid_persist);
-    }
-}
-#endif
-
-/*
- * Set our wakeup to 0 seconds, so we will be rescheduled
- * immediately.
- */
-static inline void
-context_immediate_reschedule(struct context *c)
-{
-    c->c2.timeval.tv_sec = 0;  /* ZERO-TIMEOUT */
-    c->c2.timeval.tv_usec = 0;
-}
-
-static inline void
-context_reschedule_sec(struct context *c, int sec)
-{
-    if (sec < 0)
-    {
-        sec = 0;
-    }
-    if (sec < c->c2.timeval.tv_sec)
-    {
-        c->c2.timeval.tv_sec = sec;
-        c->c2.timeval.tv_usec = 0;
-    }
-}
-
-static inline struct link_socket_info *
-get_link_socket_info(struct context *c)
-{
-    if (c->c2.link_socket_info)
-    {
-        return c->c2.link_socket_info;
-    }
-    else
-    {
-        return &c->c2.link_socket->info;
-    }
-}
-
-static inline void
-register_activity(struct context *c, const int size)
-{
-    if (c->options.inactivity_timeout)
-    {
-        c->c2.inactivity_bytes += size;
-        if (c->c2.inactivity_bytes >= c->options.inactivity_minimum_bytes)
-        {
-            c->c2.inactivity_bytes = 0;
-            event_timeout_reset(&c->c2.inactivity_interval);
-        }
-    }
-}
-
-/*
- * Return the io_wait() flags appropriate for
- * a point-to-point tunnel.
- */
-static inline unsigned int
-p2p_iow_flags(const struct context *c)
-{
-    unsigned int flags = (IOW_SHAPER|IOW_CHECK_RESIDUAL|IOW_FRAG|IOW_READ|IOW_WAIT_SIGNAL);
-    if (c->c2.to_link.len > 0)
-    {
-        flags |= IOW_TO_LINK;
-    }
-    if (c->c2.to_tun.len > 0)
-    {
-        flags |= IOW_TO_TUN;
-    }
-    return flags;
-}
-
-/*
- * This is the core I/O wait function, used for all I/O waits except
- * for TCP in server mode.
- */
-static inline void
-io_wait(struct context *c, const unsigned int flags)
-{
-    void io_wait_dowork(struct context *c, const unsigned int flags);
-
-    if (c->c2.fast_io && (flags & (IOW_TO_TUN|IOW_TO_LINK|IOW_MBUF)))
-    {
-        /* fast path -- only for TUN/TAP/UDP writes */
-        unsigned int ret = 0;
-        if (flags & IOW_TO_TUN)
-        {
-            ret |= TUN_WRITE;
-        }
-        if (flags & (IOW_TO_LINK|IOW_MBUF))
-        {
-            ret |= SOCKET_WRITE;
-        }
-        c->c2.event_set_status = ret;
-    }
-    else
-    {
-        /* slow path */
-        io_wait_dowork(c, flags);
-    }
-}
-
-#define CONNECTION_ESTABLISHED(c) (get_link_socket_info(c)->connection_established)
-
-#endif /* EVENT_INLINE_H */
diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
index 84bb584..fd7412f 100644
--- a/src/openvpn/forward.c
+++ b/src/openvpn/forward.c
@@ -35,6 +35,9 @@
 #include "gremlin.h"
 #include "mss.h"
 #include "event.h"
+#include "occ.h"
+#include "pf.h"
+#include "ping.h"
 #include "ps.h"
 #include "dhcp.h"
 #include "common.h"
@@ -42,9 +45,6 @@
 
 #include "memdbg.h"
 
-#include "forward-inline.h"
-#include "occ-inline.h"
-#include "ping-inline.h"
 #include "mstats.h"
 
 counter_type link_read_bytes_global;  /* GLOBAL */
@@ -78,6 +78,57 @@
 #endif /* ifdef ENABLE_DEBUG */
 
 /*
+ * TLS errors are fatal in TCP mode.
+ * Also check for --tls-exit trigger.
+ */
+static inline void
+check_tls_errors(struct context *c)
+{
+    if (c->c2.tls_multi && c->c2.tls_exit_signal)
+    {
+        if (link_socket_connection_oriented(c->c2.link_socket))
+        {
+            if (c->c2.tls_multi->n_soft_errors)
+            {
+                check_tls_errors_co(c);
+            }
+        }
+        else
+        {
+            if (c->c2.tls_multi->n_hard_errors)
+            {
+                check_tls_errors_nco(c);
+            }
+        }
+    }
+}
+
+/*
+ * Set our wakeup to 0 seconds, so we will be rescheduled
+ * immediately.
+ */
+static inline void
+context_immediate_reschedule(struct context *c)
+{
+    c->c2.timeval.tv_sec = 0;  /* ZERO-TIMEOUT */
+    c->c2.timeval.tv_usec = 0;
+}
+
+static inline void
+context_reschedule_sec(struct context *c, int sec)
+{
+    if (sec < 0)
+    {
+        sec = 0;
+    }
+    if (sec < c->c2.timeval.tv_sec)
+    {
+        c->c2.timeval.tv_sec = sec;
+        c->c2.timeval.tv_usec = 0;
+    }
+}
+
+/*
  * In TLS mode, let TLS level respond to any control-channel
  * packets which were received, or prepare any packets for
  * transmission.
@@ -87,9 +138,8 @@
  * traffic on the control-channel.
  *
  */
-#ifdef ENABLE_CRYPTO
 void
-check_tls_dowork(struct context *c)
+check_tls(struct context *c)
 {
     interval_t wakeup = BIG_TIMEOUT;
 
@@ -131,7 +181,6 @@
 {
     register_signal(c, c->c2.tls_exit_signal, "tls-error"); /* SOFT-SIGUSR1 -- TLS error */
 }
-#endif /* ENABLE_CRYPTO */
 
 #if P2MP
 
@@ -140,56 +189,68 @@
  * messages on the control channel.
  */
 void
-check_incoming_control_channel_dowork(struct context *c)
+check_incoming_control_channel(struct context *c)
 {
-    const int len = tls_test_payload_len(c->c2.tls_multi);
-    if (len)
+    int len = tls_test_payload_len(c->c2.tls_multi);
+    /* We should only be called with len >0 */
+    ASSERT(len > 0);
+
+    struct gc_arena gc = gc_new();
+    struct buffer buf = alloc_buf_gc(len, &gc);
+    if (tls_rec_payload(c->c2.tls_multi, &buf))
     {
-        struct gc_arena gc = gc_new();
-        struct buffer buf = alloc_buf_gc(len, &gc);
-        if (tls_rec_payload(c->c2.tls_multi, &buf))
+        /* force null termination of message */
+        buf_null_terminate(&buf);
+
+        /* enforce character class restrictions */
+        string_mod(BSTR(&buf), CC_PRINT, CC_CRLF, 0);
+
+        if (buf_string_match_head_str(&buf, "AUTH_FAILED"))
         {
-            /* force null termination of message */
-            buf_null_terminate(&buf);
-
-            /* enforce character class restrictions */
-            string_mod(BSTR(&buf), CC_PRINT, CC_CRLF, 0);
-
-            if (buf_string_match_head_str(&buf, "AUTH_FAILED"))
-            {
-                receive_auth_failed(c, &buf);
-            }
-            else if (buf_string_match_head_str(&buf, "PUSH_"))
-            {
-                incoming_push_message(c, &buf);
-            }
-            else if (buf_string_match_head_str(&buf, "RESTART"))
-            {
-                server_pushed_signal(c, &buf, true, 7);
-            }
-            else if (buf_string_match_head_str(&buf, "HALT"))
-            {
-                server_pushed_signal(c, &buf, false, 4);
-            }
-            else
-            {
-                msg(D_PUSH_ERRORS, "WARNING: Received unknown control message: %s", BSTR(&buf));
-            }
+            receive_auth_failed(c, &buf);
+        }
+        else if (buf_string_match_head_str(&buf, "PUSH_"))
+        {
+            incoming_push_message(c, &buf);
+        }
+        else if (buf_string_match_head_str(&buf, "RESTART"))
+        {
+            server_pushed_signal(c, &buf, true, 7);
+        }
+        else if (buf_string_match_head_str(&buf, "HALT"))
+        {
+            server_pushed_signal(c, &buf, false, 4);
+        }
+        else if (buf_string_match_head_str(&buf, "INFO_PRE"))
+        {
+            server_pushed_info(c, &buf, 8);
+        }
+        else if (buf_string_match_head_str(&buf, "INFO"))
+        {
+            server_pushed_info(c, &buf, 4);
+        }
+        else if (buf_string_match_head_str(&buf, "CR_RESPONSE"))
+        {
+            receive_cr_response(c, &buf);
         }
         else
         {
-            msg(D_PUSH_ERRORS, "WARNING: Receive control message failed");
+            msg(D_PUSH_ERRORS, "WARNING: Received unknown control message: %s", BSTR(&buf));
         }
-
-        gc_free(&gc);
     }
+    else
+    {
+        msg(D_PUSH_ERRORS, "WARNING: Receive control message failed");
+    }
+
+    gc_free(&gc);
 }
 
 /*
  * Periodically resend PUSH_REQUEST until PUSH message received
  */
 void
-check_push_request_dowork(struct context *c)
+check_push_request(struct context *c)
 {
     send_push_request(c);
 
@@ -201,83 +262,89 @@
 
 /*
  * Things that need to happen immediately after connection initiation should go here.
+ *
+ * Options like --up-delay need to be triggered by this function which
+ * checks for connection establishment.
+ *
+ * Note: The process_incoming_push_reply currently assumes that this function
+ * only sets up the pull request timer when pull is enabled.
  */
 void
-check_connection_established_dowork(struct context *c)
+check_connection_established(struct context *c)
 {
-    if (event_timeout_trigger(&c->c2.wait_for_connect, &c->c2.timeval, ETT_DEFAULT))
-    {
-        if (CONNECTION_ESTABLISHED(c))
-        {
-#if P2MP
-            /* if --pull was specified, send a push request to server */
-            if (c->c2.tls_multi && c->options.pull)
-            {
-#ifdef ENABLE_MANAGEMENT
-                if (management)
-                {
-                    management_set_state(management,
-                                         OPENVPN_STATE_GET_CONFIG,
-                                         NULL,
-                                         NULL,
-                                         NULL,
-                                         NULL,
-                                         NULL);
-                }
-#endif
-                /* fire up push request right away (already 1s delayed) */
-                event_timeout_init(&c->c2.push_request_interval, 0, now);
-                reset_coarse_timers(c);
-            }
-            else
-#endif /* if P2MP */
-            {
-                do_up(c, false, 0);
-            }
 
-            event_timeout_clear(&c->c2.wait_for_connect);
+    if (CONNECTION_ESTABLISHED(c))
+    {
+#if P2MP
+        /* if --pull was specified, send a push request to server */
+        if (c->c2.tls_multi && c->options.pull)
+        {
+#ifdef ENABLE_MANAGEMENT
+            if (management)
+            {
+                management_set_state(management,
+                                     OPENVPN_STATE_GET_CONFIG,
+                                     NULL,
+                                     NULL,
+                                     NULL,
+                                     NULL,
+                                     NULL);
+            }
+#endif
+            /* fire up push request right away (already 1s delayed) */
+            event_timeout_init(&c->c2.push_request_interval, 0, now);
+            reset_coarse_timers(c);
         }
+        else
+#endif /* if P2MP */
+        {
+            do_up(c, false, 0);
+        }
+
+        event_timeout_clear(&c->c2.wait_for_connect);
     }
+
 }
 
-/*
- * Send a string to remote over the TLS control channel.
- * Used for push/pull messages, passing username/password,
- * etc.
- */
+bool
+send_control_channel_string_dowork(struct tls_multi *multi,
+                                   const char *str, int msglevel)
+{
+    struct gc_arena gc = gc_new();
+    bool stat;
+
+    /* buffered cleartext write onto TLS control channel */
+    stat = tls_send_payload(multi, (uint8_t *) str, strlen(str) + 1);
+
+    msg(msglevel, "SENT CONTROL [%s]: '%s' (status=%d)",
+        tls_common_name(multi, false),
+        sanitize_control_message(str, &gc),
+        (int) stat);
+
+    gc_free(&gc);
+    return stat;
+}
+
 bool
 send_control_channel_string(struct context *c, const char *str, int msglevel)
 {
-#ifdef ENABLE_CRYPTO
     if (c->c2.tls_multi)
     {
-        struct gc_arena gc = gc_new();
-        bool stat;
-
-        /* buffered cleartext write onto TLS control channel */
-        stat = tls_send_payload(c->c2.tls_multi, (uint8_t *) str, strlen(str) + 1);
-
+        bool ret = send_control_channel_string_dowork(c->c2.tls_multi,
+                                                      str, msglevel);
         /*
          * Reschedule tls_multi_process.
          * NOTE: in multi-client mode, usually the below two statements are
          * insufficient to reschedule the client instance object unless
          * multi_schedule_context_wakeup(m, mi) is also called.
          */
+
         interval_action(&c->c2.tmp_int);
         context_immediate_reschedule(c); /* ZERO-TIMEOUT */
-
-        msg(msglevel, "SENT CONTROL [%s]: '%s' (status=%d)",
-            tls_common_name(c->c2.tls_multi, false),
-            sanitize_control_message(str, &gc),
-            (int) stat);
-
-        gc_free(&gc);
-        return stat;
+        return ret;
     }
-#endif /* ENABLE_CRYPTO */
     return true;
 }
-
 /*
  * Add routes.
  */
@@ -286,7 +353,7 @@
 check_add_routes_action(struct context *c, const bool errors)
 {
     do_route(&c->options, c->c1.route_list, c->c1.route_ipv6_list,
-             c->c1.tuntap, c->plugins, c->c2.es);
+             c->c1.tuntap, c->plugins, c->c2.es, &c->net_ctx);
     update_time();
     event_timeout_clear(&c->c2.route_wakeup);
     event_timeout_clear(&c->c2.route_wakeup_expire);
@@ -294,7 +361,7 @@
 }
 
 void
-check_add_routes_dowork(struct context *c)
+check_add_routes(struct context *c)
 {
     if (test_routes(c->c1.route_list, c->c1.tuntap))
     {
@@ -332,7 +399,7 @@
  * Should we exit due to inactivity timeout?
  */
 void
-check_inactivity_timeout_dowork(struct context *c)
+check_inactivity_timeout(struct context *c)
 {
     msg(M_INFO, "Inactivity timeout (--inactive), exiting");
     register_signal(c, SIGTERM, "inactive");
@@ -348,7 +415,7 @@
 #if P2MP
 
 void
-check_server_poll_timeout_dowork(struct context *c)
+check_server_poll_timeout(struct context *c)
 {
     event_timeout_reset(&c->c2.server_poll_interval);
     ASSERT(c->c2.tls_multi);
@@ -378,7 +445,7 @@
  * Scheduled exit?
  */
 void
-check_scheduled_exit_dowork(struct context *c)
+check_scheduled_exit(struct context *c)
 {
     register_signal(c, c->c2.scheduled_exit_signal, "delayed-exit");
 }
@@ -389,7 +456,7 @@
  * Should we write timer-triggered status file.
  */
 void
-check_status_file_dowork(struct context *c)
+check_status_file(struct context *c)
 {
     if (c->c1.status_output)
     {
@@ -402,7 +469,7 @@
  * Should we deliver a datagram fragment to remote?
  */
 void
-check_fragment_dowork(struct context *c)
+check_fragment(struct context *c)
 {
     struct link_socket_info *lsi = get_link_socket_info(c);
 
@@ -457,16 +524,15 @@
     const uint8_t *orig_buf = c->c2.buf.data;
     struct crypto_options *co = NULL;
 
-#if P2MP_SERVER
     /*
      * Drop non-TLS outgoing packet if client-connect script/plugin
-     * has not yet succeeded.
+     * has not yet succeeded. In non-TLS mode tls_multi is not defined
+     * and we always pass packets.
      */
-    if (c->c2.context_auth != CAS_SUCCEEDED)
+    if (c->c2.tls_multi && c->c2.tls_multi->multi_state != CAS_SUCCEEDED)
     {
         c->c2.buf.len = 0;
     }
-#endif
 
     if (comp_frag)
     {
@@ -485,7 +551,6 @@
 #endif
     }
 
-#ifdef ENABLE_CRYPTO
     /* initialize work buffer with FRAME_HEADROOM bytes of prepend capacity */
     ASSERT(buf_init(&b->encrypt_buf, FRAME_HEADROOM(&c->c2.frame)));
 
@@ -518,7 +583,6 @@
         }
         tls_post_encrypt(c->c2.tls_multi, &c->c2.buf);
     }
-#endif /* ifdef ENABLE_CRYPTO */
 
     /*
      * Get the address we will be sending the packet to.
@@ -536,32 +600,55 @@
 static void
 process_coarse_timers(struct context *c)
 {
-#ifdef ENABLE_CRYPTO
     /* flush current packet-id to file once per 60
-     * seconds if --replay-persist was specified */
-    check_packet_id_persist_flush(c);
-#endif
+    * seconds if --replay-persist was specified */
+    if (packet_id_persist_enabled(&c->c1.pid_persist)
+        && event_timeout_trigger(&c->c2.packet_id_persist_interval, &c->c2.timeval, ETT_DEFAULT))
+    {
+        packet_id_persist_save(&c->c1.pid_persist);
+    }
 
-    /* should we update status file? */
-    check_status_file(c);
+    /* Should we write timer-triggered status file */
+    if (c->c1.status_output
+        && event_timeout_trigger(&c->c1.status_output->et, &c->c2.timeval, ETT_DEFAULT))
+    {
+        check_status_file(c);
+    }
 
     /* process connection establishment items */
-    check_connection_established(c);
-
+    if (event_timeout_trigger(&c->c2.wait_for_connect, &c->c2.timeval, ETT_DEFAULT))
+    {
+        check_connection_established(c);
+    }
 #if P2MP
-    /* see if we should send a push_request in response to --pull */
-    check_push_request(c);
+    /* see if we should send a push_request (option --pull) */
+    if (event_timeout_trigger(&c->c2.push_request_interval, &c->c2.timeval, ETT_DEFAULT))
+    {
+        check_push_request(c);
+    }
 #endif
 
 #ifdef PLUGIN_PF
-    pf_check_reload(c);
+    if (c->c2.pf.enabled
+        && event_timeout_trigger(&c->c2.pf.reload, &c->c2.timeval, ETT_DEFAULT))
+    {
+        pf_check_reload(c);
+    }
 #endif
 
     /* process --route options */
-    check_add_routes(c);
+    if (event_timeout_trigger(&c->c2.route_wakeup, &c->c2.timeval, ETT_DEFAULT))
+    {
+        check_add_routes(c);
+    }
 
     /* possibly exit due to --inactive */
-    check_inactivity_timeout(c);
+    if (c->options.inactivity_timeout
+        && event_timeout_trigger(&c->c2.inactivity_interval, &c->c2.timeval, ETT_DEFAULT))
+    {
+        check_inactivity_timeout(c);
+    }
+
     if (c->sig->signal_received)
     {
         return;
@@ -577,13 +664,19 @@
 #if P2MP
     if (c->c2.tls_multi)
     {
-        check_server_poll_timeout(c);
+        if (c->options.ce.connect_timeout
+            && event_timeout_trigger(&c->c2.server_poll_interval, &c->c2.timeval, ETT_DEFAULT))
+        {
+            check_server_poll_timeout(c);
+        }
         if (c->sig->signal_received)
         {
             return;
         }
-
-        check_scheduled_exit(c);
+        if (event_timeout_trigger(&c->c2.scheduled_exit, &c->c2.timeval, ETT_DEFAULT))
+        {
+            check_scheduled_exit(c);
+        }
         if (c->sig->signal_received)
         {
             return;
@@ -591,7 +684,6 @@
     }
 #endif
 
-#ifdef ENABLE_OCC
     /* Should we send an OCC_REQUEST message? */
     check_send_occ_req(c);
 
@@ -603,22 +695,27 @@
     {
         process_explicit_exit_notification_timer_wakeup(c);
     }
-#endif
 
     /* Should we ping the remote? */
     check_ping_send(c);
 }
 
 static void
-check_coarse_timers_dowork(struct context *c)
+check_coarse_timers(struct context *c)
 {
+    if (now < c->c2.coarse_timer_wakeup)
+    {
+        context_reschedule_sec(c, c->c2.coarse_timer_wakeup - now);
+        return;
+    }
+
     const struct timeval save = c->c2.timeval;
     c->c2.timeval.tv_sec = BIG_TIMEOUT;
     c->c2.timeval.tv_usec = 0;
     process_coarse_timers(c);
     c->c2.coarse_timer_wakeup = now + c->c2.timeval.tv_sec;
 
-    dmsg(D_INTERVAL, "TIMER: coarse timer wakeup %d seconds", (int) c->c2.timeval.tv_sec);
+    dmsg(D_INTERVAL, "TIMER: coarse timer wakeup %" PRIi64 " seconds", (int64_t)c->c2.timeval.tv_sec);
 
     /* Is the coarse timeout NOT the earliest one? */
     if (c->c2.timeval.tv_sec > save.tv_sec)
@@ -627,20 +724,6 @@
     }
 }
 
-static inline void
-check_coarse_timers(struct context *c)
-{
-    const time_t local_now = now;
-    if (local_now >= c->c2.coarse_timer_wakeup)
-    {
-        check_coarse_timers_dowork(c);
-    }
-    else
-    {
-        context_reschedule_sec(c, c->c2.coarse_timer_wakeup - local_now);
-    }
-}
-
 static void
 check_timeout_random_component_dowork(struct context *c)
 {
@@ -649,7 +732,7 @@
     c->c2.timeout_random_component.tv_usec = (time_t) get_random() & 0x0003FFFF;
     c->c2.timeout_random_component.tv_sec = 0;
 
-    dmsg(D_INTERVAL, "RANDOM USEC=%d", (int) c->c2.timeout_random_component.tv_usec);
+    dmsg(D_INTERVAL, "RANDOM USEC=%ld", (long) c->c2.timeout_random_component.tv_usec);
 }
 
 static inline void
@@ -752,14 +835,12 @@
             }
             else
             {
-#ifdef ENABLE_OCC
                 if (event_timeout_defined(&c->c2.explicit_exit_notification_interval))
                 {
                     msg(D_STREAM_ERRORS, "Connection reset during exit notification period, ignoring [%d]", status);
                     management_sleep(1);
                 }
                 else
-#endif
                 {
                     register_signal(c, SIGUSR1, "connection-reset"); /* SOFT-SIGUSR1 -- TCP connection reset */
                     msg(D_STREAM_ERRORS, "Connection reset, restarting [%d]", status);
@@ -852,7 +933,6 @@
             link_socket_bad_incoming_addr(&c->c2.buf, lsi, &c->c2.from);
         }
 
-#ifdef ENABLE_CRYPTO
         if (c->c2.tls_multi)
         {
             /*
@@ -870,7 +950,7 @@
                                 floated, &ad_start))
             {
                 /* Restore pre-NCP frame parameters */
-                if (is_hard_reset(opcode, c->options.key_method))
+                if (is_hard_reset_method2(opcode))
                 {
                     c->c2.frame = c->c2.frame_initial;
 #ifdef ENABLE_FRAGMENT
@@ -891,16 +971,16 @@
         {
             co = &c->c2.crypto_options;
         }
-#if P2MP_SERVER
+
         /*
-         * Drop non-TLS packet if client-connect script/plugin has not
-         * yet succeeded.
+         * Drop non-TLS packet if client-connect script/plugin and cipher selection
+         * has not yet succeeded. In non-TLS mode tls_multi is not defined
+         * and we always pass packets.
          */
-        if (c->c2.context_auth != CAS_SUCCEEDED)
+        if (c->c2.tls_multi && c->c2.tls_multi->multi_state != CAS_SUCCEEDED)
         {
             c->c2.buf.len = 0;
         }
-#endif
 
         /* authenticate and decrypt the incoming packet */
         decrypt_status = openvpn_decrypt(&c->c2.buf, c->c2.buffers->decrypt_buf,
@@ -912,9 +992,6 @@
             register_signal(c, SIGUSR1, "decryption-error"); /* SOFT-SIGUSR1 -- decryption error in TCP mode */
             msg(D_STREAM_ERRORS, "Fatal decryption error (process_incoming_link), restarting");
         }
-#else /* ENABLE_CRYPTO */
-        decrypt_status = true;
-#endif /* ENABLE_CRYPTO */
     }
     else
     {
@@ -963,9 +1040,9 @@
          *
          * Also, update the persisted version of our packet-id.
          */
-        if (!TLS_MODE(c))
+        if (!TLS_MODE(c) && c->c2.buf.len > 0)
         {
-            link_socket_set_outgoing_addr(&c->c2.buf, lsi, &c->c2.from, NULL, c->c2.es);
+            link_socket_set_outgoing_addr(lsi, &c->c2.from, NULL, c->c2.es);
         }
 
         /* reset packet received timer */
@@ -988,13 +1065,11 @@
             c->c2.buf.len = 0; /* drop packet */
         }
 
-#ifdef ENABLE_OCC
         /* Did we just receive an OCC packet? */
         if (is_occ_msg(&c->c2.buf))
         {
             process_received_occ_msg(c);
         }
-#endif
 
         buffer_turnover(orig_buf, &c->c2.to_tun, &c->c2.buf, &c->c2.buffers->read_link_buf);
 
@@ -1039,13 +1114,29 @@
     perf_push(PERF_READ_IN_TUN);
 
     c->c2.buf = c->c2.buffers->read_tun_buf;
-#ifdef TUN_PASS_BUFFER
-    read_tun_buffered(c->c1.tuntap, &c->c2.buf);
-#else
+
+#ifdef _WIN32
+    if (c->c1.tuntap->windows_driver == WINDOWS_DRIVER_WINTUN)
+    {
+        read_wintun(c->c1.tuntap, &c->c2.buf);
+        if (c->c2.buf.len == -1)
+        {
+            register_signal(c, SIGHUP, "tun-abort");
+            c->persist.restart_sleep_seconds = 1;
+            msg(M_INFO, "Wintun read error, restarting");
+            perf_pop();
+            return;
+        }
+    }
+    else
+    {
+        read_tun_buffered(c->c1.tuntap, &c->c2.buf);
+    }
+#else  /* ifdef _WIN32 */
     ASSERT(buf_init(&c->c2.buf, FRAME_HEADROOM(&c->c2.frame)));
     ASSERT(buf_safe(&c->c2.buf, MAX_RW_SIZE_TUN(&c->c2.frame)));
     c->c2.buf.len = read_tun(c->c1.tuntap, BPTR(&c->c2.buf), MAX_RW_SIZE_TUN(&c->c2.frame));
-#endif
+#endif /* ifdef _WIN32 */
 
 #ifdef PACKET_TRUNCATION_CHECK
     ipv4_packet_size_verify(BPTR(&c->c2.buf),
@@ -1201,7 +1292,9 @@
          * The --passtos and --mssfix options require
          * us to examine the IP header (IPv4 or IPv6).
          */
-        process_ip_header(c, PIPV4_PASSTOS|PIP_MSSFIX|PIPV4_CLIENT_NAT, &c->c2.buf);
+        unsigned int flags = PIPV4_PASSTOS | PIP_MSSFIX | PIPV4_CLIENT_NAT
+                             | PIPV6_IMCP_NOHOST_CLIENT;
+        process_ip_header(c, flags, &c->c2.buf);
 
 #ifdef PACKET_TRUNCATION_CHECK
         /* if (c->c2.buf.len > 1) --c->c2.buf.len; */
@@ -1212,6 +1305,9 @@
                                 &c->c2.n_trunc_pre_encrypt);
 #endif
 
+    }
+    if (c->c2.buf.len > 0)
+    {
         encrypt_sign(c, true);
     }
     else
@@ -1222,6 +1318,142 @@
     gc_free(&gc);
 }
 
+/**
+ * Forges a IPv6 ICMP packet with a no route to host error code from the
+ * IPv6 packet in buf and sends it directly back to the client via the tun
+ * device when used on a client and via the link if used on the server.
+ *
+ * @param buf       - The buf containing the packet for which the icmp6
+ *                    unreachable should be constructed.
+ *
+ * @param client    - determines whether to the send packet back via tun or link
+ */
+void
+ipv6_send_icmp_unreachable(struct context *c, struct buffer *buf, bool client)
+{
+#define MAX_ICMPV6LEN 1280
+    struct openvpn_icmp6hdr icmp6out;
+    CLEAR(icmp6out);
+
+    /*
+     * Get a buffer to the ip packet, is_ipv6 automatically forwards
+     * the buffer to the ip packet
+     */
+    struct buffer inputipbuf = *buf;
+
+    is_ipv6(TUNNEL_TYPE(c->c1.tuntap), &inputipbuf);
+
+    if (BLEN(&inputipbuf) < (int)sizeof(struct openvpn_ipv6hdr))
+    {
+        return;
+    }
+
+    const struct openvpn_ipv6hdr *pip6 = (struct openvpn_ipv6hdr *)BPTR(&inputipbuf);
+
+    /* Copy version, traffic class, flow label from input packet */
+    struct openvpn_ipv6hdr pip6out = *pip6;
+
+    pip6out.version_prio = pip6->version_prio;
+    pip6out.daddr = pip6->saddr;
+
+    /*
+     * Use the IPv6 remote address if we have one, otherwise use a fake one
+     * using the remote address is preferred since it makes debugging and
+     * understanding where the ICMPv6 error originates easier
+     */
+    if (c->options.ifconfig_ipv6_remote)
+    {
+        inet_pton(AF_INET6, c->options.ifconfig_ipv6_remote, &pip6out.saddr);
+    }
+    else
+    {
+        inet_pton(AF_INET6, "fe80::7", &pip6out.saddr);
+    }
+
+    pip6out.nexthdr = OPENVPN_IPPROTO_ICMPV6;
+
+    /*
+     * The ICMPv6 unreachable code worked best in my (arne) tests with Windows,
+     * Linux and Android. Windows did not like the administratively prohibited
+     * return code (no fast fail)
+     */
+    icmp6out.icmp6_type = OPENVPN_ICMP6_DESTINATION_UNREACHABLE;
+    icmp6out.icmp6_code = OPENVPN_ICMP6_DU_NOROUTE;
+
+    int icmpheader_len = sizeof(struct openvpn_ipv6hdr)
+                         + sizeof(struct openvpn_icmp6hdr);
+    int totalheader_len = icmpheader_len;
+
+    if (TUNNEL_TYPE(c->c1.tuntap) == DEV_TYPE_TAP)
+    {
+        totalheader_len += sizeof(struct openvpn_ethhdr);
+    }
+
+    /*
+     * Calculate size for payload, defined in the standard that the resulting
+     * frame should be <= 1280 and have as much as possible of the original
+     * packet
+     */
+    int max_payload_size = min_int(MAX_ICMPV6LEN,
+                                   TUN_MTU_SIZE(&c->c2.frame) - icmpheader_len);
+    int payload_len = min_int(max_payload_size, BLEN(&inputipbuf));
+
+    pip6out.payload_len = htons(sizeof(struct openvpn_icmp6hdr) + payload_len);
+
+    /* Construct the packet as outgoing packet back to the client */
+    struct buffer *outbuf;
+    if (client)
+    {
+        c->c2.to_tun = c->c2.buffers->aux_buf;
+        outbuf = &(c->c2.to_tun);
+    }
+    else
+    {
+        c->c2.to_link = c->c2.buffers->aux_buf;
+        outbuf = &(c->c2.to_link);
+    }
+    ASSERT(buf_init(outbuf, totalheader_len));
+
+    /* Fill the end of the buffer with original packet */
+    ASSERT(buf_safe(outbuf, payload_len));
+    ASSERT(buf_copy_n(outbuf, &inputipbuf, payload_len));
+
+    /* ICMP Header, copy into buffer to allow checksum calculation */
+    ASSERT(buf_write_prepend(outbuf, &icmp6out, sizeof(struct openvpn_icmp6hdr)));
+
+    /* Calculate checksum over the packet and write to header */
+
+    uint16_t new_csum = ip_checksum(AF_INET6, BPTR(outbuf), BLEN(outbuf),
+                                    (const uint8_t *)&pip6out.saddr,
+                                    (uint8_t *)&pip6out.daddr, OPENVPN_IPPROTO_ICMPV6);
+    ((struct openvpn_icmp6hdr *) BPTR(outbuf))->icmp6_cksum = htons(new_csum);
+
+
+    /* IPv6 Header */
+    ASSERT(buf_write_prepend(outbuf, &pip6out, sizeof(struct openvpn_ipv6hdr)));
+
+    /*
+     * Tap mode, we also need to create an Ethernet header.
+     */
+    if (TUNNEL_TYPE(c->c1.tuntap) == DEV_TYPE_TAP)
+    {
+        if (BLEN(buf) < (int)sizeof(struct openvpn_ethhdr))
+        {
+            return;
+        }
+
+        const struct openvpn_ethhdr *orig_ethhdr = (struct openvpn_ethhdr *) BPTR(buf);
+
+        /* Copy frametype and reverse source/destination for the response */
+        struct openvpn_ethhdr ethhdr;
+        memcpy(ethhdr.source, orig_ethhdr->dest, OPENVPN_ETH_ALEN);
+        memcpy(ethhdr.dest, orig_ethhdr->source, OPENVPN_ETH_ALEN);
+        ethhdr.proto = htons(OPENVPN_ETH_P_IPV6);
+        ASSERT(buf_write_prepend(outbuf, &ethhdr, sizeof(struct openvpn_ethhdr)));
+    }
+#undef MAX_ICMPV6LEN
+}
+
 void
 process_ip_header(struct context *c, unsigned int flags, struct buffer *buf)
 {
@@ -1243,6 +1475,10 @@
     {
         flags &= ~PIPV4_EXTRACT_DHCP_ROUTER;
     }
+    if (!c->options.block_ipv6)
+    {
+        flags &= ~(PIPV6_IMCP_NOHOST_CLIENT | PIPV6_IMCP_NOHOST_SERVER);
+    }
 
     if (buf->len > 0)
     {
@@ -1278,7 +1514,7 @@
                 /* possibly do NAT on packet */
                 if ((flags & PIPV4_CLIENT_NAT) && c->options.client_nat)
                 {
-                    const int direction = (flags & PIPV4_OUTGOING) ? CN_INCOMING : CN_OUTGOING;
+                    const int direction = (flags & PIP_OUTGOING) ? CN_INCOMING : CN_OUTGOING;
                     client_nat_transform(c->options.client_nat, &ipbuf, direction);
                 }
                 /* possibly extract a DHCP router message */
@@ -1296,8 +1532,18 @@
                 /* possibly alter the TCP MSS */
                 if (flags & PIP_MSSFIX)
                 {
-                    mss_fixup_ipv6(&ipbuf, MTU_TO_MSS(TUN_MTU_SIZE_DYNAMIC(&c->c2.frame)));
+                    mss_fixup_ipv6(&ipbuf,
+                                   MTU_TO_MSS(TUN_MTU_SIZE_DYNAMIC(&c->c2.frame)));
                 }
+                if (!(flags & PIP_OUTGOING) && (flags
+                                                &(PIPV6_IMCP_NOHOST_CLIENT | PIPV6_IMCP_NOHOST_SERVER)))
+                {
+                    ipv6_send_icmp_unreachable(c, buf,
+                                               (bool)(flags & PIPV6_IMCP_NOHOST_CLIENT));
+                    /* Drop the IPv6 packet */
+                    buf->len = 0;
+                }
+
             }
         }
     }
@@ -1429,8 +1675,6 @@
             register_activity(c, size);
         }
 
-
-#ifdef ENABLE_CRYPTO
         /* for unreachable network and "connecting" state switch to the next host */
         if (size < 0 && ENETUNREACH == error_code && c->c2.tls_multi
             && !tls_initial_packet_received(c->c2.tls_multi) && c->options.mode == MODE_POINT_TO_POINT)
@@ -1438,7 +1682,6 @@
             msg(M_INFO, "Network unreachable, restarting");
             register_signal(c, SIGUSR1, "network-unreachable");
         }
-#endif
     }
     else
     {
@@ -1481,7 +1724,9 @@
      * The --mssfix option requires
      * us to examine the IP header (IPv4 or IPv6).
      */
-    process_ip_header(c, PIP_MSSFIX|PIPV4_EXTRACT_DHCP_ROUTER|PIPV4_CLIENT_NAT|PIPV4_OUTGOING, &c->c2.to_tun);
+    process_ip_header(c,
+                      PIP_MSSFIX | PIPV4_EXTRACT_DHCP_ROUTER | PIPV4_CLIENT_NAT | PIP_OUTGOING,
+                      &c->c2.to_tun);
 
     if (c->c2.to_tun.len <= MAX_RW_SIZE_TUN(&c->c2.frame))
     {
@@ -1506,7 +1751,7 @@
                                 &c->c2.n_trunc_tun_write);
 #endif
 
-#ifdef TUN_PASS_BUFFER
+#ifdef _WIN32
         size = write_tun_buffered(c->c1.tuntap, &c->c2.to_tun);
 #else
         size = write_tun(c->c1.tuntap, BPTR(&c->c2.to_tun), BLEN(&c->c2.to_tun));
@@ -1583,8 +1828,11 @@
         return;
     }
 
-    /* Does TLS need service? */
-    check_tls(c);
+    /* If tls is enabled, do tls control channel packet processing. */
+    if (c->c2.tls_multi)
+    {
+        check_tls(c);
+    }
 
     /* In certain cases, TLS errors will require a restart */
     check_tls_errors(c);
@@ -1593,17 +1841,24 @@
         return;
     }
 
-    /* check for incoming configuration info on the control channel */
-    check_incoming_control_channel(c);
+#if P2MP
+    /* check for incoming control messages on the control channel like
+     * push request/reply, or authentication failure and 2FA messages */
+    if (tls_test_payload_len(c->c2.tls_multi) > 0)
+    {
+        check_incoming_control_channel(c);
+    }
+#endif
 
-#ifdef ENABLE_OCC
     /* Should we send an OCC message? */
     check_send_occ_msg(c);
-#endif
 
 #ifdef ENABLE_FRAGMENT
     /* Should we deliver a datagram fragment to remote? */
-    check_fragment(c);
+    if (c->c2.fragment)
+    {
+        check_fragment(c);
+    }
 #endif
 
     /* Update random component of timeout */
@@ -1728,6 +1983,17 @@
         tuntap |= EVENT_READ;
     }
 
+#ifdef _WIN32
+    if (tuntap_is_wintun(c->c1.tuntap))
+    {
+        /*
+         * With wintun we are only interested in read event. Ring buffer is
+         * always ready for write, so we don't do wait.
+         */
+        tuntap = EVENT_READ;
+    }
+#endif
+
     /*
      * Configure event wait based on socket, tuntap flags.
      */
diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h
index 924cc5e..a8b19f6 100644
--- a/src/openvpn/forward.h
+++ b/src/openvpn/forward.h
@@ -31,9 +31,9 @@
 #ifndef FORWARD_H
 #define FORWARD_H
 
-#include "openvpn.h"
-#include "occ.h"
-#include "ping.h"
+/* the following macros must be defined before including any other header
+ * file
+ */
 
 #define TUN_OUT(c)      (BLEN(&(c)->c2.to_tun) > 0)
 #define LINK_OUT(c)     (BLEN(&(c)->c2.to_link) > 0)
@@ -47,6 +47,10 @@
 
 #define TO_LINK_DEF(c)  (LINK_OUT(c) || TO_LINK_FRAG(c))
 
+#include "openvpn.h"
+#include "occ.h"
+#include "ping.h"
+
 #define IOW_TO_TUN          (1<<0)
 #define IOW_TO_LINK         (1<<1)
 #define IOW_READ_TUN        (1<<2)
@@ -60,6 +64,41 @@
 
 #define IOW_READ            (IOW_READ_TUN|IOW_READ_LINK)
 
+extern counter_type link_read_bytes_global;
+
+extern counter_type link_write_bytes_global;
+
+void check_tls(struct context *c);
+
+void check_tls_errors_co(struct context *c);
+
+void check_tls_errors_nco(struct context *c);
+
+#if P2MP
+void check_incoming_control_channel(struct context *c);
+
+void check_scheduled_exit(struct context *c);
+
+void check_push_request(struct context *c);
+
+#endif /* P2MP */
+
+#ifdef ENABLE_FRAGMENT
+void check_fragment(struct context *c);
+
+#endif /* ENABLE_FRAGMENT */
+
+void check_connection_established(struct context *c);
+
+void check_add_routes(struct context *c);
+
+void check_inactivity_timeout(struct context *c);
+
+void check_server_poll_timeout(struct context *c);
+
+void check_status_file(struct context *c);
+
+void io_wait_dowork(struct context *c, const unsigned int flags);
 
 void pre_select(struct context *c);
 
@@ -247,13 +286,44 @@
 
 /**************************************************************************/
 
-bool send_control_channel_string(struct context *c, const char *str, int msglevel);
+/*
+ * Send a string to remote over the TLS control channel.
+ * Used for push/pull messages, passing username/password,
+ * etc.
+ * @param c          - The context structure of the VPN tunnel associated with
+ *                     the packet.
+ * @param str        - The message to be sent
+ * @param msglevel   - Message level to use for logging
+ */
+bool
+send_control_channel_string(struct context *c, const char *str, int msglevel);
 
-#define PIPV4_PASSTOS         (1<<0)
-#define PIP_MSSFIX            (1<<1)         /* v4 and v6 */
-#define PIPV4_OUTGOING        (1<<2)
-#define PIPV4_EXTRACT_DHCP_ROUTER (1<<3)
-#define PIPV4_CLIENT_NAT      (1<<4)
+/*
+ * Send a string to remote over the TLS control channel.
+ * Used for push/pull messages, passing username/password,
+ * etc.
+ *
+ * This variant does not schedule the actual sending of the message
+ * The caller needs to ensure that it is scheduled or call
+ * send_control_channel_string
+ *
+ * @param multi      - The tls_multi structure of the VPN tunnel associated
+ *                     with the packet.
+ * @param str        - The message to be sent
+ * @param msglevel   - Message level to use for logging
+ */
+
+bool
+send_control_channel_string_dowork(struct tls_multi *multi,
+                                   const char *str, int msglevel);
+
+#define PIPV4_PASSTOS                   (1<<0)
+#define PIP_MSSFIX                      (1<<1)         /* v4 and v6 */
+#define PIP_OUTGOING                    (1<<2)
+#define PIPV4_EXTRACT_DHCP_ROUTER       (1<<3)
+#define PIPV4_CLIENT_NAT                (1<<4)
+#define PIPV6_IMCP_NOHOST_CLIENT        (1<<5)
+#define PIPV6_IMCP_NOHOST_SERVER        (1<<6)
 
 void process_ip_header(struct context *c, unsigned int flags, struct buffer *buf);
 
@@ -262,4 +332,116 @@
 
 #endif
 
+static inline struct link_socket_info *
+get_link_socket_info(struct context *c)
+{
+    if (c->c2.link_socket_info)
+    {
+        return c->c2.link_socket_info;
+    }
+    else
+    {
+        return &c->c2.link_socket->info;
+    }
+}
+
+static inline void
+register_activity(struct context *c, const int size)
+{
+    if (c->options.inactivity_timeout)
+    {
+        c->c2.inactivity_bytes += size;
+        if (c->c2.inactivity_bytes >= c->options.inactivity_minimum_bytes)
+        {
+            c->c2.inactivity_bytes = 0;
+            event_timeout_reset(&c->c2.inactivity_interval);
+        }
+    }
+}
+
+/*
+ * Return the io_wait() flags appropriate for
+ * a point-to-point tunnel.
+ */
+static inline unsigned int
+p2p_iow_flags(const struct context *c)
+{
+    unsigned int flags = (IOW_SHAPER|IOW_CHECK_RESIDUAL|IOW_FRAG|IOW_READ|IOW_WAIT_SIGNAL);
+    if (c->c2.to_link.len > 0)
+    {
+        flags |= IOW_TO_LINK;
+    }
+    if (c->c2.to_tun.len > 0)
+    {
+        flags |= IOW_TO_TUN;
+    }
+#ifdef _WIN32
+    if (tuntap_ring_empty(c->c1.tuntap))
+    {
+        flags &= ~IOW_READ_TUN;
+    }
+#endif
+    return flags;
+}
+
+/*
+ * This is the core I/O wait function, used for all I/O waits except
+ * for TCP in server mode.
+ */
+static inline void
+io_wait(struct context *c, const unsigned int flags)
+{
+    void io_wait_dowork(struct context *c, const unsigned int flags);
+
+    if (c->c2.fast_io && (flags & (IOW_TO_TUN|IOW_TO_LINK|IOW_MBUF)))
+    {
+        /* fast path -- only for TUN/TAP/UDP writes */
+        unsigned int ret = 0;
+        if (flags & IOW_TO_TUN)
+        {
+            ret |= TUN_WRITE;
+        }
+        if (flags & (IOW_TO_LINK|IOW_MBUF))
+        {
+            ret |= SOCKET_WRITE;
+        }
+        c->c2.event_set_status = ret;
+    }
+    else
+    {
+#ifdef _WIN32
+        bool skip_iowait = flags & IOW_TO_TUN;
+        if (flags & IOW_READ_TUN)
+        {
+            /*
+             * don't read from tun if we have pending write to link,
+             * since every tun read overwrites to_link buffer filled
+             * by previous tun read
+             */
+            skip_iowait = !(flags & IOW_TO_LINK);
+        }
+        if (tuntap_is_wintun(c->c1.tuntap) && skip_iowait)
+        {
+            unsigned int ret = 0;
+            if (flags & IOW_TO_TUN)
+            {
+                ret |= TUN_WRITE;
+            }
+            if (flags & IOW_READ_TUN)
+            {
+                ret |= TUN_READ;
+            }
+            c->c2.event_set_status = ret;
+        }
+        else
+#endif /* ifdef _WIN32 */
+        {
+            /* slow path */
+            io_wait_dowork(c, flags);
+        }
+    }
+}
+
+#define CONNECTION_ESTABLISHED(c) (get_link_socket_info(c)->connection_established)
+
 #endif /* FORWARD_H */
diff --git a/src/openvpn/fragment.c b/src/openvpn/fragment.c
index 4eb1dd2..6df71d0 100644
--- a/src/openvpn/fragment.c
+++ b/src/openvpn/fragment.c
@@ -31,6 +31,7 @@
 
 #ifdef ENABLE_FRAGMENT
 
+#include "crypto.h"
 #include "misc.h"
 #include "fragment.h"
 #include "integer.h"
@@ -177,7 +178,7 @@
 
             if (flags & (FRAG_SEQ_ID_MASK | FRAG_ID_MASK))
             {
-                FRAG_ERR("spurrious FRAG_WHOLE flags");
+                FRAG_ERR("spurious FRAG_WHOLE flags");
             }
         }
         else if (frag_type == FRAG_YES_NOTLAST || frag_type == FRAG_YES_LAST)
diff --git a/src/openvpn/gremlin.c b/src/openvpn/gremlin.c
index 114cb19..3f2bded 100644
--- a/src/openvpn/gremlin.c
+++ b/src/openvpn/gremlin.c
@@ -38,6 +38,7 @@
 
 #include "error.h"
 #include "common.h"
+#include "crypto.h"
 #include "misc.h"
 #include "otime.h"
 #include "gremlin.h"
diff --git a/src/openvpn/helper.c b/src/openvpn/helper.c
index ff9df50..a1d0307 100644
--- a/src/openvpn/helper.c
+++ b/src/openvpn/helper.c
@@ -36,7 +36,6 @@
 
 #include "memdbg.h"
 
-#if P2MP_SERVER
 
 static const char *
 print_netmask(int netbits, struct gc_arena *gc)
@@ -139,7 +138,6 @@
     gc_free(&gc);
 }
 
-#endif /* if P2MP_SERVER */
 
 /*
  * Process server, server-bridge, and client helper
@@ -152,7 +150,6 @@
     struct gc_arena gc = gc_new();
 
 #if P2MP
-#if P2MP_SERVER
 
 /*
  * Get tun/tap/null device type
@@ -177,10 +174,11 @@
      */
     if (o->server_ipv6_defined)
     {
-        if (!o->server_defined)
+        if (o->client)
         {
-            msg(M_USAGE, "--server-ipv6 must be used together with --server");
+            msg(M_USAGE, "--server-ipv6 and --client cannot be used together");
         }
+
         if (o->server_flags & SF_NOPOOL)
         {
             msg( M_USAGE, "--server-ipv6 is incompatible with 'nopool' option" );
@@ -190,6 +188,9 @@
             msg( M_USAGE, "--server-ipv6 already defines an ifconfig-ipv6-pool, so you can't also specify --ifconfig-pool explicitly");
         }
 
+        o->mode = MODE_SERVER;
+        o->tls_server = true;
+
         /* local ifconfig is "base address + 1" and "+2" */
         o->ifconfig_ipv6_local =
             print_in6_addr( add_in6_addr( o->server_network_ipv6, 1), 0, &o->gc );
@@ -197,12 +198,17 @@
             print_in6_addr( add_in6_addr( o->server_network_ipv6, 2), 0, &o->gc );
         o->ifconfig_ipv6_netbits = o->server_netbits_ipv6;
 
-        /* pool starts at "base address + 0x1000" - leave enough room */
-        ASSERT( o->server_netbits_ipv6 <= 112 );        /* want 16 bits */
+        /* basic sanity check */
+        ASSERT(o->server_netbits_ipv6 >= 64 && o->server_netbits_ipv6 <= 124);
 
         o->ifconfig_ipv6_pool_defined = true;
-        o->ifconfig_ipv6_pool_base =
-            add_in6_addr( o->server_network_ipv6, 0x1000 );
+        /* For large enough pools we keep the original behaviour of adding
+         * 0x1000 when computing the base.
+         *
+         * Smaller pools can't get that far, therefore we just increase by 2
+         */
+        o->ifconfig_ipv6_pool_base = add_in6_addr(o->server_network_ipv6,
+                                                  o->server_netbits_ipv6 < 112 ? 0x1000 : 2);
         o->ifconfig_ipv6_pool_netbits = o->server_netbits_ipv6;
 
         push_option( o, "tun-ipv6", M_USAGE );
@@ -353,6 +359,14 @@
             }
 
             push_option(o, print_opt_topology(topology, &o->gc), M_USAGE);
+
+            if (topology == TOP_NET30 && !(o->server_flags & SF_NOPOOL))
+            {
+                msg(M_WARN, "WARNING: --topology net30 support for server "
+                    "configs with IPv4 pools will be removed in a future "
+                    "release. Please migrate to --topology subnet as soon "
+                    "as possible.");
+            }
         }
         else if (dev == DEV_TYPE_TAP)
         {
@@ -464,8 +478,6 @@
         }
     }
     else
-#endif /* P2MP_SERVER */
-
     /*
      * HELPER DIRECTIVE:
      *
@@ -478,11 +490,6 @@
      */
     if (o->client)
     {
-        if (o->key_method != 2)
-        {
-            msg(M_USAGE, "--client requires --key-method 2");
-        }
-
         o->pull = true;
         o->tls_client = true;
     }
@@ -541,7 +548,6 @@
             o->ping_send_timeout = o->keepalive_ping;
             o->ping_rec_timeout = o->keepalive_timeout;
         }
-#if P2MP_SERVER
         else if (o->mode == MODE_SERVER)
         {
             o->ping_rec_timeout_action = PING_RESTART;
@@ -550,7 +556,6 @@
             push_option(o, print_str_int("ping", o->keepalive_ping, &o->gc), M_USAGE);
             push_option(o, print_str_int("ping-restart", o->keepalive_timeout, &o->gc), M_USAGE);
         }
-#endif
         else
         {
             ASSERT(0);
@@ -573,7 +578,6 @@
 void
 helper_tcp_nodelay(struct options *o)
 {
-#if P2MP_SERVER
     if (o->server_flags & SF_TCP_NODELAY_HELPER)
     {
         if (o->mode == MODE_SERVER)
@@ -586,5 +590,4 @@
             o->sockflags |= SF_TCP_NODELAY;
         }
     }
-#endif
 }
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index 8bac74f..23c0692 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -35,26 +35,30 @@
 
 #include "win32.h"
 #include "init.h"
+#include "run_command.h"
 #include "sig.h"
 #include "occ.h"
 #include "list.h"
 #include "otime.h"
 #include "pool.h"
 #include "gremlin.h"
+#include "occ.h"
 #include "pkcs11.h"
 #include "ps.h"
 #include "lladdr.h"
 #include "ping.h"
 #include "mstats.h"
 #include "ssl_verify.h"
+#include "ssl_ncp.h"
 #include "tls_crypt.h"
-#include "forward-inline.h"
+#include "forward.h"
+#include "auth_token.h"
 
 #include "memdbg.h"
 
-#include "occ-inline.h"
 
 static struct context *static_context; /* GLOBAL */
+static const char *saved_pid_file_name; /* GLOBAL */
 
 /*
  * Crypto initialization flags
@@ -162,7 +166,7 @@
             msg(M_FATAL, "ERROR: up/down plugin call failed");
         }
 
-        argv_reset(&argv);
+        argv_free(&argv);
     }
 
     if (command)
@@ -175,7 +179,7 @@
                         ifconfig_local, ifconfig_remote, context);
         argv_msg(M_INFO, &argv);
         openvpn_run_script(&argv, es, S_FATAL, "--up/--down");
-        argv_reset(&argv);
+        argv_free(&argv);
     }
 
     gc_free(&gc);
@@ -269,6 +273,7 @@
             buf_printf(&out, ">PROXY:%u,%s,%s", (l ? l->current : 0) + 1,
                        (proto_is_udp(ce->proto) ? "UDP" : "TCP"), np(ce->remote));
             management_notify_generic(management, BSTR(&out));
+            management->persist.special_state_msg = BSTR(&out);
         }
         ce->flags |= CE_MAN_QUERY_PROXY;
         while (ce->flags & CE_MAN_QUERY_PROXY)
@@ -280,12 +285,51 @@
                 break;
             }
         }
+        management->persist.special_state_msg = NULL;
         gc_free(&gc);
     }
 
     return ret;
 }
 
+/**
+ * This method sends a custom control channel message
+ *
+ * This will write the control message
+ *
+ *  command parm1,parm2,..
+ *  .
+ * to the control channel.
+ *
+ * @param arg           The context struct
+ * @param command       The command being sent
+ * @param parameters    the parameters to the command
+ * @return              if sending was successful
+ */
+static bool
+management_callback_send_cc_message(void *arg,
+                                    const char *command,
+                                    const char *parameters)
+{
+    struct context *c = (struct context *) arg;
+    size_t len = strlen(command) + 1 + strlen(parameters) + 1;
+    if (len > PUSH_BUNDLE_SIZE)
+    {
+        return false;
+    }
+
+    struct gc_arena gc = gc_new();
+    struct buffer buf = alloc_buf_gc(len, &gc);
+    ASSERT(buf_printf(&buf, "%s", command));
+    if (parameters)
+    {
+        ASSERT(buf_printf(&buf, ",%s", parameters));
+    }
+    bool status = send_control_channel_string(c, BSTR(&buf), D_PUSH);
+
+    gc_free(&gc);
+    return status;
+}
 
 static bool
 management_callback_remote_cmd(void *arg, const char **p)
@@ -349,6 +393,7 @@
         buf_printf(&out, ">REMOTE:%s,%s,%s", np(ce->remote), ce->remote_port,
                    proto2ascii(ce->proto, ce->af, false));
         management_notify_generic(management, BSTR(&out));
+        management->persist.special_state_msg = BSTR(&out);
 
         ce->flags &= ~(CE_MAN_QUERY_REMOTE_MASK << CE_MAN_QUERY_REMOTE_SHIFT);
         ce->flags |= (CE_MAN_QUERY_REMOTE_QUERY << CE_MAN_QUERY_REMOTE_SHIFT);
@@ -362,6 +407,7 @@
                 break;
             }
         }
+        management->persist.special_state_msg = NULL;
     }
     gc_free(&gc);
 
@@ -454,6 +500,17 @@
                  */
                 if (!c->options.persist_remote_ip)
                 {
+                    /* Connection entry addrinfo objects might have been
+                     * resolved earlier but the entry itself might have been
+                     * skipped by management on the previous loop.
+                     * If so, clear the addrinfo objects as close_instance does
+                     */
+                    if (c->c1.link_socket_addr.remote_list)
+                    {
+                        clear_remote_addrlist(&c->c1.link_socket_addr,
+                                              !c->options.resolve_in_advance);
+                    }
+
                     /* close_instance should have cleared the addrinfo objects */
                     ASSERT(c->c1.link_socket_addr.current_remote == NULL);
                     ASSERT(c->c1.link_socket_addr.remote_list == NULL);
@@ -529,19 +586,17 @@
 void
 init_query_passwords(const struct context *c)
 {
-#ifdef ENABLE_CRYPTO
     /* Certificate password input */
     if (c->options.key_pass_file)
     {
         pem_password_setup(c->options.key_pass_file);
     }
-#endif
 
 #if P2MP
     /* Auth user/pass input */
     if (c->options.auth_user_pass_file)
     {
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
         auth_user_pass_setup(c->options.auth_user_pass_file, &c->options.sc_info);
 #else
         auth_user_pass_setup(c->options.auth_user_pass_file, NULL);
@@ -620,22 +675,18 @@
 static void
 save_ncp_options(struct context *c)
 {
-#ifdef ENABLE_CRYPTO
     c->c1.ciphername = c->options.ciphername;
     c->c1.authname = c->options.authname;
     c->c1.keysize = c->options.keysize;
-#endif
 }
 
 /* Restores NCP-negotiable options to original values */
 static void
 restore_ncp_options(struct context *c)
 {
-#ifdef ENABLE_CRYPTO
     c->options.ciphername = c->c1.ciphername;
     c->options.authname = c->c1.authname;
     c->options.keysize = c->c1.keysize;
-#endif
 }
 
 void
@@ -731,7 +782,7 @@
 {
     /* configure_path (); */
 
-#if defined(ENABLE_CRYPTO) && defined(DMALLOC)
+#if defined(DMALLOC)
     crypto_init_dmalloc();
 #endif
 
@@ -768,14 +819,12 @@
 
     update_time();
 
-#ifdef ENABLE_CRYPTO
     init_ssl_lib();
 
     /* init PRNG used for IV generation */
     /* When forking, copy this to more places in the code to avoid fork
      * random-state predictability */
     prng_init(NULL, 0);
-#endif
 
 #ifdef PID_TEST
     packet_id_interactive_test();       /* test the sequence number code */
@@ -838,7 +887,7 @@
 #ifdef STATUS_PRINTF_TEST
     {
         struct gc_arena gc = gc_new();
-        const char *tmp_file = create_temp_file("/tmp", "foo", &gc);
+        const char *tmp_file = platform_create_temp_file("/tmp", "foo", &gc);
         struct status_output *so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
         status_printf(so, "%s", "foo");
         status_printf(so, "%s", "bar");
@@ -851,15 +900,6 @@
     return false;
 #endif
 
-#ifdef ARGV_TEST
-    {
-        void argv_test(void);
-
-        argv_test();
-        return false;
-    }
-#endif
-
 #ifdef PRNG_TEST
     {
         struct gc_arena gc = gc_new();
@@ -969,9 +1009,7 @@
 void
 uninit_static(void)
 {
-#ifdef ENABLE_CRYPTO
     free_ssl_lib();
-#endif
 
 #ifdef ENABLE_PKCS11
     pkcs11_terminate();
@@ -981,7 +1019,7 @@
     close_port_share();
 #endif
 
-#if defined(MEASURE_TLS_HANDSHAKE_STATS) && defined(ENABLE_CRYPTO)
+#if defined(MEASURE_TLS_HANDSHAKE_STATS)
     show_tls_performance_stats();
 #endif
 }
@@ -1014,7 +1052,7 @@
 {
     if (!options->dev && options->dev_node)
     {
-        char *dev_node = string_alloc(options->dev_node, NULL); /* POSIX basename() implementaions may modify its arguments */
+        char *dev_node = string_alloc(options->dev_node, NULL); /* POSIX basename() implementations may modify its arguments */
         options->dev = basename(dev_node);
     }
 }
@@ -1025,7 +1063,6 @@
     /*
      * OpenSSL info print mode?
      */
-#ifdef ENABLE_CRYPTO
     if (options->show_ciphers || options->show_digests || options->show_engines
         || options->show_tls_ciphers || options->show_curves)
     {
@@ -1053,7 +1090,6 @@
         }
         return true;
     }
-#endif /* ifdef ENABLE_CRYPTO */
     return false;
 }
 
@@ -1063,35 +1099,88 @@
 bool
 do_genkey(const struct options *options)
 {
-#ifdef ENABLE_CRYPTO
-    if (options->genkey)
+    /* should we disable paging? */
+    if (options->mlock && (options->genkey))
+    {
+        platform_mlockall(true);
+    }
+
+    /*
+     * We do not want user to use --genkey with --secret. In the transistion
+     * phase we for secret.
+     */
+    if (options->genkey && options->genkey_type != GENKEY_SECRET
+        && options->shared_secret_file)
+    {
+        msg(M_USAGE, "Using --genkey type with --secret filename is "
+            "not supported.  Use --genkey type filename instead.");
+    }
+    if (options->genkey && options->genkey_type == GENKEY_SECRET)
     {
         int nbits_written;
-
-        notnull(options->shared_secret_file,
-                "shared secret output file (--secret)");
-
-        if (options->mlock)     /* should we disable paging? */
+        const char *genkey_filename = options->genkey_filename;
+        if (options->shared_secret_file && options->genkey_filename)
         {
-            platform_mlockall(true);
+            msg(M_USAGE, "You must provide a filename to either --genkey "
+                "or --secret, not both");
         }
 
-        nbits_written = write_key_file(2, options->shared_secret_file);
+        /*
+         * Copy filename from shared_secret_file to genkey_filename to support
+         * the old --genkey --secret foo.file syntax.
+         */
+        if (options->shared_secret_file)
+        {
+            msg(M_WARN, "WARNING: Using --genkey --secret filename is "
+                "DEPRECATED.  Use --genkey secret filename instead.");
+            genkey_filename = options->shared_secret_file;
+        }
+
+        nbits_written = write_key_file(2, genkey_filename);
+        if (nbits_written < 0)
+        {
+            msg(M_FATAL, "Failed to write key file");
+        }
 
         msg(D_GENKEY | M_NOPREFIX,
             "Randomly generated %d bit key written to %s", nbits_written,
             options->shared_secret_file);
         return true;
     }
-#endif
-    return false;
+    else if (options->genkey && options->genkey_type == GENKEY_TLS_CRYPTV2_SERVER)
+    {
+        tls_crypt_v2_write_server_key_file(options->genkey_filename);
+        return true;
+    }
+    else if (options->genkey && options->genkey_type == GENKEY_TLS_CRYPTV2_CLIENT)
+    {
+        if (!options->tls_crypt_v2_file)
+        {
+            msg(M_USAGE,
+                "--genkey tls-crypt-v2-client requires a server key to be set via --tls-crypt-v2 to create a client key");
+        }
+
+        tls_crypt_v2_write_client_key_file(options->genkey_filename,
+                                           options->genkey_extra_data, options->tls_crypt_v2_file,
+                                           options->tls_crypt_v2_file_inline);
+        return true;
+    }
+    else if (options->genkey && options->genkey_type == GENKEY_AUTH_TOKEN)
+    {
+        auth_token_write_server_key_file(options->genkey_filename);
+        return true;
+    }
+    else
+    {
+        return false;
+    }
 }
 
 /*
  * Persistent TUN/TAP device management mode?
  */
 bool
-do_persist_tuntap(const struct options *options)
+do_persist_tuntap(const struct options *options, openvpn_net_ctx_t *ctx)
 {
     if (options->persist_config)
     {
@@ -1099,10 +1188,8 @@
         notnull(options->dev, "TUN/TAP device (--dev)");
         if (options->ce.remote || options->ifconfig_local
             || options->ifconfig_remote_netmask
-#ifdef ENABLE_CRYPTO
             || options->shared_secret_file
             || options->tls_server || options->tls_client
-#endif
             )
         {
             msg(M_FATAL|M_OPTERR,
@@ -1111,7 +1198,8 @@
 #ifdef ENABLE_FEATURE_TUN_PERSIST
         tuncfg(options->dev, options->dev_type, options->dev_node,
                options->persist_mode,
-               options->username, options->groupname, &options->tuntap_options);
+               options->username, options->groupname, &options->tuntap_options,
+               ctx);
         if (options->persist_mode && options->lladdr)
         {
             set_lladdr(options->dev, options->lladdr, NULL);
@@ -1122,7 +1210,7 @@
              "options --mktun and --rmtun are not available on your operating "
              "system.  Please check 'man tun' (or 'tap'), whether your system "
              "supports using 'ifconfig %s create' / 'destroy' to create/remove "
-             "persistant tunnel interfaces.", options->dev );
+             "persistent tunnel interfaces.", options->dev );
 #endif
     }
     return false;
@@ -1254,12 +1342,10 @@
 format_common_name(struct context *c, struct gc_arena *gc)
 {
     struct buffer out = alloc_buf_gc(256, gc);
-#ifdef ENABLE_CRYPTO
     if (c->c2.tls_multi)
     {
         buf_printf(&out, "[%s] ", tls_common_name(c->c2.tls_multi, false));
     }
-#endif
     return BSTR(&out);
 }
 
@@ -1344,7 +1430,6 @@
         /* initialize connection establishment timer */
         event_timeout_init(&c->c2.wait_for_connect, 1, now);
 
-#ifdef ENABLE_OCC
         /* initialize occ timers */
 
         if (c->options.occ
@@ -1358,10 +1443,8 @@
         {
             event_timeout_init(&c->c2.occ_mtu_load_test_interval, OCC_MTU_LOAD_INTERVAL_SECONDS, now);
         }
-#endif
 
         /* initialize packet_id persistence timer */
-#ifdef ENABLE_CRYPTO
         if (c->options.packet_id_file)
         {
             event_timeout_init(&c->c2.packet_id_persist_interval, 60, now);
@@ -1370,7 +1453,6 @@
         /* initialize tmp_int optimization that limits the number of times we call
          * tls_multi_process in the main event loop */
         interval_init(&c->c2.tmp_int, TLS_MULTI_HORIZON, TLS_MULTI_REFRESH);
-#endif
     }
 }
 
@@ -1417,7 +1499,8 @@
 do_init_route_list(const struct options *options,
                    struct route_list *route_list,
                    const struct link_socket_info *link_socket_info,
-                   struct env_set *es)
+                   struct env_set *es,
+                   openvpn_net_ctx_t *ctx)
 {
     const char *gw = NULL;
     int dev = dev_type_enum(options->dev, options->dev_type);
@@ -1441,7 +1524,8 @@
                         gw,
                         metric,
                         link_socket_current_remote(link_socket_info),
-                        es))
+                        es,
+                        ctx))
     {
         /* copy routes to environment */
         setenv_routes(es, route_list);
@@ -1452,18 +1536,17 @@
 do_init_route_ipv6_list(const struct options *options,
                         struct route_ipv6_list *route_ipv6_list,
                         const struct link_socket_info *link_socket_info,
-                        struct env_set *es)
+                        struct env_set *es,
+                        openvpn_net_ctx_t *ctx)
 {
     const char *gw = NULL;
     int metric = -1;            /* no metric set */
 
     gw = options->ifconfig_ipv6_remote;         /* default GW = remote end */
-#if 0                                   /* not yet done for IPv6 - TODO!*/
-    if (options->route_ipv6_default_gateway)            /* override? */
+    if (options->route_ipv6_default_gateway)
     {
         gw = options->route_ipv6_default_gateway;
     }
-#endif
 
     if (options->route_default_metric)
     {
@@ -1490,7 +1573,8 @@
                              gw,
                              metric,
                              link_socket_current_remote_ipv6(link_socket_info),
-                             es))
+                             es,
+                             ctx))
     {
         /* copy routes to environment */
         setenv_routes_ipv6(es, route_ipv6_list);
@@ -1513,7 +1597,6 @@
     do_uid_gid_chroot(c, true);
 
 
-#ifdef ENABLE_CRYPTO
     /*
      * In some cases (i.e. when receiving auth-token via
      * push-reply) the auth-nocache option configured on the
@@ -1523,9 +1606,8 @@
      */
     if (c->options.mode == MODE_POINT_TO_POINT)
     {
-        delayed_auth_pass_purge();
+        ssl_clean_user_pass();
     }
-#endif /* ENABLE_CRYPTO */
 
     /* Test if errors */
     if (flags & ISC_ERRORS)
@@ -1628,11 +1710,13 @@
          struct route_ipv6_list *route_ipv6_list,
          const struct tuntap *tt,
          const struct plugin_list *plugins,
-         struct env_set *es)
+         struct env_set *es,
+         openvpn_net_ctx_t *ctx)
 {
     if (!options->route_noexec && ( route_list || route_ipv6_list ) )
     {
-        add_routes(route_list, route_ipv6_list, tt, ROUTE_OPTION_FLAGS(options), es);
+        add_routes(route_list, route_ipv6_list, tt, ROUTE_OPTION_FLAGS(options),
+                   es, ctx);
         setenv_int(es, "redirect_gateway", route_did_redirect_default_gateway(route_list));
     }
 #ifdef ENABLE_MANAGEMENT
@@ -1656,7 +1740,7 @@
         setenv_str(es, "script_type", "route-up");
         argv_parse_cmd(&argv, options->route_script);
         openvpn_run_script(&argv, es, 0, "--route-up");
-        argv_reset(&argv);
+        argv_free(&argv);
     }
 
 #ifdef _WIN32
@@ -1690,7 +1774,12 @@
                             c->c1.link_socket_addr.bind_local,
                             c->c1.link_socket_addr.remote_list,
                             !c->options.ifconfig_nowarn,
-                            c->c2.es);
+                            c->c2.es,
+                            &c->net_ctx);
+
+#ifdef _WIN32
+    c->c1.tuntap->windows_driver = c->options.windows_driver;
+#endif
 
     init_tun_post(c->c1.tuntap,
                   &c->c2.frame,
@@ -1733,7 +1822,7 @@
 #ifdef _WIN32
     /* store (hide) interactive service handle in tuntap_options */
     c->c1.tuntap->options.msg_channel = c->options.msg_channel;
-    msg(D_ROUTE, "interactive service msg_channel=%u", (unsigned int) c->options.msg_channel);
+    msg(D_ROUTE, "interactive service msg_channel=%" PRIu64, (unsigned long long) c->options.msg_channel);
 #endif
 
     /* allocate route list structure */
@@ -1744,12 +1833,13 @@
     if (c->options.routes && c->c1.route_list)
     {
         do_init_route_list(&c->options, c->c1.route_list,
-                           &c->c2.link_socket->info, c->c2.es);
+                           &c->c2.link_socket->info, c->c2.es, &c->net_ctx);
     }
     if (c->options.routes_ipv6 && c->c1.route_ipv6_list)
     {
         do_init_route_ipv6_list(&c->options, c->c1.route_ipv6_list,
-                                &c->c2.link_socket->info, c->c2.es);
+                                &c->c2.link_socket->info, c->c2.es,
+                                &c->net_ctx);
     }
 
     /* do ifconfig */
@@ -1762,7 +1852,8 @@
                                              c->options.dev_type,
                                              c->options.dev_node,
                                              &gc);
-        do_ifconfig(c->c1.tuntap, guess, TUN_MTU_SIZE(&c->c2.frame), c->c2.es);
+        do_ifconfig(c->c1.tuntap, guess, TUN_MTU_SIZE(&c->c2.frame), c->c2.es,
+                    &c->net_ctx);
     }
 
     /* possibly add routes */
@@ -1770,7 +1861,7 @@
     {
         /* Ignore route_delay, would cause ROUTE_BEFORE_TUN to be ignored */
         do_route(&c->options, c->c1.route_list, c->c1.route_ipv6_list,
-                 c->c1.tuntap, c->plugins, c->c2.es);
+                 c->c1.tuntap, c->plugins, c->c2.es, &c->net_ctx);
     }
 #ifdef TARGET_ANDROID
     /* Store the old fd inside the fd so open_tun can use it */
@@ -1790,7 +1881,8 @@
     if (!c->options.ifconfig_noexec
         && ifconfig_order() == IFCONFIG_AFTER_TUN_OPEN)
     {
-        do_ifconfig(c->c1.tuntap, c->c1.tuntap->actual_name, TUN_MTU_SIZE(&c->c2.frame), c->c2.es);
+        do_ifconfig(c->c1.tuntap, c->c1.tuntap->actual_name,
+                    TUN_MTU_SIZE(&c->c2.frame), c->c2.es, &c->net_ctx);
     }
 
     /* run the up script */
@@ -1826,7 +1918,7 @@
     if ((route_order() == ROUTE_AFTER_TUN) && (!c->options.route_delay_defined))
     {
         do_route(&c->options, c->c1.route_list, c->c1.route_ipv6_list,
-                 c->c1.tuntap, c->plugins, c->c2.es);
+                 c->c1.tuntap, c->plugins, c->c2.es, &c->net_ctx);
     }
 
     /*
@@ -1896,8 +1988,11 @@
 do_close_tun_simple(struct context *c)
 {
     msg(D_CLOSE, "Closing TUN/TAP interface");
-    close_tun(c->c1.tuntap);
-    c->c1.tuntap = NULL;
+    if (c->c1.tuntap)
+    {
+        close_tun(c->c1.tuntap, &c->net_ctx);
+        c->c1.tuntap = NULL;
+    }
     c->c1.tuntap_owned = false;
 #if P2MP
     CLEAR(c->c1.pulled_options_digest_save);
@@ -1952,7 +2047,8 @@
                             c->c2.es);
 
                 delete_routes(c->c1.route_list, c->c1.route_ipv6_list,
-                              c->c1.tuntap, ROUTE_OPTION_FLAGS(&c->options), c->c2.es);
+                              c->c1.tuntap, ROUTE_OPTION_FLAGS(&c->options),
+                              c->c2.es, &c->net_ctx);
             }
 
             /* actually close tun/tap device based on --down-pre flag */
@@ -2167,12 +2263,10 @@
         flags |= (OPT_P_ROUTE | OPT_P_IPWIN32);
     }
 
-#ifdef ENABLE_CRYPTO
     if (c->options.ncp_enabled)
     {
         flags |= OPT_P_NCP;
     }
-#endif
 
     return flags;
 }
@@ -2194,7 +2288,6 @@
         msg(D_PUSH, "OPTIONS IMPORT: timers and/or timeouts modified");
     }
 
-#ifdef ENABLE_OCC
     if (found & OPT_P_EXPLICIT_NOTIFY)
     {
         if (!proto_is_udp(c->options.ce.proto) && c->options.ce.explicit_exit_notification)
@@ -2207,7 +2300,6 @@
             msg(D_PUSH, "OPTIONS IMPORT: explicit notify parm(s) modified");
         }
     }
-#endif
 
 #ifdef USE_COMP
     if (found & OPT_P_COMP)
@@ -2261,7 +2353,6 @@
         msg(D_PUSH, "OPTIONS IMPORT: environment modified");
     }
 
-#ifdef ENABLE_CRYPTO
     if (found & OPT_P_PEER_ID)
     {
         msg(D_PUSH, "OPTIONS IMPORT: peer-id set");
@@ -2285,14 +2376,9 @@
     /* process (potentially pushed) crypto options */
     if (c->options.pull)
     {
-        struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
-        if (found & OPT_P_NCP)
+        if (!check_pull_client_ncp(c, found))
         {
-            msg(D_PUSH, "OPTIONS IMPORT: data channel crypto options modified");
-        }
-        else if (c->options.ncp_enabled)
-        {
-            tls_poor_mans_ncp(&c->options, c->c2.tls_multi->remote_ciphername);
+            return false;
         }
         struct frame *frame_fragment = NULL;
 #ifdef ENABLE_FRAGMENT
@@ -2302,6 +2388,7 @@
         }
 #endif
 
+        struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
         if (!tls_session_update_crypto_params(session, &c->options, &c->c2.frame,
                                               frame_fragment))
         {
@@ -2309,7 +2396,7 @@
             return false;
         }
     }
-#endif /* ifdef ENABLE_CRYPTO */
+
     return true;
 }
 
@@ -2394,7 +2481,7 @@
     }
     c->persist.restart_sleep_seconds = 0;
 
-    /* do managment hold on context restart, i.e. second, third, fourth, etc. initialization */
+    /* do management hold on context restart, i.e. second, third, fourth, etc. initialization */
     if (do_hold(sec))
     {
         sec = 0;
@@ -2461,19 +2548,14 @@
 static void
 key_schedule_free(struct key_schedule *ks, bool free_ssl_ctx)
 {
-#ifdef ENABLE_CRYPTO
     free_key_ctx_bi(&ks->static_key);
     if (tls_ctx_initialised(&ks->ssl_ctx) && free_ssl_ctx)
     {
         tls_ctx_free(&ks->ssl_ctx);
-        free_key_ctx_bi(&ks->tls_wrap_key);
     }
-#endif /* ENABLE_CRYPTO */
     CLEAR(*ks);
 }
 
-#ifdef ENABLE_CRYPTO
-
 static void
 init_crypto_pre(struct context *c, const unsigned int flags)
 {
@@ -2497,7 +2579,6 @@
         rand_ctx_enable_prediction_resistance();
     }
 #endif
-
 }
 
 /*
@@ -2512,11 +2593,6 @@
     init_crypto_pre(c, flags);
 
     /* Initialize flags */
-    if (c->options.use_iv)
-    {
-        c->c2.crypto_options.flags |= CO_USE_IV;
-    }
-
     if (c->options.mute_replay_warnings)
     {
         c->c2.crypto_options.flags |= CO_MUTE_REPLAY_WARNINGS;
@@ -2557,13 +2633,90 @@
     c->c2.crypto_options.key_ctx_bi = c->c1.ks.static_key;
 
     /* Compute MTU parameters */
-    crypto_adjust_frame_parameters(&c->c2.frame,
-                                   &c->c1.ks.key_type,
-                                   options->use_iv, options->replay, true);
+    crypto_adjust_frame_parameters(&c->c2.frame, &c->c1.ks.key_type,
+                                   options->replay, true);
 
-    /* Sanity check on IV, sequence number, and cipher mode options */
-    check_replay_iv_consistency(&c->c1.ks.key_type, options->replay,
-                                options->use_iv);
+    /* Sanity check on sequence number, and cipher mode options */
+    check_replay_consistency(&c->c1.ks.key_type, options->replay);
+}
+
+/*
+ * Initialize the tls-auth/crypt key context
+ */
+static void
+do_init_tls_wrap_key(struct context *c)
+{
+    const struct options *options = &c->options;
+
+    /* TLS handshake authentication (--tls-auth) */
+    if (options->ce.tls_auth_file)
+    {
+        /* Initialize key_type for tls-auth with auth only */
+        CLEAR(c->c1.ks.tls_auth_key_type);
+        if (!streq(options->authname, "none"))
+        {
+            c->c1.ks.tls_auth_key_type.digest = md_kt_get(options->authname);
+            c->c1.ks.tls_auth_key_type.hmac_length =
+                md_kt_size(c->c1.ks.tls_auth_key_type.digest);
+        }
+        else
+        {
+            msg(M_FATAL, "ERROR: tls-auth enabled, but no valid --auth "
+                "algorithm specified ('%s')", options->authname);
+        }
+
+        crypto_read_openvpn_key(&c->c1.ks.tls_auth_key_type,
+                                &c->c1.ks.tls_wrap_key,
+                                options->ce.tls_auth_file,
+                                options->ce.tls_auth_file_inline,
+                                options->ce.key_direction,
+                                "Control Channel Authentication", "tls-auth");
+    }
+
+    /* TLS handshake encryption+authentication (--tls-crypt) */
+    if (options->ce.tls_crypt_file)
+    {
+        tls_crypt_init_key(&c->c1.ks.tls_wrap_key,
+                           options->ce.tls_crypt_file,
+                           options->ce.tls_crypt_file_inline,
+                           options->tls_server);
+    }
+
+    /* tls-crypt with client-specific keys (--tls-crypt-v2) */
+    if (options->ce.tls_crypt_v2_file)
+    {
+        if (options->tls_server)
+        {
+            tls_crypt_v2_init_server_key(&c->c1.ks.tls_crypt_v2_server_key,
+                                         true, options->ce.tls_crypt_v2_file,
+                                         options->ce.tls_crypt_v2_file_inline);
+        }
+        else
+        {
+            tls_crypt_v2_init_client_key(&c->c1.ks.tls_wrap_key,
+                                         &c->c1.ks.tls_crypt_v2_wkc,
+                                         options->ce.tls_crypt_v2_file,
+                                         options->ce.tls_crypt_v2_file_inline);
+        }
+    }
+
+
+}
+
+/*
+ * Initialise the auth-token key context
+ */
+static void
+do_init_auth_token_key(struct context *c)
+{
+    if (!c->options.auth_token_generate)
+    {
+        return;
+    }
+
+    auth_token_init_secret(&c->c1.ks.auth_token_key,
+                           c->options.auth_token_secret_file,
+                           c->options.auth_token_secret_file_inline);
 }
 
 /*
@@ -2581,7 +2734,7 @@
          * Initialize the OpenSSL library's global
          * SSL context.
          */
-        init_ssl(options, &(c->c1.ks.ssl_ctx));
+        init_ssl(options, &(c->c1.ks.ssl_ctx), c->c0 && c->c0->uid_gid_chroot_set);
         if (!tls_ctx_initialised(&c->c1.ks.ssl_ctx))
         {
 #if P2MP
@@ -2605,45 +2758,25 @@
             return;
 #else  /* if P2MP */
             msg(M_FATAL, "Error: private key password verification failed");
-#endif
+#endif /* if P2MP */
         }
 
+        /* Do not warn if we only have BF-CBC in options->ciphername
+         * because it is still the default cipher */
+        bool warn = !streq(options->ciphername, "BF-CBC")
+             || options->enable_ncp_fallback;
         /* Get cipher & hash algorithms */
         init_key_type(&c->c1.ks.key_type, options->ciphername, options->authname,
-                      options->keysize, true, true);
+                      options->keysize, true, warn);
 
         /* Initialize PRNG with config-specified digest */
         prng_init(options->prng_hash, options->prng_nonce_secret_len);
 
-        /* TLS handshake authentication (--tls-auth) */
-        if (options->tls_auth_file)
-        {
-            /* Initialize key_type for tls-auth with auth only */
-            CLEAR(c->c1.ks.tls_auth_key_type);
-            if (!streq(options->authname, "none"))
-            {
-                c->c1.ks.tls_auth_key_type.digest = md_kt_get(options->authname);
-                c->c1.ks.tls_auth_key_type.hmac_length =
-                    md_kt_size(c->c1.ks.tls_auth_key_type.digest);
-            }
-            else
-            {
-                msg(M_FATAL, "ERROR: tls-auth enabled, but no valid --auth "
-                    "algorithm specified ('%s')", options->authname);
-            }
+        /* initialize tls-auth/crypt/crypt-v2 key */
+        do_init_tls_wrap_key(c);
 
-            crypto_read_openvpn_key(&c->c1.ks.tls_auth_key_type,
-                                    &c->c1.ks.tls_wrap_key, options->tls_auth_file,
-                                    options->tls_auth_file_inline, options->key_direction,
-                                    "Control Channel Authentication", "tls-auth");
-        }
-
-        /* TLS handshake encryption+authentication (--tls-crypt) */
-        if (options->tls_crypt_file)
-        {
-            tls_crypt_init_key(&c->c1.ks.tls_wrap_key, options->tls_crypt_file,
-                               options->tls_crypt_inline, options->tls_server);
-        }
+        /* initialise auth-token crypto support */
+        do_init_auth_token_key(c);
 
 #if 0 /* was: #if ENABLE_INLINE_FILES --  Note that enabling this code will break restarts */
         if (options->priv_key_file_inline)
@@ -2656,6 +2789,12 @@
     else
     {
         msg(D_INIT_MEDIUM, "Re-using SSL/TLS context");
+
+        /*
+         * tls-auth/crypt key can be configured per connection block, therefore
+         * we must reload it as it may have changed
+         */
+        do_init_tls_wrap_key(c);
     }
 }
 
@@ -2681,9 +2820,8 @@
         return;
     }
 
-    /* Sanity check on IV, sequence number, and cipher mode options */
-    check_replay_iv_consistency(&c->c1.ks.key_type, options->replay,
-                                options->use_iv);
+    /* Sanity check on sequence number, and cipher mode options */
+    check_replay_consistency(&c->c1.ks.key_type, options->replay);
 
     /* In short form, unique datagram identifier is 32 bits, in long form 64 bits */
     packet_id_long_form = cipher_kt_mode_ofb_cfb(c->c1.ks.key_type.cipher);
@@ -2697,18 +2835,13 @@
     else
     {
         crypto_adjust_frame_parameters(&c->c2.frame, &c->c1.ks.key_type,
-                                       options->use_iv, options->replay, packet_id_long_form);
+                                       options->replay, packet_id_long_form);
     }
     tls_adjust_frame_parameters(&c->c2.frame);
 
     /* Set all command-line TLS-related options */
     CLEAR(to);
 
-    if (options->use_iv)
-    {
-        to.crypto_flags |= CO_USE_IV;
-    }
-
     if (options->mute_replay_warnings)
     {
         to.crypto_flags |= CO_MUTE_REPLAY_WARNINGS;
@@ -2723,24 +2856,35 @@
     to.ssl_ctx = c->c1.ks.ssl_ctx;
     to.key_type = c->c1.ks.key_type;
     to.server = options->tls_server;
-    to.key_method = options->key_method;
     to.replay = options->replay;
     to.replay_window = options->replay_window;
     to.replay_time = options->replay_time;
     to.tcp_mode = link_socket_proto_connection_oriented(options->ce.proto);
     to.config_ciphername = c->c1.ciphername;
-    to.config_authname = c->c1.authname;
+    to.config_ncp_ciphers = options->ncp_ciphers;
     to.ncp_enabled = options->ncp_enabled;
     to.transition_window = options->transition_window;
     to.handshake_window = options->handshake_window;
     to.packet_timeout = options->tls_timeout;
     to.renegotiate_bytes = options->renegotiate_bytes;
     to.renegotiate_packets = options->renegotiate_packets;
-    to.renegotiate_seconds = options->renegotiate_seconds;
+    if (options->renegotiate_seconds_min < 0)
+    {
+        /* Add 10% jitter to reneg-sec by default (server side only) */
+        int auto_jitter = options->mode != MODE_SERVER ? 0 :
+                          get_random() % max_int(options->renegotiate_seconds / 10, 1);
+        to.renegotiate_seconds = options->renegotiate_seconds - auto_jitter;
+    }
+    else
+    {
+        /* Add user-specified jitter to reneg-sec */
+        to.renegotiate_seconds = options->renegotiate_seconds
+                                 -(get_random() % max_int(options->renegotiate_seconds
+                                                          - options->renegotiate_seconds_min, 1));
+    }
     to.single_session = options->single_session;
     to.mode = options->mode;
     to.pull = options->pull;
-#ifdef ENABLE_PUSH_PEER_INFO
     if (options->push_peer_info)        /* all there is */
     {
         to.push_peer_info_detail = 2;
@@ -2753,7 +2897,6 @@
     {
         to.push_peer_info_detail = 0;
     }
-#endif
 
     /* should we not xmit any packets until we get an initial
      * response from client? */
@@ -2762,9 +2905,7 @@
         to.xmit_hold = true;
     }
 
-#ifdef ENABLE_OCC
     to.disable_occ = !options->occ;
-#endif
 
     to.verify_command = options->tls_verify;
     to.verify_export_cert = options->tls_export_cert;
@@ -2784,6 +2925,7 @@
     to.x509_username_field = X509_USERNAME_FIELD_DEFAULT;
 #endif
     to.es = c->c2.es;
+    to.net_ctx = &c->net_ctx;
 
 #ifdef ENABLE_DEBUG
     to.gremlin = c->options.gremlin;
@@ -2795,7 +2937,6 @@
     to.mda_context = &c->c2.mda_context;
 #endif
 
-#if P2MP_SERVER
     to.auth_user_pass_verify_script = options->auth_user_pass_verify_script;
     to.auth_user_pass_verify_script_via_file = options->auth_user_pass_verify_script_via_file;
     to.tmp_dir = options->tmp_dir;
@@ -2806,12 +2947,13 @@
     to.auth_user_pass_file = options->auth_user_pass_file;
     to.auth_token_generate = options->auth_token_generate;
     to.auth_token_lifetime = options->auth_token_lifetime;
-#endif
+    to.auth_token_call_auth = options->auth_token_call_auth;
+    to.auth_token_key = c->c1.ks.auth_token_key;
 
     to.x509_track = options->x509_track;
 
 #if P2MP
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
     to.sci = &options->sc_info;
 #endif
 #endif
@@ -2820,7 +2962,7 @@
     to.comp_options = options->comp;
 #endif
 
-#if defined(ENABLE_CRYPTO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10001000
+#ifdef HAVE_EXPORT_KEYING_MATERIAL
     if (options->keying_material_exporter_label)
     {
         to.ekm_size = options->keying_material_exporter_length;
@@ -2836,28 +2978,43 @@
     {
         to.ekm_size = 0;
     }
-#endif
+#endif /* HAVE_EXPORT_KEYING_MATERIAL */
 
     /* TLS handshake authentication (--tls-auth) */
-    if (options->tls_auth_file)
+    if (options->ce.tls_auth_file)
     {
         to.tls_wrap.mode = TLS_WRAP_AUTH;
         to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;
         to.tls_wrap.opt.pid_persist = &c->c1.pid_persist;
         to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM;
-        crypto_adjust_frame_parameters(&to.frame,
-                                       &c->c1.ks.tls_auth_key_type,
-                                       false, true, true);
+        crypto_adjust_frame_parameters(&to.frame, &c->c1.ks.tls_auth_key_type,
+                                       true, true);
     }
 
     /* TLS handshake encryption (--tls-crypt) */
-    if (options->tls_crypt_file)
+    if (options->ce.tls_crypt_file
+        || (options->ce.tls_crypt_v2_file && options->tls_client))
     {
         to.tls_wrap.mode = TLS_WRAP_CRYPT;
         to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;
         to.tls_wrap.opt.pid_persist = &c->c1.pid_persist;
         to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM;
         tls_crypt_adjust_frame_parameters(&to.frame);
+
+        if (options->ce.tls_crypt_v2_file)
+        {
+            to.tls_wrap.tls_crypt_v2_wkc = &c->c1.ks.tls_crypt_v2_wkc;
+        }
+    }
+
+    if (options->ce.tls_crypt_v2_file)
+    {
+        to.tls_crypt_v2 = true;
+        if (options->tls_server)
+        {
+            to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+            to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script;
+        }
     }
 
     /* If we are running over TCP, allow for
@@ -2910,12 +3067,10 @@
         "protected against man-in-the-middle changes. "
         "PLEASE DO RECONSIDER THIS CONFIGURATION!");
 }
-#endif /* ifdef ENABLE_CRYPTO */
 
 static void
 do_init_crypto(struct context *c, const unsigned int flags)
 {
-#ifdef ENABLE_CRYPTO
     if (c->options.shared_secret_file)
     {
         do_init_crypto_static(c, flags);
@@ -2928,11 +3083,6 @@
     {
         do_init_crypto_none(c);
     }
-#else /* ENABLE_CRYPTO */
-    msg(M_WARN,
-        "******* WARNING *******: " PACKAGE_NAME
-        " built without crypto library -- encryption and authentication features disabled -- all data will be tunnelled as cleartext");
-#endif /* ENABLE_CRYPTO */
 }
 
 static void
@@ -3026,7 +3176,7 @@
     /* packets with peer-id (P_DATA_V2) need 3 extra bytes in frame (on client)
      * and need link_mtu+3 bytes on socket reception (on server).
      *
-     * accomodate receive path in f->extra_link, which has the side effect of
+     * accommodate receive path in f->extra_link, which has the side effect of
      * also increasing send buffers (BUF_SIZE() macro), which need to be
      * allocated big enough before receiving peer-id option from server.
      *
@@ -3045,7 +3195,7 @@
     c->c2.frame_fragment_initial = c->c2.frame_fragment;
 #endif
 
-#if defined(ENABLE_FRAGMENT) && defined(ENABLE_OCC)
+#if defined(ENABLE_FRAGMENT)
     /*
      * MTU advisories
      */
@@ -3108,7 +3258,6 @@
         msg(M_WARN, "WARNING: using --pull/--client and --ifconfig together is probably not what you want");
     }
 
-#if P2MP_SERVER
     if (o->server_bridge_defined | o->server_bridge_proxy_dhcp)
     {
         msg(M_WARN, "NOTE: when bridging your LAN adapter with the TAP adapter, note that the new bridge adapter will often take on its own IP address that is different from what the LAN adapter was previously set to");
@@ -3129,22 +3278,16 @@
             msg(M_WARN, "WARNING: --keepalive option is missing from server config");
         }
     }
-#endif /* if P2MP_SERVER */
 #endif /* if P2MP */
 
-#ifdef ENABLE_CRYPTO
     if (!o->replay)
     {
         msg(M_WARN, "WARNING: You have disabled Replay Protection (--no-replay) which may make " PACKAGE_NAME " less secure");
     }
-    if (!o->use_iv)
-    {
-        msg(M_WARN, "WARNING: You have disabled Crypto IVs (--no-iv) which may make " PACKAGE_NAME " less secure");
-    }
 
     if (o->tls_server)
     {
-        warn_on_use_of_common_subnets();
+        warn_on_use_of_common_subnets(&c->net_ctx);
     }
     if (o->tls_client
         && !o->tls_verify
@@ -3158,16 +3301,15 @@
     {
         msg(M_WARN, "WARNING: --ns-cert-type is DEPRECATED.  Use --remote-cert-tls instead.");
     }
-#endif /* ifdef ENABLE_CRYPTO */
 
-    /* If a script is used, print appropiate warnings */
+    /* If a script is used, print appropriate warnings */
     if (o->user_script_used)
     {
-        if (script_security >= SSEC_SCRIPTS)
+        if (script_security() >= SSEC_SCRIPTS)
         {
             msg(M_WARN, "NOTE: the current --script-security setting may allow this configuration to call user-defined scripts");
         }
-        else if (script_security >= SSEC_PW_ENV)
+        else if (script_security() >= SSEC_PW_ENV)
         {
             msg(M_WARN, "WARNING: the current --script-security setting may allow passwords to be passed to scripts via environmental variables");
         }
@@ -3181,9 +3323,7 @@
 static void
 do_init_frame_tls(struct context *c)
 {
-#ifdef ENABLE_CRYPTO
     do_init_finalize_tls_frame(c);
-#endif
 }
 
 struct context_buffers *
@@ -3198,10 +3338,8 @@
 
     b->aux_buf = alloc_buf(BUF_SIZE(frame));
 
-#ifdef ENABLE_CRYPTO
     b->encrypt_buf = alloc_buf(BUF_SIZE(frame));
     b->decrypt_buf = alloc_buf(BUF_SIZE(frame));
-#endif
 
 #ifdef USE_COMP
     b->compress_buf = alloc_buf(BUF_SIZE(frame));
@@ -3225,10 +3363,8 @@
         free_buf(&b->decompress_buf);
 #endif
 
-#ifdef ENABLE_CRYPTO
         free_buf(&b->encrypt_buf);
         free_buf(&b->decrypt_buf);
-#endif
 
         free(b);
     }
@@ -3313,6 +3449,7 @@
                             c->options.rcvbuf,
                             c->options.sndbuf,
                             c->options.mark,
+                            c->options.bind_dev,
                             &c->c2.server_poll_interval,
                             sockflags);
 }
@@ -3343,7 +3480,6 @@
 #endif
 }
 
-#ifdef ENABLE_OCC
 /*
  * Get local and remote options compatibility strings.
  */
@@ -3353,9 +3489,11 @@
     struct gc_arena gc = gc_new();
 
     c->c2.options_string_local =
-        options_string(&c->options, &c->c2.frame, c->c1.tuntap, false, &gc);
+        options_string(&c->options, &c->c2.frame, c->c1.tuntap, &c->net_ctx,
+                       false, &gc);
     c->c2.options_string_remote =
-        options_string(&c->options, &c->c2.frame, c->c1.tuntap, true, &gc);
+        options_string(&c->options, &c->c2.frame, c->c1.tuntap, &c->net_ctx,
+                       true, &gc);
 
     msg(D_SHOW_OCC, "Local Options String (VER=%s): '%s'",
         options_string_version(c->c2.options_string_local, &gc),
@@ -3364,18 +3502,15 @@
         options_string_version(c->c2.options_string_remote, &gc),
         c->c2.options_string_remote);
 
-#ifdef ENABLE_CRYPTO
     if (c->c2.tls_multi)
     {
         tls_multi_init_set_options(c->c2.tls_multi,
                                    c->c2.options_string_local,
                                    c->c2.options_string_remote);
     }
-#endif
 
     gc_free(&gc);
 }
-#endif /* ifdef ENABLE_OCC */
 
 /*
  * These things can only be executed once per program instantiation.
@@ -3445,14 +3580,12 @@
 static void
 do_close_tls(struct context *c)
 {
-#ifdef ENABLE_CRYPTO
     if (c->c2.tls_multi)
     {
         tls_multi_free(c->c2.tls_multi, true);
         c->c2.tls_multi = NULL;
     }
 
-#ifdef ENABLE_OCC
     /* free options compatibility strings */
     if (c->c2.options_string_local)
     {
@@ -3463,14 +3596,12 @@
         free(c->c2.options_string_remote);
     }
     c->c2.options_string_local = c->c2.options_string_remote = NULL;
-#endif
 
     if (c->c2.pulled_options_state)
     {
         md_ctx_cleanup(c->c2.pulled_options_state);
         md_ctx_free(c->c2.pulled_options_state);
     }
-#endif
 }
 
 /*
@@ -3479,6 +3610,16 @@
 static void
 do_close_free_key_schedule(struct context *c, bool free_ssl_ctx)
 {
+    /*
+     * always free the tls_auth/crypt key. If persist_key is true, the key will
+     * be reloaded from memory (pre-cached)
+     */
+    free_key_ctx(&c->c1.ks.tls_crypt_v2_server_key);
+    free_key_ctx_bi(&c->c1.ks.tls_wrap_key);
+    CLEAR(c->c1.ks.tls_wrap_key);
+    buf_clear(&c->c1.ks.tls_crypt_v2_wkc);
+    free_buf(&c->c1.ks.tls_crypt_v2_wkc);
+
     if (!(c->sig->signal_received == SIGUSR1 && c->options.persist_key))
     {
         key_schedule_free(&c->c1.ks, free_ssl_ctx);
@@ -3505,7 +3646,8 @@
           && ( (c->options.persist_remote_ip)
                ||
                ( c->sig->source != SIG_SOURCE_HARD
-                 && ((c->c1.link_socket_addr.current_remote && c->c1.link_socket_addr.current_remote->ai_next)
+                 && ((c->c1.link_socket_addr.current_remote
+                      && c->c1.link_socket_addr.current_remote->ai_next)
                      || c->options.no_advance))
                )))
     {
@@ -3530,19 +3672,17 @@
 }
 
 /*
- * Close packet-id persistance file
+ * Close packet-id persistence file
  */
 static void
 do_close_packet_id(struct context *c)
 {
-#ifdef ENABLE_CRYPTO
     packet_id_free(&c->c2.crypto_options.packet_id);
     packet_id_persist_save(&c->c1.pid_persist);
     if (!(c->sig->signal_received == SIGUSR1))
     {
         packet_id_persist_close(&c->c1.pid_persist);
     }
-#endif
 }
 
 #ifdef ENABLE_FRAGMENT
@@ -3627,25 +3767,22 @@
 }
 
 /*
- * Handle ifconfig-pool persistance object.
+ * Handle ifconfig-pool persistence object.
  */
 static void
 do_open_ifconfig_pool_persist(struct context *c)
 {
-#if P2MP_SERVER
     if (!c->c1.ifconfig_pool_persist && c->options.ifconfig_pool_persist_filename)
     {
         c->c1.ifconfig_pool_persist = ifconfig_pool_persist_init(c->options.ifconfig_pool_persist_filename,
                                                                  c->options.ifconfig_pool_persist_refresh_freq);
         c->c1.ifconfig_pool_persist_owned = true;
     }
-#endif
 }
 
 static void
 do_close_ifconfig_pool_persist(struct context *c)
 {
-#if P2MP_SERVER
     if (!(c->sig->signal_received == SIGUSR1))
     {
         if (c->c1.ifconfig_pool_persist && c->c1.ifconfig_pool_persist_owned)
@@ -3655,7 +3792,6 @@
             c->c1.ifconfig_pool_persist_owned = false;
         }
     }
-#endif
 }
 
 /*
@@ -3721,7 +3857,6 @@
 static void
 do_signal_on_tls_errors(struct context *c)
 {
-#ifdef ENABLE_CRYPTO
     if (c->options.tls_exit)
     {
         c->c2.tls_exit_signal = SIGTERM;
@@ -3730,7 +3865,6 @@
     {
         c->c2.tls_exit_signal = SIGUSR1;
     }
-#endif
 }
 
 #ifdef ENABLE_PLUGIN
@@ -3880,6 +4014,7 @@
         cb.show_net = management_show_net_callback;
         cb.proxy_cmd = management_callback_proxy_cmd;
         cb.remote_cmd = management_callback_remote_cmd;
+        cb.send_cc_message = management_callback_send_cc_message;
 #ifdef TARGET_ANDROID
         cb.network_change = management_callback_network_change;
 #endif
@@ -4121,13 +4256,11 @@
         do_open_ifconfig_pool_persist(c);
     }
 
-#ifdef ENABLE_OCC
     /* reset OCC state */
     if (c->mode == CM_P2P || child)
     {
         c->c2.occ_op = occ_reset_op();
     }
-#endif
 
     /* our wait-for-i/o objects, different for posix vs. win32 */
     if (c->mode == CM_P2P)
@@ -4227,13 +4360,11 @@
     /* print MTU info */
     do_print_data_channel_mtu_parms(c);
 
-#ifdef ENABLE_OCC
     /* get local and remote options compatibility strings */
     if (c->mode == CM_P2P || child)
     {
         do_compute_occ_strings(c);
     }
-#endif
 
     /* initialize output speed limiter */
     if (c->mode == CM_P2P)
@@ -4241,7 +4372,7 @@
         do_init_traffic_shaper(c);
     }
 
-    /* do one-time inits, and possibily become a daemon here */
+    /* do one-time inits, and possibly become a daemon here */
     do_init_first_time(c);
 
 #ifdef ENABLE_PLUGIN
@@ -4371,7 +4502,7 @@
         do_close_plugins(c);
 #endif
 
-        /* close packet-id persistance file */
+        /* close packet-id persistence file */
         do_close_packet_id(c);
 
         /* close --status file */
@@ -4412,17 +4543,19 @@
     /* c1 init */
     packet_id_persist_init(&dest->c1.pid_persist);
 
-#ifdef ENABLE_CRYPTO
     dest->c1.ks.key_type = src->c1.ks.key_type;
     /* inherit SSL context */
     dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx;
     dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key;
     dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type;
+    dest->c1.ks.tls_crypt_v2_server_key = src->c1.ks.tls_crypt_v2_server_key;
     /* inherit pre-NCP ciphers */
     dest->c1.ciphername = src->c1.ciphername;
     dest->c1.authname = src->c1.authname;
     dest->c1.keysize = src->c1.keysize;
-#endif
+
+    /* inherit auth-token */
+    dest->c1.ks.auth_token_key = src->c1.ks.auth_token_key;
 
     /* options */
     dest->options = src->options;
@@ -4496,16 +4629,12 @@
     /* detach plugins */
     dest->plugins_owned = false;
 
-#ifdef ENABLE_CRYPTO
     dest->c2.tls_multi = NULL;
-#endif
 
     /* detach c1 ownership */
     dest->c1.tuntap_owned = false;
     dest->c1.status_output_owned = false;
-#if P2MP_SERVER
     dest->c1.ifconfig_pool_persist_owned = false;
-#endif
 
     /* detach c2 ownership */
     dest->c2.event_set_owned = false;
@@ -4556,7 +4685,46 @@
     }
 }
 
-#ifdef ENABLE_CRYPTO
+/* Write our PID to a file */
+void
+write_pid_file(const char *filename, const char *chroot_dir)
+{
+    if (filename)
+    {
+        unsigned int pid = 0;
+        FILE *fp = platform_fopen(filename, "w");
+        if (!fp)
+        {
+            msg(M_ERR, "Open error on pid file %s", filename);
+            return;
+        }
+
+        pid = platform_getpid();
+        fprintf(fp, "%u\n", pid);
+        if (fclose(fp))
+        {
+            msg(M_ERR, "Close error on pid file %s", filename);
+        }
+
+        /* remember file name so it can be deleted "out of context" later */
+        /* (the chroot case is more complex and not handled today) */
+        if (!chroot_dir)
+        {
+            saved_pid_file_name = strdup(filename);
+        }
+    }
+}
+
+/* remove PID file on exit, called from openvpn_exit() */
+void
+remove_pid_file(void)
+{
+    if (saved_pid_file_name)
+    {
+        platform_unlink(saved_pid_file_name);
+    }
+}
+
 
 /*
  * Do a loopback test
@@ -4585,12 +4753,9 @@
     return NULL;
 }
 
-#endif /* ENABLE_CRYPTO */
-
 bool
 do_test_crypto(const struct options *o)
 {
-#ifdef ENABLE_CRYPTO
     if (o->test_crypto)
     {
         struct context c;
@@ -4605,6 +4770,5 @@
         test_crypto_thread((void *) &c);
         return true;
     }
-#endif
     return false;
 }
diff --git a/src/openvpn/init.h b/src/openvpn/init.h
index 2c846db..a2fdccd 100644
--- a/src/openvpn/init.h
+++ b/src/openvpn/init.h
@@ -56,7 +56,7 @@
 
 bool do_genkey(const struct options *options);
 
-bool do_persist_tuntap(const struct options *options);
+bool do_persist_tuntap(const struct options *options, openvpn_net_ctx_t *ctx);
 
 bool possibly_become_daemon(const struct options *options);
 
@@ -76,7 +76,8 @@
               struct route_ipv6_list *route_ipv6_list,
               const struct tuntap *tt,
               const struct plugin_list *plugins,
-              struct env_set *es);
+              struct env_set *es,
+              openvpn_net_ctx_t *ctx);
 
 void close_instance(struct context *c);
 
@@ -140,4 +141,9 @@
 
 #endif
 
+void tun_abort(void);
+
+void write_pid_file(const char *filename, const char *chroot_dir);
+void remove_pid_file(void);
+
 #endif /* ifndef INIT_H */
diff --git a/src/openvpn/integer.h b/src/openvpn/integer.h
index a7e19d3..3755f43 100644
--- a/src/openvpn/integer.h
+++ b/src/openvpn/integer.h
@@ -26,6 +26,16 @@
 
 #include "error.h"
 
+#ifndef htonll
+#define htonll(x) ((1==htonl(1)) ? (x) : \
+                   ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
+#endif
+
+#ifndef ntohll
+#define ntohll(x) ((1==ntohl(1)) ? (x) : \
+                   ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
+#endif
+
 /*
  * min/max functions
  */
diff --git a/src/openvpn/list.c b/src/openvpn/list.c
index 09e393a..549ebdf 100644
--- a/src/openvpn/list.c
+++ b/src/openvpn/list.c
@@ -29,7 +29,6 @@
 
 #include "syshead.h"
 
-#if P2MP_SERVER
 
 #include "integer.h"
 #include "list.h"
@@ -222,18 +221,6 @@
     }
 }
 
-uint32_t
-void_ptr_hash_function(const void *key, uint32_t iv)
-{
-    return hash_func((const void *)&key, sizeof(key), iv);
-}
-
-bool
-void_ptr_compare_function(const void *key1, const void *key2)
-{
-    return key1 == key2;
-}
-
 void
 hash_iterator_init_range(struct hash *hash,
                          struct hash_iterator *hi,
@@ -668,10 +655,3 @@
     /*-------------------------------------- report the result */
     return c;
 }
-
-#else  /* if P2MP_SERVER */
-static void
-dummy(void)
-{
-}
-#endif /* P2MP_SERVER */
diff --git a/src/openvpn/list.h b/src/openvpn/list.h
index b67301c..c381acd 100644
--- a/src/openvpn/list.h
+++ b/src/openvpn/list.h
@@ -33,8 +33,6 @@
  * client instances over various key spaces.
  */
 
-#if P2MP_SERVER
-
 /* define this to enable special list test mode */
 /*#define LIST_TEST*/
 
@@ -116,10 +114,6 @@
 
 uint32_t hash_func(const uint8_t *k, uint32_t length, uint32_t initval);
 
-uint32_t void_ptr_hash_function(const void *key, uint32_t iv);
-
-bool void_ptr_compare_function(const void *key1, const void *key2);
-
 #ifdef LIST_TEST
 void list_test(void);
 
@@ -198,5 +192,4 @@
     return ret;
 }
 
-#endif /* P2MP_SERVER */
 #endif /* LIST */
diff --git a/src/openvpn/lladdr.c b/src/openvpn/lladdr.c
index ff71e48..22857eb 100644
--- a/src/openvpn/lladdr.c
+++ b/src/openvpn/lladdr.c
@@ -11,6 +11,8 @@
 #include "syshead.h"
 #include "error.h"
 #include "misc.h"
+#include "run_command.h"
+#include "lladdr.h"
 
 int
 set_lladdr(const char *ifname, const char *lladdr,
@@ -67,6 +69,6 @@
         msg(M_INFO, "TUN/TAP link layer address set to %s", lladdr);
     }
 
-    argv_reset(&argv);
+    argv_free(&argv);
     return r;
 }
diff --git a/src/openvpn/lzo.c b/src/openvpn/lzo.c
index 8d9efea..d053fed 100644
--- a/src/openvpn/lzo.c
+++ b/src/openvpn/lzo.c
@@ -103,9 +103,11 @@
     msg(D_INIT_MEDIUM, "LZO compression initializing");
     ASSERT(!(compctx->flags & COMP_F_SWAP));
     compctx->wu.lzo.wmem_size = LZO_WORKSPACE;
-    if (lzo_init() != LZO_E_OK)
+
+    int lzo_status = lzo_init();
+    if (lzo_status != LZO_E_OK)
     {
-        msg(M_FATAL, "Cannot initialize LZO compression library");
+        msg(M_FATAL, "Cannot initialize LZO compression library (lzo_init() returns %d)", lzo_status);
     }
     compctx->wu.lzo.wmem = (lzo_voidp) lzo_malloc(compctx->wu.lzo.wmem_size);
     check_malloc_return(compctx->wu.lzo.wmem);
@@ -121,7 +123,7 @@
 static inline bool
 lzo_compression_enabled(struct compress_context *compctx)
 {
-    if (compctx->flags & COMP_F_ASYM)
+    if (!(compctx->flags & COMP_F_ALLOW_COMPRESS))
     {
         return false;
     }
diff --git a/src/openvpn/lzo.h b/src/openvpn/lzo.h
index 11e1c39..453cd8e 100644
--- a/src/openvpn/lzo.h
+++ b/src/openvpn/lzo.h
@@ -39,14 +39,14 @@
  */
 
 #if defined(HAVE_LZO_LZOUTIL_H)
-#include "lzo/lzoutil.h"
+#include <lzo/lzoutil.h>
 #elif defined(HAVE_LZOUTIL_H)
-#include "lzoutil.h"
+#include <lzoutil.h>
 #endif
 #if defined(HAVE_LZO_LZO1X_H)
-#include "lzo/lzo1x.h"
+#include <lzo/lzo1x.h>
 #elif defined(HAVE_LZO1X_H)
-#include "lzo1x.h"
+#include <lzo1x.h>
 #endif
 
 #include "buffer.h"
diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c
index 61d61ef..d86b6a7 100644
--- a/src/openvpn/manage.c
+++ b/src/openvpn/manage.c
@@ -75,6 +75,7 @@
     msg(M_CLIENT, "auth-retry t           : Auth failure retry mode (none,interact,nointeract).");
     msg(M_CLIENT, "bytecount n            : Show bytes in/out, update every n secs (0=off).");
     msg(M_CLIENT, "echo [on|off] [N|all]  : Like log, but only show messages in echo buffer.");
+    msg(M_CLIENT, "cr-response response   : Send a challenge response answer via CR_RESPONSE to server");
     msg(M_CLIENT, "exit|quit              : Close management session.");
     msg(M_CLIENT, "forget-passwords       : Forget passwords entered so far.");
     msg(M_CLIENT, "help                   : Print this message.");
@@ -104,18 +105,20 @@
     msg(M_CLIENT, "client-auth-nt CID KID : Authenticate client-id/key-id CID/KID");
     msg(M_CLIENT, "client-deny CID KID R [CR] : Deny auth client-id/key-id CID/KID with log reason");
     msg(M_CLIENT, "                             text R and optional client reason text CR");
+    msg(M_CLIENT, "client-pending-auth CID MSG : Instruct OpenVPN to send AUTH_PENDING and INFO_PRE msg"
+        "                          to the client and wait for a final client-auth/client-deny");
     msg(M_CLIENT, "client-kill CID [M]    : Kill client instance CID with message M (def=RESTART)");
     msg(M_CLIENT, "env-filter [level]     : Set env-var filter level");
 #ifdef MANAGEMENT_PF
     msg(M_CLIENT, "client-pf CID          : Define packet filter for client CID (MULTILINE)");
 #endif
 #endif
-#ifdef MANAGMENT_EXTERNAL_KEY
-    msg(M_CLIENT, "rsa-sig                : Enter an RSA signature in response to >RSA_SIGN challenge");
+    msg(M_CLIENT, "rsa-sig                : Enter a signature in response to >RSA_SIGN challenge");
+    msg(M_CLIENT, "                         Enter signature base64 on subsequent lines followed by END");
+    msg(M_CLIENT, "pk-sig                 : Enter a signature in response to >PK_SIGN challenge");
     msg(M_CLIENT, "                         Enter signature base64 on subsequent lines followed by END");
     msg(M_CLIENT, "certificate            : Enter a client certificate in response to >NEED-CERT challenge");
     msg(M_CLIENT, "                         Enter certificate base64 on subsequent lines followed by END");
-#endif
     msg(M_CLIENT, "signal s               : Send signal s to daemon,");
     msg(M_CLIENT, "                         s = SIGHUP|SIGTERM|SIGUSR1|SIGUSR2.");
     msg(M_CLIENT, "state [on|off] [N|all] : Like log, but show state history.");
@@ -123,7 +126,7 @@
     msg(M_CLIENT, "test n                 : Produce n lines of output for testing/debugging.");
     msg(M_CLIENT, "username type u        : Enter username u for a queried OpenVPN username.");
     msg(M_CLIENT, "verb [n]               : Set log verbosity level to n, or show if n is absent.");
-    msg(M_CLIENT, "version                : Show current version number.");
+    msg(M_CLIENT, "version [n]            : Set client's version to n or show current version of daemon.");
     msg(M_CLIENT, "END");
 }
 
@@ -762,10 +765,8 @@
 static void
 man_forget_passwords(struct management *man)
 {
-#ifdef ENABLE_CRYPTO
     ssl_purge_auth(false);
     msg(M_CLIENT, "SUCCESS: Passwords were forgotten");
-#endif
 }
 
 static void
@@ -781,6 +782,27 @@
     }
 }
 
+static void
+man_send_cc_message(struct management *man, const char *message, const char *parameters)
+{
+    if (man->persist.callback.send_cc_message)
+    {
+        const bool status = (*man->persist.callback.send_cc_message)
+                                (man->persist.callback.arg, message, parameters);
+        if (status)
+        {
+            msg(M_CLIENT, "SUCCESS: command succeeded");
+        }
+        else
+        {
+            msg(M_CLIENT, "ERROR: command failed");
+        }
+    }
+    else
+    {
+        msg(M_CLIENT, "ERROR: This command is not supported by the current daemon mode");
+    }
+}
 #ifdef ENABLE_PKCS11
 
 static void
@@ -847,8 +869,6 @@
     }
 }
 
-#ifdef MANAGEMENT_IN_EXTRA
-
 #define IER_RESET      0
 #define IER_NEW        1
 
@@ -936,8 +956,7 @@
             break;
 
 #endif /* ifdef MANAGEMENT_PF */
-#ifdef MANAGMENT_EXTERNAL_KEY
-        case IEC_RSA_SIGN:
+        case IEC_PK_SIGN:
             man->connection.ext_key_state = EKS_READY;
             buffer_list_free(man->connection.ext_key_input);
             man->connection.ext_key_input = man->connection.in_extra;
@@ -950,13 +969,10 @@
             man->connection.ext_cert_input = man->connection.in_extra;
             man->connection.in_extra = NULL;
             return;
-#endif
     }
     in_extra_reset(&man->connection, IER_RESET);
 }
 
-#endif /* MANAGEMENT_IN_EXTRA */
-
 #ifdef MANAGEMENT_DEF_AUTH
 
 static bool
@@ -987,6 +1003,43 @@
     }
 }
 
+/**
+ * Will send a notification to the client that succesful authentication
+ * will require an additional step (web based SSO/2-factor auth/etc)
+ *
+ * @param man           The management interface struct
+ * @param cid_str       The CID in string form
+ * @param extra         The string to be send to the client containing
+ *                      the information of the additional steps
+ */
+static void
+man_client_pending_auth(struct management *man, const char *cid_str, const char *extra)
+{
+    unsigned long cid = 0;
+    if (parse_cid(cid_str, &cid))
+    {
+        if (man->persist.callback.client_pending_auth)
+        {
+            bool ret = (*man->persist.callback.client_pending_auth)
+                           (man->persist.callback.arg, cid, extra);
+
+            if (ret)
+            {
+                msg(M_CLIENT, "SUCCESS: client-pending-auth command succeeded");
+            }
+            else
+            {
+                msg(M_CLIENT, "SUCCESS: client-pending-auth command failed."
+                    " Extra paramter might be too long");
+            }
+        }
+        else
+        {
+            msg(M_CLIENT, "ERROR: The client-pending-auth command is not supported by the current daemon mode");
+        }
+    }
+}
+
 static void
 man_client_auth(struct management *man, const char *cid_str, const char *kid_str, const bool extra)
 {
@@ -1102,21 +1155,19 @@
 #endif /* MANAGEMENT_PF */
 #endif /* MANAGEMENT_DEF_AUTH */
 
-#ifdef MANAGMENT_EXTERNAL_KEY
-
 static void
-man_rsa_sig(struct management *man)
+man_pk_sig(struct management *man, const char *cmd_name)
 {
     struct man_connection *mc = &man->connection;
     if (mc->ext_key_state == EKS_SOLICIT)
     {
         mc->ext_key_state = EKS_INPUT;
-        mc->in_extra_cmd = IEC_RSA_SIGN;
+        mc->in_extra_cmd = IEC_PK_SIGN;
         in_extra_reset(mc, IER_NEW);
     }
     else
     {
-        msg(M_CLIENT, "ERROR: The rsa-sig command is not currently available");
+        msg(M_CLIENT, "ERROR: The %s command is not currently available", cmd_name);
     }
 }
 
@@ -1136,8 +1187,6 @@
     }
 }
 
-#endif /* ifdef MANAGMENT_EXTERNAL_KEY */
-
 static void
 man_load_stats(struct management *man)
 {
@@ -1156,7 +1205,15 @@
 }
 
 #define MN_AT_LEAST (1<<0)
-
+/**
+ * Checks if the correct number of arguments to a management command are present
+ * and otherwise prints an error and returns false.
+ *
+ * @param p         pointer to the parameter array
+ * @param n         number of arguments required
+ * @param flags     if MN_AT_LEAST require at least n parameters and not exactly n
+ * @return          Return whether p has n (or at least n) parameters
+ */
 static bool
 man_need(struct management *man, const char **p, const int n, unsigned int flags)
 {
@@ -1243,6 +1300,15 @@
 #endif
 
 static void
+set_client_version(struct management *man, const char *version)
+{
+    if (version)
+    {
+        man->connection.client_version = atoi(version);
+    }
+}
+
+static void
 man_dispatch_command(struct management *man, struct status_output *so, const char **p, const int nparms)
 {
     struct gc_arena gc = gc_new();
@@ -1257,6 +1323,10 @@
     {
         man_help();
     }
+    else if (streq(p[0], "version") && p[1])
+    {
+        set_client_version(man, p[1]);
+    }
     else if (streq(p[0], "version"))
     {
         msg(M_CLIENT, "OpenVPN Version: %s", title_string);
@@ -1459,6 +1529,13 @@
             man_query_need_str(man, p[1], p[2]);
         }
     }
+    else if (streq(p[0], "cr-response"))
+    {
+        if (man_need(man, p, 1, 0))
+        {
+            man_send_cc_message(man, "CR_RESPONSE", p[1]);
+        }
+    }
     else if (streq(p[0], "net"))
     {
         man_net(man);
@@ -1503,6 +1580,13 @@
             man_client_auth(man, p[1], p[2], true);
         }
     }
+    else if (streq(p[0], "client-pending-auth"))
+    {
+        if (man_need(man, p, 2, 0))
+        {
+            man_client_pending_auth(man, p[1], p[2]);
+        }
+    }
 #ifdef MANAGEMENT_PF
     else if (streq(p[0], "client-pf"))
     {
@@ -1513,16 +1597,18 @@
     }
 #endif
 #endif /* ifdef MANAGEMENT_DEF_AUTH */
-#ifdef MANAGMENT_EXTERNAL_KEY
     else if (streq(p[0], "rsa-sig"))
     {
-        man_rsa_sig(man);
+        man_pk_sig(man, "rsa-sig");
+    }
+    else if (streq(p[0], "pk-sig"))
+    {
+        man_pk_sig(man, "pk-sig");
     }
     else if (streq(p[0], "certificate"))
     {
         man_certificate(man);
     }
-#endif
 #ifdef ENABLE_PKCS11
     else if (streq(p[0], "pkcs11-id-count"))
     {
@@ -1911,19 +1997,16 @@
         man->connection.state = MS_INITIAL;
         command_line_reset(man->connection.in);
         buffer_list_reset(man->connection.out);
-#ifdef MANAGEMENT_IN_EXTRA
         in_extra_reset(&man->connection, IER_RESET);
-#endif
         msg(D_MANAGEMENT, "MANAGEMENT: Client disconnected");
     }
     if (!exiting)
     {
-#ifdef ENABLE_CRYPTO
         if (man->settings.flags & MF_FORGET_DISCONNECT)
         {
             ssl_purge_auth(false);
         }
-#endif
+
         if (man->settings.flags & MF_SIGNAL)
         {
             int mysig = man_mod_signal(man, SIGUSR1);
@@ -1956,9 +2039,7 @@
 
     CLEAR(parms);
     so = status_open(NULL, 0, -1, &man->persist.vout, 0);
-#ifdef MANAGEMENT_IN_EXTRA
     in_extra_reset(&man->connection, IER_RESET);
-#endif
 
     if (man_password_needed(man))
     {
@@ -2021,7 +2102,7 @@
 static ssize_t
 man_send_with_fd(int fd, void *ptr, size_t nbytes, int flags, int sendfd)
 {
-    struct msghdr msg;
+    struct msghdr msg = { 0 };
     struct iovec iov[1];
 
     union {
@@ -2053,7 +2134,7 @@
 static ssize_t
 man_recv_with_fd(int fd, void *ptr, size_t nbytes, int flags, int *recvfd)
 {
-    struct msghdr msghdr;
+    struct msghdr msghdr = { 0 };
     struct iovec iov[1];
     ssize_t n;
 
@@ -2196,7 +2277,6 @@
             const char *line;
             while ((line = command_line_get(man->connection.in)))
             {
-#ifdef MANAGEMENT_IN_EXTRA
                 if (man->connection.in_extra)
                 {
                     if (!strcmp(line, "END"))
@@ -2209,8 +2289,9 @@
                     }
                 }
                 else
-#endif
-                man_process_command(man, (char *) line);
+                {
+                    man_process_command(man, (char *) line);
+                }
                 if (man->connection.halt)
                 {
                     break;
@@ -2511,6 +2592,8 @@
             man->connection.es = event_set_init(&maxevents, EVENT_METHOD_FAST);
         }
 
+        man->connection.client_version = 1; /* default version */
+
         /*
          * Listen/connect socket
          */
@@ -2554,12 +2637,8 @@
     {
         buffer_list_free(mc->out);
     }
-#ifdef MANAGEMENT_IN_EXTRA
     in_extra_reset(&man->connection, IER_RESET);
-#endif
-#ifdef MANAGMENT_EXTERNAL_KEY
     buffer_list_free(mc->ext_key_input);
-#endif
     man_connection_clear(mc);
 }
 
@@ -2740,7 +2819,9 @@
         "ifconfig_pool_netmask=",
         "time_duration=",
         "bytes_sent=",
-        "bytes_received="
+        "bytes_received=",
+        "session_id=",
+        "session_state="
     };
 
     if (env_filter_level == 0)
@@ -2827,7 +2908,7 @@
 #ifdef MANAGEMENT_DEF_AUTH
 
 static void
-man_output_peer_info_env(struct management *man, struct man_def_auth_context *mdac)
+man_output_peer_info_env(struct management *man, const struct man_def_auth_context *mdac)
 {
     char line[256];
     if (man->persist.callback.get_peer_info)
@@ -2878,6 +2959,32 @@
 }
 
 void
+management_notify_client_cr_response(unsigned mda_key_id,
+                                     const struct man_def_auth_context *mdac,
+                                     const struct env_set *es,
+                                     const char *response)
+{
+    struct gc_arena gc;
+    if (management)
+    {
+        gc = gc_new();
+
+        struct buffer out = alloc_buf_gc(256, &gc);
+        msg(M_CLIENT, ">CLIENT:CR_RESPONSE,%lu,%u,%s",
+            mdac->cid, mda_key_id, response);
+        man_output_extra_env(management, "CLIENT");
+        if (management->connection.env_filter_level>0)
+        {
+            man_output_peer_info_env(management, mdac);
+        }
+        man_output_env(es, true, management->connection.env_filter_level, "CLIENT");
+        management_notify_generic(management, BSTR(&out));
+
+        gc_free(&gc);
+    }
+}
+
+void
 management_connection_established(struct management *management,
                                   struct man_def_auth_context *mdac,
                                   const struct env_set *es)
@@ -3203,12 +3310,17 @@
 
     if (man_standalone_ok(man))
     {
+        /* expire time can be already overdue, for this case init zero
+         * timeout to avoid waiting first time and exit loop early with
+         * either obtained event or timeout.
+         */
+        tv.tv_usec = 0;
+        tv.tv_sec = 0;
+
         while (true)
         {
             event_reset(man->connection.es);
             management_socket_set(man, man->connection.es, NULL, NULL);
-            tv.tv_usec = 0;
-            tv.tv_sec = 1;
             if (man_check_for_signals(signal_received))
             {
                 status = -1;
@@ -3236,6 +3348,10 @@
                 }
                 break;
             }
+
+            /* wait one second more */
+            tv.tv_sec = 1;
+            tv.tv_usec = 0;
         }
     }
     return status;
@@ -3337,7 +3453,7 @@
 
         /* set expire time */
         update_time();
-        if (sec)
+        if (sec >= 0)
         {
             expire = now + sec;
         }
@@ -3367,7 +3483,7 @@
         /* revert state */
         man->persist.standalone_disabled = standalone_disabled_save;
     }
-    else
+    else if (sec > 0)
     {
         sleep(sec);
     }
@@ -3394,9 +3510,7 @@
         const char *alert_type = NULL;
         const char *prefix = NULL;
         unsigned int up_query_mode = 0;
-#ifdef ENABLE_CLIENT_CR
         const char *sc = NULL;
-#endif
         ret = true;
         man->persist.standalone_disabled = false; /* This is so M_CLIENT messages will be correctly passed through msg() */
         man->persist.special_state_msg = NULL;
@@ -3426,12 +3540,10 @@
             up_query_mode = UP_QUERY_USER_PASS;
             prefix = "PASSWORD";
             alert_type = "username/password";
-#ifdef ENABLE_CLIENT_CR
             if (static_challenge)
             {
                 sc = static_challenge;
             }
-#endif
         }
         buf_printf(&alert_msg, ">%s:Need '%s' %s",
                    prefix,
@@ -3443,14 +3555,12 @@
             buf_printf(&alert_msg, " MSG:%s", up->username);
         }
 
-#ifdef ENABLE_CLIENT_CR
         if (sc)
         {
             buf_printf(&alert_msg, " SC:%d,%s",
                        BOOL_CAST(flags & GET_USER_PASS_STATIC_CHALLENGE_ECHO),
                        sc);
         }
-#endif
 
         man_wait_for_client_connection(man, &signal_received, 0, MWCC_PASSWORD_WAIT);
         if (signal_received)
@@ -3503,7 +3613,6 @@
         {
             /* preserve caller's settings */
             man->connection.up_query.nocache = up->nocache;
-            man->connection.up_query.wait_for_push = up->wait_for_push;
             *up = man->connection.up_query;
         }
         secure_memzero(&man->connection.up_query, sizeof(man->connection.up_query));
@@ -3513,8 +3622,6 @@
     return ret;
 }
 
-#ifdef MANAGMENT_EXTERNAL_KEY
-
 static int
 management_query_multiline(struct management *man,
                            const char *b64_data, const char *prompt, const char *cmd, int *state, struct buffer_list **input)
@@ -3651,13 +3758,31 @@
 
 char *
 /* returns allocated base64 signature */
-management_query_rsa_sig(struct management *man,
-                         const char *b64_data)
+management_query_pk_sig(struct management *man, const char *b64_data,
+                        const char *algorithm)
 {
-    return management_query_multiline_flatten(man, b64_data, "RSA_SIGN", "rsa-sign",
-                                              &man->connection.ext_key_state, &man->connection.ext_key_input);
-}
+    const char *prompt = "PK_SIGN";
+    const char *desc = "pk-sign";
+    struct buffer buf_data = alloc_buf(strlen(b64_data) + strlen(algorithm) + 20);
 
+    if (man->connection.client_version <= 1)
+    {
+        prompt = "RSA_SIGN";
+        desc = "rsa-sign";
+    }
+
+    buf_write(&buf_data, b64_data, (int) strlen(b64_data));
+    if (man->connection.client_version > 2)
+    {
+        buf_write(&buf_data, ",", (int) strlen(","));
+        buf_write(&buf_data, algorithm, (int) strlen(algorithm));
+    }
+    char *ret = management_query_multiline_flatten(man,
+                                                   (char *)buf_bptr(&buf_data), prompt, desc,
+                                                   &man->connection.ext_key_state, &man->connection.ext_key_input);
+    free_buf(&buf_data);
+    return ret;
+}
 
 char *
 management_query_cert(struct management *man, const char *cert_name)
@@ -3675,8 +3800,6 @@
     return result;
 }
 
-#endif /* ifdef MANAGMENT_EXTERNAL_KEY */
-
 /*
  * Return true if management_hold() would block
  */
@@ -4002,11 +4125,15 @@
 void
 management_sleep(const int n)
 {
-    if (management)
+    if (n < 0)
+    {
+        return;
+    }
+    else if (management)
     {
         management_event_loop_n_seconds(management, n);
     }
-    else
+    else if (n > 0)
     {
         sleep(n);
     }
@@ -4017,7 +4144,10 @@
 void
 management_sleep(const int n)
 {
-    sleep(n);
+    if (n > 0)
+    {
+        sleep(n);
+    }
 }
 
 #endif /* ENABLE_MANAGEMENT */
diff --git a/src/openvpn/manage.h b/src/openvpn/manage.h
index f286754..881bfb1 100644
--- a/src/openvpn/manage.h
+++ b/src/openvpn/manage.h
@@ -31,7 +31,7 @@
 #include "socket.h"
 #include "mroute.h"
 
-#define MANAGEMENT_VERSION                      1
+#define MANAGEMENT_VERSION                      3
 #define MANAGEMENT_N_PASSWORD_RETRIES           3
 #define MANAGEMENT_LOG_HISTORY_INITIAL_SIZE   100
 #define MANAGEMENT_ECHO_BUFFER_SIZE           100
@@ -164,6 +164,7 @@
     int (*kill_by_addr) (void *arg, const in_addr_t addr, const int port);
     void (*delete_event) (void *arg, event_t event);
     int (*n_clients) (void *arg);
+    bool (*send_cc_message) (void *arg, const char *message, const char *parameter);
 #ifdef MANAGEMENT_DEF_AUTH
     bool (*kill_by_cid)(void *arg, const unsigned long cid, const char *kill_msg);
     bool (*client_auth) (void *arg,
@@ -173,6 +174,9 @@
                          const char *reason,
                          const char *client_reason,
                          struct buffer_list *cc_config); /* ownership transferred */
+    bool (*client_pending_auth) (void *arg,
+                                 const unsigned long cid,
+                                 const char *url);
     char *(*get_peer_info) (void *arg, const unsigned long cid);
 #endif
 #ifdef MANAGEMENT_PF
@@ -275,19 +279,18 @@
     struct command_line *in;
     struct buffer_list *out;
 
-#ifdef MANAGEMENT_IN_EXTRA
 #define IEC_UNDEF       0
 #define IEC_CLIENT_AUTH 1
 #define IEC_CLIENT_PF   2
 #define IEC_RSA_SIGN    3
 #define IEC_CERTIFICATE 4
+#define IEC_PK_SIGN     5
     int in_extra_cmd;
     struct buffer_list *in_extra;
 #ifdef MANAGEMENT_DEF_AUTH
     unsigned long in_extra_cid;
     unsigned int in_extra_kid;
 #endif
-#ifdef MANAGMENT_EXTERNAL_KEY
 #define EKS_UNDEF   0
 #define EKS_SOLICIT 1
 #define EKS_INPUT   2
@@ -296,8 +299,6 @@
     struct buffer_list *ext_key_input;
     int ext_cert_state;
     struct buffer_list *ext_cert_input;
-#endif
-#endif /* ifdef MANAGEMENT_IN_EXTRA */
     struct event_set *es;
     int env_filter_level;
 
@@ -311,13 +312,11 @@
     int up_query_mode;
     struct user_pass up_query;
 
-#ifdef MANAGMENT_EXTERNAL_KEY
-    struct buffer_list *rsa_sig;
-#endif
 #ifdef TARGET_ANDROID
     int fdtosend;
     int lastfdreceived;
 #endif
+    int client_version;
 };
 
 struct management
@@ -346,14 +345,14 @@
 #ifdef MANAGEMENT_PF
 #define MF_CLIENT_PF         (1<<7)
 #endif
-#define MF_UNIX_SOCK       (1<<8)
-#ifdef MANAGMENT_EXTERNAL_KEY
-#define MF_EXTERNAL_KEY    (1<<9)
-#endif
-#define MF_UP_DOWN          (1<<10)
-#define MF_QUERY_REMOTE     (1<<11)
-#define MF_QUERY_PROXY      (1<<12)
-#define MF_EXTERNAL_CERT    (1<<13)
+#define MF_UNIX_SOCK                (1<<8)
+#define MF_EXTERNAL_KEY             (1<<9)
+#define MF_EXTERNAL_KEY_NOPADDING   (1<<10)
+#define MF_EXTERNAL_KEY_PKCS1PAD    (1<<11)
+#define MF_UP_DOWN                  (1<<12)
+#define MF_QUERY_REMOTE             (1<<13)
+#define MF_QUERY_PROXY              (1<<14)
+#define MF_EXTERNAL_CERT            (1<<15)
 
 bool management_open(struct management *man,
                      const char *addr,
@@ -435,16 +434,18 @@
                            const struct mroute_addr *addr,
                            const bool primary);
 
-#endif
+void management_notify_client_cr_response(unsigned mda_key_id,
+                                          const struct man_def_auth_context *mdac,
+                                          const struct env_set *es,
+                                          const char *response);
 
-#ifdef MANAGMENT_EXTERNAL_KEY
+#endif /* ifdef MANAGEMENT_DEF_AUTH */
 
-char *management_query_rsa_sig(struct management *man, const char *b64_data);
+char *management_query_pk_sig(struct management *man, const char *b64_data,
+                              const char *algorithm);
 
 char *management_query_cert(struct management *man, const char *cert_name);
 
-#endif
-
 static inline bool
 management_connected(const struct management *man)
 {
@@ -583,17 +584,17 @@
 
 #ifdef MANAGEMENT_DEF_AUTH
 
+void man_bytecount_output_server(struct management *man,
+                                 const counter_type *bytes_in_total,
+                                 const counter_type *bytes_out_total,
+                                 struct man_def_auth_context *mdac);
+
 static inline void
 management_bytes_server(struct management *man,
                         const counter_type *bytes_in_total,
                         const counter_type *bytes_out_total,
                         struct man_def_auth_context *mdac)
 {
-    void man_bytecount_output_server(struct management *man,
-                                     const counter_type *bytes_in_total,
-                                     const counter_type *bytes_out_total,
-                                     struct man_def_auth_context *mdac);
-
     if (man->connection.bytecount_update_seconds > 0
         && now >= mdac->bytecount_last_update + man->connection.bytecount_update_seconds
         && (mdac->flags & (DAF_CONNECTION_ESTABLISHED|DAF_CONNECTION_CLOSED)) == DAF_CONNECTION_ESTABLISHED)
diff --git a/src/openvpn/mbuf.h b/src/openvpn/mbuf.h
index 4912c95..f37563d 100644
--- a/src/openvpn/mbuf.h
+++ b/src/openvpn/mbuf.h
@@ -96,11 +96,11 @@
     return (int) ms->max_queued;
 }
 
+struct multi_instance *mbuf_peek_dowork(struct mbuf_set *ms);
+
 static inline struct multi_instance *
 mbuf_peek(struct mbuf_set *ms)
 {
-    struct multi_instance *mbuf_peek_dowork(struct mbuf_set *ms);
-
     if (mbuf_defined(ms))
     {
         return mbuf_peek_dowork(ms);
diff --git a/src/openvpn/memdbg.h b/src/openvpn/memdbg.h
index 70c6365..6da9712 100644
--- a/src/openvpn/memdbg.h
+++ b/src/openvpn/memdbg.h
@@ -44,7 +44,7 @@
 
 #ifdef USE_VALGRIND
 
-#include "valgrind/memcheck.h"
+#include <valgrind/memcheck.h>
 
 #define VALGRIND_MAKE_READABLE(addr, len)
 
@@ -84,7 +84,7 @@
  *  #define INTERNAL_MEMORY_SPACE (1024 * 1024 * 50)
  */
 
-#include "dmalloc.h"
+#include <dmalloc.h>
 
 #define openvpn_dmalloc(file, line, size) dmalloc_malloc((file), (line), (size), DMALLOC_FUNC_MALLOC, 0, 0)
 
diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c
index 9c5e96e..84825c9 100644
--- a/src/openvpn/misc.c
+++ b/src/openvpn/misc.c
@@ -51,9 +51,6 @@
 const char *iproute_path = IPROUTE_PATH; /* GLOBAL */
 #endif
 
-/* contains an SSEC_x value defined in misc.h */
-int script_security = SSEC_BUILT_IN; /* GLOBAL */
-
 /*
  * Set standard file descriptors to /dev/null
  */
@@ -99,695 +96,6 @@
 }
 
 /*
- * Generate an error message based on the status code returned by openvpn_execve().
- */
-const char *
-system_error_message(int stat, struct gc_arena *gc)
-{
-    struct buffer out = alloc_buf_gc(256, gc);
-
-    switch (stat)
-    {
-        case OPENVPN_EXECVE_NOT_ALLOWED:
-            buf_printf(&out, "disallowed by script-security setting");
-            break;
-
-#ifdef _WIN32
-        case OPENVPN_EXECVE_ERROR:
-            buf_printf(&out, "external program did not execute -- ");
-        /* fall through */
-
-        default:
-            buf_printf(&out, "returned error code %d", stat);
-            break;
-#else  /* ifdef _WIN32 */
-
-        case OPENVPN_EXECVE_ERROR:
-            buf_printf(&out, "external program fork failed");
-            break;
-
-        default:
-            if (!WIFEXITED(stat))
-            {
-                buf_printf(&out, "external program did not exit normally");
-            }
-            else
-            {
-                const int cmd_ret = WEXITSTATUS(stat);
-                if (!cmd_ret)
-                {
-                    buf_printf(&out, "external program exited normally");
-                }
-                else if (cmd_ret == OPENVPN_EXECVE_FAILURE)
-                {
-                    buf_printf(&out, "could not execute external program");
-                }
-                else
-                {
-                    buf_printf(&out, "external program exited with error status: %d", cmd_ret);
-                }
-            }
-            break;
-#endif /* ifdef _WIN32 */
-    }
-    return (const char *)out.data;
-}
-
-/*
- * Wrapper around openvpn_execve
- */
-bool
-openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message)
-{
-    struct gc_arena gc = gc_new();
-    const int stat = openvpn_execve(a, es, flags);
-    int ret = false;
-
-    if (platform_system_ok(stat))
-    {
-        ret = true;
-    }
-    else
-    {
-        if (error_message)
-        {
-            msg(((flags & S_FATAL) ? M_FATAL : M_WARN), "%s: %s",
-                error_message,
-                system_error_message(stat, &gc));
-        }
-    }
-    gc_free(&gc);
-    return ret;
-}
-
-bool
-openvpn_execve_allowed(const unsigned int flags)
-{
-    if (flags & S_SCRIPT)
-    {
-        return script_security >= SSEC_SCRIPTS;
-    }
-    else
-    {
-        return script_security >= SSEC_BUILT_IN;
-    }
-}
-
-
-#ifndef _WIN32
-/*
- * Run execve() inside a fork().  Designed to replicate the semantics of system() but
- * in a safer way that doesn't require the invocation of a shell or the risks
- * assocated with formatting and parsing a command line.
- * Returns the exit status of child, OPENVPN_EXECVE_NOT_ALLOWED if openvpn_execve_allowed()
- * returns false, or OPENVPN_EXECVE_ERROR on other errors.
- */
-int
-openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags)
-{
-    struct gc_arena gc = gc_new();
-    int ret = OPENVPN_EXECVE_ERROR;
-    static bool warn_shown = false;
-
-    if (a && a->argv[0])
-    {
-#if defined(ENABLE_FEATURE_EXECVE)
-        if (openvpn_execve_allowed(flags))
-        {
-            const char *cmd = a->argv[0];
-            char *const *argv = a->argv;
-            char *const *envp = (char *const *)make_env_array(es, true, &gc);
-            pid_t pid;
-
-            pid = fork();
-            if (pid == (pid_t)0) /* child side */
-            {
-                execve(cmd, argv, envp);
-                exit(OPENVPN_EXECVE_FAILURE);
-            }
-            else if (pid < (pid_t)0) /* fork failed */
-            {
-                msg(M_ERR, "openvpn_execve: unable to fork");
-            }
-            else /* parent side */
-            {
-                if (waitpid(pid, &ret, 0) != pid)
-                {
-                    ret = OPENVPN_EXECVE_ERROR;
-                }
-            }
-        }
-        else
-        {
-            ret = OPENVPN_EXECVE_NOT_ALLOWED;
-            if (!warn_shown && (script_security < SSEC_SCRIPTS))
-            {
-                msg(M_WARN, SCRIPT_SECURITY_WARNING);
-                warn_shown = true;
-            }
-        }
-#else  /* if defined(ENABLE_FEATURE_EXECVE) */
-        msg(M_WARN, "openvpn_execve: execve function not available");
-#endif /* if defined(ENABLE_FEATURE_EXECVE) */
-    }
-    else
-    {
-        msg(M_FATAL, "openvpn_execve: called with empty argv");
-    }
-
-    gc_free(&gc);
-    return ret;
-}
-#endif /* ifndef _WIN32 */
-
-/*
- * Run execve() inside a fork(), duping stdout.  Designed to replicate the semantics of popen() but
- * in a safer way that doesn't require the invocation of a shell or the risks
- * assocated with formatting and parsing a command line.
- */
-int
-openvpn_popen(const struct argv *a,  const struct env_set *es)
-{
-    struct gc_arena gc = gc_new();
-    int ret = -1;
-    static bool warn_shown = false;
-
-    if (a && a->argv[0])
-    {
-#if defined(ENABLE_FEATURE_EXECVE)
-        if (script_security >= SSEC_BUILT_IN)
-        {
-            const char *cmd = a->argv[0];
-            char *const *argv = a->argv;
-            char *const *envp = (char *const *)make_env_array(es, true, &gc);
-            pid_t pid;
-            int pipe_stdout[2];
-
-            if (pipe(pipe_stdout) == 0)
-            {
-                pid = fork();
-                if (pid == (pid_t)0)       /* child side */
-                {
-                    close(pipe_stdout[0]);         /* Close read end */
-                    dup2(pipe_stdout[1],1);
-                    execve(cmd, argv, envp);
-                    exit(OPENVPN_EXECVE_FAILURE);
-                }
-                else if (pid > (pid_t)0)       /* parent side */
-                {
-                    int status = 0;
-
-                    close(pipe_stdout[1]);        /* Close write end */
-                    waitpid(pid, &status, 0);
-                    ret = pipe_stdout[0];
-                }
-                else       /* fork failed */
-                {
-                    close(pipe_stdout[0]);
-                    close(pipe_stdout[1]);
-                    msg(M_ERR, "openvpn_popen: unable to fork %s", cmd);
-                }
-            }
-            else
-            {
-                msg(M_WARN, "openvpn_popen: unable to create stdout pipe for %s", cmd);
-                ret = -1;
-            }
-        }
-        else if (!warn_shown && (script_security < SSEC_SCRIPTS))
-        {
-            msg(M_WARN, SCRIPT_SECURITY_WARNING);
-            warn_shown = true;
-        }
-#else  /* if defined(ENABLE_FEATURE_EXECVE) */
-        msg(M_WARN, "openvpn_popen: execve function not available");
-#endif /* if defined(ENABLE_FEATURE_EXECVE) */
-    }
-    else
-    {
-        msg(M_FATAL, "openvpn_popen: called with empty argv");
-    }
-
-    gc_free(&gc);
-    return ret;
-}
-
-
-
-/*
- * Set environmental variable (int or string).
- *
- * On Posix, we use putenv for portability,
- * and put up with its painful semantics
- * that require all the support code below.
- */
-
-/* General-purpose environmental variable set functions */
-
-static char *
-construct_name_value(const char *name, const char *value, struct gc_arena *gc)
-{
-    struct buffer out;
-
-    ASSERT(name);
-    if (!value)
-    {
-        value = "";
-    }
-    out = alloc_buf_gc(strlen(name) + strlen(value) + 2, gc);
-    buf_printf(&out, "%s=%s", name, value);
-    return BSTR(&out);
-}
-
-static bool
-env_string_equal(const char *s1, const char *s2)
-{
-    int c1, c2;
-    ASSERT(s1);
-    ASSERT(s2);
-
-    while (true)
-    {
-        c1 = *s1++;
-        c2 = *s2++;
-        if (c1 == '=')
-        {
-            c1 = 0;
-        }
-        if (c2 == '=')
-        {
-            c2 = 0;
-        }
-        if (!c1 && !c2)
-        {
-            return true;
-        }
-        if (c1 != c2)
-        {
-            break;
-        }
-    }
-    return false;
-}
-
-static bool
-remove_env_item(const char *str, const bool do_free, struct env_item **list)
-{
-    struct env_item *current, *prev;
-
-    ASSERT(str);
-    ASSERT(list);
-
-    for (current = *list, prev = NULL; current != NULL; current = current->next)
-    {
-        if (env_string_equal(current->string, str))
-        {
-            if (prev)
-            {
-                prev->next = current->next;
-            }
-            else
-            {
-                *list = current->next;
-            }
-            if (do_free)
-            {
-                secure_memzero(current->string, strlen(current->string));
-                free(current->string);
-                free(current);
-            }
-            return true;
-        }
-        prev = current;
-    }
-    return false;
-}
-
-static void
-add_env_item(char *str, const bool do_alloc, struct env_item **list, struct gc_arena *gc)
-{
-    struct env_item *item;
-
-    ASSERT(str);
-    ASSERT(list);
-
-    ALLOC_OBJ_GC(item, struct env_item, gc);
-    item->string = do_alloc ? string_alloc(str, gc) : str;
-    item->next = *list;
-    *list = item;
-}
-
-/* struct env_set functions */
-
-static bool
-env_set_del_nolock(struct env_set *es, const char *str)
-{
-    return remove_env_item(str, es->gc == NULL, &es->list);
-}
-
-static void
-env_set_add_nolock(struct env_set *es, const char *str)
-{
-    remove_env_item(str, es->gc == NULL, &es->list);
-    add_env_item((char *)str, true, &es->list, es->gc);
-}
-
-struct env_set *
-env_set_create(struct gc_arena *gc)
-{
-    struct env_set *es;
-    ALLOC_OBJ_CLEAR_GC(es, struct env_set, gc);
-    es->list = NULL;
-    es->gc = gc;
-    return es;
-}
-
-void
-env_set_destroy(struct env_set *es)
-{
-    if (es && es->gc == NULL)
-    {
-        struct env_item *e = es->list;
-        while (e)
-        {
-            struct env_item *next = e->next;
-            free(e->string);
-            free(e);
-            e = next;
-        }
-        free(es);
-    }
-}
-
-bool
-env_set_del(struct env_set *es, const char *str)
-{
-    bool ret;
-    ASSERT(es);
-    ASSERT(str);
-    ret = env_set_del_nolock(es, str);
-    return ret;
-}
-
-void
-env_set_add(struct env_set *es, const char *str)
-{
-    ASSERT(es);
-    ASSERT(str);
-    env_set_add_nolock(es, str);
-}
-
-const char *
-env_set_get(const struct env_set *es, const char *name)
-{
-    const struct env_item *item = es->list;
-    while (item && !env_string_equal(item->string, name))
-    {
-        item = item->next;
-    }
-    return item ? item->string : NULL;
-}
-
-void
-env_set_print(int msglevel, const struct env_set *es)
-{
-    if (check_debug_level(msglevel))
-    {
-        const struct env_item *e;
-        int i;
-
-        if (es)
-        {
-            e = es->list;
-            i = 0;
-
-            while (e)
-            {
-                if (env_safe_to_print(e->string))
-                {
-                    msg(msglevel, "ENV [%d] '%s'", i, e->string);
-                }
-                ++i;
-                e = e->next;
-            }
-        }
-    }
-}
-
-void
-env_set_inherit(struct env_set *es, const struct env_set *src)
-{
-    const struct env_item *e;
-
-    ASSERT(es);
-
-    if (src)
-    {
-        e = src->list;
-        while (e)
-        {
-            env_set_add_nolock(es, e->string);
-            e = e->next;
-        }
-    }
-}
-
-
-/* add/modify/delete environmental strings */
-
-void
-setenv_counter(struct env_set *es, const char *name, counter_type value)
-{
-    char buf[64];
-    openvpn_snprintf(buf, sizeof(buf), counter_format, value);
-    setenv_str(es, name, buf);
-}
-
-void
-setenv_int(struct env_set *es, const char *name, int value)
-{
-    char buf[64];
-    openvpn_snprintf(buf, sizeof(buf), "%d", value);
-    setenv_str(es, name, buf);
-}
-
-void
-setenv_unsigned(struct env_set *es, const char *name, unsigned int value)
-{
-    char buf[64];
-    openvpn_snprintf(buf, sizeof(buf), "%u", value);
-    setenv_str(es, name, buf);
-}
-
-void
-setenv_str(struct env_set *es, const char *name, const char *value)
-{
-    setenv_str_ex(es, name, value, CC_NAME, 0, 0, CC_PRINT, 0, 0);
-}
-
-void
-setenv_str_safe(struct env_set *es, const char *name, const char *value)
-{
-    uint8_t b[64];
-    struct buffer buf;
-    buf_set_write(&buf, b, sizeof(b));
-    if (buf_printf(&buf, "OPENVPN_%s", name))
-    {
-        setenv_str(es, BSTR(&buf), value);
-    }
-    else
-    {
-        msg(M_WARN, "setenv_str_safe: name overflow");
-    }
-}
-
-void
-setenv_str_incr(struct env_set *es, const char *name, const char *value)
-{
-    unsigned int counter = 1;
-    const size_t tmpname_len = strlen(name) + 5; /* 3 digits counter max */
-    char *tmpname = gc_malloc(tmpname_len, true, NULL);
-    strcpy(tmpname, name);
-    while (NULL != env_set_get(es, tmpname) && counter < 1000)
-    {
-        ASSERT(openvpn_snprintf(tmpname, tmpname_len, "%s_%u", name, counter));
-        counter++;
-    }
-    if (counter < 1000)
-    {
-        setenv_str(es, tmpname, value);
-    }
-    else
-    {
-        msg(D_TLS_DEBUG_MED, "Too many same-name env variables, ignoring: %s", name);
-    }
-    free(tmpname);
-}
-
-void
-setenv_del(struct env_set *es, const char *name)
-{
-    ASSERT(name);
-    setenv_str(es, name, NULL);
-}
-
-void
-setenv_str_ex(struct env_set *es,
-              const char *name,
-              const char *value,
-              const unsigned int name_include,
-              const unsigned int name_exclude,
-              const char name_replace,
-              const unsigned int value_include,
-              const unsigned int value_exclude,
-              const char value_replace)
-{
-    struct gc_arena gc = gc_new();
-    const char *name_tmp;
-    const char *val_tmp = NULL;
-
-    ASSERT(name && strlen(name) > 1);
-
-    name_tmp = string_mod_const(name, name_include, name_exclude, name_replace, &gc);
-
-    if (value)
-    {
-        val_tmp = string_mod_const(value, value_include, value_exclude, value_replace, &gc);
-    }
-
-    ASSERT(es);
-
-    if (val_tmp)
-    {
-        const char *str = construct_name_value(name_tmp, val_tmp, &gc);
-        env_set_add(es, str);
-#if DEBUG_VERBOSE_SETENV
-        msg(M_INFO, "SETENV_ES '%s'", str);
-#endif
-    }
-    else
-    {
-        env_set_del(es, name_tmp);
-    }
-
-    gc_free(&gc);
-}
-
-/*
- * Setenv functions that append an integer index to the name
- */
-static const char *
-setenv_format_indexed_name(const char *name, const int i, struct gc_arena *gc)
-{
-    struct buffer out = alloc_buf_gc(strlen(name) + 16, gc);
-    if (i >= 0)
-    {
-        buf_printf(&out, "%s_%d", name, i);
-    }
-    else
-    {
-        buf_printf(&out, "%s", name);
-    }
-    return BSTR(&out);
-}
-
-void
-setenv_int_i(struct env_set *es, const char *name, const int value, const int i)
-{
-    struct gc_arena gc = gc_new();
-    const char *name_str = setenv_format_indexed_name(name, i, &gc);
-    setenv_int(es, name_str, value);
-    gc_free(&gc);
-}
-
-void
-setenv_str_i(struct env_set *es, const char *name, const char *value, const int i)
-{
-    struct gc_arena gc = gc_new();
-    const char *name_str = setenv_format_indexed_name(name, i, &gc);
-    setenv_str(es, name_str, value);
-    gc_free(&gc);
-}
-
-/* return true if filename can be opened for read */
-bool
-test_file(const char *filename)
-{
-    bool ret = false;
-    if (filename)
-    {
-        FILE *fp = platform_fopen(filename, "r");
-        if (fp)
-        {
-            fclose(fp);
-            ret = true;
-        }
-        else
-        {
-            if (openvpn_errno() == EACCES)
-            {
-                msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename);
-            }
-        }
-    }
-
-    dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]",
-         filename ? filename : "UNDEF",
-         ret);
-
-    return ret;
-}
-
-/* create a temporary filename in directory */
-const char *
-create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
-{
-    static unsigned int counter;
-    struct buffer fname = alloc_buf_gc(256, gc);
-    int fd;
-    const char *retfname = NULL;
-    unsigned int attempts = 0;
-
-    do
-    {
-        ++attempts;
-        ++counter;
-
-        buf_printf(&fname, PACKAGE "_%s_%08lx%08lx.tmp", prefix,
-                   (unsigned long) get_random(), (unsigned long) get_random());
-
-        retfname = gen_path(directory, BSTR(&fname), gc);
-        if (!retfname)
-        {
-            msg(M_WARN, "Failed to create temporary filename and path");
-            return NULL;
-        }
-
-        /* Atomically create the file.  Errors out if the file already
-         * exists.  */
-        fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
-        if (fd != -1)
-        {
-            close(fd);
-            return retfname;
-        }
-        else if (fd == -1 && errno != EEXIST)
-        {
-            /* Something else went wrong, no need to retry.  */
-            msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'",
-                retfname);
-            return NULL;
-        }
-    }
-    while (attempts < 6);
-
-    msg(M_WARN, "Failed to create temporary file after %i attempts", attempts);
-    return NULL;
-}
-
-#ifdef ENABLE_CRYPTO
-
-/*
  * Prepend a random string to hostname to prevent DNS caching.
  * For example, foo.bar.gov would be modified to <random-chars>.foo.bar.gov.
  * Of course, this requires explicit support in the DNS server (wildcard).
@@ -808,80 +116,7 @@
 #undef n_rnd_bytes
 }
 
-#else  /* ifdef ENABLE_CRYPTO */
-
-const char *
-hostname_randomize(const char *hostname, struct gc_arena *gc)
-{
-    msg(M_WARN, "WARNING: hostname randomization disabled when crypto support is not compiled");
-    return hostname;
-}
-
-#endif /* ifdef ENABLE_CRYPTO */
-
-/*
- * Put a directory and filename together.
- */
-const char *
-gen_path(const char *directory, const char *filename, struct gc_arena *gc)
-{
-#ifdef _WIN32
-    const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON
-                                 |CC_DOUBLE_QUOTE|CC_SLASH|CC_BACKSLASH|CC_PIPE|CC_QUESTION_MARK|CC_ASTERISK;
-#else
-    const int CC_PATH_RESERVED = CC_SLASH;
-#endif
-    const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);
-
-    if (safe_filename
-        && strcmp(safe_filename, ".")
-        && strcmp(safe_filename, "..")
-#ifdef _WIN32
-        && win_safe_filename(safe_filename)
-#endif
-        )
-    {
-        const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16;
-        struct buffer out = alloc_buf_gc(outsize, gc);
-        char dirsep[2];
-
-        dirsep[0] = OS_SPECIFIC_DIRSEP;
-        dirsep[1] = '\0';
-
-        if (directory)
-        {
-            buf_printf(&out, "%s%s", directory, dirsep);
-        }
-        buf_printf(&out, "%s", safe_filename);
-
-        return BSTR(&out);
-    }
-    else
-    {
-        return NULL;
-    }
-}
-
-bool
-absolute_pathname(const char *pathname)
-{
-    if (pathname)
-    {
-        const int c = pathname[0];
-#ifdef _WIN32
-        return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\');
-#else
-        return c == '/';
-#endif
-    }
-    else
-    {
-        return false;
-    }
-}
-
 #ifdef ENABLE_MANAGEMENT
-
 /* Get username/password from the management interface */
 static bool
 auth_user_pass_mgmt(struct user_pass *up, const char *prefix, const unsigned int flags,
@@ -894,13 +129,10 @@
         management_auth_failure(management, prefix, "previous auth credentials failed");
     }
 
-#ifdef ENABLE_CLIENT_CR
     if (auth_challenge && (flags & GET_USER_PASS_STATIC_CHALLENGE))
     {
         sc = auth_challenge;
     }
-#endif
-
     if (!management_query_user_pass(management, up, prefix, flags, sc))
     {
         if ((flags & GET_USER_PASS_NOFATAL) != 0)
@@ -914,7 +146,6 @@
     }
     return true;
 }
-
 #endif /* ifdef ENABLE_MANAGEMENT */
 
 /*
@@ -1069,7 +300,7 @@
          */
         if (username_from_stdin || password_from_stdin || response_from_stdin)
         {
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
             if (auth_challenge && (flags & GET_USER_PASS_DYNAMIC_CHALLENGE) && response_from_stdin)
             {
                 struct auth_challenge_info *ac = get_auth_challenge(auth_challenge, &gc);
@@ -1096,7 +327,7 @@
                 }
             }
             else
-#endif /* ifdef ENABLE_CLIENT_CR */
+#endif /* ifdef ENABLE_MANAGEMENT */
             {
                 struct buffer user_prompt = alloc_buf_gc(128, &gc);
                 struct buffer pass_prompt = alloc_buf_gc(128, &gc);
@@ -1130,7 +361,7 @@
                     }
                 }
 
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
                 if (auth_challenge && (flags & GET_USER_PASS_STATIC_CHALLENGE) && response_from_stdin)
                 {
                     char *response = (char *) gc_malloc(USER_PASS_LEN, false, &gc);
@@ -1158,7 +389,7 @@
                     string_clear(resp64);
                     free(resp64);
                 }
-#endif /* ifdef ENABLE_CLIENT_CR */
+#endif /* ifdef ENABLE_MANAGEMENT */
             }
         }
 
@@ -1177,7 +408,7 @@
     return true;
 }
 
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
 
 /*
  * See management/management-notes.txt for more info on the
@@ -1252,52 +483,7 @@
     }
 }
 
-#endif /* ifdef ENABLE_CLIENT_CR */
-
-#if AUTO_USERID
-
-void
-get_user_pass_auto_userid(struct user_pass *up, const char *tag)
-{
-    struct gc_arena gc = gc_new();
-    struct buffer buf;
-    uint8_t macaddr[6];
-    static uint8_t digest [MD5_DIGEST_LENGTH];
-    static const uint8_t hashprefix[] = "AUTO_USERID_DIGEST";
-
-    const md_kt_t *md5_kt = md_kt_get("MD5");
-    md_ctx_t *ctx;
-
-    CLEAR(*up);
-    buf_set_write(&buf, (uint8_t *)up->username, USER_PASS_LEN);
-    buf_printf(&buf, "%s", TARGET_PREFIX);
-    if (get_default_gateway_mac_addr(macaddr))
-    {
-        dmsg(D_AUTO_USERID, "GUPAU: macaddr=%s", format_hex_ex(macaddr, sizeof(macaddr), 0, 1, ":", &gc));
-        ctx = md_ctx_new();
-        md_ctx_init(ctx, md5_kt);
-        md_ctx_update(ctx, hashprefix, sizeof(hashprefix) - 1);
-        md_ctx_update(ctx, macaddr, sizeof(macaddr));
-        md_ctx_final(ctx, digest);
-        md_ctx_cleanup(ctx);
-        md_ctx_free(ctx);
-        buf_printf(&buf, "%s", format_hex_ex(digest, sizeof(digest), 0, 256, " ", &gc));
-    }
-    else
-    {
-        buf_printf(&buf, "UNKNOWN");
-    }
-    if (tag && strcmp(tag, "stdin"))
-    {
-        buf_printf(&buf, "-%s", tag);
-    }
-    up->defined = true;
-    gc_free(&gc);
-
-    dmsg(D_AUTO_USERID, "GUPAU: AUTO_USERID: '%s'", up->username);
-}
-
-#endif /* if AUTO_USERID */
+#endif /* ifdef ENABLE_MANAGEMENT */
 
 void
 purge_user_pass(struct user_pass *up, const bool force)
@@ -1324,10 +510,15 @@
 set_auth_token(struct user_pass *up, struct user_pass *tk, const char *token)
 {
 
-    if (token && strlen(token) && up && up->defined)
+    if (strlen(token) && (up->defined || tk->defined))
     {
+        /* auth-token has no password, so it needs the username
+         * either already set or copied from up */
         strncpynt(tk->password, token, USER_PASS_LEN);
-        strncpynt(tk->username, up->username, USER_PASS_LEN);
+        if (up->defined)
+        {
+            strncpynt(tk->username, up->username, USER_PASS_LEN);
+        }
         tk->defined = true;
     }
 
@@ -1347,71 +538,6 @@
     return string_mod_const(str, CC_PRINT, CC_CRLF, '.', gc);
 }
 
-static bool
-is_password_env_var(const char *str)
-{
-    return (strncmp(str, "password", 8) == 0);
-}
-
-bool
-env_allowed(const char *str)
-{
-    return (script_security >= SSEC_PW_ENV || !is_password_env_var(str));
-}
-
-bool
-env_safe_to_print(const char *str)
-{
-#ifndef UNSAFE_DEBUG
-    if (is_password_env_var(str))
-    {
-        return false;
-    }
-#endif
-    return true;
-}
-
-/* Make arrays of strings */
-
-const char **
-make_env_array(const struct env_set *es,
-               const bool check_allowed,
-               struct gc_arena *gc)
-{
-    char **ret = NULL;
-    struct env_item *e = NULL;
-    int i = 0, n = 0;
-
-    /* figure length of es */
-    if (es)
-    {
-        for (e = es->list; e != NULL; e = e->next)
-        {
-            ++n;
-        }
-    }
-
-    /* alloc return array */
-    ALLOC_ARRAY_CLEAR_GC(ret, char *, n+1, gc);
-
-    /* fill return array */
-    if (es)
-    {
-        i = 0;
-        for (e = es->list; e != NULL; e = e->next)
-        {
-            if (!check_allowed || env_allowed(e->string))
-            {
-                ASSERT(i < n);
-                ret[i++] = e->string;
-            }
-        }
-    }
-
-    ret[i] = NULL;
-    return (const char **)ret;
-}
-
 const char **
 make_arg_array(const char *first, const char *parms, struct gc_arena *gc)
 {
@@ -1490,12 +616,12 @@
 }
 
 const char **
-make_extended_arg_array(char **p, struct gc_arena *gc)
+make_extended_arg_array(char **p, bool is_inline, struct gc_arena *gc)
 {
     const int argc = string_array_len((const char **)p);
-    if (!strcmp(p[0], INLINE_FILE_TAG) && argc == 2)
+    if (is_inline)
     {
-        return make_inline_array(p[1], gc);
+        return make_inline_array(p[0], gc);
     }
     else if (argc == 0)
     {
@@ -1579,31 +705,6 @@
     return ret;
 }
 
-/**
- * Will set or query for a global compat flag.  To modify the compat flags
- * the COMPAT_FLAG_SET must be bitwise ORed together with the flag to set.
- * If no "operator" flag is given it defaults to COMPAT_FLAG_QUERY,
- * which returns the flag state.
- *
- * @param  flag  Flag to be set/queried for bitwise ORed with the operator flag
- * @return Returns 0 if the flag is not set, otherwise the 'flag' value is returned
- */
-bool
-compat_flag(unsigned int flag)
-{
-    static unsigned int compat_flags = 0;
-
-    if (flag & COMPAT_FLAG_SET)
-    {
-        compat_flags |= (flag >> 1);
-    }
-
-    return (compat_flags & (flag >> 1));
-
-}
-
-#if P2MP_SERVER
-
 /* helper to parse peer_info received from multi client, validate
  * (this is untrusted data) and put into environment
  */
@@ -1667,4 +768,33 @@
     }
 }
 
-#endif /* P2MP_SERVER */
+int
+get_num_elements(const char *string, char delimiter)
+{
+    int string_len = strlen(string);
+
+    ASSERT(0 != string_len);
+
+    int element_count = 1;
+    /* Get number of ciphers */
+    for (int i = 0; i < string_len; i++)
+    {
+        if (string[i] == delimiter)
+        {
+            element_count++;
+        }
+    }
+
+    return element_count;
+}
+
+struct buffer
+prepend_dir(const char *dir, const char *path, struct gc_arena *gc)
+{
+    size_t len = strlen(dir) + strlen(PATH_SEPARATOR_STR) + strlen(path) + 1;
+    struct buffer combined_path = alloc_buf_gc(len, gc);
+    buf_printf(&combined_path, "%s%s%s", dir, PATH_SEPARATOR_STR, path);
+    ASSERT(combined_path.len > 0);
+
+    return combined_path;
+}
diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h
index 8a34f43..df08597 100644
--- a/src/openvpn/misc.h
+++ b/src/openvpn/misc.h
@@ -27,6 +27,7 @@
 #include "argv.h"
 #include "basic.h"
 #include "common.h"
+#include "env_set.h"
 #include "integer.h"
 #include "buffer.h"
 #include "platform.h"
@@ -37,49 +38,6 @@
 /* forward declarations */
 struct plugin_list;
 
-/*
- * Handle environmental variable lists
- */
-
-struct env_item {
-    char *string;
-    struct env_item *next;
-};
-
-struct env_set {
-    struct gc_arena *gc;
-    struct env_item *list;
-};
-
-/* system flags */
-#define S_SCRIPT (1<<0)
-#define S_FATAL  (1<<1)
-
-const char *system_error_message(int, struct gc_arena *gc);
-
-/* openvpn_execve return codes */
-#define OPENVPN_EXECVE_ERROR       -1 /* generic error while forking to run an external program */
-#define OPENVPN_EXECVE_NOT_ALLOWED -2 /* external program not run due to script security */
-#define OPENVPN_EXECVE_FAILURE    127 /* exit code passed back from child when execve fails */
-
-/* wrapper around the execve() call */
-int openvpn_popen(const struct argv *a,  const struct env_set *es);
-
-int openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags);
-
-bool openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message);
-
-bool openvpn_execve_allowed(const unsigned int flags);
-
-static inline bool
-openvpn_run_script(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *hook)
-{
-    char msg[256];
-
-    openvpn_snprintf(msg, sizeof(msg), "WARNING: Failed running command (%s)", hook);
-    return openvpn_execve_check(a, es, flags | S_SCRIPT, msg);
-}
-
 
 /* Set standard file descriptors to /dev/null */
 void set_std_files_to_null(bool stdin_only);
@@ -88,86 +46,14 @@
 extern int inetd_socket_descriptor;
 void save_inetd_socket_descriptor(void);
 
-/* set/delete environmental variable */
-void setenv_str_ex(struct env_set *es,
-                   const char *name,
-                   const char *value,
-                   const unsigned int name_include,
-                   const unsigned int name_exclude,
-                   const char name_replace,
-                   const unsigned int value_include,
-                   const unsigned int value_exclude,
-                   const char value_replace);
-
-void setenv_counter(struct env_set *es, const char *name, counter_type value);
-
-void setenv_int(struct env_set *es, const char *name, int value);
-
-void setenv_unsigned(struct env_set *es, const char *name, unsigned int value);
-
-void setenv_str(struct env_set *es, const char *name, const char *value);
-
-void setenv_str_safe(struct env_set *es, const char *name, const char *value);
-
-void setenv_del(struct env_set *es, const char *name);
-
-/**
- * Store the supplied name value pair in the env_set.  If the variable with the
- * supplied name  already exists, append _N to the name, starting at N=1.
- */
-void setenv_str_incr(struct env_set *es, const char *name, const char *value);
-
-void setenv_int_i(struct env_set *es, const char *name, const int value, const int i);
-
-void setenv_str_i(struct env_set *es, const char *name, const char *value, const int i);
-
-/* struct env_set functions */
-
-struct env_set *env_set_create(struct gc_arena *gc);
-
-void env_set_destroy(struct env_set *es);
-
-bool env_set_del(struct env_set *es, const char *str);
-
-void env_set_add(struct env_set *es, const char *str);
-
-const char *env_set_get(const struct env_set *es, const char *name);
-
-void env_set_print(int msglevel, const struct env_set *es);
-
-void env_set_inherit(struct env_set *es, const struct env_set *src);
-
 /* Make arrays of strings */
 
-const char **make_env_array(const struct env_set *es,
-                            const bool check_allowed,
-                            struct gc_arena *gc);
-
 const char **make_arg_array(const char *first, const char *parms, struct gc_arena *gc);
 
-const char **make_extended_arg_array(char **p, struct gc_arena *gc);
+const char **make_extended_arg_array(char **p, bool is_inline,
+                                     struct gc_arena *gc);
 
-/* an analogue to the random() function, but use OpenSSL functions if available */
-#ifdef ENABLE_CRYPTO
-long int get_random(void);
-
-#else
-#define get_random random
-#endif
-
-/* return true if filename can be opened for read */
-bool test_file(const char *filename);
-
-/* create a temporary file in directory, returns the filename of the created file */
-const char *create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc);
-
-/* put a directory and filename together */
-const char *gen_path(const char *directory, const char *filename, struct gc_arena *gc);
-
-/* return true if pathname is absolute */
-bool absolute_pathname(const char *pathname);
-
-/* prepend a random prefix to hostname (need ENABLE_CRYPTO) */
+/* prepend a random prefix to hostname */
 const char *hostname_randomize(const char *hostname, struct gc_arena *gc);
 
 /*
@@ -178,7 +64,6 @@
 {
     bool defined;
     bool nocache;
-    bool wait_for_push; /* true if this object is waiting for a push-reply */
 
 /* max length of username/password */
 #ifdef ENABLE_PKCS11
@@ -190,7 +75,7 @@
     char password[USER_PASS_LEN];
 };
 
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
 /*
  * Challenge response info on client as pushed by server.
  */
@@ -216,10 +101,10 @@
     const char *challenge_text;
 };
 
-#else  /* ifdef ENABLE_CLIENT_CR */
+#else  /* ifdef ENABLE_MANAGEMENT */
 struct auth_challenge_info {};
 struct static_challenge_info {};
-#endif /* ifdef ENABLE_CLIENT_CR */
+#endif /* ifdef ENABLE_MANAGEMENT */
 
 /*
  * Flags for get_user_pass and management_query_user_pass
@@ -259,6 +144,17 @@
 
 void purge_user_pass(struct user_pass *up, const bool force);
 
+/**
+ * Sets the auth-token to token if a username is available from either
+ * up or already present in tk. The method will also purge up if
+ * the auth-nocache option is active.
+ *
+ * @param up        (non Auth-token) Username/password
+ * @param tk        auth-token userpass to set
+ * @param token     token to use as password for the
+ *
+ * @note    all parameters to this function must not be null.
+ */
 void set_auth_token(struct user_pass *up, struct user_pass *tk,
                     const char *token);
 
@@ -269,21 +165,11 @@
  */
 const char *safe_print(const char *str, struct gc_arena *gc);
 
-/* returns true if environmental variable safe to print to log */
-bool env_safe_to_print(const char *str);
-
-/* returns true if environmental variable may be passed to an external program */
-bool env_allowed(const char *str);
 
 void configure_path(void);
 
 const char *sanitize_control_message(const char *str, struct gc_arena *gc);
 
-#if AUTO_USERID
-void get_user_pass_auto_userid(struct user_pass *up, const char *tag);
-
-#endif
-
 /*
  * /sbin/ip path, may be overridden
  */
@@ -291,27 +177,30 @@
 extern const char *iproute_path;
 #endif
 
-/* Script security */
-#define SSEC_NONE      0 /* strictly no calling of external programs */
-#define SSEC_BUILT_IN  1 /* only call built-in programs such as ifconfig, route, netsh, etc.*/
-#define SSEC_SCRIPTS   2 /* allow calling of built-in programs and user-defined scripts */
-#define SSEC_PW_ENV    3 /* allow calling of built-in programs and user-defined scripts that may receive a password as an environmental variable */
-extern int script_security; /* GLOBAL */
-
-
-#define COMPAT_FLAG_QUERY         0       /** compat_flags operator: Query for a flag */
-#define COMPAT_FLAG_SET           (1<<0)  /** compat_flags operator: Set a compat flag */
-#define COMPAT_NAMES              (1<<1)  /** compat flag: --compat-names set */
-#define COMPAT_NO_NAME_REMAPPING  (1<<2)  /** compat flag: --compat-names without char remapping */
-bool compat_flag(unsigned int flag);
-
-#if P2MP_SERVER
 /* helper to parse peer_info received from multi client, validate
  * (this is untrusted data) and put into environment */
 bool validate_peer_info_line(char *line);
 
 void output_peer_info_env(struct env_set *es, const char *peer_info);
 
-#endif /* P2MP_SERVER */
+/**
+ * Returns the occurrences of 'delimiter' in a string +1
+ * This is typically used to find out the number elements in a
+ * cipher string or similar that is separated by : like
+ *
+ *   X25519:secp256r1:X448:secp512r1:secp384r1:brainpoolP384r1
+ *
+ * @param string        the string to work on
+ * @param delimiter     the delimiter to count, typically ':'
+ * @return              occrrences of delimiter + 1
+ */
+int
+get_num_elements(const char *string, char delimiter);
+
+/**
+ * Prepend a directory to a path.
+ */
+struct buffer
+prepend_dir(const char *dir, const char *path, struct gc_arena *gc);
 
 #endif /* ifndef MISC_H */
diff --git a/src/openvpn/mroute.c b/src/openvpn/mroute.c
index db8c987..793c7e3 100644
--- a/src/openvpn/mroute.c
+++ b/src/openvpn/mroute.c
@@ -29,7 +29,6 @@
 
 #include "syshead.h"
 
-#if P2MP_SERVER
 
 #include "mroute.h"
 #include "proto.h"
@@ -58,7 +57,7 @@
 is_mac_mcast_maddr(const struct mroute_addr *addr)
 {
     return (addr->type & MR_ADDR_MASK) == MR_ADDR_ETHER
-           && is_mac_mcast_addr(addr->eth_addr);
+           && is_mac_mcast_addr(addr->ether.addr);
 }
 
 /*
@@ -247,11 +246,25 @@
     return ret;
 }
 
+static void
+mroute_copy_ether_to_addr(struct mroute_addr *maddr,
+                          const uint8_t *ether_addr,
+                          uint16_t vid)
+{
+    maddr->type = MR_ADDR_ETHER;
+    maddr->netbits = 0;
+    maddr->len = OPENVPN_ETH_ALEN;
+    memcpy(maddr->ether.addr, ether_addr, OPENVPN_ETH_ALEN);
+    maddr->len += sizeof(vid);
+    maddr->ether.vid = vid;
+}
+
 unsigned int
 mroute_extract_addr_ether(struct mroute_addr *src,
                           struct mroute_addr *dest,
                           struct mroute_addr *esrc,
                           struct mroute_addr *edest,
+                          uint16_t vid,
                           const struct buffer *buf)
 {
     unsigned int ret = 0;
@@ -260,17 +273,11 @@
         const struct openvpn_ethhdr *eth = (const struct openvpn_ethhdr *) BPTR(buf);
         if (src)
         {
-            src->type = MR_ADDR_ETHER;
-            src->netbits = 0;
-            src->len = 6;
-            memcpy(src->eth_addr, eth->source, sizeof(dest->eth_addr));
+            mroute_copy_ether_to_addr(src, eth->source, vid);
         }
         if (dest)
         {
-            dest->type = MR_ADDR_ETHER;
-            dest->netbits = 0;
-            dest->len = 6;
-            memcpy(dest->eth_addr, eth->dest, sizeof(dest->eth_addr));
+            mroute_copy_ether_to_addr(dest, eth->dest, vid);
 
             /* ethernet broadcast/multicast packet? */
             if (is_mac_mcast_addr(eth->dest))
@@ -285,21 +292,38 @@
         if (esrc || edest)
         {
             struct buffer b = *buf;
-            if (buf_advance(&b, sizeof(struct openvpn_ethhdr)))
+            if (!buf_advance(&b, sizeof(struct openvpn_ethhdr)))
             {
-                switch (ntohs(eth->proto))
-                {
-                    case OPENVPN_ETH_P_IPV4:
-                        ret |= (mroute_extract_addr_ip(esrc, edest, &b) << MROUTE_SEC_SHIFT);
-                        break;
+                return 0;
+            }
 
-                    case OPENVPN_ETH_P_ARP:
-                        ret |= (mroute_extract_addr_arp(esrc, edest, &b) << MROUTE_SEC_SHIFT);
-                        break;
+            uint16_t proto = eth->proto;
+            if (proto == htons(OPENVPN_ETH_P_8021Q))
+            {
+                if (!buf_advance(&b, SIZE_ETH_TO_8021Q_HDR))
+                {
+                    /* It's an 802.1Q packet, but doesn't have a full header,
+                     * so something went wrong */
+                    return 0;
                 }
+
+                const struct openvpn_8021qhdr *tag;
+                tag = (const struct openvpn_8021qhdr *)BPTR(buf);
+                proto = tag->proto;
+            }
+
+            switch (ntohs(proto))
+            {
+                case OPENVPN_ETH_P_IPV4:
+                    ret |= (mroute_extract_addr_ip(esrc, edest, &b) << MROUTE_SEC_SHIFT);
+                    break;
+
+                case OPENVPN_ETH_P_ARP:
+                    ret |= (mroute_extract_addr_arp(esrc, edest, &b) << MROUTE_SEC_SHIFT);
+                    break;
             }
         }
-#endif
+#endif /* ifdef ENABLE_PF */
     }
     return ret;
 }
@@ -440,8 +464,9 @@
         switch (maddr.type & MR_ADDR_MASK)
         {
             case MR_ADDR_ETHER:
-                buf_printf(&out, "%s", format_hex_ex(ma->eth_addr,
-                                                     sizeof(ma->eth_addr), 0, 1, ":", gc));
+                buf_printf(&out, "%s", format_hex_ex(ma->ether.addr,
+                                                     sizeof(ma->ether.addr), 0, 1, ":", gc));
+                buf_printf(&out, "@%hu", ma->ether.vid);
                 break;
 
             case MR_ADDR_IPV4:
@@ -588,10 +613,3 @@
 {
     free(mh);
 }
-
-#else  /* if P2MP_SERVER */
-static void
-dummy(void)
-{
-}
-#endif /* P2MP_SERVER */
diff --git a/src/openvpn/mroute.h b/src/openvpn/mroute.h
index 1063a18..c94b132 100644
--- a/src/openvpn/mroute.h
+++ b/src/openvpn/mroute.h
@@ -24,8 +24,6 @@
 #ifndef MROUTE_H
 #define MROUTE_H
 
-#if P2MP_SERVER
-
 #include "buffer.h"
 #include "list.h"
 #include "route.h"
@@ -82,7 +80,10 @@
                       * valid if MR_WITH_NETBITS is set */
     union {
         uint8_t raw_addr[MR_MAX_ADDR_LEN]; /* actual address */
-        uint8_t eth_addr[OPENVPN_ETH_ALEN];
+        struct {
+            uint8_t addr[OPENVPN_ETH_ALEN];
+            uint16_t vid;
+        } ether;
         struct {
             in_addr_t addr;     /* _network order_ IPv4 address */
             in_port_t port;     /* _network order_ TCP/UDP port */
@@ -100,7 +101,7 @@
 /* Wrappers to support compilers that do not grok anonymous unions */
         mroute_union
 #define raw_addr mroute_union.raw_addr
-#define eth_addr mroute_union.eth_addr
+#define ether mroute_union.ether
 #define v4 mroute_union.v4
 #define v6 mroute_union.v6
 #define v4mappedv6 mroute_union.v4mappedv6
@@ -170,6 +171,17 @@
 
 void mroute_helper_del_iroute46(struct mroute_helper *mh, int netbits);
 
+unsigned int mroute_extract_addr_ip(struct mroute_addr *src,
+                                    struct mroute_addr *dest,
+                                    const struct buffer *buf);
+
+unsigned int mroute_extract_addr_ether(struct mroute_addr *src,
+                                       struct mroute_addr *dest,
+                                       struct mroute_addr *esrc,
+                                       struct mroute_addr *edest,
+                                       uint16_t vid,
+                                       const struct buffer *buf);
+
 /*
  * Given a raw packet in buf, return the src and dest
  * addresses of the packet.
@@ -179,19 +191,10 @@
                                 struct mroute_addr *dest,
                                 struct mroute_addr *esrc,
                                 struct mroute_addr *edest,
+                                uint16_t vid,
                                 const struct buffer *buf,
                                 int tunnel_type)
 {
-    unsigned int mroute_extract_addr_ip(struct mroute_addr *src,
-                                     struct mroute_addr *dest,
-                                     const struct buffer *buf);
-
-    unsigned int mroute_extract_addr_ether(struct mroute_addr *src,
-                                           struct mroute_addr *dest,
-                                           struct mroute_addr *esrc,
-                                           struct mroute_addr *edest,
-                                           const struct buffer *buf);
-
     unsigned int ret = 0;
     verify_align_4(buf);
     if (tunnel_type == DEV_TYPE_TUN)
@@ -200,7 +203,7 @@
     }
     else if (tunnel_type == DEV_TYPE_TAP)
     {
-        ret = mroute_extract_addr_ether(src, dest, esrc, edest, buf);
+        ret = mroute_extract_addr_ether(src, dest, esrc, edest, vid, buf);
     }
     return ret;
 }
@@ -265,5 +268,4 @@
     ma->type = MR_ADDR_NONE;
 }
 
-#endif /* P2MP_SERVER */
 #endif /* MROUTE_H */
diff --git a/src/openvpn/mss.c b/src/openvpn/mss.c
index facdf7b..f15c656 100644
--- a/src/openvpn/mss.c
+++ b/src/openvpn/mss.c
@@ -110,7 +110,7 @@
      * before the final header (TCP, UDP, ...), so we'd need to walk that
      * chain (see RFC 2460 and RFC 6564 for details).
      *
-     * In practice, "most typically used" extention headers (AH, routing,
+     * In practice, "most typically used" extension headers (AH, routing,
      * fragment, mobility) are very unlikely to be seen inside an OpenVPN
      * tun, so for now, we only handle the case of "single next header = TCP"
      */
@@ -150,7 +150,7 @@
 
     if (BLEN(buf) < (int) sizeof(struct openvpn_tcphdr))
     {
-	return;
+        return;
     }
 
     verify_align_4(buf);
diff --git a/src/openvpn/mtcp.c b/src/openvpn/mtcp.c
index e8d2add..458e6e4 100644
--- a/src/openvpn/mtcp.c
+++ b/src/openvpn/mtcp.c
@@ -29,10 +29,8 @@
 
 #include "syshead.h"
 
-#if P2MP_SERVER
-
 #include "multi.h"
-#include "forward-inline.h"
+#include "forward.h"
 
 #include "memdbg.h"
 
@@ -269,8 +267,25 @@
                struct multi_tcp *mtcp)
 {
     int status;
+    unsigned int *persistent = &mtcp->tun_rwflags;
     socket_set_listen_persistent(c->c2.link_socket, mtcp->es, MTCP_SOCKET);
-    tun_set(c->c1.tuntap, mtcp->es, EVENT_READ, MTCP_TUN, &mtcp->tun_rwflags);
+
+#ifdef _WIN32
+    if (tuntap_is_wintun(c->c1.tuntap))
+    {
+        if (!tuntap_ring_empty(c->c1.tuntap))
+        {
+            /* there is data in wintun ring buffer, read it immediately */
+            mtcp->esr[0].arg = MTCP_TUN;
+            mtcp->esr[0].rwflags = EVENT_READ;
+            mtcp->n_esr = 1;
+            return 1;
+        }
+        persistent = NULL;
+    }
+#endif
+    tun_set(c->c1.tuntap, mtcp->es, EVENT_READ, MTCP_TUN, persistent);
+
 #ifdef ENABLE_MANAGEMENT
     if (management)
     {
@@ -844,5 +859,3 @@
     multi_top_free(&multi);
     close_instance(top);
 }
-
-#endif /* if P2MP_SERVER */
diff --git a/src/openvpn/mtcp.h b/src/openvpn/mtcp.h
index bba455b..680ab10 100644
--- a/src/openvpn/mtcp.h
+++ b/src/openvpn/mtcp.h
@@ -28,8 +28,6 @@
 #ifndef MTCP_H
 #define MTCP_H
 
-#if P2MP_SERVER
-
 #include "event.h"
 
 /*
@@ -75,5 +73,4 @@
 
 void multi_tcp_delete_event(struct multi_tcp *mtcp, event_t event);
 
-#endif /* if P2MP_SERVER */
 #endif /* ifndef MTCP_H */
diff --git a/src/openvpn/mtu.c b/src/openvpn/mtu.c
index 04868cd..3ddeac7 100644
--- a/src/openvpn/mtu.c
+++ b/src/openvpn/mtu.c
@@ -174,8 +174,8 @@
         {
 #if defined(HAVE_SETSOCKOPT) && defined(IP_MTU_DISCOVER)
             case AF_INET:
-                if (setsockopt
-                        (sd, IPPROTO_IP, IP_MTU_DISCOVER, &mtu_type, sizeof(mtu_type)))
+                if (setsockopt(sd, IPPROTO_IP, IP_MTU_DISCOVER,
+                               (void *) &mtu_type, sizeof(mtu_type)))
                 {
                     msg(M_ERR, "Error setting IP_MTU_DISCOVER type=%d on TCP/UDP socket",
                         mtu_type);
@@ -185,8 +185,8 @@
 #endif
 #if defined(HAVE_SETSOCKOPT) && defined(IPV6_MTU_DISCOVER)
             case AF_INET6:
-                if (setsockopt
-                        (sd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &mtu_type, sizeof(mtu_type)))
+                if (setsockopt(sd, IPPROTO_IPV6, IPV6_MTU_DISCOVER,
+                               (void *) &mtu_type, sizeof(mtu_type)))
                 {
                     msg(M_ERR, "Error setting IPV6_MTU_DISCOVER type=%d on TCP6/UDP6 socket",
                         mtu_type);
diff --git a/src/openvpn/mtu.h b/src/openvpn/mtu.h
index cfa8d2f..549c319 100644
--- a/src/openvpn/mtu.h
+++ b/src/openvpn/mtu.h
@@ -45,7 +45,7 @@
  *      ifconfig $1 10.1.0.2 pointopoint 10.1.0.1 mtu 1450
  *
  *    Compression overflow bytes is the worst-case size expansion that would be
- *    expected if we tried to compress mtu + extra_frame bytes of uncompressible data.
+ *    expected if we tried to compress mtu + extra_frame bytes of incompressible data.
  */
 
 /*
diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index 4f63654..e95a7ac 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -29,11 +29,9 @@
 
 #include "syshead.h"
 
-#if P2MP_SERVER
-
 #include "multi.h"
 #include <inttypes.h>
-#include "forward-inline.h"
+#include "forward.h"
 
 #include "memdbg.h"
 
@@ -278,7 +276,12 @@
     {
         flags |= IOW_READ;
     }
-
+#ifdef _WIN32
+    if (tuntap_ring_empty(m->top.c1.tuntap))
+    {
+        flags &= ~IOW_READ_TUN;
+    }
+#endif
     return flags;
 }
 
@@ -379,4 +382,3 @@
     tunnel_server_udp_single_threaded(top);
 }
 
-#endif /* if P2MP_SERVER */
diff --git a/src/openvpn/mudp.h b/src/openvpn/mudp.h
index 7e31151..460a768 100644
--- a/src/openvpn/mudp.h
+++ b/src/openvpn/mudp.h
@@ -28,8 +28,6 @@
 #ifndef MUDP_H
 #define MUDP_H
 
-#if P2MP_SERVER
-
 struct context;
 struct multi_context;
 
@@ -66,5 +64,4 @@
  */
 struct multi_instance *multi_get_create_instance_udp(struct multi_context *m, bool *floated);
 
-#endif
-#endif
+#endif /* ifndef MUDP_H */
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index c8c9a40..599ffd8 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -34,21 +34,23 @@
 
 #include "syshead.h"
 
-#if P2MP_SERVER
-
+#include "forward.h"
 #include "multi.h"
 #include "push.h"
-#include "misc.h"
+#include "run_command.h"
 #include "otime.h"
+#include "pf.h"
 #include "gremlin.h"
 #include "mstats.h"
 #include "ssl_verify.h"
+#include "ssl_ncp.h"
+#include "vlan.h"
 #include <inttypes.h>
 
 #include "memdbg.h"
 
-#include "forward-inline.h"
-#include "pf-inline.h"
+
+#include "crypto_backend.h"
 
 /*#define MULTI_DEBUG_EVENT_LOOP*/
 
@@ -136,7 +138,7 @@
             msg(M_WARN, "WARNING: learn-address plugin call failed");
             ret = false;
         }
-        argv_reset(&argv);
+        argv_free(&argv);
     }
 
     if (m->top.options.learn_address_script)
@@ -153,7 +155,7 @@
         {
             ret = false;
         }
-        argv_reset(&argv);
+        argv_free(&argv);
     }
 
     gc_free(&gc);
@@ -386,7 +388,8 @@
      * differently based on whether a tun or tap style
      * tunnel.
      */
-    if (t->options.ifconfig_pool_defined)
+    if (t->options.ifconfig_pool_defined
+        || t->options.ifconfig_ipv6_pool_defined)
     {
         int pool_type = IFCONFIG_POOL_INDIV;
 
@@ -395,7 +398,8 @@
             pool_type = IFCONFIG_POOL_30NET;
         }
 
-        m->ifconfig_pool = ifconfig_pool_init(pool_type,
+        m->ifconfig_pool = ifconfig_pool_init(t->options.ifconfig_pool_defined,
+                                              pool_type,
                                               t->options.ifconfig_pool_start,
                                               t->options.ifconfig_pool_end,
                                               t->options.duplicate_cn,
@@ -564,44 +568,36 @@
     setenv_stats(&mi->context);
 
     /* setenv connection duration */
-    {
-        const unsigned int duration = (unsigned int) now - mi->created;
-        setenv_unsigned(mi->context.c2.es, "time_duration", duration);
-    }
+    setenv_long_long(mi->context.c2.es, "time_duration", now - mi->created);
 }
 
 static void
 multi_client_disconnect_script(struct multi_instance *mi)
 {
-    if ((mi->context.c2.context_auth == CAS_SUCCEEDED && mi->connection_established_flag)
-        || mi->context.c2.context_auth == CAS_PARTIAL)
+    multi_client_disconnect_setenv(mi);
+
+    if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT))
     {
-        multi_client_disconnect_setenv(mi);
-
-        if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT))
+        if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT, NULL, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
         {
-            if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_DISCONNECT, NULL, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
-            {
-                msg(M_WARN, "WARNING: client-disconnect plugin call failed");
-            }
+            msg(M_WARN, "WARNING: client-disconnect plugin call failed");
         }
-
-        if (mi->context.options.client_disconnect_script)
-        {
-            struct argv argv = argv_new();
-            setenv_str(mi->context.c2.es, "script_type", "client-disconnect");
-            argv_parse_cmd(&argv, mi->context.options.client_disconnect_script);
-            openvpn_run_script(&argv, mi->context.c2.es, 0, "--client-disconnect");
-            argv_reset(&argv);
-        }
-#ifdef MANAGEMENT_DEF_AUTH
-        if (management)
-        {
-            management_notify_client_close(management, &mi->context.c2.mda_context, mi->context.c2.es);
-        }
-#endif
-
     }
+
+    if (mi->context.options.client_disconnect_script)
+    {
+        struct argv argv = argv_new();
+        setenv_str(mi->context.c2.es, "script_type", "client-disconnect");
+        argv_parse_cmd(&argv, mi->context.options.client_disconnect_script);
+        openvpn_run_script(&argv, mi->context.c2.es, 0, "--client-disconnect");
+        argv_free(&argv);
+    }
+#ifdef MANAGEMENT_DEF_AUTH
+    if (management)
+    {
+        management_notify_client_close(management, &mi->context.c2.mda_context, mi->context.c2.es);
+    }
+#endif
 }
 
 void
@@ -682,14 +678,13 @@
 #ifdef MANAGEMENT_DEF_AUTH
     set_cc_config(mi, NULL);
 #endif
-
-    multi_client_disconnect_script(mi);
-
-    if (mi->did_open_context)
+    if (mi->context.c2.tls_multi->multi_state == CAS_SUCCEEDED)
     {
-        close_context(&mi->context, SIGTERM, CC_GC_FREE);
+        multi_client_disconnect_script(mi);
     }
 
+    close_context(&mi->context, SIGTERM, CC_GC_FREE);
+
     multi_tcp_instance_specific_free(mi);
 
     ungenerate_prefix(mi);
@@ -787,14 +782,13 @@
         generate_prefix(mi);
     }
 
-    mi->did_open_context = true;
     inherit_context_child(&mi->context, &m->top);
     if (IS_SIG(&mi->context))
     {
         goto err;
     }
 
-    mi->context.c2.context_auth = CAS_PENDING;
+    mi->context.c2.tls_multi->multi_state = CAS_PENDING;
 
     if (hash_n_elements(m->hash) >= m->max_clients)
     {
@@ -827,10 +821,8 @@
     mi->did_cid_hash = true;
 #endif
 
-    mi->context.c2.push_reply_deferred = true;
-
-#ifdef ENABLE_ASYNC_PUSH
     mi->context.c2.push_request_received = false;
+#ifdef ENABLE_ASYNC_PUSH
     mi->inotify_watch = -1;
 #endif
 
@@ -941,8 +933,8 @@
              */
             status_printf(so, "TITLE%c%s", sep, title_string);
             status_printf(so, "TIME%c%s%c%u", sep, time_string(now, 0, false, &gc_top), sep, (unsigned int)now);
-            status_printf(so, "HEADER%cCLIENT_LIST%cCommon Name%cReal Address%cVirtual Address%cVirtual IPv6 Address%cBytes Received%cBytes Sent%cConnected Since%cConnected Since (time_t)%cUsername%cClient ID%cPeer ID",
-                          sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep);
+            status_printf(so, "HEADER%cCLIENT_LIST%cCommon Name%cReal Address%cVirtual Address%cVirtual IPv6 Address%cBytes Received%cBytes Sent%cConnected Since%cConnected Since (time_t)%cUsername%cClient ID%cPeer ID%cData Channel Cipher",
+                          sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep, sep);
             hash_iterator_init(m->hash, &hi);
             while ((he = hash_iterator_next(&hi)))
             {
@@ -957,7 +949,7 @@
 #else
                                   ""
 #endif
-                                  "%c%" PRIu32,
+                                  "%c%" PRIu32 "%c%s",
                                   sep, tls_common_name(mi->context.c2.tls_multi, false),
                                   sep, mroute_addr_print(&mi->real, &gc),
                                   sep, print_in_addr_t(mi->reporting_addr, IA_EMPTY_IF_UNDEF, &gc),
@@ -972,7 +964,8 @@
 #else
                                   sep,
 #endif
-                                  sep, mi->context.c2.tls_multi ? mi->context.c2.tls_multi->peer_id : UINT32_MAX);
+                                  sep, mi->context.c2.tls_multi ? mi->context.c2.tls_multi->peer_id : UINT32_MAX,
+                                  sep, translate_cipher_name_to_openvpn(mi->context.options.ciphername));
                 }
                 gc_free(&gc);
             }
@@ -1495,41 +1488,47 @@
             const int tunnel_topology = TUNNEL_TOPOLOGY(mi->context.c1.tuntap);
 
             msg( M_INFO, "MULTI_sva: pool returned IPv4=%s, IPv6=%s",
-                 print_in_addr_t( remote, 0, &gc ),
+                 (mi->context.options.ifconfig_pool_defined
+                  ? print_in_addr_t(remote, 0, &gc)
+                  : "(Not enabled)"),
                  (mi->context.options.ifconfig_ipv6_pool_defined
                   ? print_in6_addr( remote_ipv6, 0, &gc )
                   : "(Not enabled)") );
 
-            /* set push_ifconfig_remote_netmask from pool ifconfig address(es) */
-            mi->context.c2.push_ifconfig_local = remote;
-            if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET))
+            if (mi->context.options.ifconfig_pool_defined)
             {
-                mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.ifconfig_pool_netmask;
-                if (!mi->context.c2.push_ifconfig_remote_netmask)
+                /* set push_ifconfig_remote_netmask from pool ifconfig address(es) */
+                mi->context.c2.push_ifconfig_local = remote;
+                if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET))
                 {
-                    mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->remote_netmask;
+                    mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.ifconfig_pool_netmask;
+                    if (!mi->context.c2.push_ifconfig_remote_netmask)
+                    {
+                        mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->remote_netmask;
+                    }
                 }
-            }
-            else if (tunnel_type == DEV_TYPE_TUN)
-            {
-                if (tunnel_topology == TOP_P2P)
+                else if (tunnel_type == DEV_TYPE_TUN)
                 {
-                    mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->local;
+                    if (tunnel_topology == TOP_P2P)
+                    {
+                        mi->context.c2.push_ifconfig_remote_netmask = mi->context.c1.tuntap->local;
+                    }
+                    else if (tunnel_topology == TOP_NET30)
+                    {
+                        mi->context.c2.push_ifconfig_remote_netmask = local;
+                    }
                 }
-                else if (tunnel_topology == TOP_NET30)
-                {
-                    mi->context.c2.push_ifconfig_remote_netmask = local;
-                }
-            }
 
-            if (mi->context.c2.push_ifconfig_remote_netmask)
-            {
-                mi->context.c2.push_ifconfig_defined = true;
-            }
-            else
-            {
-                msg(D_MULTI_ERRORS, "MULTI: no --ifconfig-pool netmask parameter is available to push to %s",
-                    multi_instance_string(mi, false, &gc));
+                if (mi->context.c2.push_ifconfig_remote_netmask)
+                {
+                    mi->context.c2.push_ifconfig_defined = true;
+                }
+                else
+                {
+                    msg(D_MULTI_ERRORS,
+                        "MULTI: no --ifconfig-pool netmask parameter is available to push to %s",
+                        multi_instance_string(mi, false, &gc));
+                }
             }
 
             if (mi->context.options.ifconfig_ipv6_pool_defined)
@@ -1636,16 +1635,15 @@
 multi_client_connect_post(struct multi_context *m,
                           struct multi_instance *mi,
                           const char *dc_file,
-                          unsigned int option_permissions_mask,
                           unsigned int *option_types_found)
 {
     /* Did script generate a dynamic config file? */
-    if (test_file(dc_file))
+    if (platform_test_file(dc_file))
     {
         options_server_import(&mi->context.options,
                               dc_file,
                               D_IMPORT_ERRORS|M_OPTERR,
-                              option_permissions_mask,
+                              CLIENT_CONNECT_OPT_MASK,
                               option_types_found,
                               mi->context.c2.es);
 
@@ -1669,7 +1667,6 @@
 multi_client_connect_post_plugin(struct multi_context *m,
                                  struct multi_instance *mi,
                                  const struct plugin_return *pr,
-                                 unsigned int option_permissions_mask,
                                  unsigned int *option_types_found)
 {
     struct plugin_return config;
@@ -1687,7 +1684,7 @@
                 options_string_import(&mi->context.options,
                                       config.list[i]->value,
                                       D_IMPORT_ERRORS|M_OPTERR,
-                                      option_permissions_mask,
+                                      CLIENT_CONNECT_OPT_MASK,
                                       option_types_found,
                                       mi->context.c2.es);
             }
@@ -1706,29 +1703,30 @@
 
 #endif /* ifdef ENABLE_PLUGIN */
 
-#ifdef MANAGEMENT_DEF_AUTH
 
 /*
  * Called to load management-derived client-connect config
  */
-static void
+enum client_connect_return
 multi_client_connect_mda(struct multi_context *m,
                          struct multi_instance *mi,
-                         const struct buffer_list *config,
-                         unsigned int option_permissions_mask,
+                         bool deferred,
                          unsigned int *option_types_found)
 {
-    if (config)
+    /* We never return CC_RET_DEFERRED */
+    ASSERT(!deferred);
+    enum client_connect_return ret = CC_RET_SKIPPED;
+#ifdef MANAGEMENT_DEF_AUTH
+    if (mi->cc_config)
     {
         struct buffer_entry *be;
-
-        for (be = config->head; be != NULL; be = be->next)
+        for (be = mi->cc_config->head; be != NULL; be = be->next)
         {
             const char *opt = BSTR(&be->buf);
             options_string_import(&mi->context.options,
                                   opt,
                                   D_IMPORT_ERRORS|M_OPTERR,
-                                  option_permissions_mask,
+                                  CLIENT_CONNECT_OPT_MASK,
                                   option_types_found,
                                   mi->context.c2.es);
         }
@@ -1741,10 +1739,12 @@
          */
         multi_select_virtual_addr(m, mi);
         multi_set_virtual_addr_env(mi);
-    }
-}
 
+        ret = CC_RET_SUCCEEDED;
+    }
 #endif /* ifdef MANAGEMENT_DEF_AUTH */
+    return ret;
+}
 
 static void
 multi_client_connect_setenv(struct multi_context *m,
@@ -1765,350 +1765,951 @@
     {
         const char *created_ascii = time_string(mi->created, 0, false, &gc);
         setenv_str(mi->context.c2.es, "time_ascii", created_ascii);
-        setenv_unsigned(mi->context.c2.es, "time_unix", (unsigned int)mi->created);
+        setenv_long_long(mi->context.c2.es, "time_unix", mi->created);
     }
 
     gc_free(&gc);
 }
 
-/*
- * Called as soon as the SSL/TLS connection authenticates.
+/**
+ * Extracts the IV_PROTO variable and returns its value or 0
+ * if it cannot be extracted.
  *
- * Instance-specific directives to be processed:
+ */
+static unsigned int
+extract_iv_proto(const char *peer_info)
+{
+
+    const char *optstr = peer_info ? strstr(peer_info, "IV_PROTO=") : NULL;
+    if (optstr)
+    {
+        int proto = 0;
+        int r = sscanf(optstr, "IV_PROTO=%d", &proto);
+        if (r == 1 && proto > 0)
+        {
+            return proto;
+        }
+    }
+    return 0;
+}
+
+/**
+ * Calculates the options that depend on the client capabilities
+ * based on local options and available peer info
+ * - choosen cipher
+ * - peer id
+ */
+static bool
+multi_client_set_protocol_options(struct context *c)
+{
+    struct tls_multi *tls_multi = c->c2.tls_multi;
+    const char *const peer_info = tls_multi->peer_info;
+    struct options *o = &c->options;
+
+
+    unsigned int proto = extract_iv_proto(peer_info);
+    if (proto & IV_PROTO_DATA_V2)
+    {
+        tls_multi->use_peer_id = true;
+    }
+    if (proto & IV_PROTO_REQUEST_PUSH)
+    {
+        c->c2.push_request_received = true;
+    }
+
+    /* Select cipher if client supports Negotiable Crypto Parameters */
+    if (!o->ncp_enabled)
+    {
+        return true;
+    }
+
+    /* if we have already created our key, we cannot *change* our own
+     * cipher -> so log the fact and push the "what we have now" cipher
+     * (so the client is always told what we expect it to use)
+     */
+    const struct tls_session *session = &tls_multi->session[TM_ACTIVE];
+    if (session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized)
+    {
+        msg(M_INFO, "PUSH: client wants to negotiate cipher (NCP), but "
+            "server has already generated data channel keys, "
+            "re-sending previously negotiated cipher '%s'",
+            o->ciphername );
+        return true;
+    }
+
+    /*
+     * Push the first cipher from --data-ciphers to the client that
+     * the client announces to be supporting.
+     */
+    char *push_cipher = ncp_get_best_cipher(o->ncp_ciphers, peer_info,
+                                            tls_multi->remote_ciphername,
+                                            &o->gc);
+
+    if (push_cipher)
+    {
+        o->ciphername = push_cipher;
+        return true;
+    }
+
+    /* NCP cipher negotiation failed. Try to figure out why exactly it
+     * failed and give good error messages and potentially do a fallback
+     * for non NCP clients */
+    struct gc_arena gc = gc_new();
+    bool ret = false;
+
+    const char *peer_ciphers = tls_peer_ncp_list(peer_info, &gc);
+    /* If we are in a situation where we know the client ciphers, there is no
+     * reason to fall back to a cipher that will not be accepted by the other
+     * side, in this situation we fail the auth*/
+    if (strlen(peer_ciphers) > 0)
+    {
+        msg(M_INFO, "PUSH: No common cipher between server and client. "
+            "Server data-ciphers: '%s', client supported ciphers '%s'",
+            o->ncp_ciphers, peer_ciphers);
+    }
+    else if (tls_multi->remote_ciphername)
+    {
+        msg(M_INFO, "PUSH: No common cipher between server and client. "
+            "Server data-ciphers: '%s', client supports cipher '%s'",
+            o->ncp_ciphers, tls_multi->remote_ciphername);
+    }
+    else
+    {
+        msg(M_INFO, "PUSH: No NCP or OCC cipher data received from peer.");
+
+        if (o->enable_ncp_fallback && !tls_multi->remote_ciphername)
+        {
+            msg(M_INFO, "Using data channel cipher '%s' since "
+                "--data-ciphers-fallback is set.", o->ciphername);
+            ret = true;
+        }
+        else
+        {
+            msg(M_INFO, "Use --data-ciphers-fallback with the cipher the "
+                "client is using if you want to allow the client to connect");
+        }
+    }
+    if (!ret)
+    {
+        auth_set_client_reason(tls_multi, "Data channel cipher negotiation "
+                                          "failed (no shared cipher)");
+    }
+
+    gc_free(&gc);
+    return ret;
+}
+
+/**
+ * Delete the temporary file for the return value of client connect
+ * It also removes it from client_connect_defer_state and environment
+ */
+static void
+ccs_delete_deferred_ret_file(struct multi_instance *mi)
+{
+    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+    if (!ccs->deferred_ret_file)
+    {
+        return;
+    }
+
+    setenv_del(mi->context.c2.es, "client_connect_deferred_file");
+    if (!platform_unlink(ccs->deferred_ret_file))
+    {
+        msg(D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s",
+            ccs->deferred_ret_file);
+    }
+    free(ccs->deferred_ret_file);
+    ccs->deferred_ret_file = NULL;
+}
+
+/**
+ * Create a temporary file for the return value of client connect
+ * and puts it into the client_connect_defer_state and environment
+ * as "client_connect_deferred_file"
+ *
+ * @return boolean value if creation was successful
+ */
+static bool
+ccs_gen_deferred_ret_file(struct multi_instance *mi)
+{
+    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+    struct gc_arena gc = gc_new();
+    const char *fn;
+
+    /* Delete file if it already exists */
+    ccs_delete_deferred_ret_file(mi);
+
+    fn = platform_create_temp_file(mi->context.options.tmp_dir, "ccr", &gc);
+    if (!fn)
+    {
+        gc_free(&gc);
+        return false;
+    }
+    ccs->deferred_ret_file = string_alloc(fn, NULL);
+
+    setenv_str(mi->context.c2.es, "client_connect_deferred_file",
+               ccs->deferred_ret_file);
+
+    gc_free(&gc);
+    return true;
+}
+
+/**
+ * Tests whether the deferred return value file exists and returns the
+ * contained return value.
+ *
+ * @return CC_RET_SKIPPED if the file does not exist or is empty.
+ *         CC_RET_DEFERRED, CC_RET_SUCCEEDED or CC_RET_FAILED depending on
+ *         the value stored in the file.
+ */
+static enum client_connect_return
+ccs_test_deferred_ret_file(struct multi_instance *mi)
+{
+    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+    FILE *fp = fopen(ccs->deferred_ret_file, "r");
+    if (!fp)
+    {
+        return CC_RET_SKIPPED;
+    }
+
+    enum client_connect_return ret = CC_RET_SKIPPED;
+    const int c = fgetc(fp);
+    switch (c)
+    {
+        case '0':
+            ret = CC_RET_FAILED;
+            break;
+
+        case '1':
+            ret = CC_RET_SUCCEEDED;
+            break;
+
+        case '2':
+            ret = CC_RET_DEFERRED;
+            break;
+
+        case EOF:
+            if (feof(fp))
+            {
+                ret = CC_RET_SKIPPED;
+                break;
+            }
+
+        /* Not EOF but other error -> fall through to error state */
+        default:
+            /* We received an unknown/unexpected value.  Assume failure. */
+            msg(M_WARN, "WARNING: Unknown/unexpected value in deferred"
+                "client-connect resultfile");
+            ret = CC_RET_FAILED;
+    }
+    fclose(fp);
+
+    return ret;
+}
+
+/**
+ * Deletes the temporary file for the config directives of the  client connect
+ * script and removes it into the client_connect_defer_state and environment
+ *
+ */
+static void
+ccs_delete_config_file(struct multi_instance *mi)
+{
+    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+    if (ccs->config_file)
+    {
+        setenv_del(mi->context.c2.es, "client_connect_config_file");
+        if (!platform_unlink(ccs->config_file))
+        {
+            msg(D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s",
+                ccs->config_file);
+        }
+        free(ccs->config_file);
+        ccs->config_file = NULL;
+    }
+}
+
+/**
+ * Create a temporary file for the config directives of the  client connect
+ * script and puts it into the client_connect_defer_state and environment
+ * as "client_connect_config_file"
+ *
+ * @return boolean value if creation was successful
+ */
+static bool
+ccs_gen_config_file(struct multi_instance *mi)
+{
+    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+    struct gc_arena gc = gc_new();
+    const char *fn;
+
+    if (ccs->config_file)
+    {
+        ccs_delete_config_file(mi);
+    }
+
+    fn = platform_create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
+    if (!fn)
+    {
+        gc_free(&gc);
+        return false;
+    }
+    ccs->config_file = string_alloc(fn, NULL);
+
+    setenv_str(mi->context.c2.es, "client_connect_config_file",
+               ccs->config_file);
+
+    gc_free(&gc);
+    return true;
+}
+
+static enum client_connect_return
+multi_client_connect_call_plugin_v1(struct multi_context *m,
+                                    struct multi_instance *mi,
+                                    bool deferred,
+                                    unsigned int *option_types_found)
+{
+    enum client_connect_return ret = CC_RET_SKIPPED;
+#ifdef ENABLE_PLUGIN
+    ASSERT(m);
+    ASSERT(mi);
+    ASSERT(option_types_found);
+    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+
+    /* deprecated callback, use a file for passing back return info */
+    if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))
+    {
+        struct argv argv = argv_new();
+        int call;
+
+        if (!deferred)
+        {
+            call = OPENVPN_PLUGIN_CLIENT_CONNECT;
+            if (!ccs_gen_config_file(mi)
+                || !ccs_gen_deferred_ret_file(mi))
+            {
+                ret = CC_RET_FAILED;
+                goto cleanup;
+            }
+        }
+        else
+        {
+            call = OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER;
+            /* the initial call should have created these files */
+            ASSERT(ccs->config_file);
+            ASSERT(ccs->deferred_ret_file);
+        }
+
+        argv_printf(&argv, "%s", ccs->config_file);
+        int plug_ret = plugin_call(mi->context.plugins, call,
+                                   &argv, NULL, mi->context.c2.es);
+        if (plug_ret == OPENVPN_PLUGIN_FUNC_SUCCESS)
+        {
+            ret = CC_RET_SUCCEEDED;
+        }
+        else if (plug_ret == OPENVPN_PLUGIN_FUNC_DEFERRED)
+        {
+            ret = CC_RET_DEFERRED;
+            /**
+             * Contrary to the plugin v2 API, we do not demand a working
+             * deferred plugin as all return can be handled by the files
+             * and plugin_call return success if a plugin is not defined
+             */
+        }
+        else
+        {
+            msg(M_WARN, "WARNING: client-connect plugin call failed");
+            ret = CC_RET_FAILED;
+        }
+
+
+        /**
+         * plugin api v1 client connect async feature has both plugin and
+         * file return status, so in cases where the file has a code that
+         * demands override, we override our return code
+         */
+        int file_ret = ccs_test_deferred_ret_file(mi);
+
+        if (file_ret == CC_RET_FAILED)
+        {
+            ret = CC_RET_FAILED;
+        }
+        else if (ret == CC_RET_SUCCEEDED && file_ret == CC_RET_DEFERRED)
+        {
+            ret = CC_RET_DEFERRED;
+        }
+
+        /* if we still think we have succeeded, do postprocessing */
+        if (ret == CC_RET_SUCCEEDED)
+        {
+            multi_client_connect_post(m, mi, ccs->config_file,
+                                      option_types_found);
+        }
+cleanup:
+        argv_free(&argv);
+
+        if (ret != CC_RET_DEFERRED)
+        {
+            ccs_delete_config_file(mi);
+            ccs_delete_deferred_ret_file(mi);
+        }
+    }
+#endif /* ifdef ENABLE_PLUGIN */
+    return ret;
+}
+
+static enum client_connect_return
+multi_client_connect_call_plugin_v2(struct multi_context *m,
+                                    struct multi_instance *mi,
+                                    bool deferred,
+                                    unsigned int *option_types_found)
+{
+    enum client_connect_return ret = CC_RET_SKIPPED;
+#ifdef ENABLE_PLUGIN
+    ASSERT(m);
+    ASSERT(mi);
+    ASSERT(option_types_found);
+
+    int call = deferred ? OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2 :
+               OPENVPN_PLUGIN_CLIENT_CONNECT_V2;
+    /* V2 callback, use a plugin_return struct for passing back return info */
+    if (plugin_defined(mi->context.plugins, call))
+    {
+        struct plugin_return pr;
+
+        plugin_return_init(&pr);
+
+        int plug_ret = plugin_call(mi->context.plugins, call,
+                                   NULL, &pr, mi->context.c2.es);
+        if (plug_ret == OPENVPN_PLUGIN_FUNC_SUCCESS)
+        {
+            multi_client_connect_post_plugin(m, mi, &pr, option_types_found);
+            ret = CC_RET_SUCCEEDED;
+        }
+        else if (plug_ret == OPENVPN_PLUGIN_FUNC_DEFERRED)
+        {
+            ret = CC_RET_DEFERRED;
+            if (!(plugin_defined(mi->context.plugins,
+                                 OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2)))
+            {
+                msg(M_WARN, "A plugin that defers from the "
+                    "OPENVPN_PLUGIN_CLIENT_CONNECT_V2 call must also "
+                    "declare support for "
+                    "OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2");
+                ret = CC_RET_FAILED;
+            }
+        }
+        else
+        {
+            msg(M_WARN, "WARNING: client-connect-v2 plugin call failed");
+            ret = CC_RET_FAILED;
+        }
+
+
+        plugin_return_free(&pr);
+    }
+#endif /* ifdef ENABLE_PLUGIN */
+    return ret;
+}
+
+static enum client_connect_return
+multi_client_connect_script_deferred(struct multi_context *m,
+                                     struct multi_instance *mi,
+                                     unsigned int *option_types_found)
+{
+    ASSERT(mi);
+    ASSERT(option_types_found);
+    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+    enum client_connect_return ret = CC_RET_SKIPPED;
+
+    ret = ccs_test_deferred_ret_file(mi);
+
+    if (ret == CC_RET_SKIPPED)
+    {
+        /*
+         * Skipped and deferred are equivalent in this context.
+         * skipped means that the called program has not yet
+         * written a return status implicitly needing more time
+         * while deferred is the explicit notification that it
+         * needs more time
+         */
+        ret = CC_RET_DEFERRED;
+    }
+
+    if (ret == CC_RET_SUCCEEDED)
+    {
+        ccs_delete_deferred_ret_file(mi);
+        multi_client_connect_post(m, mi, ccs->config_file,
+                                  option_types_found);
+        ccs_delete_config_file(mi);
+    }
+    if (ret == CC_RET_FAILED)
+    {
+        msg(M_INFO, "MULTI: deferred --client-connect script returned CC_RET_FAILED");
+        ccs_delete_deferred_ret_file(mi);
+        ccs_delete_config_file(mi);
+    }
+    return ret;
+}
+
+/**
+ * Runs the --client-connect script if one is defined.
+ */
+static enum client_connect_return
+multi_client_connect_call_script(struct multi_context *m,
+                                 struct multi_instance *mi,
+                                 bool deferred,
+                                 unsigned int *option_types_found)
+{
+    if (deferred)
+    {
+        return multi_client_connect_script_deferred(m, mi, option_types_found);
+    }
+    ASSERT(m);
+    ASSERT(mi);
+
+    enum client_connect_return ret = CC_RET_SKIPPED;
+    struct client_connect_defer_state *ccs = &(mi->client_connect_defer_state);
+
+    if (mi->context.options.client_connect_script)
+    {
+        struct argv argv = argv_new();
+        struct gc_arena gc = gc_new();
+
+        setenv_str(mi->context.c2.es, "script_type", "client-connect");
+
+        if (!ccs_gen_config_file(mi)
+            || !ccs_gen_deferred_ret_file(mi))
+        {
+            ret = CC_RET_FAILED;
+            goto cleanup;
+        }
+
+        argv_parse_cmd(&argv, mi->context.options.client_connect_script);
+        argv_printf_cat(&argv, "%s", ccs->config_file);
+
+        if (openvpn_run_script(&argv, mi->context.c2.es, 0, "--client-connect"))
+        {
+            if (ccs_test_deferred_ret_file(mi) == CC_RET_DEFERRED)
+            {
+                ret = CC_RET_DEFERRED;
+            }
+            else
+            {
+                multi_client_connect_post(m, mi, ccs->config_file,
+                                          option_types_found);
+                ret = CC_RET_SUCCEEDED;
+            }
+        }
+        else
+        {
+            ret = CC_RET_FAILED;
+        }
+cleanup:
+        if (ret != CC_RET_DEFERRED)
+        {
+            ccs_delete_config_file(mi);
+            ccs_delete_deferred_ret_file(mi);
+        }
+        argv_free(&argv);
+        gc_free(&gc);
+    }
+    return ret;
+}
+
+/**
+ * Generates the data channel keys
+ */
+static bool
+multi_client_generate_tls_keys(struct context *c)
+{
+    struct frame *frame_fragment = NULL;
+#ifdef ENABLE_FRAGMENT
+    if (c->options.ce.fragment)
+    {
+        frame_fragment = &c->c2.frame_fragment;
+    }
+#endif
+    struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
+    if (!tls_session_update_crypto_params(session, &c->options,
+                                          &c->c2.frame, frame_fragment))
+    {
+        msg(D_TLS_ERRORS, "TLS Error: initializing data channel failed");
+        register_signal(c, SIGUSR1, "process-push-msg-failed");
+        return false;
+    }
+
+    return true;
+}
+
+static void
+multi_client_connect_late_setup(struct multi_context *m,
+                                struct multi_instance *mi,
+                                const unsigned int option_types_found)
+{
+    ASSERT(m);
+    ASSERT(mi);
+
+    struct gc_arena gc = gc_new();
+    /*
+     * Process sourced options.
+     */
+    do_deferred_options(&mi->context, option_types_found);
+
+    /*
+     * make sure we got ifconfig settings from somewhere
+     */
+    if (!mi->context.c2.push_ifconfig_defined)
+    {
+        msg(D_MULTI_ERRORS, "MULTI: no dynamic or static remote"
+            "--ifconfig address is available for %s",
+            multi_instance_string(mi, false, &gc));
+    }
+
+    /*
+     * make sure that ifconfig settings comply with constraints
+     */
+    if (!ifconfig_push_constraint_satisfied(&mi->context))
+    {
+        const char *ifconfig_constraint_network =
+            print_in_addr_t(mi->context.options.push_ifconfig_constraint_network, 0, &gc);
+        const char *ifconfig_constraint_netmask =
+            print_in_addr_t(mi->context.options.push_ifconfig_constraint_netmask, 0, &gc);
+
+        /* JYFIXME -- this should cause the connection to fail */
+        msg(D_MULTI_ERRORS, "MULTI ERROR: primary virtual IP for %s (%s)"
+            "violates tunnel network/netmask constraint (%s/%s)",
+            multi_instance_string(mi, false, &gc),
+            print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc),
+            ifconfig_constraint_network, ifconfig_constraint_netmask);
+    }
+
+    /*
+     * For routed tunnels, set up internal route to endpoint
+     * plus add all iroute routes.
+     */
+    if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
+    {
+        if (mi->context.c2.push_ifconfig_defined)
+        {
+            multi_learn_in_addr_t(m, mi,
+                                  mi->context.c2.push_ifconfig_local,
+                                  -1, true);
+            msg(D_MULTI_LOW, "MULTI: primary virtual IP for %s: %s",
+                multi_instance_string(mi, false, &gc),
+                print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc));
+        }
+
+        if (mi->context.c2.push_ifconfig_ipv6_defined)
+        {
+            multi_learn_in6_addr(m, mi,
+                                 mi->context.c2.push_ifconfig_ipv6_local,
+                                 -1, true);
+            /* TODO: find out where addresses are "unlearned"!! */
+            const char *ifconfig_local_ipv6 =
+                print_in6_addr(mi->context.c2.push_ifconfig_ipv6_local, 0, &gc);
+            msg(D_MULTI_LOW, "MULTI: primary virtual IPv6 for %s: %s",
+                multi_instance_string(mi, false, &gc),
+                ifconfig_local_ipv6);
+        }
+
+        /* add routes locally, pointing to new client, if
+         * --iroute options have been specified */
+        multi_add_iroutes(m, mi);
+
+        /*
+         * iroutes represent subnets which are "owned" by a particular
+         * client.  Therefore, do not actually push a route to a client
+         * if it matches one of the client's iroutes.
+         */
+        remove_iroutes_from_push_route_list(&mi->context.options);
+    }
+    else if (mi->context.options.iroutes)
+    {
+        msg(D_MULTI_ERRORS, "MULTI: --iroute options rejected for %s -- iroute"
+            "only works with tun-style tunnels",
+            multi_instance_string(mi, false, &gc));
+    }
+
+    /* set our client's VPN endpoint for status reporting purposes */
+    mi->reporting_addr = mi->context.c2.push_ifconfig_local;
+    mi->reporting_addr_ipv6 = mi->context.c2.push_ifconfig_ipv6_local;
+
+    /* set context-level authentication flag */
+    mi->context.c2.tls_multi->multi_state = CAS_SUCCEEDED;
+
+    /* authentication complete, calculate dynamic client specific options */
+    if (!multi_client_set_protocol_options(&mi->context))
+    {
+        mi->context.c2.tls_multi->multi_state = CAS_FAILED;
+    }
+    /* Generate data channel keys only if setting protocol options
+     * has not failed */
+    else if (!multi_client_generate_tls_keys(&mi->context))
+    {
+        mi->context.c2.tls_multi->multi_state = CAS_FAILED;
+    }
+
+    /* send push reply if ready */
+    if (mi->context.c2.push_request_received)
+    {
+        process_incoming_push_request(&mi->context);
+    }
+
+    gc_free(&gc);
+}
+
+static void
+multi_client_connect_early_setup(struct multi_context *m,
+                                 struct multi_instance *mi)
+{
+    ASSERT(mi->context.c1.tuntap);
+    /*
+     * lock down the common name and cert hashes so they can't change
+     * during future TLS renegotiations
+     */
+    tls_lock_common_name(mi->context.c2.tls_multi);
+    tls_lock_cert_hash_set(mi->context.c2.tls_multi);
+
+    /* generate a msg() prefix for this client instance */
+    generate_prefix(mi);
+
+    /* delete instances of previous clients with same common-name */
+    if (!mi->context.options.duplicate_cn)
+    {
+        multi_delete_dup(m, mi);
+    }
+
+    /* reset pool handle to null */
+    mi->vaddr_handle = -1;
+
+    /* do --client-connect setenvs */
+    multi_select_virtual_addr(m, mi);
+
+    multi_client_connect_setenv(m, mi);
+}
+
+/**
+ * Try to source a dynamic config file from the
+ * --client-config-dir directory.
+ */
+static enum client_connect_return
+multi_client_connect_source_ccd(struct multi_context *m,
+                                struct multi_instance *mi,
+                                bool deferred,
+                                unsigned int *option_types_found)
+{
+    /* Since we never return a CC_RET_DEFERRED, this indicates a serious
+     * problem */
+    ASSERT(!deferred);
+    enum client_connect_return ret = CC_RET_SKIPPED;
+    if (mi->context.options.client_config_dir)
+    {
+        struct gc_arena gc = gc_new();
+        const char *ccd_file = NULL;
+
+        const char *ccd_client =
+            platform_gen_path(mi->context.options.client_config_dir,
+                              tls_common_name(mi->context.c2.tls_multi, false),
+                              &gc);
+
+        const char *ccd_default =
+            platform_gen_path(mi->context.options.client_config_dir,
+                              CCD_DEFAULT, &gc);
+
+
+        /* try common-name file */
+        if (platform_test_file(ccd_client))
+        {
+            ccd_file = ccd_client;
+        }
+        /* try default file */
+        else if (platform_test_file(ccd_default))
+        {
+            ccd_file = ccd_default;
+        }
+
+        if (ccd_file)
+        {
+            options_server_import(&mi->context.options,
+                                  ccd_file,
+                                  D_IMPORT_ERRORS|M_OPTERR,
+                                  CLIENT_CONNECT_OPT_MASK,
+                                  option_types_found,
+                                  mi->context.c2.es);
+            /*
+             * Select a virtual address from either --ifconfig-push in
+             * --client-config-dir file or --ifconfig-pool.
+             */
+            multi_select_virtual_addr(m, mi);
+
+            multi_client_connect_setenv(m, mi);
+
+            ret = CC_RET_SUCCEEDED;
+        }
+        gc_free(&gc);
+    }
+    return ret;
+}
+
+typedef enum client_connect_return (*multi_client_connect_handler)
+    (struct multi_context *m, struct multi_instance *mi,
+    bool from_deferred, unsigned int *option_types_found);
+
+static const multi_client_connect_handler client_connect_handlers[] = {
+    multi_client_connect_source_ccd,
+    multi_client_connect_call_plugin_v1,
+    multi_client_connect_call_plugin_v2,
+    multi_client_connect_call_script,
+    multi_client_connect_mda,
+    NULL,
+};
+
+/*
+ * Called as soon as the SSL/TLS connection is authenticated.
+ *
+ * Will collect the client specific configuration from the different
+ * sources like ccd files, connect plugins and management interface.
+ *
+ * This method starts with cas_context CAS_PENDING and will move the
+ * state machine to either CAS_SUCCEEDED on success or
+ * CAS_FAILED/CAS_PARTIAL on failure.
+ *
+ * Instance-specific directives to be processed (CLIENT_CONNECT_OPT_MASK)
+ * include:
  *
  *   iroute start-ip end-ip
  *   ifconfig-push local remote-netmask
  *   push
+ *
+ *
  */
 static void
 multi_connection_established(struct multi_context *m, struct multi_instance *mi)
 {
-    if (tls_authentication_status(mi->context.c2.tls_multi, 0) == TLS_AUTHENTICATION_SUCCEEDED)
+    if (tls_authentication_status(mi->context.c2.tls_multi, 0)
+        != TLS_AUTHENTICATION_SUCCEEDED)
     {
-        struct gc_arena gc = gc_new();
-        unsigned int option_types_found = 0;
+        return;
+    }
 
-        const unsigned int option_permissions_mask =
-            OPT_P_INSTANCE
-            | OPT_P_INHERIT
-            | OPT_P_PUSH
-            | OPT_P_TIMER
-            | OPT_P_CONFIG
-            | OPT_P_ECHO
-            | OPT_P_COMP
-            | OPT_P_SOCKFLAGS;
+    /* We are only called for the CAS_PENDING_x states, so we
+     * can ignore other states here */
+    bool from_deferred = (mi->context.c2.tls_multi->multi_state != CAS_PENDING);
 
-        int cc_succeeded = true; /* client connect script status */
-        int cc_succeeded_count = 0;
+    int *cur_handler_index = &mi->client_connect_defer_state.cur_handler_index;
+    unsigned int *option_types_found =
+        &mi->client_connect_defer_state.option_types_found;
 
-        ASSERT(mi->context.c1.tuntap);
+    /* We are called for the first time */
+    if (!from_deferred)
+    {
+        *cur_handler_index = 0;
+        *option_types_found = 0;
+        /* Initially we have no handler that has returned a result */
+        mi->context.c2.tls_multi->multi_state = CAS_PENDING_DEFERRED;
 
-        /* lock down the common name and cert hashes so they can't change during future TLS renegotiations */
-        tls_lock_common_name(mi->context.c2.tls_multi);
-        tls_lock_cert_hash_set(mi->context.c2.tls_multi);
+        multi_client_connect_early_setup(m, mi);
+    }
 
-        /* generate a msg() prefix for this client instance */
-        generate_prefix(mi);
+    bool cc_succeeded = true;
 
-        /* delete instances of previous clients with same common-name */
-        if (!mi->context.options.duplicate_cn)
+    while (cc_succeeded
+           && client_connect_handlers[*cur_handler_index] != NULL)
+    {
+        enum client_connect_return ret;
+        ret = client_connect_handlers[*cur_handler_index](m, mi, from_deferred,
+                                                          option_types_found);
+
+        from_deferred = false;
+
+        switch (ret)
         {
-            multi_delete_dup(m, mi);
-        }
+            case CC_RET_SUCCEEDED:
+                /*
+                 * Remember that we already had at least one handler
+                 * returning a result should we go to into deferred state
+                 */
+                mi->context.c2.tls_multi->multi_state = CAS_PENDING_DEFERRED_PARTIAL;
+                break;
 
-        /* reset pool handle to null */
-        mi->vaddr_handle = -1;
+            case CC_RET_SKIPPED:
+                /*
+                 * Move on with the next handler without modifying any
+                 * other state
+                 */
+                break;
 
-        /*
-         * Try to source a dynamic config file from the
-         * --client-config-dir directory.
-         */
-        if (mi->context.options.client_config_dir)
-        {
-            const char *ccd_file;
+            case CC_RET_DEFERRED:
+                /*
+                 * we already set client_connect_status to DEFERRED_RESULT or
+                 * DEFERRED_NO_RESULT. We just return
+                 * from the function as having client_connect_status
+                 */
+                return;
 
-            ccd_file = gen_path(mi->context.options.client_config_dir,
-                                tls_common_name(mi->context.c2.tls_multi, false),
-                                &gc);
-
-            /* try common-name file */
-            if (test_file(ccd_file))
-            {
-                options_server_import(&mi->context.options,
-                                      ccd_file,
-                                      D_IMPORT_ERRORS|M_OPTERR,
-                                      option_permissions_mask,
-                                      &option_types_found,
-                                      mi->context.c2.es);
-            }
-            else /* try default file */
-            {
-                ccd_file = gen_path(mi->context.options.client_config_dir,
-                                    CCD_DEFAULT,
-                                    &gc);
-
-                if (test_file(ccd_file))
-                {
-                    options_server_import(&mi->context.options,
-                                          ccd_file,
-                                          D_IMPORT_ERRORS|M_OPTERR,
-                                          option_permissions_mask,
-                                          &option_types_found,
-                                          mi->context.c2.es);
-                }
-            }
-        }
-
-        /*
-         * Select a virtual address from either --ifconfig-push in --client-config-dir file
-         * or --ifconfig-pool.
-         */
-        multi_select_virtual_addr(m, mi);
-
-        /* do --client-connect setenvs */
-        multi_client_connect_setenv(m, mi);
-
-#ifdef ENABLE_PLUGIN
-        /*
-         * Call client-connect plug-in.
-         */
-
-        /* deprecated callback, use a file for passing back return info */
-        if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))
-        {
-            struct argv argv = argv_new();
-            const char *dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
-
-            if (!dc_file)
-            {
+            case CC_RET_FAILED:
+                /*
+                 * One handler failed. We abort the chain and set the final
+                 * result to failed
+                 */
                 cc_succeeded = false;
-                goto script_depr_failed;
-            }
+                break;
 
-            argv_printf(&argv, "%s", dc_file);
-            if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT, &argv, NULL, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
-            {
-                msg(M_WARN, "WARNING: client-connect plugin call failed");
-                cc_succeeded = false;
-            }
-            else
-            {
-                multi_client_connect_post(m, mi, dc_file, option_permissions_mask, &option_types_found);
-                ++cc_succeeded_count;
-            }
-
-            if (!platform_unlink(dc_file))
-            {
-                msg(D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s",
-                    dc_file);
-            }
-
-script_depr_failed:
-            argv_reset(&argv);
+            default:
+                ASSERT(0);
         }
 
-        /* V2 callback, use a plugin_return struct for passing back return info */
-        if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT_V2))
-        {
-            struct plugin_return pr;
-
-            plugin_return_init(&pr);
-
-            if (plugin_call(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT_V2, NULL, &pr, mi->context.c2.es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
-            {
-                msg(M_WARN, "WARNING: client-connect-v2 plugin call failed");
-                cc_succeeded = false;
-            }
-            else
-            {
-                multi_client_connect_post_plugin(m, mi, &pr, option_permissions_mask, &option_types_found);
-                ++cc_succeeded_count;
-            }
-
-            plugin_return_free(&pr);
-        }
-#endif /* ifdef ENABLE_PLUGIN */
-
-        /*
-         * Run --client-connect script.
-         */
-        if (mi->context.options.client_connect_script && cc_succeeded)
-        {
-            struct argv argv = argv_new();
-            const char *dc_file = NULL;
-
-            setenv_str(mi->context.c2.es, "script_type", "client-connect");
-
-            dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
-            if (!dc_file)
-            {
-                cc_succeeded = false;
-                goto script_failed;
-            }
-
-            argv_parse_cmd(&argv, mi->context.options.client_connect_script);
-            argv_printf_cat(&argv, "%s", dc_file);
-
-            if (openvpn_run_script(&argv, mi->context.c2.es, 0, "--client-connect"))
-            {
-                multi_client_connect_post(m, mi, dc_file, option_permissions_mask, &option_types_found);
-                ++cc_succeeded_count;
-            }
-            else
-            {
-                cc_succeeded = false;
-            }
-
-            if (!platform_unlink(dc_file))
-            {
-                msg(D_MULTI_ERRORS, "MULTI: problem deleting temporary file: %s",
-                    dc_file);
-            }
-
-script_failed:
-            argv_reset(&argv);
-        }
-
-        /*
-         * Check for client-connect script left by management interface client
-         */
-#ifdef MANAGEMENT_DEF_AUTH
-        if (cc_succeeded && mi->cc_config)
-        {
-            multi_client_connect_mda(m, mi, mi->cc_config, option_permissions_mask, &option_types_found);
-            ++cc_succeeded_count;
-        }
-#endif
-
         /*
          * Check for "disable" directive in client-config-dir file
          * or config file generated by --client-connect script.
          */
         if (mi->context.options.disable)
         {
-            msg(D_MULTI_ERRORS, "MULTI: client has been rejected due to 'disable' directive");
+            msg(D_MULTI_ERRORS, "MULTI: client has been rejected due to "
+                "'disable' directive");
             cc_succeeded = false;
-            cc_succeeded_count = 0;
         }
 
-        if (cc_succeeded)
-        {
-            /*
-             * Process sourced options.
-             */
-            do_deferred_options(&mi->context, option_types_found);
-
-            /*
-             * make sure we got ifconfig settings from somewhere
-             */
-            if (!mi->context.c2.push_ifconfig_defined)
-            {
-                msg(D_MULTI_ERRORS, "MULTI: no dynamic or static remote --ifconfig address is available for %s",
-                    multi_instance_string(mi, false, &gc));
-            }
-
-            /*
-             * make sure that ifconfig settings comply with constraints
-             */
-            if (!ifconfig_push_constraint_satisfied(&mi->context))
-            {
-                /* JYFIXME -- this should cause the connection to fail */
-                msg(D_MULTI_ERRORS, "MULTI ERROR: primary virtual IP for %s (%s) violates tunnel network/netmask constraint (%s/%s)",
-                    multi_instance_string(mi, false, &gc),
-                    print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc),
-                    print_in_addr_t(mi->context.options.push_ifconfig_constraint_network, 0, &gc),
-                    print_in_addr_t(mi->context.options.push_ifconfig_constraint_netmask, 0, &gc));
-            }
-
-            /*
-             * For routed tunnels, set up internal route to endpoint
-             * plus add all iroute routes.
-             */
-            if (TUNNEL_TYPE(mi->context.c1.tuntap) == DEV_TYPE_TUN)
-            {
-                if (mi->context.c2.push_ifconfig_defined)
-                {
-                    multi_learn_in_addr_t(m, mi, mi->context.c2.push_ifconfig_local, -1, true);
-                    msg(D_MULTI_LOW, "MULTI: primary virtual IP for %s: %s",
-                        multi_instance_string(mi, false, &gc),
-                        print_in_addr_t(mi->context.c2.push_ifconfig_local, 0, &gc));
-                }
-
-                if (mi->context.c2.push_ifconfig_ipv6_defined)
-                {
-                    multi_learn_in6_addr(m, mi, mi->context.c2.push_ifconfig_ipv6_local, -1, true);
-                    /* TODO: find out where addresses are "unlearned"!! */
-                    msg(D_MULTI_LOW, "MULTI: primary virtual IPv6 for %s: %s",
-                        multi_instance_string(mi, false, &gc),
-                        print_in6_addr(mi->context.c2.push_ifconfig_ipv6_local, 0, &gc));
-                }
-
-                /* add routes locally, pointing to new client, if
-                 * --iroute options have been specified */
-                multi_add_iroutes(m, mi);
-
-                /*
-                 * iroutes represent subnets which are "owned" by a particular
-                 * client.  Therefore, do not actually push a route to a client
-                 * if it matches one of the client's iroutes.
-                 */
-                remove_iroutes_from_push_route_list(&mi->context.options);
-            }
-            else if (mi->context.options.iroutes)
-            {
-                msg(D_MULTI_ERRORS, "MULTI: --iroute options rejected for %s -- iroute only works with tun-style tunnels",
-                    multi_instance_string(mi, false, &gc));
-            }
-
-            /* set our client's VPN endpoint for status reporting purposes */
-            mi->reporting_addr = mi->context.c2.push_ifconfig_local;
-            mi->reporting_addr_ipv6 = mi->context.c2.push_ifconfig_ipv6_local;
-
-            /* set context-level authentication flag */
-            mi->context.c2.context_auth = CAS_SUCCEEDED;
-
-#ifdef ENABLE_ASYNC_PUSH
-            /* authentication complete, send push reply */
-            if (mi->context.c2.push_request_received)
-            {
-                process_incoming_push_request(&mi->context);
-            }
-#endif
-        }
-        else
-        {
-            /* set context-level authentication flag */
-            mi->context.c2.context_auth = cc_succeeded_count ? CAS_PARTIAL : CAS_FAILED;
-        }
-
-        /* set flag so we don't get called again */
-        mi->connection_established_flag = true;
-
-        /* increment number of current authenticated clients */
-        ++m->n_clients;
-        update_mstat_n_clients(m->n_clients);
-        --mi->n_clients_delta;
-
-#ifdef MANAGEMENT_DEF_AUTH
-        if (management)
-        {
-            management_connection_established(management, &mi->context.c2.mda_context, mi->context.c2.es);
-        }
-#endif
-
-        gc_free(&gc);
+        (*cur_handler_index)++;
     }
 
-    /*
-     * Reply now to client's PUSH_REQUEST query
-     */
-    mi->context.c2.push_reply_deferred = false;
+    if (cc_succeeded)
+    {
+        multi_client_connect_late_setup(m, mi, *option_types_found);
+    }
+    else
+    {
+        /* run the disconnect script if we had a connect script that
+         * did not fail */
+        if (mi->context.c2.tls_multi->multi_state == CAS_PENDING_DEFERRED_PARTIAL)
+        {
+            multi_client_disconnect_script(mi);
+        }
+
+        mi->context.c2.tls_multi->multi_state = CAS_FAILED;
+    }
+
+    /* increment number of current authenticated clients */
+    ++m->n_clients;
+    update_mstat_n_clients(m->n_clients);
+    --mi->n_clients_delta;
+
+#ifdef MANAGEMENT_DEF_AUTH
+    if (management)
+    {
+        management_connection_established(management,
+                                          &mi->context.c2.mda_context, mi->context.c2.es);
+    }
+#endif
 }
 
 #ifdef ENABLE_ASYNC_PUSH
 /*
- * Called when inotify event is fired, which happens when acf file is closed or deleted.
- * Continues authentication and sends push_reply.
+ * Called when inotify event is fired, which happens when acf
+ * or connect-status file is closed or deleted.
+ * Continues authentication and sends push_reply
+ * (or be deferred again by client-connect)
  */
 void
 multi_process_file_closed(struct multi_context *m, const unsigned int mpp_flags)
@@ -2134,28 +2735,6 @@
             {
                 /* continue authentication, perform NCP negotiation and send push_reply */
                 multi_process_post(m, mi, mpp_flags);
-
-                /* With NCP and deferred authentication, we perform cipher negotiation and
-                 * data channel keys generation on incoming push request, assuming that auth
-                 * succeeded. When auth succeeds in between push requests and async push is used,
-                 * we send push reply immediately. Above multi_process_post() call performs
-                 * NCP negotiation and here we do keys generation. */
-
-                struct context *c = &mi->context;
-                struct frame *frame_fragment = NULL;
-#ifdef ENABLE_FRAGMENT
-                if (c->options.ce.fragment)
-                {
-                    frame_fragment = &c->c2.frame_fragment;
-                }
-#endif
-                struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
-                if (!tls_session_update_crypto_params(session, &c->options,
-                                                      &c->c2.frame, frame_fragment))
-                {
-                    msg(D_TLS_ERRORS, "TLS Error: initializing data channel failed");
-                    register_signal(c, SIGUSR1, "init-data-channel-failed");
-                }
             }
             else
             {
@@ -2227,7 +2806,8 @@
 multi_bcast(struct multi_context *m,
             const struct buffer *buf,
             const struct multi_instance *sender_instance,
-            const struct mroute_addr *sender_addr)
+            const struct mroute_addr *sender_addr,
+            uint16_t vid)
 {
     struct hash_iterator hi;
     struct hash_element *he;
@@ -2251,7 +2831,11 @@
 #ifdef ENABLE_PF
                 if (sender_instance)
                 {
-                    if (!pf_c2c_test(&sender_instance->context, &mi->context, "bcast_c2c"))
+                    if (!pf_c2c_test(&sender_instance->context.c2.pf,
+                                     sender_instance->context.c2.tls_multi,
+                                     &mi->context.c2.pf,
+                                     mi->context.c2.tls_multi,
+                                     "bcast_c2c"))
                     {
                         msg(D_PF_DROPPED_BCAST, "PF: client[%s] -> client[%s] packet dropped by BCAST packet filter",
                             mi_prefix(sender_instance),
@@ -2261,7 +2845,8 @@
                 }
                 if (sender_addr)
                 {
-                    if (!pf_addr_test(&mi->context, sender_addr, "bcast_src_addr"))
+                    if (!pf_addr_test(&mi->context.c2.pf, &mi->context,
+                                      sender_addr, "bcast_src_addr"))
                     {
                         struct gc_arena gc = gc_new();
                         msg(D_PF_DROPPED_BCAST, "PF: addr[%s] -> client[%s] packet dropped by BCAST packet filter",
@@ -2272,6 +2857,10 @@
                     }
                 }
 #endif /* ifdef ENABLE_PF */
+                if (vid != 0 && vid != mi->context.options.vlan_pvid)
+                {
+                    continue;
+                }
                 multi_add_mbuf(m, mi, mb);
             }
         }
@@ -2329,6 +2918,32 @@
                        compute_wakeup_sigma(&mi->context.c2.timeval));
 }
 
+#if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH)
+static void
+add_inotify_file_watch(struct multi_context *m, struct multi_instance *mi,
+                       int inotify_fd, const char *file)
+{
+    /* watch acf file */
+    long watch_descriptor = inotify_add_watch(inotify_fd, file,
+                                              IN_CLOSE_WRITE | IN_ONESHOT);
+    if (watch_descriptor >= 0)
+    {
+        if (mi->inotify_watch != -1)
+        {
+            hash_remove(m->inotify_watchers,
+                        (void *) (unsigned long)mi->inotify_watch);
+        }
+        hash_add(m->inotify_watchers, (const uintptr_t *)watch_descriptor,
+                 mi, true);
+        mi->inotify_watch = watch_descriptor;
+    }
+    else
+    {
+        msg(M_NONFATAL | M_ERRNO, "MULTI: inotify_add_watch error");
+    }
+}
+#endif /* if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH) */
+
 /*
  * Figure instance-specific timers, convert
  * earliest to absolute time in mi->wakeup,
@@ -2344,12 +2959,12 @@
     if (!IS_SIG(&mi->context) && ((flags & MPP_PRE_SELECT) || ((flags & MPP_CONDITIONAL_PRE_SELECT) && !ANY_OUT(&mi->context))))
     {
 #if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH)
-        bool was_authenticated = false;
+        bool was_unauthenticated = true;
         struct key_state *ks = NULL;
         if (mi->context.c2.tls_multi)
         {
             ks = &mi->context.c2.tls_multi->session[TM_ACTIVE].key[KS_PRIMARY];
-            was_authenticated = ks->authenticated;
+            was_unauthenticated = (ks->authenticated == KS_AUTH_FALSE);
         }
 #endif
 
@@ -2358,23 +2973,16 @@
         pre_select(&mi->context);
 
 #if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH)
-        if (ks && ks->auth_control_file && ks->auth_deferred && !was_authenticated)
+        /*
+         * if we see the state transition from unauthenticated to deferred
+         * and an auth_control_file, we assume it got just added and add
+         * inotify watch to that file
+         */
+        if (ks && ks->auth_control_file && was_unauthenticated
+            && (ks->authenticated == KS_AUTH_DEFERRED))
         {
-            /* watch acf file */
-            long watch_descriptor = inotify_add_watch(m->top.c2.inotify_fd, ks->auth_control_file, IN_CLOSE_WRITE | IN_ONESHOT);
-            if (watch_descriptor >= 0)
-            {
-                if (mi->inotify_watch != -1)
-                {
-                    hash_remove(m->inotify_watchers, (void *) (unsigned long)mi->inotify_watch);
-                }
-                hash_add(m->inotify_watchers, (const uintptr_t *)watch_descriptor, mi, true);
-                mi->inotify_watch = watch_descriptor;
-            }
-            else
-            {
-                msg(M_NONFATAL | M_ERRNO, "MULTI: inotify_add_watch error");
-            }
+            add_inotify_file_watch(m, mi, m->top.c2.inotify_fd,
+                                   ks->auth_control_file);
         }
 #endif
 
@@ -2382,11 +2990,20 @@
         {
             /* connection is "established" when SSL/TLS key negotiation succeeds
              * and (if specified) auth user/pass succeeds */
-            if (!mi->connection_established_flag && CONNECTION_ESTABLISHED(&mi->context))
+            if (is_cas_pending(mi->context.c2.tls_multi->multi_state)
+                && CONNECTION_ESTABLISHED(&mi->context))
             {
                 multi_connection_established(m, mi);
             }
-
+#if defined(ENABLE_ASYNC_PUSH) && defined(ENABLE_DEF_AUTH)
+            if (is_cas_pending(mi->context.c2.tls_multi->multi_state)
+                && mi->client_connect_defer_state.deferred_ret_file)
+            {
+                add_inotify_file_watch(m, mi, m->top.c2.inotify_fd,
+                                       mi->client_connect_defer_state.
+                                       deferred_ret_file);
+            }
+#endif
             /* tell scheduler to wake us up at some point in the future */
             multi_schedule_context_wakeup(m, mi);
         }
@@ -2406,14 +3023,14 @@
         multi_set_pending(m, ANY_OUT(&mi->context) ? mi : NULL);
 
 #ifdef MULTI_DEBUG_EVENT_LOOP
-        printf("POST %s[%d] to=%d lo=%d/%d w=%d/%d\n",
+        printf("POST %s[%d] to=%d lo=%d/%d w=%" PRIi64 "/%ld\n",
                id(mi),
                (int) (mi == m->pending),
                mi ? mi->context.c2.to_tun.len : -1,
                mi ? mi->context.c2.to_link.len : -1,
                (mi && mi->context.c2.fragment) ? mi->context.c2.fragment->outgoing.len : -1,
-               (int)mi->context.c2.timeval.tv_sec,
-               (int)mi->context.c2.timeval.tv_usec);
+               (int64_t)mi->context.c2.timeval.tv_sec,
+               (long)mi->context.c2.timeval.tv_usec);
 #endif
     }
 
@@ -2579,6 +3196,7 @@
                                                                &dest,
                                                                NULL,
                                                                NULL,
+                                                               0,
                                                                &c->c2.to_tun,
                                                                DEV_TYPE_TUN);
 
@@ -2610,7 +3228,7 @@
                     if (mroute_flags & MROUTE_EXTRACT_MCAST)
                     {
                         /* for now, treat multicast as broadcast */
-                        multi_bcast(m, &c->c2.to_tun, m->pending, NULL);
+                        multi_bcast(m, &c->c2.to_tun, m->pending, NULL, 0);
                     }
                     else /* possible client to client routing */
                     {
@@ -2621,7 +3239,10 @@
                         if (mi)
                         {
 #ifdef ENABLE_PF
-                            if (!pf_c2c_test(c, &mi->context, "tun_c2c"))
+                            if (!pf_c2c_test(&c->c2.pf, c->c2.tls_multi,
+                                             &mi->context.c2.pf,
+                                             mi->context.c2.tls_multi,
+                                             "tun_c2c"))
                             {
                                 msg(D_PF_DROPPED, "PF: client -> client[%s] packet dropped by TUN packet filter",
                                     mi_prefix(mi));
@@ -2637,7 +3258,8 @@
                     }
                 }
 #ifdef ENABLE_PF
-                if (c->c2.to_tun.len && !pf_addr_test(c, &dest, "tun_dest_addr"))
+                if (c->c2.to_tun.len && !pf_addr_test(&c->c2.pf, c, &dest,
+                                                      "tun_dest_addr"))
                 {
                     msg(D_PF_DROPPED, "PF: client -> addr[%s] packet dropped by TUN packet filter",
                         mroute_addr_print_ex(&dest, MAPF_SHOW_ARP, &gc));
@@ -2647,10 +3269,25 @@
             }
             else if (TUNNEL_TYPE(m->top.c1.tuntap) == DEV_TYPE_TAP)
             {
+                uint16_t vid = 0;
 #ifdef ENABLE_PF
                 struct mroute_addr edest;
                 mroute_addr_reset(&edest);
 #endif
+
+                if (m->top.options.vlan_tagging)
+                {
+                    if (vlan_is_tagged(&c->c2.to_tun))
+                    {
+                        /* Drop VLAN-tagged frame. */
+                        msg(D_VLAN_DEBUG, "dropping incoming VLAN-tagged frame");
+                        c->c2.to_tun.len = 0;
+                    }
+                    else
+                    {
+                        vid = c->options.vlan_pvid;
+                    }
+                }
                 /* extract packet source and dest addresses */
                 mroute_flags = mroute_extract_addr_from_packet(&src,
                                                                &dest,
@@ -2660,6 +3297,7 @@
 #else
                                                                NULL,
 #endif
+                                                               vid,
                                                                &c->c2.to_tun,
                                                                DEV_TYPE_TAP);
 
@@ -2672,7 +3310,8 @@
                         {
                             if (mroute_flags & (MROUTE_EXTRACT_BCAST|MROUTE_EXTRACT_MCAST))
                             {
-                                multi_bcast(m, &c->c2.to_tun, m->pending, NULL);
+                                multi_bcast(m, &c->c2.to_tun, m->pending, NULL,
+                                            vid);
                             }
                             else /* try client-to-client routing */
                             {
@@ -2682,7 +3321,10 @@
                                 if (mi)
                                 {
 #ifdef ENABLE_PF
-                                    if (!pf_c2c_test(c, &mi->context, "tap_c2c"))
+                                    if (!pf_c2c_test(&c->c2.pf, c->c2.tls_multi,
+                                                     &mi->context.c2.pf,
+                                                     mi->context.c2.tls_multi,
+                                                     "tap_c2c"))
                                     {
                                         msg(D_PF_DROPPED, "PF: client -> client[%s] packet dropped by TAP packet filter",
                                             mi_prefix(mi));
@@ -2698,7 +3340,9 @@
                             }
                         }
 #ifdef ENABLE_PF
-                        if (c->c2.to_tun.len && !pf_addr_test(c, &edest, "tap_dest_addr"))
+                        if (c->c2.to_tun.len && !pf_addr_test(&c->c2.pf, c,
+                                                              &edest,
+                                                              "tap_dest_addr"))
                         {
                             msg(D_PF_DROPPED, "PF: client -> addr[%s] packet dropped by TAP packet filter",
                                 mroute_addr_print_ex(&edest, MAPF_SHOW_ARP, &gc));
@@ -2745,6 +3389,7 @@
         unsigned int mroute_flags;
         struct mroute_addr src, dest;
         const int dev_type = TUNNEL_TYPE(m->top.c1.tuntap);
+        int16_t vid = 0;
 
 #ifdef ENABLE_PF
         struct mroute_addr esrc, *e1, *e2;
@@ -2769,6 +3414,15 @@
             return true;
         }
 
+        if (dev_type == DEV_TYPE_TAP && m->top.options.vlan_tagging)
+        {
+            vid = vlan_decapsulate(&m->top, &m->top.c2.buf);
+            if (vid < 0)
+            {
+                return false;
+            }
+        }
+
         /*
          * Route an incoming tun/tap packet to
          * the appropriate multi_instance object.
@@ -2782,6 +3436,7 @@
                                                        NULL,
 #endif
                                                        NULL,
+                                                       vid,
                                                        &m->top.c2.buf,
                                                        dev_type);
 
@@ -2794,9 +3449,9 @@
             {
                 /* for now, treat multicast as broadcast */
 #ifdef ENABLE_PF
-                multi_bcast(m, &m->top.c2.buf, NULL, e2);
+                multi_bcast(m, &m->top.c2.buf, NULL, e2, vid);
 #else
-                multi_bcast(m, &m->top.c2.buf, NULL, NULL);
+                multi_bcast(m, &m->top.c2.buf, NULL, NULL, vid);
 #endif
             }
             else
@@ -2811,7 +3466,7 @@
                     set_prefix(m->pending);
 
 #ifdef ENABLE_PF
-                    if (!pf_addr_test(c, e2, "tun_tap_src_addr"))
+                    if (!pf_addr_test(&c->c2.pf, c, e2, "tun_tap_src_addr"))
                     {
                         msg(D_PF_DROPPED, "PF: addr[%s] -> client packet dropped by packet filter",
                             mroute_addr_print_ex(&src, MAPF_SHOW_ARP, &gc));
@@ -2859,7 +3514,7 @@
 
     if (mbuf_extract_item(ms, &item)) /* cleartext IP packet */
     {
-        unsigned int pip_flags = PIPV4_PASSTOS;
+        unsigned int pip_flags = PIPV4_PASSTOS | PIPV6_IMCP_NOHOST_SERVER;
 
         set_prefix(item.instance);
         item.instance->context.c2.buf = item.buffer->buf;
@@ -2978,7 +3633,7 @@
 
         for (i = 0; i < parm.n_packets; ++i)
         {
-            multi_bcast(m, &buf, NULL, NULL);
+            multi_bcast(m, &buf, NULL, NULL, 0);
         }
 
         gc_free(&gc);
@@ -3260,6 +3915,24 @@
 }
 
 static bool
+management_client_pending_auth(void *arg,
+                               const unsigned long cid,
+                               const char *extra)
+{
+    struct multi_context *m = (struct multi_context *) arg;
+    struct multi_instance *mi = lookup_by_cid(m, cid);
+    if (mi)
+    {
+        /* sends INFO_PRE and AUTH_PENDING messages to client */
+        bool ret = send_auth_pending_messages(&mi->context, extra);
+        multi_schedule_context_wakeup(m, mi);
+        return ret;
+    }
+    return false;
+}
+
+
+static bool
 management_client_auth(void *arg,
                        const unsigned long cid,
                        const unsigned int mda_key_id,
@@ -3280,7 +3953,7 @@
         {
             if (auth)
             {
-                if (!mi->connection_established_flag)
+                if (is_cas_pending(mi->context.c2.tls_multi->multi_state))
                 {
                     set_cc_config(mi, cc_config);
                     cc_config_owned = false;
@@ -3292,7 +3965,7 @@
                 {
                     msg(D_MULTI_LOW, "MULTI: connection rejected: %s, CLI:%s", reason, np(client_reason));
                 }
-                if (mi->connection_established_flag)
+                if (!is_cas_pending(mi->context.c2.tls_multi->multi_state))
                 {
                     send_auth_failed(&mi->context, client_reason); /* mid-session reauth failed */
                     multi_schedule_context_wakeup(m, mi);
@@ -3366,6 +4039,7 @@
 #ifdef MANAGEMENT_DEF_AUTH
         cb.kill_by_cid = management_kill_by_cid;
         cb.client_auth = management_client_auth;
+        cb.client_pending_auth = management_client_pending_auth;
         cb.get_peer_info = management_get_peer_info;
 #endif
 #ifdef MANAGEMENT_PF
@@ -3393,10 +4067,3 @@
         tunnel_server_tcp(top);
     }
 }
-
-#else  /* if P2MP_SERVER */
-static void
-dummy(void)
-{
-}
-#endif /* P2MP_SERVER */
diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h
index ebcc22d..40e808a 100644
--- a/src/openvpn/multi.h
+++ b/src/openvpn/multi.h
@@ -28,8 +28,6 @@
 #ifndef MULTI_H
 #define MULTI_H
 
-#if P2MP_SERVER
-
 #include "init.h"
 #include "forward.h"
 #include "mroute.h"
@@ -40,6 +38,7 @@
 #include "mudp.h"
 #include "mtcp.h"
 #include "perf.h"
+#include "vlan.h"
 
 #define MULTI_PREFIX_MAX_LENGTH 256
 
@@ -64,6 +63,31 @@
 };
 
 /**
+ * Detached client connection state.  This is the state that is tracked while
+ * the client connect hooks are executed.
+ */
+struct client_connect_defer_state
+{
+    /* Index of currently executed handler.  */
+    int cur_handler_index;
+    /* Remember which option classes where processed for delayed option
+     * handling. */
+    unsigned int option_types_found;
+
+    /**
+     * The temporary file name that contains the return status of the
+     * client-connect script if it exits with defer as status
+     */
+    char *deferred_ret_file;
+
+    /**
+     * The temporary file name that contains the config directives
+     * returned by the client-connect script
+     */
+    char *config_file;
+};
+
+/**
  * Server-mode state structure for one single VPN tunnel.
  *
  * This structure is used by OpenVPN processes running in server-mode to
@@ -76,7 +100,6 @@
 struct multi_instance {
     struct schedule_entry se;  /* this must be the first element of the structure */
     struct gc_arena gc;
-    bool defined;
     bool halt;
     int refcount;
     int route_count;           /* number of routes (including cached routes) owned by this instance */
@@ -98,20 +121,18 @@
     in_addr_t reporting_addr;     /* IP address shown in status listing */
     struct in6_addr reporting_addr_ipv6; /* IPv6 address in status listing */
 
-    bool did_open_context;
     bool did_real_hash;
     bool did_iter;
 #ifdef MANAGEMENT_DEF_AUTH
     bool did_cid_hash;
     struct buffer_list *cc_config;
 #endif
-    bool connection_established_flag;
     bool did_iroutes;
     int n_clients_delta; /* added to multi_context.n_clients when instance is closed */
 
     struct context context;     /**< The context structure storing state
                                  *   for this VPN tunnel. */
-
+    struct client_connect_defer_state client_connect_defer_state;
 #ifdef ENABLE_ASYNC_PUSH
     int inotify_watch; /* watch descriptor for acf */
 #endif
@@ -191,6 +212,17 @@
     struct deferred_signal_schedule_entry deferred_shutdown_signal;
 };
 
+/**
+ * Return values used by the client connect call-back functions.
+ */
+enum client_connect_return
+{
+    CC_RET_FAILED,
+    CC_RET_SUCCEEDED,
+    CC_RET_DEFERRED,
+    CC_RET_SKIPPED
+};
+
 /*
  * Host route
  */
@@ -533,11 +565,13 @@
  */
 #define MULTI_CACHE_ROUTE_TTL 60
 
+void multi_reap_process_dowork(const struct multi_context *m);
+
+void multi_process_per_second_timers_dowork(struct multi_context *m);
+
 static inline void
 multi_reap_process(const struct multi_context *m)
 {
-    void multi_reap_process_dowork(const struct multi_context *m);
-
     if (m->reaper->last_call != now)
     {
         multi_reap_process_dowork(m);
@@ -549,8 +583,6 @@
 {
     if (m->per_second_trigger != now)
     {
-        void multi_process_per_second_timers_dowork(struct multi_context *m);
-
         multi_process_per_second_timers_dowork(m);
         m->per_second_trigger = now;
     }
@@ -620,13 +652,16 @@
            mi->context.c2.to_tun.len);
 #endif
     set_prefix(mi);
+    vlan_process_outgoing_tun(m, mi);
     process_outgoing_tun(&mi->context);
     ret = multi_process_post(m, mi, mpp_flags);
     clear_prefix();
     return ret;
 }
 
-
+#define CLIENT_CONNECT_OPT_MASK (OPT_P_INSTANCE | OPT_P_INHERIT   \
+                                 |OPT_P_PUSH | OPT_P_TIMER | OPT_P_CONFIG   \
+                                 |OPT_P_ECHO | OPT_P_COMP | OPT_P_SOCKFLAGS)
 
 static inline bool
 multi_process_outgoing_link_dowork(struct multi_context *m, struct multi_instance *mi, const unsigned int mpp_flags)
@@ -650,5 +685,4 @@
     m->pending = mi;
 }
 
-#endif /* P2MP_SERVER */
 #endif /* MULTI_H */
diff --git a/src/openvpn/networking.h b/src/openvpn/networking.h
new file mode 100644
index 0000000..9c1d169
--- /dev/null
+++ b/src/openvpn/networking.h
@@ -0,0 +1,293 @@
+/*
+ *  Generic interface to platform specific networking code
+ *
+ *  Copyright (C) 2016-2018 Antonio Quartulli <a@unstable.cc>
+ *
+ *  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_H_
+#define NETWORKING_H_
+
+#include "syshead.h"
+
+struct context;
+
+#ifdef ENABLE_SITNL
+#include "networking_sitnl.h"
+#elif ENABLE_IPROUTE
+#include "networking_iproute2.h"
+#else
+/* define mock types to ensure code builds on any platform */
+typedef void *openvpn_net_ctx_t;
+typedef void *openvpn_net_iface_t;
+
+static inline int
+net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)
+{
+    return 0;
+}
+
+static inline void
+net_ctx_reset(openvpn_net_ctx_t *ctx)
+{
+    (void)ctx;
+}
+
+static inline void
+net_ctx_free(openvpn_net_ctx_t *ctx)
+{
+    (void)ctx;
+}
+#endif /* ifdef ENABLE_SITNL */
+
+#if defined(ENABLE_SITNL) || defined(ENABLE_IPROUTE)
+
+/**
+ * Initialize the platform specific context object
+ *
+ * @param c         openvpn generic context
+ * @param ctx       the implementation specific context to initialize
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx);
+
+/**
+ * Release resources allocated by the internal garbage collector
+ *
+ * @param ctx       the implementation specific context
+ */
+void net_ctx_reset(openvpn_net_ctx_t *ctx);
+
+/**
+ * Release all resources allocated within the platform specific context object
+ *
+ * @param ctx       the implementation specific context to release
+ */
+void net_ctx_free(openvpn_net_ctx_t *ctx);
+
+/**
+ * Bring interface up or down.
+ *
+ * @param ctx       the implementation specific context
+ * @param iface     the interface to modify
+ * @param up        true if the interface has to be brought up, false otherwise
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_iface_up(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface,
+                 bool up);
+
+/**
+ * Set the MTU for an interface
+ *
+ * @param ctx       the implementation specific context
+ * @param iface     the interface to modify
+ * @param mtru      the new MTU
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_iface_mtu_set(openvpn_net_ctx_t *ctx,
+                      const openvpn_net_iface_t *iface, uint32_t mtu);
+
+/**
+ * Add an IPv4 address to an interface
+ *
+ * @param ctx       the implementation specific context
+ * @param iface     the interface where the address has to be added
+ * @param addr      the address to add
+ * @param prefixlen the prefix length of the network associated with the address
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_addr_v4_add(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface,
+                    const in_addr_t *addr, int prefixlen);
+
+/**
+ * Add an IPv6 address to an interface
+ *
+ * @param ctx       the implementation specific context
+ * @param iface     the interface where the address has to be added
+ * @param addr      the address to add
+ * @param prefixlen the prefix length of the network associated with the address
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+
+int net_addr_v6_add(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface,
+                    const struct in6_addr *addr, int prefixlen);
+
+/**
+ * Remove an IPv4 from an interface
+ *
+ * @param ctx       the implementation specific context
+ * @param iface     the interface to remove the address from
+ * @param prefixlen the prefix length of the network associated with the address
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_addr_v4_del(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface,
+                    const in_addr_t *addr, int prefixlen);
+
+/**
+ * Remove an IPv6 from an interface
+ *
+ * @param ctx       the implementation specific context
+ * @param iface     the interface to remove the address from
+ * @param prefixlen the prefix length of the network associated with the address
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_addr_v6_del(openvpn_net_ctx_t *ctx, const openvpn_net_iface_t *iface,
+                    const struct in6_addr *addr, int prefixlen);
+
+/**
+ * Add a point-to-point IPv4 address to an interface
+ *
+ * @param ctx       the implementation specific context
+ * @param iface     the interface where the address has to be added
+ * @param local     the address to add
+ * @param remote    the associated p-t-p remote address
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_addr_ptp_v4_add(openvpn_net_ctx_t *ctx,
+                        const openvpn_net_iface_t *iface,
+                        const in_addr_t *local, const in_addr_t *remote);
+
+/**
+ * Remove a point-to-point IPv4 address from an interface
+ *
+ * @param ctx       the implementation specific context
+ * @param iface     the interface to remove the address from
+ * @param local     the address to remove
+ * @param remote    the associated p-t-p remote address
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_addr_ptp_v4_del(openvpn_net_ctx_t *ctx,
+                        const openvpn_net_iface_t *iface,
+                        const in_addr_t *local, const in_addr_t *remote);
+
+
+/**
+ * Add a route for an IPv4 address/network
+ *
+ * @param ctx       the implementation specific context
+ * @param dst       the destination of the route
+ * @param prefixlen the length of the prefix of the destination
+ * @param gw        the gateway for this route
+ * @param iface     the interface for this route (can be NULL)
+ * @param table     the table to add this route to (if 0, will be added to the
+ *                  main table)
+ * @param metric    the metric associated with the route
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_route_v4_add(openvpn_net_ctx_t *ctx, const in_addr_t *dst,
+                     int prefixlen, const in_addr_t *gw,
+                     const openvpn_net_iface_t *iface, uint32_t table,
+                     int metric);
+
+/**
+ * Add a route for an IPv6 address/network
+ *
+ * @param ctx       the implementation specific context
+ * @param dst       the destination of the route
+ * @param prefixlen the length of the prefix of the destination
+ * @param gw        the gateway for this route
+ * @param iface     the interface for this route (can be NULL)
+ * @param table     the table to add this route to (if 0, will be added to the
+ *                  main table)
+ * @param metric    the metric associated with the route
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_route_v6_add(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+                     int prefixlen, const struct in6_addr *gw,
+                     const openvpn_net_iface_t *iface,
+                     uint32_t table, int metric);
+
+/**
+ * Delete a route for an IPv4 address/network
+ *
+ * @param ctx       the implementation specific context
+ * @param dst       the destination of the route
+ * @param prefixlen the length of the prefix of the destination
+ * @param gw        the gateway for this route
+ * @param iface     the interface for this route (can be NULL)
+ * @param table     the table to add this route to (if 0, will be added to the
+ *                  main table)
+ * @param metric    the metric associated with the route
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_route_v4_del(openvpn_net_ctx_t *ctx, const in_addr_t *dst,
+                     int prefixlen, const in_addr_t *gw,
+                     const openvpn_net_iface_t *iface, uint32_t table,
+                     int metric);
+
+/**
+ * Delete a route for an IPv4 address/network
+ *
+ * @param ctx       the implementation specific context
+ * @param dst       the destination of the route
+ * @param prefixlen the length of the prefix of the destination
+ * @param gw        the gateway for this route
+ * @param iface     the interface for this route (can be NULL)
+ * @param table     the table to add this route to (if 0, will be added to the
+ *                  main table)
+ * @param metric    the metric associated with the route
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int net_route_v6_del(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+                     int prefixlen, const struct in6_addr *gw,
+                     const openvpn_net_iface_t *iface,
+                     uint32_t table, int metric);
+
+/**
+ * Retrieve the gateway and outgoing interface for the specified IPv4
+ * address/network
+ *
+ * @param ctx           the implementation specific context
+ * @param dst           The destination to lookup
+ * @param best_gw       Location where the retrieved GW has to be stored
+ * @param best_iface    Location where the retrieved interface has to be stored
+ *
+ * @return              0 on success, a negative error code otherwise
+ */
+int net_route_v4_best_gw(openvpn_net_ctx_t *ctx, const in_addr_t *dst,
+                         in_addr_t *best_gw, openvpn_net_iface_t *best_iface);
+
+/**
+ * Retrieve the gateway and outgoing interface for the specified IPv6
+ * address/network
+ *
+ * @param ctx           the implementation specific context
+ * @param dst           The destination to lookup
+ * @param best_gw       Location where the retrieved GW has to be stored
+ * @param best_iface    Location where the retrieved interface has to be stored
+ *
+ * @return              0 on success, a negative error code otherwise
+ */
+int net_route_v6_best_gw(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+                         struct in6_addr *best_gw,
+                         openvpn_net_iface_t *best_iface);
+
+#endif /* ENABLE_SITNL || ENABLE_IPROUTE */
+
+#endif /* NETWORKING_H_ */
diff --git a/src/openvpn/networking_iproute2.c b/src/openvpn/networking_iproute2.c
new file mode 100644
index 0000000..3b46052
--- /dev/null
+++ b/src/openvpn/networking_iproute2.c
@@ -0,0 +1,384 @@
+/*
+ *  Networking API implementation for iproute2
+ *
+ *  Copyright (C) 2018 Antonio Quartulli <a@unstable.cc>
+ *
+ *  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(TARGET_LINUX) && defined(ENABLE_IPROUTE)
+
+#include "syshead.h"
+
+#include "argv.h"
+#include "networking.h"
+#include "misc.h"
+#include "openvpn.h"
+#include "run_command.h"
+#include "socket.h"
+
+#include <stdbool.h>
+#include <netinet/in.h>
+
+int
+net_ctx_init(struct context *c, openvpn_net_ctx_t *ctx)
+{
+    ctx->es = NULL;
+    if (c)
+    {
+        ctx->es = c->es;
+    }
+    ctx->gc = gc_new();
+
+    return 0;
+}
+
+void
+net_ctx_reset(openvpn_net_ctx_t *ctx)
+{
+    gc_reset(&ctx->gc);
+}
+
+void
+net_ctx_free(openvpn_net_ctx_t *ctx)
+{
+    gc_free(&ctx->gc);
+}
+
+int
+net_iface_up(openvpn_net_ctx_t *ctx, const char *iface, bool up)
+{
+    struct argv argv = argv_new();
+
+    argv_printf(&argv, "%s link set dev %s %s", iproute_path, iface,
+                up ? "up" : "down");
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip link set failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_iface_mtu_set(openvpn_net_ctx_t *ctx, const char *iface, uint32_t mtu)
+{
+    struct argv argv = argv_new();
+
+    argv_printf(&argv, "%s link set dev %s up mtu %d", iproute_path, iface,
+                mtu);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip link set failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_addr_v4_add(openvpn_net_ctx_t *ctx, const char *iface,
+                const in_addr_t *addr, int prefixlen)
+{
+    struct argv argv = argv_new();
+
+    const char *addr_str = print_in_addr_t(*addr, 0, &ctx->gc);
+
+    argv_printf(&argv, "%s addr add dev %s %s/%d", iproute_path, iface,
+                addr_str, prefixlen);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip addr add failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_addr_v6_add(openvpn_net_ctx_t *ctx, const char *iface,
+                const struct in6_addr *addr, int prefixlen)
+{
+    struct argv argv = argv_new();
+    char *addr_str = (char *)print_in6_addr(*addr, 0, &ctx->gc);
+
+    argv_printf(&argv, "%s -6 addr add %s/%d dev %s", iproute_path, addr_str,
+                prefixlen, iface);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, S_FATAL,
+                         "Linux ip -6 addr add failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_addr_v4_del(openvpn_net_ctx_t *ctx, const char *iface,
+                const in_addr_t *addr, int prefixlen)
+{
+    struct argv argv = argv_new();
+    const char *addr_str = print_in_addr_t(*addr, 0, &ctx->gc);
+
+    argv_printf(&argv, "%s addr del dev %s %s/%d", iproute_path, iface,
+                addr_str, prefixlen);
+
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "Linux ip addr del failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_addr_v6_del(openvpn_net_ctx_t *ctx, const char *iface,
+                const struct in6_addr *addr, int prefixlen)
+{
+    struct argv argv = argv_new();
+    char *addr_str = (char *)print_in6_addr(*addr, 0, &ctx->gc);
+
+    argv_printf(&argv, "%s -6 addr del %s/%d dev %s", iproute_path,
+                addr_str, prefixlen, iface);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "Linux ip -6 addr del failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_addr_ptp_v4_add(openvpn_net_ctx_t *ctx, const char *iface,
+                    const in_addr_t *local, const in_addr_t *remote)
+{
+    struct argv argv = argv_new();
+    const char *local_str = print_in_addr_t(*local, 0, &ctx->gc);
+    const char *remote_str = print_in_addr_t(*remote, 0, &ctx->gc);
+
+    argv_printf(&argv, "%s addr add dev %s local %s peer %s", iproute_path,
+                iface, local_str, remote_str);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, S_FATAL, "Linux ip addr add failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_addr_ptp_v4_del(openvpn_net_ctx_t *ctx, const char *iface,
+                    const in_addr_t *local, const in_addr_t *remote)
+{
+    struct argv argv = argv_new();
+    const char *local_str = print_in_addr_t(*local, 0, &ctx->gc);
+    const char *remote_str = print_in_addr_t(*remote, 0, &ctx->gc);
+
+    argv_printf(&argv, "%s addr del dev %s local %s peer %s", iproute_path,
+                iface, local_str, remote_str);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "Linux ip addr del failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_route_v4_add(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen,
+                 const in_addr_t *gw, const char *iface, uint32_t table,
+                 int metric)
+{
+    struct argv argv = argv_new();
+    const char *dst_str = print_in_addr_t(*dst, 0, &ctx->gc);
+
+    argv_printf(&argv, "%s route add %s/%d", iproute_path, dst_str, prefixlen);
+
+    if (metric > 0)
+    {
+        argv_printf_cat(&argv, "metric %d", metric);
+    }
+
+    if (iface)
+    {
+        argv_printf_cat(&argv, "dev %s", iface);
+    }
+
+    if (gw)
+    {
+        const char *gw_str = print_in_addr_t(*gw, 0, &ctx->gc);
+
+        argv_printf_cat(&argv, "via %s", gw_str);
+    }
+
+    argv_msg(D_ROUTE, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "ERROR: Linux route add command failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_route_v6_add(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+                 int prefixlen, const struct in6_addr *gw, const char *iface,
+                 uint32_t table, int metric)
+{
+    struct argv argv = argv_new();
+    char *dst_str = (char *)print_in6_addr(*dst, 0, &ctx->gc);
+
+    argv_printf(&argv, "%s -6 route add %s/%d dev %s", iproute_path, dst_str,
+                prefixlen, iface);
+
+    if (gw)
+    {
+        char *gw_str = (char *)print_in6_addr(*gw, 0, &ctx->gc);
+
+        argv_printf_cat(&argv, "via %s", gw_str);
+    }
+
+    if (metric > 0)
+    {
+        argv_printf_cat(&argv, "metric %d", metric);
+    }
+
+    argv_msg(D_ROUTE, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "ERROR: Linux route -6 add command failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_route_v4_del(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen,
+                 const in_addr_t *gw, const char *iface, uint32_t table,
+                 int metric)
+{
+    struct argv argv = argv_new();
+    const char *dst_str = print_in_addr_t(*dst, 0, &ctx->gc);
+
+    argv_printf(&argv, "%s route del %s/%d", iproute_path, dst_str, prefixlen);
+
+    if (metric > 0)
+    {
+        argv_printf_cat(&argv, "metric %d", metric);
+    }
+
+    argv_msg(D_ROUTE, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "ERROR: Linux route delete command failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_route_v6_del(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+                 int prefixlen, const struct in6_addr *gw, const char *iface,
+                 uint32_t table, int metric)
+{
+    struct argv argv = argv_new();
+    char *dst_str = (char *)print_in6_addr(*dst, 0, &ctx->gc);
+
+    argv_printf(&argv, "%s -6 route del %s/%d dev %s", iproute_path, dst_str,
+                prefixlen, iface);
+
+    if (gw)
+    {
+        char *gw_str = (char *)print_in6_addr(*gw, 0, &ctx->gc);
+
+        argv_printf_cat(&argv, "via %s", gw_str);
+    }
+
+    if (metric > 0)
+    {
+        argv_printf_cat(&argv, "metric %d", metric);
+    }
+
+    argv_msg(D_ROUTE, &argv);
+    openvpn_execve_check(&argv, ctx->es, 0, "ERROR: Linux route -6 del command failed");
+
+    argv_free(&argv);
+
+    return 0;
+}
+
+int
+net_route_v4_best_gw(openvpn_net_ctx_t *ctx, const in_addr_t *dst,
+                     in_addr_t *best_gw, char *best_iface)
+{
+    best_iface[0] = '\0';
+
+    FILE *fp = fopen("/proc/net/route", "r");
+    if (!fp)
+    {
+        return -1;
+    }
+
+    char line[256];
+    int count = 0;
+    unsigned int lowest_metric = UINT_MAX;
+    while (fgets(line, sizeof(line), fp) != NULL)
+    {
+        if (count)
+        {
+            unsigned int net_x = 0;
+            unsigned int mask_x = 0;
+            unsigned int gw_x = 0;
+            unsigned int metric = 0;
+            unsigned int flags = 0;
+            char name[16];
+            name[0] = '\0';
+
+            const int np = sscanf(line, "%15s\t%x\t%x\t%x\t%*s\t%*s\t%d\t%x",
+                                  name, &net_x, &gw_x, &flags, &metric,
+                                  &mask_x);
+
+            if (np == 6 && (flags & IFF_UP))
+            {
+                const in_addr_t net = ntohl(net_x);
+                const in_addr_t mask = ntohl(mask_x);
+                const in_addr_t gw = ntohl(gw_x);
+
+                if (!net && !mask && metric < lowest_metric)
+                {
+                    *best_gw = gw;
+                    strcpy(best_iface, name);
+                    lowest_metric = metric;
+                }
+            }
+        }
+        ++count;
+    }
+    fclose(fp);
+
+    return 0;
+}
+
+/*
+ * The following function is not implemented in the iproute backend as it
+ * uses the sitnl implementation from networking_sitnl.c.
+ *
+ * int
+ * net_route_v6_best_gw(const struct in6_addr *dst,
+ *                      struct in6_addr *best_gw, char *best_iface)
+ */
+
+#endif /* ENABLE_IPROUTE && TARGET_LINUX */
diff --git a/src/openvpn/networking_iproute2.h b/src/openvpn/networking_iproute2.h
new file mode 100644
index 0000000..24c605d
--- /dev/null
+++ b/src/openvpn/networking_iproute2.h
@@ -0,0 +1,37 @@
+/*
+ *  Generic interface to platform specific networking code
+ *
+ *  Copyright (C) 2016-2018 Antonio Quartulli <a@unstable.cc>
+ *
+ *  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_IP_H_
+#define NETWORKING_IP_H_
+
+#include "env_set.h"
+
+typedef char openvpn_net_iface_t;
+
+struct openvpn_net_ctx
+{
+    struct env_set *es;
+    struct gc_arena gc;
+};
+
+typedef struct openvpn_net_ctx openvpn_net_ctx_t;
+
+#endif /* NETWORKING_IP_H_ */
diff --git a/src/openvpn/networking_sitnl.c b/src/openvpn/networking_sitnl.c
new file mode 100644
index 0000000..ea1621e
--- /dev/null
+++ b/src/openvpn/networking_sitnl.c
@@ -0,0 +1,1304 @@
+/*
+ *  Simplified Interface To NetLink
+ *
+ *  Copyright (C) 2016-2018 Antonio Quartulli <a@unstable.cc>
+ *
+ *  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
+
+#ifdef TARGET_LINUX
+
+#include "syshead.h"
+
+#include "errlevel.h"
+#include "buffer.h"
+#include "networking.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#define SNDBUF_SIZE (1024 * 2)
+#define RCVBUF_SIZE (1024 * 4)
+
+#define SITNL_ADDATTR(_msg, _max_size, _attr, _data, _size)         \
+    {                                                               \
+        if (sitnl_addattr(_msg, _max_size, _attr, _data, _size) < 0) \
+        {                                                           \
+            goto err;                                               \
+        }                                                           \
+    }
+
+#define NLMSG_TAIL(nmsg) \
+    ((struct rtattr *)(((uint8_t *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+/**
+ * Generic address data structure used to pass addresses and prefixes as
+ * argument to AF family agnostic functions
+ */
+typedef union {
+    in_addr_t ipv4;
+    struct in6_addr ipv6;
+} inet_address_t;
+
+/**
+ * Link state request message
+ */
+struct sitnl_link_req {
+    struct nlmsghdr n;
+    struct ifinfomsg i;
+    char buf[256];
+};
+
+/**
+ * Address request message
+ */
+struct sitnl_addr_req {
+    struct nlmsghdr n;
+    struct ifaddrmsg i;
+    char buf[256];
+};
+
+/**
+ * Route request message
+ */
+struct sitnl_route_req {
+    struct nlmsghdr n;
+    struct rtmsg r;
+    char buf[256];
+};
+
+typedef int (*sitnl_parse_reply_cb)(struct nlmsghdr *msg, void *arg);
+
+/**
+ * Object returned by route request operation
+ */
+struct sitnl_route_data_cb {
+    unsigned int iface;
+    inet_address_t gw;
+};
+
+/**
+ * Helper function used to easily add attributes to a rtnl message
+ */
+static int
+sitnl_addattr(struct nlmsghdr *n, int maxlen, int type, const void *data,
+              int alen)
+{
+    int len = RTA_LENGTH(alen);
+    struct rtattr *rta;
+
+    if ((int)(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen)
+    {
+        msg(M_WARN, "%s: rtnl: message exceeded bound of %d", __func__,
+            maxlen);
+        return -EMSGSIZE;
+    }
+
+    rta = NLMSG_TAIL(n);
+    rta->rta_type = type;
+    rta->rta_len = len;
+
+    if (!data)
+    {
+        memset(RTA_DATA(rta), 0, alen);
+    }
+    else
+    {
+        memcpy(RTA_DATA(rta), data, alen);
+    }
+
+    n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
+
+    return 0;
+}
+
+/**
+ * Open RTNL socket
+ */
+static int
+sitnl_socket(void)
+{
+    int sndbuf = SNDBUF_SIZE;
+    int rcvbuf = RCVBUF_SIZE;
+    int fd;
+
+    fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+    if (fd < 0)
+    {
+        msg(M_WARN, "%s: cannot open netlink socket", __func__);
+        return fd;
+    }
+
+    if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0)
+    {
+        msg(M_WARN | M_ERRNO, "%s: SO_SNDBUF", __func__);
+        close(fd);
+        return -1;
+    }
+
+    if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0)
+    {
+        msg(M_WARN | M_ERRNO, "%s: SO_RCVBUF", __func__);
+        close(fd);
+        return -1;
+    }
+
+    return fd;
+}
+
+/**
+ * Bind socket to Netlink subsystem
+ */
+static int
+sitnl_bind(int fd, uint32_t groups)
+{
+    socklen_t addr_len;
+    struct sockaddr_nl local;
+
+    CLEAR(local);
+
+    local.nl_family = AF_NETLINK;
+    local.nl_groups = groups;
+
+    if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0)
+    {
+        msg(M_WARN | M_ERRNO, "%s: cannot bind netlink socket", __func__);
+        return -errno;
+    }
+
+    addr_len = sizeof(local);
+    if (getsockname(fd, (struct sockaddr *)&local, &addr_len) < 0)
+    {
+        msg(M_WARN | M_ERRNO, "%s: cannot getsockname", __func__);
+        return -errno;
+    }
+
+    if (addr_len != sizeof(local))
+    {
+        msg(M_WARN, "%s: wrong address length %d", __func__, addr_len);
+        return -EINVAL;
+    }
+
+    if (local.nl_family != AF_NETLINK)
+    {
+        msg(M_WARN, "%s: wrong address family %d", __func__, local.nl_family);
+        return -EINVAL;
+    }
+
+    return 0;
+}
+
+/**
+ * Send Netlink message and run callback on reply (if specified)
+ */
+static int
+sitnl_send(struct nlmsghdr *payload, pid_t peer, unsigned int groups,
+           sitnl_parse_reply_cb cb, void *arg_cb)
+{
+    int len, rem_len, fd, ret, rcv_len;
+    struct sockaddr_nl nladdr;
+    struct nlmsgerr *err;
+    struct nlmsghdr *h;
+    unsigned int seq;
+    char buf[1024 * 16];
+    struct iovec iov =
+    {
+        .iov_base = payload,
+        .iov_len = payload->nlmsg_len,
+    };
+    struct msghdr nlmsg =
+    {
+        .msg_name = &nladdr,
+        .msg_namelen = sizeof(nladdr),
+        .msg_iov = &iov,
+        .msg_iovlen = 1,
+    };
+
+    CLEAR(nladdr);
+
+    nladdr.nl_family = AF_NETLINK;
+    nladdr.nl_pid = peer;
+    nladdr.nl_groups = groups;
+
+    payload->nlmsg_seq = seq = time(NULL);
+
+    /* no need to send reply */
+    if (!cb)
+    {
+        payload->nlmsg_flags |= NLM_F_ACK;
+    }
+
+    fd = sitnl_socket();
+    if (fd < 0)
+    {
+        msg(M_WARN | M_ERRNO, "%s: can't open rtnl socket", __func__);
+        return -errno;
+    }
+
+    ret = sitnl_bind(fd, 0);
+    if (ret < 0)
+    {
+        msg(M_WARN | M_ERRNO, "%s: can't bind rtnl socket", __func__);
+        ret = -errno;
+        goto out;
+    }
+
+    ret = sendmsg(fd, &nlmsg, 0);
+    if (ret < 0)
+    {
+        msg(M_WARN | M_ERRNO, "%s: rtnl: error on sendmsg()", __func__);
+        ret = -errno;
+        goto out;
+    }
+
+    /* prepare buffer to store RTNL replies */
+    memset(buf, 0, sizeof(buf));
+    iov.iov_base = buf;
+
+    while (1)
+    {
+        /*
+         * iov_len is modified by recvmsg(), therefore has to be initialized before
+         * using it again
+         */
+        msg(D_RTNL, "%s: checking for received messages", __func__);
+        iov.iov_len = sizeof(buf);
+        rcv_len = recvmsg(fd, &nlmsg, 0);
+        msg(D_RTNL, "%s: rtnl: received %d bytes", __func__, rcv_len);
+        if (rcv_len < 0)
+        {
+            if ((errno == EINTR) || (errno == EAGAIN))
+            {
+                msg(D_RTNL, "%s: interrupted call", __func__);
+                continue;
+            }
+            msg(M_WARN | M_ERRNO, "%s: rtnl: error on recvmsg()", __func__);
+            ret = -errno;
+            goto out;
+        }
+
+        if (rcv_len == 0)
+        {
+            msg(M_WARN, "%s: rtnl: socket reached unexpected EOF", __func__);
+            ret = -EIO;
+            goto out;
+        }
+
+        if (nlmsg.msg_namelen != sizeof(nladdr))
+        {
+            msg(M_WARN, "%s: sender address length: %u (expected %zu)",
+                __func__, nlmsg.msg_namelen, sizeof(nladdr));
+            ret = -EIO;
+            goto out;
+        }
+
+        h = (struct nlmsghdr *)buf;
+        while (rcv_len >= (int)sizeof(*h))
+        {
+            len = h->nlmsg_len;
+            rem_len = len - sizeof(*h);
+
+            if ((rem_len < 0) || (len > rcv_len))
+            {
+                if (nlmsg.msg_flags & MSG_TRUNC)
+                {
+                    msg(M_WARN, "%s: truncated message", __func__);
+                    ret = -EIO;
+                    goto out;
+                }
+                msg(M_WARN, "%s: malformed message: len=%d", __func__, len);
+                ret = -EIO;
+                goto out;
+            }
+
+/*            if (((int)nladdr.nl_pid != peer) || (h->nlmsg_pid != nladdr.nl_pid)
+ *               || (h->nlmsg_seq != seq))
+ *           {
+ *               rcv_len -= NLMSG_ALIGN(len);
+ *               h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
+ *               msg(M_DEBUG, "%s: skipping unrelated message. nl_pid:%d (peer:%d) nl_msg_pid:%d nl_seq:%d seq:%d",
+ *                   __func__, (int)nladdr.nl_pid, peer, h->nlmsg_pid,
+ *                   h->nlmsg_seq, seq);
+ *               continue;
+ *           }
+ */
+
+            if (h->nlmsg_type == NLMSG_DONE)
+            {
+                ret = 0;
+                goto out;
+            }
+
+            if (h->nlmsg_type == NLMSG_ERROR)
+            {
+                err = (struct nlmsgerr *)NLMSG_DATA(h);
+                if (rem_len < (int)sizeof(struct nlmsgerr))
+                {
+                    msg(M_WARN, "%s: ERROR truncated", __func__);
+                    ret = -EIO;
+                }
+                else
+                {
+                    if (!err->error)
+                    {
+                        ret = 0;
+                        if (cb)
+                        {
+                            int r = cb(h, arg_cb);
+                            if (r <= 0)
+                            {
+                                ret = r;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        msg(M_WARN, "%s: rtnl: generic error (%d): %s",
+                            __func__, err->error, strerror(-err->error));
+                        ret = err->error;
+                    }
+                }
+                goto out;
+            }
+
+            if (cb)
+            {
+                int r = cb(h, arg_cb);
+                if (r <= 0)
+                {
+                    ret = r;
+                    goto out;
+                }
+            }
+            else
+            {
+                msg(M_WARN, "%s: RTNL: unexpected reply", __func__);
+            }
+
+            rcv_len -= NLMSG_ALIGN(len);
+            h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
+        }
+
+        if (nlmsg.msg_flags & MSG_TRUNC)
+        {
+            msg(M_WARN, "%s: message truncated", __func__);
+            continue;
+        }
+
+        if (rcv_len)
+        {
+            msg(M_WARN, "%s: rtnl: %d not parsed bytes", __func__, rcv_len);
+            ret = -1;
+            goto out;
+        }
+    }
+out:
+    close(fd);
+
+    return ret;
+}
+
+typedef struct {
+    int addr_size;
+    inet_address_t gw;
+    char iface[IFNAMSIZ];
+    bool default_only;
+    unsigned int table;
+} route_res_t;
+
+static int
+sitnl_route_save(struct nlmsghdr *n, void *arg)
+{
+    route_res_t *res = arg;
+    struct rtmsg *r = NLMSG_DATA(n);
+    struct rtattr *rta = RTM_RTA(r);
+    int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
+    unsigned int table, ifindex = 0;
+    void *gw = NULL;
+
+    /* filter-out non-zero dst prefixes */
+    if (res->default_only && r->rtm_dst_len != 0)
+    {
+        return 1;
+    }
+
+    /* route table, ignored with RTA_TABLE */
+    table = r->rtm_table;
+
+    while (RTA_OK(rta, len))
+    {
+        switch (rta->rta_type)
+        {
+            /* route interface */
+            case RTA_OIF:
+                ifindex = *(unsigned int *)RTA_DATA(rta);
+                break;
+
+            /* route prefix */
+            case RTA_DST:
+                break;
+
+            /* GW for the route */
+            case RTA_GATEWAY:
+                gw = RTA_DATA(rta);
+                break;
+
+            /* route table */
+            case RTA_TABLE:
+                table = *(unsigned int *)RTA_DATA(rta);
+                break;
+        }
+
+        rta = RTA_NEXT(rta, len);
+    }
+
+    /* filter out any route not coming from the selected table */
+    if (res->table && res->table != table)
+    {
+        return 1;
+    }
+
+    if (!if_indextoname(ifindex, res->iface))
+    {
+        msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifname for index %d",
+            __func__, ifindex);
+        return -1;
+    }
+
+    if (gw)
+    {
+        memcpy(&res->gw, gw, res->addr_size);
+    }
+
+    return 0;
+}
+
+static int
+sitnl_route_best_gw(sa_family_t af_family, const inet_address_t *dst,
+                    void *best_gw, char *best_iface)
+{
+    struct sitnl_route_req req;
+    route_res_t res;
+    int ret = -EINVAL;
+
+    ASSERT(best_gw);
+    ASSERT(best_iface);
+
+    CLEAR(req);
+    CLEAR(res);
+
+    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.r));
+    req.n.nlmsg_type = RTM_GETROUTE;
+    req.n.nlmsg_flags = NLM_F_REQUEST;
+
+    req.r.rtm_family = af_family;
+
+    switch (af_family)
+    {
+        case AF_INET:
+            res.addr_size = sizeof(in_addr_t);
+            /*
+             * kernel can't return 0.0.0.0/8 host route, dump all
+             * the routes and filter for 0.0.0.0/0 in cb()
+             */
+            if (!dst || !dst->ipv4)
+            {
+                req.n.nlmsg_flags |= NLM_F_DUMP;
+                res.default_only = true;
+                res.table = RT_TABLE_MAIN;
+            }
+            else
+            {
+                req.r.rtm_dst_len = 32;
+            }
+            break;
+
+        case AF_INET6:
+            res.addr_size = sizeof(struct in6_addr);
+            /* kernel can return ::/128 host route */
+            req.r.rtm_dst_len = 128;
+            break;
+
+        default:
+            /* unsupported */
+            return -EINVAL;
+    }
+
+    SITNL_ADDATTR(&req.n, sizeof(req), RTA_DST, dst, res.addr_size);
+
+    ret = sitnl_send(&req.n, 0, 0, sitnl_route_save, &res);
+    if (ret < 0)
+    {
+        goto err;
+    }
+
+    /* save result in output variables */
+    memcpy(best_gw, &res.gw, res.addr_size);
+    strncpy(best_iface, res.iface, IFNAMSIZ);
+err:
+    return ret;
+
+}
+
+/* used by iproute2 implementation too */
+int
+net_route_v6_best_gw(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+                     struct in6_addr *best_gw, char *best_iface)
+{
+    inet_address_t dst_v6 = {0};
+    char buf[INET6_ADDRSTRLEN];
+    int ret;
+
+    if (dst)
+    {
+        dst_v6.ipv6 = *dst;
+    }
+
+    msg(D_ROUTE, "%s query: dst %s", __func__,
+        inet_ntop(AF_INET6, &dst_v6.ipv6, buf, sizeof(buf)));
+
+    ret = sitnl_route_best_gw(AF_INET6, &dst_v6, best_gw, best_iface);
+    if (ret < 0)
+    {
+        return ret;
+    }
+
+    msg(D_ROUTE, "%s result: via %s dev %s", __func__,
+        inet_ntop(AF_INET6, best_gw, buf, sizeof(buf)), best_iface);
+
+    return ret;
+
+}
+
+#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)
+{
+    inet_address_t dst_v4 = {0};
+    char buf[INET_ADDRSTRLEN];
+    int ret;
+
+    if (dst)
+    {
+        dst_v4.ipv4 = htonl(*dst);
+    }
+
+    msg(D_ROUTE, "%s query: dst %s", __func__,
+        inet_ntop(AF_INET, &dst_v4.ipv4, buf, sizeof(buf)));
+
+    ret = sitnl_route_best_gw(AF_INET, &dst_v4, best_gw, best_iface);
+    if (ret < 0)
+    {
+        return ret;
+    }
+
+    msg(D_ROUTE, "%s result: via %s dev %s", __func__,
+        inet_ntop(AF_INET, best_gw, buf, sizeof(buf)), best_iface);
+
+    /* result is expected in Host Order */
+    *best_gw = ntohl(*best_gw);
+
+    return ret;
+}
+
+int
+net_iface_up(openvpn_net_ctx_t *ctx, const char *iface, bool up)
+{
+    struct sitnl_link_req req;
+    int ifindex;
+
+    CLEAR(req);
+
+    if (!iface)
+    {
+        msg(M_WARN, "%s: passed NULL interface", __func__);
+        return -EINVAL;
+    }
+
+    ifindex = if_nametoindex(iface);
+    if (ifindex == 0)
+    {
+        msg(M_WARN, "%s: rtnl: cannot get ifindex for %s: %s", __func__, iface,
+            strerror(errno));
+        return -ENOENT;
+    }
+
+    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
+    req.n.nlmsg_flags = NLM_F_REQUEST;
+    req.n.nlmsg_type = RTM_NEWLINK;
+
+    req.i.ifi_family = AF_PACKET;
+    req.i.ifi_index = ifindex;
+    req.i.ifi_change |= IFF_UP;
+    if (up)
+    {
+        req.i.ifi_flags |= IFF_UP;
+    }
+    else
+    {
+        req.i.ifi_flags &= ~IFF_UP;
+    }
+
+    msg(M_INFO, "%s: set %s %s", __func__, iface, up ? "up" : "down");
+
+    return sitnl_send(&req.n, 0, 0, NULL, NULL);
+}
+
+int
+net_iface_mtu_set(openvpn_net_ctx_t *ctx, const char *iface,
+                  uint32_t mtu)
+{
+    struct sitnl_link_req req;
+    int ifindex, ret = -1;
+
+    CLEAR(req);
+
+    ifindex = if_nametoindex(iface);
+    if (ifindex == 0)
+    {
+        msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__,
+            iface);
+        return -1;
+    }
+
+    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
+    req.n.nlmsg_flags = NLM_F_REQUEST;
+    req.n.nlmsg_type = RTM_NEWLINK;
+
+    req.i.ifi_family = AF_PACKET;
+    req.i.ifi_index = ifindex;
+
+    SITNL_ADDATTR(&req.n, sizeof(req), IFLA_MTU, &mtu, 4);
+
+    msg(M_INFO, "%s: mtu %u for %s", __func__, mtu, iface);
+
+    ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+err:
+    return ret;
+}
+
+static int
+sitnl_addr_set(int cmd, uint32_t flags, int ifindex, sa_family_t af_family,
+               const inet_address_t *local, const inet_address_t *remote,
+               int prefixlen)
+{
+    struct sitnl_addr_req req;
+    uint32_t size;
+    int ret = -EINVAL;
+
+    CLEAR(req);
+
+    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i));
+    req.n.nlmsg_type = cmd;
+    req.n.nlmsg_flags = NLM_F_REQUEST | flags;
+
+    req.i.ifa_index = ifindex;
+    req.i.ifa_family = af_family;
+
+    switch (af_family)
+    {
+        case AF_INET:
+            size = sizeof(struct in_addr);
+            break;
+
+        case AF_INET6:
+            size = sizeof(struct in6_addr);
+            break;
+
+        default:
+            msg(M_WARN, "%s: rtnl: unknown address family %d", __func__,
+                af_family);
+            return -EINVAL;
+    }
+
+    /* if no prefixlen has been specified, assume host address */
+    if (prefixlen == 0)
+    {
+        prefixlen = size * 8;
+    }
+    req.i.ifa_prefixlen = prefixlen;
+
+    if (remote)
+    {
+        SITNL_ADDATTR(&req.n, sizeof(req), IFA_ADDRESS, remote, size);
+    }
+
+    if (local)
+    {
+        SITNL_ADDATTR(&req.n, sizeof(req), IFA_LOCAL, local, size);
+    }
+
+    ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+    if (ret == -EEXIST)
+    {
+        ret = 0;
+    }
+err:
+    return ret;
+}
+
+static int
+sitnl_addr_ptp_add(sa_family_t af_family, const char *iface,
+                   const inet_address_t *local,
+                   const inet_address_t *remote)
+{
+    int ifindex;
+
+    switch (af_family)
+    {
+        case AF_INET:
+        case AF_INET6:
+            break;
+
+        default:
+            return -EINVAL;
+    }
+
+    if (!iface)
+    {
+        msg(M_WARN, "%s: passed NULL interface", __func__);
+        return -EINVAL;
+    }
+
+    ifindex = if_nametoindex(iface);
+    if (ifindex == 0)
+    {
+        msg(M_WARN, "%s: cannot get ifindex for %s: %s", __func__, np(iface),
+            strerror(errno));
+        return -ENOENT;
+    }
+
+    return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, ifindex,
+                          af_family, local, remote, 0);
+}
+
+static int
+sitnl_addr_ptp_del(sa_family_t af_family, const char *iface,
+                   const inet_address_t *local)
+{
+    int ifindex;
+
+    switch (af_family)
+    {
+        case AF_INET:
+        case AF_INET6:
+            break;
+
+        default:
+            return -EINVAL;
+    }
+
+    if (!iface)
+    {
+        msg(M_WARN, "%s: passed NULL interface", __func__);
+        return -EINVAL;
+    }
+
+    ifindex = if_nametoindex(iface);
+    if (ifindex == 0)
+    {
+        msg(M_WARN | M_ERRNO, "%s: cannot get ifindex for %s", __func__, iface);
+        return -ENOENT;
+    }
+
+    return sitnl_addr_set(RTM_DELADDR, 0, ifindex, af_family, local, NULL, 0);
+}
+
+static int
+sitnl_route_set(int cmd, uint32_t flags, int ifindex, sa_family_t af_family,
+                const void *dst, int prefixlen,
+                const void *gw, enum rt_class_t table, int metric,
+                enum rt_scope_t scope, int protocol, int type)
+{
+    struct sitnl_route_req req;
+    int ret = -1, size;
+
+    CLEAR(req);
+
+    switch (af_family)
+    {
+        case AF_INET:
+            size = sizeof(in_addr_t);
+            break;
+
+        case AF_INET6:
+            size = sizeof(struct in6_addr);
+            break;
+
+        default:
+            return -EINVAL;
+    }
+
+    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.r));
+    req.n.nlmsg_type = cmd;
+    req.n.nlmsg_flags = NLM_F_REQUEST | flags;
+
+    req.r.rtm_family = af_family;
+    req.r.rtm_scope = scope;
+    req.r.rtm_protocol = protocol;
+    req.r.rtm_type = type;
+    req.r.rtm_dst_len = prefixlen;
+
+    if (table < 256)
+    {
+        req.r.rtm_table = table;
+    }
+    else
+    {
+        req.r.rtm_table = RT_TABLE_UNSPEC;
+        SITNL_ADDATTR(&req.n, sizeof(req), RTA_TABLE, &table, 4);
+    }
+
+    if (dst)
+    {
+        SITNL_ADDATTR(&req.n, sizeof(req), RTA_DST, dst, size);
+    }
+
+    if (gw)
+    {
+        SITNL_ADDATTR(&req.n, sizeof(req), RTA_GATEWAY, gw, size);
+    }
+
+    if (ifindex > 0)
+    {
+        SITNL_ADDATTR(&req.n, sizeof(req), RTA_OIF, &ifindex, 4);
+    }
+
+    if (metric > 0)
+    {
+        SITNL_ADDATTR(&req.n, sizeof(req), RTA_PRIORITY, &metric, 4);
+    }
+
+    ret = sitnl_send(&req.n, 0, 0, NULL, NULL);
+    if (ret == -EEXIST)
+    {
+        ret = 0;
+    }
+err:
+    return ret;
+}
+
+static int
+sitnl_addr_add(sa_family_t af_family, const char *iface,
+               const inet_address_t *addr, int prefixlen)
+{
+    int ifindex;
+
+    switch (af_family)
+    {
+        case AF_INET:
+        case AF_INET6:
+            break;
+
+        default:
+            return -EINVAL;
+    }
+
+    if (!iface)
+    {
+        msg(M_WARN, "%s: passed NULL interface", __func__);
+        return -EINVAL;
+    }
+
+    ifindex = if_nametoindex(iface);
+    if (ifindex == 0)
+    {
+        msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__,
+            iface);
+        return -ENOENT;
+    }
+
+    return sitnl_addr_set(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, ifindex,
+                          af_family, addr, NULL, prefixlen);
+}
+
+static int
+sitnl_addr_del(sa_family_t af_family, const char *iface, inet_address_t *addr,
+               int prefixlen)
+{
+    int ifindex;
+
+    switch (af_family)
+    {
+        case AF_INET:
+        case AF_INET6:
+            break;
+
+        default:
+            return -EINVAL;
+    }
+
+    if (!iface)
+    {
+        msg(M_WARN, "%s: passed NULL interface", __func__);
+        return -EINVAL;
+    }
+
+    ifindex = if_nametoindex(iface);
+    if (ifindex == 0)
+    {
+        msg(M_WARN | M_ERRNO, "%s: rtnl: cannot get ifindex for %s", __func__,
+            iface);
+        return -ENOENT;
+    }
+
+    return sitnl_addr_set(RTM_DELADDR, 0, ifindex, af_family, addr, NULL,
+                          prefixlen);
+}
+
+int
+net_addr_v4_add(openvpn_net_ctx_t *ctx, const char *iface,
+                const in_addr_t *addr, int prefixlen)
+{
+    inet_address_t addr_v4 = { 0 };
+    char buf[INET_ADDRSTRLEN];
+
+    if (!addr)
+    {
+        return -EINVAL;
+    }
+
+    addr_v4.ipv4 = htonl(*addr);
+
+    msg(M_INFO, "%s: %s/%d dev %s", __func__,
+        inet_ntop(AF_INET, &addr_v4.ipv4, buf, sizeof(buf)), prefixlen,iface);
+
+    return sitnl_addr_add(AF_INET, iface, &addr_v4, prefixlen);
+}
+
+int
+net_addr_v6_add(openvpn_net_ctx_t *ctx, const char *iface,
+                const struct in6_addr *addr, int prefixlen)
+{
+    inet_address_t addr_v6 = { 0 };
+    char buf[INET6_ADDRSTRLEN];
+
+    if (!addr)
+    {
+        return -EINVAL;
+    }
+
+    addr_v6.ipv6 = *addr;
+
+    msg(M_INFO, "%s: %s/%d dev %s", __func__,
+        inet_ntop(AF_INET6, &addr_v6.ipv6, buf, sizeof(buf)), prefixlen, iface);
+
+    return sitnl_addr_add(AF_INET6, iface, &addr_v6, prefixlen);
+}
+
+int
+net_addr_v4_del(openvpn_net_ctx_t *ctx, const char *iface,
+                const in_addr_t *addr, int prefixlen)
+{
+    inet_address_t addr_v4 = { 0 };
+    char buf[INET_ADDRSTRLEN];
+
+    if (!addr)
+    {
+        return -EINVAL;
+    }
+
+    addr_v4.ipv4 = htonl(*addr);
+
+    msg(M_INFO, "%s: %s dev %s", __func__,
+        inet_ntop(AF_INET, &addr_v4.ipv4, buf, sizeof(buf)), iface);
+
+    return sitnl_addr_del(AF_INET, iface, &addr_v4, prefixlen);
+}
+
+int
+net_addr_v6_del(openvpn_net_ctx_t *ctx, const char *iface,
+                const struct in6_addr *addr, int prefixlen)
+{
+    inet_address_t addr_v6 = { 0 };
+    char buf[INET6_ADDRSTRLEN];
+
+    if (!addr)
+    {
+        return -EINVAL;
+    }
+
+    addr_v6.ipv6 = *addr;
+
+    msg(M_INFO, "%s: %s/%d dev %s", __func__,
+        inet_ntop(AF_INET6, &addr_v6.ipv6, buf, sizeof(buf)), prefixlen, iface);
+
+    return sitnl_addr_del(AF_INET6, iface, &addr_v6, prefixlen);
+}
+
+int
+net_addr_ptp_v4_add(openvpn_net_ctx_t *ctx, const char *iface,
+                    const in_addr_t *local, const in_addr_t *remote)
+{
+    inet_address_t local_v4 = { 0 };
+    inet_address_t remote_v4 = { 0 };
+    char buf1[INET_ADDRSTRLEN];
+    char buf2[INET_ADDRSTRLEN];
+
+    if (!local)
+    {
+        return -EINVAL;
+    }
+
+    local_v4.ipv4 = htonl(*local);
+
+    if (remote)
+    {
+        remote_v4.ipv4 = htonl(*remote);
+    }
+
+    msg(M_INFO, "%s: %s peer %s dev %s", __func__,
+        inet_ntop(AF_INET, &local_v4.ipv4, buf1, sizeof(buf1)),
+        inet_ntop(AF_INET, &remote_v4.ipv4, buf2, sizeof(buf2)), iface);
+
+    return sitnl_addr_ptp_add(AF_INET, iface, &local_v4, &remote_v4);
+}
+
+int
+net_addr_ptp_v4_del(openvpn_net_ctx_t *ctx, const char *iface,
+                    const in_addr_t *local, const in_addr_t *remote)
+{
+    inet_address_t local_v4 = { 0 };
+    char buf[INET6_ADDRSTRLEN];
+
+
+    if (!local)
+    {
+        return -EINVAL;
+    }
+
+    local_v4.ipv4 = htonl(*local);
+
+    msg(M_INFO, "%s: %s dev %s", __func__,
+        inet_ntop(AF_INET, &local_v4.ipv4, buf, sizeof(buf)), iface);
+
+    return sitnl_addr_ptp_del(AF_INET, iface, &local_v4);
+}
+
+static int
+sitnl_route_add(const char *iface, sa_family_t af_family, const void *dst,
+                int prefixlen, const void *gw, uint32_t table, int metric)
+{
+    enum rt_scope_t scope = RT_SCOPE_UNIVERSE;
+    int ifindex = 0;
+
+    if (iface)
+    {
+        ifindex = if_nametoindex(iface);
+        if (ifindex == 0)
+        {
+            msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifindex for %s",
+                __func__, iface);
+            return -ENOENT;
+        }
+    }
+
+    if (table == 0)
+    {
+        table = RT_TABLE_MAIN;
+    }
+
+    if (!gw && iface)
+    {
+        scope = RT_SCOPE_LINK;
+    }
+
+    return sitnl_route_set(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_REPLACE, ifindex,
+                           af_family, dst, prefixlen, gw, table, metric, scope,
+                           RTPROT_BOOT, RTN_UNICAST);
+}
+
+int
+net_route_v4_add(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen,
+                 const in_addr_t *gw, const char *iface,
+                 uint32_t table, int metric)
+{
+    in_addr_t *dst_ptr = NULL, *gw_ptr = NULL;
+    in_addr_t dst_be = 0, gw_be = 0;
+    char dst_str[INET_ADDRSTRLEN];
+    char gw_str[INET_ADDRSTRLEN];
+
+    if (dst)
+    {
+        dst_be = htonl(*dst);
+        dst_ptr = &dst_be;
+    }
+
+    if (gw)
+    {
+        gw_be = htonl(*gw);
+        gw_ptr = &gw_be;
+    }
+
+    msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
+        inet_ntop(AF_INET, &dst_be, dst_str, sizeof(dst_str)),
+        prefixlen, inet_ntop(AF_INET, &gw_be, gw_str, sizeof(gw_str)),
+        np(iface), table, metric);
+
+    return sitnl_route_add(iface, AF_INET, dst_ptr, prefixlen, gw_ptr, table,
+                           metric);
+}
+
+int
+net_route_v6_add(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+                 int prefixlen, const struct in6_addr *gw,
+                 const char *iface, uint32_t table, int metric)
+{
+    inet_address_t dst_v6 = { 0 };
+    inet_address_t gw_v6 = { 0 };
+    char dst_str[INET6_ADDRSTRLEN];
+    char gw_str[INET6_ADDRSTRLEN];
+
+    if (dst)
+    {
+        dst_v6.ipv6 = *dst;
+    }
+
+    if (gw)
+    {
+        gw_v6.ipv6 = *gw;
+    }
+
+    msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
+        inet_ntop(AF_INET6, &dst_v6.ipv6, dst_str, sizeof(dst_str)),
+        prefixlen, inet_ntop(AF_INET6, &gw_v6.ipv6, gw_str, sizeof(gw_str)),
+        np(iface), table, metric);
+
+    return sitnl_route_add(iface, AF_INET6, dst, prefixlen, gw, table,
+                           metric);
+}
+
+static int
+sitnl_route_del(const char *iface, sa_family_t af_family, inet_address_t *dst,
+                int prefixlen, inet_address_t *gw, uint32_t table,
+                int metric)
+{
+    int ifindex = 0;
+
+    if (iface)
+    {
+        ifindex = if_nametoindex(iface);
+        if (ifindex == 0)
+        {
+            msg(M_WARN | M_ERRNO, "%s: rtnl: can't get ifindex for %s",
+                __func__, iface);
+            return -ENOENT;
+        }
+    }
+
+    if (table == 0)
+    {
+        table = RT_TABLE_MAIN;
+    }
+
+    return sitnl_route_set(RTM_DELROUTE, 0, ifindex, af_family, dst, prefixlen,
+                           gw, table, metric, RT_SCOPE_NOWHERE, 0, 0);
+}
+
+int
+net_route_v4_del(openvpn_net_ctx_t *ctx, const in_addr_t *dst, int prefixlen,
+                 const in_addr_t *gw, const char *iface, uint32_t table,
+                 int metric)
+{
+    inet_address_t dst_v4 = { 0 };
+    inet_address_t gw_v4 = { 0 };
+    char dst_str[INET_ADDRSTRLEN];
+    char gw_str[INET_ADDRSTRLEN];
+
+    if (dst)
+    {
+        dst_v4.ipv4 = htonl(*dst);
+    }
+
+    if (gw)
+    {
+        gw_v4.ipv4 = htonl(*gw);
+    }
+
+    msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
+        inet_ntop(AF_INET, &dst_v4.ipv4, dst_str, sizeof(dst_str)),
+        prefixlen, inet_ntop(AF_INET, &gw_v4.ipv4, gw_str, sizeof(gw_str)),
+        np(iface), table, metric);
+
+    return sitnl_route_del(iface, AF_INET, &dst_v4, prefixlen, &gw_v4, table,
+                           metric);
+}
+
+int
+net_route_v6_del(openvpn_net_ctx_t *ctx, const struct in6_addr *dst,
+                 int prefixlen, const struct in6_addr *gw,
+                 const char *iface, uint32_t table, int metric)
+{
+    inet_address_t dst_v6 = { 0 };
+    inet_address_t gw_v6 = { 0 };
+    char dst_str[INET6_ADDRSTRLEN];
+    char gw_str[INET6_ADDRSTRLEN];
+
+    if (dst)
+    {
+        dst_v6.ipv6 = *dst;
+    }
+
+    if (gw)
+    {
+        gw_v6.ipv6 = *gw;
+    }
+
+    msg(D_ROUTE, "%s: %s/%d via %s dev %s table %d metric %d", __func__,
+        inet_ntop(AF_INET6, &dst_v6.ipv6, dst_str, sizeof(dst_str)),
+        prefixlen, inet_ntop(AF_INET6, &gw_v6.ipv6, gw_str, sizeof(gw_str)),
+        np(iface), table, metric);
+
+    return sitnl_route_del(iface, AF_INET6, &dst_v6, prefixlen, &gw_v6,
+                           table, metric);
+}
+
+#endif /* !ENABLE_SITNL */
+
+#endif /* TARGET_LINUX */
diff --git a/src/openvpn/networking_sitnl.h b/src/openvpn/networking_sitnl.h
new file mode 100644
index 0000000..6396b06
--- /dev/null
+++ b/src/openvpn/networking_sitnl.h
@@ -0,0 +1,28 @@
+/*
+ *  Generic interface to platform specific networking code
+ *
+ *  Copyright (C) 2016-2018 Antonio Quartulli <a@unstable.cc>
+ *
+ *  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_SITNL_H_
+#define NETWORKING_SITNL_H_
+
+typedef char openvpn_net_iface_t;
+typedef void *openvpn_net_ctx_t;
+
+#endif /* NETWORKING_SITNL_H_ */
diff --git a/src/openvpn/ntlm.c b/src/openvpn/ntlm.c
index 077fa3e..e370748 100644
--- a/src/openvpn/ntlm.c
+++ b/src/openvpn/ntlm.c
@@ -314,8 +314,8 @@
          * byte order on the wire for the NTLM header is LE.
          */
         const size_t hoff = 0x14;
-        unsigned long flags = buf2[hoff] | (buf2[hoff + 1] << 8) |
-                              (buf2[hoff + 2] << 16) | (buf2[hoff + 3] << 24);
+        unsigned long flags = buf2[hoff] | (buf2[hoff + 1] << 8)
+                              |(buf2[hoff + 2] << 16) | (buf2[hoff + 3] << 24);
         if ((flags & 0x00800000) == 0x00800000)
         {
             tib_len = buf2[0x28];            /* Get Target Information block size */
diff --git a/src/openvpn/occ-inline.h b/src/openvpn/occ-inline.h
deleted file mode 100644
index 7f6f1b2..0000000
--- a/src/openvpn/occ-inline.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- *  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) 2002-2018 OpenVPN Inc <sales@openvpn.net>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License version 2
- *  as published by the Free Software Foundation.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License along
- *  with this program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef OCC_INLINE_H
-#define OCC_INLINE_H
-
-#ifdef ENABLE_OCC
-
-/*
- * Inline functions
- */
-
-static inline int
-occ_reset_op(void)
-{
-    return -1;
-}
-
-/*
- * Should we send an OCC_REQUEST message?
- */
-static inline void
-check_send_occ_req(struct context *c)
-{
-    void check_send_occ_req_dowork(struct context *c);
-
-    if (event_timeout_defined(&c->c2.occ_interval)
-        && event_timeout_trigger(&c->c2.occ_interval,
-                                 &c->c2.timeval,
-                                 (!TO_LINK_DEF(c) && c->c2.occ_op < 0) ? ETT_DEFAULT : 0))
-    {
-        check_send_occ_req_dowork(c);
-    }
-}
-
-/*
- * Should we send an MTU load test?
- */
-static inline void
-check_send_occ_load_test(struct context *c)
-{
-    void check_send_occ_load_test_dowork(struct context *c);
-
-    if (event_timeout_defined(&c->c2.occ_mtu_load_test_interval)
-        && event_timeout_trigger(&c->c2.occ_mtu_load_test_interval,
-                                 &c->c2.timeval,
-                                 (!TO_LINK_DEF(c) && c->c2.occ_op < 0) ? ETT_DEFAULT : 0))
-    {
-        check_send_occ_load_test_dowork(c);
-    }
-}
-
-/*
- * Should we send an OCC message?
- */
-static inline void
-check_send_occ_msg(struct context *c)
-{
-    void check_send_occ_msg_dowork(struct context *c);
-
-    if (c->c2.occ_op >= 0)
-    {
-        if (!TO_LINK_DEF(c))
-        {
-            check_send_occ_msg_dowork(c);
-        }
-        else
-        {
-            tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */
-        }
-    }
-}
-
-#endif /* ifdef ENABLE_OCC */
-#endif /* ifndef OCC_INLINE_H */
diff --git a/src/openvpn/occ.c b/src/openvpn/occ.c
index 80504af..3ff351a 100644
--- a/src/openvpn/occ.c
+++ b/src/openvpn/occ.c
@@ -29,14 +29,10 @@
 
 #include "syshead.h"
 
-#ifdef ENABLE_OCC
-
 #include "occ.h"
-
+#include "forward.h"
 #include "memdbg.h"
 
-#include "forward-inline.h"
-#include "occ-inline.h"
 
 /*
  * This random string identifies an OpenVPN
@@ -426,10 +422,3 @@
     }
     c->c2.buf.len = 0; /* don't pass packet on */
 }
-
-#else  /* ifdef ENABLE_OCC */
-static void
-dummy(void)
-{
-}
-#endif /* ifdef ENABLE_OCC */
diff --git a/src/openvpn/occ.h b/src/openvpn/occ.h
index f6ff5f9..504c8c4 100644
--- a/src/openvpn/occ.h
+++ b/src/openvpn/occ.h
@@ -24,8 +24,6 @@
 #ifndef OCC_H
 #define OCC_H
 
-#ifdef ENABLE_OCC
-
 #include "forward.h"
 
 /* OCC_STRING_SIZE must be set to sizeof (occ_magic) */
@@ -90,5 +88,69 @@
 
 void process_received_occ_msg(struct context *c);
 
-#endif /* ifdef ENABLE_OCC */
+void check_send_occ_req_dowork(struct context *c);
+
+void check_send_occ_load_test_dowork(struct context *c);
+
+void check_send_occ_msg_dowork(struct context *c);
+
+/*
+ * Inline functions
+ */
+
+static inline int
+occ_reset_op(void)
+{
+    return -1;
+}
+
+/*
+ * Should we send an OCC_REQUEST message?
+ */
+static inline void
+check_send_occ_req(struct context *c)
+{
+    if (event_timeout_defined(&c->c2.occ_interval)
+        && event_timeout_trigger(&c->c2.occ_interval,
+                                 &c->c2.timeval,
+                                 (!TO_LINK_DEF(c) && c->c2.occ_op < 0) ? ETT_DEFAULT : 0))
+    {
+        check_send_occ_req_dowork(c);
+    }
+}
+
+/*
+ * Should we send an MTU load test?
+ */
+static inline void
+check_send_occ_load_test(struct context *c)
+{
+    if (event_timeout_defined(&c->c2.occ_mtu_load_test_interval)
+        && event_timeout_trigger(&c->c2.occ_mtu_load_test_interval,
+                                 &c->c2.timeval,
+                                 (!TO_LINK_DEF(c) && c->c2.occ_op < 0) ? ETT_DEFAULT : 0))
+    {
+        check_send_occ_load_test_dowork(c);
+    }
+}
+
+/*
+ * Should we send an OCC message?
+ */
+static inline void
+check_send_occ_msg(struct context *c)
+{
+    if (c->c2.occ_op >= 0)
+    {
+        if (!TO_LINK_DEF(c))
+        {
+            check_send_occ_msg_dowork(c);
+        }
+        else
+        {
+            tv_clear(&c->c2.timeval); /* ZERO-TIMEOUT */
+        }
+    }
+}
+
 #endif /* ifndef OCC_H */
diff --git a/src/openvpn/openssl_compat.h b/src/openvpn/openssl_compat.h
index 8acc7d1..eb6c9c9 100644
--- a/src/openvpn/openssl_compat.h
+++ b/src/openvpn/openssl_compat.h
@@ -42,6 +42,7 @@
 
 #include "buffer.h"
 
+#include <openssl/rsa.h>
 #include <openssl/ssl.h>
 #include <openssl/x509.h>
 
@@ -182,6 +183,12 @@
 }
 #endif
 
+/* This function is implemented as macro, so the configure check for the
+ * function may fail, so we check for both variants here */
+#if !defined(HAVE_SSL_CTX_SET1_GROUPS) && !defined(SSL_CTX_set1_groups)
+#define SSL_CTX_set1_groups SSL_CTX_set1_curves
+#endif
+
 #if !defined(HAVE_X509_GET0_PUBKEY)
 /**
  * Get the public key from a X509 certificate
@@ -204,8 +211,8 @@
  * @param store              X509 object store
  * @return                   the X509 object stack
  */
-static inline STACK_OF(X509_OBJECT) *
-X509_STORE_get0_objects(X509_STORE *store)
+static inline STACK_OF(X509_OBJECT)
+*X509_STORE_get0_objects(X509_STORE *store)
 {
     return store ? store->objs : NULL;
 }
@@ -270,20 +277,6 @@
 }
 #endif
 
-#if !defined(HAVE_EVP_PKEY_ID)
-/**
- * Get the PKEY type
- *
- * @param pkey                Public key object
- * @return                    The key type
- */
-static inline int
-EVP_PKEY_id(const EVP_PKEY *pkey)
-{
-    return pkey ? pkey->type : EVP_PKEY_NONE;
-}
-#endif
-
 #if !defined(HAVE_EVP_PKEY_GET0_DSA)
 /**
  * Get the DSA object of a public key
@@ -380,7 +373,7 @@
 
     return 1;
 }
-#endif
+#endif /* if !defined(HAVE_RSA_SET0_KEY) */
 
 #if !defined(HAVE_RSA_BITS)
 /**
@@ -494,9 +487,9 @@
  */
 static inline int
 RSA_meth_set_pub_enc(RSA_METHOD *meth,
-                     int (*pub_enc) (int flen, const unsigned char *from,
-                                     unsigned char *to, RSA *rsa,
-                                     int padding))
+                     int (*pub_enc)(int flen, const unsigned char *from,
+                                    unsigned char *to, RSA *rsa,
+                                    int padding))
 {
     if (meth)
     {
@@ -517,9 +510,9 @@
  */
 static inline int
 RSA_meth_set_pub_dec(RSA_METHOD *meth,
-                     int (*pub_dec) (int flen, const unsigned char *from,
-                                     unsigned char *to, RSA *rsa,
-                                     int padding))
+                     int (*pub_dec)(int flen, const unsigned char *from,
+                                    unsigned char *to, RSA *rsa,
+                                    int padding))
 {
     if (meth)
     {
@@ -540,9 +533,9 @@
  */
 static inline int
 RSA_meth_set_priv_enc(RSA_METHOD *meth,
-                      int (*priv_enc) (int flen, const unsigned char *from,
-                                       unsigned char *to, RSA *rsa,
-                                       int padding))
+                      int (*priv_enc)(int flen, const unsigned char *from,
+                                      unsigned char *to, RSA *rsa,
+                                      int padding))
 {
     if (meth)
     {
@@ -563,9 +556,9 @@
  */
 static inline int
 RSA_meth_set_priv_dec(RSA_METHOD *meth,
-                      int (*priv_dec) (int flen, const unsigned char *from,
-                                       unsigned char *to, RSA *rsa,
-                                       int padding))
+                      int (*priv_dec)(int flen, const unsigned char *from,
+                                      unsigned char *to, RSA *rsa,
+                                      int padding))
 {
     if (meth)
     {
@@ -585,7 +578,7 @@
  * @return                   1 on success, 0 on error
  */
 static inline int
-RSA_meth_set_init(RSA_METHOD *meth, int (*init) (RSA *rsa))
+RSA_meth_set_init(RSA_METHOD *meth, int (*init)(RSA *rsa))
 {
     if (meth)
     {
@@ -605,11 +598,12 @@
  * @return                   1 on success, 0 on error
  */
 static inline
-int RSA_meth_set_sign(RSA_METHOD *meth,
-                      int (*sign) (int type, const unsigned char *m,
-                                   unsigned int m_length,
-                                   unsigned char *sigret, unsigned int *siglen,
-                                   const RSA *rsa))
+int
+RSA_meth_set_sign(RSA_METHOD *meth,
+                  int (*sign)(int type, const unsigned char *m,
+                              unsigned int m_length,
+                              unsigned char *sigret, unsigned int *siglen,
+                              const RSA *rsa))
 {
     meth->rsa_sign = sign;
     return 1;
@@ -625,7 +619,7 @@
  * @return                   1 on success, 0 on error
  */
 static inline int
-RSA_meth_set_finish(RSA_METHOD *meth, int (*finish) (RSA *rsa))
+RSA_meth_set_finish(RSA_METHOD *meth, int (*finish)(RSA *rsa))
 {
     if (meth)
     {
@@ -680,7 +674,7 @@
 static inline int
 EC_GROUP_order_bits(const EC_GROUP *group)
 {
-    BIGNUM* order = BN_new();
+    BIGNUM *order = BN_new();
     EC_GROUP_get_order(group, order, NULL);
     int bits = BN_num_bits(order);
     BN_free(order);
@@ -689,6 +683,14 @@
 #endif
 
 /* SSLeay symbols have been renamed in OpenSSL 1.1 */
+#ifndef OPENSSL_VERSION
+#define OPENSSL_VERSION SSLEAY_VERSION
+#endif
+
+#ifndef HAVE_OPENSSL_VERSION
+#define OpenSSL_version SSLeay_version
+#endif
+
 #if !defined(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT)
 #define RSA_F_RSA_OSSL_PRIVATE_ENCRYPT       RSA_F_RSA_EAY_PRIVATE_ENCRYPT
 #endif
@@ -704,18 +706,14 @@
     {
         return TLS1_VERSION;
     }
-#ifdef SSL_OP_NO_TLSv1_1
     if (!(sslopt & SSL_OP_NO_TLSv1_1))
     {
         return TLS1_1_VERSION;
     }
-#endif
-#ifdef SSL_OP_NO_TLSv1_2
     if (!(sslopt & SSL_OP_NO_TLSv1_2))
     {
         return TLS1_2_VERSION;
     }
-#endif
     return 0;
 }
 #endif /* SSL_CTX_get_min_proto_version */
@@ -727,18 +725,14 @@
 SSL_CTX_get_max_proto_version(SSL_CTX *ctx)
 {
     long sslopt = SSL_CTX_get_options(ctx);
-#ifdef SSL_OP_NO_TLSv1_2
     if (!(sslopt & SSL_OP_NO_TLSv1_2))
     {
         return TLS1_2_VERSION;
     }
-#endif
-#ifdef SSL_OP_NO_TLSv1_1
     if (!(sslopt & SSL_OP_NO_TLSv1_1))
     {
         return TLS1_1_VERSION;
     }
-#endif
     if (!(sslopt & SSL_OP_NO_TLSv1))
     {
         return TLS1_VERSION;
diff --git a/src/openvpn/openvpn.c b/src/openvpn/openvpn.c
index 3d244fc..857c5fa 100644
--- a/src/openvpn/openvpn.c
+++ b/src/openvpn/openvpn.c
@@ -37,8 +37,6 @@
 
 #include "memdbg.h"
 
-#include "forward-inline.h"
-
 #define P2P_CHECK_SIG() EVENT_LOOP_CHECK_SIGNAL(c, process_signal_p2p, c);
 
 static bool
@@ -48,28 +46,6 @@
     return process_signal(c);
 }
 
-/* Write our PID to a file */
-static void
-write_pid(const char *filename)
-{
-    if (filename)
-    {
-        unsigned int pid = 0;
-        FILE *fp = platform_fopen(filename, "w");
-        if (!fp)
-        {
-            msg(M_ERR, "Open error on pid file %s", filename);
-        }
-
-        pid = platform_getpid();
-        fprintf(fp, "%u\n", pid);
-        if (fclose(fp))
-        {
-            msg(M_ERR, "Close error on pid file %s", filename);
-        }
-    }
-}
-
 
 /**************************************************************************/
 /**
@@ -217,6 +193,8 @@
             open_plugins(&c, true, OPENVPN_PLUGIN_INIT_PRE_CONFIG_PARSE);
 #endif
 
+            net_ctx_init(&c, &c.net_ctx);
+
             /* init verbosity and mute levels */
             init_verb_mute(&c, IVM_LEVEL_1);
 
@@ -236,7 +214,7 @@
             }
 
             /* tun/tap persist command? */
-            if (do_persist_tuntap(&c.options))
+            if (do_persist_tuntap(&c.options, &c.net_ctx))
             {
                 break;
             }
@@ -274,7 +252,7 @@
             if (c.first_time)
             {
                 c.did_we_daemonize = possibly_become_daemon(&c.options);
-                write_pid(c.options.writepid);
+                write_pid_file(c.options.writepid, c.options.chroot_dir);
             }
 
 #ifdef ENABLE_MANAGEMENT
@@ -305,12 +283,10 @@
                         tunnel_point_to_point(&c);
                         break;
 
-#if P2MP_SERVER
                     case MODE_SERVER:
                         tunnel_server(&c);
                         break;
 
-#endif
                     default:
                         ASSERT(0);
                 }
@@ -332,6 +308,7 @@
             env_set_destroy(c.es);
             uninit_options(&c.options);
             gc_reset(&c.gc);
+            net_ctx_free(&c.net_ctx);
         }
         while (c.sig->signal_received == SIGHUP);
     }
diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h
index ed7975c..d131ac5 100644
--- a/src/openvpn/openvpn.h
+++ b/src/openvpn/openvpn.h
@@ -42,10 +42,10 @@
 #include "sig.h"
 #include "misc.h"
 #include "mbuf.h"
+#include "pf.h"
 #include "pool.h"
 #include "plugin.h"
 #include "manage.h"
-#include "pf.h"
 
 /*
  * Our global key schedules, packaged thusly
@@ -54,7 +54,6 @@
 
 struct key_schedule
 {
-#ifdef ENABLE_CRYPTO
     /* which cipher, HMAC digest, and key sizes are we using? */
     struct key_type key_type;
 
@@ -67,9 +66,9 @@
     /* optional TLS control channel wrapping */
     struct key_type tls_auth_key_type;
     struct key_ctx_bi tls_wrap_key;
-#else                           /* ENABLE_CRYPTO */
-    int dummy;
-#endif                          /* ENABLE_CRYPTO */
+    struct key_ctx tls_crypt_v2_server_key;
+    struct buffer tls_crypt_v2_wkc;             /**< Wrapped client key */
+    struct key_ctx auth_token_key;
 };
 
 /*
@@ -96,10 +95,8 @@
     struct buffer aux_buf;
 
     /* workspace buffers used by crypto routines */
-#ifdef ENABLE_CRYPTO
     struct buffer encrypt_buf;
     struct buffer decrypt_buf;
-#endif
 
     /* workspace buffers for compression */
 #ifdef USE_COMP
@@ -193,12 +190,9 @@
     bool socks_proxy_owned;
 
 #if P2MP
-
-#if P2MP_SERVER
     /* persist --ifconfig-pool db to file */
     struct ifconfig_pool_persist *ifconfig_pool_persist;
     bool ifconfig_pool_persist_owned;
-#endif
 
     /* if client mode, hash of option strings we pulled from server */
     struct sha256_digest pulled_options_digest_save;
@@ -216,6 +210,14 @@
 #endif
 };
 
+
+static inline bool
+is_cas_pending(enum client_connect_status cas)
+{
+    return cas == CAS_PENDING || cas == CAS_PENDING_DEFERRED
+           || cas == CAS_PENDING_DEFERRED_PARTIAL;
+}
+
 /**
  * Level 2 %context containing state that is reset on both \c SIGHUP and
  * \c SIGUSR1 restarts.
@@ -307,7 +309,6 @@
     struct event_timeout inactivity_interval;
     int inactivity_bytes;
 
-#ifdef ENABLE_OCC
     /* the option strings must match across peers */
     char *options_string_local;
     char *options_string_remote;
@@ -315,7 +316,6 @@
     int occ_op;                 /* INIT to -1 */
     int occ_n_tries;
     struct event_timeout occ_interval;
-#endif
 
     /*
      * Keep track of maximum packet size received so far
@@ -327,15 +327,12 @@
     int max_send_size_local;    /* max packet size sent */
     int max_send_size_remote;   /* max packet size sent by remote */
 
-#ifdef ENABLE_OCC
+
     /* remote wants us to send back a load test packet of this size */
     int occ_mtu_load_size;
 
     struct event_timeout occ_mtu_load_test_interval;
     int occ_mtu_load_n_tries;
-#endif
-
-#ifdef ENABLE_CRYPTO
 
     /*
      * TLS-mode crypto objects.
@@ -368,8 +365,6 @@
 
     struct event_timeout packet_id_persist_interval;
 
-#endif /* ENABLE_CRYPTO */
-
 #ifdef USE_COMP
     struct compress_context *comp_context;
     /**< Compression context used by the
@@ -424,13 +419,11 @@
     /* indicates that the do_up_delay function has run */
     bool do_up_ran;
 
-#ifdef ENABLE_OCC
     /* indicates that we have received a SIGTERM when
      * options->explicit_exit_notification is enabled,
      * but we have not exited yet */
     time_t explicit_exit_notification_time_wait;
     struct event_timeout explicit_exit_notification_interval;
-#endif
 
     /* environmental variables to pass to scripts */
     struct env_set *es;
@@ -441,12 +434,8 @@
 
 #if P2MP
 
-#if P2MP_SERVER
     /* --ifconfig endpoints to be pushed to client */
-    bool push_reply_deferred;
-#ifdef ENABLE_ASYNC_PUSH
     bool push_request_received;
-#endif
     bool push_ifconfig_defined;
     time_t sent_push_reply_expiry;
     in_addr_t push_ifconfig_local;
@@ -458,15 +447,6 @@
     int push_ifconfig_ipv6_netbits;
     struct in6_addr push_ifconfig_ipv6_remote;
 
-    /* client authentication state, CAS_SUCCEEDED must be 0 */
-#define CAS_SUCCEEDED 0
-#define CAS_PENDING   1
-#define CAS_FAILED    2
-#define CAS_PARTIAL   3  /* at least one client-connect script/plugin
-                          * succeeded while a later one in the chain failed */
-    int context_auth;
-#endif /* if P2MP_SERVER */
-
     struct event_timeout push_request_interval;
     int n_sent_push_requests;
     bool did_pre_pull_restore;
@@ -531,6 +511,8 @@
 
     struct env_set *es;         /**< Set of environment variables. */
 
+    openvpn_net_ctx_t net_ctx;  /**< Networking API opaque context */
+
     struct signal_info *sig;    /**< Internal error signaling object. */
 
     struct plugin_list *plugins; /**< List of plug-ins. */
@@ -567,7 +549,6 @@
  * have been compiled in.
  */
 
-#ifdef ENABLE_CRYPTO
 #define TLS_MODE(c) ((c)->c2.tls_multi != NULL)
 #define PROTO_DUMP_FLAGS (check_debug_level(D_LINK_RW_VERBOSE) ? (PD_SHOW_DATA|PD_VERBOSE) : 0)
 #define PROTO_DUMP(buf, gc) protocol_dump((buf), \
@@ -575,22 +556,8 @@
                                           |(c->c2.tls_multi ? PD_TLS : 0)   \
                                           |(c->options.tls_auth_file ? c->c1.ks.key_type.hmac_length : 0), \
                                           gc)
-#else  /* ifdef ENABLE_CRYPTO */
-#define TLS_MODE(c) (false)
-#define PROTO_DUMP(buf, gc) format_hex(BPTR(buf), BLEN(buf), 80, gc)
-#endif
 
-#ifdef ENABLE_CRYPTO
-#define MD5SUM(buf, len, gc) md5sum((buf), (len), 0, (gc))
-#else
-#define MD5SUM(buf, len, gc) "[unavailable]"
-#endif
-
-#ifdef ENABLE_CRYPTO
 #define CIPHER_ENABLED(c) (c->c1.ks.key_type.cipher != NULL)
-#else
-#define CIPHER_ENABLED(c) (false)
-#endif
 
 /* this represents "disabled peer-id" */
 #define MAX_PEER_ID 0xFFFFFF
diff --git a/src/openvpn/openvpn.manifest b/src/openvpn/openvpn.manifest
new file mode 100644
index 0000000..fa5b3d7
--- /dev/null
+++ b/src/openvpn/openvpn.manifest
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+        <application>
+            <!-- Windows 10 -->
+            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+            <!-- Windows 8.1 -->
+            <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+            <!-- Windows 8 -->
+            <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+            <!-- Windows 7 -->
+            <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+            <!-- Windows Vista -->
+            <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+        </application>
+    </compatibility>
+    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+        <security>
+            <requestedPrivileges>
+                <!--
+                  UAC settings:
+                  - app should run at same integrity level as calling process
+                  - app does not need to manipulate windows belonging to
+                    higher-integrity-level processes
+                  -->
+                <requestedExecutionLevel
+                    level="asInvoker"
+                    uiAccess="false"
+                />
+            </requestedPrivileges>
+        </security>
+    </trustInfo>
+</assembly>
diff --git a/src/openvpn/openvpn_win32_resources.rc b/src/openvpn/openvpn_win32_resources.rc
index e4f1ee9..1ea5f87 100644
--- a/src/openvpn/openvpn_win32_resources.rc
+++ b/src/openvpn/openvpn_win32_resources.rc
@@ -7,6 +7,8 @@
 
 #pragma code_page(65001) /* UTF8 */
 
+1 RT_MANIFEST "openvpn.manifest"
+
 LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
 
 VS_VERSION_INFO VERSIONINFO
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 32e4c95..ce3a09c 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -41,9 +41,11 @@
 #include "buffer.h"
 #include "error.h"
 #include "common.h"
+#include "run_command.h"
 #include "shaper.h"
 #include "crypto.h"
 #include "ssl.h"
+#include "ssl_ncp.h"
 #include "options.h"
 #include "misc.h"
 #include "socket.h"
@@ -52,6 +54,7 @@
 #include "win32.h"
 #include "push.h"
 #include "pool.h"
+#include "proto.h"
 #include "helper.h"
 #include "manage.h"
 #include "forward.h"
@@ -67,7 +70,6 @@
     " [git:" CONFIGURE_GIT_REVISION CONFIGURE_GIT_FLAGS "]"
 #endif
     " " TARGET_ALIAS
-#ifdef ENABLE_CRYPTO
 #if defined(ENABLE_CRYPTO_MBEDTLS)
     " [SSL (mbed TLS)]"
 #elif defined(ENABLE_CRYPTO_OPENSSL)
@@ -75,7 +77,6 @@
 #else
     " [SSL]"
 #endif /* defined(ENABLE_CRYPTO_MBEDTLS) */
-#endif /* ENABLE_CRYPTO */
 #ifdef USE_COMP
 #ifdef ENABLE_LZO
     " [LZO]"
@@ -103,9 +104,7 @@
     " [MH/RECVDA]"
 #endif
 #endif
-#ifdef HAVE_AEAD_CIPHER_MODES
     " [AEAD]"
-#endif
     " built on " __DATE__
 ;
 
@@ -201,8 +200,10 @@
     "--route-ipv6 network/bits [gateway] [metric] :\n"
     "                  Add IPv6 route to routing table after connection\n"
     "                  is established.  Multiple routes can be specified.\n"
-    "                  gateway default: taken from 'remote' in --ifconfig-ipv6\n"
+    "                  gateway default: taken from --route-ipv6-gateway or 'remote'\n"
+    "                  in --ifconfig-ipv6\n"
     "--route-gateway gw|'dhcp' : Specify a default gateway for use with --route.\n"
+    "--route-ipv6-gateway gw : Specify a default gateway for use with --route-ipv6.\n"
     "--route-metric m : Specify a default metric for use with --route.\n"
     "--route-delay n [w] : Delay n seconds after connection initiation before\n"
     "                  adding routes (may be 0).  If not specified, routes will\n"
@@ -226,10 +227,12 @@
     "                  Add 'bypass-dns' flag to similarly bypass tunnel for DNS.\n"
     "--redirect-private [flags]: Like --redirect-gateway, but omit actually changing\n"
     "                  the default gateway.  Useful when pushing private subnets.\n"
+    "--block-ipv6     : (Client) Instead sending IPv6 to the server generate\n"
+    "                   ICMPv6 host unreachable messages on the client.\n"
+    "                   (Server) Instead of forwarding IPv6 packets send\n"
+    "                   ICMPv6 host unreachable packets to the client.\n"
     "--client-nat snat|dnat network netmask alias : on client add 1-to-1 NAT rule.\n"
-#ifdef ENABLE_PUSH_PEER_INFO
     "--push-peer-info : (client only) push client info to server.\n"
-#endif
     "--setenv name value : Set a custom environmental variable to pass to script.\n"
     "--setenv FORWARD_COMPATIBLE 1 : Relax config file syntax checking to allow\n"
     "                  directives for future OpenVPN versions to be ignored.\n"
@@ -274,9 +277,7 @@
     "                  'no'    -- Never send DF (Don't Fragment) frames\n"
     "                  'maybe' -- Use per-route hints\n"
     "                  'yes'   -- Always DF (Don't Fragment)\n"
-#ifdef ENABLE_OCC
     "--mtu-test      : Empirically measure and report MTU.\n"
-#endif
 #ifdef ENABLE_FRAGMENT
     "--fragment max  : Enable internal datagram fragmentation so that no UDP\n"
     "                  datagrams are sent which are larger than max bytes.\n"
@@ -289,6 +290,9 @@
 #if defined(TARGET_LINUX) && HAVE_DECL_SO_MARK
     "--mark value    : Mark encrypted packets being sent with value. The mark value\n"
     "                  can be matched in policy routing and packetfilter rules.\n"
+    "--bind-dev dev  : Bind to the given device when making connection to a peer or\n"
+    "                  listening for connections. This allows sending encrypted packets\n"
+    "                  via a VRF present on the system.\n"
 #endif
     "--txqueuelen n  : Set the tun/tap TX queue length to n (Linux only).\n"
 #ifdef ENABLE_MEMSTATS
@@ -344,17 +348,16 @@
     "--status file n : Write operational status to file every n seconds.\n"
     "--status-version [n] : Choose the status file format version number.\n"
     "                  Currently, n can be 1, 2, or 3 (default=1).\n"
-#ifdef ENABLE_OCC
     "--disable-occ   : Disable options consistency check between peers.\n"
-#endif
 #ifdef ENABLE_DEBUG
     "--gremlin mask  : Special stress testing mode (for debugging only).\n"
 #endif
 #if defined(USE_COMP)
     "--compress alg  : Use compression algorithm alg\n"
+    "--allow-compression: Specify whether compression should be allowed\n"
 #if defined(ENABLE_LZO)
     "--comp-lzo      : Use LZO compression -- may add up to 1 byte per\n"
-    "                  packet for uncompressible data.\n"
+    "                  packet for incompressible data.\n"
     "--comp-noadapt  : Don't use adaptive compression when --comp-lzo\n"
     "                  is specified.\n"
 #endif
@@ -401,8 +404,10 @@
     "--plugin m [str]: Load plug-in module m passing str as an argument\n"
     "                  to its initialization function.\n"
 #endif
+    "--vlan-tagging  : Enable 802.1Q-based VLAN tagging.\n"
+    "--vlan-accept tagged|untagged|all : Set VLAN tagging mode. Default is 'all'.\n"
+    "--vlan-pvid v   : Sets the Port VLAN Identifier. Defaults to 1.\n"
 #if P2MP
-#if P2MP_SERVER
     "\n"
     "Multi-Client Server options (when --mode server is used):\n"
     "--server network netmask : Helper option to easily configure server mode.\n"
@@ -415,9 +420,6 @@
     "                  client instance.\n"
     "--ifconfig-pool start-IP end-IP [netmask] : Set aside a pool of subnets\n"
     "                  to be dynamically allocated to connecting clients.\n"
-    "--ifconfig-pool-linear : (DEPRECATED) Use individual addresses rather \n"
-    "                  than /30 subnets\n in tun mode.  Not compatible with\n"
-    "                  Windows clients.\n"
     "--ifconfig-pool-persist file [seconds] : Persist/unpersist ifconfig-pool\n"
     "                  data to file, at seconds intervals (default=600).\n"
     "                  If seconds=0, file will be treated as read-only.\n"
@@ -435,8 +437,6 @@
     "                  Only valid in a client-specific config file.\n"
     "--disable       : Client is disabled.\n"
     "                  Only valid in a client-specific config file.\n"
-    "--client-cert-not-required : (DEPRECATED) Don't require client certificate, client\n"
-    "                  will authenticate using username/password.\n"
     "--verify-client-cert [none|optional|require] : perform no, optional or\n"
     "                  mandatory client certificate verification.\n"
     "                  Default is to require the client to supply a certificate.\n"
@@ -448,7 +448,7 @@
     "                  user/pass via environment, if method='via-file', pass\n"
     "                  user/pass via temporary file.\n"
     "--auth-gen-token  [lifetime] Generate a random authentication token which is pushed\n"
-    "                  to each client, replacing the password.  Usefull when\n"
+    "                  to each client, replacing the password.  Useful when\n"
     "                  OTP based two-factor auth mechanisms are in use and\n"
     "                  --reneg-* options are enabled. Optionally a lifetime in seconds\n"
     "                  for generated tokens can be set.\n"
@@ -487,7 +487,6 @@
     "                  sessions to a web server at host:port.  dir specifies an\n"
     "                  optional directory to write origin IP:port data.\n"
 #endif
-#endif /* if P2MP_SERVER */
     "\n"
     "Client options (when connecting to a multi-client server):\n"
     "--client         : Helper option to easily configure client mode.\n"
@@ -514,11 +513,8 @@
     "--allow-recursive-routing : When this option is set, OpenVPN will not drop\n"
     "                  incoming tun packets with same destination as host.\n"
 #endif /* if P2MP */
-#ifdef ENABLE_OCC
     "--explicit-exit-notify [n] : On exit/restart, send exit signal to\n"
     "                  server/remote. n = # of retries, default=1.\n"
-#endif
-#ifdef ENABLE_CRYPTO
     "\n"
     "Data Channel Encryption Options (must be compatible between peers):\n"
     "(These options are meaningful for both Static Key & TLS-mode)\n"
@@ -535,8 +531,8 @@
     "--cipher alg    : Encrypt packets with cipher algorithm alg\n"
     "                  (default=%s).\n"
     "                  Set alg=none to disable encryption.\n"
-    "--ncp-ciphers list : List of ciphers that are allowed to be negotiated.\n"
-    "--ncp-disable   : Disable cipher negotiation.\n"
+    "--data-ciphers list : List of ciphers that are allowed to be negotiated.\n"
+    "--ncp-disable   : (DEPRECATED) Disable cipher negotiation.\n"
     "--prng alg [nsl] : For PRNG, use digest algorithm alg, and\n"
     "                   nonce_secret_len=nsl.  Set alg=none to disable PRNG.\n"
 #ifdef HAVE_EVP_CIPHER_CTX_SET_KEY_LENGTH
@@ -551,7 +547,6 @@
     "--replay-window n [t]  : Use a replay protection sliding window of size n\n"
     "                         and a time window of t seconds.\n"
     "                         Default n=%d t=%d\n"
-    "--no-iv         : Disable cipher IV -- only allowed with CBC mode ciphers.\n"
     "--replay-persist file : Persist replay-protection state across sessions\n"
     "                  using file.\n"
     "--test-crypto   : Run a self-test of crypto features enabled.\n"
@@ -599,14 +594,17 @@
     "                  Windows Certificate System Store.\n"
 #endif
     "--tls-cipher l  : A list l of allowable TLS ciphers separated by : (optional).\n"
-    "                : Use --show-tls to see a list of supported TLS ciphers.\n"
+    "--tls-ciphersuites l: A list of allowed TLS 1.3 cipher suites seperated by : (optional)\n"
+    "                : Use --show-tls to see a list of supported TLS ciphers (suites).\n"
     "--tls-cert-profile p : Set the allowed certificate crypto algorithm profile\n"
     "                  (default=legacy).\n"
     "--tls-timeout n : Packet retransmit timeout on TLS control channel\n"
     "                  if no ACK from remote within n seconds (default=%d).\n"
     "--reneg-bytes n : Renegotiate data chan. key after n bytes sent and recvd.\n"
     "--reneg-pkts n  : Renegotiate data chan. key after n packets sent and recvd.\n"
-    "--reneg-sec n   : Renegotiate data chan. key after n seconds (default=%d).\n"
+    "--reneg-sec max [min] : Renegotiate data chan. key after at most max (default=%d)\n"
+    "                  and at least min (defaults to 90%% of max on servers and equal\n"
+    "                  to max on clients).\n"
     "--hand-window n : Data channel key exchange must finalize within n seconds\n"
     "                  of handshake initiation by any peer (default=%d).\n"
     "--tran-window n : Transition window -- old key can live this many seconds\n"
@@ -625,6 +623,17 @@
     "                  attacks on the TLS stack and DoS attacks.\n"
     "                  key (required) provides the pre-shared key file.\n"
     "                  see --secret option for more info.\n"
+    "--tls-crypt-v2 key : For clients: use key as a client-specific tls-crypt key.\n"
+    "                  For servers: use key to decrypt client-specific keys.  For\n"
+    "                  key generation (--tls-crypt-v2-genkey): use key to\n"
+    "                  encrypt generated client-specific key.  (See --tls-crypt.)\n"
+    "--genkey tls-crypt-v2-client [keyfile] [base64 metadata]: Generate a\n"
+    "                  fresh tls-crypt-v2 client key, and store to\n"
+    "                  keyfile.  If supplied, include metadata in wrapped key.\n"
+    "--genkey tls-crypt-v2-server [keyfile] [base64 metadata]: Generate a\n"
+    "                  fresh tls-crypt-v2 server key, and store to keyfile\n"
+    "--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\n"
+    "                  client-supplied tls-crypt-v2 client key\n"
     "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
     "--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\n"
     "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
@@ -643,7 +652,7 @@
     "                  an explicit nsCertType designation t = 'client' | 'server'.\n"
     "--x509-track x  : Save peer X509 attribute x in environment for use by\n"
     "                  plugins and management interface.\n"
-#if defined(ENABLE_CRYPTO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10001000
+#ifdef HAVE_EXPORT_KEYING_MATERIAL
     "--keying-material-exporter label len : Save Exported Keying Material (RFC5705)\n"
     "                  of len bytes (min. 16 bytes) using label in environment for use by plugins.\n"
 #endif
@@ -663,7 +672,7 @@
     "--pkcs11-protected-authentication [0|1] ... : Use PKCS#11 protected authentication\n"
     "                              path. Set for each provider.\n"
     "--pkcs11-private-mode hex ...   : PKCS#11 private key mode mask.\n"
-    "                              0       : Try  to determind automatically (default).\n"
+    "                              0       : Try  to determine automatically (default).\n"
     "                              1       : Use Sign.\n"
     "                              2       : Use SignRecover.\n"
     "                              4       : Use Decrypt.\n"
@@ -707,6 +716,7 @@
     "                    which allow multiple addresses,\n"
     "                    --dhcp-option must be repeated.\n"
     "                    DOMAIN name : Set DNS suffix\n"
+    "                    DOMAIN-SEARCH entry : Add entry to DNS domain search list\n"
     "                    DNS addr    : Set domain name server address(es) (IPv4 and IPv6)\n"
     "                    NTP         : Set NTP server address(es)\n"
     "                    NBDD        : Set NBDD server address(es)\n"
@@ -730,9 +740,10 @@
     "                       optional parameter controls the initial state of ex.\n"
     "--show-net-up   : Show " PACKAGE_NAME "'s view of routing table and net adapter list\n"
     "                  after TAP adapter is up and routes have been added.\n"
-#ifdef _WIN32
+    "--windows-driver   : Which tun driver to use?\n"
+    "                     tap-windows6 (default)\n"
+    "                     wintun\n"
     "--block-outside-dns   : Block DNS on other network adapters to prevent DNS leaks\n"
-#endif
     "Windows Standalone Options:\n"
     "\n"
     "--show-adapters : Show all TAP-Windows adapters.\n"
@@ -742,11 +753,9 @@
     "                                 to access TAP adapter.\n"
 #endif /* ifdef _WIN32 */
     "\n"
-    "Generate a random key (only for non-TLS static key encryption mode):\n"
-    "--genkey        : Generate a random key to be used as a shared secret,\n"
-    "                  for use with the --secret option.\n"
-    "--secret file   : Write key to file.\n"
-#endif                          /* ENABLE_CRYPTO */
+    "Generate a new key :\n"
+    "--genkey secret file   : Generate a new random key of type and write to file\n"
+    "                         (for use with --secret, --tls-auth or --tls-crypt)."
 #ifdef ENABLE_FEATURE_TUN_PERSIST
     "\n"
     "Tun/tap config mode (available with linux 2.4+):\n"
@@ -812,9 +821,7 @@
     o->resolve_retry_seconds = RESOLV_RETRY_INFINITE;
     o->resolve_in_advance = false;
     o->proto_force = -1;
-#ifdef ENABLE_OCC
     o->occ = true;
-#endif
 #ifdef ENABLE_MANAGEMENT
     o->management_log_history_cache = 250;
     o->management_echo_buffer_size = 100;
@@ -823,9 +830,6 @@
 #ifdef ENABLE_FEATURE_TUN_PERSIST
     o->persist_mode = 1;
 #endif
-#ifdef TARGET_LINUX
-    o->tuntap_options.txqueuelen = 100;
-#endif
 #ifdef _WIN32
 #if 0
     o->tuntap_options.ip_win32_type = IPW32_SET_ADAPTIVE;
@@ -836,8 +840,10 @@
     o->tuntap_options.dhcp_masq_offset = 0;     /* use network address as internal DHCP server address */
     o->route_method = ROUTE_METHOD_ADAPTIVE;
     o->block_outside_dns = false;
+    o->windows_driver = WINDOWS_DRIVER_TAP_WINDOWS6;
 #endif
-#if P2MP_SERVER
+    o->vlan_accept = VLAN_ALL;
+    o->vlan_pvid = 1;
     o->real_hash_size = 256;
     o->virtual_hash_size = 256;
     o->n_bcast_buf = 256;
@@ -846,17 +852,10 @@
     o->max_routes_per_client = 256;
     o->stale_routes_check_interval = 0;
     o->ifconfig_pool_persist_refresh_freq = 600;
-#endif
 #if P2MP
     o->scheduled_exit_interval = 5;
 #endif
-#ifdef ENABLE_CRYPTO
-    o->ciphername = "BF-CBC";
-#ifdef HAVE_AEAD_CIPHER_MODES /* IV_NCP=2 requires GCM support */
     o->ncp_enabled = true;
-#else
-    o->ncp_enabled = false;
-#endif
     o->ncp_ciphers = "AES-256-GCM:AES-128-GCM";
     o->authname = "SHA1";
     o->prng_hash = "SHA1";
@@ -864,15 +863,14 @@
     o->replay = true;
     o->replay_window = DEFAULT_SEQ_BACKTRACK;
     o->replay_time = DEFAULT_TIME_BACKTRACK;
-    o->use_iv = true;
     o->key_direction = KEY_DIRECTION_BIDIRECTIONAL;
 #ifdef ENABLE_PREDICTION_RESISTANCE
     o->use_prediction_resistance = false;
 #endif
-    o->key_method = 2;
     o->tls_timeout = 2;
     o->renegotiate_bytes = -1;
     o->renegotiate_seconds = 3600;
+    o->renegotiate_seconds_min = -1;
     o->handshake_window = 60;
     o->transition_window = 3600;
     o->tls_cert_profile = NULL;
@@ -880,18 +878,16 @@
 #ifdef ENABLE_X509ALTUSERNAME
     o->x509_username_field = X509_USERNAME_FIELD_DEFAULT;
 #endif
-#endif /* ENABLE_CRYPTO */
 #ifdef ENABLE_PKCS11
     o->pkcs11_pin_cache_period = -1;
 #endif                  /* ENABLE_PKCS11 */
 
 /* P2MP server context features */
-#if P2MP_SERVER
     o->auth_token_generate = false;
 
     /* Set default --tmp-dir */
 #ifdef _WIN32
-    /* On Windows, find temp dir via enviroment variables */
+    /* On Windows, find temp dir via environment variables */
     o->tmp_dir = win_get_tempdir();
 #else
     /* Non-windows platforms use $TMPDIR, and if not set, default to '/tmp' */
@@ -901,7 +897,6 @@
         o->tmp_dir = "/tmp";
     }
 #endif /* _WIN32 */
-#endif /* P2MP_SERVER */
     o->allow_recursive_routing = false;
 }
 
@@ -957,6 +952,10 @@
 
 #define SHOW_PARM(name, value, format) msg(D_SHOW_PARMS, "  " #name " = " format, (value))
 #define SHOW_STR(var)       SHOW_PARM(var, (o->var ? o->var : "[UNDEF]"), "'%s'")
+#define SHOW_STR_INLINE(var)    SHOW_PARM(var, \
+                                          o->var ## _inline ? "[INLINE]" : \
+                                          (o->var ? o->var : "[UNDEF]"), \
+                                          "'%s'")
 #define SHOW_INT(var)       SHOW_PARM(var, o->var, "%d")
 #define SHOW_UINT(var)      SHOW_PARM(var, o->var, "%u")
 #define SHOW_UNSIGNED(var)  SHOW_PARM(var, o->var, "0x%08x")
@@ -994,7 +993,7 @@
     setenv_int(es, "verb", o->verbosity);
     setenv_int(es, "daemon", o->daemon);
     setenv_int(es, "daemon_log_redirect", o->log);
-    setenv_unsigned(es, "daemon_start_time", time(NULL));
+    setenv_long_long(es, "daemon_start_time", time(NULL));
     setenv_int(es, "daemon_pid", platform_getpid());
 
     if (o->connection_list)
@@ -1083,7 +1082,6 @@
     return ret;
 }
 
-#ifdef ENABLE_CRYPTO
 static uint8_t *
 parse_hash_fingerprint(const char *str, int nbytes, int msglevel, struct gc_arena *gc)
 {
@@ -1125,13 +1123,22 @@
     }
     return ret;
 }
-#endif /* ifdef ENABLE_CRYPTO */
 
 #ifdef _WIN32
 
 #ifndef ENABLE_SMALL
 
 static void
+show_dhcp_option_list(const char *name, const char * const*array, int len)
+{
+    int i;
+    for (i = 0; i < len; ++i)
+    {
+        msg(D_SHOW_PARMS, "  %s[%d] = %s", name, i, array[i] );
+    }
+}
+
+static void
 show_dhcp_option_addrs(const char *name, const in_addr_t *array, int len)
 {
     struct gc_arena gc = gc_new();
@@ -1166,6 +1173,7 @@
     show_dhcp_option_addrs("WINS", o->wins, o->wins_len);
     show_dhcp_option_addrs("NTP", o->ntp, o->ntp_len);
     show_dhcp_option_addrs("NBDD", o->nbdd, o->nbdd_len);
+    show_dhcp_option_list("DOMAIN-SEARCH", o->domain_search_list, o->domain_search_list_len);
 }
 
 #endif /* ifndef ENABLE_SMALL */
@@ -1216,6 +1224,23 @@
 
 #endif /* if defined(_WIN32) || defined(TARGET_ANDROID) */
 
+static const char *
+print_vlan_accept(enum vlan_acceptable_frames mode)
+{
+    switch (mode)
+    {
+        case VLAN_ONLY_TAGGED:
+            return "tagged";
+
+        case VLAN_ONLY_UNTAGGED_OR_PRIORITY:
+            return "untagged";
+
+        case VLAN_ALL:
+            return "all";
+    }
+    return NULL;
+}
+
 #if P2MP
 
 #ifndef ENABLE_SMALL
@@ -1225,7 +1250,6 @@
 {
     struct gc_arena gc = gc_new();
 
-#if P2MP_SERVER
     msg(D_SHOW_PARMS, "  server_network = %s", print_in_addr_t(o->server_network, 0, &gc));
     msg(D_SHOW_PARMS, "  server_netmask = %s", print_in_addr_t(o->server_netmask, 0, &gc));
     msg(D_SHOW_PARMS, "  server_network_ipv6 = %s", print_in6_addr(o->server_network_ipv6, 0, &gc) );
@@ -1281,11 +1305,14 @@
     SHOW_BOOL(auth_user_pass_verify_script_via_file);
     SHOW_BOOL(auth_token_generate);
     SHOW_INT(auth_token_lifetime);
+    SHOW_STR_INLINE(auth_token_secret_file);
 #if PORT_SHARE
     SHOW_STR(port_share_host);
     SHOW_STR(port_share_port);
 #endif
-#endif /* P2MP_SERVER */
+    SHOW_BOOL(vlan_tagging);
+    msg(D_SHOW_PARMS, "  vlan_accept = %s", print_vlan_accept(o->vlan_accept));
+    SHOW_INT(vlan_pvid);
 
     SHOW_BOOL(client);
     SHOW_BOOL(pull);
@@ -1296,8 +1323,6 @@
 
 #endif /* ! ENABLE_SMALL */
 
-#if P2MP_SERVER
-
 static void
 option_iroute(struct options *o,
               const char *network_str,
@@ -1345,7 +1370,6 @@
     ir->next = o->iroutes_ipv6;
     o->iroutes_ipv6 = ir;
 }
-#endif /* P2MP_SERVER */
 #endif /* P2MP */
 
 #ifndef ENABLE_SMALL
@@ -1383,9 +1407,7 @@
     gc_detach(&o->gc);
     o->routes = NULL;
     o->client_nat = NULL;
-#if P2MP_SERVER
     clone_push_list(o);
-#endif
 }
 
 void
@@ -1451,9 +1473,13 @@
 #endif
     SHOW_INT(mssfix);
 
-#ifdef ENABLE_OCC
     SHOW_INT(explicit_exit_notification);
-#endif
+
+    SHOW_STR_INLINE(tls_auth_file);
+    SHOW_PARM(key_direction, keydirection2ascii(o->key_direction, false, true),
+              "%s");
+    SHOW_STR_INLINE(tls_crypt_file);
+    SHOW_STR_INLINE(tls_crypt_v2_file);
 }
 
 
@@ -1511,14 +1537,13 @@
     SHOW_INT(persist_mode);
 #endif
 
-#ifdef ENABLE_CRYPTO
     SHOW_BOOL(show_ciphers);
     SHOW_BOOL(show_digests);
     SHOW_BOOL(show_engines);
     SHOW_BOOL(genkey);
+    SHOW_STR(genkey_filename);
     SHOW_STR(key_pass_file);
     SHOW_BOOL(show_tls_ciphers);
-#endif
 
     SHOW_INT(connect_retry_max);
     show_connection_entries(o);
@@ -1542,9 +1567,7 @@
 #ifdef ENABLE_FEATURE_SHAPER
     SHOW_INT(shaper);
 #endif
-#ifdef ENABLE_OCC
     SHOW_INT(mtu_test);
-#endif
 
     SHOW_BOOL(mlock);
 
@@ -1596,9 +1619,7 @@
     SHOW_INT(status_file_version);
     SHOW_INT(status_file_update_freq);
 
-#ifdef ENABLE_OCC
     SHOW_BOOL(occ);
-#endif
     SHOW_INT(rcvbuf);
     SHOW_INT(sndbuf);
 #if defined(TARGET_LINUX) && HAVE_DECL_SO_MARK
@@ -1653,8 +1674,7 @@
     }
 #endif
 
-#ifdef ENABLE_CRYPTO
-    SHOW_STR(shared_secret_file);
+    SHOW_STR_INLINE(shared_secret_file);
     SHOW_PARM(key_direction, keydirection2ascii(o->key_direction, false, true), "%s");
     SHOW_STR(ciphername);
     SHOW_BOOL(ncp_enabled);
@@ -1671,7 +1691,6 @@
     SHOW_INT(replay_window);
     SHOW_INT(replay_time);
     SHOW_STR(packet_id_file);
-    SHOW_BOOL(use_iv);
     SHOW_BOOL(test_crypto);
 #ifdef ENABLE_PREDICTION_RESISTANCE
     SHOW_BOOL(use_prediction_resistance);
@@ -1679,30 +1698,29 @@
 
     SHOW_BOOL(tls_server);
     SHOW_BOOL(tls_client);
-    SHOW_INT(key_method);
-    SHOW_STR(ca_file);
+    SHOW_STR_INLINE(ca_file);
     SHOW_STR(ca_path);
-    SHOW_STR(dh_file);
-#ifdef MANAGMENT_EXTERNAL_KEY
+    SHOW_STR_INLINE(dh_file);
+#ifdef ENABLE_MANAGEMENT
     if ((o->management_flags & MF_EXTERNAL_CERT))
     {
         SHOW_PARM("cert_file","EXTERNAL_CERT","%s");
     }
     else
 #endif
-    SHOW_STR(cert_file);
-    SHOW_STR(extra_certs_file);
+    SHOW_STR_INLINE(cert_file);
+    SHOW_STR_INLINE(extra_certs_file);
 
-#ifdef MANAGMENT_EXTERNAL_KEY
+#ifdef ENABLE_MANAGEMENT
     if ((o->management_flags & MF_EXTERNAL_KEY))
     {
         SHOW_PARM("priv_key_file","EXTERNAL_PRIVATE_KEY","%s");
     }
     else
 #endif
-    SHOW_STR(priv_key_file);
+    SHOW_STR_INLINE(priv_key_file);
 #ifndef ENABLE_CRYPTO_MBEDTLS
-    SHOW_STR(pkcs12_file);
+    SHOW_STR_INLINE(pkcs12_file);
 #endif
 #ifdef ENABLE_CRYPTOAPI
     SHOW_STR(cryptoapi_cert);
@@ -1714,7 +1732,7 @@
     SHOW_STR(tls_export_cert);
     SHOW_INT(verify_x509_type);
     SHOW_STR(verify_x509_name);
-    SHOW_STR(crl_file);
+    SHOW_STR_INLINE(crl_file);
     SHOW_INT(ns_cert_type);
     {
         int i;
@@ -1736,14 +1754,10 @@
     SHOW_INT(transition_window);
 
     SHOW_BOOL(single_session);
-#ifdef ENABLE_PUSH_PEER_INFO
     SHOW_BOOL(push_peer_info);
-#endif
     SHOW_BOOL(tls_exit);
 
-    SHOW_STR(tls_auth_file);
-    SHOW_STR(tls_crypt_file);
-#endif /* ENABLE_CRYPTO */
+    SHOW_STR(tls_crypt_v2_metadata);
 
 #ifdef ENABLE_PKCS11
     {
@@ -1969,7 +1983,25 @@
 }
 
 static void
-options_postprocess_verify_ce(const struct options *options, const struct connection_entry *ce)
+connection_entry_preload_key(const char **key_file, bool *key_inline,
+                             struct gc_arena *gc)
+{
+    if (key_file && *key_file && !(*key_inline))
+    {
+        struct buffer in = buffer_read_from_file(*key_file, gc);
+        if (!buf_valid(&in))
+        {
+            msg(M_FATAL, "Cannot pre-load keyfile (%s)", *key_file);
+        }
+
+        *key_file = (const char *) in.data;
+        *key_inline = true;
+    }
+}
+
+static void
+options_postprocess_verify_ce(const struct options *options,
+                              const struct connection_entry *ce)
 {
     struct options defaults;
     int dev = DEV_TYPE_UNDEF;
@@ -1977,14 +2009,14 @@
 
     init_options(&defaults, true);
 
-#ifdef ENABLE_CRYPTO
     if (options->test_crypto)
     {
         notnull(options->shared_secret_file, "key file (--secret)");
     }
     else
-#endif
-    notnull(options->dev, "TUN/TAP device (--dev)");
+    {
+        notnull(options->dev, "TUN/TAP device (--dev)");
+    }
 
     /*
      * Get tun/tap/null device type
@@ -1997,7 +2029,9 @@
      */
     if (ce->proto == PROTO_TCP)
     {
-        msg(M_USAGE, "--proto tcp is ambiguous in this context.  Please specify --proto tcp-server or --proto tcp-client");
+        msg(M_USAGE,
+            "--proto tcp is ambiguous in this context. Please specify "
+            "--proto tcp-server or --proto tcp-client");
     }
 
     /*
@@ -2025,10 +2059,7 @@
     }
 
     if (options->inetd == INETD_NOWAIT
-#ifdef ENABLE_CRYPTO
-        && !(options->tls_server || options->tls_client)
-#endif
-        )
+        && !(options->tls_server || options->tls_client))
     {
         msg(M_USAGE, "--inetd nowait can only be used in TLS mode");
     }
@@ -2038,6 +2069,12 @@
         msg(M_USAGE, "--inetd nowait only makes sense in --dev tap mode");
     }
 
+    if (options->inetd)
+    {
+        msg(M_WARN,
+            "DEPRECATED OPTION: --inetd mode is deprecated and will be removed "
+            "in OpenVPN 2.6");
+    }
 
     if (options->lladdr && dev != DEV_TYPE_TAP)
     {
@@ -2049,15 +2086,15 @@
      */
     if (options->ce.tun_mtu_defined && options->ce.link_mtu_defined)
     {
-        msg(M_USAGE, "only one of --tun-mtu or --link-mtu may be defined (note that --ifconfig implies --link-mtu %d)", LINK_MTU_DEFAULT);
+        msg(M_USAGE,
+            "only one of --tun-mtu or --link-mtu may be defined (note that "
+            "--ifconfig implies --link-mtu %d)", LINK_MTU_DEFAULT);
     }
 
-#ifdef ENABLE_OCC
     if (!proto_is_udp(ce->proto) && options->mtu_test)
     {
         msg(M_USAGE, "--mtu-test only makes sense with --proto udp");
     }
-#endif
 
     /* will we be pulling options from server? */
 #if P2MP
@@ -2078,18 +2115,23 @@
     if (string_defined_equal(ce->remote, options->ifconfig_local)
         || string_defined_equal(ce->remote, options->ifconfig_remote_netmask))
     {
-        msg(M_USAGE, "--local and --remote addresses must be distinct from --ifconfig addresses");
+        msg(M_USAGE,
+            "--local and --remote addresses must be distinct from --ifconfig "
+            "addresses");
     }
 
     if (string_defined_equal(ce->local, options->ifconfig_local)
         || string_defined_equal(ce->local, options->ifconfig_remote_netmask))
     {
-        msg(M_USAGE, "--local addresses must be distinct from --ifconfig addresses");
+        msg(M_USAGE,
+            "--local addresses must be distinct from --ifconfig addresses");
     }
 
-    if (string_defined_equal(options->ifconfig_local, options->ifconfig_remote_netmask))
+    if (string_defined_equal(options->ifconfig_local,
+                             options->ifconfig_remote_netmask))
     {
-        msg(M_USAGE, "local and remote/netmask --ifconfig addresses must be different");
+        msg(M_USAGE,
+            "local and remote/netmask --ifconfig addresses must be different");
     }
 
     if (ce->bind_defined && !ce->bind_local)
@@ -2099,12 +2141,14 @@
 
     if (ce->local && !ce->bind_local)
     {
-        msg(M_USAGE, "--local and --nobind don't make sense when used together");
+        msg(M_USAGE,
+            "--local and --nobind don't make sense when used together");
     }
 
     if (ce->local_port_defined && !ce->bind_local)
     {
-        msg(M_USAGE, "--lport and --nobind don't make sense when used together");
+        msg(M_USAGE,
+            "--lport and --nobind don't make sense when used together");
     }
 
     if (!ce->remote && !ce->bind_local)
@@ -2138,8 +2182,18 @@
             "passwords is STRONGLY discouraged and considered insecure");
     }
 
-#endif
+#endif /* ifdef ENABLE_MANAGEMENT */
 
+#if  defined(ENABLE_MANAGEMENT)
+    if ((tls_version_max() >= TLS_VER_1_3)
+        && (options->management_flags & MF_EXTERNAL_KEY)
+        && !(options->management_flags & (MF_EXTERNAL_KEY_NOPADDING))
+        )
+    {
+        msg(M_ERR, "management-external-key with OpenSSL 1.1.1 requires "
+            "the nopadding argument/support");
+    }
+#endif
     /*
      * Windows-specific options.
      */
@@ -2157,12 +2211,18 @@
     }
 
     if (options->tuntap_options.dhcp_options
+        && options->windows_driver != WINDOWS_DRIVER_WINTUN
         && options->tuntap_options.ip_win32_type != IPW32_SET_DHCP_MASQ
         && options->tuntap_options.ip_win32_type != IPW32_SET_ADAPTIVE)
     {
-        msg(M_USAGE, "--dhcp-options requires --ip-win32 dynamic or adaptive");
+        msg(M_USAGE, "--dhcp-option requires --ip-win32 dynamic or adaptive");
     }
-#endif
+
+    if (options->windows_driver == WINDOWS_DRIVER_WINTUN && dev != DEV_TYPE_TUN)
+    {
+        msg(M_USAGE, "--windows-driver wintun requires --dev tun");
+    }
+#endif /* ifdef _WIN32 */
 
     /*
      * Check that protocol options make sense.
@@ -2175,12 +2235,11 @@
     }
 #endif
 
-#ifdef ENABLE_OCC
     if (!proto_is_udp(ce->proto) && ce->explicit_exit_notification)
     {
-        msg(M_USAGE, "--explicit-exit-notify can only be used with --proto udp");
+        msg(M_USAGE,
+            "--explicit-exit-notify can only be used with --proto udp");
     }
-#endif
 
     if (!ce->remote && ce->proto == PROTO_TCP_CLIENT)
     {
@@ -2189,16 +2248,21 @@
 
     if ((ce->http_proxy_options) && ce->proto != PROTO_TCP_CLIENT)
     {
-        msg(M_USAGE, "--http-proxy MUST be used in TCP Client mode (i.e. --proto tcp-client)");
+        msg(M_USAGE,
+            "--http-proxy MUST be used in TCP Client mode (i.e. --proto "
+            "tcp-client)");
     }
+
     if ((ce->http_proxy_options) && !ce->http_proxy_options->server)
     {
-        msg(M_USAGE, "--http-proxy not specified but other http proxy options present");
+        msg(M_USAGE,
+            "--http-proxy not specified but other http proxy options present");
     }
 
     if (ce->http_proxy_options && ce->socks_proxy_server)
     {
-        msg(M_USAGE, "--http-proxy can not be used together with --socks-proxy");
+        msg(M_USAGE,
+            "--http-proxy can not be used together with --socks-proxy");
     }
 
     if (ce->socks_proxy_server && ce->proto == PROTO_TCP_SERVER)
@@ -2211,13 +2275,14 @@
         msg(M_USAGE, "TCP server mode allows at most one --remote address");
     }
 
-#if P2MP_SERVER
-
     /*
      * Check consistency of --mode server options.
      */
     if (options->mode == MODE_SERVER)
     {
+#ifdef TARGET_ANDROID
+        msg(M_FATAL, "--mode server not supported on Android");
+#endif
         if (!(dev == DEV_TYPE_TUN || dev == DEV_TYPE_TAP))
         {
             msg(M_USAGE, "--mode server only works with --dev tun or --dev tap");
@@ -2263,8 +2328,9 @@
         {
             msg(M_USAGE, "--socks-proxy cannot be used with --mode server");
         }
-        /* <connection> blocks force to have a remote embedded, so we check for the
-         * --remote and bail out if it  is present */
+        /* <connection> blocks force to have a remote embedded, so we check
+         * for the --remote and bail out if it is present
+         */
         if (options->connection_list->len >1
             || options->connection_list->array[0]->remote)
         {
@@ -2281,12 +2347,15 @@
         }
         if (options->ipchange)
         {
-            msg(M_USAGE, "--ipchange cannot be used with --mode server (use --client-connect instead)");
+            msg(M_USAGE,
+                "--ipchange cannot be used with --mode server (use "
+                "--client-connect instead)");
         }
         if (!(proto_is_dgram(ce->proto) || ce->proto == PROTO_TCP_SERVER))
         {
-            msg(M_USAGE, "--mode server currently only supports "
-                "--proto udp or --proto tcp-server or --proto tcp6-server");
+            msg(M_USAGE,
+                "--mode server currently only supports --proto udp or --proto "
+                "tcp-server or --proto tcp6-server");
         }
         if (!proto_is_udp(ce->proto) && (options->cf_max || options->cf_per))
         {
@@ -2308,9 +2377,12 @@
         {
             msg(M_USAGE, "--up-delay cannot be used with --mode server");
         }
-        if (!options->ifconfig_pool_defined && options->ifconfig_pool_persist_filename)
+        if (!options->ifconfig_pool_defined
+            && !options->ifconfig_ipv6_pool_defined
+            && options->ifconfig_pool_persist_filename)
         {
-            msg(M_USAGE, "--ifconfig-pool-persist must be used with --ifconfig-pool");
+            msg(M_USAGE,
+                "--ifconfig-pool-persist must be used with --ifconfig-pool or --ifconfig-ipv6-pool");
         }
         if (options->ifconfig_ipv6_pool_defined && !options->ifconfig_ipv6_local)
         {
@@ -2328,11 +2400,11 @@
         {
             msg(M_USAGE, "--ccd-exclusive must be used with --client-config-dir");
         }
-        if (options->key_method != 2)
+        if (options->auth_token_generate && !options->renegotiate_seconds)
         {
-            msg(M_USAGE, "--mode server requires --key-method 2");
+            msg(M_USAGE, "--auth-gen-token needs a non-infinite "
+                "--renegotiate_seconds setting");
         }
-
         {
             const bool ccnr = (options->auth_user_pass_verify_script
                                || PLUGIN_OPTION_LIST(options)
@@ -2351,6 +2423,22 @@
                 msg(M_USAGE, "--auth-user-pass-optional %s", postfix);
             }
         }
+
+        if (options->vlan_tagging && dev != DEV_TYPE_TAP)
+        {
+            msg(M_USAGE, "--vlan-tagging must be used with --dev tap");
+        }
+        if (!options->vlan_tagging)
+        {
+            if (options->vlan_accept != defaults.vlan_accept)
+            {
+                msg(M_USAGE, "--vlan-accept requires --vlan-tagging");
+            }
+            if (options->vlan_pvid != defaults.vlan_pvid)
+            {
+                msg(M_USAGE, "--vlan-pvid requires --vlan-tagging");
+            }
+        }
     }
     else
     {
@@ -2401,7 +2489,7 @@
         }
         if (options->ssl_flags & (SSLF_CLIENT_CERT_NOT_REQUIRED|SSLF_CLIENT_CERT_OPTIONAL))
         {
-            msg(M_USAGE, "--client-cert-not-required and --verify-client-cert require --mode server");
+            msg(M_USAGE, "--verify-client-cert requires --mode server");
         }
         if (options->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME)
         {
@@ -2440,38 +2528,18 @@
         {
             msg(M_USAGE, "--stale-routes-check requires --mode server");
         }
-        if (compat_flag(COMPAT_FLAG_QUERY | COMPAT_NO_NAME_REMAPPING))
+
+        if (options->vlan_tagging)
         {
-            msg(M_USAGE, "--compat-x509-names no-remapping requires --mode server");
+            msg(M_USAGE, "--vlan-tagging requires --mode server");
         }
     }
-#endif /* P2MP_SERVER */
-
-#ifdef ENABLE_CRYPTO
-
-    if (options->ncp_enabled && !tls_check_ncp_cipher_list(options->ncp_ciphers))
-    {
-        msg(M_USAGE, "NCP cipher list contains unsupported ciphers.");
-    }
-    if (options->ncp_enabled && !options->use_iv)
-    {
-        msg(M_USAGE, "--no-iv not allowed when NCP is enabled.");
-    }
-    if (!options->use_iv)
-    {
-        msg(M_WARN, "WARNING: --no-iv is deprecated and will be removed in 2.5");
-    }
 
     if (options->keysize)
     {
         msg(M_WARN, "WARNING: --keysize is DEPRECATED and will be removed in OpenVPN 2.6");
     }
 
-    if (!options->replay)
-    {
-        msg(M_WARN, "WARNING: --no-replay is DEPRECATED and will be removed in OpenVPN 2.5");
-    }
-
     /*
      * Check consistency of replay options
      */
@@ -2494,17 +2562,10 @@
     if (options->ssl_flags & (SSLF_CLIENT_CERT_NOT_REQUIRED|SSLF_CLIENT_CERT_OPTIONAL))
     {
         msg(M_WARN, "WARNING: POTENTIALLY DANGEROUS OPTION "
-            "--verify-client-cert none|optional (or --client-cert-not-required) "
+            "--verify-client-cert none|optional "
             "may accept clients which do not present a certificate");
     }
 
-    if (options->key_method == 1)
-    {
-        msg(M_WARN, "WARNING: --key-method 1 is deprecated and will be removed "
-            "in OpenVPN 2.5.  By default --key-method 2 will be used if not set "
-            "in the configuration file, which is the recommended approach.");
-    }
-
     const int tls_version_max =
         (options->ssl_flags >> SSLF_TLS_VERSION_MAX_SHIFT)
         & SSLF_TLS_VERSION_MAX_MASK;
@@ -2540,7 +2601,7 @@
             {
                 msg(M_USAGE, "Parameter --key cannot be used when --pkcs11-provider is also specified.");
             }
-#ifdef MANAGMENT_EXTERNAL_KEY
+#ifdef ENABLE_MANAGEMENT
             if (options->management_flags & MF_EXTERNAL_KEY)
             {
                 msg(M_USAGE, "Parameter --management-external-key cannot be used when --pkcs11-provider is also specified.");
@@ -2563,7 +2624,7 @@
         }
         else
 #endif /* ifdef ENABLE_PKCS11 */
-#ifdef MANAGMENT_EXTERNAL_KEY
+#ifdef ENABLE_MANAGEMENT
         if ((options->management_flags & MF_EXTERNAL_KEY) && options->priv_key_file)
         {
             msg(M_USAGE, "--key and --management-external-key are mutually exclusive");
@@ -2600,7 +2661,7 @@
             {
                 msg(M_USAGE, "Parameter --pkcs12 cannot be used when --cryptoapicert is also specified.");
             }
-#ifdef MANAGMENT_EXTERNAL_KEY
+#ifdef ENABLE_MANAGEMENT
             if (options->management_flags & MF_EXTERNAL_KEY)
             {
                 msg(M_USAGE, "Parameter --management-external-key cannot be used when --cryptoapicert is also specified.");
@@ -2630,7 +2691,7 @@
             {
                 msg(M_USAGE, "Parameter --key cannot be used when --pkcs12 is also specified.");
             }
-#ifdef MANAGMENT_EXTERNAL_KEY
+#ifdef ENABLE_MANAGEMENT
             if (options->management_flags & MF_EXTERNAL_KEY)
             {
                 msg(M_USAGE, "Parameter --management-external-key cannot be used when --pkcs12 is also specified.");
@@ -2663,7 +2724,7 @@
             {
 
                 const int sum =
-#ifdef MANAGMENT_EXTERNAL_KEY
+#ifdef ENABLE_MANAGEMENT
                     ((options->cert_file != NULL) || (options->management_flags & MF_EXTERNAL_CERT))
                     +((options->priv_key_file != NULL) || (options->management_flags & MF_EXTERNAL_KEY));
 #else
@@ -2687,20 +2748,25 @@
             }
             else
             {
-#ifdef MANAGMENT_EXTERNAL_KEY
+#ifdef ENABLE_MANAGEMENT
                 if (!(options->management_flags & MF_EXTERNAL_CERT))
 #endif
                 notnull(options->cert_file, "certificate file (--cert) or PKCS#12 file (--pkcs12)");
-#ifdef MANAGMENT_EXTERNAL_KEY
+#ifdef ENABLE_MANAGEMENT
                 if (!(options->management_flags & MF_EXTERNAL_KEY))
 #endif
                 notnull(options->priv_key_file, "private key file (--key) or PKCS#12 file (--pkcs12)");
             }
         }
-        if (options->tls_auth_file && options->tls_crypt_file)
+        if (ce->tls_auth_file && ce->tls_crypt_file)
         {
             msg(M_USAGE, "--tls-auth and --tls-crypt are mutually exclusive");
         }
+        if (options->tls_client && ce->tls_crypt_v2_file
+            && (ce->tls_auth_file || ce->tls_crypt_file))
+        {
+            msg(M_USAGE, "--tls-crypt-v2, --tls-auth and --tls-crypt are mutually exclusive in client mode");
+        }
     }
     else
     {
@@ -2736,13 +2802,11 @@
         MUST_BE_UNDEF(transition_window);
         MUST_BE_UNDEF(tls_auth_file);
         MUST_BE_UNDEF(tls_crypt_file);
+        MUST_BE_UNDEF(tls_crypt_v2_file);
         MUST_BE_UNDEF(single_session);
-#ifdef ENABLE_PUSH_PEER_INFO
         MUST_BE_UNDEF(push_peer_info);
-#endif
         MUST_BE_UNDEF(tls_exit);
         MUST_BE_UNDEF(crl_file);
-        MUST_BE_UNDEF(key_method);
         MUST_BE_UNDEF(ns_cert_type);
         MUST_BE_UNDEF(remote_cert_ku[0]);
         MUST_BE_UNDEF(remote_cert_eku);
@@ -2759,7 +2823,6 @@
         }
     }
 #undef MUST_BE_UNDEF
-#endif /* ENABLE_CRYPTO */
 
 #if P2MP
     if (options->auth_user_pass_file && !options->pull)
@@ -2776,7 +2839,6 @@
 {
     const int dev = dev_type_enum(o->dev, o->dev_type);
 
-#if P2MP_SERVER
     if (o->server_defined || o->server_bridge_defined || o->server_bridge_proxy_dhcp)
     {
         if (ce->proto == PROTO_TCP)
@@ -2784,7 +2846,7 @@
             ce->proto = PROTO_TCP_SERVER;
         }
     }
-#endif
+
 #if P2MP
     if (o->client)
     {
@@ -2795,12 +2857,14 @@
     }
 #endif
 
-    if (ce->proto == PROTO_TCP_CLIENT && !ce->local && !ce->local_port_defined && !ce->bind_defined)
+    if (ce->proto == PROTO_TCP_CLIENT && !ce->local
+        && !ce->local_port_defined && !ce->bind_defined)
     {
         ce->bind_local = false;
     }
 
-    if (ce->proto == PROTO_UDP && ce->socks_proxy_server && !ce->local && !ce->local_port_defined && !ce->bind_defined)
+    if (ce->proto == PROTO_UDP && ce->socks_proxy_server && !ce->local
+        && !ce->local_port_defined && !ce->bind_defined)
     {
         ce->bind_local = false;
     }
@@ -2810,7 +2874,9 @@
         ce->local_port = NULL;
     }
 
-    /* if protocol forcing is enabled, disable all protocols except for the forced one */
+    /* if protocol forcing is enabled, disable all protocols
+     * except for the forced one
+     */
     if (o->proto_force >= 0 && o->proto_force != ce->proto)
     {
         ce->flags |= CE_DISABLED;
@@ -2865,6 +2931,37 @@
         }
     }
 
+    /*
+     * Set per-connection block tls-auth/crypt/crypto-v2 fields if undefined.
+     *
+     * At the end only one of these will be really set because the parser
+     * logic prevents configurations where more are set.
+     */
+    if (!ce->tls_auth_file && !ce->tls_crypt_file && !ce->tls_crypt_v2_file)
+    {
+        ce->tls_auth_file = o->tls_auth_file;
+        ce->tls_auth_file_inline = o->tls_auth_file_inline;
+        ce->key_direction = o->key_direction;
+
+        ce->tls_crypt_file = o->tls_crypt_file;
+        ce->tls_crypt_file_inline = o->tls_crypt_file_inline;
+
+        ce->tls_crypt_v2_file = o->tls_crypt_v2_file;
+        ce->tls_crypt_v2_file_inline = o->tls_crypt_v2_file_inline;
+    }
+
+    /* Pre-cache tls-auth/crypt(-v2) key file if persist-key was specified and
+     * keys were not already embedded in the config file.
+     */
+    if (o->persist_key)
+    {
+        connection_entry_preload_key(&ce->tls_auth_file,
+                                     &ce->tls_auth_file_inline, &o->gc);
+        connection_entry_preload_key(&ce->tls_crypt_file,
+                                     &ce->tls_crypt_file_inline, &o->gc);
+        connection_entry_preload_key(&ce->tls_crypt_v2_file,
+                                     &ce->tls_crypt_v2_file_inline, &o->gc);
+    }
 }
 
 #ifdef _WIN32
@@ -2900,9 +2997,19 @@
     }
 
 #ifdef _WIN32
+    /* when using wintun, kernel doesn't send DHCP requests, so don't use it */
+    if (options->windows_driver == WINDOWS_DRIVER_WINTUN
+        && (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;
+    }
+
     if ((dev == DEV_TYPE_TUN || dev == DEV_TYPE_TAP) && !options->route_delay_defined)
     {
-        if (options->mode == MODE_POINT_TO_POINT)
+        /* delay may only be necessary when we perform DHCP handshake */
+        const bool dhcp = (options->tuntap_options.ip_win32_type == IPW32_SET_DHCP_MASQ)
+                          || (options->tuntap_options.ip_win32_type == IPW32_SET_ADAPTIVE);
+        if ((options->mode == MODE_POINT_TO_POINT) && dhcp)
         {
             options->route_delay_defined = true;
             options->route_delay = 5; /* Vista sometimes has a race without this */
@@ -2916,15 +3023,12 @@
     }
 
     remap_redirect_gateway_flags(options);
-#endif
 
-#if P2MP_SERVER
     /*
      * Check consistency of --mode server options.
      */
     if (options->mode == MODE_SERVER)
     {
-#ifdef _WIN32
         /*
          * We need to explicitly set --tap-sleep because
          * we do not schedule event timers in the top-level context.
@@ -2935,9 +3039,8 @@
             options->tuntap_options.tap_sleep = options->route_delay;
         }
         options->route_delay_defined = false;
-#endif
     }
-#endif
+#endif /* ifdef _WIN32 */
 
 #ifdef DEFAULT_PKCS11_MODULE
     /* If p11-kit is present on the system then load its p11-kit-proxy.so
@@ -2969,6 +3072,67 @@
 }
 
 static void
+options_postprocess_cipher(struct options *o)
+{
+    if (!o->pull && !(o->mode == MODE_SERVER))
+    {
+        /* we are in the classic P2P mode */
+        o->ncp_enabled = false;
+        msg( M_WARN, "Cipher negotiation is disabled since neither "
+             "P2MP client nor server mode is enabled");
+
+        /* If the cipher is not set, use the old default of BF-CBC. We will
+         * warn that this is deprecated on cipher initialisation, no need
+         * to warn here as well */
+        if (!o->ciphername)
+        {
+            o->ciphername = "BF-CBC";
+        }
+        return;
+    }
+
+    /* pull or P2MP mode */
+    if (!o->ciphername)
+    {
+        if (!o->ncp_enabled)
+        {
+            msg(M_USAGE, "--ncp-disable needs an explicit --cipher or "
+                         "--data-ciphers-fallback config option");
+        }
+
+        msg(M_WARN, "--cipher is not set. Previous OpenVPN version defaulted to "
+            "BF-CBC as fallback when cipher negotiation failed in this case. "
+            "If you need this fallback please add '--data-ciphers-fallback "
+            "BF-CBC' to your configuration and/or add BF-CBC to "
+            "--data-ciphers.");
+
+        /* We still need to set the ciphername to BF-CBC since various other
+         * parts of OpenVPN assert that the ciphername is set */
+        o->ciphername = "BF-CBC";
+    }
+    else if (!o->enable_ncp_fallback
+             && !tls_item_in_cipher_list(o->ciphername, o->ncp_ciphers))
+    {
+        msg(M_WARN, "DEPRECATED OPTION: --cipher set to '%s' but missing in"
+            " --data-ciphers (%s). Future OpenVPN version will "
+            "ignore --cipher for cipher negotiations. "
+            "Add '%s' to --data-ciphers or change --cipher '%s' to "
+            "--data-ciphers-fallback '%s' to silence this warning.",
+            o->ciphername, o->ncp_ciphers, o->ciphername,
+            o->ciphername, o->ciphername);
+        o->enable_ncp_fallback = true;
+
+        /* Append the --cipher to ncp_ciphers to allow it in NCP */
+        size_t newlen = strlen(o->ncp_ciphers) + 1 + strlen(o->ciphername) + 1;
+        char *ncp_ciphers = gc_malloc(newlen, false, &o->gc);
+
+        ASSERT(openvpn_snprintf(ncp_ciphers, newlen, "%s:%s", o->ncp_ciphers,
+                                o->ciphername));
+        o->ncp_ciphers = ncp_ciphers;
+    }
+}
+
+static void
 options_postprocess_mutate(struct options *o)
 {
     int i;
@@ -2980,8 +3144,18 @@
     helper_keepalive(o);
     helper_tcp_nodelay(o);
 
+    options_postprocess_cipher(o);
     options_postprocess_mutate_invariant(o);
 
+    if (o->ncp_enabled)
+    {
+        o->ncp_ciphers = mutate_ncp_cipher_list(o->ncp_ciphers, &o->gc);
+        if (o->ncp_ciphers == NULL)
+        {
+            msg(M_USAGE, "NCP cipher list contains unsupported ciphers or is too long.");
+        }
+    }
+
     if (o->remote_list && !o->connection_list)
     {
         /*
@@ -3015,7 +3189,6 @@
         options_postprocess_mutate_ce(o, o->connection_list->array[i]);
     }
 
-#ifdef ENABLE_CRYPTO
     if (o->tls_server)
     {
         /* Check that DH file is specified, or explicitly disabled */
@@ -3029,20 +3202,9 @@
     {
         /* DH file is only meaningful in a tls-server context. */
         msg(M_WARN, "WARNING: Ignoring option 'dh' in tls-client mode, please only "
-                    "include this in your server configuration");
+            "include this in your server configuration");
         o->dh_file = NULL;
     }
-
-    /* cipher negotiation (NCP) currently assumes --pull or --mode server */
-    if (o->ncp_enabled
-        && !(o->pull || o->mode == MODE_SERVER) )
-    {
-        msg( M_WARN, "disabling NCP mode (--ncp-disable) because not "
-             "in P2MP client or server mode" );
-        o->ncp_enabled = false;
-    }
-#endif
-
 #if ENABLE_MANAGEMENT
     if (o->http_proxy_override)
     {
@@ -3064,12 +3226,11 @@
  */
 #ifndef ENABLE_SMALL  /** Expect people using the stripped down version to know what they do */
 
-#define CHKACC_FILE (1<<0)       /** Check for a file/directory precense */
-#define CHKACC_DIRPATH (1<<1)    /** Check for directory precense where a file should reside */
+#define CHKACC_FILE (1<<0)       /** Check for a file/directory presence */
+#define CHKACC_DIRPATH (1<<1)    /** Check for directory presence where a file should reside */
 #define CHKACC_FILEXSTWR (1<<2)  /** If file exists, is it writable? */
-#define CHKACC_INLINE (1<<3)     /** File is present if it's an inline file */
-#define CHKACC_ACPTSTDIN (1<<4)  /** If filename is stdin, it's allowed and "exists" */
-#define CHKACC_PRIVATE (1<<5)    /** Warn if this (private) file is group/others accessible */
+#define CHKACC_ACPTSTDIN (1<<3)  /** If filename is stdin, it's allowed and "exists" */
+#define CHKACC_PRIVATE (1<<4)    /** Warn if this (private) file is group/others accessible */
 
 static bool
 check_file_access(const int type, const char *file, const int mode, const char *opt)
@@ -3082,12 +3243,6 @@
         return false;
     }
 
-    /* If this may be an inline file, and the proper inline "filename" is set - no issues */
-    if ((type & CHKACC_INLINE) && streq(file, INLINE_FILE_TAG) )
-    {
-        return false;
-    }
-
     /* If stdin is allowed and the file name is 'stdin', then do no
      * further checks as stdin is always available
      */
@@ -3099,7 +3254,7 @@
     /* Is the directory path leading to the given file accessible? */
     if (type & CHKACC_DIRPATH)
     {
-        char *fullpath = string_alloc(file, NULL); /* POSIX dirname() implementaion may modify its arguments */
+        char *fullpath = string_alloc(file, NULL); /* POSIX dirname() implementation may modify its arguments */
         char *dirpath = dirname(fullpath);
 
         if (platform_access(dirpath, mode|X_OK) != 0)
@@ -3149,7 +3304,7 @@
         msg(M_NOPREFIX | M_OPTERR | M_ERRNO, "%s fails with '%s'", opt, file);
     }
 
-    /* Return true if an error occured */
+    /* Return true if an error occurred */
     return (errcode != 0 ? true : false);
 }
 
@@ -3173,14 +3328,8 @@
     {
         struct gc_arena gc = gc_new();
         struct buffer chroot_file;
-        int len = 0;
 
-        /* Build up a new full path including chroot directory */
-        len = strlen(chroot) + strlen(PATH_SEPARATOR_STR) + strlen(file) + 1;
-        chroot_file = alloc_buf_gc(len, &gc);
-        buf_printf(&chroot_file, "%s%s%s", chroot, PATH_SEPARATOR_STR, file);
-        ASSERT(chroot_file.len > 0);
-
+        chroot_file = prepend_dir(chroot, file, &gc);
         ret = check_file_access(type, BSTR(&chroot_file), mode, opt);
         gc_free(&gc);
     }
@@ -3192,6 +3341,38 @@
     return ret;
 }
 
+/**
+ * A wrapper for check_file_access_chroot() that returns false immediately if
+ * the file is inline (and therefore there is no access to check)
+ */
+static bool
+check_file_access_chroot_inline(bool is_inline, const char *chroot,
+                                const int type, const char *file,
+                                const int mode, const char *opt)
+{
+    if (is_inline)
+    {
+        return false;
+    }
+
+    return check_file_access_chroot(chroot, type, file, mode, opt);
+}
+
+/**
+ * A wrapper for check_file_access() that returns false immediately if the file
+ * is inline (and therefore there is no access to check)
+ */
+static bool
+check_file_access_inline(bool is_inline, const int type, const char *file,
+                         const int mode, const char *opt)
+{
+    if (is_inline)
+    {
+        return false;
+    }
+
+    return check_file_access(type, file, mode, opt);
+}
 
 /*
  * Verifies that the path in the "command" that comes after certain script options (e.g., --up) is a
@@ -3241,7 +3422,7 @@
         return_code = true;
     }
 
-    argv_reset(&argv);
+    argv_free(&argv);
 
     return return_code;
 }
@@ -3255,48 +3436,80 @@
 {
     bool errs = false;
 
-#ifdef ENABLE_CRYPTO
     /* ** SSL/TLS/crypto related files ** */
-    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->dh_file, R_OK, "--dh");
-    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->ca_file, R_OK, "--ca");
-    errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, options->ca_path, R_OK, "--capath");
-    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->cert_file, R_OK, "--cert");
-    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->extra_certs_file, R_OK,
-                              "--extra-certs");
-#ifdef MANAGMENT_EXTERNAL_KEY
+    errs |= check_file_access_inline(options->dh_file_inline, CHKACC_FILE,
+                                     options->dh_file, R_OK, "--dh");
+
+    errs |= check_file_access_inline(options->ca_file_inline, CHKACC_FILE,
+                                     options->ca_file, R_OK, "--ca");
+
+    errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE,
+                                     options->ca_path, R_OK, "--capath");
+
+    errs |= check_file_access_inline(options->cert_file_inline, CHKACC_FILE,
+                                     options->cert_file, R_OK, "--cert");
+
+    errs |= check_file_access_inline(options->extra_certs_file, CHKACC_FILE,
+                                     options->extra_certs_file, R_OK,
+                                     "--extra-certs");
+
+#ifdef ENABLE_MANAGMENT
     if (!(options->management_flags & MF_EXTERNAL_KEY))
 #endif
     {
-        errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
-                                  options->priv_key_file, R_OK, "--key");
+        errs |= check_file_access_inline(options->priv_key_file_inline,
+                                         CHKACC_FILE|CHKACC_PRIVATE,
+                                         options->priv_key_file, R_OK, "--key");
     }
-    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
-                              options->pkcs12_file, R_OK, "--pkcs12");
+
+    errs |= check_file_access_inline(options->pkcs12_file_inline,
+                                     CHKACC_FILE|CHKACC_PRIVATE,
+                                     options->pkcs12_file, R_OK, "--pkcs12");
 
     if (options->ssl_flags & SSLF_CRL_VERIFY_DIR)
     {
-        errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, options->crl_file, R_OK|X_OK,
+        errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE,
+                                         options->crl_file, R_OK|X_OK,
                                          "--crl-verify directory");
     }
     else
     {
-        errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE|CHKACC_INLINE,
-                                         options->crl_file, R_OK, "--crl-verify");
+        errs |= check_file_access_chroot_inline(options->crl_file_inline,
+                                                options->chroot_dir,
+                                                CHKACC_FILE, options->crl_file,
+                                                R_OK, "--crl-verify");
     }
 
-    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
-                              options->tls_auth_file, R_OK, "--tls-auth");
-    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
-                              options->tls_crypt_file, R_OK, "--tls-crypt");
-    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
-                              options->shared_secret_file, R_OK, "--secret");
+    ASSERT(options->connection_list);
+    for (int i = 0; i < options->connection_list->len; ++i)
+    {
+        struct connection_entry *ce = options->connection_list->array[i];
+
+        errs |= check_file_access_inline(ce->tls_auth_file_inline,
+                                         CHKACC_FILE|CHKACC_PRIVATE,
+                                         ce->tls_auth_file, R_OK,
+                                         "--tls-auth");
+        errs |= check_file_access_inline(ce->tls_crypt_file_inline,
+                                         CHKACC_FILE|CHKACC_PRIVATE,
+                                         ce->tls_crypt_file, R_OK,
+                                         "--tls-crypt");
+        errs |= check_file_access_inline(ce->tls_crypt_v2_file_inline,
+                                         CHKACC_FILE|CHKACC_PRIVATE,
+                                         ce->tls_crypt_v2_file, R_OK,
+                                         "--tls-crypt-v2");
+    }
+
+    errs |= check_file_access_inline(options->shared_secret_file_inline,
+                                     CHKACC_FILE|CHKACC_PRIVATE,
+                                     options->shared_secret_file, R_OK,
+                                     "--secret");
+
     errs |= check_file_access(CHKACC_DIRPATH|CHKACC_FILEXSTWR,
                               options->packet_id_file, R_OK|W_OK, "--replay-persist");
 
     /* ** Password files ** */
     errs |= check_file_access(CHKACC_FILE|CHKACC_ACPTSTDIN|CHKACC_PRIVATE,
                               options->key_pass_file, R_OK, "--askpass");
-#endif /* ENABLE_CRYPTO */
 #ifdef ENABLE_MANAGEMENT
     errs |= check_file_access(CHKACC_FILE|CHKACC_ACPTSTDIN|CHKACC_PRIVATE,
                               options->management_user_pass, R_OK,
@@ -3319,18 +3532,13 @@
                               R_OK|W_OK, "--status");
 
     /* ** Config related ** */
-#ifdef ENABLE_CRYPTO
     errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, options->tls_export_cert,
                                      R_OK|W_OK|X_OK, "--tls-export-cert");
-#endif /* ENABLE_CRYPTO */
-#if P2MP_SERVER
     errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, options->client_config_dir,
                                      R_OK|X_OK, "--client-config-dir");
     errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, options->tmp_dir,
                                      R_OK|W_OK|X_OK, "Temporary directory (--tmp-dir)");
 
-#endif /* P2MP_SERVER */
-
     if (errs)
     {
         msg(M_USAGE, "Please correct these errors.");
@@ -3383,6 +3591,14 @@
             o->pre_pull->client_nat = clone_client_nat_option_list(o->client_nat, &o->gc);
             o->pre_pull->client_nat_defined = true;
         }
+
+        o->pre_pull->route_default_gateway = o->route_default_gateway;
+        o->pre_pull->route_ipv6_default_gateway = o->route_ipv6_default_gateway;
+
+        /* Ping related options should be reset to the config values on reconnect */
+        o->pre_pull->ping_rec_timeout = o->ping_rec_timeout;
+        o->pre_pull->ping_rec_timeout_action = o->ping_rec_timeout_action;
+        o->pre_pull->ping_send_timeout = o->ping_send_timeout;
     }
 }
 
@@ -3418,6 +3634,9 @@
             o->routes_ipv6 = NULL;
         }
 
+        o->route_default_gateway = pp->route_default_gateway;
+        o->route_ipv6_default_gateway = pp->route_ipv6_default_gateway;
+
         if (pp->client_nat_defined)
         {
             cnol_check_alloc(o);
@@ -3429,6 +3648,10 @@
         }
 
         o->foreign_option_index = pp->foreign_option_index;
+
+        o->ping_rec_timeout = pp->ping_rec_timeout;
+        o->ping_rec_timeout_action = pp->ping_rec_timeout_action;
+        o->ping_send_timeout = pp->ping_send_timeout;
     }
 
     o->push_continuation = 0;
@@ -3436,9 +3659,6 @@
 }
 
 #endif /* if P2MP */
-
-#ifdef ENABLE_OCC
-
 /**
  * Calculate the link-mtu to advertise to our peer.  The actual value is not
  * relevant, because we will possibly perform data channel cipher negotiation
@@ -3450,7 +3670,7 @@
 calc_options_string_link_mtu(const struct options *o, const struct frame *frame)
 {
     size_t link_mtu = EXPANDED_SIZE(frame);
-#ifdef ENABLE_CRYPTO
+
     if (o->pull || o->mode == MODE_SERVER)
     {
         struct frame fake_frame = *frame;
@@ -3458,18 +3678,16 @@
         init_key_type(&fake_kt, o->ciphername, o->authname, o->keysize, true,
                       false);
         frame_remove_from_extra_frame(&fake_frame, crypto_max_overhead());
-        crypto_adjust_frame_parameters(&fake_frame, &fake_kt, o->use_iv,
-                                       o->replay, cipher_kt_mode_ofb_cfb(fake_kt.cipher));
+        crypto_adjust_frame_parameters(&fake_frame, &fake_kt, o->replay,
+                                       cipher_kt_mode_ofb_cfb(fake_kt.cipher));
         frame_finalize(&fake_frame, o->ce.link_mtu_defined, o->ce.link_mtu,
                        o->ce.tun_mtu_defined, o->ce.tun_mtu);
         msg(D_MTU_DEBUG, "%s: link-mtu %u -> %d", __func__, (unsigned int) link_mtu,
             EXPANDED_SIZE(&fake_frame));
         link_mtu = EXPANDED_SIZE(&fake_frame);
     }
-#endif
     return link_mtu;
 }
-
 /*
  * Build an options string to represent data channel encryption options.
  * This string must match exactly between peers.  The keysize is checked
@@ -3504,7 +3722,6 @@
  * --keysize
  * --secret
  * --no-replay
- * --no-iv
  *
  * SSL Options:
  *
@@ -3518,6 +3735,7 @@
 options_string(const struct options *o,
                const struct frame *frame,
                struct tuntap *tt,
+               openvpn_net_ctx_t *ctx,
                bool remote,
                struct gc_arena *gc)
 {
@@ -3531,14 +3749,21 @@
      */
 
     buf_printf(&out, ",dev-type %s", dev_type_string(o->dev, o->dev_type));
-    buf_printf(&out, ",link-mtu %u", (unsigned int) calc_options_string_link_mtu(o, frame));
+    /* the link-mtu that we send has only a meaning if have a fixed
+     * cipher (p2p) or have a fallback cipher configured for older non
+     * ncp clients. But not sending it will make even 2.4 complain
+     * about it being missing. So still send it. */
+    buf_printf(&out, ",link-mtu %u",
+               (unsigned int) calc_options_string_link_mtu(o, frame));
+
     buf_printf(&out, ",tun-mtu %d", PAYLOAD_SIZE(frame));
     buf_printf(&out, ",proto %s",  proto_remote(o->ce.proto, remote));
 
+    bool p2p_nopull = o->mode == MODE_POINT_TO_POINT && !PULL_DEFINED(o);
     /* send tun_ipv6 only in peer2peer mode - in client/server mode, it
      * is usually pushed by the server, triggering a non-helpful warning
      */
-    if (o->ifconfig_ipv6_local && o->mode == MODE_POINT_TO_POINT && !PULL_DEFINED(o))
+    if (o->ifconfig_ipv6_local && p2p_nopull)
     {
         buf_printf(&out, ",tun-ipv6");
     }
@@ -3560,14 +3785,15 @@
                       NULL,
                       NULL,
                       false,
-                      NULL);
+                      NULL,
+                      ctx);
         if (tt)
         {
             tt_local = true;
         }
     }
 
-    if (tt && o->mode == MODE_POINT_TO_POINT && !PULL_DEFINED(o))
+    if (tt && p2p_nopull)
     {
         const char *ios = ifconfig_options_string(tt, remote, o->ifconfig_nowarn, gc);
         if (ios && strlen(ios))
@@ -3595,8 +3821,6 @@
     }
 #endif
 
-#ifdef ENABLE_CRYPTO
-
 #define TLS_CLIENT (o->tls_client)
 #define TLS_SERVER (o->tls_server)
 
@@ -3625,9 +3849,14 @@
 
         init_key_type(&kt, o->ciphername, o->authname, o->keysize, true,
                       false);
-
-        buf_printf(&out, ",cipher %s",
-                   translate_cipher_name_to_openvpn(cipher_kt_name(kt.cipher)));
+        /* Only announce the cipher to our peer if we are willing to
+         * support it */
+        const char *ciphername = cipher_kt_name(kt.cipher);
+        if (p2p_nopull || !o->ncp_enabled
+            || tls_item_in_cipher_list(ciphername, o->ncp_ciphers))
+        {
+            buf_printf(&out, ",cipher %s", ciphername);
+        }
         buf_printf(&out, ",auth %s", md_kt_name(kt.digest));
         buf_printf(&out, ",keysize %d", kt.cipher_length * 8);
         if (o->shared_secret_file)
@@ -3638,10 +3867,6 @@
         {
             buf_printf(&out, ",no-replay");
         }
-        if (!o->use_iv)
-        {
-            buf_printf(&out, ",no-iv");
-        }
 
 #ifdef ENABLE_PREDICTION_RESISTANCE
         if (o->use_prediction_resistance)
@@ -3657,7 +3882,7 @@
     {
         if (TLS_CLIENT || TLS_SERVER)
         {
-            if (o->tls_auth_file)
+            if (o->ce.tls_auth_file)
             {
                 buf_printf(&out, ",tls-auth");
             }
@@ -3665,10 +3890,7 @@
              * tls-auth/tls-crypt does not match.  Removing tls-auth here would
              * break stuff, so leaving that in place. */
 
-            if (o->key_method > 1)
-            {
-                buf_printf(&out, ",key-method %d", o->key_method);
-            }
+            buf_printf(&out, ",key-method %d", KEY_METHOD_2);
         }
 
         if (remote)
@@ -3698,8 +3920,6 @@
 #undef TLS_CLIENT
 #undef TLS_SERVER
 
-#endif /* ENABLE_CRYPTO */
-
     return BSTR(&out);
 }
 
@@ -3753,8 +3973,9 @@
     if (strprefix(p1, "key-method ")
         || strprefix(p1, "keydir ")
         || strprefix(p1, "proto ")
-        || strprefix(p1, "tls-auth ")
-        || strprefix(p1, "tun-ipv6"))
+        || streq(p1, "tls-auth")
+        || strprefix(p1, "tun-ipv6")
+        || strprefix(p1, "cipher "))
     {
         return;
     }
@@ -3888,8 +4109,6 @@
     return BSTR(&out);
 }
 
-#endif /* ENABLE_OCC */
-
 char *
 options_string_extract_option(const char *options_string,const char *opt_name,
                               struct gc_arena *gc)
@@ -3958,6 +4177,33 @@
     }
 }
 
+#ifdef _WIN32
+/**
+ * Parses --windows-driver config option
+ *
+ * @param str       value of --windows-driver option
+ * @param msglevel  msglevel to report parsing error
+ * @return enum windows_driver_type  driver type, WINDOWS_DRIVER_UNSPECIFIED on unknown --windows-driver value
+ */
+static enum windows_driver_type
+parse_windows_driver(const char *str, const int msglevel)
+{
+    if (streq(str, "tap-windows6"))
+    {
+        return WINDOWS_DRIVER_TAP_WINDOWS6;
+    }
+    else if (streq(str, "wintun"))
+    {
+        return WINDOWS_DRIVER_WINTUN;
+    }
+    else
+    {
+        msg(msglevel, "--windows-driver must be tap-windows6 or wintun");
+        return WINDOWS_DRIVER_UNSPECIFIED;
+    }
+}
+#endif
+
 /*
  * parse/print topology coding
  */
@@ -4081,7 +4327,6 @@
     struct options o;
     init_options(&o, true);
 
-#ifdef ENABLE_CRYPTO
     fprintf(fp, usage_message,
             title_string,
             o.ce.connect_retry_seconds,
@@ -4093,15 +4338,6 @@
             o.replay_window, o.replay_time,
             o.tls_timeout, o.renegotiate_seconds,
             o.handshake_window, o.transition_window);
-#else  /* ifdef ENABLE_CRYPTO */
-    fprintf(fp, usage_message,
-            title_string,
-            o.ce.connect_retry_seconds,
-            o.ce.connect_retry_seconds_max,
-            o.ce.local_port, o.ce.remote_port,
-            TUN_MTU_DEFAULT, TAP_MTU_EXTRA_DEFAULT,
-            o.verbosity);
-#endif
     fflush(fp);
 
 #endif /* ENABLE_SMALL */
@@ -4129,20 +4365,15 @@
 void
 show_library_versions(const unsigned int flags)
 {
-#ifdef ENABLE_CRYPTO
-#define SSL_LIB_VER_STR get_ssl_library_version()
-#else
-#define SSL_LIB_VER_STR ""
-#endif
 #ifdef ENABLE_LZO
 #define LZO_LIB_VER_STR ", LZO ", lzo_version_string()
 #else
 #define LZO_LIB_VER_STR "", ""
 #endif
 
-    msg(flags, "library versions: %s%s%s", SSL_LIB_VER_STR, LZO_LIB_VER_STR);
+    msg(flags, "library versions: %s%s%s", get_ssl_library_version(),
+        LZO_LIB_VER_STR);
 
-#undef SSL_LIB_VER_STR
 #undef LZO_LIB_VER_STR
 }
 
@@ -4164,7 +4395,7 @@
     msg(M_INFO|M_NOPREFIX, "special build: %s", CONFIGURE_SPECIAL_BUILD);
 #endif
 #endif
-    openvpn_exit(OPENVPN_EXIT_STATUS_USAGE); /* exit point */
+    openvpn_exit(OPENVPN_EXIT_STATUS_GOOD);
 }
 
 void
@@ -4442,7 +4673,8 @@
 }
 
 static char *
-read_inline_file(struct in_src *is, const char *close_tag, struct gc_arena *gc)
+read_inline_file(struct in_src *is, const char *close_tag,
+                 int *num_lines, struct gc_arena *gc)
 {
     char line[OPTION_LINE_SIZE];
     struct buffer buf = alloc_buf(8*OPTION_LINE_SIZE);
@@ -4451,6 +4683,7 @@
 
     while (in_src_get(is, line, sizeof(line)))
     {
+        (*num_lines)++;
         char *line_ptr = line;
         /* Remove leading spaces */
         while (isspace(*line_ptr))
@@ -4484,31 +4717,31 @@
     return ret;
 }
 
-static bool
+static int
 check_inline_file(struct in_src *is, char *p[], struct gc_arena *gc)
 {
-    bool ret = false;
+    int num_inline_lines = 0;
+
     if (p[0] && !p[1])
     {
         char *arg = p[0];
         if (arg[0] == '<' && arg[strlen(arg)-1] == '>')
         {
             struct buffer close_tag;
-            arg[strlen(arg)-1] = '\0';
-            p[0] = string_alloc(arg+1, gc);
-            p[1] = string_alloc(INLINE_FILE_TAG, gc);
+
+            arg[strlen(arg) - 1] = '\0';
+            p[0] = string_alloc(arg + 1, gc);
             close_tag = alloc_buf(strlen(p[0]) + 4);
             buf_printf(&close_tag, "</%s>", p[0]);
-            p[2] = read_inline_file(is, BSTR(&close_tag), gc);
-            p[3] = NULL;
+            p[1] = read_inline_file(is, BSTR(&close_tag), &num_inline_lines, gc);
+            p[2] = NULL;
             free_buf(&close_tag);
-            ret = true;
         }
     }
-    return ret;
+    return num_inline_lines;
 }
 
-static bool
+static int
 check_inline_file_via_fp(FILE *fp, char *p[], struct gc_arena *gc)
 {
     struct in_src is;
@@ -4517,8 +4750,9 @@
     return check_inline_file(&is, p, gc);
 }
 
-static bool
-check_inline_file_via_buf(struct buffer *multiline, char *p[], struct gc_arena *gc)
+static int
+check_inline_file_via_buf(struct buffer *multiline, char *p[],
+                          struct gc_arena *gc)
 {
     struct in_src is;
     is.type = IS_TYPE_BUF;
@@ -4529,6 +4763,7 @@
 static void
 add_option(struct options *options,
            char *p[],
+           bool is_inline,
            const char *file,
            int line,
            const int level,
@@ -4587,8 +4822,11 @@
                 if (parse_line(line + offset, p, SIZE(p)-1, file, line_num, msglevel, &options->gc))
                 {
                     bypass_doubledash(&p[0]);
-                    check_inline_file_via_fp(fp, p, &options->gc);
-                    add_option(options, p, file, line_num, level, msglevel, permission_mask, option_types_found, es);
+                    int lines_inline = check_inline_file_via_fp(fp, p, &options->gc);
+                    add_option(options, p, lines_inline, file, line_num, level,
+                               msglevel, permission_mask, option_types_found,
+                               es);
+                    line_num += lines_inline;
                 }
             }
             if (fp != stdin)
@@ -4632,8 +4870,10 @@
         if (parse_line(line, p, SIZE(p)-1, prefix, line_num, msglevel, &options->gc))
         {
             bypass_doubledash(&p[0]);
-            check_inline_file_via_buf(&multiline, p, &options->gc);
-            add_option(options, p, prefix, line_num, 0, msglevel, permission_mask, option_types_found, es);
+            int lines_inline = check_inline_file_via_buf(&multiline, p, &options->gc);
+            add_option(options, p, lines_inline, prefix, line_num, 0, msglevel,
+                       permission_mask, option_types_found, es);
+            line_num += lines_inline;
         }
         CLEAR(p);
     }
@@ -4664,7 +4904,8 @@
         CLEAR(p);
         p[0] = "config";
         p[1] = argv[1];
-        add_option(options, p, NULL, 0, 0, msglevel, permission_mask, option_types_found, es);
+        add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask,
+                   option_types_found, es);
     }
     else
     {
@@ -4698,7 +4939,8 @@
                     }
                 }
             }
-            add_option(options, p, NULL, 0, 0, msglevel, permission_mask, option_types_found, es);
+            add_option(options, p, false, NULL, 0, 0, msglevel, permission_mask,
+                       option_types_found, es);
             i += j - 1;
         }
     }
@@ -4769,7 +5011,8 @@
         }
         if (parse_line(line, p, SIZE(p)-1, file, line_num, msglevel, &options->gc))
         {
-            add_option(options, p, file, line_num, 0, msglevel, permission_mask, option_types_found, es);
+            add_option(options, p, false, file, line_num, 0, msglevel,
+                       permission_mask, option_types_found, es);
         }
     }
     return true;
@@ -4808,7 +5051,13 @@
 
 #if P2MP
 
-#define VERIFY_PERMISSION(mask) { if (!verify_permission(p[0], file, line, (mask), permission_mask, option_types_found, msglevel, options)) {goto err;}}
+#define VERIFY_PERMISSION(mask) {                                            \
+        if (!verify_permission(p[0], file, line, (mask), permission_mask,        \
+                               option_types_found, msglevel, options, is_inline)) \
+        {                                                                        \
+            goto err;                                                            \
+        }                                                                        \
+}
 
 static bool
 verify_permission(const char *name,
@@ -4818,7 +5067,8 @@
                   const unsigned int allowed,
                   unsigned int *found,
                   const int msglevel,
-                  struct options *options)
+                  struct options *options,
+                  bool is_inline)
 {
     if (!(type & allowed))
     {
@@ -4826,6 +5076,13 @@
         return false;
     }
 
+    if (is_inline && !(type & OPT_P_INLINE))
+    {
+        msg(msglevel, "option '%s' is not expected to be inline (%s:%d)", name,
+            file, line);
+        return false;
+    }
+
     if (found)
     {
         *found |= type;
@@ -4932,10 +5189,31 @@
 #endif
 }
 
+#ifdef USE_COMP
+static void
+show_compression_warning(struct compress_options *info)
+{
+    if (comp_non_stub_enabled(info))
+    {
+        /*
+         * Check if already displayed the strong warning and enabled full
+         * compression
+         */
+        if (!(info->flags & COMP_F_ALLOW_COMPRESS))
+        {
+            msg(M_WARN, "WARNING: Compression for receiving enabled. "
+                "Compression has been used in the past to break encryption. "
+                "Sent packets are not compressed unless \"allow-compression yes\" "
+                "is also set.");
+        }
+    }
+}
+#endif
 
 static void
 add_option(struct options *options,
            char *p[],
+           bool is_inline,
            const char *file,
            int line,
            const int level,
@@ -5002,13 +5280,15 @@
         struct route_gateway_info rgi;
         struct route_ipv6_gateway_info rgi6;
         struct in6_addr remote = IN6ADDR_ANY_INIT;
+        openvpn_net_ctx_t net_ctx;
         VERIFY_PERMISSION(OPT_P_GENERAL);
         if (p[1])
         {
             get_ipv6_addr(p[1], &remote, NULL, M_WARN);
         }
-        get_default_gateway(&rgi);
-        get_default_gateway_ipv6(&rgi6, &remote);
+        net_ctx_init(NULL, &net_ctx);
+        get_default_gateway(&rgi, &net_ctx);
+        get_default_gateway_ipv6(&rgi6, &remote, &net_ctx);
         print_default_gateway(M_INFO, &rgi, &rgi6);
         openvpn_exit(OPENVPN_EXIT_STATUS_GOOD); /* exit point */
     }
@@ -5042,13 +5322,14 @@
         }
         if (good)
         {
-#if 0
-            /* removed for now since ECHO can potentially include
-             * security-sensitive strings */
-            msg(M_INFO, "%s:%s",
-                pull_mode ? "ECHO-PULL" : "ECHO",
-                BSTR(&string));
-#endif
+            /* only message-related ECHO are logged, since other ECHOs
+             * can potentially include security-sensitive strings */
+            if (strncmp(p[1], "msg", 3) == 0)
+            {
+                msg(M_INFO, "%s:%s",
+                    pull_mode ? "ECHO-PULL" : "ECHO",
+                    BSTR(&string));
+            }
 #ifdef ENABLE_MANAGEMENT
             if (management)
             {
@@ -5133,10 +5414,34 @@
         options->management_flags |= MF_CONNECT_AS_CLIENT;
         options->management_write_peer_info_file = p[1];
     }
-#ifdef MANAGMENT_EXTERNAL_KEY
-    else if (streq(p[0], "management-external-key") && !p[1])
+#ifdef ENABLE_MANAGEMENT
+    else if (streq(p[0], "management-external-key"))
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
+        for (int j = 1; j < MAX_PARMS && p[j] != NULL; ++j)
+        {
+            if (streq(p[j], "nopadding"))
+            {
+                options->management_flags |= MF_EXTERNAL_KEY_NOPADDING;
+            }
+            else if (streq(p[j], "pkcs1"))
+            {
+                options->management_flags |= MF_EXTERNAL_KEY_PKCS1PAD;
+            }
+            else
+            {
+                msg(msglevel, "Unknown management-external-key flag: %s", p[j]);
+            }
+        }
+        /*
+         * When no option is present, assume that only PKCS1
+         * padding is supported
+         */
+        if (!(options->management_flags
+              &(MF_EXTERNAL_KEY_NOPADDING | MF_EXTERNAL_KEY_PKCS1PAD)))
+        {
+            options->management_flags |= MF_EXTERNAL_KEY_PKCS1PAD;
+        }
         options->management_flags |= MF_EXTERNAL_KEY;
     }
     else if (streq(p[0], "management-external-cert") && p[1] && !p[2])
@@ -5145,7 +5450,7 @@
         options->management_flags |= MF_EXTERNAL_CERT;
         options->management_certificate = p[1];
     }
-#endif
+#endif /* ifdef ENABLE_MANAGEMENT */
 #ifdef MANAGEMENT_DEF_AUTH
     else if (streq(p[0], "management-client-auth") && !p[1])
     {
@@ -5196,12 +5501,10 @@
         {
             options->mode = MODE_POINT_TO_POINT;
         }
-#if P2MP_SERVER
         else if (streq(p[1], "server"))
         {
             options->mode = MODE_SERVER;
         }
-#endif
         else
         {
             msg(msglevel, "Bad --mode parameter: %s", p[1]);
@@ -5218,6 +5521,13 @@
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->dev_type = p[1];
     }
+#ifdef _WIN32
+    else if (streq(p[0], "windows-driver") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->windows_driver = parse_windows_driver(p[1], M_FATAL);
+    }
+#endif
     else if (streq(p[0], "dev-node") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
@@ -5315,15 +5625,16 @@
     }
     else if (streq(p[0], "connection") && p[1] && !p[3])
     {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
+        if (is_inline)
         {
             struct options sub;
             struct connection_entry *e;
 
             init_options(&sub, true);
             sub.ce = options->ce;
-            read_config_string("[CONNECTION-OPTIONS]", &sub, p[2], msglevel, OPT_P_CONNECTION, option_types_found, es);
+            read_config_string("[CONNECTION-OPTIONS]", &sub, p[1], msglevel,
+                               OPT_P_CONNECTION, option_types_found, es);
             if (!sub.ce.remote)
             {
                 msg(msglevel, "Each 'connection' block must contain exactly one 'remote' directive");
@@ -5418,7 +5729,9 @@
                 const sa_family_t af = ascii2af(p[3]);
                 if (proto < 0)
                 {
-                    msg(msglevel, "remote: bad protocol associated with host %s: '%s'", p[1], p[3]);
+                    msg(msglevel,
+                        "remote: bad protocol associated with host %s: '%s'",
+                        p[1], p[3]);
                     goto err;
                 }
                 re.proto = proto;
@@ -5709,6 +6022,12 @@
     {
         VERIFY_PERMISSION(OPT_P_MESSAGES);
         options->verbosity = positive_atoi(p[1]);
+        if (options->verbosity >= (D_TLS_DEBUG_MED & M_DEBUG_LEVEL))
+        {
+            /* We pass this flag to the SSL library to avoid
+             * mbed TLS always generating debug level logging */
+            options->ssl_flags |= SSLF_TLS_DEBUG_ENABLED;
+        }
 #if !defined(ENABLE_DEBUG) && !defined(ENABLE_SMALL)
         /* Warn when a debug verbosity is supplied when built without debug support */
         if (options->verbosity >= 7)
@@ -5804,13 +6123,11 @@
         VERIFY_PERMISSION(OPT_P_MTU|OPT_P_CONNECTION);
         options->ce.mtu_discover_type = translate_mtu_discover_type_name(p[1]);
     }
-#ifdef ENABLE_OCC
     else if (streq(p[0], "mtu-test") && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->mtu_test = true;
     }
-#endif
     else if (streq(p[0], "nice") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_NICE);
@@ -5849,6 +6166,13 @@
             }
         }
     }
+#ifdef TARGET_LINUX
+    else if (streq (p[0], "bind-dev") && p[1])
+    {
+        VERIFY_PERMISSION (OPT_P_SOCKFLAGS);
+        options->bind_dev = p[1];
+    }
+#endif
     else if (streq(p[0], "txqueuelen") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
@@ -5933,7 +6257,8 @@
         af = ascii2af(p[1]);
         if (proto < 0)
         {
-            msg(msglevel, "Bad protocol: '%s'.  Allowed protocols with --proto option: %s",
+            msg(msglevel,
+                "Bad protocol: '%s'. Allowed protocols with --proto option: %s",
                 p[1],
                 proto2ascii_all(&gc));
             goto err;
@@ -6004,17 +6329,10 @@
     else if (streq(p[0], "http-proxy-user-pass") && p[1])
     {
         struct http_proxy_options *ho;
-        VERIFY_PERMISSION(OPT_P_GENERAL);
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
         ho = init_http_proxy_options_once(&options->ce.http_proxy_options, &options->gc);
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
-        {
-            ho->auth_file = p[2];
-            ho->inline_creds = true;
-        }
-        else
-        {
-            ho->auth_file = p[1];
-        }
+        ho->auth_file = p[1];
+        ho->inline_creds = is_inline;
     }
     else if (streq(p[0], "http-proxy-retry") || streq(p[0], "socks-proxy-retry"))
     {
@@ -6121,7 +6439,6 @@
         VERIFY_PERMISSION(OPT_P_TIMER);
         options->ping_timer_remote = true;
     }
-#ifdef ENABLE_OCC
     else if (streq(p[0], "explicit-exit-notify") && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_CONNECTION|OPT_P_EXPLICIT_NOTIFY);
@@ -6134,7 +6451,6 @@
             options->ce.explicit_exit_notification = 1;
         }
     }
-#endif
     else if (streq(p[0], "persist-tun") && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_PERSIST);
@@ -6232,6 +6548,18 @@
             }
         }
     }
+    else if (streq(p[0], "route-ipv6-gateway") && p[1] && !p[2])
+    {
+        if (ipv6_addr_safe(p[1]))
+        {
+            options->route_ipv6_default_gateway = p[1];
+        }
+        else
+        {
+            msg(msglevel, "route-ipv6-gateway parm '%s' must be a valid address", p[1]);
+            goto err;
+        }
+    }
     else if (streq(p[0], "route-metric") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_ROUTE);
@@ -6321,6 +6649,18 @@
         int j;
         VERIFY_PERMISSION(OPT_P_ROUTE);
         rol_check_alloc(options);
+
+        if (options->routes->flags & RG_ENABLE)
+        {
+            msg(M_WARN,
+                "WARNING: You have specified redirect-gateway and "
+                "redirect-private at the same time (or the same option "
+                "multiple times). This is not well supported and may lead to "
+                "unexpected results");
+        }
+
+        options->routes->flags |= RG_ENABLE;
+
         if (streq(p[0], "redirect-gateway"))
         {
             options->routes->flags |= RG_REROUTE_GW;
@@ -6358,7 +6698,7 @@
             }
             else if (streq(p[j], "!ipv4"))
             {
-                options->routes->flags &= ~RG_REROUTE_GW;
+                options->routes->flags &= ~(RG_REROUTE_GW | RG_ENABLE);
             }
             else
             {
@@ -6370,7 +6710,11 @@
         /* we need this here to handle pushed --redirect-gateway */
         remap_redirect_gateway_flags(options);
 #endif
-        options->routes->flags |= RG_ENABLE;
+    }
+    else if (streq(p[0], "block-ipv6") && !p[1])
+    {
+        VERIFY_PERMISSION(OPT_P_ROUTE);
+        options->block_ipv6 = true;
     }
     else if (streq(p[0], "remote-random-hostname") && !p[1])
     {
@@ -6389,12 +6733,10 @@
             msg(msglevel, "this is a generic configuration and cannot directly be used");
             goto err;
         }
-#ifdef ENABLE_PUSH_PEER_INFO
         else if (streq(p[1], "PUSH_PEER_INFO") && !p[2])
         {
             options->push_peer_info = true;
         }
-#endif
         else if (streq(p[1], "SERVER_POLL_TIMEOUT") && p[2])
         {
             options->ce.connect_timeout = positive_atoi(p[2]);
@@ -6417,7 +6759,7 @@
     else if (streq(p[0], "script-security") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        script_security = atoi(p[1]);
+        script_security_set(atoi(p[1]));
     }
     else if (streq(p[0], "mssfix") && !p[2])
     {
@@ -6432,15 +6774,12 @@
         }
 
     }
-#ifdef ENABLE_OCC
     else if (streq(p[0], "disable-occ") && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->occ = false;
     }
-#endif
 #if P2MP
-#if P2MP_SERVER
     else if (streq(p[0], "server") && p[1] && p[2] && !p[4])
     {
         const int lev = M_WARN;
@@ -6484,9 +6823,12 @@
             msg(msglevel, "error parsing --server-ipv6 parameter");
             goto err;
         }
-        if (netbits < 64 || netbits > 112)
+        if (netbits < 64 || netbits > 124)
         {
-            msg( msglevel, "--server-ipv6 settings: only /64../112 supported right now (not /%d)", netbits );
+            msg(msglevel,
+                "--server-ipv6 settings: network must be between /64 and /124 (not /%d)",
+                netbits);
+
             goto err;
         }
         options->server_ipv6_defined = true;
@@ -6588,12 +6930,6 @@
             options->ifconfig_pool_persist_refresh_freq = positive_atoi(p[2]);
         }
     }
-    else if (streq(p[0], "ifconfig-pool-linear") && !p[1])
-    {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
-        options->topology = TOP_P2P;
-        msg(M_WARN, "DEPRECATED OPTION: --ifconfig-pool-linear, use --topology p2p instead");
-    }
     else if (streq(p[0], "ifconfig-ipv6-pool") && p[1] && !p[2])
     {
         const int lev = M_WARN;
@@ -6606,9 +6942,11 @@
             msg(msglevel, "error parsing --ifconfig-ipv6-pool parameters");
             goto err;
         }
-        if (netbits < 64 || netbits > 112)
+        if (netbits < 64 || netbits > 124)
         {
-            msg( msglevel, "--ifconfig-ipv6-pool settings: only /64../112 supported right now (not /%d)", netbits );
+            msg(msglevel,
+                "--ifconfig-ipv6-pool settings: network must be between /64 and /124 (not /%d)",
+                netbits);
             goto err;
         }
 
@@ -6672,8 +7010,7 @@
     else if (streq(p[0], "client-cert-not-required") && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        options->ssl_flags |= SSLF_CLIENT_CERT_NOT_REQUIRED;
-        msg(M_WARN, "DEPRECATED OPTION: --client-cert-not-required, use --verify-client-cert instead");
+        msg(M_FATAL, "REMOVED OPTION: --client-cert-not-required, use '--verify-client-cert none' instead");
     }
     else if (streq(p[0], "verify-client-cert") && !p[2])
     {
@@ -6746,11 +7083,30 @@
                         &options->auth_user_pass_verify_script,
                         p[1], "auth-user-pass-verify", true);
     }
-    else if (streq(p[0], "auth-gen-token"))
+    else if (streq(p[0], "auth-gen-token") && !p[3])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->auth_token_generate = true;
         options->auth_token_lifetime = p[1] ? positive_atoi(p[1]) : 0;
+        if (p[2])
+        {
+            if (streq(p[2], "external-auth"))
+            {
+                options->auth_token_call_auth = true;
+            }
+            else
+            {
+                msg(msglevel, "Invalid argument to auth-gen-token: %s", p[2]);
+            }
+        }
+
+    }
+    else if (streq(p[0], "auth-gen-token-secret") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
+        options->auth_token_secret_file = p[1];
+        options->auth_token_secret_file_inline = is_inline;
+
     }
     else if (streq(p[0], "client-connect") && p[1])
     {
@@ -6969,7 +7325,6 @@
         options->stale_routes_ageing_time  = ageing_time;
         options->stale_routes_check_interval = check_interval;
     }
-#endif /* P2MP_SERVER */
 
     else if (streq(p[0], "client") && !p[1])
     {
@@ -7003,7 +7358,7 @@
         VERIFY_PERMISSION(OPT_P_GENERAL);
         auth_retry_set(msglevel, p[1]);
     }
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
     else if (streq(p[0], "static-challenge") && p[1] && p[2] && !p[3])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
@@ -7020,7 +7375,7 @@
 #ifdef _WIN32
         VERIFY_PERMISSION(OPT_P_GENERAL);
         HANDLE process = GetCurrentProcess();
-        HANDLE handle = (HANDLE) atoi(p[1]);
+        HANDLE handle = (HANDLE) atoll(p[1]);
         if (!DuplicateHandle(process, handle, process, &options->msg_channel, 0,
                              FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS))
         {
@@ -7133,7 +7488,8 @@
         VERIFY_PERMISSION(OPT_P_IPWIN32);
         bool ipv6dns = false;
 
-        if (streq(p[1], "DOMAIN") && p[2])
+        if ((streq(p[1], "DOMAIN") || streq(p[1], "ADAPTER_DOMAIN_SUFFIX"))
+            && p[2])
         {
             o->domain = p[2];
         }
@@ -7156,7 +7512,7 @@
         {
             if (strstr(p[2], ":"))
             {
-                ipv6dns=true;
+                ipv6dns = true;
                 foreign_option(options, p, 3, es);
                 dhcp_option_dns6_parse(p[2], o->dns6, &o->dns6_len, msglevel);
             }
@@ -7177,6 +7533,18 @@
         {
             dhcp_option_address_parse("NBDD", p[2], o->nbdd, &o->nbdd_len, msglevel);
         }
+        else if (streq(p[1], "DOMAIN-SEARCH") && p[2])
+        {
+            if (o->domain_search_list_len < N_SEARCH_LIST_LEN)
+            {
+                o->domain_search_list[o->domain_search_list_len++] = p[2];
+            }
+            else
+            {
+                msg(msglevel, "--dhcp-option %s: maximum of %d search entries can be specified",
+                    p[1], N_SEARCH_LIST_LEN);
+            }
+        }
         else if (streq(p[1], "DISABLE-NBT") && !p[2])
         {
             o->disable_nbt = 1;
@@ -7350,29 +7718,80 @@
     }
 #endif
 #if defined(USE_COMP)
+    else if (streq(p[0], "allow-compression") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+
+        if (streq(p[1], "no"))
+        {
+            options->comp.flags =
+                COMP_F_ALLOW_STUB_ONLY|COMP_F_ADVERTISE_STUBS_ONLY;
+            if (comp_non_stub_enabled(&options->comp))
+            {
+                msg(msglevel, "'--allow-compression no' conflicts with "
+                    " enabling compression");
+            }
+        }
+        else if (options->comp.flags & COMP_F_ALLOW_STUB_ONLY)
+        {
+            /* Also printed on a push to hint at configuration problems */
+            msg(msglevel, "Cannot set allow-compression to '%s' "
+                "after set to 'no'", p[1]);
+            goto err;
+        }
+        else if (streq(p[1], "asym"))
+        {
+            options->comp.flags &= ~COMP_F_ALLOW_COMPRESS;
+        }
+        else if (streq(p[1], "yes"))
+        {
+            msg(M_WARN, "WARNING: Compression for sending and receiving enabled. Compression has "
+                "been used in the past to break encryption. Allowing compression allows "
+                "attacks that break encryption. Using \"--allow-compression yes\" is "
+                "strongly discouraged for common usage. See --compress in the manual "
+                "page for more information ");
+
+            options->comp.flags |= COMP_F_ALLOW_COMPRESS;
+        }
+        else
+        {
+            msg(msglevel, "bad allow-compression option: %s -- "
+                "must be 'yes', 'no', or 'asym'", p[1]);
+            goto err;
+        }
+    }
     else if (streq(p[0], "comp-lzo") && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_COMP);
 
+        /* All lzo variants do not use swap */
+        options->comp.flags &= ~COMP_F_SWAP;
 #if defined(ENABLE_LZO)
         if (p[1] && streq(p[1], "no"))
 #endif
         {
             options->comp.alg = COMP_ALG_STUB;
-            options->comp.flags = 0;
+            options->comp.flags &= ~COMP_F_ADAPTIVE;
         }
 #if defined(ENABLE_LZO)
+        else if (options->comp.flags & COMP_F_ALLOW_STUB_ONLY)
+        {
+            /* Also printed on a push to hint at configuration problems */
+            msg(msglevel, "Cannot set comp-lzo to '%s', "
+                "allow-compression is set to 'no'", p[1]);
+            goto err;
+        }
         else if (p[1])
         {
             if (streq(p[1], "yes"))
             {
                 options->comp.alg = COMP_ALG_LZO;
-                options->comp.flags = 0;
+                options->comp.flags &= ~COMP_F_ADAPTIVE;
             }
             else if (streq(p[1], "adaptive"))
             {
                 options->comp.alg = COMP_ALG_LZO;
-                options->comp.flags = COMP_F_ADAPTIVE;
+                options->comp.flags |= COMP_F_ADAPTIVE;
             }
             else
             {
@@ -7383,12 +7802,17 @@
         else
         {
             options->comp.alg = COMP_ALG_LZO;
-            options->comp.flags = COMP_F_ADAPTIVE;
+            options->comp.flags |= COMP_F_ADAPTIVE;
         }
+        show_compression_warning(&options->comp);
 #endif /* if defined(ENABLE_LZO) */
     }
     else if (streq(p[0], "comp-noadapt") && !p[1])
     {
+        /*
+         * We do not need to check here if we allow compression since
+         * it only modifies a flag if compression is enabled
+         */
         VERIFY_PERMISSION(OPT_P_COMP);
         options->comp.flags &= ~COMP_F_ADAPTIVE;
     }
@@ -7400,30 +7824,36 @@
             if (streq(p[1], "stub"))
             {
                 options->comp.alg = COMP_ALG_STUB;
-                options->comp.flags = (COMP_F_SWAP|COMP_F_ADVERTISE_STUBS_ONLY);
+                options->comp.flags |= (COMP_F_SWAP|COMP_F_ADVERTISE_STUBS_ONLY);
             }
             else if (streq(p[1], "stub-v2"))
             {
                 options->comp.alg = COMP_ALGV2_UNCOMPRESSED;
-                options->comp.flags = COMP_F_ADVERTISE_STUBS_ONLY;
+                options->comp.flags |= COMP_F_ADVERTISE_STUBS_ONLY;
+            }
+            else if (options->comp.flags & COMP_F_ALLOW_STUB_ONLY)
+            {
+                /* Also printed on a push to hint at configuration problems */
+                msg(msglevel, "Cannot set compress to '%s', "
+                    "allow-compression is set to 'no'", p[1]);
+                goto err;
             }
 #if defined(ENABLE_LZO)
             else if (streq(p[1], "lzo"))
             {
                 options->comp.alg = COMP_ALG_LZO;
-                options->comp.flags = 0;
+                options->comp.flags &= ~(COMP_F_ADAPTIVE | COMP_F_SWAP);
             }
 #endif
 #if defined(ENABLE_LZ4)
             else if (streq(p[1], "lz4"))
             {
                 options->comp.alg = COMP_ALG_LZ4;
-                options->comp.flags = COMP_F_SWAP;
+                options->comp.flags |= COMP_F_SWAP;
             }
             else if (streq(p[1], "lz4-v2"))
             {
                 options->comp.alg = COMP_ALGV2_LZ4;
-                options->comp.flags = 0;
             }
 #endif
             else
@@ -7435,11 +7865,11 @@
         else
         {
             options->comp.alg = COMP_ALG_STUB;
-            options->comp.flags = COMP_F_SWAP;
+            options->comp.flags |= COMP_F_SWAP;
         }
+        show_compression_warning(&options->comp);
     }
 #endif /* USE_COMP */
-#ifdef ENABLE_CRYPTO
     else if (streq(p[0], "show-ciphers") && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
@@ -7459,10 +7889,19 @@
     {
         int key_direction;
 
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_CONNECTION);
+
         key_direction = ascii2keydirection(msglevel, p[1]);
         if (key_direction >= 0)
         {
-            options->key_direction = key_direction;
+            if (permission_mask & OPT_P_GENERAL)
+            {
+                options->key_direction = key_direction;
+            }
+            else if (permission_mask & OPT_P_CONNECTION)
+            {
+                options->ce.key_direction = key_direction;
+            }
         }
         else
         {
@@ -7471,12 +7910,10 @@
     }
     else if (streq(p[0], "secret") && p[1] && !p[3])
     {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
-        {
-            options->shared_secret_file_inline = p[2];
-        }
-        else if (p[2])
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
+        options->shared_secret_file = p[1];
+        options->shared_secret_file_inline = is_inline;
+        if (!is_inline && p[2])
         {
             int key_direction;
 
@@ -7490,12 +7927,48 @@
                 goto err;
             }
         }
-        options->shared_secret_file = p[1];
     }
-    else if (streq(p[0], "genkey") && !p[1])
+    else if (streq(p[0], "genkey") && !p[4])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->genkey = true;
+        if (!p[1])
+        {
+            options->genkey_type = GENKEY_SECRET;
+        }
+        else
+        {
+            if (streq(p[1], "secret") || streq(p[1], "tls-auth")
+                || streq(p[1], "tls-crypt"))
+            {
+                options->genkey_type = GENKEY_SECRET;
+            }
+            else if (streq(p[1], "tls-crypt-v2-server"))
+            {
+                options->genkey_type = GENKEY_TLS_CRYPTV2_SERVER;
+            }
+            else if (streq(p[1], "tls-crypt-v2-client"))
+            {
+                options->genkey_type = GENKEY_TLS_CRYPTV2_CLIENT;
+                if (p[3])
+                {
+                    options->genkey_extra_data = p[3];
+                }
+            }
+            else if (streq(p[1], "auth-token"))
+            {
+                options->genkey_type = GENKEY_AUTH_TOKEN;
+            }
+            else
+            {
+                msg(msglevel, "unknown --genkey type: %s", p[1]);
+            }
+
+        }
+        if (p[2])
+        {
+            options->genkey_filename = p[2];
+        }
     }
     else if (streq(p[0], "auth") && p[1] && !p[2])
     {
@@ -7504,18 +7977,33 @@
     }
     else if (streq(p[0], "cipher") && p[1] && !p[2])
     {
-        VERIFY_PERMISSION(OPT_P_NCP);
+        VERIFY_PERMISSION(OPT_P_NCP|OPT_P_INSTANCE);
         options->ciphername = p[1];
     }
-    else if (streq(p[0], "ncp-ciphers") && p[1] && !p[2])
+    else if (streq(p[0], "data-ciphers-fallback") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INSTANCE);
+        options->ciphername = p[1];
+        options->enable_ncp_fallback = true;
+    }
+    else if ((streq(p[0], "data-ciphers") || streq(p[0], "ncp-ciphers"))
+             && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INSTANCE);
+        if (streq(p[0], "ncp-ciphers"))
+        {
+            msg(M_INFO, "Note: Treating option '--ncp-ciphers' as "
+                " '--data-ciphers' (renamed in OpenVPN 2.5).");
+        }
         options->ncp_ciphers = p[1];
     }
     else if (streq(p[0], "ncp-disable") && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INSTANCE);
         options->ncp_enabled = false;
+        msg(M_WARN, "DEPRECATED OPTION: ncp-disable. Disabling "
+            "cipher negotiation is a deprecated debug feature that "
+            "will be removed in OpenVPN 2.6");
     }
     else if (streq(p[0], "prng") && p[1] && !p[3])
     {
@@ -7593,11 +8081,6 @@
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->mute_replay_warnings = true;
     }
-    else if (streq(p[0], "no-iv") && !p[1])
-    {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
-        options->use_iv = false;
-    }
     else if (streq(p[0], "replay-persist") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
@@ -7649,7 +8132,7 @@
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->show_tls_ciphers = true;
     }
-    else if (streq(p[0], "show-curves") && !p[1])
+    else if ((streq(p[0], "show-curves") || streq(p[0], "show-groups")) && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->show_curves = true;
@@ -7657,6 +8140,9 @@
     else if (streq(p[0], "ecdh-curve") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
+        msg(M_WARN, "Consider setting groups/curves preference with "
+            "tls-groups instead of forcing a specific curve with "
+            "ecdh-curve.");
         options->ecdh_curve = p[1];
     }
     else if (streq(p[0], "tls-server") && !p[1])
@@ -7669,14 +8155,11 @@
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->tls_client = true;
     }
-    else if (streq(p[0], "ca") && p[1] && ((streq(p[1], INLINE_FILE_TAG) && p[2]) || !p[2]) && !p[3])
+    else if (streq(p[0], "ca") && p[1] && !p[2])
     {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
         options->ca_file = p[1];
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
-        {
-            options->ca_file_inline = p[2];
-        }
+        options->ca_file_inline = is_inline;
     }
 #ifndef ENABLE_CRYPTO_MBEDTLS
     else if (streq(p[0], "capath") && p[1] && !p[2])
@@ -7685,32 +8168,23 @@
         options->ca_path = p[1];
     }
 #endif /* ENABLE_CRYPTO_MBEDTLS */
-    else if (streq(p[0], "dh") && p[1] && ((streq(p[1], INLINE_FILE_TAG) && p[2]) || !p[2]) && !p[3])
+    else if (streq(p[0], "dh") && p[1] && !p[2])
     {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
         options->dh_file = p[1];
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
-        {
-            options->dh_file_inline = p[2];
-        }
+        options->dh_file_inline = is_inline;
     }
-    else if (streq(p[0], "cert") && p[1] && ((streq(p[1], INLINE_FILE_TAG) && p[2]) || !p[2]) && !p[3])
+    else if (streq(p[0], "cert") && p[1] && !p[2])
     {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
         options->cert_file = p[1];
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
-        {
-            options->cert_file_inline = p[2];
-        }
+        options->cert_file_inline = is_inline;
     }
-    else if (streq(p[0], "extra-certs") && p[1] && ((streq(p[1], INLINE_FILE_TAG) && p[2]) || !p[2]) && !p[3])
+    else if (streq(p[0], "extra-certs") && p[1] && !p[2])
     {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
         options->extra_certs_file = p[1];
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
-        {
-            options->extra_certs_file_inline = p[2];
-        }
+        options->extra_certs_file_inline = is_inline;
     }
     else if (streq(p[0], "verify-hash") && p[1] && !p[3])
     {
@@ -7739,14 +8213,11 @@
         options->cryptoapi_cert = p[1];
     }
 #endif
-    else if (streq(p[0], "key") && p[1] && ((streq(p[1], INLINE_FILE_TAG) && p[2]) || !p[2]) && !p[3])
+    else if (streq(p[0], "key") && p[1] && !p[2])
     {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
         options->priv_key_file = p[1];
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
-        {
-            options->priv_key_file_inline = p[2];
-        }
+        options->priv_key_file_inline = is_inline;
     }
     else if (streq(p[0], "tls-version-min") && p[1] && !p[3])
     {
@@ -7777,14 +8248,11 @@
         options->ssl_flags |= (ver << SSLF_TLS_VERSION_MAX_SHIFT);
     }
 #ifndef ENABLE_CRYPTO_MBEDTLS
-    else if (streq(p[0], "pkcs12") && p[1] && ((streq(p[1], INLINE_FILE_TAG) && p[2]) || !p[2]) && !p[3])
+    else if (streq(p[0], "pkcs12") && p[1] && !p[2])
     {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
         options->pkcs12_file = p[1];
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
-        {
-            options->pkcs12_file_inline = p[2];
-        }
+        options->pkcs12_file_inline = is_inline;
     }
 #endif /* ENABLE_CRYPTO_MBEDTLS */
     else if (streq(p[0], "askpass") && !p[2])
@@ -7820,13 +8288,11 @@
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->single_session = true;
     }
-#ifdef ENABLE_PUSH_PEER_INFO
     else if (streq(p[0], "push-peer-info") && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->push_peer_info = true;
     }
-#endif
     else if (streq(p[0], "tls-exit") && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
@@ -7847,19 +8313,21 @@
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->cipher_list_tls13 = p[1];
     }
-    else if (streq(p[0], "crl-verify") && p[1] && ((p[2] && streq(p[2], "dir"))
-                                                   || (p[2] && streq(p[1], INLINE_FILE_TAG) ) || !p[2]) && !p[3])
+    else if (streq(p[0], "tls-groups") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->tls_groups = p[1];
+    }
+    else if (streq(p[0], "crl-verify") && p[1] && ((p[2] && streq(p[2], "dir"))
+                                                   || !p[2]))
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INLINE);
         if (p[2] && streq(p[2], "dir"))
         {
             options->ssl_flags |= SSLF_CRL_VERIFY_DIR;
         }
         options->crl_file = p[1];
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
-        {
-            options->crl_file_inline = p[2];
-        }
+        options->crl_file_inline = is_inline;
     }
     else if (streq(p[0], "tls-verify") && p[1])
     {
@@ -7879,49 +8347,24 @@
         options->tls_export_cert = p[1];
     }
 #endif
-#if P2MP_SERVER
-    else if (streq(p[0], "compat-names") && ((p[1] && streq(p[1], "no-remapping")) || !p[1]) && !p[2])
-#else
-    else if (streq(p[0], "compat-names") && !p[1])
-#endif
+    else if (streq(p[0], "compat-names"))
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        if (options->verify_x509_type != VERIFY_X509_NONE)
-        {
-            msg(msglevel, "you cannot use --compat-names with --verify-x509-name");
-            goto err;
-        }
-        msg(M_WARN, "DEPRECATED OPTION: --compat-names, please update your configuration. This will be removed in OpenVPN 2.5.");
-        compat_flag(COMPAT_FLAG_SET | COMPAT_NAMES);
-#if P2MP_SERVER
-        if (p[1] && streq(p[1], "no-remapping"))
-        {
-            compat_flag(COMPAT_FLAG_SET | COMPAT_NO_NAME_REMAPPING);
-        }
+        msg(msglevel, "--compat-names was removed in OpenVPN 2.5. "
+            "Update your configuration.");
+        goto err;
     }
     else if (streq(p[0], "no-name-remapping") && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        if (options->verify_x509_type != VERIFY_X509_NONE)
-        {
-            msg(msglevel, "you cannot use --no-name-remapping with --verify-x509-name");
-            goto err;
-        }
-        msg(M_WARN, "DEPRECATED OPTION: --no-name-remapping, please update your configuration. This will be removed in OpenVPN 2.5.");
-        compat_flag(COMPAT_FLAG_SET | COMPAT_NAMES);
-        compat_flag(COMPAT_FLAG_SET | COMPAT_NO_NAME_REMAPPING);
-#endif
+        msg(msglevel, "--no-name-remapping was removed in OpenVPN 2.5. "
+            "Update your configuration.");
+        goto err;
     }
     else if (streq(p[0], "verify-x509-name") && p[1] && strlen(p[1]) && !p[3])
     {
         int type = VERIFY_X509_SUBJECT_DN;
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        if (compat_flag(COMPAT_FLAG_QUERY | COMPAT_NAMES))
-        {
-            msg(msglevel, "you cannot use --verify-x509-name with "
-                "--compat-names or --no-name-remapping");
-            goto err;
-        }
         if (p[2])
         {
             if (streq(p[2], "subject"))
@@ -8017,10 +8460,14 @@
         VERIFY_PERMISSION(OPT_P_TLS_PARMS);
         options->renegotiate_packets = positive_atoi(p[1]);
     }
-    else if (streq(p[0], "reneg-sec") && p[1] && !p[2])
+    else if (streq(p[0], "reneg-sec") && p[1] && !p[3])
     {
         VERIFY_PERMISSION(OPT_P_TLS_PARMS);
         options->renegotiate_seconds = positive_atoi(p[1]);
+        if (p[2])
+        {
+            options->renegotiate_seconds_min = positive_atoi(p[2]);
+        }
     }
     else if (streq(p[0], "hand-window") && p[1] && !p[2])
     {
@@ -8034,51 +8481,75 @@
     }
     else if (streq(p[0], "tls-auth") && p[1] && !p[3])
     {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
-        {
-            options->tls_auth_file_inline = p[2];
-        }
-        else if (p[2])
-        {
-            int key_direction;
+        int key_direction = -1;
 
-            key_direction = ascii2keydirection(msglevel, p[2]);
-            if (key_direction >= 0)
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_CONNECTION|OPT_P_INLINE);
+
+        if (permission_mask & OPT_P_GENERAL)
+        {
+            options->tls_auth_file = p[1];
+            options->tls_auth_file_inline = is_inline;
+
+            if (!is_inline && p[2])
             {
+                key_direction = ascii2keydirection(msglevel, p[2]);
+                if (key_direction < 0)
+                {
+                    goto err;
+                }
                 options->key_direction = key_direction;
             }
-            else
+
+        }
+        else if (permission_mask & OPT_P_CONNECTION)
+        {
+            options->ce.tls_auth_file = p[1];
+            options->ce.tls_auth_file_inline = is_inline;
+            options->ce.key_direction = KEY_DIRECTION_BIDIRECTIONAL;
+
+            if (!is_inline && p[2])
             {
-                goto err;
+                key_direction = ascii2keydirection(msglevel, p[2]);
+                if (key_direction < 0)
+                {
+                    goto err;
+                }
+                options->ce.key_direction = key_direction;
             }
         }
-        options->tls_auth_file = p[1];
     }
     else if (streq(p[0], "tls-crypt") && p[1] && !p[3])
     {
-        VERIFY_PERMISSION(OPT_P_GENERAL);
-        if (streq(p[1], INLINE_FILE_TAG) && p[2])
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_CONNECTION|OPT_P_INLINE);
+        if (permission_mask & OPT_P_GENERAL)
         {
-            options->tls_crypt_inline = p[2];
+            options->tls_crypt_file = p[1];
+            options->tls_crypt_file_inline = is_inline;
         }
-        options->tls_crypt_file = p[1];
+        else if (permission_mask & OPT_P_CONNECTION)
+        {
+            options->ce.tls_crypt_file = p[1];
+            options->ce.tls_crypt_file_inline = is_inline;
+        }
     }
-    else if (streq(p[0], "key-method") && p[1] && !p[2])
+    else if (streq(p[0], "tls-crypt-v2") && p[1] && !p[3])
     {
-        int key_method;
-
-        VERIFY_PERMISSION(OPT_P_GENERAL);
-        key_method = atoi(p[1]);
-        if (key_method < KEY_METHOD_MIN || key_method > KEY_METHOD_MAX)
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_CONNECTION|OPT_P_INLINE);
+        if (permission_mask & OPT_P_GENERAL)
         {
-            msg(msglevel, "key_method parameter (%d) must be >= %d and <= %d",
-                key_method,
-                KEY_METHOD_MIN,
-                KEY_METHOD_MAX);
-            goto err;
+            options->tls_crypt_v2_file = p[1];
+            options->tls_crypt_v2_file_inline = is_inline;
         }
-        options->key_method = key_method;
+        else if (permission_mask & OPT_P_CONNECTION)
+        {
+            options->ce.tls_crypt_v2_file = p[1];
+            options->ce.tls_crypt_v2_file_inline = is_inline;
+        }
+    }
+    else if (streq(p[0], "tls-crypt-v2-verify") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->tls_crypt_v2_verify_script = p[1];
     }
     else if (streq(p[0], "x509-track") && p[1] && !p[2])
     {
@@ -8123,7 +8594,6 @@
         options->x509_username_field = p[1];
     }
 #endif /* ENABLE_X509ALTUSERNAME */
-#endif /* ENABLE_CRYPTO */
 #ifdef ENABLE_PKCS11
     else if (streq(p[0], "show-pkcs11-ids") && !p[3])
     {
@@ -8239,7 +8709,7 @@
         options->use_peer_id = true;
         options->peer_id = atoi(p[1]);
     }
-#if defined(ENABLE_CRYPTO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10001000
+#ifdef HAVE_EXPORT_KEYING_MATERIAL
     else if (streq(p[0], "keying-material-exporter") && p[1] && p[2])
     {
         int ekm_length = positive_atoi(p[2]);
@@ -8261,12 +8731,51 @@
         options->keying_material_exporter_label = p[1];
         options->keying_material_exporter_length = ekm_length;
     }
-#endif /* if defined(ENABLE_CRYPTO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10001000 */
+#endif /* HAVE_EXPORT_KEYING_MATERIAL */
     else if (streq(p[0], "allow-recursive-routing") && !p[1])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
         options->allow_recursive_routing = true;
     }
+    else if (streq(p[0], "vlan-tagging") && !p[1])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->vlan_tagging = true;
+    }
+    else if (streq(p[0], "vlan-accept") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        if (streq(p[1], "tagged"))
+        {
+            options->vlan_accept = VLAN_ONLY_TAGGED;
+        }
+        else if (streq(p[1], "untagged"))
+        {
+            options->vlan_accept = VLAN_ONLY_UNTAGGED_OR_PRIORITY;
+        }
+        else if (streq(p[1], "all"))
+        {
+            options->vlan_accept = VLAN_ALL;
+        }
+        else
+        {
+            msg(msglevel, "--vlan-accept must be 'tagged', 'untagged' or 'all'");
+            goto err;
+        }
+    }
+    else if (streq(p[0], "vlan-pvid") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INSTANCE);
+        options->vlan_pvid = positive_atoi(p[1]);
+        if (options->vlan_pvid < OPENVPN_8021Q_MIN_VID
+            || options->vlan_pvid > OPENVPN_8021Q_MAX_VID)
+        {
+            msg(msglevel,
+                "the parameter of --vlan-pvid parameters must be >= %u and <= %u",
+                OPENVPN_8021Q_MIN_VID, OPENVPN_8021Q_MAX_VID);
+            goto err;
+        }
+    }
     else
     {
         int i;
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index f3cafea..15ef967 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -41,9 +41,7 @@
 #include "comp.h"
 #include "pushlist.h"
 #include "clinat.h"
-#ifdef ENABLE_CRYPTO
 #include "crypto_backend.h"
-#endif
 
 
 /*
@@ -74,14 +72,21 @@
     bool routes_ipv6_defined;
     struct route_ipv6_option_list *routes_ipv6;
 
+    const char *route_default_gateway;
+    const char *route_ipv6_default_gateway;
+
     bool client_nat_defined;
     struct client_nat_option_list *client_nat;
 
+    int ping_send_timeout;
+    int ping_rec_timeout;
+    int ping_rec_timeout_action;
+
     int foreign_option_index;
 };
 
 #endif
-#if defined(ENABLE_CRYPTO) && !defined(ENABLE_CRYPTO_OPENSSL) && !defined(ENABLE_CRYPTO_MBEDTLS)
+#if !defined(ENABLE_CRYPTO_OPENSSL) && !defined(ENABLE_CRYPTO_MBEDTLS)
 #error "At least one of OpenSSL or mbed TLS needs to be defined."
 #endif
 
@@ -132,6 +137,20 @@
 #define CE_MAN_QUERY_REMOTE_MASK   (0x07)
 #define CE_MAN_QUERY_REMOTE_SHIFT  (2)
     unsigned int flags;
+
+    /* Shared secret used for TLS control channel authentication */
+    const char *tls_auth_file;
+    bool tls_auth_file_inline;
+    int key_direction;
+
+    /* Shared secret used for TLS control channel authenticated encryption */
+    const char *tls_crypt_file;
+    bool tls_crypt_file_inline;
+
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    bool tls_crypt_v2_file_inline;
 };
 
 struct remote_entry
@@ -157,6 +176,13 @@
     struct remote_entry *array[CONNECTION_LIST_SIZE];
 };
 
+enum vlan_acceptable_frames
+{
+    VLAN_ONLY_TAGGED,
+    VLAN_ONLY_UNTAGGED_OR_PRIORITY,
+    VLAN_ALL,
+};
+
 struct remote_host_store
 {
 #define RH_HOST_LEN 80
@@ -165,6 +191,13 @@
     char port[RH_PORT_LEN];
 };
 
+enum genkey_type {
+    GENKEY_SECRET,
+    GENKEY_TLS_CRYPTV2_CLIENT,
+    GENKEY_TLS_CRYPTV2_SERVER,
+    GENKEY_AUTH_TOKEN
+};
+
 /* Command line options */
 struct options
 {
@@ -188,7 +221,6 @@
     bool persist_config;
     int persist_mode;
 
-#ifdef ENABLE_CRYPTO
     const char *key_pass_file;
     bool show_ciphers;
     bool show_digests;
@@ -196,7 +228,9 @@
     bool show_tls_ciphers;
     bool show_curves;
     bool genkey;
-#endif
+    enum genkey_type genkey_type;
+    const char *genkey_filename;
+    const char *genkey_extra_data;
 
     /* Networking parms */
     int connect_retry_max;
@@ -235,9 +269,7 @@
 
     int proto_force;
 
-#ifdef ENABLE_OCC
     bool mtu_test;
-#endif
 
 #ifdef ENABLE_MEMSTATS
     char *memstats_fn;
@@ -325,6 +357,7 @@
 
     /* mark value */
     int mark;
+    char *bind_dev;
 
     /* socket flags */
     unsigned int sockflags;
@@ -333,6 +366,7 @@
     const char *route_script;
     const char *route_predown_script;
     const char *route_default_gateway;
+    const char *route_ipv6_default_gateway;
     int route_default_metric;
     bool route_noexec;
     int route_delay;
@@ -340,15 +374,14 @@
     bool route_delay_defined;
     struct route_option_list *routes;
     struct route_ipv6_option_list *routes_ipv6;                 /* IPv6 */
+    bool block_ipv6;
     bool route_nopull;
     bool route_gateway_via_dhcp;
     bool allow_pull_fqdn; /* as a client, allow server to push a FQDN for certain parameters */
     struct client_nat_option_list *client_nat;
 
-#ifdef ENABLE_OCC
     /* Enable options consistency check between peers */
     bool occ;
-#endif
 
 #ifdef ENABLE_MANAGEMENT
     const char *management_addr;
@@ -375,7 +408,6 @@
 
 #if P2MP
 
-#if P2MP_SERVER
     /* the tmp dir is for now only used in the P2P server context */
     const char *tmp_dir;
     bool server_defined;
@@ -429,6 +461,7 @@
     bool push_ifconfig_constraint_defined;
     in_addr_t push_ifconfig_constraint_network;
     in_addr_t push_ifconfig_constraint_netmask;
+    bool push_ifconfig_ipv4_blocked;                    /* IPv4 */
     bool push_ifconfig_ipv6_defined;                    /* IPv6 */
     struct in6_addr push_ifconfig_ipv6_local;           /* IPv6 */
     int push_ifconfig_ipv6_netbits;                     /* IPv6 */
@@ -446,13 +479,17 @@
     const char *auth_user_pass_verify_script;
     bool auth_user_pass_verify_script_via_file;
     bool auth_token_generate;
-    unsigned int auth_token_lifetime;
+    bool auth_token_gen_secret_file;
+    bool auth_token_call_auth;
+    int auth_token_lifetime;
+    const char *auth_token_secret_file;
+    bool auth_token_secret_file_inline;
+
 #if PORT_SHARE
     char *port_share_host;
     char *port_share_port;
     const char *port_share_journal_dir;
 #endif
-#endif /* if P2MP_SERVER */
 
     bool client;
     bool pull; /* client pull of config options from server */
@@ -463,17 +500,18 @@
 
     int scheduled_exit_interval;
 
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
     struct static_challenge_info sc_info;
 #endif
 #endif /* if P2MP */
 
-#ifdef ENABLE_CRYPTO
     /* Cipher parms */
     const char *shared_secret_file;
-    const char *shared_secret_file_inline;
+    bool shared_secret_file_inline;
     int key_direction;
     const char *ciphername;
+    bool enable_ncp_fallback;      /**< If defined fall back to
+                                    * ciphername if NCP fails */
     bool ncp_enabled;
     const char *ncp_ciphers;
     const char *authname;
@@ -486,7 +524,6 @@
     int replay_window;
     int replay_time;
     const char *packet_id_file;
-    bool use_iv;
     bool test_crypto;
 #ifdef ENABLE_PREDICTION_RESISTANCE
     bool use_prediction_resistance;
@@ -496,14 +533,21 @@
     bool tls_server;
     bool tls_client;
     const char *ca_file;
+    bool ca_file_inline;
     const char *ca_path;
     const char *dh_file;
+    bool dh_file_inline;
     const char *cert_file;
+    bool cert_file_inline;
     const char *extra_certs_file;
+    bool extra_certs_file_inline;
     const char *priv_key_file;
+    bool priv_key_file_inline;
     const char *pkcs12_file;
+    bool pkcs12_file_inline;
     const char *cipher_list;
     const char *cipher_list_tls13;
+    const char *tls_groups;
     const char *tls_cert_profile;
     const char *ecdh_curve;
     const char *tls_verify;
@@ -511,14 +555,7 @@
     const char *verify_x509_name;
     const char *tls_export_cert;
     const char *crl_file;
-
-    const char *ca_file_inline;
-    const char *cert_file_inline;
-    const char *extra_certs_file_inline;
-    const char *crl_file_inline;
-    char *priv_key_file_inline;
-    const char *dh_file_inline;
-    const char *pkcs12_file_inline; /* contains the base64 encoding of pkcs12 file */
+    bool crl_file_inline;
 
     int ns_cert_type; /* set to 0, NS_CERT_CHECK_SERVER, or NS_CERT_CHECK_CLIENT */
     unsigned remote_cert_ku[MAX_PARMS];
@@ -540,10 +577,6 @@
 #ifdef ENABLE_CRYPTOAPI
     const char *cryptoapi_cert;
 #endif
-
-    /* data channel key exchange method */
-    int key_method;
-
     /* Per-packet timeout on control channel */
     int tls_timeout;
 
@@ -551,6 +584,7 @@
     int renegotiate_bytes;
     int renegotiate_packets;
     int renegotiate_seconds;
+    int renegotiate_seconds_min;
 
     /* Data channel key handshake must finalize
      * within n seconds of handshake initiation. */
@@ -566,23 +600,28 @@
 
     /* Shared secret used for TLS control channel authentication */
     const char *tls_auth_file;
-    const char *tls_auth_file_inline;
+    bool tls_auth_file_inline;
 
     /* Shared secret used for TLS control channel authenticated encryption */
     const char *tls_crypt_file;
-    const char *tls_crypt_inline;
+    bool tls_crypt_file_inline;
+
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    bool tls_crypt_v2_file_inline;
+
+    const char *tls_crypt_v2_metadata;
+
+    const char *tls_crypt_v2_verify_script;
 
     /* Allow only one session */
     bool single_session;
 
-#ifdef ENABLE_PUSH_PEER_INFO
     bool push_peer_info;
-#endif
 
     bool tls_exit;
 
-#endif /* ENABLE_CRYPTO */
-
     const struct x509_track *x509_track;
 
     /* special state parms */
@@ -595,17 +634,22 @@
     bool show_net_up;
     int route_method;
     bool block_outside_dns;
+    enum windows_driver_type windows_driver;
 #endif
 
     bool use_peer_id;
     uint32_t peer_id;
 
-#if defined(ENABLE_CRYPTO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10001000
+#ifdef HAVE_EXPORT_KEYING_MATERIAL
     /* Keying Material Exporters [RFC 5705] */
     const char *keying_material_exporter_label;
     int keying_material_exporter_length;
 #endif
 
+    bool vlan_tagging;
+    enum vlan_acceptable_frames vlan_accept;
+    uint16_t vlan_pvid;
+
     struct pull_filter_list *pull_filter_list;
 
     /* Useful when packets sent by openvpn itself are not subject
@@ -635,7 +679,7 @@
 #define OPT_P_MTU             (1<<14) /* TODO */
 #define OPT_P_NICE            (1<<15)
 #define OPT_P_PUSH            (1<<16)
-#define OPT_P_INSTANCE        (1<<17)
+#define OPT_P_INSTANCE        (1<<17) /**< allowed in ccd, client-connect etc*/
 #define OPT_P_CONFIG          (1<<18)
 #define OPT_P_EXPLICIT_NOTIFY (1<<19)
 #define OPT_P_ECHO            (1<<20)
@@ -647,15 +691,14 @@
 #define OPT_P_SOCKFLAGS       (1<<26)
 #define OPT_P_CONNECTION      (1<<27)
 #define OPT_P_PEER_ID         (1<<28)
+#define OPT_P_INLINE          (1<<29)
 
 #define OPT_P_DEFAULT   (~(OPT_P_INSTANCE|OPT_P_PULL_MODE))
 
 #if P2MP
 #define PULL_DEFINED(opt) ((opt)->pull)
-#if P2MP_SERVER
 #define PUSH_DEFINED(opt) ((opt)->push_list)
 #endif
-#endif
 
 #ifndef PULL_DEFINED
 #define PULL_DEFINED(opt) (false)
@@ -718,13 +761,12 @@
 
 bool string_defined_equal(const char *s1, const char *s2);
 
-#ifdef ENABLE_OCC
-
 const char *options_string_version(const char *s, struct gc_arena *gc);
 
 char *options_string(const struct options *o,
                      const struct frame *frame,
                      struct tuntap *tt,
+                     openvpn_net_ctx_t *ctx,
                      bool remote,
                      struct gc_arena *gc);
 
@@ -736,8 +778,6 @@
 
 void options_warning(char *actual, const char *expected);
 
-#endif
-
 /**
  * Given an OpenVPN options string, extract the value of an option.
  *
diff --git a/src/openvpn/otime.c b/src/openvpn/otime.c
index 805aac9..640168a 100644
--- a/src/openvpn/otime.c
+++ b/src/openvpn/otime.c
@@ -88,9 +88,9 @@
 tv_string(const struct timeval *tv, struct gc_arena *gc)
 {
     struct buffer out = alloc_buf_gc(64, gc);
-    buf_printf(&out, "[%d/%d]",
-               (int) tv->tv_sec,
-               (int )tv->tv_usec);
+    buf_printf(&out, "[%" PRIi64 "/%ld]",
+               (int64_t)tv->tv_sec,
+               (long)tv->tv_usec);
     return BSTR(&out);
 }
 
@@ -103,7 +103,7 @@
 tv_string_abs(const struct timeval *tv, struct gc_arena *gc)
 {
     return time_string((time_t) tv->tv_sec,
-                       (int) tv->tv_usec,
+                       (long) tv->tv_usec,
                        true,
                        gc);
 }
@@ -127,12 +127,15 @@
     }
 
     t = tv.tv_sec;
-    buf_printf(&out, "%s", ctime(&t));
-    buf_rmtail(&out, '\n');
+    struct tm *tm = localtime(&t);
+
+    buf_printf(&out, "%04d-%02d-%02d %02d:%02d:%02d",
+               tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
+               tm->tm_hour, tm->tm_min, tm->tm_sec);
 
     if (show_usec && tv.tv_usec)
     {
-        buf_printf(&out, " us=%d", (int)tv.tv_usec);
+        buf_printf(&out, " us=%ld", (long)tv.tv_usec);
     }
 
     return BSTR(&out);
@@ -198,10 +201,10 @@
         t = time(NULL);
         gettimeofday(&tv, NULL);
 #if 1
-        msg(M_INFO, "t=%u s=%u us=%u",
-            (unsigned int)t,
-            (unsigned int)tv.tv_sec,
-            (unsigned int)tv.tv_usec);
+        msg(M_INFO, "t=%" PRIi64 " s=%" PRIi64 " us=%ld",
+            (int64_t)t,
+            (int64_t)tv.tv_sec,
+            (long)tv.tv_usec);
 #endif
     }
 }
diff --git a/src/openvpn/otime.h b/src/openvpn/otime.h
index a6f7ec2..78d20ba 100644
--- a/src/openvpn/otime.h
+++ b/src/openvpn/otime.h
@@ -84,6 +84,7 @@
     openvpn_gettimeofday(&tv, NULL);
 #else
     update_now(time(NULL));
+    now_usec = 0;
 #endif
 }
 
diff --git a/src/openvpn/packet_id.c b/src/openvpn/packet_id.c
index d58761b..0c74487 100644
--- a/src/openvpn/packet_id.c
+++ b/src/openvpn/packet_id.c
@@ -38,8 +38,6 @@
 
 #include "syshead.h"
 
-#ifdef ENABLE_CRYPTO
-
 #include "packet_id.h"
 #include "misc.h"
 #include "integer.h"
@@ -349,7 +347,7 @@
 
 bool
 packet_id_write(struct packet_id_send *p, struct buffer *buf, bool long_form,
-        bool prepend)
+                bool prepend)
 {
     if (!packet_id_send_update(p, long_form))
     {
@@ -608,14 +606,14 @@
         }
         buf_printf(&out, "%c", c);
     }
-    buf_printf(&out, "] " time_format ":" packet_id_format, (time_type)p->time, (packet_id_print_type)p->id);
+    buf_printf(&out, "] %" PRIi64 ":" packet_id_format, (int64_t)p->time, (packet_id_print_type)p->id);
     if (pin)
     {
-        buf_printf(&out, " " time_format ":" packet_id_format, (time_type)pin->time, (packet_id_print_type)pin->id);
+        buf_printf(&out, " %" PRIi64 ":" packet_id_format, (int64_t)pin->time, (packet_id_print_type)pin->id);
     }
 
-    buf_printf(&out, " t=" time_format "[%d]",
-               (time_type)prev_now,
+    buf_printf(&out, " t=%" PRIi64 "[%d]",
+               (int64_t)prev_now,
                (int)(prev_now - tv.tv_sec));
 
     buf_printf(&out, " r=[%d,%d,%d,%d,%d]",
@@ -668,8 +666,8 @@
         {
             packet_id_reap_test(&pid.rec);
             test = packet_id_test(&pid.rec, &pin);
-            printf("packet_id_test (" time_format ", " packet_id_format ") returned %d\n",
-                   (time_type)pin.time,
+            printf("packet_id_test (%" PRIi64 ", " packet_id_format ") returned %d\n",
+                   (int64_t)pin.time,
                    (packet_id_print_type)pin.id,
                    test);
             if (test)
@@ -681,8 +679,8 @@
         {
             long_form = (count < 20);
             packet_id_alloc_outgoing(&pid.send, &pin, long_form);
-            printf("(" time_format "(" packet_id_format "), %d)\n",
-                   (time_type)pin.time,
+            printf("(%" PRIi64 "(" packet_id_format "), %d)\n",
+                   (int64_t)pin.time,
                    (packet_id_print_type)pin.id,
                    long_form);
             if (pid.send.id == 10)
@@ -695,5 +693,3 @@
     packet_id_free(&pid);
 }
 #endif /* ifdef PID_TEST */
-
-#endif /* ENABLE_CRYPTO */
diff --git a/src/openvpn/packet_id.h b/src/openvpn/packet_id.h
index f984e7c..3b58da2 100644
--- a/src/openvpn/packet_id.h
+++ b/src/openvpn/packet_id.h
@@ -27,8 +27,6 @@
  * attempts to replay them back later.
  */
 
-#ifdef ENABLE_CRYPTO
-
 #ifndef PACKET_ID_H
 #define PACKET_ID_H
 
@@ -260,12 +258,12 @@
  * @param p             Packet ID state.
  * @param buf           Buffer to write the packet ID too
  * @param long_form     If true, also update and write time_t to buf
- * @param prepend       If true, prepend to buffer, otherwise apppend.
+ * @param prepend       If true, prepend to buffer, otherwise append.
  *
  * @return true if successful, false otherwise.
  */
 bool packet_id_write(struct packet_id_send *p, struct buffer *buf,
-        bool long_form, bool prepend);
+                     bool long_form, bool prepend);
 
 /*
  * Inline functions.
@@ -342,4 +340,3 @@
 }
 
 #endif /* PACKET_ID_H */
-#endif /* ENABLE_CRYPTO */
diff --git a/src/openvpn/pf-inline.h b/src/openvpn/pf-inline.h
deleted file mode 100644
index 90cc41c..0000000
--- a/src/openvpn/pf-inline.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- *  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) 2002-2018 OpenVPN Inc <sales@openvpn.net>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License version 2
- *  as published by the Free Software Foundation.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License along
- *  with this program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#if defined(ENABLE_PF) && !defined(PF_INLINE_H)
-#define PF_INLINE_H
-
-/*
- * Inline functions
- */
-
-#define PCT_SRC  1
-#define PCT_DEST 2
-static inline bool
-pf_c2c_test(const struct context *src, const struct context *dest, const char *prefix)
-{
-    bool pf_cn_test(struct pf_set *pfs, const struct tls_multi *tm, const int type, const char *prefix);
-
-    return (!src->c2.pf.enabled  || pf_cn_test(src->c2.pf.pfs,  dest->c2.tls_multi, PCT_DEST, prefix))
-           && (!dest->c2.pf.enabled || pf_cn_test(dest->c2.pf.pfs, src->c2.tls_multi,  PCT_SRC,  prefix));
-}
-
-static inline bool
-pf_addr_test(const struct context *src, const struct mroute_addr *dest, const char *prefix)
-{
-    bool pf_addr_test_dowork(const struct context *src, const struct mroute_addr *dest, const char *prefix);
-
-    if (src->c2.pf.enabled)
-    {
-        return pf_addr_test_dowork(src, dest, prefix);
-    }
-    else
-    {
-        return true;
-    }
-}
-
-static inline bool
-pf_kill_test(const struct pf_set *pfs)
-{
-    return pfs->kill;
-}
-
-#endif /* if defined(ENABLE_PF) && !defined(PF_INLINE_H) */
diff --git a/src/openvpn/pf.c b/src/openvpn/pf.c
index 7277ae6..3f472ef 100644
--- a/src/openvpn/pf.c
+++ b/src/openvpn/pf.c
@@ -35,9 +35,9 @@
 
 #include "init.h"
 #include "memdbg.h"
+#include "pf.h"
 #include "ssl_verify.h"
 
-#include "pf-inline.h"
 
 static void
 pf_destroy(struct pf_set *pfs)
@@ -547,9 +547,7 @@
     const int wakeup_transition = 60;
     bool reloaded = false;
 
-    if (c->c2.pf.enabled
-        && c->c2.pf.filename
-        && event_timeout_trigger(&c->c2.pf.reload, &c->c2.timeval, ETT_DEFAULT))
+    if (c->c2.pf.filename)
     {
         platform_stat_t s;
         if (!platform_stat(c->c2.pf.filename, &s))
@@ -618,19 +616,18 @@
 void
 pf_init_context(struct context *c)
 {
-    struct gc_arena gc = gc_new();
 #ifdef PLUGIN_PF
     if (plugin_defined(c->plugins, OPENVPN_PLUGIN_ENABLE_PF))
     {
-        const char *pf_file = create_temp_file(c->options.tmp_dir, "pf", &gc);
-        if (pf_file)
+        c->c2.pf.filename = platform_create_temp_file(c->options.tmp_dir, "pf",
+                                                      &c->c2.gc);
+        if (c->c2.pf.filename)
         {
-            setenv_str(c->c2.es, "pf_file", pf_file);
+            setenv_str(c->c2.es, "pf_file", c->c2.pf.filename);
 
             if (plugin_call(c->plugins, OPENVPN_PLUGIN_ENABLE_PF, NULL, NULL, c->c2.es) == OPENVPN_PLUGIN_FUNC_SUCCESS)
             {
                 event_timeout_init(&c->c2.pf.reload, 1, now);
-                c->c2.pf.filename = string_alloc(pf_file, &c->c2.gc);
                 c->c2.pf.enabled = true;
 #ifdef ENABLE_DEBUG
                 if (check_debug_level(D_PF_DEBUG))
@@ -639,10 +636,21 @@
                 }
 #endif
             }
-            else
-            {
-                msg(M_WARN, "WARNING: OPENVPN_PLUGIN_ENABLE_PF disabled");
-            }
+        }
+        if (!c->c2.pf.enabled)
+        {
+            /* At some point in openvpn history, this code just printed a
+             * warning and signalled itself (SIGUSR1, "plugin-pf-init-failed")
+             * to terminate the client instance.  This got broken at one of
+             * the client auth state refactorings (leading to SIGSEGV crashes)
+             * and due to "pf will be removed anyway" reasons the easiest way
+             * to prevent crashes is to REQUIRE that plugins succeed - so if
+             * the plugin fails, we cleanly abort OpenVPN
+             *
+             * see also: https://community.openvpn.net/openvpn/ticket/1377
+             */
+            msg(M_FATAL, "FATAL: failed to init PF plugin, must succeed.");
+            return;
         }
     }
 #endif /* ifdef PLUGIN_PF */
@@ -658,7 +666,6 @@
 #endif
     }
 #endif
-    gc_free(&gc);
 }
 
 void
diff --git a/src/openvpn/pf.h b/src/openvpn/pf.h
index ff75a00..c64d21b 100644
--- a/src/openvpn/pf.h
+++ b/src/openvpn/pf.h
@@ -31,6 +31,9 @@
 
 #define PF_MAX_LINE_LEN 256
 
+#define PCT_SRC  1
+#define PCT_DEST 2
+
 struct context;
 
 struct ipv4_subnet {
@@ -75,7 +78,7 @@
     bool enabled;
     struct pf_set *pfs;
 #ifdef PLUGIN_PF
-    char *filename;
+    const char *filename;
     time_t file_last_mod;
     unsigned int n_check_reload;
     struct event_timeout reload;
@@ -101,4 +104,44 @@
 
 #endif
 
+bool pf_addr_test_dowork(const struct context *src,
+                         const struct mroute_addr *dest, const char *prefix);
+
+static inline bool
+pf_addr_test(const struct pf_context *src_pf, const struct context *src,
+             const struct mroute_addr *dest, const char *prefix)
+{
+    if (src_pf->enabled)
+    {
+        return pf_addr_test_dowork(src, dest, prefix);
+    }
+    else
+    {
+        return true;
+    }
+}
+
+/*
+ * Inline functions
+ */
+
+bool pf_cn_test(struct pf_set *pfs, const struct tls_multi *tm, const int type,
+                const char *prefix);
+
+static inline bool
+pf_c2c_test(const struct pf_context *src_pf, const struct tls_multi *src,
+            const struct pf_context *dest_pf, const struct tls_multi *dest,
+            const char *prefix)
+{
+    return (!src_pf->enabled || pf_cn_test(src_pf->pfs, dest, PCT_DEST, prefix))
+           && (!dest_pf->enabled || pf_cn_test(dest_pf->pfs, src, PCT_SRC,
+                                               prefix));
+}
+
+static inline bool
+pf_kill_test(const struct pf_set *pfs)
+{
+    return pfs->kill;
+}
+
 #endif /* if defined(ENABLE_PF) && !defined(OPENVPN_PF_H) */
diff --git a/src/openvpn/ping-inline.h b/src/openvpn/ping-inline.h
deleted file mode 100644
index 1a5c8bc..0000000
--- a/src/openvpn/ping-inline.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- *  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) 2002-2018 OpenVPN Inc <sales@openvpn.net>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License version 2
- *  as published by the Free Software Foundation.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License along
- *  with this program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef PING_INLINE_H
-#define PING_INLINE_H
-
-/*
- * Should we exit or restart due to ping (or other authenticated packet)
- * not received in n seconds?
- */
-static inline void
-check_ping_restart(struct context *c)
-{
-    void check_ping_restart_dowork(struct context *c);
-
-    if (c->options.ping_rec_timeout
-        && event_timeout_trigger(&c->c2.ping_rec_interval,
-                                 &c->c2.timeval,
-                                 (!c->options.ping_timer_remote
-                                  || link_socket_actual_defined(&c->c1.link_socket_addr.actual))
-                                 ? ETT_DEFAULT : 15))
-    {
-        check_ping_restart_dowork(c);
-    }
-}
-
-/*
- * Should we ping the remote?
- */
-static inline void
-check_ping_send(struct context *c)
-{
-    void check_ping_send_dowork(struct context *c);
-
-    if (c->options.ping_send_timeout
-        && event_timeout_trigger(&c->c2.ping_send_interval,
-                                 &c->c2.timeval,
-                                 !TO_LINK_DEF(c) ? ETT_DEFAULT : 1))
-    {
-        check_ping_send_dowork(c);
-    }
-}
-
-#endif /* ifndef PING_INLINE_H */
diff --git a/src/openvpn/ping.c b/src/openvpn/ping.c
index 208170d..aa176fd 100644
--- a/src/openvpn/ping.c
+++ b/src/openvpn/ping.c
@@ -33,7 +33,6 @@
 
 #include "memdbg.h"
 
-#include "ping-inline.h"
 
 /*
  * This random string identifies an OpenVPN ping packet.
@@ -47,12 +46,8 @@
     0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48
 };
 
-/*
- * Should we exit or restart due to ping (or other authenticated packet)
- * not received in n seconds?
- */
 void
-check_ping_restart_dowork(struct context *c)
+trigger_ping_timeout_signal(struct context *c)
 {
     struct gc_arena gc = gc_new();
     switch (c->options.ping_rec_timeout_action)
diff --git a/src/openvpn/ping.h b/src/openvpn/ping.h
index 05793b4..6feaa87 100644
--- a/src/openvpn/ping.h
+++ b/src/openvpn/ping.h
@@ -43,4 +43,46 @@
     return buf_string_match(buf, ping_string, PING_STRING_SIZE);
 }
 
-#endif
+/**
+ * Trigger the correct signal on a --ping timeout
+ * depending if --ping-exit is set (SIGTERM) or not
+ * (SIGUSR1)
+ */
+void trigger_ping_timeout_signal(struct context *c);
+
+void check_ping_send_dowork(struct context *c);
+
+/*
+ * Should we exit or restart due to ping (or other authenticated packet)
+ * not received in n seconds?
+ */
+static inline void
+check_ping_restart(struct context *c)
+{
+    if (c->options.ping_rec_timeout
+        && event_timeout_trigger(&c->c2.ping_rec_interval,
+                                 &c->c2.timeval,
+                                 (!c->options.ping_timer_remote
+                                  || link_socket_actual_defined(&c->c1.link_socket_addr.actual))
+                                 ? ETT_DEFAULT : 15))
+    {
+        trigger_ping_timeout_signal(c);
+    }
+}
+
+/*
+ * Should we ping the remote?
+ */
+static inline void
+check_ping_send(struct context *c)
+{
+    if (c->options.ping_send_timeout
+        && event_timeout_trigger(&c->c2.ping_send_interval,
+                                 &c->c2.timeval,
+                                 !TO_LINK_DEF(c) ? ETT_DEFAULT : 1))
+    {
+        check_ping_send_dowork(c);
+    }
+}
+
+#endif /* ifndef PING_H */
diff --git a/src/openvpn/pkcs11_mbedtls.c b/src/openvpn/pkcs11_mbedtls.c
index 7620624..bd704e0 100644
--- a/src/openvpn/pkcs11_mbedtls.c
+++ b/src/openvpn/pkcs11_mbedtls.c
@@ -39,60 +39,89 @@
 #include "errlevel.h"
 #include "pkcs11_backend.h"
 #include "ssl_verify_backend.h"
-#include <mbedtls/pkcs11.h>
 #include <mbedtls/x509.h>
 
+static bool
+pkcs11_get_x509_cert(pkcs11h_certificate_t pkcs11_cert, mbedtls_x509_crt *cert)
+{
+    unsigned char *cert_blob = NULL;
+    size_t cert_blob_size = 0;
+    bool ret = false;
+
+    if (pkcs11h_certificate_getCertificateBlob(pkcs11_cert, NULL,
+                                               &cert_blob_size) != CKR_OK)
+    {
+        msg(M_WARN, "PKCS#11: Cannot retrieve certificate object size");
+        goto cleanup;
+    }
+
+    check_malloc_return((cert_blob = calloc(1, cert_blob_size)));
+    if (pkcs11h_certificate_getCertificateBlob(pkcs11_cert, cert_blob,
+                                               &cert_blob_size) != CKR_OK)
+    {
+        msg(M_WARN, "PKCS#11: Cannot retrieve certificate object");
+        goto cleanup;
+    }
+
+    if (!mbed_ok(mbedtls_x509_crt_parse(cert, cert_blob, cert_blob_size)))
+    {
+        msg(M_WARN, "PKCS#11: Could not parse certificate");
+        goto cleanup;
+    }
+
+    ret = true;
+cleanup:
+    free(cert_blob);
+    return ret;
+}
+
+static bool
+pkcs11_sign(void *pkcs11_cert, const void *src, size_t src_len,
+            void *dst, size_t dst_len)
+{
+    return CKR_OK == pkcs11h_certificate_signAny(pkcs11_cert, CKM_RSA_PKCS,
+                                                 src, src_len, dst, &dst_len);
+}
+
 int
 pkcs11_init_tls_session(pkcs11h_certificate_t certificate,
                         struct tls_root_ctx *const ssl_ctx)
 {
-    int ret = 1;
-
     ASSERT(NULL != ssl_ctx);
 
+    ssl_ctx->pkcs11_cert = certificate;
+
     ALLOC_OBJ_CLEAR(ssl_ctx->crt_chain, mbedtls_x509_crt);
-    if (mbedtls_pkcs11_x509_cert_bind(ssl_ctx->crt_chain, certificate))
+    if (!pkcs11_get_x509_cert(certificate, ssl_ctx->crt_chain))
     {
-        msg(M_FATAL, "PKCS#11: Cannot retrieve mbed TLS certificate object");
-        goto cleanup;
+        msg(M_WARN, "PKCS#11: Cannot initialize certificate");
+        return 1;
     }
 
-    ALLOC_OBJ_CLEAR(ssl_ctx->priv_key_pkcs11, mbedtls_pkcs11_context);
-    if (mbedtls_pkcs11_priv_key_bind(ssl_ctx->priv_key_pkcs11, certificate))
+    if (tls_ctx_use_external_signing_func(ssl_ctx, pkcs11_sign, certificate))
     {
-        msg(M_FATAL, "PKCS#11: Cannot initialize mbed TLS private key object");
-        goto cleanup;
+        msg(M_WARN, "PKCS#11: Cannot register signing function");
+        return 1;
     }
 
-    ALLOC_OBJ_CLEAR(ssl_ctx->priv_key, mbedtls_pk_context);
-    if (!mbed_ok(mbedtls_pk_setup_rsa_alt(ssl_ctx->priv_key,
-                                          ssl_ctx->priv_key_pkcs11, mbedtls_ssl_pkcs11_decrypt,
-                                          mbedtls_ssl_pkcs11_sign, mbedtls_ssl_pkcs11_key_len)))
-    {
-        goto cleanup;
-    }
-
-    ret = 0;
-
-cleanup:
-    return ret;
+    return 0;
 }
 
 char *
 pkcs11_certificate_dn(pkcs11h_certificate_t cert, struct gc_arena *gc)
 {
     char *ret = NULL;
-    mbedtls_x509_crt mbed_crt = {0};
+    mbedtls_x509_crt mbed_crt = { 0 };
 
-    if (mbedtls_pkcs11_x509_cert_bind(&mbed_crt, cert))
+    if (!pkcs11_get_x509_cert(cert, &mbed_crt))
     {
-        msg(M_FATAL, "PKCS#11: Cannot retrieve mbed TLS certificate object");
+        msg(M_WARN, "PKCS#11: Cannot retrieve mbed TLS certificate object");
         goto cleanup;
     }
 
     if (!(ret = x509_get_subject(&mbed_crt, gc)))
     {
-        msg(M_FATAL, "PKCS#11: mbed TLS cannot parse subject");
+        msg(M_WARN, "PKCS#11: mbed TLS cannot parse subject");
         goto cleanup;
     }
 
@@ -107,23 +136,21 @@
                           size_t serial_len)
 {
     int ret = 1;
+    mbedtls_x509_crt mbed_crt = { 0 };
 
-    mbedtls_x509_crt mbed_crt = {0};
-
-    if (mbedtls_pkcs11_x509_cert_bind(&mbed_crt, cert))
+    if (!pkcs11_get_x509_cert(cert, &mbed_crt))
     {
-        msg(M_FATAL, "PKCS#11: Cannot retrieve mbed TLS certificate object");
+        msg(M_WARN, "PKCS#11: Cannot retrieve mbed TLS certificate object");
         goto cleanup;
     }
 
-    if (-1 == mbedtls_x509_serial_gets(serial, serial_len, &mbed_crt.serial))
+    if (mbedtls_x509_serial_gets(serial, serial_len, &mbed_crt.serial) < 0)
     {
-        msg(M_FATAL, "PKCS#11: mbed TLS cannot parse serial");
+        msg(M_WARN, "PKCS#11: mbed TLS cannot parse serial");
         goto cleanup;
     }
 
     ret = 0;
-
 cleanup:
     mbedtls_x509_crt_free(&mbed_crt);
 
diff --git a/src/openvpn/platform.c b/src/openvpn/platform.c
index fbffd0f..53d07f9 100644
--- a/src/openvpn/platform.c
+++ b/src/openvpn/platform.c
@@ -30,7 +30,9 @@
 #include "syshead.h"
 
 #include "buffer.h"
+#include "crypto.h"
 #include "error.h"
+#include "misc.h"
 #include "win32.h"
 
 #include "memdbg.h"
@@ -335,3 +337,150 @@
 #endif
 }
 
+/* create a temporary filename in directory */
+const char *
+platform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
+{
+    int fd;
+    const char *retfname = NULL;
+    unsigned int attempts = 0;
+    char fname[256] = { 0 };
+    const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp";
+    const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8));
+
+    while (attempts < 6)
+    {
+        ++attempts;
+
+        if (!openvpn_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len,
+                              prefix, (unsigned long) get_random(),
+                              (unsigned long) get_random()))
+        {
+            msg(M_WARN, "ERROR: temporary filename too long");
+            return NULL;
+        }
+
+        retfname = platform_gen_path(directory, fname, gc);
+        if (!retfname)
+        {
+            msg(M_WARN, "Failed to create temporary filename and path");
+            return NULL;
+        }
+
+        /* Atomically create the file.  Errors out if the file already
+         * exists.  */
+        fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
+        if (fd != -1)
+        {
+            close(fd);
+            return retfname;
+        }
+        else if (fd == -1 && errno != EEXIST)
+        {
+            /* Something else went wrong, no need to retry.  */
+            msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'",
+                retfname);
+            return NULL;
+        }
+    }
+
+    msg(M_WARN, "Failed to create temporary file after %i attempts", attempts);
+    return NULL;
+}
+
+/*
+ * Put a directory and filename together.
+ */
+const char *
+platform_gen_path(const char *directory, const char *filename,
+                  struct gc_arena *gc)
+{
+#ifdef _WIN32
+    const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON
+                                 |CC_DOUBLE_QUOTE|CC_SLASH|CC_BACKSLASH|CC_PIPE|CC_QUESTION_MARK|CC_ASTERISK;
+#else
+    const int CC_PATH_RESERVED = CC_SLASH;
+#endif
+
+    if (!gc)
+    {
+        return NULL; /* Would leak memory otherwise */
+    }
+
+    const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);
+
+    if (safe_filename
+        && strcmp(safe_filename, ".")
+        && strcmp(safe_filename, "..")
+#ifdef _WIN32
+        && win_safe_filename(safe_filename)
+#endif
+        )
+    {
+        const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16;
+        struct buffer out = alloc_buf_gc(outsize, gc);
+        char dirsep[2];
+
+        dirsep[0] = OS_SPECIFIC_DIRSEP;
+        dirsep[1] = '\0';
+
+        if (directory)
+        {
+            buf_printf(&out, "%s%s", directory, dirsep);
+        }
+        buf_printf(&out, "%s", safe_filename);
+
+        return BSTR(&out);
+    }
+    else
+    {
+        return NULL;
+    }
+}
+
+bool
+platform_absolute_pathname(const char *pathname)
+{
+    if (pathname)
+    {
+        const int c = pathname[0];
+#ifdef _WIN32
+        return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\');
+#else
+        return c == '/';
+#endif
+    }
+    else
+    {
+        return false;
+    }
+}
+
+/* return true if filename can be opened for read */
+bool
+platform_test_file(const char *filename)
+{
+    bool ret = false;
+    if (filename)
+    {
+        FILE *fp = platform_fopen(filename, "r");
+        if (fp)
+        {
+            fclose(fp);
+            ret = true;
+        }
+        else
+        {
+            if (openvpn_errno() == EACCES)
+            {
+                msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename);
+            }
+        }
+    }
+
+    dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]",
+         filename ? filename : "UNDEF",
+         ret);
+
+    return ret;
+}
diff --git a/src/openvpn/platform.h b/src/openvpn/platform.h
index 288937d..091fc9c 100644
--- a/src/openvpn/platform.h
+++ b/src/openvpn/platform.h
@@ -49,6 +49,7 @@
 #endif
 
 #include "basic.h"
+#include "buffer.h"
 
 /* Get/Set UID of process */
 
@@ -143,4 +144,21 @@
 #endif
 int platform_stat(const char *path, platform_stat_t *buf);
 
+/**
+ * Create a temporary file in directory, returns the filename of the created
+ * file.
+ */
+const char *platform_create_temp_file(const char *directory, const char *prefix,
+                                      struct gc_arena *gc);
+
+/** Put a directory and filename together. */
+const char *platform_gen_path(const char *directory, const char *filename,
+                              struct gc_arena *gc);
+
+/** Return true if pathname is absolute. */
+bool platform_absolute_pathname(const char *pathname);
+
+/** Return true if filename can be opened for read. */
+bool platform_test_file(const char *filename);
+
 #endif /* ifndef PLATFORM_H */
diff --git a/src/openvpn/plugin.c b/src/openvpn/plugin.c
index 0ab99ab..8b351c4 100644
--- a/src/openvpn/plugin.c
+++ b/src/openvpn/plugin.c
@@ -104,6 +104,12 @@
         case OPENVPN_PLUGIN_CLIENT_CONNECT_V2:
             return "PLUGIN_CLIENT_CONNECT";
 
+        case OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER:
+            return "PLUGIN_CLIENT_CONNECT_DEFER";
+
+        case OPENVPN_PLUGIN_CLIENT_CONNECT_DEFER_V2:
+            return "PLUGIN_CLIENT_CONNECT_DEFER_V2";
+
         case OPENVPN_PLUGIN_CLIENT_DISCONNECT:
             return "PLUGIN_CLIENT_DISCONNECT";
 
@@ -161,12 +167,13 @@
 }
 
 bool
-plugin_option_list_add(struct plugin_option_list *list, char **p, struct gc_arena *gc)
+plugin_option_list_add(struct plugin_option_list *list, char **p,
+                       struct gc_arena *gc)
 {
     if (list->n < MAX_PLUGINS)
     {
         struct plugin_option *o = &list->plugins[list->n++];
-        o->argv = make_extended_arg_array(p, gc);
+        o->argv = make_extended_arg_array(p, false, gc);
         if (o->argv[0])
         {
             o->so_pathname = o->argv[0];
@@ -250,7 +257,7 @@
      * was parsed.
      *
      */
-    if (!absolute_pathname(p->so_pathname)
+    if (!platform_absolute_pathname(p->so_pathname)
         && p->so_pathname[0] != '.')
     {
         char full[PATH_MAX];
@@ -260,7 +267,7 @@
     }
     else
     {
-        rel = !absolute_pathname(p->so_pathname);
+        rel = !platform_absolute_pathname(p->so_pathname);
         p->handle = dlopen(p->so_pathname, RTLD_NOW);
     }
     if (!p->handle)
@@ -272,7 +279,7 @@
 
 #else  /* ifndef _WIN32 */
 
-    rel = !absolute_pathname(p->so_pathname);
+    rel = !platform_absolute_pathname(p->so_pathname);
     p->module = LoadLibraryW(wide_string(p->so_pathname, &gc));
     if (!p->module)
     {
@@ -520,11 +527,9 @@
                  const int type,
                  const struct argv *av,
                  struct openvpn_plugin_string_list **retlist,
-                 const char **envp
-#ifdef ENABLE_CRYPTO
-                 , int certdepth,
+                 const char **envp,
+                 int certdepth,
                  openvpn_x509_cert_t *current_cert
-#endif
                  )
 {
     int status = OPENVPN_PLUGIN_FUNC_SUCCESS;
@@ -553,14 +558,8 @@
                                                         (const char **const) envp,
                                                         p->plugin_handle,
                                                         per_client_context,
-#ifdef ENABLE_CRYPTO
                                                         (current_cert ? certdepth : -1),
-                                                        current_cert
-#else
-                                                        -1,
-                                                        NULL
-#endif
-            };
+                                                        current_cert };
 
             struct openvpn_plugin_args_func_return retargs;
 
@@ -594,7 +593,7 @@
                 p->so_pathname);
         }
 
-        argv_reset(&a);
+        argv_free(&a);
         gc_free(&gc);
     }
     return status;
@@ -789,11 +788,9 @@
                 const int type,
                 const struct argv *av,
                 struct plugin_return *pr,
-                struct env_set *es
-#ifdef ENABLE_CRYPTO
-                , int certdepth,
+                struct env_set *es,
+                int certdepth,
                 openvpn_x509_cert_t *current_cert
-#endif
                 )
 {
     if (pr)
@@ -821,11 +818,9 @@
                                                 type,
                                                 av,
                                                 pr ? &pr->list[i] : NULL,
-                                                envp
-#ifdef ENABLE_CRYPTO
-                                                ,certdepth,
+                                                envp,
+                                                certdepth,
                                                 current_cert
-#endif
                                                 );
             switch (status)
             {
diff --git a/src/openvpn/plugin.h b/src/openvpn/plugin.h
index ec2d1fe..bf4d71b 100644
--- a/src/openvpn/plugin.h
+++ b/src/openvpn/plugin.h
@@ -106,7 +106,8 @@
 
 struct plugin_option_list *plugin_option_list_new(struct gc_arena *gc);
 
-bool plugin_option_list_add(struct plugin_option_list *list, char **p, struct gc_arena *gc);
+bool plugin_option_list_add(struct plugin_option_list *list, char **p,
+                            struct gc_arena *gc);
 
 #ifndef ENABLE_SMALL
 void plugin_option_list_print(const struct plugin_option_list *list, int msglevel);
@@ -127,11 +128,9 @@
                     const int type,
                     const struct argv *av,
                     struct plugin_return *pr,
-                    struct env_set *es
-#ifdef ENABLE_CRYPTO
-                    , int current_cert_depth,
+                    struct env_set *es,
+                    int current_cert_depth,
                     openvpn_x509_cert_t *current_cert
-#endif
                     );
 
 void plugin_list_close(struct plugin_list *pl);
@@ -189,11 +188,9 @@
                 const int type,
                 const struct argv *av,
                 struct plugin_return *pr,
-                struct env_set *es
-#ifdef ENABLE_CRYPTO
-                , int current_cert_depth,
+                struct env_set *es,
+                int current_cert_depth,
                 openvpn_x509_cert_t *current_cert
-#endif
                 )
 {
     return 0;
@@ -208,11 +205,9 @@
             struct plugin_return *pr,
             struct env_set *es)
 {
-    return plugin_call_ssl(pl, type, av, pr, es
-#ifdef ENABLE_CRYPTO
-                           , -1, NULL
-#endif
-                           );
+    return plugin_call_ssl(pl, type, av, pr, es, -1, NULL);
 }
 
+void plugin_abort(void);
+
 #endif /* OPENVPN_PLUGIN_H */
diff --git a/src/openvpn/pool.c b/src/openvpn/pool.c
index da28bc0..ece0784 100644
--- a/src/openvpn/pool.c
+++ b/src/openvpn/pool.c
@@ -147,61 +147,144 @@
 }
 
 struct ifconfig_pool *
-ifconfig_pool_init(int type, in_addr_t start, in_addr_t end,
-                   const bool duplicate_cn,
+ifconfig_pool_init(const bool ipv4_pool, enum pool_type type, in_addr_t start,
+                   in_addr_t end, const bool duplicate_cn,
                    const bool ipv6_pool, const struct in6_addr ipv6_base,
                    const int ipv6_netbits )
 {
     struct gc_arena gc = gc_new();
     struct ifconfig_pool *pool = NULL;
+    int pool_ipv4_size = -1, pool_ipv6_size = -1;
 
     ASSERT(start <= end && end - start < IFCONFIG_POOL_MAX);
     ALLOC_OBJ_CLEAR(pool, struct ifconfig_pool);
 
-    pool->type = type;
     pool->duplicate_cn = duplicate_cn;
 
-    switch (type)
+    pool->ipv4.enabled = ipv4_pool;
+
+    if (pool->ipv4.enabled)
     {
-        case IFCONFIG_POOL_30NET:
-            pool->base = start & ~3;
-            pool->size = (((end | 3) + 1) - pool->base) >> 2;
-            break;
+        pool->ipv4.type = type;
+        switch (pool->ipv4.type)
+        {
+            case IFCONFIG_POOL_30NET:
+                pool->ipv4.base = start & ~3;
+                pool_ipv4_size = (((end | 3) + 1) - pool->ipv4.base) >> 2;
+                break;
 
-        case IFCONFIG_POOL_INDIV:
-            pool->base = start;
-            pool->size = end - start + 1;
-            break;
+            case IFCONFIG_POOL_INDIV:
+                pool->ipv4.base = start;
+                pool_ipv4_size = end - start + 1;
+                break;
 
-        default:
-            ASSERT(0);
+            default:
+                ASSERT(0);
+        }
+
+        if (pool_ipv4_size < 2)
+        {
+            msg(M_FATAL, "IPv4 pool size is too small (%d), must be at least 2",
+                pool_ipv4_size);
+        }
+
+        msg(D_IFCONFIG_POOL, "IFCONFIG POOL IPv4: base=%s size=%d",
+            print_in_addr_t(pool->ipv4.base, 0, &gc), pool_ipv4_size);
+
+        pool->size = pool_ipv4_size;
     }
 
     /* IPv6 pools are always "INDIV" type */
-    pool->ipv6 = ipv6_pool;
+    pool->ipv6.enabled = ipv6_pool;
 
-    if (pool->ipv6)
+    if (pool->ipv6.enabled)
     {
-        pool->base_ipv6 = ipv6_base;
-        pool->size_ipv6 = ipv6_netbits>96 ? ( 1<<(128-ipv6_netbits) )
+        /* the host portion of the address will always be contained in the last
+         * 4 bytes, therefore we can just extract that and use it as base in
+         * integer form
+         */
+        uint32_t base = (ipv6_base.s6_addr[12] << 24)
+                        | (ipv6_base.s6_addr[13] << 16)
+                        | (ipv6_base.s6_addr[14] << 8)
+                        | ipv6_base.s6_addr[15];
+        /* some bits of the last 4 bytes may still be part of the network
+         * portion of the address, therefore we need to set them to 0
+         */
+        if ((128 - ipv6_netbits) < 32)
+        {
+            /* extract only the bits that are really part of the host portion of
+             * the address.
+             *
+             * Example: if we have netbits=31, the first bit has to be zero'd,
+             * the following operation first computes mask=0x3fffff and then
+             * uses mask to extract the wanted bits from base
+             */
+            uint32_t mask = (1 << (128 - ipv6_netbits) ) - 1;
+            base &= mask;
+        }
+
+        pool->ipv6.base = ipv6_base;
+
+        /* if a pool starts at a base address that has all-zero in the
+         * host part, that first IPv6 address must not be assigned to
+         * clients because it is not usable (subnet anycast address).
+         * Start with 1, then.
+         *
+         * NOTE: this will also (mis-)fire for something like
+         *    ifconfig-ipv6-pool 2001:db8:0:1:1234::0/64
+         * as we only check the rightmost 32 bits of the host part.  So be it.
+         */
+        if (base == 0)
+        {
+            msg(D_IFCONFIG_POOL, "IFCONFIG POOL IPv6: incrementing pool start "
+                "to avoid ::0 assignment");
+            base++;
+            pool->ipv6.base.s6_addr[15]++;
+        }
+
+        pool_ipv6_size = ipv6_netbits >= 112
+                          ? (1 << (128 - ipv6_netbits)) - base
                           : IFCONFIG_POOL_MAX;
 
-        msg( D_IFCONFIG_POOL, "IFCONFIG POOL IPv6: (IPv4) size=%d, size_ipv6=%d, netbits=%d, base_ipv6=%s",
-             pool->size, pool->size_ipv6, ipv6_netbits,
-             print_in6_addr( pool->base_ipv6, 0, &gc ));
+        if (pool_ipv6_size < 2)
+        {
+            msg(M_FATAL, "IPv6 pool size is too small (%d), must be at least 2",
+                pool_ipv6_size);
+        }
 
-        /* the current code is very simple and assumes that the IPv6
-         * pool is at least as big as the IPv4 pool, and we don't need
-         * to do separate math etc. for IPv6
+        msg(D_IFCONFIG_POOL, "IFCONFIG POOL IPv6: base=%s size=%d netbits=%d",
+            print_in6_addr(pool->ipv6.base, 0, &gc), pool_ipv6_size,
+            ipv6_netbits);
+
+        /* if there is no v4 pool, or the v6 pool is smaller, use
+         * v6 pool size as "unified pool size"
          */
-        ASSERT( pool->size < pool->size_ipv6 );
+        if (pool->size <= 0 || pool_ipv6_size < pool->size)
+        {
+            pool->size = pool_ipv6_size;
+        }
     }
 
-    ALLOC_ARRAY_CLEAR(pool->list, struct ifconfig_pool_entry, pool->size);
+    if (pool->ipv4.enabled && pool->ipv6.enabled)
+    {
+        if (pool_ipv4_size < pool_ipv6_size)
+        {
+            msg(M_INFO, "NOTE: IPv4 pool size is %d, IPv6 pool size is %d. "
+                "IPv4 pool size limits the number of clients that can be "
+                "served from the pool", pool_ipv4_size, pool_ipv6_size);
+        }
+        else if (pool_ipv4_size > pool_ipv6_size)
+        {
+            msg(M_WARN, "WARNING: IPv4 pool size is %d, IPv6 pool size is %d. "
+                "IPv6 pool size limits the number of clients that can be "
+                "served from the pool. This is likely a MISTAKE - please check "
+                "your configuration", pool_ipv4_size, pool_ipv6_size);
+        }
+    }
 
-    msg(D_IFCONFIG_POOL, "IFCONFIG POOL: base=%s size=%d, ipv6=%d",
-        print_in_addr_t(pool->base, 0, &gc),
-        pool->size, pool->ipv6 );
+    ASSERT(pool->size > 0);
+
+    ALLOC_ARRAY_CLEAR(pool->list, struct ifconfig_pool_entry, pool->size);
 
     gc_free(&gc);
     return pool;
@@ -213,6 +296,7 @@
     if (pool)
     {
         int i;
+
         for (i = 0; i < pool->size; ++i)
         {
             ifconfig_pool_entry_free(&pool->list[i], true);
@@ -239,32 +323,35 @@
             ipe->common_name = string_alloc(common_name, NULL);
         }
 
-        switch (pool->type)
+        if (pool->ipv4.enabled && local && remote)
         {
-            case IFCONFIG_POOL_30NET:
+            switch (pool->ipv4.type)
             {
-                in_addr_t b = pool->base + (i << 2);
-                *local = b + 1;
-                *remote = b + 2;
-                break;
-            }
+                case IFCONFIG_POOL_30NET:
+                {
+                    in_addr_t b = pool->ipv4.base + (i << 2);
+                    *local = b + 1;
+                    *remote = b + 2;
+                    break;
+                }
 
-            case IFCONFIG_POOL_INDIV:
-            {
-                in_addr_t b = pool->base + i;
-                *local = 0;
-                *remote = b;
-                break;
-            }
+                case IFCONFIG_POOL_INDIV:
+                {
+                    in_addr_t b = pool->ipv4.base + i;
+                    *local = 0;
+                    *remote = b;
+                    break;
+                }
 
-            default:
-                ASSERT(0);
+                default:
+                    ASSERT(0);
+            }
         }
 
         /* IPv6 pools are always INDIV (--linear) */
-        if (pool->ipv6 && remote_ipv6)
+        if (pool->ipv6.enabled && remote_ipv6)
         {
-            *remote_ipv6 = add_in6_addr( pool->base_ipv6, i );
+            *remote_ipv6 = add_in6_addr(pool->ipv6.base, i);
         }
     }
     return i;
@@ -274,6 +361,7 @@
 ifconfig_pool_release(struct ifconfig_pool *pool, ifconfig_pool_handle hand, const bool hard)
 {
     bool ret = false;
+
     if (pool && hand >= 0 && hand < pool->size)
     {
         ifconfig_pool_entry_free(&pool->list[hand], hard);
@@ -286,22 +374,23 @@
  * private access functions
  */
 
+/* currently handling IPv4 logic only */
 static ifconfig_pool_handle
 ifconfig_pool_ip_base_to_handle(const struct ifconfig_pool *pool, const in_addr_t addr)
 {
     ifconfig_pool_handle ret = -1;
 
-    switch (pool->type)
+    switch (pool->ipv4.type)
     {
         case IFCONFIG_POOL_30NET:
         {
-            ret = (addr - pool->base) >> 2;
+            ret = (addr - pool->ipv4.base) >> 2;
             break;
         }
 
         case IFCONFIG_POOL_INDIV:
         {
-            ret = (addr - pool->base);
+            ret = (addr - pool->ipv4.base);
             break;
         }
 
@@ -317,24 +406,64 @@
     return ret;
 }
 
+static ifconfig_pool_handle
+ifconfig_pool_ipv6_base_to_handle(const struct ifconfig_pool *pool,
+                                  const struct in6_addr *in_addr)
+{
+    ifconfig_pool_handle ret;
+    uint32_t base, addr;
+
+    /* IPv6 pool is always IFCONFIG_POOL_INDIV.
+     *
+     * We assume the offset can't be larger than 2^32-1, therefore we compute
+     * the difference only among the last 4 bytes like if they were two 32bit
+     * long integers. The rest of the address must match.
+     */
+    for (int i = 0; i < (12); i++)
+    {
+        if (pool->ipv6.base.s6_addr[i] != in_addr->s6_addr[i])
+        {
+            return -1;
+        }
+    }
+
+    base = (pool->ipv6.base.s6_addr[12] << 24)
+           | (pool->ipv6.base.s6_addr[13] << 16)
+           | (pool->ipv6.base.s6_addr[14] << 8)
+           | pool->ipv6.base.s6_addr[15];
+
+    addr = (in_addr->s6_addr[12] << 24)
+           | (in_addr->s6_addr[13] << 16)
+           | (in_addr->s6_addr[14] << 8)
+           | in_addr->s6_addr[15];
+
+    ret = addr - base;
+    if (ret < 0 || ret >= pool->size)
+    {
+        ret = -1;
+    }
+
+    return ret;
+}
+
 static in_addr_t
 ifconfig_pool_handle_to_ip_base(const struct ifconfig_pool *pool, ifconfig_pool_handle hand)
 {
     in_addr_t ret = 0;
 
-    if (hand >= 0 && hand < pool->size)
+    if (pool->ipv4.enabled && hand >= 0 && hand < pool->size)
     {
-        switch (pool->type)
+        switch (pool->ipv4.type)
         {
             case IFCONFIG_POOL_30NET:
             {
-                ret = pool->base + (hand << 2);
+                ret = pool->ipv4.base + (hand << 2);
                 break;
             }
 
             case IFCONFIG_POOL_INDIV:
             {
-                ret = pool->base + hand;
+                ret = pool->ipv4.base + hand;
                 break;
             }
 
@@ -349,29 +478,26 @@
 static struct in6_addr
 ifconfig_pool_handle_to_ipv6_base(const struct ifconfig_pool *pool, ifconfig_pool_handle hand)
 {
-    struct in6_addr ret = in6addr_any;
+    struct in6_addr ret = IN6ADDR_ANY_INIT;
 
     /* IPv6 pools are always INDIV (--linear) */
-    if (hand >= 0 && hand < pool->size_ipv6)
+    if (pool->ipv6.enabled && hand >= 0 && hand < pool->size)
     {
-        ret = add_in6_addr( pool->base_ipv6, hand );
+        ret = add_in6_addr( pool->ipv6.base, hand );
     }
     return ret;
 }
 
 static void
-ifconfig_pool_set(struct ifconfig_pool *pool, const char *cn, const in_addr_t addr, const bool fixed)
+ifconfig_pool_set(struct ifconfig_pool *pool, const char *cn,
+                  ifconfig_pool_handle h, const bool fixed)
 {
-    ifconfig_pool_handle h = ifconfig_pool_ip_base_to_handle(pool, addr);
-    if (h >= 0)
-    {
-        struct ifconfig_pool_entry *e = &pool->list[h];
-        ifconfig_pool_entry_free(e, true);
-        e->in_use = false;
-        e->common_name = string_alloc(cn, NULL);
-        e->last_release = now;
-        e->fixed = fixed;
-    }
+    struct ifconfig_pool_entry *e = &pool->list[h];
+    ifconfig_pool_entry_free(e, true);
+    e->in_use = false;
+    e->common_name = string_alloc(cn, NULL);
+    e->last_release = now;
+    e->fixed = fixed;
 }
 
 static void
@@ -385,23 +511,26 @@
         for (i = 0; i < pool->size; ++i)
         {
             const struct ifconfig_pool_entry *e = &pool->list[i];
+            struct in6_addr ip6;
+            in_addr_t ip;
+            const char *ip6_str = "";
+            const char *ip_str = "";
+
             if (e->common_name)
             {
-                const in_addr_t ip = ifconfig_pool_handle_to_ip_base(pool, i);
-                if (pool->ipv6)
+                if (pool->ipv4.enabled)
                 {
-                    struct in6_addr ip6 = ifconfig_pool_handle_to_ipv6_base(pool, i);
-                    status_printf(out, "%s,%s,%s",
-                                  e->common_name,
-                                  print_in_addr_t(ip, 0, &gc),
-                                  print_in6_addr(ip6, 0, &gc));
+                    ip = ifconfig_pool_handle_to_ip_base(pool, i);
+                    ip_str = print_in_addr_t(ip, 0, &gc);
                 }
-                else
+
+                if (pool->ipv6.enabled)
                 {
-                    status_printf(out, "%s,%s",
-                                  e->common_name,
-                                  print_in_addr_t(ip, 0, &gc));
+                    ip6 = ifconfig_pool_handle_to_ipv6_base(pool, i);
+                    ip6_str = print_in6_addr(ip6, 0, &gc);
                 }
+
+                status_printf(out, "%s,%s,%s", e->common_name, ip_str, ip6_str);
             }
         }
         gc_free(&gc);
@@ -475,16 +604,17 @@
     const int buf_size = 128;
 
     update_time();
+
     if (persist && persist->file && pool)
     {
         struct gc_arena gc = gc_new();
         struct buffer in = alloc_buf_gc(256, &gc);
-        char *cn_buf;
-        char *ip_buf;
+        char *cn_buf, *ip_buf, *ip6_buf;
         int line = 0;
 
         ALLOC_ARRAY_CLEAR_GC(cn_buf, char, buf_size, &gc);
         ALLOC_ARRAY_CLEAR_GC(ip_buf, char, buf_size, &gc);
+        ALLOC_ARRAY_CLEAR_GC(ip6_buf, char, buf_size, &gc);
 
         while (true)
         {
@@ -494,28 +624,109 @@
                 break;
             }
             ++line;
-            if (BLEN(&in))
+            if (!BLEN(&in))
             {
-                int c = *BSTR(&in);
-                if (c == '#' || c == ';')
-                {
-                    continue;
-                }
-                msg( M_INFO, "ifconfig_pool_read(), in='%s', TODO: IPv6",
-                     BSTR(&in) );
+                continue;
+            }
 
-                if (buf_parse(&in, ',', cn_buf, buf_size)
-                    && buf_parse(&in, ',', ip_buf, buf_size))
+            int c = *BSTR(&in);
+            if (c == '#' || c == ';')
+            {
+                continue;
+            }
+
+            msg(M_INFO, "ifconfig_pool_read(), in='%s'", BSTR(&in));
+
+            /* The expected format of a line is: "CN,IP4,IP6".
+             *
+             * IP4 or IP6 may be empty when respectively no v4 or v6 pool
+             * was previously specified.
+             *
+             * This means that accepted strings can be:
+             * - CN,IP4,IP6
+             * - CN,IP4
+             * - CN,,IP6
+             */
+            if (!buf_parse(&in, ',', cn_buf, buf_size)
+                || !buf_parse(&in, ',', ip_buf, buf_size))
+            {
+                continue;
+            }
+
+            ifconfig_pool_handle h = -1, h6 = -1;
+
+            if (strlen(ip_buf) > 0)
+            {
+                bool v4_ok = true;
+                in_addr_t addr = getaddr(GETADDR_HOST_ORDER, ip_buf, 0, &v4_ok,
+                                         NULL);
+
+                if (!v4_ok)
                 {
-                    bool succeeded;
-                    const in_addr_t addr = getaddr(GETADDR_HOST_ORDER, ip_buf, 0, &succeeded, NULL);
-                    if (succeeded)
+                    msg(M_WARN, "pool: invalid IPv4 (%s) for CN=%s", ip_buf,
+                        cn_buf);
+                }
+                else
+                {
+                    h = ifconfig_pool_ip_base_to_handle(pool, addr);
+                    if (h < 0)
                     {
-                        msg( M_INFO, "succeeded -> ifconfig_pool_set()");
-                        ifconfig_pool_set(pool, cn_buf, addr, persist->fixed);
+                        msg(M_WARN,
+                            "pool: IPv4 (%s) out of pool range for CN=%s",
+                            ip_buf, cn_buf);
                     }
                 }
             }
+
+            if (buf_parse(&in, ',', ip6_buf, buf_size) && strlen(ip6_buf) > 0)
+            {
+                struct in6_addr addr6;
+
+                if (!get_ipv6_addr(ip6_buf, &addr6, NULL, M_WARN))
+                {
+                    msg(M_WARN, "pool: invalid IPv6 (%s) for CN=%s", ip6_buf,
+                        cn_buf);
+                }
+                else
+                {
+                    h6 = ifconfig_pool_ipv6_base_to_handle(pool, &addr6);
+                    if (h6 < 0)
+                    {
+                        msg(M_WARN,
+                            "pool: IPv6 (%s) out of pool range for CN=%s",
+                            ip6_buf, cn_buf);
+                    }
+
+                    /* Rely on IPv6 if no IPv4 was provided or the one provided
+                     * was not valid
+                     */
+                    if (h < 0)
+                    {
+                        h = h6;
+                    }
+                }
+            }
+
+            /* at the moment IPv4 and IPv6 share the same pool, therefore offsets
+             * have to match for the same client.
+             *
+             * If offsets differ we use the IPv4, therefore warn the user about this.
+             */
+            if ((h6 >= 0) && (h != h6))
+            {
+                msg(M_WARN,
+                    "pool: IPv4 (%s) and IPv6 (%s) have different offsets! Relying on IPv4",
+                    ip_buf, ip6_buf);
+            }
+
+            /* if at least one among v4 and v6 was properly parsed, attempt
+             * setting an handle for this client
+             */
+            if (h >= 0)
+            {
+                msg(M_INFO, "succeeded -> ifconfig_pool_set(hand=%d)",h);
+                ifconfig_pool_set(pool, cn_buf, h, persist->fixed);
+            }
         }
 
         ifconfig_pool_msg(pool, D_IFCONFIG_POOL);
diff --git a/src/openvpn/pool.h b/src/openvpn/pool.h
index 6de28ac..b06424c 100644
--- a/src/openvpn/pool.h
+++ b/src/openvpn/pool.h
@@ -34,8 +34,11 @@
 #define IFCONFIG_POOL_MAX         65536
 #define IFCONFIG_POOL_MIN_NETBITS    16
 
-#define IFCONFIG_POOL_30NET   0
-#define IFCONFIG_POOL_INDIV   1
+enum pool_type
+{
+    IFCONFIG_POOL_30NET,
+    IFCONFIG_POOL_INDIV
+};
 
 struct ifconfig_pool_entry
 {
@@ -47,13 +50,17 @@
 
 struct ifconfig_pool
 {
-    in_addr_t base;
-    int size;
-    int type;
     bool duplicate_cn;
-    bool ipv6;
-    struct in6_addr base_ipv6;
-    unsigned int size_ipv6;
+    struct {
+        bool enabled;
+        enum pool_type type;
+        in_addr_t base;
+    } ipv4;
+    struct {
+        bool enabled;
+        struct in6_addr base;
+    } ipv6;
+    int size;
     struct ifconfig_pool_entry *list;
 };
 
@@ -65,7 +72,12 @@
 
 typedef int ifconfig_pool_handle;
 
-struct ifconfig_pool *ifconfig_pool_init(int type, in_addr_t start, in_addr_t end, const bool duplicate_cn, const bool ipv6_pool, const struct in6_addr ipv6_base, const int ipv6_netbits );
+struct ifconfig_pool *ifconfig_pool_init(const bool ipv4_pool,
+                                         enum pool_type type, in_addr_t start,
+                                         in_addr_t end, const bool duplicate_cn,
+                                         const bool ipv6_pool,
+                                         const struct in6_addr ipv6_base,
+                                         const int ipv6_netbits);
 
 void ifconfig_pool_free(struct ifconfig_pool *pool);
 
diff --git a/src/openvpn/proto.c b/src/openvpn/proto.c
index 87c18e8..6f4d929 100644
--- a/src/openvpn/proto.c
+++ b/src/openvpn/proto.c
@@ -38,17 +38,17 @@
  * If raw tunnel packet is IPv<X>, return true and increment
  * buffer offset to start of IP header.
  */
-static
-bool
-is_ipv_X( int tunnel_type, struct buffer *buf, int ip_ver )
+static bool
+is_ipv_X(int tunnel_type, struct buffer *buf, int ip_ver)
 {
     int offset;
+    uint16_t proto;
     const struct openvpn_iphdr *ih;
 
     verify_align_4(buf);
     if (tunnel_type == DEV_TYPE_TUN)
     {
-        if (BLEN(buf) < (int) sizeof(struct openvpn_iphdr))
+        if (BLEN(buf) < sizeof(struct openvpn_iphdr))
         {
             return false;
         }
@@ -57,24 +57,46 @@
     else if (tunnel_type == DEV_TYPE_TAP)
     {
         const struct openvpn_ethhdr *eh;
-        if (BLEN(buf) < (int)(sizeof(struct openvpn_ethhdr)
-                              + sizeof(struct openvpn_iphdr)))
+        if (BLEN(buf) < (sizeof(struct openvpn_ethhdr)
+                         + sizeof(struct openvpn_iphdr)))
         {
             return false;
         }
-        eh = (const struct openvpn_ethhdr *) BPTR(buf);
-        if (ntohs(eh->proto) != (ip_ver == 6 ? OPENVPN_ETH_P_IPV6 : OPENVPN_ETH_P_IPV4))
-        {
-            return false;
-        }
+        eh = (const struct openvpn_ethhdr *)BPTR(buf);
+
+        /* start by assuming this is a standard Eth fram */
+        proto = eh->proto;
         offset = sizeof(struct openvpn_ethhdr);
+
+        /* if this is a 802.1q frame, parse the header using the according
+         * format
+         */
+        if (proto == htons(OPENVPN_ETH_P_8021Q))
+        {
+            const struct openvpn_8021qhdr *evh;
+            if (BLEN(buf) < (sizeof(struct openvpn_ethhdr)
+                             + sizeof(struct openvpn_iphdr)))
+            {
+                return false;
+            }
+
+            evh = (const struct openvpn_8021qhdr *)BPTR(buf);
+
+            proto = evh->proto;
+            offset = sizeof(struct openvpn_8021qhdr);
+        }
+
+        if (ntohs(proto) != (ip_ver == 6 ? OPENVPN_ETH_P_IPV6 : OPENVPN_ETH_P_IPV4))
+        {
+            return false;
+        }
     }
     else
     {
         return false;
     }
 
-    ih = (const struct openvpn_iphdr *) (BPTR(buf) + offset);
+    ih = (const struct openvpn_iphdr *)(BPTR(buf) + offset);
 
     /* IP version is stored in the same bits for IPv4 or IPv6 header */
     if (OPENVPN_IPH_GET_VER(ih->version_len) == ip_ver)
@@ -98,6 +120,58 @@
     return is_ipv_X( tunnel_type, buf, 6 );
 }
 
+
+uint16_t
+ip_checksum(const sa_family_t af, const uint8_t *payload, const int len_payload,
+            const uint8_t *src_addr, const uint8_t *dest_addr, const int proto)
+{
+    uint32_t sum = 0;
+    int addr_len = (af == AF_INET) ? 4 : 16;
+
+    /*
+     * make 16 bit words out of every two adjacent 8 bit words and  */
+    /* calculate the sum of all 16 bit words
+     */
+    for (int i = 0; i < len_payload; i += 2)
+    {
+        sum +=  (uint16_t)(((payload[i] << 8) & 0xFF00)
+                           +((i + 1 < len_payload) ? (payload[i + 1] & 0xFF) : 0));
+
+    }
+
+    /*
+     * add the pseudo header which contains the IP source and destination
+     * addresses
+     */
+    for (int i = 0; i < addr_len; i += 2)
+    {
+        sum += (uint16_t)((src_addr[i] << 8) & 0xFF00) + (src_addr[i + 1] & 0xFF);
+
+    }
+    for (int i = 0; i < addr_len; i += 2)
+    {
+        sum += (uint16_t)((dest_addr[i] << 8) & 0xFF00) + (dest_addr[i + 1] & 0xFF);
+    }
+
+    /* the length of the payload */
+    sum += (uint16_t)len_payload;
+
+    /* The next header or proto field*/
+    sum += (uint16_t)proto;
+
+    /*
+     * keep only the last 16 bits of the 32 bit calculated sum and add
+     * the carries
+     */
+    while (sum >> 16)
+    {
+        sum = (sum & 0xFFFF) + (sum >> 16);
+    }
+
+    /* Take the one's complement of sum */
+    return ((uint16_t) ~sum);
+}
+
 #ifdef PACKET_TRUNCATION_CHECK
 
 void
diff --git a/src/openvpn/proto.h b/src/openvpn/proto.h
index 985aa99..c251767 100644
--- a/src/openvpn/proto.h
+++ b/src/openvpn/proto.h
@@ -60,9 +60,31 @@
 #define OPENVPN_ETH_P_IPV4   0x0800   /* IPv4 protocol */
 #define OPENVPN_ETH_P_IPV6   0x86DD   /* IPv6 protocol */
 #define OPENVPN_ETH_P_ARP    0x0806   /* ARP protocol */
+#define OPENVPN_ETH_P_8021Q  0x8100   /* 802.1Q protocol */
     uint16_t proto;                   /* packet type ID field */
 };
 
+struct openvpn_8021qhdr
+{
+    uint8_t dest[OPENVPN_ETH_ALEN];     /* destination ethernet addr */
+    uint8_t source[OPENVPN_ETH_ALEN];   /* source ethernet addr */
+
+    uint16_t tpid;                      /* 802.1Q Tag Protocol Identifier */
+#define OPENVPN_8021Q_MASK_PCP htons(0xE000) /* mask PCP out of pcp_cfi_vid */
+#define OPENVPN_8021Q_MASK_CFI htons(0x1000) /* mask CFI out of pcp_cfi_vid */
+#define OPENVPN_8021Q_MASK_VID htons(0x0FFF) /* mask VID out of pcp_cfi_vid */
+    uint16_t pcp_cfi_vid;               /* bit fields, see IEEE 802.1Q */
+    uint16_t proto;                     /* contained packet type ID field */
+};
+
+/*
+ * Size difference between a regular Ethernet II header and an Ethernet II
+ * header with additional IEEE 802.1Q tagging.
+ */
+#define SIZE_ETH_TO_8021Q_HDR (sizeof(struct openvpn_8021qhdr) \
+                               - sizeof(struct openvpn_ethhdr))
+
+
 struct openvpn_arp {
 #define ARP_MAC_ADDR_TYPE 0x0001
     uint16_t mac_addr_type;     /* 0x0001 */
@@ -95,9 +117,10 @@
 
     uint8_t ttl;
 
-#define OPENVPN_IPPROTO_IGMP 2  /* IGMP protocol */
-#define OPENVPN_IPPROTO_TCP  6  /* TCP protocol */
-#define OPENVPN_IPPROTO_UDP 17  /* UDP protocol */
+#define OPENVPN_IPPROTO_IGMP    2  /* IGMP protocol */
+#define OPENVPN_IPPROTO_TCP     6  /* TCP protocol */
+#define OPENVPN_IPPROTO_UDP    17  /* UDP protocol */
+#define OPENVPN_IPPROTO_ICMPV6 58 /* ICMPV6 protocol */
     uint8_t protocol;
 
     uint16_t check;
@@ -120,6 +143,24 @@
     struct  in6_addr daddr;
 };
 
+/*
+ * ICMPv6 header
+ */
+struct openvpn_icmp6hdr {
+#define OPENVPN_ICMP6_DESTINATION_UNREACHABLE       1
+#define OPENVPN_ND_ROUTER_SOLICIT                 133
+#define OPENVPN_ND_ROUTER_ADVERT                  134
+#define OPENVPN_ND_NEIGHBOR_SOLICIT               135
+#define OPENVPN_ND_NEIGHBOR_ADVERT                136
+#define OPENVPN_ND_INVERSE_SOLICIT                141
+#define OPENVPN_ND_INVERSE_ADVERT                 142
+    uint8_t icmp6_type;
+#define OPENVPN_ICMP6_DU_NOROUTE                    0
+#define OPENVPN_ICMP6_DU_COMMUNICATION_PROHIBTED    1
+    uint8_t icmp6_code;
+    uint16_t icmp6_cksum;
+    uint8_t icmp6_dataun[4];
+};
 
 /*
  * UDP header
@@ -265,6 +306,23 @@
 
 bool is_ipv6(int tunnel_type, struct buffer *buf);
 
+/**
+ *  Calculates an IP or IPv6 checksum with a pseudo header as required by
+ *  TCP, UDP and ICMPv6
+ *
+ * @param af            - Address family for which the checksum is calculated
+ *                        AF_INET or AF_INET6
+ * @param payload       - the TCP, ICMPv6 or UDP packet
+ * @param len_payload   - length of payload
+ * @param src_addr      - Source address of the packet
+ * @param dest_addr     - Destination address of the packet
+ * @param proto next    - header or IP protocol of the packet
+ * @return The calculated checksum in host order
+ */
+uint16_t
+ip_checksum(const sa_family_t af, const uint8_t *payload, const int len_payload,
+            const uint8_t *src_addr, const uint8_t *dest_addr,  const int proto);
+
 #ifdef PACKET_TRUNCATION_CHECK
 void ipv4_packet_size_verify(const uint8_t *data,
                              const int size,
@@ -275,4 +333,7 @@
 
 #endif
 
+#define OPENVPN_8021Q_MIN_VID 1
+#define OPENVPN_8021Q_MAX_VID 4094
+
 #endif /* ifndef PROTO_H */
diff --git a/src/openvpn/proxy.c b/src/openvpn/proxy.c
index afcca86..9998623 100644
--- a/src/openvpn/proxy.c
+++ b/src/openvpn/proxy.c
@@ -318,7 +318,6 @@
 get_proxy_authenticate(socket_descriptor_t sd,
                        int timeout,
                        char **data,
-                       struct gc_arena *gc,
                        volatile int *signal_received)
 {
     char buf[256];
@@ -341,14 +340,14 @@
             if (!strncmp(buf+20, "Basic ", 6))
             {
                 msg(D_PROXY, "PROXY AUTH BASIC: '%s'", buf);
-                *data = string_alloc(buf+26, gc);
+                *data = string_alloc(buf+26, NULL);
                 ret = HTTP_AUTH_BASIC;
             }
 #if PROXY_DIGEST_AUTH
             else if (!strncmp(buf+20, "Digest ", 7))
             {
                 msg(D_PROXY, "PROXY AUTH DIGEST: '%s'", buf);
-                *data = string_alloc(buf+27, gc);
+                *data = string_alloc(buf+27, NULL);
                 ret = HTTP_AUTH_DIGEST;
             }
 #endif
@@ -885,10 +884,10 @@
                 const char *algor = get_pa_var("algorithm", pa, &gc);
                 const char *opaque = get_pa_var("opaque", pa, &gc);
 
-                if ( !realm || !nonce )
+                if (!realm || !nonce)
                 {
                     msg(D_LINK_ERRORS, "HTTP proxy: digest auth failed, malformed response "
-                            "from server: realm= or nonce= missing" );
+                        "from server: realm= or nonce= missing" );
                     goto error;
                 }
 
@@ -997,7 +996,6 @@
             const int method = get_proxy_authenticate(sd,
                                                       get_server_poll_remaining_time(server_poll_timeout),
                                                       &pa,
-                                                      NULL,
                                                       signal_received);
             if (method != HTTP_AUTH_NONE)
             {
diff --git a/src/openvpn/ps.c b/src/openvpn/ps.c
index 25ab374..5d76078 100644
--- a/src/openvpn/ps.c
+++ b/src/openvpn/ps.c
@@ -983,13 +983,38 @@
     const int len = BLEN(buf);
     if (len >= 3)
     {
-        return p[0] == 0
-               && p[1] >= 14
-               && p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT);
+        int plen = (p[0] << 8) | p[1];
+
+        if (p[2] == (P_CONTROL_HARD_RESET_CLIENT_V3 << P_OPCODE_SHIFT))
+        {
+            /* WKc is at least 290 byte (not including metadata):
+             *
+             * 16 bit len + 256 bit HMAC + 2048 bit Kc = 2320 bit
+             *
+             * This is increased by the normal length of client handshake +
+             * tls-crypt overhead (32)
+             *
+             * For metadata tls-crypt-v2.txt does not explicitly specify
+             * an upper limit but we also have TLS_CRYPT_V2_MAX_WKC_LEN
+             * as 1024 bytes. We err on the safe side with 255 extra overhead
+             *
+             * We don't do the 2 byte check for tls-crypt-v2 because it is very
+             * unrealistic to have only 2 bytes available.
+             */
+            return  (plen >= 336 && plen < (1024 + 255));
+        }
+        else
+        {
+            /* For non tls-crypt2 we assume the packet length to valid between
+             * 14 and 255 */
+            return plen >= 14 && plen <= 255
+                   && (p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2 << P_OPCODE_SHIFT));
+        }
     }
     else if (len >= 2)
     {
-        return p[0] == 0 && p[1] >= 14;
+        int plen = (p[0] << 8) | p[1];
+        return plen >= 14 && plen <= 255;
     }
     else
     {
diff --git a/src/openvpn/push.c b/src/openvpn/push.c
index 002be23..2147aca 100644
--- a/src/openvpn/push.c
+++ b/src/openvpn/push.c
@@ -33,6 +33,7 @@
 #include "options.h"
 #include "ssl.h"
 #include "ssl_verify.h"
+#include "ssl_ncp.h"
 #include "manage.h"
 
 #include "memdbg.h"
@@ -69,19 +70,19 @@
         {
             switch (auth_retry_get())
             {
-            case AR_NONE:
-                c->sig->signal_received = SIGTERM; /* SOFT-SIGTERM -- Auth failure error */
-                break;
+                case AR_NONE:
+                    c->sig->signal_received = SIGTERM; /* SOFT-SIGTERM -- Auth failure error */
+                    break;
 
-            case AR_INTERACT:
-                ssl_purge_auth(false);
+                case AR_INTERACT:
+                    ssl_purge_auth(false);
 
-            case AR_NOINTERACT:
-                c->sig->signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- Auth failure error */
-                break;
+                case AR_NOINTERACT:
+                    c->sig->signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- Auth failure error */
+                    break;
 
-            default:
-                ASSERT(0);
+                default:
+                    ASSERT(0);
             }
             c->sig->signal_text = "auth-failure";
         }
@@ -101,7 +102,7 @@
          * Save the dynamic-challenge text even when management is defined
          */
         {
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
             struct buffer buf = *buffer;
             if (buf_string_match_head_str(&buf, "AUTH_FAILED,CRV1:") && BLEN(&buf))
             {
@@ -176,7 +177,60 @@
     }
 }
 
-#if P2MP_SERVER
+void
+server_pushed_info(struct context *c, const struct buffer *buffer,
+                   const int adv)
+{
+    const char *m = "";
+    struct buffer buf = *buffer;
+
+    if (buf_advance(&buf, adv) && buf_read_u8(&buf) == ',' && BLEN(&buf))
+    {
+        m = BSTR(&buf);
+    }
+
+#ifdef ENABLE_MANAGEMENT
+    struct gc_arena gc;
+    if (management)
+    {
+        gc = gc_new();
+
+        /*
+         * We use >INFOMSG here instead of plain >INFO since INFO is used to
+         * for management greeting and we don't want to confuse the client
+         */
+        struct buffer out = alloc_buf_gc(256, &gc);
+        buf_printf(&out, ">%s:%s", "INFOMSG", m);
+        management_notify_generic(management, BSTR(&out));
+
+        gc_free(&gc);
+    }
+    #endif
+    msg(D_PUSH, "Info command was pushed by server ('%s')", m);
+}
+
+void
+receive_cr_response(struct context *c, const struct buffer *buffer)
+{
+    struct buffer buf = *buffer;
+    const char *m = "";
+
+    if (buf_advance(&buf, 11) && buf_read_u8(&buf) == ',' && BLEN(&buf))
+    {
+        m = BSTR(&buf);
+    }
+#ifdef MANAGEMENT_DEF_AUTH
+    struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
+    struct man_def_auth_context *mda = session->opt->mda_context;
+    struct env_set *es = session->opt->es;
+    int key_id = session->key[KS_PRIMARY].key_id;
+
+
+    management_notify_client_cr_response(key_id, mda, es, m);
+#endif
+    msg(D_PUSH, "CR response was sent by client ('%s')", m);
+}
+
 /**
  * Add an option to the given push list by providing a format string.
  *
@@ -233,6 +287,30 @@
     gc_free(&gc);
 }
 
+bool
+send_auth_pending_messages(struct context *c, const char *extra)
+{
+    send_control_channel_string(c, "AUTH_PENDING", D_PUSH);
+
+    static const char info_pre[] = "INFO_PRE,";
+
+
+    size_t len = strlen(extra)+1 + sizeof(info_pre);
+    if (len > PUSH_BUNDLE_SIZE)
+    {
+        return false;
+    }
+    struct gc_arena gc = gc_new();
+
+    struct buffer buf = alloc_buf_gc(len, &gc);
+    buf_printf(&buf, info_pre);
+    buf_printf(&buf, "%s", extra);
+    send_control_channel_string(c, BSTR(&buf), D_PUSH);
+
+    gc_free(&gc);
+    return true;
+}
+
 /*
  * Send restart message from server to client.
  */
@@ -243,8 +321,6 @@
     send_control_channel_string(c, kill_msg ? kill_msg : "RESTART", D_PUSH);
 }
 
-#endif /* if P2MP_SERVER */
-
 /*
  * Push/Pull
  */
@@ -254,15 +330,12 @@
 {
     struct gc_arena gc = gc_new();
     unsigned int option_types_found = 0;
-    int status;
 
     msg(D_PUSH, "PUSH: Received control message: '%s'", sanitize_control_message(BSTR(buffer), &gc));
 
-    status = process_incoming_push_msg(c,
-                                       buffer,
-                                       c->options.pull,
-                                       pull_permission_mask(c),
-                                       &option_types_found);
+    int status = process_incoming_push_msg(c, buffer, c->options.pull,
+                                           pull_permission_mask(c),
+                                           &option_types_found);
 
     if (status == PUSH_MSG_ERROR)
     {
@@ -282,29 +355,11 @@
             }
         }
         event_timeout_clear(&c->c2.push_request_interval);
-    }
-    else if (status == PUSH_MSG_REQUEST)
-    {
-        if (c->options.mode == MODE_SERVER)
-        {
-            struct frame *frame_fragment = NULL;
-#ifdef ENABLE_FRAGMENT
-            if (c->options.ce.fragment)
-            {
-                frame_fragment = &c->c2.frame_fragment;
-            }
-#endif
-            struct tls_session *session = &c->c2.tls_multi->session[TM_ACTIVE];
-            if (!tls_session_update_crypto_params(session, &c->options,
-                                                  &c->c2.frame, frame_fragment))
-            {
-                msg(D_TLS_ERRORS, "TLS Error: initializing data channel failed");
-                goto error;
-            }
-        }
+        event_timeout_clear(&c->c2.wait_for_connect);
     }
 
     goto cleanup;
+
 error:
     register_signal(c, SIGUSR1, "process-push-msg-failed");
 cleanup:
@@ -328,10 +383,40 @@
     }
 }
 
-#if P2MP_SERVER
+/**
+ * Prepare push option for auth-token
+ * @param tls_multi     tls multi context of VPN tunnel
+ * @param gc            gc arena for allocating push options
+ * @param push_list     push list to where options are added
+ *
+ * @return true on success, false on failure.
+ */
+void
+prepare_auth_token_push_reply(struct tls_multi *tls_multi, struct gc_arena *gc,
+                              struct push_list *push_list)
+{
+    /*
+     * If server uses --auth-gen-token and we have an auth token
+     * to send to the client
+     */
+    if (tls_multi->auth_token)
+    {
+        push_option_fmt(gc, push_list, M_USAGE,
+                        "auth-token %s",
+                        tls_multi->auth_token);
+        if (!tls_multi->auth_token_initial)
+        {
+            /*
+             * Save the initial auth token for clients that ignore
+             * the updates to the token
+             */
+            tls_multi->auth_token_initial = strdup(tls_multi->auth_token);
+        }
+    }
+}
 
 /**
- * Prepare push options, based on local options and available peer info.
+ * Prepare push options, based on local options
  *
  * @param context       context structure storing data for VPN tunnel
  * @param gc            gc arena for allocating push options
@@ -339,13 +424,11 @@
  *
  * @return true on success, false on failure.
  */
-static bool
+bool
 prepare_push_reply(struct context *c, struct gc_arena *gc,
                    struct push_list *push_list)
 {
-    const char *optstr = NULL;
     struct tls_multi *tls_multi = c->c2.tls_multi;
-    const char *const peer_info = tls_multi->peer_info;
     struct options *o = &c->options;
 
     /* ipv6 */
@@ -360,7 +443,8 @@
 
     /* ipv4 */
     if (c->c2.push_ifconfig_defined && c->c2.push_ifconfig_local
-        && c->c2.push_ifconfig_remote_netmask)
+        && c->c2.push_ifconfig_remote_netmask
+        && !o->push_ifconfig_ipv4_blocked)
     {
         in_addr_t ifconfig_local = c->c2.push_ifconfig_local;
         if (c->c2.push_ifconfig_local_alias)
@@ -373,58 +457,29 @@
                                         0, gc));
     }
 
-    /* Send peer-id if client supports it */
-    optstr = peer_info ? strstr(peer_info, "IV_PROTO=") : NULL;
-    if (optstr)
+    if (tls_multi->use_peer_id)
     {
-        int proto = 0;
-        int r = sscanf(optstr, "IV_PROTO=%d", &proto);
-        if ((r == 1) && (proto >= 2))
-        {
-            push_option_fmt(gc, push_list, M_USAGE, "peer-id %d",
-                            tls_multi->peer_id);
-            tls_multi->use_peer_id = true;
-        }
+        push_option_fmt(gc, push_list, M_USAGE, "peer-id %d",
+                        tls_multi->peer_id);
     }
-
-    /* Push cipher if client supports Negotiable Crypto Parameters */
-    if (tls_peer_info_ncp_ver(peer_info) >= 2 && o->ncp_enabled)
-    {
-        /* if we have already created our key, we cannot *change* our own
-         * cipher -> so log the fact and push the "what we have now" cipher
-         * (so the client is always told what we expect it to use)
-         */
-        const struct tls_session *session = &tls_multi->session[TM_ACTIVE];
-        if (session->key[KS_PRIMARY].crypto_options.key_ctx_bi.initialized)
-        {
-            msg( M_INFO, "PUSH: client wants to negotiate cipher (NCP), but "
-                 "server has already generated data channel keys, "
-                 "re-sending previously negotiated cipher '%s'",
-                 o->ciphername );
-        }
-        else
-        {
-            /* Push the first cipher from --ncp-ciphers to the client.
-             * TODO: actual negotiation, instead of server dictatorship. */
-            char *push_cipher = string_alloc(o->ncp_ciphers, &o->gc);
-            o->ciphername = strtok(push_cipher, ":");
-        }
-        push_option_fmt(gc, push_list, M_USAGE, "cipher %s", o->ciphername);
-    }
-    else if (o->ncp_enabled)
-    {
-        tls_poor_mans_ncp(o, tls_multi->remote_ciphername);
-    }
-
-    /* If server uses --auth-gen-token and we have an auth token
+    /*
+     * If server uses --auth-gen-token and we have an auth token
      * to send to the client
      */
-    if (false == tls_multi->auth_token_sent && NULL != tls_multi->auth_token)
+    prepare_auth_token_push_reply(tls_multi, gc, push_list);
+
+    /*
+     * Push the selected cipher, at this point the cipher has been
+     * already negotiated and been fixed.
+     *
+     * We avoid pushing the cipher to clients not supporting NCP
+     * to avoid error messages in their logs
+     */
+    if (tls_peer_supports_ncp(c->c2.tls_multi->peer_info))
     {
-        push_option_fmt(gc, push_list, M_USAGE,
-                        "auth-token %s", tls_multi->auth_token);
-        tls_multi->auth_token_sent = true;
+        push_option_fmt(gc, push_list, M_USAGE, "cipher %s", o->ciphername);
     }
+
     return true;
 }
 
@@ -435,6 +490,7 @@
 {
     struct push_entry *e = push_list->head;
 
+    e = push_list->head;
     while (e)
     {
         if (e->enable)
@@ -467,7 +523,26 @@
     return true;
 }
 
-static bool
+void
+send_push_reply_auth_token(struct tls_multi *multi)
+{
+    struct gc_arena gc = gc_new();
+    struct push_list push_list = { 0 };
+
+    prepare_auth_token_push_reply(multi, &gc, &push_list);
+
+    /* prepare auth token should always add the auth-token option */
+    struct push_entry *e = push_list.head;
+    ASSERT(e && e->enable);
+
+    /* Construct a mimimal control channel push reply message */
+    struct buffer buf = alloc_buf_gc(PUSH_BUNDLE_SIZE, &gc);
+    buf_printf(&buf, "%s, %s", push_reply_cmd, e->option);
+    send_control_channel_string_dowork(multi, BSTR(&buf), D_PUSH);
+    gc_free(&gc);
+}
+
+bool
 send_push_reply(struct context *c, struct push_list *per_client_push_list)
 {
     struct gc_arena gc = gc_new();
@@ -586,7 +661,7 @@
 void
 push_options(struct options *o, char **p, int msglevel, struct gc_arena *gc)
 {
-    const char **argv = make_extended_arg_array(p, gc);
+    const char **argv = make_extended_arg_array(p, false, gc);
     char *opt = print_argv(argv, gc, 0);
     push_option(o, opt, msglevel);
 }
@@ -620,6 +695,13 @@
 {
     msg(D_PUSH_DEBUG, "PUSH_REMOVE searching for: '%s'", p);
 
+    /* ifconfig is special, as not part of the push list */
+    if (streq(p, "ifconfig"))
+    {
+        o->push_ifconfig_ipv4_blocked = true;
+        return;
+    }
+
     /* ifconfig-ipv6 is special, as not part of the push list */
     if (streq( p, "ifconfig-ipv6" ))
     {
@@ -645,24 +727,22 @@
         }
     }
 }
-#endif /* if P2MP_SERVER */
 
-#if P2MP_SERVER
 int
 process_incoming_push_request(struct context *c)
 {
     int ret = PUSH_MSG_ERROR;
+    struct key_state *ks = &c->c2.tls_multi->session[TM_ACTIVE].key[KS_PRIMARY];
 
-#ifdef ENABLE_ASYNC_PUSH
-    c->c2.push_request_received = true;
-#endif
-    if (tls_authentication_status(c->c2.tls_multi, 0) == TLS_AUTHENTICATION_FAILED || c->c2.context_auth == CAS_FAILED)
+    if (tls_authentication_status(c->c2.tls_multi, 0) == TLS_AUTHENTICATION_FAILED
+        || c->c2.tls_multi->multi_state == CAS_FAILED)
     {
         const char *client_reason = tls_client_reason(c->c2.tls_multi);
         send_auth_failed(c, client_reason);
         ret = PUSH_MSG_AUTH_FAILURE;
     }
-    else if (!c->c2.push_reply_deferred && c->c2.context_auth == CAS_SUCCEEDED)
+    else if (c->c2.tls_multi->multi_state == CAS_SUCCEEDED
+             && ks->authenticated == KS_AUTH_TRUE)
     {
         time_t now;
 
@@ -674,10 +754,9 @@
         else
         {
             /* per-client push options - peer-id, cipher, ifconfig, ipv6-ifconfig */
-            struct push_list push_list;
+            struct push_list push_list = { 0 };
             struct gc_arena gc = gc_new();
 
-            CLEAR(push_list);
             if (prepare_push_reply(c, &gc, &push_list)
                 && send_push_reply(c, &push_list))
             {
@@ -694,7 +773,6 @@
 
     return ret;
 }
-#endif /* if P2MP_SERVER */
 
 static void
 push_update_digest(md_ctx_t *ctx, struct buffer *buf, const struct options *opt)
@@ -716,6 +794,63 @@
     }
 }
 
+static int
+process_incoming_push_reply(struct context *c,
+                            unsigned int permission_mask,
+                            unsigned int *option_types_found,
+                            struct buffer *buf)
+{
+    int ret = PUSH_MSG_ERROR;
+    const uint8_t ch = buf_read_u8(buf);
+    if (ch == ',')
+    {
+        struct buffer buf_orig = (*buf);
+        if (!c->c2.pulled_options_digest_init_done)
+        {
+            c->c2.pulled_options_state = md_ctx_new();
+            md_ctx_init(c->c2.pulled_options_state, md_kt_get("SHA256"));
+            c->c2.pulled_options_digest_init_done = true;
+        }
+        if (!c->c2.did_pre_pull_restore)
+        {
+            pre_pull_restore(&c->options, &c->c2.gc);
+            c->c2.did_pre_pull_restore = true;
+        }
+        if (apply_push_options(&c->options,
+                               buf,
+                               permission_mask,
+                               option_types_found,
+                               c->c2.es))
+        {
+            push_update_digest(c->c2.pulled_options_state, &buf_orig,
+                               &c->options);
+            switch (c->options.push_continuation)
+            {
+                case 0:
+                case 1:
+                    md_ctx_final(c->c2.pulled_options_state,
+                                 c->c2.pulled_options_digest.digest);
+                    md_ctx_cleanup(c->c2.pulled_options_state);
+                    md_ctx_free(c->c2.pulled_options_state);
+                    c->c2.pulled_options_state = NULL;
+                    c->c2.pulled_options_digest_init_done = false;
+                    ret = PUSH_MSG_REPLY;
+                    break;
+
+                case 2:
+                    ret = PUSH_MSG_CONTINUATION;
+                    break;
+            }
+        }
+    }
+    else if (ch == '\0')
+    {
+        ret = PUSH_MSG_REPLY;
+    }
+    /* show_settings (&c->options); */
+    return ret;
+}
+
 int
 process_incoming_push_msg(struct context *c,
                           const struct buffer *buffer,
@@ -723,70 +858,25 @@
                           unsigned int permission_mask,
                           unsigned int *option_types_found)
 {
-    int ret = PUSH_MSG_ERROR;
     struct buffer buf = *buffer;
 
-#if P2MP_SERVER
     if (buf_string_compare_advance(&buf, "PUSH_REQUEST"))
     {
-        ret = process_incoming_push_request(c);
+        c->c2.push_request_received = true;
+        return process_incoming_push_request(c);
+    }
+    else if (honor_received_options
+             && buf_string_compare_advance(&buf, push_reply_cmd))
+    {
+        return process_incoming_push_reply(c, permission_mask,
+                                           option_types_found, &buf);
     }
     else
-#endif
-
-    if (honor_received_options && buf_string_compare_advance(&buf, "PUSH_REPLY"))
     {
-        const uint8_t ch = buf_read_u8(&buf);
-        if (ch == ',')
-        {
-            struct buffer buf_orig = buf;
-            if (!c->c2.pulled_options_digest_init_done)
-            {
-                c->c2.pulled_options_state = md_ctx_new();
-                md_ctx_init(c->c2.pulled_options_state, md_kt_get("SHA256"));
-                c->c2.pulled_options_digest_init_done = true;
-            }
-            if (!c->c2.did_pre_pull_restore)
-            {
-                pre_pull_restore(&c->options, &c->c2.gc);
-                c->c2.did_pre_pull_restore = true;
-            }
-            if (apply_push_options(&c->options,
-                                   &buf,
-                                   permission_mask,
-                                   option_types_found,
-                                   c->c2.es))
-            {
-                push_update_digest(c->c2.pulled_options_state, &buf_orig,
-                                   &c->options);
-                switch (c->options.push_continuation)
-                {
-                    case 0:
-                    case 1:
-                        md_ctx_final(c->c2.pulled_options_state, c->c2.pulled_options_digest.digest);
-                        md_ctx_cleanup(c->c2.pulled_options_state);
-                        md_ctx_free(c->c2.pulled_options_state);
-                        c->c2.pulled_options_state = NULL;
-                        c->c2.pulled_options_digest_init_done = false;
-                        ret = PUSH_MSG_REPLY;
-                        break;
-
-                    case 2:
-                        ret = PUSH_MSG_CONTINUATION;
-                        break;
-                }
-            }
-        }
-        else if (ch == '\0')
-        {
-            ret = PUSH_MSG_REPLY;
-        }
-        /* show_settings (&c->options); */
+        return PUSH_MSG_ERROR;
     }
-    return ret;
 }
 
-#if P2MP_SERVER
 
 /*
  * Remove iroutes from the push_list.
@@ -850,6 +940,4 @@
     }
 }
 
-#endif /* if P2MP_SERVER */
-
 #endif /* if P2MP */
diff --git a/src/openvpn/push.h b/src/openvpn/push.h
index 5f6181e..2faf19a 100644
--- a/src/openvpn/push.h
+++ b/src/openvpn/push.h
@@ -50,14 +50,19 @@
 
 void server_pushed_signal(struct context *c, const struct buffer *buffer, const bool restart, const int adv);
 
+void server_pushed_info(struct context *c, const struct buffer *buffer,
+                        const int adv);
+
+void receive_cr_response(struct context *c, const struct buffer *buffer);
+
 void incoming_push_message(struct context *c, const struct buffer *buffer);
 
-#if P2MP_SERVER
 void clone_push_list(struct options *o);
 
 void push_option(struct options *o, const char *opt, int msglevel);
 
-void push_options(struct options *o, char **p, int msglevel, struct gc_arena *gc);
+void push_options(struct options *o, char **p, int msglevel,
+                  struct gc_arena *gc);
 
 void push_reset(struct options *o);
 
@@ -67,8 +72,22 @@
 
 void send_auth_failed(struct context *c, const char *client_reason);
 
+/**
+ * Sends the auth pending control messages to a client. See
+ * doc/management-notes.txt under client-pending-auth for
+ * more details on message format
+ */
+bool send_auth_pending_messages(struct context *c, const char *extra);
+
 void send_restart(struct context *c, const char *kill_msg);
 
-#endif
+/**
+ * Sends a push reply message only containin the auth-token to update
+ * the auth-token on the client
+ *
+ * @param multi  - The tls_multi structure belonging to the instance to push to
+ */
+void send_push_reply_auth_token(struct tls_multi *multi);
+
 #endif /* if P2MP */
 #endif /* ifndef PUSH_H */
diff --git a/src/openvpn/pushlist.h b/src/openvpn/pushlist.h
index 23b0ee5..967eda2 100644
--- a/src/openvpn/pushlist.h
+++ b/src/openvpn/pushlist.h
@@ -21,7 +21,7 @@
  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#if !defined(PUSHLIST_H) && P2MP && P2MP_SERVER
+#if !defined(PUSHLIST_H) && P2MP
 #define PUSHLIST_H
 
 /* parameters to be pushed to peer */
@@ -37,5 +37,4 @@
     struct push_entry *tail;
 };
 
-
-#endif
+#endif /* if !defined(PUSHLIST_H) && P2MP */
diff --git a/src/openvpn/reliable.c b/src/openvpn/reliable.c
index 8f5e173..eae1e0c 100644
--- a/src/openvpn/reliable.c
+++ b/src/openvpn/reliable.c
@@ -34,8 +34,6 @@
 
 #include "syshead.h"
 
-#ifdef ENABLE_CRYPTO
-
 #include "buffer.h"
 #include "error.h"
 #include "common.h"
@@ -354,7 +352,7 @@
 
 /* del acknowledged items from send buf */
 void
-reliable_send_purge(struct reliable *rel, struct reliable_ack *ack)
+reliable_send_purge(struct reliable *rel, const struct reliable_ack *ack)
 {
     int i, j;
     for (i = 0; i < ack->len; ++i)
@@ -464,7 +462,7 @@
              (packet_id_print_type)id, reliable_print_ids(rel, &gc));
     }
 
-    dmsg(D_REL_DEBUG, "ACK RWBS rel->size=%d rel->packet_id=%08x id=%08x ret=%d\n", rel->size, rel->packet_id, id, ret);
+    dmsg(D_REL_DEBUG, "ACK RWBS rel->size=%d rel->packet_id=%08x id=%08x ret=%d", rel->size, rel->packet_id, id, ret);
 
     gc_free(&gc);
     return ret;
@@ -567,30 +565,6 @@
     return n_current > 0 && !rel->hold;
 }
 
-#ifdef EXPONENTIAL_BACKOFF
-/* return a unique point-in-time to trigger retry */
-static time_t
-reliable_unique_retry(struct reliable *rel, time_t retry)
-{
-    int i;
-    while (true)
-    {
-        for (i = 0; i < rel->size; ++i)
-        {
-            struct reliable_entry *e = &rel->array[i];
-            if (e->active && e->next_try == retry)
-            {
-                goto again;
-            }
-        }
-        break;
-again:
-        ++retry;
-    }
-    return retry;
-}
-#endif /* ifdef EXPONENTIAL_BACKOFF */
-
 /* return next buffer to send to remote */
 struct buffer *
 reliable_send(struct reliable *rel, int *opcode)
@@ -614,7 +588,7 @@
     {
 #ifdef EXPONENTIAL_BACKOFF
         /* exponential backoff */
-        best->next_try = reliable_unique_retry(rel, local_now + best->timeout);
+        best->next_try = local_now + best->timeout;
         best->timeout *= 2;
 #else
         /* constant timeout, no backoff */
@@ -788,24 +762,17 @@
     printf("********* struct reliable %s\n", desc);
     printf("  initial_timeout=%d\n", (int)rel->initial_timeout);
     printf("  packet_id=" packet_id_format "\n", rel->packet_id);
-    printf("  now=" time_format "\n", now);
+    printf("  now=%" PRIi64 "\n", (int64_t)now);
     for (i = 0; i < rel->size; ++i)
     {
         const struct reliable_entry *e = &rel->array[i];
         if (e->active)
         {
             printf("  %d: packet_id=" packet_id_format " len=%d", i, e->packet_id, e->buf.len);
-            printf(" next_try=" time_format, e->next_try);
+            printf(" next_try=%" PRIi64, (int64_t)e->next_try);
             printf("\n");
         }
     }
 }
 
 #endif /* if 0 */
-
-#else  /* ifdef ENABLE_CRYPTO */
-static void
-dummy(void)
-{
-}
-#endif /* ENABLE_CRYPTO */
diff --git a/src/openvpn/reliable.h b/src/openvpn/reliable.h
index bc32ad9..688c65c 100644
--- a/src/openvpn/reliable.h
+++ b/src/openvpn/reliable.h
@@ -28,8 +28,6 @@
  */
 
 
-#ifdef ENABLE_CRYPTO
-
 #ifndef RELIABLE_H
 #define RELIABLE_H
 
@@ -125,7 +123,7 @@
  * @param ack The acknowledgment structure containing received
  *     acknowledgments.
  */
-void reliable_send_purge(struct reliable *rel, struct reliable_ack *ack);
+void reliable_send_purge(struct reliable *rel, const struct reliable_ack *ack);
 
 /** @} name Functions for processing incoming acknowledgments */
 
@@ -476,4 +474,3 @@
 
 
 #endif /* RELIABLE_H */
-#endif /* ENABLE_CRYPTO */
diff --git a/src/openvpn/ring_buffer.h b/src/openvpn/ring_buffer.h
new file mode 100644
index 0000000..4293f63
--- /dev/null
+++ b/src/openvpn/ring_buffer.h
@@ -0,0 +1,125 @@
+/*
+ *  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) 2002-2019 OpenVPN Inc <sales@openvpn.net>
+ *                2019 Lev Stipakov <lev@openvpn.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef _WIN32
+#ifndef OPENVPN_RING_BUFFER_H
+#define OPENVPN_RING_BUFFER_H
+
+#include <windows.h>
+#include <winioctl.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/*
+ * Values below are taken from Wireguard Windows client
+ * https://github.com/WireGuard/wireguard-go/blob/master/tun/wintun/ring_windows.go#L14
+ */
+#define WINTUN_RING_CAPACITY        0x800000
+#define WINTUN_RING_TRAILING_BYTES  0x10000
+#define WINTUN_MAX_PACKET_SIZE      0xffff
+#define WINTUN_PACKET_ALIGN         4
+
+#define TUN_IOCTL_REGISTER_RINGS CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)
+
+/**
+ * Wintun ring buffer
+ * See https://github.com/WireGuard/wintun#ring-layout
+ */
+struct tun_ring
+{
+    volatile ULONG head;
+    volatile ULONG tail;
+    volatile LONG alertable;
+    UCHAR data[WINTUN_RING_CAPACITY + WINTUN_RING_TRAILING_BYTES];
+};
+
+/**
+ * Struct for ring buffers registration
+ * See https://github.com/WireGuard/wintun#registering-rings
+ */
+struct tun_register_rings
+{
+    struct
+    {
+        ULONG ring_size;
+        struct tun_ring *ring;
+        HANDLE tail_moved;
+    } send, receive;
+};
+
+struct TUN_PACKET_HEADER
+{
+    uint32_t size;
+};
+
+struct TUN_PACKET
+{
+    uint32_t size;
+    UCHAR data[WINTUN_MAX_PACKET_SIZE];
+};
+
+/**
+ * Registers ring buffers used to exchange data between
+ * userspace openvpn process and wintun kernel driver,
+ * see https://github.com/WireGuard/wintun#registering-rings
+ *
+ * @param device              handle to opened wintun device
+ * @param send_ring           pointer to send ring
+ * @param receive_ring        pointer to receive ring
+ * @param send_tail_moved     event set by wintun to signal openvpn
+ *                            that data is available for reading in send ring
+ * @param receive_tail_moved  event set by openvpn to signal wintun
+ *                            that data has been written to receive ring
+ * @return                    true if registration is successful, false otherwise - use GetLastError()
+ */
+static bool
+register_ring_buffers(HANDLE device,
+                      struct tun_ring *send_ring,
+                      struct tun_ring *receive_ring,
+                      HANDLE send_tail_moved,
+                      HANDLE receive_tail_moved)
+{
+    struct tun_register_rings rr;
+    BOOL res;
+    DWORD bytes_returned;
+
+    ZeroMemory(&rr, sizeof(rr));
+
+    rr.send.ring = send_ring;
+    rr.send.ring_size = sizeof(struct tun_ring);
+    rr.send.tail_moved = send_tail_moved;
+
+    rr.receive.ring = receive_ring;
+    rr.receive.ring_size = sizeof(struct tun_ring);
+    rr.receive.tail_moved = receive_tail_moved;
+
+    res = DeviceIoControl(device, TUN_IOCTL_REGISTER_RINGS, &rr, sizeof(rr),
+      NULL, 0, &bytes_returned, NULL);
+
+    return res != FALSE;
+}
+
+#endif /* ifndef OPENVPN_RING_BUFFER_H */
+#endif /* ifdef _WIN32 */
diff --git a/src/openvpn/route.c b/src/openvpn/route.c
index 4199da3..5e1dca6 100644
--- a/src/openvpn/route.c
+++ b/src/openvpn/route.c
@@ -36,11 +36,12 @@
 #include "common.h"
 #include "error.h"
 #include "route.h"
-#include "misc.h"
+#include "run_command.h"
 #include "socket.h"
 #include "manage.h"
 #include "win32.h"
 #include "options.h"
+#include "networking.h"
 
 #include "memdbg.h"
 
@@ -48,6 +49,10 @@
 #include <linux/rtnetlink.h>            /* RTM_GETROUTE etc. */
 #endif
 
+#if defined(TARGET_NETBSD)
+#include <net/route.h>			/* RT_ROUNDUP(), RT_ADVANCE() */
+#endif
+
 #ifdef _WIN32
 #include "openvpn-msg.h"
 
@@ -62,7 +67,7 @@
 
 #endif
 
-static void delete_route(struct route_ipv4 *r, const struct tuntap *tt, unsigned int flags, const struct route_gateway_info *rgi, const struct env_set *es);
+static void delete_route(struct route_ipv4 *r, const struct tuntap *tt, unsigned int flags, const struct route_gateway_info *rgi, const struct env_set *es, openvpn_net_ctx_t *ctx);
 
 static void get_bypass_addresses(struct route_bypass *rb, const unsigned int flags);
 
@@ -322,6 +327,10 @@
 
     if (get_special_addr(rl, ro->network, (in_addr_t *) &special.s_addr, &status))
     {
+        if (!status)
+        {
+            goto fail;
+        }
         special.s_addr = htonl(special.s_addr);
         ret = openvpn_getaddrinfo(0, inet_ntoa(special), NULL, 0, NULL,
                                   AF_INET, network_list);
@@ -448,11 +457,6 @@
     {
         r6->gateway = rl6->remote_endpoint_ipv6;
     }
-    else
-    {
-        msg(M_WARN, PACKAGE_NAME " ROUTE6: " PACKAGE_NAME " needs a gateway parameter for a --route-ipv6 option and no default was specified by either --route-ipv6-gateway or --ifconfig-ipv6 options");
-        goto fail;
-    }
 
     /* metric */
 
@@ -613,7 +617,8 @@
                 const char *remote_endpoint,
                 int default_metric,
                 in_addr_t remote_host,
-                struct env_set *es)
+                struct env_set *es,
+                openvpn_net_ctx_t *ctx)
 {
     struct gc_arena gc = gc_new();
     bool ret = true;
@@ -622,7 +627,7 @@
 
     rl->flags = opt->flags;
 
-    if (remote_host)
+    if (remote_host != IPV4_INVALID_ADDR)
     {
         rl->spec.remote_host = remote_host;
         rl->spec.flags |= RTSA_REMOTE_HOST;
@@ -634,7 +639,7 @@
         rl->spec.flags |= RTSA_DEFAULT_METRIC;
     }
 
-    get_default_gateway(&rl->rgi);
+    get_default_gateway(&rl->rgi, ctx);
     if (rl->rgi.flags & RGI_ADDR_DEFINED)
     {
         setenv_route_addr(es, "net_gateway", rl->rgi.gateway.addr, -1);
@@ -768,7 +773,8 @@
                      const char *remote_endpoint,
                      int default_metric,
                      const struct in6_addr *remote_host_ipv6,
-                     struct env_set *es)
+                     struct env_set *es,
+                     openvpn_net_ctx_t *ctx)
 {
     struct gc_arena gc = gc_new();
     bool ret = true;
@@ -793,7 +799,7 @@
     msg(D_ROUTE, "GDG6: remote_host_ipv6=%s",
         remote_host_ipv6 ?  print_in6_addr(*remote_host_ipv6, 0, &gc) : "n/a" );
 
-    get_default_gateway_ipv6(&rl6->rgi6, remote_host_ipv6);
+    get_default_gateway_ipv6(&rl6->rgi6, remote_host_ipv6, ctx);
     if (rl6->rgi6.flags & RGI_ADDR_DEFINED)
     {
         setenv_str(es, "net_gateway_ipv6", print_in6_addr(rl6->rgi6.gateway.addr_ipv6, 0, &gc));
@@ -901,7 +907,8 @@
            const struct tuntap *tt,
            unsigned int flags,
            const struct route_gateway_info *rgi,
-           const struct env_set *es)
+           const struct env_set *es,
+           openvpn_net_ctx_t *ctx)
 {
     struct route_ipv4 r;
     CLEAR(r);
@@ -909,7 +916,7 @@
     r.network = network;
     r.netmask = netmask;
     r.gateway = gateway;
-    add_route(&r, tt, flags, rgi, es);
+    add_route(&r, tt, flags, rgi, es, ctx);
 }
 
 static void
@@ -919,7 +926,8 @@
            const struct tuntap *tt,
            unsigned int flags,
            const struct route_gateway_info *rgi,
-           const struct env_set *es)
+           const struct env_set *es,
+           openvpn_net_ctx_t *ctx)
 {
     struct route_ipv4 r;
     CLEAR(r);
@@ -927,7 +935,7 @@
     r.network = network;
     r.netmask = netmask;
     r.gateway = gateway;
-    delete_route(&r, tt, flags, rgi, es);
+    delete_route(&r, tt, flags, rgi, es, ctx);
 }
 
 static void
@@ -936,7 +944,8 @@
                   const struct tuntap *tt,
                   unsigned int flags,
                   const struct route_gateway_info *rgi,
-                  const struct env_set *es)
+                  const struct env_set *es,
+                  openvpn_net_ctx_t *ctx)
 {
     int i;
     for (i = 0; i < rb->n_bypass; ++i)
@@ -949,7 +958,8 @@
                        tt,
                        flags | ROUTE_REF_GW,
                        rgi,
-                       es);
+                       es,
+                       ctx);
         }
     }
 }
@@ -960,7 +970,8 @@
                   const struct tuntap *tt,
                   unsigned int flags,
                   const struct route_gateway_info *rgi,
-                  const struct env_set *es)
+                  const struct env_set *es,
+                  openvpn_net_ctx_t *ctx)
 {
     int i;
     for (i = 0; i < rb->n_bypass; ++i)
@@ -973,15 +984,18 @@
                        tt,
                        flags | ROUTE_REF_GW,
                        rgi,
-                       es);
+                       es,
+                       ctx);
         }
     }
 }
 
 static void
-redirect_default_route_to_vpn(struct route_list *rl, const struct tuntap *tt, unsigned int flags, const struct env_set *es)
+redirect_default_route_to_vpn(struct route_list *rl, const struct tuntap *tt,
+                              unsigned int flags, const struct env_set *es,
+                              openvpn_net_ctx_t *ctx)
 {
-    const char err[] = "NOTE: unable to redirect default gateway --";
+    const char err[] = "NOTE: unable to redirect IPv4 default gateway --";
 
     if (rl && rl->flags & RG_ENABLE)
     {
@@ -997,14 +1011,10 @@
          * - we are connecting to a non-IPv4 remote host (i.e. we use IPv6)
          */
         else if (!(rl->rgi.flags & RGI_ADDR_DEFINED) && !local
-                 && (rl->spec.remote_host != IPV4_INVALID_ADDR))
+                 && (rl->spec.flags & RTSA_REMOTE_HOST))
         {
             msg(M_WARN, "%s Cannot read current default gateway from system", err);
         }
-        else if (!(rl->spec.flags & RTSA_REMOTE_HOST))
-        {
-            msg(M_WARN, "%s Cannot obtain current remote host address", err);
-        }
         else
         {
 #ifndef TARGET_ANDROID
@@ -1027,7 +1037,8 @@
                 /* route remote host to original default gateway */
                 /* if remote_host is not ipv4 (ie: ipv6), just skip
                  * adding this special /32 route */
-                if (rl->spec.remote_host != IPV4_INVALID_ADDR)
+                if ((rl->spec.flags & RTSA_REMOTE_HOST)
+                    && rl->spec.remote_host != IPV4_INVALID_ADDR)
                 {
                     add_route3(rl->spec.remote_host,
                                IPV4_NETMASK_HOST,
@@ -1035,7 +1046,8 @@
                                tt,
                                flags | ROUTE_REF_GW,
                                &rl->rgi,
-                               es);
+                               es,
+                               ctx);
                     rl->iflags |= RL_DID_LOCAL;
                 }
                 else
@@ -1046,7 +1058,8 @@
 #endif /* ifndef TARGET_ANDROID */
 
             /* route DHCP/DNS server traffic through original default gateway */
-            add_bypass_routes(&rl->spec.bypass, rl->rgi.gateway.addr, tt, flags, &rl->rgi, es);
+            add_bypass_routes(&rl->spec.bypass, rl->rgi.gateway.addr, tt, flags,
+                              &rl->rgi, es, ctx);
 
             if (rl->flags & RG_REROUTE_GW)
             {
@@ -1059,7 +1072,8 @@
                                tt,
                                flags,
                                &rl->rgi,
-                               es);
+                               es,
+                               ctx);
 
                     /* add new default route (2nd component) */
                     add_route3(0x80000000,
@@ -1068,7 +1082,8 @@
                                tt,
                                flags,
                                &rl->rgi,
-                               es);
+                               es,
+                               ctx);
                 }
                 else
                 {
@@ -1077,7 +1092,7 @@
                     {
                         /* delete default route */
                         del_route3(0, 0, rl->rgi.gateway.addr, tt,
-                                   flags | ROUTE_REF_GW, &rl->rgi, es);
+                                   flags | ROUTE_REF_GW, &rl->rgi, es, ctx);
                     }
 
                     /* add new default route */
@@ -1087,7 +1102,8 @@
                                tt,
                                flags,
                                &rl->rgi,
-                               es);
+                               es,
+                               ctx);
                 }
             }
 
@@ -1098,7 +1114,10 @@
 }
 
 static void
-undo_redirect_default_route_to_vpn(struct route_list *rl, const struct tuntap *tt, unsigned int flags, const struct env_set *es)
+undo_redirect_default_route_to_vpn(struct route_list *rl,
+                                   const struct tuntap *tt, unsigned int flags,
+                                   const struct env_set *es,
+                                   openvpn_net_ctx_t *ctx)
 {
     if (rl && rl->iflags & RL_DID_REDIRECT_DEFAULT_GATEWAY)
     {
@@ -1111,12 +1130,14 @@
                        tt,
                        flags | ROUTE_REF_GW,
                        &rl->rgi,
-                       es);
+                       es,
+                       ctx);
             rl->iflags &= ~RL_DID_LOCAL;
         }
 
         /* delete special DHCP/DNS bypass route */
-        del_bypass_routes(&rl->spec.bypass, rl->rgi.gateway.addr, tt, flags, &rl->rgi, es);
+        del_bypass_routes(&rl->spec.bypass, rl->rgi.gateway.addr, tt, flags,
+                          &rl->rgi, es, ctx);
 
         if (rl->flags & RG_REROUTE_GW)
         {
@@ -1129,7 +1150,8 @@
                            tt,
                            flags,
                            &rl->rgi,
-                           es);
+                           es,
+                           ctx);
 
                 /* delete default route (2nd component) */
                 del_route3(0x80000000,
@@ -1138,7 +1160,8 @@
                            tt,
                            flags,
                            &rl->rgi,
-                           es);
+                           es,
+                           ctx);
             }
             else
             {
@@ -1149,12 +1172,13 @@
                            tt,
                            flags,
                            &rl->rgi,
-                           es);
+                           es,
+                           ctx);
                 /* restore original default route if there was any */
                 if (rl->rgi.flags & RGI_ADDR_DEFINED)
                 {
                     add_route3(0, 0, rl->rgi.gateway.addr, tt,
-                               flags | ROUTE_REF_GW, &rl->rgi, es);
+                               flags | ROUTE_REF_GW, &rl->rgi, es, ctx);
                 }
             }
         }
@@ -1164,13 +1188,23 @@
 }
 
 void
-add_routes(struct route_list *rl, struct route_ipv6_list *rl6, const struct tuntap *tt, unsigned int flags, const struct env_set *es)
+add_routes(struct route_list *rl, struct route_ipv6_list *rl6,
+           const struct tuntap *tt, unsigned int flags,
+           const struct env_set *es, openvpn_net_ctx_t *ctx)
 {
-    redirect_default_route_to_vpn(rl, tt, flags, es);
+    redirect_default_route_to_vpn(rl, tt, flags, es, ctx);
     if (rl && !(rl->iflags & RL_ROUTES_ADDED) )
     {
         struct route_ipv4 *r;
 
+        if (rl->routes && !tt->did_ifconfig_setup)
+        {
+            msg(M_INFO, "WARNING: OpenVPN was configured to add an IPv4 "
+                "route. However, no IPv4 has been configured for %s, "
+                "therefore the route installation may fail or may not work "
+                "as expected.", tt->actual_name);
+        }
+
 #ifdef ENABLE_MANAGEMENT
         if (management && rl->routes)
         {
@@ -1189,9 +1223,9 @@
             check_subnet_conflict(r->network, r->netmask, "route");
             if (flags & ROUTE_DELETE_FIRST)
             {
-                delete_route(r, tt, flags, &rl->rgi, es);
+                delete_route(r, tt, flags, &rl->rgi, es, ctx);
             }
-            add_route(r, tt, flags, &rl->rgi, es);
+            add_route(r, tt, flags, &rl->rgi, es, ctx);
         }
         rl->iflags |= RL_ROUTES_ADDED;
     }
@@ -1202,18 +1236,18 @@
         if (!tt->did_ifconfig_ipv6_setup)
         {
             msg(M_INFO, "WARNING: OpenVPN was configured to add an IPv6 "
-                "route over %s. However, no IPv6 has been configured for "
-                "this interface, therefore the route installation may "
-                "fail or may not work as expected.", tt->actual_name);
+                "route. However, no IPv6 has been configured for %s, "
+                "therefore the route installation may fail or may not work "
+                "as expected.", tt->actual_name);
         }
 
         for (r = rl6->routes_ipv6; r; r = r->next)
         {
             if (flags & ROUTE_DELETE_FIRST)
             {
-                delete_route_ipv6(r, tt, flags, es);
+                delete_route_ipv6(r, tt, flags, es, ctx);
             }
-            add_route_ipv6(r, tt, flags, es);
+            add_route_ipv6(r, tt, flags, es, ctx);
         }
         rl6->iflags |= RL_ROUTES_ADDED;
     }
@@ -1221,19 +1255,20 @@
 
 void
 delete_routes(struct route_list *rl, struct route_ipv6_list *rl6,
-              const struct tuntap *tt, unsigned int flags, const struct env_set *es)
+              const struct tuntap *tt, unsigned int flags,
+              const struct env_set *es, openvpn_net_ctx_t *ctx)
 {
     if (rl && rl->iflags & RL_ROUTES_ADDED)
     {
         struct route_ipv4 *r;
         for (r = rl->routes; r; r = r->next)
         {
-            delete_route(r, tt, flags, &rl->rgi, es);
+            delete_route(r, tt, flags, &rl->rgi, es, ctx);
         }
         rl->iflags &= ~RL_ROUTES_ADDED;
     }
 
-    undo_redirect_default_route_to_vpn(rl, tt, flags, es);
+    undo_redirect_default_route_to_vpn(rl, tt, flags, es, ctx);
 
     if (rl)
     {
@@ -1245,7 +1280,7 @@
         struct route_ipv6 *r6;
         for (r6 = rl6->routes_ipv6; r6; r6 = r6->next)
         {
-            delete_route_ipv6(r6, tt, flags, es);
+            delete_route_ipv6(r6, tt, flags, es, ctx);
         }
         rl6->iflags &= ~RL_ROUTES_ADDED;
     }
@@ -1322,7 +1357,7 @@
 #ifdef _WIN32
         if (rgi->flags & RGI_IFACE_DEFINED)
         {
-            buf_printf(&out, " I=%u", (unsigned int)rgi->adapter_index);
+            buf_printf(&out, " I=%lu", rgi->adapter_index);
         }
 #else
         if (rgi->flags & RGI_IFACE_DEFINED)
@@ -1353,7 +1388,7 @@
 #ifdef _WIN32
         if (rgi6->flags & RGI_IFACE_DEFINED)
         {
-            buf_printf(&out, " I=%u", (unsigned int)rgi6->adapter_index);
+            buf_printf(&out, " I=%lu", rgi6->adapter_index);
         }
 #else
         if (rgi6->flags & RGI_IFACE_DEFINED)
@@ -1441,6 +1476,13 @@
 
         buf_printf( &name2, "route_ipv6_gateway_%d", i );
         setenv_str( es, BSTR(&name2), print_in6_addr( r6->gateway, 0, &gc ));
+
+        if (r6->flags & RT_METRIC_DEFINED)
+        {
+            struct buffer name3 = alloc_buf_gc( 256, &gc );
+            buf_printf( &name3, "route_ipv6_metric_%d", i) ;
+            setenv_int( es, BSTR(&name3), r6->metric);
+        }
     }
     gc_free(&gc);
 }
@@ -1525,15 +1567,18 @@
           const struct tuntap *tt,
           unsigned int flags,
           const struct route_gateway_info *rgi,  /* may be NULL */
-          const struct env_set *es)
+          const struct env_set *es,
+          openvpn_net_ctx_t *ctx)
 {
     struct gc_arena gc;
     struct argv argv = argv_new();
+#if !defined(TARGET_LINUX)
     const char *network;
-#if !defined(ENABLE_IPROUTE) && !defined(TARGET_AIX)
+#if !defined(TARGET_AIX)
     const char *netmask;
 #endif
     const char *gateway;
+#endif
     bool status = false;
     int is_local_route;
 
@@ -1544,11 +1589,13 @@
 
     gc_init(&gc);
 
+#if !defined(TARGET_LINUX)
     network = print_in_addr_t(r->network, 0, &gc);
-#if !defined(ENABLE_IPROUTE) && !defined(TARGET_AIX)
+#if !defined(TARGET_AIX)
     netmask = print_in_addr_t(r->netmask, 0, &gc);
 #endif
     gateway = print_in_addr_t(r->gateway, 0, &gc);
+#endif
 
     is_local_route = local_route(r->network, r->netmask, r->gateway, rgi);
     if (is_local_route == LR_ERROR)
@@ -1557,64 +1604,44 @@
     }
 
 #if defined(TARGET_LINUX)
-#ifdef ENABLE_IPROUTE
-    argv_printf(&argv, "%s route add %s/%d",
-                iproute_path,
-                network,
-                netmask_to_netbits2(r->netmask));
-
-    if (r->flags & RT_METRIC_DEFINED)
-    {
-        argv_printf_cat(&argv, "metric %d", r->metric);
-    }
+    const char *iface = NULL;
+    int metric = -1;
 
     if (is_on_link(is_local_route, flags, rgi))
     {
-        argv_printf_cat(&argv, "dev %s", rgi->iface);
-    }
-    else
-    {
-        argv_printf_cat(&argv, "via %s", gateway);
-    }
-#else  /* ifdef ENABLE_IPROUTE */
-    argv_printf(&argv, "%s add -net %s netmask %s",
-                ROUTE_PATH,
-                network,
-                netmask);
-    if (r->flags & RT_METRIC_DEFINED)
-    {
-        argv_printf_cat(&argv, "metric %d", r->metric);
-    }
-    if (is_on_link(is_local_route, flags, rgi))
-    {
-        argv_printf_cat(&argv, "dev %s", rgi->iface);
-    }
-    else
-    {
-        argv_printf_cat(&argv, "gw %s", gateway);
+        iface = rgi->iface;
     }
 
-#endif  /*ENABLE_IPROUTE*/
-    argv_msg(D_ROUTE, &argv);
-    status = openvpn_execve_check(&argv, es, 0, "ERROR: Linux route add command failed");
+    if (r->flags & RT_METRIC_DEFINED)
+    {
+        metric = r->metric;
+    }
+
+    status = true;
+    if (net_route_v4_add(ctx, &r->network, netmask_to_netbits2(r->netmask),
+                         &r->gateway, iface, 0, metric) < 0)
+    {
+        msg(M_WARN, "ERROR: Linux route add command failed");
+        status = false;
+    }
 
 #elif defined (TARGET_ANDROID)
-    struct buffer out = alloc_buf_gc(128, &gc);
+    char out[128];
 
     if (rgi)
     {
-        buf_printf(&out, "%s %s %s dev %s", network, netmask, gateway, rgi->iface);
+        openvpn_snprintf(out, sizeof(out), "%s %s %s dev %s", network, netmask, gateway, rgi->iface);
     }
     else
     {
-        buf_printf(&out, "%s %s %s", network, netmask, gateway);
+        openvpn_snprintf(out, sizeof(out), "%s %s %s", network, netmask, gateway);
     }
-    management_android_control(management, "ROUTE", buf_bptr(&out));
+    management_android_control(management, "ROUTE", out);
 
 #elif defined (_WIN32)
     {
         DWORD ai = TUN_ADAPTER_INDEX_INVALID;
-        argv_printf(&argv, "%s%sc ADD %s MASK %s %s",
+        argv_printf(&argv, "%s%s ADD %s MASK %s %s",
                     get_win_sys_path(),
                     WIN_ROUTE_PATH_SUFFIX,
                     network,
@@ -1627,7 +1654,7 @@
         if (is_on_link(is_local_route, flags, rgi))
         {
             ai = rgi->adapter_index;
-            argv_printf_cat(&argv, "IF %u", (unsigned int)ai);
+            argv_printf_cat(&argv, "IF %lu", ai);
         }
 
         argv_msg(D_ROUTE, &argv);
@@ -1815,8 +1842,10 @@
     {
         r->flags &= ~RT_ADDED;
     }
-    argv_reset(&argv);
+    argv_free(&argv);
     gc_free(&gc);
+    /* release resources potentially allocated during route setup */
+    net_ctx_reset(ctx);
 }
 
 
@@ -1825,7 +1854,7 @@
 {
     /* clear host bit parts of route
      * (needed if routes are specified improperly, or if we need to
-     * explicitely setup/clear the "connected" network routes on some OSes)
+     * explicitly setup/clear the "connected" network routes on some OSes)
      */
     int byte = 15;
     int bits_to_clear = 128 - r6->netbits;
@@ -1844,7 +1873,9 @@
 }
 
 void
-add_route_ipv6(struct route_ipv6 *r6, const struct tuntap *tt, unsigned int flags, const struct env_set *es)
+add_route_ipv6(struct route_ipv6 *r6, const struct tuntap *tt,
+               unsigned int flags, const struct env_set *es,
+               openvpn_net_ctx_t *ctx)
 {
     struct gc_arena gc;
     struct argv argv = argv_new();
@@ -1853,7 +1884,9 @@
     const char *gateway;
     bool status = false;
     const char *device = tt->actual_name;
-
+#if defined(TARGET_LINUX)
+    int metric;
+#endif
     bool gateway_needed = false;
 
     if (!(r6->flags & RT_DEFINED) )
@@ -1917,46 +1950,38 @@
         gateway_needed = true;
     }
 
-#if defined(TARGET_LINUX)
-#ifdef ENABLE_IPROUTE
-    argv_printf(&argv, "%s -6 route add %s/%d dev %s",
-                iproute_path,
-                network,
-                r6->netbits,
-                device);
-    if (gateway_needed)
+    if (gateway_needed && IN6_IS_ADDR_UNSPECIFIED(&r6->gateway))
     {
-        argv_printf_cat(&argv, "via %s", gateway);
-    }
-    if ( (r6->flags & RT_METRIC_DEFINED) && r6->metric > 0)
-    {
-        argv_printf_cat(&argv, " metric %d", r6->metric);
+        msg(M_WARN, "ROUTE6 WARNING: " PACKAGE_NAME " needs a gateway "
+            "parameter for a --route-ipv6 option and no default was set via "
+            "--ifconfig-ipv6 or --route-ipv6-gateway option.  Not installing "
+            "IPv6 route to %s/%d.", network, r6->netbits);
+        status = false;
+        goto done;
     }
 
-#else  /* ifdef ENABLE_IPROUTE */
-    argv_printf(&argv, "%s -A inet6 add %s/%d dev %s",
-                ROUTE_PATH,
-                network,
-                r6->netbits,
-                device);
-    if (gateway_needed)
+#if defined(TARGET_LINUX)
+    metric = -1;
+    if ((r6->flags & RT_METRIC_DEFINED) && (r6->metric > 0))
     {
-        argv_printf_cat(&argv, "gw %s", gateway);
+        metric = r6->metric;
     }
-    if ( (r6->flags & RT_METRIC_DEFINED) && r6->metric > 0)
+
+    status = true;
+    if (net_route_v6_add(ctx, &r6->network, r6->netbits,
+                         gateway_needed ? &r6->gateway : NULL, device, 0,
+                         metric) < 0)
     {
-        argv_printf_cat(&argv, " metric %d", r6->metric);
+        msg(M_WARN, "ERROR: Linux IPv6 route can't be added");
+        status = false;
     }
-#endif  /*ENABLE_IPROUTE*/
-    argv_msg(D_ROUTE, &argv);
-    status = openvpn_execve_check(&argv, es, 0, "ERROR: Linux route -6/-A inet6 add command failed");
 
 #elif defined (TARGET_ANDROID)
-    struct buffer out = alloc_buf_gc(64, &gc);
+    char out[64];
 
-    buf_printf(&out, "%s/%d %s", network, r6->netbits, device);
+    openvpn_snprintf(out, sizeof(out), "%s/%d %s", network, r6->netbits, device);
 
-    management_android_control(management, "ROUTE6", buf_bptr(&out));
+    management_android_control(management, "ROUTE6", out);
 
 #elif defined (_WIN32)
 
@@ -1966,25 +1991,24 @@
     }
     else
     {
-        struct buffer out = alloc_buf_gc(64, &gc);
+        DWORD adapter_index;
         if (r6->adapter_index)          /* vpn server special route */
         {
-            buf_printf(&out, "interface=%lu", r6->adapter_index );
+            adapter_index = r6->adapter_index;
             gateway_needed = true;
         }
         else
         {
-            buf_printf(&out, "interface=%lu", tt->adapter_index );
+            adapter_index = tt->adapter_index;
         }
-        device = buf_bptr(&out);
 
-        /* netsh interface ipv6 add route 2001:db8::/32 MyTunDevice */
-        argv_printf(&argv, "%s%sc interface ipv6 add route %s/%d %s",
+        /* netsh interface ipv6 add route 2001:db8::/32 42 */
+        argv_printf(&argv, "%s%s interface ipv6 add route %s/%d %lu",
                     get_win_sys_path(),
                     NETSH_PATH_SUFFIX,
                     network,
                     r6->netbits,
-                    device);
+                    adapter_index);
 
         /* next-hop depends on TUN or TAP mode:
          * - in TAP mode, we use the "real" next-hop
@@ -2114,6 +2138,7 @@
     msg(M_FATAL, "Sorry, but I don't know how to do 'route ipv6' commands on this operating system.  Try putting your routes in a --route-up script");
 #endif /* if defined(TARGET_LINUX) */
 
+done:
     if (status)
     {
         r6->flags |= RT_ADDED;
@@ -2122,8 +2147,10 @@
     {
         r6->flags &= ~RT_ADDED;
     }
-    argv_reset(&argv);
+    argv_free(&argv);
     gc_free(&gc);
+    /* release resources potentially allocated during route setup */
+    net_ctx_reset(ctx);
 }
 
 static void
@@ -2131,17 +2158,22 @@
              const struct tuntap *tt,
              unsigned int flags,
              const struct route_gateway_info *rgi,
-             const struct env_set *es)
+             const struct env_set *es,
+             openvpn_net_ctx_t *ctx)
 {
     struct gc_arena gc;
     struct argv argv = argv_new();
+#if !defined(TARGET_LINUX)
     const char *network;
-#if !defined(ENABLE_IPROUTE) && !defined(TARGET_AIX)
+#if !defined(TARGET_AIX)
     const char *netmask;
 #endif
-#if !defined(TARGET_LINUX) && !defined(TARGET_ANDROID)
+#if !defined(TARGET_ANDROID)
     const char *gateway;
 #endif
+#else  /* if !defined(TARGET_LINUX) */
+    int metric;
+#endif
     int is_local_route;
 
     if ((r->flags & (RT_DEFINED|RT_ADDED)) != (RT_DEFINED|RT_ADDED))
@@ -2151,13 +2183,15 @@
 
     gc_init(&gc);
 
+#if !defined(TARGET_LINUX)
     network = print_in_addr_t(r->network, 0, &gc);
-#if !defined(ENABLE_IPROUTE) && !defined(TARGET_AIX)
+#if !defined(TARGET_AIX)
     netmask = print_in_addr_t(r->netmask, 0, &gc);
 #endif
-#if !defined(TARGET_LINUX) && !defined(TARGET_ANDROID)
+#if !defined(TARGET_ANDROID)
     gateway = print_in_addr_t(r->gateway, 0, &gc);
 #endif
+#endif
 
     is_local_route = local_route(r->network, r->netmask, r->gateway, rgi);
     if (is_local_route == LR_ERROR)
@@ -2166,27 +2200,20 @@
     }
 
 #if defined(TARGET_LINUX)
-#ifdef ENABLE_IPROUTE
-    argv_printf(&argv, "%s route del %s/%d",
-                iproute_path,
-                network,
-                netmask_to_netbits2(r->netmask));
-#else
-    argv_printf(&argv, "%s del -net %s netmask %s",
-                ROUTE_PATH,
-                network,
-                netmask);
-#endif /*ENABLE_IPROUTE*/
+    metric = -1;
     if (r->flags & RT_METRIC_DEFINED)
     {
-        argv_printf_cat(&argv, "metric %d", r->metric);
+        metric = r->metric;
     }
-    argv_msg(D_ROUTE, &argv);
-    openvpn_execve_check(&argv, es, 0, "ERROR: Linux route delete command failed");
 
+    if (net_route_v4_del(ctx, &r->network, netmask_to_netbits2(r->netmask),
+                         &r->gateway, NULL, 0, metric) < 0)
+    {
+        msg(M_WARN, "ERROR: Linux route delete command failed");
+    }
 #elif defined (_WIN32)
 
-    argv_printf(&argv, "%s%sc DELETE %s MASK %s %s",
+    argv_printf(&argv, "%s%s DELETE %s MASK %s %s",
                 get_win_sys_path(),
                 WIN_ROUTE_PATH_SUFFIX,
                 network,
@@ -2314,17 +2341,25 @@
 
 done:
     r->flags &= ~RT_ADDED;
-    argv_reset(&argv);
+    argv_free(&argv);
     gc_free(&gc);
+    /* release resources potentially allocated during route cleanup */
+    net_ctx_reset(ctx);
 }
 
 void
-delete_route_ipv6(const struct route_ipv6 *r6, const struct tuntap *tt, unsigned int flags, const struct env_set *es)
+delete_route_ipv6(const struct route_ipv6 *r6, const struct tuntap *tt,
+                  unsigned int flags, const struct env_set *es,
+                  openvpn_net_ctx_t *ctx)
 {
     struct gc_arena gc;
     struct argv argv = argv_new();
     const char *network;
+#if !defined(TARGET_LINUX)
     const char *gateway;
+#else
+    int metric;
+#endif
     const char *device = tt->actual_name;
     bool gateway_needed = false;
 
@@ -2344,7 +2379,9 @@
     gc_init(&gc);
 
     network = print_in6_addr( r6->network, 0, &gc);
+#if !defined(TARGET_LINUX)
     gateway = print_in6_addr( r6->gateway, 0, &gc);
+#endif
 
 #if defined(TARGET_DARWIN)    \
     || defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY)    \
@@ -2375,35 +2412,19 @@
         gateway_needed = true;
     }
 
-
 #if defined(TARGET_LINUX)
-#ifdef ENABLE_IPROUTE
-    argv_printf(&argv, "%s -6 route del %s/%d dev %s",
-                iproute_path,
-                network,
-                r6->netbits,
-                device);
-    if (gateway_needed)
+    metric = -1;
+    if ((r6->flags & RT_METRIC_DEFINED) && (r6->metric > 0))
     {
-        argv_printf_cat(&argv, "via %s", gateway);
+        metric = r6->metric;
     }
-#else  /* ifdef ENABLE_IPROUTE */
-    argv_printf(&argv, "%s -A inet6 del %s/%d dev %s",
-                ROUTE_PATH,
-                network,
-                r6->netbits,
-                device);
-    if (gateway_needed)
+
+    if (net_route_v6_del(ctx, &r6->network, r6->netbits,
+                         gateway_needed ? &r6->gateway : NULL, device, 0,
+                         metric) < 0)
     {
-        argv_printf_cat(&argv, "gw %s", gateway);
+        msg(M_WARN, "ERROR: Linux route v6 delete command failed");
     }
-    if ( (r6->flags & RT_METRIC_DEFINED) && r6->metric > 0)
-    {
-        argv_printf_cat(&argv, " metric %d", r6->metric);
-    }
-#endif  /*ENABLE_IPROUTE*/
-    argv_msg(D_ROUTE, &argv);
-    openvpn_execve_check(&argv, es, 0, "ERROR: Linux route -6/-A inet6 del command failed");
 
 #elif defined (_WIN32)
 
@@ -2413,25 +2434,24 @@
     }
     else
     {
-        struct buffer out = alloc_buf_gc(64, &gc);
+        DWORD adapter_index;
         if (r6->adapter_index)          /* vpn server special route */
         {
-            buf_printf(&out, "interface=%lu", r6->adapter_index );
+            adapter_index = r6->adapter_index;
             gateway_needed = true;
         }
         else
         {
-            buf_printf(&out, "interface=%lu", tt->adapter_index );
+            adapter_index = tt->adapter_index;
         }
-        device = buf_bptr(&out);
 
-        /* netsh interface ipv6 delete route 2001:db8::/32 MyTunDevice */
-        argv_printf(&argv, "%s%sc interface ipv6 delete route %s/%d %s",
+        /* netsh interface ipv6 delete route 2001:db8::/32 42 */
+        argv_printf(&argv, "%s%s interface ipv6 delete route %s/%d %lu",
                     get_win_sys_path(),
                     NETSH_PATH_SUFFIX,
                     network,
                     r6->netbits,
-                    device);
+                    adapter_index);
 
         /* next-hop depends on TUN or TAP mode:
          * - in TAP mode, we use the "real" next-hop
@@ -2548,8 +2568,10 @@
     msg(M_FATAL, "Sorry, but I don't know how to do 'route ipv6' commands on this operating system.  Try putting your routes in a --route-down script");
 #endif /* if defined(TARGET_LINUX) */
 
-    argv_reset(&argv);
+    argv_free(&argv);
     gc_free(&gc);
+    /* release resources potentially allocated during route cleanup */
+    net_ctx_reset(ctx);
 }
 
 /*
@@ -2642,7 +2664,11 @@
         ret = true;
         adapter_up = true;
 
-        if (rl)
+        /* we do this test only if we have IPv4 routes to install, and if
+         * the tun/tap interface has seen IPv4 ifconfig - because if we
+         * have no IPv4, the check will always fail, failing tun init
+         */
+        if (rl && tt->did_ifconfig_setup)
         {
             struct route_ipv4 *r;
             for (r = rl->routes, len = 0; r; r = r->next, ++len)
@@ -2711,7 +2737,7 @@
 }
 
 void
-get_default_gateway(struct route_gateway_info *rgi)
+get_default_gateway(struct route_gateway_info *rgi, openvpn_net_ctx_t *ctx)
 {
     struct gc_arena gc = gc_new();
 
@@ -2798,7 +2824,7 @@
  */
 void
 get_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6,
-                         const struct in6_addr *dest)
+                         const struct in6_addr *dest, openvpn_net_ctx_t *ctx)
 {
     struct gc_arena gc = gc_new();
     MIB_IPFORWARD_ROW2 BestRoute;
@@ -2817,7 +2843,7 @@
         DestinationAddress.Ipv6.sin6_addr = *dest;
     }
 
-    status = GetBestInterfaceEx( &DestinationAddress, &BestIfIndex );
+    status = GetBestInterfaceEx( (struct sockaddr *)&DestinationAddress, &BestIfIndex );
 
     if (status != NO_ERROR)
     {
@@ -2987,16 +3013,12 @@
 static bool
 do_route_service(const bool add, const route_message_t *rt, const size_t size, HANDLE pipe)
 {
-    DWORD len;
     bool ret = false;
     ack_message_t ack;
     struct gc_arena gc = gc_new();
 
-    if (!WriteFile(pipe, rt, size, &len, NULL)
-        || !ReadFile(pipe, &ack, sizeof(ack), &len, NULL))
+    if (!send_msg_iservice(pipe, rt, size, &ack, "ROUTE"))
     {
-        msg(M_WARN, "ROUTE: could not talk to service: %s [%lu]",
-            strerror_win32(GetLastError(), &gc), GetLastError());
         goto out;
     }
 
@@ -3074,7 +3096,7 @@
      * (only do this for routes actually using the tun/tap device)
      */
     if (tt->type == DEV_TYPE_TUN
-	 && msg.iface.index == tt->adapter_index )
+        && msg.iface.index == tt->adapter_index)
     {
         inet_pton(AF_INET6, "fe80::8", &msg.gateway.ipv6);
     }
@@ -3160,78 +3182,11 @@
     gc_free(&gc);
 }
 
-#elif defined(TARGET_LINUX) || defined(TARGET_ANDROID)
+#elif defined(TARGET_ANDROID)
 
 void
-get_default_gateway(struct route_gateway_info *rgi)
+get_default_gateway(struct route_gateway_info *rgi, openvpn_net_ctx_t *ctx)
 {
-    struct gc_arena gc = gc_new();
-    int sd = -1;
-    char best_name[16];
-    best_name[0] = 0;
-
-    CLEAR(*rgi);
-
-#ifndef TARGET_ANDROID
-    /* get default gateway IP addr */
-    {
-        FILE *fp = fopen("/proc/net/route", "r");
-        if (fp)
-        {
-            char line[256];
-            int count = 0;
-            unsigned int lowest_metric = UINT_MAX;
-            in_addr_t best_gw = 0;
-            bool found = false;
-            while (fgets(line, sizeof(line), fp) != NULL)
-            {
-                if (count)
-                {
-                    unsigned int net_x = 0;
-                    unsigned int mask_x = 0;
-                    unsigned int gw_x = 0;
-                    unsigned int metric = 0;
-                    unsigned int flags = 0;
-                    char name[16];
-                    name[0] = 0;
-                    const int np = sscanf(line, "%15s\t%x\t%x\t%x\t%*s\t%*s\t%d\t%x",
-                                          name,
-                                          &net_x,
-                                          &gw_x,
-                                          &flags,
-                                          &metric,
-                                          &mask_x);
-                    if (np == 6 && (flags & IFF_UP))
-                    {
-                        const in_addr_t net = ntohl(net_x);
-                        const in_addr_t mask = ntohl(mask_x);
-                        const in_addr_t gw = ntohl(gw_x);
-
-                        if (!net && !mask && metric < lowest_metric)
-                        {
-                            found = true;
-                            best_gw = gw;
-                            strcpy(best_name, name);
-                            lowest_metric = metric;
-                        }
-                    }
-                }
-                ++count;
-            }
-            fclose(fp);
-
-            if (found)
-            {
-                rgi->gateway.addr = best_gw;
-                rgi->flags |= RGI_ADDR_DEFINED;
-                if (!rgi->gateway.addr && best_name[0])
-                {
-                    rgi->flags |= RGI_ON_LINK;
-                }
-            }
-        }
-    }
-#else  /* ifndef TARGET_ANDROID */
     /* Android, set some pseudo GW, addr is in host byte order,
      * Determining the default GW on Android 5.0+ is non trivial
      * and serves almost no purpose since OpenVPN only uses the
@@ -3240,10 +3195,55 @@
      * (127.'d'.'g'.'w') for the default GW make detecting
      * these routes easier from the controlling app.
      */
+    CLEAR(*rgi);
+
     rgi->gateway.addr = 127 << 24 | 'd' << 16 | 'g' << 8 | 'w';
-    rgi->flags |= RGI_ADDR_DEFINED;
-    strcpy(best_name, "android-gw");
-#endif /* ifndef TARGET_ANDROID */
+    rgi->flags = RGI_ADDR_DEFINED | RGI_IFACE_DEFINED;
+    strcpy(rgi->iface, "android-gw");
+
+    /* Skip scanning/fetching interface from loopback interface we do
+     * normally on Linux.
+     * It always fails and "ioctl(SIOCGIFCONF) failed" confuses users
+     */
+
+}
+
+void
+get_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6,
+                         const struct in6_addr *dest, openvpn_net_ctx_t *ctx)
+{
+    /* Same for ipv6 */
+
+    CLEAR(*rgi6);
+
+    /* Use a fake link-local address */
+    ASSERT(inet_pton(AF_INET6, "fe80::ad", &rgi6->addrs->addr_ipv6) == 1);
+    rgi6->addrs->netbits_ipv6 = 64;
+    rgi6->flags = RGI_ADDR_DEFINED | RGI_IFACE_DEFINED;
+    strcpy(rgi6->iface, "android-gw");
+}
+
+#elif defined(TARGET_LINUX)
+
+void
+get_default_gateway(struct route_gateway_info *rgi, openvpn_net_ctx_t *ctx)
+{
+    struct gc_arena gc = gc_new();
+    int sd = -1;
+    char best_name[IFNAMSIZ];
+
+    CLEAR(*rgi);
+    CLEAR(best_name);
+
+    /* get default gateway IP addr */
+    if (net_route_v4_best_gw(ctx, NULL, &rgi->gateway.addr, best_name) == 0)
+    {
+        rgi->flags |= RGI_ADDR_DEFINED;
+        if (!rgi->gateway.addr && best_name[0])
+        {
+            rgi->flags |= RGI_ON_LINK;
+        }
+    }
 
     /* scan adapter list */
     if (rgi->flags & RGI_ADDR_DEFINED)
@@ -3292,7 +3292,7 @@
                 if (rgi->flags & RGI_ON_LINK)
                 {
                     /* check that interface name of current interface
-                     * matches interface name of best default route */
+                    * matches interface name of best default route */
                     if (strcmp(ifreq.ifr_name, best_name))
                     {
                         continue;
@@ -3369,152 +3369,29 @@
 
 void
 get_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6,
-                         const struct in6_addr *dest)
+                         const struct in6_addr *dest, openvpn_net_ctx_t *ctx)
 {
-    int nls = -1;
-    struct rtreq rtreq;
-    struct rtattr *rta;
-
-    char rtbuf[2000];
-    ssize_t ssize;
+    int flags;
 
     CLEAR(*rgi6);
 
-    nls = socket( PF_NETLINK, SOCK_RAW, NETLINK_ROUTE );
-    if (nls < 0)
+    if (net_route_v6_best_gw(ctx, dest, &rgi6->gateway.addr_ipv6,
+                             rgi6->iface) == 0)
     {
-        msg(M_WARN|M_ERRNO, "GDG6: socket() failed" ); goto done;
-    }
-
-    /* bind() is not needed, no unsolicited msgs coming in */
-
-    /* request best matching route, see netlink(7) for explanations
-     */
-    CLEAR(rtreq);
-    rtreq.nh.nlmsg_type = RTM_GETROUTE;
-    rtreq.nh.nlmsg_flags = NLM_F_REQUEST;       /* best match only */
-    rtreq.rtm.rtm_family = AF_INET6;
-    rtreq.rtm.rtm_src_len = 0;                  /* not source dependent */
-    rtreq.rtm.rtm_dst_len = 128;                /* exact dst */
-    rtreq.rtm.rtm_table = RT_TABLE_MAIN;
-    rtreq.rtm.rtm_protocol = RTPROT_UNSPEC;
-    rtreq.nh.nlmsg_len = NLMSG_SPACE(sizeof(rtreq.rtm));
-
-    /* set RTA_DST for target IPv6 address we want */
-    rta = (struct rtattr *)(((char *) &rtreq)+NLMSG_ALIGN(rtreq.nh.nlmsg_len));
-    rta->rta_type = RTA_DST;
-    rta->rta_len = RTA_LENGTH(16);
-    rtreq.nh.nlmsg_len = NLMSG_ALIGN(rtreq.nh.nlmsg_len)
-                         +RTA_LENGTH(16);
-
-    if (dest == NULL)                   /* ::, unspecified */
-    {
-        memset( RTA_DATA(rta), 0, 16 ); /* :: = all-zero */
-    }
-    else
-    {
-        memcpy( RTA_DATA(rta), (void *)dest, 16 );
-    }
-
-    /* send and receive reply */
-    if (send( nls, &rtreq, rtreq.nh.nlmsg_len, 0 ) < 0)
-    {
-        msg(M_WARN|M_ERRNO, "GDG6: send() failed" ); goto done;
-    }
-
-    ssize = recv(nls, rtbuf, sizeof(rtbuf), MSG_TRUNC);
-
-    if (ssize < 0)
-    {
-        msg(M_WARN|M_ERRNO, "GDG6: recv() failed" ); goto done;
-    }
-
-    if (ssize > sizeof(rtbuf))
-    {
-        msg(M_WARN, "get_default_gateway_ipv6: returned message too big for buffer (%d>%d)", (int)ssize, (int)sizeof(rtbuf) );
-        goto done;
-    }
-
-    struct nlmsghdr *nh;
-
-    for (nh = (struct nlmsghdr *)rtbuf;
-         NLMSG_OK(nh, ssize);
-         nh = NLMSG_NEXT(nh, ssize))
-    {
-        struct rtmsg *rtm;
-        int attrlen;
-
-        if (nh->nlmsg_type == NLMSG_DONE)
+        if (!IN6_IS_ADDR_UNSPECIFIED(rgi6->gateway.addr_ipv6.s6_addr))
         {
-            break;
+            rgi6->flags |= RGI_ADDR_DEFINED;
         }
 
-        if (nh->nlmsg_type == NLMSG_ERROR)
+        if (strlen(rgi6->iface) > 0)
         {
-            struct nlmsgerr *ne = (struct nlmsgerr *)NLMSG_DATA(nh);
-
-            /* since linux-4.11 -ENETUNREACH is returned when no route can be
-             * found. Don't print any error message in this case */
-            if (ne->error != -ENETUNREACH)
-            {
-                msg(M_WARN, "GDG6: NLMSG_ERROR: error %s\n",
-                    strerror(-ne->error));
-            }
-            break;
-        }
-
-        if (nh->nlmsg_type != RTM_NEWROUTE)
-        {
-            /* shouldn't happen */
-            msg(M_WARN, "GDG6: unexpected msg_type %d", nh->nlmsg_type );
-            continue;
-        }
-
-        rtm = (struct rtmsg *)NLMSG_DATA(nh);
-        attrlen = RTM_PAYLOAD(nh);
-
-        /* we're only looking for routes in the main table, as "we have
-         * no IPv6" will lead to a lookup result in "Local" (::/0 reject)
-         */
-        if (rtm->rtm_family != AF_INET6
-            || rtm->rtm_table != RT_TABLE_MAIN)
-        {
-            continue;
-        }                               /* we're not interested */
-
-        for (rta = RTM_RTA(rtm);
-             RTA_OK(rta, attrlen);
-             rta = RTA_NEXT(rta, attrlen))
-        {
-            if (rta->rta_type == RTA_GATEWAY)
-            {
-                if (RTA_PAYLOAD(rta) != sizeof(struct in6_addr) )
-                {
-                    msg(M_WARN, "GDG6: RTA_GW size mismatch"); continue;
-                }
-                rgi6->gateway.addr_ipv6 = *(struct in6_addr *) RTA_DATA(rta);
-                rgi6->flags |= RGI_ADDR_DEFINED;
-            }
-            else if (rta->rta_type == RTA_OIF)
-            {
-                char ifname[IF_NAMESIZE+1];
-                int oif;
-                if (RTA_PAYLOAD(rta) != sizeof(oif) )
-                {
-                    msg(M_WARN, "GDG6: oif size mismatch"); continue;
-                }
-
-                memcpy(&oif, RTA_DATA(rta), sizeof(oif));
-                if_indextoname(oif,ifname);
-                strncpy( rgi6->iface, ifname, sizeof(rgi6->iface)-1 );
-                rgi6->flags |= RGI_IFACE_DEFINED;
-            }
+            rgi6->flags |= RGI_IFACE_DEFINED;
         }
     }
 
     /* if we have an interface but no gateway, the destination is on-link */
-    if ( ( rgi6->flags & (RGI_IFACE_DEFINED|RGI_ADDR_DEFINED) ) ==
-         RGI_IFACE_DEFINED)
+    flags = rgi6->flags & (RGI_IFACE_DEFINED | RGI_ADDR_DEFINED);
+    if (flags == RGI_IFACE_DEFINED)
     {
         rgi6->flags |= (RGI_ADDR_DEFINED | RGI_ON_LINK);
         if (dest)
@@ -3522,12 +3399,6 @@
             rgi6->gateway.addr_ipv6 = *dest;
         }
     }
-
-done:
-    if (nls >= 0)
-    {
-        close(nls);
-    }
 }
 
 #elif defined(TARGET_DARWIN) || defined(TARGET_SOLARIS)    \
@@ -3547,11 +3418,15 @@
 
 /* the route socket code is identical for all 4 supported BSDs and for
  * MacOS X (Darwin), with one crucial difference: when going from
- * 32 bit to 64 bit, the BSDs increased the structure size but kept
+ * 32 bit to 64 bit, FreeBSD/OpenBSD increased the structure size but kept
  * source code compatibility by keeping the use of "long", while
  * MacOS X decided to keep binary compatibility by *changing* the API
  * to use "uint32_t", thus 32 bit on all OS X variants
  *
+ * NetBSD does the MacOS way of "fixed number of bits, no matter if
+ * 32 or 64 bit OS", but chose uint64_t.  For maximum portability, we
+ * just use the OS RT_ROUNDUP() macro, which is guaranteed to be correct.
+ *
  * We used to have a large amount of duplicate code here which really
  * differed only in this (long) vs. (uint32_t) - IMHO, worse than
  * having a combined block for all BSDs with this single #ifdef inside
@@ -3560,6 +3435,8 @@
 #if defined(TARGET_DARWIN)
 #define ROUNDUP(a) \
     ((a) > 0 ? (1 + (((a) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t))
+#elif defined(TARGET_NETBSD)
+#define ROUNDUP(a) RT_ROUNDUP(a)
 #else
 #define ROUNDUP(a) \
     ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
@@ -3568,14 +3445,14 @@
 #if defined(TARGET_SOLARIS)
 #define NEXTADDR(w, u) \
     if (rtm_addrs & (w)) { \
-        l = ROUNDUP(sizeof(u)); memmove(cp, &(u), l); cp += l; \
+        l = sizeof(u); memmove(cp, &(u), l); cp += ROUNDUP(l); \
     }
 
 #define ADVANCE(x, n) (x += ROUNDUP(sizeof(struct sockaddr_in)))
 #else  /* if defined(TARGET_SOLARIS) */
 #define NEXTADDR(w, u) \
     if (rtm_addrs & (w)) { \
-        l = ROUNDUP( ((struct sockaddr *)&(u))->sa_len); memmove(cp, &(u), l); cp += l; \
+        l = ((struct sockaddr *)&(u))->sa_len; memmove(cp, &(u), l); cp += ROUNDUP(l); \
     }
 
 #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
@@ -3584,7 +3461,7 @@
 #define max(a,b) ((a) > (b) ? (a) : (b))
 
 void
-get_default_gateway(struct route_gateway_info *rgi)
+get_default_gateway(struct route_gateway_info *rgi, openvpn_net_ctx_t *ctx)
 {
     struct gc_arena gc = gc_new();
     struct rtmsg m_rtmsg;
@@ -3804,7 +3681,7 @@
 
 void
 get_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6,
-                         const struct in6_addr *dest)
+                         const struct in6_addr *dest, openvpn_net_ctx_t *ctx)
 {
 
     struct rtmsg m_rtmsg;
@@ -3868,7 +3745,7 @@
     }
     if (write(sockfd, (char *)&m_rtmsg, l) < 0)
     {
-        msg(M_WARN, "GDG6: problem writing to routing socket");
+        msg(M_WARN|M_ERRNO, "GDG6: problem writing to routing socket");
         goto done;
     }
 
@@ -3984,13 +3861,13 @@
  * may be disabled by missing items.
  */
 void
-get_default_gateway(struct route_gateway_info *rgi)
+get_default_gateway(struct route_gateway_info *rgi, openvpn_net_ctx_t *ctx)
 {
     CLEAR(*rgi);
 }
 void
 get_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6,
-                         const struct in6_addr *dest)
+                         const struct in6_addr *dest, openvpn_net_ctx_t *ctx)
 {
     msg(D_ROUTE, "no support for get_default_gateway_ipv6() on this system");
     CLEAR(*rgi6);
diff --git a/src/openvpn/route.h b/src/openvpn/route.h
index 6942022..7dd9609 100644
--- a/src/openvpn/route.h
+++ b/src/openvpn/route.h
@@ -31,6 +31,7 @@
 #include "basic.h"
 #include "tun.h"
 #include "misc.h"
+#include "networking.h"
 
 #ifdef _WIN32
 /*
@@ -183,7 +184,11 @@
 #ifdef _WIN32
     DWORD adapter_index; /* interface or ~0 if undefined */
 #else
-    char iface[16]; /* interface name (null terminated), may be empty */
+    /* non linux platform don't have this constant defined */
+#ifndef IFNAMSIZ
+#define IFNAMSIZ 16
+#endif
+    char iface[IFNAMSIZ]; /* interface name (null terminated), may be empty */
 #endif
 
     /* gateway interface hardware address */
@@ -256,15 +261,16 @@
 
 void route_ipv6_clear_host_bits( struct route_ipv6 *r6 );
 
-void add_route_ipv6(struct route_ipv6 *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es);
+void add_route_ipv6(struct route_ipv6 *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es, openvpn_net_ctx_t *ctx);
 
-void delete_route_ipv6(const struct route_ipv6 *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es);
+void delete_route_ipv6(const struct route_ipv6 *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es, openvpn_net_ctx_t *ctx);
 
 void add_route(struct route_ipv4 *r,
                const struct tuntap *tt,
                unsigned int flags,
                const struct route_gateway_info *rgi,
-               const struct env_set *es);
+               const struct env_set *es,
+               openvpn_net_ctx_t *ctx);
 
 void add_route_to_option_list(struct route_option_list *l,
                               const char *network,
@@ -282,14 +288,16 @@
                      const char *remote_endpoint,
                      int default_metric,
                      in_addr_t remote_host,
-                     struct env_set *es);
+                     struct env_set *es,
+                     openvpn_net_ctx_t *ctx);
 
 bool init_route_ipv6_list(struct route_ipv6_list *rl6,
                           const struct route_ipv6_option_list *opt6,
                           const char *remote_endpoint,
                           int default_metric,
                           const struct in6_addr *remote_host,
-                          struct env_set *es);
+                          struct env_set *es,
+                          openvpn_net_ctx_t *ctx);
 
 void route_list_add_vpn_gateway(struct route_list *rl,
                                 struct env_set *es,
@@ -299,26 +307,28 @@
                 struct route_ipv6_list *rl6,
                 const struct tuntap *tt,
                 unsigned int flags,
-                const struct env_set *es);
+                const struct env_set *es,
+                openvpn_net_ctx_t *ctx);
 
 void delete_routes(struct route_list *rl,
                    struct route_ipv6_list *rl6,
                    const struct tuntap *tt,
                    unsigned int flags,
-                   const struct env_set *es);
+                   const struct env_set *es,
+                   openvpn_net_ctx_t *ctx);
 
 void setenv_routes(struct env_set *es, const struct route_list *rl);
 
 void setenv_routes_ipv6(struct env_set *es, const struct route_ipv6_list *rl6);
 
-
-
 bool is_special_addr(const char *addr_str);
 
-void get_default_gateway(struct route_gateway_info *rgi);
+void get_default_gateway(struct route_gateway_info *rgi,
+                         openvpn_net_ctx_t *ctx);
 
 void get_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi,
-                              const struct in6_addr *dest);
+                              const struct in6_addr *dest,
+                              openvpn_net_ctx_t *ctx);
 
 void print_default_gateway(const int msglevel,
                            const struct route_gateway_info *rgi,
diff --git a/src/openvpn/run_command.c b/src/openvpn/run_command.c
new file mode 100644
index 0000000..4c4adf9
--- /dev/null
+++ b/src/openvpn/run_command.c
@@ -0,0 +1,288 @@
+/*
+ *  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) 2002-2017 OpenVPN Technologies, Inc. <sales@openvpn.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#include "buffer.h"
+#include "error.h"
+#include "platform.h"
+#include "win32.h"
+
+#include "memdbg.h"
+
+#include "run_command.h"
+
+/* contains an SSEC_x value defined in platform.h */
+static int script_security_level = SSEC_BUILT_IN; /* GLOBAL */
+
+int
+script_security(void)
+{
+    return script_security_level;
+}
+
+void
+script_security_set(int level)
+{
+    script_security_level = level;
+}
+
+/*
+ * Generate an error message based on the status code returned by openvpn_execve().
+ */
+static const char *
+system_error_message(int stat, struct gc_arena *gc)
+{
+    struct buffer out = alloc_buf_gc(256, gc);
+
+    switch (stat)
+    {
+        case OPENVPN_EXECVE_NOT_ALLOWED:
+            buf_printf(&out, "disallowed by script-security setting");
+            break;
+
+#ifdef _WIN32
+        case OPENVPN_EXECVE_ERROR:
+            buf_printf(&out, "external program did not execute -- ");
+        /* fall through */
+
+        default:
+            buf_printf(&out, "returned error code %d", stat);
+            break;
+#else  /* ifdef _WIN32 */
+
+        case OPENVPN_EXECVE_ERROR:
+            buf_printf(&out, "external program fork failed");
+            break;
+
+        default:
+            if (!WIFEXITED(stat))
+            {
+                buf_printf(&out, "external program did not exit normally");
+            }
+            else
+            {
+                const int cmd_ret = WEXITSTATUS(stat);
+                if (!cmd_ret)
+                {
+                    buf_printf(&out, "external program exited normally");
+                }
+                else if (cmd_ret == OPENVPN_EXECVE_FAILURE)
+                {
+                    buf_printf(&out, "could not execute external program");
+                }
+                else
+                {
+                    buf_printf(&out, "external program exited with error status: %d", cmd_ret);
+                }
+            }
+            break;
+#endif /* ifdef _WIN32 */
+    }
+    return (const char *)out.data;
+}
+
+bool
+openvpn_execve_allowed(const unsigned int flags)
+{
+    if (flags & S_SCRIPT)
+    {
+        return script_security() >= SSEC_SCRIPTS;
+    }
+    else
+    {
+        return script_security() >= SSEC_BUILT_IN;
+    }
+}
+
+
+#ifndef _WIN32
+/*
+ * Run execve() inside a fork().  Designed to replicate the semantics of system() but
+ * in a safer way that doesn't require the invocation of a shell or the risks
+ * associated with formatting and parsing a command line.
+ * Returns the exit status of child, OPENVPN_EXECVE_NOT_ALLOWED if openvpn_execve_allowed()
+ * returns false, or OPENVPN_EXECVE_ERROR on other errors.
+ */
+int
+openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags)
+{
+    struct gc_arena gc = gc_new();
+    int ret = OPENVPN_EXECVE_ERROR;
+    static bool warn_shown = false;
+
+    if (a && a->argv[0])
+    {
+#if defined(ENABLE_FEATURE_EXECVE)
+        if (openvpn_execve_allowed(flags))
+        {
+            const char *cmd = a->argv[0];
+            char *const *argv = a->argv;
+            char *const *envp = (char *const *)make_env_array(es, true, &gc);
+            pid_t pid;
+
+            pid = fork();
+            if (pid == (pid_t)0) /* child side */
+            {
+                execve(cmd, argv, envp);
+                exit(OPENVPN_EXECVE_FAILURE);
+            }
+            else if (pid < (pid_t)0) /* fork failed */
+            {
+                msg(M_ERR, "openvpn_execve: unable to fork");
+            }
+            else /* parent side */
+            {
+                if (waitpid(pid, &ret, 0) != pid)
+                {
+                    ret = OPENVPN_EXECVE_ERROR;
+                }
+            }
+        }
+        else
+        {
+            ret = OPENVPN_EXECVE_NOT_ALLOWED;
+            if (!warn_shown && (script_security() < SSEC_SCRIPTS))
+            {
+                msg(M_WARN, SCRIPT_SECURITY_WARNING);
+                warn_shown = true;
+            }
+        }
+#else  /* if defined(ENABLE_FEATURE_EXECVE) */
+        msg(M_WARN, "openvpn_execve: execve function not available");
+#endif /* if defined(ENABLE_FEATURE_EXECVE) */
+    }
+    else
+    {
+        msg(M_FATAL, "openvpn_execve: called with empty argv");
+    }
+
+    gc_free(&gc);
+    return ret;
+}
+#endif /* ifndef _WIN32 */
+
+/*
+ * Wrapper around openvpn_execve
+ */
+bool
+openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message)
+{
+    struct gc_arena gc = gc_new();
+    const int stat = openvpn_execve(a, es, flags);
+    int ret = false;
+
+    if (platform_system_ok(stat))
+    {
+        ret = true;
+    }
+    else
+    {
+        if (error_message)
+        {
+            msg(((flags & S_FATAL) ? M_FATAL : M_WARN), "%s: %s",
+                error_message,
+                system_error_message(stat, &gc));
+        }
+    }
+    gc_free(&gc);
+    return ret;
+}
+
+/*
+ * Run execve() inside a fork(), duping stdout.  Designed to replicate the semantics of popen() but
+ * in a safer way that doesn't require the invocation of a shell or the risks
+ * associated with formatting and parsing a command line.
+ */
+int
+openvpn_popen(const struct argv *a,  const struct env_set *es)
+{
+    struct gc_arena gc = gc_new();
+    int ret = -1;
+    static bool warn_shown = false;
+
+    if (a && a->argv[0])
+    {
+#if defined(ENABLE_FEATURE_EXECVE)
+        if (script_security() >= SSEC_BUILT_IN)
+        {
+            const char *cmd = a->argv[0];
+            char *const *argv = a->argv;
+            char *const *envp = (char *const *)make_env_array(es, true, &gc);
+            pid_t pid;
+            int pipe_stdout[2];
+
+            if (pipe(pipe_stdout) == 0)
+            {
+                pid = fork();
+                if (pid == (pid_t)0)       /* child side */
+                {
+                    close(pipe_stdout[0]);         /* Close read end */
+                    dup2(pipe_stdout[1],1);
+                    execve(cmd, argv, envp);
+                    exit(OPENVPN_EXECVE_FAILURE);
+                }
+                else if (pid > (pid_t)0)       /* parent side */
+                {
+                    int status = 0;
+
+                    close(pipe_stdout[1]);        /* Close write end */
+                    waitpid(pid, &status, 0);
+                    ret = pipe_stdout[0];
+                }
+                else       /* fork failed */
+                {
+                    close(pipe_stdout[0]);
+                    close(pipe_stdout[1]);
+                    msg(M_ERR, "openvpn_popen: unable to fork %s", cmd);
+                }
+            }
+            else
+            {
+                msg(M_WARN, "openvpn_popen: unable to create stdout pipe for %s", cmd);
+                ret = -1;
+            }
+        }
+        else if (!warn_shown && (script_security() < SSEC_SCRIPTS))
+        {
+            msg(M_WARN, SCRIPT_SECURITY_WARNING);
+            warn_shown = true;
+        }
+#else  /* if defined(ENABLE_FEATURE_EXECVE) */
+        msg(M_WARN, "openvpn_popen: execve function not available");
+#endif /* if defined(ENABLE_FEATURE_EXECVE) */
+    }
+    else
+    {
+        msg(M_FATAL, "openvpn_popen: called with empty argv");
+    }
+
+    gc_free(&gc);
+    return ret;
+}
diff --git a/src/openvpn/run_command.h b/src/openvpn/run_command.h
new file mode 100644
index 0000000..7ccb13c
--- /dev/null
+++ b/src/openvpn/run_command.h
@@ -0,0 +1,67 @@
+/*
+ *  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) 2002-2017 OpenVPN Technologies, Inc. <sales@openvpn.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef RUN_COMMAND_H
+#define RUN_COMMAND_H
+
+#include "basic.h"
+#include "env_set.h"
+
+/* Script security */
+#define SSEC_NONE      0 /* strictly no calling of external programs */
+#define SSEC_BUILT_IN  1 /* only call built-in programs such as ifconfig, route, netsh, etc.*/
+#define SSEC_SCRIPTS   2 /* allow calling of built-in programs and user-defined scripts */
+#define SSEC_PW_ENV    3 /* allow calling of built-in programs and user-defined scripts that may receive a password as an environmental variable */
+
+#define OPENVPN_EXECVE_ERROR       -1 /* generic error while forking to run an external program */
+#define OPENVPN_EXECVE_NOT_ALLOWED -2 /* external program not run due to script security */
+#define OPENVPN_EXECVE_FAILURE    127 /* exit code passed back from child when execve fails */
+
+int script_security(void);
+
+void script_security_set(int level);
+
+/* openvpn_execve flags */
+#define S_SCRIPT (1<<0)
+#define S_FATAL  (1<<1)
+
+/* wrapper around the execve() call */
+int openvpn_popen(const struct argv *a,  const struct env_set *es);
+
+bool openvpn_execve_allowed(const unsigned int flags);
+
+bool openvpn_execve_check(const struct argv *a, const struct env_set *es,
+                          const unsigned int flags, const char *error_message);
+
+static inline bool
+openvpn_run_script(const struct argv *a, const struct env_set *es,
+                   const unsigned int flags, const char *hook)
+{
+    char msg[256];
+
+    openvpn_snprintf(msg, sizeof(msg),
+                     "WARNING: Failed running command (%s)", hook);
+    return openvpn_execve_check(a, es, flags | S_SCRIPT, msg);
+}
+
+#endif /* ifndef RUN_COMMAND_H */
diff --git a/src/openvpn/schedule.c b/src/openvpn/schedule.c
index 76cf7c3..13be323 100644
--- a/src/openvpn/schedule.c
+++ b/src/openvpn/schedule.c
@@ -29,8 +29,6 @@
 
 #include "syshead.h"
 
-#if P2MP_SERVER
-
 #include "buffer.h"
 #include "misc.h"
 #include "crypto.h"
@@ -723,4 +721,3 @@
 }
 
 #endif /* ifdef SCHEDULE_TEST */
-#endif /* if P2MP_SERVER */
diff --git a/src/openvpn/schedule.h b/src/openvpn/schedule.h
index 74d37fb..8c476fd 100644
--- a/src/openvpn/schedule.h
+++ b/src/openvpn/schedule.h
@@ -35,8 +35,6 @@
  * a ping or scheduling a TLS renegotiation.
  */
 
-#if P2MP_SERVER
-
 /* define to enable a special test mode */
 /*#define SCHEDULE_TEST*/
 
@@ -136,5 +134,4 @@
     return ret;
 }
 
-#endif /* if P2MP_SERVER */
 #endif /* ifndef SCHEDULE_H */
diff --git a/src/openvpn/session_id.c b/src/openvpn/session_id.c
index 2b50feb..d57609c 100644
--- a/src/openvpn/session_id.c
+++ b/src/openvpn/session_id.c
@@ -38,8 +38,6 @@
 
 #include "syshead.h"
 
-#ifdef ENABLE_CRYPTO
-
 #include "error.h"
 #include "common.h"
 #include "crypto.h"
@@ -60,10 +58,3 @@
 {
     return format_hex(sid->id, SID_SIZE, 0, gc);
 }
-
-#else  /* ifdef ENABLE_CRYPTO */
-static void
-dummy(void)
-{
-}
-#endif /* ENABLE_CRYPTO */
diff --git a/src/openvpn/session_id.h b/src/openvpn/session_id.h
index 5e950a6..c0a128d 100644
--- a/src/openvpn/session_id.h
+++ b/src/openvpn/session_id.h
@@ -29,8 +29,6 @@
  * negotiated).
  */
 
-#ifdef ENABLE_CRYPTO
-
 #ifndef SESSION_ID_H
 #define SESSION_ID_H
 
@@ -82,4 +80,3 @@
 const char *session_id_print(const struct session_id *sid, struct gc_arena *gc);
 
 #endif /* SESSION_ID_H */
-#endif /* ENABLE_CRYPTO */
diff --git a/src/openvpn/shaper.c b/src/openvpn/shaper.c
index 00eb2e9..6257984 100644
--- a/src/openvpn/shaper.c
+++ b/src/openvpn/shaper.c
@@ -76,8 +76,8 @@
         }
     }
 #ifdef SHAPER_DEBUG
-    dmsg(D_SHAPER_DEBUG, "SHAPER shaper_soonest_event sec=%d usec=%d ret=%d",
-         (int)tv->tv_sec, (int)tv->tv_usec, (int)ret);
+    dmsg(D_SHAPER_DEBUG, "SHAPER shaper_soonest_event sec=%" PRIi64 " usec=%ld ret=%d",
+         (int64_t)tv->tv_sec, (long)tv->tv_usec, (int)ret);
 #endif
     return ret;
 }
diff --git a/src/openvpn/shaper.h b/src/openvpn/shaper.h
index 0496c71..bcdb5e3 100644
--- a/src/openvpn/shaper.h
+++ b/src/openvpn/shaper.h
@@ -147,11 +147,11 @@
         tv_add(&s->wakeup, &tv);
 
 #ifdef SHAPER_DEBUG
-        dmsg(D_SHAPER_DEBUG, "SHAPER shaper_wrote_bytes bytes=%d delay=%d sec=%d usec=%d",
+        dmsg(D_SHAPER_DEBUG, "SHAPER shaper_wrote_bytes bytes=%d delay=%ld sec=%" PRIi64 " usec=%ld",
              nbytes,
-             (int)tv.tv_usec,
-             (int)s->wakeup.tv_sec,
-             (int)s->wakeup.tv_usec);
+             (long)tv.tv_usec,
+             (int64_t)s->wakeup.tv_sec,
+             (long)s->wakeup.tv_usec);
 #endif
     }
 }
diff --git a/src/openvpn/sig.c b/src/openvpn/sig.c
index d7f2abb..24a2878 100644
--- a/src/openvpn/sig.c
+++ b/src/openvpn/sig.c
@@ -317,8 +317,11 @@
 #ifdef _WIN32
     if (tuntap_defined(c->c1.tuntap))
     {
-        status_printf(so, "TAP-WIN32 driver status,\"%s\"",
-                      tap_win_getinfo(c->c1.tuntap, &gc));
+        const char *extended_msg = tap_win_getinfo(c->c1.tuntap, &gc);
+        if (extended_msg)
+        {
+            status_printf(so, "TAP-WIN32 driver status,\"%s\"", extended_msg);
+        }
     }
 #endif
 
@@ -327,7 +330,6 @@
     gc_free(&gc);
 }
 
-#ifdef ENABLE_OCC
 /*
  * Handle the triggering and time-wait of explicit
  * exit notification.
@@ -364,7 +366,6 @@
         }
     }
 }
-#endif /* ifdef ENABLE_OCC */
 
 /*
  * Process signals
@@ -392,14 +393,12 @@
 process_sigterm(struct context *c)
 {
     bool ret = true;
-#ifdef ENABLE_OCC
     if (c->options.ce.explicit_exit_notification
         && !c->c2.explicit_exit_notification_time_wait)
     {
         process_explicit_exit_notification_init(c);
         ret = false;
     }
-#endif
     return ret;
 }
 
@@ -412,7 +411,6 @@
 ignore_restart_signals(struct context *c)
 {
     bool ret = false;
-#ifdef ENABLE_OCC
     if ( (c->sig->signal_received == SIGUSR1 || c->sig->signal_received == SIGHUP)
          && event_timeout_defined(&c->c2.explicit_exit_notification_interval) )
     {
@@ -431,7 +429,6 @@
             ret = false;
         }
     }
-#endif
     return ret;
 }
 
diff --git a/src/openvpn/sig.h b/src/openvpn/sig.h
index 887d833..59f30fd 100644
--- a/src/openvpn/sig.h
+++ b/src/openvpn/sig.h
@@ -81,11 +81,8 @@
 
 void register_signal(struct context *c, int sig, const char *text);
 
-#ifdef ENABLE_OCC
 void process_explicit_exit_notification_timer_wakeup(struct context *c);
 
-#endif
-
 #ifdef _WIN32
 
 static inline void
diff --git a/src/openvpn/sitnl.h b/src/openvpn/sitnl.h
new file mode 100644
index 0000000..937522f
--- /dev/null
+++ b/src/openvpn/sitnl.h
@@ -0,0 +1,217 @@
+/*
+ *  Simplified Interface To NetLink
+ *
+ *  Copyright (C) 2016-2018 Antonio Quartulli <a@unstable.cc>
+ *
+ *  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 SITNL_H_
+#define SITNL_H_
+
+#ifdef TARGET_LINUX
+
+#include <stdbool.h>
+#include <netinet/in.h>
+
+/**
+ * Bring interface up or down.
+ *
+ * @param iface     the interface to modify
+ * @param up        true if the interface has to be brought up, false otherwise
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int sitnl_iface_up(const char *iface, bool up);
+
+/**
+ * Set the MTU for an interface
+ *
+ * @param iface     the interface to modify
+ * @param mtru      the new MTU
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int sitnl_iface_mtu_set(const char *iface, uint32_t mtu);
+
+/**
+ * Add an IPv4 address to an interface
+ *
+ * @param iface     the interface where the address has to be added
+ * @param addr      the address to add
+ * @param prefixlen the prefix length of the network associated with the address
+ * @param broadcast the broadcast address to configure on the interface
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int sitnl_addr_v4_add(const char *iface, const in_addr_t *addr, int prefixlen,
+                      const in_addr_t *broadcast);
+
+/**
+ * Add an IPv6 address to an interface
+ *
+ * @param iface     the interface where the address has to be added
+ * @param addr      the address to add
+ * @param prefixlen the prefix length of the network associated with the address
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+
+int sitnl_addr_v6_add(const char *iface, const struct in6_addr *addr,
+                      int prefixlen);
+
+/**
+ * Remove an IPv4 from an interface
+ *
+ * @param iface     the interface to remove the address from
+ * @param prefixlen the prefix length of the network associated with the address
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int sitnl_addr_v4_del(const char *iface, const in_addr_t *addr, int prefixlen);
+
+/**
+ * Remove an IPv6 from an interface
+ *
+ * @param iface     the interface to remove the address from
+ * @param prefixlen the prefix length of the network associated with the address
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int sitnl_addr_v6_del(const char *iface, const struct in6_addr *addr,
+                      int prefixlen);
+
+/**
+ * Add a point-to-point IPv4 address to an interface
+ *
+ * @param iface     the interface where the address has to be added
+ * @param local     the address to add
+ * @param remote    the associated p-t-p remote address
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int sitnl_addr_ptp_v4_add(const char *iface, const in_addr_t *local,
+                          const in_addr_t *remote);
+
+/**
+ * Remove a point-to-point IPv4 address from an interface
+ *
+ * @param iface     the interface to remove the address from
+ * @param local     the address to remove
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int sitnl_addr_ptp_v4_del(const char *iface, const in_addr_t *local);
+
+
+/**
+ * Add a route for an IPv4 address/network
+ *
+ * @param dst       the destination of the route
+ * @param prefixlen the length of the prefix of the destination
+ * @param gw        the gateway for this route
+ * @param iface     the interface for this route (can be NULL)
+ * @param table     the table to add this route to (if 0, will be added to the
+ *                  main table)
+ * @param metric    the metric associated with the route
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int sitnl_route_v4_add(const in_addr_t *dst, int prefixlen,
+                       const in_addr_t *gw, const char *iface, uint32_t table,
+                       int metric);
+
+/**
+ * Add a route for an IPv6 address/network
+ *
+ * @param dst       the destination of the route
+ * @param prefixlen the length of the prefix of the destination
+ * @param gw        the gateway for this route
+ * @param iface     the interface for this route (can be NULL)
+ * @param table     the table to add this route to (if 0, will be added to the
+ *                  main table)
+ * @param metric    the metric associated with the route
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int sitnl_route_v6_add(const struct in6_addr *dst, int prefixlen,
+                       const struct in6_addr *gw, const char *iface,
+                       uint32_t table, int metric);
+
+/**
+ * Delete a route for an IPv4 address/network
+ *
+ * @param dst       the destination of the route
+ * @param prefixlen the length of the prefix of the destination
+ * @param gw        the gateway for this route
+ * @param iface     the interface for this route (can be NULL)
+ * @param table     the table to add this route to (if 0, will be added to the
+ *                  main table)
+ * @param metric    the metric associated with the route
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int sitnl_route_v4_del(const in_addr_t *dst, int prefixlen,
+                       const in_addr_t *gw, const char *iface, uint32_t table,
+                       int metric);
+
+/**
+ * Delete a route for an IPv4 address/network
+ *
+ * @param dst       the destination of the route
+ * @param prefixlen the length of the prefix of the destination
+ * @param gw        the gateway for this route
+ * @param iface     the interface for this route (can be NULL)
+ * @param table     the table to add this route to (if 0, will be added to the
+ *                  main table)
+ * @param metric    the metric associated with the route
+ *
+ * @return          0 on success, a negative error code otherwise
+ */
+int sitnl_route_v6_del(const struct in6_addr *dst, int prefixlen,
+                       const struct in6_addr *gw, const char *iface,
+                       uint32_t table, int metric);
+
+/**
+ * Retrieve the gateway and outgoing interface for the specified IPv4
+ * address/network
+ *
+ * @param dst           The destination to lookup
+ * @param prefixlen     The length of the prefix of the destination
+ * @param best_gw       Location where the retrieved GW has to be stored
+ * @param best_iface    Location where the retrieved interface has to be stored
+ *
+ * @return              0 on success, a negative error code otherwise
+ */
+int sitnl_route_v4_best_gw(const in_addr_t *dst, int prefixlen,
+                           in_addr_t *best_gw, char *best_iface);
+
+/**
+ * Retrieve the gateway and outgoing interface for the specified IPv6
+ * address/network
+ *
+ * @param dst           The destination to lookup
+ * @param prefixlen     The length of the prefix of the destination
+ * @param best_gw       Location where the retrieved GW has to be stored
+ * @param best_iface    Location where the retrieved interface has to be stored
+ *
+ * @return              0 on success, a negative error code otherwise
+ */
+int sitnl_route_v6_best_gw(const struct in6_addr *dst, int prefixlen,
+                           struct in6_addr *best_gw, char *best_iface);
+
+#endif /* TARGET_LINUX */
+
+#endif /* SITNL_H_ */
diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c
index 9131ec2..9775068 100644
--- a/src/openvpn/socket.c
+++ b/src/openvpn/socket.c
@@ -35,6 +35,7 @@
 #include "gremlin.h"
 #include "plugin.h"
 #include "ps.h"
+#include "run_command.h"
 #include "manage.h"
 #include "misc.h"
 #include "manage.h"
@@ -99,10 +100,12 @@
             bits = 0;
             max_bits = sizeof(in_addr_t) * 8;
             break;
+
         case AF_INET6:
             bits = 64;
             max_bits = sizeof(struct in6_addr) * 8;
             break;
+
         default:
             msg(M_WARN,
                 "Unsupported AF family passed to getaddrinfo for %s (%d)",
@@ -124,7 +127,7 @@
     }
 
     /* check if this hostname has a /bits suffix */
-    sep = strchr(var_host , '/');
+    sep = strchr(var_host, '/');
     if (sep)
     {
         bits = strtoul(sep + 1, &endp, 10);
@@ -155,10 +158,12 @@
                     *ip4 = ntohl(*ip4);
                 }
                 break;
+
             case AF_INET6:
                 ip6 = network;
                 *ip6 = ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
                 break;
+
             default:
                 /* can't get here because 'af' was previously checked */
                 msg(M_WARN,
@@ -373,7 +378,8 @@
         /* HTTP remote hostname does not need to be resolved */
         if (!ce->http_proxy_options)
         {
-            status = do_preresolve_host(c, remote, ce->remote_port, ce->af, flags);
+            status = do_preresolve_host(c, remote, ce->remote_port,
+                                        ce->af, flags);
             if (status != 0)
             {
                 goto err;
@@ -412,7 +418,8 @@
         {
             flags |= GETADDR_PASSIVE;
             flags &= ~GETADDR_RANDOMIZE;
-            status = do_preresolve_host(c, ce->local, ce->local_port, ce->af, flags);
+            status = do_preresolve_host(c, ce->local, ce->local_port,
+                                        ce->af, flags);
             if (status != 0)
             {
                 goto err;
@@ -521,7 +528,9 @@
         if ((flags & GETADDR_MENTION_RESOLVE_RETRY)
             && !resolve_retry_seconds)
         {
-            fmt = "RESOLVE: Cannot resolve host address: %s:%s (%s) (I would have retried this name query if you had specified the --resolv-retry option.)";
+            fmt = "RESOLVE: Cannot resolve host address: %s:%s (%s) "
+                  "(I would have retried this name query if you had "
+                  "specified the --resolv-retry option.)";
         }
 
         if (!(flags & GETADDR_RESOLVE) || status == EAI_FAIL)
@@ -553,11 +562,13 @@
         while (true)
         {
 #ifndef _WIN32
+            /* force resolv.conf reload */
             res_init();
 #endif
             /* try hostname lookup */
             hints.ai_flags &= ~AI_NUMERICHOST;
-            dmsg(D_SOCKET_DEBUG, "GETADDRINFO flags=0x%04x ai_family=%d ai_socktype=%d",
+            dmsg(D_SOCKET_DEBUG,
+                 "GETADDRINFO flags=0x%04x ai_family=%d ai_socktype=%d",
                  flags, hints.ai_family, hints.ai_socktype);
             status = getaddrinfo(hostname, servname, &hints, res);
 
@@ -568,7 +579,9 @@
                 {
                     if (*signal_received == SIGUSR1) /* ignore SIGUSR1 */
                     {
-                        msg(level, "RESOLVE: Ignored SIGUSR1 signal received during DNS resolution attempt");
+                        msg(level,
+                            "RESOLVE: Ignored SIGUSR1 signal received during "
+                            "DNS resolution attempt");
                         *signal_received = 0;
                     }
                     else
@@ -629,7 +642,9 @@
         /* IP address parse succeeded */
         if (flags & GETADDR_RANDOMIZE)
         {
-            msg(M_WARN, "WARNING: ignoring --remote-random-hostname because the hostname is an IP address");
+            msg(M_WARN,
+                "WARNING: ignoring --remote-random-hostname because the "
+                "hostname is an IP address");
         }
     }
 
@@ -987,7 +1002,7 @@
 }
 
 /*
- * SOCKET INITALIZATION CODE.
+ * SOCKET INITIALIZATION CODE.
  * Create a TCP/UDP socket
  */
 
@@ -1133,6 +1148,18 @@
     /* set socket to --mark packets with given value */
     socket_set_mark(sock->sd, sock->mark);
 
+#if defined(TARGET_LINUX)
+    if (sock->bind_dev)
+    {
+        msg(M_INFO, "Using bind-dev %s", sock->bind_dev);
+        if (setsockopt(sock->sd, SOL_SOCKET, SO_BINDTODEVICE, sock->bind_dev, strlen(sock->bind_dev) + 1) != 0)
+        {
+            msg(M_WARN|M_ERRNO, "WARN: setsockopt SO_BINDTODEVICE=%s failed", sock->bind_dev);
+        }
+
+    }
+#endif
+
     bind_local(sock, addr->ai_family);
 }
 
@@ -1453,14 +1480,14 @@
             struct pollfd fds[1];
             fds[0].fd = sd;
             fds[0].events = POLLOUT;
-            status = poll(fds, 1, 0);
+            status = poll(fds, 1, (connect_timeout > 0) ? 1000 : 0);
 #else
             fd_set writes;
             struct timeval tv;
 
             FD_ZERO(&writes);
             openvpn_fd_set(sd, &writes);
-            tv.tv_sec = 0;
+            tv.tv_sec = (connect_timeout > 0) ? 1 : 0;
             tv.tv_usec = 0;
 
             status = select(sd + 1, NULL, &writes, NULL, &tv);
@@ -1490,7 +1517,7 @@
 #endif
                     break;
                 }
-                management_sleep(1);
+                management_sleep(0);
                 continue;
             }
 
@@ -1607,6 +1634,22 @@
     gc_free(&gc);
 }
 
+/*
+ * Stream buffer handling prototypes -- stream_buf is a helper class
+ * to assist in the packetization of stream transport protocols
+ * such as TCP.
+ */
+
+static void
+stream_buf_init(struct stream_buf *sb, struct buffer *buf,
+                const unsigned int sockflags, const int proto);
+
+static void
+stream_buf_close(struct stream_buf *sb);
+
+static bool
+stream_buf_added(struct stream_buf *sb, int length_added);
+
 /* For stream protocols, allocate a buffer to build up packet.
  * Called after frame has been finalized. */
 
@@ -1769,7 +1812,8 @@
                 sock->info.lsa->remote_list = ai;
                 sock->info.lsa->current_remote = ai;
 
-                dmsg(D_SOCKET_DEBUG, "RESOLVE_REMOTE flags=0x%04x phase=%d rrs=%d sig=%d status=%d",
+                dmsg(D_SOCKET_DEBUG,
+                     "RESOLVE_REMOTE flags=0x%04x phase=%d rrs=%d sig=%d status=%d",
                      flags,
                      phase,
                      retry,
@@ -1859,6 +1903,7 @@
                         int rcvbuf,
                         int sndbuf,
                         int mark,
+                        const char *bind_dev,
                         struct event_timeout *server_poll_timeout,
                         unsigned int sockflags)
 {
@@ -1885,6 +1930,7 @@
 
     sock->sockflags = sockflags;
     sock->mark = mark;
+    sock->bind_dev = bind_dev;
 
     sock->info.proto = proto;
     sock->info.af = af;
@@ -1995,8 +2041,14 @@
             }
             else
             {
-                msg(M_WARN, "inetd(%s): getsockname(%d) failed, using AF_INET",
+                int saved_errno = errno;
+                msg(M_WARN|M_ERRNO, "inetd(%s): getsockname(%d) failed, using AF_INET",
                     proto2ascii(sock->info.proto, sock->info.af, false), (int)sock->sd);
+                /* if not called with a socket on stdin, --inetd cannot work */
+                if (saved_errno == ENOTSOCK)
+                {
+                    msg(M_FATAL, "ERROR: socket required for --inetd operation");
+                }
             }
         }
 #else  /* ifdef HAVE_GETSOCKNAME */
@@ -2012,7 +2064,6 @@
                                  false,
                                  sock->inetd == INETD_NOWAIT,
                                  signal_received);
-
     }
     ASSERT(!remote_changed);
 }
@@ -2415,8 +2466,7 @@
 }
 
 void
-link_socket_connection_initiated(const struct buffer *buf,
-                                 struct link_socket_info *info,
+link_socket_connection_initiated(struct link_socket_info *info,
                                  const struct link_socket_actual *act,
                                  const char *common_name,
                                  struct env_set *es)
@@ -2450,7 +2500,7 @@
         {
             msg(M_WARN, "WARNING: ipchange plugin call failed");
         }
-        argv_reset(&argv);
+        argv_free(&argv);
     }
 
     /* Process --ipchange option */
@@ -2460,7 +2510,7 @@
         setenv_str(es, "script_type", "ipchange");
         ipchange_fmt(true, &argv, info, &gc);
         openvpn_run_script(&argv, es, 0, "--ipchange");
-        argv_reset(&argv);
+        argv_free(&argv);
     }
 
     gc_free(&gc);
@@ -2514,7 +2564,7 @@
  * by now just ignore it
  *
  * For --remote entries with multiple addresses this
- * only return the actual endpoint we have sucessfully connected to
+ * only return the actual endpoint we have successfully connected to
  */
     if (lsa->actual.dest.addr.sa.sa_family != AF_INET)
     {
@@ -2545,7 +2595,7 @@
  * for PF_INET6 routes over PF_INET6 endpoints
  *
  * For --remote entries with multiple addresses this
- * only return the actual endpoint we have sucessfully connected to
+ * only return the actual endpoint we have successfully connected to
  */
     if (lsa->actual.dest.addr.sa.sa_family != AF_INET6)
     {
@@ -2616,7 +2666,7 @@
     sb->len = -1;
 }
 
-void
+static void
 stream_buf_init(struct stream_buf *sb,
                 struct buffer *buf,
                 const unsigned int sockflags,
@@ -2690,7 +2740,7 @@
     return !sock->stream_buf.residual_fully_formed;
 }
 
-bool
+static bool
 stream_buf_added(struct stream_buf *sb,
                  int length_added)
 {
@@ -2757,7 +2807,7 @@
     }
 }
 
-void
+static void
 stream_buf_close(struct stream_buf *sb)
 {
     free_buf(&sb->residual);
@@ -3116,22 +3166,22 @@
 
 /* Indexed by PROTO_x */
 static const struct proto_names proto_names[] = {
-    {"proto-uninitialized",        "proto-NONE", AF_UNSPEC, PROTO_NONE},
+    {"proto-uninitialized", "proto-NONE", AF_UNSPEC, PROTO_NONE},
     /* try IPv4 and IPv6 (client), bind dual-stack (server) */
-    {"udp",        "UDP", AF_UNSPEC, PROTO_UDP},
-    {"tcp-server", "TCP_SERVER", AF_UNSPEC, PROTO_TCP_SERVER},
-    {"tcp-client", "TCP_CLIENT", AF_UNSPEC, PROTO_TCP_CLIENT},
-    {"tcp",        "TCP", AF_UNSPEC, PROTO_TCP},
+    {"udp",         "UDP", AF_UNSPEC, PROTO_UDP},
+    {"tcp-server",  "TCP_SERVER", AF_UNSPEC, PROTO_TCP_SERVER},
+    {"tcp-client",  "TCP_CLIENT", AF_UNSPEC, PROTO_TCP_CLIENT},
+    {"tcp",         "TCP", AF_UNSPEC, PROTO_TCP},
     /* force IPv4 */
-    {"udp4",       "UDPv4", AF_INET, PROTO_UDP},
-    {"tcp4-server","TCPv4_SERVER", AF_INET, PROTO_TCP_SERVER},
-    {"tcp4-client","TCPv4_CLIENT", AF_INET, PROTO_TCP_CLIENT},
-    {"tcp4",       "TCPv4", AF_INET, PROTO_TCP},
+    {"udp4",        "UDPv4", AF_INET, PROTO_UDP},
+    {"tcp4-server", "TCPv4_SERVER", AF_INET, PROTO_TCP_SERVER},
+    {"tcp4-client", "TCPv4_CLIENT", AF_INET, PROTO_TCP_CLIENT},
+    {"tcp4",        "TCPv4", AF_INET, PROTO_TCP},
     /* force IPv6 */
-    {"udp6","UDPv6", AF_INET6, PROTO_UDP},
-    {"tcp6-server","TCPv6_SERVER", AF_INET6, PROTO_TCP_SERVER},
-    {"tcp6-client","TCPv6_CLIENT", AF_INET6, PROTO_TCP_CLIENT},
-    {"tcp6","TCPv6", AF_INET6, PROTO_TCP},
+    {"udp6",        "UDPv6", AF_INET6, PROTO_UDP},
+    {"tcp6-server", "TCPv6_SERVER", AF_INET6, PROTO_TCP_SERVER},
+    {"tcp6-client", "TCPv6_CLIENT", AF_INET6, PROTO_TCP_CLIENT},
+    {"tcp6",        "TCPv6", AF_INET6, PROTO_TCP},
 };
 
 bool
@@ -3143,6 +3193,7 @@
     }
     return proto != PROTO_NONE;
 }
+
 bool
 proto_is_dgram(int proto)
 {
@@ -3258,7 +3309,7 @@
  *
  * IPv6 and IPv4 protocols are comptabile but OpenVPN
  * has always sent UDPv4, TCPv4 over the wire. Keep these
- * strings for backward compatbility
+ * strings for backward compatibility
  */
 const char *
 proto_remote(int proto, bool remote)
@@ -3343,7 +3394,7 @@
 
 #if ENABLE_IP_PKTINFO
 
-/* make the buffer large enough to handle ancilliary socket data for
+/* make the buffer large enough to handle ancillary socket data for
  * both IPv4 and IPv6 destination addresses, plus padding (see RFC 2292)
  */
 #if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)
@@ -3858,7 +3909,7 @@
         if (ret >= 0 && io->addr_defined)
         {
             /* TODO(jjo): streamline this mess */
-            /* in this func we dont have relevant info about the PF_ of this
+            /* in this func we don't have relevant info about the PF_ of this
              * endpoint, as link_socket_actual will be zero for the 1st received packet
              *
              * Test for inets PF_ possible sizes
diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h
index 80e8128..7aeae52 100644
--- a/src/openvpn/socket.h
+++ b/src/openvpn/socket.h
@@ -99,7 +99,7 @@
 #endif
 };
 
-/* IP addresses which are persistant across SIGUSR1s */
+/* IP addresses which are persistent across SIGUSR1s */
 struct link_socket_addr
 {
     struct addrinfo *bind_local;
@@ -138,7 +138,7 @@
     int len;   /* -1 if not yet known */
 
     bool error; /* if true, fatal TCP error has occurred,
-                *  requiring that connection be restarted */
+                 *  requiring that connection be restarted */
 #if PORT_SHARE
 #define PS_DISABLED 0
 #define PS_ENABLED  1
@@ -212,6 +212,7 @@
 #define SF_GETADDRINFO_DGRAM (1<<4)
     unsigned int sockflags;
     int mark;
+    const char *bind_dev;
 
     /* for stream sockets */
     struct stream_buf stream_buf;
@@ -296,7 +297,8 @@
 /*
  * Initialize link_socket object.
  */
-
+/* *INDENT-OFF* uncrustify misparses this function declarion because of
+ * embedded #if/#endif tell it to skip this section */
 void
 link_socket_init_phase1(struct link_socket *sock,
                         const char *local_host,
@@ -325,8 +327,10 @@
                         int rcvbuf,
                         int sndbuf,
                         int mark,
+                        const char *bind_dev,
                         struct event_timeout *server_poll_timeout,
                         unsigned int sockflags);
+/* Reenable uncrustify *INDENT-ON* */
 
 void link_socket_init_phase2(struct link_socket *sock,
                              const struct frame *frame,
@@ -431,8 +435,7 @@
 const struct in6_addr *link_socket_current_remote_ipv6
     (const struct link_socket_info *info);
 
-void link_socket_connection_initiated(const struct buffer *buf,
-                                      struct link_socket_info *info,
+void link_socket_connection_initiated(struct link_socket_info *info,
                                       const struct link_socket_actual *addr,
                                       const char *common_name,
                                       struct env_set *es);
@@ -980,52 +983,33 @@
 }
 
 static inline void
-link_socket_set_outgoing_addr(const struct buffer *buf,
-                              struct link_socket_info *info,
+link_socket_set_outgoing_addr(struct link_socket_info *info,
                               const struct link_socket_actual *act,
                               const char *common_name,
                               struct env_set *es)
 {
-    if (!buf || buf->len > 0)
+    struct link_socket_addr *lsa = info->lsa;
+    if (
+        /* new or changed address? */
+        (!info->connection_established
+         || !addr_match_proto(&act->dest, &lsa->actual.dest, info->proto)
+        )
+        &&
+        /* address undef or address == remote or --float */
+        (info->remote_float
+         || (!lsa->remote_list || addrlist_match_proto(&act->dest, lsa->remote_list, info->proto))
+        )
+        )
     {
-        struct link_socket_addr *lsa = info->lsa;
-        if (
-            /* new or changed address? */
-            (!info->connection_established
-             || !addr_match_proto(&act->dest, &lsa->actual.dest, info->proto)
-            )
-            &&
-            /* address undef or address == remote or --float */
-            (info->remote_float
-             || (!lsa->remote_list || addrlist_match_proto(&act->dest, lsa->remote_list, info->proto))
-            )
-            )
-        {
-            link_socket_connection_initiated(buf, info, act, common_name, es);
-        }
+        link_socket_connection_initiated(info, act, common_name, es);
     }
 }
 
-/*
- * Stream buffer handling -- stream_buf is a helper class
- * to assist in the packetization of stream transport protocols
- * such as TCP.
- */
-
-void stream_buf_init(struct stream_buf *sb,
-                     struct buffer *buf,
-                     const unsigned int sockflags,
-                     const int proto);
-
-void stream_buf_close(struct stream_buf *sb);
-
-bool stream_buf_added(struct stream_buf *sb, int length_added);
+bool stream_buf_read_setup_dowork(struct link_socket *sock);
 
 static inline bool
 stream_buf_read_setup(struct link_socket *sock)
 {
-    bool stream_buf_read_setup_dowork(struct link_socket *sock);
-
     if (link_socket_connection_oriented(sock))
     {
         return stream_buf_read_setup_dowork(sock);
@@ -1130,16 +1114,17 @@
 
 #else  /* ifdef _WIN32 */
 
+size_t link_socket_write_udp_posix_sendmsg(struct link_socket *sock,
+                                           struct buffer *buf,
+                                           struct link_socket_actual *to);
+
+
 static inline size_t
 link_socket_write_udp_posix(struct link_socket *sock,
                             struct buffer *buf,
                             struct link_socket_actual *to)
 {
 #if ENABLE_IP_PKTINFO
-    size_t link_socket_write_udp_posix_sendmsg(struct link_socket *sock,
-                                               struct buffer *buf,
-                                               struct link_socket_actual *to);
-
     if (proto_is_udp(sock->info.proto) && (sock->sockflags & SF_USE_IP_PKTINFO)
         && addr_defined_ipi(to))
     {
diff --git a/src/openvpn/socks.c b/src/openvpn/socks.c
index 57f0cee..36df747 100644
--- a/src/openvpn/socks.c
+++ b/src/openvpn/socks.c
@@ -312,7 +312,7 @@
     char atyp = '\0';
     int alen = 0;
     int len = 0;
-    char buf[22];
+    char buf[270];		/* 4 + alen(max 256) + 2 */
     const int timeout_sec = 5;
 
     if (addr != NULL)
@@ -381,7 +381,10 @@
                     break;
 
                 case '\x03':    /* DOMAINNAME */
-                    alen = (unsigned char) c;
+                    /* RFC 1928, section 5: 1 byte length, <n> bytes name,
+                     * so the total "address length" is (length+1)
+                     */
+                    alen = (unsigned char) c + 1;
                     break;
 
                 case '\x04':    /* IP V6 */
@@ -451,7 +454,7 @@
                                const char *servname,    /* openvpn server port */
                                volatile int *signal_received)
 {
-    char buf[128];
+    char buf[270];
     size_t len;
 
     if (!socks_handshake(p, sd, signal_received))
diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c
index cf66899..b1b6d22 100644
--- a/src/openvpn/ssl.c
+++ b/src/openvpn/ssl.c
@@ -43,8 +43,6 @@
 #include "syshead.h"
 #include "win32.h"
 
-#if defined(ENABLE_CRYPTO)
-
 #include "error.h"
 #include "common.h"
 #include "socket.h"
@@ -61,23 +59,11 @@
 #include "ssl.h"
 #include "ssl_verify.h"
 #include "ssl_backend.h"
+#include "ssl_ncp.h"
+#include "auth_token.h"
 
 #include "memdbg.h"
 
-#ifndef ENABLE_OCC
-static const char ssl_default_options_string[] = "V0 UNDEF";
-#endif
-
-static inline const char *
-local_options_string(const struct tls_session *session)
-{
-#ifdef ENABLE_OCC
-    return session->opt->local_options;
-#else
-    return ssl_default_options_string;
-#endif
-}
-
 #ifdef MEASURE_TLS_HANDSHAKE_STATS
 
 static int tls_handshake_success; /* GLOBAL */
@@ -296,7 +282,7 @@
 static void
 tls_limit_reneg_bytes(const cipher_kt_t *cipher, int *reneg_bytes)
 {
-    if (cipher && (cipher_kt_block_size(cipher) < 128/8))
+    if (cipher && cipher_kt_insecure(cipher))
     {
         if (*reneg_bytes == -1) /* Not user-specified */
         {
@@ -402,7 +388,7 @@
 static struct user_pass auth_user_pass; /* GLOBAL */
 static struct user_pass auth_token;     /* GLOBAL */
 
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
 static char *auth_challenge; /* GLOBAL */
 #endif
 
@@ -412,10 +398,7 @@
     auth_user_pass_enabled = true;
     if (!auth_user_pass.defined && !auth_token.defined)
     {
-#if AUTO_USERID
-        get_user_pass_auto_userid(&auth_user_pass, auth_file);
-#else
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
         if (auth_challenge) /* dynamic challenge/response */
         {
             get_user_pass_cr(&auth_user_pass,
@@ -438,9 +421,8 @@
                              sci->challenge_text);
         }
         else
-#endif /* ifdef ENABLE_CLIENT_CR */
+#endif /* ifdef ENABLE_MANAGEMENT */
         get_user_pass(&auth_user_pass, auth_file, UP_TYPE_AUTH, GET_USER_PASS_MANAGEMENT);
-#endif /* if AUTO_USERID */
     }
 }
 
@@ -452,8 +434,6 @@
 {
     passbuf.nocache = true;
     auth_user_pass.nocache = true;
-    /* wait for push-reply, because auth-token may still need the username */
-    auth_user_pass.wait_for_push = true;
 }
 
 /*
@@ -469,7 +449,7 @@
  * Cleans an auth token and checks if it was active
  */
 bool
-ssl_clean_auth_token (void)
+ssl_clean_auth_token(void)
 {
     bool wasdefined = auth_token.defined;
     purge_user_pass(&auth_token, true);
@@ -490,12 +470,12 @@
         purge_user_pass(&passbuf, true);
     }
     purge_user_pass(&auth_user_pass, true);
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
     ssl_purge_auth_challenge();
 #endif
 }
 
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
 
 void
 ssl_purge_auth_challenge(void)
@@ -561,7 +541,7 @@
  */
 static void
 tls_ctx_reload_crl(struct tls_root_ctx *ssl_ctx, const char *crl_file,
-                   const char *crl_file_inline)
+                   bool crl_file_inline)
 {
     /* if something goes wrong with stat(), we'll store 0 as mtime */
     platform_stat_t crl_stat = {0};
@@ -578,7 +558,15 @@
     }
     else if (platform_stat(crl_file, &crl_stat) < 0)
     {
-        msg(M_WARN, "WARNING: Failed to stat CRL file, not (re)loading CRL.");
+        /* If crl_last_mtime is zero, the CRL file has not been read before. */
+        if (ssl_ctx->crl_last_mtime == 0)
+        {
+            msg(M_FATAL, "ERROR: Failed to stat CRL file during initialization, exiting.");
+        }
+        else
+        {
+            msg(M_WARN, "WARNING: Failed to stat CRL file, not reloading CRL.");
+        }
         return;
     }
 
@@ -603,7 +591,7 @@
  * All files are in PEM format.
  */
 void
-init_ssl(const struct options *options, struct tls_root_ctx *new_ctx)
+init_ssl(const struct options *options, struct tls_root_ctx *new_ctx, bool in_chroot)
 {
     ASSERT(NULL != new_ctx);
 
@@ -633,6 +621,12 @@
     tls_ctx_restrict_ciphers(new_ctx, options->cipher_list);
     tls_ctx_restrict_ciphers_tls13(new_ctx, options->cipher_list_tls13);
 
+    /* Set the allow groups/curves for TLS if we want to override them */
+    if (options->tls_groups)
+    {
+        tls_ctx_set_tls_groups(new_ctx, options->tls_groups);
+    }
+
     if (!tls_ctx_set_options(new_ctx, options->ssl_flags))
     {
         goto err;
@@ -663,42 +657,38 @@
         tls_ctx_load_cryptoapi(new_ctx, options->cryptoapi_cert);
     }
 #endif
-#ifdef MANAGMENT_EXTERNAL_KEY
-    else if ((options->management_flags & MF_EXTERNAL_KEY)
-             && (options->cert_file || options->management_flags & MF_EXTERNAL_CERT))
+#ifdef ENABLE_MANAGEMENT
+    else if (options->management_flags & MF_EXTERNAL_CERT)
     {
-        if (options->cert_file)
+        char *cert = management_query_cert(management,
+                                           options->management_certificate);
+        tls_ctx_load_cert_file(new_ctx, cert, true);
+        free(cert);
+    }
+#endif
+    else if (options->cert_file)
+    {
+        tls_ctx_load_cert_file(new_ctx, options->cert_file, options->cert_file_inline);
+    }
+
+    if (options->priv_key_file)
+    {
+        if (0 != tls_ctx_load_priv_file(new_ctx, options->priv_key_file,
+                                        options->priv_key_file_inline))
         {
-            tls_ctx_use_external_private_key(new_ctx, options->cert_file,
-                                             options->cert_file_inline);
+            goto err;
         }
-        else
+    }
+#ifdef ENABLE_MANAGEMENT
+    else if (options->management_flags & MF_EXTERNAL_KEY)
+    {
+        if (tls_ctx_use_management_external_key(new_ctx))
         {
-            char *external_certificate = management_query_cert(management,
-                                                               options->management_certificate);
-            tls_ctx_use_external_private_key(new_ctx, INLINE_FILE_TAG,
-                                             external_certificate);
-            free(external_certificate);
+            msg(M_WARN, "Cannot initialize mamagement-external-key");
+            goto err;
         }
     }
 #endif
-    else
-    {
-        /* Load Certificate */
-        if (options->cert_file)
-        {
-            tls_ctx_load_cert_file(new_ctx, options->cert_file, options->cert_file_inline);
-        }
-
-        /* Load Private Key */
-        if (options->priv_key_file)
-        {
-            if (0 != tls_ctx_load_priv_file(new_ctx, options->priv_key_file, options->priv_key_file_inline))
-            {
-                goto err;
-            }
-        }
-    }
 
     if (options->ca_file || options->ca_path)
     {
@@ -719,7 +709,24 @@
     /* Read CRL */
     if (options->crl_file && !(options->ssl_flags & SSLF_CRL_VERIFY_DIR))
     {
-        tls_ctx_reload_crl(new_ctx, options->crl_file, options->crl_file_inline);
+        /* If we're running with the chroot option, we may run init_ssl() before
+         * and after chroot-ing. We can use the crl_file path as-is if we're
+         * not going to chroot, or if we already are inside the chroot.
+         *
+         * If we're going to chroot later, we need to prefix the path of the
+         * chroot directory to crl_file.
+         */
+        if (!options->chroot_dir || in_chroot || options->crl_file_inline)
+        {
+            tls_ctx_reload_crl(new_ctx, options->crl_file, options->crl_file_inline);
+        }
+        else
+        {
+            struct gc_arena gc = gc_new();
+            struct buffer crl_file_buf = prepend_dir(options->chroot_dir, options->crl_file, &gc);
+            tls_ctx_reload_crl(new_ctx, BSTR(&crl_file_buf), options->crl_file_inline);
+            gc_free(&gc);
+        }
     }
 
     /* Once keys and cert are loaded, load ECDH parameters */
@@ -771,9 +778,6 @@
         case S_ACTIVE:
             return "S_ACTIVE";
 
-        case S_NORMAL_OP:
-            return "S_NORMAL_OP";
-
         case S_ERROR:
             return "S_ERROR";
 
@@ -799,6 +803,9 @@
         case P_CONTROL_HARD_RESET_SERVER_V2:
             return "P_CONTROL_HARD_RESET_SERVER_V2";
 
+        case P_CONTROL_HARD_RESET_CLIENT_V3:
+            return "P_CONTROL_HARD_RESET_CLIENT_V3";
+
         case P_CONTROL_SOFT_RESET_V1:
             return "P_CONTROL_SOFT_RESET_V1";
 
@@ -844,10 +851,9 @@
 static const char *
 print_key_id(struct tls_multi *multi, struct gc_arena *gc)
 {
-    int i;
     struct buffer out = alloc_buf_gc(256, gc);
 
-    for (i = 0; i < KEY_SCAN_SIZE; ++i)
+    for (int i = 0; i < KEY_SCAN_SIZE; ++i)
     {
         struct key_state *ks = multi->key_scan[i];
         buf_printf(&out, " [key#%d state=%s id=%d sid=%s]", i,
@@ -859,22 +865,12 @@
 }
 
 bool
-is_hard_reset(int op, int key_method)
+is_hard_reset_method2(int op)
 {
-    if (!key_method || key_method == 1)
+    if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2
+        || op == P_CONTROL_HARD_RESET_CLIENT_V3)
     {
-        if (op == P_CONTROL_HARD_RESET_CLIENT_V1 || op == P_CONTROL_HARD_RESET_SERVER_V1)
-        {
-            return true;
-        }
-    }
-
-    if (!key_method || key_method >= 2)
-    {
-        if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2)
-        {
-            return true;
-        }
+        return true;
     }
 
     return false;
@@ -1094,16 +1090,14 @@
     }
 
     /* Are we a TLS server or client? */
-    ASSERT(session->opt->key_method >= 1);
-    if (session->opt->key_method == 1)
+    if (session->opt->server)
     {
-        session->initial_opcode = session->opt->server ?
-                                  P_CONTROL_HARD_RESET_SERVER_V1 : P_CONTROL_HARD_RESET_CLIENT_V1;
+        session->initial_opcode = P_CONTROL_HARD_RESET_SERVER_V2;
     }
-    else /* session->opt->key_method >= 2 */
+    else
     {
-        session->initial_opcode = session->opt->server ?
-                                  P_CONTROL_HARD_RESET_SERVER_V2 : P_CONTROL_HARD_RESET_CLIENT_V2;
+        session->initial_opcode = session->opt->tls_crypt_v2 ?
+                                  P_CONTROL_HARD_RESET_CLIENT_V3 : P_CONTROL_HARD_RESET_CLIENT_V2;
     }
 
     /* Initialize control channel authentication parameters */
@@ -1143,16 +1137,9 @@
 static void
 tls_session_free(struct tls_session *session, bool clear)
 {
-    int i;
+    tls_wrap_free(&session->tls_wrap);
 
-    if (packet_id_initialized(&session->tls_wrap.opt.packet_id))
-    {
-        packet_id_free(&session->tls_wrap.opt.packet_id);
-    }
-
-    free_buf(&session->tls_wrap.work);
-
-    for (i = 0; i < KS_SIZE; ++i)
+    for (size_t i = 0; i < KS_SIZE; ++i)
     {
         key_state_free(&session->key[i], false);
     }
@@ -1234,11 +1221,10 @@
     const struct key_state *lame = &session->key[KS_LAME_DUCK];
     if (lame->state >= S_INITIAL)
     {
-        const time_t local_now = now;
         ASSERT(lame->must_die); /* a lame duck key must always have an expiration */
-        if (local_now < lame->must_die)
+        if (now < lame->must_die)
         {
-            compute_earliest_wakeup(wakeup, lame->must_die - local_now);
+            compute_earliest_wakeup(wakeup, lame->must_die - now);
             return false;
         }
         else
@@ -1337,11 +1323,9 @@
                            const char *local,
                            const char *remote)
 {
-#ifdef ENABLE_OCC
     /* initialize options string */
     multi->opt.local_options = local;
     multi->opt.remote_options = remote;
-#endif
 }
 
 /*
@@ -1350,17 +1334,11 @@
 void
 tls_multi_free(struct tls_multi *multi, bool clear)
 {
-    int i;
-
     ASSERT(multi);
 
-#ifdef MANAGEMENT_DEF_AUTH
-    man_def_auth_set_client_reason(multi, NULL);
+    auth_set_client_reason(multi, NULL);
 
-#endif
-#if P2MP_SERVER
     free(multi->peer_info);
-#endif
 
     if (multi->locked_cn)
     {
@@ -1374,15 +1352,11 @@
 
     cert_hash_free(multi->locked_cert_hash_set);
 
-    if (multi->auth_token)
-    {
-        secure_memzero(multi->auth_token, AUTH_TOKEN_SIZE);
-        free(multi->auth_token);
-    }
+    wipe_auth_token(multi);
 
     free(multi->remote_ciphername);
 
-    for (i = 0; i < TM_SIZE; ++i)
+    for (int i = 0; i < TM_SIZE; ++i)
     {
         tls_session_free(&multi->session[i], false);
     }
@@ -1410,11 +1384,10 @@
 static bool
 swap_hmac(struct buffer *buf, const struct crypto_options *co, bool incoming)
 {
-    const struct key_ctx *ctx;
-
     ASSERT(co);
 
-    ctx = (incoming ? &co->key_ctx_bi.decrypt : &co->key_ctx_bi.encrypt);
+    const struct key_ctx *ctx = (incoming ? &co->key_ctx_bi.decrypt :
+                                 &co->key_ctx_bi.encrypt);
     ASSERT(ctx->hmac);
 
     {
@@ -1478,6 +1451,8 @@
     ASSERT(reliable_ack_write
                (ks->rec_ack, buf, &ks->session_id_remote, max_ack, prepend_ack));
 
+    msg(D_TLS_DEBUG, "%s(): %s", __func__, packet_opcode_name(opcode));
+
     if (session->tls_wrap.mode == TLS_WRAP_AUTH
         || session->tls_wrap.mode == TLS_WRAP_NONE)
     {
@@ -1495,17 +1470,26 @@
         ASSERT(buf_init(&session->tls_wrap.work, buf->offset));
         ASSERT(buf_write(&session->tls_wrap.work, &header, sizeof(header)));
         ASSERT(session_id_write(&session->session_id, &session->tls_wrap.work));
-        if (tls_crypt_wrap(buf, &session->tls_wrap.work, &session->tls_wrap.opt))
-        {
-            /* Don't change the original data in buf, it's used by the reliability
-             * layer to resend on failure. */
-            *buf = session->tls_wrap.work;
-        }
-        else
+        if (!tls_crypt_wrap(buf, &session->tls_wrap.work, &session->tls_wrap.opt))
         {
             buf->len = 0;
             return;
         }
+
+        if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3)
+        {
+            if (!buf_copy(&session->tls_wrap.work,
+                          session->tls_wrap.tls_crypt_v2_wkc))
+            {
+                msg(D_TLS_ERRORS, "Could not append tls-crypt-v2 client key");
+                buf->len = 0;
+                return;
+            }
+        }
+
+        /* Don't change the original data in buf, it's used by the reliability
+         * layer to resend on failure. */
+        *buf = session->tls_wrap.work;
     }
     *to_link_addr = &ks->remote_addr;
 }
@@ -1516,11 +1500,22 @@
 static bool
 read_control_auth(struct buffer *buf,
                   struct tls_wrap_ctx *ctx,
-                  const struct link_socket_actual *from)
+                  const struct link_socket_actual *from,
+                  const struct tls_options *opt)
 {
     struct gc_arena gc = gc_new();
     bool ret = false;
 
+    const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT;
+    if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3
+        && !tls_crypt_v2_extract_client_key(buf, ctx, opt))
+    {
+        msg(D_TLS_ERRORS,
+            "TLS Error: can not extract tls-crypt-v2 client key from %s",
+            print_link_socket_actual(from, &gc));
+        goto cleanup;
+    }
+
     if (ctx->mode == TLS_WRAP_AUTH)
     {
         struct buffer null = clear_buf();
@@ -1560,6 +1555,18 @@
         ASSERT(buf_copy(buf, &tmp));
         buf_clear(&tmp);
     }
+    else if (ctx->tls_crypt_v2_server_key.cipher)
+    {
+        /* If tls-crypt-v2 is enabled, require *some* wrapping */
+        msg(D_TLS_ERRORS, "TLS Error: could not determine wrapping from %s",
+            print_link_socket_actual(from, &gc));
+        /* TODO Do we want to support using tls-crypt-v2 and no control channel
+         * wrapping at all simultaneously?  That would allow server admins to
+         * upgrade clients one-by-one without running a second instance, but we
+         * should not enable it by default because it breaks DoS-protection.
+         * So, add something like --tls-crypt-v2-allow-insecure-fallback ? */
+        goto cleanup;
+    }
 
     if (ctx->mode == TLS_WRAP_NONE || ctx->mode == TLS_WRAP_AUTH)
     {
@@ -1632,25 +1639,21 @@
             int olen)
 {
     struct gc_arena gc = gc_new();
-    int chunk;
-    hmac_ctx_t *ctx;
-    hmac_ctx_t *ctx_tmp;
     uint8_t A1[MAX_HMAC_KEY_LENGTH];
-    unsigned int A1_len;
 
 #ifdef ENABLE_DEBUG
     const int olen_orig = olen;
     const uint8_t *out_orig = out;
 #endif
 
-    ctx = hmac_ctx_new();
-    ctx_tmp = hmac_ctx_new();
+    hmac_ctx_t *ctx = hmac_ctx_new();
+    hmac_ctx_t *ctx_tmp = hmac_ctx_new();
 
     dmsg(D_SHOW_KEY_SOURCE, "tls1_P_hash sec: %s", format_hex(sec, sec_len, 0, &gc));
     dmsg(D_SHOW_KEY_SOURCE, "tls1_P_hash seed: %s", format_hex(seed, seed_len, 0, &gc));
 
-    chunk = md_kt_size(md_kt);
-    A1_len = md_kt_size(md_kt);
+    int chunk = md_kt_size(md_kt);
+    unsigned int A1_len = md_kt_size(md_kt);
 
     hmac_ctx_init(ctx, sec, sec_len, md_kt);
     hmac_ctx_init(ctx_tmp, sec, sec_len, md_kt);
@@ -1720,21 +1723,18 @@
     struct gc_arena gc = gc_new();
     const md_kt_t *md5 = md_kt_get("MD5");
     const md_kt_t *sha1 = md_kt_get("SHA1");
-    int len,i;
-    const uint8_t *S1,*S2;
-    uint8_t *out2;
 
-    out2 = (uint8_t *) gc_malloc(olen, false, &gc);
+    uint8_t *out2 = (uint8_t *) gc_malloc(olen, false, &gc);
 
-    len = slen/2;
-    S1 = sec;
-    S2 = &(sec[len]);
+    int len = slen/2;
+    const uint8_t *S1 = sec;
+    const uint8_t *S2 = &(sec[len]);
     len += (slen&1); /* add for odd, make longer */
 
     tls1_P_hash(md5,S1,len,label,label_len,out1,olen);
     tls1_P_hash(sha1,S2,len,label,label_len,out2,olen);
 
-    for (i = 0; i<olen; i++)
+    for (int i = 0; i<olen; i++)
     {
         out1[i] ^= out2[i];
     }
@@ -1891,40 +1891,6 @@
     }
 }
 
-bool
-tls_item_in_cipher_list(const char *item, const char *list)
-{
-    char *tmp_ciphers = string_alloc(list, NULL);
-    char *tmp_ciphers_orig = tmp_ciphers;
-
-    const char *token = strtok(tmp_ciphers, ":");
-    while (token)
-    {
-        if (0 == strcmp(token, item))
-        {
-            break;
-        }
-        token = strtok(NULL, ":");
-    }
-    free(tmp_ciphers_orig);
-
-    return token != NULL;
-}
-
-void
-tls_poor_mans_ncp(struct options *o, const char *remote_ciphername)
-{
-    if (o->ncp_enabled && remote_ciphername
-        && 0 != strcmp(o->ciphername, remote_ciphername))
-    {
-        if (tls_item_in_cipher_list(remote_ciphername, o->ncp_ciphers))
-        {
-            o->ciphername = string_alloc(remote_ciphername, &o->gc);
-            msg(D_TLS_DEBUG_LOW, "Using peer cipher '%s'", o->ciphername);
-        }
-    }
-}
-
 /**
  * Generate data channel keys for the supplied TLS session.
  *
@@ -1941,7 +1907,11 @@
     const struct session_id *server_sid = !session->opt->server ?
                                           &ks->session_id_remote : &session->session_id;
 
-    ASSERT(ks->authenticated);
+    if (ks->authenticated == KS_AUTH_FALSE)
+    {
+        msg(D_TLS_ERRORS, "TLS Error: key_state not authenticated");
+        goto cleanup;
+    }
 
     ks->crypto_options.flags = session->opt->crypto_flags;
     if (!generate_key_expansion(&ks->crypto_options.key_ctx_bi,
@@ -1971,13 +1941,14 @@
         return true;
     }
 
-    if (!session->opt->server
-        && 0 != strcmp(options->ciphername, session->opt->config_ciphername)
+    bool cipher_allowed_as_fallback = options->enable_ncp_fallback
+                                      && streq(options->ciphername, session->opt->config_ciphername);
+
+    if (!session->opt->server && !cipher_allowed_as_fallback
         && !tls_item_in_cipher_list(options->ciphername, options->ncp_ciphers))
     {
-        msg(D_TLS_ERRORS, "Error: pushed cipher not allowed - %s not in %s or %s",
-            options->ciphername, session->opt->config_ciphername,
-            options->ncp_ciphers);
+        msg(D_TLS_ERRORS, "Error: pushed cipher not allowed - %s not in %s",
+            options->ciphername, options->ncp_ciphers);
         /* undo cipher push, abort connection setup */
         options->ciphername = session->opt->config_ciphername;
         return false;
@@ -1993,6 +1964,13 @@
             options->keysize = 0;
         }
     }
+    else
+    {
+        /* Very hacky workaround and quick fix for frame calculation
+         * different when adjusting frame size when the original and new cipher
+         * are identical to avoid a regression with client without NCP */
+        return tls_session_generate_data_channel_keys(session);
+    }
 
     init_key_type(&session->opt->key_type, options->ciphername,
                   options->authname, options->keysize, true, true);
@@ -2007,7 +1985,7 @@
     /* Update frame parameters: undo worst-case overhead, add actual overhead */
     frame_remove_from_extra_frame(frame, crypto_max_overhead());
     crypto_adjust_frame_parameters(frame, &session->opt->key_type,
-                                   options->use_iv, options->replay, packet_id_long_form);
+                                   options->replay, packet_id_long_form);
     frame_finalize(frame, options->ce.link_mtu_defined, options->ce.link_mtu,
                    options->ce.tun_mtu_defined, options->ce.tun_mtu);
     frame_init_mssfix(frame, options);
@@ -2024,7 +2002,7 @@
     {
         frame_remove_from_extra_frame(frame_fragment, crypto_max_overhead());
         crypto_adjust_frame_parameters(frame_fragment, &session->opt->key_type,
-                                       options->use_iv, options->replay, packet_id_long_form);
+                                       options->replay, packet_id_long_form);
         frame_set_mtu_dynamic(frame_fragment, options->ce.fragment, SET_MTU_UPPER_BOUND);
         frame_print(frame_fragment, D_MTU_INFO, "Fragmentation MTU parms");
     }
@@ -2220,63 +2198,15 @@
     return str;
 }
 
-/*
- * Handle the reading and writing of key data to and from
- * the TLS control channel (cleartext).
- */
-
-static bool
-key_method_1_write(struct buffer *buf, struct tls_session *session)
-{
-    struct key key;
-    struct key_state *ks = &session->key[KS_PRIMARY];      /* primary key */
-
-    ASSERT(session->opt->key_method == 1);
-    ASSERT(buf_init(buf, 0));
-
-    generate_key_random(&key, &session->opt->key_type);
-    if (!check_key(&key, &session->opt->key_type))
-    {
-        msg(D_TLS_ERRORS, "TLS Error: Bad encrypting key generated");
-        return false;
-    }
-
-    if (!write_key(&key, &session->opt->key_type, buf))
-    {
-        msg(D_TLS_ERRORS, "TLS Error: write_key failed");
-        return false;
-    }
-
-    init_key_ctx(&ks->crypto_options.key_ctx_bi.encrypt, &key,
-                 &session->opt->key_type, OPENVPN_OP_ENCRYPT,
-                 "Data Channel Encrypt");
-    secure_memzero(&key, sizeof(key));
-
-    /* send local options string */
-    {
-        const char *local_options = local_options_string(session);
-        const int optlen = strlen(local_options) + 1;
-        if (!buf_write(buf, local_options, optlen))
-        {
-            msg(D_TLS_ERRORS, "TLS Error: KM1 write options failed");
-            return false;
-        }
-    }
-
-    return true;
-}
-
 static bool
 push_peer_info(struct buffer *buf, struct tls_session *session)
 {
     struct gc_arena gc = gc_new();
     bool ret = false;
 
-#ifdef ENABLE_PUSH_PEER_INFO
     if (session->opt->push_peer_info_detail > 0)
     {
         struct env_set *es = session->opt->es;
-        struct env_item *e;
         struct buffer out = alloc_buf_gc(512*3, &gc);
 
         /* push version */
@@ -2302,13 +2232,30 @@
 #endif
 
         /* support for P_DATA_V2 */
-        buf_printf(&out, "IV_PROTO=2\n");
+        int iv_proto = IV_PROTO_DATA_V2;
 
-        /* support for Negotiable Crypto Paramters */
+        /* support for receiving push_reply before sending
+         * push request, also signal that the client wants
+         * to get push-reply messages without without requiring a round
+         * trip for a push request message*/
+        if(session->opt->pull)
+        {
+            iv_proto |= IV_PROTO_REQUEST_PUSH;
+        }
+
+        buf_printf(&out, "IV_PROTO=%d\n", iv_proto);
+
+        /* support for Negotiable Crypto Parameters */
         if (session->opt->ncp_enabled
             && (session->opt->mode == MODE_SERVER || session->opt->pull))
         {
-            buf_printf(&out, "IV_NCP=2\n");
+            if (tls_item_in_cipher_list("AES-128-GCM", session->opt->config_ncp_ciphers)
+                && tls_item_in_cipher_list("AES-256-GCM", session->opt->config_ncp_ciphers))
+            {
+
+                buf_printf(&out, "IV_NCP=2\n");
+            }
+            buf_printf(&out, "IV_CIPHERS=%s\n", session->opt->config_ncp_ciphers);
         }
 
         /* push compression status */
@@ -2320,7 +2267,7 @@
         {
             /* push mac addr */
             struct route_gateway_info rgi;
-            get_default_gateway(&rgi);
+            get_default_gateway(&rgi, session->opt->net_ctx);
             if (rgi.flags & RGI_HWADDR_DEFINED)
             {
                 buf_printf(&out, "IV_HWADDR=%s\n", format_hex_ex(rgi.hwaddr, 6, 0, 1, ":", &gc));
@@ -2332,14 +2279,16 @@
         }
 
         /* push env vars that begin with UV_, IV_PLAT_VER and IV_GUI_VER */
-        for (e = es->list; e != NULL; e = e->next)
+        for (struct env_item *e = es->list; e != NULL; e = e->next)
         {
             if (e->string)
             {
                 if ((((strncmp(e->string, "UV_", 3)==0
                        || strncmp(e->string, "IV_PLAT_VER=", sizeof("IV_PLAT_VER=")-1)==0)
                       && session->opt->push_peer_info_detail >= 2)
-                     || (strncmp(e->string,"IV_GUI_VER=",sizeof("IV_GUI_VER=")-1)==0))
+                     || (strncmp(e->string,"IV_GUI_VER=",sizeof("IV_GUI_VER=")-1)==0)
+                     || (strncmp(e->string,"IV_SSO=",sizeof("IV_SSO=")-1)==0)
+                     )
                     && buf_safe(&out, strlen(e->string)+1))
                 {
                     buf_printf(&out, "%s\n", e->string);
@@ -2353,7 +2302,6 @@
         }
     }
     else
-#endif /* ifdef ENABLE_PUSH_PEER_INFO */
     {
         if (!write_empty_string(buf)) /* no peer info */
         {
@@ -2367,12 +2315,16 @@
     return ret;
 }
 
+/**
+ * Handle the writing of key data, peer-info, username/password, OCC
+ * to the TLS control channel (cleartext).
+ */
 static bool
-key_method_2_write(struct buffer *buf, struct tls_session *session)
+key_method_2_write(struct buffer *buf, struct tls_multi *multi,
+                   struct tls_session *session)
 {
     struct key_state *ks = &session->key[KS_PRIMARY];      /* primary key */
 
-    ASSERT(session->opt->key_method == 2);
     ASSERT(buf_init(buf, 0));
 
     /* write a uint32 0 */
@@ -2382,7 +2334,7 @@
     }
 
     /* write key_method + flags */
-    if (!buf_write_u8(buf, (session->opt->key_method & KEY_METHOD_MASK)))
+    if (!buf_write_u8(buf, KEY_METHOD_2))
     {
         goto error;
     }
@@ -2395,7 +2347,7 @@
 
     /* write options string */
     {
-        if (!write_string(buf, local_options_string(session), TLS_OPTIONS_LEN))
+        if (!write_string(buf, session->opt->local_options, TLS_OPTIONS_LEN))
         {
             goto error;
         }
@@ -2404,7 +2356,7 @@
     /* write username/password if specified */
     if (auth_user_pass_enabled)
     {
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
         auth_user_pass_setup(session->opt->auth_user_pass_file, session->opt->sci);
 #else
         auth_user_pass_setup(session->opt->auth_user_pass_file, NULL);
@@ -2416,7 +2368,9 @@
          * username/password
          */
         if (auth_token.defined)
+        {
             up = &auth_token;
+        }
 
         if (!write_string(buf, up->username, -1))
         {
@@ -2428,14 +2382,15 @@
         }
         /* if auth-nocache was specified, the auth_user_pass object reaches
          * a "complete" state only after having received the push-reply
-         * message.
+         * message. The push message might contain an auth-token that needs
+         * the username of auth_user_pass.
          *
          * For this reason, skip the purge operation here if no push-reply
          * message has been received yet.
          *
          * This normally happens upon first negotiation only.
          */
-        if (!auth_user_pass.wait_for_push)
+        if (!session->opt->pull)
         {
             purge_user_pass(&auth_user_pass, false);
         }
@@ -2457,15 +2412,19 @@
         goto error;
     }
 
-    /* Generate tunnel keys if we're a TLS server.
-     * If we're a p2mp server and IV_NCP >= 2 is negotiated, the first key
-     * generation is postponed until after the pull/push, so we can process pushed
-     * cipher directives.
+    /*
+     * Generate tunnel keys if we're a TLS server.
+     *
+     * If we're a p2mp server to allow NCP, the first key
+     * generation is postponed until after the connect script finished and the
+     * NCP options can be processed. Since that always happens at after connect
+     * script options are available the CAS_SUCCEEDED status is identical to
+     * NCP options are processed and we have no extra state for NCP finished.
      */
-    if (session->opt->server && !(session->opt->ncp_enabled
-                                  && session->opt->mode == MODE_SERVER && ks->key_id <= 0))
+    if (session->opt->server && (session->opt->mode != MODE_SERVER
+            || multi->multi_state == CAS_SUCCEEDED))
     {
-        if (ks->authenticated)
+        if (ks->authenticated > KS_AUTH_FALSE)
         {
             if (!tls_session_generate_data_channel_keys(session))
             {
@@ -2483,73 +2442,15 @@
     return false;
 }
 
-static bool
-key_method_1_read(struct buffer *buf, struct tls_session *session)
-{
-    int status;
-    struct key key;
-    struct key_state *ks = &session->key[KS_PRIMARY];      /* primary key */
-
-    ASSERT(session->opt->key_method == 1);
-
-    if (!session->verified)
-    {
-        msg(D_TLS_ERRORS,
-            "TLS Error: Certificate verification failed (key-method 1)");
-        goto error;
-    }
-
-    status = read_key(&key, &session->opt->key_type, buf);
-    if (status != 1)
-    {
-        msg(D_TLS_ERRORS,
-            "TLS Error: Error reading data channel key from plaintext buffer");
-        goto error;
-    }
-
-    if (!check_key(&key, &session->opt->key_type))
-    {
-        msg(D_TLS_ERRORS, "TLS Error: Bad decrypting key received from peer");
-        goto error;
-    }
-
-    if (buf->len < 1)
-    {
-        msg(D_TLS_ERRORS, "TLS Error: Missing options string");
-        goto error;
-    }
-
-#ifdef ENABLE_OCC
-    /* compare received remote options string
-     * with our locally computed options string */
-    if (!session->opt->disable_occ
-        && !options_cmp_equal_safe((char *) BPTR(buf), session->opt->remote_options, buf->len))
-    {
-        options_warning_safe((char *) BPTR(buf), session->opt->remote_options, buf->len);
-    }
-#endif
-
-    buf_clear(buf);
-
-    init_key_ctx(&ks->crypto_options.key_ctx_bi.decrypt, &key,
-                 &session->opt->key_type, OPENVPN_OP_DECRYPT,
-                 "Data Channel Decrypt");
-    secure_memzero(&key, sizeof(key));
-    ks->authenticated = true;
-    return true;
-
-error:
-    buf_clear(buf);
-    secure_memzero(&key, sizeof(key));
-    return false;
-}
-
+/**
+ * Handle reading key data, peer-info, username/password, OCC
+ * from the TLS control channel (cleartext).
+ */
 static bool
 key_method_2_read(struct buffer *buf, struct tls_multi *multi, struct tls_session *session)
 {
     struct key_state *ks = &session->key[KS_PRIMARY];      /* primary key */
 
-    int key_method_flags;
     bool username_status, password_status;
 
     struct gc_arena gc = gc_new();
@@ -2559,8 +2460,6 @@
     /* allocate temporary objects */
     ALLOC_ARRAY_CLEAR_GC(options, char, TLS_OPTIONS_LEN, &gc);
 
-    ASSERT(session->opt->key_method == 2);
-
     /* discard leading uint32 */
     if (!buf_advance(buf, 4))
     {
@@ -2570,7 +2469,7 @@
     }
 
     /* get key method */
-    key_method_flags = buf_read_u8(buf);
+    int key_method_flags = buf_read_u8(buf);
     if ((key_method_flags & KEY_METHOD_MASK) != 2)
     {
         msg(D_TLS_ERRORS,
@@ -2593,7 +2492,7 @@
         goto error;
     }
 
-    ks->authenticated = false;
+    ks->authenticated = KS_AUTH_FALSE;
 
     /* always extract username + password fields from buf, even if not
      * authenticating for it, because otherwise we can't get at the
@@ -2603,7 +2502,6 @@
     username_status = read_string(buf, up->username, USER_PASS_LEN);
     password_status = read_string(buf, up->password, USER_PASS_LEN);
 
-#if P2MP_SERVER
     /* get peer info from control channel */
     free(multi->peer_info);
     multi->peer_info = read_string_alloc(buf);
@@ -2616,18 +2514,13 @@
     multi->remote_ciphername =
         options_string_extract_option(options, "cipher", NULL);
 
-    if (tls_peer_info_ncp_ver(multi->peer_info) < 2)
+    /* In OCC we send '[null-cipher]' instead 'none' */
+    if (multi->remote_ciphername
+        && strcmp(multi->remote_ciphername, "[null-cipher]") == 0)
     {
-        /* Peer does not support NCP, but leave NCP enabled if the local and
-         * remote cipher do not match to attempt 'poor-man's NCP'.
-         */
-        if (multi->remote_ciphername == NULL
-            || 0 == strcmp(multi->remote_ciphername, multi->opt.config_ciphername))
-        {
-            session->opt->ncp_enabled = false;
-        }
+        free(multi->remote_ciphername);
+        multi->remote_ciphername = string_alloc("none", NULL);
     }
-#endif /* if P2MP_SERVER */
 
     if (tls_session_user_pass_enabled(session))
     {
@@ -2653,19 +2546,18 @@
                 "TLS Error: Certificate verification failed (key-method 2)");
             goto error;
         }
-        ks->authenticated = true;
+        ks->authenticated = KS_AUTH_TRUE;
     }
 
     /* clear username and password from memory */
     secure_memzero(up, sizeof(*up));
 
     /* Perform final authentication checks */
-    if (ks->authenticated)
+    if (ks->authenticated > KS_AUTH_FALSE)
     {
         verify_final_auth_checks(multi, session);
     }
 
-#ifdef ENABLE_OCC
     /* check options consistency */
     if (!session->opt->disable_occ
         && !options_cmp_equal(options, session->opt->remote_options))
@@ -2674,10 +2566,9 @@
         if (session->opt->ssl_flags & SSLF_OPT_VERIFY)
         {
             msg(D_TLS_ERRORS, "Option inconsistency warnings triggering disconnect due to --opt-verify");
-            ks->authenticated = false;
+            ks->authenticated = KS_AUTH_FALSE;
         }
     }
-#endif
 
     buf_clear(buf);
 
@@ -2685,13 +2576,14 @@
      * Call OPENVPN_PLUGIN_TLS_FINAL plugin if defined, for final
      * veto opportunity over authentication decision.
      */
-    if (ks->authenticated && plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_TLS_FINAL))
+    if ((ks->authenticated > KS_AUTH_FALSE)
+        && plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_TLS_FINAL))
     {
         key_state_export_keying_material(&ks->ks_ssl, session);
 
         if (plugin_call(session->opt->plugins, OPENVPN_PLUGIN_TLS_FINAL, NULL, NULL, session->opt->es) != OPENVPN_PLUGIN_FUNC_SUCCESS)
         {
-            ks->authenticated = false;
+            ks->authenticated = KS_AUTH_FALSE;
         }
 
         setenv_del(session->opt->es, "exported_keying_material");
@@ -2715,6 +2607,7 @@
     return true;
 
 error:
+    ks->authenticated = KS_AUTH_FALSE;
     secure_memzero(ks->key_src, sizeof(*ks->key_src));
     if (up)
     {
@@ -2777,9 +2670,9 @@
                 && ks->n_packets >= session->opt->renegotiate_packets)
             || (packet_id_close_to_wrapping(&ks->crypto_options.packet_id.send))))
     {
-        msg(D_TLS_DEBUG_LOW,
-            "TLS: soft reset sec=%d bytes=" counter_format "/%d pkts=" counter_format "/%d",
-            (int)(ks->established + session->opt->renegotiate_seconds - now),
+        msg(D_TLS_DEBUG_LOW, "TLS: soft reset sec=%d/%d bytes=" counter_format
+            "/%d pkts=" counter_format "/%d",
+            (int) (now - ks->established), session->opt->renegotiate_seconds,
             ks->n_bytes, session->opt->renegotiate_bytes,
             ks->n_packets, session->opt->renegotiate_packets);
         key_state_soft_reset(session);
@@ -2847,21 +2740,12 @@
         }
 
         /* Are we timed out on receive? */
-        if (now >= ks->must_negotiate)
+        if (now >= ks->must_negotiate && ks->state < S_ACTIVE)
         {
-            if (ks->state < S_ACTIVE)
-            {
-                msg(D_TLS_ERRORS,
-                    "TLS Error: TLS key negotiation failed to occur within %d seconds (check your network connectivity)",
-                    session->opt->handshake_window);
-                goto error;
-            }
-            else /* assume that ks->state == S_ACTIVE */
-            {
-                dmsg(D_TLS_DEBUG_MED, "STATE S_NORMAL_OP");
-                ks->state = S_NORMAL_OP;
-                ks->must_negotiate = 0;
-            }
+            msg(D_TLS_ERRORS,
+                "TLS Error: TLS key negotiation failed to occur within %d seconds (check your network connectivity)",
+                session->opt->handshake_window);
+            goto error;
         }
 
         /* Wait for Initial Handshake ACK */
@@ -2901,10 +2785,12 @@
                 }
                 state_change = true;
                 ks->state = S_ACTIVE;
+                /* Cancel negotiation timeout */
+                ks->must_negotiate = 0;
                 INCR_SUCCESS;
 
                 /* Set outgoing address for data channel packets */
-                link_socket_set_outgoing_addr(NULL, to_link_socket_info, &ks->remote_addr, session->common_name, session->opt->es);
+                link_socket_set_outgoing_addr(to_link_socket_info, &ks->remote_addr, session->common_name, session->opt->es);
 
                 /* Flush any payload packets that were buffered before our state transitioned to S_ACTIVE */
                 flush_payload_buffer(ks);
@@ -2992,23 +2878,9 @@
         if (!buf->len && ((ks->state == S_START && !session->opt->server)
                           || (ks->state == S_GOT_KEY && session->opt->server)))
         {
-            if (session->opt->key_method == 1)
+            if (!key_method_2_write(buf, multi, session))
             {
-                if (!key_method_1_write(buf, session))
-                {
-                    goto error;
-                }
-            }
-            else if (session->opt->key_method == 2)
-            {
-                if (!key_method_2_write(buf, session))
-                {
-                    goto error;
-                }
-            }
-            else
-            {
-                ASSERT(0);
+                goto error;
             }
 
             state_change = true;
@@ -3022,23 +2894,9 @@
             && ((ks->state == S_SENT_KEY && !session->opt->server)
                 || (ks->state == S_START && session->opt->server)))
         {
-            if (session->opt->key_method == 1)
+            if (!key_method_2_read(buf, multi, session))
             {
-                if (!key_method_1_read(buf, session))
-                {
-                    goto error;
-                }
-            }
-            else if (session->opt->key_method == 2)
-            {
-                if (!key_method_2_read(buf, multi, session))
-                {
-                    goto error;
-                }
-            }
-            else
-            {
-                ASSERT(0);
+                goto error;
             }
 
             state_change = true;
@@ -3162,10 +3020,8 @@
                   interval_t *wakeup)
 {
     struct gc_arena gc = gc_new();
-    int i;
     int active = TLSMP_INACTIVE;
     bool error = false;
-    int tas;
 
     perf_push(PERF_TLS_MULTI_PROCESS);
 
@@ -3176,7 +3032,7 @@
      * and which has a defined remote IP addr.
      */
 
-    for (i = 0; i < TM_SIZE; ++i)
+    for (int i = 0; i < TM_SIZE; ++i)
     {
         struct tls_session *session = &multi->session[i];
         struct key_state *ks = &session->key[KS_PRIMARY];
@@ -3251,7 +3107,7 @@
 
     update_time();
 
-    tas = tls_authentication_status(multi, TLS_MULTI_AUTH_STATUS_INTERVAL);
+    int tas = tls_authentication_status(multi, TLS_MULTI_AUTH_STATUS_INTERVAL);
 
     /*
      * If lame duck session expires, kill it.
@@ -3284,7 +3140,7 @@
      */
     if (error)
     {
-        for (i = 0; i < (int) SIZE(multi->key_scan); ++i)
+        for (int i = 0; i < (int) SIZE(multi->key_scan); ++i)
         {
             if (multi->key_scan[i]->state >= S_ACTIVE)
             {
@@ -3301,7 +3157,7 @@
         const int throw_level = GREMLIN_CONNECTION_FLOOD_LEVEL(multi->opt.gremlin);
         if (throw_level)
         {
-            for (i = 0; i < (int) SIZE(multi->key_scan); ++i)
+            for (int i = 0; i < (int) SIZE(multi->key_scan); ++i)
             {
                 if (multi->key_scan[i]->state >= throw_level)
                 {
@@ -3324,6 +3180,95 @@
  * to implement a multiplexed TLS channel over the TCP/UDP port.
  */
 
+static inline void
+handle_data_channel_packet(struct tls_multi *multi,
+                           const struct link_socket_actual *from,
+                           struct buffer *buf,
+                           struct crypto_options **opt,
+                           bool floated,
+                           const uint8_t **ad_start)
+{
+    struct gc_arena gc = gc_new();
+
+    uint8_t c = *BPTR(buf);
+    int op = c >> P_OPCODE_SHIFT;
+    int key_id = c & P_KEY_ID_MASK;
+
+    /* data channel packet */
+    for (int i = 0; i < KEY_SCAN_SIZE; ++i)
+    {
+        struct key_state *ks = multi->key_scan[i];
+
+        /*
+         * This is the basic test of TLS state compatibility between a local OpenVPN
+         * instance and its remote peer.
+         *
+         * If the test fails, it tells us that we are getting a packet from a source
+         * which claims reference to a prior negotiated TLS session, but the local
+         * OpenVPN instance has no memory of such a negotiation.
+         *
+         * It almost always occurs on UDP sessions when the passive side of the
+         * connection is restarted without the active side restarting as well (the
+         * passive side is the server which only listens for the connections, the
+         * active side is the client which initiates connections).
+         */
+        if (DECRYPT_KEY_ENABLED(multi, ks)
+            && key_id == ks->key_id
+            && (ks->authenticated == KS_AUTH_TRUE)
+            && (floated || link_socket_actual_match(from, &ks->remote_addr)))
+        {
+            if (!ks->crypto_options.key_ctx_bi.initialized)
+            {
+                msg(D_MULTI_DROPPED,
+                    "Key %s [%d] not initialized (yet), dropping packet.",
+                    print_link_socket_actual(from, &gc), key_id);
+                goto done;
+            }
+
+            /* return appropriate data channel decrypt key in opt */
+            *opt = &ks->crypto_options;
+            if (op == P_DATA_V2)
+            {
+                *ad_start = BPTR(buf);
+            }
+            ASSERT(buf_advance(buf, 1));
+            if (op == P_DATA_V1)
+            {
+                *ad_start = BPTR(buf);
+            }
+            else if (op == P_DATA_V2)
+            {
+                if (buf->len < 4)
+                {
+                    msg(D_TLS_ERRORS, "Protocol error: received P_DATA_V2 from %s but length is < 4",
+                        print_link_socket_actual(from, &gc));
+                    ++multi->n_soft_errors;
+                    goto done;
+                }
+                ASSERT(buf_advance(buf, 3));
+            }
+
+            ++ks->n_packets;
+            ks->n_bytes += buf->len;
+            dmsg(D_TLS_KEYSELECT,
+                 "TLS: tls_pre_decrypt, key_id=%d, IP=%s",
+                 key_id, print_link_socket_actual(from, &gc));
+            gc_free(&gc);
+            return;
+        }
+    }
+
+    msg(D_TLS_ERRORS,
+        "TLS Error: local/remote TLS keys are out of sync: %s [%d]",
+        print_link_socket_actual(from, &gc), key_id);
+
+done:
+    tls_clear_error();
+    buf->len = 0;
+    *opt = NULL;
+    gc_free(&gc);
+}
+
 /*
  *
  * When we are in TLS mode, this is the first routine which sees
@@ -3357,448 +3302,374 @@
                 bool floated,
                 const uint8_t **ad_start)
 {
+
+    if (buf->len <= 0)
+    {
+        buf->len = 0;
+        *opt = NULL;
+        return false;
+    }
+
     struct gc_arena gc = gc_new();
     bool ret = false;
 
-    if (buf->len > 0)
+    /* get opcode  */
+    uint8_t pkt_firstbyte = *BPTR(buf);
+    int op = pkt_firstbyte >> P_OPCODE_SHIFT;
+
+    if ((op == P_DATA_V1) || (op == P_DATA_V2))
     {
-        int i;
-        int op;
-        int key_id;
+        handle_data_channel_packet(multi, from, buf, opt, floated, ad_start);
+        return false;
+    }
 
-        /* get opcode and key ID */
+    /* get key_id */
+    int key_id = pkt_firstbyte & P_KEY_ID_MASK;
+
+    /* control channel packet */
+    bool do_burst = false;
+    bool new_link = false;
+    struct session_id sid;         /* remote session ID */
+
+    /* verify legal opcode */
+    if (op < P_FIRST_OPCODE || op > P_LAST_OPCODE)
+    {
+        if (op == P_CONTROL_HARD_RESET_CLIENT_V1
+            || op == P_CONTROL_HARD_RESET_SERVER_V1)
         {
-            uint8_t c = *BPTR(buf);
-            op = c >> P_OPCODE_SHIFT;
-            key_id = c & P_KEY_ID_MASK;
+            msg(D_TLS_ERRORS, "Peer tried unsupported key-method 1");
         }
+        msg(D_TLS_ERRORS,
+            "TLS Error: unknown opcode received from %s op=%d",
+            print_link_socket_actual(from, &gc), op);
+        goto error;
+    }
 
-        if ((op == P_DATA_V1) || (op == P_DATA_V2))
+    /* hard reset ? */
+    if (is_hard_reset_method2(op))
+    {
+        /* verify client -> server or server -> client connection */
+        if (((op == P_CONTROL_HARD_RESET_CLIENT_V2
+              || op == P_CONTROL_HARD_RESET_CLIENT_V3) && !multi->opt.server)
+            || ((op == P_CONTROL_HARD_RESET_SERVER_V2) && multi->opt.server))
         {
-            /* data channel packet */
-            for (i = 0; i < KEY_SCAN_SIZE; ++i)
-            {
-                struct key_state *ks = multi->key_scan[i];
-
-                /*
-                 * This is the basic test of TLS state compatibility between a local OpenVPN
-                 * instance and its remote peer.
-                 *
-                 * If the test fails, it tells us that we are getting a packet from a source
-                 * which claims reference to a prior negotiated TLS session, but the local
-                 * OpenVPN instance has no memory of such a negotiation.
-                 *
-                 * It almost always occurs on UDP sessions when the passive side of the
-                 * connection is restarted without the active side restarting as well (the
-                 * passive side is the server which only listens for the connections, the
-                 * active side is the client which initiates connections).
-                 */
-                if (DECRYPT_KEY_ENABLED(multi, ks)
-                    && key_id == ks->key_id
-                    && ks->authenticated
-#ifdef ENABLE_DEF_AUTH
-                    && !ks->auth_deferred
-#endif
-                    && (floated || link_socket_actual_match(from, &ks->remote_addr)))
-                {
-                    if (!ks->crypto_options.key_ctx_bi.initialized)
-                    {
-                        msg(D_MULTI_DROPPED,
-                            "Key %s [%d] not initialized (yet), dropping packet.",
-                            print_link_socket_actual(from, &gc), key_id);
-                        goto error_lite;
-                    }
-
-                    /* return appropriate data channel decrypt key in opt */
-                    *opt = &ks->crypto_options;
-                    if (op == P_DATA_V2)
-                    {
-                        *ad_start = BPTR(buf);
-                    }
-                    ASSERT(buf_advance(buf, 1));
-                    if (op == P_DATA_V1)
-                    {
-                        *ad_start = BPTR(buf);
-                    }
-                    else if (op == P_DATA_V2)
-                    {
-                        if (buf->len < 4)
-                        {
-                            msg(D_TLS_ERRORS, "Protocol error: received P_DATA_V2 from %s but length is < 4",
-                                print_link_socket_actual(from, &gc));
-                            goto error;
-                        }
-                        ASSERT(buf_advance(buf, 3));
-                    }
-
-                    ++ks->n_packets;
-                    ks->n_bytes += buf->len;
-                    dmsg(D_TLS_KEYSELECT,
-                         "TLS: tls_pre_decrypt, key_id=%d, IP=%s",
-                         key_id, print_link_socket_actual(from, &gc));
-                    gc_free(&gc);
-                    return ret;
-                }
-            }
-
             msg(D_TLS_ERRORS,
-                "TLS Error: local/remote TLS keys are out of sync: %s [%d]",
-                print_link_socket_actual(from, &gc), key_id);
-            goto error_lite;
+                "TLS Error: client->client or server->server connection attempted from %s",
+                print_link_socket_actual(from, &gc));
+            goto error;
         }
-        else                      /* control channel packet */
-        {
-            bool do_burst = false;
-            bool new_link = false;
-            struct session_id sid; /* remote session ID */
+    }
 
-            /* verify legal opcode */
-            if (op < P_FIRST_OPCODE || op > P_LAST_OPCODE)
+    /*
+     * Authenticate Packet
+     */
+    dmsg(D_TLS_DEBUG, "TLS: control channel, op=%s, IP=%s",
+         packet_opcode_name(op), print_link_socket_actual(from, &gc));
+
+    /* get remote session-id */
+    {
+        struct buffer tmp = *buf;
+        buf_advance(&tmp, 1);
+        if (!session_id_read(&sid, &tmp) || !session_id_defined(&sid))
+        {
+            msg(D_TLS_ERRORS,
+                "TLS Error: session-id not found in packet from %s",
+                print_link_socket_actual(from, &gc));
+            goto error;
+        }
+    }
+
+    int i;
+    /* use session ID to match up packet with appropriate tls_session object */
+    for (i = 0; i < TM_SIZE; ++i)
+    {
+        struct tls_session *session = &multi->session[i];
+        struct key_state *ks = &session->key[KS_PRIMARY];
+
+        dmsg(D_TLS_DEBUG,
+             "TLS: initial packet test, i=%d state=%s, mysid=%s, rec-sid=%s, rec-ip=%s, stored-sid=%s, stored-ip=%s",
+             i,
+             state_name(ks->state),
+             session_id_print(&session->session_id, &gc),
+             session_id_print(&sid, &gc),
+             print_link_socket_actual(from, &gc),
+             session_id_print(&ks->session_id_remote, &gc),
+             print_link_socket_actual(&ks->remote_addr, &gc));
+
+        if (session_id_equal(&ks->session_id_remote, &sid))
+        /* found a match */
+        {
+            if (i == TM_LAME_DUCK)
             {
                 msg(D_TLS_ERRORS,
-                    "TLS Error: unknown opcode received from %s op=%d",
-                    print_link_socket_actual(from, &gc), op);
+                    "TLS ERROR: received control packet with stale session-id=%s",
+                    session_id_print(&sid, &gc));
+                goto error;
+            }
+            dmsg(D_TLS_DEBUG,
+                 "TLS: found match, session[%d], sid=%s",
+                 i, session_id_print(&sid, &gc));
+            break;
+        }
+    }
+
+    /*
+     * Hard reset and session id does not match any session in
+     * multi->session: Possible initial packet
+     */
+    if (i == TM_SIZE && is_hard_reset_method2(op))
+    {
+        struct tls_session *session = &multi->session[TM_ACTIVE];
+        struct key_state *ks = &session->key[KS_PRIMARY];
+
+        /*
+         * If we have no session currently in progress, the initial packet will
+         * open a new session in TM_ACTIVE rather than TM_UNTRUSTED.
+         */
+        if (!session_id_defined(&ks->session_id_remote))
+        {
+            if (multi->opt.single_session && multi->n_sessions)
+            {
+                msg(D_TLS_ERRORS,
+                    "TLS Error: Cannot accept new session request from %s due to session context expire or --single-session [1]",
+                    print_link_socket_actual(from, &gc));
                 goto error;
             }
 
-            /* hard reset ? */
-            if (is_hard_reset(op, 0))
-            {
-                /* verify client -> server or server -> client connection */
-                if (((op == P_CONTROL_HARD_RESET_CLIENT_V1
-                      || op == P_CONTROL_HARD_RESET_CLIENT_V2) && !multi->opt.server)
-                    || ((op == P_CONTROL_HARD_RESET_SERVER_V1
-                         || op == P_CONTROL_HARD_RESET_SERVER_V2) && multi->opt.server))
-                {
-                    msg(D_TLS_ERRORS,
-                        "TLS Error: client->client or server->server connection attempted from %s",
-                        print_link_socket_actual(from, &gc));
-                    goto error;
-                }
-            }
-
-            /*
-             * Authenticate Packet
-             */
-            dmsg(D_TLS_DEBUG, "TLS: control channel, op=%s, IP=%s",
-                 packet_opcode_name(op), print_link_socket_actual(from, &gc));
-
-            /* get remote session-id */
-            {
-                struct buffer tmp = *buf;
-                buf_advance(&tmp, 1);
-                if (!session_id_read(&sid, &tmp) || !session_id_defined(&sid))
-                {
-                    msg(D_TLS_ERRORS,
-                        "TLS Error: session-id not found in packet from %s",
-                        print_link_socket_actual(from, &gc));
-                    goto error;
-                }
-            }
-
-            /* use session ID to match up packet with appropriate tls_session object */
-            for (i = 0; i < TM_SIZE; ++i)
-            {
-                struct tls_session *session = &multi->session[i];
-                struct key_state *ks = &session->key[KS_PRIMARY];
-
-                dmsg(D_TLS_DEBUG,
-                     "TLS: initial packet test, i=%d state=%s, mysid=%s, rec-sid=%s, rec-ip=%s, stored-sid=%s, stored-ip=%s",
-                     i,
-                     state_name(ks->state),
-                     session_id_print(&session->session_id, &gc),
-                     session_id_print(&sid, &gc),
-                     print_link_socket_actual(from, &gc),
-                     session_id_print(&ks->session_id_remote, &gc),
-                     print_link_socket_actual(&ks->remote_addr, &gc));
-
-                if (session_id_equal(&ks->session_id_remote, &sid))
-                /* found a match */
-                {
-                    if (i == TM_LAME_DUCK)
-                    {
-                        msg(D_TLS_ERRORS,
-                            "TLS ERROR: received control packet with stale session-id=%s",
-                            session_id_print(&sid, &gc));
-                        goto error;
-                    }
-                    dmsg(D_TLS_DEBUG,
-                         "TLS: found match, session[%d], sid=%s",
-                         i, session_id_print(&sid, &gc));
-                    break;
-                }
-            }
-
-            /*
-             * Initial packet received.
-             */
-
-            if (i == TM_SIZE && is_hard_reset(op, 0))
-            {
-                struct tls_session *session = &multi->session[TM_ACTIVE];
-                struct key_state *ks = &session->key[KS_PRIMARY];
-
-                if (!is_hard_reset(op, multi->opt.key_method))
-                {
-                    msg(D_TLS_ERRORS, "TLS ERROR: initial packet local/remote key_method mismatch, local key_method=%d, op=%s",
-                        multi->opt.key_method,
-                        packet_opcode_name(op));
-                    goto error;
-                }
-
-                /*
-                 * If we have no session currently in progress, the initial packet will
-                 * open a new session in TM_ACTIVE rather than TM_UNTRUSTED.
-                 */
-                if (!session_id_defined(&ks->session_id_remote))
-                {
-                    if (multi->opt.single_session && multi->n_sessions)
-                    {
-                        msg(D_TLS_ERRORS,
-                            "TLS Error: Cannot accept new session request from %s due to session context expire or --single-session [1]",
-                            print_link_socket_actual(from, &gc));
-                        goto error;
-                    }
-
 #ifdef ENABLE_MANAGEMENT
-                    if (management)
-                    {
-                        management_set_state(management,
-                                             OPENVPN_STATE_AUTH,
-                                             NULL,
-                                             NULL,
-                                             NULL,
-                                             NULL,
-                                             NULL);
-                    }
+            if (management)
+            {
+                management_set_state(management,
+                                     OPENVPN_STATE_AUTH,
+                                     NULL,
+                                     NULL,
+                                     NULL,
+                                     NULL,
+                                     NULL);
+            }
 #endif
 
-                    msg(D_TLS_DEBUG_LOW,
-                        "TLS: Initial packet from %s, sid=%s",
-                        print_link_socket_actual(from, &gc),
-                        session_id_print(&sid, &gc));
+            msg(D_TLS_DEBUG_LOW,
+                "TLS: Initial packet from %s, sid=%s",
+                print_link_socket_actual(from, &gc),
+                session_id_print(&sid, &gc));
 
-                    do_burst = true;
-                    new_link = true;
-                    i = TM_ACTIVE;
-                    session->untrusted_addr = *from;
-                }
-            }
+            do_burst = true;
+            new_link = true;
+            i = TM_ACTIVE;
+            session->untrusted_addr = *from;
+        }
+    }
 
-            if (i == TM_SIZE && is_hard_reset(op, 0))
+    /*
+     * If we detected new session in the last if block, variable i has
+     * changed to TM_ACTIVE, so check the condition again.
+     */
+    if (i == TM_SIZE && is_hard_reset_method2(op))
+    {
+        /*
+         * No match with existing sessions,
+         * probably a new session.
+         */
+        struct tls_session *session = &multi->session[TM_UNTRUSTED];
+
+        /*
+         * If --single-session, don't allow any hard-reset connection request
+         * unless it the first packet of the session.
+         */
+        if (multi->opt.single_session)
+        {
+            msg(D_TLS_ERRORS,
+                "TLS Error: Cannot accept new session request from %s due to session context expire or --single-session [2]",
+                print_link_socket_actual(from, &gc));
+            goto error;
+        }
+
+        if (!read_control_auth(buf, &session->tls_wrap, from,
+                               session->opt))
+        {
+            goto error;
+        }
+
+        /*
+         * New session-initiating control packet is authenticated at this point,
+         * assuming that the --tls-auth command line option was used.
+         *
+         * Without --tls-auth, we leave authentication entirely up to TLS.
+         */
+        msg(D_TLS_DEBUG_LOW,
+            "TLS: new session incoming connection from %s",
+            print_link_socket_actual(from, &gc));
+
+        new_link = true;
+        i = TM_UNTRUSTED;
+        session->untrusted_addr = *from;
+    }
+    else
+    {
+        struct tls_session *session = &multi->session[i];
+        struct key_state *ks = &session->key[KS_PRIMARY];
+
+        /*
+         * Packet must belong to an existing session.
+         */
+        if (i != TM_ACTIVE && i != TM_UNTRUSTED)
+        {
+            msg(D_TLS_ERRORS,
+                "TLS Error: Unroutable control packet received from %s (si=%d op=%s)",
+                print_link_socket_actual(from, &gc),
+                i,
+                packet_opcode_name(op));
+            goto error;
+        }
+
+        /*
+         * Verify remote IP address
+         */
+        if (!new_link && !link_socket_actual_match(&ks->remote_addr, from))
+        {
+            msg(D_TLS_ERRORS, "TLS Error: Received control packet from unexpected IP addr: %s",
+                print_link_socket_actual(from, &gc));
+            goto error;
+        }
+
+        /*
+         * Remote is requesting a key renegotiation
+         */
+        if (op == P_CONTROL_SOFT_RESET_V1
+            && DECRYPT_KEY_ENABLED(multi, ks))
+        {
+            if (!read_control_auth(buf, &session->tls_wrap, from,
+                                   session->opt))
             {
-                /*
-                 * No match with existing sessions,
-                 * probably a new session.
-                 */
-                struct tls_session *session = &multi->session[TM_UNTRUSTED];
-
-                /*
-                 * If --single-session, don't allow any hard-reset connection request
-                 * unless it the the first packet of the session.
-                 */
-                if (multi->opt.single_session)
-                {
-                    msg(D_TLS_ERRORS,
-                        "TLS Error: Cannot accept new session request from %s due to session context expire or --single-session [2]",
-                        print_link_socket_actual(from, &gc));
-                    goto error;
-                }
-
-                if (!is_hard_reset(op, multi->opt.key_method))
-                {
-                    msg(D_TLS_ERRORS, "TLS ERROR: new session local/remote key_method mismatch, local key_method=%d, op=%s",
-                        multi->opt.key_method,
-                        packet_opcode_name(op));
-                    goto error;
-                }
-
-                if (!read_control_auth(buf, &session->tls_wrap, from))
-                {
-                    goto error;
-                }
-
-                /*
-                 * New session-initiating control packet is authenticated at this point,
-                 * assuming that the --tls-auth command line option was used.
-                 *
-                 * Without --tls-auth, we leave authentication entirely up to TLS.
-                 */
-                msg(D_TLS_DEBUG_LOW,
-                    "TLS: new session incoming connection from %s",
-                    print_link_socket_actual(from, &gc));
-
-                new_link = true;
-                i = TM_UNTRUSTED;
-                session->untrusted_addr = *from;
-            }
-            else
-            {
-                struct tls_session *session = &multi->session[i];
-                struct key_state *ks = &session->key[KS_PRIMARY];
-
-                /*
-                 * Packet must belong to an existing session.
-                 */
-                if (i != TM_ACTIVE && i != TM_UNTRUSTED)
-                {
-                    msg(D_TLS_ERRORS,
-                        "TLS Error: Unroutable control packet received from %s (si=%d op=%s)",
-                        print_link_socket_actual(from, &gc),
-                        i,
-                        packet_opcode_name(op));
-                    goto error;
-                }
-
-                /*
-                 * Verify remote IP address
-                 */
-                if (!new_link && !link_socket_actual_match(&ks->remote_addr, from))
-                {
-                    msg(D_TLS_ERRORS, "TLS Error: Received control packet from unexpected IP addr: %s",
-                        print_link_socket_actual(from, &gc));
-                    goto error;
-                }
-
-                /*
-                 * Remote is requesting a key renegotiation
-                 */
-                if (op == P_CONTROL_SOFT_RESET_V1
-                    && DECRYPT_KEY_ENABLED(multi, ks))
-                {
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
-                    {
-                        goto error;
-                    }
-
-                    key_state_soft_reset(session);
-
-                    dmsg(D_TLS_DEBUG,
-                         "TLS: received P_CONTROL_SOFT_RESET_V1 s=%d sid=%s",
-                         i, session_id_print(&sid, &gc));
-                }
-                else
-                {
-                    /*
-                     * Remote responding to our key renegotiation request?
-                     */
-                    if (op == P_CONTROL_SOFT_RESET_V1)
-                    {
-                        do_burst = true;
-                    }
-
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
-                    {
-                        goto error;
-                    }
-
-                    dmsg(D_TLS_DEBUG,
-                         "TLS: received control channel packet s#=%d sid=%s",
-                         i, session_id_print(&sid, &gc));
-                }
+                goto error;
             }
 
+            key_state_soft_reset(session);
+
+            dmsg(D_TLS_DEBUG,
+                 "TLS: received P_CONTROL_SOFT_RESET_V1 s=%d sid=%s",
+                 i, session_id_print(&sid, &gc));
+        }
+        else
+        {
             /*
-             * We have an authenticated control channel packet (if --tls-auth was set).
-             * Now pass to our reliability layer which deals with
-             * packet acknowledgements, retransmits, sequencing, etc.
+             * Remote responding to our key renegotiation request?
              */
+            if (op == P_CONTROL_SOFT_RESET_V1)
             {
-                struct tls_session *session = &multi->session[i];
-                struct key_state *ks = &session->key[KS_PRIMARY];
+                do_burst = true;
+            }
 
-                /* Make sure we were initialized and that we're not in an error state */
-                ASSERT(ks->state != S_UNDEF);
-                ASSERT(ks->state != S_ERROR);
-                ASSERT(session_id_defined(&session->session_id));
+            if (!read_control_auth(buf, &session->tls_wrap, from,
+                                   session->opt))
+            {
+                goto error;
+            }
 
-                /* Let our caller know we processed a control channel packet */
-                ret = true;
+            dmsg(D_TLS_DEBUG,
+                 "TLS: received control channel packet s#=%d sid=%s",
+                 i, session_id_print(&sid, &gc));
+        }
+    }
 
-                /*
-                 * Set our remote address and remote session_id
-                 */
-                if (new_link)
+    /*
+     * We have an authenticated control channel packet (if --tls-auth was set).
+     * Now pass to our reliability layer which deals with
+     * packet acknowledgements, retransmits, sequencing, etc.
+     */
+    struct tls_session *session = &multi->session[i];
+    struct key_state *ks = &session->key[KS_PRIMARY];
+
+    /* Make sure we were initialized and that we're not in an error state */
+    ASSERT(ks->state != S_UNDEF);
+    ASSERT(ks->state != S_ERROR);
+    ASSERT(session_id_defined(&session->session_id));
+
+    /* Let our caller know we processed a control channel packet */
+    ret = true;
+
+    /*
+     * Set our remote address and remote session_id
+     */
+    if (new_link)
+    {
+        ks->session_id_remote = sid;
+        ks->remote_addr = *from;
+        ++multi->n_sessions;
+    }
+    else if (!link_socket_actual_match(&ks->remote_addr, from))
+    {
+        msg(D_TLS_ERRORS,
+            "TLS Error: Existing session control channel packet from unknown IP address: %s",
+            print_link_socket_actual(from, &gc));
+        goto error;
+    }
+
+    /*
+     * Should we do a retransmit of all unacknowledged packets in
+     * the send buffer?  This improves the start-up efficiency of the
+     * initial key negotiation after the 2nd peer comes online.
+     */
+    if (do_burst && !session->burst)
+    {
+        reliable_schedule_now(ks->send_reliable);
+        session->burst = true;
+    }
+
+    /* Check key_id */
+    if (ks->key_id != key_id)
+    {
+        msg(D_TLS_ERRORS,
+            "TLS ERROR: local/remote key IDs out of sync (%d/%d) ID: %s",
+            ks->key_id, key_id, print_key_id(multi, &gc));
+        goto error;
+    }
+
+    /*
+     * Process incoming ACKs for packets we can now
+     * delete from reliable send buffer
+     */
+    {
+        /* buffers all packet IDs to delete from send_reliable */
+        struct reliable_ack send_ack;
+
+        send_ack.len = 0;
+        if (!reliable_ack_read(&send_ack, buf, &session->session_id))
+        {
+            msg(D_TLS_ERRORS,
+                "TLS Error: reading acknowledgement record from packet");
+            goto error;
+        }
+        reliable_send_purge(ks->send_reliable, &send_ack);
+    }
+
+    if (op != P_ACK_V1 && reliable_can_get(ks->rec_reliable))
+    {
+        packet_id_type id;
+
+        /* Extract the packet ID from the packet */
+        if (reliable_ack_read_packet_id(buf, &id))
+        {
+            /* Avoid deadlock by rejecting packet that would de-sequentialize receive buffer */
+            if (reliable_wont_break_sequentiality(ks->rec_reliable, id))
+            {
+                if (reliable_not_replay(ks->rec_reliable, id))
                 {
-                    ks->session_id_remote = sid;
-                    ks->remote_addr = *from;
-                    ++multi->n_sessions;
-                }
-                else if (!link_socket_actual_match(&ks->remote_addr, from))
-                {
-                    msg(D_TLS_ERRORS,
-                        "TLS Error: Existing session control channel packet from unknown IP address: %s",
-                        print_link_socket_actual(from, &gc));
-                    goto error;
-                }
-
-                /*
-                 * Should we do a retransmit of all unacknowledged packets in
-                 * the send buffer?  This improves the start-up efficiency of the
-                 * initial key negotiation after the 2nd peer comes online.
-                 */
-                if (do_burst && !session->burst)
-                {
-                    reliable_schedule_now(ks->send_reliable);
-                    session->burst = true;
-                }
-
-                /* Check key_id */
-                if (ks->key_id != key_id)
-                {
-                    msg(D_TLS_ERRORS,
-                        "TLS ERROR: local/remote key IDs out of sync (%d/%d) ID: %s",
-                        ks->key_id, key_id, print_key_id(multi, &gc));
-                    goto error;
-                }
-
-                /*
-                 * Process incoming ACKs for packets we can now
-                 * delete from reliable send buffer
-                 */
-                {
-                    /* buffers all packet IDs to delete from send_reliable */
-                    struct reliable_ack send_ack;
-
-                    send_ack.len = 0;
-                    if (!reliable_ack_read(&send_ack, buf, &session->session_id))
+                    /* Save incoming ciphertext packet to reliable buffer */
+                    struct buffer *in = reliable_get_buf(ks->rec_reliable);
+                    ASSERT(in);
+                    if (!buf_copy(in, buf))
                     {
-                        msg(D_TLS_ERRORS,
-                            "TLS Error: reading acknowledgement record from packet");
+                        msg(D_MULTI_DROPPED,
+                            "Incoming control channel packet too big, dropping.");
                         goto error;
                     }
-                    reliable_send_purge(ks->send_reliable, &send_ack);
+                    reliable_mark_active_incoming(ks->rec_reliable, in, id, op);
                 }
 
-                if (op != P_ACK_V1 && reliable_can_get(ks->rec_reliable))
-                {
-                    packet_id_type id;
-
-                    /* Extract the packet ID from the packet */
-                    if (reliable_ack_read_packet_id(buf, &id))
-                    {
-                        /* Avoid deadlock by rejecting packet that would de-sequentialize receive buffer */
-                        if (reliable_wont_break_sequentiality(ks->rec_reliable, id))
-                        {
-                            if (reliable_not_replay(ks->rec_reliable, id))
-                            {
-                                /* Save incoming ciphertext packet to reliable buffer */
-                                struct buffer *in = reliable_get_buf(ks->rec_reliable);
-                                ASSERT(in);
-                                if(!buf_copy(in, buf))
-                                {
-                                    msg(D_MULTI_DROPPED,
-                                        "Incoming control channel packet too big, dropping.");
-                                    goto error;
-                                }
-                                reliable_mark_active_incoming(ks->rec_reliable, in, id, op);
-                            }
-
-                            /* Process outgoing acknowledgment for packet just received, even if it's a replay */
-                            reliable_ack_acknowledge_packet_id(ks->rec_ack, id);
-                        }
-                    }
-                }
+                /* Process outgoing acknowledgment for packet just received, even if it's a replay */
+                reliable_ack_acknowledge_packet_id(ks->rec_ack, id);
             }
         }
     }
@@ -3811,7 +3682,6 @@
 
 error:
     ++multi->n_soft_errors;
-error_lite:
     tls_clear_error();
     goto done;
 }
@@ -3833,94 +3703,91 @@
                      const struct buffer *buf)
 
 {
-    struct gc_arena gc = gc_new();
-    bool ret = false;
-
-    if (buf->len > 0)
+    if (buf->len <= 0)
     {
-        int op;
-        int key_id;
-
-        /* get opcode and key ID */
-        {
-            uint8_t c = *BPTR(buf);
-            op = c >> P_OPCODE_SHIFT;
-            key_id = c & P_KEY_ID_MASK;
-        }
-
-        /* this packet is from an as-yet untrusted source, so
-         * scrutinize carefully */
-
-        if (op != P_CONTROL_HARD_RESET_CLIENT_V2)
-        {
-            /*
-             * This can occur due to bogus data or DoS packets.
-             */
-            dmsg(D_TLS_STATE_ERRORS,
-                 "TLS State Error: No TLS state for client %s, opcode=%d",
-                 print_link_socket_actual(from, &gc),
-                 op);
-            goto error;
-        }
-
-        if (key_id != 0)
-        {
-            dmsg(D_TLS_STATE_ERRORS,
-                 "TLS State Error: Unknown key ID (%d) received from %s -- 0 was expected",
-                 key_id,
-                 print_link_socket_actual(from, &gc));
-            goto error;
-        }
-
-        if (buf->len > EXPANDED_SIZE_DYNAMIC(&tas->frame))
-        {
-            dmsg(D_TLS_STATE_ERRORS,
-                 "TLS State Error: Large packet (size %d) received from %s -- a packet no larger than %d bytes was expected",
-                 buf->len,
-                 print_link_socket_actual(from, &gc),
-                 EXPANDED_SIZE_DYNAMIC(&tas->frame));
-            goto error;
-        }
-
-        {
-            struct buffer newbuf = clone_buf(buf);
-            struct tls_wrap_ctx tls_wrap_tmp = tas->tls_wrap;
-            bool status;
-
-            /* HMAC test, if --tls-auth was specified */
-            status = read_control_auth(&newbuf, &tls_wrap_tmp, from);
-            free_buf(&newbuf);
-            if (!status)
-            {
-                goto error;
-            }
-
-            /*
-             * At this point, if --tls-auth is being used, we know that
-             * the packet has passed the HMAC test, but we don't know if
-             * it is a replay yet.  We will attempt to defeat replays
-             * by not advancing to the S_START state until we
-             * receive an ACK from our first reply to the client
-             * that includes an HMAC of our randomly generated 64 bit
-             * session ID.
-             *
-             * On the other hand if --tls-auth is not being used, we
-             * will proceed to begin the TLS authentication
-             * handshake with only cursory integrity checks having
-             * been performed, since we will be leaving the task
-             * of authentication solely up to TLS.
-             */
-
-            ret = true;
-        }
+        return false;
     }
+    struct gc_arena gc = gc_new();
+
+    /* get opcode and key ID */
+    uint8_t pkt_firstbyte = *BPTR(buf);
+    int op = pkt_firstbyte >> P_OPCODE_SHIFT;
+    int key_id = pkt_firstbyte & P_KEY_ID_MASK;
+
+    /* this packet is from an as-yet untrusted source, so
+     * scrutinize carefully */
+
+    if (op != P_CONTROL_HARD_RESET_CLIENT_V2
+        && op != P_CONTROL_HARD_RESET_CLIENT_V3)
+    {
+        /*
+         * This can occur due to bogus data or DoS packets.
+         */
+        dmsg(D_TLS_STATE_ERRORS,
+             "TLS State Error: No TLS state for client %s, opcode=%d",
+             print_link_socket_actual(from, &gc),
+             op);
+        goto error;
+    }
+
+    if (key_id != 0)
+    {
+        dmsg(D_TLS_STATE_ERRORS,
+             "TLS State Error: Unknown key ID (%d) received from %s -- 0 was expected",
+             key_id,
+             print_link_socket_actual(from, &gc));
+        goto error;
+    }
+
+    if (buf->len > EXPANDED_SIZE_DYNAMIC(&tas->frame))
+    {
+        dmsg(D_TLS_STATE_ERRORS,
+             "TLS State Error: Large packet (size %d) received from %s -- a packet no larger than %d bytes was expected",
+             buf->len,
+             print_link_socket_actual(from, &gc),
+             EXPANDED_SIZE_DYNAMIC(&tas->frame));
+        goto error;
+    }
+
+
+    struct buffer newbuf = clone_buf(buf);
+    struct tls_wrap_ctx tls_wrap_tmp = tas->tls_wrap;
+
+    /* HMAC test, if --tls-auth was specified */
+    bool status = read_control_auth(&newbuf, &tls_wrap_tmp, from, NULL);
+    free_buf(&newbuf);
+    free_buf(&tls_wrap_tmp.tls_crypt_v2_metadata);
+    if (tls_wrap_tmp.cleanup_key_ctx)
+    {
+        free_key_ctx_bi(&tls_wrap_tmp.opt.key_ctx_bi);
+    }
+    if (!status)
+    {
+        goto error;
+    }
+
+    /*
+     * At this point, if --tls-auth is being used, we know that
+     * the packet has passed the HMAC test, but we don't know if
+     * it is a replay yet.  We will attempt to defeat replays
+     * by not advancing to the S_START state until we
+     * receive an ACK from our first reply to the client
+     * that includes an HMAC of our randomly generated 64 bit
+     * session ID.
+     *
+     * On the other hand if --tls-auth is not being used, we
+     * will proceed to begin the TLS authentication
+     * handshake with only cursory integrity checks having
+     * been performed, since we will be leaving the task
+     * of authentication solely up to TLS.
+     */
     gc_free(&gc);
-    return ret;
+    return true;
 
 error:
     tls_clear_error();
     gc_free(&gc);
-    return ret;
+    return false;
 }
 
 /* Choose the key with which to encrypt a data packet */
@@ -3929,51 +3796,51 @@
                 struct buffer *buf, struct crypto_options **opt)
 {
     multi->save_ks = NULL;
-    if (buf->len > 0)
+    if (buf->len <= 0)
     {
-        int i;
-        struct key_state *ks_select = NULL;
-        for (i = 0; i < KEY_SCAN_SIZE; ++i)
-        {
-            struct key_state *ks = multi->key_scan[i];
-            if (ks->state >= S_ACTIVE
-                && ks->authenticated
-                && ks->crypto_options.key_ctx_bi.initialized
-#ifdef ENABLE_DEF_AUTH
-                && !ks->auth_deferred
-#endif
-                )
-            {
-                if (!ks_select)
-                {
-                    ks_select = ks;
-                }
-                if (now >= ks->auth_deferred_expire)
-                {
-                    ks_select = ks;
-                    break;
-                }
-            }
-        }
+        buf->len = 0;
+        *opt = NULL;
+        return;
+    }
 
-        if (ks_select)
+    struct key_state *ks_select = NULL;
+    for (int i = 0; i < KEY_SCAN_SIZE; ++i)
+    {
+        struct key_state *ks = multi->key_scan[i];
+        if (ks->state >= S_ACTIVE
+            && (ks->authenticated == KS_AUTH_TRUE)
+            && ks->crypto_options.key_ctx_bi.initialized
+            )
         {
-            *opt = &ks_select->crypto_options;
-            multi->save_ks = ks_select;
-            dmsg(D_TLS_KEYSELECT, "TLS: tls_pre_encrypt: key_id=%d", ks_select->key_id);
-            return;
-        }
-        else
-        {
-            struct gc_arena gc = gc_new();
-            dmsg(D_TLS_KEYSELECT, "TLS Warning: no data channel send key available: %s",
-                 print_key_id(multi, &gc));
-            gc_free(&gc);
+            if (!ks_select)
+            {
+                ks_select = ks;
+            }
+            if (now >= ks->auth_deferred_expire)
+            {
+                ks_select = ks;
+                break;
+            }
         }
     }
 
-    buf->len = 0;
-    *opt = NULL;
+    if (ks_select)
+    {
+        *opt = &ks_select->crypto_options;
+        multi->save_ks = ks_select;
+        dmsg(D_TLS_KEYSELECT, "TLS: tls_pre_encrypt: key_id=%d", ks_select->key_id);
+        return;
+    }
+    else
+    {
+        struct gc_arena gc = gc_new();
+        dmsg(D_TLS_KEYSELECT, "TLS Warning: no data channel send key available: %s",
+             print_key_id(multi, &gc));
+        gc_free(&gc);
+
+        *opt = NULL;
+        buf->len = 0;
+    }
 }
 
 void
@@ -4097,13 +3964,11 @@
 tls_update_remote_addr(struct tls_multi *multi, const struct link_socket_actual *addr)
 {
     struct gc_arena gc = gc_new();
-    int i, j;
-
-    for (i = 0; i < TM_SIZE; ++i)
+    for (int i = 0; i < TM_SIZE; ++i)
     {
         struct tls_session *session = &multi->session[i];
 
-        for (j = 0; j < KS_SIZE; ++j)
+        for (int j = 0; j < KS_SIZE; ++j)
         {
             struct key_state *ks = &session->key[j];
 
@@ -4123,45 +3988,6 @@
     gc_free(&gc);
 }
 
-int
-tls_peer_info_ncp_ver(const char *peer_info)
-{
-    const char *ncpstr = peer_info ? strstr(peer_info, "IV_NCP=") : NULL;
-    if (ncpstr)
-    {
-        int ncp = 0;
-        int r = sscanf(ncpstr, "IV_NCP=%d", &ncp);
-        if (r == 1)
-        {
-            return ncp;
-        }
-    }
-    return 0;
-}
-
-bool
-tls_check_ncp_cipher_list(const char *list)
-{
-    bool unsupported_cipher_found = false;
-
-    ASSERT(list);
-
-    char *const tmp_ciphers = string_alloc(list, NULL);
-    const char *token = strtok(tmp_ciphers, ":");
-    while (token)
-    {
-        if (!cipher_kt_get(translate_cipher_name_from_openvpn(token)))
-        {
-            msg(M_WARN, "Unsupported cipher in --ncp-ciphers: %s", token);
-            unsupported_cipher_found = true;
-        }
-        token = strtok(NULL, ":");
-    }
-    free(tmp_ciphers);
-
-    return 0 < strlen(list) && !unsupported_cipher_found;
-}
-
 void
 show_available_tls_ciphers(const char *cipher_list,
                            const char *cipher_list_tls13,
@@ -4169,21 +3995,20 @@
 {
     printf("Available TLS Ciphers, listed in order of preference:\n");
 
-#if (ENABLE_CRYPTO_OPENSSL && OPENSSL_VERSION_NUMBER >= 0x1010100fL)
-    printf("\nFor TLS 1.3 and newer (--tls-ciphersuites):\n\n");
-    show_available_tls_ciphers_list(cipher_list_tls13, tls_cert_profile, true);
-#else
-    (void) cipher_list_tls13;  /* Avoid unused warning */
-#endif
+    if (tls_version_max() >= TLS_VER_1_3)
+    {
+        printf("\nFor TLS 1.3 and newer (--tls-ciphersuites):\n\n");
+        show_available_tls_ciphers_list(cipher_list_tls13, tls_cert_profile, true);
+    }
 
     printf("\nFor TLS 1.2 and older (--tls-cipher):\n\n");
     show_available_tls_ciphers_list(cipher_list, tls_cert_profile, false);
 
     printf("\n"
-    "Be aware that that whether a cipher suite in this list can actually work\n"
-    "depends on the specific setup of both peers. See the man page entries of\n"
-    "--tls-cipher and --show-tls for more details.\n\n"
-    );
+           "Be aware that that whether a cipher suite in this list can actually work\n"
+           "depends on the specific setup of both peers. See the man page entries of\n"
+           "--tls-cipher and --show-tls for more details.\n\n"
+           );
 }
 
 /*
@@ -4309,15 +4134,7 @@
 }
 
 void
-delayed_auth_pass_purge(void)
+ssl_clean_user_pass(void)
 {
-    auth_user_pass.wait_for_push = false;
     purge_user_pass(&auth_user_pass, false);
 }
-
-#else  /* if defined(ENABLE_CRYPTO) */
-static void
-dummy(void)
-{
-}
-#endif /* ENABLE_CRYPTO */
diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h
index 3266f38..bb6240d 100644
--- a/src/openvpn/ssl.h
+++ b/src/openvpn/ssl.h
@@ -29,8 +29,6 @@
 #ifndef OPENVPN_SSL_H
 #define OPENVPN_SSL_H
 
-#if defined(ENABLE_CRYPTO)
-
 #include "basic.h"
 #include "common.h"
 #include "crypto.h"
@@ -65,9 +63,14 @@
 #define P_CONTROL_HARD_RESET_CLIENT_V2 7     /* initial key from client, forget previous state */
 #define P_CONTROL_HARD_RESET_SERVER_V2 8     /* initial key from server, forget previous state */
 
-/* define the range of legal opcodes */
-#define P_FIRST_OPCODE                 1
-#define P_LAST_OPCODE                  9
+/* indicates key_method >= 2 and client-specific tls-crypt key */
+#define P_CONTROL_HARD_RESET_CLIENT_V3 10    /* initial key from client, forget previous state */
+
+/* define the range of legal opcodes
+ * Since we do no longer support key-method 1 we consider
+ * the v1 op codes invalid */
+#define P_FIRST_OPCODE                 3
+#define P_LAST_OPCODE                  10
 
 /*
  * Set the max number of acknowledgments that can "hitch a ride" on an outgoing
@@ -88,13 +91,6 @@
 #define TLS_MULTI_HORIZON 2     /* call tls_multi_process frequently for n seconds after
                                  * every packet sent/received action */
 
-/*
- * The SSL/TLS worker thread will wait at most this many seconds for the
- * interprocess communication pipe to the main thread to be ready to accept
- * writes.
- */
-#define TLS_MULTI_THREAD_SEND_TIMEOUT 5
-
 /* Interval that tls_multi_process should call tls_authentication_status */
 #define TLS_MULTI_AUTH_STATUS_INTERVAL 10
 
@@ -105,14 +101,26 @@
 /* Maximum length of OCC options string passed as part of auth handshake */
 #define TLS_OPTIONS_LEN 512
 
+/* Definitions of the bits in the IV_PROTO bitfield
+ *
+ * In older OpenVPN versions this used in a comparison
+ * IV_PROTO >= 2 to determine if DATA_V2 is supported.
+ * Therefore any client announcing any of the flags must
+ * also announce IV_PROTO_DATA_V2. We also treat bit 0
+ * as reserved for this reason */
+
+/** Support P_DATA_V2 */
+#define IV_PROTO_DATA_V2        (1<<1)
+
+/** Assume client will send a push request and server does not need
+ * to wait for a push-request to send a push-reply */
+#define IV_PROTO_REQUEST_PUSH   (1<<2)
+
+
 /* Default field in X509 to be username */
 #define X509_USERNAME_FIELD_DEFAULT "CN"
 
-/*
- * Range of key exchange methods
- */
-#define KEY_METHOD_MIN 1
-#define KEY_METHOD_MAX 2
+#define KEY_METHOD_2  2
 
 /* key method taken from lower 4 bits */
 #define KEY_METHOD_MASK 0x0F
@@ -146,7 +154,7 @@
  * Build master SSL context object that serves for the whole of OpenVPN
  * instantiation
  */
-void init_ssl(const struct options *options, struct tls_root_ctx *ctx);
+void init_ssl(const struct options *options, struct tls_root_ctx *ctx, bool in_chroot);
 
 /** @addtogroup control_processor
  *  @{ */
@@ -430,7 +438,9 @@
 
 void ssl_set_auth_token(const char *token);
 
-#ifdef ENABLE_CLIENT_CR
+bool ssl_clean_auth_token(void);
+
+#ifdef ENABLE_MANAGEMENT
 /*
  * ssl_get_auth_challenge will parse the server-pushed auth-failed
  * reason string and return a dynamically allocated
@@ -438,8 +448,6 @@
  */
 void ssl_purge_auth_challenge(void);
 
-bool ssl_clean_auth_token(void);
-
 void ssl_put_auth_challenge(const char *cr_str);
 
 #endif
@@ -489,15 +497,6 @@
                                       struct frame *frame,
                                       struct frame *frame_fragment);
 
-/**
- * "Poor man's NCP": Use peer cipher if it is an allowed (NCP) cipher.
- * Allows non-NCP peers to upgrade their cipher individually.
- *
- * Make sure to call tls_session_update_crypto_params() after calling this
- * function.
- */
-void tls_poor_mans_ncp(struct options *o, const char *remote_ciphername);
-
 #ifdef MANAGEMENT_DEF_AUTH
 static inline char *
 tls_get_peer_info(const struct tls_multi *multi)
@@ -506,32 +505,28 @@
 }
 #endif
 
-/**
- * Return the Negotiable Crypto Parameters version advertised in the peer info
- * string, or 0 if none specified.
- */
-int tls_peer_info_ncp_ver(const char *peer_info);
-
-/**
- * Check whether the ciphers in the supplied list are supported.
- *
- * @param list          Colon-separated list of ciphers
- *
- * @returns true iff all ciphers in list are supported.
- */
-bool tls_check_ncp_cipher_list(const char *list);
-
-/**
- * Return true iff item is present in the colon-separated zero-terminated
- * cipher list.
- */
-bool tls_item_in_cipher_list(const char *item, const char *list);
-
-
 /*
  * inline functions
  */
 
+/** Free the elements of a tls_wrap_ctx structure */
+static inline void
+tls_wrap_free(struct tls_wrap_ctx *tls_wrap)
+{
+    if (packet_id_initialized(&tls_wrap->opt.packet_id))
+    {
+        packet_id_free(&tls_wrap->opt.packet_id);
+    }
+
+    if (tls_wrap->cleanup_key_ctx)
+    {
+        free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi);
+    }
+
+    free_buf(&tls_wrap->tls_crypt_v2_metadata);
+    free_buf(&tls_wrap->work);
+}
+
 static inline bool
 tls_initial_packet_received(const struct tls_multi *multi)
 {
@@ -597,14 +592,16 @@
 void extract_x509_field_test(void);
 
 /**
- * Given a key_method, return true if opcode represents the required form of
- * hard_reset.
+ * Given a key_method, return true if opcode represents the one of the
+ * hard_reset op codes for key-method 2
  *
- * If key_method == 0, return true if any form of hard reset is used.
  */
-bool is_hard_reset(int op, int key_method);
+bool is_hard_reset_method2(int op);
 
-void delayed_auth_pass_purge(void);
+/**
+ * Cleans the saved user/password unless auth-nocache is in use.
+ */
+void ssl_clean_user_pass(void);
 
 
 /*
@@ -619,6 +616,5 @@
 show_available_tls_ciphers(const char *cipher_list,
                            const char *cipher_list_tls13,
                            const char *tls_cert_profile);
-#endif /* ENABLE_CRYPTO */
 
 #endif /* ifndef OPENVPN_SSL_H */
diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h
index c614efa..7f52ab1 100644
--- a/src/openvpn/ssl_backend.h
+++ b/src/openvpn/ssl_backend.h
@@ -125,8 +125,6 @@
  */
 int tls_version_max(void);
 
-#ifdef ENABLE_CRYPTO
-
 /**
  * Initialise a library-specific TLS context for a server.
  *
@@ -201,6 +199,16 @@
 void tls_ctx_set_cert_profile(struct tls_root_ctx *ctx, const char *profile);
 
 /**
+ * Set the (elliptic curve) group allowed for signatures and
+ * key exchange.
+ *
+ * @param ctx       TLS context to restrict, must be valid.
+ * @param groups    List of groups that will be allowed, in priority,
+ *                  separated by :
+ */
+void tls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups);
+
+/**
  * Check our certificate notBefore and notAfter fields, and warn if the cert is
  * either not yet valid or has expired.  Note that this is a non-fatal error,
  * since we compare against the system time, which might be incorrect.
@@ -215,11 +223,12 @@
  *
  * @param ctx                   TLS context to use
  * @param dh_file               The file name to load the parameters from, or
- *                              "[[INLINE]]" in the case of inline files.
- * @param dh_file_inline        A string containing the parameters
+ *                              a string containing the parameters in the case
+ *                              of inline files.
+ * @param dh_file_inline        True if dh_file is an inline file.
  */
 void tls_ctx_load_dh_params(struct tls_root_ctx *ctx, const char *dh_file,
-                            const char *dh_file_inline);
+                            bool dh_file_inline);
 
 /**
  * Load Elliptic Curve Parameters, and load them into the library-specific
@@ -237,15 +246,15 @@
  *
  * @param ctx                   TLS context to use
  * @param pkcs12_file           The file name to load the information from, or
- *                              "[[INLINE]]" in the case of inline files.
- * @param pkcs12_file_inline    A string containing the information
+ *                              a string containing the information in the case
+ *                              of inline files.
+ * @param pkcs12_file_inline    True if pkcs12_file is an inline file.
  *
  * @return                      1 if an error occurred, 0 if parsing was
  *                              successful.
  */
 int tls_ctx_load_pkcs12(struct tls_root_ctx *ctx, const char *pkcs12_file,
-                        const char *pkcs12_file_inline, bool load_ca_file
-                        );
+                        bool pkcs12_file_inline, bool load_ca_file);
 
 /**
  * Use Windows cryptoapi for key and cert, and add to library-specific TLS
@@ -265,46 +274,41 @@
  *
  * @param ctx                   TLS context to use
  * @param cert_file             The file name to load the certificate from, or
- *                              "[[INLINE]]" in the case of inline files.
- * @param cert_file_inline      A string containing the certificate
+ *                              a string containing the certificate in the case
+ *                              of inline files.
+ * @param cert_file_inline      True if cert_file is an inline file.
  */
 void tls_ctx_load_cert_file(struct tls_root_ctx *ctx, const char *cert_file,
-                            const char *cert_file_inline);
+                            bool cert_file_inline);
 
 /**
  * Load private key file into the given TLS context.
  *
  * @param ctx                   TLS context to use
  * @param priv_key_file         The file name to load the private key from, or
- *                              "[[INLINE]]" in the case of inline files.
- * @param priv_key_file_inline  A string containing the private key
+ *                              a string containing the private key in the case
+ *                              of inline files.
+ * @param priv_key_file_inline  True if priv_key_file is an inline file
  *
  * @return                      1 if an error occurred, 0 if parsing was
  *                              successful.
  */
 int tls_ctx_load_priv_file(struct tls_root_ctx *ctx, const char *priv_key_file,
-                           const char *priv_key_file_inline
-                           );
+                           bool priv_key_file_inline);
 
-#ifdef MANAGMENT_EXTERNAL_KEY
+#ifdef ENABLE_MANAGEMENT
 
 /**
  * Tell the management interface to load the given certificate and the external
  * private key matching the given certificate.
  *
  * @param ctx                   TLS context to use
- * @param cert_file             The file name to load the certificate from, or
- *                              "[[INLINE]]" in the case of inline files.
- * @param cert_file_inline      A string containing the certificate
  *
- * @return                      1 if an error occurred, 0 if parsing was
- *                              successful.
+ * @return                      1 if an error occurred, 0 if successful.
  */
-int tls_ctx_use_external_private_key(struct tls_root_ctx *ctx,
-                                     const char *cert_file, const char *cert_file_inline);
+int tls_ctx_use_management_external_key(struct tls_root_ctx *ctx);
 
-#endif
-
+#endif /* ENABLE_MANAGEMENT */
 
 /**
  * Load certificate authority certificates from the given file or path.
@@ -313,13 +317,13 @@
  *
  * @param ctx                   TLS context to use
  * @param ca_file               The file name to load the CAs from, or
- *                              "[[INLINE]]" in the case of inline files.
- * @param ca_file_inline        A string containing the CAs
+ *                              a string containing the CAs in the case of
+ *                              inline files.
+ * @param ca_file_inline        True if ca_file is an inline file
  * @param ca_path               The path to load the CAs from
  */
 void tls_ctx_load_ca(struct tls_root_ctx *ctx, const char *ca_file,
-                     const char *ca_file_inline, const char *ca_path, bool tls_server
-                     );
+                     bool ca_file_inline, const char *ca_path, bool tls_server);
 
 /**
  * Load extra certificate authority certificates from the given file or path.
@@ -329,12 +333,14 @@
  *
  * @param ctx                           TLS context to use
  * @param extra_certs_file              The file name to load the certs from, or
- *                                      "[[INLINE]]" in the case of inline files.
- * @param extra_certs_file_inline       A string containing the certs
+ *                                      a string containing the certs in the
+ *                                      case of inline files.
+ * @param extra_certs_file_inline       True if extra_certs_file is an inline
+ *                                      file.
  */
-void tls_ctx_load_extra_certs(struct tls_root_ctx *ctx, const char *extra_certs_file,
-                              const char *extra_certs_file_inline
-                              );
+void tls_ctx_load_extra_certs(struct tls_root_ctx *ctx,
+                              const char *extra_certs_file,
+                              bool extra_certs_file_inline);
 
 #ifdef ENABLE_CRYPTO_MBEDTLS
 /**
@@ -377,11 +383,11 @@
  *
  * @param ssl_ctx       The TLS context to use when reloading the CRL
  * @param crl_file      The file name to load the CRL from, or
- *                      "[[INLINE]]" in the case of inline files.
- * @param crl_inline    A string containing the CRL
+ *                      an array containing the inline CRL.
+ * @param crl_inline    True if crl_file is an inline CRL.
  */
 void backend_tls_ctx_reload_crl(struct tls_root_ctx *ssl_ctx,
-                                const char *crl_file, const char *crl_inline);
+                                const char *crl_file, bool crl_inline);
 
 /**
  * Keying Material Exporters [RFC 5705] allows additional keying material to be
@@ -557,5 +563,4 @@
  */
 const char *get_ssl_library_version(void);
 
-#endif /* ENABLE_CRYPTO */
 #endif /* SSL_BACKEND_H_ */
diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h
index ac25ffa..d6fd50b 100644
--- a/src/openvpn/ssl_common.h
+++ b/src/openvpn/ssl_common.h
@@ -64,8 +64,7 @@
  *      material.
  *   -# \c S_GOT_KEY, have received remote part of \c key_source2 random
  *      material.
- *   -# \c S_ACTIVE, normal operation during remaining handshake window.
- *   -# \c S_NORMAL_OP, normal operation.
+ *   -# \c S_ACTIVE, normal operation
  *
  * Servers follow the same order, except for \c S_SENT_KEY and \c
  * S_GOT_KEY being reversed, because the server first receives the
@@ -94,9 +93,9 @@
                                  *   immediately after negotiation has
                                  *   completed while still within the
                                  *   handshake window. */
-/* ready to exchange data channel packets */
-#define S_NORMAL_OP       7     /**< Normal operational \c key_state
-                                 *   state. */
+/* Note that earlier versions also had a S_OP_NORMAL state that was
+ * virtually identical with S_ACTIVE and the code still assumes everything
+ * >= S_ACTIVE to be fully operational */
 /** @} name Control channel negotiation states */
 /** @} addtogroup control_processor */
 
@@ -127,6 +126,25 @@
     struct key_source server;   /**< Random provided by server. */
 };
 
+
+/**
+ * This reflects the (server side) authentication state after the TLS
+ * session has been established and key_method_2_read is called. If async auth
+ * is enabled the state will first move to KS_AUTH_DEFERRED before eventually
+ * being set to KS_AUTH_TRUE or KS_AUTH_FALSE
+ * Only KS_AUTH_TRUE is fully authenticated
+ */
+enum ks_auth_state {
+  KS_AUTH_FALSE,              /**< Key state is not authenticated  */
+  KS_AUTH_DEFERRED,           /**< Key state authentication is being deferred,
+                                * by async auth */
+  KS_AUTH_TRUE                /**< Key state is authenticated. TLS and user/pass
+                                * succeeded. This includes AUTH_PENDING/OOB
+                                * authentication as those hold the
+                                * connection artificially in KS_AUTH_DEFERRED
+                                */
+};
+
 /**
  * Security parameter state of one TLS and data channel %key session.
  * @ingroup control_processor
@@ -148,6 +166,8 @@
 struct key_state
 {
     int state;
+    /** The state of the auth-token sent from the client */
+    int auth_token_state_flags;
 
     /**
      * Key id for this key_state,  inherited from struct tls_session.
@@ -185,12 +205,9 @@
     /*
      * If bad username/password, TLS connection will come up but 'authenticated' will be false.
      */
-    bool authenticated;
+    enum ks_auth_state authenticated;
     time_t auth_deferred_expire;
 
-#ifdef ENABLE_DEF_AUTH
-    /* If auth_deferred is true, authentication is being deferred */
-    bool auth_deferred;
 #ifdef MANAGEMENT_DEF_AUTH
     unsigned int mda_key_id;
     unsigned int mda_status;
@@ -200,7 +217,6 @@
     time_t acf_last_mod;
     char *auth_control_file;
 #endif
-#endif
 };
 
 /** Control channel wrapping (--tls-auth/--tls-crypt) context */
@@ -213,6 +229,12 @@
     } mode;                     /**< Control channel wrapping mode */
     struct crypto_options opt;  /**< Crypto state */
     struct buffer work;         /**< Work buffer (only for --tls-crypt) */
+    struct key_ctx tls_crypt_v2_server_key;  /**< Decrypts client keys */
+    const struct buffer *tls_crypt_v2_wkc;   /**< Wrapped client key,
+                                              *   sent to server */
+    struct buffer tls_crypt_v2_metadata;     /**< Received from client */
+    bool cleanup_key_ctx;                    /**< opt.key_ctx_bi is owned by
+                                              *   this context */
 };
 
 /*
@@ -233,25 +255,18 @@
     /* if true, don't xmit until first packet from peer is received */
     bool xmit_hold;
 
-#ifdef ENABLE_OCC
     /* local and remote options strings
      * that must match between client and server */
     const char *local_options;
     const char *remote_options;
-#endif
 
     /* from command line */
-    int key_method;
     bool replay;
     bool single_session;
-#ifdef ENABLE_OCC
     bool disable_occ;
-#endif
     int mode;
     bool pull;
-#ifdef ENABLE_PUSH_PEER_INFO
     int push_peer_info_detail;
-#endif
     int transition_window;
     int handshake_window;
     interval_t packet_timeout;
@@ -265,7 +280,7 @@
     int verify_x509_type;
     const char *verify_x509_name;
     const char *crl_file;
-    const char *crl_file_inline;
+    bool crl_file_inline;
     int ns_cert_type;
     unsigned remote_cert_ku[MAX_PARMS];
     const char *remote_cert_eku;
@@ -285,13 +300,15 @@
     bool tcp_mode;
 
     const char *config_ciphername;
-    const char *config_authname;
+    const char *config_ncp_ciphers;
     bool ncp_enabled;
 
+    bool tls_crypt_v2;
+    const char *tls_crypt_v2_verify_script;
+
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
 
-    /* frame parameters for TLS control channel */
     struct frame frame;
 
     /* used for username/password authentication */
@@ -299,15 +316,21 @@
     bool auth_user_pass_verify_script_via_file;
     const char *tmp_dir;
     const char *auth_user_pass_file;
-    bool auth_token_generate;   /**< Generate auth-tokens on successful user/pass auth,
-                                 *   set via options->auth_token_generate. */
+
+    bool auth_token_generate;   /**< Generate auth-tokens on successful
+                                 * user/pass auth,seet via
+                                 * options->auth_token_generate. */
+    bool auth_token_call_auth; /**< always call normal authentication */
     unsigned int auth_token_lifetime;
 
+    struct key_ctx auth_token_key;
+
     /* use the client-config-dir as a positive authenticator */
     const char *client_config_dir_exclusive;
 
     /* instance-wide environment variable set */
     struct env_set *es;
+    openvpn_net_ctx_t *net_ctx;
     const struct plugin_list *plugins;
 
     /* compression parms */
@@ -326,6 +349,7 @@
 #define SSLF_TLS_VERSION_MIN_MASK     0xF  /* (uses bit positions 6 to 9) */
 #define SSLF_TLS_VERSION_MAX_SHIFT    10
 #define SSLF_TLS_VERSION_MAX_MASK     0xF  /* (uses bit positions 10 to 13) */
+#define SSLF_TLS_DEBUG_ENABLED        (1<<14)
     unsigned int ssl_flags;
 
 #ifdef MANAGEMENT_DEF_AUTH
@@ -334,7 +358,7 @@
 
     const struct x509_track *x509_track;
 
-#ifdef ENABLE_CLIENT_CR
+#ifdef ENABLE_MANAGEMENT
     const struct static_challenge_info *sci;
 #endif
 
@@ -361,10 +385,6 @@
 /** @} name Index of key_state objects within a tls_session structure */
 /** @} addtogroup control_processor */
 
-#define AUTH_TOKEN_SIZE 32      /**< Size of server side generated auth tokens.
-                                 *   32 bytes == 256 bits
-                                 */
-
 /**
  * Security parameter state of a single session within a VPN tunnel.
  * @ingroup control_processor
@@ -462,6 +482,18 @@
 #define KEY_SCAN_SIZE 3
 
 
+/* client authentication state, CAS_SUCCEEDED must be 0 since
+ * non multi code path still checks this variable but does not initialise it
+ * so the code depends on zero initialisation */
+enum client_connect_status {
+    CAS_SUCCEEDED=0,
+    CAS_PENDING,
+    CAS_PENDING_DEFERRED,
+    CAS_PENDING_DEFERRED_PARTIAL,   /**< at least handler succeeded, no result yet*/
+    CAS_FAILED,
+};
+
+
 /**
  * Security parameter state for a single VPN tunnel.
  * @ingroup control_processor
@@ -502,6 +534,7 @@
 
     int n_sessions;             /**< Number of sessions negotiated thus
                                  *   far. */
+    enum client_connect_status multi_state;
 
     /*
      * Number of errors.
@@ -517,22 +550,40 @@
     struct cert_hash_set *locked_cert_hash_set;
 
 #ifdef ENABLE_DEF_AUTH
+    /* Time of last call to tls_authentication_status */
+    time_t tas_last;
+#endif
+
     /*
      * An error message to send to client on AUTH_FAILED
      */
     char *client_reason;
 
-    /* Time of last call to tls_authentication_status */
-    time_t tas_last;
-#endif
-
-#if P2MP_SERVER
     /*
      * A multi-line string of general-purpose info received from peer
      * over control channel.
      */
     char *peer_info;
-#endif
+    char *auth_token;    /**< If server sends a generated auth-token,
+                          *   this is the token to use for future
+                          *   user/pass authentications in this session.
+                          */
+    char *auth_token_initial;
+    /**< The first auth-token we sent to a client, for clients that do
+     * not update their auth-token (older OpenVPN3 core versions)
+     */
+#define  AUTH_TOKEN_HMAC_OK              (1<<0)
+    /**< Auth-token sent from client has valid hmac */
+#define  AUTH_TOKEN_EXPIRED              (1<<1)
+    /**< Auth-token sent from client has expired */
+#define  AUTH_TOKEN_VALID_EMPTYUSER      (1<<2)
+    /**<
+     * Auth-token is only valid for an empty username
+     * and not the username actually supplied from the client
+     *
+     * OpenVPN 3 clients sometimes wipes or replaces the username with a
+     * username hint from their config.
+     */
 
     /* For P_DATA_V2 */
     uint32_t peer_id;
@@ -540,13 +591,6 @@
 
     char *remote_ciphername;    /**< cipher specified in peer's config file */
 
-    char *auth_token;    /**< If server sends a generated auth-token,
-                          *   this is the token to use for future
-                          *   user/pass authentications in this session.
-                          */
-    time_t auth_token_tstamp; /**< timestamp of the generated token */
-    bool auth_token_sent; /**< If server uses --auth-gen-token and
-                           *   token has been sent to client */
     /*
      * Our session objects.
      */
diff --git a/src/openvpn/ssl_mbedtls.c b/src/openvpn/ssl_mbedtls.c
index 4746261..881d089 100644
--- a/src/openvpn/ssl_mbedtls.c
+++ b/src/openvpn/ssl_mbedtls.c
@@ -35,7 +35,7 @@
 
 #include "syshead.h"
 
-#if defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_MBEDTLS)
+#if defined(ENABLE_CRYPTO_MBEDTLS)
 
 #include "errlevel.h"
 #include "ssl_backend.h"
@@ -43,6 +43,7 @@
 #include "buffer.h"
 #include "misc.h"
 #include "manage.h"
+#include "pkcs11_backend.h"
 #include "ssl_common.h"
 
 #include <mbedtls/havege.h>
@@ -64,12 +65,12 @@
 static const mbedtls_x509_crt_profile openvpn_x509_crt_profile_legacy =
 {
     /* Hashes from SHA-1 and above */
-    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA1 ) |
-    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_RIPEMD160 ) |
-    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA224 ) |
-    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA256 ) |
-    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA384 ) |
-    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA512 ),
+    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA1 )
+    |MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_RIPEMD160 )
+    |MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA224 )
+    |MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA256 )
+    |MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA384 )
+    |MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA512 ),
     0xFFFFFFF, /* Any PK alg    */
     0xFFFFFFF, /* Any curve     */
     1024,      /* RSA-1024 and larger */
@@ -78,10 +79,10 @@
 static const mbedtls_x509_crt_profile openvpn_x509_crt_profile_preferred =
 {
     /* SHA-2 and above */
-    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA224 ) |
-    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA256 ) |
-    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA384 ) |
-    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA512 ),
+    MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA224 )
+    |MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA256 )
+    |MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA384 )
+    |MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA512 ),
     0xFFFFFFF, /* Any PK alg    */
     0xFFFFFFF, /* Any curve     */
     2048,      /* RSA-2048 and larger */
@@ -167,17 +168,7 @@
         }
 
 #if defined(ENABLE_PKCS11)
-        if (ctx->priv_key_pkcs11 != NULL)
-        {
-            mbedtls_pkcs11_priv_key_free(ctx->priv_key_pkcs11);
-            free(ctx->priv_key_pkcs11);
-        }
-#endif
-#if defined(MANAGMENT_EXTERNAL_KEY)
-        if (ctx->external_key != NULL)
-        {
-            free(ctx->external_key);
-        }
+        pkcs11h_certificate_freeCertificate(ctx->pkcs11_cert);
 #endif
 
         if (ctx->allowed_ciphers)
@@ -185,6 +176,11 @@
             free(ctx->allowed_ciphers);
         }
 
+        if (ctx->groups)
+        {
+            free(ctx->groups);
+        }
+
         CLEAR(*ctx);
 
         ctx->initialised = false;
@@ -199,12 +195,63 @@
     return ctx->initialised;
 }
 
+#ifdef HAVE_EXPORT_KEYING_MATERIAL
+int
+mbedtls_ssl_export_keys_cb(void *p_expkey, const unsigned char *ms,
+                           const unsigned char *kb, size_t maclen,
+                           size_t keylen, size_t ivlen,
+                           const unsigned char client_random[32],
+                           const unsigned char server_random[32],
+                           mbedtls_tls_prf_types tls_prf_type)
+{
+    struct tls_session *session = p_expkey;
+    struct key_state_ssl *ks_ssl = &session->key[KS_PRIMARY].ks_ssl;
+    unsigned char client_server_random[64];
+
+    ks_ssl->exported_key_material = gc_malloc(session->opt->ekm_size,
+                                              true, NULL);
+
+    memcpy(client_server_random, client_random, 32);
+    memcpy(client_server_random + 32, server_random, 32);
+
+    const size_t ms_len = sizeof(ks_ssl->ctx->session->master);
+    int ret = mbedtls_ssl_tls_prf(tls_prf_type, ms, ms_len,
+                                  session->opt->ekm_label, client_server_random,
+                                  sizeof(client_server_random), ks_ssl->exported_key_material,
+                                  session->opt->ekm_size);
+
+    if (!mbed_ok(ret))
+    {
+        secure_memzero(ks_ssl->exported_key_material, session->opt->ekm_size);
+    }
+
+    secure_memzero(client_server_random, sizeof(client_server_random));
+
+    return ret;
+}
+#endif /* HAVE_EXPORT_KEYING_MATERIAL */
+
 void
 key_state_export_keying_material(struct key_state_ssl *ssl,
                                  struct tls_session *session)
 {
+    if (ssl->exported_key_material)
+    {
+        unsigned int size = session->opt->ekm_size;
+        struct gc_arena gc = gc_new();
+        unsigned int len = (size * 2) + 2;
+
+        const char *key = format_hex_ex(ssl->exported_key_material,
+                                        size, len, 0, NULL, &gc);
+        setenv_str(session->opt->es, "exported_keying_material", key);
+
+        dmsg(D_TLS_DEBUG_MED, "%s: exported keying material: %s",
+             __func__, key);
+        gc_free(&gc);
+    }
 }
 
+
 bool
 tls_ctx_set_options(struct tls_root_ctx *ctx, unsigned int ssl_flags)
 {
@@ -241,40 +288,29 @@
     }
 
     msg(M_WARN, "mbed TLS does not support setting tls-ciphersuites. "
-                "Ignoring TLS 1.3 cipher list: %s", ciphers);
+        "Ignoring TLS 1.3 cipher list: %s", ciphers);
 }
 
 void
 tls_ctx_restrict_ciphers(struct tls_root_ctx *ctx, const char *ciphers)
 {
     char *tmp_ciphers, *tmp_ciphers_orig, *token;
-    int i, cipher_count;
-    int ciphers_len;
 
     if (NULL == ciphers)
     {
         return; /* Nothing to do */
-
     }
-    ciphers_len = strlen(ciphers);
 
     ASSERT(NULL != ctx);
-    ASSERT(0 != ciphers_len);
 
     /* Get number of ciphers */
-    for (i = 0, cipher_count = 1; i < ciphers_len; i++)
-    {
-        if (ciphers[i] == ':')
-        {
-            cipher_count++;
-        }
-    }
+    int cipher_count = get_num_elements(ciphers, ':');
 
     /* Allocate an array for them */
     ALLOC_ARRAY_CLEAR(ctx->allowed_ciphers, int, cipher_count+1)
 
     /* Parse allowed ciphers, getting IDs */
-    i = 0;
+    int i = 0;
     tmp_ciphers_orig = tmp_ciphers = string_alloc(ciphers, NULL);
 
     token = strtok(tmp_ciphers, ":");
@@ -308,11 +344,46 @@
     }
     else
     {
-        msg (M_FATAL, "ERROR: Invalid cert profile: %s", profile);
+        msg(M_FATAL, "ERROR: Invalid cert profile: %s", profile);
     }
 }
 
 void
+tls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups)
+{
+    ASSERT(ctx);
+    struct gc_arena gc = gc_new();
+
+    /* Get number of groups and allocate an array in ctx */
+    int groups_count = get_num_elements(groups, ':');
+    ALLOC_ARRAY_CLEAR(ctx->groups, mbedtls_ecp_group_id, groups_count + 1)
+
+    /* Parse allowed ciphers, getting IDs */
+    int i = 0;
+    char *tmp_groups = string_alloc(groups, &gc);
+
+    const char *token;
+    while ((token = strsep(&tmp_groups, ":")))
+    {
+        const mbedtls_ecp_curve_info *ci =
+            mbedtls_ecp_curve_info_from_name(token);
+        if (!ci)
+        {
+            msg(M_WARN, "Warning unknown curve/group specified: %s", token);
+        }
+        else
+        {
+            ctx->groups[i] = ci->grp_id;
+            i++;
+        }
+    }
+    ctx->groups[i] = MBEDTLS_ECP_DP_NONE;
+
+    gc_free(&gc);
+}
+
+
+void
 tls_ctx_check_cert_time(const struct tls_root_ctx *ctx)
 {
     ASSERT(ctx);
@@ -334,13 +405,13 @@
 
 void
 tls_ctx_load_dh_params(struct tls_root_ctx *ctx, const char *dh_file,
-                       const char *dh_inline
-                       )
+                       bool dh_inline)
 {
-    if (!strcmp(dh_file, INLINE_FILE_TAG) && dh_inline)
+    if (dh_inline)
     {
         if (!mbed_ok(mbedtls_dhm_parse_dhm(ctx->dhm_ctx,
-                                           (const unsigned char *) dh_inline, strlen(dh_inline)+1)))
+                                           (const unsigned char *) dh_file,
+                                           strlen(dh_file) + 1)))
         {
             msg(M_FATAL, "Cannot read inline DH parameters");
         }
@@ -370,9 +441,7 @@
 
 int
 tls_ctx_load_pkcs12(struct tls_root_ctx *ctx, const char *pkcs12_file,
-                    const char *pkcs12_file_inline,
-                    bool load_ca_file
-                    )
+                    bool pkcs12_file_inline, bool load_ca_file)
 {
     msg(M_FATAL, "PKCS #12 files not yet supported for mbed TLS.");
     return 0;
@@ -388,8 +457,7 @@
 
 void
 tls_ctx_load_cert_file(struct tls_root_ctx *ctx, const char *cert_file,
-                       const char *cert_inline
-                       )
+                       bool cert_inline)
 {
     ASSERT(NULL != ctx);
 
@@ -398,10 +466,11 @@
         ALLOC_OBJ_CLEAR(ctx->crt_chain, mbedtls_x509_crt);
     }
 
-    if (!strcmp(cert_file, INLINE_FILE_TAG) && cert_inline)
+    if (cert_inline)
     {
         if (!mbed_ok(mbedtls_x509_crt_parse(ctx->crt_chain,
-                                            (const unsigned char *) cert_inline, strlen(cert_inline)+1)))
+                                            (const unsigned char *)cert_file,
+                                            strlen(cert_file) + 1)))
         {
             msg(M_FATAL, "Cannot load inline certificate file");
         }
@@ -417,8 +486,7 @@
 
 int
 tls_ctx_load_priv_file(struct tls_root_ctx *ctx, const char *priv_key_file,
-                       const char *priv_key_inline
-                       )
+                       bool priv_key_inline)
 {
     int status;
     ASSERT(NULL != ctx);
@@ -428,19 +496,20 @@
         ALLOC_OBJ_CLEAR(ctx->priv_key, mbedtls_pk_context);
     }
 
-    if (!strcmp(priv_key_file, INLINE_FILE_TAG) && priv_key_inline)
+    if (priv_key_inline)
     {
         status = mbedtls_pk_parse_key(ctx->priv_key,
-                                      (const unsigned char *) priv_key_inline, strlen(priv_key_inline)+1,
-                                      NULL, 0);
+                                      (const unsigned char *) priv_key_file,
+                                      strlen(priv_key_file) + 1, NULL, 0);
 
         if (MBEDTLS_ERR_PK_PASSWORD_REQUIRED == status)
         {
             char passbuf[512] = {0};
             pem_password_callback(passbuf, 512, 0, NULL);
             status = mbedtls_pk_parse_key(ctx->priv_key,
-                                          (const unsigned char *) priv_key_inline,
-                                          strlen(priv_key_inline)+1, (unsigned char *) passbuf,
+                                          (const unsigned char *) priv_key_file,
+                                          strlen(priv_key_file) + 1,
+                                          (unsigned char *) passbuf,
                                           strlen(passbuf));
         }
     }
@@ -462,7 +531,8 @@
             management_auth_failure(management, UP_TYPE_PRIVATE_KEY, NULL);
         }
 #endif
-        msg(M_WARN, "Cannot load private key file %s", priv_key_file);
+        msg(M_WARN, "Cannot load private key file %s",
+            print_key_filename(priv_key_file, priv_key_inline));
         return 1;
     }
 
@@ -475,13 +545,6 @@
     return 0;
 }
 
-#ifdef MANAGMENT_EXTERNAL_KEY
-
-
-struct external_context {
-    size_t signature_length;
-};
-
 /**
  * external_pkcs1_sign implements a mbed TLS rsa_sign_func callback, that uses
  * the management interface to request an RSA signature for the supplied hash.
@@ -508,11 +571,9 @@
                      unsigned char *sig )
 {
     struct external_context *const ctx = ctx_voidptr;
-    char *in_b64 = NULL;
-    char *out_b64 = NULL;
     int rv;
-    unsigned char *p = sig;
-    size_t asn_len = 0, oid_size = 0, sig_len = 0;
+    uint8_t *to_sign = NULL;
+    size_t asn_len = 0, oid_size = 0;
     const char *oid = NULL;
 
     if (NULL == ctx)
@@ -548,12 +609,14 @@
         asn_len = 10 + oid_size;
     }
 
-    sig_len = ctx->signature_length;
-    if ( (SIZE_MAX - hashlen) < asn_len || (hashlen + asn_len) > sig_len)
+    if ((SIZE_MAX - hashlen) < asn_len
+        || ctx->signature_length < (asn_len + hashlen))
     {
         return MBEDTLS_ERR_RSA_BAD_INPUT_DATA;
     }
 
+    ALLOC_ARRAY_CLEAR(to_sign, uint8_t, asn_len + hashlen);
+    uint8_t *p = to_sign;
     if (md_alg != MBEDTLS_MD_NONE)
     {
         /*
@@ -578,34 +641,16 @@
         *p++ = MBEDTLS_ASN1_OCTET_STRING;
         *p++ = hashlen;
 
-        /* Determine added ASN length */
-        asn_len = p - sig;
+        /* Double-check ASN length */
+        ASSERT(asn_len == p - to_sign);
     }
 
     /* Copy the hash to be signed */
-    memcpy( p, hash, hashlen );
+    memcpy(p, hash, hashlen);
 
-    /* convert 'from' to base64 */
-    if (openvpn_base64_encode(sig, asn_len + hashlen, &in_b64) <= 0)
-    {
-        rv = MBEDTLS_ERR_RSA_BAD_INPUT_DATA;
-        goto done;
-    }
-
-    /* call MI for signature */
-    if (management)
-    {
-        out_b64 = management_query_rsa_sig(management, in_b64);
-    }
-    if (!out_b64)
-    {
-        rv = MBEDTLS_ERR_RSA_PRIVATE_FAILED;
-        goto done;
-    }
-
-    /* decode base64 signature to binary and verify length */
-    if (openvpn_base64_decode(out_b64, sig, ctx->signature_length) !=
-        ctx->signature_length)
+    /* Call external signature function */
+    if (!ctx->sign(ctx->sign_ctx, to_sign, asn_len + hashlen, sig,
+                   ctx->signature_length))
     {
         rv = MBEDTLS_ERR_RSA_PRIVATE_FAILED;
         goto done;
@@ -614,14 +659,7 @@
     rv = 0;
 
 done:
-    if (in_b64)
-    {
-        free(in_b64);
-    }
-    if (out_b64)
-    {
-        free(out_b64);
-    }
+    free(to_sign);
     return rv;
 }
 
@@ -634,23 +672,30 @@
 }
 
 int
-tls_ctx_use_external_private_key(struct tls_root_ctx *ctx,
-                                 const char *cert_file, const char *cert_file_inline)
+tls_ctx_use_external_signing_func(struct tls_root_ctx *ctx,
+                                  external_sign_func sign_func, void *sign_ctx)
 {
     ASSERT(NULL != ctx);
 
-    tls_ctx_load_cert_file(ctx, cert_file, cert_file_inline);
-
     if (ctx->crt_chain == NULL)
     {
+        msg(M_WARN, "ERROR: external key requires a certificate.");
         return 1;
     }
 
-    ALLOC_OBJ_CLEAR(ctx->external_key, struct external_context);
-    ctx->external_key->signature_length = mbedtls_pk_get_len(&ctx->crt_chain->pk);
+    if (mbedtls_pk_get_type(&ctx->crt_chain->pk) != MBEDTLS_PK_RSA)
+    {
+        msg(M_WARN, "ERROR: external key with mbed TLS requires a "
+            "certificate with an RSA key.");
+        return 1;
+    }
+
+    ctx->external_key.signature_length = mbedtls_pk_get_len(&ctx->crt_chain->pk);
+    ctx->external_key.sign = sign_func;
+    ctx->external_key.sign_ctx = sign_ctx;
 
     ALLOC_OBJ_CLEAR(ctx->priv_key, mbedtls_pk_context);
-    if (!mbed_ok(mbedtls_pk_setup_rsa_alt(ctx->priv_key, ctx->external_key,
+    if (!mbed_ok(mbedtls_pk_setup_rsa_alt(ctx->priv_key, &ctx->external_key,
                                           NULL, external_pkcs1_sign, external_key_len)))
     {
         return 1;
@@ -658,22 +703,67 @@
 
     return 0;
 }
-#endif /* ifdef MANAGMENT_EXTERNAL_KEY */
+
+#ifdef ENABLE_MANAGEMENT
+/** Query the management interface for a signature, see external_sign_func. */
+static bool
+management_sign_func(void *sign_ctx, const void *src, size_t src_len,
+                     void *dst, size_t dst_len)
+{
+    bool ret = false;
+    char *src_b64 = NULL;
+    char *dst_b64 = NULL;
+
+    if (!management || (openvpn_base64_encode(src, src_len, &src_b64) <= 0))
+    {
+        goto cleanup;
+    }
+
+    /*
+     * We only support RSA external keys and PKCS1 signatures at the moment
+     * in mbed TLS, so the signature parameter is hardcoded to this encoding
+     */
+    if (!(dst_b64 = management_query_pk_sig(management, src_b64,
+                                            "RSA_PKCS1_PADDING")))
+    {
+        goto cleanup;
+    }
+
+    if (openvpn_base64_decode(dst_b64, dst, dst_len) != dst_len)
+    {
+        goto cleanup;
+    }
+
+    ret = true;
+cleanup:
+    free(src_b64);
+    free(dst_b64);
+
+    return ret;
+}
+
+int
+tls_ctx_use_management_external_key(struct tls_root_ctx *ctx)
+{
+    return tls_ctx_use_external_signing_func(ctx, management_sign_func, NULL);
+}
+
+#endif /* ifdef ENABLE_MANAGEMENT */
 
 void
 tls_ctx_load_ca(struct tls_root_ctx *ctx, const char *ca_file,
-                const char *ca_inline, const char *ca_path, bool tls_server
-                )
+                bool ca_inline, const char *ca_path, bool tls_server)
 {
     if (ca_path)
     {
         msg(M_FATAL, "ERROR: mbed TLS cannot handle the capath directive");
     }
 
-    if (ca_file && !strcmp(ca_file, INLINE_FILE_TAG) && ca_inline)
+    if (ca_file && ca_inline)
     {
         if (!mbed_ok(mbedtls_x509_crt_parse(ctx->ca_chain,
-                                            (const unsigned char *) ca_inline, strlen(ca_inline)+1)))
+                                            (const unsigned char *) ca_file,
+                                            strlen(ca_file) + 1)))
         {
             msg(M_FATAL, "Cannot load inline CA certificates");
         }
@@ -690,8 +780,7 @@
 
 void
 tls_ctx_load_extra_certs(struct tls_root_ctx *ctx, const char *extra_certs_file,
-                         const char *extra_certs_inline
-                         )
+                         bool extra_certs_inline)
 {
     ASSERT(NULL != ctx);
 
@@ -700,11 +789,11 @@
         ALLOC_OBJ_CLEAR(ctx->crt_chain, mbedtls_x509_crt);
     }
 
-    if (!strcmp(extra_certs_file, INLINE_FILE_TAG) && extra_certs_inline)
+    if (extra_certs_inline)
     {
         if (!mbed_ok(mbedtls_x509_crt_parse(ctx->crt_chain,
-                                            (const unsigned char *) extra_certs_inline,
-                                            strlen(extra_certs_inline)+1)))
+                                            (const unsigned char *) extra_certs_file,
+                                            strlen(extra_certs_file) + 1)))
         {
             msg(M_FATAL, "Cannot load inline extra-certs file");
         }
@@ -932,7 +1021,7 @@
 
 void
 backend_tls_ctx_reload_crl(struct tls_root_ctx *ctx, const char *crl_file,
-                           const char *crl_inline)
+                           bool crl_inline)
 {
     ASSERT(crl_file);
 
@@ -942,10 +1031,11 @@
     }
     mbedtls_x509_crl_free(ctx->crl);
 
-    if (!strcmp(crl_file, INLINE_FILE_TAG) && crl_inline)
+    if (crl_inline)
     {
         if (!mbed_ok(mbedtls_x509_crl_parse(ctx->crl,
-                                            (const unsigned char *)crl_inline, strlen(crl_inline)+1)))
+                                            (const unsigned char *)crl_file,
+                                            strlen(crl_file) + 1)))
         {
             msg(M_WARN, "CRL: cannot parse inline CRL");
             goto err;
@@ -967,7 +1057,8 @@
 
 void
 key_state_ssl_init(struct key_state_ssl *ks_ssl,
-                   const struct tls_root_ctx *ssl_ctx, bool is_server, struct tls_session *session)
+                   const struct tls_root_ctx *ssl_ctx, bool is_server,
+                   struct tls_session *session)
 {
     ASSERT(NULL != ssl_ctx);
     ASSERT(ks_ssl);
@@ -979,7 +1070,18 @@
     mbedtls_ssl_config_defaults(ks_ssl->ssl_config, ssl_ctx->endpoint,
                                 MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
 #ifdef MBEDTLS_DEBUG_C
-    mbedtls_debug_set_threshold(3);
+    /* We only want to have mbed TLS generate debug level logging when we would
+     * also display it.
+     * In fact mbed TLS 2.25.0 crashes generating debug log if Curve25591 is
+     * selected for DH (https://github.com/ARMmbed/mbedtls/issues/4208) */
+    if (session->opt->ssl_flags & SSLF_TLS_DEBUG_ENABLED)
+    {
+        mbedtls_debug_set_threshold(3);
+    }
+    else
+    {
+        mbedtls_debug_set_threshold(2);
+    }
 #endif
     mbedtls_ssl_conf_dbg(ks_ssl->ssl_config, my_debug, NULL);
     mbedtls_ssl_conf_rng(ks_ssl->ssl_config, mbedtls_ctr_drbg_random,
@@ -992,6 +1094,15 @@
         mbedtls_ssl_conf_ciphersuites(ks_ssl->ssl_config, ssl_ctx->allowed_ciphers);
     }
 
+    if (ssl_ctx->groups)
+    {
+        mbedtls_ssl_conf_curves(ks_ssl->ssl_config, ssl_ctx->groups);
+    }
+    /* Disable TLS renegotiations. OpenVPN's renegotiation creates new SSL
+     * session and does not depend on this feature. And TLS renegotiations have
+     * been problematic in the past */
+    mbedtls_ssl_conf_renegotiation(ks_ssl->ssl_config, MBEDTLS_SSL_RENEGOTIATION_DISABLED);
+
     /* Disable record splitting (for now).  OpenVPN assumes records are sent
      * unfragmented, and changing that will require thorough review and
      * testing.  Since OpenVPN is not susceptible to BEAST, we can just
@@ -1012,13 +1123,11 @@
                                       ssl_ctx->priv_key));
 
     /* Initialise SSL verification */
-#if P2MP_SERVER
     if (session->opt->ssl_flags & SSLF_CLIENT_CERT_OPTIONAL)
     {
         mbedtls_ssl_conf_authmode(ks_ssl->ssl_config, MBEDTLS_SSL_VERIFY_OPTIONAL);
     }
     else if (!(session->opt->ssl_flags & SSLF_CLIENT_CERT_NOT_REQUIRED))
-#endif
     {
         mbedtls_ssl_conf_authmode(ks_ssl->ssl_config, MBEDTLS_SSL_VERIFY_REQUIRED);
     }
@@ -1059,6 +1168,15 @@
         }
     }
 
+#ifdef HAVE_EXPORT_KEYING_MATERIAL
+    /* Initialize keying material exporter */
+    if (session->opt->ekm_size)
+    {
+        mbedtls_ssl_conf_export_keys_ext_cb(ks_ssl->ssl_config,
+                                            mbedtls_ssl_export_keys_cb, session);
+    }
+#endif
+
     /* Initialise SSL context */
     ALLOC_OBJ_CLEAR(ks_ssl->ctx, mbedtls_ssl_context);
     mbedtls_ssl_init(ks_ssl->ctx);
@@ -1075,6 +1193,8 @@
 {
     if (ks_ssl)
     {
+        free(ks_ssl->exported_key_material);
+
         if (ks_ssl->ctx)
         {
             mbedtls_ssl_free(ks_ssl->ctx);
@@ -1421,4 +1541,4 @@
     return mbedtls_version;
 }
 
-#endif /* defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_MBEDTLS) */
+#endif /* defined(ENABLE_CRYPTO_MBEDTLS) */
diff --git a/src/openvpn/ssl_mbedtls.h b/src/openvpn/ssl_mbedtls.h
index f99aba1..0525134 100644
--- a/src/openvpn/ssl_mbedtls.h
+++ b/src/openvpn/ssl_mbedtls.h
@@ -33,9 +33,10 @@
 
 #include <mbedtls/ssl.h>
 #include <mbedtls/x509_crt.h>
+#include <mbedtls/version.h>
 
 #if defined(ENABLE_PKCS11)
-#include <mbedtls/pkcs11.h>
+#include <pkcs11-helper-1.0/pkcs11h-certificate.h>
 #endif
 
 typedef struct _buffer_entry buffer_entry;
@@ -58,6 +59,30 @@
 } bio_ctx;
 
 /**
+ * External signing function prototype.  A function pointer to a function
+ * implementing this prototype is provided to
+ * tls_ctx_use_external_signing_func().
+ *
+ * @param sign_ctx  The context for the signing function.
+ * @param src       The data to be signed,
+ * @param src_len   The length of src, in bytes.
+ * @param dst       The destination buffer for the signature.
+ * @param dst_len   The length of the destination buffer.
+ *
+ * @return true if signing succeeded, false otherwise.
+ */
+typedef bool (*external_sign_func)(
+    void *sign_ctx, const void *src, size_t src_size,
+    void *dst, size_t dst_size);
+
+/** Context used by external_pkcs1_sign() */
+struct external_context {
+    size_t signature_length;
+    external_sign_func sign;
+    void *sign_ctx;
+};
+
+/**
  * Structure that wraps the TLS context. Contents differ depending on the
  * SSL library used.
  *
@@ -75,13 +100,12 @@
     mbedtls_x509_crl *crl;              /**< Certificate Revocation List */
     time_t crl_last_mtime;              /**< CRL last modification time */
     off_t crl_last_size;                /**< size of last loaded CRL */
-#if defined(ENABLE_PKCS11)
-    mbedtls_pkcs11_context *priv_key_pkcs11;    /**< PKCS11 private key */
+#ifdef ENABLE_PKCS11
+    pkcs11h_certificate_t pkcs11_cert;  /**< PKCS11 certificate */
 #endif
-#ifdef MANAGMENT_EXTERNAL_KEY
-    struct external_context *external_key; /**< Management external key */
-#endif
+    struct external_context external_key; /**< External key context */
     int *allowed_ciphers;       /**< List of allowed ciphers for this connection */
+    mbedtls_ecp_group_id *groups;     /**< List of allowed groups for this connection */
     mbedtls_x509_crt_profile cert_profile; /**< Allowed certificate types */
 };
 
@@ -89,7 +113,24 @@
     mbedtls_ssl_config *ssl_config;     /**< mbedTLS global ssl config */
     mbedtls_ssl_context *ctx;           /**< mbedTLS connection context */
     bio_ctx *bio_ctx;
+
+    /** Keying material exporter cache (RFC 5705). */
+    uint8_t *exported_key_material;
+
 };
 
+/**
+ * Call the supplied signing function to create a TLS signature during the
+ * TLS handshake.
+ *
+ * @param ctx                   TLS context to use.
+ * @param sign_func             Signing function to call.
+ * @param sign_ctx              Context for the sign function.
+ *
+ * @return                      0 if successful, 1 if an error occurred.
+ */
+int tls_ctx_use_external_signing_func(struct tls_root_ctx *ctx,
+                                      external_sign_func sign_func,
+                                      void *sign_ctx);
 
 #endif /* SSL_MBEDTLS_H_ */
diff --git a/src/openvpn/ssl_ncp.c b/src/openvpn/ssl_ncp.c
new file mode 100644
index 0000000..45bddbe
--- /dev/null
+++ b/src/openvpn/ssl_ncp.c
@@ -0,0 +1,342 @@
+/*
+ *  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) 2002-2018 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2010-2018 Fox Crypto B.V. <openvpn@fox-it.com>
+ *  Copyright (C) 2008-2013 David Sommerseth <dazo@users.sourceforge.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * @file Control Channel SSL/Data dynamic negotion Module
+ * This file is split from ssl.c to be able to unit test it.
+ */
+
+/*
+ * The routines in this file deal with dynamically negotiating
+ * the data channel HMAC and cipher keys through a TLS session.
+ *
+ * Both the TLS session and the data channel are multiplexed
+ * over the same TCP/UDP port.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+#include "win32.h"
+
+#include "error.h"
+#include "common.h"
+
+#include "ssl_ncp.h"
+#include "openvpn.h"
+
+/**
+ * Return the Negotiable Crypto Parameters version advertised in the peer info
+ * string, or 0 if none specified.
+ */
+static int
+tls_peer_info_ncp_ver(const char *peer_info)
+{
+    const char *ncpstr = peer_info ? strstr(peer_info, "IV_NCP=") : NULL;
+    if (ncpstr)
+    {
+        int ncp = 0;
+        int r = sscanf(ncpstr, "IV_NCP=%d", &ncp);
+        if (r == 1)
+        {
+            return ncp;
+        }
+    }
+    return 0;
+}
+
+/**
+ * Returns whether the client supports NCP either by
+ * announcing IV_NCP>=2 or the IV_CIPHERS list
+ */
+bool
+tls_peer_supports_ncp(const char *peer_info)
+{
+    if (!peer_info)
+    {
+        return false;
+    }
+    else if (tls_peer_info_ncp_ver(peer_info) >= 2
+             || strstr(peer_info, "IV_CIPHERS="))
+    {
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+char *
+mutate_ncp_cipher_list(const char *list, struct gc_arena *gc)
+{
+    bool error_found = false;
+
+    struct buffer new_list  = alloc_buf(MAX_NCP_CIPHERS_LENGTH);
+
+    char *const tmp_ciphers = string_alloc(list, NULL);
+    const char *token = strtok(tmp_ciphers, ":");
+    while (token)
+    {
+        /*
+         * Going through a roundtrip by using cipher_kt_get/cipher_kt_name
+         * (and translate_cipher_name_from_openvpn/
+         * translate_cipher_name_to_openvpn) also normalises the cipher name,
+         * e.g. replacing AeS-128-gCm with AES-128-GCM
+         */
+        const cipher_kt_t *ktc = cipher_kt_get(token);
+        if (strcmp(token, "none") == 0)
+        {
+            msg(M_WARN, "WARNING: cipher 'none' specified for --data-ciphers. "
+                        "This allows negotiation of NO encryption and "
+                        "tunnelled data WILL then be transmitted in clear text "
+                        "over the network! "
+                        "PLEASE DO RECONSIDER THIS SETTING!");
+        }
+        if (!ktc && strcmp(token, "none") != 0)
+        {
+            msg(M_WARN, "Unsupported cipher in --data-ciphers: %s", token);
+            error_found = true;
+        }
+        else
+        {
+            const char *ovpn_cipher_name = cipher_kt_name(ktc);
+            if (ktc == NULL)
+            {
+                /* NULL resolves to [null-cipher] but we need none for
+                 * data-ciphers */
+                ovpn_cipher_name = "none";
+            }
+
+            if (buf_len(&new_list)> 0)
+            {
+                /* The next if condition ensure there is always space for
+                 * a :
+                 */
+                buf_puts(&new_list, ":");
+            }
+
+            /* Ensure buffer has capacity for cipher name + : + \0 */
+            if (!(buf_forward_capacity(&new_list) >
+                  strlen(ovpn_cipher_name) + 2))
+            {
+                msg(M_WARN, "Length of --data-ciphers is over the "
+                    "limit of 127 chars");
+                error_found = true;
+            }
+            else
+            {
+                buf_puts(&new_list, ovpn_cipher_name);
+            }
+        }
+        token = strtok(NULL, ":");
+    }
+
+
+
+    char *ret = NULL;
+    if (!error_found && buf_len(&new_list) > 0)
+    {
+        buf_null_terminate(&new_list);
+        ret = string_alloc(buf_str(&new_list), gc);
+    }
+    free(tmp_ciphers);
+    free_buf(&new_list);
+
+    return ret;
+}
+
+bool
+tls_item_in_cipher_list(const char *item, const char *list)
+{
+    char *tmp_ciphers = string_alloc(list, NULL);
+    char *tmp_ciphers_orig = tmp_ciphers;
+
+    const char *token = strtok(tmp_ciphers, ":");
+    while (token)
+    {
+        if (0 == strcmp(token, item))
+        {
+            break;
+        }
+        token = strtok(NULL, ":");
+    }
+    free(tmp_ciphers_orig);
+
+    return token != NULL;
+}
+
+const char *
+tls_peer_ncp_list(const char *peer_info, struct gc_arena *gc)
+{
+    /* Check if the peer sends the IV_CIPHERS list */
+    const char *ncp_ciphers_start;
+    if (peer_info && (ncp_ciphers_start = strstr(peer_info, "IV_CIPHERS=")))
+    {
+        ncp_ciphers_start += strlen("IV_CIPHERS=");
+        const char *ncp_ciphers_end = strstr(ncp_ciphers_start, "\n");
+        if (!ncp_ciphers_end)
+        {
+            /* IV_CIPHERS is at end of the peer_info list and no '\n'
+             * follows */
+            ncp_ciphers_end = ncp_ciphers_start + strlen(ncp_ciphers_start);
+        }
+
+        char *ncp_ciphers_peer = string_alloc(ncp_ciphers_start, gc);
+        /* NULL terminate the copy at the right position */
+        ncp_ciphers_peer[ncp_ciphers_end - ncp_ciphers_start] = '\0';
+        return ncp_ciphers_peer;
+
+    }
+    else if (tls_peer_info_ncp_ver(peer_info)>=2)
+    {
+        /* If the peer announces IV_NCP=2 then it supports the AES GCM
+         * ciphers */
+        return "AES-256-GCM:AES-128-GCM";
+    }
+    else
+    {
+        return "";
+    }
+}
+
+char *
+ncp_get_best_cipher(const char *server_list, const char *peer_info,
+                    const char *remote_cipher, struct gc_arena *gc)
+{
+    /*
+     * The gc of the parameter is tied to the VPN session, create a
+     * short lived gc arena that is only valid for the duration of
+     * this function
+     */
+
+    struct gc_arena gc_tmp = gc_new();
+
+    const char *peer_ncp_list = tls_peer_ncp_list(peer_info, &gc_tmp);
+
+    /* non-NCP client without OCC?  "assume nothing" */
+    /* For client doing the newer version of NCP (that send IV_CIPHER)
+     * we cannot assume that they will accept remote_cipher */
+    if (remote_cipher == NULL ||
+        (peer_info && strstr(peer_info, "IV_CIPHERS=")))
+    {
+        remote_cipher = "";
+    }
+
+    char *tmp_ciphers = string_alloc(server_list, &gc_tmp);
+
+    const char *token;
+    while ((token = strsep(&tmp_ciphers, ":")))
+    {
+        if (tls_item_in_cipher_list(token, peer_ncp_list)
+            || streq(token, remote_cipher))
+        {
+            break;
+        }
+    }
+
+    char *ret = NULL;
+    if (token != NULL)
+    {
+        ret = string_alloc(token, gc);
+    }
+
+    gc_free(&gc_tmp);
+    return ret;
+}
+
+/**
+ * "Poor man's NCP": Use peer cipher if it is an allowed (NCP) cipher.
+ * Allows non-NCP peers to upgrade their cipher individually.
+ *
+ * Returns true if we switched to the peer's cipher
+ *
+ * Make sure to call tls_session_update_crypto_params() after calling this
+ * function.
+ */
+static bool
+tls_poor_mans_ncp(struct options *o, const char *remote_ciphername)
+{
+    if (remote_ciphername
+        && tls_item_in_cipher_list(remote_ciphername, o->ncp_ciphers))
+    {
+        o->ciphername = string_alloc(remote_ciphername, &o->gc);
+        msg(D_TLS_DEBUG_LOW, "Using peer cipher '%s'", o->ciphername);
+        return true;
+    }
+    return false;
+}
+
+bool
+check_pull_client_ncp(struct context *c, const int found)
+{
+    if (found & OPT_P_NCP)
+    {
+        msg(D_PUSH, "OPTIONS IMPORT: data channel crypto options modified");
+        return true;
+    }
+
+    if (!c->options.ncp_enabled)
+    {
+        return true;
+    }
+    /* If the server did not push a --cipher, we will switch to the
+     * remote cipher if it is in our ncp-ciphers list */
+    if(tls_poor_mans_ncp(&c->options, c->c2.tls_multi->remote_ciphername))
+    {
+        return true;
+    }
+
+    /* We could not figure out the peer's cipher but we have fallback
+     * enabled */
+    if (!c->c2.tls_multi->remote_ciphername && c->options.enable_ncp_fallback)
+    {
+        return true;
+    }
+
+    /* We failed negotiation, give appropiate error message */
+    if (c->c2.tls_multi->remote_ciphername)
+    {
+        msg(D_TLS_ERRORS, "OPTIONS ERROR: failed to negotiate "
+            "cipher with server.  Add the server's "
+            "cipher ('%s') to --data-ciphers (currently '%s') if "
+            "you want to connect to this server.",
+            c->c2.tls_multi->remote_ciphername,
+            c->options.ncp_ciphers);
+        return false;
+
+    }
+    else
+    {
+        msg(D_TLS_ERRORS, "OPTIONS ERROR: failed to negotiate "
+            "cipher with server. Configure "
+            "--data-ciphers-fallback if you want to connect "
+            "to this server.");
+        return false;
+    }
+}
diff --git a/src/openvpn/ssl_ncp.h b/src/openvpn/ssl_ncp.h
new file mode 100644
index 0000000..39158a5
--- /dev/null
+++ b/src/openvpn/ssl_ncp.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) 2002-2018 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2010-2018 Fox Crypto B.V. <openvpn@fox-it.com>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * @file Control Channel SSL/Data dynamic negotion Module
+ * This file is split from ssl.h to be able to unit test it.
+ */
+
+#ifndef OPENVPN_SSL_NCP_H
+#define OPENVPN_SSL_NCP_H
+
+#include "buffer.h"
+#include "options.h"
+
+/**
+ * Returns whether the client supports NCP either by
+ * announcing IV_NCP>=2 or the IV_CIPHERS list
+ */
+bool
+tls_peer_supports_ncp(const char *peer_info);
+
+/* forward declaration to break include dependency loop */
+struct context;
+
+/**
+ * Checks whether the cipher negotiation is in an acceptable state
+ * and we continue to connect or should abort.
+ *
+ * @return  Wether the client NCP process suceeded or failed
+ */
+bool
+check_pull_client_ncp(struct context *c, int found);
+
+/**
+ * Iterates through the ciphers in server_list and return the first
+ * cipher that is also supported by the peer according to the IV_NCP
+ * and IV_CIPHER values in peer_info.
+ *
+ * We also accept a cipher that is the remote cipher of the client for
+ * "Poor man's NCP": Use peer cipher if it is an allowed (NCP) cipher.
+ * Allows non-NCP peers to upgrade their cipher individually.
+ *
+ * Make sure to call tls_session_update_crypto_params() after calling this
+ * function.
+ *
+ * @param gc   gc arena that is ONLY used to allocate the returned string
+ *
+ * @returns NULL if no common cipher is available, otherwise the best common
+ * cipher
+ */
+char *
+ncp_get_best_cipher(const char *server_list, const char *peer_info,
+                    const char *remote_cipher, struct gc_arena *gc);
+
+
+/**
+ * Returns the support cipher list from the peer according to the IV_NCP
+ * and IV_CIPHER values in peer_info.
+ *
+ * @returns Either a string containing the ncp list that is either static
+ * or allocated via gc. If no information is available an empty string
+ * ("") is returned.
+ */
+const char *
+tls_peer_ncp_list(const char *peer_info, struct gc_arena *gc);
+
+/**
+ * Check whether the ciphers in the supplied list are supported.
+ *
+ * @param list          Colon-separated list of ciphers
+ * @parms gc            gc_arena to allocate the returned string
+ *
+ * @returns             colon separated string of normalised (via
+ *                      translate_cipher_name_from_openvpn) and
+ *                      zero terminated string iff all ciphers
+ *                      in list are supported and the total length
+ *                      is short than MAX_NCP_CIPHERS_LENGTH. NULL
+ *                      otherwise.
+ */
+char *
+mutate_ncp_cipher_list(const char *list, struct gc_arena *gc);
+
+/**
+ * Return true iff item is present in the colon-separated zero-terminated
+ * cipher list.
+ */
+bool tls_item_in_cipher_list(const char *item, const char *list);
+
+/**
+ * The maximum length of a ncp-cipher string that is accepted.
+ *
+ * Since this list needs to be pushed as IV_CIPHERS, we are conservative
+ * about its length.
+ */
+#define MAX_NCP_CIPHERS_LENGTH 127
+
+#endif /* ifndef OPENVPN_SSL_NCP_H */
diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c
index 19509b7..7b06beb 100644
--- a/src/openvpn/ssl_openssl.c
+++ b/src/openvpn/ssl_openssl.c
@@ -34,7 +34,7 @@
 
 #include "syshead.h"
 
-#if defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_OPENSSL)
+#if defined(ENABLE_CRYPTO_OPENSSL)
 
 #include "errlevel.h"
 #include "buffer.h"
@@ -52,10 +52,15 @@
 
 #include "ssl_verify_openssl.h"
 
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/dh.h>
+#include <openssl/dsa.h>
 #include <openssl/err.h>
 #include <openssl/pkcs12.h>
+#include <openssl/rsa.h>
 #include <openssl/x509.h>
-#include <openssl/crypto.h>
+#include <openssl/ssl.h>
 #ifndef OPENSSL_NO_EC
 #include <openssl/ec.h>
 #endif
@@ -110,6 +115,11 @@
     {
         crypto_msg(M_FATAL, "SSL_CTX_new SSLv23_server_method");
     }
+    if (ERR_peek_error() != 0)
+    {
+        crypto_msg(M_WARN, "Warning: TLS server context initialisation "
+                   "has warnings.");
+    }
 }
 
 void
@@ -123,6 +133,11 @@
     {
         crypto_msg(M_FATAL, "SSL_CTX_new SSLv23_client_method");
     }
+    if (ERR_peek_error() != 0)
+    {
+        crypto_msg(M_WARN, "Warning: TLS client context initialisation "
+                   "has warnings.");
+    }
 }
 
 void
@@ -149,13 +164,14 @@
 {
     if (session->opt->ekm_size > 0)
     {
-#if (OPENSSL_VERSION_NUMBER >= 0x10001000)
         unsigned int size = session->opt->ekm_size;
         struct gc_arena gc = gc_new();
         unsigned char *ekm = (unsigned char *) gc_malloc(size, true, &gc);
 
         if (SSL_export_keying_material(ssl->ssl, ekm, size,
-                                       session->opt->ekm_label, session->opt->ekm_label_size, NULL, 0, 0))
+                                       session->opt->ekm_label,
+                                       session->opt->ekm_label_size,
+                                       NULL, 0, 0))
         {
             unsigned int len = (size * 2) + 2;
 
@@ -171,7 +187,6 @@
             setenv_del(session->opt->es, "exported_keying_material");
         }
         gc_free(&gc);
-#endif /* if (OPENSSL_VERSION_NUMBER >= 0x10001000) */
     }
 }
 
@@ -209,13 +224,34 @@
 int
 tls_version_max(void)
 {
-#if defined(TLS1_3_VERSION) && !defined(OPENSSL_NO_TLS1_3)
+#if defined(TLS1_3_VERSION)
+    /* If this is defined we can safely assume TLS 1.3 support */
     return TLS_VER_1_3;
+#elif OPENSSL_VERSION_NUMBER >= 0x10100000L
+    /*
+     * If TLS_VER_1_3 is not defined, we were compiled against a version that
+     * did not support TLS 1.3.
+     *
+     * However, the library we are *linked* against might be OpenSSL 1.1.1
+     * and therefore supports TLS 1.3. This needs to be checked at runtime
+     * since we can be compiled against 1.1.0 and then the library can be
+     * upgraded to 1.1.1.
+     * We only need to check this for OpenSSL versions that can be
+     * upgraded to 1.1.1 without recompile (>= 1.1.0)
+     */
+    if (OpenSSL_version_num() >= 0x1010100fL)
+    {
+        return TLS_VER_1_3;
+    }
+    else
+    {
+        return TLS_VER_1_2;
+    }
 #elif defined(TLS1_2_VERSION) || defined(SSL_OP_NO_TLSv1_2)
     return TLS_VER_1_2;
 #elif defined(TLS1_1_VERSION) || defined(SSL_OP_NO_TLSv1_1)
     return TLS_VER_1_1;
-#else
+#else  /* if defined(TLS1_3_VERSION) */
     return TLS_VER_1_0;
 #endif
 }
@@ -236,12 +272,25 @@
     {
         return TLS1_2_VERSION;
     }
-#if defined(TLS1_3_VERSION) && !defined(OPENSSL_NO_TLS1_3)
     else if (ver == TLS_VER_1_3)
     {
+        /*
+         * Supporting the library upgraded to TLS1.3 without recompile
+         * is enough to support here with a simple constant that the same
+         * as in the TLS 1.3, so spec it is very unlikely that OpenSSL
+         * will change this constant
+         */
+#ifndef TLS1_3_VERSION
+        /*
+         * We do not want to define TLS_VER_1_3 if not defined
+         * since other parts of the code use the existance of this macro
+         * as proxy for TLS 1.3 support
+         */
+        return 0x0304;
+#else
         return TLS1_3_VERSION;
-    }
 #endif
+    }
     return 0;
 }
 
@@ -280,17 +329,17 @@
 {
     ASSERT(NULL != ctx);
 
-    /* default certificate verification flags */
-    int flags = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
-
     /* process SSL options */
     long sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_NO_TICKET;
 #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
     sslopt |= SSL_OP_CIPHER_SERVER_PREFERENCE;
 #endif
-#ifdef SSL_OP_NO_COMPRESSION
-    /* Disable compression - flag not available in OpenSSL 0.9.8 */
     sslopt |= SSL_OP_NO_COMPRESSION;
+    /* Disable TLS renegotiations. OpenVPN's renegotiation creates new SSL
+     * session and does not depend on this feature. And TLS renegotiations have
+     * been problematic in the past */
+#ifdef SSL_OP_NO_RENEGOTIATION
+    sslopt |= SSL_OP_NO_RENEGOTIATION;
 #endif
 
     SSL_CTX_set_options(ctx->ctx, sslopt);
@@ -307,17 +356,16 @@
     SSL_CTX_set_default_passwd_cb(ctx->ctx, pem_password_callback);
 
     /* Require peer certificate verification */
-#if P2MP_SERVER
+    int verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
     if (ssl_flags & SSLF_CLIENT_CERT_NOT_REQUIRED)
     {
-        flags = 0;
+        verify_flags = 0;
     }
     else if (ssl_flags & SSLF_CLIENT_CERT_OPTIONAL)
     {
-        flags = SSL_VERIFY_PEER;
+        verify_flags = SSL_VERIFY_PEER;
     }
-#endif
-    SSL_CTX_set_verify(ctx->ctx, flags, verify_callback);
+    SSL_CTX_set_verify(ctx->ctx, verify_flags, verify_callback);
 
     SSL_CTX_set_info_callback(ctx->ctx, info_callback);
 
@@ -325,28 +373,8 @@
 }
 
 void
-tls_ctx_restrict_ciphers(struct tls_root_ctx *ctx, const char *ciphers)
+convert_tls_list_to_openssl(char *openssl_ciphers, size_t len,const char *ciphers)
 {
-    if (ciphers == NULL)
-    {
-        /* Use sane default TLS cipher list */
-        if (!SSL_CTX_set_cipher_list(ctx->ctx,
-                                     /* Use openssl's default list as a basis */
-                                     "DEFAULT"
-                                     /* Disable export ciphers and openssl's 'low' and 'medium' ciphers */
-                                     ":!EXP:!LOW:!MEDIUM"
-                                     /* Disable static (EC)DH keys (no forward secrecy) */
-                                     ":!kDH:!kECDH"
-                                     /* Disable DSA private keys */
-                                     ":!DSS"
-                                     /* Disable unsupported TLS modes */
-                                     ":!PSK:!SRP:!kRSA"))
-        {
-            crypto_msg(M_FATAL, "Failed to set default TLS cipher list.");
-        }
-        return;
-    }
-
     /* Parse supplied cipher list and pass on to OpenSSL */
     size_t begin_of_cipher, end_of_cipher;
 
@@ -355,12 +383,9 @@
 
     const tls_cipher_name_pair *cipher_pair;
 
-    char openssl_ciphers[4096];
     size_t openssl_ciphers_len = 0;
     openssl_ciphers[0] = '\0';
 
-    ASSERT(NULL != ctx);
-
     /* Translate IANA cipher suite names to OpenSSL names */
     begin_of_cipher = end_of_cipher = 0;
     for (; begin_of_cipher < strlen(ciphers); begin_of_cipher = end_of_cipher)
@@ -397,11 +422,11 @@
 
         /* Make sure new cipher name fits in cipher string */
         if ((SIZE_MAX - openssl_ciphers_len) < current_cipher_len
-            || ((sizeof(openssl_ciphers)-1) < openssl_ciphers_len + current_cipher_len))
+            || (len - 1) < (openssl_ciphers_len + current_cipher_len))
         {
             msg(M_FATAL,
                 "Failed to set restricted TLS cipher list, too long (>%d).",
-                (int)sizeof(openssl_ciphers)-1);
+                (int)(len - 1));
         }
 
         /* Concatenate cipher name to OpenSSL cipher string */
@@ -417,6 +442,35 @@
     {
         openssl_ciphers[openssl_ciphers_len-1] = '\0';
     }
+}
+
+void
+tls_ctx_restrict_ciphers(struct tls_root_ctx *ctx, const char *ciphers)
+{
+    if (ciphers == NULL)
+    {
+        /* Use sane default TLS cipher list */
+        if (!SSL_CTX_set_cipher_list(ctx->ctx,
+                                     /* Use openssl's default list as a basis */
+                                     "DEFAULT"
+                                     /* Disable export ciphers and openssl's 'low' and 'medium' ciphers */
+                                     ":!EXP:!LOW:!MEDIUM"
+                                     /* Disable static (EC)DH keys (no forward secrecy) */
+                                     ":!kDH:!kECDH"
+                                     /* Disable DSA private keys */
+                                     ":!DSS"
+                                     /* Disable unsupported TLS modes */
+                                     ":!PSK:!SRP:!kRSA"))
+        {
+            crypto_msg(M_FATAL, "Failed to set default TLS cipher list.");
+        }
+        return;
+    }
+
+    char openssl_ciphers[4096];
+    convert_tls_list_to_openssl(openssl_ciphers, sizeof(openssl_ciphers), ciphers);
+
+    ASSERT(NULL != ctx);
 
     /* Set OpenSSL cipher list */
     if (!SSL_CTX_set_cipher_list(ctx->ctx, openssl_ciphers))
@@ -462,10 +516,10 @@
         return;
     }
 
-#if (OPENSSL_VERSION_NUMBER < 0x1010100fL) || !defined(TLS1_3_VERSION) || defined(OPENSSL_NO_TLS1_3)
-        crypto_msg(M_WARN, "Not compiled with OpenSSL 1.1.1 or higher, or without TLS 1.3 support. "
-                       "Ignoring TLS 1.3 only tls-ciphersuites '%s' setting.",
-                        ciphers);
+#if !defined(TLS1_3_VERSION)
+    crypto_msg(M_WARN, "Not compiled with OpenSSL 1.1.1 or higher. "
+               "Ignoring TLS 1.3 only tls-ciphersuites '%s' setting.",
+               ciphers);
 #else
     ASSERT(NULL != ctx);
 
@@ -506,13 +560,64 @@
     {
         msg(M_FATAL, "ERROR: Invalid cert profile: %s", profile);
     }
-#else
+#else  /* ifdef HAVE_SSL_CTX_SET_SECURITY_LEVEL */
     if (profile)
     {
-        msg(M_WARN, "WARNING: OpenSSL 1.0.1 does not support --tls-cert-profile"
+        msg(M_WARN, "WARNING: OpenSSL 1.0.2 does not support --tls-cert-profile"
             ", ignoring user-set profile: '%s'", profile);
     }
-#endif
+#endif /* ifdef HAVE_SSL_CTX_SET_SECURITY_LEVEL */
+}
+
+void
+tls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups)
+{
+    ASSERT(ctx);
+    struct gc_arena gc = gc_new();
+    /* This method could be as easy as
+     *  SSL_CTX_set1_groups_list(ctx->ctx, groups)
+     * but OpenSSL does not like the name secp256r1 for prime256v1
+     * This is one of the important curves.
+     * To support the same name for OpenSSL and mbedTLS, we do
+     * this dance.
+     */
+
+    int groups_count = get_num_elements(groups, ':');
+
+    int *glist;
+    /* Allocate an array for them */
+    ALLOC_ARRAY_CLEAR_GC(glist, int, groups_count, &gc);
+
+    /* Parse allowed ciphers, getting IDs */
+    int glistlen = 0;
+    char *tmp_groups = string_alloc(groups, &gc);
+
+    const char *token;
+    while ((token = strsep(&tmp_groups, ":")))
+    {
+        if (streq(token, "secp256r1"))
+        {
+            token = "prime256v1";
+        }
+        int nid = OBJ_sn2nid(token);
+
+        if (nid == 0)
+        {
+            msg(M_WARN, "Warning unknown curve/group specified: %s", token);
+        }
+        else
+        {
+            glist[glistlen] = nid;
+            glistlen++;
+        }
+    }
+
+    if (!SSL_CTX_set1_groups(ctx->ctx, glist, glistlen))
+    {
+        crypto_msg(M_FATAL, "Failed to set allowed TLS group list: %s",
+                   groups);
+    }
+    gc_free(&gc);
 }
 
 void
@@ -523,18 +628,11 @@
 
     ASSERT(ctx);
 
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)
-    /* OpenSSL 1.0.2 and up */
     cert = SSL_CTX_get0_certificate(ctx->ctx);
-#else
-    /* OpenSSL 1.0.1 and earlier need an SSL object to get at the certificate */
-    SSL *ssl = SSL_new(ctx->ctx);
-    cert = SSL_get_certificate(ssl);
-#endif
 
     if (cert == NULL)
     {
-        goto cleanup; /* Nothing to check if there is no certificate */
+        return; /* Nothing to check if there is no certificate */
     }
 
     ret = X509_cmp_time(X509_get0_notBefore(cert), NULL);
@@ -556,27 +654,20 @@
     {
         msg(M_WARN, "WARNING: Your certificate has expired!");
     }
-
-cleanup:
-#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER)
-    SSL_free(ssl);
-#endif
-    return;
 }
 
 void
 tls_ctx_load_dh_params(struct tls_root_ctx *ctx, const char *dh_file,
-                       const char *dh_file_inline
-                       )
+                       bool dh_file_inline)
 {
     DH *dh;
     BIO *bio;
 
     ASSERT(NULL != ctx);
 
-    if (!strcmp(dh_file, INLINE_FILE_TAG) && dh_file_inline)
+    if (dh_file_inline)
     {
-        if (!(bio = BIO_new_mem_buf((char *)dh_file_inline, -1)))
+        if (!(bio = BIO_new_mem_buf((char *)dh_file, -1)))
         {
             crypto_msg(M_FATAL, "Cannot open memory BIO for inline DH parameters");
         }
@@ -595,7 +686,8 @@
 
     if (!dh)
     {
-        crypto_msg(M_FATAL, "Cannot load DH parameters from %s", dh_file);
+        crypto_msg(M_FATAL, "Cannot load DH parameters from %s",
+                   print_key_filename(dh_file, dh_file_inline));
     }
     if (!SSL_CTX_set_tmp_dh(ctx->ctx, dh))
     {
@@ -628,7 +720,6 @@
     }
     else
     {
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L
 #if (OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER))
 
         /* OpenSSL 1.0.2 and newer can automatically handle ECDH parameter
@@ -639,29 +730,6 @@
          * so do nothing */
 #endif
         return;
-#else
-        /* For older OpenSSL we have to extract the curve from key on our own */
-        EC_KEY *eckey = NULL;
-        const EC_GROUP *ecgrp = NULL;
-        EVP_PKEY *pkey = NULL;
-
-        /* Little hack to get private key ref from SSL_CTX, yay OpenSSL... */
-        SSL *ssl = SSL_new(ctx->ctx);
-        if (!ssl)
-        {
-            crypto_msg(M_FATAL, "SSL_new failed");
-        }
-        pkey = SSL_get_privatekey(ssl);
-        SSL_free(ssl);
-
-        msg(D_TLS_DEBUG, "Extracting ECDH curve from private key");
-
-        if (pkey != NULL && (eckey = EVP_PKEY_get1_EC_KEY(pkey)) != NULL
-            && (ecgrp = EC_KEY_get0_group(eckey)) != NULL)
-        {
-            nid = EC_GROUP_get_curve_name(ecgrp);
-        }
-#endif
     }
 
     /* Translate NID back to name , just for kicks */
@@ -699,9 +767,7 @@
 
 int
 tls_ctx_load_pkcs12(struct tls_root_ctx *ctx, const char *pkcs12_file,
-                    const char *pkcs12_file_inline,
-                    bool load_ca_file
-                    )
+                    bool pkcs12_file_inline, bool load_ca_file)
 {
     FILE *fp;
     EVP_PKEY *pkey;
@@ -713,11 +779,11 @@
 
     ASSERT(NULL != ctx);
 
-    if (!strcmp(pkcs12_file, INLINE_FILE_TAG) && pkcs12_file_inline)
+    if (pkcs12_file_inline)
     {
         BIO *b64 = BIO_new(BIO_f_base64());
-        BIO *bio = BIO_new_mem_buf((void *) pkcs12_file_inline,
-                                   (int) strlen(pkcs12_file_inline));
+        BIO *bio = BIO_new_mem_buf((void *) pkcs12_file,
+                                   (int) strlen(pkcs12_file));
         ASSERT(b64 && bio);
         BIO_push(b64, bio);
         p12 = d2i_PKCS12_bio(b64, NULL);
@@ -873,28 +939,19 @@
     }
 }
 
-/* Like tls_ctx_load_cert, but returns a copy of the certificate in **X509 */
-static void
-tls_ctx_load_cert_file_and_copy(struct tls_root_ctx *ctx,
-                                const char *cert_file, const char *cert_file_inline, X509 **x509
-                                )
+void
+tls_ctx_load_cert_file(struct tls_root_ctx *ctx, const char *cert_file,
+                       bool cert_file_inline)
 {
     BIO *in = NULL;
     X509 *x = NULL;
     int ret = 0;
-    bool inline_file = false;
 
     ASSERT(NULL != ctx);
-    if (NULL != x509)
-    {
-        ASSERT(NULL == *x509);
-    }
 
-    inline_file = (strcmp(cert_file, INLINE_FILE_TAG) == 0);
-
-    if (inline_file && cert_file_inline)
+    if (cert_file_inline)
     {
-        in = BIO_new_mem_buf((char *)cert_file_inline, -1);
+        in = BIO_new_mem_buf((char *) cert_file, -1);
     }
     else
     {
@@ -925,7 +982,7 @@
 end:
     if (!ret)
     {
-        if (inline_file)
+        if (cert_file_inline)
         {
             crypto_msg(M_FATAL, "Cannot load inline certificate file");
         }
@@ -943,27 +1000,15 @@
     {
         BIO_free(in);
     }
-    if (x509)
-    {
-        *x509 = x;
-    }
-    else if (x)
+    if (x)
     {
         X509_free(x);
     }
 }
 
-void
-tls_ctx_load_cert_file(struct tls_root_ctx *ctx, const char *cert_file,
-                       const char *cert_file_inline)
-{
-    tls_ctx_load_cert_file_and_copy(ctx, cert_file, cert_file_inline, NULL);
-}
-
 int
 tls_ctx_load_priv_file(struct tls_root_ctx *ctx, const char *priv_key_file,
-                       const char *priv_key_file_inline
-                       )
+                       bool priv_key_file_inline)
 {
     SSL_CTX *ssl_ctx = NULL;
     BIO *in = NULL;
@@ -974,9 +1019,9 @@
 
     ssl_ctx = ctx->ctx;
 
-    if (!strcmp(priv_key_file, INLINE_FILE_TAG) && priv_key_file_inline)
+    if (priv_key_file_inline)
     {
-        in = BIO_new_mem_buf((char *)priv_key_file_inline, -1);
+        in = BIO_new_mem_buf((char *) priv_key_file, -1);
     }
     else
     {
@@ -991,6 +1036,11 @@
     pkey = PEM_read_bio_PrivateKey(in, NULL,
                                    SSL_CTX_get_default_passwd_cb(ctx->ctx),
                                    SSL_CTX_get_default_passwd_cb_userdata(ctx->ctx));
+    if (!pkey)
+    {
+        pkey = engine_load_key(priv_key_file, ctx->ctx);
+    }
+
     if (!pkey || !SSL_CTX_use_PrivateKey(ssl_ctx, pkey))
     {
 #ifdef ENABLE_MANAGEMENT
@@ -999,7 +1049,8 @@
             management_auth_failure(management, UP_TYPE_PRIVATE_KEY, NULL);
         }
 #endif
-        crypto_msg(M_WARN, "Cannot load private key file %s", priv_key_file);
+        crypto_msg(M_WARN, "Cannot load private key file %s",
+                   print_key_filename(priv_key_file, priv_key_file_inline));
         goto end;
     }
 
@@ -1024,7 +1075,7 @@
 
 void
 backend_tls_ctx_reload_crl(struct tls_root_ctx *ssl_ctx, const char *crl_file,
-                           const char *crl_inline)
+                           bool crl_inline)
 {
     BIO *in = NULL;
 
@@ -1051,9 +1102,9 @@
 
     X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
 
-    if (!strcmp(crl_file, INLINE_FILE_TAG) && crl_inline)
+    if (crl_inline)
     {
-        in = BIO_new_mem_buf((char *)crl_inline, -1);
+        in = BIO_new_mem_buf((char *) crl_file, -1);
     }
     else
     {
@@ -1062,7 +1113,8 @@
 
     if (in == NULL)
     {
-        msg(M_WARN, "CRL: cannot read: %s", crl_file);
+        msg(M_WARN, "CRL: cannot read: %s",
+            print_key_filename(crl_file, crl_inline));
         goto end;
     }
 
@@ -1084,14 +1136,16 @@
                 break;
             }
 
-            crypto_msg(M_WARN, "CRL: cannot read CRL from file %s", crl_file);
+            crypto_msg(M_WARN, "CRL: cannot read CRL from file %s",
+                       print_key_filename(crl_file, crl_inline));
             break;
         }
 
         if (!X509_STORE_add_crl(store, crl))
         {
             X509_CRL_free(crl);
-            crypto_msg(M_WARN, "CRL: cannot add %s to store", crl_file);
+            crypto_msg(M_WARN, "CRL: cannot add %s to store",
+                       print_key_filename(crl_file, crl_inline));
             break;
         }
         X509_CRL_free(crl);
@@ -1103,7 +1157,7 @@
 }
 
 
-#ifdef MANAGMENT_EXTERNAL_KEY
+#ifdef ENABLE_MANAGEMENT
 
 /* encrypt */
 static int
@@ -1133,7 +1187,7 @@
 static int
 openvpn_extkey_rsa_finish(RSA *rsa)
 {
-    /* meth was allocated in tls_ctx_use_external_private_key() ; since
+    /* meth was allocated in tls_ctx_use_management_external_key() ; since
      * this function is called when the parent RSA object is destroyed,
      * it is no longer used after this point so kill it. */
     const RSA_METHOD *meth = RSA_get_method(rsa);
@@ -1141,74 +1195,93 @@
     return 1;
 }
 
-/* sign arbitrary data */
-static int
-rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
+/*
+ * Convert OpenSSL's constant to the strings used in the management
+ * interface query
+ */
+const char *
+get_rsa_padding_name(const int padding)
 {
-    /* optional app data in rsa->meth->app_data; */
+    switch (padding)
+    {
+        case RSA_PKCS1_PADDING:
+            return "RSA_PKCS1_PADDING";
+
+        case RSA_NO_PADDING:
+            return "RSA_NO_PADDING";
+
+        default:
+            return "UNKNOWN";
+    }
+}
+
+/**
+ * Pass the input hash in 'dgst' to management and get the signature back.
+ *
+ * @param dgst          hash to be signed
+ * @param dgstlen       len of data in dgst
+ * @param sig           On successful return signature is in sig.
+ * @param siglen        length of buffer sig
+ * @param algorithm     padding/hashing algorithm for the signature
+ *
+ * @return              signature length or -1 on error.
+ */
+static int
+get_sig_from_man(const unsigned char *dgst, unsigned int dgstlen,
+                 unsigned char *sig, unsigned int siglen,
+                 const char *algorithm)
+{
     char *in_b64 = NULL;
     char *out_b64 = NULL;
-    int ret = -1;
-    int len;
+    int len = -1;
 
-    if (padding != RSA_PKCS1_PADDING)
-    {
-        RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
-        goto done;
-    }
+    int bencret = openvpn_base64_encode(dgst, dgstlen, &in_b64);
 
-    /* convert 'from' to base64 */
-    if (openvpn_base64_encode(from, flen, &in_b64) <= 0)
+    if (management && bencret > 0)
     {
-        goto done;
-    }
+        out_b64 = management_query_pk_sig(management, in_b64, algorithm);
 
-    /* call MI for signature */
-    if (management)
-    {
-        out_b64 = management_query_rsa_sig(management, in_b64);
-    }
-    if (!out_b64)
-    {
-        goto done;
-    }
-
-    /* decode base64 signature to binary */
-    len = RSA_size(rsa);
-    ret = openvpn_base64_decode(out_b64, to, len);
-
-    /* verify length */
-    if (ret != len)
-    {
-        ret = -1;
-    }
-
-done:
-    if (in_b64)
-    {
-        free(in_b64);
     }
     if (out_b64)
     {
-        free(out_b64);
+        len = openvpn_base64_decode(out_b64, sig, siglen);
     }
-    return ret;
+
+    free(in_b64);
+    free(out_b64);
+    return len;
 }
 
-int
-tls_ctx_use_external_private_key(struct tls_root_ctx *ctx,
-                                 const char *cert_file, const char *cert_file_inline)
+/* sign arbitrary data */
+static int
+rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,
+             int padding)
+{
+    unsigned int len = RSA_size(rsa);
+    int ret = -1;
+
+    if (padding != RSA_PKCS1_PADDING && padding != RSA_NO_PADDING)
+    {
+        RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
+        return -1;
+    }
+
+    ret = get_sig_from_man(from, flen, to, len, get_rsa_padding_name(padding));
+
+    return (ret == len) ? ret : -1;
+}
+
+static int
+tls_ctx_use_external_rsa_key(struct tls_root_ctx *ctx, EVP_PKEY *pkey)
 {
     RSA *rsa = NULL;
     RSA *pub_rsa;
     RSA_METHOD *rsa_meth;
-    X509 *cert = NULL;
 
     ASSERT(NULL != ctx);
 
-    tls_ctx_load_cert_file_and_copy(ctx, cert_file, cert_file_inline, &cert);
-
-    ASSERT(NULL != cert);
+    pub_rsa = EVP_PKEY_get0_RSA(pkey);
+    ASSERT(NULL != pub_rsa);
 
     /* allocate custom RSA method object */
     rsa_meth = RSA_meth_new("OpenVPN external private key RSA Method",
@@ -1230,18 +1303,6 @@
         goto err;
     }
 
-    /* get the public key */
-    EVP_PKEY *pkey = X509_get0_pubkey(cert);
-    ASSERT(pkey); /* NULL before SSL_CTX_use_certificate() is called */
-    pub_rsa = EVP_PKEY_get0_RSA(pkey);
-
-    /* Certificate might not be RSA but DSA or EC */
-    if (!pub_rsa)
-    {
-        crypto_msg(M_WARN, "management-external-key requires a RSA certificate");
-        goto err;
-    }
-
     /* initialize RSA object */
     const BIGNUM *n = NULL;
     const BIGNUM *e = NULL;
@@ -1250,8 +1311,10 @@
     RSA_set_flags(rsa, RSA_flags(rsa) | RSA_FLAG_EXT_PKEY);
     if (!RSA_set_method(rsa, rsa_meth))
     {
+        RSA_meth_free(rsa_meth);
         goto err;
     }
+    /* from this point rsa_meth will get freed with rsa */
 
     /* bind our custom RSA object to ssl_ctx */
     if (!SSL_CTX_use_RSAPrivateKey(ctx->ctx, rsa))
@@ -1259,15 +1322,10 @@
         goto err;
     }
 
-    X509_free(cert);
     RSA_free(rsa); /* doesn't necessarily free, just decrements refcount */
-    return 0;
+    return 1;
 
 err:
-    if (cert)
-    {
-        X509_free(cert);
-    }
     if (rsa)
     {
         RSA_free(rsa);
@@ -1279,11 +1337,195 @@
             RSA_meth_free(rsa_meth);
         }
     }
-    crypto_msg(M_FATAL, "Cannot enable SSL external private key capability");
+    return 0;
+}
+
+#if ((OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)) \
+    || LIBRESSL_VERSION_NUMBER > 0x2090000fL) \
+    && !defined(OPENSSL_NO_EC)
+
+/* called when EC_KEY is destroyed */
+static void
+openvpn_extkey_ec_finish(EC_KEY *ec)
+{
+    /* release the method structure */
+    const EC_KEY_METHOD *ec_meth = EC_KEY_get_method(ec);
+    EC_KEY_METHOD_free((EC_KEY_METHOD *) ec_meth);
+}
+
+/* EC_KEY_METHOD callback: sign().
+ * Sign the hash using EC key and return DER encoded signature in sig,
+ * its length in siglen. Return value is 1 on success, 0 on error.
+ */
+static int
+ecdsa_sign(int type, const unsigned char *dgst, int dgstlen, unsigned char *sig,
+           unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *ec)
+{
+    int capacity = ECDSA_size(ec);
+    /*
+     * ECDSA does not seem to have proper constants for paddings since
+     * there are only signatures without padding at the moment, use
+     * a generic ECDSA for the moment
+     */
+    int len = get_sig_from_man(dgst, dgstlen, sig, capacity, "ECDSA");
+
+    if (len > 0)
+    {
+        *siglen = len;
+        return 1;
+    }
+    return 0;
+}
+
+/* EC_KEY_METHOD callback: sign_setup(). We do no precomputations */
+static int
+ecdsa_sign_setup(EC_KEY *ec, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp)
+{
     return 1;
 }
 
-#endif /* ifdef MANAGMENT_EXTERNAL_KEY */
+/* EC_KEY_METHOD callback: sign_sig().
+ * Sign the hash and return the result as a newly allocated ECDS_SIG
+ * struct or NULL on error.
+ */
+static ECDSA_SIG *
+ecdsa_sign_sig(const unsigned char *dgst, int dgstlen, const BIGNUM *in_kinv,
+               const BIGNUM *in_r, EC_KEY *ec)
+{
+    ECDSA_SIG *ecsig = NULL;
+    unsigned int len = ECDSA_size(ec);
+    struct gc_arena gc = gc_new();
+
+    unsigned char *buf = gc_malloc(len, false, &gc);
+    if (ecdsa_sign(0, dgst, dgstlen, buf, &len, NULL, NULL, ec) != 1)
+    {
+        goto out;
+    }
+    /* const char ** should be avoided: not up to us, so we cast our way through */
+    ecsig = d2i_ECDSA_SIG(NULL, (const unsigned char **)&buf, len);
+
+out:
+    gc_free(&gc);
+    return ecsig;
+}
+
+static int
+tls_ctx_use_external_ec_key(struct tls_root_ctx *ctx, EVP_PKEY *pkey)
+{
+    EC_KEY *ec = NULL;
+    EVP_PKEY *privkey = NULL;
+    EC_KEY_METHOD *ec_method;
+
+    ASSERT(ctx);
+
+    ec_method = EC_KEY_METHOD_new(EC_KEY_OpenSSL());
+    if (!ec_method)
+    {
+        goto err;
+    }
+
+    /* Among init methods, we only need the finish method */
+    EC_KEY_METHOD_set_init(ec_method, NULL, openvpn_extkey_ec_finish, NULL, NULL, NULL, NULL);
+    EC_KEY_METHOD_set_sign(ec_method, ecdsa_sign, ecdsa_sign_setup, ecdsa_sign_sig);
+
+    ec = EC_KEY_dup(EVP_PKEY_get0_EC_KEY(pkey));
+    if (!ec)
+    {
+        EC_KEY_METHOD_free(ec_method);
+        goto err;
+    }
+    if (!EC_KEY_set_method(ec, ec_method))
+    {
+        EC_KEY_METHOD_free(ec_method);
+        goto err;
+    }
+    /* from this point ec_method will get freed when ec is freed */
+
+    privkey = EVP_PKEY_new();
+    if (!EVP_PKEY_assign_EC_KEY(privkey, ec))
+    {
+        goto err;
+    }
+    /* from this point ec will get freed when privkey is freed */
+
+    if (!SSL_CTX_use_PrivateKey(ctx->ctx, privkey))
+    {
+        ec = NULL; /* avoid double freeing it below */
+        goto err;
+    }
+
+    EVP_PKEY_free(privkey); /* this will down ref privkey and ec */
+    return 1;
+
+err:
+    /* Reach here only when ec and privkey can be independenly freed */
+    if (privkey)
+    {
+        EVP_PKEY_free(privkey);
+    }
+    if (ec)
+    {
+        EC_KEY_free(ec);
+    }
+    return 0;
+}
+#endif /* OPENSSL_VERSION_NUMBER > 1.1.0 dev && !defined(OPENSSL_NO_EC) */
+
+int
+tls_ctx_use_management_external_key(struct tls_root_ctx *ctx)
+{
+    int ret = 1;
+
+    ASSERT(NULL != ctx);
+
+    X509 *cert = SSL_CTX_get0_certificate(ctx->ctx);
+
+    ASSERT(NULL != cert);
+
+    /* get the public key */
+    EVP_PKEY *pkey = X509_get0_pubkey(cert);
+    ASSERT(pkey); /* NULL before SSL_CTX_use_certificate() is called */
+
+    if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA)
+    {
+        if (!tls_ctx_use_external_rsa_key(ctx, pkey))
+        {
+            goto cleanup;
+        }
+    }
+#if ((OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)) \
+    || LIBRESSL_VERSION_NUMBER > 0x2090000fL) \
+    && !defined(OPENSSL_NO_EC)
+    else if (EVP_PKEY_id(pkey) == EVP_PKEY_EC)
+    {
+        if (!tls_ctx_use_external_ec_key(ctx, pkey))
+        {
+            goto cleanup;
+        }
+    }
+    else
+    {
+        crypto_msg(M_WARN, "management-external-key requires an RSA or EC certificate");
+        goto cleanup;
+    }
+#else  /* OPENSSL_VERSION_NUMBER > 1.1.0 dev && !defined(OPENSSL_NO_EC) */
+    else
+    {
+        crypto_msg(M_WARN, "management-external-key requires an RSA certificate");
+        goto cleanup;
+    }
+#endif /* OPENSSL_VERSION_NUMBER > 1.1.0 dev && !defined(OPENSSL_NO_EC) */
+
+    ret = 0;
+cleanup:
+    if (ret)
+    {
+        crypto_msg(M_FATAL, "Cannot enable SSL external private key capability");
+    }
+    return ret;
+}
+
+#endif /* ifdef ENABLE_MANAGEMENT */
 
 static int
 sk_x509_name_cmp(const X509_NAME *const *a, const X509_NAME *const *b)
@@ -1293,9 +1535,7 @@
 
 void
 tls_ctx_load_ca(struct tls_root_ctx *ctx, const char *ca_file,
-                const char *ca_file_inline,
-                const char *ca_path, bool tls_server
-                )
+                bool ca_file_inline, const char *ca_path, bool tls_server)
 {
     STACK_OF(X509_INFO) *info_stack = NULL;
     STACK_OF(X509_NAME) *cert_names = NULL;
@@ -1316,9 +1556,9 @@
     /* Try to add certificates and CRLs from ca_file */
     if (ca_file)
     {
-        if (!strcmp(ca_file, INLINE_FILE_TAG) && ca_file_inline)
+        if (ca_file_inline)
         {
-            in = BIO_new_mem_buf((char *)ca_file_inline, -1);
+            in = BIO_new_mem_buf((char *)ca_file, -1);
         }
         else
         {
@@ -1390,11 +1630,11 @@
                     {
                         crypto_msg(M_WARN,
                                    "Cannot load CA certificate file %s (entry %d did not validate)",
-                                   np(ca_file), added);
+                                   print_key_filename(ca_file, ca_file_inline),
+                                   added);
                     }
                     prev = cnum;
                 }
-
             }
             sk_X509_INFO_pop_free(info_stack, X509_INFO_free);
         }
@@ -1408,7 +1648,7 @@
         {
             crypto_msg(M_FATAL,
                        "Cannot load CA certificate file %s (no entries were read)",
-                       np(ca_file));
+                       print_key_filename(ca_file, ca_file_inline));
         }
 
         if (tls_server)
@@ -1418,7 +1658,8 @@
             {
                 crypto_msg(M_FATAL, "Cannot load CA certificate file %s (only %d "
                            "of %d entries were valid X509 names)",
-                           np(ca_file), cnum, added);
+                           print_key_filename(ca_file, ca_file_inline), cnum,
+                           added);
             }
         }
 
@@ -1446,13 +1687,12 @@
 
 void
 tls_ctx_load_extra_certs(struct tls_root_ctx *ctx, const char *extra_certs_file,
-                         const char *extra_certs_file_inline
-                         )
+                         bool extra_certs_file_inline)
 {
     BIO *in;
-    if (!strcmp(extra_certs_file, INLINE_FILE_TAG) && extra_certs_file_inline)
+    if (extra_certs_file_inline)
     {
-        in = BIO_new_mem_buf((char *)extra_certs_file_inline, -1);
+        in = BIO_new_mem_buf((char *)extra_certs_file, -1);
     }
     else
     {
@@ -1461,7 +1701,10 @@
 
     if (in == NULL)
     {
-        crypto_msg(M_FATAL, "Cannot load extra-certs file: %s", extra_certs_file);
+        crypto_msg(M_FATAL, "Cannot load extra-certs file: %s",
+                   print_key_filename(extra_certs_file,
+                                      extra_certs_file_inline));
+
     }
     else
     {
@@ -1529,8 +1772,8 @@
     if (len > 0)
     {
         open_biofp();
-        fprintf(biofp, "BIO_%s %s time=" time_format " bio=" ptr_format " len=%d data=%s\n",
-                mode, desc, time(NULL), (ptr_type)bio, len, format_hex(buf, len, 0, &gc));
+        fprintf(biofp, "BIO_%s %s time=%" PRIi64 " bio=" ptr_format " len=%d data=%s\n",
+                mode, desc, (int64_t)time(NULL), (ptr_type)bio, len, format_hex(buf, len, 0, &gc));
         fflush(biofp);
     }
     gc_free(&gc);
@@ -1540,8 +1783,8 @@
 bio_debug_oc(const char *mode, BIO *bio)
 {
     open_biofp();
-    fprintf(biofp, "BIO %s time=" time_format " bio=" ptr_format "\n",
-            mode, time(NULL), (ptr_type)bio);
+    fprintf(biofp, "BIO %s time=%" PRIi64 " bio=" ptr_format "\n",
+            mode, (int64_t)time(NULL), (ptr_type)bio);
     fflush(biofp);
 }
 
@@ -1803,6 +2046,80 @@
     return ret;
 }
 
+/**
+ * Print human readable information about the certifcate into buf
+ * @param cert      the certificate being used
+ * @param buf       output buffer
+ * @param buflen    output buffer length
+ */
+static void
+print_cert_details(X509 *cert, char *buf, size_t buflen)
+{
+    const char *curve = "";
+    const char *type = "(error getting type)";
+    EVP_PKEY *pkey = X509_get_pubkey(cert);
+
+    if (pkey == NULL)
+    {
+        buf[0] = 0;
+        return;
+    }
+
+    int typeid = EVP_PKEY_id(pkey);
+
+#ifndef OPENSSL_NO_EC
+    if (typeid == EVP_PKEY_EC && EVP_PKEY_get0_EC_KEY(pkey) != NULL)
+    {
+        EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey);
+        const EC_GROUP *group = EC_KEY_get0_group(ec);
+
+        int nid = EC_GROUP_get_curve_name(group);
+        if (nid == 0 || (curve = OBJ_nid2sn(nid)) == NULL)
+        {
+            curve = "(error getting curve name)";
+        }
+    }
+#endif
+    if (EVP_PKEY_id(pkey) != 0)
+    {
+        int typeid = EVP_PKEY_id(pkey);
+        type = OBJ_nid2sn(typeid);
+
+        /* OpenSSL reports rsaEncryption, dsaEncryption and
+        * id-ecPublicKey, map these values to nicer ones */
+        if (typeid == EVP_PKEY_RSA)
+        {
+            type = "RSA";
+        }
+        else if (typeid == EVP_PKEY_DSA)
+        {
+            type = "DSA";
+        }
+        else if (typeid == EVP_PKEY_EC)
+        {
+            /* EC gets the curve appended after the type */
+            type = "EC, curve ";
+        }
+        else if (type == NULL)
+        {
+            type = "unknown type";
+        }
+    }
+
+    char sig[128] = { 0 };
+    int signature_nid = X509_get_signature_nid(cert);
+    if (signature_nid != 0)
+    {
+        openvpn_snprintf(sig, sizeof(sig), ", signature: %s",
+                         OBJ_nid2sn(signature_nid));
+    }
+
+    openvpn_snprintf(buf, buflen, ", peer certificate: %d bit %s%s%s",
+                     EVP_PKEY_bits(pkey), type, curve, sig);
+
+    EVP_PKEY_free(pkey);
+}
+
 /* **************************************
  *
  * Information functions
@@ -1814,7 +2131,6 @@
 print_details(struct key_state_ssl *ks_ssl, const char *prefix)
 {
     const SSL_CIPHER *ciph;
-    X509 *cert;
     char s1[256];
     char s2[256];
 
@@ -1825,55 +2141,20 @@
                      SSL_get_version(ks_ssl->ssl),
                      SSL_CIPHER_get_version(ciph),
                      SSL_CIPHER_get_name(ciph));
-    cert = SSL_get_peer_certificate(ks_ssl->ssl);
-    if (cert != NULL)
+    X509 *cert = SSL_get_peer_certificate(ks_ssl->ssl);
+
+    if (cert)
     {
-        EVP_PKEY *pkey = X509_get_pubkey(cert);
-        if (pkey != NULL)
-        {
-            if ((EVP_PKEY_id(pkey) == EVP_PKEY_RSA) && (EVP_PKEY_get0_RSA(pkey) != NULL))
-            {
-                RSA *rsa = EVP_PKEY_get0_RSA(pkey);
-                openvpn_snprintf(s2, sizeof(s2), ", %d bit RSA",
-                                 RSA_bits(rsa));
-            }
-            else if ((EVP_PKEY_id(pkey) == EVP_PKEY_DSA) && (EVP_PKEY_get0_DSA(pkey) != NULL))
-            {
-                DSA *dsa = EVP_PKEY_get0_DSA(pkey);
-                openvpn_snprintf(s2, sizeof(s2), ", %d bit DSA",
-                                 DSA_bits(dsa));
-            }
-#ifndef OPENSSL_NO_EC
-            else if ((EVP_PKEY_id(pkey) == EVP_PKEY_EC) && (EVP_PKEY_get0_EC_KEY(pkey) != NULL))
-            {
-                EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey);
-                const EC_GROUP *group = EC_KEY_get0_group(ec);
-                const char* curve;
-
-                int nid = EC_GROUP_get_curve_name(group);
-                if (nid == 0 || (curve = OBJ_nid2sn(nid)) == NULL)
-                {
-                    curve = "Error getting curve name";
-                }
-
-                openvpn_snprintf(s2, sizeof(s2), ", %d bit EC, curve: %s",
-                                 EC_GROUP_order_bits(group), curve);
-
-            }
-#endif
-            EVP_PKEY_free(pkey);
-        }
+        print_cert_details(cert, s2, sizeof(s2));
         X509_free(cert);
     }
-    /* The SSL API does not allow us to look at temporary RSA/DH keys,
-     * otherwise we should print their lengths too */
     msg(D_HANDSHAKE, "%s%s", s1, s2);
 }
 
 void
 show_available_tls_ciphers_list(const char *cipher_list,
                                 const char *tls_cert_profile,
-                                const bool tls13)
+                                bool tls13)
 {
     struct tls_root_ctx tls_ctx;
 
@@ -1883,10 +2164,11 @@
         crypto_msg(M_FATAL, "Cannot create SSL_CTX object");
     }
 
-#if (OPENSSL_VERSION_NUMBER >= 0x1010100fL) && defined(TLS1_3_VERSION) && !defined(OPENSSL_NO_TLS1_3)
+#if defined(TLS1_3_VERSION)
     if (tls13)
     {
-        SSL_CTX_set_min_proto_version(tls_ctx.ctx, TLS1_3_VERSION);
+        SSL_CTX_set_min_proto_version(tls_ctx.ctx,
+                                      openssl_tls_version(TLS_VER_1_3));
         tls_ctx_restrict_ciphers_tls13(&tls_ctx, cipher_list);
     }
     else
@@ -1904,12 +2186,13 @@
         crypto_msg(M_FATAL, "Cannot create SSL object");
     }
 
-#if (OPENSSL_VERSION_NUMBER < 0x1010000fL)
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL)    \
+    || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER <= 0x2090000fL)
     STACK_OF(SSL_CIPHER) *sk = SSL_get_ciphers(ssl);
 #else
     STACK_OF(SSL_CIPHER) *sk = SSL_get1_supported_ciphers(ssl);
 #endif
-    for (int i=0;i < sk_SSL_CIPHER_num(sk);i++)
+    for (int i = 0; i < sk_SSL_CIPHER_num(sk); i++)
     {
         const SSL_CIPHER *c = sk_SSL_CIPHER_value(sk, i);
 
@@ -1920,7 +2203,7 @@
 
         if (tls13)
         {
-              printf("%s\n", cipher_name);
+            printf("%s\n", cipher_name);
         }
         else if (NULL == pair)
         {
@@ -1947,6 +2230,8 @@
 void
 show_available_curves(void)
 {
+    printf("Consider using openssl 'ecparam -list_curves' as\n"
+           "alternative to running this command.\n");
 #ifndef OPENSSL_NO_EC
     EC_builtin_curve *curves = NULL;
     size_t crv_len = 0;
@@ -1956,7 +2241,7 @@
     ALLOC_ARRAY(curves, EC_builtin_curve, crv_len);
     if (EC_get_builtin_curves(curves, crv_len))
     {
-        printf("Available Elliptic curves:\n");
+        printf("\nAvailable Elliptic curves/groups:\n");
         for (n = 0; n < crv_len; n++)
         {
             const char *sname;
@@ -2008,7 +2293,7 @@
 const char *
 get_ssl_library_version(void)
 {
-    return SSLeay_version(SSLEAY_VERSION);
+    return OpenSSL_version(OPENSSL_VERSION);
 }
 
-#endif /* defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_OPENSSL) */
+#endif /* defined(ENABLE_CRYPTO_OPENSSL) */
diff --git a/src/openvpn/ssl_openssl.h b/src/openvpn/ssl_openssl.h
index dabb941..835878c 100644
--- a/src/openvpn/ssl_openssl.h
+++ b/src/openvpn/ssl_openssl.h
@@ -32,17 +32,6 @@
 #include <openssl/ssl.h>
 
 /**
- * SSL_OP_NO_TICKET tells OpenSSL to disable "stateless session resumption",
- * as this is something we do not want nor need, but could potentially be
- * used for a future attack.  For compatibility reasons we keep building if the
- * OpenSSL version is too old (pre-0.9.8f) to support stateless session
- * resumption (and the accompanying SSL_OP_NO_TICKET flag).
- */
-#ifndef SSL_OP_NO_TICKET
-#define SSL_OP_NO_TICKET 0
-#endif
-
-/**
  * Structure that wraps the TLS context. Contents differ depending on the
  * SSL library used.
  */
diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c
index c7e595e..55e7fed 100644
--- a/src/openvpn/ssl_verify.c
+++ b/src/openvpn/ssl_verify.c
@@ -34,40 +34,26 @@
 
 #include "syshead.h"
 
-#ifdef ENABLE_CRYPTO
-
-#include "misc.h"
+#include "base64.h"
 #include "manage.h"
 #include "otime.h"
-#include "base64.h"
+#include "run_command.h"
 #include "ssl_verify.h"
 #include "ssl_verify_backend.h"
 
 #ifdef ENABLE_CRYPTO_OPENSSL
 #include "ssl_verify_openssl.h"
 #endif
+#include "auth_token.h"
+#include "push.h"
 
 /** Maximum length of common name */
 #define TLS_USERNAME_LEN 64
 
-/** Legal characters in an X509 name with --compat-names */
-#define X509_NAME_CHAR_CLASS   (CC_ALNUM|CC_UNDERBAR|CC_DASH|CC_DOT|CC_AT|CC_SLASH|CC_COLON|CC_EQUAL)
-
-/** Legal characters in a common name with --compat-names */
-#define COMMON_NAME_CHAR_CLASS (CC_ALNUM|CC_UNDERBAR|CC_DASH|CC_DOT|CC_AT|CC_SLASH)
-
 static void
-string_mod_remap_name(char *str, const unsigned int restrictive_flags)
+string_mod_remap_name(char *str)
 {
-    if (compat_flag(COMPAT_FLAG_QUERY | COMPAT_NAMES)
-        && !compat_flag(COMPAT_FLAG_QUERY | COMPAT_NO_NAME_REMAPPING))
-    {
-        string_mod(str, restrictive_flags, 0, '_');
-    }
-    else
-    {
-        string_mod(str, CC_PRINT, CC_CRLF, '_');
-    }
+    string_mod(str, CC_PRINT, CC_CRLF, '_');
 }
 
 /*
@@ -79,28 +65,6 @@
     setenv_link_socket_actual(session->opt->es, "untrusted", &session->untrusted_addr, SA_IP_PORT);
 }
 
-
-/**
- *  Wipes the authentication token out of the memory, frees and cleans up related buffers and flags
- *
- *  @param multi  Pointer to a multi object holding the auth_token variables
- */
-static void
-wipe_auth_token(struct tls_multi *multi)
-{
-    if(multi)
-    {
-        if (multi->auth_token)
-        {
-            secure_memzero(multi->auth_token, AUTH_TOKEN_SIZE);
-            free(multi->auth_token);
-        }
-        multi->auth_token = NULL;
-        multi->auth_token_sent = false;
-    }
-}
-
-
 /*
  * Remove authenticated state from all sessions in the given tunnel
  */
@@ -114,7 +78,7 @@
         {
             for (int j = 0; j < KS_SIZE; ++j)
             {
-                multi->session[i].key[j].authenticated = false;
+                multi->session[i].key[j].authenticated = KS_AUTH_FALSE;
             }
         }
     }
@@ -524,7 +488,7 @@
 
         ret = plugin_call_ssl(plugins, OPENVPN_PLUGIN_TLS_VERIFY, &argv, NULL, es, cert_depth, cert);
 
-        argv_reset(&argv);
+        argv_free(&argv);
 
         if (ret == OPENVPN_PLUGIN_FUNC_SUCCESS)
         {
@@ -549,9 +513,9 @@
 
     /* create tmp file to store peer cert */
     if (!tmp_dir
-        || !(peercert_filename = create_temp_file(tmp_dir, "pcf", gc)))
+        || !(peercert_filename = platform_create_temp_file(tmp_dir, "pcf", gc)))
     {
-        msg (M_WARN, "Failed to create peer cert file");
+        msg(M_NONFATAL, "Failed to create peer cert file");
         return NULL;
     }
 
@@ -559,13 +523,16 @@
     peercert_file = fopen(peercert_filename, "w+");
     if (!peercert_file)
     {
-        msg(M_ERR, "Failed to open temporary file : %s", peercert_filename);
+        msg(M_NONFATAL|M_ERRNO, "Failed to open temporary file: %s",
+            peercert_filename);
         return NULL;
     }
 
     if (SUCCESS != x509_write_pem(peercert_file, peercert))
     {
-        msg(M_ERR, "Error writing PEM file containing certificate");
+        msg(M_NONFATAL, "Error writing PEM file containing certificate");
+        (void) platform_unlink(peercert_filename);
+        peercert_filename = NULL;
     }
 
     fclose(peercert_file);
@@ -614,7 +581,7 @@
 
 cleanup:
     gc_free(&gc);
-    argv_reset(&argv);
+    argv_free(&argv);
 
     if (ret)
     {
@@ -632,7 +599,8 @@
  * check peer cert against CRL directory
  */
 static result_t
-verify_check_crl_dir(const char *crl_dir, openvpn_x509_cert_t *cert)
+verify_check_crl_dir(const char *crl_dir, openvpn_x509_cert_t *cert,
+                     const char *subject, int cert_depth)
 {
     result_t ret = FAILURE;
     char fn[256];
@@ -640,6 +608,12 @@
     struct gc_arena gc = gc_new();
 
     char *serial = backend_x509_get_serial(cert, &gc);
+    if (!serial)
+    {
+        msg(D_HANDSHAKE, "VERIFY CRL: depth=%d, %s, serial number is not available",
+            cert_depth, subject);
+        goto cleanup;
+    }
 
     if (!openvpn_snprintf(fn, sizeof(fn), "%s%c%s", crl_dir, OS_SPECIFIC_DIRSEP, serial))
     {
@@ -649,7 +623,8 @@
     fd = platform_open(fn, O_RDONLY, 0);
     if (fd >= 0)
     {
-        msg(D_HANDSHAKE, "VERIFY CRL: certificate serial number %s is revoked", serial);
+        msg(D_HANDSHAKE, "VERIFY CRL: depth=%d, %s, serial=%s is revoked",
+            cert_depth, subject, serial);
         goto cleanup;
     }
 
@@ -689,7 +664,7 @@
     }
 
     /* enforce character class restrictions in X509 name */
-    string_mod_remap_name(subject, X509_NAME_CHAR_CLASS);
+    string_mod_remap_name(subject);
     string_replace_leading(subject, '-', '_');
 
     /* extract the username (default is CN) */
@@ -709,7 +684,7 @@
     }
 
     /* enforce character class restrictions in common name */
-    string_mod_remap_name(common_name, COMMON_NAME_CHAR_CLASS);
+    string_mod_remap_name(common_name);
 
     /* warn if cert chain is too deep */
     if (cert_depth >= MAX_CERT_DEPTH)
@@ -725,24 +700,24 @@
 
         switch (opt->verify_hash_algo)
         {
-        case MD_SHA1:
-            ca_hash = x509_get_sha1_fingerprint(cert, &gc);
-            break;
+            case MD_SHA1:
+                ca_hash = x509_get_sha1_fingerprint(cert, &gc);
+                break;
 
-        case MD_SHA256:
-            ca_hash = x509_get_sha256_fingerprint(cert, &gc);
-            break;
+            case MD_SHA256:
+                ca_hash = x509_get_sha256_fingerprint(cert, &gc);
+                break;
 
-        default:
-            /* This should normally not happen at all; the algorithm used
-             * is parsed by add_option() [options.c] and set to a predefined
-             * value in an enumerated type.  So if this unlikely scenario
-             * happens, consider this a failure
-             */
-            msg(M_WARN, "Unexpected invalid algorithm used with "
-                "--verify-hash (%i)", opt->verify_hash_algo);
-            ret = FAILURE;
-            goto cleanup;
+            default:
+                /* This should normally not happen at all; the algorithm used
+                 * is parsed by add_option() [options.c] and set to a predefined
+                 * value in an enumerated type.  So if this unlikely scenario
+                 * happens, consider this a failure
+                 */
+                msg(M_WARN, "Unexpected invalid algorithm used with "
+                    "--verify-hash (%i)", opt->verify_hash_algo);
+                ret = FAILURE;
+                goto cleanup;
         }
 
         if (memcmp(BPTR(&ca_hash), opt->verify_hash, BLEN(&ca_hash)))
@@ -791,7 +766,7 @@
     {
         if (opt->ssl_flags & SSLF_CRL_VERIFY_DIR)
         {
-            if (SUCCESS != verify_check_crl_dir(opt->crl_file, cert))
+            if (SUCCESS != verify_check_crl_dir(opt->crl_file, cert, subject, cert_depth))
             {
                 goto cleanup;
             }
@@ -836,9 +811,8 @@
 #define ACF_FAILED    3
 #endif
 
-#ifdef MANAGEMENT_DEF_AUTH
 void
-man_def_auth_set_client_reason(struct tls_multi *multi, const char *client_reason)
+auth_set_client_reason(struct tls_multi *multi, const char *client_reason)
 {
     if (multi->client_reason)
     {
@@ -847,11 +821,12 @@
     }
     if (client_reason && strlen(client_reason))
     {
-        /* FIXME: Last alloc will never be freed */
         multi->client_reason = string_alloc(client_reason, NULL);
     }
 }
 
+#ifdef MANAGEMENT_DEF_AUTH
+
 static inline unsigned int
 man_def_auth_test(const struct key_state *ks)
 {
@@ -889,7 +864,7 @@
     struct gc_arena gc = gc_new();
 
     key_state_rm_auth_control_file(ks);
-    const char *acf = create_temp_file(opt->tmp_dir, "acf", &gc);
+    const char *acf = platform_create_temp_file(opt->tmp_dir, "acf", &gc);
     if (acf)
     {
         ks->auth_control_file = string_alloc(acf, NULL);
@@ -931,6 +906,39 @@
 
 #endif /* ifdef PLUGIN_DEF_AUTH */
 
+/* This function is called when a session's primary key state first becomes KS_TRUE */
+void ssl_session_fully_authenticated(struct tls_multi *multi, struct tls_session* session)
+{
+    struct key_state *ks = &session->key[KS_PRIMARY];
+    if (ks->key_id == 0)
+    {
+        /* A key id of 0 indicates a new session and the client will
+         * get the auth-token as part of the initial push reply */
+        return;
+    }
+
+    /*
+     * Auth token already sent to client, update auth-token on client.
+     * The initial auth-token is sent as part of the push message, for this
+     * update we need to schedule an extra push message.
+     *
+     * Otherwise the auth-token get pushed out as part of the "normal"
+     * push-reply
+     */
+    if (multi->auth_token_initial)
+    {
+        /*
+         * We do not explicitly schedule the sending of the
+         * control message here but control message are only
+         * postponed when the control channel  is not yet fully
+         * established and furthermore since this is called in
+         * the middle of authentication, there are other messages
+         * (new data channel keys) that are sent anyway and will
+         * trigger scheduling
+         */
+        send_push_reply_auth_token(multi);
+    }
+}
 /*
  * Return current session authentication state.  Return
  * value is TLS_AUTHENTICATION_x.
@@ -983,7 +991,7 @@
             if (DECRYPT_KEY_ENABLED(multi, ks))
             {
                 active = true;
-                if (ks->authenticated)
+                if (ks->authenticated > KS_AUTH_FALSE)
                 {
 #ifdef ENABLE_DEF_AUTH
                     unsigned int s1 = ACF_DISABLED;
@@ -1000,7 +1008,13 @@
                         case ACF_SUCCEEDED:
                         case ACF_DISABLED:
                             success = true;
-                            ks->auth_deferred = false;
+                            /* i=0 is the TM_ACTIVE/KS_PRIMARY session */
+                            if (i == 0 && ks->authenticated == KS_AUTH_DEFERRED)
+                            {
+                                ssl_session_fully_authenticated(multi,
+                                                                &multi->session[TM_ACTIVE]);
+                            }
+                            ks->authenticated = KS_AUTH_TRUE;
                             break;
 
                         case ACF_UNDEFINED:
@@ -1011,7 +1025,7 @@
                             break;
 
                         case ACF_FAILED:
-                            ks->authenticated = false;
+                            ks->authenticated = KS_AUTH_FALSE;
                             break;
 
                         default:
@@ -1055,7 +1069,7 @@
     if (multi)
     {
         int i;
-        man_def_auth_set_client_reason(multi, client_reason);
+        auth_set_client_reason(multi, client_reason);
         for (i = 0; i < KEY_SCAN_SIZE; ++i)
         {
             struct key_state *ks = multi->key_scan[i];
@@ -1085,79 +1099,66 @@
  * Verify the user name and password using a script
  */
 static bool
-verify_user_pass_script(struct tls_session *session, const struct user_pass *up)
+verify_user_pass_script(struct tls_session *session, struct tls_multi *multi,
+                        const struct user_pass *up)
 {
     struct gc_arena gc = gc_new();
     struct argv argv = argv_new();
     const char *tmp_file = "";
     bool ret = false;
 
-    /* Is username defined? */
-    if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen(up->username))
+    /* Set environmental variables prior to calling script */
+    setenv_str(session->opt->es, "script_type", "user-pass-verify");
+
+    /* format command line */
+    argv_parse_cmd(&argv, session->opt->auth_user_pass_verify_script);
+
+    if (session->opt->auth_user_pass_verify_script_via_file)
     {
-        /* Set environmental variables prior to calling script */
-        setenv_str(session->opt->es, "script_type", "user-pass-verify");
+        struct status_output *so;
 
-        if (session->opt->auth_user_pass_verify_script_via_file)
+        tmp_file = platform_create_temp_file(session->opt->tmp_dir, "up",
+                                             &gc);
+        if (tmp_file)
         {
-            struct status_output *so;
-
-            tmp_file = create_temp_file(session->opt->tmp_dir, "up", &gc);
-            if (tmp_file)
+            so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
+            status_printf(so, "%s", up->username);
+            status_printf(so, "%s", up->password);
+            if (!status_close(so))
             {
-                so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
-                status_printf(so, "%s", up->username);
-                status_printf(so, "%s", up->password);
-                if (!status_close(so))
-                {
-                    msg(D_TLS_ERRORS, "TLS Auth Error: could not write username/password to file: %s",
-                        tmp_file);
-                    goto done;
-                }
+                msg(D_TLS_ERRORS, "TLS Auth Error: could not write username/password to file: %s",
+                    tmp_file);
+                goto done;
             }
-            else
-            {
-                msg(D_TLS_ERRORS, "TLS Auth Error: could not create write "
-                    "username/password to temp file");
-            }
+            /* pass temp file name to script */
+            argv_printf_cat(&argv, "%s", tmp_file);
         }
         else
         {
-            setenv_str(session->opt->es, "username", up->username);
-            setenv_str(session->opt->es, "password", up->password);
-        }
-
-        /* setenv incoming cert common name for script */
-        setenv_str(session->opt->es, "common_name", session->common_name);
-
-        /* setenv client real IP address */
-        setenv_untrusted(session);
-
-        /* format command line */
-        argv_parse_cmd(&argv, session->opt->auth_user_pass_verify_script);
-        argv_printf_cat(&argv, "%s", tmp_file);
-
-        /* call command */
-        ret = openvpn_run_script(&argv, session->opt->es, 0,
-                                 "--auth-user-pass-verify");
-
-        if (!session->opt->auth_user_pass_verify_script_via_file)
-        {
-            setenv_del(session->opt->es, "password");
+            msg(D_TLS_ERRORS, "TLS Auth Error: could not create write "
+                "username/password to temp file");
         }
     }
     else
     {
-        msg(D_TLS_ERRORS, "TLS Auth Error: peer provided a blank username");
+        setenv_str(session->opt->es, "password", up->password);
     }
 
+    /* call command */
+    ret = openvpn_run_script(&argv, session->opt->es, 0,
+                             "--auth-user-pass-verify");
+
+    if (!session->opt->auth_user_pass_verify_script_via_file)
+    {
+        setenv_del(session->opt->es, "password");
+    }
 done:
     if (tmp_file && strlen(tmp_file) > 0)
     {
         platform_unlink(tmp_file);
     }
 
-    argv_reset(&argv);
+    argv_free(&argv);
     gc_free(&gc);
     return ret;
 }
@@ -1166,59 +1167,40 @@
  * Verify the username and password using a plugin
  */
 static int
-verify_user_pass_plugin(struct tls_session *session, const struct user_pass *up, const char *raw_username)
+verify_user_pass_plugin(struct tls_session *session, struct tls_multi *multi,
+                        const struct user_pass *up)
 {
     int retval = OPENVPN_PLUGIN_FUNC_ERROR;
 #ifdef PLUGIN_DEF_AUTH
     struct key_state *ks = &session->key[KS_PRIMARY];      /* primary key */
 #endif
 
-    /* Is username defined? */
-    if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen(up->username))
-    {
-        /* set username/password in private env space */
-        setenv_str(session->opt->es, "username", (raw_username ? raw_username : up->username));
-        setenv_str(session->opt->es, "password", up->password);
-
-        /* setenv incoming cert common name for script */
-        setenv_str(session->opt->es, "common_name", session->common_name);
-
-        /* setenv client real IP address */
-        setenv_untrusted(session);
+    /* set password in private env space */
+    setenv_str(session->opt->es, "password", up->password);
 
 #ifdef PLUGIN_DEF_AUTH
-        /* generate filename for deferred auth control file */
-        if (!key_state_gen_auth_control_file(ks, session->opt))
-        {
-            msg (D_TLS_ERRORS, "TLS Auth Error (%s): "
-                 "could not create deferred auth control file", __func__);
-            goto cleanup;
-        }
+    /* generate filename for deferred auth control file */
+    if (!key_state_gen_auth_control_file(ks, session->opt))
+    {
+        msg(D_TLS_ERRORS, "TLS Auth Error (%s): "
+            "could not create deferred auth control file", __func__);
+        return retval;
+    }
 #endif
 
-        /* call command */
-        retval = plugin_call(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, NULL, NULL, session->opt->es);
+    /* call command */
+    retval = plugin_call(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, NULL, NULL, session->opt->es);
 
 #ifdef PLUGIN_DEF_AUTH
-        /* purge auth control filename (and file itself) for non-deferred returns */
-        if (retval != OPENVPN_PLUGIN_FUNC_DEFERRED)
-        {
-            key_state_rm_auth_control_file(ks);
-        }
+    /* purge auth control filename (and file itself) for non-deferred returns */
+    if (retval != OPENVPN_PLUGIN_FUNC_DEFERRED)
+    {
+        key_state_rm_auth_control_file(ks);
+    }
 #endif
 
-        setenv_del(session->opt->es, "password");
-        if (raw_username)
-        {
-            setenv_str(session->opt->es, "username", up->username);
-        }
-    }
-    else
-    {
-        msg(D_TLS_ERRORS, "TLS Auth Error (verify_user_pass_plugin): peer provided a blank username");
-    }
+    setenv_del(session->opt->es, "password");
 
-cleanup:
     return retval;
 }
 
@@ -1233,17 +1215,37 @@
 #define KMDA_DEF     3
 
 static int
-verify_user_pass_management(struct tls_session *session, const struct user_pass *up, const char *raw_username)
+verify_user_pass_management(struct tls_session *session,
+                            struct tls_multi *multi,
+                            const struct user_pass *up)
 {
     int retval = KMDA_ERROR;
     struct key_state *ks = &session->key[KS_PRIMARY];      /* primary key */
 
+    /* set username/password in private env space */
+    setenv_str(session->opt->es, "password", up->password);
+
+    if (management)
+    {
+        management_notify_client_needing_auth(management, ks->mda_key_id, session->opt->mda_context, session->opt->es);
+    }
+
+    setenv_del(session->opt->es, "password");
+
+    retval = KMDA_SUCCESS;
+
+    return retval;
+}
+#endif /* ifdef MANAGEMENT_DEF_AUTH */
+
+static bool
+set_verify_user_pass_env(struct user_pass *up, struct tls_multi *multi,
+                         struct tls_session *session)
+{
     /* Is username defined? */
     if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen(up->username))
     {
-        /* set username/password in private env space */
-        setenv_str(session->opt->es, "username", (raw_username ? raw_username : up->username));
-        setenv_str(session->opt->es, "password", up->password);
+        setenv_str(session->opt->es, "username", up->username);
 
         /* setenv incoming cert common name for script */
         setenv_str(session->opt->es, "common_name", session->common_name);
@@ -1251,30 +1253,25 @@
         /* setenv client real IP address */
         setenv_untrusted(session);
 
-        if (management)
-        {
-            management_notify_client_needing_auth(management, ks->mda_key_id, session->opt->mda_context, session->opt->es);
-        }
-
-        setenv_del(session->opt->es, "password");
-        if (raw_username)
-        {
-            setenv_str(session->opt->es, "username", up->username);
-        }
-
-        retval = KMDA_SUCCESS;
+        /*
+         * if we are using auth-gen-token, send also the session id of auth gen token to
+         * allow the management to figure out if it is a new session or a continued one
+         */
+        add_session_token_env(session, multi, up);
+        return true;
     }
     else
     {
-        msg(D_TLS_ERRORS, "TLS Auth Error (verify_user_pass_management): peer provided a blank username");
+        msg(D_TLS_ERRORS, "TLS Auth Error: peer provided a blank username");
+        return false;
     }
-
-    return retval;
 }
-#endif /* ifdef MANAGEMENT_DEF_AUTH */
 
 /*
  * Main username/password verification entry point
+ *
+ * Will set session->ks[KS_PRIMARY].authenticated according to
+ * result of the username/password verification
  */
 void
 verify_user_pass(struct user_pass *up, struct tls_multi *multi,
@@ -1284,9 +1281,6 @@
     bool s2 = true;
     struct key_state *ks = &session->key[KS_PRIMARY];      /* primary key */
 
-    struct gc_arena gc = gc_new();
-    char *raw_username = NULL;
-
 #ifdef MANAGEMENT_DEF_AUTH
     int man_def_auth = KMDA_UNDEF;
 
@@ -1296,101 +1290,90 @@
     }
 #endif
 
-    /*
-     * Preserve the raw username before string_mod remapping, for plugins
-     * and management clients when in --compat-names mode
-     */
-    if (compat_flag(COMPAT_FLAG_QUERY | COMPAT_NAMES))
-    {
-        ALLOC_ARRAY_CLEAR_GC(raw_username, char, USER_PASS_LEN, &gc);
-        strcpy(raw_username, up->username);
-        string_mod(raw_username, CC_PRINT, CC_CRLF, '_');
-    }
-
     /* enforce character class restrictions in username/password */
-    string_mod_remap_name(up->username, COMMON_NAME_CHAR_CLASS);
+    string_mod_remap_name(up->username);
     string_mod(up->password, CC_PRINT, CC_CRLF, '_');
 
-    /* If server is configured with --auth-gen-token and we have an
-     * authentication token for this client, this authentication
+    /*
+     * If auth token succeeds we skip the auth
+     * methods unless otherwise specified
+     */
+    bool skip_auth = false;
+
+    /*
+     * If server is configured with --auth-gen-token and the client sends
+     * something that looks like an authentication token, this
      * round will be done internally using the token instead of
      * calling any external authentication modules.
      */
-    if (session->opt->auth_token_generate && multi->auth_token_sent
-        && NULL != multi->auth_token)
+    if (session->opt->auth_token_generate && is_auth_token(up->password))
     {
-        unsigned int ssl_flags = session->opt->ssl_flags;
-
-        /* Ensure that the username has not changed */
-        if (!tls_lock_username(multi, up->username))
+        ks->auth_token_state_flags = verify_auth_token(up, multi, session);
+        if (session->opt->auth_token_call_auth)
         {
-            /* auth-token cleared in tls_lock_username() on failure */
-            ks->authenticated = false;
-            goto done;
+            /*
+             * we do not care about the result here because it is
+             * the responsibility of the external authentication to
+             * decide what to do with the result
+             */
         }
-
-        /* If auth-token lifetime has been enabled,
-         * ensure the token has not expired
-         */
-        if (session->opt->auth_token_lifetime > 0
-            && (multi->auth_token_tstamp + session->opt->auth_token_lifetime) < now)
+        else if (ks->auth_token_state_flags == AUTH_TOKEN_HMAC_OK)
         {
-            msg(D_HANDSHAKE, "Auth-token for client expired\n");
-            wipe_auth_token(multi);
-            ks->authenticated = false;
-            goto done;
-        }
-
-        /* The core authentication of the token itself */
-        if (memcmp_constant_time(multi->auth_token, up->password,
-                                 strlen(multi->auth_token)) != 0)
-        {
-            ks->authenticated = false;
-            tls_deauthenticate(multi);
-
-            msg(D_TLS_ERRORS, "TLS Auth Error: Auth-token verification "
-                "failed for username '%s' %s", up->username,
-                (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
+            /*
+             * We do not want the EXPIRED or EMPTY USER flags here so check
+             * for equality with AUTH_TOKEN_HMAC_OK
+             */
+            msg(M_WARN, "TLS: Username/auth-token authentication "
+                "succeeded for username '%s'",
+                up->username);
+            skip_auth = true;
         }
         else
         {
-            ks->authenticated = true;
-
-            if (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME)
-            {
-                set_common_name(session, up->username);
-            }
-            msg(D_HANDSHAKE, "TLS: Username/auth-token authentication "
-                "succeeded for username '%s' %s",
-                up->username,
-                (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
+            wipe_auth_token(multi);
+            ks->authenticated = KS_AUTH_FALSE;
+            msg(M_WARN, "TLS: Username/auth-token authentication "
+                "failed for username '%s'", up->username);
+            return;
         }
-        goto done;
     }
 
-    /* call plugin(s) and/or script */
-#ifdef MANAGEMENT_DEF_AUTH
-    if (man_def_auth == KMDA_DEF)
+    /* Set the environment variables used by all auth variants */
+    if (!set_verify_user_pass_env(up, multi, session))
     {
-        man_def_auth = verify_user_pass_management(session, up, raw_username);
-    }
-#endif
-    if (plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY))
-    {
-        s1 = verify_user_pass_plugin(session, up, raw_username);
-    }
-    if (session->opt->auth_user_pass_verify_script)
-    {
-        s2 = verify_user_pass_script(session, up);
-    }
-
-    /* check sizing of username if it will become our common name */
-    if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) && strlen(up->username) > TLS_USERNAME_LEN)
-    {
-        msg(D_TLS_ERRORS, "TLS Auth Error: --username-as-common name specified and username is longer than the maximum permitted Common Name length of %d characters", TLS_USERNAME_LEN);
+        skip_auth = true;
         s1 = OPENVPN_PLUGIN_FUNC_ERROR;
     }
 
+    /* call plugin(s) and/or script */
+    if (!skip_auth)
+    {
+#ifdef MANAGEMENT_DEF_AUTH
+        if (man_def_auth==KMDA_DEF)
+        {
+            man_def_auth = verify_user_pass_management(session, multi, up);
+        }
+#endif
+        if (plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY))
+        {
+            s1 = verify_user_pass_plugin(session, multi, up);
+        }
+
+        if (session->opt->auth_user_pass_verify_script)
+        {
+            s2 = verify_user_pass_script(session, multi, up);
+        }
+    }
+
+    /* check sizing of username if it will become our common name */
+    if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME)
+        && strlen(up->username)>TLS_USERNAME_LEN)
+    {
+        msg(D_TLS_ERRORS,
+            "TLS Auth Error: --username-as-common name specified and username is longer than the maximum permitted Common Name length of %d characters",
+            TLS_USERNAME_LEN);
+        s1 = OPENVPN_PLUGIN_FUNC_ERROR;
+    }
     /* auth succeeded? */
     if ((s1 == OPENVPN_PLUGIN_FUNC_SUCCESS
 #ifdef PLUGIN_DEF_AUTH
@@ -1402,67 +1385,59 @@
 #endif
         && tls_lock_username(multi, up->username))
     {
-        ks->authenticated = true;
+        ks->authenticated = KS_AUTH_TRUE;
 #ifdef PLUGIN_DEF_AUTH
         if (s1 == OPENVPN_PLUGIN_FUNC_DEFERRED)
         {
-            ks->auth_deferred = true;
+            ks->authenticated = KS_AUTH_DEFERRED;
         }
 #endif
 #ifdef MANAGEMENT_DEF_AUTH
         if (man_def_auth != KMDA_UNDEF)
         {
-            ks->auth_deferred = true;
+            ks->authenticated = KS_AUTH_DEFERRED;
         }
 #endif
-
-        if ((session->opt->auth_token_generate) && (NULL == multi->auth_token))
-        {
-            /* Server is configured with --auth-gen-token but no token has yet
-             * been generated for this client.  Generate one and save it.
-             */
-            uint8_t tok[AUTH_TOKEN_SIZE];
-
-            if (!rand_bytes(tok, AUTH_TOKEN_SIZE))
-            {
-                msg( M_FATAL, "Failed to get enough randomness for "
-                     "authentication token");
-            }
-
-            /* The token should be longer than the input when
-             * being base64 encoded
-             */
-            ASSERT(openvpn_base64_encode(tok, AUTH_TOKEN_SIZE,
-                                         &multi->auth_token) > AUTH_TOKEN_SIZE);
-            multi->auth_token_tstamp = now;
-            dmsg(D_SHOW_KEYS, "Generated token for client: %s",
-                 multi->auth_token);
-        }
-
         if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME))
         {
             set_common_name(session, up->username);
         }
 
-#ifdef ENABLE_DEF_AUTH
+        if ((session->opt->auth_token_generate))
+        {
+            /*
+             * If we accepted a (not expired) token, i.e.
+             * initial auth via token on new connection, we need
+             * to store the auth-token in multi->auth_token, so
+             * the initial timestamp and session id can be extracted from it
+             */
+            if (!multi->auth_token
+                && (ks->auth_token_state_flags & AUTH_TOKEN_HMAC_OK)
+                && !(ks->auth_token_state_flags & AUTH_TOKEN_EXPIRED))
+            {
+                multi->auth_token = strdup(up->password);
+            }
+
+            /*
+             * Server is configured with --auth-gen-token. Generate or renew
+             * the token.
+             */
+            generate_auth_token(up, multi);
+        }
         msg(D_HANDSHAKE, "TLS: Username/Password authentication %s for username '%s' %s",
-            ks->auth_deferred ? "deferred" : "succeeded",
+            (ks->authenticated == KS_AUTH_DEFERRED) ? "deferred" : "succeeded",
             up->username,
             (session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
-#else
-        msg(D_HANDSHAKE, "TLS: Username/Password authentication %s for username '%s' %s",
-            "succeeded",
-            up->username,
-            (session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
-#endif
+        if (ks->authenticated == KS_AUTH_TRUE)
+        {
+            ssl_session_fully_authenticated(multi, session);
+        }
     }
     else
     {
+        ks->authenticated = KS_AUTH_FALSE;
         msg(D_TLS_ERRORS, "TLS Auth Error: Auth Username/Password verification failed for peer");
     }
-
-done:
-    gc_free(&gc);
 }
 
 void
@@ -1477,7 +1452,7 @@
     }
 
     /* Don't allow the CN to change once it's been locked */
-    if (ks->authenticated && multi->locked_cn)
+    if (ks->authenticated > KS_AUTH_FALSE && multi->locked_cn)
     {
         const char *cn = session->common_name;
         if (cn && strcmp(cn, multi->locked_cn))
@@ -1493,7 +1468,7 @@
     }
 
     /* Don't allow the cert hashes to change once they have been locked */
-    if (ks->authenticated && multi->locked_cert_hash_set)
+    if (ks->authenticated > KS_AUTH_FALSE && multi->locked_cert_hash_set)
     {
         const struct cert_hash_set *chs = session->cert_hash_set;
         if (chs && !cert_hash_compare(chs, multi->locked_cert_hash_set))
@@ -1507,15 +1482,16 @@
     }
 
     /* verify --client-config-dir based authentication */
-    if (ks->authenticated && session->opt->client_config_dir_exclusive)
+    if (ks->authenticated > KS_AUTH_FALSE && session->opt->client_config_dir_exclusive)
     {
         struct gc_arena gc = gc_new();
 
         const char *cn = session->common_name;
-        const char *path = gen_path(session->opt->client_config_dir_exclusive, cn, &gc);
-        if (!cn || !strcmp(cn, CCD_DEFAULT) || !test_file(path))
+        const char *path = platform_gen_path(session->opt->client_config_dir_exclusive,
+                                             cn, &gc);
+        if (!cn || !strcmp(cn, CCD_DEFAULT) || !platform_test_file(path))
         {
-            ks->authenticated = false;
+            ks->authenticated = KS_AUTH_FALSE;
             wipe_auth_token(multi);
             msg(D_TLS_ERRORS, "TLS Auth Error: --client-config-dir authentication failed for common name '%s' file='%s'",
                 session->common_name,
@@ -1541,5 +1517,3 @@
         item = next;
     }
 }
-
-#endif /* ENABLE_CRYPTO */
diff --git a/src/openvpn/ssl_verify.h b/src/openvpn/ssl_verify.h
index 3e2267a..b1ced95 100644
--- a/src/openvpn/ssl_verify.h
+++ b/src/openvpn/ssl_verify.h
@@ -29,8 +29,6 @@
 #ifndef SSL_VERIFY_H_
 #define SSL_VERIFY_H_
 
-#ifdef ENABLE_CRYPTO
-
 #include "syshead.h"
 #include "misc.h"
 #include "ssl_common.h"
@@ -226,23 +224,24 @@
 #ifdef MANAGEMENT_DEF_AUTH
 bool tls_authenticate_key(struct tls_multi *multi, const unsigned int mda_key_id, const bool auth, const char *client_reason);
 
-void man_def_auth_set_client_reason(struct tls_multi *multi, const char *client_reason);
-
 #endif
 
+/**
+ * Sets the reason why authentication of a client failed. This be will send to the client
+ * when the AUTH_FAILED message is sent
+ * An example would be "SESSION: Token expired"
+ * @param multi             The multi tls struct
+ * @param client_reason     The string to send to the client as part of AUTH_FAILED
+ */
+void auth_set_client_reason(struct tls_multi *multi, const char *client_reason);
+
 static inline const char *
 tls_client_reason(struct tls_multi *multi)
 {
-#ifdef ENABLE_DEF_AUTH
     return multi->client_reason;
-#else
-    return NULL;
-#endif
 }
 
 /** Remove any X509_ env variables from env_set es */
 void tls_x509_clear_env(struct env_set *es);
 
-#endif /* ENABLE_CRYPTO */
-
 #endif /* SSL_VERIFY_H_ */
diff --git a/src/openvpn/ssl_verify_backend.h b/src/openvpn/ssl_verify_backend.h
index 2a9e8bb..d6b31bf 100644
--- a/src/openvpn/ssl_verify_backend.h
+++ b/src/openvpn/ssl_verify_backend.h
@@ -130,6 +130,7 @@
  * --x509-username-field option.
  */
 bool x509_username_field_ext_supported(const char *extname);
+
 #endif
 
 /*
@@ -175,7 +176,7 @@
  *
  * The tracked attributes are stored in ll_head.
  *
- * @param ll_head       The x509_track to store tracked atttributes in
+ * @param ll_head       The x509_track to store tracked attributes in
  * @param name          Name of the attribute to track
  * @param msglevel      Message level for errors
  * @param gc            Garbage collection arena for temp data
diff --git a/src/openvpn/ssl_verify_mbedtls.c b/src/openvpn/ssl_verify_mbedtls.c
index 2d019ab..9389103 100644
--- a/src/openvpn/ssl_verify_mbedtls.c
+++ b/src/openvpn/ssl_verify_mbedtls.c
@@ -34,7 +34,7 @@
 
 #include "syshead.h"
 
-#if defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_MBEDTLS)
+#if defined(ENABLE_CRYPTO_MBEDTLS)
 
 #include "crypto_mbedtls.h"
 #include "ssl_verify.h"
@@ -68,6 +68,7 @@
         int ret = 0;
         char errstr[512] = { 0 };
         char *subject = x509_get_subject(cert, &gc);
+        char *serial = backend_x509_get_serial(cert, &gc);
 
         ret = mbedtls_x509_crt_verify_info(errstr, sizeof(errstr)-1, "", *flags);
         if (ret <= 0 && !openvpn_snprintf(errstr, sizeof(errstr),
@@ -82,8 +83,8 @@
 
         if (subject)
         {
-            msg(D_TLS_ERRORS, "VERIFY ERROR: depth=%d, subject=%s: %s",
-                cert_depth, subject, errstr);
+            msg(D_TLS_ERRORS, "VERIFY ERROR: depth=%d, subject=%s, serial=%s: %s",
+                cert_depth, subject, serial ? serial : "<not available>", errstr);
         }
         else
         {
@@ -550,4 +551,4 @@
     return false;
 }
 
-#endif /* #if defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_MBEDTLS) */
+#endif /* #if defined(ENABLE_CRYPTO_MBEDTLS) */
diff --git a/src/openvpn/ssl_verify_openssl.c b/src/openvpn/ssl_verify_openssl.c
index b1ce06b..454efee 100644
--- a/src/openvpn/ssl_verify_openssl.c
+++ b/src/openvpn/ssl_verify_openssl.c
@@ -34,7 +34,7 @@
 
 #include "syshead.h"
 
-#if defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_OPENSSL)
+#if defined(ENABLE_CRYPTO_OPENSSL)
 
 #include "ssl_verify_openssl.h"
 
@@ -44,8 +44,9 @@
 #include "ssl_verify_backend.h"
 #include "openssl_compat.h"
 
-#include <openssl/x509v3.h>
+#include <openssl/bn.h>
 #include <openssl/err.h>
+#include <openssl/x509v3.h>
 
 int
 verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
@@ -70,6 +71,7 @@
     {
         /* get the X509 name */
         char *subject = x509_get_subject(current_cert, &gc);
+        char *serial = backend_x509_get_serial(current_cert, &gc);
 
         if (!subject)
         {
@@ -88,10 +90,10 @@
         }
 
         /* Remote site specified a certificate, but it's not correct */
-        msg(D_TLS_ERRORS, "VERIFY ERROR: depth=%d, error=%s: %s",
+        msg(D_TLS_ERRORS, "VERIFY ERROR: depth=%d, error=%s: %s, serial=%s",
             X509_STORE_CTX_get_error_depth(ctx),
             X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx)),
-            subject);
+            subject, serial ? serial : "<not available>");
 
         ERR_clear_error();
 
@@ -113,7 +115,8 @@
 }
 
 #ifdef ENABLE_X509ALTUSERNAME
-bool x509_username_field_ext_supported(const char *fieldname)
+bool
+x509_username_field_ext_supported(const char *fieldname)
 {
     int nid = OBJ_txt2nid(fieldname);
     return nid == NID_subject_alt_name || nid == NID_issuer_alt_name;
@@ -331,18 +334,6 @@
     BUF_MEM *subject_mem;
     char *subject = NULL;
 
-    /*
-     * Generate the subject string in OpenSSL proprietary format,
-     * when in --compat-names mode
-     */
-    if (compat_flag(COMPAT_FLAG_QUERY | COMPAT_NAMES))
-    {
-        subject = gc_malloc(256, false, gc);
-        X509_NAME_oneline(X509_get_subject_name(cert), subject, 256);
-        subject[255] = '\0';
-        return subject;
-    }
-
     subject_bio = BIO_new(BIO_s_mem());
     if (subject_bio == NULL)
     {
@@ -479,8 +470,7 @@
                         if (ent)
                         {
                             ASN1_STRING *val = X509_NAME_ENTRY_get_data(ent);
-                            unsigned char *buf;
-                            buf = (unsigned char *)1; /* bug in OpenSSL 0.9.6b ASN1_STRING_to_UTF8 requires this workaround */
+                            unsigned char *buf = NULL;
                             if (ASN1_STRING_to_UTF8(&buf, val) >= 0)
                             {
                                 do_setenv_x509(es, xt->name, (char *)buf, depth);
@@ -535,7 +525,7 @@
     ASN1_STRING *val;
     X509_NAME_ENTRY *ent;
     const char *objbuf;
-    unsigned char *buf;
+    unsigned char *buf = NULL;
     char *name_expand;
     size_t name_expand_size;
     X509_NAME *x509 = X509_get_subject_name(peer_cert);
@@ -568,7 +558,6 @@
         {
             continue;
         }
-        buf = (unsigned char *)1; /* bug in OpenSSL 0.9.6b ASN1_STRING_to_UTF8 requires this workaround */
         if (ASN1_STRING_to_UTF8(&buf, val) < 0)
         {
             continue;
@@ -600,7 +589,7 @@
          * prevent it to take a const argument
          */
         result_t result = X509_check_purpose(peer_cert, X509_PURPOSE_SSL_CLIENT, 0) ?
-	       SUCCESS : FAILURE;
+                          SUCCESS : FAILURE;
 
         /*
          * old versions of OpenSSL allow us to make the less strict check we used to
@@ -628,7 +617,7 @@
          * prevent it to take a const argument
          */
         result_t result = X509_check_purpose(peer_cert, X509_PURPOSE_SSL_SERVER, 0) ?
-	       SUCCESS : FAILURE;
+                          SUCCESS : FAILURE;
 
         /*
          * old versions of OpenSSL allow us to make the less strict check we used to
@@ -769,7 +758,7 @@
 {
     if (PEM_write_X509(peercert_file, peercert) < 0)
     {
-        msg(M_ERR, "Failed to write peer certificate in PEM format");
+        msg(M_NONFATAL, "Failed to write peer certificate in PEM format");
         return FAILURE;
     }
     return SUCCESS;
@@ -802,4 +791,4 @@
     return true;
 }
 
-#endif /* defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_OPENSSL) */
+#endif /* defined(ENABLE_CRYPTO_OPENSSL) */
diff --git a/src/openvpn/status.c b/src/openvpn/status.c
index 91391d1..e8dcf7c 100644
--- a/src/openvpn/status.c
+++ b/src/openvpn/status.c
@@ -146,19 +146,6 @@
     }
 }
 
-bool
-status_trigger_tv(struct status_output *so, struct timeval *tv)
-{
-    if (so)
-    {
-        return event_timeout_trigger(&so->et, tv, ETT_DEFAULT);
-    }
-    else
-    {
-        return false;
-    }
-}
-
 void
 status_reset(struct status_output *so)
 {
diff --git a/src/openvpn/status.h b/src/openvpn/status.h
index 2a399d7..66e5bc5 100644
--- a/src/openvpn/status.h
+++ b/src/openvpn/status.h
@@ -69,8 +69,6 @@
                                   const struct virtual_output *vout,
                                   const unsigned int flags);
 
-bool status_trigger_tv(struct status_output *so, struct timeval *tv);
-
 bool status_trigger(struct status_output *so);
 
 void status_reset(struct status_output *so);
diff --git a/src/openvpn/syshead.h b/src/openvpn/syshead.h
index 3ac9d70..8342eae 100644
--- a/src/openvpn/syshead.h
+++ b/src/openvpn/syshead.h
@@ -39,6 +39,7 @@
 #ifdef _WIN32
 #include <windows.h>
 #include <winsock2.h>
+#include <tlhelp32.h>
 #define sleep(x) Sleep((x)*1000)
 #define random rand
 #define srandom srand
@@ -47,6 +48,7 @@
 #ifdef _MSC_VER /* Visual Studio */
 #define __func__ __FUNCTION__
 #define __attribute__(x)
+#include <inttypes.h>
 #endif
 
 #if defined(__APPLE__)
@@ -178,8 +180,8 @@
 #include <resolv.h>
 #endif
 
-#ifdef HAVE_SYS_POLL_H
-#include <sys/poll.h>
+#ifdef HAVE_POLL_H
+#include <poll.h>
 #endif
 
 #ifdef HAVE_SYS_EPOLL_H
@@ -513,22 +515,16 @@
  * Do we have point-to-multipoint capability?
  */
 
-#if defined(ENABLE_CRYPTO) && defined(HAVE_GETTIMEOFDAY_NANOSECONDS)
+#if defined(HAVE_GETTIMEOFDAY_NANOSECONDS)
 #define P2MP 1
 #else
 #define P2MP 0
 #endif
 
-#if P2MP && !defined(ENABLE_CLIENT_ONLY)
-#define P2MP_SERVER 1
-#else
-#define P2MP_SERVER 0
-#endif
-
 /*
  * HTTPS port sharing capability
  */
-#if defined(ENABLE_PORT_SHARE) && P2MP_SERVER && defined(SCM_RIGHTS) && defined(HAVE_MSGHDR) && defined(HAVE_CMSGHDR) && defined(HAVE_IOVEC) && defined(CMSG_FIRSTHDR) && defined(CMSG_NXTHDR) && defined(HAVE_RECVMSG) && defined(HAVE_SENDMSG)
+#if defined(ENABLE_PORT_SHARE) && defined(SCM_RIGHTS) && defined(HAVE_MSGHDR) && defined(HAVE_CMSGHDR) && defined(HAVE_IOVEC) && defined(CMSG_FIRSTHDR) && defined(CMSG_NXTHDR) && defined(HAVE_RECVMSG) && defined(HAVE_SENDMSG)
 #define PORT_SHARE 1
 #else
 #define PORT_SHARE 0
@@ -537,43 +533,27 @@
 /*
  * Enable deferred authentication?
  */
-#if defined(ENABLE_DEF_AUTH) && P2MP_SERVER && defined(ENABLE_PLUGIN)
+#if defined(ENABLE_DEF_AUTH) && defined(ENABLE_PLUGIN)
 #define PLUGIN_DEF_AUTH
 #endif
-#if defined(ENABLE_DEF_AUTH) && P2MP_SERVER && defined(ENABLE_MANAGEMENT)
+#if defined(ENABLE_DEF_AUTH) && defined(ENABLE_MANAGEMENT)
 #define MANAGEMENT_DEF_AUTH
 #endif
 #if !defined(PLUGIN_DEF_AUTH) && !defined(MANAGEMENT_DEF_AUTH)
 #undef ENABLE_DEF_AUTH
 #endif
 
-/*
- * Enable external private key
- */
-#if defined(ENABLE_MANAGEMENT) && defined(ENABLE_CRYPTO)
-#define MANAGMENT_EXTERNAL_KEY
-#endif
-
-/* Enable mbed TLS RNG prediction resistance support */
 #ifdef ENABLE_CRYPTO_MBEDTLS
 #define ENABLE_PREDICTION_RESISTANCE
 #endif /* ENABLE_CRYPTO_MBEDTLS */
 
 /*
- * MANAGEMENT_IN_EXTRA allows the management interface to
- * read multi-line inputs from clients.
- */
-#if defined(MANAGEMENT_DEF_AUTH) || defined(MANAGMENT_EXTERNAL_KEY)
-#define MANAGEMENT_IN_EXTRA
-#endif
-
-/*
  * Enable packet filter?
  */
-#if defined(ENABLE_PF) && P2MP_SERVER && defined(ENABLE_PLUGIN) && defined(HAVE_STAT)
+#if defined(ENABLE_PF) && defined(ENABLE_PLUGIN) && defined(HAVE_STAT)
 #define PLUGIN_PF
 #endif
-#if defined(ENABLE_PF) && P2MP_SERVER && defined(MANAGEMENT_DEF_AUTH)
+#if defined(ENABLE_PF) && defined(MANAGEMENT_DEF_AUTH)
 #define MANAGEMENT_PF
 #endif
 #if !defined(PLUGIN_PF) && !defined(MANAGEMENT_PF)
@@ -590,39 +570,26 @@
 #endif
 
 /*
- * Should we include OCC (options consistency check) code?
- */
-#define ENABLE_OCC
-
-/*
  * Should we include NTLM proxy functionality
  */
-#if defined(ENABLE_CRYPTO)
 #define NTLM 1
-#else
-#define NTLM 0
-#endif
 
 /*
  * Should we include proxy digest auth functionality
  */
-#if defined(ENABLE_CRYPTO)
 #define PROXY_DIGEST_AUTH 1
-#else
-#define PROXY_DIGEST_AUTH 0
-#endif
 
 /*
  * Do we have CryptoAPI capability?
  */
-#if defined(_WIN32) && defined(ENABLE_CRYPTO) && defined(ENABLE_CRYPTO_OPENSSL)
+#if defined(_WIN32) && defined(ENABLE_CRYPTO_OPENSSL)
 #define ENABLE_CRYPTOAPI
 #endif
 
 /*
  * Is poll available on this platform?
  */
-#if defined(HAVE_POLL) && defined(HAVE_SYS_POLL_H)
+#if defined(HAVE_POLL) && defined(HAVE_POLL_H)
 #define POLL 1
 #else
 #define POLL 0
@@ -666,29 +633,6 @@
 #endif
 
 /*
- * Do we have the capability to support the AUTO_USERID feature?
- */
-#if defined(ENABLE_AUTO_USERID)
-#define AUTO_USERID 1
-#else
-#define AUTO_USERID 0
-#endif
-
-/*
- * Do we support challenge/response authentication as client?
- */
-#if defined(ENABLE_MANAGEMENT)
-#define ENABLE_CLIENT_CR
-#endif
-
-/*
- * Do we support pushing peer info?
- */
-#if defined(ENABLE_CRYPTO)
-#define ENABLE_PUSH_PEER_INFO
-#endif
-
-/*
  * Compression support
  */
 #if defined(ENABLE_LZO) || defined(ENABLE_LZ4)    \
diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c
index ecc654e..7b5016d 100644
--- a/src/openvpn/tls_crypt.c
+++ b/src/openvpn/tls_crypt.c
@@ -29,12 +29,24 @@
 
 #include "syshead.h"
 
-#ifdef ENABLE_CRYPTO
+#include "argv.h"
+#include "base64.h"
 #include "crypto.h"
+#include "platform.h"
+#include "run_command.h"
 #include "session_id.h"
+#include "ssl.h"
 
 #include "tls_crypt.h"
 
+const char *tls_crypt_v2_cli_pem_name = "OpenVPN tls-crypt-v2 client key";
+const char *tls_crypt_v2_srv_pem_name = "OpenVPN tls-crypt-v2 server key";
+
+/** Metadata contains user-specified data */
+static const uint8_t TLS_CRYPT_METADATA_TYPE_USER           = 0x00;
+/** Metadata contains a 64-bit unix timestamp in network byte order */
+static const uint8_t TLS_CRYPT_METADATA_TYPE_TIMESTAMP      = 0x01;
+
 static struct key_type
 tls_crypt_kt(void)
 {
@@ -67,14 +79,14 @@
 
 void
 tls_crypt_init_key(struct key_ctx_bi *key, const char *key_file,
-                   const char *key_inline, bool tls_server)
+                   bool key_inline, bool tls_server)
 {
     const int key_direction = tls_server ?
                               KEY_DIRECTION_NORMAL : KEY_DIRECTION_INVERSE;
     struct key_type kt = tls_crypt_kt();
     if (!kt.cipher || !kt.digest)
     {
-        msg (M_FATAL, "ERROR: --tls-crypt not supported");
+        msg(M_FATAL, "ERROR: --tls-crypt not supported");
     }
     crypto_read_openvpn_key(&kt, key, key_file, key_inline, key_direction,
                             "Control Channel Encryption", "tls-crypt");
@@ -266,4 +278,466 @@
     return false;
 }
 
-#endif /* EMABLE_CRYPTO */
+static inline void
+tls_crypt_v2_load_client_key(struct key_ctx_bi *key, const struct key2 *key2,
+                             bool tls_server)
+{
+    const int key_direction = tls_server ?
+                              KEY_DIRECTION_NORMAL : KEY_DIRECTION_INVERSE;
+    struct key_type kt = tls_crypt_kt();
+    if (!kt.cipher || !kt.digest)
+    {
+        msg(M_FATAL, "ERROR: --tls-crypt-v2 not supported");
+    }
+    init_key_ctx_bi(key, key2, key_direction, &kt,
+                    "Control Channel Encryption");
+}
+
+void
+tls_crypt_v2_init_client_key(struct key_ctx_bi *key, struct buffer *wkc_buf,
+                             const char *key_file, bool key_inline)
+{
+    struct buffer client_key = alloc_buf(TLS_CRYPT_V2_CLIENT_KEY_LEN
+                                         + TLS_CRYPT_V2_MAX_WKC_LEN);
+
+    if (!read_pem_key_file(&client_key, tls_crypt_v2_cli_pem_name,
+                           key_file, key_inline))
+    {
+        msg(M_FATAL, "ERROR: invalid tls-crypt-v2 client key format");
+    }
+
+    struct key2 key2;
+    if (!buf_read(&client_key, &key2.keys, sizeof(key2.keys)))
+    {
+        msg(M_FATAL, "ERROR: not enough data in tls-crypt-v2 client key");
+    }
+
+    tls_crypt_v2_load_client_key(key, &key2, false);
+    secure_memzero(&key2, sizeof(key2));
+
+    *wkc_buf = client_key;
+}
+
+void
+tls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt,
+                             const char *key_file, bool key_inline)
+{
+    struct key srv_key;
+    struct buffer srv_key_buf;
+
+    buf_set_write(&srv_key_buf, (void *)&srv_key, sizeof(srv_key));
+    if (!read_pem_key_file(&srv_key_buf, tls_crypt_v2_srv_pem_name,
+                           key_file, key_inline))
+    {
+        msg(M_FATAL, "ERROR: invalid tls-crypt-v2 server key format");
+    }
+
+    struct key_type kt = tls_crypt_kt();
+    if (!kt.cipher || !kt.digest)
+    {
+        msg(M_FATAL, "ERROR: --tls-crypt-v2 not supported");
+    }
+    init_key_ctx(key_ctx, &srv_key, &kt, encrypt, "tls-crypt-v2 server key");
+    secure_memzero(&srv_key, sizeof(srv_key));
+}
+
+static bool
+tls_crypt_v2_wrap_client_key(struct buffer *wkc,
+                             const struct key2 *src_key,
+                             const struct buffer *src_metadata,
+                             struct key_ctx *server_key, struct gc_arena *gc)
+{
+    cipher_ctx_t *cipher_ctx = server_key->cipher;
+    struct buffer work = alloc_buf_gc(TLS_CRYPT_V2_MAX_WKC_LEN
+                                      + cipher_ctx_block_size(cipher_ctx), gc);
+
+    /* Calculate auth tag and synthetic IV */
+    uint8_t *tag = buf_write_alloc(&work, TLS_CRYPT_TAG_SIZE);
+    if (!tag)
+    {
+        msg(M_WARN, "ERROR: could not write tag");
+        return false;
+    }
+    uint16_t net_len = htons(sizeof(src_key->keys) + BLEN(src_metadata)
+                             + TLS_CRYPT_V2_TAG_SIZE + sizeof(uint16_t));
+    hmac_ctx_t *hmac_ctx = server_key->hmac;
+    hmac_ctx_reset(hmac_ctx);
+    hmac_ctx_update(hmac_ctx, (void *)&net_len, sizeof(net_len));
+    hmac_ctx_update(hmac_ctx, (void *)src_key->keys, sizeof(src_key->keys));
+    hmac_ctx_update(hmac_ctx, BPTR(src_metadata), BLEN(src_metadata));
+    hmac_ctx_final(hmac_ctx, tag);
+
+    dmsg(D_CRYPTO_DEBUG, "TLS-CRYPT WRAP TAG: %s",
+         format_hex(tag, TLS_CRYPT_TAG_SIZE, 0, gc));
+
+    /* Use the 128 most significant bits of the tag as IV */
+    ASSERT(cipher_ctx_reset(cipher_ctx, tag));
+
+    /* Overflow check (OpenSSL requires an extra block in the dst buffer) */
+    if (buf_forward_capacity(&work) < (sizeof(src_key->keys)
+                                       + BLEN(src_metadata)
+                                       + sizeof(net_len)
+                                       + cipher_ctx_block_size(cipher_ctx)))
+    {
+        msg(M_WARN, "ERROR: could not crypt: insufficient space in dst");
+        return false;
+    }
+
+    /* Encrypt */
+    int outlen = 0;
+    ASSERT(cipher_ctx_update(cipher_ctx, BEND(&work), &outlen,
+                             (void *)src_key->keys, sizeof(src_key->keys)));
+    ASSERT(buf_inc_len(&work, outlen));
+    ASSERT(cipher_ctx_update(cipher_ctx, BEND(&work), &outlen,
+                             BPTR(src_metadata), BLEN(src_metadata)));
+    ASSERT(buf_inc_len(&work, outlen));
+    ASSERT(cipher_ctx_final(cipher_ctx, BEND(&work), &outlen));
+    ASSERT(buf_inc_len(&work, outlen));
+    ASSERT(buf_write(&work, &net_len, sizeof(net_len)));
+
+    return buf_copy(wkc, &work);
+}
+
+static bool
+tls_crypt_v2_unwrap_client_key(struct key2 *client_key, struct buffer *metadata,
+                               struct buffer wrapped_client_key,
+                               struct key_ctx *server_key)
+{
+    const char *error_prefix = __func__;
+    bool ret = false;
+    struct gc_arena gc = gc_new();
+    /* The crypto API requires one extra cipher block of buffer head room when
+     * decrypting, which nicely matches the tag size of WKc.  So
+     * TLS_CRYPT_V2_MAX_WKC_LEN is always large enough for the plaintext. */
+    uint8_t plaintext_buf_data[TLS_CRYPT_V2_MAX_WKC_LEN] = { 0 };
+    struct buffer plaintext = { 0 };
+
+    dmsg(D_TLS_DEBUG_MED, "%s: unwrapping client key (len=%d): %s", __func__,
+         BLEN(&wrapped_client_key), format_hex(BPTR(&wrapped_client_key),
+                                               BLEN(&wrapped_client_key),
+                                               0, &gc));
+
+    if (TLS_CRYPT_V2_MAX_WKC_LEN < BLEN(&wrapped_client_key))
+    {
+        CRYPT_ERROR("wrapped client key too big");
+    }
+
+    /* Decrypt client key and metadata */
+    uint16_t net_len = 0;
+    const uint8_t *tag = BPTR(&wrapped_client_key);
+
+    if (BLEN(&wrapped_client_key) < sizeof(net_len))
+    {
+        CRYPT_ERROR("failed to read length");
+    }
+    memcpy(&net_len, BEND(&wrapped_client_key) - sizeof(net_len),
+           sizeof(net_len));
+
+    if (ntohs(net_len) != BLEN(&wrapped_client_key))
+    {
+        dmsg(D_TLS_DEBUG_LOW, "%s: net_len=%u, BLEN=%i", __func__,
+             ntohs(net_len), BLEN(&wrapped_client_key));
+        CRYPT_ERROR("invalid length");
+    }
+
+    buf_inc_len(&wrapped_client_key, -(int)sizeof(net_len));
+
+    if (!buf_advance(&wrapped_client_key, TLS_CRYPT_TAG_SIZE))
+    {
+        CRYPT_ERROR("failed to read tag");
+    }
+
+    if (!cipher_ctx_reset(server_key->cipher, tag))
+    {
+        CRYPT_ERROR("failed to initialize IV");
+    }
+    buf_set_write(&plaintext, plaintext_buf_data, sizeof(plaintext_buf_data));
+    int outlen = 0;
+    if (!cipher_ctx_update(server_key->cipher, BPTR(&plaintext), &outlen,
+                           BPTR(&wrapped_client_key),
+                           BLEN(&wrapped_client_key)))
+    {
+        CRYPT_ERROR("could not decrypt client key");
+    }
+    ASSERT(buf_inc_len(&plaintext, outlen));
+
+    if (!cipher_ctx_final(server_key->cipher, BEND(&plaintext), &outlen))
+    {
+        CRYPT_ERROR("cipher final failed");
+    }
+    ASSERT(buf_inc_len(&plaintext, outlen));
+
+    /* Check authentication */
+    uint8_t tag_check[TLS_CRYPT_TAG_SIZE] = { 0 };
+    hmac_ctx_reset(server_key->hmac);
+    hmac_ctx_update(server_key->hmac, (void *)&net_len, sizeof(net_len));
+    hmac_ctx_update(server_key->hmac, BPTR(&plaintext),
+                    BLEN(&plaintext));
+    hmac_ctx_final(server_key->hmac, tag_check);
+
+    if (memcmp_constant_time(tag, tag_check, sizeof(tag_check)))
+    {
+        dmsg(D_CRYPTO_DEBUG, "tag      : %s",
+             format_hex(tag, sizeof(tag_check), 0, &gc));
+        dmsg(D_CRYPTO_DEBUG, "tag_check: %s",
+             format_hex(tag_check, sizeof(tag_check), 0, &gc));
+        CRYPT_ERROR("client key authentication error");
+    }
+
+    if (buf_len(&plaintext) < sizeof(client_key->keys))
+    {
+        CRYPT_ERROR("failed to read client key");
+    }
+    memcpy(&client_key->keys, BPTR(&plaintext), sizeof(client_key->keys));
+    ASSERT(buf_advance(&plaintext, sizeof(client_key->keys)));
+
+    if (!buf_copy(metadata, &plaintext))
+    {
+        CRYPT_ERROR("metadata too large for supplied buffer");
+    }
+
+    ret = true;
+error_exit:
+    if (!ret)
+    {
+        secure_memzero(client_key, sizeof(*client_key));
+    }
+    buf_clear(&plaintext);
+    gc_free(&gc);
+    return ret;
+}
+
+static bool
+tls_crypt_v2_verify_metadata(const struct tls_wrap_ctx *ctx,
+                             const struct tls_options *opt)
+{
+    bool ret = false;
+    struct gc_arena gc = gc_new();
+    const char *tmp_file = NULL;
+    struct buffer metadata = ctx->tls_crypt_v2_metadata;
+    int metadata_type = buf_read_u8(&metadata);
+    if (metadata_type < 0)
+    {
+        msg(M_WARN, "ERROR: no metadata type");
+        goto cleanup;
+    }
+
+    tmp_file = platform_create_temp_file(opt->tmp_dir, "tls_crypt_v2_metadata_",
+                                         &gc);
+    if (!tmp_file || !buffer_write_file(tmp_file, &metadata))
+    {
+        msg(M_WARN, "ERROR: could not write metadata to file");
+        goto cleanup;
+    }
+
+    char metadata_type_str[4] = { 0 }; /* Max value: 255 */
+    openvpn_snprintf(metadata_type_str, sizeof(metadata_type_str),
+                     "%i", metadata_type);
+    struct env_set *es = env_set_create(NULL);
+    setenv_str(es, "script_type", "tls-crypt-v2-verify");
+    setenv_str(es, "metadata_type", metadata_type_str);
+    setenv_str(es, "metadata_file", tmp_file);
+
+    struct argv argv = argv_new();
+    argv_parse_cmd(&argv, opt->tls_crypt_v2_verify_script);
+    argv_msg_prefix(D_TLS_DEBUG, &argv, "Executing tls-crypt-v2-verify");
+
+    ret = openvpn_run_script(&argv, es, 0, "--tls-crypt-v2-verify");
+
+    argv_free(&argv);
+    env_set_destroy(es);
+
+    if (!platform_unlink(tmp_file))
+    {
+        msg(M_WARN, "WARNING: failed to remove temp file '%s", tmp_file);
+    }
+
+    if (ret)
+    {
+        msg(D_HANDSHAKE, "TLS CRYPT V2 VERIFY SCRIPT OK");
+    }
+    else
+    {
+        msg(D_HANDSHAKE, "TLS CRYPT V2 VERIFY SCRIPT ERROR");
+    }
+
+cleanup:
+    gc_free(&gc);
+    return ret;
+}
+
+bool
+tls_crypt_v2_extract_client_key(struct buffer *buf,
+                                struct tls_wrap_ctx *ctx,
+                                const struct tls_options *opt)
+{
+    if (!ctx->tls_crypt_v2_server_key.cipher)
+    {
+        msg(D_TLS_ERRORS,
+            "Client wants tls-crypt-v2, but no server key present.");
+        return false;
+    }
+
+    msg(D_HANDSHAKE, "Control Channel: using tls-crypt-v2 key");
+
+    struct buffer wrapped_client_key = *buf;
+    uint16_t net_len = 0;
+
+    if (BLEN(&wrapped_client_key) < sizeof(net_len))
+    {
+        msg(D_TLS_ERRORS, "failed to read length");
+    }
+    memcpy(&net_len, BEND(&wrapped_client_key) - sizeof(net_len),
+           sizeof(net_len));
+
+    size_t wkc_len = ntohs(net_len);
+    if (!buf_advance(&wrapped_client_key, BLEN(&wrapped_client_key) - wkc_len))
+    {
+        msg(D_TLS_ERRORS, "Can not locate tls-crypt-v2 client key");
+        return false;
+    }
+
+    struct key2 client_key = { 0 };
+    ctx->tls_crypt_v2_metadata = alloc_buf(TLS_CRYPT_V2_MAX_METADATA_LEN);
+    if (!tls_crypt_v2_unwrap_client_key(&client_key,
+                                        &ctx->tls_crypt_v2_metadata,
+                                        wrapped_client_key,
+                                        &ctx->tls_crypt_v2_server_key))
+    {
+        msg(D_TLS_ERRORS, "Can not unwrap tls-crypt-v2 client key");
+        secure_memzero(&client_key, sizeof(client_key));
+        return false;
+    }
+
+    /* Load the decrypted key */
+    ctx->mode = TLS_WRAP_CRYPT;
+    ctx->cleanup_key_ctx = true;
+    ctx->opt.flags |= CO_PACKET_ID_LONG_FORM;
+    memset(&ctx->opt.key_ctx_bi, 0, sizeof(ctx->opt.key_ctx_bi));
+    tls_crypt_v2_load_client_key(&ctx->opt.key_ctx_bi, &client_key, true);
+    secure_memzero(&client_key, sizeof(client_key));
+
+    /* Remove client key from buffer so tls-crypt code can unwrap message */
+    ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key))));
+
+    if (opt && opt->tls_crypt_v2_verify_script)
+    {
+        return tls_crypt_v2_verify_metadata(ctx, opt);
+    }
+
+    return true;
+}
+
+void
+tls_crypt_v2_write_server_key_file(const char *filename)
+{
+    write_pem_key_file(filename, tls_crypt_v2_srv_pem_name);
+}
+
+void
+tls_crypt_v2_write_client_key_file(const char *filename,
+                                   const char *b64_metadata,
+                                   const char *server_key_file,
+                                   bool server_key_inline)
+{
+    struct gc_arena gc = gc_new();
+    struct key_ctx server_key = { 0 };
+    struct buffer client_key_pem = { 0 };
+    struct buffer dst = alloc_buf_gc(TLS_CRYPT_V2_CLIENT_KEY_LEN
+                                     + TLS_CRYPT_V2_MAX_WKC_LEN, &gc);
+    struct key2 client_key = { 2 };
+
+    if (!rand_bytes((void *)client_key.keys, sizeof(client_key.keys)))
+    {
+        msg(M_FATAL, "ERROR: could not generate random key");
+        goto cleanup;
+    }
+    ASSERT(buf_write(&dst, client_key.keys, sizeof(client_key.keys)));
+
+    struct buffer metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, &gc);
+    if (b64_metadata)
+    {
+        if (TLS_CRYPT_V2_MAX_B64_METADATA_LEN < strlen(b64_metadata))
+        {
+            msg(M_FATAL,
+                "ERROR: metadata too long (%d bytes, max %u bytes)",
+                (int)strlen(b64_metadata), TLS_CRYPT_V2_MAX_B64_METADATA_LEN);
+        }
+        ASSERT(buf_write(&metadata, &TLS_CRYPT_METADATA_TYPE_USER, 1));
+        int decoded_len = openvpn_base64_decode(b64_metadata, BEND(&metadata),
+                                                BCAP(&metadata));
+        if (decoded_len < 0)
+        {
+            msg(M_FATAL, "ERROR: failed to base64 decode provided metadata");
+            goto cleanup;
+        }
+        ASSERT(buf_inc_len(&metadata, decoded_len));
+    }
+    else
+    {
+        int64_t timestamp = htonll((uint64_t)now);
+        ASSERT(buf_write(&metadata, &TLS_CRYPT_METADATA_TYPE_TIMESTAMP, 1));
+        ASSERT(buf_write(&metadata, &timestamp, sizeof(timestamp)));
+    }
+
+    tls_crypt_v2_init_server_key(&server_key, true, server_key_file,
+                                 server_key_inline);
+    if (!tls_crypt_v2_wrap_client_key(&dst, &client_key, &metadata, &server_key,
+                                      &gc))
+    {
+        msg(M_FATAL, "ERROR: could not wrap generated client key");
+        goto cleanup;
+    }
+
+    /* PEM-encode Kc || WKc */
+    if (!crypto_pem_encode(tls_crypt_v2_cli_pem_name, &client_key_pem, &dst,
+                           &gc))
+    {
+        msg(M_FATAL, "ERROR: could not PEM-encode client key");
+        goto cleanup;
+    }
+
+    const char *client_file = filename;
+    bool client_inline = false;
+
+    if (!filename || streq(filename, ""))
+    {
+        printf("%.*s\n", BLEN(&client_key_pem), BPTR(&client_key_pem));
+        client_file = (const char *)BPTR(&client_key_pem);
+        client_inline = true;
+    }
+    else if (!buffer_write_file(filename, &client_key_pem))
+    {
+        msg(M_FATAL, "ERROR: could not write client key file");
+        goto cleanup;
+    }
+
+    /* Sanity check: load client key (as "client") */
+    struct key_ctx_bi test_client_key;
+    struct buffer test_wrapped_client_key;
+    msg(D_GENKEY, "Testing client-side key loading...");
+    tls_crypt_v2_init_client_key(&test_client_key, &test_wrapped_client_key,
+                                 client_file, client_inline);
+    free_key_ctx_bi(&test_client_key);
+
+    /* Sanity check: unwrap and load client key (as "server") */
+    struct buffer test_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN,
+                                               &gc);
+    struct key2 test_client_key2 = { 0 };
+    free_key_ctx(&server_key);
+    tls_crypt_v2_init_server_key(&server_key, false, server_key_file,
+                                 server_key_inline);
+    msg(D_GENKEY, "Testing server-side key loading...");
+    ASSERT(tls_crypt_v2_unwrap_client_key(&test_client_key2, &test_metadata,
+                                          test_wrapped_client_key, &server_key));
+    secure_memzero(&test_client_key2, sizeof(test_client_key2));
+    free_buf(&test_wrapped_client_key);
+
+cleanup:
+    secure_memzero(&client_key, sizeof(client_key));
+    free_key_ctx(&server_key);
+    buf_clear(&client_key_pem);
+    buf_clear(&dst);
+
+    gc_free(&gc);
+}
diff --git a/src/openvpn/tls_crypt.h b/src/openvpn/tls_crypt.h
index 05fcc4e..2e127f2 100644
--- a/src/openvpn/tls_crypt.h
+++ b/src/openvpn/tls_crypt.h
@@ -22,15 +22,13 @@
  */
 
 /**
- * @defgroup tls_crypt Control channel encryption (--tls-crypt)
+ * @defgroup tls_crypt Control channel encryption (--tls-crypt, --tls-crypt-v2)
  * @ingroup control_tls
  * @{
  *
- * @par
  * Control channel encryption uses a pre-shared static key (like the --tls-auth
  * key) to encrypt control channel packets.
  *
- * @par
  * Encrypting control channel packets has three main advantages:
  *  - It provides more privacy by hiding the certificate used for the TLS
  *    connection.
@@ -38,11 +36,20 @@
  *  - It provides "poor-man's" post-quantum security, against attackers who
  *    will never know the pre-shared key (i.e. no forward secrecy).
  *
- * @par Specification
+ * --tls-crypt uses a tls-auth-style group key, where all servers and clients
+ * share the same group key. --tls-crypt-v2 adds support for client-specific
+ * keys, where all servers share the same client-key encryption key, and each
+ * clients receives a unique client key, both in plaintext and in encrypted
+ * form.  When connecting to a server, the client sends the encrypted key to
+ * the server in the first packet (P_CONTROL_HARD_RESET_CLIENT_V3). The server
+ * then decrypts that key, and both parties can use the same client-specific
+ * key for tls-crypt packets. See doc/tls-crypt-v2.txt for more details.
+ *
+ * @par On-the-wire tls-crypt packet specification
+ * @parblock
  * Control channel encryption is based on the SIV construction [0], to achieve
  * nonce misuse-resistant authenticated encryption:
  *
- * @par
  * \code{.unparsed}
  * msg      = control channel plaintext
  * header   = opcode (1 byte) || session_id (8 bytes) || packet_id (8 bytes)
@@ -57,28 +64,27 @@
  * output   = Header || Tag || Ciph
  * \endcode
  *
- * @par
  * This boils down to the following on-the-wire packet format:
  *
- * @par
  * \code{.unparsed}
  * - opcode - || - session_id - || - packet_id - || auth_tag || * payload *
  * \endcode
  *
- * @par
  * Where
  * <tt>- XXX -</tt> means authenticated, and
  * <tt>* XXX *</tt> means authenticated and encrypted.
+ *
+ * @endparblock
  */
 
 #ifndef TLSCRYPT_H
 #define TLSCRYPT_H
 
-#ifdef ENABLE_CRYPTO
-
+#include "base64.h"
 #include "buffer.h"
 #include "crypto.h"
 #include "session_id.h"
+#include "ssl_common.h"
 
 #define TLS_CRYPT_TAG_SIZE (256/8)
 #define TLS_CRYPT_PID_SIZE (sizeof(packet_id_type) + sizeof(net_time_t))
@@ -88,18 +94,28 @@
 #define TLS_CRYPT_OFF_TAG (TLS_CRYPT_OFF_PID + TLS_CRYPT_PID_SIZE)
 #define TLS_CRYPT_OFF_CT (TLS_CRYPT_OFF_TAG + TLS_CRYPT_TAG_SIZE)
 
+#define TLS_CRYPT_V2_MAX_WKC_LEN (1024)
+#define TLS_CRYPT_V2_CLIENT_KEY_LEN (2048 / 8)
+#define TLS_CRYPT_V2_SERVER_KEY_LEN (sizeof(struct key))
+#define TLS_CRYPT_V2_TAG_SIZE (TLS_CRYPT_TAG_SIZE)
+#define TLS_CRYPT_V2_MAX_METADATA_LEN (unsigned)(TLS_CRYPT_V2_MAX_WKC_LEN \
+                                                 - (TLS_CRYPT_V2_CLIENT_KEY_LEN + TLS_CRYPT_V2_TAG_SIZE \
+                                                    + sizeof(uint16_t)))
+#define TLS_CRYPT_V2_MAX_B64_METADATA_LEN \
+    OPENVPN_BASE64_LENGTH(TLS_CRYPT_V2_MAX_METADATA_LEN - 1)
+
 /**
  * Initialize a key_ctx_bi structure for use with --tls-crypt.
  *
  * @param key           The key context to initialize
- * @param key_file      The file to read the key from (or the inline tag to
- *                      indicate and inline key).
- * @param key_inline    Array containing (zero-terminated) inline key, or NULL
- *                      if not used.
+ * @param key_file      The file to read the key from or the key itself if
+ *                      key_inline is true.
+ * @param key_inline    True if key_file contains an inline key, False
+ *                      otherwise.
  * @param tls_server    Must be set to true is this is a TLS server instance.
  */
 void tls_crypt_init_key(struct key_ctx_bi *key, const char *key_file,
-                        const char *key_inline, bool tls_server);
+                        bool key_inline, bool tls_server);
 
 /**
  * Returns the maximum overhead (in bytes) added to the destination buffer by
@@ -140,8 +156,73 @@
 bool tls_crypt_unwrap(const struct buffer *src, struct buffer *dst,
                       struct crypto_options *opt);
 
-/** @} */
+/**
+ * Initialize a tls-crypt-v2 server key (used to encrypt/decrypt client keys).
+ *
+ * @param key           Key structure to be initialized.  Must be non-NULL.
+ * @parem encrypt       If true, initialize the key structure for encryption,
+ *                      otherwise for decryption.
+ * @param key_file      File path of the key file to load or the key itself if
+ *                      key_inline is true.
+ * @param key_inline    True if key_file contains an inline key, False
+ *                      otherwise.
+ *
+ */
+void tls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt,
+                                  const char *key_file, bool key_inline);
 
-#endif /* ENABLE_CRYPTO */
+/**
+ * Initialize a tls-crypt-v2 client key.
+ *
+ * @param key               Key structure to be initialized with the client
+ *                          key.
+ * @param wrapped_key_buf   Returns buffer containing the wrapped key that will
+ *                          be sent to the server when connecting.  Caller must
+ *                          free this buffer when no longer needed.
+ * @param key_file          File path of the key file to load or the key itself
+ *                          if key_inline is true.
+ * @param key_inline        True if key_file contains an inline key, False
+ *                          otherwise.
+ */
+void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
+                                  struct buffer *wrapped_key_buf,
+                                  const char *key_file, bool key_inline);
+
+/**
+ * Extract a tls-crypt-v2 client key from a P_CONTROL_HARD_RESET_CLIENT_V3
+ * message, and load the key into the supplied tls wrap context.
+ *
+ * @param buf   Buffer containing a received P_CONTROL_HARD_RESET_CLIENT_V3
+ *              message.
+ * @param ctx   tls-wrap context to be initialized with the client key.
+ *
+ * @returns true if a key was successfully extracted.
+ */
+bool tls_crypt_v2_extract_client_key(struct buffer *buf,
+                                     struct tls_wrap_ctx *ctx,
+                                     const struct tls_options *opt);
+
+/**
+ * Generate a tls-crypt-v2 server key, and write to file.
+ *
+ * @param filename          Filename of the server key file to create.
+ */
+void tls_crypt_v2_write_server_key_file(const char *filename);
+
+/**
+ * Generate a tls-crypt-v2 client key, and write to file.
+ *
+ * @param filename          Filename of the client key file to create.
+ * @param b64_metadata      Base64 metadata to be included in the client key.
+ * @param key_file          File path of the server key to use for wrapping the
+ *                          client key or the key itself if key_inline is true.
+ * @param key_inline        True if key_file contains an inline key, False
+ *                          otherwise.
+ */
+void tls_crypt_v2_write_client_key_file(const char *filename,
+                                        const char *b64_metadata,
+                                        const char *key_file, bool key_inline);
+
+/** @} */
 
 #endif /* TLSCRYPT_H */
diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c
index 80eaa2c..1767420 100644
--- a/src/openvpn/tun.c
+++ b/src/openvpn/tun.c
@@ -40,12 +40,13 @@
 #include "tun.h"
 #include "fdmisc.h"
 #include "common.h"
-#include "misc.h"
+#include "run_command.h"
 #include "socket.h"
 #include "manage.h"
 #include "route.h"
 #include "win32.h"
 #include "block_dns.h"
+#include "networking.h"
 
 #include "memdbg.h"
 
@@ -57,6 +58,9 @@
 
 #ifdef _WIN32
 
+const static GUID GUID_DEVCLASS_NET = { 0x4d36e972L, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 } };
+const static GUID GUID_DEVINTERFACE_NET = { 0xcac88484, 0x7515, 0x4c03, { 0x82, 0xe6, 0x71, 0xa8, 0x7a, 0xba, 0xc3, 0x61 } };
+
 /* #define SIMULATE_DHCP_FAILED */       /* simulate bad DHCP negotiation */
 
 #define NI_TEST_FIRST  (1<<0)
@@ -64,14 +68,18 @@
 #define NI_OPTIONS     (1<<2)
 
 static void netsh_ifconfig(const struct tuntap_options *to,
-                           const char *flex_name,
+                           DWORD adapter_index,
                            const in_addr_t ip,
                            const in_addr_t netmask,
                            const unsigned int flags);
 
+static void windows_set_mtu(const int iface_index,
+                            const short family,
+                            const int mtu);
+
 static void netsh_set_dns6_servers(const struct in6_addr *addr_list,
                                    const int addr_len,
-                                   const char *flex_name);
+                                   DWORD adapter_index);
 
 static void netsh_command(const struct argv *a, int n, int msglevel);
 
@@ -82,7 +90,6 @@
 static bool
 do_address_service(const bool add, const short family, const struct tuntap *tt)
 {
-    DWORD len;
     bool ret = false;
     ack_message_t ack;
     struct gc_arena gc = gc_new();
@@ -106,20 +113,23 @@
 
     if (addr.family == AF_INET)
     {
-        addr.address.ipv4.s_addr = tt->local;
-        addr.prefix_len = 32;
+        addr.address.ipv4.s_addr = htonl(tt->local);
+        addr.prefix_len = netmask_to_netbits2(tt->adapter_netmask);
+        msg(D_IFCONFIG, "INET address service: %s %s/%d",
+            add ? "add" : "remove",
+            print_in_addr_t(tt->local, 0, &gc), addr.prefix_len);
     }
     else
     {
         addr.address.ipv6 = tt->local_ipv6;
-        addr.prefix_len = tt->netbits_ipv6;
+        addr.prefix_len = (tt->type == DEV_TYPE_TUN) ? 128 : tt->netbits_ipv6;
+        msg(D_IFCONFIG, "INET6 address service: %s %s/%d",
+            add ? "add" : "remove",
+            print_in6_addr(tt->local_ipv6, 0, &gc), addr.prefix_len);
     }
 
-    if (!WriteFile(pipe, &addr, sizeof(addr), &len, NULL)
-        || !ReadFile(pipe, &ack, sizeof(ack), &len, NULL))
+    if (!send_msg_iservice(pipe, &addr, sizeof(addr), &ack, "TUN"))
     {
-        msg(M_WARN, "TUN: could not talk to service: %s [%lu]",
-            strerror_win32(GetLastError(), &gc), GetLastError());
         goto out;
     }
 
@@ -139,20 +149,77 @@
 }
 
 static bool
-do_dns6_service(bool add, const struct tuntap *tt)
+do_dns_domain_service(bool add, const struct tuntap *tt)
 {
-    DWORD len;
     bool ret = false;
     ack_message_t ack;
     struct gc_arena gc = gc_new();
     HANDLE pipe = tt->options.msg_channel;
-    int addr_len = add ? tt->options.dns6_len : 0;
+
+    if (!tt->options.domain) /* no  domain to add or delete */
+    {
+        return true;
+    }
+
+    /* Use dns_cfg_msg with addr_len = 0 for setting only the DOMAIN */
+    dns_cfg_message_t dns = {
+        .header = {
+            (add ? msg_add_dns_cfg : msg_del_dns_cfg),
+            sizeof(dns_cfg_message_t),
+            0
+        },
+        .iface = { .index = tt->adapter_index, .name = "" },
+        .domains = "",      /* set below */
+        .family = AF_INET,  /* unused */
+        .addr_len = 0       /* add/delete only the domain, not DNS servers */
+    };
+
+    strncpynt(dns.iface.name, tt->actual_name, sizeof(dns.iface.name));
+    strncpynt(dns.domains, tt->options.domain, sizeof(dns.domains));
+    /* truncation of domain name is not checked as it can't happen
+     * with 512 bytes room in dns.domains.
+     */
+
+    msg(D_LOW, "%s dns domain on '%s' (if_index = %d) using service",
+            (add ? "Setting" : "Deleting"), dns.iface.name, dns.iface.index);
+    if (!send_msg_iservice(pipe, &dns, sizeof(dns), &ack, "TUN"))
+    {
+        goto out;
+    }
+
+    if (ack.error_number != NO_ERROR)
+    {
+        msg(M_WARN, "TUN: %s dns domain failed using service: %s [status=%u if_name=%s]",
+            (add ? "adding" : "deleting"), strerror_win32(ack.error_number, &gc),
+            ack.error_number, dns.iface.name);
+        goto out;
+    }
+
+    msg(M_INFO, "DNS domain %s using service", (add ? "set" : "deleted"));
+    ret = true;
+
+out:
+    gc_free(&gc);
+    return ret;
+}
+
+static bool
+do_dns_service(bool add, const short family, const struct tuntap *tt)
+{
+    bool ret = false;
+    ack_message_t ack;
+    struct gc_arena gc = gc_new();
+    HANDLE pipe = tt->options.msg_channel;
+    int len = family == AF_INET6 ? tt->options.dns6_len : tt->options.dns_len;
+    int addr_len = add ? len : 0;
+    const char *ip_proto_name = family == AF_INET6 ? "IPv6" : "IPv4";
 
     if (addr_len == 0 && add) /* no addresses to add */
     {
         return true;
     }
 
+    /* Use dns_cfg_msg with domain = "" for setting only the DNS servers */
     dns_cfg_message_t dns = {
         .header = {
             (add ? msg_add_dns_cfg : msg_del_dns_cfg),
@@ -161,7 +228,7 @@
         },
         .iface = { .index = tt->adapter_index, .name = "" },
         .domains = "",
-        .family = AF_INET6,
+        .family = family,
         .addr_len = addr_len
     };
 
@@ -173,35 +240,39 @@
     {
         addr_len = _countof(dns.addr);
         dns.addr_len = addr_len;
-        msg(M_WARN, "Number of IPv6 DNS addresses sent to service truncated to %d",
-            addr_len);
+        msg(M_WARN, "Number of %s DNS addresses sent to service truncated to %d",
+            ip_proto_name, addr_len);
     }
 
     for (int i = 0; i < addr_len; ++i)
     {
-        dns.addr[i].ipv6 = tt->options.dns6[i];
+        if (family == AF_INET6)
+        {
+            dns.addr[i].ipv6 = tt->options.dns6[i];
+        }
+        else
+        {
+            dns.addr[i].ipv4.s_addr = htonl(tt->options.dns[i]);
+        }
     }
 
-    msg(D_LOW, "%s IPv6 dns servers on '%s' (if_index = %d) using service",
-        (add ? "Setting" : "Deleting"), dns.iface.name, dns.iface.index);
+    msg(D_LOW, "%s %s dns servers on '%s' (if_index = %d) using service",
+        (add ? "Setting" : "Deleting"), ip_proto_name, dns.iface.name, dns.iface.index);
 
-    if (!WriteFile(pipe, &dns, sizeof(dns), &len, NULL)
-        || !ReadFile(pipe, &ack, sizeof(ack), &len, NULL))
+    if (!send_msg_iservice(pipe, &dns, sizeof(dns), &ack, "TUN"))
     {
-        msg(M_WARN, "TUN: could not talk to service: %s [%lu]",
-            strerror_win32(GetLastError(), &gc), GetLastError());
         goto out;
     }
 
     if (ack.error_number != NO_ERROR)
     {
-        msg(M_WARN, "TUN: %s IPv6 dns failed using service: %s [status=%u if_name=%s]",
-            (add ? "adding" : "deleting"), strerror_win32(ack.error_number, &gc),
+        msg(M_WARN, "TUN: %s %s dns failed using service: %s [status=%u if_name=%s]",
+            (add ? "adding" : "deleting"), ip_proto_name, strerror_win32(ack.error_number, &gc),
             ack.error_number, dns.iface.name);
         goto out;
     }
 
-    msg(M_INFO, "IPv6 dns servers %s using service", (add ? "set" : "deleted"));
+    msg(M_INFO, "%s dns servers %s using service", ip_proto_name, (add ? "set" : "deleted"));
     ret = true;
 
 out:
@@ -209,6 +280,52 @@
     return ret;
 }
 
+static bool
+do_set_mtu_service(const struct tuntap *tt, const short family, const int mtu)
+{
+    DWORD len;
+    bool ret = false;
+    ack_message_t ack;
+    struct gc_arena gc = gc_new();
+    HANDLE pipe = tt->options.msg_channel;
+    const char *family_name = (family == AF_INET6) ? "IPv6" : "IPv4";
+    set_mtu_message_t mtu_msg = {
+        .header = {
+            msg_set_mtu,
+            sizeof(set_mtu_message_t),
+            0
+        },
+        .iface = {.index = tt->adapter_index},
+        .mtu = mtu,
+        .family = family
+    };
+    strncpynt(mtu_msg.iface.name, tt->actual_name, sizeof(mtu_msg.iface.name));
+    if (family == AF_INET6 && mtu < 1280)
+    {
+        msg(M_INFO, "NOTE: IPv6 interface MTU < 1280 conflicts with IETF standards and might not work");
+    }
+
+    if (!send_msg_iservice(pipe, &mtu_msg, sizeof(mtu_msg), &ack, "Set_mtu"))
+    {
+        goto out;
+    }
+
+    if (ack.error_number != NO_ERROR)
+    {
+        msg(M_NONFATAL, "TUN: setting %s mtu using service failed: %s [status=%u if_index=%d]",
+            family_name, strerror_win32(ack.error_number, &gc), ack.error_number, mtu_msg.iface.index);
+    }
+    else
+    {
+        msg(M_INFO, "%s MTU set to %d on interface %d using service", family_name, mtu, mtu_msg.iface.index);
+        ret = true;
+    }
+
+out:
+    gc_free(&gc);
+    return ret;
+}
+
 #endif /* ifdef _WIN32 */
 
 #ifdef TARGET_SOLARIS
@@ -342,16 +459,6 @@
 }
 
 /*
- * For TAP-style devices, generate a broadcast address.
- */
-static in_addr_t
-generate_ifconfig_broadcast_addr(in_addr_t local,
-                                 in_addr_t netmask)
-{
-    return local | ~netmask;
-}
-
-/*
  * Check that --local and --remote addresses do not
  * clash with ifconfig addresses or subnet.
  */
@@ -460,13 +567,13 @@
 }
 
 void
-warn_on_use_of_common_subnets(void)
+warn_on_use_of_common_subnets(openvpn_net_ctx_t *ctx)
 {
     struct gc_arena gc = gc_new();
     struct route_gateway_info rgi;
     const int needed = (RGI_ADDR_DEFINED|RGI_NETMASK_DEFINED);
 
-    get_default_gateway(&rgi);
+    get_default_gateway(&rgi, ctx);
     if ((rgi.flags & needed) == needed)
     {
         const in_addr_t lan_network = rgi.gateway.addr & rgi.gateway.netmask;
@@ -561,8 +668,8 @@
     bool tun = false;
 
     if (tt->type == DEV_TYPE_TAP
-          || (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
-          || tt->type == DEV_TYPE_NULL )
+        || (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
+        || tt->type == DEV_TYPE_NULL)
     {
         tun = false;
     }
@@ -602,9 +709,7 @@
         }
         else
         {
-            const char *ifconfig_broadcast = print_in_addr_t(tt->broadcast, 0, &gc);
             setenv_str(es, "ifconfig_netmask", ifconfig_remote_netmask);
-            setenv_str(es, "ifconfig_broadcast", ifconfig_broadcast);
         }
     }
 
@@ -639,7 +744,8 @@
          struct addrinfo *local_public,
          struct addrinfo *remote_public,
          const bool strict_warn,
-         struct env_set *es)
+         struct env_set *es,
+         openvpn_net_ctx_t *ctx)
 {
     struct gc_arena gc = gc_new();
     struct tuntap *tt;
@@ -730,14 +836,6 @@
             }
         }
 
-        /*
-         * If TAP-style interface, generate broadcast address.
-         */
-        if (!tun)
-        {
-            tt->broadcast = generate_ifconfig_broadcast_addr(tt->local, tt->remote_netmask);
-        }
-
 #ifdef _WIN32
         /*
          * Make sure that both ifconfig addresses are part of the
@@ -798,10 +896,40 @@
 #ifdef _WIN32
     overlapped_io_init(&tt->reads, frame, FALSE, true);
     overlapped_io_init(&tt->writes, frame, TRUE, true);
-    tt->rw_handle.read = tt->reads.overlapped.hEvent;
-    tt->rw_handle.write = tt->writes.overlapped.hEvent;
     tt->adapter_index = TUN_ADAPTER_INDEX_INVALID;
-#endif
+
+    if (tt->windows_driver == WINDOWS_DRIVER_WINTUN)
+    {
+        tt->wintun_send_ring_handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,
+                                                        PAGE_READWRITE,
+                                                        0,
+                                                        sizeof(struct tun_ring),
+                                                        NULL);
+        tt->wintun_receive_ring_handle = CreateFileMapping(INVALID_HANDLE_VALUE,
+                                                           NULL,
+                                                           PAGE_READWRITE,
+                                                           0,
+                                                           sizeof(struct tun_ring),
+                                                           NULL);
+        if ((tt->wintun_send_ring_handle == NULL) || (tt->wintun_receive_ring_handle == NULL))
+        {
+            msg(M_FATAL, "Cannot allocate memory for ring buffer");
+        }
+
+        tt->rw_handle.read = CreateEvent(NULL, FALSE, FALSE, NULL);
+        tt->rw_handle.write = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+        if ((tt->rw_handle.read == NULL) || (tt->rw_handle.write == NULL))
+        {
+            msg(M_FATAL, "Cannot create events for ring buffer");
+        }
+    }
+    else
+    {
+        tt->rw_handle.read = tt->reads.overlapped.hEvent;
+        tt->rw_handle.write = tt->writes.overlapped.hEvent;
+    }
+#endif /* ifdef _WIN32 */
 }
 
 #if defined(_WIN32)    \
@@ -812,7 +940,7 @@
  * an extra call to "route add..."
  * -> helper function to simplify code below
  */
-void
+static void
 add_route_connected_v6_net(struct tuntap *tt,
                            const struct env_set *es)
 {
@@ -824,12 +952,11 @@
     r6.gateway = tt->local_ipv6;
     r6.metric  = 0;                     /* connected route */
     r6.flags   = RT_DEFINED | RT_METRIC_DEFINED;
-    add_route_ipv6(&r6, tt, 0, es);
+    add_route_ipv6(&r6, tt, 0, es, NULL);
 }
 
 void
-delete_route_connected_v6_net(struct tuntap *tt,
-                              const struct env_set *es)
+delete_route_connected_v6_net(const struct tuntap *tt)
 {
     struct route_ipv6 r6;
 
@@ -840,7 +967,7 @@
     r6.metric  = 0;                     /* connected route */
     r6.flags   = RT_DEFINED | RT_ADDED | RT_METRIC_DEFINED;
     route_ipv6_clear_host_bits(&r6);
-    delete_route_ipv6(&r6, tt, 0, es);
+    delete_route_ipv6(&r6, tt, 0, NULL, NULL);
 }
 #endif /* if defined(_WIN32) || defined(TARGET_DARWIN) || defined(TARGET_NETBSD) || defined(TARGET_OPENBSD) */
 
@@ -871,739 +998,612 @@
 }
 #endif
 
-/* execute the ifconfig command through the shell */
-void
-do_ifconfig(struct tuntap *tt,
-            const char *actual,     /* actual device name */
-            int tun_mtu,
-            const struct env_set *es)
+/**
+ * do_ifconfig_ipv6 - perform platform specific ifconfig6 commands
+ *
+ * @param tt        the tuntap interface context
+ * @param ifname    the human readable interface name
+ * @param mtu       the MTU value to set the interface to
+ * @param es        the environment to be used when executing the commands
+ * @param ctx       the networking API opaque context
+ */
+static void
+do_ifconfig_ipv6(struct tuntap *tt, const char *ifname, int tun_mtu,
+                 const struct env_set *es, openvpn_net_ctx_t *ctx)
 {
+#if !defined(TARGET_LINUX)
+    struct argv argv = argv_new();
     struct gc_arena gc = gc_new();
-
-    if (tt->did_ifconfig_setup)
-    {
-        bool tun = false;
-        const char *ifconfig_local = NULL;
-        const char *ifconfig_remote_netmask = NULL;
-        const char *ifconfig_broadcast = NULL;
-        const char *ifconfig_ipv6_local = NULL;
-        bool do_ipv6 = false;
-        struct argv argv = argv_new();
-
-        msg( D_LOW, "do_ifconfig, tt->did_ifconfig_ipv6_setup=%d",
-             tt->did_ifconfig_ipv6_setup );
-
-        /*
-         * We only handle TUN/TAP devices here, not --dev null devices.
-         */
-        tun = is_tun_p2p(tt);
-
-        /*
-         * Set ifconfig parameters
-         */
-        ifconfig_local = print_in_addr_t(tt->local, 0, &gc);
-        ifconfig_remote_netmask = print_in_addr_t(tt->remote_netmask, 0, &gc);
-
-        if (tt->did_ifconfig_ipv6_setup)
-        {
-            ifconfig_ipv6_local = print_in6_addr(tt->local_ipv6, 0, &gc);
-            do_ipv6 = true;
-        }
-
-        /*
-         * If TAP-style device, generate broadcast address.
-         */
-        if (!tun)
-        {
-            ifconfig_broadcast = print_in_addr_t(tt->broadcast, 0, &gc);
-        }
-
-#ifdef ENABLE_MANAGEMENT
-        if (management)
-        {
-            management_set_state(management,
-                                 OPENVPN_STATE_ASSIGN_IP,
-                                 NULL,
-                                 &tt->local,
-                                 &tt->local_ipv6,
-                                 NULL,
-                                 NULL);
-        }
+    const char *ifconfig_ipv6_local = print_in6_addr(tt->local_ipv6, 0, &gc);
 #endif
 
+#if defined(TARGET_LINUX)
+    if (net_iface_mtu_set(ctx, ifname, tun_mtu) < 0)
+    {
+        msg(M_FATAL, "Linux can't set mtu (%d) on %s", tun_mtu, ifname);
+    }
+
+    if (net_iface_up(ctx, ifname, true) < 0)
+    {
+        msg(M_FATAL, "Linux can't bring %s up", ifname);
+    }
+
+    if (net_addr_v6_add(ctx, ifname, &tt->local_ipv6,
+                        tt->netbits_ipv6) < 0)
+    {
+        msg(M_FATAL, "Linux can't add IPv6 to interface %s", ifname);
+    }
+#elif defined(TARGET_ANDROID)
+    char out6[64];
+
+    openvpn_snprintf(out6, sizeof(out6), "%s/%d %d",
+                     ifconfig_ipv6_local, tt->netbits_ipv6, tun_mtu);
+    management_android_control(management, "IFCONFIG6", out6);
+#elif defined(TARGET_SOLARIS)
+    argv_printf(&argv, "%s %s inet6 unplumb", IFCONFIG_PATH, ifname);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, es, 0, NULL);
+
+    if (tt->type == DEV_TYPE_TUN)
+    {
+        const char *ifconfig_ipv6_remote = print_in6_addr(tt->remote_ipv6, 0, &gc);
+
+        argv_printf(&argv, "%s %s inet6 plumb %s/%d %s mtu %d up",
+                    IFCONFIG_PATH, ifname, ifconfig_ipv6_local,
+                    tt->netbits_ipv6, ifconfig_ipv6_remote, tun_mtu);
+    }
+    else /* tap mode */
+    {
+        /* base IPv6 tap interface needs to be brought up first */
+        argv_printf(&argv, "%s %s inet6 plumb up", IFCONFIG_PATH, ifname);
+        argv_msg(M_INFO, &argv);
+
+        if (!openvpn_execve_check(&argv, es, 0,
+                                  "Solaris ifconfig IPv6 (prepare) failed"))
+        {
+            solaris_error_close(tt, es, ifname, true);
+        }
+
+        /* we might need to do "ifconfig %s inet6 auto-dhcp drop"
+         * after the system has noticed the interface and fired up
+         * the DHCPv6 client - but this takes quite a while, and the
+         * server will ignore the DHCPv6 packets anyway.  So we don't.
+         */
+
+        /* static IPv6 addresses need to go to a subinterface (tap0:1)
+         * and we cannot set an mtu here (must go to the "parent")
+         */
+        argv_printf(&argv, "%s %s inet6 addif %s/%d up", IFCONFIG_PATH,
+                    ifname, ifconfig_ipv6_local, tt->netbits_ipv6 );
+    }
+    argv_msg(M_INFO, &argv);
+
+    if (!openvpn_execve_check(&argv, es, 0, "Solaris ifconfig IPv6 failed"))
+    {
+        solaris_error_close(tt, es, ifname, true);
+    }
+
+    if (tt->type != DEV_TYPE_TUN)
+    {
+        argv_printf(&argv, "%s %s inet6 mtu %d", IFCONFIG_PATH,
+                    ifname, tun_mtu);
+        argv_msg(M_INFO, &argv);
+        openvpn_execve_check(&argv, es, 0, "Solaris ifconfig IPv6 mtu failed");
+    }
+#elif defined(TARGET_OPENBSD) || defined(TARGET_NETBSD) \
+    || defined(TARGET_DARWIN) || defined(TARGET_FREEBSD) \
+    || defined(TARGET_DRAGONFLY)
+    argv_printf(&argv, "%s %s inet6 %s/%d mtu %d up", IFCONFIG_PATH, ifname,
+                ifconfig_ipv6_local, tt->netbits_ipv6, tun_mtu);
+    argv_msg(M_INFO, &argv);
+
+    openvpn_execve_check(&argv, es, S_FATAL,
+                         "generic BSD ifconfig inet6 failed");
+
+#if defined(TARGET_FREEBSD) && __FreeBSD_version >= 1200000
+    /* On FreeBSD 12 and up, there is ipv6_activate_all_interfaces="YES"
+     * in rc.conf, which is not set by default.  If it is *not* set,
+     * "all new interfaces that are not already up" are configured by
+     * devd + /etc/pccard_ether as "inet6 ifdisabled".
+     *
+     * The "is this interface already up?" test is a non-zero time window
+     * which we manage to hit with our ifconfig often enough to cause
+     * frequent fails in the openvpn test environment.
+     *
+     * Thus: assume that the system might interfere, wait for things to
+     * settle (it's a very short time window), and remove -ifdisable again.
+     *
+     * See: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=248172
+     */
+    sleep(1);
+    argv_printf(&argv, "%s %s inet6 -ifdisabled", IFCONFIG_PATH, ifname);
+    argv_msg(M_INFO, &argv);
+
+    openvpn_execve_check(&argv, es, S_FATAL,
+                         "FreeBSD BSD 'ifconfig inet6 -ifdisabled' failed");
+#endif
+
+#if defined(TARGET_OPENBSD) || defined(TARGET_NETBSD) \
+    || defined(TARGET_DARWIN)
+    /* and, hooray, we explicitly need to add a route... */
+    add_route_connected_v6_net(tt, es);
+#endif
+#elif defined(TARGET_AIX)
+    argv_printf(&argv, "%s %s inet6 %s/%d mtu %d up", IFCONFIG_PATH, ifname,
+                ifconfig_ipv6_local, tt->netbits_ipv6, tun_mtu);
+    argv_msg(M_INFO, &argv);
+
+    /* AIX ifconfig will complain if it can't find ODM path in env */
+    es = env_set_create(NULL);
+    env_set_add(es, "ODMDIR=/etc/objrepos");
+
+    openvpn_execve_check(&argv, es, S_FATAL,
+                         "generic BSD ifconfig inet6 failed");
+
+    env_set_destroy(es);
+#elif defined (_WIN32)
+    if (tt->options.ip_win32_type == IPW32_SET_MANUAL)
+    {
+        msg(M_INFO, "******** NOTE:  Please manually set the v6 IP of '%s' to %s (if it is not already set)",
+            ifname, ifconfig_ipv6_local);
+    }
+    else if (tt->options.msg_channel)
+    {
+        do_address_service(true, AF_INET6, tt);
+        if (tt->type == DEV_TYPE_TUN)
+        {
+            add_route_connected_v6_net(tt, es);
+        }
+        do_dns_service(true, AF_INET6, tt);
+        do_set_mtu_service(tt, AF_INET6, tun_mtu);
+        /* If IPv4 is not enabled, set DNS domain here */
+        if (!tt->did_ifconfig_setup)
+        {
+           do_dns_domain_service(true, tt);
+        }
+    }
+    else
+    {
+        /* example: netsh interface ipv6 set address 42
+         *                  2001:608:8003::d/bits store=active
+         */
+
+        /* in TUN mode, we only simulate a subnet, so the interface
+         * is configured with /128 + a route to fe80::8.  In TAP mode,
+         * the correct netbits must be set, and no on-link route
+         */
+        int netbits = (tt->type == DEV_TYPE_TUN) ? 128 : tt->netbits_ipv6;
+
+        argv_printf(&argv, "%s%s interface ipv6 set address %lu %s/%d store=active",
+                    get_win_sys_path(), NETSH_PATH_SUFFIX, tt->adapter_index,
+                    ifconfig_ipv6_local, netbits);
+        netsh_command(&argv, 4, M_FATAL);
+        if (tt->type == DEV_TYPE_TUN)
+        {
+            add_route_connected_v6_net(tt, es);
+        }
+        /* set ipv6 dns servers if any are specified */
+        netsh_set_dns6_servers(tt->options.dns6, tt->options.dns6_len, tt->adapter_index);
+        windows_set_mtu(tt->adapter_index, AF_INET6, tun_mtu);
+    }
+#else /* platforms we have no IPv6 code for */
+    msg(M_FATAL, "Sorry, but I don't know how to do IPv6 'ifconfig' commands on this operating system.  You should ifconfig your TUN/TAP device manually or use an --up script.");
+#endif /* outer "if defined(TARGET_xxx)" conditional */
+
+#if !defined(TARGET_LINUX)
+    gc_free(&gc);
+    argv_free(&argv);
+#endif
+}
+
+/**
+ * do_ifconfig_ipv4 - perform platform specific ifconfig commands
+ *
+ * @param tt        the tuntap interface context
+ * @param ifname    the human readable interface name
+ * @param mtu       the MTU value to set the interface to
+ * @param es        the environment to be used when executing the commands
+ * @param ctx       the networking API opaque context
+ */
+static void
+do_ifconfig_ipv4(struct tuntap *tt, const char *ifname, int tun_mtu,
+                 const struct env_set *es, openvpn_net_ctx_t *ctx)
+{
+    /*
+     * We only handle TUN/TAP devices here, not --dev null devices.
+     */
+    bool tun = is_tun_p2p(tt);
+
+#if !defined(TARGET_LINUX)
+    const char *ifconfig_local = NULL;
+    const char *ifconfig_remote_netmask = NULL;
+    struct argv argv = argv_new();
+    struct gc_arena gc = gc_new();
+
+    /*
+     * Set ifconfig parameters
+     */
+    ifconfig_local = print_in_addr_t(tt->local, 0, &gc);
+    ifconfig_remote_netmask = print_in_addr_t(tt->remote_netmask, 0, &gc);
+#endif
 
 #if defined(TARGET_LINUX)
-#ifdef ENABLE_IPROUTE
-        /*
-         * Set the MTU for the device
-         */
-        argv_printf(&argv,
-                    "%s link set dev %s up mtu %d",
-                    iproute_path,
-                    actual,
-                    tun_mtu
-                    );
-        argv_msg(M_INFO, &argv);
-        openvpn_execve_check(&argv, es, S_FATAL, "Linux ip link set failed");
+    if (net_iface_mtu_set(ctx, ifname, tun_mtu) < 0)
+    {
+        msg(M_FATAL, "Linux can't set mtu (%d) on %s", tun_mtu, ifname);
+    }
 
-        if (tun)
-        {
+    if (net_iface_up(ctx, ifname, true) < 0)
+    {
+        msg(M_FATAL, "Linux can't bring %s up", ifname);
+    }
 
-            /*
-             * Set the address for the device
-             */
-            argv_printf(&argv,
-                        "%s addr add dev %s local %s peer %s",
-                        iproute_path,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask
-                        );
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, es, S_FATAL, "Linux ip addr add failed");
-        }
-        else
+    if (tun)
+    {
+        if (net_addr_ptp_v4_add(ctx, ifname, &tt->local,
+                                &tt->remote_netmask) < 0)
         {
-            argv_printf(&argv,
-                        "%s addr add dev %s %s/%d broadcast %s",
-                        iproute_path,
-                        actual,
-                        ifconfig_local,
-                        netmask_to_netbits2(tt->remote_netmask),
-                        ifconfig_broadcast
-                        );
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, es, S_FATAL, "Linux ip addr add failed");
+            msg(M_FATAL, "Linux can't add IP to interface %s", ifname);
         }
-        if (do_ipv6)
+    }
+    else
+    {
+        if (net_addr_v4_add(ctx, ifname, &tt->local,
+                            netmask_to_netbits2(tt->remote_netmask)) < 0)
         {
-            argv_printf( &argv,
-                         "%s -6 addr add %s/%d dev %s",
-                         iproute_path,
-                         ifconfig_ipv6_local,
-                         tt->netbits_ipv6,
-                         actual
-                         );
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, es, S_FATAL, "Linux ip -6 addr add failed");
+            msg(M_FATAL, "Linux can't add IP to interface %s", ifname);
         }
-        tt->did_ifconfig = true;
-#else  /* ifdef ENABLE_IPROUTE */
-        if (tun)
-        {
-            argv_printf(&argv,
-                        "%s %s %s pointopoint %s mtu %d",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu
-                        );
-        }
-        else
-        {
-            argv_printf(&argv,
-                        "%s %s %s netmask %s mtu %d broadcast %s",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu,
-                        ifconfig_broadcast
-                        );
-        }
-        argv_msg(M_INFO, &argv);
-        openvpn_execve_check(&argv, es, S_FATAL, "Linux ifconfig failed");
-        if (do_ipv6)
-        {
-            argv_printf(&argv,
-                        "%s %s add %s/%d",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_ipv6_local,
-                        tt->netbits_ipv6
-                        );
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, es, S_FATAL, "Linux ifconfig inet6 failed");
-        }
-        tt->did_ifconfig = true;
-
-#endif /*ENABLE_IPROUTE*/
+    }
 #elif defined(TARGET_ANDROID)
+    char out[64];
 
-        if (do_ipv6)
-        {
-            struct buffer out6 = alloc_buf_gc(64, &gc);
-            buf_printf(&out6, "%s/%d", ifconfig_ipv6_local,tt->netbits_ipv6);
-            management_android_control(management, "IFCONFIG6",buf_bptr(&out6));
-        }
+    char *top;
+    switch (tt->topology)
+    {
+        case TOP_NET30:
+            top = "net30";
+            break;
 
-        struct buffer out = alloc_buf_gc(64, &gc);
+        case TOP_P2P:
+            top = "p2p";
+            break;
 
-        char *top;
-        switch (tt->topology)
-        {
-            case TOP_NET30:
-                top = "net30";
-                break;
+        case TOP_SUBNET:
+            top = "subnet";
+            break;
 
-            case TOP_P2P:
-                top = "p2p";
-                break;
+        default:
+            top = "undef";
+    }
 
-            case TOP_SUBNET:
-                top = "subnet";
-                break;
-
-            default:
-                top = "undef";
-        }
-
-        buf_printf(&out, "%s %s %d %s", ifconfig_local, ifconfig_remote_netmask, tun_mtu, top);
-        management_android_control(management, "IFCONFIG", buf_bptr(&out));
+    openvpn_snprintf(out, sizeof(out), "%s %s %d %s", ifconfig_local,
+                     ifconfig_remote_netmask, tun_mtu, top);
+    management_android_control(management, "IFCONFIG", out);
 
 #elif defined(TARGET_SOLARIS)
-        /* Solaris 2.6 (and 7?) cannot set all parameters in one go...
-         * example:
-         *    ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 up
-         *    ifconfig tun2 netmask 255.255.255.255
-         */
-        if (tun)
-        {
-            argv_printf(&argv,
-                        "%s %s %s %s mtu %d up",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu
-                        );
-
-            argv_msg(M_INFO, &argv);
-            if (!openvpn_execve_check(&argv, es, 0, "Solaris ifconfig phase-1 failed"))
-            {
-                solaris_error_close(tt, es, actual, false);
-            }
-
-            argv_printf(&argv,
-                        "%s %s netmask 255.255.255.255",
-                        IFCONFIG_PATH,
-                        actual
-                        );
-        }
-        else if (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
-        {
-            argv_printf(&argv,
-                        "%s %s %s %s netmask %s mtu %d up",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu
-                        );
-        }
-        else
-        {
-            argv_printf(&argv,
-                        " %s %s %s netmask %s broadcast + up",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask
-                        );
-        }
+    /* Solaris 2.6 (and 7?) cannot set all parameters in one go...
+     * example:
+     *    ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 up
+     *    ifconfig tun2 netmask 255.255.255.255
+     */
+    if (tun)
+    {
+        argv_printf(&argv, "%s %s %s %s mtu %d up", IFCONFIG_PATH, ifname,
+                    ifconfig_local, ifconfig_remote_netmask, tun_mtu);
 
         argv_msg(M_INFO, &argv);
-        if (!openvpn_execve_check(&argv, es, 0, "Solaris ifconfig phase-2 failed"))
+        if (!openvpn_execve_check(&argv, es, 0, "Solaris ifconfig phase-1 failed"))
         {
-            solaris_error_close(tt, es, actual, false);
+            solaris_error_close(tt, es, ifname, false);
         }
 
-        if (do_ipv6)
-        {
-            argv_printf(&argv, "%s %s inet6 unplumb",
-                        IFCONFIG_PATH, actual );
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, es, 0, NULL);
+        argv_printf(&argv, "%s %s netmask 255.255.255.255", IFCONFIG_PATH,
+                    ifname);
+    }
+    else if (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
+    {
+        argv_printf(&argv, "%s %s %s %s netmask %s mtu %d up", IFCONFIG_PATH,
+                    ifname, ifconfig_local, ifconfig_local,
+                    ifconfig_remote_netmask, tun_mtu);
+    }
+    else
+    {
+        argv_printf(&argv, "%s %s %s netmask %s up",
+                    IFCONFIG_PATH, ifname, ifconfig_local,
+                    ifconfig_remote_netmask);
+    }
 
-            if (tt->type == DEV_TYPE_TUN)
-            {
-                const char *ifconfig_ipv6_remote =
-                    print_in6_addr(tt->remote_ipv6, 0, &gc);
+    argv_msg(M_INFO, &argv);
+    if (!openvpn_execve_check(&argv, es, 0, "Solaris ifconfig phase-2 failed"))
+    {
+        solaris_error_close(tt, es, ifname, false);
+    }
 
-                argv_printf(&argv,
-                            "%s %s inet6 plumb %s/%d %s up",
-                            IFCONFIG_PATH,
-                            actual,
-                            ifconfig_ipv6_local,
-                            tt->netbits_ipv6,
-                            ifconfig_ipv6_remote
-                            );
-            }
-            else                                        /* tap mode */
-            {
-                /* base IPv6 tap interface needs to be brought up first
-                 */
-                argv_printf(&argv, "%s %s inet6 plumb up",
-                            IFCONFIG_PATH, actual );
-                argv_msg(M_INFO, &argv);
-                if (!openvpn_execve_check(&argv, es, 0, "Solaris ifconfig IPv6 (prepare) failed"))
-                {
-                    solaris_error_close(tt, es, actual, true);
-                }
-
-                /* we might need to do "ifconfig %s inet6 auto-dhcp drop"
-                 * after the system has noticed the interface and fired up
-                 * the DHCPv6 client - but this takes quite a while, and the
-                 * server will ignore the DHCPv6 packets anyway.  So we don't.
-                 */
-
-                /* static IPv6 addresses need to go to a subinterface (tap0:1)
-                 */
-                argv_printf(&argv,
-                            "%s %s inet6 addif %s/%d up",
-                            IFCONFIG_PATH, actual,
-                            ifconfig_ipv6_local, tt->netbits_ipv6 );
-            }
-            argv_msg(M_INFO, &argv);
-            if (!openvpn_execve_check(&argv, es, 0, "Solaris ifconfig IPv6 failed"))
-            {
-                solaris_error_close(tt, es, actual, true);
-            }
-        }
-
-        if (!tun && tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
-        {
-            /* Add a network route for the local tun interface */
-            struct route_ipv4 r;
-            CLEAR(r);
-            r.flags = RT_DEFINED | RT_METRIC_DEFINED;
-            r.network = tt->local & tt->remote_netmask;
-            r.netmask = tt->remote_netmask;
-            r.gateway = tt->local;
-            r.metric = 0;
-            add_route(&r, tt, 0, NULL, es);
-        }
-
-        tt->did_ifconfig = true;
+    if (!tun && tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
+    {
+        /* Add a network route for the local tun interface */
+        struct route_ipv4 r;
+        CLEAR(r);
+        r.flags = RT_DEFINED | RT_METRIC_DEFINED;
+        r.network = tt->local & tt->remote_netmask;
+        r.netmask = tt->remote_netmask;
+        r.gateway = tt->local;
+        r.metric = 0;
+        add_route(&r, tt, 0, NULL, es, NULL);
+    }
 
 #elif defined(TARGET_OPENBSD)
 
-        in_addr_t remote_end;           /* for "virtual" subnet topology */
+    in_addr_t remote_end;           /* for "virtual" subnet topology */
 
-        /*
-         * On OpenBSD, tun interfaces are persistent if created with
-         * "ifconfig tunX create", and auto-destroyed if created by
-         * opening "/dev/tunX" (so we just use the /dev/tunX)
-         */
+    /*
+     * On OpenBSD, tun interfaces are persistent if created with
+     * "ifconfig tunX create", and auto-destroyed if created by
+     * opening "/dev/tunX" (so we just use the /dev/tunX)
+     */
 
-        /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */
-        if (tun)
-        {
-            argv_printf(&argv,
-                        "%s %s %s %s mtu %d netmask 255.255.255.255 up -link0",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu
-                        );
-        }
-        else if (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
-        {
-            remote_end = create_arbitrary_remote( tt );
-            argv_printf(&argv,
-                        "%s %s %s %s mtu %d netmask %s up -link0",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        print_in_addr_t(remote_end, 0, &gc),
-                        tun_mtu,
-                        ifconfig_remote_netmask
-                        );
-        }
-        else
-        {
-            argv_printf(&argv,
-                        "%s %s %s netmask %s mtu %d broadcast %s link0",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu,
-                        ifconfig_broadcast
-                        );
-        }
-        argv_msg(M_INFO, &argv);
-        openvpn_execve_check(&argv, es, S_FATAL, "OpenBSD ifconfig failed");
+    /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */
+    if (tun)
+    {
+        argv_printf(&argv,
+                    "%s %s %s %s mtu %d netmask 255.255.255.255 up -link0",
+                    IFCONFIG_PATH, ifname, ifconfig_local,
+                    ifconfig_remote_netmask, tun_mtu);
+    }
+    else if (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
+    {
+        remote_end = create_arbitrary_remote( tt );
+        argv_printf(&argv, "%s %s %s %s mtu %d netmask %s up -link0",
+                    IFCONFIG_PATH, ifname, ifconfig_local,
+                    print_in_addr_t(remote_end, 0, &gc), tun_mtu,
+                    ifconfig_remote_netmask);
+    }
+    else
+    {
+        argv_printf(&argv, "%s %s %s netmask %s mtu %d link0",
+                    IFCONFIG_PATH, ifname, ifconfig_local,
+                    ifconfig_remote_netmask, tun_mtu);
+    }
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, es, S_FATAL, "OpenBSD ifconfig failed");
 
-        /* Add a network route for the local tun interface */
-        if (!tun && tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
-        {
-            struct route_ipv4 r;
-            CLEAR(r);
-            r.flags = RT_DEFINED;
-            r.network = tt->local & tt->remote_netmask;
-            r.netmask = tt->remote_netmask;
-            r.gateway = remote_end;
-            add_route(&r, tt, 0, NULL, es);
-        }
-
-        if (do_ipv6)
-        {
-            argv_printf(&argv,
-                        "%s %s inet6 %s/%d",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_ipv6_local,
-                        tt->netbits_ipv6
-                        );
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, es, S_FATAL, "OpenBSD ifconfig inet6 failed");
-
-            /* and, hooray, we explicitely need to add a route... */
-            add_route_connected_v6_net(tt, es);
-        }
-        tt->did_ifconfig = true;
+    /* Add a network route for the local tun interface */
+    if (!tun && tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
+    {
+        struct route_ipv4 r;
+        CLEAR(r);
+        r.flags = RT_DEFINED;
+        r.network = tt->local & tt->remote_netmask;
+        r.netmask = tt->remote_netmask;
+        r.gateway = remote_end;
+        add_route(&r, tt, 0, NULL, es, NULL);
+    }
 
 #elif defined(TARGET_NETBSD)
+    in_addr_t remote_end;           /* for "virtual" subnet topology */
 
-        in_addr_t remote_end;           /* for "virtual" subnet topology */
+    if (tun)
+    {
+        argv_printf(&argv, "%s %s %s %s mtu %d netmask 255.255.255.255 up",
+                    IFCONFIG_PATH, ifname, ifconfig_local,
+                    ifconfig_remote_netmask, tun_mtu);
+    }
+    else if (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
+    {
+        remote_end = create_arbitrary_remote(tt);
+        argv_printf(&argv, "%s %s %s %s mtu %d netmask %s up", IFCONFIG_PATH,
+                    ifname, ifconfig_local, print_in_addr_t(remote_end, 0, &gc),
+                    tun_mtu, ifconfig_remote_netmask);
+    }
+    else
+    {
+        /*
+         * NetBSD has distinct tun and tap devices
+         * so we don't need the "link0" extra parameter to specify we want to do
+         * tunneling at the ethernet level
+         */
+        argv_printf(&argv, "%s %s %s netmask %s mtu %d",
+                    IFCONFIG_PATH, ifname, ifconfig_local,
+                    ifconfig_remote_netmask, tun_mtu);
+    }
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, es, S_FATAL, "NetBSD ifconfig failed");
 
-        if (tun)
-        {
-            argv_printf(&argv,
-                        "%s %s %s %s mtu %d netmask 255.255.255.255 up",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu
-                        );
-        }
-        else if (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
-        {
-            remote_end = create_arbitrary_remote( tt );
-            argv_printf(&argv,
-                        "%s %s %s %s mtu %d netmask %s up",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        print_in_addr_t(remote_end, 0, &gc),
-                        tun_mtu,
-                        ifconfig_remote_netmask
-                        );
-        }
-        else
-        {
-            /*
-             * NetBSD has distinct tun and tap devices
-             * so we don't need the "link0" extra parameter to specify we want to do
-             * tunneling at the ethernet level
-             */
-            argv_printf(&argv,
-                        "%s %s %s netmask %s mtu %d broadcast %s",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu,
-                        ifconfig_broadcast
-                        );
-        }
-        argv_msg(M_INFO, &argv);
-        openvpn_execve_check(&argv, es, S_FATAL, "NetBSD ifconfig failed");
-
-        /* Add a network route for the local tun interface */
-        if (!tun && tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
-        {
-            struct route_ipv4 r;
-            CLEAR(r);
-            r.flags = RT_DEFINED;
-            r.network = tt->local & tt->remote_netmask;
-            r.netmask = tt->remote_netmask;
-            r.gateway = remote_end;
-            add_route(&r, tt, 0, NULL, es);
-        }
-
-        if (do_ipv6)
-        {
-            argv_printf(&argv,
-                        "%s %s inet6 %s/%d",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_ipv6_local,
-                        tt->netbits_ipv6
-                        );
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, es, S_FATAL, "NetBSD ifconfig inet6 failed");
-
-            /* and, hooray, we explicitely need to add a route... */
-            add_route_connected_v6_net(tt, es);
-        }
-        tt->did_ifconfig = true;
+    /* Add a network route for the local tun interface */
+    if (!tun && tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
+    {
+        struct route_ipv4 r;
+        CLEAR(r);
+        r.flags = RT_DEFINED;
+        r.network = tt->local & tt->remote_netmask;
+        r.netmask = tt->remote_netmask;
+        r.gateway = remote_end;
+        add_route(&r, tt, 0, NULL, es, NULL);
+    }
 
 #elif defined(TARGET_DARWIN)
-        /*
-         * Darwin (i.e. Mac OS X) seems to exhibit similar behaviour to OpenBSD...
-         */
+    /*
+     * Darwin (i.e. Mac OS X) seems to exhibit similar behaviour to OpenBSD...
+     */
 
-        argv_printf(&argv,
-                    "%s %s delete",
-                    IFCONFIG_PATH,
-                    actual);
-        argv_msg(M_INFO, &argv);
-        openvpn_execve_check(&argv, es, 0, NULL);
-        msg(M_INFO, "NOTE: Tried to delete pre-existing tun/tap instance -- No Problem if failure");
+    argv_printf(&argv, "%s %s delete", IFCONFIG_PATH, ifname);
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, es, 0, NULL);
+    msg(M_INFO,
+        "NOTE: Tried to delete pre-existing tun/tap instance -- No Problem if failure");
 
 
-        /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */
-        if (tun)
+    /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */
+    if (tun)
+    {
+        argv_printf(&argv, "%s %s %s %s mtu %d netmask 255.255.255.255 up",
+                    IFCONFIG_PATH, ifname, ifconfig_local,
+                    ifconfig_remote_netmask, tun_mtu);
+    }
+    else
+    {
+        if (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
         {
-            argv_printf(&argv,
-                        "%s %s %s %s mtu %d netmask 255.255.255.255 up",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu
-                        );
+            argv_printf(&argv, "%s %s %s %s netmask %s mtu %d up",
+                        IFCONFIG_PATH, ifname, ifconfig_local, ifconfig_local,
+                        ifconfig_remote_netmask, tun_mtu);
         }
         else
         {
-            if (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
-            {
-                argv_printf(&argv,
-                            "%s %s %s %s netmask %s mtu %d up",
-                            IFCONFIG_PATH,
-                            actual,
-                            ifconfig_local,
-                            ifconfig_local,
-                            ifconfig_remote_netmask,
-                            tun_mtu
-                            );
-            }
-            else
-            {
-                argv_printf(&argv,
-                            "%s %s %s netmask %s mtu %d up",
-                            IFCONFIG_PATH,
-                            actual,
-                            ifconfig_local,
-                            ifconfig_remote_netmask,
-                            tun_mtu
-                            );
-            }
+            argv_printf(&argv, "%s %s %s netmask %s mtu %d up", IFCONFIG_PATH,
+                        ifname, ifconfig_local, ifconfig_remote_netmask,
+                        tun_mtu);
         }
+    }
 
-        argv_msg(M_INFO, &argv);
-        openvpn_execve_check(&argv, es, S_FATAL, "Mac OS X ifconfig failed");
-        tt->did_ifconfig = true;
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, es, S_FATAL, "Mac OS X ifconfig failed");
 
-        /* Add a network route for the local tun interface */
-        if (!tun && tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
-        {
-            struct route_ipv4 r;
-            CLEAR(r);
-            r.flags = RT_DEFINED;
-            r.network = tt->local & tt->remote_netmask;
-            r.netmask = tt->remote_netmask;
-            r.gateway = tt->local;
-            add_route(&r, tt, 0, NULL, es);
-        }
-
-        if (do_ipv6)
-        {
-            argv_printf(&argv,
-                        "%s %s inet6 %s/%d",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_ipv6_local,
-                        tt->netbits_ipv6
-                        );
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, es, S_FATAL, "MacOS X ifconfig inet6 failed");
-
-            /* and, hooray, we explicitely need to add a route... */
-            add_route_connected_v6_net(tt, es);
-        }
+    /* Add a network route for the local tun interface */
+    if (!tun && tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
+    {
+        struct route_ipv4 r;
+        CLEAR(r);
+        r.flags = RT_DEFINED;
+        r.network = tt->local & tt->remote_netmask;
+        r.netmask = tt->remote_netmask;
+        r.gateway = tt->local;
+        add_route(&r, tt, 0, NULL, es, NULL);
+    }
 
 #elif defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY)
 
-        in_addr_t remote_end;           /* for "virtual" subnet topology */
+    in_addr_t remote_end;           /* for "virtual" subnet topology */
 
-        /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */
-        if (tun)
-        {
-            argv_printf(&argv,
-                        "%s %s %s %s mtu %d netmask 255.255.255.255 up",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu
-                        );
-        }
-        else if (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
-        {
-            remote_end = create_arbitrary_remote( tt );
-            argv_printf(&argv,
-                        "%s %s %s %s mtu %d netmask %s up",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        print_in_addr_t(remote_end, 0, &gc),
-                        tun_mtu,
-                        ifconfig_remote_netmask
-                        );
-        }
-        else
-        {
-            argv_printf(&argv,
-                        "%s %s %s netmask %s mtu %d up",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu
-                        );
-        }
+    /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */
+    if (tun)
+    {
+        argv_printf(&argv, "%s %s %s %s mtu %d netmask 255.255.255.255 up",
+                    IFCONFIG_PATH, ifname, ifconfig_local,
+                    ifconfig_remote_netmask, tun_mtu);
+    }
+    else if (tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
+    {
+        remote_end = create_arbitrary_remote( tt );
+        argv_printf(&argv, "%s %s %s %s mtu %d netmask %s up", IFCONFIG_PATH,
+                    ifname, ifconfig_local, print_in_addr_t(remote_end, 0, &gc),
+                    tun_mtu, ifconfig_remote_netmask);
+    }
+    else
+    {
+        argv_printf(&argv, "%s %s %s netmask %s mtu %d up", IFCONFIG_PATH,
+                    ifname, ifconfig_local, ifconfig_remote_netmask, tun_mtu);
+    }
 
-        argv_msg(M_INFO, &argv);
-        openvpn_execve_check(&argv, es, S_FATAL, "FreeBSD ifconfig failed");
-        tt->did_ifconfig = true;
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, es, S_FATAL, "FreeBSD ifconfig failed");
 
-        /* Add a network route for the local tun interface */
-        if (!tun && tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
-        {
-            struct route_ipv4 r;
-            CLEAR(r);
-            r.flags = RT_DEFINED;
-            r.network = tt->local & tt->remote_netmask;
-            r.netmask = tt->remote_netmask;
-            r.gateway = remote_end;
-            add_route(&r, tt, 0, NULL, es);
-        }
-
-        if (do_ipv6)
-        {
-            argv_printf(&argv,
-                        "%s %s inet6 %s/%d",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_ipv6_local,
-                        tt->netbits_ipv6
-                        );
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, es, S_FATAL, "FreeBSD ifconfig inet6 failed");
-        }
+    /* Add a network route for the local tun interface */
+    if (!tun && tt->type == DEV_TYPE_TUN && tt->topology == TOP_SUBNET)
+    {
+        struct route_ipv4 r;
+        CLEAR(r);
+        r.flags = RT_DEFINED;
+        r.network = tt->local & tt->remote_netmask;
+        r.netmask = tt->remote_netmask;
+        r.gateway = remote_end;
+        add_route(&r, tt, 0, NULL, es, NULL);
+    }
 
 #elif defined(TARGET_AIX)
+    {
+        /* AIX ifconfig will complain if it can't find ODM path in env */
+        struct env_set *aix_es = env_set_create(NULL);
+        env_set_add( aix_es, "ODMDIR=/etc/objrepos" );
+
+        if (tun)
         {
-            /* AIX ifconfig will complain if it can't find ODM path in env */
-            struct env_set *aix_es = env_set_create(NULL);
-            env_set_add( aix_es, "ODMDIR=/etc/objrepos" );
-
-            if (tun)
-            {
-                msg(M_FATAL, "no tun support on AIX (canthappen)");
-            }
-
-            /* example: ifconfig tap0 172.30.1.1 netmask 255.255.254.0 up */
-            argv_printf(&argv,
-                        "%s %s %s netmask %s mtu %d up",
-                        IFCONFIG_PATH,
-                        actual,
-                        ifconfig_local,
-                        ifconfig_remote_netmask,
-                        tun_mtu
-                        );
-
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, aix_es, S_FATAL, "AIX ifconfig failed");
-            tt->did_ifconfig = true;
-
-            if (do_ipv6)
-            {
-                argv_printf(&argv,
-                            "%s %s inet6 %s/%d",
-                            IFCONFIG_PATH,
-                            actual,
-                            ifconfig_ipv6_local,
-                            tt->netbits_ipv6
-                            );
-                argv_msg(M_INFO, &argv);
-                openvpn_execve_check(&argv, aix_es, S_FATAL, "AIX ifconfig inet6 failed");
-            }
-            env_set_destroy(aix_es);
-        }
-#elif defined (_WIN32)
-        {
-            ASSERT(actual != NULL);
-
-            switch (tt->options.ip_win32_type)
-            {
-                case IPW32_SET_MANUAL:
-                    msg(M_INFO, "******** NOTE:  Please manually set the IP/netmask of '%s' to %s/%s (if it is not already set)",
-                        actual,
-                        ifconfig_local,
-                        print_in_addr_t(tt->adapter_netmask, 0, &gc));
-                    break;
-
-                case IPW32_SET_NETSH:
-                    netsh_ifconfig(&tt->options,
-                                   actual,
-                                   tt->local,
-                                   tt->adapter_netmask,
-                                   NI_IP_NETMASK|NI_OPTIONS);
-
-                    break;
-            }
-            tt->did_ifconfig = true;
+            msg(M_FATAL, "no tun support on AIX (canthappen)");
         }
 
-        if (do_ipv6)
-        {
-            if (tt->options.ip_win32_type == IPW32_SET_MANUAL)
-            {
-                msg(M_INFO, "******** NOTE:  Please manually set the v6 IP of '%s' to %s (if it is not already set)",
-                    actual,
-                    ifconfig_ipv6_local);
-            }
-            else if (tt->options.msg_channel)
-            {
-                do_address_service(true, AF_INET6, tt);
-                do_dns6_service(true, tt);
-            }
-            else
-            {
-                /* example: netsh interface ipv6 set address interface=42 2001:608:8003::d store=active */
-                char iface[64];
-                openvpn_snprintf(iface, sizeof(iface), "interface=%lu", tt->adapter_index );
-                argv_printf(&argv,
-                            "%s%sc interface ipv6 set address %s %s store=active",
-                            get_win_sys_path(),
-                            NETSH_PATH_SUFFIX,
-                            iface,
-                            ifconfig_ipv6_local );
-                netsh_command(&argv, 4, M_FATAL);
-                /* set ipv6 dns servers if any are specified */
-                netsh_set_dns6_servers(tt->options.dns6, tt->options.dns6_len, actual);
-            }
+        /* example: ifconfig tap0 172.30.1.1 netmask 255.255.254.0 up */
+        argv_printf(&argv, "%s %s %s netmask %s mtu %d up", IFCONFIG_PATH,
+                    ifname, ifconfig_local, ifconfig_remote_netmask, tun_mtu);
 
-            /* explicit route needed */
-            if (tt->options.ip_win32_type != IPW32_SET_MANUAL)
-            {
-                add_route_connected_v6_net(tt, es);
-            }
-        }
-#else  /* if defined(TARGET_LINUX) */
-        msg(M_FATAL, "Sorry, but I don't know how to do 'ifconfig' commands on this operating system.  You should ifconfig your TUN/TAP device manually or use an --up script.");
-#endif /* if defined(TARGET_LINUX) */
-        argv_reset(&argv);
+        argv_msg(M_INFO, &argv);
+        openvpn_execve_check(&argv, aix_es, S_FATAL, "AIX ifconfig failed");
+
+        env_set_destroy(aix_es);
     }
+#elif defined (_WIN32)
+    if (tt->options.ip_win32_type == IPW32_SET_MANUAL)
+    {
+        msg(M_INFO,
+            "******** NOTE:  Please manually set the IP/netmask of '%s' to %s/%s (if it is not already set)",
+            ifname, ifconfig_local,
+            print_in_addr_t(tt->adapter_netmask, 0, &gc));
+    }
+    else if (tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ || tt->options.ip_win32_type == IPW32_SET_ADAPTIVE)
+    {
+        /* Let the DHCP configure the interface. */
+    }
+    else if (tt->options.msg_channel)
+    {
+        do_address_service(true, AF_INET, tt);
+        do_dns_service(true, AF_INET, tt);
+        do_dns_domain_service(true, tt);
+    }
+    else if (tt->options.ip_win32_type == IPW32_SET_NETSH)
+    {
+        netsh_ifconfig(&tt->options, tt->adapter_index, tt->local,
+                       tt->adapter_netmask, NI_IP_NETMASK|NI_OPTIONS);
+    }
+    if (tt->options.msg_channel)
+    {
+        do_set_mtu_service(tt, AF_INET, tun_mtu);
+    }
+    else
+    {
+        windows_set_mtu(tt->adapter_index, AF_INET, tun_mtu);
+    }
+#else  /* if defined(TARGET_LINUX) */
+    msg(M_FATAL, "Sorry, but I don't know how to do 'ifconfig' commands on this operating system.  You should ifconfig your TUN/TAP device manually or use an --up script.");
+#endif /* if defined(TARGET_LINUX) */
+
+#if !defined(TARGET_LINUX)
     gc_free(&gc);
+    argv_free(&argv);
+#endif
+}
+
+/* execute the ifconfig command through the shell */
+void
+do_ifconfig(struct tuntap *tt, const char *ifname, int tun_mtu,
+            const struct env_set *es, openvpn_net_ctx_t *ctx)
+{
+    msg(D_LOW, "do_ifconfig, ipv4=%d, ipv6=%d", tt->did_ifconfig_setup,
+        tt->did_ifconfig_ipv6_setup);
+
+#ifdef ENABLE_MANAGEMENT
+    if (management)
+    {
+        management_set_state(management,
+                             OPENVPN_STATE_ASSIGN_IP,
+                             NULL,
+                             &tt->local,
+                             &tt->local_ipv6,
+                             NULL,
+                             NULL);
+    }
+#endif
+
+    if (tt->did_ifconfig_setup)
+    {
+        do_ifconfig_ipv4(tt, ifname, tun_mtu, es, ctx);
+    }
+
+    if (tt->did_ifconfig_ipv6_setup)
+    {
+        do_ifconfig_ipv6(tt, ifname, tun_mtu, es, ctx);
+    }
+
+    /* release resources potentially allocated during interface setup */
+    net_ctx_free(ctx);
 }
 
 static void
@@ -1913,13 +1913,12 @@
 }
 
 void
-close_tun(struct tuntap *tt)
+close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 {
-    if (tt)
-    {
-        close_tun_generic(tt);
-        free(tt);
-    }
+    ASSERT(tt);
+
+    close_tun_generic(tt);
+    free(tt);
 }
 
 int
@@ -2065,12 +2064,19 @@
     ASSERT(0);
 }
 
-#endif /* !PENDANTIC */
+#endif /* !PEDANTIC */
 
 #ifdef ENABLE_FEATURE_TUN_PERSIST
 
+/* TUNSETGROUP appeared in 2.6.23 */
+#ifndef TUNSETGROUP
+# define TUNSETGROUP   _IOW('T', 206, int)
+#endif
+
 void
-tuncfg(const char *dev, const char *dev_type, const char *dev_node, int persist_mode, const char *username, const char *groupname, const struct tuntap_options *options)
+tuncfg(const char *dev, const char *dev_type, const char *dev_node,
+       int persist_mode, const char *username, const char *groupname,
+       const struct tuntap_options *options, openvpn_net_ctx_t *ctx)
 {
     struct tuntap *tt;
 
@@ -2106,89 +2112,98 @@
         }
         else if (ioctl(tt->fd, TUNSETGROUP, platform_state_group.gr->gr_gid) < 0)
         {
-            msg(M_ERR, "Cannot ioctl TUNSETOWNER(%s) %s", groupname, dev);
+            msg(M_ERR, "Cannot ioctl TUNSETGROUP(%s) %s", groupname, dev);
         }
     }
-    close_tun(tt);
+    close_tun(tt, ctx);
     msg(M_INFO, "Persist state set to: %s", (persist_mode ? "ON" : "OFF"));
 }
 
 #endif /* ENABLE_FEATURE_TUN_PERSIST */
 
-void
-close_tun(struct tuntap *tt)
+static void
+undo_ifconfig_ipv4(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 {
-    if (tt)
+#if defined(TARGET_LINUX)
+    int netbits = netmask_to_netbits2(tt->remote_netmask);
+
+    if (is_tun_p2p(tt))
     {
-        if (tt->type != DEV_TYPE_NULL && tt->did_ifconfig)
+        if (net_addr_ptp_v4_del(ctx, tt->actual_name, &tt->local,
+                                &tt->remote_netmask) < 0)
         {
-            struct argv argv = argv_new();
-            struct gc_arena gc = gc_new();
-
-#ifdef ENABLE_IPROUTE
-            if (is_tun_p2p(tt))
-            {
-                argv_printf(&argv,
-                            "%s addr del dev %s local %s peer %s",
-                            iproute_path,
-                            tt->actual_name,
-                            print_in_addr_t(tt->local, 0, &gc),
-                            print_in_addr_t(tt->remote_netmask, 0, &gc)
-                            );
-            }
-            else
-            {
-                argv_printf(&argv,
-                            "%s addr del dev %s %s/%d",
-                            iproute_path,
-                            tt->actual_name,
-                            print_in_addr_t(tt->local, 0, &gc),
-                            netmask_to_netbits2(tt->remote_netmask)
-                            );
-            }
-#else  /* ifdef ENABLE_IPROUTE */
-            argv_printf(&argv,
-                        "%s %s 0.0.0.0",
-                        IFCONFIG_PATH,
-                        tt->actual_name
-                        );
-#endif /* ifdef ENABLE_IPROUTE */
-
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, NULL, 0, "Linux ip addr del failed");
-
-            if (tt->did_ifconfig_ipv6_setup)
-            {
-                const char *ifconfig_ipv6_local = print_in6_addr(tt->local_ipv6, 0, &gc);
-
-#ifdef ENABLE_IPROUTE
-                argv_printf(&argv, "%s -6 addr del %s/%d dev %s",
-                            iproute_path,
-                            ifconfig_ipv6_local,
-                            tt->netbits_ipv6,
-                            tt->actual_name
-                            );
-                argv_msg(M_INFO, &argv);
-                openvpn_execve_check(&argv, NULL, 0, "Linux ip -6 addr del failed");
-#else  /* ifdef ENABLE_IPROUTE */
-                argv_printf(&argv,
-                            "%s %s del %s/%d",
-                            IFCONFIG_PATH,
-                            tt->actual_name,
-                            ifconfig_ipv6_local,
-                            tt->netbits_ipv6
-                            );
-                argv_msg(M_INFO, &argv);
-                openvpn_execve_check(&argv, NULL, 0, "Linux ifconfig inet6 del failed");
-#endif
-            }
-
-            argv_reset(&argv);
-            gc_free(&gc);
+            msg(M_WARN, "Linux can't del IP from iface %s",
+                tt->actual_name);
         }
-        close_tun_generic(tt);
-        free(tt);
     }
+    else
+    {
+        if (net_addr_v4_del(ctx, tt->actual_name, &tt->local, netbits) < 0)
+        {
+            msg(M_WARN, "Linux can't del IP from iface %s",
+                tt->actual_name);
+        }
+    }
+#else  /* ifndef TARGET_LINUX */
+    struct argv argv = argv_new();
+
+    argv_printf(&argv, "%s %s 0.0.0.0", IFCONFIG_PATH, tt->actual_name);
+
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, NULL, 0, "Generic ip addr del failed");
+
+    argv_free(&argv);
+#endif /* ifdef TARGET_LINUX */
+}
+
+static void
+undo_ifconfig_ipv6(struct tuntap *tt, openvpn_net_ctx_t *ctx)
+{
+#if defined(TARGET_LINUX)
+    if (net_addr_v6_del(ctx, tt->actual_name, &tt->local_ipv6,
+                        tt->netbits_ipv6) < 0)
+    {
+        msg(M_WARN, "Linux can't del IPv6 from iface %s", tt->actual_name);
+    }
+#else  /* ifndef TARGET_LINUX */
+    struct gc_arena gc = gc_new();
+    const char *ifconfig_ipv6_local = print_in6_addr(tt->local_ipv6, 0, gc);
+    struct argv argv = argv_new();
+
+    argv_printf(&argv, "%s %s del %s/%d", IFCONFIG_PATH, tt->actual_name,
+                ifconfig_ipv6_local, tt->netbits_ipv6);
+
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, NULL, 0, "Linux ip -6 addr del failed");
+
+    argv_free(&argv);
+    gc_free(&gc);
+#endif /* ifdef TARGET_LINUX */
+}
+
+void
+close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
+{
+    ASSERT(tt);
+
+    if (tt->type != DEV_TYPE_NULL)
+    {
+        if (tt->did_ifconfig_setup)
+        {
+            undo_ifconfig_ipv4(tt, ctx);
+        }
+
+        if (tt->did_ifconfig_ipv6_setup)
+        {
+            undo_ifconfig_ipv6(tt, ctx);
+        }
+
+        /* release resources potentially allocated during undo */
+        net_ctx_reset(ctx);
+    }
+
+    close_tun_generic(tt);
+    free(tt);
 }
 
 int
@@ -2446,57 +2461,54 @@
 static void
 solaris_close_tun(struct tuntap *tt)
 {
-    if (tt)
+    /* IPv6 interfaces need to be 'manually' de-configured */
+    if (tt->did_ifconfig_ipv6_setup)
     {
-        /* IPv6 interfaces need to be 'manually' de-configured */
-        if (tt->did_ifconfig_ipv6_setup)
+        struct argv argv = argv_new();
+        argv_printf( &argv, "%s %s inet6 unplumb",
+                     IFCONFIG_PATH, tt->actual_name );
+        argv_msg(M_INFO, &argv);
+        openvpn_execve_check(&argv, NULL, 0, "Solaris ifconfig inet6 unplumb failed");
+        argv_free(&argv);
+    }
+
+    if (tt->ip_fd >= 0)
+    {
+        struct lifreq ifr;
+        CLEAR(ifr);
+        strncpynt(ifr.lifr_name, tt->actual_name, sizeof(ifr.lifr_name));
+
+        if (ioctl(tt->ip_fd, SIOCGLIFFLAGS, &ifr) < 0)
         {
-            struct argv argv = argv_new();
-            argv_printf( &argv, "%s %s inet6 unplumb",
-                         IFCONFIG_PATH, tt->actual_name );
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, NULL, 0, "Solaris ifconfig inet6 unplumb failed");
-            argv_reset(&argv);
+            msg(M_WARN | M_ERRNO, "Can't get iface flags");
         }
 
-        if (tt->ip_fd >= 0)
+        if (ioctl(tt->ip_fd, SIOCGLIFMUXID, &ifr) < 0)
         {
-            struct lifreq ifr;
-            CLEAR(ifr);
-            strncpynt(ifr.lifr_name, tt->actual_name, sizeof(ifr.lifr_name));
-
-            if (ioctl(tt->ip_fd, SIOCGLIFFLAGS, &ifr) < 0)
-            {
-                msg(M_WARN | M_ERRNO, "Can't get iface flags");
-            }
-
-            if (ioctl(tt->ip_fd, SIOCGLIFMUXID, &ifr) < 0)
-            {
-                msg(M_WARN | M_ERRNO, "Can't get multiplexor id");
-            }
-
-            if (tt->type == DEV_TYPE_TAP)
-            {
-                if (ioctl(tt->ip_fd, I_PUNLINK, ifr.lifr_arp_muxid) < 0)
-                {
-                    msg(M_WARN | M_ERRNO, "Can't unlink interface(arp)");
-                }
-            }
-
-            if (ioctl(tt->ip_fd, I_PUNLINK, ifr.lifr_ip_muxid) < 0)
-            {
-                msg(M_WARN | M_ERRNO, "Can't unlink interface(ip)");
-            }
-
-            close(tt->ip_fd);
-            tt->ip_fd = -1;
+            msg(M_WARN | M_ERRNO, "Can't get multiplexor id");
         }
 
-        if (tt->fd >= 0)
+        if (tt->type == DEV_TYPE_TAP)
         {
-            close(tt->fd);
-            tt->fd = -1;
+            if (ioctl(tt->ip_fd, I_PUNLINK, ifr.lifr_arp_muxid) < 0)
+            {
+                msg(M_WARN | M_ERRNO, "Can't unlink interface(arp)");
+            }
         }
+
+        if (ioctl(tt->ip_fd, I_PUNLINK, ifr.lifr_ip_muxid) < 0)
+        {
+            msg(M_WARN | M_ERRNO, "Can't unlink interface(ip)");
+        }
+
+        close(tt->ip_fd);
+        tt->ip_fd = -1;
+    }
+
+    if (tt->fd >= 0)
+    {
+        close(tt->fd);
+        tt->fd = -1;
     }
 }
 
@@ -2504,20 +2516,19 @@
  * Close TUN device.
  */
 void
-close_tun(struct tuntap *tt)
+close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 {
-    if (tt)
+    ASSERT(tt);
+
+    solaris_close_tun(tt);
+
+    if (tt->actual_name)
     {
-        solaris_close_tun(tt);
-
-        if (tt->actual_name)
-        {
-            free(tt->actual_name);
-        }
-
-        clear_tuntap(tt);
-        free(tt);
+        free(tt->actual_name);
     }
+
+    clear_tuntap(tt);
+    free(tt);
 }
 
 static void
@@ -2541,9 +2552,9 @@
 
     argv_msg(M_INFO, &argv);
     openvpn_execve_check(&argv, es, 0, "Solaris ifconfig unplumb failed");
-    close_tun(tt);
+    close_tun(tt, NULL);
     msg(M_FATAL, "Solaris ifconfig failed");
-    argv_reset(&argv);
+    argv_free(&argv);
 }
 
 int
@@ -2604,33 +2615,34 @@
  */
 
 void
-close_tun(struct tuntap *tt)
+close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 {
+    ASSERT(tt);
+
     /* only *TAP* devices need destroying, tun devices auto-self-destruct
      */
-    if (tt && (tt->type == DEV_TYPE_TUN || tt->persistent_if ) )
+    if (tt->type == DEV_TYPE_TUN || tt->persistent_if)
     {
         close_tun_generic(tt);
         free(tt);
+        return;
     }
-    else if (tt)
-    {
-        struct gc_arena gc = gc_new();
-        struct argv argv = argv_new();
 
-        /* setup command, close tun dev (clears tt->actual_name!), run command
-         */
+    struct argv argv = argv_new();
 
-        argv_printf(&argv, "%s %s destroy",
-                    IFCONFIG_PATH, tt->actual_name);
+    /* setup command, close tun dev (clears tt->actual_name!), run command
+     */
 
-        close_tun_generic(tt);
+    argv_printf(&argv, "%s %s destroy",
+                IFCONFIG_PATH, tt->actual_name);
 
-        argv_msg(M_INFO, &argv);
-        openvpn_execve_check(&argv, NULL, 0, "OpenBSD 'destroy tun interface' failed (non-critical)");
+    close_tun_generic(tt);
 
-        free(tt);
-    }
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, NULL, 0, "OpenBSD 'destroy tun interface' failed (non-critical)");
+
+    free(tt);
+    argv_free(&argv);
 }
 
 int
@@ -2686,36 +2698,37 @@
 
 /* the current way OpenVPN handles tun devices on NetBSD leads to
  * lingering tunX interfaces after close -> for a full cleanup, they
- * need to be explicitely destroyed
+ * need to be explicitly destroyed
  */
 void
-close_tun(struct tuntap *tt)
+close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 {
+    ASSERT(tt);
+
     /* only tun devices need destroying, tap devices auto-self-destruct
      */
-    if (tt && ( tt->type != DEV_TYPE_TUN || tt->persistent_if ) )
+    if (tt->type != DEV_TYPE_TUN || tt->persistent_if)
     {
         close_tun_generic(tt);
         free(tt);
+        return;
     }
-    else if (tt)
-    {
-        struct gc_arena gc = gc_new();
-        struct argv argv = argv_new();
 
-        /* setup command, close tun dev (clears tt->actual_name!), run command
-         */
+    struct argv argv = argv_new();
 
-        argv_printf(&argv, "%s %s destroy",
-                    IFCONFIG_PATH, tt->actual_name);
+    /* setup command, close tun dev (clears tt->actual_name!), run command
+     */
 
-        close_tun_generic(tt);
+    argv_printf(&argv, "%s %s destroy",
+                IFCONFIG_PATH, tt->actual_name);
 
-        argv_msg(M_INFO, &argv);
-        openvpn_execve_check(&argv, NULL, 0, "NetBSD 'destroy tun interface' failed (non-critical)");
+    close_tun_generic(tt);
 
-        free(tt);
-    }
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, NULL, 0, "NetBSD 'destroy tun interface' failed (non-critical)");
+
+    free(tt);
+    argv_free(&argv);
 }
 
 static inline int
@@ -2829,30 +2842,34 @@
  * we need to call "ifconfig ... destroy" for cleanup
  */
 void
-close_tun(struct tuntap *tt)
+close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 {
-    if (tt && tt->persistent_if)        /* keep pre-existing if around */
+    ASSERT(tt);
+
+    if (tt->persistent_if)        /* keep pre-existing if around */
     {
         close_tun_generic(tt);
         free(tt);
+        return;
     }
-    else if (tt)                        /* close and destroy */
-    {
-        struct argv argv = argv_new();
 
-        /* setup command, close tun dev (clears tt->actual_name!), run command
-         */
+    /* close and destroy */
+    struct argv argv = argv_new();
 
-        argv_printf(&argv, "%s %s destroy",
-                    IFCONFIG_PATH, tt->actual_name);
+    /* setup command, close tun dev (clears tt->actual_name!), run command
+     */
 
-        close_tun_generic(tt);
+    argv_printf(&argv, "%s %s destroy",
+                IFCONFIG_PATH, tt->actual_name);
 
-        argv_msg(M_INFO, &argv);
-        openvpn_execve_check(&argv, NULL, 0, "FreeBSD 'destroy tun interface' failed (non-critical)");
+    close_tun_generic(tt);
 
-        free(tt);
-    }
+    argv_msg(M_INFO, &argv);
+    openvpn_execve_check(&argv, NULL, 0,
+                         "FreeBSD 'destroy tun interface' failed (non-critical)");
+
+    free(tt);
+    argv_free(&argv);
 }
 
 int
@@ -2941,13 +2958,12 @@
 }
 
 void
-close_tun(struct tuntap *tt)
+close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 {
-    if (tt)
-    {
-        close_tun_generic(tt);
-        free(tt);
-    }
+    ASSERT(tt);
+
+    close_tun_generic(tt);
+    free(tt);
 }
 
 int
@@ -3037,14 +3053,16 @@
 
     if (fd < 0)
     {
-        msg(M_INFO | M_ERRNO, "Opening utun (socket(SYSPROTO_CONTROL))");
+        msg(M_INFO | M_ERRNO, "Opening utun%d failed (socket(SYSPROTO_CONTROL))",
+            utunnum);
         return -2;
     }
 
     if (ioctl(fd, CTLIOCGINFO, &ctlInfo) == -1)
     {
         close(fd);
-        msg(M_INFO | M_ERRNO, "Opening utun (ioctl(CTLIOCGINFO))");
+        msg(M_INFO | M_ERRNO, "Opening utun%d failed (ioctl(CTLIOCGINFO))",
+            utunnum);
         return -2;
     }
 
@@ -3062,7 +3080,8 @@
 
     if (connect(fd, (struct sockaddr *)&sc, sizeof(sc)) < 0)
     {
-        msg(M_INFO | M_ERRNO, "Opening utun (connect(AF_SYS_CONTROL))");
+        msg(M_INFO | M_ERRNO, "Opening utun%d failed (connect(AF_SYS_CONTROL))",
+            utunnum);
         close(fd);
         return -1;
     }
@@ -3105,11 +3124,18 @@
     /* try to open first available utun device if no specific utun is requested */
     if (utunnum == -1)
     {
-        for (utunnum = 0; utunnum<255; utunnum++)
+        for (utunnum = 0; utunnum < 255; utunnum++)
         {
+            char ifname[20];
+            /* if the interface exists silently skip it */
+            ASSERT(snprintf(ifname, sizeof(ifname), "utun%d", utunnum) > 0);
+            if (if_nametoindex(ifname))
+            {
+                continue;
+            }
             fd = utun_open_helper(ctlInfo, utunnum);
             /* Break if the fd is valid,
-             * or if early initalization failed (-2) */
+             * or if early initialization failed (-2) */
             if (fd !=-1)
             {
                 break;
@@ -3198,29 +3224,28 @@
 }
 
 void
-close_tun(struct tuntap *tt)
+close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 {
-    if (tt)
+    ASSERT(tt);
+
+    struct gc_arena gc = gc_new();
+    struct argv argv = argv_new();
+
+    if (tt->did_ifconfig_ipv6_setup)
     {
-        struct gc_arena gc = gc_new();
-        struct argv argv = argv_new();
+        const char *ifconfig_ipv6_local =
+            print_in6_addr(tt->local_ipv6, 0, &gc);
 
-        if (tt->did_ifconfig_ipv6_setup)
-        {
-            const char *ifconfig_ipv6_local =
-                print_in6_addr(tt->local_ipv6, 0, &gc);
-
-            argv_printf(&argv, "%s delete -inet6 %s",
-                        ROUTE_PATH, ifconfig_ipv6_local );
-            argv_msg(M_INFO, &argv);
-            openvpn_execve_check(&argv, NULL, 0, "MacOS X 'remove inet6 route' failed (non-critical)");
-        }
-
-        close_tun_generic(tt);
-        free(tt);
-        argv_reset(&argv);
-        gc_free(&gc);
+        argv_printf(&argv, "%s delete -inet6 %s",
+                    ROUTE_PATH, ifconfig_ipv6_local );
+        argv_msg(M_INFO, &argv);
+        openvpn_execve_check(&argv, NULL, 0, "MacOS X 'remove inet6 route' failed (non-critical)");
     }
+
+    close_tun_generic(tt);
+    free(tt);
+    argv_free(&argv);
+    gc_free(&gc);
 }
 
 int
@@ -3323,6 +3348,7 @@
         env_set_add( es, "ODMDIR=/etc/objrepos" );
         openvpn_execve_check(&argv, es, S_FATAL, "AIX 'create tun interface' failed");
         env_set_destroy(es);
+        argv_free(&argv);
     }
     else
     {
@@ -3346,17 +3372,13 @@
 /* tap devices need to be manually destroyed on AIX
  */
 void
-close_tun(struct tuntap *tt)
+close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 {
-    struct gc_arena gc = gc_new();
+    ASSERT(tt);
+
     struct argv argv = argv_new();
     struct env_set *es = env_set_create(NULL);
 
-    if (!tt)
-    {
-        return;
-    }
-
     /* persistent devices need IP address unconfig, others need destroyal
      */
     if (tt->persistent_if)
@@ -3377,6 +3399,7 @@
 
     free(tt);
     env_set_destroy(es);
+    argv_free(&argv);
 }
 
 int
@@ -3393,6 +3416,22 @@
 
 #elif defined(_WIN32)
 
+static const char *
+print_windows_driver(enum windows_driver_type windows_driver)
+{
+    switch (windows_driver)
+    {
+        case WINDOWS_DRIVER_TAP_WINDOWS6:
+            return "tap-windows6";
+
+        case WINDOWS_DRIVER_WINTUN:
+            return "wintun";
+
+        default:
+            return "unspecified";
+    }
+}
+
 int
 tun_read_queue(struct tuntap *tt, int maxsize)
 {
@@ -3604,7 +3643,123 @@
     return ret;
 }
 
-const struct tap_reg *
+static const struct device_instance_id_interface *
+get_device_instance_id_interface(struct gc_arena *gc)
+{
+    HDEVINFO dev_info_set;
+    DWORD err;
+    struct device_instance_id_interface *first = NULL;
+    struct device_instance_id_interface *last = NULL;
+
+    dev_info_set = SetupDiGetClassDevsEx(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
+    if (dev_info_set == INVALID_HANDLE_VALUE)
+    {
+        err = GetLastError();
+        msg(M_FATAL, "Error [%u] opening device information set key: %s", (unsigned int)err, strerror_win32(err, gc));
+    }
+
+    for (DWORD i = 0;; ++i)
+    {
+        SP_DEVINFO_DATA device_info_data;
+        BOOL res;
+        HKEY dev_key;
+        char net_cfg_instance_id_string[] = "NetCfgInstanceId";
+        char net_cfg_instance_id[256];
+        char device_instance_id[256];
+        DWORD len;
+        DWORD data_type;
+        LONG status;
+        ULONG dev_interface_list_size;
+        CONFIGRET cr;
+        struct buffer dev_interface_list;
+
+        ZeroMemory(&device_info_data, sizeof(SP_DEVINFO_DATA));
+        device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
+        res = SetupDiEnumDeviceInfo(dev_info_set, i, &device_info_data);
+        if (!res)
+        {
+            if (GetLastError() == ERROR_NO_MORE_ITEMS)
+            {
+                break;
+            }
+            else
+            {
+                continue;
+            }
+        }
+
+        dev_key = SetupDiOpenDevRegKey(dev_info_set, &device_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE);
+        if (dev_key == INVALID_HANDLE_VALUE)
+        {
+            continue;
+        }
+
+        len = sizeof(net_cfg_instance_id);
+        data_type = REG_SZ;
+        status = RegQueryValueEx(dev_key,
+                                 net_cfg_instance_id_string,
+                                 NULL,
+                                 &data_type,
+                                 net_cfg_instance_id,
+                                 &len);
+        if (status != ERROR_SUCCESS)
+        {
+            goto next;
+        }
+
+        len = sizeof(device_instance_id);
+        res = SetupDiGetDeviceInstanceId(dev_info_set, &device_info_data, device_instance_id, len, &len);
+        if (!res)
+        {
+            goto next;
+        }
+
+        cr = CM_Get_Device_Interface_List_Size(&dev_interface_list_size,
+                                               (LPGUID)&GUID_DEVINTERFACE_NET,
+                                               device_instance_id,
+                                               CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
+
+        if (cr != CR_SUCCESS)
+        {
+            goto next;
+        }
+
+        dev_interface_list = alloc_buf_gc(dev_interface_list_size, gc);
+        cr = CM_Get_Device_Interface_List((LPGUID)&GUID_DEVINTERFACE_NET, device_instance_id,
+                                          BPTR(&dev_interface_list),
+                                          dev_interface_list_size,
+                                          CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
+        if (cr != CR_SUCCESS)
+        {
+            goto next;
+        }
+
+        struct device_instance_id_interface *dev_if;
+        ALLOC_OBJ_CLEAR_GC(dev_if, struct device_instance_id_interface, gc);
+        dev_if->net_cfg_instance_id = string_alloc(net_cfg_instance_id, gc);
+        dev_if->device_interface_list = string_alloc(BSTR(&dev_interface_list), gc);
+
+        /* link into return list */
+        if (!first)
+        {
+            first = dev_if;
+        }
+        if (last)
+        {
+            last->next = dev_if;
+        }
+        last = dev_if;
+
+next:
+        RegCloseKey(dev_key);
+    }
+
+    SetupDiDestroyDeviceInfoList(dev_info_set);
+
+    return first;
+}
+
+static const struct tap_reg *
 get_tap_reg(struct gc_arena *gc)
 {
     HKEY adapter_key;
@@ -3700,12 +3855,24 @@
 
                 if (status == ERROR_SUCCESS && data_type == REG_SZ)
                 {
-                    if (!strcmp(component_id, TAP_WIN_COMPONENT_ID) ||
-                        !strcmp(component_id, "root\\" TAP_WIN_COMPONENT_ID))
+                    /* Is this adapter supported? */
+                    enum windows_driver_type windows_driver = WINDOWS_DRIVER_UNSPECIFIED;
+                    if (strcasecmp(component_id, TAP_WIN_COMPONENT_ID) == 0
+                        || strcasecmp(component_id, "root\\" TAP_WIN_COMPONENT_ID) == 0)
+                    {
+                        windows_driver = WINDOWS_DRIVER_TAP_WINDOWS6;
+                    }
+                    else if (strcasecmp(component_id, WINTUN_COMPONENT_ID) == 0)
+                    {
+                        windows_driver = WINDOWS_DRIVER_WINTUN;
+                    }
+
+                    if (windows_driver != WINDOWS_DRIVER_UNSPECIFIED)
                     {
                         struct tap_reg *reg;
                         ALLOC_OBJ_CLEAR_GC(reg, struct tap_reg, gc);
                         reg->guid = string_alloc(net_cfg_instance_id, gc);
+                        reg->windows_driver = windows_driver;
 
                         /* link into return list */
                         if (!first)
@@ -3729,7 +3896,7 @@
     return first;
 }
 
-const struct panel_reg *
+static const struct panel_reg *
 get_panel_reg(struct gc_arena *gc)
 {
     LONG status;
@@ -3936,7 +4103,7 @@
     const struct tap_reg *tap_reg = get_tap_reg(&gc);
     const struct panel_reg *panel_reg = get_panel_reg(&gc);
 
-    msg(msglev, "Available TAP-WIN32 adapters [name, GUID]:");
+    msg(msglev, "Available TAP-WIN32 / Wintun adapters [name, GUID, driver]:");
 
     /* loop through each TAP-Windows adapter registry entry */
     for (tr = tap_reg; tr != NULL; tr = tr->next)
@@ -3948,7 +4115,7 @@
         {
             if (!strcmp(tr->guid, pr->guid))
             {
-                msg(msglev, "'%s' %s", pr->name, tr->guid);
+                msg(msglev, "'%s' %s %s", pr->name, tr->guid, print_windows_driver(tr->windows_driver));
                 ++links;
             }
         }
@@ -3998,10 +4165,10 @@
 }
 
 /*
- * Confirm that GUID is a TAP-Windows adapter.
+ * Lookup a TAP-Windows or Wintun adapter by GUID.
  */
-static bool
-is_tap_win(const char *guid, const struct tap_reg *tap_reg)
+static const struct tap_reg *
+get_adapter_by_guid(const char *guid, const struct tap_reg *tap_reg)
 {
     const struct tap_reg *tr;
 
@@ -4009,11 +4176,11 @@
     {
         if (guid && !strcmp(tr->guid, guid))
         {
-            return true;
+            return tr;
         }
     }
 
-    return false;
+    return NULL;
 }
 
 static const char *
@@ -4032,16 +4199,16 @@
     return NULL;
 }
 
-static const char *
-name_to_guid(const char *name, const struct tap_reg *tap_reg, const struct panel_reg *panel_reg)
+static const struct tap_reg *
+get_adapter_by_name(const char *name, const struct tap_reg *tap_reg, const struct panel_reg *panel_reg)
 {
     const struct panel_reg *pr;
 
     for (pr = panel_reg; pr != NULL; pr = pr->next)
     {
-        if (name && !strcmp(pr->name, name) && is_tap_win(pr->guid, tap_reg))
+        if (name && !strcmp(pr->name, name))
         {
-            return pr->guid;
+            return get_adapter_by_guid(pr->guid, tap_reg);
         }
     }
 
@@ -4053,7 +4220,7 @@
 {
     if (!tap_reg)
     {
-        msg(M_FATAL, "There are no TAP-Windows adapters on this system.  You should be able to create a TAP-Windows adapter by going to Start -> All Programs -> TAP-Windows -> Utilities -> Add a new TAP-Windows virtual ethernet adapter.");
+        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.");
     }
 }
 
@@ -4067,6 +4234,7 @@
                             int actual_name_size,
                             const struct tap_reg *tap_reg_src,
                             const struct panel_reg *panel_reg_src,
+                            enum windows_driver_type *windows_driver,
                             struct gc_arena *gc)
 {
     const struct tap_reg *tap_reg = tap_reg_src;
@@ -4116,23 +4284,29 @@
     /* Save GUID for return value */
     ret = alloc_buf_gc(256, gc);
     buf_printf(&ret, "%s", tap_reg->guid);
+    if (windows_driver != NULL)
+    {
+        *windows_driver = tap_reg->windows_driver;
+    }
     return BSTR(&ret);
 }
 
 /*
  * Lookup a --dev-node adapter name in the registry
- * returning the GUID and optional actual_name.
+ * returning the GUID and optional actual_name and device type
  */
 static const char *
 get_device_guid(const char *name,
                 char *actual_name,
                 int actual_name_size,
+                enum windows_driver_type *windows_driver,
                 const struct tap_reg *tap_reg,
                 const struct panel_reg *panel_reg,
                 struct gc_arena *gc)
 {
     struct buffer ret = alloc_buf_gc(256, gc);
     struct buffer actual = clear_buf();
+    const struct tap_reg *tr;
 
     /* Make sure we have at least one TAP adapter */
     if (!tap_reg)
@@ -4148,7 +4322,8 @@
     }
 
     /* Check if GUID was explicitly specified as --dev-node parameter */
-    if (is_tap_win(name, tap_reg))
+    tr = get_adapter_by_guid(name, tap_reg);
+    if (tr)
     {
         const char *act = guid_to_name(name, panel_reg);
         buf_printf(&ret, "%s", name);
@@ -4160,16 +4335,24 @@
         {
             buf_printf(&actual, "%s", name);
         }
+        if (windows_driver)
+        {
+            *windows_driver = tr->windows_driver;
+        }
         return BSTR(&ret);
     }
 
     /* Lookup TAP adapter in network connections list */
     {
-        const char *guid = name_to_guid(name, tap_reg, panel_reg);
-        if (guid)
+        tr = get_adapter_by_name(name, tap_reg, panel_reg);
+        if (tr)
         {
             buf_printf(&actual, "%s", name);
-            buf_printf(&ret, "%s", guid);
+            if (windows_driver)
+            {
+                *windows_driver = tr->windows_driver;
+            }
+            buf_printf(&ret, "%s", tr->guid);
             return BSTR(&ret);
         }
     }
@@ -4649,8 +4832,7 @@
     DWORD index;
     ULONG aindex;
     wchar_t wbuf[256];
-    swprintf(wbuf, SIZE(wbuf), L"\\DEVICE\\TCPIP_%S", guid);
-    wbuf [SIZE(wbuf) - 1] = 0;
+    openvpn_swprintf(wbuf, SIZE(wbuf), L"\\DEVICE\\TCPIP_%S", guid);
     if (GetAdapterIndex(wbuf, &aindex) != NO_ERROR)
     {
         index = TUN_ADAPTER_INDEX_INVALID;
@@ -4714,11 +4896,14 @@
     {
         const struct tap_reg *tap_reg = get_tap_reg(&gc);
         const struct panel_reg *panel_reg = get_panel_reg(&gc);
-        const char *guid = name_to_guid(name, tap_reg, panel_reg);
-        index = get_adapter_index_method_1(guid);
-        if (index == TUN_ADAPTER_INDEX_INVALID)
+        const struct tap_reg *tr = get_adapter_by_name(name, tap_reg, panel_reg);
+        if (tr)
         {
-            index = get_adapter_index_method_2(guid);
+            index = get_adapter_index_method_1(tr->guid);
+            if (index == TUN_ADAPTER_INDEX_INVALID)
+            {
+                index = get_adapter_index_method_2(tr->guid);
+            }
         }
     }
     if (index == TUN_ADAPTER_INDEX_INVALID)
@@ -4851,7 +5036,7 @@
     if (dev_node)
     {
         /* Get the device GUID for the device specified with --dev-node. */
-        device_guid = get_device_guid(dev_node, actual_buffer, sizeof(actual_buffer), tap_reg, panel_reg, &gc);
+        device_guid = get_device_guid(dev_node, actual_buffer, sizeof(actual_buffer), NULL, tap_reg, panel_reg, &gc);
 
         if (!device_guid)
         {
@@ -4894,6 +5079,7 @@
                                                       sizeof(actual_buffer),
                                                       tap_reg,
                                                       panel_reg,
+                                                      NULL,
                                                       &gc);
 
             if (!device_guid)
@@ -5026,7 +5212,7 @@
     for (i = 0; i < n; ++i)
     {
         bool status;
-        management_sleep(1);
+        management_sleep(0);
         netcmd_semaphore_lock();
         argv_msg_prefix(M_INFO, a, "NETSH");
         status = openvpn_execve_check(a, NULL, 0, "ERROR: netsh command failed");
@@ -5049,19 +5235,18 @@
     msg(D_TUNTAP_INFO, "Start ipconfig commands for register-dns...");
     netcmd_semaphore_lock();
 
-    argv_printf(&argv, "%s%sc /flushdns",
+    argv_printf(&argv, "%s%s /flushdns",
                 get_win_sys_path(),
                 WIN_IPCONFIG_PATH_SUFFIX);
     argv_msg(D_TUNTAP_INFO, &argv);
     openvpn_execve_check(&argv, es, 0, err);
-    argv_reset(&argv);
 
-    argv_printf(&argv, "%s%sc /registerdns",
+    argv_printf(&argv, "%s%s /registerdns",
                 get_win_sys_path(),
                 WIN_IPCONFIG_PATH_SUFFIX);
     argv_msg(D_TUNTAP_INFO, &argv);
     openvpn_execve_check(&argv, es, 0, err);
-    argv_reset(&argv);
+    argv_free(&argv);
 
     netcmd_semaphore_release();
     msg(D_TUNTAP_INFO, "End ipconfig commands for register-dns...");
@@ -5157,23 +5342,29 @@
  * Set the ipv6 dns servers on the specified interface.
  * The list of dns servers currently set on the interface
  * are cleared first.
- * No action is taken if number of addresses (addr_len) < 1.
  */
 static void
 netsh_set_dns6_servers(const struct in6_addr *addr_list,
                        const int addr_len,
-                       const char *flex_name)
+                       DWORD adapter_index)
 {
     struct gc_arena gc = gc_new();
     struct argv argv = argv_new();
 
+    /* delete existing DNS settings from TAP interface */
+    argv_printf(&argv, "%s%s interface ipv6 delete dns %lu all",
+                get_win_sys_path(),
+                NETSH_PATH_SUFFIX,
+                adapter_index);
+    netsh_command(&argv, 2, M_FATAL);
+
     for (int i = 0; i < addr_len; ++i)
     {
         const char *fmt = (i == 0) ?
-                          "%s%sc interface ipv6 set dns %s static %s"
-                          : "%s%sc interface ipv6 add dns %s %s";
+                          "%s%s interface ipv6 set dns %lu static %s"
+                          : "%s%s interface ipv6 add dns %lu %s";
         argv_printf(&argv, fmt, get_win_sys_path(),
-                    NETSH_PATH_SUFFIX, flex_name,
+                    NETSH_PATH_SUFFIX, adapter_index,
                     print_in6_addr(addr_list[i], 0, &gc));
 
         /* disable slow address validation on Windows 7 and higher */
@@ -5186,7 +5377,7 @@
         netsh_command(&argv, 1, (i==0) ? M_FATAL : M_NONFATAL);
     }
 
-    argv_reset(&argv);
+    argv_free(&argv);
     gc_free(&gc);
 }
 
@@ -5195,12 +5386,13 @@
                        const in_addr_t *addr_list,
                        const int addr_len,
                        const IP_ADDR_STRING *current,
-                       const char *flex_name,
+                       DWORD adapter_index,
                        const bool test_first)
 {
     struct gc_arena gc = gc_new();
     struct argv argv = argv_new();
     bool delete_first = false;
+    bool is_dns = !strcmp(type, "dns");
 
     /* first check if we should delete existing DNS/WINS settings from TAP interface */
     if (test_first)
@@ -5218,11 +5410,11 @@
     /* delete existing DNS/WINS settings from TAP interface */
     if (delete_first)
     {
-        argv_printf(&argv, "%s%sc interface ip delete %s %s all",
+        argv_printf(&argv, "%s%s interface ip delete %s %lu all",
                     get_win_sys_path(),
                     NETSH_PATH_SUFFIX,
                     type,
-                    flex_name);
+                    adapter_index);
         netsh_command(&argv, 2, M_FATAL);
     }
 
@@ -5235,30 +5427,38 @@
             if (delete_first || !test_first || !ip_addr_member_of(addr_list[i], current))
             {
                 const char *fmt = count ?
-                                  "%s%sc interface ip add %s %s %s"
-                                  : "%s%sc interface ip set %s %s static %s";
+                                  "%s%s interface ip add %s %lu %s"
+                                  : "%s%s interface ip set %s %lu static %s";
 
                 argv_printf(&argv, fmt,
                             get_win_sys_path(),
                             NETSH_PATH_SUFFIX,
                             type,
-                            flex_name,
+                            adapter_index,
                             print_in_addr_t(addr_list[i], 0, &gc));
+
+                /* disable slow address validation on Windows 7 and higher */
+                /* only for DNS */
+                if (is_dns && win32_version_info() >= WIN_7)
+                {
+                    argv_printf_cat(&argv, "%s", "validate=no");
+                }
+
                 netsh_command(&argv, 2, M_FATAL);
 
                 ++count;
             }
             else
             {
-                msg(M_INFO, "NETSH: \"%s\" %s %s [already set]",
-                    flex_name,
+                msg(M_INFO, "NETSH: %lu %s %s [already set]",
+                    adapter_index,
                     type,
                     print_in_addr_t(addr_list[i], 0, &gc));
             }
         }
     }
 
-    argv_reset(&argv);
+    argv_free(&argv);
     gc_free(&gc);
 }
 
@@ -5282,7 +5482,7 @@
 
 static void
 netsh_ifconfig(const struct tuntap_options *to,
-               const char *flex_name,
+               DWORD adapter_index,
                const in_addr_t ip,
                const in_addr_t netmask,
                const unsigned int flags)
@@ -5295,27 +5495,26 @@
     if (flags & NI_TEST_FIRST)
     {
         const IP_ADAPTER_INFO *list = get_adapter_info_list(&gc);
-        const int index = get_adapter_index_flexible(flex_name);
-        ai = get_adapter(list, index);
-        pai = get_per_adapter_info(index, &gc);
+        ai = get_adapter(list, adapter_index);
+        pai = get_per_adapter_info(adapter_index, &gc);
     }
 
     if (flags & NI_IP_NETMASK)
     {
         if (test_adapter_ip_netmask(ai, ip, netmask))
         {
-            msg(M_INFO, "NETSH: \"%s\" %s/%s [already set]",
-                flex_name,
+            msg(M_INFO, "NETSH: %lu %s/%s [already set]",
+                adapter_index,
                 print_in_addr_t(ip, 0, &gc),
                 print_in_addr_t(netmask, 0, &gc));
         }
         else
         {
-            /* example: netsh interface ip set address my-tap static 10.3.0.1 255.255.255.0 */
-            argv_printf(&argv, "%s%sc interface ip set address %s static %s %s",
+            /* example: netsh interface ip set address 42 static 10.3.0.1 255.255.255.0 */
+            argv_printf(&argv, "%s%s interface ip set address %lu static %s %s",
                         get_win_sys_path(),
                         NETSH_PATH_SUFFIX,
-                        flex_name,
+                        adapter_index,
                         print_in_addr_t(ip, 0, &gc),
                         print_in_addr_t(netmask, 0, &gc));
 
@@ -5334,7 +5533,7 @@
                                to->dns,
                                to->dns_len,
                                pai ? &pai->DnsServerList : NULL,
-                               flex_name,
+                               adapter_index,
                                BOOL_CAST(flags & NI_TEST_FIRST));
         if (ai && ai->HaveWins)
         {
@@ -5345,36 +5544,35 @@
                                to->wins,
                                to->wins_len,
                                ai ? wins : NULL,
-                               flex_name,
+                               adapter_index,
                                BOOL_CAST(flags & NI_TEST_FIRST));
     }
 
-    argv_reset(&argv);
+    argv_free(&argv);
     gc_free(&gc);
 }
 
 static void
-netsh_enable_dhcp(const char *actual_name)
+netsh_enable_dhcp(DWORD adapter_index)
 {
     struct argv argv = argv_new();
 
-    /* example: netsh interface ip set address my-tap dhcp */
+    /* example: netsh interface ip set address 42 dhcp */
     argv_printf(&argv,
-                "%s%sc interface ip set address %s dhcp",
+                "%s%s interface ip set address %lu dhcp",
                 get_win_sys_path(),
                 NETSH_PATH_SUFFIX,
-                actual_name);
+                adapter_index);
 
     netsh_command(&argv, 4, M_FATAL);
 
-    argv_reset(&argv);
+    argv_free(&argv);
 }
 
 /* Enable dhcp on tap adapter using iservice */
 static bool
 service_enable_dhcp(const struct tuntap *tt)
 {
-    DWORD len;
     bool ret = false;
     ack_message_t ack;
     struct gc_arena gc = gc_new();
@@ -5389,11 +5587,8 @@
         .iface = { .index = tt->adapter_index, .name = "" }
     };
 
-    if (!WriteFile(pipe, &dhcp, sizeof(dhcp), &len, NULL)
-        || !ReadFile(pipe, &ack, sizeof(ack), &len, NULL))
+    if (!send_msg_iservice(pipe, &dhcp, sizeof(dhcp), &ack, "Enable_dhcp"))
     {
-        msg(M_WARN, "Enable_dhcp: could not talk to service: %s [%lu]",
-            strerror_win32(GetLastError(), &gc), GetLastError());
         goto out;
     }
 
@@ -5413,6 +5608,45 @@
     return ret;
 }
 
+static void
+windows_set_mtu(const int iface_index, const short family,
+                const int mtu)
+{
+    DWORD err = 0;
+    struct gc_arena gc = gc_new();
+    MIB_IPINTERFACE_ROW ipiface;
+    InitializeIpInterfaceEntry(&ipiface);
+    const char *family_name = (family == AF_INET6) ? "IPv6" : "IPv4";
+    ipiface.Family = family;
+    ipiface.InterfaceIndex = iface_index;
+    if (family == AF_INET6 && mtu < 1280)
+    {
+        msg(M_INFO, "NOTE: IPv6 interface MTU < 1280 conflicts with IETF standards and might not work");
+    }
+
+    err = GetIpInterfaceEntry(&ipiface);
+    if (err == NO_ERROR)
+    {
+        if (family == AF_INET)
+        {
+            ipiface.SitePrefixLength = 0;
+        }
+        ipiface.NlMtu = mtu;
+        err = SetIpInterfaceEntry(&ipiface);
+    }
+
+    if (err != NO_ERROR)
+    {
+        msg(M_WARN, "TUN: Setting %s mtu failed: %s [status=%u if_index=%d]",
+            family_name, strerror_win32(err, &gc), err, iface_index);
+    }
+    else
+    {
+        msg(M_INFO, "%s MTU set to %d on interface %d using SetIpInterfaceEntry()", family_name, mtu, iface_index);
+    }
+}
+
+
 /*
  * Return a TAP name for netsh commands.
  */
@@ -5428,13 +5662,13 @@
 
     if (dev_node)
     {
-        guid = get_device_guid(dev_node, BPTR(&actual), BCAP(&actual), tap_reg, panel_reg, gc);
+        guid = get_device_guid(dev_node, BPTR(&actual), BCAP(&actual), NULL, tap_reg, panel_reg, gc);
     }
     else
     {
-        guid = get_unspecified_device_guid(0, BPTR(&actual), BCAP(&actual), tap_reg, panel_reg, gc);
+        guid = get_unspecified_device_guid(0, BPTR(&actual), BCAP(&actual), tap_reg, panel_reg, NULL, gc);
 
-        if (get_unspecified_device_guid(1, NULL, 0, tap_reg, panel_reg, gc)) /* ambiguous if more than one TAP-Windows adapter */
+        if (get_unspecified_device_guid(1, NULL, 0, tap_reg, panel_reg, NULL, gc)) /* ambiguous if more than one TAP-Windows adapter */
         {
             guid = NULL;
         }
@@ -5474,7 +5708,7 @@
         {
             msg(M_INFO, "NOTE: now trying netsh (this may take some time)");
             netsh_ifconfig(&tt->options,
-                           tt->actual_name,
+                           tt->adapter_index,
                            tt->local,
                            tt->adapter_netmask,
                            NI_TEST_FIRST|NI_IP_NETMASK|NI_OPTIONS);
@@ -5556,6 +5790,75 @@
     buf_write(buf, str, len);
 }
 
+/*
+ * RFC3397 states that multiple searchdomains are encoded as follows:
+ *  - at start the length of the entire option is given
+ *  - each subdomain is preceded by its length
+ *  - each searchdomain is separated by a NUL character
+ * e.g. if you want "openvpn.net" and "duckduckgo.com" then you end up with
+ *  0x1D  0x7 openvpn 0x3 net 0x00 0x0A duckduckgo 0x3 com 0x00
+ */
+static void
+write_dhcp_search_str(struct buffer *buf, const int type, const char * const *str_array,
+                      int array_len, bool *error)
+{
+    char         tmp_buf[256];
+    int          i;
+    int          len = 0;
+    int          label_length_pos;
+
+    for (i=0; i < array_len; i++)
+    {
+        const char  *ptr = str_array[i];
+
+        if (strlen(ptr) + len + 1 > sizeof(tmp_buf))
+        {
+            *error = true;
+            msg(M_WARN, "write_dhcp_search_str: temp buffer overflow building DHCP options");
+            return;
+        }
+        /* Loop over all subdomains separated by a dot and replace the dot
+           with the length of the subdomain */
+
+        /* label_length_pos points to the byte to be replaced by the length
+         * of the following domain label */
+        label_length_pos = len++;
+
+        while (true)
+        {
+            if (*ptr == '.' || *ptr == '\0' )
+            {
+                tmp_buf[label_length_pos] = (len-label_length_pos)-1;
+                label_length_pos = len;
+                if (*ptr == '\0')
+                {
+                    break;
+                }
+            }
+            tmp_buf[len++] = *ptr++;
+        }
+        /* And close off with an extra NUL char */
+        tmp_buf[len++] = 0;
+    }
+
+    if (!buf_safe(buf, 2 + len))
+    {
+        *error = true;
+        msg(M_WARN, "write_search_dhcp_str: buffer overflow building DHCP options");
+        return;
+    }
+    if (len > 255)
+    {
+        *error = true;
+        msg(M_WARN, "write_dhcp_search_str: search domain string must be <= 255 bytes");
+        return;
+    }
+
+    buf_write_u8(buf, type);
+    buf_write_u8(buf, len);
+    buf_write(buf, tmp_buf, len);
+}
+
 static bool
 build_dhcp_options_string(struct buffer *buf, const struct tuntap_options *o)
 {
@@ -5580,6 +5883,13 @@
     write_dhcp_u32_array(buf, 42, (uint32_t *)o->ntp, o->ntp_len, &error);
     write_dhcp_u32_array(buf, 45, (uint32_t *)o->nbdd, o->nbdd_len, &error);
 
+    if (o->domain_search_list_len > 0)
+    {
+        write_dhcp_search_str(buf, 119, o->domain_search_list,
+                                        o->domain_search_list_len,
+                                       &error);
+    }
+
     /* the MS DHCP server option 'Disable Netbios-over-TCP/IP
      * is implemented as vendor option 001, value 002.
      * A value of 001 means 'leave NBT alone' which is the default */
@@ -5618,7 +5928,7 @@
         {
             buf_printf(&cmd, " --dhcp-renew");
         }
-        buf_printf(&cmd, " --dhcp-internal %u", (unsigned int)tt->adapter_index);
+        buf_printf(&cmd, " --dhcp-internal %lu", tt->adapter_index);
 
         fork_to_self(BSTR(&cmd));
         gc_free(&gc);
@@ -5628,18 +5938,16 @@
 static void
 register_dns_service(const struct tuntap *tt)
 {
-    DWORD len;
     HANDLE msg_channel = tt->options.msg_channel;
     ack_message_t ack;
     struct gc_arena gc = gc_new();
 
     message_header_t rdns = { msg_register_dns, sizeof(message_header_t), 0 };
 
-    if (!WriteFile(msg_channel, &rdns, sizeof(rdns), &len, NULL)
-        || !ReadFile(msg_channel, &ack, sizeof(ack), &len, NULL))
+    if (!send_msg_iservice(msg_channel, &rdns, sizeof(rdns), &ack, "Register_dns"))
     {
-        msg(M_WARN, "Register_dns: could not talk to service: %s [status=0x%lx]",
-            strerror_win32(GetLastError(), &gc), GetLastError());
+        gc_free(&gc);
+        return;
     }
 
     else if (ack.error_number != NO_ERROR)
@@ -5656,6 +5964,46 @@
     gc_free(&gc);
 }
 
+static bool
+service_register_ring_buffers(const struct tuntap *tt)
+{
+    HANDLE msg_channel = tt->options.msg_channel;
+    ack_message_t ack;
+    bool ret = true;
+    struct gc_arena gc = gc_new();
+
+    register_ring_buffers_message_t msg = {
+        .header = {
+            msg_register_ring_buffers,
+            sizeof(register_ring_buffers_message_t),
+            0
+        },
+        .device = tt->hand,
+        .send_ring_handle = tt->wintun_send_ring_handle,
+        .receive_ring_handle = tt->wintun_receive_ring_handle,
+        .send_tail_moved = tt->rw_handle.read,
+        .receive_tail_moved = tt->rw_handle.write
+    };
+
+    if (!send_msg_iservice(msg_channel, &msg, sizeof(msg), &ack, "Register ring buffers"))
+    {
+        ret = false;
+    }
+    else if (ack.error_number != NO_ERROR)
+    {
+        msg(M_NONFATAL, "Register ring buffers failed using service: %s [status=0x%x]",
+            strerror_win32(ack.error_number, &gc), ack.error_number);
+        ret = false;
+    }
+    else
+    {
+        msg(M_INFO, "Ring buffers registered via service");
+    }
+
+    gc_free(&gc);
+    return ret;
+}
+
 void
 fork_register_dns_action(struct tuntap *tt)
 {
@@ -5704,423 +6052,112 @@
     return htonl(dsa);
 }
 
-void
-open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt)
+static void
+tuntap_get_version_info(const struct tuntap *tt)
+{
+    ULONG info[3];
+    DWORD len;
+    CLEAR(info);
+    if (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_VERSION,
+                        &info, sizeof(info),
+                        &info, sizeof(info), &len, NULL))
+    {
+        msg(D_TUNTAP_INFO, "TAP-Windows Driver Version %d.%d %s",
+            (int)info[0],
+            (int)info[1],
+            (info[2] ? "(DEBUG)" : ""));
+
+    }
+    if (!(info[0] == TAP_WIN_MIN_MAJOR && info[1] >= TAP_WIN_MIN_MINOR))
+    {
+        msg(M_FATAL, "ERROR:  This version of " PACKAGE_NAME " requires a TAP-Windows driver that is at least version %d.%d -- If you recently upgraded your " PACKAGE_NAME " distribution, a reboot is probably required at this point to get Windows to see the new driver.",
+            TAP_WIN_MIN_MAJOR,
+            TAP_WIN_MIN_MINOR);
+    }
+
+    /* usage of numeric constants is ugly, but this is really tied to
+     * *this* version of the driver
+     */
+    if (tt->type == DEV_TYPE_TUN
+        && info[0] == 9 && info[1] < 8)
+    {
+        msg(M_INFO, "WARNING:  Tap-Win32 driver version %d.%d does not support IPv6 in TUN mode. IPv6 will not work. Upgrade your Tap-Win32 driver.", (int)info[0], (int)info[1]);
+    }
+
+    /* tap driver 9.8 (2.2.0 and 2.2.1 release) is buggy
+     */
+    if (tt->type == DEV_TYPE_TUN
+        && info[0] == 9 && info[1] == 8)
+    {
+        msg(M_FATAL, "ERROR:  Tap-Win32 driver version %d.%d is buggy regarding small IPv4 packets in TUN mode. Upgrade your Tap-Win32 driver.", (int)info[0], (int)info[1]);
+    }
+}
+
+static void
+tuntap_get_mtu(struct tuntap *tt)
+{
+    ULONG mtu = 0;
+    DWORD len;
+    if (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_MTU,
+                        &mtu, sizeof(mtu),
+                        &mtu, sizeof(mtu), &len, NULL))
+    {
+        tt->post_open_mtu = (int)mtu;
+        msg(D_MTU_INFO, "TAP-Windows MTU=%d", (int)mtu);
+    }
+}
+
+static void
+tuntap_set_ip_addr(struct tuntap *tt,
+                   const char *device_guid,
+                   bool dhcp_masq_post)
 {
     struct gc_arena gc = gc_new();
-    char device_path[256];
-    const char *device_guid = NULL;
-    DWORD len;
-    bool dhcp_masq = false;
-    bool dhcp_masq_post = false;
+    const DWORD index = tt->adapter_index;
 
-    /*netcmd_semaphore_lock ();*/
-
-    msg( M_INFO, "open_tun");
-
-    if (tt->type == DEV_TYPE_NULL)
+    /* flush arp cache */
+    if (tt->windows_driver == WINDOWS_DRIVER_TAP_WINDOWS6
+        && index != TUN_ADAPTER_INDEX_INVALID)
     {
-        open_null(tt);
-        gc_free(&gc);
-        return;
-    }
-    else if (tt->type == DEV_TYPE_TAP || tt->type == DEV_TYPE_TUN)
-    {
-    }
-    else
-    {
-        msg(M_FATAL|M_NOPREFIX, "Unknown virtual device type: '%s'", dev);
-    }
+        DWORD status = -1;
 
-    /*
-     * Lookup the device name in the registry, using the --dev-node high level name.
-     */
-    {
-        const struct tap_reg *tap_reg = get_tap_reg(&gc);
-        const struct panel_reg *panel_reg = get_panel_reg(&gc);
-        char actual_buffer[256];
-
-        at_least_one_tap_win(tap_reg);
-
-        if (dev_node)
+        if (tt->options.msg_channel)
         {
-            /* Get the device GUID for the device specified with --dev-node. */
-            device_guid = get_device_guid(dev_node, actual_buffer, sizeof(actual_buffer), tap_reg, panel_reg, &gc);
-
-            if (!device_guid)
-            {
-                msg(M_FATAL, "TAP-Windows adapter '%s' not found", dev_node);
-            }
-
-            /* Open Windows TAP-Windows adapter */
-            openvpn_snprintf(device_path, sizeof(device_path), "%s%s%s",
-                             USERMODEDEVICEDIR,
-                             device_guid,
-                             TAP_WIN_SUFFIX);
-
-            tt->hand = CreateFile(
-                device_path,
-                GENERIC_READ | GENERIC_WRITE,
-                0,                /* was: FILE_SHARE_READ */
-                0,
-                OPEN_EXISTING,
-                FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
-                0
-                );
-
-            if (tt->hand == INVALID_HANDLE_VALUE)
-            {
-                msg(M_ERR, "CreateFile failed on TAP device: %s", device_path);
-            }
-        }
-        else
-        {
-            int device_number = 0;
-
-            /* Try opening all TAP devices until we find one available */
-            while (true)
-            {
-                device_guid = get_unspecified_device_guid(device_number,
-                                                          actual_buffer,
-                                                          sizeof(actual_buffer),
-                                                          tap_reg,
-                                                          panel_reg,
-                                                          &gc);
-
-                if (!device_guid)
-                {
-                    msg(M_FATAL, "All TAP-Windows adapters on this system are currently in use.");
-                }
-
-                /* Open Windows TAP-Windows adapter */
-                openvpn_snprintf(device_path, sizeof(device_path), "%s%s%s",
-                                 USERMODEDEVICEDIR,
-                                 device_guid,
-                                 TAP_WIN_SUFFIX);
-
-                tt->hand = CreateFile(
-                    device_path,
-                    GENERIC_READ | GENERIC_WRITE,
-                    0,                /* was: FILE_SHARE_READ */
-                    0,
-                    OPEN_EXISTING,
-                    FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
+            ack_message_t ack;
+            flush_neighbors_message_t msg = {
+                .header = {
+                    msg_flush_neighbors,
+                    sizeof(flush_neighbors_message_t),
                     0
-                    );
+                },
+                .family = AF_INET,
+                .iface = {.index = index, .name = "" }
+            };
 
-                if (tt->hand == INVALID_HANDLE_VALUE)
-                {
-                    msg(D_TUNTAP_INFO, "CreateFile failed on TAP device: %s", device_path);
-                }
-                else
-                {
-                    break;
-                }
-
-                device_number++;
-            }
-        }
-
-        /* translate high-level device name into a device instance
-         * GUID using the registry */
-        tt->actual_name = string_alloc(actual_buffer, NULL);
-    }
-
-    msg(M_INFO, "TAP-WIN32 device [%s] opened: %s", tt->actual_name, device_path);
-    tt->adapter_index = get_adapter_index(device_guid);
-
-    /* get driver version info */
-    {
-        ULONG info[3];
-        CLEAR(info);
-        if (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_VERSION,
-                            &info, sizeof(info),
-                            &info, sizeof(info), &len, NULL))
-        {
-            msg(D_TUNTAP_INFO, "TAP-Windows Driver Version %d.%d %s",
-                (int) info[0],
-                (int) info[1],
-                (info[2] ? "(DEBUG)" : ""));
-
-        }
-        if (!(info[0] == TAP_WIN_MIN_MAJOR && info[1] >= TAP_WIN_MIN_MINOR))
-        {
-            msg(M_FATAL, "ERROR:  This version of " PACKAGE_NAME " requires a TAP-Windows driver that is at least version %d.%d -- If you recently upgraded your " PACKAGE_NAME " distribution, a reboot is probably required at this point to get Windows to see the new driver.",
-                TAP_WIN_MIN_MAJOR,
-                TAP_WIN_MIN_MINOR);
-        }
-
-        /* usage of numeric constants is ugly, but this is really tied to
-         * *this* version of the driver
-         */
-        if (tt->type == DEV_TYPE_TUN
-            && info[0] == 9 && info[1] < 8)
-        {
-            msg( M_INFO, "WARNING:  Tap-Win32 driver version %d.%d does not support IPv6 in TUN mode. IPv6 will not work. Upgrade your Tap-Win32 driver.", (int) info[0], (int) info[1] );
-        }
-
-        /* tap driver 9.8 (2.2.0 and 2.2.1 release) is buggy
-         */
-        if (tt->type == DEV_TYPE_TUN
-            && info[0] == 9 && info[1] == 8)
-        {
-            msg( M_FATAL, "ERROR:  Tap-Win32 driver version %d.%d is buggy regarding small IPv4 packets in TUN mode. Upgrade your Tap-Win32 driver.", (int) info[0], (int) info[1] );
-        }
-    }
-
-    /* get driver MTU */
-    {
-        ULONG mtu;
-        if (DeviceIoControl(tt->hand, TAP_WIN_IOCTL_GET_MTU,
-                            &mtu, sizeof(mtu),
-                            &mtu, sizeof(mtu), &len, NULL))
-        {
-            tt->post_open_mtu = (int) mtu;
-            msg(D_MTU_INFO, "TAP-Windows MTU=%d", (int) mtu);
-        }
-    }
-
-    /*
-     * Preliminaries for setting TAP-Windows adapter TCP/IP
-     * properties via --ip-win32 dynamic or --ip-win32 adaptive.
-     */
-    if (tt->did_ifconfig_setup)
-    {
-        if (tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ)
-        {
-            /*
-             * If adapter is set to non-DHCP, set to DHCP mode.
-             */
-            if (dhcp_status(tt->adapter_index) == DHCP_STATUS_DISABLED)
+            if (send_msg_iservice(tt->options.msg_channel, &msg, sizeof(msg),
+                                  &ack, "TUN"))
             {
-                /* try using the service if available, else directly execute netsh */
-                if (tt->options.msg_channel)
-                {
-                    service_enable_dhcp(tt);
-                }
-                else
-                {
-                    netsh_enable_dhcp(tt->actual_name);
-                }
-            }
-            dhcp_masq = true;
-            dhcp_masq_post = true;
-        }
-        else if (tt->options.ip_win32_type == IPW32_SET_ADAPTIVE)
-        {
-            /*
-             * If adapter is set to non-DHCP, use netsh right away.
-             */
-            if (dhcp_status(tt->adapter_index) != DHCP_STATUS_ENABLED)
-            {
-                netsh_ifconfig(&tt->options,
-                               tt->actual_name,
-                               tt->local,
-                               tt->adapter_netmask,
-                               NI_TEST_FIRST|NI_IP_NETMASK|NI_OPTIONS);
-            }
-            else
-            {
-                dhcp_masq = true;
-            }
-        }
-    }
-
-    /* set point-to-point mode if TUN device */
-
-    if (tt->type == DEV_TYPE_TUN)
-    {
-        if (!tt->did_ifconfig_setup)
-        {
-            msg(M_FATAL, "ERROR: --dev tun also requires --ifconfig");
-        }
-
-        if (tt->topology == TOP_SUBNET)
-        {
-            in_addr_t ep[3];
-            BOOL status;
-
-            ep[0] = htonl(tt->local);
-            ep[1] = htonl(tt->local & tt->remote_netmask);
-            ep[2] = htonl(tt->remote_netmask);
-
-            status = DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_TUN,
-                                     ep, sizeof(ep),
-                                     ep, sizeof(ep), &len, NULL);
-
-            msg(status ? M_INFO : M_FATAL, "Set TAP-Windows TUN subnet mode network/local/netmask = %s/%s/%s [%s]",
-                print_in_addr_t(ep[1], IA_NET_ORDER, &gc),
-                print_in_addr_t(ep[0], IA_NET_ORDER, &gc),
-                print_in_addr_t(ep[2], IA_NET_ORDER, &gc),
-                status ? "SUCCEEDED" : "FAILED");
-
-        }
-        else
-        {
-
-            in_addr_t ep[2];
-            ep[0] = htonl(tt->local);
-            ep[1] = htonl(tt->remote_netmask);
-
-            if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT,
-                                 ep, sizeof(ep),
-                                 ep, sizeof(ep), &len, NULL))
-            {
-                msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a DeviceIoControl call to set Point-to-Point mode, which is required for --dev tun");
-            }
-        }
-    }
-
-    /* should we tell the TAP-Windows driver to masquerade as a DHCP server as a means
-     * of setting the adapter address? */
-    if (dhcp_masq)
-    {
-        uint32_t ep[4];
-
-        /* We will answer DHCP requests with a reply to set IP/subnet to these values */
-        ep[0] = htonl(tt->local);
-        ep[1] = htonl(tt->adapter_netmask);
-
-        /* At what IP address should the DHCP server masquerade at? */
-        if (tt->type == DEV_TYPE_TUN)
-        {
-            if (tt->topology == TOP_SUBNET)
-            {
-                if (tt->options.dhcp_masq_custom_offset)
-                {
-                    ep[2] = dhcp_masq_addr(tt->local, tt->remote_netmask, tt->options.dhcp_masq_offset);
-                }
-                else
-                {
-                    ep[2] = dhcp_masq_addr(tt->local, tt->remote_netmask, -1);
-                }
-            }
-            else
-            {
-                ep[2] = htonl(tt->remote_netmask);
-            }
-        }
-        else
-        {
-            ASSERT(tt->type == DEV_TYPE_TAP);
-            ep[2] = dhcp_masq_addr(tt->local, tt->adapter_netmask, tt->options.dhcp_masq_custom_offset ? tt->options.dhcp_masq_offset : 0);
-        }
-
-        /* lease time in seconds */
-        ep[3] = (uint32_t) tt->options.dhcp_lease_time;
-
-        ASSERT(ep[3] > 0);
-
-#ifndef SIMULATE_DHCP_FAILED /* this code is disabled to simulate bad DHCP negotiation */
-        if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_MASQ,
-                             ep, sizeof(ep),
-                             ep, sizeof(ep), &len, NULL))
-        {
-            msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a DeviceIoControl call to set TAP_WIN_IOCTL_CONFIG_DHCP_MASQ mode");
-        }
-
-        msg(M_INFO, "Notified TAP-Windows driver to set a DHCP IP/netmask of %s/%s on interface %s [DHCP-serv: %s, lease-time: %d]",
-            print_in_addr_t(tt->local, 0, &gc),
-            print_in_addr_t(tt->adapter_netmask, 0, &gc),
-            device_guid,
-            print_in_addr_t(ep[2], IA_NET_ORDER, &gc),
-            ep[3]
-            );
-
-        /* user-supplied DHCP options capability */
-        if (tt->options.dhcp_options)
-        {
-            struct buffer buf = alloc_buf(256);
-            if (build_dhcp_options_string(&buf, &tt->options))
-            {
-                msg(D_DHCP_OPT, "DHCP option string: %s", format_hex(BPTR(&buf), BLEN(&buf), 0, &gc));
-                if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT,
-                                     BPTR(&buf), BLEN(&buf),
-                                     BPTR(&buf), BLEN(&buf), &len, NULL))
-                {
-                    msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT DeviceIoControl call");
-                }
-            }
-            else
-            {
-                msg(M_WARN, "DHCP option string not set due to error");
-            }
-            free_buf(&buf);
-        }
-#endif /* ifndef SIMULATE_DHCP_FAILED */
-    }
-
-    /* set driver media status to 'connected' */
-    {
-        ULONG status = TRUE;
-        if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_SET_MEDIA_STATUS,
-                             &status, sizeof(status),
-                             &status, sizeof(status), &len, NULL))
-        {
-            msg(M_WARN, "WARNING: The TAP-Windows driver rejected a TAP_WIN_IOCTL_SET_MEDIA_STATUS DeviceIoControl call.");
-        }
-    }
-
-    /* possible wait for adapter to come up */
-    {
-        int s = tt->options.tap_sleep;
-        if (s > 0)
-        {
-            msg(M_INFO, "Sleeping for %d seconds...", s);
-            management_sleep(s);
-        }
-    }
-
-    /* possibly use IP Helper API to set IP address on adapter */
-    {
-        const DWORD index = tt->adapter_index;
-
-        /* flush arp cache */
-        if (index != TUN_ADAPTER_INDEX_INVALID)
-        {
-            DWORD status = -1;
-
-            if (tt->options.msg_channel)
-            {
-                ack_message_t ack;
-                flush_neighbors_message_t msg = {
-                    .header = {
-                        msg_flush_neighbors,
-                        sizeof(flush_neighbors_message_t),
-                        0
-                    },
-                    .family = AF_INET,
-                    .iface = { .index = index, .name = "" }
-                };
-
-                if (!WriteFile(tt->options.msg_channel, &msg, sizeof(msg), &len, NULL)
-                    || !ReadFile(tt->options.msg_channel, &ack, sizeof(ack), &len, NULL))
-                {
-                    msg(M_WARN, "TUN: could not talk to service: %s [%lu]",
-                        strerror_win32(GetLastError(), &gc), GetLastError());
-                }
-
                 status = ack.error_number;
             }
-            else
-            {
-                status = FlushIpNetTable(index);
-            }
+        }
+        else
+        {
+            status = FlushIpNetTable(index);
+        }
 
-            if (status == NO_ERROR)
-            {
-                msg(M_INFO, "Successful ARP Flush on interface [%u] %s",
-                    (unsigned int)index,
-                    device_guid);
-            }
-            else if (status != -1)
-            {
-                msg(D_TUNTAP_INFO, "NOTE: FlushIpNetTable failed on interface [%u] %s (status=%u) : %s",
-                    (unsigned int)index,
-                    device_guid,
-                    (unsigned int)status,
-                    strerror_win32(status, &gc));
-            }
+        if (status == NO_ERROR)
+        {
+            msg(M_INFO, "Successful ARP Flush on interface [%lu] %s",
+                index,
+                device_guid);
+        }
+        else if (status != -1)
+        {
+            msg(D_TUNTAP_INFO, "NOTE: FlushIpNetTable failed on interface [%lu] %s (status=%lu) : %s",
+                index,
+                device_guid,
+                status,
+                strerror_win32(status, &gc));
         }
 
         /*
@@ -6150,65 +6187,535 @@
         {
             fork_dhcp_action(tt);
         }
+    }
 
-        if (tt->did_ifconfig_setup && tt->options.ip_win32_type == IPW32_SET_IPAPI)
+    if (tt->did_ifconfig_setup && tt->options.ip_win32_type == IPW32_SET_IPAPI)
+    {
+        DWORD status;
+        const char *error_suffix = "I am having trouble using the Windows 'IP helper API' to automatically set the IP address -- consider using other --ip-win32 methods (not 'ipapi')";
+
+        /* couldn't get adapter index */
+        if (index == TUN_ADAPTER_INDEX_INVALID)
         {
-            DWORD status;
-            const char *error_suffix = "I am having trouble using the Windows 'IP helper API' to automatically set the IP address -- consider using other --ip-win32 methods (not 'ipapi')";
+            msg(M_FATAL, "ERROR: unable to get adapter index for interface %s -- %s",
+                device_guid,
+                error_suffix);
+        }
 
-            /* couldn't get adapter index */
-            if (index == TUN_ADAPTER_INDEX_INVALID)
+        /* check dhcp enable status */
+        if (dhcp_status(index) == DHCP_STATUS_DISABLED)
+        {
+            msg(M_WARN, "NOTE: You have selected (explicitly or by default) '--ip-win32 ipapi', which has a better chance of working correctly if the TAP-Windows TCP/IP properties are set to 'Obtain an IP address automatically'");
+        }
+
+        /* delete previously added IP addresses which were not
+         * correctly deleted */
+        delete_temp_addresses(index);
+
+        /* add a new IP address */
+        if ((status = AddIPAddress(htonl(tt->local),
+                                   htonl(tt->adapter_netmask),
+                                   index,
+                                   &tt->ipapi_context,
+                                   &tt->ipapi_instance)) == NO_ERROR)
+        {
+            msg(M_INFO, "Succeeded in adding a temporary IP/netmask of %s/%s to interface %s using the Win32 IP Helper API",
+                print_in_addr_t(tt->local, 0, &gc),
+                print_in_addr_t(tt->adapter_netmask, 0, &gc),
+                device_guid
+                );
+        }
+        else
+        {
+            msg(M_FATAL, "ERROR: AddIPAddress %s/%s failed on interface %s, index=%lu, status=%lu (windows error: '%s') -- %s",
+                print_in_addr_t(tt->local, 0, &gc),
+                print_in_addr_t(tt->adapter_netmask, 0, &gc),
+                device_guid,
+                index,
+                status,
+                strerror_win32(status, &gc),
+                error_suffix);
+        }
+        tt->ipapi_context_defined = true;
+    }
+
+    gc_free(&gc);
+}
+
+static bool
+wintun_register_ring_buffer(struct tuntap *tt, const char *device_guid)
+{
+    bool ret = true;
+
+    tt->wintun_send_ring = (struct tun_ring *)MapViewOfFile(tt->wintun_send_ring_handle,
+                                                            FILE_MAP_ALL_ACCESS,
+                                                            0,
+                                                            0,
+                                                            sizeof(struct tun_ring));
+
+    tt->wintun_receive_ring = (struct tun_ring *)MapViewOfFile(tt->wintun_receive_ring_handle,
+                                                               FILE_MAP_ALL_ACCESS,
+                                                               0,
+                                                               0,
+                                                               sizeof(struct tun_ring));
+
+    if (tt->options.msg_channel)
+    {
+        ret = service_register_ring_buffers(tt);
+    }
+    else
+    {
+        if (!register_ring_buffers(tt->hand,
+                                   tt->wintun_send_ring,
+                                   tt->wintun_receive_ring,
+                                   tt->rw_handle.read,
+                                   tt->rw_handle.write))
+        {
+            switch (GetLastError())
             {
-                msg(M_FATAL, "ERROR: unable to get adapter index for interface %s -- %s",
-                    device_guid,
-                    error_suffix);
+                case ERROR_ACCESS_DENIED:
+                    msg(M_FATAL, "ERROR:  Wintun requires SYSTEM privileges and therefore "
+                                 "should be used with interactive service. If you want to "
+                                 "use openvpn from command line, you need to do SYSTEM "
+                                 "elevation yourself (for example with psexec).");
+                    break;
+
+                case ERROR_ALREADY_INITIALIZED:
+                    msg(M_NONFATAL, "Adapter %s is already in use", device_guid);
+                    break;
+
+                default:
+                    msg(M_NONFATAL | M_ERRNO, "Failed to register ring buffers");
             }
+            ret = false;
+        }
 
-            /* check dhcp enable status */
-            if (dhcp_status(index) == DHCP_STATUS_DISABLED)
+    }
+    return ret;
+}
+
+static void
+tuntap_set_connected(const struct tuntap *tt)
+{
+    ULONG status = TRUE;
+    DWORD len;
+    if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_SET_MEDIA_STATUS,
+                         &status, sizeof(status),
+                         &status, sizeof(status), &len, NULL))
+    {
+        msg(M_WARN, "WARNING: The TAP-Windows driver rejected a TAP_WIN_IOCTL_SET_MEDIA_STATUS DeviceIoControl call.");
+    }
+
+    int s = tt->options.tap_sleep;
+    if (s > 0)
+    {
+        msg(M_INFO, "Sleeping for %d seconds...", s);
+        management_sleep(s);
+    }
+}
+
+static void
+tuntap_set_ptp(const struct tuntap *tt)
+{
+    DWORD len;
+    struct gc_arena gc = gc_new();
+
+    if (!tt->did_ifconfig_setup && !tt->did_ifconfig_ipv6_setup)
+    {
+        msg(M_FATAL, "ERROR: --dev tun also requires --ifconfig");
+    }
+
+    /* send 0/0/0 to the TAP driver even if we have no IPv4 configured to
+     * ensure it is somehow initialized.
+     */
+    if (!tt->did_ifconfig_setup || tt->topology == TOP_SUBNET)
+    {
+        in_addr_t ep[3];
+        BOOL status;
+
+        ep[0] = htonl(tt->local);
+        ep[1] = htonl(tt->local & tt->remote_netmask);
+        ep[2] = htonl(tt->remote_netmask);
+
+        status = DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_TUN,
+                                 ep, sizeof(ep),
+                                 ep, sizeof(ep), &len, NULL);
+
+        if (tt->did_ifconfig_setup)
+        {
+            msg(status ? M_INFO : M_FATAL, "Set TAP-Windows TUN subnet mode network/local/netmask = %s/%s/%s [%s]",
+                print_in_addr_t(ep[1], IA_NET_ORDER, &gc),
+                print_in_addr_t(ep[0], IA_NET_ORDER, &gc),
+                print_in_addr_t(ep[2], IA_NET_ORDER, &gc),
+                status ? "SUCCEEDED" : "FAILED");
+        }
+        else
+        {
+            msg(status ? M_INFO : M_FATAL, "Set TAP-Windows TUN with fake IPv4 [%s]",
+                status ? "SUCCEEDED" : "FAILED");
+        }
+    }
+    else
+    {
+        in_addr_t ep[2];
+        ep[0] = htonl(tt->local);
+        ep[1] = htonl(tt->remote_netmask);
+
+        if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT,
+                             ep, sizeof(ep),
+                             ep, sizeof(ep), &len, NULL))
+        {
+            msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a DeviceIoControl call to set Point-to-Point mode, which is required for --dev tun");
+        }
+    }
+
+    gc_free(&gc);
+}
+
+static void
+tuntap_dhcp_mask(const struct tuntap *tt, const char *device_guid)
+{
+    struct gc_arena gc = gc_new();
+    DWORD len;
+    uint32_t ep[4];
+
+    /* We will answer DHCP requests with a reply to set IP/subnet to these values */
+    ep[0] = htonl(tt->local);
+    ep[1] = htonl(tt->adapter_netmask);
+
+    /* At what IP address should the DHCP server masquerade at? */
+    if (tt->type == DEV_TYPE_TUN)
+    {
+        if (tt->topology == TOP_SUBNET)
+        {
+            if (tt->options.dhcp_masq_custom_offset)
             {
-                msg(M_WARN, "NOTE: You have selected (explicitly or by default) '--ip-win32 ipapi', which has a better chance of working correctly if the TAP-Windows TCP/IP properties are set to 'Obtain an IP address automatically'");
-            }
-
-            /* delete previously added IP addresses which were not
-             * correctly deleted */
-            delete_temp_addresses(index);
-
-            /* add a new IP address */
-            if ((status = AddIPAddress(htonl(tt->local),
-                                       htonl(tt->adapter_netmask),
-                                       index,
-                                       &tt->ipapi_context,
-                                       &tt->ipapi_instance)) == NO_ERROR)
-            {
-                msg(M_INFO, "Succeeded in adding a temporary IP/netmask of %s/%s to interface %s using the Win32 IP Helper API",
-                    print_in_addr_t(tt->local, 0, &gc),
-                    print_in_addr_t(tt->adapter_netmask, 0, &gc),
-                    device_guid
-                    );
+                ep[2] = dhcp_masq_addr(tt->local, tt->remote_netmask, tt->options.dhcp_masq_offset);
             }
             else
             {
-                msg(M_FATAL, "ERROR: AddIPAddress %s/%s failed on interface %s, index=%d, status=%u (windows error: '%s') -- %s",
-                    print_in_addr_t(tt->local, 0, &gc),
-                    print_in_addr_t(tt->adapter_netmask, 0, &gc),
-                    device_guid,
-                    (int)index,
-                    (unsigned int)status,
-                    strerror_win32(status, &gc),
-                    error_suffix);
+                ep[2] = dhcp_masq_addr(tt->local, tt->remote_netmask, -1);
             }
-            tt->ipapi_context_defined = true;
+        }
+        else
+        {
+            ep[2] = htonl(tt->remote_netmask);
         }
     }
-    /*netcmd_semaphore_release ();*/
+    else
+    {
+        ASSERT(tt->type == DEV_TYPE_TAP);
+        ep[2] = dhcp_masq_addr(tt->local, tt->adapter_netmask, tt->options.dhcp_masq_custom_offset ? tt->options.dhcp_masq_offset : 0);
+    }
+
+    /* lease time in seconds */
+    ep[3] = (uint32_t)tt->options.dhcp_lease_time;
+
+    ASSERT(ep[3] > 0);
+
+#ifndef SIMULATE_DHCP_FAILED /* this code is disabled to simulate bad DHCP negotiation */
+    if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_MASQ,
+                         ep, sizeof(ep),
+                         ep, sizeof(ep), &len, NULL))
+    {
+        msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a DeviceIoControl call to set TAP_WIN_IOCTL_CONFIG_DHCP_MASQ mode");
+    }
+
+    msg(M_INFO, "Notified TAP-Windows driver to set a DHCP IP/netmask of %s/%s on interface %s [DHCP-serv: %s, lease-time: %d]",
+        print_in_addr_t(tt->local, 0, &gc),
+        print_in_addr_t(tt->adapter_netmask, 0, &gc),
+        device_guid,
+        print_in_addr_t(ep[2], IA_NET_ORDER, &gc),
+        ep[3]
+        );
+
+    /* user-supplied DHCP options capability */
+    if (tt->options.dhcp_options)
+    {
+        struct buffer buf = alloc_buf(256);
+        if (build_dhcp_options_string(&buf, &tt->options))
+        {
+            msg(D_DHCP_OPT, "DHCP option string: %s", format_hex(BPTR(&buf), BLEN(&buf), 0, &gc));
+            if (!DeviceIoControl(tt->hand, TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT,
+                                 BPTR(&buf), BLEN(&buf),
+                                 BPTR(&buf), BLEN(&buf), &len, NULL))
+            {
+                msg(M_FATAL, "ERROR: The TAP-Windows driver rejected a TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT DeviceIoControl call");
+            }
+        }
+        else
+        {
+            msg(M_WARN, "DHCP option string not set due to error");
+        }
+        free_buf(&buf);
+    }
+#endif /* ifndef SIMULATE_DHCP_FAILED */
+
     gc_free(&gc);
 }
 
+static bool
+tun_try_open_device(struct tuntap *tt, const char *device_guid, const struct device_instance_id_interface *device_instance_id_interface)
+{
+    const char *path = NULL;
+    char tuntap_device_path[256];
+
+    if (tt->windows_driver == WINDOWS_DRIVER_WINTUN)
+    {
+        const struct device_instance_id_interface *dev_if;
+
+        /* Open Wintun adapter */
+        for (dev_if = device_instance_id_interface; dev_if != NULL; dev_if = dev_if->next)
+        {
+            if (strcmp(dev_if->net_cfg_instance_id, device_guid) == 0)
+            {
+                path = dev_if->device_interface_list;
+                break;
+            }
+        }
+        if (path == NULL)
+        {
+            return false;
+        }
+    }
+    else
+    {
+        /* Open TAP-Windows adapter */
+        openvpn_snprintf(tuntap_device_path, sizeof(tuntap_device_path), "%s%s%s",
+                         USERMODEDEVICEDIR,
+                         device_guid,
+                         TAP_WIN_SUFFIX);
+        path = tuntap_device_path;
+    }
+
+    tt->hand = CreateFile(path,
+                          GENERIC_READ | GENERIC_WRITE,
+                          0,         /* was: FILE_SHARE_READ */
+                          0,
+                          OPEN_EXISTING,
+                          FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED,
+                          0);
+    if (tt->hand == INVALID_HANDLE_VALUE)
+    {
+        msg(D_TUNTAP_INFO, "CreateFile failed on %s device: %s", print_windows_driver(tt->windows_driver), path);
+        return false;
+    }
+
+    if (tt->windows_driver == WINDOWS_DRIVER_WINTUN)
+    {
+        /* Wintun adapter may be considered "open" after ring buffers are successfuly registered. */
+        if (!wintun_register_ring_buffer(tt, device_guid))
+        {
+            msg(D_TUNTAP_INFO, "Failed to register %s adapter ring buffers", device_guid);
+            CloseHandle(tt->hand);
+            tt->hand = NULL;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static 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);
+    const struct panel_reg *panel_reg = get_panel_reg(gc);
+    const struct device_instance_id_interface *device_instance_id_interface = get_device_instance_id_interface(gc);
+    char actual_buffer[256];
+
+    at_least_one_tap_win(tap_reg);
+
+    /*
+     * Lookup the device name in the registry, using the --dev-node high level name.
+     */
+    if (dev_node)
+    {
+        enum windows_driver_type windows_driver = WINDOWS_DRIVER_UNSPECIFIED;
+
+        /* Get the device GUID for the device specified with --dev-node. */
+        *device_guid = get_device_guid(dev_node, actual_buffer, sizeof(actual_buffer), &windows_driver, tap_reg, panel_reg, gc);
+
+        if (!*device_guid)
+        {
+            msg(M_FATAL, "Adapter '%s' not found", dev_node);
+        }
+
+        if (tt->windows_driver != windows_driver)
+        {
+            msg(M_FATAL, "Adapter '%s' is using %s driver, %s expected. If you want to use this device, adjust --windows-driver.",
+                dev_node, print_windows_driver(windows_driver), print_windows_driver(tt->windows_driver));
+        }
+
+        if (!tun_try_open_device(tt, *device_guid, device_instance_id_interface))
+        {
+            msg(M_FATAL, "Failed to open %s adapter: %s", print_windows_driver(tt->windows_driver), dev_node);
+        }
+    }
+    else
+    {
+        int device_number = 0;
+
+        /* Try opening all TAP devices until we find one available */
+        while (true)
+        {
+            enum windows_driver_type windows_driver = WINDOWS_DRIVER_UNSPECIFIED;
+            *device_guid = get_unspecified_device_guid(device_number,
+                                                       actual_buffer,
+                                                       sizeof(actual_buffer),
+                                                       tap_reg,
+                                                       panel_reg,
+                                                       &windows_driver,
+                                                       gc);
+
+            if (!*device_guid)
+            {
+                msg(M_FATAL, "All %s adapters on this system are currently in use or disabled.", print_windows_driver(tt->windows_driver));
+            }
+
+            if (tt->windows_driver != windows_driver)
+            {
+                goto next;
+            }
+
+            if (tun_try_open_device(tt, *device_guid, device_instance_id_interface))
+            {
+                break;
+            }
+
+next:
+            device_number++;
+        }
+    }
+
+    /* translate high-level device name into a device instance
+     * GUID using the registry */
+    tt->actual_name = string_alloc(actual_buffer, NULL);
+
+    msg(M_INFO, "%s device [%s] opened", print_windows_driver(tt->windows_driver), tt->actual_name);
+    tt->adapter_index = get_adapter_index(*device_guid);
+}
+
+static void
+tuntap_set_ip_props(const struct tuntap *tt, bool *dhcp_masq, bool *dhcp_masq_post)
+{
+    if (tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ)
+    {
+        /*
+         * If adapter is set to non-DHCP, set to DHCP mode.
+         */
+        if (dhcp_status(tt->adapter_index) == DHCP_STATUS_DISABLED)
+        {
+            /* try using the service if available, else directly execute netsh */
+            if (tt->options.msg_channel)
+            {
+                service_enable_dhcp(tt);
+            }
+            else
+            {
+                netsh_enable_dhcp(tt->adapter_index);
+            }
+        }
+        *dhcp_masq = true;
+        *dhcp_masq_post = true;
+    }
+    else if (tt->options.ip_win32_type == IPW32_SET_ADAPTIVE)
+    {
+        /*
+         * If adapter is set to non-DHCP, use netsh right away.
+         */
+        if (dhcp_status(tt->adapter_index) != DHCP_STATUS_ENABLED)
+        {
+            netsh_ifconfig(&tt->options,
+                           tt->adapter_index,
+                           tt->local,
+                           tt->adapter_netmask,
+                           NI_TEST_FIRST | NI_IP_NETMASK | NI_OPTIONS);
+        }
+        else
+        {
+            *dhcp_masq = true;
+        }
+    }
+}
+
+static void
+tuntap_post_open(struct tuntap *tt, const char *device_guid)
+{
+    bool dhcp_masq = false;
+    bool dhcp_masq_post = false;
+
+    if (tt->windows_driver == WINDOWS_DRIVER_TAP_WINDOWS6)
+    {
+        /* get driver version info */
+        tuntap_get_version_info(tt);
+
+        /* get driver MTU */
+        tuntap_get_mtu(tt);
+
+        /*
+         * Preliminaries for setting TAP-Windows adapter TCP/IP
+         * properties via --ip-win32 dynamic or --ip-win32 adaptive.
+         */
+        if (tt->did_ifconfig_setup)
+        {
+            tuntap_set_ip_props(tt, &dhcp_masq, &dhcp_masq_post);
+        }
+
+        /* set point-to-point mode if TUN device */
+        if (tt->type == DEV_TYPE_TUN)
+        {
+            tuntap_set_ptp(tt);
+        }
+
+        /* should we tell the TAP-Windows driver to masquerade as a DHCP server as a means
+         * of setting the adapter address? */
+        if (dhcp_masq)
+        {
+            tuntap_dhcp_mask(tt, device_guid);
+        }
+
+        /* set driver media status to 'connected' */
+        tuntap_set_connected(tt);
+    }
+
+    /* possibly use IP Helper API to set IP address on adapter */
+    tuntap_set_ip_addr(tt, device_guid, dhcp_masq_post);
+}
+
+void
+open_tun(const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt)
+{
+    const char *device_guid = NULL;
+
+    /*netcmd_semaphore_lock ();*/
+
+    msg( M_INFO, "open_tun");
+
+    if (tt->type == DEV_TYPE_NULL)
+    {
+        open_null(tt);
+        return;
+    }
+    else if (tt->type != DEV_TYPE_TAP && tt->type != DEV_TYPE_TUN)
+    {
+        msg(M_FATAL|M_NOPREFIX, "Unknown virtual device type: '%s'", dev);
+    }
+
+    struct gc_arena gc = gc_new(); /* used also for device_guid allocation */
+    tun_open_device(tt, dev_node, &device_guid, &gc);
+
+    tuntap_post_open(tt, device_guid);
+
+    gc_free(&gc);
+
+    /*netcmd_semaphore_release ();*/
+}
+
 const char *
 tap_win_getinfo(const struct tuntap *tt, struct gc_arena *gc)
 {
-    if (tt && tt->hand != NULL)
+    if (tt->windows_driver == WINDOWS_DRIVER_TAP_WINDOWS6)
     {
         struct buffer out = alloc_buf_gc(256, gc);
         DWORD len;
@@ -6226,7 +6733,7 @@
 void
 tun_show_debug(struct tuntap *tt)
 {
-    if (tt && tt->hand != NULL)
+    if (tt->windows_driver == WINDOWS_DRIVER_TAP_WINDOWS6)
     {
         struct buffer out = alloc_buf(1024);
         DWORD len;
@@ -6241,107 +6748,176 @@
     }
 }
 
-void
-close_tun(struct tuntap *tt)
+static void
+netsh_delete_address_dns(const struct tuntap *tt, bool ipv6, struct gc_arena *gc)
 {
+    const char *ifconfig_ip_local;
+    struct argv argv = argv_new();
+
+    /* delete ipvX dns servers if any were set */
+    int len = ipv6 ? tt->options.dns6_len : tt->options.dns_len;
+    if (len > 0)
+    {
+        argv_printf(&argv,
+                    "%s%s interface %s delete dns %lu all",
+                    get_win_sys_path(),
+                    NETSH_PATH_SUFFIX,
+                    ipv6 ? "ipv6" : "ipv4",
+                    tt->adapter_index);
+        netsh_command(&argv, 1, M_WARN);
+    }
+
+    if (!ipv6 && tt->options.wins_len > 0)
+    {
+        argv_printf(&argv,
+                    "%s%s interface ipv4 delete winsservers %lu all",
+                    get_win_sys_path(),
+                    NETSH_PATH_SUFFIX,
+                    tt->adapter_index);
+        netsh_command(&argv, 1, M_WARN);
+    }
+
+    if (ipv6 && tt->type == DEV_TYPE_TUN)
+    {
+        delete_route_connected_v6_net(tt);
+    }
+
+    /* "store=active" is needed in Windows 8(.1) to delete the
+     * address we added (pointed out by Cedric Tabary).
+     */
+
+    /* netsh interface ipvX delete address %lu %s */
+    if (ipv6)
+    {
+        ifconfig_ip_local = print_in6_addr(tt->local_ipv6, 0, gc);
+    }
+    else
+    {
+        ifconfig_ip_local = print_in_addr_t(tt->local, 0, gc);
+    }
+    argv_printf(&argv,
+                "%s%s interface %s delete address %lu %s store=active",
+                get_win_sys_path(),
+                NETSH_PATH_SUFFIX,
+                ipv6 ? "ipv6" : "ipv4",
+                tt->adapter_index,
+                ifconfig_ip_local);
+    netsh_command(&argv, 1, M_WARN);
+
+    argv_free(&argv);
+}
+
+void
+close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
+{
+    ASSERT(tt);
+
     struct gc_arena gc = gc_new();
 
-    if (tt)
+    if (tt->did_ifconfig_ipv6_setup)
     {
-        if (tt->did_ifconfig_ipv6_setup)
+        if (tt->options.ip_win32_type == IPW32_SET_MANUAL)
         {
-            /* remove route pointing to interface */
-            delete_route_connected_v6_net(tt, NULL);
-
-            if (tt->options.msg_channel)
-            {
-                do_address_service(false, AF_INET6, tt);
-                if (tt->options.dns6_len > 0)
-                {
-                    do_dns6_service(false, tt);
-                }
-            }
-            else
-            {
-                const char *ifconfig_ipv6_local;
-                struct argv argv = argv_new();
-
-                /* "store=active" is needed in Windows 8(.1) to delete the
-                 * address we added (pointed out by Cedric Tabary).
-                 */
-
-                /* netsh interface ipv6 delete address \"%s\" %s */
-                ifconfig_ipv6_local = print_in6_addr(tt->local_ipv6, 0,  &gc);
-                argv_printf(&argv,
-                            "%s%sc interface ipv6 delete address %s %s store=active",
-                            get_win_sys_path(),
-                            NETSH_PATH_SUFFIX,
-                            tt->actual_name,
-                            ifconfig_ipv6_local);
-
-                netsh_command(&argv, 1, M_WARN);
-
-                /* delete ipv6 dns servers if any were set */
-                if (tt->options.dns6_len > 0)
-                {
-                    argv_printf(&argv,
-                                "%s%sc interface ipv6 delete dns %s all",
-                                get_win_sys_path(),
-                                NETSH_PATH_SUFFIX,
-                                tt->actual_name);
-                    netsh_command(&argv, 1, M_WARN);
-                }
-                argv_reset(&argv);
-            }
+            /* We didn't do ifconfig. */
         }
-#if 1
-        if (tt->ipapi_context_defined)
+        else if (tt->options.msg_channel)
         {
-            DWORD status;
-            if ((status = DeleteIPAddress(tt->ipapi_context)) != NO_ERROR)
+            /* If IPv4 is not enabled, delete DNS domain here */
+            if (!tt->did_ifconfig_setup)
             {
-                msg(M_WARN, "Warning: DeleteIPAddress[%u] failed on TAP-Windows adapter, status=%u : %s",
-                    (unsigned int)tt->ipapi_context,
-                    (unsigned int)status,
-                    strerror_win32(status, &gc));
+                do_dns_domain_service(false, tt);
             }
-        }
-#endif
-
-        dhcp_release(tt);
-
-        if (tt->hand != NULL)
-        {
-            dmsg(D_WIN32_IO_LOW, "Attempting CancelIO on TAP-Windows adapter");
-            if (!CancelIo(tt->hand))
+            if (tt->options.dns6_len > 0)
             {
-                msg(M_WARN | M_ERRNO, "Warning: CancelIO failed on TAP-Windows adapter");
+                do_dns_service(false, AF_INET6, tt);
             }
+            delete_route_connected_v6_net(tt);
+            do_address_service(false, AF_INET6, tt);
         }
-
-        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)
+        else
         {
-            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");
-            }
+            netsh_delete_address_dns(tt, true, &gc);
         }
-
-        if (tt->actual_name)
-        {
-            free(tt->actual_name);
-        }
-
-        clear_tuntap(tt);
-        free(tt);
     }
+
+    if (tt->did_ifconfig_setup)
+    {
+        if (tt->options.ip_win32_type == IPW32_SET_MANUAL)
+        {
+            /* We didn't do ifconfig. */
+        }
+        else if (tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ || tt->options.ip_win32_type == IPW32_SET_ADAPTIVE)
+        {
+            /* We don't have to clean the configuration with DHCP. */
+        }
+        else if (tt->options.msg_channel)
+        {
+            do_dns_domain_service(false, tt);
+            do_dns_service(false, AF_INET, tt);
+            do_address_service(false, AF_INET, tt);
+        }
+        else if (tt->options.ip_win32_type == IPW32_SET_NETSH)
+        {
+            netsh_delete_address_dns(tt, false, &gc);
+        }
+    }
+
+    if (tt->ipapi_context_defined)
+    {
+        DWORD status;
+        if ((status = DeleteIPAddress(tt->ipapi_context)) != NO_ERROR)
+        {
+            msg(M_WARN, "Warning: DeleteIPAddress[%u] failed on TAP-Windows adapter, status=%u : %s",
+                (unsigned int)tt->ipapi_context,
+                (unsigned int)status,
+                strerror_win32(status, &gc));
+        }
+    }
+
+    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");
+        }
+    }
+
+    if (tt->actual_name)
+    {
+        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);
 }
 
@@ -6418,13 +6994,12 @@
 }
 
 void
-close_tun(struct tuntap *tt)
+close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx)
 {
-    if (tt)
-    {
-        close_tun_generic(tt);
-        free(tt);
-    }
+    ASSERT(tt);
+
+    close_tun_generic(tt);
+    free(tt);
 }
 
 int
diff --git a/src/openvpn/tun.h b/src/openvpn/tun.h
index 54e1dfa..99826cf 100644
--- a/src/openvpn/tun.h
+++ b/src/openvpn/tun.h
@@ -27,6 +27,8 @@
 #ifdef _WIN32
 #include <winioctl.h>
 #include <tap-windows.h>
+#include <setupapi.h>
+#include <cfgmgr32.h>
 #endif
 
 #include "buffer.h"
@@ -36,6 +38,18 @@
 #include "event.h"
 #include "proto.h"
 #include "misc.h"
+#include "networking.h"
+#include "ring_buffer.h"
+
+#ifdef _WIN32
+#define WINTUN_COMPONENT_ID "wintun"
+
+enum windows_driver_type {
+    WINDOWS_DRIVER_UNSPECIFIED,
+    WINDOWS_DRIVER_TAP_WINDOWS6,
+    WINDOWS_DRIVER_WINTUN
+};
+#endif
 
 #if defined(_WIN32) || defined(TARGET_ANDROID)
 
@@ -98,6 +112,12 @@
     in_addr_t nbdd[N_DHCP_ADDR];
     int nbdd_len;
 
+#define N_SEARCH_LIST_LEN 10 /* Max # of entries in domin-search list */
+
+    /* SEARCH (119), MacOS, Linux, Win10 1809+ */
+    const char *domain_search_list[N_SEARCH_LIST_LEN];
+    int domain_search_list_len;
+
     /* DISABLE_NBT (43, Vendor option 001) */
     bool disable_nbt;
 
@@ -138,7 +158,6 @@
 
     bool did_ifconfig_setup;
     bool did_ifconfig_ipv6_setup;
-    bool did_ifconfig;
 
     bool persistent_if;         /* if existed before, keep on program end */
 
@@ -152,7 +171,6 @@
     /* ifconfig parameters */
     in_addr_t local;
     in_addr_t remote_netmask;
-    in_addr_t broadcast;
 
     struct in6_addr local_ipv6;
     struct in6_addr remote_ipv6;
@@ -175,10 +193,16 @@
      * ~0 if undefined */
     DWORD adapter_index;
 
+    enum windows_driver_type windows_driver;
     int standby_iter;
+
+    HANDLE wintun_send_ring_handle;
+    HANDLE wintun_receive_ring_handle;
+    struct tun_ring *wintun_send_ring;
+    struct tun_ring *wintun_receive_ring;
 #else  /* ifdef _WIN32 */
     int fd; /* file descriptor for TUN/TAP dev */
-#endif
+#endif /* ifdef _WIN32 */
 
 #ifdef TARGET_SOLARIS
     int ip_fd;
@@ -205,6 +229,20 @@
 #endif
 }
 
+#ifdef _WIN32
+static inline bool
+tuntap_is_wintun(struct tuntap *tt)
+{
+    return tt && tt->windows_driver == WINDOWS_DRIVER_WINTUN;
+}
+
+static inline bool
+tuntap_ring_empty(struct tuntap *tt)
+{
+    return tuntap_is_wintun(tt) && (tt->wintun_send_ring->head == tt->wintun_send_ring->tail);
+}
+#endif
+
 /*
  * Function prototypes
  */
@@ -212,7 +250,7 @@
 void open_tun(const char *dev, const char *dev_type, const char *dev_node,
               struct tuntap *tt);
 
-void close_tun(struct tuntap *tt);
+void close_tun(struct tuntap *tt, openvpn_net_ctx_t *ctx);
 
 int write_tun(struct tuntap *tt, uint8_t *buf, int len);
 
@@ -220,7 +258,8 @@
 
 void tuncfg(const char *dev, const char *dev_type, const char *dev_node,
             int persist_mode, const char *username,
-            const char *groupname, const struct tuntap_options *options);
+            const char *groupname, const struct tuntap_options *options,
+            openvpn_net_ctx_t *ctx);
 
 const char *guess_tuntap_dev(const char *dev,
                              const char *dev_type,
@@ -238,7 +277,8 @@
                         struct addrinfo *local_public,
                         struct addrinfo *remote_public,
                         const bool strict_warn,
-                        struct env_set *es);
+                        struct env_set *es,
+                        openvpn_net_ctx_t *ctx);
 
 void init_tun_post(struct tuntap *tt,
                    const struct frame *frame,
@@ -247,10 +287,17 @@
 void do_ifconfig_setenv(const struct tuntap *tt,
                         struct env_set *es);
 
-void do_ifconfig(struct tuntap *tt,
-                 const char *actual,     /* actual device name */
-                 int tun_mtu,
-                 const struct env_set *es);
+/**
+ * do_ifconfig - configure the tunnel interface
+ *
+ * @param tt        the tuntap interface context
+ * @param ifname    the human readable interface name
+ * @param mtu       the MTU value to set the interface to
+ * @param es        the environment to be used when executing the commands
+ * @param ctx       the networking API opaque context
+ */
+void do_ifconfig(struct tuntap *tt, const char *ifname, int tun_mtu,
+                 const struct env_set *es, openvpn_net_ctx_t *ctx);
 
 bool is_dev_type(const char *dev, const char *dev_type, const char *match_type);
 
@@ -266,7 +313,7 @@
                            const in_addr_t netmask,
                            const char *prefix);
 
-void warn_on_use_of_common_subnets(void);
+void warn_on_use_of_common_subnets(openvpn_net_ctx_t *ctx);
 
 /*
  * Inline functions
@@ -327,11 +374,10 @@
 
 #ifdef _WIN32
 
-#define TUN_PASS_BUFFER
-
 struct tap_reg
 {
     const char *guid;
+    enum windows_driver_type windows_driver;
     struct tap_reg *next;
 };
 
@@ -342,6 +388,13 @@
     struct panel_reg *next;
 };
 
+struct device_instance_id_interface
+{
+    const char *net_cfg_instance_id;
+    const char *device_interface_list;
+    struct device_instance_id_interface *next;
+};
+
 int ascii2ipset(const char *name);
 
 const char *ipset2ascii(int index);
@@ -457,10 +510,158 @@
     return tun_finalize(tt->hand, &tt->reads, buf);
 }
 
+static inline ULONG
+wintun_ring_packet_align(ULONG size)
+{
+    return (size + (WINTUN_PACKET_ALIGN - 1)) & ~(WINTUN_PACKET_ALIGN - 1);
+}
+
+static inline ULONG
+wintun_ring_wrap(ULONG value)
+{
+    return value & (WINTUN_RING_CAPACITY - 1);
+}
+
+static inline void
+read_wintun(struct tuntap *tt, struct buffer *buf)
+{
+    struct tun_ring *ring = tt->wintun_send_ring;
+    ULONG head = ring->head;
+    ULONG tail = ring->tail;
+    ULONG content_len;
+    struct TUN_PACKET *packet;
+    ULONG aligned_packet_size;
+
+    *buf = tt->reads.buf_init;
+    buf->len = 0;
+
+    if ((head >= WINTUN_RING_CAPACITY) || (tail >= WINTUN_RING_CAPACITY))
+    {
+        msg(M_INFO, "Wintun: ring capacity exceeded");
+        buf->len = -1;
+        return;
+    }
+
+    if (head == tail)
+    {
+        /* nothing to read */
+        return;
+    }
+
+    content_len = wintun_ring_wrap(tail - head);
+    if (content_len < sizeof(struct TUN_PACKET_HEADER))
+    {
+        msg(M_INFO, "Wintun: incomplete packet header in send ring");
+        buf->len = -1;
+        return;
+    }
+
+    packet = (struct TUN_PACKET *) &ring->data[head];
+    if (packet->size > WINTUN_MAX_PACKET_SIZE)
+    {
+        msg(M_INFO, "Wintun: packet too big in send ring");
+        buf->len = -1;
+        return;
+    }
+
+    aligned_packet_size = wintun_ring_packet_align(sizeof(struct TUN_PACKET_HEADER) + packet->size);
+    if (aligned_packet_size > content_len)
+    {
+        msg(M_INFO, "Wintun: incomplete packet in send ring");
+        buf->len = -1;
+        return;
+    }
+
+    buf_write(buf, packet->data, packet->size);
+
+    head = wintun_ring_wrap(head + aligned_packet_size);
+    ring->head = head;
+}
+
+static inline bool
+is_ip_packet_valid(const struct buffer *buf)
+{
+    const struct openvpn_iphdr *ih = (const struct openvpn_iphdr *)BPTR(buf);
+
+    if (OPENVPN_IPH_GET_VER(ih->version_len) == 4)
+    {
+        if (BLEN(buf) < sizeof(struct openvpn_iphdr))
+        {
+            return false;
+        }
+    }
+    else if (OPENVPN_IPH_GET_VER(ih->version_len) == 6)
+    {
+        if (BLEN(buf) < sizeof(struct openvpn_ipv6hdr))
+        {
+            return false;
+        }
+    }
+    else
+    {
+        return false;
+    }
+
+    return true;
+}
+
+static inline int
+write_wintun(struct tuntap *tt, struct buffer *buf)
+{
+    struct tun_ring *ring = tt->wintun_receive_ring;
+    ULONG head = ring->head;
+    ULONG tail = ring->tail;
+    ULONG aligned_packet_size;
+    ULONG buf_space;
+    struct TUN_PACKET *packet;
+
+    /* wintun marks ring as corrupted (overcapacity) if it receives invalid IP packet */
+    if (!is_ip_packet_valid(buf))
+    {
+        msg(D_LOW, "write_wintun(): drop invalid IP packet");
+        return 0;
+    }
+
+    if ((head >= WINTUN_RING_CAPACITY) || (tail >= WINTUN_RING_CAPACITY))
+    {
+        msg(M_INFO, "write_wintun(): head/tail value is over capacity");
+        return -1;
+    }
+
+    aligned_packet_size = wintun_ring_packet_align(sizeof(struct TUN_PACKET_HEADER) + BLEN(buf));
+    buf_space = wintun_ring_wrap(head - tail - WINTUN_PACKET_ALIGN);
+    if (aligned_packet_size > buf_space)
+    {
+        msg(M_INFO, "write_wintun(): ring is full");
+        return 0;
+    }
+
+    /* copy packet size and data into ring */
+    packet = (struct TUN_PACKET * )&ring->data[tail];
+    packet->size = BLEN(buf);
+    memcpy(packet->data, BPTR(buf), BLEN(buf));
+
+    /* move ring tail */
+    ring->tail = wintun_ring_wrap(tail + aligned_packet_size);
+    if (ring->alertable != 0)
+    {
+        SetEvent(tt->rw_handle.write);
+    }
+
+    return BLEN(buf);
+}
+
 static inline int
 write_tun_buffered(struct tuntap *tt, struct buffer *buf)
 {
-    return tun_write_win32(tt, buf);
+    if (tt->windows_driver == WINDOWS_DRIVER_WINTUN)
+    {
+        return write_wintun(tt, buf);
+    }
+    else
+    {
+        return tun_write_win32(tt, buf);
+    }
 }
 
 #else  /* ifdef _WIN32 */
@@ -504,7 +705,7 @@
 #endif
 }
 
-static inline unsigned int
+static inline void
 tun_set(struct tuntap *tt,
         struct event_set *es,
         unsigned int rwflags,
@@ -523,14 +724,13 @@
             }
         }
 #ifdef _WIN32
-        if (rwflags & EVENT_READ)
+        if (tt->windows_driver == WINDOWS_DRIVER_TAP_WINDOWS6 && (rwflags & EVENT_READ))
         {
             tun_read_queue(tt, 0);
         }
 #endif
         tt->rwflags_debug = rwflags;
     }
-    return rwflags;
 }
 
 const char *tun_stat(const struct tuntap *tt, unsigned int rwflags, struct gc_arena *gc);
diff --git a/src/openvpn/vlan.c b/src/openvpn/vlan.c
new file mode 100644
index 0000000..dd8d7c1
--- /dev/null
+++ b/src/openvpn/vlan.c
@@ -0,0 +1,333 @@
+/*
+ *  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) 2002-2019 OpenVPN Technologies, Inc. <sales@openvpn.net>
+ *  Copyright (C) 2010      Fabian Knittel <fabian.knittel@lettink.de>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#include "multi.h"
+#include "options.h"
+#include "vlan.h"
+
+/*
+ * Retrieve the VLAN Identifier (VID) from the IEEE 802.1Q header.
+ *
+ * @param hdr Pointer to the Ethernet header with IEEE 802.1Q tagging.
+ * @return    Returns the VID in host byte order.
+ */
+static uint16_t
+vlanhdr_get_vid(const struct openvpn_8021qhdr *hdr)
+{
+    return ntohs(hdr->pcp_cfi_vid & OPENVPN_8021Q_MASK_VID);
+}
+
+/*
+ * Set the VLAN Identifier (VID) in an IEEE 802.1Q header.
+ *
+ * @param hdr Pointer to the Ethernet header with IEEE 802.1Q tagging.
+ * @param vid The VID to set (in host byte order).
+ */
+static void
+vlanhdr_set_vid(struct openvpn_8021qhdr *hdr, const uint16_t vid)
+{
+    hdr->pcp_cfi_vid = (hdr->pcp_cfi_vid & ~OPENVPN_8021Q_MASK_VID)
+                       | (htons(vid) & OPENVPN_8021Q_MASK_VID);
+}
+
+/*
+ * vlan_decapsulate - remove 802.1q header and return VID
+ *
+ * For vlan_accept == VLAN_ONLY_UNTAGGED_OR_PRIORITY:
+ *   Only untagged frames and frames that are priority-tagged (VID == 0) are
+ *   accepted.  (This means that VLAN-tagged frames are dropped.)  For frames
+ *   that aren't dropped, the global vlan_pvid is returned as VID.
+ *
+ * For vlan_accept == VLAN_ONLY_TAGGED:
+ *   If a frame is VLAN-tagged the tagging is removed and the embedded VID is
+ *   returned.  Any included priority information is lost.
+ *   If a frame isn't VLAN-tagged, the frame is dropped.
+ *
+ * For vlan_accept == VLAN_ALL:
+ *   Accepts both VLAN-tagged and untagged (or priority-tagged) frames and
+ *   and handles them as described above.
+ *
+ * @param c   The global context.
+ * @param buf The ethernet frame.
+ * @return    Returns -1 if the frame is dropped or the VID if it is accepted.
+ */
+int16_t
+vlan_decapsulate(const struct context *c, struct buffer *buf)
+{
+    const struct openvpn_8021qhdr *vlanhdr;
+    struct openvpn_ethhdr *ethhdr;
+    uint16_t vid;
+
+    /* assume untagged frame */
+    if (BLEN(buf) < sizeof(*ethhdr))
+    {
+        goto drop;
+    }
+
+    ethhdr = (struct openvpn_ethhdr *)BPTR(buf);
+    if (ethhdr->proto != htons(OPENVPN_ETH_P_8021Q))
+    {
+        /* reject untagged frame */
+        if (c->options.vlan_accept == VLAN_ONLY_TAGGED)
+        {
+            msg(D_VLAN_DEBUG,
+                "dropping frame without vlan-tag (proto/len 0x%04x)",
+                ntohs(ethhdr->proto));
+            goto drop;
+        }
+
+        /* untagged frame is accepted and associated with the global VID */
+        msg(D_VLAN_DEBUG,
+            "assuming pvid for frame without vlan-tag, pvid: %u (proto/len 0x%04x)",
+            c->options.vlan_pvid, ntohs(ethhdr->proto));
+
+        return c->options.vlan_pvid;
+    }
+
+    /* tagged frame */
+    if (BLEN(buf) < sizeof(*vlanhdr))
+    {
+        goto drop;
+    }
+
+    vlanhdr = (const struct openvpn_8021qhdr *)BPTR(buf);
+    vid = vlanhdr_get_vid(vlanhdr);
+
+    switch (c->options.vlan_accept)
+    {
+        case VLAN_ONLY_UNTAGGED_OR_PRIORITY:
+            /* VLAN-tagged frame: drop packet */
+            if (vid != 0)
+            {
+                msg(D_VLAN_DEBUG, "dropping frame with vlan-tag, vid: %u (proto/len 0x%04x)",
+                    vid, ntohs(vlanhdr->proto));
+                goto drop;
+            }
+
+        /* vid == 0 means prio-tagged packet: don't drop and fall-through */
+        case VLAN_ONLY_TAGGED:
+        case VLAN_ALL:
+            /* tagged frame can be accepted: extract vid and strip encapsulation */
+
+            /* in case of prio-tagged frame (vid == 0), assume the sender
+             * knows what he is doing and forward the packet as it is, so to
+             * keep the priority information intact.
+             */
+            if (vid == 0)
+            {
+                /* return the global VID for priority-tagged frames */
+                return c->options.vlan_pvid;
+            }
+
+            /* here we have a proper VLAN tagged frame: perform decapsulation
+             * and return embedded VID
+             */
+            msg(D_VLAN_DEBUG,
+                "removing vlan-tag from frame: vid: %u, wrapped proto/len: 0x%04x",
+                vid, ntohs(vlanhdr->proto));
+
+            /* save inner protocol to be restored later after decapsulation */
+            uint16_t proto = vlanhdr->proto;
+            /* move the buffer head forward to adjust the headroom to a
+             * non-tagged frame
+             */
+            buf_advance(buf, SIZE_ETH_TO_8021Q_HDR);
+            /* move the content of the 802.1q header to the new head, so that
+             * src/dst addresses are copied over
+             */
+            ethhdr = memmove(BPTR(buf), vlanhdr, sizeof(*ethhdr));
+            /* restore the inner protocol value */
+            ethhdr->proto = proto;
+
+            return vid;
+    }
+
+drop:
+    buf->len = 0;
+    return -1;
+}
+
+/*
+ * vlan_encapsulate - add 802.1q header and set the context related VID
+ *
+ * Assumes vlan_accept == VLAN_ONLY_TAGGED
+ *
+ * @param c   The current context.
+ * @param buf The ethernet frame to encapsulate.
+ */
+void
+vlan_encapsulate(const struct context *c, struct buffer *buf)
+{
+    const struct openvpn_ethhdr *ethhdr;
+    struct openvpn_8021qhdr *vlanhdr;
+
+    if (BLEN(buf) < sizeof(*ethhdr))
+    {
+        goto drop;
+    }
+
+    ethhdr = (const struct openvpn_ethhdr *)BPTR(buf);
+    if (ethhdr->proto == htons(OPENVPN_ETH_P_8021Q))
+    {
+        /* Priority-tagged frame. (VLAN-tagged frames have been dropped before
+         * getting to this point)
+         */
+
+        /* Frame too small for header type? */
+        if (BLEN(buf) < sizeof(*vlanhdr))
+        {
+            goto drop;
+        }
+
+        vlanhdr = (struct openvpn_8021qhdr *)BPTR(buf);
+
+        /* sanity check: ensure this packet is really just prio-tagged */
+        uint16_t vid = vlanhdr_get_vid(vlanhdr);
+        if (vid != 0)
+        {
+            goto drop;
+        }
+    }
+    else
+    {
+        /* Untagged frame. */
+
+        /* Not enough head room for VLAN tag? */
+        if (buf_reverse_capacity(buf) < SIZE_ETH_TO_8021Q_HDR)
+        {
+            goto drop;
+        }
+
+        vlanhdr = (struct openvpn_8021qhdr *)buf_prepend(buf,
+                                                         SIZE_ETH_TO_8021Q_HDR);
+
+        /* Initialise VLAN/802.1q header.
+         * Move the Eth header so to keep dst/src addresses the same and then
+         * assign the other fields.
+         *
+         * Also, save the inner protocol first, so that it can be restored later
+         * after the memmove()
+         */
+        uint16_t proto = ethhdr->proto;
+        memmove(vlanhdr, ethhdr, sizeof(*ethhdr));
+        vlanhdr->tpid = htons(OPENVPN_ETH_P_8021Q);
+        vlanhdr->pcp_cfi_vid = 0;
+        vlanhdr->proto = proto;
+    }
+
+    /* set the VID corresponding to the current context (client) */
+    vlanhdr_set_vid(vlanhdr, c->options.vlan_pvid);
+
+    msg(D_VLAN_DEBUG, "tagging frame: vid %u (wrapping proto/len: %04x)",
+        c->options.vlan_pvid, vlanhdr->proto);
+    return;
+
+drop:
+    /* Drop the frame. */
+    buf->len = 0;
+}
+
+/*
+ * vlan_is_tagged - check if a packet is VLAN-tagged
+ *
+ * Checks whether ethernet frame is VLAN-tagged.
+ *
+ * @param buf The ethernet frame.
+ * @return    Returns true if the frame is VLAN-tagged, false otherwise.
+ */
+bool
+vlan_is_tagged(const struct buffer *buf)
+{
+    const struct openvpn_8021qhdr *vlanhdr;
+    uint16_t vid;
+
+    if (BLEN(buf) < sizeof(struct openvpn_8021qhdr))
+    {
+        /* frame too small to be VLAN-tagged */
+        return false;
+    }
+
+    vlanhdr = (const struct openvpn_8021qhdr *)BPTR(buf);
+
+    if (ntohs(vlanhdr->tpid) != OPENVPN_ETH_P_8021Q)
+    {
+        /* non tagged frame */
+        return false;
+    }
+
+    vid = vlanhdr_get_vid(vlanhdr);
+    if (vid == 0)
+    {
+        /* no vid: piority tagged only */
+        return false;
+    }
+
+    return true;
+}
+
+void
+vlan_process_outgoing_tun(struct multi_context *m, struct multi_instance *mi)
+{
+    if (!m->top.options.vlan_tagging)
+    {
+        return;
+    }
+
+    if (m->top.options.vlan_accept == VLAN_ONLY_UNTAGGED_OR_PRIORITY)
+    {
+        /* Packets forwarded to the TAP devices aren't VLAN-tagged. Only packets
+         * matching the PVID configured globally are allowed to be received
+         */
+        if (m->top.options.vlan_pvid != mi->context.options.vlan_pvid)
+        {
+            /* Packet is coming from the wrong VID, drop it.  */
+            mi->context.c2.to_tun.len = 0;
+        }
+    }
+    else if (m->top.options.vlan_accept == VLAN_ALL)
+    {
+        /* Packets either need to be VLAN-tagged or not, depending on the
+         * packet's originating VID and the port's native VID (PVID).  */
+
+        if (m->top.options.vlan_pvid != mi->context.options.vlan_pvid)
+        {
+            /* Packets need to be VLAN-tagged, because the packet's VID does not
+             * match the port's PVID.  */
+            vlan_encapsulate(&mi->context, &mi->context.c2.to_tun);
+        }
+    }
+    else if (m->top.options.vlan_accept == VLAN_ONLY_TAGGED)
+    {
+        /* All packets on the port (the tap device) need to be VLAN-tagged.  */
+        vlan_encapsulate(&mi->context, &mi->context.c2.to_tun);
+    }
+}
diff --git a/src/openvpn/vlan.h b/src/openvpn/vlan.h
new file mode 100644
index 0000000..ed25c1d
--- /dev/null
+++ b/src/openvpn/vlan.h
@@ -0,0 +1,44 @@
+/*
+ *  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) 2002-2019 OpenVPN Technologies, Inc. <sales@openvpn.net>
+ *  Copyright (C) 2010      Fabian Knittel <fabian.knittel@lettink.de>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef VLAN_H
+#define VLAN_H
+
+#include "buffer.h"
+#include "mroute.h"
+#include "openvpn.h"
+
+struct multi_context;
+struct multi_instance;
+
+int16_t
+vlan_decapsulate(const struct context *c, struct buffer *buf);
+
+bool
+vlan_is_tagged(const struct buffer *buf);
+
+void
+vlan_process_outgoing_tun(struct multi_context *m, struct multi_instance *mi);
+
+#endif /* VLAN_H */
diff --git a/src/openvpn/win32.c b/src/openvpn/win32.c
index f13807f..7e91316 100644
--- a/src/openvpn/win32.c
+++ b/src/openvpn/win32.c
@@ -22,7 +22,7 @@
  */
 
 /*
- * Win32-specific OpenVPN code, targetted at the mingw
+ * Win32-specific OpenVPN code, targeted at the mingw
  * development environment.
  */
 
@@ -39,9 +39,9 @@
 #include "buffer.h"
 #include "error.h"
 #include "mtu.h"
+#include "run_command.h"
 #include "sig.h"
 #include "win32.h"
-#include "misc.h"
 #include "openvpn-msg.h"
 
 #include "memdbg.h"
@@ -1139,7 +1139,7 @@
         else
         {
             ret = OPENVPN_EXECVE_NOT_ALLOWED;
-            if (!exec_warn && (script_security < SSEC_SCRIPTS))
+            if (!exec_warn && (script_security() < SSEC_SCRIPTS))
             {
                 msg(M_WARN, SCRIPT_SECURITY_WARNING);
                 exec_warn = true;
@@ -1267,7 +1267,6 @@
 static bool
 win_block_dns_service(bool add, int index, const HANDLE pipe)
 {
-    DWORD len;
     bool ret = false;
     ack_message_t ack;
     struct gc_arena gc = gc_new();
@@ -1281,11 +1280,8 @@
         .iface = { .index = index, .name = "" }
     };
 
-    if (!WriteFile(pipe, &data, sizeof(data), &len, NULL)
-        || !ReadFile(pipe, &ack, sizeof(ack), &len, NULL))
+    if (!send_msg_iservice(pipe, &data, sizeof(data), &ack, "Block_DNS"))
     {
-        msg(M_WARN, "Block_DNS: could not talk to service: %s [%lu]",
-            strerror_win32(GetLastError(), &gc), GetLastError());
         goto out;
     }
 
@@ -1421,10 +1417,18 @@
     {
         return WIN_7;
     }
-    else
+
+    if (!IsWindows8Point1OrGreater())
     {
         return WIN_8;
     }
+
+    if (!IsWindows10OrGreater())
+    {
+        return WIN_8_1;
+    }
+
+    return WIN_10;
 }
 
 bool
@@ -1462,7 +1466,15 @@
             break;
 
         case WIN_8:
-            buf_printf(&out, "6.2%s", add_name ? " (Windows 8 or greater)" : "");
+            buf_printf(&out, "6.2%s", add_name ? " (Windows 8)" : "");
+            break;
+
+        case WIN_8_1:
+            buf_printf(&out, "6.3%s", add_name ? " (Windows 8.1)" : "");
+            break;
+
+        case WIN_10:
+            buf_printf(&out, "10.0%s", add_name ? " (Windows 10 or greater)" : "");
             break;
 
         default:
@@ -1476,4 +1488,25 @@
     return (const char *)out.data;
 }
 
+bool
+send_msg_iservice(HANDLE pipe, const void *data, size_t size,
+                  ack_message_t *ack, const char *context)
+{
+    struct gc_arena gc = gc_new();
+    DWORD len;
+    bool ret = true;
+
+    if (!WriteFile(pipe, data, size, &len, NULL)
+        || !ReadFile(pipe, ack, sizeof(*ack), &len, NULL))
+    {
+        msg(M_WARN, "%s: could not talk to service: %s [%lu]",
+            context ? context : "Unknown",
+            strerror_win32(GetLastError(), &gc), GetLastError());
+        ret = false;
+    }
+
+    gc_free(&gc);
+    return ret;
+}
+
 #endif /* ifdef _WIN32 */
diff --git a/src/openvpn/win32.h b/src/openvpn/win32.h
index 4b99a5e..da85ed4 100644
--- a/src/openvpn/win32.h
+++ b/src/openvpn/win32.h
@@ -25,7 +25,11 @@
 #ifndef OPENVPN_WIN32_H
 #define OPENVPN_WIN32_H
 
+#include <winioctl.h>
+
 #include "mtu.h"
+#include "openvpn-msg.h"
+#include "argv.h"
 
 /* location of executables */
 #define SYS_PATH_ENV_VAR_NAME "SystemRoot"  /* environmental variable name that normally contains the system path */
@@ -35,7 +39,7 @@
 #define WIN_NET_PATH_SUFFIX "\\system32\\net.exe"
 
 /*
- * Win32-specific OpenVPN code, targetted at the mingw
+ * Win32-specific OpenVPN code, targeted at the mingw
  * development environment.
  */
 
@@ -65,7 +69,7 @@
 struct window_title
 {
     bool saved;
-    char old_window_title [256];
+    char old_window_title[256];
 };
 
 struct rw_handle {
@@ -294,10 +298,12 @@
 
 bool win_wfp_uninit(const NET_IFINDEX index, const HANDLE msg_channel);
 
-#define WIN_XP 0
+#define WIN_XP    0
 #define WIN_VISTA 1
-#define WIN_7 2
-#define WIN_8 3
+#define WIN_7     2
+#define WIN_8     3
+#define WIN_8_1   4
+#define WIN_10    5
 
 int win32_version_info(void);
 
@@ -307,5 +313,21 @@
  */
 const char *win32_version_string(struct gc_arena *gc, bool add_name);
 
+/*
+ * Send the |size| bytes in buffer |data| to the interactive service |pipe|
+ * and read the result in |ack|. Returns false on communication error.
+ * The string in |context| is used to prefix error messages.
+ */
+bool send_msg_iservice(HANDLE pipe, const void *data, size_t size,
+                       ack_message_t *ack, const char *context);
+
+/*
+ * Attempt to simulate fork/execve on Windows
+ */
+int
+openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags);
+
+bool impersonate_as_system();
+
 #endif /* ifndef OPENVPN_WIN32_H */
 #endif /* ifdef _WIN32 */
diff --git a/src/openvpnmsica/Makefile.am b/src/openvpnmsica/Makefile.am
new file mode 100644
index 0000000..9d18854
--- /dev/null
+++ b/src/openvpnmsica/Makefile.am
@@ -0,0 +1,56 @@
+#
+#  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
+#
+#  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
+#  Copyright (C) 2018-2020 Simon Rozman <simon@rozman.si>
+#
+#  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; if not, write to the Free Software Foundation, Inc.,
+#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+include $(top_srcdir)/build/ltrc.inc
+
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+
+EXTRA_DIST = \
+	openvpnmsica.vcxproj \
+	openvpnmsica.vcxproj.filters \
+	openvpnmsica.props \
+	openvpnmsica-Debug.props \
+	openvpnmsica-Release.props
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/include -I$(top_srcdir)/src/compat
+
+AM_CFLAGS = \
+	$(TAP_CFLAGS)
+
+if WIN32
+lib_LTLIBRARIES = libopenvpnmsica.la
+libopenvpnmsica_la_CFLAGS = \
+	-municode -D_UNICODE \
+	-UNTDDI_VERSION -U_WIN32_WINNT \
+	-D_WIN32_WINNT=_WIN32_WINNT_VISTA \
+	-Wl,--kill-at
+libopenvpnmsica_la_LDFLAGS = -ladvapi32 -lole32 -lmsi -lsetupapi -liphlpapi -lshell32 -lshlwapi -lversion -no-undefined -avoid-version
+endif
+
+libopenvpnmsica_la_SOURCES = \
+	dllmain.c \
+	msiex.c msiex.h \
+	msica_arg.c msica_arg.h \
+	openvpnmsica.c openvpnmsica.h \
+	$(top_srcdir)/src/tapctl/basic.h \
+	$(top_srcdir)/src/tapctl/error.c $(top_srcdir)/src/tapctl/error.h \
+	$(top_srcdir)/src/tapctl/tap.c $(top_srcdir)/src/tapctl/tap.h \
+	openvpnmsica_resources.rc
diff --git a/src/openvpnmsica/dllmain.c b/src/openvpnmsica/dllmain.c
new file mode 100644
index 0000000..34946ed
--- /dev/null
+++ b/src/openvpnmsica/dllmain.c
@@ -0,0 +1,198 @@
+/*
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
+ *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA
+ *
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#elif defined(_MSC_VER)
+#include <config-msvc.h>
+#endif
+
+#include "openvpnmsica.h"
+#include "../tapctl/error.h"
+
+#include <windows.h>
+#include <msi.h>
+#include <msiquery.h>
+#ifdef _MSC_VER
+#pragma comment(lib, "msi.lib")
+#endif
+#include <stdio.h>
+#include <tchar.h>
+
+
+DWORD openvpnmsica_thread_data_idx = TLS_OUT_OF_INDEXES;
+
+
+/**
+ * DLL entry point
+ */
+BOOL WINAPI
+DllMain(
+    _In_ HINSTANCE hinstDLL,
+    _In_ DWORD dwReason,
+    _In_ LPVOID lpReserved)
+{
+    UNREFERENCED_PARAMETER(hinstDLL);
+    UNREFERENCED_PARAMETER(lpReserved);
+
+    switch (dwReason)
+    {
+        case DLL_PROCESS_ATTACH:
+            /* Allocate thread local storage index. */
+            openvpnmsica_thread_data_idx = TlsAlloc();
+            if (openvpnmsica_thread_data_idx == TLS_OUT_OF_INDEXES)
+            {
+                return FALSE;
+            }
+        /* Fall through. */
+
+        case DLL_THREAD_ATTACH:
+        {
+            /* Create thread local storage data. */
+            struct openvpnmsica_thread_data *s = (struct openvpnmsica_thread_data *)calloc(1, sizeof(struct openvpnmsica_thread_data));
+            if (s == NULL)
+            {
+                return FALSE;
+            }
+
+            TlsSetValue(openvpnmsica_thread_data_idx, s);
+            break;
+        }
+
+        case DLL_PROCESS_DETACH:
+            if (openvpnmsica_thread_data_idx != TLS_OUT_OF_INDEXES)
+            {
+                /* Free thread local storage data and index. */
+                free(TlsGetValue(openvpnmsica_thread_data_idx));
+                TlsFree(openvpnmsica_thread_data_idx);
+            }
+            break;
+
+        case DLL_THREAD_DETACH:
+            /* Free thread local storage data. */
+            free(TlsGetValue(openvpnmsica_thread_data_idx));
+            break;
+    }
+
+    return TRUE;
+}
+
+
+bool
+dont_mute(unsigned int flags)
+{
+    UNREFERENCED_PARAMETER(flags);
+
+    return true;
+}
+
+
+void
+x_msg_va(const unsigned int flags, const char *format, va_list arglist)
+{
+    /* Secure last error before it is overridden. */
+    DWORD dwResult = (flags & M_ERRNO) != 0 ? GetLastError() : ERROR_SUCCESS;
+
+    struct openvpnmsica_thread_data *s = (struct openvpnmsica_thread_data *)TlsGetValue(openvpnmsica_thread_data_idx);
+    if (s->hInstall == 0)
+    {
+        /* No MSI session, no fun. */
+        return;
+    }
+
+    /* Prepare the message record. The record will contain up to four fields. */
+    MSIHANDLE hRecordProg = MsiCreateRecord(4);
+
+    {
+        /* Field 2: The message string. */
+        char szBufStack[128];
+        int iResultLen = vsnprintf(szBufStack, _countof(szBufStack), format, arglist);
+        if (iResultLen < _countof(szBufStack))
+        {
+            /* Use from stack. */
+            MsiRecordSetStringA(hRecordProg, 2, szBufStack);
+        }
+        else
+        {
+            /* Allocate on heap and retry. */
+            char *szMessage = (char *)malloc(++iResultLen * sizeof(char));
+            if (szMessage != NULL)
+            {
+                vsnprintf(szMessage, iResultLen, format, arglist);
+                MsiRecordSetStringA(hRecordProg, 2, szMessage);
+                free(szMessage);
+            }
+            else
+            {
+                /* Use stack variant anyway, but make sure it's zero-terminated. */
+                szBufStack[_countof(szBufStack) - 1] = 0;
+                MsiRecordSetStringA(hRecordProg, 2, szBufStack);
+            }
+        }
+    }
+
+    if ((flags & M_ERRNO) == 0)
+    {
+        /* Field 1: MSI Error Code */
+        MsiRecordSetInteger(hRecordProg, 1, ERROR_MSICA);
+    }
+    else
+    {
+        /* Field 1: MSI Error Code */
+        MsiRecordSetInteger(hRecordProg, 1, ERROR_MSICA_ERRNO);
+
+        /* Field 3: The Windows error number. */
+        MsiRecordSetInteger(hRecordProg, 3, dwResult);
+
+        /* Field 4: The Windows error description. */
+        LPTSTR szErrMessage = NULL;
+        if (FormatMessage(
+                FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
+                0,
+                dwResult,
+                0,
+                (LPTSTR)&szErrMessage,
+                0,
+                NULL) && szErrMessage)
+        {
+            /* Trim trailing whitespace. Set terminator after the last non-whitespace character. This prevents excessive trailing line breaks. */
+            for (size_t i = 0, i_last = 0;; i++)
+            {
+                if (szErrMessage[i])
+                {
+                    if (!_istspace(szErrMessage[i]))
+                    {
+                        i_last = i + 1;
+                    }
+                }
+                else
+                {
+                    szErrMessage[i_last] = 0;
+                    break;
+                }
+            }
+            MsiRecordSetString(hRecordProg, 4, szErrMessage);
+            LocalFree(szErrMessage);
+        }
+    }
+
+    MsiProcessMessage(s->hInstall, (flags & M_WARN) ? INSTALLMESSAGE_INFO : INSTALLMESSAGE_ERROR, hRecordProg);
+    MsiCloseHandle(hRecordProg);
+}
diff --git a/src/openvpnmsica/msica_arg.c b/src/openvpnmsica/msica_arg.c
new file mode 100644
index 0000000..0014537
--- /dev/null
+++ b/src/openvpnmsica/msica_arg.c
@@ -0,0 +1,139 @@
+/*
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
+ *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA
+ *
+ *  Copyright (C) 2018-2020 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#elif defined(_MSC_VER)
+#include <config-msvc.h>
+#endif
+
+#include "msica_arg.h"
+#include "../tapctl/error.h"
+#include "../tapctl/tap.h"
+
+#include <windows.h>
+#include <malloc.h>
+
+
+void
+msica_arg_seq_init(_Inout_ struct msica_arg_seq *seq)
+{
+    seq->head = NULL;
+    seq->tail = NULL;
+}
+
+
+void
+msica_arg_seq_free(_Inout_ struct msica_arg_seq *seq)
+{
+    while (seq->head)
+    {
+        struct msica_arg *p = seq->head;
+        seq->head = seq->head->next;
+        free(p);
+    }
+    seq->tail = NULL;
+}
+
+
+void
+msica_arg_seq_add_head(
+    _Inout_ struct msica_arg_seq *seq,
+    _In_z_ LPCTSTR argument)
+{
+    size_t argument_size = (_tcslen(argument) + 1) * sizeof(TCHAR);
+    struct msica_arg *p = malloc(sizeof(struct msica_arg) + argument_size);
+    if (p == NULL)
+    {
+        msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeof(struct msica_arg) + argument_size);
+    }
+    memcpy(p->val, argument, argument_size);
+    p->next = seq->head;
+    seq->head = p;
+    if (seq->tail == NULL)
+    {
+        seq->tail = p;
+    }
+}
+
+
+void
+msica_arg_seq_add_tail(
+    _Inout_ struct msica_arg_seq *seq,
+    _Inout_ LPCTSTR argument)
+{
+    size_t argument_size = (_tcslen(argument) + 1) * sizeof(TCHAR);
+    struct msica_arg *p = malloc(sizeof(struct msica_arg) + argument_size);
+    if (p == NULL)
+    {
+        msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeof(struct msica_arg) + argument_size);
+    }
+    memcpy(p->val, argument, argument_size);
+    p->next = NULL;
+    *(seq->tail ? &seq->tail->next : &seq->head) = p;
+    seq->tail = p;
+}
+
+
+LPTSTR
+msica_arg_seq_join(_In_ const struct msica_arg_seq *seq)
+{
+    /* Count required space. */
+    size_t size = 2 /*x + zero-terminator*/;
+    for (struct msica_arg *p = seq->head; p != NULL; p = p->next)
+    {
+        size += _tcslen(p->val) + 1 /*space delimiter|zero-terminator*/;
+    }
+    size *= sizeof(TCHAR);
+
+    /* Allocate. */
+    LPTSTR str = malloc(size);
+    if (str == NULL)
+    {
+        msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, size);
+        return NULL;
+    }
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4996) /* Using unsafe string functions: The space in s and termination of p->val has been implicitly verified at the beginning of this function. */
+#endif
+
+    /* Dummy argv[0] (i.e. executable name), for CommandLineToArgvW to work correctly when parsing this string. */
+    _tcscpy(str, TEXT("x"));
+
+    /* Join. */
+    LPTSTR s = str + 1 /*x*/;
+    for (struct msica_arg *p = seq->head; p != NULL; p = p->next)
+    {
+        /* Convert zero-terminator into space delimiter. */
+        s[0] = TEXT(' ');
+        s++;
+        /* Append argument. */
+        _tcscpy(s, p->val);
+        s += _tcslen(p->val);
+    }
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+    return str;
+}
diff --git a/src/openvpnmsica/msica_arg.h b/src/openvpnmsica/msica_arg.h
new file mode 100644
index 0000000..d2158e0
--- /dev/null
+++ b/src/openvpnmsica/msica_arg.h
@@ -0,0 +1,112 @@
+/*
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
+ *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA
+ *
+ *  Copyright (C) 2018-2020 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MSICA_ARG_H
+#define MSICA_ARG_H
+
+#include <windows.h>
+#include <tchar.h>
+#include "../tapctl/basic.h"
+
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4200) /* Using zero-sized arrays in struct/union. */
+#endif
+
+
+/**
+ * Argument list
+ */
+struct msica_arg
+{
+    struct msica_arg *next; /** Pointer to the next argument in the sequence */
+    TCHAR val[];            /** Zero terminated argument string */
+};
+
+
+/**
+ * Argument sequence
+ */
+struct msica_arg_seq
+{
+    struct msica_arg *head; /** Pointer to the first argument in the sequence */
+    struct msica_arg *tail; /** Pointer to the last argument in the sequence */
+};
+
+
+/**
+ * Initializes argument sequence
+ *
+ * @param seq           Pointer to uninitialized argument sequence
+ */
+void
+msica_arg_seq_init(_Inout_ struct msica_arg_seq *seq);
+
+
+/**
+ * Frees argument sequence
+ *
+ * @param seq           Pointer to the argument sequence
+ */
+void
+msica_arg_seq_free(_Inout_ struct msica_arg_seq *seq);
+
+
+/**
+ * Inserts argument to the beginning of the argument sequence
+ *
+ * @param seq           Pointer to the argument sequence
+ *
+ * @param argument      Zero-terminated argument string to insert.
+ */
+void
+msica_arg_seq_add_head(
+    _Inout_ struct msica_arg_seq *seq,
+    _In_z_ LPCTSTR argument);
+
+
+/**
+ * Appends argument to the end of the argument sequence
+ *
+ * @param seq           Pointer to the argument sequence
+ *
+ * @param argument      Zero-terminated argument string to append.
+ */
+void
+msica_arg_seq_add_tail(
+    _Inout_ struct msica_arg_seq *seq,
+    _Inout_ LPCTSTR argument);
+
+/**
+ * Join arguments of the argument sequence into a space delimited string
+ *
+ * @param seq           Pointer to the argument sequence
+ *
+ * @return Joined argument string. Must be released with free() after use.
+ */
+LPTSTR
+msica_arg_seq_join(_In_ const struct msica_arg_seq *seq);
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif /* ifndef MSICA_ARG_H */
diff --git a/src/openvpnmsica/msiex.c b/src/openvpnmsica/msiex.c
new file mode 100644
index 0000000..00265d0
--- /dev/null
+++ b/src/openvpnmsica/msiex.c
@@ -0,0 +1,265 @@
+/*
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
+ *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA
+ *
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#elif defined(_MSC_VER)
+#include <config-msvc.h>
+#endif
+
+#include "msiex.h"
+#include "../tapctl/error.h"
+
+#include <windows.h>
+#include <malloc.h>
+#include <memory.h>
+#include <msiquery.h>
+#ifdef _MSC_VER
+#pragma comment(lib, "msi.lib")
+#endif
+
+
+UINT
+msi_get_string(
+    _In_ MSIHANDLE hInstall,
+    _In_z_ LPCTSTR szName,
+    _Out_ LPTSTR *pszValue)
+{
+    if (pszValue == NULL)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    /* Try with stack buffer first. */
+    TCHAR szBufStack[128];
+    DWORD dwLength = _countof(szBufStack);
+    UINT uiResult = MsiGetProperty(hInstall, szName, szBufStack, &dwLength);
+    if (uiResult == ERROR_SUCCESS)
+    {
+        /* Copy from stack. */
+        *pszValue = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
+        if (*pszValue == NULL)
+        {
+            msg(M_FATAL, "%s: malloc(%u) failed", dwLength * sizeof(TCHAR));
+            return ERROR_OUTOFMEMORY;
+        }
+
+        memcpy(*pszValue, szBufStack, dwLength * sizeof(TCHAR));
+        return ERROR_SUCCESS;
+    }
+    else if (uiResult == ERROR_MORE_DATA)
+    {
+        /* Allocate on heap and retry. */
+        LPTSTR szBufHeap = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
+        if (szBufHeap == NULL)
+        {
+            msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwLength * sizeof(TCHAR));
+            return ERROR_OUTOFMEMORY;
+        }
+
+        uiResult = MsiGetProperty(hInstall, szName, szBufHeap, &dwLength);
+        if (uiResult == ERROR_SUCCESS)
+        {
+            *pszValue = szBufHeap;
+        }
+        else
+        {
+            free(szBufHeap);
+        }
+        return uiResult;
+    }
+    else
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiGetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiGetProperty failed", __FUNCTION__);
+        return uiResult;
+    }
+}
+
+
+UINT
+msi_get_record_string(
+    _In_ MSIHANDLE hRecord,
+    _In_ unsigned int iField,
+    _Out_ LPTSTR *pszValue)
+{
+    if (pszValue == NULL)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    /* Try with stack buffer first. */
+    TCHAR szBufStack[128];
+    DWORD dwLength = _countof(szBufStack);
+    UINT uiResult = MsiRecordGetString(hRecord, iField, szBufStack, &dwLength);
+    if (uiResult == ERROR_SUCCESS)
+    {
+        /* Copy from stack. */
+        *pszValue = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
+        if (*pszValue == NULL)
+        {
+            msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwLength * sizeof(TCHAR));
+            return ERROR_OUTOFMEMORY;
+        }
+
+        memcpy(*pszValue, szBufStack, dwLength * sizeof(TCHAR));
+        return ERROR_SUCCESS;
+    }
+    else if (uiResult == ERROR_MORE_DATA)
+    {
+        /* Allocate on heap and retry. */
+        LPTSTR szBufHeap = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
+        if (szBufHeap == NULL)
+        {
+            msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwLength * sizeof(TCHAR));
+            return ERROR_OUTOFMEMORY;
+        }
+
+        uiResult = MsiRecordGetString(hRecord, iField, szBufHeap, &dwLength);
+        if (uiResult == ERROR_SUCCESS)
+        {
+            *pszValue = szBufHeap;
+        }
+        else
+        {
+            free(szBufHeap);
+        }
+        return uiResult;
+    }
+    else
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiRecordGetString() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiRecordGetString failed", __FUNCTION__);
+        return uiResult;
+    }
+}
+
+
+UINT
+msi_format_record(
+    _In_ MSIHANDLE hInstall,
+    _In_ MSIHANDLE hRecord,
+    _Out_ LPTSTR *pszValue)
+{
+    if (pszValue == NULL)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    /* Try with stack buffer first. */
+    TCHAR szBufStack[128];
+    DWORD dwLength = _countof(szBufStack);
+    UINT uiResult = MsiFormatRecord(hInstall, hRecord, szBufStack, &dwLength);
+    if (uiResult == ERROR_SUCCESS)
+    {
+        /* Copy from stack. */
+        *pszValue = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
+        if (*pszValue == NULL)
+        {
+            msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwLength * sizeof(TCHAR));
+            return ERROR_OUTOFMEMORY;
+        }
+
+        memcpy(*pszValue, szBufStack, dwLength * sizeof(TCHAR));
+        return ERROR_SUCCESS;
+    }
+    else if (uiResult == ERROR_MORE_DATA)
+    {
+        /* Allocate on heap and retry. */
+        LPTSTR szBufHeap = (LPTSTR)malloc(++dwLength * sizeof(TCHAR));
+        if (szBufHeap == NULL)
+        {
+            msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwLength * sizeof(TCHAR));
+            return ERROR_OUTOFMEMORY;
+        }
+
+        uiResult = MsiFormatRecord(hInstall, hRecord, szBufHeap, &dwLength);
+        if (uiResult == ERROR_SUCCESS)
+        {
+            *pszValue = szBufHeap;
+        }
+        else
+        {
+            free(szBufHeap);
+        }
+        return uiResult;
+    }
+    else
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiFormatRecord() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiFormatRecord failed", __FUNCTION__);
+        return uiResult;
+    }
+}
+
+
+UINT
+msi_format_field(
+    _In_ MSIHANDLE hInstall,
+    _In_ MSIHANDLE hRecord,
+    _In_ unsigned int iField,
+    _Out_ LPTSTR *pszValue)
+{
+    if (pszValue == NULL)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    /* Read string to format. */
+    LPTSTR szValue = NULL;
+    UINT uiResult = msi_get_record_string(hRecord, iField, &szValue);
+    if (uiResult != ERROR_SUCCESS)
+    {
+        return uiResult;
+    }
+    if (szValue[0] == 0)
+    {
+        /* The string is empty. There's nothing left to do. */
+        *pszValue = szValue;
+        return ERROR_SUCCESS;
+    }
+
+    /* Create a temporary record. */
+    MSIHANDLE hRecordEx = MsiCreateRecord(1);
+    if (!hRecordEx)
+    {
+        uiResult = ERROR_INVALID_HANDLE;
+        msg(M_NONFATAL, "%s: MsiCreateRecord failed", __FUNCTION__);
+        goto cleanup_szValue;
+    }
+
+    /* Populate the record with data. */
+    uiResult = MsiRecordSetString(hRecordEx, 0, szValue);
+    if (uiResult != ERROR_SUCCESS)
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiRecordSetString() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiRecordSetString failed", __FUNCTION__);
+        goto cleanup_hRecordEx;
+    }
+
+    /* Do the formatting. */
+    uiResult = msi_format_record(hInstall, hRecordEx, pszValue);
+
+cleanup_hRecordEx:
+    MsiCloseHandle(hRecordEx);
+cleanup_szValue:
+    free(szValue);
+    return uiResult;
+}
diff --git a/src/openvpnmsica/msiex.h b/src/openvpnmsica/msiex.h
new file mode 100644
index 0000000..d819b87
--- /dev/null
+++ b/src/openvpnmsica/msiex.h
@@ -0,0 +1,112 @@
+/*
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
+ *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA
+ *
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MSIHLP_H
+#define MSIHLP_H
+
+#include <windows.h>
+#include <msi.h>
+#include "../tapctl/basic.h"
+
+
+/**
+ * Gets MSI property value
+ *
+ * @param hInstall      Handle to the installation provided to the DLL custom action
+ *
+ * @param szName        Property name
+ *
+ * @param pszValue      Pointer to string to retrieve property value. The string must
+ *                      be released with free() after use.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ */
+UINT
+msi_get_string(
+    _In_ MSIHANDLE hInstall,
+    _In_z_ LPCTSTR szName,
+    _Out_ LPTSTR *pszValue);
+
+
+/**
+ * Gets MSI record string value
+ *
+ * @param hRecord       Handle to the record
+ *
+ * @param iField        Field index
+ *
+ * @param pszValue      Pointer to string to retrieve field value. The string must be
+ *                      released with free() after use.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ */
+UINT
+msi_get_record_string(
+    _In_ MSIHANDLE hRecord,
+    _In_ unsigned int iField,
+    _Out_ LPTSTR *pszValue);
+
+
+/**
+ * Formats MSI record
+ *
+ * @param hInstall      Handle to the installation. This may be omitted, in which case only the
+ *                      record field parameters are processed and properties are not available
+ *                      for substitution.
+ *
+ * @param hRecord       Handle to the record to format. The template string must be stored in
+ *                      record field 0 followed by referenced data parameters.
+ *
+ * @param pszValue      Pointer to string to retrieve formatted value. The string must be
+ *                      released with free() after use.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ */
+UINT
+msi_format_record(
+    _In_ MSIHANDLE hInstall,
+    _In_ MSIHANDLE hRecord,
+    _Out_ LPTSTR *pszValue);
+
+
+/**
+ * Formats MSI record field
+ *
+ * @param hInstall      Handle to the installation. This may be omitted, in which case only the
+ *                      record field parameters are processed and properties are not available
+ *                      for substitution.
+ *
+ * @param hRecord       Handle to the field record
+ *
+ * @param iField        Field index
+ *
+ * @param pszValue      Pointer to string to retrieve formatted value. The string must be
+ *                      released with free() after use.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ */
+UINT
+msi_format_field(
+    _In_ MSIHANDLE hInstall,
+    _In_ MSIHANDLE hRecord,
+    _In_ unsigned int iField,
+    _Out_ LPTSTR *pszValue);
+
+#endif /* ifndef MSIHLP_H */
diff --git a/src/openvpnmsica/openvpnmsica-Debug.props b/src/openvpnmsica/openvpnmsica-Debug.props
new file mode 100644
index 0000000..43532cf
--- /dev/null
+++ b/src/openvpnmsica/openvpnmsica-Debug.props
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets">
+    <Import Project="openvpnmsica.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup />
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemGroup />
+</Project>
\ No newline at end of file
diff --git a/src/openvpnmsica/openvpnmsica-Release.props b/src/openvpnmsica/openvpnmsica-Release.props
new file mode 100644
index 0000000..848fda8
--- /dev/null
+++ b/src/openvpnmsica/openvpnmsica-Release.props
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets">
+    <Import Project="openvpnmsica.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup />
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemGroup />
+</Project>
\ No newline at end of file
diff --git a/src/openvpnmsica/openvpnmsica.c b/src/openvpnmsica/openvpnmsica.c
new file mode 100644
index 0000000..de1cf65
--- /dev/null
+++ b/src/openvpnmsica/openvpnmsica.c
@@ -0,0 +1,1211 @@
+/*
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
+ *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA
+ *
+ *  Copyright (C) 2018-2020 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#elif defined(_MSC_VER)
+#include <config-msvc.h>
+#endif
+#include <winsock2.h> /* Must be included _before_ <windows.h> */
+
+#include "openvpnmsica.h"
+#include "msica_arg.h"
+#include "msiex.h"
+
+#include "../tapctl/basic.h"
+#include "../tapctl/error.h"
+#include "../tapctl/tap.h"
+
+#include <windows.h>
+#include <iphlpapi.h>
+#include <malloc.h>
+#include <memory.h>
+#include <msiquery.h>
+#include <shellapi.h>
+#include <shlwapi.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <tchar.h>
+
+#ifdef _MSC_VER
+#pragma comment(lib, "advapi32.lib")
+#pragma comment(lib, "iphlpapi.lib")
+#pragma comment(lib, "shell32.lib")
+#pragma comment(lib, "shlwapi.lib")
+#pragma comment(lib, "version.lib")
+#endif
+
+
+/**
+ * Local constants
+ */
+
+#define MSICA_ADAPTER_TICK_SIZE (16*1024) /** Amount of tick space to reserve for one TAP/TUN adapter creation/deletition. */
+
+
+/**
+ * Joins an argument sequence and sets it to the MSI property.
+ *
+ * @param hInstall      Handle to the installation provided to the DLL custom action
+ *
+ * @param szProperty    MSI property name to set to the joined argument sequence.
+ *
+ * @param seq           The argument sequence.
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ */
+static UINT
+setup_sequence(
+    _In_ MSIHANDLE hInstall,
+    _In_z_ LPCTSTR szProperty,
+    _In_ struct msica_arg_seq *seq)
+{
+    UINT uiResult;
+    LPTSTR szSequence = msica_arg_seq_join(seq);
+    uiResult = MsiSetProperty(hInstall, szProperty, szSequence);
+    free(szSequence);
+    if (uiResult != ERROR_SUCCESS)
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szProperty);
+        return uiResult;
+    }
+    return ERROR_SUCCESS;
+}
+
+
+#ifdef _DEBUG
+
+/**
+ * Pops up a message box creating a time window to attach a debugger to the installer process in
+ * order to debug custom actions.
+ *
+ * @param szFunctionName  Function name that triggered the pop-up. Displayed in message box's
+ *                        title.
+ */
+static void
+_debug_popup(_In_z_ LPCTSTR szFunctionName)
+{
+    TCHAR szTitle[0x100], szMessage[0x100+MAX_PATH], szProcessPath[MAX_PATH];
+
+    /* Compose pop-up title. The dialog title will contain function name to ease the process
+     * locating. Mind that Visual Studio displays window titles on the process list. */
+    _stprintf_s(szTitle, _countof(szTitle), TEXT("%s v%s"), szFunctionName, TEXT(PACKAGE_VERSION));
+
+    /* Get process name. */
+    GetModuleFileName(NULL, szProcessPath, _countof(szProcessPath));
+    LPCTSTR szProcessName = _tcsrchr(szProcessPath, TEXT('\\'));
+    szProcessName = szProcessName ? szProcessName + 1 : szProcessPath;
+
+    /* Compose the pop-up message. */
+    _stprintf_s(
+        szMessage, _countof(szMessage),
+        TEXT("The %s process (PID: %u) has started to execute the %s custom action.\r\n")
+        TEXT("\r\n")
+        TEXT("If you would like to debug the custom action, attach a debugger to this process and set breakpoints before dismissing this dialog.\r\n")
+        TEXT("\r\n")
+        TEXT("If you are not debugging this custom action, you can safely ignore this message."),
+        szProcessName,
+        GetCurrentProcessId(),
+        szFunctionName);
+
+    MessageBox(NULL, szMessage, szTitle, MB_OK);
+}
+
+#define debug_popup(f) _debug_popup(f)
+#else  /* ifdef _DEBUG */
+#define debug_popup(f)
+#endif /* ifdef _DEBUG */
+
+
+/**
+ * Detects if the OpenVPNService service is in use (running or paused) and sets
+ * OPENVPNSERVICE to the service process PID, or its path if it is set to
+ * auto-start, but not running.
+ *
+ * @param hInstall      Handle to the installation provided to the DLL custom action
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
+ */
+static UINT
+set_openvpnserv_state(_In_ MSIHANDLE hInstall)
+{
+    UINT uiResult;
+
+    /* Get Service Control Manager handle. */
+    SC_HANDLE hSCManager = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT);
+    if (hSCManager == NULL)
+    {
+        uiResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: OpenSCManager() failed", __FUNCTION__);
+        return uiResult;
+    }
+
+    /* Get OpenVPNService service handle. */
+    SC_HANDLE hService = OpenService(hSCManager, TEXT("OpenVPNService"), SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
+    if (hService == NULL)
+    {
+        uiResult = GetLastError();
+        if (uiResult == ERROR_SERVICE_DOES_NOT_EXIST)
+        {
+            /* This is not actually an error. */
+            goto cleanup_OpenSCManager;
+        }
+        msg(M_NONFATAL | M_ERRNO, "%s: OpenService(\"OpenVPNService\") failed", __FUNCTION__);
+        goto cleanup_OpenSCManager;
+    }
+
+    /* Query service status. */
+    SERVICE_STATUS_PROCESS ssp;
+    DWORD dwBufSize;
+    if (QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, sizeof(ssp), &dwBufSize))
+    {
+        switch (ssp.dwCurrentState)
+        {
+            case SERVICE_START_PENDING:
+            case SERVICE_RUNNING:
+            case SERVICE_STOP_PENDING:
+            case SERVICE_PAUSE_PENDING:
+            case SERVICE_PAUSED:
+            case SERVICE_CONTINUE_PENDING:
+            {
+                /* Service is started (kind of). Set OPENVPNSERVICE property to service PID. */
+                TCHAR szPID[10 /*MAXDWORD in decimal*/ + 1 /*terminator*/];
+                _stprintf_s(
+                    szPID, _countof(szPID),
+                    TEXT("%u"),
+                    ssp.dwProcessId);
+
+                uiResult = MsiSetProperty(hInstall, TEXT("OPENVPNSERVICE"), szPID);
+                if (uiResult != ERROR_SUCCESS)
+                {
+                    SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
+                    msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"OPENVPNSERVICE\") failed", __FUNCTION__);
+                }
+
+                /* We know user is using the service. Skip auto-start setting check. */
+                goto cleanup_OpenService;
+            }
+            break;
+        }
+    }
+    else
+    {
+        uiResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: QueryServiceStatusEx(\"OpenVPNService\") failed", __FUNCTION__);
+    }
+
+    /* Service is not started. Is it set to auto-start? */
+    /* MSDN describes the maximum buffer size for QueryServiceConfig() to be 8kB. */
+    /* This is small enough to fit on stack. */
+    BYTE _buffer_8k[8192];
+    LPQUERY_SERVICE_CONFIG pQsc = (LPQUERY_SERVICE_CONFIG)_buffer_8k;
+    dwBufSize = sizeof(_buffer_8k);
+    if (!QueryServiceConfig(hService, pQsc, dwBufSize, &dwBufSize))
+    {
+        uiResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: QueryServiceStatusEx(\"QueryServiceConfig\") failed", __FUNCTION__);
+        goto cleanup_OpenService;
+    }
+
+    if (pQsc->dwStartType <= SERVICE_AUTO_START)
+    {
+        /* Service is set to auto-start. Set OPENVPNSERVICE property to its path. */
+        uiResult = MsiSetProperty(hInstall, TEXT("OPENVPNSERVICE"), pQsc->lpBinaryPathName);
+        if (uiResult != ERROR_SUCCESS)
+        {
+            SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
+            msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"OPENVPNSERVICE\") failed", __FUNCTION__);
+            goto cleanup_OpenService;
+        }
+    }
+
+    uiResult = ERROR_SUCCESS;
+
+cleanup_OpenService:
+    CloseServiceHandle(hService);
+cleanup_OpenSCManager:
+    CloseServiceHandle(hSCManager);
+    return uiResult;
+}
+
+
+static void
+find_adapters(
+    _In_ MSIHANDLE hInstall,
+    _In_z_ LPCTSTR szzHardwareIDs,
+    _In_z_ LPCTSTR szAdaptersPropertyName,
+    _In_z_ LPCTSTR szActiveAdaptersPropertyName)
+{
+    UINT uiResult;
+
+    /* Get network adapters with given hardware ID. */
+    struct tap_adapter_node *pAdapterList = NULL;
+    uiResult = tap_list_adapters(NULL, szzHardwareIDs, &pAdapterList);
+    if (uiResult != ERROR_SUCCESS)
+    {
+        return;
+    }
+    else if (pAdapterList == NULL)
+    {
+        /* No adapters - no fun. */
+        return;
+    }
+
+    /* Get IPv4/v6 info for all network adapters. Actually, we're interested in link status only: up/down? */
+    PIP_ADAPTER_ADDRESSES pAdapterAdresses = NULL;
+    ULONG ulAdapterAdressesSize = 16*1024;
+    for (size_t iteration = 0; iteration < 2; iteration++)
+    {
+        pAdapterAdresses = (PIP_ADAPTER_ADDRESSES)malloc(ulAdapterAdressesSize);
+        if (pAdapterAdresses == NULL)
+        {
+            msg(M_NONFATAL, "%s: malloc(%u) failed", __FUNCTION__, ulAdapterAdressesSize);
+            uiResult = ERROR_OUTOFMEMORY; goto cleanup_pAdapterList;
+        }
+
+        ULONG ulResult = GetAdaptersAddresses(
+            AF_UNSPEC,
+            GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_INCLUDE_ALL_INTERFACES,
+            NULL,
+            pAdapterAdresses,
+            &ulAdapterAdressesSize);
+
+        if (ulResult == ERROR_SUCCESS)
+        {
+            break;
+        }
+
+        free(pAdapterAdresses);
+        if (ulResult != ERROR_BUFFER_OVERFLOW)
+        {
+            SetLastError(ulResult); /* MSDN does not mention GetAdaptersAddresses() to set GetLastError(). But we do have an error code. Set last error manually. */
+            msg(M_NONFATAL | M_ERRNO, "%s: GetAdaptersAddresses() failed", __FUNCTION__);
+            uiResult = ulResult; goto cleanup_pAdapterList;
+        }
+    }
+
+    /* Count adapters. */
+    size_t adapter_count = 0;
+    for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext)
+    {
+        adapter_count++;
+    }
+
+    /* Prepare semicolon delimited list of TAP adapter ID(s) and active TAP adapter ID(s). */
+    LPTSTR
+        szAdapters     = (LPTSTR)malloc(adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR)),
+        szAdaptersTail = szAdapters;
+    if (szAdapters == NULL)
+    {
+        msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR));
+        uiResult = ERROR_OUTOFMEMORY; goto cleanup_pAdapterAdresses;
+    }
+
+    LPTSTR
+        szAdaptersActive     = (LPTSTR)malloc(adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR)),
+        szAdaptersActiveTail = szAdaptersActive;
+    if (szAdaptersActive == NULL)
+    {
+        msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR));
+        uiResult = ERROR_OUTOFMEMORY; goto cleanup_szAdapters;
+    }
+
+    for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext)
+    {
+        /* Convert adapter GUID to UTF-16 string. (LPOLESTR defaults to LPWSTR) */
+        LPOLESTR szAdapterId = NULL;
+        StringFromIID((REFIID)&pAdapter->guid, &szAdapterId);
+
+        /* Append to the list of TAP adapter ID(s). */
+        if (szAdapters < szAdaptersTail)
+        {
+            *(szAdaptersTail++) = TEXT(';');
+        }
+        memcpy(szAdaptersTail, szAdapterId, 38 * sizeof(TCHAR));
+        szAdaptersTail += 38;
+
+        /* If this adapter is active (connected), add it to the list of active TAP adapter ID(s). */
+        for (PIP_ADAPTER_ADDRESSES p = pAdapterAdresses; p; p = p->Next)
+        {
+            OLECHAR szId[38 /*GUID*/ + 1 /*terminator*/];
+            GUID guid;
+            if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p->AdapterName, -1, szId, _countof(szId)) > 0
+                && SUCCEEDED(IIDFromString(szId, &guid))
+                && memcmp(&guid, &pAdapter->guid, sizeof(GUID)) == 0)
+            {
+                if (p->OperStatus == IfOperStatusUp)
+                {
+                    /* This TAP adapter is active (connected). */
+                    if (szAdaptersActive < szAdaptersActiveTail)
+                    {
+                        *(szAdaptersActiveTail++) = TEXT(';');
+                    }
+                    memcpy(szAdaptersActiveTail, szAdapterId, 38 * sizeof(TCHAR));
+                    szAdaptersActiveTail += 38;
+                }
+                break;
+            }
+        }
+        CoTaskMemFree(szAdapterId);
+    }
+    szAdaptersTail      [0] = 0;
+    szAdaptersActiveTail[0] = 0;
+
+    /* Set Installer properties. */
+    uiResult = MsiSetProperty(hInstall, szAdaptersPropertyName, szAdapters);
+    if (uiResult != ERROR_SUCCESS)
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"%s\") failed", __FUNCTION__, szAdaptersPropertyName);
+        goto cleanup_szAdaptersActive;
+    }
+    uiResult = MsiSetProperty(hInstall, szActiveAdaptersPropertyName, szAdaptersActive);
+    if (uiResult != ERROR_SUCCESS)
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"%s\") failed", __FUNCTION__, szActiveAdaptersPropertyName);
+        goto cleanup_szAdaptersActive;
+    }
+
+cleanup_szAdaptersActive:
+    free(szAdaptersActive);
+cleanup_szAdapters:
+    free(szAdapters);
+cleanup_pAdapterAdresses:
+    free(pAdapterAdresses);
+cleanup_pAdapterList:
+    tap_free_adapter_list(pAdapterList);
+}
+
+
+UINT __stdcall
+FindSystemInfo(_In_ MSIHANDLE hInstall)
+{
+#ifdef _MSC_VER
+#pragma comment(linker, DLLEXP_EXPORT)
+#endif
+
+    debug_popup(TEXT(__FUNCTION__));
+
+    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
+
+    OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);
+
+    set_openvpnserv_state(hInstall);
+    find_adapters(
+        hInstall,
+        TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0") TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0"),
+        TEXT("TAPWINDOWS6ADAPTERS"),
+        TEXT("ACTIVETAPWINDOWS6ADAPTERS"));
+    find_adapters(
+        hInstall,
+        TEXT("Wintun") TEXT("\0"),
+        TEXT("WINTUNADAPTERS"),
+        TEXT("ACTIVEWINTUNADAPTERS"));
+
+    if (bIsCoInitialized)
+    {
+        CoUninitialize();
+    }
+    return ERROR_SUCCESS;
+}
+
+
+UINT __stdcall
+CloseOpenVPNGUI(_In_ MSIHANDLE hInstall)
+{
+#ifdef _MSC_VER
+#pragma comment(linker, DLLEXP_EXPORT)
+#endif
+    UNREFERENCED_PARAMETER(hInstall); /* This CA is does not interact with MSI session (report errors, access properties, tables, etc.). */
+
+    debug_popup(TEXT(__FUNCTION__));
+
+    /* Find OpenVPN GUI window. */
+    HWND hWnd = FindWindow(TEXT("OpenVPN-GUI"), NULL);
+    if (hWnd)
+    {
+        /* Ask it to close and wait for 100ms. Unfortunately, this will succeed only for recent OpenVPN GUI that do not run elevated. */
+        SendMessage(hWnd, WM_CLOSE, 0, 0);
+        Sleep(100);
+    }
+
+    return ERROR_SUCCESS;
+}
+
+
+UINT __stdcall
+StartOpenVPNGUI(_In_ MSIHANDLE hInstall)
+{
+#ifdef _MSC_VER
+#pragma comment(linker, DLLEXP_EXPORT)
+#endif
+
+    debug_popup(TEXT(__FUNCTION__));
+
+    UINT uiResult;
+    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
+
+    OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);
+
+    /* Create and populate a MSI record. */
+    MSIHANDLE hRecord = MsiCreateRecord(1);
+    if (!hRecord)
+    {
+        uiResult = ERROR_INVALID_HANDLE;
+        msg(M_NONFATAL, "%s: MsiCreateRecord failed", __FUNCTION__);
+        goto cleanup_CoInitialize;
+    }
+    uiResult = MsiRecordSetString(hRecord, 0, TEXT("\"[#bin.openvpn_gui.exe]\""));
+    if (uiResult != ERROR_SUCCESS)
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiRecordSetString() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiRecordSetString failed", __FUNCTION__);
+        goto cleanup_MsiCreateRecord;
+    }
+
+    /* Format string. */
+    TCHAR szStackBuf[MAX_PATH];
+    DWORD dwPathSize = _countof(szStackBuf);
+    LPTSTR szPath = szStackBuf;
+    uiResult = MsiFormatRecord(hInstall, hRecord, szPath, &dwPathSize);
+    if (uiResult == ERROR_MORE_DATA)
+    {
+        /* Allocate buffer on heap (+1 for terminator), and retry. */
+        szPath = (LPTSTR)malloc((++dwPathSize) * sizeof(TCHAR));
+        if (szPath == NULL)
+        {
+            msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwPathSize * sizeof(TCHAR));
+            uiResult = ERROR_OUTOFMEMORY; goto cleanup_MsiCreateRecord;
+        }
+
+        uiResult = MsiFormatRecord(hInstall, hRecord, szPath, &dwPathSize);
+    }
+    if (uiResult != ERROR_SUCCESS)
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiFormatRecord() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiFormatRecord failed", __FUNCTION__);
+        goto cleanup_malloc_szPath;
+    }
+
+    /* Launch the OpenVPN GUI. */
+    SHELLEXECUTEINFO sei = {
+        .cbSize = sizeof(SHELLEXECUTEINFO),
+        .fMask  = SEE_MASK_FLAG_NO_UI, /* Don't show error UI, we'll display it. */
+        .lpFile = szPath,
+        .nShow  = SW_SHOWNORMAL
+    };
+    if (!ShellExecuteEx(&sei))
+    {
+        uiResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: ShellExecuteEx(%s) failed", __FUNCTION__, szPath);
+        goto cleanup_malloc_szPath;
+    }
+
+    uiResult = ERROR_SUCCESS;
+
+cleanup_malloc_szPath:
+    if (szPath != szStackBuf)
+    {
+        free(szPath);
+    }
+cleanup_MsiCreateRecord:
+    MsiCloseHandle(hRecord);
+cleanup_CoInitialize:
+    if (bIsCoInitialized)
+    {
+        CoUninitialize();
+    }
+    return uiResult;
+}
+
+
+/**
+ * Schedules adapter creation.
+ *
+ * When the rollback is enabled, the adapter deletition is scheduled on rollback.
+ *
+ * @param seq           The argument sequence to pass to InstallTUNTAPAdapters custom action
+ *
+ * @param seqRollback   The argument sequence to pass to InstallTUNTAPAdaptersRollback custom
+ *                      action. NULL when rollback is disabled.
+ *
+ * @param szDisplayName  Adapter display name
+ *
+ * @param szHardwareId  Adapter hardware ID
+ *
+ * @param iTicks        Pointer to an integer that represents amount of work (on progress
+ *                      indicator) the InstallTUNTAPAdapters will take. This function increments it
+ *                      by MSICA_ADAPTER_TICK_SIZE for each adapter to create.
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ */
+static DWORD
+schedule_adapter_create(
+    _Inout_ struct msica_arg_seq *seq,
+    _Inout_opt_ struct msica_arg_seq *seqRollback,
+    _In_z_ LPCTSTR szDisplayName,
+    _In_z_ LPCTSTR szHardwareId,
+    _Inout_ int *iTicks)
+{
+    /* Get existing network adapters. */
+    struct tap_adapter_node *pAdapterList = NULL;
+    DWORD dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);
+    if (dwResult != ERROR_SUCCESS)
+    {
+        return dwResult;
+    }
+
+    /* Does adapter exist? */
+    for (struct tap_adapter_node *pAdapterOther = pAdapterList;; pAdapterOther = pAdapterOther->pNext)
+    {
+        if (pAdapterOther == NULL)
+        {
+            /* No adapter with a same name found. */
+            TCHAR szArgument[10 /*create=""|deleteN=""*/ + MAX_PATH /*szDisplayName*/ + 1 /*|*/ + MAX_PATH /*szHardwareId*/ + 1 /*terminator*/];
+
+            /* InstallTUNTAPAdapters will create the adapter. */
+            _stprintf_s(
+                szArgument, _countof(szArgument),
+                TEXT("create=\"%.*s|%.*s\""),
+                MAX_PATH, szDisplayName,
+                MAX_PATH, szHardwareId);
+            msica_arg_seq_add_tail(seq, szArgument);
+
+            if (seqRollback)
+            {
+                /* InstallTUNTAPAdaptersRollback will delete the adapter. */
+                _stprintf_s(
+                    szArgument, _countof(szArgument),
+                    TEXT("deleteN=\"%.*s\""),
+                    MAX_PATH, szDisplayName);
+                msica_arg_seq_add_head(seqRollback, szArgument);
+            }
+
+            *iTicks += MSICA_ADAPTER_TICK_SIZE;
+            break;
+        }
+        else if (_tcsicmp(szDisplayName, pAdapterOther->szName) == 0)
+        {
+            /* Adapter with a same name found. */
+            for (LPCTSTR hwid = pAdapterOther->szzHardwareIDs;; hwid += _tcslen(hwid) + 1)
+            {
+                if (hwid[0] == 0)
+                {
+                    /* This adapter has a different hardware ID. */
+                    msg(M_NONFATAL, "%s: Adapter with name \"%" PRIsLPTSTR "\" already exists", __FUNCTION__, pAdapterOther->szName);
+                    dwResult = ERROR_ALREADY_EXISTS;
+                    goto cleanup_pAdapterList;
+                }
+                else if (_tcsicmp(hwid, szHardwareId) == 0)
+                {
+                    /* This is an adapter with the requested hardware ID. We already have what we want! */
+                    break;
+                }
+            }
+            break; /* Adapter names are unique. There should be no other adapter with this name. */
+        }
+    }
+
+cleanup_pAdapterList:
+    tap_free_adapter_list(pAdapterList);
+    return dwResult;
+}
+
+
+/**
+ * Schedules adapter deletion.
+ *
+ * When the rollback is enabled, the adapter deletition is scheduled as: disable in
+ * UninstallTUNTAPAdapters, enable on rollback, delete on commit.
+ *
+ * When rollback is disabled, the adapter deletition is scheduled as delete in
+ * UninstallTUNTAPAdapters.
+ *
+ * @param seq           The argument sequence to pass to UninstallTUNTAPAdapters custom action
+ *
+ * @param seqCommit     The argument sequence to pass to UninstallTUNTAPAdaptersCommit custom
+ *                      action. NULL when rollback is disabled.
+ *
+ * @param seqRollback   The argument sequence to pass to UninstallTUNTAPAdaptersRollback custom
+ *                      action. NULL when rollback is disabled.
+ *
+ * @param szDisplayName  Adapter display name
+ *
+ * @param szzHardwareIDs  String of strings with acceptable adapter hardware IDs
+ *
+ * @param iTicks        Pointer to an integer that represents amount of work (on progress
+ *                      indicator) the UninstallTUNTAPAdapters will take. This function increments
+ *                      it by MSICA_ADAPTER_TICK_SIZE for each adapter to delete.
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ */
+static DWORD
+schedule_adapter_delete(
+    _Inout_ struct msica_arg_seq *seq,
+    _Inout_opt_ struct msica_arg_seq *seqCommit,
+    _Inout_opt_ struct msica_arg_seq *seqRollback,
+    _In_z_ LPCTSTR szDisplayName,
+    _In_z_ LPCTSTR szzHardwareIDs,
+    _Inout_ int *iTicks)
+{
+    /* Get adapters with given hardware ID. */
+    struct tap_adapter_node *pAdapterList = NULL;
+    DWORD dwResult = tap_list_adapters(NULL, szzHardwareIDs, &pAdapterList);
+    if (dwResult != ERROR_SUCCESS)
+    {
+        return dwResult;
+    }
+
+    /* Does adapter exist? */
+    for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter != NULL; pAdapter = pAdapter->pNext)
+    {
+        if (_tcsicmp(szDisplayName, pAdapter->szName) == 0)
+        {
+            /* Adapter found. */
+            TCHAR szArgument[8 /*disable=|enable=|delete=*/ + 38 /*{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}*/ + 1 /*terminator*/];
+            if (seqCommit && seqRollback)
+            {
+                /* UninstallTUNTAPAdapters will disable the adapter. */
+                _stprintf_s(
+                    szArgument, _countof(szArgument),
+                    TEXT("disable=") TEXT(PRIXGUID),
+                    PRIGUID_PARAM(pAdapter->guid));
+                msica_arg_seq_add_tail(seq, szArgument);
+
+                /* UninstallTUNTAPAdaptersRollback will re-enable the adapter. */
+                _stprintf_s(
+                    szArgument, _countof(szArgument),
+                    TEXT("enable=") TEXT(PRIXGUID),
+                    PRIGUID_PARAM(pAdapter->guid));
+                msica_arg_seq_add_head(seqRollback, szArgument);
+
+                /* UninstallTUNTAPAdaptersCommit will delete the adapter. */
+                _stprintf_s(
+                    szArgument, _countof(szArgument),
+                    TEXT("delete=") TEXT(PRIXGUID),
+                    PRIGUID_PARAM(pAdapter->guid));
+                msica_arg_seq_add_tail(seqCommit, szArgument);
+            }
+            else
+            {
+                /* UninstallTUNTAPAdapters will delete the adapter. */
+                _stprintf_s(
+                    szArgument, _countof(szArgument),
+                    TEXT("delete=") TEXT(PRIXGUID),
+                    PRIGUID_PARAM(pAdapter->guid));
+                msica_arg_seq_add_tail(seq, szArgument);
+            }
+
+            iTicks += MSICA_ADAPTER_TICK_SIZE;
+            break; /* Adapter names are unique. There should be no other adapter with this name. */
+        }
+    }
+
+    tap_free_adapter_list(pAdapterList);
+    return dwResult;
+}
+
+
+UINT __stdcall
+EvaluateTUNTAPAdapters(_In_ MSIHANDLE hInstall)
+{
+#ifdef _MSC_VER
+#pragma comment(linker, DLLEXP_EXPORT)
+#endif
+
+    debug_popup(TEXT(__FUNCTION__));
+
+    UINT uiResult;
+    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
+
+    OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);
+
+    struct msica_arg_seq
+        seqInstall,
+        seqInstallCommit,
+        seqInstallRollback,
+        seqUninstall,
+        seqUninstallCommit,
+        seqUninstallRollback;
+    msica_arg_seq_init(&seqInstall);
+    msica_arg_seq_init(&seqInstallCommit);
+    msica_arg_seq_init(&seqInstallRollback);
+    msica_arg_seq_init(&seqUninstall);
+    msica_arg_seq_init(&seqUninstallCommit);
+    msica_arg_seq_init(&seqUninstallRollback);
+
+    /* Check rollback state. */
+    bool bRollbackEnabled = MsiEvaluateCondition(hInstall, TEXT("RollbackDisabled")) != MSICONDITION_TRUE;
+
+    /* Open MSI database. */
+    MSIHANDLE hDatabase = MsiGetActiveDatabase(hInstall);
+    if (hDatabase == 0)
+    {
+        msg(M_NONFATAL, "%s: MsiGetActiveDatabase failed", __FUNCTION__);
+        uiResult = ERROR_INVALID_HANDLE;
+        goto cleanup_exec_seq;
+    }
+
+    /* Check if TUNTAPAdapter table exists. If it doesn't exist, there's nothing to do. */
+    switch (MsiDatabaseIsTablePersistent(hDatabase, TEXT("TUNTAPAdapter")))
+    {
+        case MSICONDITION_FALSE:
+        case MSICONDITION_TRUE: break;
+
+        default:
+            uiResult = ERROR_SUCCESS;
+            goto cleanup_hDatabase;
+    }
+
+    /* Prepare a query to get a list/view of adapters. */
+    MSIHANDLE hViewST = 0;
+    LPCTSTR szQuery = TEXT("SELECT `Adapter`,`DisplayName`,`Condition`,`Component_`,`HardwareId` FROM `TUNTAPAdapter`");
+    uiResult = MsiDatabaseOpenView(hDatabase, szQuery, &hViewST);
+    if (uiResult != ERROR_SUCCESS)
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiDatabaseOpenView() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiDatabaseOpenView(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szQuery);
+        goto cleanup_hDatabase;
+    }
+
+    /* Execute query! */
+    uiResult = MsiViewExecute(hViewST, 0);
+    if (uiResult != ERROR_SUCCESS)
+    {
+        SetLastError(uiResult); /* MSDN does not mention MsiViewExecute() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: MsiViewExecute(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szQuery);
+        goto cleanup_hViewST;
+    }
+
+    /* Create a record to report progress with. */
+    MSIHANDLE hRecordProg = MsiCreateRecord(2);
+    if (!hRecordProg)
+    {
+        uiResult = ERROR_INVALID_HANDLE;
+        msg(M_NONFATAL, "%s: MsiCreateRecord failed", __FUNCTION__);
+        goto cleanup_hViewST_close;
+    }
+
+    for (;; )
+    {
+        /* Fetch one record from the view. */
+        MSIHANDLE hRecord = 0;
+        uiResult = MsiViewFetch(hViewST, &hRecord);
+        if (uiResult == ERROR_NO_MORE_ITEMS)
+        {
+            uiResult = ERROR_SUCCESS;
+            break;
+        }
+        else if (uiResult != ERROR_SUCCESS)
+        {
+            SetLastError(uiResult); /* MSDN does not mention MsiViewFetch() to set GetLastError(). But we do have an error code. Set last error manually. */
+            msg(M_NONFATAL | M_ERRNO, "%s: MsiViewFetch failed", __FUNCTION__);
+            goto cleanup_hRecordProg;
+        }
+
+        INSTALLSTATE iInstalled, iAction;
+        {
+            /* Read adapter component ID (`Component_` is field #4). */
+            LPTSTR szValue = NULL;
+            uiResult = msi_get_record_string(hRecord, 4, &szValue);
+            if (uiResult != ERROR_SUCCESS)
+            {
+                goto cleanup_hRecord;
+            }
+
+            /* Get the component state. */
+            uiResult = MsiGetComponentState(hInstall, szValue, &iInstalled, &iAction);
+            if (uiResult != ERROR_SUCCESS)
+            {
+                SetLastError(uiResult); /* MSDN does not mention MsiGetComponentState() to set GetLastError(). But we do have an error code. Set last error manually. */
+                msg(M_NONFATAL | M_ERRNO, "%s: MsiGetComponentState(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szValue);
+                free(szValue);
+                goto cleanup_hRecord;
+            }
+            free(szValue);
+        }
+
+        /* Get adapter display name (`DisplayName` is field #2). */
+        LPTSTR szDisplayName = NULL;
+        uiResult = msi_format_field(hInstall, hRecord, 2, &szDisplayName);
+        if (uiResult != ERROR_SUCCESS)
+        {
+            goto cleanup_hRecord;
+        }
+        /* `DisplayName` field type is [Filename](https://docs.microsoft.com/en-us/windows/win32/msi/filename), which is either "8.3|long name" or "8.3". */
+        LPTSTR szDisplayNameEx = _tcschr(szDisplayName, TEXT('|'));
+        szDisplayNameEx = szDisplayNameEx != NULL ? szDisplayNameEx + 1 : szDisplayName;
+
+        /* Get adapter hardware ID (`HardwareId` is field #5). */
+        TCHAR szzHardwareIDs[0x100] = { 0 };
+        {
+            LPTSTR szHwId = NULL;
+            uiResult = msi_get_record_string(hRecord, 5, &szHwId);
+            if (uiResult != ERROR_SUCCESS)
+            {
+                goto cleanup_szDisplayName;
+            }
+            memcpy_s(szzHardwareIDs, sizeof(szzHardwareIDs) - 2*sizeof(TCHAR) /*requires double zero termination*/, szHwId, _tcslen(szHwId)*sizeof(TCHAR));
+            free(szHwId);
+        }
+
+        if (iAction > INSTALLSTATE_BROKEN)
+        {
+            int iTicks = 0;
+
+            if (iAction >= INSTALLSTATE_LOCAL)
+            {
+                /* Read and evaluate adapter condition (`Condition` is field #3). */
+                LPTSTR szValue = NULL;
+                uiResult = msi_get_record_string(hRecord, 3, &szValue);
+                if (uiResult != ERROR_SUCCESS)
+                {
+                    goto cleanup_szDisplayName;
+                }
+#ifdef __GNUC__
+/*
+ * warning: enumeration value ‘MSICONDITION_TRUE’ not handled in switch
+ * warning: enumeration value ‘MSICONDITION_NONE’ not handled in switch
+ */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wswitch"
+#endif
+                switch (MsiEvaluateCondition(hInstall, szValue))
+                {
+                    case MSICONDITION_FALSE:
+                        free(szValue);
+                        goto cleanup_szDisplayName;
+
+                    case MSICONDITION_ERROR:
+                        uiResult = ERROR_INVALID_FIELD;
+                        msg(M_NONFATAL | M_ERRNO, "%s: MsiEvaluateCondition(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szValue);
+                        free(szValue);
+                        goto cleanup_szDisplayName;
+                }
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+                free(szValue);
+
+                /* Component is or should be installed. Schedule adapter creation. */
+                if (schedule_adapter_create(
+                        &seqInstall,
+                        bRollbackEnabled ? &seqInstallRollback : NULL,
+                        szDisplayNameEx,
+                        szzHardwareIDs,
+                        &iTicks) != ERROR_SUCCESS)
+                {
+                    uiResult = ERROR_INSTALL_FAILED;
+                    goto cleanup_szDisplayName;
+                }
+            }
+            else
+            {
+                /* Component is installed, but should be degraded to advertised/removed. Schedule adapter deletition.
+                 *
+                 * Note: On adapter removal (product is being uninstalled), we tolerate dwResult error.
+                 * Better a partial uninstallation than no uninstallation at all.
+                 */
+                schedule_adapter_delete(
+                    &seqUninstall,
+                    bRollbackEnabled ? &seqUninstallCommit : NULL,
+                    bRollbackEnabled ? &seqUninstallRollback : NULL,
+                    szDisplayNameEx,
+                    szzHardwareIDs,
+                    &iTicks);
+            }
+
+            /* Arrange the amount of tick space to add to the progress indicator.
+             * Do this within the loop to poll for user cancellation. */
+            MsiRecordSetInteger(hRecordProg, 1, 3 /* OP3 = Add ticks to the expected total number of progress of the progress bar */);
+            MsiRecordSetInteger(hRecordProg, 2, iTicks);
+            if (MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL)
+            {
+                uiResult = ERROR_INSTALL_USEREXIT;
+                goto cleanup_szDisplayName;
+            }
+        }
+
+cleanup_szDisplayName:
+        free(szDisplayName);
+cleanup_hRecord:
+        MsiCloseHandle(hRecord);
+        if (uiResult != ERROR_SUCCESS)
+        {
+            goto cleanup_hRecordProg;
+        }
+    }
+
+    /* Store deferred custom action parameters. */
+    if ((uiResult = setup_sequence(hInstall, TEXT("InstallTUNTAPAdapters"          ), &seqInstall          )) != ERROR_SUCCESS
+        || (uiResult = setup_sequence(hInstall, TEXT("InstallTUNTAPAdaptersCommit"    ), &seqInstallCommit    )) != ERROR_SUCCESS
+        || (uiResult = setup_sequence(hInstall, TEXT("InstallTUNTAPAdaptersRollback"  ), &seqInstallRollback  )) != ERROR_SUCCESS
+        || (uiResult = setup_sequence(hInstall, TEXT("UninstallTUNTAPAdapters"        ), &seqUninstall        )) != ERROR_SUCCESS
+        || (uiResult = setup_sequence(hInstall, TEXT("UninstallTUNTAPAdaptersCommit"  ), &seqUninstallCommit  )) != ERROR_SUCCESS
+        || (uiResult = setup_sequence(hInstall, TEXT("UninstallTUNTAPAdaptersRollback"), &seqUninstallRollback)) != ERROR_SUCCESS)
+    {
+        goto cleanup_hRecordProg;
+    }
+
+    uiResult = ERROR_SUCCESS;
+
+cleanup_hRecordProg:
+    MsiCloseHandle(hRecordProg);
+cleanup_hViewST_close:
+    MsiViewClose(hViewST);
+cleanup_hViewST:
+    MsiCloseHandle(hViewST);
+cleanup_hDatabase:
+    MsiCloseHandle(hDatabase);
+cleanup_exec_seq:
+    msica_arg_seq_free(&seqInstall);
+    msica_arg_seq_free(&seqInstallCommit);
+    msica_arg_seq_free(&seqInstallRollback);
+    msica_arg_seq_free(&seqUninstall);
+    msica_arg_seq_free(&seqUninstallCommit);
+    msica_arg_seq_free(&seqUninstallRollback);
+    if (bIsCoInitialized)
+    {
+        CoUninitialize();
+    }
+    return uiResult;
+}
+
+
+/**
+ * Parses string encoded GUID.
+ *
+ * @param szArg         Zero terminated string where the GUID string starts
+ *
+ * @param guid          Pointer to GUID that receives parsed value
+ *
+ * @return TRUE on success; FALSE otherwise
+ */
+static BOOL
+parse_guid(
+    _In_z_ LPCWSTR szArg,
+    _Out_ GUID *guid)
+{
+    if (swscanf_s(szArg, _L(PRIXGUID), PRIGUID_PARAM_REF(*guid)) != 11)
+    {
+        msg(M_NONFATAL | M_ERRNO, "%s: swscanf_s(\"%ls\") failed", __FUNCTION__, szArg);
+        return FALSE;
+    }
+    return TRUE;
+}
+
+
+UINT __stdcall
+ProcessDeferredAction(_In_ MSIHANDLE hInstall)
+{
+#ifdef _MSC_VER
+#pragma comment(linker, DLLEXP_EXPORT)
+#endif
+
+    debug_popup(TEXT(__FUNCTION__));
+
+    UINT uiResult;
+    BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL));
+
+    OPENVPNMSICA_SAVE_MSI_SESSION(hInstall);
+
+    BOOL bIsCleanup = MsiGetMode(hInstall, MSIRUNMODE_COMMIT) || MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK);
+
+    /* Get sequence arguments. Always Unicode as CommandLineToArgvW() is available as Unicode-only. */
+    LPWSTR szSequence = NULL;
+    uiResult = msi_get_string(hInstall, L"CustomActionData", &szSequence);
+    if (uiResult != ERROR_SUCCESS)
+    {
+        goto cleanup_CoInitialize;
+    }
+    int nArgs;
+    LPWSTR *szArg = CommandLineToArgvW(szSequence, &nArgs);
+    if (szArg == NULL)
+    {
+        uiResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: CommandLineToArgvW(\"%ls\") failed", __FUNCTION__, szSequence);
+        goto cleanup_szSequence;
+    }
+
+    /* Tell the installer to use explicit progress messages. */
+    MSIHANDLE hRecordProg = MsiCreateRecord(3);
+    MsiRecordSetInteger(hRecordProg, 1, 1);
+    MsiRecordSetInteger(hRecordProg, 2, 1);
+    MsiRecordSetInteger(hRecordProg, 3, 0);
+    MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg);
+
+    /* Prepare hRecordProg for progress messages. */
+    MsiRecordSetInteger(hRecordProg, 1, 2);
+    MsiRecordSetInteger(hRecordProg, 3, 0);
+
+    BOOL bRebootRequired = FALSE;
+
+    for (int i = 1 /*CommandLineToArgvW injects msiexec.exe as szArg[0]*/; i < nArgs; ++i)
+    {
+        DWORD dwResult = ERROR_SUCCESS;
+
+        if (wcsncmp(szArg[i], L"create=", 7) == 0)
+        {
+            /* Create an adapter with a given name and hardware ID. */
+            LPWSTR szName = szArg[i] + 7;
+            LPWSTR szHardwareId = wcschr(szName, L'|');
+            if (szHardwareId == NULL)
+            {
+                goto invalid_argument;
+            }
+            szHardwareId[0] = 0;
+            ++szHardwareId;
+
+            {
+                /* Report the name of the adapter to installer. */
+                MSIHANDLE hRecord = MsiCreateRecord(4);
+                MsiRecordSetString(hRecord, 1, TEXT("Creating adapter"));
+                MsiRecordSetString(hRecord, 2, szName);
+                MsiRecordSetString(hRecord, 3, szHardwareId);
+                int iResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
+                MsiCloseHandle(hRecord);
+                if (iResult == IDCANCEL)
+                {
+                    uiResult = ERROR_INSTALL_USEREXIT;
+                    goto cleanup;
+                }
+            }
+
+            GUID guidAdapter;
+            dwResult = tap_create_adapter(NULL, NULL, szHardwareId, &bRebootRequired, &guidAdapter);
+            if (dwResult == ERROR_SUCCESS)
+            {
+                /* Set adapter name. May fail on some machines, but that is not critical - use silent
+                   flag to mute messagebox and print error only to log */
+                tap_set_adapter_name(&guidAdapter, szName, TRUE);
+            }
+        }
+        else if (wcsncmp(szArg[i], L"deleteN=", 8) == 0)
+        {
+            /* Delete the adapter by name. */
+            LPCWSTR szName = szArg[i] + 8;
+
+            {
+                /* Report the name of the adapter to installer. */
+                MSIHANDLE hRecord = MsiCreateRecord(3);
+                MsiRecordSetString(hRecord, 1, TEXT("Deleting adapter"));
+                MsiRecordSetString(hRecord, 2, szName);
+                int iResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord);
+                MsiCloseHandle(hRecord);
+                if (iResult == IDCANCEL)
+                {
+                    uiResult = ERROR_INSTALL_USEREXIT;
+                    goto cleanup;
+                }
+            }
+
+            /* Get existing adapters. */
+            struct tap_adapter_node *pAdapterList = NULL;
+            dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);
+            if (dwResult == ERROR_SUCCESS)
+            {
+                /* Does the adapter exist? */
+                for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter != NULL; pAdapter = pAdapter->pNext)
+                {
+                    if (_tcsicmp(szName, pAdapter->szName) == 0)
+                    {
+                        /* Adapter found. */
+                        dwResult = tap_delete_adapter(NULL, &pAdapter->guid, &bRebootRequired);
+                        break;
+                    }
+                }
+
+                tap_free_adapter_list(pAdapterList);
+            }
+        }
+        else if (wcsncmp(szArg[i], L"delete=", 7) == 0)
+        {
+            /* Delete the adapter by GUID. */
+            GUID guid;
+            if (!parse_guid(szArg[i] + 7, &guid))
+            {
+                goto invalid_argument;
+            }
+            dwResult = tap_delete_adapter(NULL, &guid, &bRebootRequired);
+        }
+        else if (wcsncmp(szArg[i], L"enable=", 7) == 0)
+        {
+            /* Enable the adapter. */
+            GUID guid;
+            if (!parse_guid(szArg[i] + 7, &guid))
+            {
+                goto invalid_argument;
+            }
+            dwResult = tap_enable_adapter(NULL, &guid, TRUE, &bRebootRequired);
+        }
+        else if (wcsncmp(szArg[i], L"disable=", 8) == 0)
+        {
+            /* Disable the adapter. */
+            GUID guid;
+            if (!parse_guid(szArg[i] + 8, &guid))
+            {
+                goto invalid_argument;
+            }
+            dwResult = tap_enable_adapter(NULL, &guid, FALSE, &bRebootRequired);
+        }
+        else
+        {
+            goto invalid_argument;
+        }
+
+        if (dwResult != ERROR_SUCCESS && !bIsCleanup /* Ignore errors in case of commit/rollback to do as much work as possible. */)
+        {
+            uiResult = ERROR_INSTALL_FAILURE;
+            goto cleanup;
+        }
+
+        /* Report progress and check for user cancellation. */
+        MsiRecordSetInteger(hRecordProg, 2, MSICA_ADAPTER_TICK_SIZE);
+        if (MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL)
+        {
+            dwResult = ERROR_INSTALL_USEREXIT;
+            goto cleanup;
+        }
+
+        continue;
+
+invalid_argument:
+        msg(M_NONFATAL, "%s: Ignoring invalid argument: %ls", __FUNCTION__, szArg[i]);
+    }
+
+cleanup:
+    if (bRebootRequired)
+    {
+        MsiSetMode(hInstall, MSIRUNMODE_REBOOTATEND, TRUE);
+    }
+    MsiCloseHandle(hRecordProg);
+    LocalFree(szArg);
+cleanup_szSequence:
+    free(szSequence);
+cleanup_CoInitialize:
+    if (bIsCoInitialized)
+    {
+        CoUninitialize();
+    }
+    return uiResult;
+}
diff --git a/src/openvpnmsica/openvpnmsica.h b/src/openvpnmsica/openvpnmsica.h
new file mode 100644
index 0000000..221d03c
--- /dev/null
+++ b/src/openvpnmsica/openvpnmsica.h
@@ -0,0 +1,153 @@
+/*
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
+ *                  https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA
+ *
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MSICA_H
+#define MSICA_H
+
+#include <windows.h>
+#include <msi.h>
+#include "../tapctl/basic.h"
+
+
+/*
+ * Error codes (next unused 2552L)
+ */
+#define ERROR_MSICA       2550L
+#define ERROR_MSICA_ERRNO 2551L
+
+
+/**
+ * Thread local storage data
+ */
+struct openvpnmsica_thread_data
+{
+    MSIHANDLE hInstall; /** Handle to the installation session. */
+};
+
+
+/**
+ * MSI session handle thread local storage index
+ */
+extern DWORD openvpnmsica_thread_data_idx;
+
+
+/**
+ * Set MSI session handle in thread local storage.
+ */
+#define OPENVPNMSICA_SAVE_MSI_SESSION(hInstall) \
+{ \
+    struct openvpnmsica_thread_data *s = (struct openvpnmsica_thread_data *)TlsGetValue(openvpnmsica_thread_data_idx); \
+    s->hInstall = (hInstall); \
+}
+
+
+/*
+ * Exported DLL Functions
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __GNUC__
+#define DLLEXP_DECL __declspec(dllexport)
+#else
+#define DLLEXP_DECL
+#define DLLEXP_EXPORT "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__
+#endif
+
+
+/**
+ * Determines Windows information:
+ *
+ * - Sets `OPENVPNSERVICE` MSI property to PID of OpenVPN Service if running, or its EXE path if
+ *   configured for auto-start.
+ *
+ * - Finds existing TAP-Windows6 adapters and set TAPWINDOWS6ADAPTERS and
+ *   ACTIVETAPWINDOWS6ADAPTERS properties with semicolon delimited list of all installed adapter
+ *   GUIDs and active adapter GUIDs respectively.
+ *
+ * - Finds existing Wintun adapters and set WINTUNADAPTERS and ACTIVEWINTUNADAPTERS properties
+ *   with semicolon delimited list of all installed adapter GUIDs and active adapter GUIDs
+ *   respectively.
+ *
+ * @param hInstall      Handle to the installation provided to the DLL custom action
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
+ */
+DLLEXP_DECL UINT __stdcall
+FindSystemInfo(_In_ MSIHANDLE hInstall);
+
+
+/**
+ * Find OpenVPN GUI window and send it a WM_CLOSE message.
+ *
+ * @param hInstall      Handle to the installation provided to the DLL custom action
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
+ */
+DLLEXP_DECL UINT __stdcall
+CloseOpenVPNGUI(_In_ MSIHANDLE hInstall);
+
+
+/**
+ * Launches OpenVPN GUI. It's path is obtained by expanding the `[#bin.openvpn_gui.exe]`
+ * therefore, its Id field in File table must be "bin.openvpn_gui.exe".
+ *
+ * @param hInstall      Handle to the installation provided to the DLL custom action
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
+ */
+DLLEXP_DECL UINT __stdcall
+StartOpenVPNGUI(_In_ MSIHANDLE hInstall);
+
+
+/**
+ * Evaluate the TUNTAPAdapter table of the MSI package database and prepare a list of TAP
+ * adapters to install/remove.
+ *
+ * @param hInstall      Handle to the installation provided to the DLL custom action
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
+ */
+DLLEXP_DECL UINT __stdcall
+EvaluateTUNTAPAdapters(_In_ MSIHANDLE hInstall);
+
+
+/**
+ * Perform scheduled deferred action.
+ *
+ * @param hInstall      Handle to the installation provided to the DLL custom action
+ *
+ * @return ERROR_SUCCESS on success; An error code otherwise
+ *         See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx
+ */
+DLLEXP_DECL UINT __stdcall
+ProcessDeferredAction(_In_ MSIHANDLE hInstall);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ifndef MSICA_H */
diff --git a/src/openvpnmsica/openvpnmsica.props b/src/openvpnmsica/openvpnmsica.props
new file mode 100644
index 0000000..074635d
--- /dev/null
+++ b/src/openvpnmsica/openvpnmsica.props
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets" />
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup>
+    <TargetName>lib$(ProjectName)</TargetName>
+  </PropertyGroup>
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <AdditionalIncludeDirectories>..\compat;$(TAP_WINDOWS_HOME)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>_WIN32_WINNT=_WIN32_WINNT_VISTA;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup />
+</Project>
\ No newline at end of file
diff --git a/src/openvpnmsica/openvpnmsica.vcxproj b/src/openvpnmsica/openvpnmsica.vcxproj
new file mode 100644
index 0000000..741800d
--- /dev/null
+++ b/src/openvpnmsica/openvpnmsica.vcxproj
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>

+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

+  <ItemGroup Label="ProjectConfigurations">

+    <ProjectConfiguration Include="Debug|ARM64">

+      <Configuration>Debug</Configuration>

+      <Platform>ARM64</Platform>

+    </ProjectConfiguration>

+    <ProjectConfiguration Include="Debug|Win32">

+      <Configuration>Debug</Configuration>

+      <Platform>Win32</Platform>

+    </ProjectConfiguration>

+    <ProjectConfiguration Include="Debug|x64">

+      <Configuration>Debug</Configuration>

+      <Platform>x64</Platform>

+    </ProjectConfiguration>

+    <ProjectConfiguration Include="Release|ARM64">

+      <Configuration>Release</Configuration>

+      <Platform>ARM64</Platform>

+    </ProjectConfiguration>

+    <ProjectConfiguration Include="Release|Win32">

+      <Configuration>Release</Configuration>

+      <Platform>Win32</Platform>

+    </ProjectConfiguration>

+    <ProjectConfiguration Include="Release|x64">

+      <Configuration>Release</Configuration>

+      <Platform>x64</Platform>

+    </ProjectConfiguration>

+  </ItemGroup>

+  <PropertyGroup Label="Globals">

+    <VCProjectVersion>15.0</VCProjectVersion>

+    <ProjectGuid>{D41AA9D6-B818-476E-992E-0E16EB86BEE2}</ProjectGuid>

+    <Keyword>Win32Proj</Keyword>

+    <RootNamespace>openvpnmsica</RootNamespace>

+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>

+  </PropertyGroup>

+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">

+    <ConfigurationType>DynamicLibrary</ConfigurationType>

+    <UseDebugLibraries>true</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <CharacterSet>Unicode</CharacterSet>

+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">

+    <ConfigurationType>DynamicLibrary</ConfigurationType>

+    <UseDebugLibraries>true</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <CharacterSet>Unicode</CharacterSet>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">

+    <ConfigurationType>DynamicLibrary</ConfigurationType>

+    <UseDebugLibraries>true</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <CharacterSet>Unicode</CharacterSet>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">

+    <ConfigurationType>DynamicLibrary</ConfigurationType>

+    <UseDebugLibraries>false</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <WholeProgramOptimization>true</WholeProgramOptimization>

+    <CharacterSet>Unicode</CharacterSet>

+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">

+    <ConfigurationType>DynamicLibrary</ConfigurationType>

+    <UseDebugLibraries>false</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <WholeProgramOptimization>true</WholeProgramOptimization>

+    <CharacterSet>Unicode</CharacterSet>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">

+    <ConfigurationType>DynamicLibrary</ConfigurationType>

+    <UseDebugLibraries>false</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <WholeProgramOptimization>true</WholeProgramOptimization>

+    <CharacterSet>Unicode</CharacterSet>

+  </PropertyGroup>

+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />

+  <ImportGroup Label="ExtensionSettings">

+  </ImportGroup>

+  <ImportGroup Label="Shared">

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Debug.props" />

+    <Import Project="openvpnmsica-Debug.props" />

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Debug.props" />

+    <Import Project="openvpnmsica-Debug.props" />

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Debug.props" />

+    <Import Project="openvpnmsica-Debug.props" />

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Release.props" />

+    <Import Project="openvpnmsica-Release.props" />

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Release.props" />

+    <Import Project="openvpnmsica-Release.props" />

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Release.props" />

+    <Import Project="openvpnmsica-Release.props" />

+  </ImportGroup>

+  <PropertyGroup Label="UserMacros" />

+  <ItemGroup>

+    <ClCompile Include="..\tapctl\error.c" />

+    <ClCompile Include="..\tapctl\tap.c" />

+    <ClCompile Include="dllmain.c" />

+    <ClCompile Include="msiex.c" />

+    <ClCompile Include="msica_arg.c" />

+    <ClCompile Include="openvpnmsica.c" />

+  </ItemGroup>

+  <ItemGroup>

+    <ClInclude Include="..\tapctl\basic.h" />

+    <ClInclude Include="..\tapctl\error.h" />

+    <ClInclude Include="..\tapctl\tap.h" />

+    <ClInclude Include="msiex.h" />

+    <ClInclude Include="msica_arg.h" />

+    <ClInclude Include="openvpnmsica.h" />

+  </ItemGroup>

+  <ItemGroup>

+    <ResourceCompile Include="openvpnmsica_resources.rc" />

+  </ItemGroup>

+  <ItemGroup>

+    <ProjectReference Include="..\..\build\msvc\msvc-generate\msvc-generate.vcxproj">

+      <Project>{8598c2c8-34c4-47a1-99b0-7c295a890615}</Project>

+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>

+    </ProjectReference>

+  </ItemGroup>

+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

+  <ImportGroup Label="ExtensionTargets">

+  </ImportGroup>

+</Project>
\ No newline at end of file
diff --git a/src/openvpnmsica/openvpnmsica.vcxproj.filters b/src/openvpnmsica/openvpnmsica.vcxproj.filters
new file mode 100644
index 0000000..1222502
--- /dev/null
+++ b/src/openvpnmsica/openvpnmsica.vcxproj.filters
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>

+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

+  <ItemGroup>

+    <Filter Include="Source Files">

+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>

+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>

+    </Filter>

+    <Filter Include="Header Files">

+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>

+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>

+    </Filter>

+    <Filter Include="Resource Files">

+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>

+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>

+    </Filter>

+  </ItemGroup>

+  <ItemGroup>

+    <ClCompile Include="dllmain.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="..\tapctl\error.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="msiex.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="openvpnmsica.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="msica_arg.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="..\tapctl\tap.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+  </ItemGroup>

+  <ItemGroup>

+    <ClInclude Include="openvpnmsica.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+    <ClInclude Include="msiex.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+    <ClInclude Include="msica_arg.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+    <ClInclude Include="..\tapctl\tap.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+    <ClInclude Include="..\tapctl\error.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+    <ClInclude Include="..\tapctl\basic.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+  </ItemGroup>

+  <ItemGroup>

+    <ResourceCompile Include="openvpnmsica_resources.rc">

+      <Filter>Resource Files</Filter>

+    </ResourceCompile>

+  </ItemGroup>

+</Project>
\ No newline at end of file
diff --git a/src/openvpnmsica/openvpnmsica_resources.rc b/src/openvpnmsica/openvpnmsica_resources.rc
new file mode 100644
index 0000000..1859fc3
--- /dev/null
+++ b/src/openvpnmsica/openvpnmsica_resources.rc
@@ -0,0 +1,62 @@
+/*
+ *  openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages
+ *
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#else
+#include <config-msvc-version.h>
+#endif
+#include <winresrc.h>
+
+#pragma code_page(65001) /* UTF8 */
+
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+
+VS_VERSION_INFO VERSIONINFO
+    FILEVERSION OPENVPN_VERSION_RESOURCE
+    PRODUCTVERSION OPENVPN_VERSION_RESOURCE
+    FILEFLAGSMASK VS_FF_DEBUG | VS_FF_PRERELEASE | VS_FF_PATCHED | VS_FF_PRIVATEBUILD | VS_FF_SPECIALBUILD
+#ifdef _DEBUG
+    FILEFLAGS VS_FF_DEBUG
+#else
+    FILEFLAGS 0x0L
+#endif
+    FILEOS VOS_NT_WINDOWS32
+    FILETYPE VFT_DLL
+    FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904b0"
+        BEGIN
+            VALUE "CompanyName", "The OpenVPN Project"
+            VALUE "FileDescription", "Custom Action DLL to provide OpenVPN-specific support to MSI packages"
+            VALUE "FileVersion", PACKAGE_VERSION ".0"
+            VALUE "InternalName", "OpenVPN"
+            VALUE "LegalCopyright", "Copyright © The OpenVPN Project"
+            VALUE "OriginalFilename", "libopenvpnmsica.dll"
+            VALUE "ProductName", "OpenVPN"
+            VALUE "ProductVersion", PACKAGE_VERSION ".0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1200
+    END
+END
diff --git a/src/openvpnserv/Makefile.am b/src/openvpnserv/Makefile.am
index bc65070..5dc38c9 100644
--- a/src/openvpnserv/Makefile.am
+++ b/src/openvpnserv/Makefile.am
@@ -36,4 +36,5 @@
 	service.c service.h \
 	validate.c validate.h \
 	$(top_srcdir)/src/openvpn/block_dns.c $(top_srcdir)/src/openvpn/block_dns.h \
-	openvpnserv_resources.rc
+	openvpnserv_resources.rc \
+	$(top_srcdir)/src/openvpn/ring_buffer.h
diff --git a/src/openvpnserv/automatic.c b/src/openvpnserv/automatic.c
index 5569ce9..3f2ca34 100644
--- a/src/openvpnserv/automatic.c
+++ b/src/openvpnserv/automatic.c
@@ -36,13 +36,9 @@
 
 #include <stdio.h>
 #include <stdarg.h>
+#include <stdbool.h>
 #include <process.h>
 
-/* bool definitions */
-#define bool int
-#define true 1
-#define false 0
-
 static SERVICE_STATUS_HANDLE service;
 static SERVICE_STATUS status = { .dwServiceType = SERVICE_WIN32_SHARE_PROCESS };
 
@@ -115,41 +111,36 @@
 static bool
 match(const WIN32_FIND_DATA *find, LPCTSTR ext)
 {
-    int i;
-
     if (find->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
     {
         return false;
     }
 
-    if (!_tcslen(ext))
+    if (*ext == TEXT('\0'))
     {
         return true;
     }
 
-    i = _tcslen(find->cFileName) - _tcslen(ext) - 1;
-    if (i < 1)
-    {
-        return false;
-    }
+    /* find the pointer to that last '.' in filename and match ext against the rest */
 
-    return find->cFileName[i] == '.' && !_tcsicmp(find->cFileName + i + 1, ext);
+    const TCHAR *p = _tcsrchr(find->cFileName, TEXT('.'));
+    return p && p != find->cFileName && _tcsicmp(p + 1, ext) == 0;
 }
 
 /*
  * Modify the extension on a filename.
  */
 static bool
-modext(LPTSTR dest, int size, LPCTSTR src, LPCTSTR newext)
+modext(LPTSTR dest, size_t size, LPCTSTR src, LPCTSTR newext)
 {
-    int i;
+    size_t i;
 
     if (size > 0 && (_tcslen(src) + 1) <= size)
     {
         _tcscpy(dest, src);
         dest [size - 1] = TEXT('\0');
         i = _tcslen(dest);
-        while (--i >= 0)
+        while (i-- > 0)
         {
             if (dest[i] == TEXT('\\'))
             {
diff --git a/src/openvpnserv/common.c b/src/openvpnserv/common.c
index eb718d4..6e6deae 100644
--- a/src/openvpnserv/common.c
+++ b/src/openvpnserv/common.c
@@ -31,7 +31,7 @@
  * These are necessary due to certain buggy implementations of (v)snprintf,
  * that don't guarantee null termination for size > 0.
  */
-int
+BOOL
 openvpn_vsntprintf(LPTSTR str, size_t size, LPCTSTR format, va_list arglist)
 {
     int len = -1;
@@ -40,20 +40,36 @@
         len = _vsntprintf(str, size, format, arglist);
         str[size - 1] = 0;
     }
-    return (len >= 0 && len < size);
+    return (len >= 0 && (size_t)len < size);
 }
-int
+
+BOOL
 openvpn_sntprintf(LPTSTR str, size_t size, LPCTSTR format, ...)
 {
     va_list arglist;
+    BOOL res = FALSE;
+    if (size > 0)
+    {
+        va_start(arglist, format);
+        res = openvpn_vsntprintf(str, size, format, arglist);
+        va_end(arglist);
+    }
+    return res;
+}
+
+BOOL
+openvpn_swprintf(wchar_t *const str, const size_t size, const wchar_t *const format, ...)
+{
+    va_list arglist;
     int len = -1;
     if (size > 0)
     {
         va_start(arglist, format);
-        len = openvpn_vsntprintf(str, size, format, arglist);
+        len = vswprintf(str, size, format, arglist);
         va_end(arglist);
+        str[size - 1] = L'\0';
     }
-    return len;
+    return (len >= 0 && len < size);
 }
 
 static DWORD
@@ -65,7 +81,7 @@
     if (status == ERROR_FILE_NOT_FOUND && default_value)
     {
         size_t len = size/sizeof(data[0]);
-        if (openvpn_sntprintf(data, len, default_value) > 0)
+        if (openvpn_sntprintf(data, len, default_value))
         {
             status = ERROR_SUCCESS;
         }
@@ -212,12 +228,14 @@
 LPCTSTR
 GetLastErrorText()
 {
+    DWORD error;
     static TCHAR buf[256];
     DWORD len;
     LPTSTR tmp = NULL;
 
+    error = GetLastError();
     len = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
-                        NULL, GetLastError(), LANG_NEUTRAL, (LPTSTR)&tmp, 0, NULL);
+                        NULL, error, LANG_NEUTRAL, (LPTSTR)&tmp, 0, NULL);
 
     if (len == 0 || (long) _countof(buf) < (long) len + 14)
     {
@@ -226,7 +244,7 @@
     else
     {
         tmp[_tcslen(tmp) - 2] = TEXT('\0'); /* remove CR/LF characters */
-        openvpn_sntprintf(buf, _countof(buf), TEXT("%s (0x%x)"), tmp, GetLastError());
+        openvpn_sntprintf(buf, _countof(buf), TEXT("%s (0x%x)"), tmp, error);
     }
 
     if (tmp)
diff --git a/src/openvpnserv/interactive.c b/src/openvpnserv/interactive.c
index aecbd84..5d5cbfe 100644
--- a/src/openvpnserv/interactive.c
+++ b/src/openvpnserv/interactive.c
@@ -24,7 +24,6 @@
 
 #include "service.h"
 
-#include <winsock2.h>
 #include <ws2tcpip.h>
 #include <iphlpapi.h>
 #include <userenv.h>
@@ -44,13 +43,14 @@
 #include "openvpn-msg.h"
 #include "validate.h"
 #include "block_dns.h"
+#include "ring_buffer.h"
 
 #define IO_TIMEOUT  2000 /*ms*/
 
-#define ERROR_OPENVPN_STARTUP  0x20000000
-#define ERROR_STARTUP_DATA     0x20000001
-#define ERROR_MESSAGE_DATA     0x20000002
-#define ERROR_MESSAGE_TYPE     0x20000003
+#define ERROR_OPENVPN_STARTUP        0x20000000
+#define ERROR_STARTUP_DATA           0x20000001
+#define ERROR_MESSAGE_DATA           0x20000002
+#define ERROR_MESSAGE_TYPE           0x20000003
 
 static SERVICE_STATUS_HANDLE service;
 static SERVICE_STATUS status = { .dwServiceType = SERVICE_WIN32_SHARE_PROCESS };
@@ -59,6 +59,7 @@
 static HANDLE rdns_semaphore = NULL;
 #define RDNS_TIMEOUT 600  /* seconds to wait for the semaphore */
 
+#define TUN_IOCTL_REGISTER_RINGS CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)
 
 openvpn_service_t interactive_service = {
     interactive,
@@ -90,6 +91,7 @@
     block_dns,
     undo_dns4,
     undo_dns6,
+    undo_domain,
     _undo_type_max
 } undo_type_t;
 typedef list_item_t *undo_lists_t[_undo_type_max];
@@ -101,6 +103,14 @@
     int metric_v6;
 } block_dns_data_t;
 
+typedef struct {
+    HANDLE send_ring_handle;
+    HANDLE receive_ring_handle;
+    HANDLE send_tail_moved;
+    HANDLE receive_tail_moved;
+    HANDLE device;
+} ring_buffer_handles_t;
+
 
 static DWORD
 AddListItem(list_item_t **pfirst, LPVOID data)
@@ -155,6 +165,26 @@
     return INVALID_HANDLE_VALUE;
 }
 
+static HANDLE
+OvpnUnmapViewOfFile(LPHANDLE handle)
+{
+    if (handle && *handle && *handle != INVALID_HANDLE_VALUE)
+    {
+        UnmapViewOfFile(*handle);
+        *handle = INVALID_HANDLE_VALUE;
+    }
+    return INVALID_HANDLE_VALUE;
+}
+
+static void
+CloseRingBufferHandles(ring_buffer_handles_t *ring_buffer_handles)
+{
+    CloseHandleEx(&ring_buffer_handles->device);
+    CloseHandleEx(&ring_buffer_handles->receive_tail_moved);
+    CloseHandleEx(&ring_buffer_handles->send_tail_moved);
+    OvpnUnmapViewOfFile(&ring_buffer_handles->send_ring_handle);
+    OvpnUnmapViewOfFile(&ring_buffer_handles->receive_ring_handle);
+}
 
 static HANDLE
 InitOverlapped(LPOVERLAPPED overlapped)
@@ -188,7 +218,7 @@
 static DWORD
 AsyncPipeOp(async_op_t op, HANDLE pipe, LPVOID buffer, DWORD size, DWORD count, LPHANDLE events)
 {
-    int i;
+    DWORD i;
     BOOL success;
     HANDLE io_event;
     DWORD res, bytes = 0;
@@ -277,10 +307,9 @@
      * Same format as error messages (3 line string) with error = 0 in
      * 0x%08x format, PID on line 2 and a description "Process ID" on line 3
      */
-    swprintf(buf, _countof(buf), L"0x%08x\n0x%08x\n%s", 0, pid, msg);
-    buf[_countof(buf) - 1] = '\0';
+    openvpn_swprintf(buf, _countof(buf), L"0x%08x\n0x%08x\n%s", 0, pid, msg);
 
-    WritePipeAsync(pipe, buf, wcslen(buf) * 2, count, events);
+    WritePipeAsync(pipe, buf, (DWORD)(wcslen(buf) * 2), count, events);
 }
 
 static VOID
@@ -308,7 +337,7 @@
                                 L"0x%1!08x!\n%2!s!\n%3!s!", 0, 0,
                                 (LPWSTR) &result, 0, (va_list *) args);
 
-    WritePipeAsync(pipe, result, wcslen(result) * 2, count, events);
+    WritePipeAsync(pipe, result, (DWORD)(wcslen(result) * 2), count, events);
 #ifdef UNICODE
     MsgToEventLog(MSG_FLAGS_ERROR, result);
 #else
@@ -332,31 +361,6 @@
     ReturnError(pipe, GetLastError(), func, 1, &exit_event);
 }
 
-
-static VOID
-ReturnOpenvpnOutput(HANDLE pipe, HANDLE ovpn_output, DWORD count, LPHANDLE events)
-{
-    WCHAR *wide_output = NULL;
-    CHAR output[512];
-    DWORD size;
-
-    ReadFile(ovpn_output, output, sizeof(output), &size, NULL);
-    if (size == 0)
-    {
-        return;
-    }
-
-    wide_output = malloc((size) * sizeof(WCHAR));
-    if (wide_output)
-    {
-        MultiByteToWideChar(CP_UTF8, 0, output, size, wide_output, size);
-        wide_output[size - 1] = 0;
-    }
-
-    ReturnError(pipe, ERROR_OPENVPN_STARTUP, wide_output, count, events);
-    free(wide_output);
-}
-
 /*
  * Validate options against a white list. Also check the config_file is
  * inside the config_dir. The white list is defined in validate.c
@@ -381,10 +385,9 @@
 
     if (!argv)
     {
-        swprintf(errmsg, capacity,
-	         L"Cannot validate options: CommandLineToArgvW failed with error = 0x%08x",
-	         GetLastError());
-        errmsg[capacity-1] = L'\0';
+        openvpn_swprintf(errmsg, capacity,
+	                 L"Cannot validate options: CommandLineToArgvW failed with error = 0x%08x",
+	                 GetLastError());
         goto out;
     }
 
@@ -404,9 +407,8 @@
 
         if (!CheckOption(workdir, 2, argv_tmp, &settings))
         {
-            swprintf(errmsg, capacity, msg1, argv[0], workdir,
-                     settings.ovpn_admin_group);
-            errmsg[capacity-1] = L'\0';
+            openvpn_swprintf(errmsg, capacity, msg1, argv[0], workdir,
+                             settings.ovpn_admin_group);
         }
         goto out;
     }
@@ -422,15 +424,14 @@
         {
             if (wcscmp(L"--config", argv[i]) == 0 && argc-i > 1)
             {
-                swprintf(errmsg, capacity, msg1, argv[i+1], workdir,
-                         settings.ovpn_admin_group);
+                openvpn_swprintf(errmsg, capacity, msg1, argv[i+1], workdir,
+                                 settings.ovpn_admin_group);
             }
             else
             {
-                swprintf(errmsg, capacity, msg2, argv[i],
-                         settings.ovpn_admin_group);
+                openvpn_swprintf(errmsg, capacity, msg2, argv[i],
+                                 settings.ovpn_admin_group);
             }
-            errmsg[capacity-1] = L'\0';
             goto out;
         }
     }
@@ -449,9 +450,9 @@
 static BOOL
 GetStartupData(HANDLE pipe, STARTUP_DATA *sud)
 {
-    size_t len;
+    size_t size, len;
     WCHAR *data = NULL;
-    DWORD size, bytes, read;
+    DWORD bytes, read;
 
     bytes = PeekNamedPipeAsync(pipe, 1, &exit_event);
     if (bytes == 0)
@@ -516,7 +517,7 @@
     return TRUE;
 
 err:
-    sud->directory = NULL;		/* caller must not free() */
+    sud->directory = NULL;              /* caller must not free() */
     free(data);
     return FALSE;
 }
@@ -564,6 +565,24 @@
     return status;
 }
 
+static DWORD
+ConvertInterfaceNameToIndex(const wchar_t *ifname, NET_IFINDEX *index)
+{
+   NET_LUID luid;
+   DWORD err;
+
+   err = ConvertInterfaceAliasToLuid(ifname, &luid);
+   if (err == ERROR_SUCCESS)
+   {
+       err = ConvertInterfaceLuidToIndex(&luid, index);
+   }
+   if (err != ERROR_SUCCESS)
+   {
+       MsgToEventLog(M_ERR, L"Failed to find interface index for <%s>", ifname);
+   }
+   return err;
+}
+
 static BOOL
 CmpAddress(LPVOID item, LPVOID address)
 {
@@ -930,7 +949,7 @@
 RegisterDNS(LPVOID unused)
 {
     DWORD err;
-    DWORD i;
+    size_t i;
     DWORD timeout = RDNS_TIMEOUT * 1000; /* in milliseconds */
 
     /* path of ipconfig command */
@@ -945,17 +964,15 @@
         { ipcfg, L"ipconfig /flushdns",    timeout },
         { ipcfg, L"ipconfig /registerdns", timeout },
     };
-    int ncmds = sizeof(cmds) / sizeof(cmds[0]);
 
     HANDLE wait_handles[2] = {rdns_semaphore, exit_event};
 
-    swprintf(ipcfg, _countof(ipcfg), L"%s\\%s", get_win_sys_path(), L"ipconfig.exe");
-    ipcfg[_countof(ipcfg) - 1] = L'\0';
+    openvpn_swprintf(ipcfg, MAX_PATH, L"%s\\%s", get_win_sys_path(), L"ipconfig.exe");
 
     if (WaitForMultipleObjects(2, wait_handles, FALSE, timeout) == WAIT_OBJECT_0)
     {
         /* Semaphore locked */
-        for (i = 0; i < ncmds; ++i)
+        for (i = 0; i < _countof(cmds); ++i)
         {
             ExecCommand(cmds[i].argv0, cmds[i].cmdline, cmds[i].timeout);
         }
@@ -1038,7 +1055,7 @@
     const wchar_t *fmt = L"netsh interface %s %s dns \"%s\" %s";
 
     /* max cmdline length in wchars -- include room for worst case and some */
-    int ncmdline = wcslen(fmt) + wcslen(if_name) + wcslen(addr) + 32 + 1;
+    size_t ncmdline = wcslen(fmt) + wcslen(if_name) + wcslen(addr) + 32 + 1;
     cmdline = malloc(ncmdline*sizeof(wchar_t));
     if (!cmdline)
     {
@@ -1059,6 +1076,53 @@
     return err;
 }
 
+/**
+ * Run command: wmic nicconfig (InterfaceIndex=$if_index) call $action ($data)
+ * @param  if_index    "index of interface"
+ * @param  action      e.g., "SetDNSDomain"
+ * @param  data        data if required for action
+ *                     - a single word for SetDNSDomain, empty or NULL to delete
+ *                     - comma separated values for a list
+ */
+static DWORD
+wmic_nicconfig_cmd(const wchar_t *action, const NET_IFINDEX if_index,
+                   const wchar_t *data)
+{
+    DWORD err = 0;
+    wchar_t argv0[MAX_PATH];
+    wchar_t *cmdline = NULL;
+    int timeout = 10000; /* in msec */
+
+    swprintf(argv0, _countof(argv0), L"%s\\%s", get_win_sys_path(), L"wbem\\wmic.exe");
+    argv0[_countof(argv0) - 1] = L'\0';
+
+    const wchar_t *fmt;
+    /* comma separated list must be enclosed in parenthesis */
+    if (data && wcschr(data, L','))
+    {
+       fmt = L"wmic nicconfig where (InterfaceIndex=%ld) call %s (%s)";
+    }
+    else
+    {
+       fmt = L"wmic nicconfig where (InterfaceIndex=%ld) call %s \"%s\"";
+    }
+
+    size_t ncmdline = wcslen(fmt) + 20 + wcslen(action) /* max 20 for ifindex */
+                    + (data ? wcslen(data) + 1 : 1);
+    cmdline = malloc(ncmdline*sizeof(wchar_t));
+    if (!cmdline)
+    {
+        return ERROR_OUTOFMEMORY;
+    }
+
+    openvpn_sntprintf(cmdline, ncmdline, fmt, if_index, action,
+                      data? data : L"");
+    err = ExecCommand(argv0, cmdline, timeout);
+
+    free(cmdline);
+    return err;
+}
+
 /* Delete all IPv4 or IPv6 dns servers for an interface */
 static DWORD
 DeleteDNS(short family, wchar_t *if_name)
@@ -1081,6 +1145,54 @@
     return (wcscmp(item, str) == 0) ? TRUE : FALSE;
 }
 
+/**
+ * Set interface specific DNS domain suffix
+ * @param  if_name    name of the the interface
+ * @param  domain     a single domain name
+ * @param  lists      pointer to the undo lists. If NULL
+ *                    undo lists are not altered.
+ * Will delete the currently set value if domain is empty.
+ */
+static DWORD
+SetDNSDomain(const wchar_t *if_name, const char *domain, undo_lists_t *lists)
+{
+   NET_IFINDEX if_index;
+
+   DWORD err  = ConvertInterfaceNameToIndex(if_name, &if_index);
+   if (err != ERROR_SUCCESS)
+   {
+       return err;
+   }
+
+   wchar_t *wdomain = utf8to16(domain); /* utf8 to wide-char */
+   if (!wdomain)
+   {
+       return ERROR_OUTOFMEMORY;
+   }
+
+   /* free undo list if previously set */
+   if (lists)
+   {
+       free(RemoveListItem(&(*lists)[undo_domain], CmpWString, (void *)if_name));
+   }
+
+   err = wmic_nicconfig_cmd(L"SetDNSDomain", if_index, wdomain);
+
+   /* Add to undo list if domain is non-empty */
+   if (err == 0 && wdomain[0] && lists)
+   {
+        wchar_t *tmp_name = wcsdup(if_name);
+        if (!tmp_name || AddListItem(&(*lists)[undo_domain], tmp_name))
+        {
+            free(tmp_name);
+            err = ERROR_OUTOFMEMORY;
+        }
+   }
+
+   free(wdomain);
+   return err;
+}
+
 static DWORD
 HandleDNSConfigMessage(const dns_cfg_message_t *msg, undo_lists_t *lists)
 {
@@ -1100,6 +1212,13 @@
         return ERROR_MESSAGE_DATA;
     }
 
+    /* use a non-const reference with limited scope to enforce null-termination of strings from client */
+    {
+        dns_cfg_message_t *msgptr = (dns_cfg_message_t *) msg;
+        msgptr->iface.name[_countof(msg->iface.name)-1] = '\0';
+        msgptr->domains[_countof(msg->domains)-1] = '\0';
+    }
+
     wchar_t *wide_name = utf8to16(msg->iface.name); /* utf8 to wide-char */
     if (!wide_name)
     {
@@ -1119,9 +1238,14 @@
         free(RemoveListItem(&(*lists)[undo_type], CmpWString, wide_name));
     }
 
-    if (msg->header.type == msg_del_dns_cfg) /* job done */
+    if (msg->header.type == msg_del_dns_cfg)
     {
-        goto out;
+        if (msg->domains[0])
+        {
+            /* setting an empty domain removes any previous value */
+            err = SetDNSDomain(wide_name, "", lists);
+        }
+        goto out;  /* job done */
     }
 
     for (int i = 0; i < addr_len; ++i)
@@ -1144,6 +1268,8 @@
          */
     }
 
+    err = 0;
+
     if (msg->addr_len > 0)
     {
         wchar_t *tmp_name = wcsdup(wide_name);
@@ -1156,7 +1282,10 @@
         }
     }
 
-    err = 0;
+    if (msg->domains[0])
+    {
+        err = SetDNSDomain(wide_name, msg->domains, lists);
+    }
 
 out:
     free(wide_name);
@@ -1202,8 +1331,118 @@
     return err;
 }
 
+static DWORD
+OvpnDuplicateHandle(HANDLE ovpn_proc, HANDLE orig_handle, HANDLE* new_handle)
+{
+    DWORD err = ERROR_SUCCESS;
+
+    if (!DuplicateHandle(ovpn_proc, orig_handle, GetCurrentProcess(), new_handle, 0, FALSE, DUPLICATE_SAME_ACCESS))
+    {
+        err = GetLastError();
+        MsgToEventLog(M_SYSERR, TEXT("Could not duplicate handle"));
+        return err;
+    }
+
+    return err;
+}
+
+static DWORD
+DuplicateAndMapRing(HANDLE ovpn_proc, HANDLE orig_handle, HANDLE *new_handle, struct tun_ring **ring)
+{
+    DWORD err = ERROR_SUCCESS;
+
+    err = OvpnDuplicateHandle(ovpn_proc, orig_handle, new_handle);
+    if (err != ERROR_SUCCESS)
+    {
+        return err;
+    }
+    *ring = (struct tun_ring *)MapViewOfFile(*new_handle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(struct tun_ring));
+    if (*ring == NULL)
+    {
+        err = GetLastError();
+        MsgToEventLog(M_SYSERR, TEXT("Could not map shared memory"));
+        return err;
+    }
+
+    return err;
+}
+
+static DWORD
+HandleRegisterRingBuffers(const register_ring_buffers_message_t *rrb, HANDLE ovpn_proc,
+                          ring_buffer_handles_t *ring_buffer_handles)
+{
+    DWORD err = 0;
+    struct tun_ring *send_ring;
+    struct tun_ring *receive_ring;
+
+    CloseRingBufferHandles(ring_buffer_handles);
+
+    err = OvpnDuplicateHandle(ovpn_proc, rrb->device, &ring_buffer_handles->device);
+    if (err != ERROR_SUCCESS)
+    {
+        return err;
+    }
+
+    err = DuplicateAndMapRing(ovpn_proc, rrb->send_ring_handle, &ring_buffer_handles->send_ring_handle, &send_ring);
+    if (err != ERROR_SUCCESS)
+    {
+        return err;
+    }
+
+    err = DuplicateAndMapRing(ovpn_proc, rrb->receive_ring_handle, &ring_buffer_handles->receive_ring_handle, &receive_ring);
+    if (err != ERROR_SUCCESS)
+    {
+        return err;
+    }
+
+    err = OvpnDuplicateHandle(ovpn_proc, rrb->send_tail_moved, &ring_buffer_handles->send_tail_moved);
+    if (err != ERROR_SUCCESS)
+    {
+        return err;
+    }
+
+    err = OvpnDuplicateHandle(ovpn_proc, rrb->receive_tail_moved, &ring_buffer_handles->receive_tail_moved);
+    if (err != ERROR_SUCCESS)
+    {
+        return err;
+    }
+
+    if (!register_ring_buffers(ring_buffer_handles->device, send_ring, receive_ring,
+                               ring_buffer_handles->send_tail_moved, ring_buffer_handles->receive_tail_moved))
+    {
+        err = GetLastError();
+        MsgToEventLog(M_SYSERR, TEXT("Could not register ring buffers"));
+    }
+
+    return err;
+}
+
+static DWORD
+HandleMTUMessage(const set_mtu_message_t *mtu)
+{
+    DWORD err = 0;
+    MIB_IPINTERFACE_ROW ipiface;
+    InitializeIpInterfaceEntry(&ipiface);
+    ipiface.Family = mtu->family;
+    ipiface.InterfaceIndex = mtu->iface.index;
+    err = GetIpInterfaceEntry(&ipiface);
+    if (err != NO_ERROR)
+    {
+        return err;
+    }
+    if (mtu->family == AF_INET)
+    {
+        ipiface.SitePrefixLength = 0;
+    }
+    ipiface.NlMtu = mtu->mtu;
+
+    err = SetIpInterfaceEntry(&ipiface);
+    return err;
+}
+
 static VOID
-HandleMessage(HANDLE pipe, DWORD bytes, DWORD count, LPHANDLE events, undo_lists_t *lists)
+HandleMessage(HANDLE pipe, HANDLE ovpn_proc, ring_buffer_handles_t *ring_buffer_handles,
+              DWORD bytes, DWORD count, LPHANDLE events, undo_lists_t *lists)
 {
     DWORD read;
     union {
@@ -1214,6 +1453,8 @@
         block_dns_message_t block_dns;
         dns_cfg_message_t dns;
         enable_dhcp_message_t dhcp;
+        register_ring_buffers_message_t rrb;
+        set_mtu_message_t mtu;
     } msg;
     ack_message_t ack = {
         .header = {
@@ -1281,6 +1522,20 @@
             }
             break;
 
+        case msg_register_ring_buffers:
+            if (msg.header.size == sizeof(msg.rrb))
+            {
+                ack.error_number = HandleRegisterRingBuffers(&msg.rrb, ovpn_proc, ring_buffer_handles);
+            }
+            break;
+
+        case msg_set_mtu:
+            if (msg.header.size == sizeof(msg.mtu))
+            {
+                ack.error_number = HandleMTUMessage(&msg.mtu);
+            }
+            break;
+
         default:
             ack.error_number = ERROR_MESSAGE_TYPE;
             MsgToEventLog(MSG_FLAGS_ERROR, TEXT("Unknown message type %d"), msg.header.type);
@@ -1321,8 +1576,12 @@
                     DeleteDNS(AF_INET6, item->data);
                     break;
 
+                case undo_domain:
+                    SetDNSDomain(item->data, "", NULL);
+                    break;
+
                 case block_dns:
-                    interface_data = (block_dns_data_t*)(item->data);
+                    interface_data = (block_dns_data_t *)(item->data);
                     delete_block_dns_filters(interface_data->engine);
                     if (interface_data->metric_v4 >= 0)
                     {
@@ -1364,6 +1623,7 @@
     WCHAR *cmdline = NULL;
     size_t cmdline_size;
     undo_lists_t undo_lists;
+    ring_buffer_handles_t ring_buffer_handles;
     WCHAR errmsg[512] = L"";
 
     SECURITY_ATTRIBUTES inheritable = {
@@ -1385,6 +1645,7 @@
     ZeroMemory(&startup_info, sizeof(startup_info));
     ZeroMemory(&undo_lists, sizeof(undo_lists));
     ZeroMemory(&proc_info, sizeof(proc_info));
+    ZeroMemory(&ring_buffer_handles, sizeof(ring_buffer_handles));
 
     if (!GetStartupData(pipe, &sud))
     {
@@ -1611,7 +1872,7 @@
     {
         DWORD written;
         WideCharToMultiByte(CP_UTF8, 0, sud.std_input, -1, input, input_size, NULL, NULL);
-        WriteFile(stdin_write, input, strlen(input), &written, NULL);
+        WriteFile(stdin_write, input, (DWORD)strlen(input), &written, NULL);
         free(input);
     }
 
@@ -1623,7 +1884,7 @@
             break;
         }
 
-        HandleMessage(ovpn_pipe, bytes, 1, &exit_event, &undo_lists);
+        HandleMessage(ovpn_pipe, proc_info.hProcess, &ring_buffer_handles, bytes, 1, &exit_event, &undo_lists);
     }
 
     WaitForSingleObject(proc_info.hProcess, IO_TIMEOUT);
@@ -1635,9 +1896,8 @@
     else if (exit_code != 0)
     {
         WCHAR buf[256];
-        swprintf(buf, _countof(buf),
-                 L"OpenVPN exited with error: exit code = %lu", exit_code);
-        buf[_countof(buf) - 1] =  L'\0';
+        openvpn_swprintf(buf, _countof(buf),
+                         L"OpenVPN exited with error: exit code = %lu", exit_code);
         ReturnError(pipe, ERROR_OPENVPN_STARTUP, buf, 1, &exit_event);
     }
     Undo(&undo_lists);
@@ -1651,6 +1911,7 @@
     free(cmdline);
     DestroyEnvironmentBlock(user_env);
     FreeStartupData(&sud);
+    CloseRingBufferHandles(&ring_buffer_handles);
     CloseHandleEx(&proc_info.hProcess);
     CloseHandleEx(&proc_info.hThread);
     CloseHandleEx(&stdin_read);
diff --git a/src/openvpnserv/openvpnserv.vcxproj b/src/openvpnserv/openvpnserv.vcxproj
index 655d80c..a75bbb2 100644
--- a/src/openvpnserv/openvpnserv.vcxproj
+++ b/src/openvpnserv/openvpnserv.vcxproj
@@ -1,103 +1,132 @@
 <?xml version="1.0" encoding="utf-8"?>

-<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

   <ItemGroup Label="ProjectConfigurations">

     <ProjectConfiguration Include="Debug|Win32">

       <Configuration>Debug</Configuration>

       <Platform>Win32</Platform>

     </ProjectConfiguration>

+    <ProjectConfiguration Include="Debug|x64">

+      <Configuration>Debug</Configuration>

+      <Platform>x64</Platform>

+    </ProjectConfiguration>

     <ProjectConfiguration Include="Release|Win32">

       <Configuration>Release</Configuration>

       <Platform>Win32</Platform>

     </ProjectConfiguration>

+    <ProjectConfiguration Include="Release|x64">

+      <Configuration>Release</Configuration>

+      <Platform>x64</Platform>

+    </ProjectConfiguration>

   </ItemGroup>

   <PropertyGroup Label="Globals">

     <ProjectGuid>{9C91EE0B-817D-420A-A1E6-15A5A9D98BAD}</ProjectGuid>

     <RootNamespace>openvpnserv</RootNamespace>

     <Keyword>Win32Proj</Keyword>

+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>

   </PropertyGroup>

   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />

   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">

     <ConfigurationType>Application</ConfigurationType>

-    <CharacterSet>MultiByte</CharacterSet>

+    <CharacterSet>Unicode</CharacterSet>

     <WholeProgramOptimization>true</WholeProgramOptimization>

-    <PlatformToolset>v120</PlatformToolset>

+    <PlatformToolset>v142</PlatformToolset>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">

+    <ConfigurationType>Application</ConfigurationType>

+    <CharacterSet>Unicode</CharacterSet>

+    <WholeProgramOptimization>true</WholeProgramOptimization>

+    <PlatformToolset>v142</PlatformToolset>

   </PropertyGroup>

   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">

     <ConfigurationType>Application</ConfigurationType>

-    <CharacterSet>MultiByte</CharacterSet>

-    <PlatformToolset>v120</PlatformToolset>

+    <CharacterSet>Unicode</CharacterSet>

+    <PlatformToolset>v142</PlatformToolset>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">

+    <ConfigurationType>Application</ConfigurationType>

+    <CharacterSet>Unicode</CharacterSet>

+    <PlatformToolset>v142</PlatformToolset>

   </PropertyGroup>

   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />

   <ImportGroup Label="ExtensionSettings">

   </ImportGroup>

   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">

     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Release.props" />

+  </ImportGroup>

+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Release.props" />

   </ImportGroup>

   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">

     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Debug.props" />

+  </ImportGroup>

+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Debug.props" />

   </ImportGroup>

   <PropertyGroup Label="UserMacros" />

   <PropertyGroup>

     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>

-    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)$(Platform)-Output\$(Configuration)\</OutDir>

-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)\</IntDir>

-    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>

-    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Platform)-Output\$(Configuration)\</OutDir>

-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration)\</IntDir>

-    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>

   </PropertyGroup>

   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">

     <ClCompile>

-      <Optimization>Disabled</Optimization>

-      <AdditionalIncludeDirectories>$(SOURCEBASE);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

-      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;$(CPPFLAGS);%(PreprocessorDefinitions)</PreprocessorDefinitions>

-      <MinimalRebuild>true</MinimalRebuild>

-      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>

-      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>

-      <PrecompiledHeader>

-      </PrecompiledHeader>

-      <WarningLevel>Level3</WarningLevel>

-      <DebugInformationFormat>EditAndContinue</DebugInformationFormat>

+      <AdditionalIncludeDirectories>..\openvpn;..\compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

+      <PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>

     </ClCompile>

-    <ResourceCompile>

-      <AdditionalIncludeDirectories>$(SOURCEBASE);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

-    </ResourceCompile>

+    <ResourceCompile />

     <Link>

-      <GenerateDebugInformation>true</GenerateDebugInformation>

+      <AdditionalDependencies>Userenv.lib;Iphlpapi.lib;ntdll.lib;Fwpuclnt.lib;Netapi32.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>

       <SubSystem>Console</SubSystem>

-      <TargetMachine>MachineX86</TargetMachine>

+    </Link>

+  </ItemDefinitionGroup>

+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">

+    <ClCompile>

+      <AdditionalIncludeDirectories>..\openvpn;..\compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

+      <PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>

+    </ClCompile>

+    <ResourceCompile />

+    <Link>

+      <AdditionalDependencies>legacy_stdio_definitions.lib;Userenv.lib;Iphlpapi.lib;ntdll.lib;Fwpuclnt.lib;Netapi32.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>

+      <SubSystem>Console</SubSystem>

     </Link>

   </ItemDefinitionGroup>

   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">

     <ClCompile>

-      <Optimization>MaxSpeed</Optimization>

-      <IntrinsicFunctions>true</IntrinsicFunctions>

-      <AdditionalIncludeDirectories>$(SOURCEBASE);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

-      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;$(CPPFLAGS);%(PreprocessorDefinitions)</PreprocessorDefinitions>

-      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>

-      <FunctionLevelLinking>true</FunctionLevelLinking>

-      <PrecompiledHeader>

-      </PrecompiledHeader>

-      <WarningLevel>Level3</WarningLevel>

-      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>

+      <AdditionalIncludeDirectories>..\openvpn;..\compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

+      <PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>

     </ClCompile>

-    <ResourceCompile>

-      <AdditionalIncludeDirectories>$(SOURCEBASE);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

-    </ResourceCompile>

+    <ResourceCompile />

     <Link>

-      <GenerateDebugInformation>true</GenerateDebugInformation>

+      <AdditionalDependencies>Userenv.lib;Iphlpapi.lib;ntdll.lib;Fwpuclnt.lib;Netapi32.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>

       <SubSystem>Console</SubSystem>

-      <OptimizeReferences>true</OptimizeReferences>

-      <EnableCOMDATFolding>true</EnableCOMDATFolding>

-      <TargetMachine>MachineX86</TargetMachine>

+    </Link>

+  </ItemDefinitionGroup>

+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">

+    <ClCompile>

+      <AdditionalIncludeDirectories>..\openvpn;..\compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

+      <PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>

+    </ClCompile>

+    <ResourceCompile />

+    <Link>

+      <AdditionalDependencies>legacy_stdio_definitions.lib;Userenv.lib;Iphlpapi.lib;ntdll.lib;Fwpuclnt.lib;Netapi32.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>

+      <SubSystem>Console</SubSystem>

     </Link>

   </ItemDefinitionGroup>

   <ItemGroup>

-    <ClCompile Include="openvpnserv.c" />

+    <ClCompile Include="automatic.c" />

+    <ClCompile Include="common.c" />

+    <ClCompile Include="interactive.c" />

     <ClCompile Include="service.c" />

+    <ClCompile Include="validate.c" />

+    <ClCompile Include="..\openvpn\block_dns.c" />

   </ItemGroup>

   <ItemGroup>

+    <ClInclude Include="..\openvpn\ring_buffer.h" />

     <ClInclude Include="service.h" />

+    <ClInclude Include="validate.h" />

+    <ClInclude Include="..\openvpn\block_dns.h" />

   </ItemGroup>

   <ItemGroup>

     <ResourceCompile Include="openvpnserv_resources.rc" />

@@ -111,4 +140,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

   <ImportGroup Label="ExtensionTargets">

   </ImportGroup>

-</Project>

+</Project>
\ No newline at end of file
diff --git a/src/openvpnserv/openvpnserv.vcxproj.filters b/src/openvpnserv/openvpnserv.vcxproj.filters
index a6f8ecc..5f7d906 100644
--- a/src/openvpnserv/openvpnserv.vcxproj.filters
+++ b/src/openvpnserv/openvpnserv.vcxproj.filters
@@ -15,10 +15,22 @@
     </Filter>

   </ItemGroup>

   <ItemGroup>

-    <ClCompile Include="openvpnserv.c">

+    <ClCompile Include="service.c">

       <Filter>Source Files</Filter>

     </ClCompile>

-    <ClCompile Include="service.c">

+    <ClCompile Include="automatic.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="common.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="interactive.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="validate.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="..\openvpn\block_dns.c">

       <Filter>Source Files</Filter>

     </ClCompile>

   </ItemGroup>

@@ -26,6 +38,15 @@
     <ClInclude Include="service.h">

       <Filter>Header Files</Filter>

     </ClInclude>

+    <ClInclude Include="validate.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+    <ClInclude Include="..\openvpn\block_dns.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+    <ClInclude Include="..\openvpn\ring_buffer.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

   </ItemGroup>

   <ItemGroup>

     <ResourceCompile Include="openvpnserv_resources.rc">

diff --git a/src/openvpnserv/service.c b/src/openvpnserv/service.c
index 7157bea..8efe25f 100644
--- a/src/openvpnserv/service.c
+++ b/src/openvpnserv/service.c
@@ -270,8 +270,8 @@
             else if (argc > i + 2 && _tcsicmp(TEXT("instance"), argv[i] + 1) == 0)
             {
                 dispatchTable = _tcsicmp(TEXT("interactive"), argv[i + 1]) != 0 ?
-                    dispatchTable_automatic :
-                    dispatchTable_interactive;
+                                dispatchTable_automatic :
+                                dispatchTable_interactive;
 
                 service_instance = argv[i + 2];
                 i += 2;
diff --git a/src/openvpnserv/service.h b/src/openvpnserv/service.h
index 23b105f..f5afe2f 100644
--- a/src/openvpnserv/service.h
+++ b/src/openvpnserv/service.h
@@ -30,6 +30,7 @@
 #include "config-msvc.h"
 #endif
 
+#include <winsock2.h>
 #include <windows.h>
 #include <stdlib.h>
 #include <tchar.h>
@@ -76,14 +77,18 @@
 extern LPCTSTR service_instance;
 
 VOID WINAPI ServiceStartAutomaticOwn(DWORD argc, LPTSTR *argv);
+
 VOID WINAPI ServiceStartAutomatic(DWORD argc, LPTSTR *argv);
 
 VOID WINAPI ServiceStartInteractiveOwn(DWORD argc, LPTSTR *argv);
+
 VOID WINAPI ServiceStartInteractive(DWORD argc, LPTSTR *argv);
 
-int openvpn_vsntprintf(LPTSTR str, size_t size, LPCTSTR format, va_list arglist);
+BOOL openvpn_vsntprintf(LPTSTR str, size_t size, LPCTSTR format, va_list arglist);
 
-int openvpn_sntprintf(LPTSTR str, size_t size, LPCTSTR format, ...);
+BOOL openvpn_sntprintf(LPTSTR str, size_t size, LPCTSTR format, ...);
+
+BOOL openvpn_swprintf(wchar_t *const str, const size_t size, const wchar_t *const format, ...);
 
 DWORD GetOpenvpnSettings(settings_t *s);
 
diff --git a/src/openvpnserv/validate.c b/src/openvpnserv/validate.c
index d35938c..9b01770 100644
--- a/src/openvpnserv/validate.c
+++ b/src/openvpnserv/validate.c
@@ -51,6 +51,7 @@
 };
 
 static BOOL IsUserInGroup(PSID sid, const PTOKEN_GROUPS groups, const WCHAR *group_name);
+
 static PTOKEN_GROUPS GetTokenGroups(const HANDLE token);
 
 /*
@@ -63,12 +64,14 @@
     WCHAR tmp[MAX_PATH];
     const WCHAR *config_file = NULL;
     const WCHAR *config_dir = NULL;
+#ifndef UNICODE
+    WCHAR widepath[MAX_PATH];
+#endif
 
     /* convert fname to full path */
     if (PathIsRelativeW(fname) )
     {
-        swprintf(tmp, _countof(tmp), L"%s\\%s", workdir, fname);
-        tmp[_countof(tmp)-1] = L'\0';
+        openvpn_swprintf(tmp, _countof(tmp), L"%s\\%s", workdir, fname);
         config_file = tmp;
     }
     else
@@ -300,12 +303,12 @@
             break;
         }
         /* If a match is already found, ret == TRUE and the loop is skipped */
-        for (int i = 0; i < nread && !ret; ++i)
+        for (DWORD i = 0; i < nread && !ret; ++i)
         {
             ret = EqualSid(members[i].lgrmi0_sid, sid);
         }
         NetApiBufferFree(members);
-    /* MSDN says the lookup should always iterate until err != ERROR_MORE_DATA */
+        /* MSDN says the lookup should always iterate until err != ERROR_MORE_DATA */
     } while (err == ERROR_MORE_DATA && nloop++ < 100);
 
     if (err != NERR_Success && err != NERR_GroupNotFound)
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index b1358d9..f346178 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -12,4 +12,4 @@
 MAINTAINERCLEANFILES = \
 	$(srcdir)/Makefile.in
 
-SUBDIRS = down-root
+SUBDIRS = auth-pam down-root
diff --git a/src/tapctl/Makefile.am b/src/tapctl/Makefile.am
new file mode 100644
index 0000000..583a45f
--- /dev/null
+++ b/src/tapctl/Makefile.am
@@ -0,0 +1,51 @@
+#
+#  tapctl -- Utility to manipulate TUN/TAP interfaces on Windows
+#
+#  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
+#  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
+#
+#  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; if not, write to the Free Software Foundation, Inc.,
+#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+include $(top_srcdir)/build/ltrc.inc
+
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+
+EXTRA_DIST = \
+	tapctl.vcxproj \
+	tapctl.vcxproj.filters \
+	tapctl.props \
+	tapctl.exe.manifest
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/include -I$(top_srcdir)/src/compat
+
+AM_CFLAGS = \
+	$(TAP_CFLAGS)
+
+if WIN32
+sbin_PROGRAMS = tapctl
+tapctl_CFLAGS = \
+	-municode -D_UNICODE \
+	-UNTDDI_VERSION -U_WIN32_WINNT \
+	-D_WIN32_WINNT=_WIN32_WINNT_VISTA
+tapctl_LDADD = -ladvapi32 -lole32 -lsetupapi
+endif
+
+tapctl_SOURCES = \
+	basic.h \
+	error.c error.h \
+	main.c \
+	tap.c tap.h \
+	tapctl_resources.rc
diff --git a/src/tapctl/basic.h b/src/tapctl/basic.h
new file mode 100644
index 0000000..a0a8851
--- /dev/null
+++ b/src/tapctl/basic.h
@@ -0,0 +1,66 @@
+/*
+ *  basic -- Basic macros
+ *           https://community.openvpn.net/openvpn/wiki/Tapctl
+ *
+ *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BASIC_H
+#define BASIC_H
+
+#ifdef _UNICODE
+#define PRIsLPTSTR      "ls"
+#define PRIsLPOLESTR    "ls"
+#else
+#define PRIsLPTSTR      "s"
+#define PRIsLPOLESTR    "ls"
+#endif
+#define PRIXGUID        "{%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX}"
+#define PRIGUID_PARAM(g) \
+    (g).Data1, (g).Data2, (g).Data3, (g).Data4[0], (g).Data4[1], (g).Data4[2], (g).Data4[3], (g).Data4[4], (g).Data4[5], (g).Data4[6], (g).Data4[7]
+#define PRIGUID_PARAM_REF(g) \
+    &(g).Data1, &(g).Data2, &(g).Data3, &(g).Data4[0], &(g).Data4[1], &(g).Data4[2], &(g).Data4[3], &(g).Data4[4], &(g).Data4[5], &(g).Data4[6], &(g).Data4[7]
+
+#define __L(q)          L ## q
+#define _L(q)           __L(q)
+
+#ifndef _In_
+#define _In_
+#endif
+#ifndef _In_opt_
+#define _In_opt_
+#endif
+#ifndef _In_z_
+#define _In_z_
+#endif
+#ifndef _Inout_
+#define _Inout_
+#endif
+#ifndef _Inout_opt_
+#define _Inout_opt_
+#endif
+#ifndef _Out_
+#define _Out_
+#endif
+#ifndef _Out_opt_
+#define _Out_opt_
+#endif
+#ifndef _Out_z_cap_
+#define _Out_z_cap_(n)
+#endif
+
+#endif /* ifndef BASIC_H */
diff --git a/src/tapctl/error.c b/src/tapctl/error.c
new file mode 100644
index 0000000..d1f77d2
--- /dev/null
+++ b/src/tapctl/error.c
@@ -0,0 +1,36 @@
+/*
+ *  error -- OpenVPN compatible error reporting API
+ *           https://community.openvpn.net/openvpn/wiki/Tapctl
+ *
+ *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "error.h"
+
+
+/* Globals */
+unsigned int x_debug_level; /* GLOBAL */
+
+
+void
+x_msg(const unsigned int flags, const char *format, ...)
+{
+    va_list arglist;
+    va_start(arglist, format);
+    x_msg_va(flags, format, arglist);
+    va_end(arglist);
+}
diff --git a/src/tapctl/error.h b/src/tapctl/error.h
new file mode 100644
index 0000000..924cbbe
--- /dev/null
+++ b/src/tapctl/error.h
@@ -0,0 +1,97 @@
+/*
+ *  error -- OpenVPN compatible error reporting API
+ *           https://community.openvpn.net/openvpn/wiki/Tapctl
+ *
+ *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef ERROR_H
+#define ERROR_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+/*
+ * These globals should not be accessed directly,
+ * but rather through macros or inline functions defined below.
+ */
+extern unsigned int x_debug_level;
+extern int x_msg_line_num;
+
+/* msg() flags */
+
+#define M_DEBUG_LEVEL     (0x0F)         /* debug level mask */
+
+#define M_FATAL           (1<<4)         /* exit program */
+#define M_NONFATAL        (1<<5)         /* non-fatal error */
+#define M_WARN            (1<<6)         /* call syslog with LOG_WARNING */
+#define M_DEBUG           (1<<7)
+
+#define M_ERRNO           (1<<8)         /* show errno description */
+
+#define M_NOMUTE          (1<<11)        /* don't do mute processing */
+#define M_NOPREFIX        (1<<12)        /* don't show date/time prefix */
+#define M_USAGE_SMALL     (1<<13)        /* fatal options error, call usage_small */
+#define M_MSG_VIRT_OUT    (1<<14)        /* output message through msg_status_output callback */
+#define M_OPTERR          (1<<15)        /* print "Options error:" prefix */
+#define M_NOLF            (1<<16)        /* don't print new line */
+#define M_NOIPREFIX       (1<<17)        /* don't print instance prefix */
+
+/* flag combinations which are frequently used */
+#define M_ERR     (M_FATAL | M_ERRNO)
+#define M_USAGE   (M_USAGE_SMALL | M_NOPREFIX | M_OPTERR)
+#define M_CLIENT  (M_MSG_VIRT_OUT | M_NOMUTE | M_NOIPREFIX)
+
+
+/** Check muting filter */
+bool dont_mute(unsigned int flags);
+
+/* Macro to ensure (and teach static analysis tools) we exit on fatal errors */
+#ifdef _MSC_VER
+#pragma warning(disable: 4127) /* EXIT_FATAL(flags) macro raises "warning C4127: conditional expression is constant" on each non M_FATAL invocation. */
+#endif
+#define EXIT_FATAL(flags) do { if ((flags) & M_FATAL) {_exit(1);}} while (false)
+
+#define HAVE_VARARG_MACROS
+#define msg(flags, ...) do { if (msg_test(flags)) {x_msg((flags), __VA_ARGS__);} EXIT_FATAL(flags); } while (false)
+#ifdef ENABLE_DEBUG
+#define dmsg(flags, ...) do { if (msg_test(flags)) {x_msg((flags), __VA_ARGS__);} EXIT_FATAL(flags); } while (false)
+#else
+#define dmsg(flags, ...)
+#endif
+
+void x_msg(const unsigned int flags, const char *format, ...);     /* should be called via msg above */
+
+void x_msg_va(const unsigned int flags, const char *format, va_list arglist);
+
+/* Inline functions */
+
+static inline bool
+check_debug_level(unsigned int level)
+{
+    return (level & M_DEBUG_LEVEL) <= x_debug_level;
+}
+
+/** Return true if flags represent and enabled, not muted log level */
+static inline bool
+msg_test(unsigned int flags)
+{
+    return check_debug_level(flags) && dont_mute(flags);
+}
+
+#endif /* ifndef ERROR_H */
diff --git a/src/tapctl/main.c b/src/tapctl/main.c
new file mode 100644
index 0000000..d5bc729
--- /dev/null
+++ b/src/tapctl/main.c
@@ -0,0 +1,445 @@
+/*
+ *  tapctl -- Utility to manipulate TUN/TAP adapters on Windows
+ *            https://community.openvpn.net/openvpn/wiki/Tapctl
+ *
+ *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
+ *  Copyright (C) 2008-2013 David Sommerseth <dazo@users.sourceforge.net>
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#elif defined(_MSC_VER)
+#include <config-msvc.h>
+#endif
+#ifdef HAVE_CONFIG_VERSION_H
+#include <config-version.h>
+#endif
+
+#include "tap.h"
+#include "error.h"
+
+#include <objbase.h>
+#include <setupapi.h>
+#include <stdio.h>
+#include <tchar.h>
+
+#ifdef _MSC_VER
+#pragma comment(lib, "ole32.lib")
+#pragma comment(lib, "setupapi.lib")
+#endif
+
+
+const TCHAR title_string[] =
+    TEXT(PACKAGE_NAME) TEXT(" ") TEXT(PACKAGE_VERSION)
+    TEXT(" built on ") TEXT(__DATE__)
+;
+
+static const TCHAR usage_message[] =
+    TEXT("%s\n")
+    TEXT("\n")
+    TEXT("Usage:\n")
+    TEXT("\n")
+    TEXT("tapctl <command> [<command specific options>]\n")
+    TEXT("\n")
+    TEXT("Commands:\n")
+    TEXT("\n")
+    TEXT("create     Create a new TUN/TAP adapter\n")
+    TEXT("list       List TUN/TAP adapters\n")
+    TEXT("delete     Delete specified network adapter\n")
+    TEXT("help       Display this text\n")
+    TEXT("\n")
+    TEXT("Hint: Use \"tapctl help <command>\" to display help for particular command.\n")
+;
+
+static const TCHAR usage_message_create[] =
+    TEXT("%s\n")
+    TEXT("\n")
+    TEXT("Creates a new TUN/TAP adapter\n")
+    TEXT("\n")
+    TEXT("Usage:\n")
+    TEXT("\n")
+    TEXT("tapctl create [<options>]\n")
+    TEXT("\n")
+    TEXT("Options:\n")
+    TEXT("\n")
+    TEXT("--name <name>  Set TUN/TAP adapter name. Should the adapter with given name    \n")
+    TEXT("               already exist, an error is returned. If this option is not      \n")
+    TEXT("               specified, a default adapter name is chosen by Windows.         \n")
+    TEXT("               Note: This name can also be specified as OpenVPN's --dev-node   \n")
+    TEXT("               option.                                                         \n")
+    TEXT("--hwid <hwid>  Adapter hardware ID. Default value is root\\tap0901, which       \n")
+    TEXT("               describes tap-windows6 driver. To work with wintun driver,      \n")
+    TEXT("               specify 'wintun'.                                               \n")
+    TEXT("\n")
+    TEXT("Output:\n")
+    TEXT("\n")
+    TEXT("This command prints newly created TUN/TAP adapter's GUID to stdout.            \n")
+;
+
+static const TCHAR usage_message_list[] =
+    TEXT("%s\n")
+    TEXT("\n")
+    TEXT("Lists TUN/TAP adapters\n")
+    TEXT("\n")
+    TEXT("Usage:\n")
+    TEXT("\n")
+    TEXT("tapctl list\n")
+    TEXT("\n")
+    TEXT("Options:\n")
+    TEXT("\n")
+    TEXT("--hwid <hwid>  Adapter hardware ID. By default, root\\tap0901, tap0901 and      \n")
+    TEXT("               wintun adapters are listed. Use this switch to limit the list.  \n")
+    TEXT("\n")
+    TEXT("Output:\n")
+    TEXT("\n")
+    TEXT("This command prints all TUN/TAP adapters to stdout.                            \n")
+;
+
+static const TCHAR usage_message_delete[] =
+    TEXT("%s\n")
+    TEXT("\n")
+    TEXT("Deletes the specified network adapter\n")
+    TEXT("\n")
+    TEXT("Usage:\n")
+    TEXT("\n")
+    TEXT("tapctl delete <adapter GUID | adapter name>\n")
+;
+
+
+/**
+ * Print the help message.
+ */
+static void
+usage(void)
+{
+    _ftprintf(stderr,
+              usage_message,
+              title_string);
+}
+
+
+/**
+ * Program entry point
+ */
+int __cdecl
+_tmain(int argc, LPCTSTR argv[])
+{
+    int iResult;
+    BOOL bRebootRequired = FALSE;
+
+    /* Ask SetupAPI to keep quiet. */
+    SetupSetNonInteractiveMode(TRUE);
+
+    if (argc < 2)
+    {
+        usage();
+        return 1;
+    }
+    else if (_tcsicmp(argv[1], TEXT("help")) == 0)
+    {
+        /* Output help. */
+        if (argc < 3)
+        {
+            usage();
+        }
+        else if (_tcsicmp(argv[2], TEXT("create")) == 0)
+        {
+            _ftprintf(stderr, usage_message_create, title_string);
+        }
+        else if (_tcsicmp(argv[2], TEXT("list")) == 0)
+        {
+            _ftprintf(stderr, usage_message_list, title_string);
+        }
+        else if (_tcsicmp(argv[2], TEXT("delete")) == 0)
+        {
+            _ftprintf(stderr, usage_message_delete, title_string);
+        }
+        else
+        {
+            _ftprintf(stderr, TEXT("Unknown command \"%s\". Please, use \"tapctl help\" to list supported commands.\n"), argv[2]);
+        }
+
+        return 1;
+    }
+    else if (_tcsicmp(argv[1], TEXT("create")) == 0)
+    {
+        LPCTSTR szName = NULL;
+        LPCTSTR szHwId = TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID);
+
+        /* Parse options. */
+        for (int i = 2; i < argc; i++)
+        {
+            if (_tcsicmp(argv[i], TEXT("--name")) == 0)
+            {
+                szName = argv[++i];
+            }
+            else
+            if (_tcsicmp(argv[i], TEXT("--hwid")) == 0)
+            {
+                szHwId = argv[++i];
+            }
+            else
+            {
+                _ftprintf(stderr, TEXT("Unknown option \"%s\". Please, use \"tapctl help create\" to list supported options. Ignored.\n"), argv[i]);
+            }
+        }
+
+        /* Create TUN/TAP adapter. */
+        GUID guidAdapter;
+        LPOLESTR szAdapterId = NULL;
+        DWORD dwResult = tap_create_adapter(
+            NULL,
+            TEXT("Virtual Ethernet"),
+            szHwId,
+            &bRebootRequired,
+            &guidAdapter);
+        if (dwResult != ERROR_SUCCESS)
+        {
+            _ftprintf(stderr, TEXT("Creating TUN/TAP adapter failed (error 0x%x).\n"), dwResult);
+            iResult = 1; goto quit;
+        }
+
+        if (szName)
+        {
+            /* Get existing network adapters. */
+            struct tap_adapter_node *pAdapterList = NULL;
+            dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);
+            if (dwResult != ERROR_SUCCESS)
+            {
+                _ftprintf(stderr, TEXT("Enumerating adapters failed (error 0x%x).\n"), dwResult);
+                iResult = 1; goto create_delete_adapter;
+            }
+
+            /* Check for duplicates. */
+            for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext)
+            {
+                if (_tcsicmp(szName, pAdapter->szName) == 0)
+                {
+                    StringFromIID((REFIID)&pAdapter->guid, &szAdapterId);
+                    _ftprintf(stderr, TEXT("Adapter \"%s\" already exists (GUID %") TEXT(PRIsLPOLESTR) TEXT(").\n"), pAdapter->szName, szAdapterId);
+                    CoTaskMemFree(szAdapterId);
+                    iResult = 1; goto create_cleanup_pAdapterList;
+                }
+            }
+
+            /* Rename the adapter. */
+            dwResult = tap_set_adapter_name(&guidAdapter, szName, FALSE);
+            if (dwResult != ERROR_SUCCESS)
+            {
+                StringFromIID((REFIID)&guidAdapter, &szAdapterId);
+                _ftprintf(stderr, TEXT("Renaming TUN/TAP adapter %") TEXT(PRIsLPOLESTR) TEXT(" to \"%s\" failed (error 0x%x).\n"), szAdapterId, szName, dwResult);
+                CoTaskMemFree(szAdapterId);
+                iResult = 1; goto quit;
+            }
+
+            iResult = 0;
+
+create_cleanup_pAdapterList:
+            tap_free_adapter_list(pAdapterList);
+            if (iResult)
+            {
+                goto create_delete_adapter;
+            }
+        }
+
+        /* Output adapter GUID. */
+        StringFromIID((REFIID)&guidAdapter, &szAdapterId);
+        _ftprintf(stdout, TEXT("%") TEXT(PRIsLPOLESTR) TEXT("\n"), szAdapterId);
+        CoTaskMemFree(szAdapterId);
+
+        iResult = 0; goto quit;
+
+create_delete_adapter:
+        tap_delete_adapter(
+            NULL,
+            &guidAdapter,
+            &bRebootRequired);
+        iResult = 1; goto quit;
+    }
+    else if (_tcsicmp(argv[1], TEXT("list")) == 0)
+    {
+        TCHAR szzHwId[0x100] =
+            TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0")
+            TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0")
+            TEXT("Wintun\0");
+
+        /* Parse options. */
+        for (int i = 2; i < argc; i++)
+        {
+            if (_tcsicmp(argv[i], TEXT("--hwid")) == 0)
+            {
+                memset(szzHwId, 0, sizeof(szzHwId));
+                ++i;
+                memcpy_s(szzHwId, sizeof(szzHwId) - 2*sizeof(TCHAR) /*requires double zero termination*/, argv[i], _tcslen(argv[i])*sizeof(TCHAR));
+            }
+            else
+            {
+                _ftprintf(stderr, TEXT("Unknown option \"%s\". Please, use \"tapctl help list\" to list supported options. Ignored.\n"), argv[i]);
+            }
+        }
+
+        /* Output list of adapters with given hardware ID. */
+        struct tap_adapter_node *pAdapterList = NULL;
+        DWORD dwResult = tap_list_adapters(NULL, szzHwId, &pAdapterList);
+        if (dwResult != ERROR_SUCCESS)
+        {
+            _ftprintf(stderr, TEXT("Enumerating TUN/TAP adapters failed (error 0x%x).\n"), dwResult);
+            iResult = 1; goto quit;
+        }
+
+        for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext)
+        {
+            LPOLESTR szAdapterId = NULL;
+            StringFromIID((REFIID)&pAdapter->guid, &szAdapterId);
+            _ftprintf(stdout, TEXT("%") TEXT(PRIsLPOLESTR) TEXT("\t%") TEXT(PRIsLPTSTR) TEXT("\n"), szAdapterId, pAdapter->szName);
+            CoTaskMemFree(szAdapterId);
+        }
+
+        iResult = 0;
+        tap_free_adapter_list(pAdapterList);
+    }
+    else if (_tcsicmp(argv[1], TEXT("delete")) == 0)
+    {
+        if (argc < 3)
+        {
+            _ftprintf(stderr, TEXT("Missing adapter GUID or name. Please, use \"tapctl help delete\" for usage info.\n"));
+            return 1;
+        }
+
+        GUID guidAdapter;
+        if (FAILED(IIDFromString(argv[2], (LPIID)&guidAdapter)))
+        {
+            /* The argument failed to covert to GUID. Treat it as the adapter name. */
+            struct tap_adapter_node *pAdapterList = NULL;
+            DWORD dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);
+            if (dwResult != ERROR_SUCCESS)
+            {
+                _ftprintf(stderr, TEXT("Enumerating TUN/TAP adapters failed (error 0x%x).\n"), dwResult);
+                iResult = 1; goto quit;
+            }
+
+            for (struct tap_adapter_node *pAdapter = pAdapterList;; pAdapter = pAdapter->pNext)
+            {
+                if (pAdapter == NULL)
+                {
+                    _ftprintf(stderr, TEXT("\"%s\" adapter not found.\n"), argv[2]);
+                    iResult = 1; goto delete_cleanup_pAdapterList;
+                }
+                else if (_tcsicmp(argv[2], pAdapter->szName) == 0)
+                {
+                    memcpy(&guidAdapter, &pAdapter->guid, sizeof(GUID));
+                    break;
+                }
+            }
+
+            iResult = 0;
+
+delete_cleanup_pAdapterList:
+            tap_free_adapter_list(pAdapterList);
+            if (iResult)
+            {
+                goto quit;
+            }
+        }
+
+        /* Delete the network adapter. */
+        DWORD dwResult = tap_delete_adapter(
+            NULL,
+            &guidAdapter,
+            &bRebootRequired);
+        if (dwResult != ERROR_SUCCESS)
+        {
+            _ftprintf(stderr, TEXT("Deleting adapter \"%s\" failed (error 0x%x).\n"), argv[2], dwResult);
+            iResult = 1; goto quit;
+        }
+
+        iResult = 0; goto quit;
+    }
+    else
+    {
+        _ftprintf(stderr, TEXT("Unknown command \"%s\". Please, use \"tapctl help\" to list supported commands.\n"), argv[1]);
+        return 1;
+    }
+
+quit:
+    if (bRebootRequired)
+    {
+        _ftprintf(stderr, TEXT("A system reboot is required.\n"));
+    }
+
+    return iResult;
+}
+
+
+bool
+dont_mute(unsigned int flags)
+{
+    UNREFERENCED_PARAMETER(flags);
+
+    return true;
+}
+
+
+void
+x_msg_va(const unsigned int flags, const char *format, va_list arglist)
+{
+    /* Output message string. Note: Message strings don't contain line terminators. */
+    vfprintf(stderr, format, arglist);
+    _ftprintf(stderr, TEXT("\n"));
+
+    if ((flags & M_ERRNO) != 0)
+    {
+        /* Output system error message (if possible). */
+        DWORD dwResult = GetLastError();
+        LPTSTR szErrMessage = NULL;
+        if (FormatMessage(
+                FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
+                0,
+                dwResult,
+                0,
+                (LPTSTR)&szErrMessage,
+                0,
+                NULL) && szErrMessage)
+        {
+            /* Trim trailing whitespace. Set terminator after the last non-whitespace character. This prevents excessive trailing line breaks. */
+            for (size_t i = 0, i_last = 0;; i++)
+            {
+                if (szErrMessage[i])
+                {
+                    if (!_istspace(szErrMessage[i]))
+                    {
+                        i_last = i + 1;
+                    }
+                }
+                else
+                {
+                    szErrMessage[i_last] = 0;
+                    break;
+                }
+            }
+
+            /* Output error message. */
+            _ftprintf(stderr, TEXT("Error 0x%x: %s\n"), dwResult, szErrMessage);
+
+            LocalFree(szErrMessage);
+        }
+        else
+        {
+            _ftprintf(stderr, TEXT("Error 0x%x\n"), dwResult);
+        }
+    }
+}
diff --git a/src/tapctl/tap.c b/src/tapctl/tap.c
new file mode 100644
index 0000000..dd4a10a
--- /dev/null
+++ b/src/tapctl/tap.c
@@ -0,0 +1,1367 @@
+/*
+ *  tapctl -- Utility to manipulate TUN/TAP adapters on Windows
+ *            https://community.openvpn.net/openvpn/wiki/Tapctl
+ *
+ *  Copyright (C) 2018-2020 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#elif defined(_MSC_VER)
+#include <config-msvc.h>
+#endif
+
+#include "tap.h"
+#include "error.h"
+
+#include <windows.h>
+#include <cfgmgr32.h>
+#include <objbase.h>
+#include <setupapi.h>
+#include <stdio.h>
+#include <tchar.h>
+#include <newdev.h>
+
+#ifdef _MSC_VER
+#pragma comment(lib, "advapi32.lib")
+#pragma comment(lib, "ole32.lib")
+#pragma comment(lib, "setupapi.lib")
+#pragma comment(lib, "newdev.lib")
+#endif
+
+
+const static GUID GUID_DEVCLASS_NET = { 0x4d36e972L, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 } };
+
+const static TCHAR szAdapterRegKeyPathTemplate[] = TEXT("SYSTEM\\CurrentControlSet\\Control\\Network\\%") TEXT(PRIsLPOLESTR) TEXT("\\%") TEXT(PRIsLPOLESTR) TEXT("\\Connection");
+#define ADAPTER_REGKEY_PATH_MAX (_countof(TEXT("SYSTEM\\CurrentControlSet\\Control\\Network\\")) - 1 + 38 + _countof(TEXT("\\")) - 1 + 38 + _countof(TEXT("\\Connection")))
+
+/**
+ * Dynamically load a library and find a function in it
+ *
+ * @param libname     Name of the library to load
+ * @param funcname    Name of the function to find
+ * @param m           Pointer to a module. On return this is set to the
+ *                    the handle to the loaded library. The caller must
+ *                    free it by calling FreeLibrary() if not NULL.
+ *
+ * @return            Pointer to the function
+ *                    NULL on error -- use GetLastError() to find the error code.
+ *
+ **/
+static void *
+find_function(const WCHAR *libname, const char *funcname, HMODULE *m)
+{
+    WCHAR libpath[MAX_PATH];
+    void *fptr = NULL;
+
+    /* Make sure the dll is loaded from the system32 folder */
+    if (!GetSystemDirectoryW(libpath, _countof(libpath)))
+    {
+       return NULL;
+    }
+
+    size_t len = _countof(libpath) - wcslen(libpath) - 1;
+    if (len < wcslen(libname) + 1)
+    {
+       SetLastError(ERROR_INSUFFICIENT_BUFFER);
+       return NULL;
+    }
+    wcsncat(libpath, L"\\", len);
+    wcsncat(libpath, libname, len-1);
+
+    *m = LoadLibraryW(libpath);
+    if (*m == NULL)
+    {
+       return NULL;
+    }
+    fptr = GetProcAddress(*m, funcname);
+    if (!fptr)
+    {
+       FreeLibrary(*m);
+       *m = NULL;
+       return NULL;
+    }
+    return fptr;
+}
+
+/**
+ * Returns length of string of strings
+ *
+ * @param szz           Pointer to a string of strings (terminated by an empty string)
+ *
+ * @return Number of characters not counting the final zero terminator
+ **/
+static inline size_t
+_tcszlen(_In_z_ LPCTSTR szz)
+{
+    LPCTSTR s;
+    for (s = szz; s[0]; s += _tcslen(s) + 1)
+    {
+    }
+    return s - szz;
+}
+
+
+/**
+ * Checks if string is contained in the string of strings. Comparison is made case-insensitive.
+ *
+ * @param szzHay        Pointer to a string of strings (terminated by an empty string) we are
+ *                      looking in
+ *
+ * @param szNeedle      The string we are searching for
+ *
+ * @return Pointer to the string in szzHay that matches szNeedle is found; NULL otherwise
+ */
+static LPCTSTR
+_tcszistr(_In_z_ LPCTSTR szzHay, _In_z_ LPCTSTR szNeedle)
+{
+    for (LPCTSTR s = szzHay; s[0]; s += _tcslen(s) + 1)
+    {
+        if (_tcsicmp(s, szNeedle) == 0)
+        {
+            return s;
+        }
+    }
+
+    return NULL;
+}
+
+
+/**
+ * Function that performs a specific task on a device
+ *
+ * @param hDeviceInfoSet  A handle to a device information set that contains a device
+ *                      information element that represents the device.
+ *
+ * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the
+ *                      device information element in hDeviceInfoSet.
+ *
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,
+ *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ *                      allows the flag to be globally initialized to FALSE and reused for multiple
+ *                      adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+typedef DWORD (*devop_func_t)(
+    _In_ HDEVINFO hDeviceInfoSet,
+    _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+    _Inout_ LPBOOL pbRebootRequired);
+
+
+/**
+ * Checks device install parameters if a system reboot is required.
+ *
+ * @param hDeviceInfoSet  A handle to a device information set that contains a device
+ *                      information element that represents the device.
+ *
+ * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the
+ *                      device information element in hDeviceInfoSet.
+ *
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,
+ *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ *                      allows the flag to be globally initialized to FALSE and reused for multiple
+ *                      adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+check_reboot(
+    _In_ HDEVINFO hDeviceInfoSet,
+    _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+    _Inout_ LPBOOL pbRebootRequired)
+{
+    if (pbRebootRequired == NULL)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    SP_DEVINSTALL_PARAMS devinstall_params = { .cbSize = sizeof(SP_DEVINSTALL_PARAMS) };
+    if (!SetupDiGetDeviceInstallParams(
+            hDeviceInfoSet,
+            pDeviceInfoData,
+            &devinstall_params))
+    {
+        DWORD dwResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceInstallParams failed", __FUNCTION__);
+        return dwResult;
+    }
+
+    if ((devinstall_params.Flags & (DI_NEEDREBOOT | DI_NEEDRESTART)) != 0)
+    {
+        *pbRebootRequired = TRUE;
+    }
+
+    return ERROR_SUCCESS;
+}
+
+
+/**
+ * Deletes the device.
+ *
+ * @param hDeviceInfoSet  A handle to a device information set that contains a device
+ *                      information element that represents the device.
+ *
+ * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the
+ *                      device information element in hDeviceInfoSet.
+ *
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,
+ *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ *                      allows the flag to be globally initialized to FALSE and reused for multiple
+ *                      adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+delete_device(
+    _In_ HDEVINFO hDeviceInfoSet,
+    _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+    _Inout_ LPBOOL pbRebootRequired)
+{
+    SP_REMOVEDEVICE_PARAMS params =
+    {
+        .ClassInstallHeader =
+        {
+            .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
+            .InstallFunction = DIF_REMOVE,
+        },
+        .Scope = DI_REMOVEDEVICE_GLOBAL,
+        .HwProfile = 0,
+    };
+
+    /* Set class installer parameters for DIF_REMOVE. */
+    if (!SetupDiSetClassInstallParams(
+            hDeviceInfoSet,
+            pDeviceInfoData,
+            &params.ClassInstallHeader,
+            sizeof(SP_REMOVEDEVICE_PARAMS)))
+    {
+        DWORD dwResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
+        return dwResult;
+    }
+
+    /* Call appropriate class installer. */
+    if (!SetupDiCallClassInstaller(
+            DIF_REMOVE,
+            hDeviceInfoSet,
+            pDeviceInfoData))
+    {
+        DWORD dwResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_REMOVE) failed", __FUNCTION__);
+        return dwResult;
+    }
+
+    /* Check if a system reboot is required. */
+    check_reboot(hDeviceInfoSet, pDeviceInfoData, pbRebootRequired);
+    return ERROR_SUCCESS;
+}
+
+
+/**
+ * Changes the device state.
+ *
+ * @param hDeviceInfoSet  A handle to a device information set that contains a device
+ *                      information element that represents the device.
+ *
+ * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the
+ *                      device information element in hDeviceInfoSet.
+ *
+ * @param bEnable       TRUE to enable the device; FALSE to disable.
+ *
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,
+ *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ *                      allows the flag to be globally initialized to FALSE and reused for multiple
+ *                      adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+change_device_state(
+    _In_ HDEVINFO hDeviceInfoSet,
+    _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+    _In_ BOOL bEnable,
+    _Inout_ LPBOOL pbRebootRequired)
+{
+    SP_PROPCHANGE_PARAMS params =
+    {
+        .ClassInstallHeader =
+        {
+            .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
+            .InstallFunction = DIF_PROPERTYCHANGE,
+        },
+        .StateChange = bEnable ? DICS_ENABLE : DICS_DISABLE,
+        .Scope = DICS_FLAG_GLOBAL,
+        .HwProfile = 0,
+    };
+
+    /* Set class installer parameters for DIF_PROPERTYCHANGE. */
+    if (!SetupDiSetClassInstallParams(
+            hDeviceInfoSet,
+            pDeviceInfoData,
+            &params.ClassInstallHeader,
+            sizeof(SP_PROPCHANGE_PARAMS)))
+    {
+        DWORD dwResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
+        return dwResult;
+    }
+
+    /* Call appropriate class installer. */
+    if (!SetupDiCallClassInstaller(
+            DIF_PROPERTYCHANGE,
+            hDeviceInfoSet,
+            pDeviceInfoData))
+    {
+        DWORD dwResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_PROPERTYCHANGE) failed", __FUNCTION__);
+        return dwResult;
+    }
+
+    /* Check if a system reboot is required. */
+    check_reboot(hDeviceInfoSet, pDeviceInfoData, pbRebootRequired);
+    return ERROR_SUCCESS;
+}
+
+
+/**
+ * Enables the device.
+ *
+ * @param hDeviceInfoSet  A handle to a device information set that contains a device
+ *                      information element that represents the device.
+ *
+ * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the
+ *                      device information element in hDeviceInfoSet.
+ *
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,
+ *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ *                      allows the flag to be globally initialized to FALSE and reused for multiple
+ *                      adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+enable_device(
+    _In_ HDEVINFO hDeviceInfoSet,
+    _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+    _Inout_ LPBOOL pbRebootRequired)
+{
+    return change_device_state(hDeviceInfoSet, pDeviceInfoData, TRUE, pbRebootRequired);
+}
+
+
+/**
+ * Disables the device.
+ *
+ * @param hDeviceInfoSet  A handle to a device information set that contains a device
+ *                      information element that represents the device.
+ *
+ * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the
+ *                      device information element in hDeviceInfoSet.
+ *
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,
+ *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ *                      allows the flag to be globally initialized to FALSE and reused for multiple
+ *                      adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+disable_device(
+    _In_ HDEVINFO hDeviceInfoSet,
+    _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+    _Inout_ LPBOOL pbRebootRequired)
+{
+    return change_device_state(hDeviceInfoSet, pDeviceInfoData, FALSE, pbRebootRequired);
+}
+
+
+/**
+ * Reads string value from registry key.
+ *
+ * @param hKey          Handle of the registry key to read from. Must be opened with read
+ *                      access.
+ *
+ * @param szName        Name of the value to read.
+ *
+ * @param pszValue      Pointer to string to retrieve registry value. If the value type is
+ *                      REG_EXPAND_SZ the value is expanded using ExpandEnvironmentStrings().
+ *                      The string must be released with free() after use.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ */
+static DWORD
+get_reg_string(
+    _In_ HKEY hKey,
+    _In_ LPCTSTR szName,
+    _Out_ LPTSTR *pszValue)
+{
+    if (pszValue == NULL)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    DWORD dwValueType = REG_NONE, dwSize = 0;
+    DWORD dwResult = RegQueryValueEx(
+        hKey,
+        szName,
+        NULL,
+        &dwValueType,
+        NULL,
+        &dwSize);
+    if (dwResult != ERROR_SUCCESS)
+    {
+        SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(M_NONFATAL | M_ERRNO, "%s: enumerating \"%" PRIsLPTSTR "\" registry value failed", __FUNCTION__, szName);
+        return dwResult;
+    }
+
+    switch (dwValueType)
+    {
+        case REG_SZ:
+        case REG_EXPAND_SZ:
+        {
+            /* Read value. */
+            LPTSTR szValue = (LPTSTR)malloc(dwSize);
+            if (szValue == NULL)
+            {
+                msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSize);
+                return ERROR_OUTOFMEMORY;
+            }
+
+            dwResult = RegQueryValueEx(
+                hKey,
+                szName,
+                NULL,
+                NULL,
+                (LPBYTE)szValue,
+                &dwSize);
+            if (dwResult != ERROR_SUCCESS)
+            {
+                SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we do have an error code. Set last error manually. */
+                msg(M_NONFATAL | M_ERRNO, "%s: reading \"%" PRIsLPTSTR "\" registry value failed", __FUNCTION__, szName);
+                free(szValue);
+                return dwResult;
+            }
+
+            if (dwValueType == REG_EXPAND_SZ)
+            {
+                /* Expand the environment strings. */
+                DWORD
+                    dwSizeExp = dwSize * 2,
+                    dwCountExp =
+#ifdef UNICODE
+                    dwSizeExp / sizeof(TCHAR);
+#else
+                    dwSizeExp / sizeof(TCHAR) - 1;     /* Note: ANSI version requires one extra char. */
+#endif
+                LPTSTR szValueExp = (LPTSTR)malloc(dwSizeExp);
+                if (szValueExp == NULL)
+                {
+                    free(szValue);
+                    msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSizeExp);
+                    return ERROR_OUTOFMEMORY;
+                }
+
+                DWORD dwCountExpResult = ExpandEnvironmentStrings(
+                    szValue,
+                    szValueExp, dwCountExp
+                    );
+                if (dwCountExpResult == 0)
+                {
+                    msg(M_NONFATAL | M_ERRNO, "%s: expanding \"%" PRIsLPTSTR "\" registry value failed", __FUNCTION__, szName);
+                    free(szValueExp);
+                    free(szValue);
+                    return dwResult;
+                }
+                else if (dwCountExpResult <= dwCountExp)
+                {
+                    /* The buffer was big enough. */
+                    free(szValue);
+                    *pszValue = szValueExp;
+                    return ERROR_SUCCESS;
+                }
+                else
+                {
+                    /* Retry with a bigger buffer. */
+                    free(szValueExp);
+#ifdef UNICODE
+                    dwSizeExp = dwCountExpResult * sizeof(TCHAR);
+#else
+                    /* Note: ANSI version requires one extra char. */
+                    dwSizeExp = (dwCountExpResult + 1) * sizeof(TCHAR);
+#endif
+                    dwCountExp = dwCountExpResult;
+                    szValueExp = (LPTSTR)malloc(dwSizeExp);
+                    if (szValueExp == NULL)
+                    {
+                        free(szValue);
+                        msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSizeExp);
+                        return ERROR_OUTOFMEMORY;
+                    }
+
+                    dwCountExpResult = ExpandEnvironmentStrings(
+                        szValue,
+                        szValueExp, dwCountExp);
+                    free(szValue);
+                    *pszValue = szValueExp;
+                    return ERROR_SUCCESS;
+                }
+            }
+            else
+            {
+                *pszValue = szValue;
+                return ERROR_SUCCESS;
+            }
+        }
+
+        default:
+            msg(M_NONFATAL, "%s: \"%" PRIsLPTSTR "\" registry value is not string (type %u)", __FUNCTION__, dwValueType);
+            return ERROR_UNSUPPORTED_TYPE;
+    }
+}
+
+
+/**
+ * Returns network adapter ID.
+ *
+ * @param hDeviceInfoSet  A handle to a device information set that contains a device
+ *                      information element that represents the device.
+ *
+ * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the
+ *                      device information element in hDeviceInfoSet.
+ *
+ * @param iNumAttempts  After the device is created, it might take some time before the
+ *                      registry key is populated. This parameter specifies the number of
+ *                      attempts to read NetCfgInstanceId value from registry. A 1sec sleep
+ *                      is inserted between retry attempts.
+ *
+ * @param pguidAdapter  A pointer to GUID that receives network adapter ID.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+get_net_adapter_guid(
+    _In_ HDEVINFO hDeviceInfoSet,
+    _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+    _In_ int iNumAttempts,
+    _Out_ LPGUID pguidAdapter)
+{
+    DWORD dwResult = ERROR_BAD_ARGUMENTS;
+
+    if (pguidAdapter == NULL || iNumAttempts < 1)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    /* Open HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\<class>\<id> registry key. */
+    HKEY hKey = SetupDiOpenDevRegKey(
+        hDeviceInfoSet,
+        pDeviceInfoData,
+        DICS_FLAG_GLOBAL,
+        0,
+        DIREG_DRV,
+        KEY_READ);
+    if (hKey == INVALID_HANDLE_VALUE)
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: SetupDiOpenDevRegKey failed", __FUNCTION__);
+        return dwResult;
+    }
+
+    while (iNumAttempts > 0)
+    {
+        /* Query the NetCfgInstanceId value. Using get_reg_string() right on might clutter the output with error messages while the registry is still being populated. */
+        LPTSTR szCfgGuidString = NULL;
+        dwResult = RegQueryValueEx(hKey, TEXT("NetCfgInstanceId"), NULL, NULL, NULL, NULL);
+        if (dwResult != ERROR_SUCCESS)
+        {
+            if (dwResult == ERROR_FILE_NOT_FOUND && --iNumAttempts > 0)
+            {
+                /* Wait and retry. */
+                Sleep(1000);
+                continue;
+            }
+
+            SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we do have an error code. Set last error manually. */
+            msg(M_NONFATAL | M_ERRNO, "%s: querying \"NetCfgInstanceId\" registry value failed", __FUNCTION__);
+            break;
+        }
+
+        /* Read the NetCfgInstanceId value now. */
+        dwResult = get_reg_string(
+            hKey,
+            TEXT("NetCfgInstanceId"),
+            &szCfgGuidString);
+        if (dwResult != ERROR_SUCCESS)
+        {
+            break;
+        }
+
+        dwResult = SUCCEEDED(CLSIDFromString(szCfgGuidString, (LPCLSID)pguidAdapter)) ? ERROR_SUCCESS : ERROR_INVALID_DATA;
+        free(szCfgGuidString);
+        break;
+    }
+
+    RegCloseKey(hKey);
+    return dwResult;
+}
+
+
+/**
+ * Returns a specified Plug and Play device property.
+ *
+ * @param hDeviceInfoSet  A handle to a device information set that contains a device
+ *                      information element that represents the device.
+ *
+ * @param pDeviceInfoData  A pointer to an SP_DEVINFO_DATA structure that specifies the
+ *                      device information element in hDeviceInfoSet.
+ *
+ * @param dwProperty     Specifies the property to be retrieved. See
+ *                       https://msdn.microsoft.com/en-us/library/windows/hardware/ff551967.aspx
+ *
+ * @pdwPropertyRegDataType  A pointer to a variable that receives the data type of the
+ *                       property that is being retrieved. This is one of the standard
+ *                       registry data types. This parameter is optional and can be NULL.
+ *
+ * @param ppData         A pointer to pointer to data that receives the device property. The
+ *                       data must be released with free() after use.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+get_device_reg_property(
+    _In_ HDEVINFO hDeviceInfoSet,
+    _In_ PSP_DEVINFO_DATA pDeviceInfoData,
+    _In_ DWORD dwProperty,
+    _Out_opt_ LPDWORD pdwPropertyRegDataType,
+    _Out_ LPVOID *ppData)
+{
+    DWORD dwResult = ERROR_BAD_ARGUMENTS;
+
+    if (ppData == NULL)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    /* Try with stack buffer first. */
+    BYTE bBufStack[128];
+    DWORD dwRequiredSize = 0;
+    if (SetupDiGetDeviceRegistryProperty(
+            hDeviceInfoSet,
+            pDeviceInfoData,
+            dwProperty,
+            pdwPropertyRegDataType,
+            bBufStack,
+            sizeof(bBufStack),
+            &dwRequiredSize))
+    {
+        /* Copy from stack. */
+        *ppData = malloc(dwRequiredSize);
+        if (*ppData == NULL)
+        {
+            msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwRequiredSize);
+            return ERROR_OUTOFMEMORY;
+        }
+
+        memcpy(*ppData, bBufStack, dwRequiredSize);
+        return ERROR_SUCCESS;
+    }
+    else
+    {
+        dwResult = GetLastError();
+        if (dwResult == ERROR_INSUFFICIENT_BUFFER)
+        {
+            /* Allocate on heap and retry. */
+            *ppData = malloc(dwRequiredSize);
+            if (*ppData == NULL)
+            {
+                msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwRequiredSize);
+                return ERROR_OUTOFMEMORY;
+            }
+
+            if (SetupDiGetDeviceRegistryProperty(
+                    hDeviceInfoSet,
+                    pDeviceInfoData,
+                    dwProperty,
+                    pdwPropertyRegDataType,
+                    *ppData,
+                    dwRequiredSize,
+                    &dwRequiredSize))
+            {
+                return ERROR_SUCCESS;
+            }
+            else
+            {
+                dwResult = GetLastError();
+                msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceRegistryProperty(%u) failed", __FUNCTION__, dwProperty);
+                return dwResult;
+            }
+        }
+        else
+        {
+            msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceRegistryProperty(%u) failed", __FUNCTION__, dwProperty);
+            return dwResult;
+        }
+    }
+}
+
+
+DWORD
+tap_create_adapter(
+    _In_opt_ HWND hwndParent,
+    _In_opt_ LPCTSTR szDeviceDescription,
+    _In_ LPCTSTR szHwId,
+    _Inout_ LPBOOL pbRebootRequired,
+    _Out_ LPGUID pguidAdapter)
+{
+    DWORD dwResult;
+    HMODULE libnewdev = NULL;
+
+    if (szHwId == NULL
+        || pbRebootRequired == NULL
+        || pguidAdapter == NULL)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    /* Create an empty device info set for network adapter device class. */
+    HDEVINFO hDevInfoList = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_NET, hwndParent);
+    if (hDevInfoList == INVALID_HANDLE_VALUE)
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL, "%s: SetupDiCreateDeviceInfoList failed", __FUNCTION__);
+        return dwResult;
+    }
+
+    /* Get the device class name from GUID. */
+    TCHAR szClassName[MAX_CLASS_NAME_LEN];
+    if (!SetupDiClassNameFromGuid(
+            &GUID_DEVCLASS_NET,
+            szClassName,
+            _countof(szClassName),
+            NULL))
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL, "%s: SetupDiClassNameFromGuid failed", __FUNCTION__);
+        goto cleanup_hDevInfoList;
+    }
+
+    /* Create a new device info element and add it to the device info set. */
+    SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
+    if (!SetupDiCreateDeviceInfo(
+            hDevInfoList,
+            szClassName,
+            &GUID_DEVCLASS_NET,
+            szDeviceDescription,
+            hwndParent,
+            DICD_GENERATE_ID,
+            &devinfo_data))
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL, "%s: SetupDiCreateDeviceInfo failed", __FUNCTION__);
+        goto cleanup_hDevInfoList;
+    }
+
+    /* Set a device information element as the selected member of a device information set. */
+    if (!SetupDiSetSelectedDevice(
+            hDevInfoList,
+            &devinfo_data))
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL, "%s: SetupDiSetSelectedDevice failed", __FUNCTION__);
+        goto cleanup_hDevInfoList;
+    }
+
+    /* Set Plug&Play device hardware ID property. */
+    if (!SetupDiSetDeviceRegistryProperty(
+            hDevInfoList,
+            &devinfo_data,
+            SPDRP_HARDWAREID,
+            (const BYTE *)szHwId, (DWORD)((_tcslen(szHwId) + 1) * sizeof(TCHAR))))
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL, "%s: SetupDiSetDeviceRegistryProperty failed", __FUNCTION__);
+        goto cleanup_hDevInfoList;
+    }
+
+    /* Register the device instance with the PnP Manager */
+    if (!SetupDiCallClassInstaller(
+            DIF_REGISTERDEVICE,
+            hDevInfoList,
+            &devinfo_data))
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL, "%s: SetupDiCallClassInstaller(DIF_REGISTERDEVICE) failed", __FUNCTION__);
+        goto cleanup_hDevInfoList;
+    }
+
+    /* Install the device using DiInstallDevice()
+     * We instruct the system to use the best driver in the driver store
+     * by setting the drvinfo argument of DiInstallDevice as NULL. This
+     * assumes a driver is already installed in the driver store.
+     */
+#ifdef HAVE_DIINSTALLDEVICE
+    if (!DiInstallDevice(hwndParent, hDevInfoList, &devinfo_data, NULL, 0, pbRebootRequired))
+#else
+    /* mingw does not resolve DiInstallDevice, so load it at run time. */
+    typedef BOOL (WINAPI *DiInstallDeviceFn) (HWND, HDEVINFO, SP_DEVINFO_DATA *,
+                                                  SP_DRVINFO_DATA *, DWORD, BOOL *);
+    DiInstallDeviceFn installfn
+           = find_function (L"newdev.dll", "DiInstallDevice", &libnewdev);
+
+    if (!installfn)
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: Failed to locate DiInstallDevice()", __FUNCTION__);
+        goto cleanup_hDevInfoList;
+    }
+
+    if (!installfn(hwndParent, hDevInfoList, &devinfo_data, NULL, 0, pbRebootRequired))
+#endif
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL | M_ERRNO, "%s: DiInstallDevice failed", __FUNCTION__);
+        goto cleanup_remove_device;
+    }
+
+    /* Get network adapter ID from registry. Retry for max 30sec. */
+    dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 30, pguidAdapter);
+
+cleanup_remove_device:
+    if (dwResult != ERROR_SUCCESS)
+    {
+        /* The adapter was installed. But, the adapter ID was unobtainable. Clean-up. */
+        SP_REMOVEDEVICE_PARAMS removedevice_params =
+        {
+            .ClassInstallHeader =
+            {
+                .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
+                .InstallFunction = DIF_REMOVE,
+            },
+            .Scope = DI_REMOVEDEVICE_GLOBAL,
+            .HwProfile = 0,
+        };
+
+        /* Set class installer parameters for DIF_REMOVE. */
+        if (SetupDiSetClassInstallParams(
+                hDevInfoList,
+                &devinfo_data,
+                &removedevice_params.ClassInstallHeader,
+                sizeof(SP_REMOVEDEVICE_PARAMS)))
+        {
+            /* Call appropriate class installer. */
+            if (SetupDiCallClassInstaller(
+                    DIF_REMOVE,
+                    hDevInfoList,
+                    &devinfo_data))
+            {
+                /* Check if a system reboot is required. */
+                check_reboot(hDevInfoList, &devinfo_data, pbRebootRequired);
+            }
+            else
+            {
+                msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_REMOVE) failed", __FUNCTION__);
+            }
+        }
+        else
+        {
+            msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
+        }
+    }
+
+cleanup_hDevInfoList:
+    if (libnewdev)
+    {
+        FreeLibrary(libnewdev);
+    }
+    SetupDiDestroyDeviceInfoList(hDevInfoList);
+    return dwResult;
+}
+
+
+/**
+ * Performs a given task on an adapter.
+ *
+ * @param hwndParent    A handle to the top-level window to use for any user adapter that is
+ *                      related to non-device-specific actions (such as a select-device dialog
+ *                      box that uses the global class driver list). This handle is optional
+ *                      and can be NULL. If a specific top-level window is not required, set
+ *                      hwndParent to NULL.
+ *
+ * @param pguidAdapter  A pointer to GUID that contains network adapter ID.
+ *
+ * @param funcOperation  A pointer for the function to perform specific task on the adapter.
+ *
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,
+ *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ *                      allows the flag to be globally initialized to FALSE and reused for multiple
+ *                      adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+static DWORD
+execute_on_first_adapter(
+    _In_opt_ HWND hwndParent,
+    _In_ LPCGUID pguidAdapter,
+    _In_ devop_func_t funcOperation,
+    _Inout_ LPBOOL pbRebootRequired)
+{
+    DWORD dwResult;
+
+    if (pguidAdapter == NULL)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    /* Create a list of network devices. */
+    HDEVINFO hDevInfoList = SetupDiGetClassDevsEx(
+        &GUID_DEVCLASS_NET,
+        NULL,
+        hwndParent,
+        DIGCF_PRESENT,
+        NULL,
+        NULL,
+        NULL);
+    if (hDevInfoList == INVALID_HANDLE_VALUE)
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL, "%s: SetupDiGetClassDevsEx failed", __FUNCTION__);
+        return dwResult;
+    }
+
+    /* Retrieve information associated with a device information set. */
+    SP_DEVINFO_LIST_DETAIL_DATA devinfo_list_detail_data = { .cbSize = sizeof(SP_DEVINFO_LIST_DETAIL_DATA) };
+    if (!SetupDiGetDeviceInfoListDetail(hDevInfoList, &devinfo_list_detail_data))
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL, "%s: SetupDiGetDeviceInfoListDetail failed", __FUNCTION__);
+        goto cleanup_hDevInfoList;
+    }
+
+    /* Iterate. */
+    for (DWORD dwIndex = 0;; dwIndex++)
+    {
+        /* Get the device from the list. */
+        SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
+        if (!SetupDiEnumDeviceInfo(
+                hDevInfoList,
+                dwIndex,
+                &devinfo_data))
+        {
+            if (GetLastError() == ERROR_NO_MORE_ITEMS)
+            {
+                LPOLESTR szAdapterId = NULL;
+                StringFromIID((REFIID)pguidAdapter, &szAdapterId);
+                msg(M_NONFATAL, "%s: Adapter %" PRIsLPOLESTR " not found", __FUNCTION__, szAdapterId);
+                CoTaskMemFree(szAdapterId);
+                dwResult = ERROR_FILE_NOT_FOUND;
+                goto cleanup_hDevInfoList;
+            }
+            else
+            {
+                /* Something is wrong with this device. Skip it. */
+                msg(M_WARN | M_ERRNO, "%s: SetupDiEnumDeviceInfo(%u) failed", __FUNCTION__, dwIndex);
+                continue;
+            }
+        }
+
+        /* Get adapter GUID. */
+        GUID guidAdapter;
+        dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 1, &guidAdapter);
+        if (dwResult != ERROR_SUCCESS)
+        {
+            /* Something is wrong with this device. Skip it. */
+            continue;
+        }
+
+        /* Compare GUIDs. */
+        if (memcmp(pguidAdapter, &guidAdapter, sizeof(GUID)) == 0)
+        {
+            dwResult = funcOperation(hDevInfoList, &devinfo_data, pbRebootRequired);
+            break;
+        }
+    }
+
+cleanup_hDevInfoList:
+    SetupDiDestroyDeviceInfoList(hDevInfoList);
+    return dwResult;
+}
+
+
+DWORD
+tap_delete_adapter(
+    _In_opt_ HWND hwndParent,
+    _In_ LPCGUID pguidAdapter,
+    _Inout_ LPBOOL pbRebootRequired)
+{
+    return execute_on_first_adapter(hwndParent, pguidAdapter, delete_device, pbRebootRequired);
+}
+
+
+DWORD
+tap_enable_adapter(
+    _In_opt_ HWND hwndParent,
+    _In_ LPCGUID pguidAdapter,
+    _In_ BOOL bEnable,
+    _Inout_ LPBOOL pbRebootRequired)
+{
+    return execute_on_first_adapter(hwndParent, pguidAdapter, bEnable ? enable_device : disable_device, pbRebootRequired);
+}
+
+/* stripped version of ExecCommand in interactive.c */
+static DWORD
+ExecCommand(const WCHAR* cmdline)
+{
+    DWORD exit_code;
+    STARTUPINFOW si;
+    PROCESS_INFORMATION pi;
+    DWORD proc_flags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
+    WCHAR* cmdline_dup = NULL;
+
+    ZeroMemory(&si, sizeof(si));
+    ZeroMemory(&pi, sizeof(pi));
+
+    si.cb = sizeof(si);
+
+    /* CreateProcess needs a modifiable cmdline: make a copy */
+    cmdline_dup = _wcsdup(cmdline);
+    if (cmdline_dup && CreateProcessW(NULL, cmdline_dup, NULL, NULL, FALSE,
+        proc_flags, NULL, NULL, &si, &pi))
+    {
+        WaitForSingleObject(pi.hProcess, INFINITE);
+        if (!GetExitCodeProcess(pi.hProcess, &exit_code))
+        {
+            exit_code = GetLastError();
+        }
+
+        CloseHandle(pi.hProcess);
+        CloseHandle(pi.hThread);
+    }
+    else
+    {
+        exit_code = GetLastError();
+    }
+
+    free(cmdline_dup);
+    return exit_code;
+}
+
+DWORD
+tap_set_adapter_name(
+    _In_ LPCGUID pguidAdapter,
+    _In_ LPCTSTR szName,
+    _In_ BOOL bSilent)
+{
+    DWORD dwResult;
+    int msg_flag = bSilent ? M_WARN : M_NONFATAL;
+    msg_flag |= M_ERRNO;
+
+    if (pguidAdapter == NULL || szName == NULL)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    /* Get the device class GUID as string. */
+    LPOLESTR szDevClassNetId = NULL;
+    StringFromIID((REFIID)&GUID_DEVCLASS_NET, &szDevClassNetId);
+
+    /* Get the adapter GUID as string. */
+    LPOLESTR szAdapterId = NULL;
+    StringFromIID((REFIID)pguidAdapter, &szAdapterId);
+
+    /* Render registry key path. */
+    TCHAR szRegKey[ADAPTER_REGKEY_PATH_MAX];
+    _stprintf_s(
+        szRegKey, _countof(szRegKey),
+        szAdapterRegKeyPathTemplate,
+        szDevClassNetId,
+        szAdapterId);
+
+    /* Open network adapter registry key. */
+    HKEY hKey = NULL;
+    dwResult = RegOpenKeyEx(
+        HKEY_LOCAL_MACHINE,
+        szRegKey,
+        0,
+        KEY_QUERY_VALUE,
+        &hKey);
+    if (dwResult != ERROR_SUCCESS)
+    {
+        SetLastError(dwResult); /* MSDN does not mention RegOpenKeyEx() to set GetLastError(). But we do have an error code. Set last error manually. */
+        msg(msg_flag, "%s: RegOpenKeyEx(HKLM, \"%" PRIsLPTSTR "\") failed", __FUNCTION__, szRegKey);
+        goto cleanup_szAdapterId;
+    }
+
+    LPTSTR szOldName = NULL;
+    dwResult = get_reg_string(hKey, TEXT("Name"), &szOldName);
+    if (dwResult != ERROR_SUCCESS)
+    {
+        SetLastError(dwResult);
+        msg(msg_flag, "%s: Error reading adapter name", __FUNCTION__);
+        goto cleanup_hKey;
+    }
+
+    /* rename adapter via netsh call */
+    const TCHAR* szFmt = _T("netsh interface set interface name=\"%s\" newname=\"%s\"");
+    size_t ncmdline = _tcslen(szFmt) + _tcslen(szOldName) + _tcslen(szName) + 1;
+    WCHAR* szCmdLine = malloc(ncmdline * sizeof(TCHAR));
+    _stprintf_s(szCmdLine, ncmdline, szFmt, szOldName, szName);
+
+    free(szOldName);
+
+    dwResult = ExecCommand(szCmdLine);
+    free(szCmdLine);
+
+    if (dwResult != ERROR_SUCCESS)
+    {
+        SetLastError(dwResult);
+        msg(msg_flag, "%s: Error renaming adapter", __FUNCTION__);
+        goto cleanup_hKey;
+    }
+
+cleanup_hKey:
+    RegCloseKey(hKey);
+cleanup_szAdapterId:
+    CoTaskMemFree(szAdapterId);
+    CoTaskMemFree(szDevClassNetId);
+    return dwResult;
+}
+
+
+DWORD
+tap_list_adapters(
+    _In_opt_ HWND hwndParent,
+    _In_opt_ LPCTSTR szzHwIDs,
+    _Out_ struct tap_adapter_node **ppAdapter)
+{
+    DWORD dwResult;
+
+    if (ppAdapter == NULL)
+    {
+        return ERROR_BAD_ARGUMENTS;
+    }
+
+    /* Create a list of network devices. */
+    HDEVINFO hDevInfoList = SetupDiGetClassDevsEx(
+        &GUID_DEVCLASS_NET,
+        NULL,
+        hwndParent,
+        DIGCF_PRESENT,
+        NULL,
+        NULL,
+        NULL);
+    if (hDevInfoList == INVALID_HANDLE_VALUE)
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL, "%s: SetupDiGetClassDevsEx failed", __FUNCTION__);
+        return dwResult;
+    }
+
+    /* Retrieve information associated with a device information set. */
+    SP_DEVINFO_LIST_DETAIL_DATA devinfo_list_detail_data = { .cbSize = sizeof(SP_DEVINFO_LIST_DETAIL_DATA) };
+    if (!SetupDiGetDeviceInfoListDetail(hDevInfoList, &devinfo_list_detail_data))
+    {
+        dwResult = GetLastError();
+        msg(M_NONFATAL, "%s: SetupDiGetDeviceInfoListDetail failed", __FUNCTION__);
+        goto cleanup_hDevInfoList;
+    }
+
+    /* Get the device class GUID as string. */
+    LPOLESTR szDevClassNetId = NULL;
+    StringFromIID((REFIID)&GUID_DEVCLASS_NET, &szDevClassNetId);
+
+    /* Iterate. */
+    *ppAdapter = NULL;
+    struct tap_adapter_node *pAdapterTail = NULL;
+    for (DWORD dwIndex = 0;; dwIndex++)
+    {
+        /* Get the device from the list. */
+        SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
+        if (!SetupDiEnumDeviceInfo(
+                hDevInfoList,
+                dwIndex,
+                &devinfo_data))
+        {
+            if (GetLastError() == ERROR_NO_MORE_ITEMS)
+            {
+                break;
+            }
+            else
+            {
+                /* Something is wrong with this device. Skip it. */
+                msg(M_WARN | M_ERRNO, "%s: SetupDiEnumDeviceInfo(%u) failed", __FUNCTION__, dwIndex);
+                continue;
+            }
+        }
+
+        /* Get device hardware ID(s). */
+        DWORD dwDataType = REG_NONE;
+        LPTSTR szzDeviceHardwareIDs = NULL;
+        dwResult = get_device_reg_property(
+            hDevInfoList,
+            &devinfo_data,
+            SPDRP_HARDWAREID,
+            &dwDataType,
+            (LPVOID)&szzDeviceHardwareIDs);
+        if (dwResult != ERROR_SUCCESS)
+        {
+            /* Something is wrong with this device. Skip it. */
+            continue;
+        }
+
+        /* Check that hardware ID is REG_SZ/REG_MULTI_SZ, and optionally if it matches ours. */
+        if (dwDataType == REG_SZ)
+        {
+            if (szzHwIDs && !_tcszistr(szzHwIDs, szzDeviceHardwareIDs))
+            {
+                /* This is not our device. Skip it. */
+                goto cleanup_szzDeviceHardwareIDs;
+            }
+        }
+        else if (dwDataType == REG_MULTI_SZ)
+        {
+            if (szzHwIDs)
+            {
+                for (LPTSTR s = szzDeviceHardwareIDs;; s += _tcslen(s) + 1)
+                {
+                    if (s[0] == 0)
+                    {
+                        /* This is not our device. Skip it. */
+                        goto cleanup_szzDeviceHardwareIDs;
+                    }
+                    else if (_tcszistr(szzHwIDs, s))
+                    {
+                        /* This is our device. */
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            /* Unexpected hardware ID format. Skip device. */
+            goto cleanup_szzDeviceHardwareIDs;
+        }
+
+        /* Get adapter GUID. */
+        GUID guidAdapter;
+        dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 1, &guidAdapter);
+        if (dwResult != ERROR_SUCCESS)
+        {
+            /* Something is wrong with this device. Skip it. */
+            goto cleanup_szzDeviceHardwareIDs;
+        }
+
+        /* Get the adapter GUID as string. */
+        LPOLESTR szAdapterId = NULL;
+        StringFromIID((REFIID)&guidAdapter, &szAdapterId);
+
+        /* Render registry key path. */
+        TCHAR szRegKey[ADAPTER_REGKEY_PATH_MAX];
+        _stprintf_s(
+            szRegKey, _countof(szRegKey),
+            szAdapterRegKeyPathTemplate,
+            szDevClassNetId,
+            szAdapterId);
+
+        /* Open network adapter registry key. */
+        HKEY hKey = NULL;
+        dwResult = RegOpenKeyEx(
+            HKEY_LOCAL_MACHINE,
+            szRegKey,
+            0,
+            KEY_READ,
+            &hKey);
+        if (dwResult != ERROR_SUCCESS)
+        {
+            SetLastError(dwResult); /* MSDN does not mention RegOpenKeyEx() to set GetLastError(). But we do have an error code. Set last error manually. */
+            msg(M_WARN | M_ERRNO, "%s: RegOpenKeyEx(HKLM, \"%" PRIsLPTSTR "\") failed", __FUNCTION__, szRegKey);
+            goto cleanup_szAdapterId;
+        }
+
+        /* Read adapter name. */
+        LPTSTR szName = NULL;
+        dwResult = get_reg_string(
+            hKey,
+            TEXT("Name"),
+            &szName);
+        if (dwResult != ERROR_SUCCESS)
+        {
+            SetLastError(dwResult);
+            msg(M_WARN | M_ERRNO, "%s: Cannot determine %" PRIsLPOLESTR " adapter name", __FUNCTION__, szAdapterId);
+            goto cleanup_hKey;
+        }
+
+        /* Append to the list. */
+        size_t hwid_size = (_tcszlen(szzDeviceHardwareIDs) + 1) * sizeof(TCHAR);
+        size_t name_size = (_tcslen(szName) + 1) * sizeof(TCHAR);
+        struct tap_adapter_node *node = (struct tap_adapter_node *)malloc(sizeof(struct tap_adapter_node) + hwid_size + name_size);
+        if (node == NULL)
+        {
+            msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeof(struct tap_adapter_node) + hwid_size + name_size);
+            dwResult = ERROR_OUTOFMEMORY; goto cleanup_szName;
+        }
+
+        memcpy(&node->guid, &guidAdapter, sizeof(GUID));
+        node->szzHardwareIDs = (LPTSTR)(node + 1);
+        memcpy(node->szzHardwareIDs, szzDeviceHardwareIDs, hwid_size);
+        node->szName = (LPTSTR)((LPBYTE)node->szzHardwareIDs + hwid_size);
+        memcpy(node->szName, szName, name_size);
+        node->pNext = NULL;
+        if (pAdapterTail)
+        {
+            pAdapterTail->pNext = node;
+            pAdapterTail = node;
+        }
+        else
+        {
+            *ppAdapter = pAdapterTail = node;
+        }
+
+cleanup_szName:
+        free(szName);
+cleanup_hKey:
+        RegCloseKey(hKey);
+cleanup_szAdapterId:
+        CoTaskMemFree(szAdapterId);
+cleanup_szzDeviceHardwareIDs:
+        free(szzDeviceHardwareIDs);
+    }
+
+    dwResult = ERROR_SUCCESS;
+
+    CoTaskMemFree(szDevClassNetId);
+cleanup_hDevInfoList:
+    SetupDiDestroyDeviceInfoList(hDevInfoList);
+    return dwResult;
+}
+
+
+void
+tap_free_adapter_list(
+    _In_ struct tap_adapter_node *pAdapterList)
+{
+    /* Iterate over all nodes of the list. */
+    while (pAdapterList)
+    {
+        struct tap_adapter_node *node = pAdapterList;
+        pAdapterList = pAdapterList->pNext;
+
+        /* Free the adapter node. */
+        free(node);
+    }
+}
diff --git a/src/tapctl/tap.h b/src/tapctl/tap.h
new file mode 100644
index 0000000..63d791c
--- /dev/null
+++ b/src/tapctl/tap.h
@@ -0,0 +1,181 @@
+/*
+ *  tapctl -- Utility to manipulate TUN/TAP adapters on Windows
+ *            https://community.openvpn.net/openvpn/wiki/Tapctl
+ *
+ *  Copyright (C) 2018-2020 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TAP_H
+#define TAP_H
+
+#include <windows.h>
+#include "basic.h"
+
+
+/**
+ * Creates a TUN/TAP adapter.
+ *
+ * @param hwndParent    A handle to the top-level window to use for any user adapter that is
+ *                      related to non-device-specific actions (such as a select-device dialog
+ *                      box that uses the global class driver list). This handle is optional
+ *                      and can be NULL. If a specific top-level window is not required, set
+ *                      hwndParent to NULL.
+ *
+ * @param szDeviceDescription  A pointer to a NULL-terminated string that supplies the text
+ *                      description of the device. This pointer is optional and can be NULL.
+ *
+ * @param szHwId        A pointer to a NULL-terminated string that supplies the hardware id
+ *                      of the device (e.g. "root\\tap0901", "Wintun").
+ *
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,
+ *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ *                      allows the flag to be globally initialized to FALSE and reused for multiple
+ *                      adapter manipulations.
+ *
+ * @param pguidAdapter  A pointer to GUID that receives network adapter ID.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+DWORD
+tap_create_adapter(
+    _In_opt_ HWND hwndParent,
+    _In_opt_ LPCTSTR szDeviceDescription,
+    _In_ LPCTSTR szHwId,
+    _Inout_ LPBOOL pbRebootRequired,
+    _Out_ LPGUID pguidAdapter);
+
+
+/**
+ * Deletes an adapter.
+ *
+ * @param hwndParent    A handle to the top-level window to use for any user adapter that is
+ *                      related to non-device-specific actions (such as a select-device dialog
+ *                      box that uses the global class driver list). This handle is optional
+ *                      and can be NULL. If a specific top-level window is not required, set
+ *                      hwndParent to NULL.
+ *
+ * @param pguidAdapter  A pointer to GUID that contains network adapter ID.
+ *
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,
+ *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ *                      allows the flag to be globally initialized to FALSE and reused for multiple
+ *                      adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+DWORD
+tap_delete_adapter(
+    _In_opt_ HWND hwndParent,
+    _In_ LPCGUID pguidAdapter,
+    _Inout_ LPBOOL pbRebootRequired);
+
+
+/**
+ * Enables or disables an adapter.
+ *
+ * @param hwndParent    A handle to the top-level window to use for any user adapter that is
+ *                      related to non-device-specific actions (such as a select-device dialog
+ *                      box that uses the global class driver list). This handle is optional
+ *                      and can be NULL. If a specific top-level window is not required, set
+ *                      hwndParent to NULL.
+ *
+ * @param pguidAdapter  A pointer to GUID that contains network adapter ID.
+ *
+ * @param bEnable       TRUE to enable; FALSE to disable
+ *
+ * @param pbRebootRequired  A pointer to a BOOL flag. If the device requires a system restart,
+ *                      this flag is set to TRUE. Otherwise, the flag is left unmodified. This
+ *                      allows the flag to be globally initialized to FALSE and reused for multiple
+ *                      adapter manipulations.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+DWORD
+tap_enable_adapter(
+    _In_opt_ HWND hwndParent,
+    _In_ LPCGUID pguidAdapter,
+    _In_ BOOL bEnable,
+    _Inout_ LPBOOL pbRebootRequired);
+
+
+/**
+ * Sets adapter name.
+ *
+ * @param pguidAdapter  A pointer to GUID that contains network adapter ID.
+ *
+ * @param szName        New adapter name - must be unique
+ *
+ * @param bSilent       If true, MSI installer won't display message box and
+ *                      only print error to log.
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ **/
+DWORD
+tap_set_adapter_name(
+    _In_ LPCGUID pguidAdapter,
+    _In_ LPCTSTR szName,
+    _In_ BOOL bSilent);
+
+
+/**
+ * Network adapter list node
+ */
+struct tap_adapter_node
+{
+    GUID guid;             /** Adapter GUID */
+    LPTSTR szzHardwareIDs; /** Device hardware ID(s) */
+    LPTSTR szName;         /** Adapter name */
+
+    struct tap_adapter_node *pNext; /** Pointer to next adapter */
+};
+
+
+/**
+ * Creates a list of existing network adapters.
+ *
+ * @param hwndParent    A handle to the top-level window to use for any user adapter that is
+ *                      related to non-device-specific actions (such as a select-device dialog
+ *                      box that uses the global class driver list). This handle is optional
+ *                      and can be NULL. If a specific top-level window is not required, set
+ *                      hwndParent to NULL.
+ *
+ * @param szzHwIDs      A string of strings that supplies the list of hardware IDs of the device.
+ *                      This pointer is optional and can be NULL. When NULL, all network adapters
+ *                      found are added to the list.
+ *
+ * @param ppAdapterList  A pointer to the list to receive pointer to the first adapter in
+ *                      the list. After the list is no longer required, free it using
+ *                      tap_free_adapter_list().
+ *
+ * @return ERROR_SUCCESS on success; Win32 error code otherwise
+ */
+DWORD
+tap_list_adapters(
+    _In_opt_ HWND hwndParent,
+    _In_opt_ LPCTSTR szzHwIDs,
+    _Out_ struct tap_adapter_node **ppAdapterList);
+
+
+/**
+ * Frees a list of network adapters.
+ *
+ * @param pAdapterList  A pointer to the first adapter in the list to free.
+ */
+void
+tap_free_adapter_list(
+    _In_ struct tap_adapter_node *pAdapterList);
+
+#endif /* ifndef TAP_H */
diff --git a/src/tapctl/tapctl.exe.manifest b/src/tapctl/tapctl.exe.manifest
new file mode 100644
index 0000000..1eb5ea8
--- /dev/null
+++ b/src/tapctl/tapctl.exe.manifest
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
+<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+    <security>
+      <requestedPrivileges>
+        <requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+</assembly>
diff --git a/src/tapctl/tapctl.props b/src/tapctl/tapctl.props
new file mode 100644
index 0000000..0257b9f
--- /dev/null
+++ b/src/tapctl/tapctl.props
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets" />
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup>
+    <GenerateManifest>false</GenerateManifest>
+  </PropertyGroup>
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <PreprocessorDefinitions>_CONSOLE;_WIN32_WINNT=_WIN32_WINNT_VISTA;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>..\compat;$(TAP_WINDOWS_HOME)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup />
+</Project>
\ No newline at end of file
diff --git a/src/tapctl/tapctl.vcxproj b/src/tapctl/tapctl.vcxproj
new file mode 100644
index 0000000..0b5957f
--- /dev/null
+++ b/src/tapctl/tapctl.vcxproj
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="utf-8"?>

+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

+  <ItemGroup Label="ProjectConfigurations">

+    <ProjectConfiguration Include="Debug|ARM64">

+      <Configuration>Debug</Configuration>

+      <Platform>ARM64</Platform>

+    </ProjectConfiguration>

+    <ProjectConfiguration Include="Debug|Win32">

+      <Configuration>Debug</Configuration>

+      <Platform>Win32</Platform>

+    </ProjectConfiguration>

+    <ProjectConfiguration Include="Debug|x64">

+      <Configuration>Debug</Configuration>

+      <Platform>x64</Platform>

+    </ProjectConfiguration>

+    <ProjectConfiguration Include="Release|ARM64">

+      <Configuration>Release</Configuration>

+      <Platform>ARM64</Platform>

+    </ProjectConfiguration>

+    <ProjectConfiguration Include="Release|Win32">

+      <Configuration>Release</Configuration>

+      <Platform>Win32</Platform>

+    </ProjectConfiguration>

+    <ProjectConfiguration Include="Release|x64">

+      <Configuration>Release</Configuration>

+      <Platform>x64</Platform>

+    </ProjectConfiguration>

+  </ItemGroup>

+  <PropertyGroup Label="Globals">

+    <VCProjectVersion>15.0</VCProjectVersion>

+    <ProjectGuid>{A06436E7-D576-490D-8BA0-0751D920334A}</ProjectGuid>

+    <Keyword>Win32Proj</Keyword>

+    <RootNamespace>tapctl</RootNamespace>

+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>

+  </PropertyGroup>

+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">

+    <ConfigurationType>Application</ConfigurationType>

+    <UseDebugLibraries>true</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <CharacterSet>Unicode</CharacterSet>

+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">

+    <ConfigurationType>Application</ConfigurationType>

+    <UseDebugLibraries>true</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <CharacterSet>Unicode</CharacterSet>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">

+    <ConfigurationType>Application</ConfigurationType>

+    <UseDebugLibraries>true</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <CharacterSet>Unicode</CharacterSet>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">

+    <ConfigurationType>Application</ConfigurationType>

+    <UseDebugLibraries>false</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <WholeProgramOptimization>true</WholeProgramOptimization>

+    <CharacterSet>Unicode</CharacterSet>

+    <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">

+    <ConfigurationType>Application</ConfigurationType>

+    <UseDebugLibraries>false</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <WholeProgramOptimization>true</WholeProgramOptimization>

+    <CharacterSet>Unicode</CharacterSet>

+  </PropertyGroup>

+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">

+    <ConfigurationType>Application</ConfigurationType>

+    <UseDebugLibraries>false</UseDebugLibraries>

+    <PlatformToolset>v142</PlatformToolset>

+    <WholeProgramOptimization>true</WholeProgramOptimization>

+    <CharacterSet>Unicode</CharacterSet>

+  </PropertyGroup>

+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />

+  <ImportGroup Label="ExtensionSettings">

+  </ImportGroup>

+  <ImportGroup Label="Shared">

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Debug.props" />

+    <Import Project="tapctl.props" />

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Debug.props" />

+    <Import Project="tapctl.props" />

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Debug.props" />

+    <Import Project="tapctl.props" />

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Release.props" />

+    <Import Project="tapctl.props" />

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Release.props" />

+    <Import Project="tapctl.props" />

+  </ImportGroup>

+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">

+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />

+    <Import Project="..\compat\Release.props" />

+    <Import Project="tapctl.props" />

+  </ImportGroup>

+  <PropertyGroup Label="UserMacros" />

+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" />

+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" />

+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" />

+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" />

+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" />

+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />

+  <ItemGroup>

+    <ClCompile Include="error.c" />

+    <ClCompile Include="tap.c" />

+    <ClCompile Include="main.c" />

+  </ItemGroup>

+  <ItemGroup>

+    <ClInclude Include="basic.h" />

+    <ClInclude Include="error.h" />

+    <ClInclude Include="tap.h" />

+  </ItemGroup>

+  <ItemGroup>

+    <ResourceCompile Include="tapctl_resources.rc" />

+  </ItemGroup>

+  <ItemGroup>

+    <ProjectReference Include="..\..\build\msvc\msvc-generate\msvc-generate.vcxproj">

+      <Project>{8598c2c8-34c4-47a1-99b0-7c295a890615}</Project>

+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>

+    </ProjectReference>

+  </ItemGroup>

+  <ItemGroup>

+    <Manifest Include="tapctl.exe.manifest" />

+  </ItemGroup>

+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

+  <ImportGroup Label="ExtensionTargets">

+  </ImportGroup>

+</Project>
\ No newline at end of file
diff --git a/src/tapctl/tapctl.vcxproj.filters b/src/tapctl/tapctl.vcxproj.filters
new file mode 100644
index 0000000..5d0c597
--- /dev/null
+++ b/src/tapctl/tapctl.vcxproj.filters
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>

+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

+  <ItemGroup>

+    <Filter Include="Source Files">

+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>

+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>

+    </Filter>

+    <Filter Include="Header Files">

+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>

+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>

+    </Filter>

+    <Filter Include="Resource Files">

+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>

+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>

+    </Filter>

+  </ItemGroup>

+  <ItemGroup>

+    <ClCompile Include="tap.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="main.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+    <ClCompile Include="error.c">

+      <Filter>Source Files</Filter>

+    </ClCompile>

+  </ItemGroup>

+  <ItemGroup>

+    <ClInclude Include="tap.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+    <ClInclude Include="error.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+    <ClInclude Include="basic.h">

+      <Filter>Header Files</Filter>

+    </ClInclude>

+  </ItemGroup>

+  <ItemGroup>

+    <ResourceCompile Include="tapctl_resources.rc">

+      <Filter>Resource Files</Filter>

+    </ResourceCompile>

+  </ItemGroup>

+  <ItemGroup>

+    <Manifest Include="tapctl.exe.manifest">

+      <Filter>Resource Files</Filter>

+    </Manifest>

+  </ItemGroup>

+</Project>
\ No newline at end of file
diff --git a/src/tapctl/tapctl_resources.rc b/src/tapctl/tapctl_resources.rc
new file mode 100644
index 0000000..2b3ff23
--- /dev/null
+++ b/src/tapctl/tapctl_resources.rc
@@ -0,0 +1,64 @@
+/*
+ *  tapctl -- Utility to manipulate TUN/TAP adapters on Windows
+ *
+ *  Copyright (C) 2018 Simon Rozman <simon@rozman.si>
+ *
+ *  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; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#else
+#include <config-msvc-version.h>
+#endif
+#include <winresrc.h>
+
+#pragma code_page(65001) /* UTF8 */
+
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+
+VS_VERSION_INFO VERSIONINFO
+    FILEVERSION OPENVPN_VERSION_RESOURCE
+    PRODUCTVERSION OPENVPN_VERSION_RESOURCE
+    FILEFLAGSMASK VS_FF_DEBUG | VS_FF_PRERELEASE | VS_FF_PATCHED | VS_FF_PRIVATEBUILD | VS_FF_SPECIALBUILD
+#ifdef _DEBUG
+    FILEFLAGS VS_FF_DEBUG
+#else
+    FILEFLAGS 0x0L
+#endif
+    FILEOS VOS_NT_WINDOWS32
+    FILETYPE VFT_APP
+    FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904b0"
+        BEGIN
+            VALUE "CompanyName", "The OpenVPN Project"
+            VALUE "FileDescription", "Utility to manipulate TUN/TAP adapters on Windows"
+            VALUE "FileVersion", PACKAGE_VERSION ".0"
+            VALUE "InternalName", "OpenVPN"
+            VALUE "LegalCopyright", "Copyright © The OpenVPN Project"
+            VALUE "OriginalFilename", "tapctl.exe"
+            VALUE "ProductName", "OpenVPN"
+            VALUE "ProductVersion", PACKAGE_VERSION ".0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1200
+    END
+END
+
+1 RT_MANIFEST "tapctl.exe.manifest"
diff --git a/version.m4 b/version.m4
index 2e23539..bbb6372 100644
--- a/version.m4
+++ b/version.m4
@@ -2,13 +2,13 @@
 define([PRODUCT_NAME], [OpenVPN])
 define([PRODUCT_TARNAME], [openvpn])
 define([PRODUCT_VERSION_MAJOR], [2])
-define([PRODUCT_VERSION_MINOR], [4])
-define([PRODUCT_VERSION_PATCH], [.9])
+define([PRODUCT_VERSION_MINOR], [5])
+define([PRODUCT_VERSION_PATCH], [.2])
 m4_append([PRODUCT_VERSION], [PRODUCT_VERSION_MAJOR])
 m4_append([PRODUCT_VERSION], [PRODUCT_VERSION_MINOR], [[.]])
 m4_append([PRODUCT_VERSION], [PRODUCT_VERSION_PATCH], [[]])
 define([PRODUCT_BUGREPORT], [openvpn-users@lists.sourceforge.net])
-define([PRODUCT_VERSION_RESOURCE], [2,4,9,0])
+define([PRODUCT_VERSION_RESOURCE], [2,5,2,0])
 dnl define the TAP version
 define([PRODUCT_TAP_WIN_COMPONENT_ID], [tap0901])
 define([PRODUCT_TAP_WIN_MIN_MAJOR], [9])