Skip to contentSkip to conversation

Transport API: offload traffic manipulation to plugins

PATCH In review Antonio Quartulli 3 participants 14 messages Dec 30, 2018 – Jan 29, 2019

lore
Share & tools
lore
#0 lore raw

cover Transport API: offload traffic manipulation to plugins

From: Antonio Quartulli <[email protected]...>

Dear all,

This patchset implements a new API called "Transport API" which allows the core codebase to offload traffic/socket manipulations to an external plugin.

To understand its potential, imagine you want to apply a specific "mutation" on any packet going out and apply the its inverse, on the receiver side, for any incoming packet.
Without this patchset, achieving this result would require a big surgery through the OpenVPN code, and it would be the same for any new mutation one wants to implements. Thanks to this new API the deemed effort
is drastically reduced.

In a few words, OpenVPN becomes extendible on a new front, by cutting the need to touch the core codebase any longer.

An easy to imagine use case for this new API would be any "traffic obfuscation technique": instead of patching the core code (like the famous "xor patch"), a developer is now allowed to implement an external obfuscation plugin and distribute/maintain it indepdendently.

This patchset comes with a sample plugin (obfs-test) which can be used as starting point for new implementations.

Any feedback is welcome!

Note: the code has gone through the following tests with positive outcome:
- uncrustify
- GitLab-CI (with my configs)
- openvpn community buildbots

Since it is quite a change (even though most of the new code is fairly isolated in its own module - transport.c/h), I expect more comments/discussion/bugs to spark from here.

In any case, please consider myself as direct contact point to discuss further changes and improvements (even though I am not the signer of the patches). I'll be in responsible for ensuring this code gets merged sooner rather than later.

Thanks a lot!!

== Credits ==

This code has been developed by the Operator Foundation[1], under the umbrella project called "Jigsaw"[2], mainly promoted/sponsored by Google, Inc.

Brandon, reading this email in CC, has been the contact point on the Operator Foundation's side, while Justin (in CC as well), managed the task on Google's behalf.

OpenVPN, Inc. has offered its support to the cause by allowing its devs to allocate a number of hours to follow the project and ensure it could get all the guidance it required.

[1] https://operatorfoundation.org/
[2] https://jigsaw.google.com/

Best Regards,

Robin Tarsiger (4):
transport: introduce tranport API plugin codebase
socket: introduce INDIRECT transport protocol abstraction
options: add support for --transport-plugin
transport-plugin: add sample obfs-test plugin

 configure.ac                              |   9 +
 doc/openvpn.8                             |  40 ++
 include/Makefile.am                       |   1 +
 include/openvpn-plugin.h.in               |  31 +-
 include/openvpn-transport.h               | 240 +++++++++
 src/openvpn/Makefile.am                   |   1 +
 src/openvpn/forward.c                     |   5 +
 src/openvpn/init.c                        |   1 +
 src/openvpn/options.c                     |  31 ++
 src/openvpn/options.h                     |   1 +
 src/openvpn/plugin.c                      |   4 +
 src/openvpn/plugin.h                      |   1 +
 src/openvpn/socket.c                      | 148 +++++-
 src/openvpn/socket.h                      |  74 +++
 src/openvpn/transport.c                   | 303 +++++++++++
 src/openvpn/transport.h                   |  99 ++++
 src/plugins/Makefile.am                   |   2 +-
 src/plugins/obfs-test/Makefile.am         |  29 ++
 src/plugins/obfs-test/README.obfs-test    |  26 +
 src/plugins/obfs-test/obfs-test-args.c    |  60 +++
 src/plugins/obfs-test/obfs-test-munging.c | 129 +++++
 src/plugins/obfs-test/obfs-test-posix.c   | 207 ++++++++
 src/plugins/obfs-test/obfs-test-win32.c   | 579 ++++++++++++++++++++++
 src/plugins/obfs-test/obfs-test.c         |  94 ++++
 src/plugins/obfs-test/obfs-test.exports   |   4 +
 src/plugins/obfs-test/obfs-test.h         |  42 ++
 26 files changed, 2155 insertions(+), 6 deletions(-)
 create mode 100644 include/openvpn-transport.h
 create mode 100644 src/openvpn/transport.c
 create mode 100644 src/openvpn/transport.h
 create mode 100644 src/plugins/obfs-test/Makefile.am
 create mode 100644 src/plugins/obfs-test/README.obfs-test
 create mode 100644 src/plugins/obfs-test/obfs-test-args.c
 create mode 100644 src/plugins/obfs-test/obfs-test-munging.c
 create mode 100644 src/plugins/obfs-test/obfs-test-posix.c
 create mode 100644 src/plugins/obfs-test/obfs-test-win32.c
 create mode 100644 src/plugins/obfs-test/obfs-test.c
 create mode 100644 src/plugins/obfs-test/obfs-test.exports
 create mode 100644 src/plugins/obfs-test/obfs-test.h
Signature · 1 line
2.19.2
#1 lore raw

PATCH 1/4 transport: introduce tranport API plugin codebase

From: Robin Tarsiger <[email protected]...>

This API will allow plugins to provide virtual socket implementations with their own table of functions for operations on these sockets, with limited but functional integration with the main OpenVPN event loop.

This new mechanism can be used to implement alternative transport protocols via external plugins, without interfering with the OpenVPN core codebase.

A sample user of this API might be an obsufcation schema which manipulates the traffic before sending/receiving it.

[[email protected]...: refactored commits, restyled code]
---
 include/Makefile.am         |   1 +
 include/openvpn-plugin.h.in |  31 +++-
 include/openvpn-transport.h | 240 ++++++++++++++++++++++++++++
 src/openvpn/Makefile.am     |   1 +
 src/openvpn/plugin.c        |   4 +
 src/openvpn/plugin.h        |   1 +
 src/openvpn/socket.h        |   3 +
 src/openvpn/transport.c     | 303 ++++++++++++++++++++++++++++++++++++
 src/openvpn/transport.h     |  94 +++++++++++
 9 files changed, 677 insertions(+), 1 deletion(-)
 create mode 100644 include/openvpn-transport.h
 create mode 100644 src/openvpn/transport.c
 create mode 100644 src/openvpn/transport.h
include/Makefile.am +1 -0
index 484e4e12..5c0ffcc4 100644
@@ -15,4 +15,5 @@ MAINTAINERCLEANFILES = \
 
 include_HEADERS = \
 	openvpn-plugin.h \
+	openvpn-transport.h \
 	openvpn-msg.h
include/openvpn-plugin.h.in +30 -1
index 103844f7..3539e534 100644
@@ -129,7 +129,8 @@ extern "C" {
 #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_TRANSPORT             13
+#define OPENVPN_PLUGIN_N                     14
 
 /*
  * Build a mask out of a set of plug-in types.
@@ -852,6 +853,34 @@ OPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_select_initialization_
 OPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_min_version_required_v1)
     (void);
 
+/*
+ * FUNCTION: openvpn_plugin_get_vtab_v1
+ *
+ * This is only used for TRANSPORT plugins presently.  It is called to
+ * retrieve a vtable structure to be used for binding virtual sockets
+ * which use the transport provided by the plugin. The selector is an
+ * OPENVPN_VTAB constant. *size_out must be set to the size of the
+ * structure returned.
+ *
+ * REQUIRED: only for TRANSPORT plugins
+ *
+ * RETURN VALUE
+ *
+ * A pointer to a vtable of the requested type, or NULL if no
+ * such vtable is provided by this plugin.
+ */
+
+enum {
+    /* Return type: struct openvpn_transport_bind_vtab1 *
+       Plugin should provide OPENVPN_PLUGIN_TRANSPORT at open time.
+     */
+    OPENVPN_VTAB_TRANSPORT_BIND_V1 = 0x54726e31 /* 'Trn1' */
+};
+
+
+OPENVPN_PLUGIN_DEF void *OPENVPN_PLUGIN_FUNC(openvpn_plugin_get_vtab_v1)
+    (int selector, size_t *size_out);
+
 /*
  * Deprecated functions which are still supported for backward compatibility.
  */
include/openvpn-transport.h new file +240 -0

240 lines not on this page · raw message

src/openvpn/Makefile.am +1 -0
index 197e62ba..41cd90aa 100644
@@ -119,6 +119,7 @@ openvpn_SOURCES = \
 	status.c status.h \
 	syshead.h \
 	tls_crypt.c tls_crypt.h \
+	transport.c transport.h \
 	tun.c tun.h \
 	win32.h win32.c \
 	cryptoapi.h cryptoapi.c
src/openvpn/plugin.c +4 -0
index b9708342..2ab81842 100644
@@ -119,6 +119,9 @@ plugin_type_name(const int type)
         case OPENVPN_PLUGIN_ROUTE_PREDOWN:
             return "PLUGIN_ROUTE_PREDOWN";
 
+        case OPENVPN_PLUGIN_TRANSPORT:
+            return "PLUGIN_TRANSPORT";
+
         default:
             return "PLUGIN_???";
     }
@@ -295,6 +298,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o)
     PLUGIN_SYM(client_destructor, "openvpn_plugin_client_destructor_v1", 0);
     PLUGIN_SYM(min_version_required, "openvpn_plugin_min_version_required_v1", 0);
     PLUGIN_SYM(initialization_point, "openvpn_plugin_select_initialization_point_v1", 0);
+    PLUGIN_SYM(get_vtab, "openvpn_plugin_get_vtab_v1", 0);
 
     if (!p->open1 && !p->open2 && !p->open3)
     {
src/openvpn/plugin.h +1 -0
index 791d476b..435c4763 100644
@@ -76,6 +76,7 @@ struct plugin {
     openvpn_plugin_client_destructor_v1 client_destructor;
     openvpn_plugin_min_version_required_v1 min_version_required;
     openvpn_plugin_select_initialization_point_v1 initialization_point;
+    openvpn_plugin_get_vtab_v1 get_vtab;
 
     openvpn_plugin_handle_t plugin_handle;
 };
src/openvpn/socket.h +3 -0
index 17801418..f49e6315 100644
@@ -572,6 +572,9 @@ enum proto_num {
     PROTO_TCP,
     PROTO_TCP_SERVER,
     PROTO_TCP_CLIENT,
+#ifdef ENABLE_PLUGIN
+    PROTO_INDIRECT,
+#endif
     PROTO_N
 };
 
src/openvpn/transport.c new file +303 -0

303 lines not on this page · raw message

src/openvpn/transport.h new file +94 -0

94 lines not on this page · raw message

Signature · 1 line
2.19.2
#4 lore raw

PATCH 2/4 socket: introduce INDIRECT transport protocol abstraction

In reply to Antonio Quartulli

From: Robin Tarsiger <[email protected]...>

This new transport protocol is used to tell the core code that traffic should not be directly processed, but should rather be rerouted to a transport plugin. It is basically an abstraction as it does not say tell the code how to process the data, but simply forces its redirection to the external code.

[[email protected]...: refactored commits, restyled code]
---
 src/openvpn/forward.c   |   5 ++
 src/openvpn/socket.c    | 146 ++++++++++++++++++++++++++++++++++++++--
 src/openvpn/socket.h    |  70 +++++++++++++++++++
 src/openvpn/transport.h |   5 ++
 4 files changed, 222 insertions(+), 4 deletions(-)
src/openvpn/forward.c +5 -0
index 0a90fff0..a7092c7e 100644
@@ -2150,6 +2150,11 @@ io_wait_dowork(struct context *c, const unsigned int flags)
             {
                 int i;
                 c->c2.event_set_status = 0;
+#ifdef ENABLE_PLUGIN
+                c->c2.event_set_status |=
+                    (socket_indirect_pump(c->c2.link_socket, esr, &status) & 3)
+                        << socket_shift;
+#endif
                 for (i = 0; i < status; ++i)
                 {
                     const struct event_set_return *e = &esr[i];
src/openvpn/socket.c +142 -4

243 lines not on this page · raw message

src/openvpn/socket.h +70 -0

112 lines not on this page · raw message

src/openvpn/transport.h +5 -0
index 344ce44b..37050eaf 100644
@@ -27,6 +27,11 @@
 #include "plugin.h"
 #include "openvpn-transport.h"
 
+/* INDIRECT does not have any overhead per se, but it depends on what is
+ * implemented by the transport plugin
+ */
+#define INDIRECT_HEADER_SIZE    0
+
 /* Given a list of plugins and an argument list for a desired
  * transport plugin instance, prepare to bind new link sockets using
  * that transport plugin and args. If all succeeds, return true, and:
Signature · 1 line
2.19.2
#8 lore raw

PATCH 3/4 options: add support for --transport-plugin

In reply to Antonio Quartulli

From: Robin Tarsiger <[email protected]...>

Add a new config option to allow the user to specify a transport plugin implementing the new API. This plugin can be used to manipulate traffic in any way, as designed by the plugin developer.

The fondamental advantage of this plugin is that the core codebase does not need to know anything about its implementation, as soon as it implements the transport API properly.

A plugin specified with --transport-plugin must be already loaded via --plugin. --transport-plugin is a per-connection-block option and specifies which plugin to use for this particular connection. It can take additional arguments, if required by the specific plugin.

The manpage has been extended accordingly.

[[email protected]...: refactored commits, restyled code]
---
 doc/openvpn.8         | 40 ++++++++++++++++++++++++++++++++++++++++
 src/openvpn/init.c    |  1 +
 src/openvpn/options.c | 31 +++++++++++++++++++++++++++++++
 src/openvpn/options.h |  1 +
 src/openvpn/socket.c  |  2 ++
 src/openvpn/socket.h  |  1 +
 6 files changed, 76 insertions(+)
doc/openvpn.8 +40 -0
index 7abcaf1e..9325dabd 100644
@@ -2847,6 +2847,46 @@ every module and script must return success (0) in order for
 the connection to be authenticated.
 .\"*********************************************************
 .TP
+.B \-\-transport-plugin module-pathname [connection-args]
+Use the loaded plugin module identified by
+.B module-pathname
+to provide a transport layer for the connection. The
+.B module-pathname
+must be exactly equivalent to a pathname supplied to a
+.B \-\-plugin
+option. The same transport plugin may be used for
+multiple connections, in which case the
+.B \-\-plugin
+option which loads it should only occur once. However,
+only one transport plugin may be specified per
+connection.
+
+If
+.B connection-args
+are present, these arguments are passed to the transport
+plugin when establishing this connection specifically; this
+is distinct from any per-plugin arguments which may have
+been specified using the
+.B \-\-plugin
+option. Documentation for possible
+.B connection-args
+may be provided along with the plugin in use.
+
+When a transport plugin is in use, the
+.B \-\-proto
+option should not normally be used and will usually result in
+an error, as the transport plugin takes over from the native
+transport protocol that would otherwise be specified. The
+rest of OpenVPN will operate in a manner similar to that of
+UDP mode, using the pseudo-protocol "indirect". There is one
+remaining rare use for
+.B \-\-proto
+in this case, which is to force a specific address family for
+transport plugins for which this is still meaningful. This can
+be done by specifying "indirect4" or "indirect6" as the
+protocol.
+.\"*********************************************************
+.TP
 .B \-\-keying\-material\-exporter label len
 Save Exported Keying Material [RFC5705] of len bytes (must be
 between 16 and 4095 bytes) using label in environment
src/openvpn/init.c +1 -0
index 560d87db..9f7b5fdd 100644
@@ -3335,6 +3335,7 @@ do_init_socket_1(struct context *c, const int mode)
                             &c->c1.link_socket_addr,
                             c->options.ipchange,
                             c->plugins,
+                            c->options.ce.transport_plugin_argv,
                             c->options.resolve_retry_seconds,
                             c->options.ce.mtu_discover_type,
                             c->options.rcvbuf,
src/openvpn/options.c +31 -0
index 0cf8db76..7e905532 100644
@@ -403,6 +403,9 @@ static const char usage_message[] =
 #ifdef ENABLE_PLUGIN
     "--plugin m [str]: Load plug-in module m passing str as an argument\n"
     "                  to its initialization function.\n"
+    "--transport-plugin m [args]: Use plug-in module m to provide the transport\n"
+    "                             layer, with optional per-connection args. The\n"
+    "                             module must already be loaded with --plugin.\n"
 #endif
 #if P2MP
 #if P2MP_SERVER
@@ -2005,6 +2008,22 @@ options_postprocess_verify_ce(const struct options *options, const struct connec
         msg(M_USAGE, "--proto tcp is ambiguous in this context.  Please specify --proto tcp-server or --proto tcp-client");
     }
 
+#ifdef ENABLE_PLUGIN
+    /*
+     * "proto indirect" may not be specified directly without a
+     * transport-plugin, and vice versa.
+     */
+    if (ce->proto == PROTO_INDIRECT && !ce->transport_plugin_argv)
+    {
+        msg(M_USAGE, "--proto indirect may not be used without a transport-plugin line");
+    }
+
+    if (ce->transport_plugin_argv && ce->proto != PROTO_INDIRECT)
+    {
+        msg(M_USAGE, "--transport-plugin must be used with --proto indirect");
+    }
+#endif
+
     /*
      * Sanity check on daemon/inetd modes
      */
@@ -5190,6 +5209,18 @@ add_option(struct options *options,
             goto err;
         }
     }
+    else if (streq(p[0], "transport-plugin") && p[1])
+    {
+        VERIFY_PERMISSION(OPT_P_PLUGIN|OPT_P_CONNECTION);
+
+        /* p[1] is the shared object name, which becomes
+         * argv[0]. p[2..] are connection-specific transport
+         * parameters, which become argv[1..].
+         */
+        options->ce.transport_plugin_argv = make_extended_arg_array(&p[1],
+                                                                    &options->gc);
+        options->ce.proto = PROTO_INDIRECT;
+    }
 #endif
     else if (streq(p[0], "mode") && p[1] && !p[2])
     {
src/openvpn/options.h +1 -0
index e2b38939..c2d0e9ac 100644
@@ -85,6 +85,7 @@ struct options_pre_pull
 
 struct connection_entry
 {
+    const char **transport_plugin_argv;
     int proto;
     sa_family_t af;
     const char *local_port;
src/openvpn/socket.c +2 -0
index b548ab7a..e8f790ea 100644
@@ -1916,6 +1916,7 @@ link_socket_init_phase1(struct link_socket *sock,
                         struct link_socket_addr *lsa,
                         const char *ipchange_command,
                         const struct plugin_list *plugins,
+                        const char **transport_plugin_argv,
                         int resolve_retry_seconds,
                         int mtu_discover_type,
                         int rcvbuf,
@@ -1955,6 +1956,7 @@ link_socket_init_phase1(struct link_socket *sock,
     sock->info.bind_ipv6_only = bind_ipv6_only;
     sock->info.ipchange_command = ipchange_command;
     sock->info.plugins = plugins;
+    sock->info.transport_plugin_argv = transport_plugin_argv;
     sock->server_poll_timeout = server_poll_timeout;
 
     sock->mode = mode;
src/openvpn/socket.h +1 -0
index 73a4ab6f..eb0b2a73 100644
@@ -327,6 +327,7 @@ link_socket_init_phase1(struct link_socket *sock,
                         struct link_socket_addr *lsa,
                         const char *ipchange_command,
                         const struct plugin_list *plugins,
+                        const char **transport_plugin_argv,
                         int resolve_retry_seconds,
                         int mtu_discover_type,
                         int rcvbuf,
Signature · 1 line
2.19.2
#11 lore raw

PATCH 4/4 transport-plugin: add sample obfs-test plugin

In reply to Antonio Quartulli

From: Robin Tarsiger <[email protected]...>

Add a sample plugin to explain how the new transport API is expected to be implemented and work. It can be used for testing.

[[email protected]...: refactored commits, restyled code]
---
 configure.ac                              |   9 +
 src/plugins/Makefile.am                   |   2 +-
 src/plugins/obfs-test/Makefile.am         |  29 ++
 src/plugins/obfs-test/README.obfs-test    |  26 +
 src/plugins/obfs-test/obfs-test-args.c    |  60 +++
 src/plugins/obfs-test/obfs-test-munging.c | 129 +++++
 src/plugins/obfs-test/obfs-test-posix.c   | 207 ++++++++
 src/plugins/obfs-test/obfs-test-win32.c   | 579 ++++++++++++++++++++++
 src/plugins/obfs-test/obfs-test.c         |  94 ++++
 src/plugins/obfs-test/obfs-test.exports   |   4 +
 src/plugins/obfs-test/obfs-test.h         |  42 ++
 11 files changed, 1180 insertions(+), 1 deletion(-)
 create mode 100644 src/plugins/obfs-test/Makefile.am
 create mode 100644 src/plugins/obfs-test/README.obfs-test
 create mode 100644 src/plugins/obfs-test/obfs-test-args.c
 create mode 100644 src/plugins/obfs-test/obfs-test-munging.c
 create mode 100644 src/plugins/obfs-test/obfs-test-posix.c
 create mode 100644 src/plugins/obfs-test/obfs-test-win32.c
 create mode 100644 src/plugins/obfs-test/obfs-test.c
 create mode 100644 src/plugins/obfs-test/obfs-test.exports
 create mode 100644 src/plugins/obfs-test/obfs-test.h
configure.ac +9 -0
index 1e6891b1..b4196812 100644
@@ -200,6 +200,13 @@ AC_ARG_ENABLE(
 	]
 )
 
+AC_ARG_ENABLE(
+	[plugin-obfs-test],
+	[AS_HELP_STRING([--disable-plugin-obfs-test], [disable obfs-test plugin @<:@default=platform specific@:>@])],
+	,
+	[enable_plugin_obfs_test="no"]
+)
+
 AC_ARG_ENABLE(
 	[pam-dlopen],
 	[AS_HELP_STRING([--enable-pam-dlopen], [dlopen libpam @<:@default=no@:>@])],
@@ -1344,6 +1351,7 @@ AM_CONDITIONAL([WIN32], [test "${WIN32}" = "yes"])
 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_PLUGIN_OBFS_TEST], [test "${enable_plugin_obfs_test}" = "yes"])
 AM_CONDITIONAL([HAVE_LD_WRAP_SUPPORT], [test "${have_ld_wrap_support}" = "yes"])
 
 sampledir="\$(docdir)/sample"
@@ -1403,6 +1411,7 @@ AC_CONFIG_FILES([
 	src/plugins/Makefile
 	src/plugins/auth-pam/Makefile
 	src/plugins/down-root/Makefile
+	src/plugins/obfs-test/Makefile
 	tests/Makefile
         tests/unit_tests/Makefile
         tests/unit_tests/example_test/Makefile
src/plugins/Makefile.am +1 -1
index f3461786..848bac03 100644
@@ -12,4 +12,4 @@
 MAINTAINERCLEANFILES = \
 	$(srcdir)/Makefile.in
 
-SUBDIRS = auth-pam down-root
+SUBDIRS = auth-pam down-root obfs-test
src/plugins/obfs-test/Makefile.am new file +29 -0
new file mode 100644
index 00000000..4cc8d183
@@ -0,0 +1,29 @@
+MAINTAINERCLEANFILES = \
+	$(srcdir)/Makefile.in
+
+AM_CFLAGS = \
+	-I$(top_srcdir)/include \
+	$(OPTIONAL_CRYPTO_CFLAGS)
+
+if ENABLE_PLUGIN_OBFS_TEST
+plugin_LTLIBRARIES = openvpn-plugin-obfs-test.la
+endif
+
+openvpn_plugin_obfs_test_la_SOURCES = \
+	obfs-test.c \
+	obfs-test-munging.c \
+	obfs-test-args.c \
+	obfs-test.exports
+
+if WIN32
+openvpn_plugin_obfs_test_la_SOURCES += obfs-test-win32.c
+openvpn_plugin_obfs_test_la_LIBADD = -lws2_32 -lwininet
+else !WIN32
+openvpn_plugin_obfs_test_la_SOURCES += obfs-test-posix.c
+# No LIBADD necessary; we assume we can access the global symbol space,
+# and core OpenVPN will already link with everything needed for sockets.
+endif
+
+openvpn_plugin_obfs_test_la_LDFLAGS = $(AM_LDFLAGS) \
+	-export-symbols "$(srcdir)/obfs-test.exports" \
+	-module -shared -avoid-version -no-undefined
src/plugins/obfs-test/README.obfs-test new file +26 -0
new file mode 100644
index 00000000..5492ee02
@@ -0,0 +1,26 @@
+obfs-test
+
+SYNOPSIS
+
+The obfs-test plugin is a proof of concept for supporting protocol
+obfuscation for OpenVPN via a socket intercept plugin.
+
+BUILD
+
+You must specify --enable-plugin-obfs-test at configure time to
+trigger building this plugin. It should function on POSIX-y platforms
+and Windows.
+
+USAGE
+
+To invoke this plugin, load it via an appropriate plugin line in the
+configuration file, and then specify 'proto indirect' rather than any
+other protocol. Packets will then be passed via UDP, but they will
+also undergo a very basic content transformation, and the bind port
+will be altered (see obfs-test-munging.c for details).
+
+CAVEATS
+
+This has undergone basic functionality testing, but not any kind of
+full-on stress test. Extended socket or I/O handling options are not
+supported at all.
src/plugins/obfs-test/obfs-test-args.c new file +60 -0

60 lines not on this page · raw message

src/plugins/obfs-test/obfs-test-munging.c new file +129 -0

129 lines not on this page · raw message

src/plugins/obfs-test/obfs-test-posix.c new file +207 -0

207 lines not on this page · raw message

src/plugins/obfs-test/obfs-test-win32.c new file +579 -0

579 lines not on this page · raw message

src/plugins/obfs-test/obfs-test.c new file +94 -0

94 lines not on this page · raw message

src/plugins/obfs-test/obfs-test.exports new file +4 -0
new file mode 100644
index 00000000..e7baada4
@@ -0,0 +1,4 @@
+openvpn_plugin_open_v3
+openvpn_plugin_close_v1
+openvpn_plugin_get_vtab_v1
+openvpn_plugin_func_v3
src/plugins/obfs-test/obfs-test.h new file +42 -0

42 lines not on this page · raw message

Signature · 1 line
2.19.2
#2 lore raw

Re: [Openvpn-devel] [PATCH 1/4] transport: introduce tranport API plugin codebase

In reply to Antonio Quartulli
Quoted 4 lines

+/*
+ * FUNCTION: openvpn_plugin_get_vtab_v1
+ *

It would be nice if we also use the docutils style to document the new functions in this file rather than is this different documentation style.

Quoted 5 lines

+ * This is only used for TRANSPORT plugins presently. It is called to
+ * retrieve a vtable structure to be used for binding virtual sockets
+ * which use the transport provided by the plugin. The selector is an
+ * OPENVPN_VTAB constant. *size_out must be set to the size of the
+ * structure returned.

A reference to openvpn_transport_bind_vtab1 might be a good idea here.

Quoted 1 line

+/* On Windows, platform-native events to wait on are provided to OpenVPN core as

I think for multi line comments we use the following style

/*
* first line of text
* last line of text
*/

but not sure. Might want to double check that style.

Rest of the file look fine but I also would like to see docstyle comments.

Quoted 83 lines
(quoted diff) +1 -0
+#endif
src/openvpn/Makefile.am +70 -0
index 197e62ba..41cd90aa 100644
@@ -119,6 +119,7 @@ openvpn_SOURCES = \
 	status.c status.h \
 	syshead.h \
 	tls_crypt.c tls_crypt.h \
+	transport.c transport.h \
 	tun.c tun.h \
 	win32.h win32.c \
 	cryptoapi.h cryptoapi.c
 
+openvpn_transport_socket_t
+transport_bind(const struct plugin_list *plugins,
+               const char **transport_plugin_argv, sa_family_t ai_family,
+               struct addrinfo *bind_addresses)
+{
+    openvpn_plugin_handle_t handle;
+    openvpn_transport_args_t args;
+    openvpn_transport_socket_t indirect;
+    struct openvpn_transport_bind_vtab1 *vtab;
+    struct addrinfo *cur = NULL;
+    struct openvpn_sockaddr zero;
+
+    if (!transport_prepare(plugins, transport_plugin_argv, &vtab, &handle,
+                           &args))
+    {
+        msg(M_FATAL, "INDIRECT: Socket bind failed: provider plugin not found");
+    }
+
+    /* Partially replicates the functionality of socket_bind. No bind_ipv6_only
+     * or other such options, presently.
+     */
+    if (bind_addresses)
+    {
+        for (cur = bind_addresses; cur; cur = cur->ai_next)
+        {
+            if (cur->ai_family == ai_family)
+            {
+                break;
+            }
+        }
+
+        if (!cur)
+        {
+            msg(M_FATAL, "INDIRECT: Socket bind failed: Addr to bind has no %s record",
+                addr_family_name(ai_family));
+        }
+    }
+
+    if (cur)
+    {
+        indirect = vtab->bind(handle, args, cur->ai_addr, cur->ai_addrlen);
+    }
+    else if (ai_family == AF_UNSPEC)
+    {
+        msg(M_ERR, "INDIRECT: cannot bind with unspecified address family");
+    }
+    else
+    {
+        memset(&zero, 0, sizeof(zero));
+        zero.addr.sa.sa_family = ai_family;
+        addr_zero_host(&zero);
+        indirect = vtab->bind(handle, args, &zero.addr.sa, af_addr_size(ai_family));
+    }
+
+    if (!indirect)
+    {
+        msg(M_ERR, "INDIRECT: Socket bind failed");
+    }
+
+    if (vtab->freeargs)
+    {
+        vtab->freeargs(args);
+    }
+
+    return indirect;
+}
+
+struct encapsulated_event_set
+{

src/openvpn/Makefile.am openvpn_SOURCES = \

Quoted 10 lines
src/openvpn/Makefile.am +10 -0
+    struct openvpn_transport_event_set_handle handle;
+    struct event_set *real;
+};
+
+#if EVENT_READ == OPENVPN_TRANSPORT_EVENT_READ \
+    && EVENT_WRITE == OPENVPN_TRANSPORT_EVENT_WRITE
+#define TRANSPORT_EVENT_BITS_IDENTICAL 1
+#else
+#define TRANSPORT_EVENT_BITS_IDENTICAL 0
+#endif

A small comment when this is identical like Win32 vs posix-y would be nice.

src/openvpn/Makefile.am

Quoted 5 lines
src/openvpn/Makefile.am +5 -0
+
+unsigned
+transport_pump(openvpn_transport_socket_t indirect,
+               struct event_set_return *esr, int *esrlen)
+{

The name 'pump' is confusing, especially without comment. Why not transport_update_event? Something that tells what the function actually does.

Arne

#5 lore raw

Re: [Openvpn-devel] [PATCH 2/4] socket: introduce INDIRECT transport protocol abstraction

In reply to Antonio Quartulli

Am 30.12.18 um 12:28 schrieb Antonio Quartulli:

Quoted 22 lines

From: Robin Tarsiger <[email protected]...>

This new transport protocol is used to tell the core code that traffic should not be directly processed, but should rather be rerouted to a transport plugin. It is basically an abstraction as it does not say tell the code how to process the data, but simply forces its redirection to the external code.

[[email protected]...: refactored commits, restyled code]
---
 src/openvpn/forward.c   |   5 ++
 src/openvpn/socket.c    | 146 ++++++++++++++++++++++++++++++++++++++--
 src/openvpn/socket.h    |  70 +++++++++++++++++++
 src/openvpn/transport.h |   5 ++
 4 files changed, 222 insertions(+), 4 deletions(-)
src/openvpn/forward.c +0 -0
index 0a90fff0..a7092c7e 100644

src/openvpn/forward.c io_wait_dowork(struct context *c, const unsigned int flags) · line 2155

Quoted 6 lines
src/openvpn/forward.c +3 -0
@@ -2150,6 +2150,11 @@ io_wait_dowork(struct context *c, const unsigned int flags)
             {
                 int i;
                 c->c2.event_set_status = 0;
+#ifdef ENABLE_PLUGIN
+                c->c2.event_set_status |=
+                    (socket_indirect_pump(c->c2.link_socket, esr, &status) & 3)

The & 3 looks like some defined should be used instead.

Quoted 17 lines
src/openvpn/forward.c +5 -1
     if (addr->ai_protocol == IPPROTO_UDP || addr->ai_socktype == SOCK_DGRAM)
     {
         sock->sd = create_socket_udp(addr, sock->sockflags);
@@ -2279,7 +2320,11 @@ link_socket_init_phase2(struct link_socket *sock,
         }
 
         /* If socket has not already been created create it now */
-        if (sock->sd == SOCKET_UNDEFINED)
+        if (sock->sd == SOCKET_UNDEFINED
+#ifdef ENABLE_PLUGIN
+            && !sock->indirect
+#endif
+            )
         {
             /* If we have no --remote and have still not figured out the
              * protocol family to use we will use the first of the bind */

src/openvpn/forward.c link_socket_init_phase2(struct link_socket *sock, · line 2352

Quoted 9 lines
src/openvpn/forward.c +5 -1
@@ -2300,7 +2345,11 @@ link_socket_init_phase2(struct link_socket *sock,
         }
 
         /* Socket still undefined, give a warning and abort connection */
-        if (sock->sd == SOCKET_UNDEFINED)
+        if (sock->sd == SOCKET_UNDEFINED
+#ifdef ENABLE_PLUGIN
+            && !sock->indirect
+#endif
+            )

Better use the inline funtcion proto_is_indirect (or similar sock_is_indirect) that always returns false here and ifdef in header than to add the ifdefs inline in the code.

src/openvpn/forward.c proto_is_net(int proto) · line 3244

Quoted 11 lines
src/openvpn/forward.c +4 -0
 bool
@@ -3167,6 +3236,10 @@ proto_is_net(int proto)
 bool
 proto_is_dgram(int proto)
 {
+    if (proto_is_indirect(proto))
+    {
+        return true;
+    }
     return proto_is_udp(proto);
 }

I think here need to be a good explaination why indirect is dgram but not udp and also proto_is_dgram and proto_is_udp need to get some comment to explain their difference in usage as this is now different.

Quoted 16 lines
src/openvpn/forward.c +11 -0
@@ -3301,6 +3374,18 @@ proto_remote(int proto, bool remote)
         return "TCPv4_CLIENT";
     }
 
+#ifdef ENABLE_PLUGIN
+    if (proto == PROTO_INDIRECT)
+    {
+        /* FIXME: the string reported here should match the actual transport
+         * protocol being used, however in this function we have no knowledge of
+         * what protocol is exactly being used by the transport-plugin.
+         * Therefore we simply return INDIRECT for now.
+         */
+        return "INDIRECT";
+    }
+#endif
#9 lore raw

Re: [Openvpn-devel] [PATCH 3/4] options: add support for --transport-plugin

In reply to Antonio Quartulli

Am 30.12.18 um 12:29 schrieb Antonio Quartulli:

Quoted 7 lines

From: Robin Tarsiger <[email protected]...>

Add a new config option to allow the user to specify a transport plugin implementing the new API. This plugin can be used to manipulate traffic in any way, as designed by the plugin developer.

The fondamental advantage of this plugin is that the core codebase does

typo fundamental.

Quoted 1 line

+.B \-\-transport-plugin module-pathname [connection-args]

\- missing here.

doc/openvpn.8 every module and script must return success (0) in order for · line 2862

Quoted 12 lines
doc/openvpn.8 +12 -0
+Use the loaded plugin module identified by
+.B module-pathname
+to provide a transport layer for the connection. The
+.B module-pathname
+must be exactly equivalent to a pathname supplied to a
+.B \-\-plugin
+option. The same transport plugin may be used for
+multiple connections, in which case the
+.B \-\-plugin
+option which loads it should only occur once. However,
+only one transport plugin may be specified per
+connection.

Specify if this includes the .dll/.so suffix or not.

Quoted 2 lines

+If
+.B connection-args

I think there is a \- missing here.

Quoted 15 lines
(quoted diff) +14 -0
+are present, these arguments are passed to the transport
+plugin when establishing this connection specifically; this
+is distinct from any per-plugin arguments which may have
+been specified using the
+.B \-\-plugin
+option. Documentation for possible
+.B connection-args
+may be provided along with the plugin in use.
+
 
+#ifdef ENABLE_PLUGIN
+    /*
+     * "proto indirect" may not be specified directly without a
+     * transport-plugin, and vice versa.
+     */

Review

Quoted 10 lines
(quoted diff) +10 -0
+    if (ce->proto == PROTO_INDIRECT && !ce->transport_plugin_argv)
+    {
+        msg(M_USAGE, "--proto indirect may not be used without a transport-plugin line");
+    }
+
+    if (ce->transport_plugin_argv && ce->proto != PROTO_INDIRECT)
+    {
+        msg(M_USAGE, "--transport-plugin must be used with --proto indirect");
+    }
+#endif

Why are not doing that implicitly when transport-plugin is specified. Any particular reason or just to get a more consistent way of specifying it?

Arne

#13 lore raw
In reply to Antonio Quartulli

Hi all,
I just wanted to bump this to see if there is any feedback on the API?

Thanks!
Justin

Justin Henck
Product Manager
212-565-9811
google.com/jigsaw

PGP: EA8E 8C27 2D75 974D B357 482B 1039 9F2D 869A 117B

Quoted 120 lines · Antonio Quartulli

From: Antonio Quartulli <[email protected]...>

Dear all,

This patchset implements a new API called "Transport API" which allows the core codebase to offload traffic/socket manipulations to an external plugin.

To understand its potential, imagine you want to apply a specific "mutation" on any packet going out and apply the its inverse, on the receiver
side, for any incoming packet.
Without this patchset, achieving this result would require a big surgery through the OpenVPN code, and it would be the same for any new mutation one wants to implements. Thanks to this new API the deemed effort is drastically reduced.

In a few words, OpenVPN becomes extendible on a new front, by cutting the need to touch the core codebase any longer.

An easy to imagine use case for this new API would be any "traffic obfuscation
technique": instead of patching the core code (like the famous "xor patch"), a developer is now allowed to implement an external obfuscation plugin and distribute/maintain it indepdendently.

This patchset comes with a sample plugin (obfs-test) which can be used as starting point for new implementations.

Any feedback is welcome!

Note: the code has gone through the following tests with positive outcome:
- uncrustify
- GitLab-CI (with my configs)
- openvpn community buildbots

Since it is quite a change (even though most of the new code is fairly isolated in its own module - transport.c/h), I expect more comments/discussion/bugs to spark from here.

In any case, please consider myself as direct contact point to discuss further changes and improvements (even though I am not the signer of the patches). I'll be in responsible for ensuring this code gets merged sooner rather than later.

Thanks a lot!!

== Credits ==

This code has been developed by the Operator Foundation[1], under the umbrella project called "Jigsaw"[2], mainly promoted/sponsored by Google, Inc.

Brandon, reading this email in CC, has been the contact point on the Operator Foundation's side, while Justin (in CC as well), managed the task on Google's behalf.

OpenVPN, Inc. has offered its support to the cause by allowing its devs to allocate a number of hours to follow the project and ensure it could get all the guidance it required.

[1] https://operatorfoundation.org/
[2] https://jigsaw.google.com/

Best Regards,

Robin Tarsiger (4):
transport: introduce tranport API plugin codebase
socket: introduce INDIRECT transport protocol abstraction
options: add support for --transport-plugin
transport-plugin: add sample obfs-test plugin

 configure.ac                              |   9 +
 doc/openvpn.8                             |  40 ++
 include/Makefile.am                       |   1 +
 include/openvpn-plugin.h.in               |  31 +-
 include/openvpn-transport.h               | 240 +++++++++
 src/openvpn/Makefile.am                   |   1 +
 src/openvpn/forward.c                     |   5 +
 src/openvpn/init.c                        |   1 +
 src/openvpn/options.c                     |  31 ++
 src/openvpn/options.h                     |   1 +
 src/openvpn/plugin.c                      |   4 +
 src/openvpn/plugin.h                      |   1 +
 src/openvpn/socket.c                      | 148 +++++-
 src/openvpn/socket.h                      |  74 +++
 src/openvpn/transport.c                   | 303 +++++++++++
 src/openvpn/transport.h                   |  99 ++++
 src/plugins/Makefile.am                   |   2 +-
 src/plugins/obfs-test/Makefile.am         |  29 ++
 src/plugins/obfs-test/README.obfs-test    |  26 +
 src/plugins/obfs-test/obfs-test-args.c    |  60 +++
 src/plugins/obfs-test/obfs-test-munging.c | 129 +++++
 src/plugins/obfs-test/obfs-test-posix.c   | 207 ++++++++
 src/plugins/obfs-test/obfs-test-win32.c   | 579 ++++++++++++++++++++++
 src/plugins/obfs-test/obfs-test.c         |  94 ++++
 src/plugins/obfs-test/obfs-test.exports   |   4 +
 src/plugins/obfs-test/obfs-test.h         |  42 ++
 26 files changed, 2155 insertions(+), 6 deletions(-)
 create mode 100644 include/openvpn-transport.h
 create mode 100644 src/openvpn/transport.c
 create mode 100644 src/openvpn/transport.h
 create mode 100644 src/plugins/obfs-test/Makefile.am
 create mode 100644 src/plugins/obfs-test/README.obfs-test
 create mode 100644 src/plugins/obfs-test/obfs-test-args.c
 create mode 100644 src/plugins/obfs-test/obfs-test-munging.c
 create mode 100644 src/plugins/obfs-test/obfs-test-posix.c
 create mode 100644 src/plugins/obfs-test/obfs-test-win32.c
 create mode 100644 src/plugins/obfs-test/obfs-test.c
 create mode 100644 src/plugins/obfs-test/obfs-test.exports
 create mode 100644 src/plugins/obfs-test/obfs-test.h

--
2.19.2

#12 lore raw

Re: [Openvpn-devel] [PATCH 4/4] transport-plugin: add sample obfs-test plugin

In reply to Antonio Quartulli

Bumping this as well given the holiday hiatus - it seems like there was feedback on the patches 2 & 3. Does anyone have any feedback for this one?

Thanks!
Justin

Justin Henck
Product Manager
212-565-9811
google.com/jigsaw

PGP: EA8E 8C27 2D75 974D B357 482B 1039 9F2D 869A 117B

Quoted 1,385 lines · Antonio Quartulli

From: Robin Tarsiger <[email protected]...>

Add a sample plugin to explain how the new transport API is expected to be implemented and work. It can be used for testing.

[[email protected]...: refactored commits, restyled code]
---
 configure.ac                              |   9 +
 src/plugins/Makefile.am                   |   2 +-
 src/plugins/obfs-test/Makefile.am         |  29 ++
 src/plugins/obfs-test/README.obfs-test    |  26 +
 src/plugins/obfs-test/obfs-test-args.c    |  60 +++
 src/plugins/obfs-test/obfs-test-munging.c | 129 +++++
 src/plugins/obfs-test/obfs-test-posix.c   | 207 ++++++++
 src/plugins/obfs-test/obfs-test-win32.c   | 579 ++++++++++++++++++++++
 src/plugins/obfs-test/obfs-test.c         |  94 ++++
 src/plugins/obfs-test/obfs-test.exports   |   4 +
 src/plugins/obfs-test/obfs-test.h         |  42 ++
 11 files changed, 1180 insertions(+), 1 deletion(-)
 create mode 100644 src/plugins/obfs-test/Makefile.am
 create mode 100644 src/plugins/obfs-test/README.obfs-test
 create mode 100644 src/plugins/obfs-test/obfs-test-args.c
 create mode 100644 src/plugins/obfs-test/obfs-test-munging.c
 create mode 100644 src/plugins/obfs-test/obfs-test-posix.c
 create mode 100644 src/plugins/obfs-test/obfs-test-win32.c
 create mode 100644 src/plugins/obfs-test/obfs-test.c
 create mode 100644 src/plugins/obfs-test/obfs-test.exports
 create mode 100644 src/plugins/obfs-test/obfs-test.h
configure.ac +7 -0
index 1e6891b1..b4196812 100644
@@ -200,6 +200,13 @@ AC_ARG_ENABLE(
        ]
 )
 
+AC_ARG_ENABLE(
+       [plugin-obfs-test],
+       [AS_HELP_STRING([--disable-plugin-obfs-test], [disable obfs-test
 plugin @<:@default=platform specific@:>@])],
+       ,
+       [enable_plugin_obfs_test="no"]
+)
+
 AC_ARG_ENABLE(
        [pam-dlopen],

[AS_HELP_STRING([--enable-pam-dlopen], [dlopen libpam
@<:@default=no@:>@])],

configure.ac +1 -0
@@ -1344,6 +1351,7 @@ AM_CONDITIONAL([WIN32], [test "${WIN32}" = "yes"])
 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_PLUGIN_OBFS_TEST], [test
 "${enable_plugin_obfs_test}" = "yes"])

AM_CONDITIONAL([HAVE_LD_WRAP_SUPPORT], [test "${have_ld_wrap_support}" =
"yes"])

sampledir="\$(docdir)/sample"

configure.ac +1 -0
@@ -1403,6 +1411,7 @@ AC_CONFIG_FILES([
        src/plugins/Makefile
        src/plugins/auth-pam/Makefile
        src/plugins/down-root/Makefile
+       src/plugins/obfs-test/Makefile
        tests/Makefile
         tests/unit_tests/Makefile
         tests/unit_tests/example_test/Makefile
src/plugins/Makefile.am +1 -1
index f3461786..848bac03 100644
@@ -12,4 +12,4 @@
 MAINTAINERCLEANFILES = \
        $(srcdir)/Makefile.in
 
-SUBDIRS = auth-pam down-root
+SUBDIRS = auth-pam down-root obfs-test
src/plugins/obfs-test/Makefile.am new file +29 -0
new file mode 100644
index 00000000..4cc8d183
@@ -0,0 +1,29 @@
+MAINTAINERCLEANFILES = \
+       $(srcdir)/Makefile.in
+
+AM_CFLAGS = \
+       -I$(top_srcdir)/include \
+       $(OPTIONAL_CRYPTO_CFLAGS)
+
+if ENABLE_PLUGIN_OBFS_TEST
+plugin_LTLIBRARIES = openvpn-plugin-obfs-test.la
+endif
+
+openvpn_plugin_obfs_test_la_SOURCES = \
+       obfs-test.c \
+       obfs-test-munging.c \
+       obfs-test-args.c \
+       obfs-test.exports
+
+if WIN32
+openvpn_plugin_obfs_test_la_SOURCES += obfs-test-win32.c
+openvpn_plugin_obfs_test_la_LIBADD = -lws2_32 -lwininet
+else !WIN32
+openvpn_plugin_obfs_test_la_SOURCES += obfs-test-posix.c
+# No LIBADD necessary; we assume we can access the global symbol space,
+# and core OpenVPN will already link with everything needed for sockets.
+endif
+
+openvpn_plugin_obfs_test_la_LDFLAGS = $(AM_LDFLAGS) \
+       -export-symbols "$(srcdir)/obfs-test.exports" \
+       -module -shared -avoid-version -no-undefined
src/plugins/obfs-test/README.obfs-test new file +26 -0
new file mode 100644
index 00000000..5492ee02
@@ -0,0 +1,26 @@
+obfs-test
+
+SYNOPSIS
+
+The obfs-test plugin is a proof of concept for supporting protocol
+obfuscation for OpenVPN via a socket intercept plugin.
+
+BUILD
+
+You must specify --enable-plugin-obfs-test at configure time to
+trigger building this plugin. It should function on POSIX-y platforms
+and Windows.
+
+USAGE
+
+To invoke this plugin, load it via an appropriate plugin line in the
+configuration file, and then specify 'proto indirect' rather than any
+other protocol. Packets will then be passed via UDP, but they will
+also undergo a very basic content transformation, and the bind port
+will be altered (see obfs-test-munging.c for details).
+
+CAVEATS
+
+This has undergone basic functionality testing, but not any kind of
+full-on stress test. Extended socket or I/O handling options are not
+supported at all.
src/plugins/obfs-test/obfs-test-args.c new file +7 -0
new file mode 100644
index 00000000..e6756f8f
@@ -0,0 +1,60 @@
+#include "obfs-test.h"
+
+openvpn_transport_args_t
+obfs_test_parseargs(void *plugin_handle,
+                    const char *const *argv, int argc)
+{
+    struct obfs_test_args *args = calloc(1, sizeof(struct

obfs_test_args));

src/plugins/obfs-test/obfs-test-args.c new file +53 -0
+    if (!args)
+    {
+        return NULL;
+    }
+
+    if (argc < 2)
+    {
+        args->offset = 0;
+    }
+    else if (argc == 2)
+    {
+        char *end;
+        long offset = strtol(argv[1], &end, 10);
+        if (*end != '\0')
+        {
+            args->error = "offset must be a decimal number";
+        }
+        else if (!(0 <= offset && offset <= 42))
+        {
+            args->error = "offset must be between 0 and 42";
+        }
+        else
+        {
+            args->offset = (int) offset;
+        }
+    }
+    else
+    {
+        args->error = "too many arguments";
+    }
+
+    return args;
+}
+
+const char *
+obfs_test_argerror(openvpn_transport_args_t args_)
+{
+    if (!args_)
+    {
+        return "cannot allocate";
+    }
+    else
+    {
+        return ((struct obfs_test_args *) args_)->error;
+    }
+}
+
+void
+obfs_test_freeargs(openvpn_transport_args_t args_)
+{
+    free(args_);
+    struct obfs_test_args *args = (struct obfs_test_args *) args_;
+}
src/plugins/obfs-test/obfs-test-munging.c new file +23 -0
new file mode 100644
index 00000000..37d27039
@@ -0,0 +1,129 @@
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include "obfs-test.h"
+#ifdef OPENVPN_TRANSPORT_PLATFORM_POSIX
+#include <sys/socket.h>
+#include <netinet/in.h>
+typedef in_port_t obfs_test_in_port_t;
+#else
+#include <winsock2.h>
+#include <ws2tcpip.h>
+typedef u_short obfs_test_in_port_t;
+#endif
+
+static obfs_test_in_port_t
+munge_port(obfs_test_in_port_t port)
+{
+    return port ^ 15;
+}
+
+/* Reversible. */
+void
+obfs_test_munge_addr(struct sockaddr *addr, openvpn_transport_socklen_t

len)

src/plugins/obfs-test/obfs-test-munging.c new file +22 -0
+{
+    struct sockaddr_in *inet;
+    struct sockaddr_in6 *inet6;
+
+    switch (addr->sa_family)
+    {
+        case AF_INET:
+            inet = (struct sockaddr_in *) addr;
+            inet->sin_port = munge_port(inet->sin_port);
+            break;
+
+        case AF_INET6:
+            inet6 = (struct sockaddr_in6 *) addr;
+            inet6->sin6_port = munge_port(inet6->sin6_port);
+            break;
+
+        default:
+            break;
+    }
+}
+
+/* Six fixed bytes, six repeated bytes. It's only a silly transformation.

*/

src/plugins/obfs-test/obfs-test-munging.c new file +84 -0
+#define MUNGE_OVERHEAD 12
+
+size_t
+obfs_test_max_munged_buf_size(size_t clear_size)
+{
+    return clear_size + MUNGE_OVERHEAD;
+}
+
+ssize_t
+obfs_test_unmunge_buf(struct obfs_test_args *how,
+                      char *buf, size_t len)
+{
+    int i;
+
+    if (len < 6)
+    {
+        goto bad;
+    }
+    for (i = 0; i < 6; i++)
+    {
+        if (buf[i] != i + how->offset)
+        {
+            goto bad;
+        }
+    }
+
+    for (i = 0; i < 6 && (6 + 2*i) < len; i++)
+    {
+        if (len < (6 + 2*i + 1) || buf[6 + 2*i] != buf[6 + 2*i + 1])
+        {
+            goto bad;
+        }
+        buf[i] = buf[6 + 2*i];
+    }
+
+    if (len > 18)
+    {
+        memmove(buf + 6, buf + 18, len - 18);
+        len -= 12;
+    }
+    else
+    {
+        len -= 6;
+        len /= 2;
+    }
+
+    return len;
+
+bad:
+    /* TODO: this really isn't the best way to report this error */
+    errno = EIO;
+    return -1;
+}
+
+/* out must have space for len+MUNGE_OVERHEAD bytes. out and in must
+ * not overlap. */
+size_t
+obfs_test_munge_buf(struct obfs_test_args *how,
+                    char *out, const char *in, size_t len)
+{
+    int i, n;
+    size_t out_len = 6;
+
+    for (i = 0; i < 6; i++)
+    {
+        out[i] = i + how->offset;
+    }
+    n = len < 6 ? len : 6;
+    for (i = 0; i < n; i++)
+    {
+        out[6 + 2*i] = out[6 + 2*i + 1] = in[i];
+    }
+    if (len > 6)
+    {
+        memmove(out + 18, in + 6, len - 6);
+        out_len = len + 12;
+    }
+    else
+    {
+        out_len = 6 + 2*len;
+    }
+
+    return out_len;
+}
src/plugins/obfs-test/obfs-test-posix.c new file +57 -0
new file mode 100644
index 00000000..826381c5
@@ -0,0 +1,207 @@
+#include "obfs-test.h"
+#include <stdbool.h>
+#include <string.h>
+#include <err.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+struct obfs_test_socket_posix
+{
+    struct openvpn_transport_socket handle;
+    struct obfs_test_args args;
+    struct obfs_test_context *ctx;
+    int fd;
+    unsigned last_rwflags;
+};
+
+static void
+free_socket(struct obfs_test_socket_posix *sock)
+{
+    if (!sock)
+    {
+        return;
+    }
+    if (sock->fd != -1)
+    {
+        close(sock->fd);
+    }
+    free(sock);
+}
+
+static openvpn_transport_socket_t
+obfs_test_posix_bind(void *plugin_handle, openvpn_transport_args_t args,
+                     const struct sockaddr *addr, socklen_t len)
+{
+    struct obfs_test_socket_posix *sock = NULL;
+    struct sockaddr *addr_rev = NULL;
+
+    addr_rev = calloc(1, len);
+    if (!addr_rev)
+    {
+        goto error;
+    }
+    memcpy(addr_rev, addr, len);
+    obfs_test_munge_addr(addr_rev, len);
+
+    sock = calloc(1, sizeof(struct obfs_test_socket_posix));
+    if (!sock)
+    {
+        goto error;
+    }
+    sock->handle.vtab = &obfs_test_socket_vtab;
+    sock->ctx = (struct obfs_test_context *) plugin_handle;
+    memcpy(&sock->args, args, sizeof(sock->args));
+    /* Note that sock->fd isn't -1 yet. Set it explicitly if there are

ever any

src/plugins/obfs-test/obfs-test-posix.c new file +28 -0
+     * error exits before the socket() call. */
+
+    sock->fd = socket(addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
+    if (sock->fd == -1)
+    {
+        goto error;
+    }
+    if (fcntl(sock->fd, F_SETFL, fcntl(sock->fd, F_GETFL) | O_NONBLOCK))
+    {
+        goto error;
+    }
+
+    if (bind(sock->fd, addr_rev, len))
+    {
+        goto error;
+    }
+    free(addr_rev);
+    return &sock->handle;
+
+error:
+    free_socket(sock);
+    free(addr_rev);
+    return NULL;
+}
+
+static void
+obfs_test_posix_request_event(openvpn_transport_socket_t handle,
+                              openvpn_transport_event_set_handle_t

event_set, unsigned rwflags)

src/plugins/obfs-test/obfs-test-posix.c new file +7 -0
+{
+    obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,
+                  PLOG_DEBUG, "request-event: %d", rwflags);
+    ((struct obfs_test_socket_posix *) handle)->last_rwflags = 0;
+    if (rwflags)
+    {
+        event_set->vtab->set_event(event_set, ((struct

obfs_test_socket_posix *) handle)->fd,

src/plugins/obfs-test/obfs-test-posix.c new file +6 -0
+                                   rwflags, handle);
+    }
+}
+
+static bool
+obfs_test_posix_update_event(openvpn_transport_socket_t handle, void

*arg, unsigned rwflags)

src/plugins/obfs-test/obfs-test-posix.c new file +3 -0
+{
+    obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,
+                  PLOG_DEBUG, "update-event: %p, %p, %d", handle, arg,

rwflags);

src/plugins/obfs-test/obfs-test-posix.c new file +13 -0
+    if (arg != handle)
+    {
+        return false;
+    }
+    ((struct obfs_test_socket_posix *) handle)->last_rwflags |= rwflags;
+    return true;
+}
+
+static unsigned
+obfs_test_posix_pump(openvpn_transport_socket_t handle)
+{
+    obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,
+                  PLOG_DEBUG, "pump -> %d", ((struct

obfs_test_socket_posix *) handle)->last_rwflags);

src/plugins/obfs-test/obfs-test-posix.c new file +5 -0
+    return ((struct obfs_test_socket_posix *) handle)->last_rwflags;
+}
+
+static ssize_t
+obfs_test_posix_recvfrom(openvpn_transport_socket_t handle, void *buf,

size_t len,

src/plugins/obfs-test/obfs-test-posix.c new file +10 -0
+                         struct sockaddr *addr, socklen_t *addrlen)
+{
+    int fd = ((struct obfs_test_socket_posix *) handle)->fd;
+    ssize_t result;
+
+again:
+    result = recvfrom(fd, buf, len, 0, addr, addrlen);
+    if (result < 0 && errno == EAGAIN)
+    {
+        ((struct obfs_test_socket_posix *) handle)->last_rwflags &=

~OPENVPN_TRANSPORT_EVENT_READ;

src/plugins/obfs-test/obfs-test-posix.c new file +8 -0
+    }
+    if (*addrlen > 0)
+    {
+        obfs_test_munge_addr(addr, *addrlen);
+    }
+    if (result > 0)
+    {
+        struct obfs_test_args *how = &((struct obfs_test_socket_posix *)

handle)->args;

src/plugins/obfs-test/obfs-test-posix.c new file +10 -0
+        result = obfs_test_unmunge_buf(how, buf, result);
+        if (result < 0)
+        {
+            /* Pretend that read never happened. */
+            goto again;
+        }
+    }
+
+    obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,
+                  PLOG_DEBUG, "recvfrom(%d) -> %d", (int)len,

(int)result);

src/plugins/obfs-test/obfs-test-posix.c new file +5 -0
+    return result;
+}
+
+static ssize_t
+obfs_test_posix_sendto(openvpn_transport_socket_t handle, const void

*buf, size_t len,

src/plugins/obfs-test/obfs-test-posix.c new file +15 -0
+                       const struct sockaddr *addr, socklen_t addrlen)
+{
+    int fd = ((struct obfs_test_socket_posix *) handle)->fd;
+    struct sockaddr *addr_rev = calloc(1, addrlen);
+    void *buf_munged = malloc(obfs_test_max_munged_buf_size(len));
+    size_t len_munged;
+    ssize_t result;
+    if (!addr_rev || !buf_munged)
+    {
+        goto error;
+    }
+
+    memcpy(addr_rev, addr, addrlen);
+    obfs_test_munge_addr(addr_rev, addrlen);
+    struct obfs_test_args *how = &((struct obfs_test_socket_posix *)

handle)->args;

src/plugins/obfs-test/obfs-test-posix.c new file +5 -0
+    len_munged = obfs_test_munge_buf(how, buf_munged, buf, len);
+    result = sendto(fd, buf_munged, len_munged, 0, addr_rev, addrlen);
+    if (result < 0 && errno == EAGAIN)
+    {
+        ((struct obfs_test_socket_posix *) handle)->last_rwflags &=

~OPENVPN_TRANSPORT_EVENT_WRITE;

src/plugins/obfs-test/obfs-test-posix.c new file +35 -0
+    }
+    /* TODO: not clear what to do here for partial transfers. */
+    if (result > len)
+    {
+        result = len;
+    }
+    obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,
+                  PLOG_DEBUG, "sendto(%d) -> %d", (int)len, (int)result);
+    free(addr_rev);
+    free(buf_munged);
+    return result;
+
+error:
+    free(addr_rev);
+    free(buf_munged);
+    return -1;
+}
+
+static void
+obfs_test_posix_close(openvpn_transport_socket_t handle)
+{
+    free_socket((struct obfs_test_socket_posix *) handle);
+}
+
+void
+obfs_test_initialize_vtabs_platform(void)
+{
+    obfs_test_bind_vtab.bind = obfs_test_posix_bind;
+    obfs_test_socket_vtab.request_event = obfs_test_posix_request_event;
+    obfs_test_socket_vtab.update_event = obfs_test_posix_update_event;
+    obfs_test_socket_vtab.pump = obfs_test_posix_pump;
+    obfs_test_socket_vtab.recvfrom = obfs_test_posix_recvfrom;
+    obfs_test_socket_vtab.sendto = obfs_test_posix_sendto;
+    obfs_test_socket_vtab.close = obfs_test_posix_close;
+}
src/plugins/obfs-test/obfs-test-win32.c new file +19 -0
new file mode 100644
index 00000000..46c95f55
@@ -0,0 +1,579 @@
+#include "obfs-test.h"
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <windows.h>
+#include <winsock2.h>
+#include <assert.h>
+
+static inline bool
+is_invalid_handle(HANDLE h)
+{
+    return h == NULL || h == INVALID_HANDLE_VALUE;
+}
+
+typedef enum {
+    IO_SLOT_DORMANT,            /* must be 0 for calloc purposes */
+    IO_SLOT_PENDING,
+    /* success/failure is determined by succeeded flag in COMPLETE state

*/

src/plugins/obfs-test/obfs-test-win32.c new file +34 -0
+    IO_SLOT_COMPLETE
+} io_slot_status_t;
+
+/* must be calloc'able */
+struct io_slot
+{
+    struct obfs_test_context *ctx;
+    io_slot_status_t status;
+    OVERLAPPED overlapped;
+    SOCKET socket;
+    SOCKADDR_STORAGE addr;
+    int addr_len, addr_cap;
+    DWORD bytes, flags;
+    bool succeeded;
+    int wsa_error;
+
+    /* realloc'd as needed; always private copy, never aliased */
+    char *buf;
+    size_t buf_len, buf_cap;
+};
+
+static bool
+setup_io_slot(struct io_slot *slot, struct obfs_test_context *ctx,
+              SOCKET socket, HANDLE event)
+{
+    slot->ctx = ctx;
+    slot->status = IO_SLOT_DORMANT;
+    slot->addr_cap = sizeof(SOCKADDR_STORAGE);
+    slot->socket = socket;
+    slot->overlapped.hEvent = event;
+    return true;
+}
+
+/* Note that this assumes any I/O has already been implicitly canceled

(via closesocket),
+ * but not waited for yet. */

src/plugins/obfs-test/obfs-test-win32.c new file +7 -0
+static bool
+destroy_io_slot(struct io_slot *slot)
+{
+    if (slot->status == IO_SLOT_PENDING)
+    {
+        DWORD bytes, flags;
+        BOOL ok = WSAGetOverlappedResult(slot->socket, &slot->overlapped,

&bytes,

src/plugins/obfs-test/obfs-test-win32.c new file +5 -0
+                                         TRUE /* wait */, &flags);
+        if (!ok && WSAGetLastError() == WSA_IO_INCOMPLETE)
+        {
+            obfs_test_log(slot->ctx, PLOG_ERR,
+                          "destroying I/O slot: canceled operation is

still incomplete after wait?!");

src/plugins/obfs-test/obfs-test-win32.c new file +35 -0
+            return false;
+        }
+    }
+
+    slot->status = IO_SLOT_DORMANT;
+    return true;
+}
+
+/* FIXME: aborts on error. */
+static void
+resize_io_buf(struct io_slot *slot, size_t cap)
+{
+    if (slot->buf)
+    {
+        free(slot->buf);
+        slot->buf = NULL;
+    }
+
+    char *new_buf = malloc(cap);
+    if (!new_buf)
+    {
+        abort();
+    }
+    slot->buf = new_buf;
+    slot->buf_cap = cap;
+}
+
+struct obfs_test_socket_win32
+{
+    struct openvpn_transport_socket handle;
+    struct obfs_test_args args;
+    struct obfs_test_context *ctx;
+    SOCKET socket;
+
+    /* Write is ready when idle; read is not-ready when idle. Both

level-triggered. */

src/plugins/obfs-test/obfs-test-win32.c new file +10 -0
+    struct openvpn_transport_win32_event_pair completion_events;
+    struct io_slot slot_read, slot_write;
+
+    int last_rwflags;
+};
+
+static void
+free_socket(struct obfs_test_socket_win32 *sock)
+{
+    /* This only ever becomes false in strange situations where we leak

the entire structure for

src/plugins/obfs-test/obfs-test-win32.c new file +13 -0
+     * lack of anything else to do. */
+    bool can_free = true;
+
+    if (!sock)
+    {
+        return;
+    }
+    if (sock->socket != INVALID_SOCKET)
+    {
+        closesocket(sock->socket);
+    }
+
+    /* closesocket cancels any pending overlapped I/O, but we still have

to potentially
+ * wait for it here before we can free the buffers. This has to
happen before closing

src/plugins/obfs-test/obfs-test-win32.c new file +3 -0
+     * the event handles.
+     *
+     * If we can't figure out when the canceled overlapped I/O is done,

for any reason, we defensively
+ * leak the entire structure; freeing it would be permitting the
system to corrupt memory later.
+ * TODO: possibly abort() instead, but make sure we've handled all
the possible "have to try again"

src/plugins/obfs-test/obfs-test-win32.c new file +14 -0
+     * cases above first
+     */
+    if (!destroy_io_slot(&sock->slot_read))
+    {
+        can_free = false;
+    }
+    if (!destroy_io_slot(&sock->slot_write))
+    {
+        can_free = false;
+    }
+    if (!can_free)
+    {
+        /* Skip deinitialization of everything else. Doomed. */
+        obfs_test_log(sock->ctx, PLOG_ERR, "doomed, leaking the entire

socket structure");

src/plugins/obfs-test/obfs-test-win32.c new file +18 -0
+        return;
+    }
+
+    if (!is_invalid_handle(sock->completion_events.read))
+    {
+        CloseHandle(sock->completion_events.read);
+    }
+    if (!is_invalid_handle(sock->completion_events.write))
+    {
+        CloseHandle(sock->completion_events.write);
+    }
+
+    free(sock);
+}
+
+static openvpn_transport_socket_t
+obfs_test_win32_bind(void *plugin_handle, openvpn_transport_args_t args,
+                     const struct sockaddr *addr,

openvpn_transport_socklen_t len)

src/plugins/obfs-test/obfs-test-win32.c new file +23 -0
+{
+    struct obfs_test_socket_win32 *sock = NULL;
+    struct sockaddr *addr_rev = NULL;
+
+    /* TODO: would be nice to factor out some of these sequences */
+    addr_rev = calloc(1, len);
+    if (!addr_rev)
+    {
+        goto error;
+    }
+    memcpy(addr_rev, addr, len);
+    obfs_test_munge_addr(addr_rev, len);
+
+    sock = calloc(1, sizeof(struct obfs_test_socket_win32));
+    if (!sock)
+    {
+        goto error;
+    }
+    sock->handle.vtab = &obfs_test_socket_vtab;
+    sock->ctx = (struct obfs_test_context *) plugin_handle;
+    memcpy(&sock->args, args, sizeof(sock->args));
+
+    /* Preemptively initialize the members of some Win32 types so error

exits are okay later on.

src/plugins/obfs-test/obfs-test-win32.c new file +10 -0
+     * HANDLEs of NULL are considered invalid per above. */
+    sock->socket = INVALID_SOCKET;
+
+    sock->socket = socket(addr_rev->sa_family, SOCK_DGRAM, IPPROTO_UDP);
+    if (sock->socket == INVALID_SOCKET)
+    {
+        goto error;
+    }
+
+    /* See above: write is ready when idle, read is not-ready when idle.

*/

src/plugins/obfs-test/obfs-test-win32.c new file +3 -0
+    sock->completion_events.read = CreateEvent(NULL, TRUE, FALSE, NULL);
+    sock->completion_events.write = CreateEvent(NULL, TRUE, TRUE, NULL);
+    if (is_invalid_handle(sock->completion_events.read) ||

is_invalid_handle(sock->completion_events.write))

src/plugins/obfs-test/obfs-test-win32.c new file +69 -0
+    {
+        goto error;
+    }
+    if (!setup_io_slot(&sock->slot_read, sock->ctx,
+                       sock->socket, sock->completion_events.read))
+    {
+        goto error;
+    }
+    if (!setup_io_slot(&sock->slot_write, sock->ctx,
+                       sock->socket, sock->completion_events.write))
+    {
+        goto error;
+    }
+
+    if (bind(sock->socket, addr_rev, len))
+    {
+        goto error;
+    }
+    free(addr_rev);
+    return &sock->handle;
+
+error:
+    obfs_test_log((struct obfs_test_context *) plugin_handle, PLOG_ERR,
+                  "bind failure: WSA error = %d", WSAGetLastError());
+    free_socket(sock);
+    free(addr_rev);
+    return NULL;
+}
+
+static void
+handle_sendrecv_return(struct io_slot *slot, int status)
+{
+    if (status == 0)
+    {
+        /* Immediately completed. Set the event so it stays consistent. */
+        slot->status = IO_SLOT_COMPLETE;
+        slot->succeeded = true;
+        slot->buf_len = slot->bytes;
+        SetEvent(slot->overlapped.hEvent);
+    }
+    else if (WSAGetLastError() == WSA_IO_PENDING)
+    {
+        /* Queued. */
+        slot->status = IO_SLOT_PENDING;
+    }
+    else
+    {
+        /* Error. */
+        slot->status = IO_SLOT_COMPLETE;
+        slot->succeeded = false;
+        slot->wsa_error = WSAGetLastError();
+        slot->buf_len = 0;
+    }
+}
+
+static void
+queue_new_read(struct io_slot *slot, size_t cap)
+{
+    int status;
+    WSABUF sbuf;
+    assert(slot->status == IO_SLOT_DORMANT);
+
+    ResetEvent(slot->overlapped.hEvent);
+    resize_io_buf(slot, cap);
+    sbuf.buf = slot->buf;
+    sbuf.len = slot->buf_cap;
+    slot->addr_len = slot->addr_cap;
+    slot->flags = 0;
+    status = WSARecvFrom(slot->socket, &sbuf, 1, &slot->bytes,

&slot->flags,

src/plugins/obfs-test/obfs-test-win32.c new file +12 -0
+                         (struct sockaddr *)&slot->addr, &slot->addr_len,
+                         &slot->overlapped, NULL);
+    handle_sendrecv_return(slot, status);
+}
+
+/* write slot buffer must already be full. */
+static void
+queue_new_write(struct io_slot *slot)
+{
+    int status;
+    WSABUF sbuf;
+    assert(slot->status == IO_SLOT_COMPLETE || slot->status ==

IO_SLOT_DORMANT);

src/plugins/obfs-test/obfs-test-win32.c new file +6 -0
+
+    ResetEvent(slot->overlapped.hEvent);
+    sbuf.buf = slot->buf;
+    sbuf.len = slot->buf_len;
+    slot->flags = 0;
+    status = WSASendTo(slot->socket, &sbuf, 1, &slot->bytes, 0 /* flags

*/,

src/plugins/obfs-test/obfs-test-win32.c new file +21 -0
+                       (struct sockaddr *)&slot->addr, slot->addr_len,
+                       &slot->overlapped, NULL);
+    handle_sendrecv_return(slot, status);
+}
+
+static void
+ensure_pending_read(struct obfs_test_socket_win32 *sock)
+{
+    struct io_slot *slot = &sock->slot_read;
+    switch (slot->status)
+    {
+        case IO_SLOT_PENDING:
+            return;
+
+        case IO_SLOT_COMPLETE:
+            /* Set the event manually here just in case. */
+            SetEvent(slot->overlapped.hEvent);
+            return;
+
+        case IO_SLOT_DORMANT:
+            /* TODO: we don't propagate max read size here, so we just

have to assume the maximum. */

src/plugins/obfs-test/obfs-test-win32.c new file +25 -0
+            queue_new_read(slot, 65536);
+            return;
+
+        default:
+            abort();
+    }
+}
+
+static bool
+complete_pending_operation(struct io_slot *slot)
+{
+    DWORD bytes, flags;
+    BOOL ok;
+
+    switch (slot->status)
+    {
+        case IO_SLOT_DORMANT:
+            /* TODO: shouldn't get here? */
+            return false;
+
+        case IO_SLOT_COMPLETE:
+            return true;
+
+        case IO_SLOT_PENDING:
+            ok = WSAGetOverlappedResult(slot->socket, &slot->overlapped,

&bytes,

src/plugins/obfs-test/obfs-test-win32.c new file +64 -0
+                                        FALSE /* don't wait */, &flags);
+            if (!ok && WSAGetLastError() == WSA_IO_INCOMPLETE)
+            {
+                /* Still waiting. */
+                return false;
+            }
+            else if (ok)
+            {
+                /* Completed. slot->addr_len has already been updated. */
+                slot->buf_len = bytes;
+                slot->status = IO_SLOT_COMPLETE;
+                slot->succeeded = true;
+                return true;
+            }
+            else
+            {
+                /* Error. */
+                slot->buf_len = 0;
+                slot->status = IO_SLOT_COMPLETE;
+                slot->succeeded = false;
+                slot->wsa_error = WSAGetLastError();
+                return true;
+            }
+
+        default:
+            abort();
+    }
+}
+
+static bool
+complete_pending_read(struct obfs_test_socket_win32 *sock)
+{
+    bool done = complete_pending_operation(&sock->slot_read);
+    if (done)
+    {
+        ResetEvent(sock->completion_events.read);
+    }
+    return done;
+}
+
+static void
+consumed_pending_read(struct obfs_test_socket_win32 *sock)
+{
+    struct io_slot *slot = &sock->slot_read;
+    assert(slot->status == IO_SLOT_COMPLETE);
+    slot->status = IO_SLOT_DORMANT;
+    slot->succeeded = false;
+    ResetEvent(slot->overlapped.hEvent);
+}
+
+static inline bool
+complete_pending_write(struct obfs_test_socket_win32 *sock)
+{
+    bool done = complete_pending_operation(&sock->slot_write);
+    if (done)
+    {
+        SetEvent(sock->completion_events.write);
+    }
+    return done;
+}
+
+static void
+obfs_test_win32_request_event(openvpn_transport_socket_t handle,
+                              openvpn_transport_event_set_handle_t

event_set, unsigned rwflags)

src/plugins/obfs-test/obfs-test-win32.c new file +2 -0
+{
+    struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32

*)handle;

src/plugins/obfs-test/obfs-test-win32.c new file +10 -0
+    obfs_test_log(sock->ctx, PLOG_DEBUG, "request-event: %d", rwflags);
+    sock->last_rwflags = 0;
+
+    if (rwflags & OPENVPN_TRANSPORT_EVENT_READ)
+    {
+        ensure_pending_read(sock);
+    }
+    if (rwflags)
+    {
+        event_set->vtab->set_event(event_set, &sock->completion_events,

rwflags, handle);

src/plugins/obfs-test/obfs-test-win32.c new file +5 -0
+    }
+}
+
+static bool
+obfs_test_win32_update_event(openvpn_transport_socket_t handle, void

*arg, unsigned rwflags)

src/plugins/obfs-test/obfs-test-win32.c new file +2 -0
+{
+    obfs_test_log(((struct obfs_test_socket_win32 *) handle)->ctx,

PLOG_DEBUG,

src/plugins/obfs-test/obfs-test-win32.c new file +13 -0
+                  "update-event: %p, %p, %d", handle, arg, rwflags);
+    if (arg != handle)
+    {
+        return false;
+    }
+    ((struct obfs_test_socket_win32 *) handle)->last_rwflags |= rwflags;
+    return true;
+}
+
+static unsigned
+obfs_test_win32_pump(openvpn_transport_socket_t handle)
+{
+    struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32

*)handle;

src/plugins/obfs-test/obfs-test-win32.c new file +3 -0
+    unsigned result = 0;
+
+    if ((sock->last_rwflags & OPENVPN_TRANSPORT_EVENT_READ) &&

complete_pending_read(sock))

src/plugins/obfs-test/obfs-test-win32.c new file +5 -0
+    {
+        result |= OPENVPN_TRANSPORT_EVENT_READ;
+    }
+    if ((sock->last_rwflags & OPENVPN_TRANSPORT_EVENT_WRITE)
+        && (sock->slot_write.status != IO_SLOT_PENDING ||

complete_pending_write(sock)))

src/plugins/obfs-test/obfs-test-win32.c new file +10 -0
+    {
+        result |= OPENVPN_TRANSPORT_EVENT_WRITE;
+    }
+
+    obfs_test_log(sock->ctx, PLOG_DEBUG, "pump -> %d", result);
+    return result;
+}
+
+static ssize_t
+obfs_test_win32_recvfrom(openvpn_transport_socket_t handle, void *buf,

size_t len,
+ struct sockaddr *addr,
openvpn_transport_socklen_t *addrlen)

src/plugins/obfs-test/obfs-test-win32.c new file +2 -0
+{
+    struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32

*)handle;

src/plugins/obfs-test/obfs-test-win32.c new file +22 -0
+    if (!complete_pending_read(sock))
+    {
+        WSASetLastError(WSA_IO_INCOMPLETE);
+        return -1;
+    }
+
+    if (!sock->slot_read.succeeded)
+    {
+        int wsa_error = sock->slot_read.wsa_error;
+        consumed_pending_read(sock);
+        WSASetLastError(wsa_error);
+        return -1;
+    }
+
+    /* sock->slot_read now has valid data. */
+    char *working_buf = sock->slot_read.buf;
+    ssize_t unmunged_len =
+        obfs_test_unmunge_buf(&sock->args, working_buf,
+                              sock->slot_read.buf_len);
+    if (unmunged_len < 0)
+    {
+        /* Act as though this read never happened. Assume one was queued

before, so it should

src/plugins/obfs-test/obfs-test-win32.c new file +15 -0
+         * still remain queued. */
+        consumed_pending_read(sock);
+        ensure_pending_read(sock);
+        WSASetLastError(WSA_IO_INCOMPLETE);
+        return -1;
+    }
+
+    size_t copy_len = unmunged_len;
+    if (copy_len > len)
+    {
+        copy_len = len;
+    }
+    memcpy(buf, sock->slot_read.buf, copy_len);
+
+    /* TODO: shouldn't truncate, should signal error (but this shouldn't

happen for any

src/plugins/obfs-test/obfs-test-win32.c new file +20 -0
+     * supported address families anyway). */
+    openvpn_transport_socklen_t addr_copy_len = *addrlen;
+    if (sock->slot_read.addr_len < addr_copy_len)
+    {
+        addr_copy_len = sock->slot_read.addr_len;
+    }
+    memcpy(addr, &sock->slot_read.addr, addr_copy_len);
+    *addrlen = addr_copy_len;
+    if (addr_copy_len > 0)
+    {
+        obfs_test_munge_addr(addr, addr_copy_len);
+    }
+
+    /* Reset the I/O slot before returning. */
+    consumed_pending_read(sock);
+    return copy_len;
+}
+
+static ssize_t
+obfs_test_win32_sendto(openvpn_transport_socket_t handle, const void

*buf, size_t len,
+ const struct sockaddr *addr,
openvpn_transport_socklen_t addrlen)

src/plugins/obfs-test/obfs-test-win32.c new file +2 -0
+{
+    struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32

*)handle;

src/plugins/obfs-test/obfs-test-win32.c new file +17 -0
+    complete_pending_write(sock);
+
+    if (sock->slot_write.status == IO_SLOT_PENDING)
+    {
+        /* This shouldn't really happen, but. */
+        WSASetLastError(WSAEWOULDBLOCK);
+        return -1;
+    }
+
+    if (addrlen > sock->slot_write.addr_cap)
+    {
+        /* Shouldn't happen. */
+        WSASetLastError(WSAEFAULT);
+        return -1;
+    }
+
+    /* TODO: propagate previous write errors---what does core expect

here? */

src/plugins/obfs-test/obfs-test-win32.c new file +5 -0
+    memcpy(&sock->slot_write.addr, addr, addrlen);
+    sock->slot_write.addr_len = addrlen;
+    if (addrlen > 0)
+    {
+        obfs_test_munge_addr((struct sockaddr *)&sock->slot_write.addr,

addrlen);

src/plugins/obfs-test/obfs-test-win32.c new file +9 -0
+    }
+    resize_io_buf(&sock->slot_write, obfs_test_max_munged_buf_size(len));
+    sock->slot_write.buf_len =
+        obfs_test_munge_buf(&sock->args, sock->slot_write.buf, buf, len);
+    queue_new_write(&sock->slot_write);
+    switch (sock->slot_write.status)
+    {
+        case IO_SLOT_PENDING:
+            /* The network hasn't given us an error yet, but _we've_

consumed all the bytes.

src/plugins/obfs-test/obfs-test-win32.c new file +40 -0
+             * ... sort of. */
+            return len;
+
+        case IO_SLOT_DORMANT:
+            /* Huh?? But we just queued a write. */
+            abort();
+
+        case IO_SLOT_COMPLETE:
+            if (sock->slot_write.succeeded)
+            {
+                /* TODO: more partial length handling */
+                return len;
+            }
+            else
+            {
+                return -1;
+            }
+
+        default:
+            abort();
+    }
+}
+
+static void
+obfs_test_win32_close(openvpn_transport_socket_t handle)
+{
+    free_socket((struct obfs_test_socket_win32 *) handle);
+}
+
+void
+obfs_test_initialize_vtabs_platform(void)
+{
+    obfs_test_bind_vtab.bind = obfs_test_win32_bind;
+    obfs_test_socket_vtab.request_event = obfs_test_win32_request_event;
+    obfs_test_socket_vtab.update_event = obfs_test_win32_update_event;
+    obfs_test_socket_vtab.pump = obfs_test_win32_pump;
+    obfs_test_socket_vtab.recvfrom = obfs_test_win32_recvfrom;
+    obfs_test_socket_vtab.sendto = obfs_test_win32_sendto;
+    obfs_test_socket_vtab.close = obfs_test_win32_close;
+}
src/plugins/obfs-test/obfs-test.c new file +27 -0
new file mode 100644
index 00000000..27a3d21e
@@ -0,0 +1,94 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include "openvpn-plugin.h"
+#include "openvpn-transport.h"
+#include "obfs-test.h"
+
+struct openvpn_transport_bind_vtab1 obfs_test_bind_vtab = { 0 };
+struct openvpn_transport_socket_vtab1 obfs_test_socket_vtab = { 0 };
+
+struct obfs_test_context
+{
+    struct openvpn_plugin_callbacks *global_vtab;
+};
+
+static void
+free_context(struct obfs_test_context *context)
+{
+    if (!context)
+    {
+        return;
+    }
+    free(context);
+}
+
+OPENVPN_EXPORT int
+openvpn_plugin_open_v3(int version, struct openvpn_plugin_args_open_in

const *args,

src/plugins/obfs-test/obfs-test.c new file +5 -0
+                       struct openvpn_plugin_args_open_return *out)
+{
+    struct obfs_test_context *context;
+
+    context = (struct obfs_test_context *) calloc(1, sizeof(struct

obfs_test_context));

src/plugins/obfs-test/obfs-test.c new file +29 -0
+    if (!context)
+    {
+        return OPENVPN_PLUGIN_FUNC_ERROR;
+    }
+
+    context->global_vtab = args->callbacks;
+    obfs_test_initialize_vtabs_platform();
+    obfs_test_bind_vtab.parseargs = obfs_test_parseargs;
+    obfs_test_bind_vtab.argerror = obfs_test_argerror;
+    obfs_test_bind_vtab.freeargs = obfs_test_freeargs;
+
+    out->type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TRANSPORT);
+    out->handle = (openvpn_plugin_handle_t *) context;
+    return OPENVPN_PLUGIN_FUNC_SUCCESS;
+
+err:
+    free_context(context);
+    return OPENVPN_PLUGIN_FUNC_ERROR;
+}
+
+OPENVPN_EXPORT void
+openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
+{
+    free_context((struct obfs_test_context *) handle);
+}
+
+OPENVPN_EXPORT int
+openvpn_plugin_func_v3(int version,
+                       struct openvpn_plugin_args_func_in const

*arguments,

src/plugins/obfs-test/obfs-test.c new file +33 -0
+                       struct openvpn_plugin_args_func_return *retptr)
+{
+    /* We don't ask for any bits that use this interface. */
+    return OPENVPN_PLUGIN_FUNC_ERROR;
+}
+
+OPENVPN_EXPORT void *
+openvpn_plugin_get_vtab_v1(int selector, size_t *size_out)
+{
+    switch (selector)
+    {
+        case OPENVPN_VTAB_TRANSPORT_BIND_V1:
+            if (obfs_test_bind_vtab.bind == NULL)
+            {
+                return NULL;
+            }
+            *size_out = sizeof(struct openvpn_transport_bind_vtab1);
+            return &obfs_test_bind_vtab;
+
+        default:
+            return NULL;
+    }
+}
+
+void
+obfs_test_log(struct obfs_test_context *ctx,
+              openvpn_plugin_log_flags_t flags, const char *fmt, ...)
+{
+    va_list va;
+    va_start(va, fmt);
+    ctx->global_vtab->plugin_vlog(flags, OBFS_TEST_PLUGIN_NAME, fmt, va);
+    va_end(va);
+}
src/plugins/obfs-test/obfs-test.exports new file +4 -0
new file mode 100644
index 00000000..e7baada4
@@ -0,0 +1,4 @@
+openvpn_plugin_open_v3
+openvpn_plugin_close_v1
+openvpn_plugin_get_vtab_v1
+openvpn_plugin_func_v3
src/plugins/obfs-test/obfs-test.h new file +22 -0
new file mode 100644
index 00000000..b9a6f8b4
@@ -0,0 +1,42 @@
+#ifndef OPENVPN_PLUGIN_OBFS_TEST_H
+#define OPENVPN_PLUGIN_OBFS_TEST_H 1
+
+#include "openvpn-plugin.h"
+#include "openvpn-transport.h"
+
+#define OBFS_TEST_PLUGIN_NAME "obfs-test"
+
+struct obfs_test_context;
+
+struct obfs_test_args
+{
+    const char *error;
+    int offset;
+};
+
+extern struct openvpn_transport_bind_vtab1 obfs_test_bind_vtab;
+extern struct openvpn_transport_socket_vtab1 obfs_test_socket_vtab;
+
+void obfs_test_initialize_vtabs_platform(void);
+
+void obfs_test_munge_addr(struct sockaddr *addr,

openvpn_transport_socklen_t len);

src/plugins/obfs-test/obfs-test.h new file +11 -0
+
+size_t obfs_test_max_munged_buf_size(size_t clear_size);
+
+size_t obfs_test_munge_buf(struct obfs_test_args *how,
+                           char *out, const char *in, size_t len);
+
+ssize_t obfs_test_unmunge_buf(struct obfs_test_args *how,
+                              char *buf, size_t len);
+
+openvpn_transport_args_t obfs_test_parseargs(void *plugin_handle,
+                                             const char *const *argv, int

argc);

src/plugins/obfs-test/obfs-test.h new file +7 -0
+
+const char *obfs_test_argerror(openvpn_transport_args_t args);
+
+void obfs_test_freeargs(openvpn_transport_args_t args);
+
+void obfs_test_log(struct obfs_test_context *ctx,
+                   openvpn_plugin_log_flags_t flags, const char *fmt,

...);

src/plugins/obfs-test/obfs-test.h new file +2 -1
+
+#endif /* !OPENVPN_PLUGIN_OBFS_TEST_H */
--

2.19.2

_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

#3 lore raw

Re: [Openvpn-devel] [PATCH 1/4] transport: introduce tranport API plugin codebase

In reply to Arne Schwabe

Hi Arne and thanks for the review!

Quoted 7 lines · Arne Schwabe
Quoted 4 lines

+/*
+ * FUNCTION: openvpn_plugin_get_vtab_v1
+ *

It would be nice if we also use the docutils style to document the new functions in this file rather than is this different documentation style.

I agree - will fix this.

Quoted 8 lines
Quoted 6 lines

+ * This is only used for TRANSPORT plugins presently. It is called to
+ * retrieve a vtable structure to be used for binding virtual sockets
+ * which use the transport provided by the plugin. The selector is an
+ * OPENVPN_VTAB constant. *size_out must be set to the size of the
+ * structure returned.

A reference to openvpn_transport_bind_vtab1 might be a good idea here.

ok, will have a look.

Quoted 12 lines
Quoted 2 lines

+/* On Windows, platform-native events to wait on are provided to OpenVPN core as

I think for multi line comments we use the following style

/*
* first line of text
* last line of text
*/

but not sure. Might want to double check that style.

as discussed on IRC it is acceptable to have multi-line comments starting directly on the first line. So I'll keep them as they are.

Quoted 88 lines

Rest of the file look fine but I also would like to see docstyle comments.

Quoted 87 lines
(quoted diff) +1 -0
+#endif
src/openvpn/Makefile.am +74 -0
index 197e62ba..41cd90aa 100644
@@ -119,6 +119,7 @@ openvpn_SOURCES = \
 	status.c status.h \
 	syshead.h \
 	tls_crypt.c tls_crypt.h \
+	transport.c transport.h \
 	tun.c tun.h \
 	win32.h win32.c \
 	cryptoapi.h cryptoapi.c
 
+openvpn_transport_socket_t
+transport_bind(const struct plugin_list *plugins,
+               const char **transport_plugin_argv, sa_family_t ai_family,
+               struct addrinfo *bind_addresses)
+{
+    openvpn_plugin_handle_t handle;
+    openvpn_transport_args_t args;
+    openvpn_transport_socket_t indirect;
+    struct openvpn_transport_bind_vtab1 *vtab;
+    struct addrinfo *cur = NULL;
+    struct openvpn_sockaddr zero;
+
+    if (!transport_prepare(plugins, transport_plugin_argv, &vtab, &handle,
+                           &args))
+    {
+        msg(M_FATAL, "INDIRECT: Socket bind failed: provider plugin not found");
+    }
+
+    /* Partially replicates the functionality of socket_bind. No bind_ipv6_only
+     * or other such options, presently.
+     */
+    if (bind_addresses)
+    {
+        for (cur = bind_addresses; cur; cur = cur->ai_next)
+        {
+            if (cur->ai_family == ai_family)
+            {
+                break;
+            }
+        }
+
+        if (!cur)
+        {
+            msg(M_FATAL, "INDIRECT: Socket bind failed: Addr to bind has no %s record",
+                addr_family_name(ai_family));
+        }
+    }
+
+    if (cur)
+    {
+        indirect = vtab->bind(handle, args, cur->ai_addr, cur->ai_addrlen);
+    }
+    else if (ai_family == AF_UNSPEC)
+    {
+        msg(M_ERR, "INDIRECT: cannot bind with unspecified address family");
+    }
+    else
+    {
+        memset(&zero, 0, sizeof(zero));
+        zero.addr.sa.sa_family = ai_family;
+        addr_zero_host(&zero);
+        indirect = vtab->bind(handle, args, &zero.addr.sa, af_addr_size(ai_family));
+    }
+
+    if (!indirect)
+    {
+        msg(M_ERR, "INDIRECT: Socket bind failed");
+    }
+
+    if (vtab->freeargs)
+    {
+        vtab->freeargs(args);
+    }
+
+    return indirect;
+}
+
+struct encapsulated_event_set
+{
+    struct openvpn_transport_event_set_handle handle;
+    struct event_set *real;
+};
+
Quoted 8 lines

src/openvpn/Makefile.am

Quoted 6 lines
src/openvpn/Makefile.am +6 -0
+#if EVENT_READ == OPENVPN_TRANSPORT_EVENT_READ \
+    && EVENT_WRITE == OPENVPN_TRANSPORT_EVENT_WRITE
+#define TRANSPORT_EVENT_BITS_IDENTICAL 1
+#else
+#define TRANSPORT_EVENT_BITS_IDENTICAL 0
+#endif

A small comment when this is identical like Win32 vs posix-y would be nice.

ok!

Quoted 12 lines

src/openvpn/Makefile.am

Quoted 7 lines
src/openvpn/Makefile.am +5 -0
+
+unsigned
+transport_pump(openvpn_transport_socket_t indirect,
+               struct event_set_return *esr, int *esrlen)
+{

The name 'pump' is confusing, especially without comment. Why not transport_update_event? Something that tells what the function actually does.

I believe this is called such because it "injects" (hence pump) data into the event buffer (esr). But I agree that a better name can be found.

'transport_update_event' might be ok.

Regards,

Signature · 1 line
Antonio Quartulli
#6 lore raw

Re: [Openvpn-devel] [PATCH 2/4] socket: introduce INDIRECT transport protocol abstraction

In reply to Arne Schwabe

Hi,

Quoted 18 lines · Arne Schwabe

Am 30.12.18 um 12:28 schrieb Antonio Quartulli:

Quoted 17 lines

From: Robin Tarsiger <[email protected]...>

This new transport protocol is used to tell the core code that traffic should not be directly processed, but should rather be rerouted to a transport plugin. It is basically an abstraction as it does not say tell the code how to process the data, but simply forces its redirection to the external code.

[[email protected]...: refactored commits, restyled code]
---
 src/openvpn/forward.c   |   5 ++
 src/openvpn/socket.c    | 146 ++++++++++++++++++++++++++++++++++++++--
 src/openvpn/socket.h    |  70 +++++++++++++++++++
 src/openvpn/transport.h |   5 ++
 4 files changed, 222 insertions(+), 4 deletions(-)
Quoted 13 lines

src/openvpn/forward.c io_wait_dowork(struct context *c, const unsigned int flags) · line 2155

Quoted 11 lines
src/openvpn/forward.c +3 -0
index 0a90fff0..a7092c7e 100644
@@ -2150,6 +2150,11 @@ io_wait_dowork(struct context *c, const unsigned int flags)
             {
                 int i;
                 c->c2.event_set_status = 0;
+#ifdef ENABLE_PLUGIN
+                c->c2.event_set_status |=
+                    (socket_indirect_pump(c->c2.link_socket, esr, &status) & 3)

The & 3 looks like some defined should be used instead.

I think this is simply about fetching the last two bits..not much fuzz here..this is mostly because of how event_set_status works. But will see if I find a meaningful name.

Quoted 22 lines
Quoted 22 lines
src/openvpn/forward.c +5 -1
     if (addr->ai_protocol == IPPROTO_UDP || addr->ai_socktype == SOCK_DGRAM)
     {
         sock->sd = create_socket_udp(addr, sock->sockflags);
@@ -2279,7 +2320,11 @@ link_socket_init_phase2(struct link_socket *sock,
         }
 
         /* If socket has not already been created create it now */
-        if (sock->sd == SOCKET_UNDEFINED)
+        if (sock->sd == SOCKET_UNDEFINED
+#ifdef ENABLE_PLUGIN
+            && !sock->indirect
+#endif
+            )
         {
             /* If we have no --remote and have still not figured out the
              * protocol family to use we will use the first of the bind */
@@ -2300,7 +2345,11 @@ link_socket_init_phase2(struct link_socket *sock,
         }
 
Quoted 11 lines

src/openvpn/forward.c link_socket_init_phase2(struct link_socket *sock, · line 2352

Quoted 7 lines
src/openvpn/forward.c +5 -1
         /* Socket still undefined, give a warning and abort connection */
-        if (sock->sd == SOCKET_UNDEFINED)
+        if (sock->sd == SOCKET_UNDEFINED
+#ifdef ENABLE_PLUGIN
+            && !sock->indirect
+#endif
+            )

Better use the inline funtcion proto_is_indirect (or similar sock_is_indirect) that always returns false here and ifdef in header than to add the ifdefs inline in the code.

agreed! I am all for that! will change

Quoted 17 lines

src/openvpn/forward.c proto_is_net(int proto) · line 3244

Quoted 13 lines
src/openvpn/forward.c +4 -0
 bool
@@ -3167,6 +3236,10 @@ proto_is_net(int proto)
 bool
 proto_is_dgram(int proto)
 {
+    if (proto_is_indirect(proto))
+    {
+        return true;
+    }
     return proto_is_udp(proto);
 }

I think here need to be a good explaination why indirect is dgram but not udp and also proto_is_dgram and proto_is_udp need to get some comment to explain their difference in usage as this is now different.

I wouldn't say is_udp and is_dgram are now different, but is_dgram has been extended to account for the indirect case as well.

Quoted 6 lines
Quoted 6 lines
src/openvpn/forward.c +0 -0
@@ -3301,6 +3374,18 @@ proto_remote(int proto, bool remote)
         return "TCPv4_CLIENT";
     }
 
Quoted 16 lines

src/openvpn/forward.c proto_remote(int proto, bool remote) · line 3387

Quoted 11 lines
src/openvpn/forward.c +11 -0
+#ifdef ENABLE_PLUGIN
+    if (proto == PROTO_INDIRECT)
+    {
+        /* FIXME: the string reported here should match the actual transport
+         * protocol being used, however in this function we have no knowledge of
+         * what protocol is exactly being used by the transport-plugin.
+         * Therefore we simply return INDIRECT for now.
+         */
+        return "INDIRECT";
+    }
+#endif

From reading the code this function is only used in creating the string in options_string which needs to match between peers. As the plugin emulates UDP/DGRAM behaviour I think we should instead return here the same as a real UDP protocol.

well, the problem here is that if I use transport-plugin1 and you use transport-plugin2 these strings will match between client and server even though they should not (because the protocols being used are actually different).

I don't think INDIRECT is UDP only. That is just how it prefers to be treated, but could be non-UDP as well, I think. (need to double check)

Quoted 17 lines
Quoted 4 lines
src/openvpn/forward.c +2 -0
+#ifdef ENABLE_PLUGIN
+

src/openvpn/forward.c proto_remote(int proto, bool remote)

Quoted 10 lines
src/openvpn/forward.c +10 -0
+int link_socket_read_indirect(struct link_socket *sock,
+                              struct buffer *buf,
+                              struct link_socket_actual *from);
+
+int link_socket_write_indirect(struct link_socket *sock,
+                               struct buffer *buf,
+                               struct link_socket_actual *from);
+
+bool proto_is_indirect(int proto);
+

This prototype definition looks a bit weird here. I see not reason why the real proto_is_indirect cannot be here

you mean the proto_is_indirect() implementation should be here in the header? Why?

Quoted 9 lines
Quoted 8 lines
src/openvpn/forward.c +7 -0
+#else  /* ifdef ENABLE_PLUGIN */
+
+static int
+link_socket_read_indirect(struct link_socket *sock,
+
+static int
+link_socket_write_indirect(struct link_socket *sock,
Quoted 5 lines

src/openvpn/forward.c

Quoted 2 lines
src/openvpn/forward.c +2 -0
+static bool
+proto_is_indirect(int proto)

I think functions implemented in the header should have the inline keyword. At least the rest of the header files do it that way.

yeah they should. will fix that.

Quoted 4 lines

Arne

Signature · 1 line
Antonio Quartulli
#10 lore raw

Re: [Openvpn-devel] [PATCH 3/4] options: add support for --transport-plugin

In reply to Arne Schwabe

Hi,

Quoted 7 lines · Arne Schwabe

Am 30.12.18 um 12:29 schrieb Antonio Quartulli:

Quoted 6 lines

From: Robin Tarsiger <[email protected]...>

Add a new config option to allow the user to specify a transport plugin implementing the new API. This plugin can be used to manipulate traffic in any way, as designed by the plugin developer.

Quoted 6 lines
Quoted 1 line

The fondamental advantage of this plugin is that the core codebase does

typo fundamental.

Quoted 1 line

+.B \-\-transport-plugin module-pathname [connection-args]

\- missing here.

oh right! thanks!

Quoted 15 lines

doc/openvpn.8 every module and script must return success (0) in order for · line 2862

Quoted 13 lines
doc/openvpn.8 +12 -0
+Use the loaded plugin module identified by
+.B module-pathname
+to provide a transport layer for the connection. The
+.B module-pathname
+must be exactly equivalent to a pathname supplied to a
+.B \-\-plugin
+option. The same transport plugin may be used for
+multiple connections, in which case the
+.B \-\-plugin
+option which loads it should only occur once. However,
+only one transport plugin may be specified per
+connection.

Specify if this includes the .dll/.so suffix or not.

it is matched against the pathname of the --plugin option, therefore the same form is expected (hence including the .so/.dll extension).

Quoted 5 lines
Quoted 4 lines

+If
+.B connection-args

I think there is a \- missing here.

thanks!

Quoted 21 lines
Quoted 21 lines
(quoted diff) +19 -0
+are present, these arguments are passed to the transport
+plugin when establishing this connection specifically; this
+is distinct from any per-plugin arguments which may have
+been specified using the
+.B \-\-plugin
+option. Documentation for possible
+.B connection-args
+may be provided along with the plugin in use.
+
 
+#ifdef ENABLE_PLUGIN
+    /*
+     * "proto indirect" may not be specified directly without a
+     * transport-plugin, and vice versa.
+     */
+    if (ce->proto == PROTO_INDIRECT && !ce->transport_plugin_argv)
+    {
+        msg(M_USAGE, "--proto indirect may not be used without a transport-plugin line");
+    }
+
Quoted 10 lines

src/openvpn/options.c options_postprocess_verify_ce(const struct options *options, const struct connec · line 2025

Quoted 5 lines
src/openvpn/options.c +5 -0
+    if (ce->transport_plugin_argv && ce->proto != PROTO_INDIRECT)
+    {
+        msg(M_USAGE, "--transport-plugin must be used with --proto indirect");
+    }
+#endif

Why are not doing that implicitly when transport-plugin is specified. Any particular reason or just to get a more consistent way of specifying it?

It is done implicitly, but the user has a chance to override it because he may specify indirect6 or indirect4 to limit the plugin to work on a single address family only.

Therefore, by giving a chance to the user to override the value, we also need to check that was set.

Cheers,

Quoted 3 lines

Arne

Signature · 1 line
Antonio Quartulli
#7 lore raw

Re: [Openvpn-devel] [PATCH 2/4] socket: introduce INDIRECT transport protocol abstraction

In reply to Antonio Quartulli
Quoted 12 lines
Quoted 11 lines

src/openvpn/forward.c io_wait_dowork(struct context *c, const unsigned int flags) · line 2155

Quoted 9 lines

+++ b/src/openvpn/forward.c

src/openvpn/forward.c +3 -0
@@ -2150,6 +2150,11 @@ io_wait_dowork(struct context *c, const unsigned int flags)
             {
                 int i;
                 c->c2.event_set_status = 0;
+#ifdef ENABLE_PLUGIN
+                c->c2.event_set_status |=
+                    (socket_indirect_pump(c->c2.link_socket, esr, &status) & 3)

The & 3 looks like some defined should be used instead.

Quoted 4 lines

I think this is simply about fetching the last two bits..not much fuzz here..this is mostly because of how event_set_status works. But will see if I find a meaningful name.

INDIRECT_FLAG_MASK or something.

Quoted 16 lines
Quoted 7 lines
Quoted 7 lines
src/openvpn/socket.c +2 -0
 bool
@@ -3167,6 +3236,10 @@ proto_is_net(int proto)
 bool
 proto_is_dgram(int proto)
 {
+    if (proto_is_indirect(proto))
+    {
Quoted 8 lines

src/openvpn/socket.c proto_is_net(int proto) · line 3244

Quoted 4 lines
src/openvpn/socket.c +2 -0
+        return true;
+    }
     return proto_is_udp(proto);
 }

I think here need to be a good explaination why indirect is dgram but not udp and also proto_is_dgram and proto_is_udp need to get some comment to explain their difference in usage as this is now different.

Quoted 3 lines

I wouldn't say is_udp and is_dgram are now different, but is_dgram has been extended to account for the indirect case as well.

is_dgram and is_udp return different values for INDIRECT, so they are different. I would like some comment why that is. Without having looked deeper I suspect the one function is used to determine the wire format and the other is used for determing the actual outer protocol. Just adding that as comment in the is_dgram function would be nice (double check first).

Quoted 23 lines
Quoted 6 lines
Quoted 6 lines
src/openvpn/socket.c +0 -0
@@ -3301,6 +3374,18 @@ proto_remote(int proto, bool remote)
         return "TCPv4_CLIENT";
     }
 
Quoted 16 lines

src/openvpn/socket.c proto_remote(int proto, bool remote) · line 3387

Quoted 11 lines
src/openvpn/socket.c +11 -0
+#ifdef ENABLE_PLUGIN
+    if (proto == PROTO_INDIRECT)
+    {
+        /* FIXME: the string reported here should match the actual transport
+         * protocol being used, however in this function we have no knowledge of
+         * what protocol is exactly being used by the transport-plugin.
+         * Therefore we simply return INDIRECT for now.
+         */
+        return "INDIRECT";
+    }
+#endif

From reading the code this function is only used in creating the string in options_string which needs to match between peers. As the plugin emulates UDP/DGRAM behaviour I think we should instead return here the same as a real UDP protocol.

Quoted 4 lines

well, the problem here is that if I use transport-plugin1 and you use transport-plugin2 these strings will match between client and server even though they should not (because the protocols being used are actually different).

If we get to the point that the cleartext inside the openvpn tunnel is compared the methods are compatible. And if you have a client on the one side that has plugin in it and a proxy+openvpn server on the other side, it should also work.

Quoted 2 lines

I don't think INDIRECT is UDP only. That is just how it prefers to be treated, but could be non-UDP as well, I think. (need to double check)

But for this check it is only important that the wire format is the TCP or UDP wire format. Not the actual protocol being used.

Quoted 19 lines
Quoted 18 lines
Quoted 5 lines
src/openvpn/socket.h +2 -0
+#ifdef ENABLE_PLUGIN
+

src/openvpn/socket.h int link_socket_read_udp_posix(struct link_socket *sock, · line 1070

Quoted 10 lines
src/openvpn/socket.h +10 -0
+int link_socket_read_indirect(struct link_socket *sock,
+                              struct buffer *buf,
+                              struct link_socket_actual *from);
+
+int link_socket_write_indirect(struct link_socket *sock,
+                               struct buffer *buf,
+                               struct link_socket_actual *from);
+
+bool proto_is_indirect(int proto);
+

This prototype definition looks a bit weird here. I see not reason why the real proto_is_indirect cannot be here

Quoted 2 lines

you mean the proto_is_indirect() implementation should be here in the header? Why?

It is already in the header. Having an inline implementation AND a prototype in the same header is what I am complaining about.

Arne