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
On Sun, Dec 30, 2018 at 6:31 AM Antonio Quartulli <a@unstable.cc> wrote:
> From: Robin Tarsiger <rtt@dasyatidae.com>
>
> Add a sample plugin to explain how the new transport API is expected to
> be implemented and work. It can be used for testing.
>
> Signed-off-by: Robin Tarsiger <rtt@dasyatidae.com>
> [antonio@openvpn.net: 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
>
> diff --git a/configure.ac b/configure.ac
> index 1e6891b1..b4196812 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -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
> diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
> index f3461786..848bac03 100644
> --- a/src/plugins/Makefile.am
> +++ b/src/plugins/Makefile.am
> @@ -12,4 +12,4 @@
> MAINTAINERCLEANFILES = \
> $(srcdir)/Makefile.in
>
> -SUBDIRS = auth-pam down-root
> +SUBDIRS = auth-pam down-root obfs-test
> diff --git a/src/plugins/obfs-test/Makefile.am
> b/src/plugins/obfs-test/Makefile.am
> new file mode 100644
> index 00000000..4cc8d183
> --- /dev/null
> +++ b/src/plugins/obfs-test/Makefile.am
> @@ -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
> diff --git a/src/plugins/obfs-test/README.obfs-test
> b/src/plugins/obfs-test/README.obfs-test
> new file mode 100644
> index 00000000..5492ee02
> --- /dev/null
> +++ b/src/plugins/obfs-test/README.obfs-test
> @@ -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.
> diff --git a/src/plugins/obfs-test/obfs-test-args.c
> b/src/plugins/obfs-test/obfs-test-args.c
> new file mode 100644
> index 00000000..e6756f8f
> --- /dev/null
> +++ b/src/plugins/obfs-test/obfs-test-args.c
> @@ -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));
> + 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_;
> +}
> diff --git a/src/plugins/obfs-test/obfs-test-munging.c
> b/src/plugins/obfs-test/obfs-test-munging.c
> new file mode 100644
> index 00000000..37d27039
> --- /dev/null
> +++ b/src/plugins/obfs-test/obfs-test-munging.c
> @@ -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)
> +{
> + 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.
> */
> +#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;
> +}
> diff --git a/src/plugins/obfs-test/obfs-test-posix.c
> b/src/plugins/obfs-test/obfs-test-posix.c
> new file mode 100644
> index 00000000..826381c5
> --- /dev/null
> +++ b/src/plugins/obfs-test/obfs-test-posix.c
> @@ -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
> + * 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)
> +{
> + 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,
> + rwflags, handle);
> + }
> +}
> +
> +static bool
> +obfs_test_posix_update_event(openvpn_transport_socket_t handle, void
> *arg, unsigned rwflags)
> +{
> + obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,
> + PLOG_DEBUG, "update-event: %p, %p, %d", handle, arg,
> rwflags);
> + 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);
> + 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,
> + 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;
> + }
> + if (*addrlen > 0)
> + {
> + obfs_test_munge_addr(addr, *addrlen);
> + }
> + if (result > 0)
> + {
> + struct obfs_test_args *how = &((struct obfs_test_socket_posix *)
> handle)->args;
> + 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);
> + return result;
> +}
> +
> +static ssize_t
> +obfs_test_posix_sendto(openvpn_transport_socket_t handle, const void
> *buf, size_t len,
> + 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;
> + 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;
> + }
> + /* 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;
> +}
> diff --git a/src/plugins/obfs-test/obfs-test-win32.c
> b/src/plugins/obfs-test/obfs-test-win32.c
> new file mode 100644
> index 00000000..46c95f55
> --- /dev/null
> +++ b/src/plugins/obfs-test/obfs-test-win32.c
> @@ -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
> */
> + 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. */
> +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,
> + 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?!");
> + 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. */
> + 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
> + * 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
> + * 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"
> + * 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");
> + 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)
> +{
> + 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.
> + * 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.
> */
> + 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))
> + {
> + 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,
> + (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);
> +
> + 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
> */,
> + (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. */
> + 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,
> + 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)
> +{
> + struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32
> *)handle;
> + 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);
> + }
> +}
> +
> +static bool
> +obfs_test_win32_update_event(openvpn_transport_socket_t handle, void
> *arg, unsigned rwflags)
> +{
> + obfs_test_log(((struct obfs_test_socket_win32 *) handle)->ctx,
> PLOG_DEBUG,
> + "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;
> + unsigned result = 0;
> +
> + if ((sock->last_rwflags & OPENVPN_TRANSPORT_EVENT_READ) &&
> complete_pending_read(sock))
> + {
> + result |= OPENVPN_TRANSPORT_EVENT_READ;
> + }
> + if ((sock->last_rwflags & OPENVPN_TRANSPORT_EVENT_WRITE)
> + && (sock->slot_write.status != IO_SLOT_PENDING ||
> complete_pending_write(sock)))
> + {
> + 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)
> +{
> + struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32
> *)handle;
> + 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
> + * 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
> + * 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)
> +{
> + struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32
> *)handle;
> + 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? */
> + 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);
> + }
> + 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.
> + * ... 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;
> +}
> diff --git a/src/plugins/obfs-test/obfs-test.c
> b/src/plugins/obfs-test/obfs-test.c
> new file mode 100644
> index 00000000..27a3d21e
> --- /dev/null
> +++ b/src/plugins/obfs-test/obfs-test.c
> @@ -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,
> + struct openvpn_plugin_args_open_return *out)
> +{
> + struct obfs_test_context *context;
> +
> + context = (struct obfs_test_context *) calloc(1, sizeof(struct
> obfs_test_context));
> + 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,
> + 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);
> +}
> diff --git a/src/plugins/obfs-test/obfs-test.exports
> b/src/plugins/obfs-test/obfs-test.exports
> new file mode 100644
> index 00000000..e7baada4
> --- /dev/null
> +++ b/src/plugins/obfs-test/obfs-test.exports
> @@ -0,0 +1,4 @@
> +openvpn_plugin_open_v3
> +openvpn_plugin_close_v1
> +openvpn_plugin_get_vtab_v1
> +openvpn_plugin_func_v3
> diff --git a/src/plugins/obfs-test/obfs-test.h
> b/src/plugins/obfs-test/obfs-test.h
> new file mode 100644
> index 00000000..b9a6f8b4
> --- /dev/null
> +++ b/src/plugins/obfs-test/obfs-test.h
> @@ -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);
> +
> +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);
> +
> +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,
> ...);
> +
> +#endif /* !OPENVPN_PLUGIN_OBFS_TEST_H */
> --
> 2.19.2
>
>
>
> _______________________________________________
> Openvpn-devel mailing list
> Openvpn-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/openvpn-devel
>
<div dir="ltr">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?<div><br></div><div>Thanks!</div><div>Justin<br clear="all"><div><div dir="ltr" class="gmail_signature" data-smartmail="gmail_signature"><div dir="ltr"><div><div dir="ltr"><div><div dir="ltr"><div><div dir="ltr"><div><div dir="ltr"><div><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><br><table cellspacing="0" cellpadding="0" style="font-family:"open sans",sans-serif"><tbody><tr style="color:rgb(102,102,102);font-family:sans-serif;font-size:small"><td nowrap valign="top" style="border:none;padding-right:22px"><img src="https://www.gstatic.com/jigsaw/Jigsaw_logo.png" height="45" width="45" style="height:45px;width:45px"></td><td nowrap style="border:none"><span style="font-weight:bold">Justin Henck</span>Â <br><span>Product Manager</span><span></span>Â <br><div style="display:inline"><span>212-565-9811</span>Â <br></div><a href="https://google.com/jigsaw" style="color:rgb(102,102,102)" target="_blank">google.com/jigsaw</a></td></tr></tbody></table></div><div dir="ltr"><div dir="auto"><div><span><font color="#666666"><br></font></span></div><div><span><font color="#666666">PGP: EA8E 8C27 2D75 974D B357 482B 1039 9F2D 869A 117B</font></span></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Sun, Dec 30, 2018 at 6:31 AM Antonio Quartulli <a@unstable.cc> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">From: Robin Tarsiger <<a href="mailto:rtt@dasyatidae.com" target="_blank">rtt@dasyatidae.com</a>><br>
<br>
Add a sample plugin to explain how the new transport API is expected to<br>
be implemented and work. It can be used for testing.<br>
<br>
Signed-off-by: Robin Tarsiger <<a href="mailto:rtt@dasyatidae.com" target="_blank">rtt@dasyatidae.com</a>><br>
[<a href="mailto:antonio@openvpn.net" target="_blank">antonio@openvpn.net</a>: refactored commits, restyled code]<br>
---<br>
 <a href="http://configure.ac" rel="noreferrer" target="_blank">configure.ac</a>               |  9 +<br>
 src/plugins/Makefile.am          |  2 +-<br>
 src/plugins/obfs-test/Makefile.am     | 29 ++<br>
 src/plugins/obfs-test/README.obfs-test  | 26 +<br>
 src/plugins/obfs-test/obfs-test-args.c  | 60 +++<br>
 src/plugins/obfs-test/obfs-test-munging.c | 129 +++++<br>
 src/plugins/obfs-test/obfs-test-posix.c  | 207 ++++++++<br>
 src/plugins/obfs-test/obfs-test-win32.c  | 579 ++++++++++++++++++++++<br>
 src/plugins/obfs-test/obfs-test.c     | 94 ++++<br>
 src/plugins/obfs-test/obfs-test.exports  |  4 +<br>
 src/plugins/obfs-test/obfs-test.h     | 42 ++<br>
 11 files changed, 1180 insertions(+), 1 deletion(-)<br>
 create mode 100644 src/plugins/obfs-test/Makefile.am<br>
 create mode 100644 src/plugins/obfs-test/README.obfs-test<br>
 create mode 100644 src/plugins/obfs-test/obfs-test-args.c<br>
 create mode 100644 src/plugins/obfs-test/obfs-test-munging.c<br>
 create mode 100644 src/plugins/obfs-test/obfs-test-posix.c<br>
 create mode 100644 src/plugins/obfs-test/obfs-test-win32.c<br>
 create mode 100644 src/plugins/obfs-test/obfs-test.c<br>
 create mode 100644 src/plugins/obfs-test/obfs-test.exports<br>
 create mode 100644 src/plugins/obfs-test/obfs-test.h<br>
<br>
diff --git a/<a href="http://configure.ac" rel="noreferrer" target="_blank">configure.ac</a> b/<a href="http://configure.ac" rel="noreferrer" target="_blank">configure.ac</a><br>
index 1e6891b1..b4196812 100644<br>
--- a/<a href="http://configure.ac" rel="noreferrer" target="_blank">configure.ac</a><br>
+++ b/<a href="http://configure.ac" rel="noreferrer" target="_blank">configure.ac</a><br>
@@ -200,6 +200,13 @@ AC_ARG_ENABLE(<br>
    ]<br>
 )<br>
<br>
+AC_ARG_ENABLE(<br>
+Â Â Â Â [plugin-obfs-test],<br>
+Â Â Â Â [AS_HELP_STRING([--disable-plugin-obfs-test], [disable obfs-test plugin @<:@default=platform specific@:>@])],<br>
+Â Â Â Â ,<br>
+Â Â Â Â [enable_plugin_obfs_test="no"]<br>
+)<br>
+<br>
 AC_ARG_ENABLE(<br>
    [pam-dlopen],<br>
    [AS_HELP_STRING([--enable-pam-dlopen], [dlopen libpam @<:@default=no@:>@])],<br>
@@ -1344,6 +1351,7 @@ AM_CONDITIONAL([WIN32], [test "${WIN32}" = "yes"])<br>
 AM_CONDITIONAL([GIT_CHECKOUT], [test "${GIT_CHECKOUT}" = "yes"])<br>
 AM_CONDITIONAL([ENABLE_PLUGIN_AUTH_PAM], [test "${enable_plugin_auth_pam}" = "yes"])<br>
 AM_CONDITIONAL([ENABLE_PLUGIN_DOWN_ROOT], [test "${enable_plugin_down_root}" = "yes"])<br>
+AM_CONDITIONAL([ENABLE_PLUGIN_OBFS_TEST], [test "${enable_plugin_obfs_test}" = "yes"])<br>
 AM_CONDITIONAL([HAVE_LD_WRAP_SUPPORT], [test "${have_ld_wrap_support}" = "yes"])<br>
<br>
 sampledir="\$(docdir)/sample"<br>
@@ -1403,6 +1411,7 @@ AC_CONFIG_FILES([<br>
    src/plugins/Makefile<br>
    src/plugins/auth-pam/Makefile<br>
    src/plugins/down-root/Makefile<br>
+Â Â Â Â src/plugins/obfs-test/Makefile<br>
    tests/Makefile<br>
     tests/unit_tests/Makefile<br>
     tests/unit_tests/example_test/Makefile<br>
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am<br>
index f3461786..848bac03 100644<br>
--- a/src/plugins/Makefile.am<br>
+++ b/src/plugins/Makefile.am<br>
@@ -12,4 +12,4 @@<br>
 MAINTAINERCLEANFILES = \<br>
    $(srcdir)/Makefile.in<br>
<br>
-SUBDIRS = auth-pam down-root<br>
+SUBDIRS = auth-pam down-root obfs-test<br>
diff --git a/src/plugins/obfs-test/Makefile.am b/src/plugins/obfs-test/Makefile.am<br>
new file mode 100644<br>
index 00000000..4cc8d183<br>
--- /dev/null<br>
+++ b/src/plugins/obfs-test/Makefile.am<br>
@@ -0,0 +1,29 @@<br>
+MAINTAINERCLEANFILES = \<br>
+Â Â Â Â $(srcdir)/Makefile.in<br>
+<br>
+AM_CFLAGS = \<br>
+Â Â Â Â -I$(top_srcdir)/include \<br>
+Â Â Â Â $(OPTIONAL_CRYPTO_CFLAGS)<br>
+<br>
+if ENABLE_PLUGIN_OBFS_TEST<br>
+plugin_LTLIBRARIES = <a href="http://openvpn-plugin-obfs-test.la" rel="noreferrer" target="_blank">openvpn-plugin-obfs-test.la</a><br>
+endif<br>
+<br>
+openvpn_plugin_obfs_test_la_SOURCES = \<br>
+Â Â Â Â obfs-test.c \<br>
+Â Â Â Â obfs-test-munging.c \<br>
+Â Â Â Â obfs-test-args.c \<br>
+Â Â Â Â obfs-test.exports<br>
+<br>
+if WIN32<br>
+openvpn_plugin_obfs_test_la_SOURCES += obfs-test-win32.c<br>
+openvpn_plugin_obfs_test_la_LIBADD = -lws2_32 -lwininet<br>
+else !WIN32<br>
+openvpn_plugin_obfs_test_la_SOURCES += obfs-test-posix.c<br>
+# No LIBADD necessary; we assume we can access the global symbol space,<br>
+# and core OpenVPN will already link with everything needed for sockets.<br>
+endif<br>
+<br>
+openvpn_plugin_obfs_test_la_LDFLAGS = $(AM_LDFLAGS) \<br>
+Â Â Â Â -export-symbols "$(srcdir)/obfs-test.exports" \<br>
+Â Â Â Â -module -shared -avoid-version -no-undefined<br>
diff --git a/src/plugins/obfs-test/README.obfs-test b/src/plugins/obfs-test/README.obfs-test<br>
new file mode 100644<br>
index 00000000..5492ee02<br>
--- /dev/null<br>
+++ b/src/plugins/obfs-test/README.obfs-test<br>
@@ -0,0 +1,26 @@<br>
+obfs-test<br>
+<br>
+SYNOPSIS<br>
+<br>
+The obfs-test plugin is a proof of concept for supporting protocol<br>
+obfuscation for OpenVPN via a socket intercept plugin.<br>
+<br>
+BUILD<br>
+<br>
+You must specify --enable-plugin-obfs-test at configure time to<br>
+trigger building this plugin. It should function on POSIX-y platforms<br>
+and Windows.<br>
+<br>
+USAGE<br>
+<br>
+To invoke this plugin, load it via an appropriate plugin line in the<br>
+configuration file, and then specify 'proto indirect' rather than any<br>
+other protocol. Packets will then be passed via UDP, but they will<br>
+also undergo a very basic content transformation, and the bind port<br>
+will be altered (see obfs-test-munging.c for details).<br>
+<br>
+CAVEATS<br>
+<br>
+This has undergone basic functionality testing, but not any kind of<br>
+full-on stress test. Extended socket or I/O handling options are not<br>
+supported at all.<br>
diff --git a/src/plugins/obfs-test/obfs-test-args.c b/src/plugins/obfs-test/obfs-test-args.c<br>
new file mode 100644<br>
index 00000000..e6756f8f<br>
--- /dev/null<br>
+++ b/src/plugins/obfs-test/obfs-test-args.c<br>
@@ -0,0 +1,60 @@<br>
+#include "obfs-test.h"<br>
+<br>
+openvpn_transport_args_t<br>
+obfs_test_parseargs(void *plugin_handle,<br>
+Â Â Â Â Â Â Â Â Â Â const char *const *argv, int argc)<br>
+{<br>
+Â Â struct obfs_test_args *args = calloc(1, sizeof(struct obfs_test_args));<br>
+Â Â if (!args)<br>
+Â Â {<br>
+Â Â Â Â return NULL;<br>
+Â Â }<br>
+<br>
+Â Â if (argc < 2)<br>
+Â Â {<br>
+Â Â Â Â args->offset = 0;<br>
+Â Â }<br>
+Â Â else if (argc == 2)<br>
+Â Â {<br>
+Â Â Â Â char *end;<br>
+Â Â Â Â long offset = strtol(argv[1], &end, 10);<br>
+Â Â Â Â if (*end != '\0')<br>
+Â Â Â Â {<br>
+Â Â Â Â Â Â args->error = "offset must be a decimal number";<br>
+Â Â Â Â }<br>
+Â Â Â Â else if (!(0 <= offset && offset <= 42))<br>
+Â Â Â Â {<br>
+Â Â Â Â Â Â args->error = "offset must be between 0 and 42";<br>
+Â Â Â Â }<br>
+Â Â Â Â else<br>
+Â Â Â Â {<br>
+Â Â Â Â Â Â args->offset = (int) offset;<br>
+Â Â Â Â }<br>
+Â Â }<br>
+Â Â else<br>
+Â Â {<br>
+Â Â Â Â args->error = "too many arguments";<br>
+Â Â }<br>
+<br>
+Â Â return args;<br>
+}<br>
+<br>
+const char *<br>
+obfs_test_argerror(openvpn_transport_args_t args_)<br>
+{<br>
+Â Â if (!args_)<br>
+Â Â {<br>
+Â Â Â Â return "cannot allocate";<br>
+Â Â }<br>
+Â Â else<br>
+Â Â {<br>
+Â Â Â Â return ((struct obfs_test_args *) args_)->error;<br>
+Â Â }<br>
+}<br>
+<br>
+void<br>
+obfs_test_freeargs(openvpn_transport_args_t args_)<br>
+{<br>
+Â Â free(args_);<br>
+Â Â struct obfs_test_args *args = (struct obfs_test_args *) args_;<br>
+}<br>
diff --git a/src/plugins/obfs-test/obfs-test-munging.c b/src/plugins/obfs-test/obfs-test-munging.c<br>
new file mode 100644<br>
index 00000000..37d27039<br>
--- /dev/null<br>
+++ b/src/plugins/obfs-test/obfs-test-munging.c<br>
@@ -0,0 +1,129 @@<br>
+#include <string.h><br>
+#include <errno.h><br>
+#include <stdbool.h><br>
+#include "obfs-test.h"<br>
+#ifdef OPENVPN_TRANSPORT_PLATFORM_POSIX<br>
+#include <sys/socket.h><br>
+#include <netinet/in.h><br>
+typedef in_port_t obfs_test_in_port_t;<br>
+#else<br>
+#include <winsock2.h><br>
+#include <ws2tcpip.h><br>
+typedef u_short obfs_test_in_port_t;<br>
+#endif<br>
+<br>
+static obfs_test_in_port_t<br>
+munge_port(obfs_test_in_port_t port)<br>
+{<br>
+Â Â return port ^ 15;<br>
+}<br>
+<br>
+/* Reversible. */<br>
+void<br>
+obfs_test_munge_addr(struct sockaddr *addr, openvpn_transport_socklen_t len)<br>
+{<br>
+Â Â struct sockaddr_in *inet;<br>
+Â Â struct sockaddr_in6 *inet6;<br>
+<br>
+Â Â switch (addr->sa_family)<br>
+Â Â {<br>
+Â Â Â Â case AF_INET:<br>
+Â Â Â Â Â Â inet = (struct sockaddr_in *) addr;<br>
+Â Â Â Â Â Â inet->sin_port = munge_port(inet->sin_port);<br>
+Â Â Â Â Â Â break;<br>
+<br>
+Â Â Â Â case AF_INET6:<br>
+Â Â Â Â Â Â inet6 = (struct sockaddr_in6 *) addr;<br>
+Â Â Â Â Â Â inet6->sin6_port = munge_port(inet6->sin6_port);<br>
+Â Â Â Â Â Â break;<br>
+<br>
+Â Â Â Â default:<br>
+Â Â Â Â Â Â break;<br>
+Â Â }<br>
+}<br>
+<br>
+/* Six fixed bytes, six repeated bytes. It's only a silly transformation. */<br>
+#define MUNGE_OVERHEAD 12<br>
+<br>
+size_t<br>
+obfs_test_max_munged_buf_size(size_t clear_size)<br>
+{<br>
+Â Â return clear_size + MUNGE_OVERHEAD;<br>
+}<br>
+<br>
+ssize_t<br>
+obfs_test_unmunge_buf(struct obfs_test_args *how,<br>
+Â Â Â Â Â Â Â Â Â Â Â char *buf, size_t len)<br>
+{<br>
+Â Â int i;<br>
+<br>
+Â Â if (len < 6)<br>
+Â Â {<br>
+Â Â Â Â goto bad;<br>
+Â Â }<br>
+Â Â for (i = 0; i < 6; i++)<br>
+Â Â {<br>
+Â Â Â Â if (buf[i] != i + how->offset)<br>
+Â Â Â Â {<br>
+Â Â Â Â Â Â goto bad;<br>
+Â Â Â Â }<br>
+Â Â }<br>
+<br>
+Â Â for (i = 0; i < 6 && (6 + 2*i) < len; i++)<br>
+Â Â {<br>
+Â Â Â Â if (len < (6 + 2*i + 1) || buf[6 + 2*i] != buf[6 + 2*i + 1])<br>
+Â Â Â Â {<br>
+Â Â Â Â Â Â goto bad;<br>
+Â Â Â Â }<br>
+Â Â Â Â buf[i] = buf[6 + 2*i];<br>
+Â Â }<br>
+<br>
+Â Â if (len > 18)<br>
+Â Â {<br>
+Â Â Â Â memmove(buf + 6, buf + 18, len - 18);<br>
+Â Â Â Â len -= 12;<br>
+Â Â }<br>
+Â Â else<br>
+Â Â {<br>
+Â Â Â Â len -= 6;<br>
+Â Â Â Â len /= 2;<br>
+Â Â }<br>
+<br>
+Â Â return len;<br>
+<br>
+bad:<br>
+Â Â /* TODO: this really isn't the best way to report this error */<br>
+Â Â errno = EIO;<br>
+Â Â return -1;<br>
+}<br>
+<br>
+/* out must have space for len+MUNGE_OVERHEAD bytes. out and in must<br>
+ * not overlap. */<br>
+size_t<br>
+obfs_test_munge_buf(struct obfs_test_args *how,<br>
+Â Â Â Â Â Â Â Â Â Â char *out, const char *in, size_t len)<br>
+{<br>
+Â Â int i, n;<br>
+Â Â size_t out_len = 6;<br>
+<br>
+Â Â for (i = 0; i < 6; i++)<br>
+Â Â {<br>
+Â Â Â Â out[i] = i + how->offset;<br>
+Â Â }<br>
+Â Â n = len < 6 ? len : 6;<br>
+Â Â for (i = 0; i < n; i++)<br>
+Â Â {<br>
+Â Â Â Â out[6 + 2*i] = out[6 + 2*i + 1] = in[i];<br>
+Â Â }<br>
+Â Â if (len > 6)<br>
+Â Â {<br>
+Â Â Â Â memmove(out + 18, in + 6, len - 6);<br>
+Â Â Â Â out_len = len + 12;<br>
+Â Â }<br>
+Â Â else<br>
+Â Â {<br>
+Â Â Â Â out_len = 6 + 2*len;<br>
+Â Â }<br>
+<br>
+Â Â return out_len;<br>
+}<br>
diff --git a/src/plugins/obfs-test/obfs-test-posix.c b/src/plugins/obfs-test/obfs-test-posix.c<br>
new file mode 100644<br>
index 00000000..826381c5<br>
--- /dev/null<br>
+++ b/src/plugins/obfs-test/obfs-test-posix.c<br>
@@ -0,0 +1,207 @@<br>
+#include "obfs-test.h"<br>
+#include <stdbool.h><br>
+#include <string.h><br>
+#include <err.h><br>
+#include <errno.h><br>
+#include <unistd.h><br>
+#include <fcntl.h><br>
+#include <sys/socket.h><br>
+#include <netinet/in.h><br>
+<br>
+struct obfs_test_socket_posix<br>
+{<br>
+Â Â struct openvpn_transport_socket handle;<br>
+Â Â struct obfs_test_args args;<br>
+Â Â struct obfs_test_context *ctx;<br>
+Â Â int fd;<br>
+Â Â unsigned last_rwflags;<br>
+};<br>
+<br>
+static void<br>
+free_socket(struct obfs_test_socket_posix *sock)<br>
+{<br>
+Â Â if (!sock)<br>
+Â Â {<br>
+Â Â Â Â return;<br>
+Â Â }<br>
+Â Â if (sock->fd != -1)<br>
+Â Â {<br>
+Â Â Â Â close(sock->fd);<br>
+Â Â }<br>
+Â Â free(sock);<br>
+}<br>
+<br>
+static openvpn_transport_socket_t<br>
+obfs_test_posix_bind(void *plugin_handle, openvpn_transport_args_t args,<br>
+Â Â Â Â Â Â Â Â Â Â Â const struct sockaddr *addr, socklen_t len)<br>
+{<br>
+Â Â struct obfs_test_socket_posix *sock = NULL;<br>
+Â Â struct sockaddr *addr_rev = NULL;<br>
+<br>
+Â Â addr_rev = calloc(1, len);<br>
+Â Â if (!addr_rev)<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+Â Â memcpy(addr_rev, addr, len);<br>
+Â Â obfs_test_munge_addr(addr_rev, len);<br>
+<br>
+Â Â sock = calloc(1, sizeof(struct obfs_test_socket_posix));<br>
+Â Â if (!sock)<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+Â Â sock->handle.vtab = &obfs_test_socket_vtab;<br>
+Â Â sock->ctx = (struct obfs_test_context *) plugin_handle;<br>
+Â Â memcpy(&sock->args, args, sizeof(sock->args));<br>
+Â Â /* Note that sock->fd isn't -1 yet. Set it explicitly if there are ever any<br>
+Â Â Â * error exits before the socket() call. */<br>
+<br>
+Â Â sock->fd = socket(addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);<br>
+Â Â if (sock->fd == -1)<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+Â Â if (fcntl(sock->fd, F_SETFL, fcntl(sock->fd, F_GETFL) | O_NONBLOCK))<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+<br>
+Â Â if (bind(sock->fd, addr_rev, len))<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+Â Â free(addr_rev);<br>
+Â Â return &sock->handle;<br>
+<br>
+error:<br>
+Â Â free_socket(sock);<br>
+Â Â free(addr_rev);<br>
+Â Â return NULL;<br>
+}<br>
+<br>
+static void<br>
+obfs_test_posix_request_event(openvpn_transport_socket_t handle,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â openvpn_transport_event_set_handle_t event_set, unsigned rwflags)<br>
+{<br>
+Â Â obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,<br>
+Â Â Â Â Â Â Â Â Â PLOG_DEBUG, "request-event: %d", rwflags);<br>
+Â Â ((struct obfs_test_socket_posix *) handle)->last_rwflags = 0;<br>
+Â Â if (rwflags)<br>
+Â Â {<br>
+Â Â Â Â event_set->vtab->set_event(event_set, ((struct obfs_test_socket_posix *) handle)->fd,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â rwflags, handle);<br>
+Â Â }<br>
+}<br>
+<br>
+static bool<br>
+obfs_test_posix_update_event(openvpn_transport_socket_t handle, void *arg, unsigned rwflags)<br>
+{<br>
+Â Â obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,<br>
+Â Â Â Â Â Â Â Â Â PLOG_DEBUG, "update-event: %p, %p, %d", handle, arg, rwflags);<br>
+Â Â if (arg != handle)<br>
+Â Â {<br>
+Â Â Â Â return false;<br>
+Â Â }<br>
+Â Â ((struct obfs_test_socket_posix *) handle)->last_rwflags |= rwflags;<br>
+Â Â return true;<br>
+}<br>
+<br>
+static unsigned<br>
+obfs_test_posix_pump(openvpn_transport_socket_t handle)<br>
+{<br>
+Â Â obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,<br>
+Â Â Â Â Â Â Â Â Â PLOG_DEBUG, "pump -> %d", ((struct obfs_test_socket_posix *) handle)->last_rwflags);<br>
+Â Â return ((struct obfs_test_socket_posix *) handle)->last_rwflags;<br>
+}<br>
+<br>
+static ssize_t<br>
+obfs_test_posix_recvfrom(openvpn_transport_socket_t handle, void *buf, size_t len,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â struct sockaddr *addr, socklen_t *addrlen)<br>
+{<br>
+Â Â int fd = ((struct obfs_test_socket_posix *) handle)->fd;<br>
+Â Â ssize_t result;<br>
+<br>
+again:<br>
+Â Â result = recvfrom(fd, buf, len, 0, addr, addrlen);<br>
+Â Â if (result < 0 && errno == EAGAIN)<br>
+Â Â {<br>
+Â Â Â Â ((struct obfs_test_socket_posix *) handle)->last_rwflags &= ~OPENVPN_TRANSPORT_EVENT_READ;<br>
+Â Â }<br>
+Â Â if (*addrlen > 0)<br>
+Â Â {<br>
+Â Â Â Â obfs_test_munge_addr(addr, *addrlen);<br>
+Â Â }<br>
+Â Â if (result > 0)<br>
+Â Â {<br>
+Â Â Â Â struct obfs_test_args *how = &((struct obfs_test_socket_posix *) handle)->args;<br>
+Â Â Â Â result = obfs_test_unmunge_buf(how, buf, result);<br>
+Â Â Â Â if (result < 0)<br>
+Â Â Â Â {<br>
+Â Â Â Â Â Â /* Pretend that read never happened. */<br>
+Â Â Â Â Â Â goto again;<br>
+Â Â Â Â }<br>
+Â Â }<br>
+<br>
+Â Â obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,<br>
+Â Â Â Â Â Â Â Â Â PLOG_DEBUG, "recvfrom(%d) -> %d", (int)len, (int)result);<br>
+Â Â return result;<br>
+}<br>
+<br>
+static ssize_t<br>
+obfs_test_posix_sendto(openvpn_transport_socket_t handle, const void *buf, size_t len,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â const struct sockaddr *addr, socklen_t addrlen)<br>
+{<br>
+Â Â int fd = ((struct obfs_test_socket_posix *) handle)->fd;<br>
+Â Â struct sockaddr *addr_rev = calloc(1, addrlen);<br>
+Â Â void *buf_munged = malloc(obfs_test_max_munged_buf_size(len));<br>
+Â Â size_t len_munged;<br>
+Â Â ssize_t result;<br>
+Â Â if (!addr_rev || !buf_munged)<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+<br>
+Â Â memcpy(addr_rev, addr, addrlen);<br>
+Â Â obfs_test_munge_addr(addr_rev, addrlen);<br>
+Â Â struct obfs_test_args *how = &((struct obfs_test_socket_posix *) handle)->args;<br>
+Â Â len_munged = obfs_test_munge_buf(how, buf_munged, buf, len);<br>
+Â Â result = sendto(fd, buf_munged, len_munged, 0, addr_rev, addrlen);<br>
+Â Â if (result < 0 && errno == EAGAIN)<br>
+Â Â {<br>
+Â Â Â Â ((struct obfs_test_socket_posix *) handle)->last_rwflags &= ~OPENVPN_TRANSPORT_EVENT_WRITE;<br>
+Â Â }<br>
+Â Â /* TODO: not clear what to do here for partial transfers. */<br>
+Â Â if (result > len)<br>
+Â Â {<br>
+Â Â Â Â result = len;<br>
+Â Â }<br>
+Â Â obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,<br>
+Â Â Â Â Â Â Â Â Â PLOG_DEBUG, "sendto(%d) -> %d", (int)len, (int)result);<br>
+Â Â free(addr_rev);<br>
+Â Â free(buf_munged);<br>
+Â Â return result;<br>
+<br>
+error:<br>
+Â Â free(addr_rev);<br>
+Â Â free(buf_munged);<br>
+Â Â return -1;<br>
+}<br>
+<br>
+static void<br>
+obfs_test_posix_close(openvpn_transport_socket_t handle)<br>
+{<br>
+Â Â free_socket((struct obfs_test_socket_posix *) handle);<br>
+}<br>
+<br>
+void<br>
+obfs_test_initialize_vtabs_platform(void)<br>
+{<br>
+Â Â obfs_test_bind_vtab.bind = obfs_test_posix_bind;<br>
+Â Â obfs_test_socket_vtab.request_event = obfs_test_posix_request_event;<br>
+Â Â obfs_test_socket_vtab.update_event = obfs_test_posix_update_event;<br>
+Â Â obfs_test_socket_vtab.pump = obfs_test_posix_pump;<br>
+Â Â obfs_test_socket_vtab.recvfrom = obfs_test_posix_recvfrom;<br>
+Â Â obfs_test_socket_vtab.sendto = obfs_test_posix_sendto;<br>
+Â Â obfs_test_socket_vtab.close = obfs_test_posix_close;<br>
+}<br>
diff --git a/src/plugins/obfs-test/obfs-test-win32.c b/src/plugins/obfs-test/obfs-test-win32.c<br>
new file mode 100644<br>
index 00000000..46c95f55<br>
--- /dev/null<br>
+++ b/src/plugins/obfs-test/obfs-test-win32.c<br>
@@ -0,0 +1,579 @@<br>
+#include "obfs-test.h"<br>
+#include <stdbool.h><br>
+#include <string.h><br>
+#include <stdio.h><br>
+#include <stdarg.h><br>
+#include <windows.h><br>
+#include <winsock2.h><br>
+#include <assert.h><br>
+<br>
+static inline bool<br>
+is_invalid_handle(HANDLE h)<br>
+{<br>
+Â Â return h == NULL || h == INVALID_HANDLE_VALUE;<br>
+}<br>
+<br>
+typedef enum {<br>
+  IO_SLOT_DORMANT,      /* must be 0 for calloc purposes */<br>
+Â Â IO_SLOT_PENDING,<br>
+Â Â /* success/failure is determined by succeeded flag in COMPLETE state */<br>
+Â Â IO_SLOT_COMPLETE<br>
+} io_slot_status_t;<br>
+<br>
+/* must be calloc'able */<br>
+struct io_slot<br>
+{<br>
+Â Â struct obfs_test_context *ctx;<br>
+Â Â io_slot_status_t status;<br>
+Â Â OVERLAPPED overlapped;<br>
+Â Â SOCKET socket;<br>
+Â Â SOCKADDR_STORAGE addr;<br>
+Â Â int addr_len, addr_cap;<br>
+Â Â DWORD bytes, flags;<br>
+Â Â bool succeeded;<br>
+Â Â int wsa_error;<br>
+<br>
+Â Â /* realloc'd as needed; always private copy, never aliased */<br>
+Â Â char *buf;<br>
+Â Â size_t buf_len, buf_cap;<br>
+};<br>
+<br>
+static bool<br>
+setup_io_slot(struct io_slot *slot, struct obfs_test_context *ctx,<br>
+Â Â Â Â Â Â Â SOCKET socket, HANDLE event)<br>
+{<br>
+Â Â slot->ctx = ctx;<br>
+Â Â slot->status = IO_SLOT_DORMANT;<br>
+Â Â slot->addr_cap = sizeof(SOCKADDR_STORAGE);<br>
+Â Â slot->socket = socket;<br>
+Â Â slot->overlapped.hEvent = event;<br>
+Â Â return true;<br>
+}<br>
+<br>
+/* Note that this assumes any I/O has already been implicitly canceled (via closesocket),<br>
+ * but not waited for yet. */<br>
+static bool<br>
+destroy_io_slot(struct io_slot *slot)<br>
+{<br>
+Â Â if (slot->status == IO_SLOT_PENDING)<br>
+Â Â {<br>
+Â Â Â Â DWORD bytes, flags;<br>
+Â Â Â Â BOOL ok = WSAGetOverlappedResult(slot->socket, &slot->overlapped, &bytes,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â TRUE /* wait */, &flags);<br>
+Â Â Â Â if (!ok && WSAGetLastError() == WSA_IO_INCOMPLETE)<br>
+Â Â Â Â {<br>
+Â Â Â Â Â Â obfs_test_log(slot->ctx, PLOG_ERR,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â "destroying I/O slot: canceled operation is still incomplete after wait?!");<br>
+Â Â Â Â Â Â return false;<br>
+Â Â Â Â }<br>
+Â Â }<br>
+<br>
+Â Â slot->status = IO_SLOT_DORMANT;<br>
+Â Â return true;<br>
+}<br>
+<br>
+/* FIXME: aborts on error. */<br>
+static void<br>
+resize_io_buf(struct io_slot *slot, size_t cap)<br>
+{<br>
+Â Â if (slot->buf)<br>
+Â Â {<br>
+Â Â Â Â free(slot->buf);<br>
+Â Â Â Â slot->buf = NULL;<br>
+Â Â }<br>
+<br>
+Â Â char *new_buf = malloc(cap);<br>
+Â Â if (!new_buf)<br>
+Â Â {<br>
+Â Â Â Â abort();<br>
+Â Â }<br>
+Â Â slot->buf = new_buf;<br>
+Â Â slot->buf_cap = cap;<br>
+}<br>
+<br>
+struct obfs_test_socket_win32<br>
+{<br>
+Â Â struct openvpn_transport_socket handle;<br>
+Â Â struct obfs_test_args args;<br>
+Â Â struct obfs_test_context *ctx;<br>
+Â Â SOCKET socket;<br>
+<br>
+Â Â /* Write is ready when idle; read is not-ready when idle. Both level-triggered. */<br>
+Â Â struct openvpn_transport_win32_event_pair completion_events;<br>
+Â Â struct io_slot slot_read, slot_write;<br>
+<br>
+Â Â int last_rwflags;<br>
+};<br>
+<br>
+static void<br>
+free_socket(struct obfs_test_socket_win32 *sock)<br>
+{<br>
+Â Â /* This only ever becomes false in strange situations where we leak the entire structure for<br>
+Â Â Â * lack of anything else to do. */<br>
+Â Â bool can_free = true;<br>
+<br>
+Â Â if (!sock)<br>
+Â Â {<br>
+Â Â Â Â return;<br>
+Â Â }<br>
+Â Â if (sock->socket != INVALID_SOCKET)<br>
+Â Â {<br>
+Â Â Â Â closesocket(sock->socket);<br>
+Â Â }<br>
+<br>
+Â Â /* closesocket cancels any pending overlapped I/O, but we still have to potentially<br>
+Â Â Â * wait for it here before we can free the buffers. This has to happen before closing<br>
+Â Â Â * the event handles.<br>
+Â Â Â *<br>
+Â Â Â * If we can't figure out when the canceled overlapped I/O is done, for any reason, we defensively<br>
+Â Â Â * leak the entire structure; freeing it would be permitting the system to corrupt memory later.<br>
+Â Â Â * TODO: possibly abort() instead, but make sure we've handled all the possible "have to try again"<br>
+Â Â Â * cases above first<br>
+Â Â Â */<br>
+Â Â if (!destroy_io_slot(&sock->slot_read))<br>
+Â Â {<br>
+Â Â Â Â can_free = false;<br>
+Â Â }<br>
+Â Â if (!destroy_io_slot(&sock->slot_write))<br>
+Â Â {<br>
+Â Â Â Â can_free = false;<br>
+Â Â }<br>
+Â Â if (!can_free)<br>
+Â Â {<br>
+Â Â Â Â /* Skip deinitialization of everything else. Doomed. */<br>
+Â Â Â Â obfs_test_log(sock->ctx, PLOG_ERR, "doomed, leaking the entire socket structure");<br>
+Â Â Â Â return;<br>
+Â Â }<br>
+<br>
+Â Â if (!is_invalid_handle(sock->completion_events.read))<br>
+Â Â {<br>
+Â Â Â Â CloseHandle(sock->completion_events.read);<br>
+Â Â }<br>
+Â Â if (!is_invalid_handle(sock->completion_events.write))<br>
+Â Â {<br>
+Â Â Â Â CloseHandle(sock->completion_events.write);<br>
+Â Â }<br>
+<br>
+Â Â free(sock);<br>
+}<br>
+<br>
+static openvpn_transport_socket_t<br>
+obfs_test_win32_bind(void *plugin_handle, openvpn_transport_args_t args,<br>
+Â Â Â Â Â Â Â Â Â Â Â const struct sockaddr *addr, openvpn_transport_socklen_t len)<br>
+{<br>
+Â Â struct obfs_test_socket_win32 *sock = NULL;<br>
+Â Â struct sockaddr *addr_rev = NULL;<br>
+<br>
+Â Â /* TODO: would be nice to factor out some of these sequences */<br>
+Â Â addr_rev = calloc(1, len);<br>
+Â Â if (!addr_rev)<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+Â Â memcpy(addr_rev, addr, len);<br>
+Â Â obfs_test_munge_addr(addr_rev, len);<br>
+<br>
+Â Â sock = calloc(1, sizeof(struct obfs_test_socket_win32));<br>
+Â Â if (!sock)<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+Â Â sock->handle.vtab = &obfs_test_socket_vtab;<br>
+Â Â sock->ctx = (struct obfs_test_context *) plugin_handle;<br>
+Â Â memcpy(&sock->args, args, sizeof(sock->args));<br>
+<br>
+Â Â /* Preemptively initialize the members of some Win32 types so error exits are okay later on.<br>
+Â Â Â * HANDLEs of NULL are considered invalid per above. */<br>
+Â Â sock->socket = INVALID_SOCKET;<br>
+<br>
+Â Â sock->socket = socket(addr_rev->sa_family, SOCK_DGRAM, IPPROTO_UDP);<br>
+Â Â if (sock->socket == INVALID_SOCKET)<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+<br>
+Â Â /* See above: write is ready when idle, read is not-ready when idle. */<br>
+Â Â sock->completion_events.read = CreateEvent(NULL, TRUE, FALSE, NULL);<br>
+Â Â sock->completion_events.write = CreateEvent(NULL, TRUE, TRUE, NULL);<br>
+Â Â if (is_invalid_handle(sock->completion_events.read) || is_invalid_handle(sock->completion_events.write))<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+Â Â if (!setup_io_slot(&sock->slot_read, sock->ctx,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â sock->socket, sock->completion_events.read))<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+Â Â if (!setup_io_slot(&sock->slot_write, sock->ctx,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â sock->socket, sock->completion_events.write))<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+<br>
+Â Â if (bind(sock->socket, addr_rev, len))<br>
+Â Â {<br>
+Â Â Â Â goto error;<br>
+Â Â }<br>
+Â Â free(addr_rev);<br>
+Â Â return &sock->handle;<br>
+<br>
+error:<br>
+Â Â obfs_test_log((struct obfs_test_context *) plugin_handle, PLOG_ERR,<br>
+Â Â Â Â Â Â Â Â Â "bind failure: WSA error = %d", WSAGetLastError());<br>
+Â Â free_socket(sock);<br>
+Â Â free(addr_rev);<br>
+Â Â return NULL;<br>
+}<br>
+<br>
+static void<br>
+handle_sendrecv_return(struct io_slot *slot, int status)<br>
+{<br>
+Â Â if (status == 0)<br>
+Â Â {<br>
+Â Â Â Â /* Immediately completed. Set the event so it stays consistent. */<br>
+Â Â Â Â slot->status = IO_SLOT_COMPLETE;<br>
+Â Â Â Â slot->succeeded = true;<br>
+Â Â Â Â slot->buf_len = slot->bytes;<br>
+Â Â Â Â SetEvent(slot->overlapped.hEvent);<br>
+Â Â }<br>
+Â Â else if (WSAGetLastError() == WSA_IO_PENDING)<br>
+Â Â {<br>
+Â Â Â Â /* Queued. */<br>
+Â Â Â Â slot->status = IO_SLOT_PENDING;<br>
+Â Â }<br>
+Â Â else<br>
+Â Â {<br>
+Â Â Â Â /* Error. */<br>
+Â Â Â Â slot->status = IO_SLOT_COMPLETE;<br>
+Â Â Â Â slot->succeeded = false;<br>
+Â Â Â Â slot->wsa_error = WSAGetLastError();<br>
+Â Â Â Â slot->buf_len = 0;<br>
+Â Â }<br>
+}<br>
+<br>
+static void<br>
+queue_new_read(struct io_slot *slot, size_t cap)<br>
+{<br>
+Â Â int status;<br>
+Â Â WSABUF sbuf;<br>
+Â Â assert(slot->status == IO_SLOT_DORMANT);<br>
+<br>
+Â Â ResetEvent(slot->overlapped.hEvent);<br>
+Â Â resize_io_buf(slot, cap);<br>
+Â Â sbuf.buf = slot->buf;<br>
+Â Â sbuf.len = slot->buf_cap;<br>
+Â Â slot->addr_len = slot->addr_cap;<br>
+Â Â slot->flags = 0;<br>
+Â Â status = WSARecvFrom(slot->socket, &sbuf, 1, &slot->bytes, &slot->flags,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â (struct sockaddr *)&slot->addr, &slot->addr_len,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â &slot->overlapped, NULL);<br>
+Â Â handle_sendrecv_return(slot, status);<br>
+}<br>
+<br>
+/* write slot buffer must already be full. */<br>
+static void<br>
+queue_new_write(struct io_slot *slot)<br>
+{<br>
+Â Â int status;<br>
+Â Â WSABUF sbuf;<br>
+Â Â assert(slot->status == IO_SLOT_COMPLETE || slot->status == IO_SLOT_DORMANT);<br>
+<br>
+Â Â ResetEvent(slot->overlapped.hEvent);<br>
+Â Â sbuf.buf = slot->buf;<br>
+Â Â sbuf.len = slot->buf_len;<br>
+Â Â slot->flags = 0;<br>
+Â Â status = WSASendTo(slot->socket, &sbuf, 1, &slot->bytes, 0 /* flags */,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â (struct sockaddr *)&slot->addr, slot->addr_len,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â &slot->overlapped, NULL);<br>
+Â Â handle_sendrecv_return(slot, status);<br>
+}<br>
+<br>
+static void<br>
+ensure_pending_read(struct obfs_test_socket_win32 *sock)<br>
+{<br>
+Â Â struct io_slot *slot = &sock->slot_read;<br>
+Â Â switch (slot->status)<br>
+Â Â {<br>
+Â Â Â Â case IO_SLOT_PENDING:<br>
+Â Â Â Â Â Â return;<br>
+<br>
+Â Â Â Â case IO_SLOT_COMPLETE:<br>
+Â Â Â Â Â Â /* Set the event manually here just in case. */<br>
+Â Â Â Â Â Â SetEvent(slot->overlapped.hEvent);<br>
+Â Â Â Â Â Â return;<br>
+<br>
+Â Â Â Â case IO_SLOT_DORMANT:<br>
+Â Â Â Â Â Â /* TODO: we don't propagate max read size here, so we just have to assume the maximum. */<br>
+Â Â Â Â Â Â queue_new_read(slot, 65536);<br>
+Â Â Â Â Â Â return;<br>
+<br>
+Â Â Â Â default:<br>
+Â Â Â Â Â Â abort();<br>
+Â Â }<br>
+}<br>
+<br>
+static bool<br>
+complete_pending_operation(struct io_slot *slot)<br>
+{<br>
+Â Â DWORD bytes, flags;<br>
+Â Â BOOL ok;<br>
+<br>
+Â Â switch (slot->status)<br>
+Â Â {<br>
+Â Â Â Â case IO_SLOT_DORMANT:<br>
+Â Â Â Â Â Â /* TODO: shouldn't get here? */<br>
+Â Â Â Â Â Â return false;<br>
+<br>
+Â Â Â Â case IO_SLOT_COMPLETE:<br>
+Â Â Â Â Â Â return true;<br>
+<br>
+Â Â Â Â case IO_SLOT_PENDING:<br>
+Â Â Â Â Â Â ok = WSAGetOverlappedResult(slot->socket, &slot->overlapped, &bytes,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â FALSE /* don't wait */, &flags);<br>
+Â Â Â Â Â Â if (!ok && WSAGetLastError() == WSA_IO_INCOMPLETE)<br>
+Â Â Â Â Â Â {<br>
+Â Â Â Â Â Â Â Â /* Still waiting. */<br>
+Â Â Â Â Â Â Â Â return false;<br>
+Â Â Â Â Â Â }<br>
+Â Â Â Â Â Â else if (ok)<br>
+Â Â Â Â Â Â {<br>
+Â Â Â Â Â Â Â Â /* Completed. slot->addr_len has already been updated. */<br>
+Â Â Â Â Â Â Â Â slot->buf_len = bytes;<br>
+Â Â Â Â Â Â Â Â slot->status = IO_SLOT_COMPLETE;<br>
+Â Â Â Â Â Â Â Â slot->succeeded = true;<br>
+Â Â Â Â Â Â Â Â return true;<br>
+Â Â Â Â Â Â }<br>
+Â Â Â Â Â Â else<br>
+Â Â Â Â Â Â {<br>
+Â Â Â Â Â Â Â Â /* Error. */<br>
+Â Â Â Â Â Â Â Â slot->buf_len = 0;<br>
+Â Â Â Â Â Â Â Â slot->status = IO_SLOT_COMPLETE;<br>
+Â Â Â Â Â Â Â Â slot->succeeded = false;<br>
+Â Â Â Â Â Â Â Â slot->wsa_error = WSAGetLastError();<br>
+Â Â Â Â Â Â Â Â return true;<br>
+Â Â Â Â Â Â }<br>
+<br>
+Â Â Â Â default:<br>
+Â Â Â Â Â Â abort();<br>
+Â Â }<br>
+}<br>
+<br>
+static bool<br>
+complete_pending_read(struct obfs_test_socket_win32 *sock)<br>
+{<br>
+Â Â bool done = complete_pending_operation(&sock->slot_read);<br>
+Â Â if (done)<br>
+Â Â {<br>
+Â Â Â Â ResetEvent(sock->completion_events.read);<br>
+Â Â }<br>
+Â Â return done;<br>
+}<br>
+<br>
+static void<br>
+consumed_pending_read(struct obfs_test_socket_win32 *sock)<br>
+{<br>
+Â Â struct io_slot *slot = &sock->slot_read;<br>
+Â Â assert(slot->status == IO_SLOT_COMPLETE);<br>
+Â Â slot->status = IO_SLOT_DORMANT;<br>
+Â Â slot->succeeded = false;<br>
+Â Â ResetEvent(slot->overlapped.hEvent);<br>
+}<br>
+<br>
+static inline bool<br>
+complete_pending_write(struct obfs_test_socket_win32 *sock)<br>
+{<br>
+Â Â bool done = complete_pending_operation(&sock->slot_write);<br>
+Â Â if (done)<br>
+Â Â {<br>
+Â Â Â Â SetEvent(sock->completion_events.write);<br>
+Â Â }<br>
+Â Â return done;<br>
+}<br>
+<br>
+static void<br>
+obfs_test_win32_request_event(openvpn_transport_socket_t handle,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â openvpn_transport_event_set_handle_t event_set, unsigned rwflags)<br>
+{<br>
+Â Â struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32 *)handle;<br>
+Â Â obfs_test_log(sock->ctx, PLOG_DEBUG, "request-event: %d", rwflags);<br>
+Â Â sock->last_rwflags = 0;<br>
+<br>
+Â Â if (rwflags & OPENVPN_TRANSPORT_EVENT_READ)<br>
+Â Â {<br>
+Â Â Â Â ensure_pending_read(sock);<br>
+Â Â }<br>
+Â Â if (rwflags)<br>
+Â Â {<br>
+Â Â Â Â event_set->vtab->set_event(event_set, &sock->completion_events, rwflags, handle);<br>
+Â Â }<br>
+}<br>
+<br>
+static bool<br>
+obfs_test_win32_update_event(openvpn_transport_socket_t handle, void *arg, unsigned rwflags)<br>
+{<br>
+Â Â obfs_test_log(((struct obfs_test_socket_win32 *) handle)->ctx, PLOG_DEBUG,<br>
+Â Â Â Â Â Â Â Â Â "update-event: %p, %p, %d", handle, arg, rwflags);<br>
+Â Â if (arg != handle)<br>
+Â Â {<br>
+Â Â Â Â return false;<br>
+Â Â }<br>
+Â Â ((struct obfs_test_socket_win32 *) handle)->last_rwflags |= rwflags;<br>
+Â Â return true;<br>
+}<br>
+<br>
+static unsigned<br>
+obfs_test_win32_pump(openvpn_transport_socket_t handle)<br>
+{<br>
+Â Â struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32 *)handle;<br>
+Â Â unsigned result = 0;<br>
+<br>
+Â Â if ((sock->last_rwflags & OPENVPN_TRANSPORT_EVENT_READ) && complete_pending_read(sock))<br>
+Â Â {<br>
+Â Â Â Â result |= OPENVPN_TRANSPORT_EVENT_READ;<br>
+Â Â }<br>
+Â Â if ((sock->last_rwflags & OPENVPN_TRANSPORT_EVENT_WRITE)<br>
+Â Â Â Â && (sock->slot_write.status != IO_SLOT_PENDING || complete_pending_write(sock)))<br>
+Â Â {<br>
+Â Â Â Â result |= OPENVPN_TRANSPORT_EVENT_WRITE;<br>
+Â Â }<br>
+<br>
+Â Â obfs_test_log(sock->ctx, PLOG_DEBUG, "pump -> %d", result);<br>
+Â Â return result;<br>
+}<br>
+<br>
+static ssize_t<br>
+obfs_test_win32_recvfrom(openvpn_transport_socket_t handle, void *buf, size_t len,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â struct sockaddr *addr, openvpn_transport_socklen_t *addrlen)<br>
+{<br>
+Â Â struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32 *)handle;<br>
+Â Â if (!complete_pending_read(sock))<br>
+Â Â {<br>
+Â Â Â Â WSASetLastError(WSA_IO_INCOMPLETE);<br>
+Â Â Â Â return -1;<br>
+Â Â }<br>
+<br>
+Â Â if (!sock->slot_read.succeeded)<br>
+Â Â {<br>
+Â Â Â Â int wsa_error = sock->slot_read.wsa_error;<br>
+Â Â Â Â consumed_pending_read(sock);<br>
+Â Â Â Â WSASetLastError(wsa_error);<br>
+Â Â Â Â return -1;<br>
+Â Â }<br>
+<br>
+Â Â /* sock->slot_read now has valid data. */<br>
+Â Â char *working_buf = sock->slot_read.buf;<br>
+Â Â ssize_t unmunged_len =<br>
+Â Â Â Â obfs_test_unmunge_buf(&sock->args, working_buf,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â sock->slot_read.buf_len);<br>
+Â Â if (unmunged_len < 0)<br>
+Â Â {<br>
+Â Â Â Â /* Act as though this read never happened. Assume one was queued before, so it should<br>
+Â Â Â Â Â * still remain queued. */<br>
+Â Â Â Â consumed_pending_read(sock);<br>
+Â Â Â Â ensure_pending_read(sock);<br>
+Â Â Â Â WSASetLastError(WSA_IO_INCOMPLETE);<br>
+Â Â Â Â return -1;<br>
+Â Â }<br>
+<br>
+Â Â size_t copy_len = unmunged_len;<br>
+Â Â if (copy_len > len)<br>
+Â Â {<br>
+Â Â Â Â copy_len = len;<br>
+Â Â }<br>
+Â Â memcpy(buf, sock->slot_read.buf, copy_len);<br>
+<br>
+Â Â /* TODO: shouldn't truncate, should signal error (but this shouldn't happen for any<br>
+Â Â Â * supported address families anyway). */<br>
+Â Â openvpn_transport_socklen_t addr_copy_len = *addrlen;<br>
+Â Â if (sock->slot_read.addr_len < addr_copy_len)<br>
+Â Â {<br>
+Â Â Â Â addr_copy_len = sock->slot_read.addr_len;<br>
+Â Â }<br>
+Â Â memcpy(addr, &sock->slot_read.addr, addr_copy_len);<br>
+Â Â *addrlen = addr_copy_len;<br>
+Â Â if (addr_copy_len > 0)<br>
+Â Â {<br>
+Â Â Â Â obfs_test_munge_addr(addr, addr_copy_len);<br>
+Â Â }<br>
+<br>
+Â Â /* Reset the I/O slot before returning. */<br>
+Â Â consumed_pending_read(sock);<br>
+Â Â return copy_len;<br>
+}<br>
+<br>
+static ssize_t<br>
+obfs_test_win32_sendto(openvpn_transport_socket_t handle, const void *buf, size_t len,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â const struct sockaddr *addr, openvpn_transport_socklen_t addrlen)<br>
+{<br>
+Â Â struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32 *)handle;<br>
+Â Â complete_pending_write(sock);<br>
+<br>
+Â Â if (sock->slot_write.status == IO_SLOT_PENDING)<br>
+Â Â {<br>
+Â Â Â Â /* This shouldn't really happen, but. */<br>
+Â Â Â Â WSASetLastError(WSAEWOULDBLOCK);<br>
+Â Â Â Â return -1;<br>
+Â Â }<br>
+<br>
+Â Â if (addrlen > sock->slot_write.addr_cap)<br>
+Â Â {<br>
+Â Â Â Â /* Shouldn't happen. */<br>
+Â Â Â Â WSASetLastError(WSAEFAULT);<br>
+Â Â Â Â return -1;<br>
+Â Â }<br>
+<br>
+Â Â /* TODO: propagate previous write errors---what does core expect here? */<br>
+Â Â memcpy(&sock->slot_write.addr, addr, addrlen);<br>
+Â Â sock->slot_write.addr_len = addrlen;<br>
+Â Â if (addrlen > 0)<br>
+Â Â {<br>
+Â Â Â Â obfs_test_munge_addr((struct sockaddr *)&sock->slot_write.addr, addrlen);<br>
+Â Â }<br>
+Â Â resize_io_buf(&sock->slot_write, obfs_test_max_munged_buf_size(len));<br>
+Â Â sock->slot_write.buf_len =<br>
+Â Â Â Â obfs_test_munge_buf(&sock->args, sock->slot_write.buf, buf, len);<br>
+Â Â queue_new_write(&sock->slot_write);<br>
+Â Â switch (sock->slot_write.status)<br>
+Â Â {<br>
+Â Â Â Â case IO_SLOT_PENDING:<br>
+Â Â Â Â Â Â /* The network hasn't given us an error yet, but _we've_ consumed all the bytes.<br>
+Â Â Â Â Â Â Â * ... sort of. */<br>
+Â Â Â Â Â Â return len;<br>
+<br>
+Â Â Â Â case IO_SLOT_DORMANT:<br>
+Â Â Â Â Â Â /* Huh?? But we just queued a write. */<br>
+Â Â Â Â Â Â abort();<br>
+<br>
+Â Â Â Â case IO_SLOT_COMPLETE:<br>
+Â Â Â Â Â Â if (sock->slot_write.succeeded)<br>
+Â Â Â Â Â Â {<br>
+Â Â Â Â Â Â Â Â /* TODO: more partial length handling */<br>
+Â Â Â Â Â Â Â Â return len;<br>
+Â Â Â Â Â Â }<br>
+Â Â Â Â Â Â else<br>
+Â Â Â Â Â Â {<br>
+Â Â Â Â Â Â Â Â return -1;<br>
+Â Â Â Â Â Â }<br>
+<br>
+Â Â Â Â default:<br>
+Â Â Â Â Â Â abort();<br>
+Â Â }<br>
+}<br>
+<br>
+static void<br>
+obfs_test_win32_close(openvpn_transport_socket_t handle)<br>
+{<br>
+Â Â free_socket((struct obfs_test_socket_win32 *) handle);<br>
+}<br>
+<br>
+void<br>
+obfs_test_initialize_vtabs_platform(void)<br>
+{<br>
+Â Â obfs_test_bind_vtab.bind = obfs_test_win32_bind;<br>
+Â Â obfs_test_socket_vtab.request_event = obfs_test_win32_request_event;<br>
+Â Â obfs_test_socket_vtab.update_event = obfs_test_win32_update_event;<br>
+Â Â obfs_test_socket_vtab.pump = obfs_test_win32_pump;<br>
+Â Â obfs_test_socket_vtab.recvfrom = obfs_test_win32_recvfrom;<br>
+Â Â obfs_test_socket_vtab.sendto = obfs_test_win32_sendto;<br>
+Â Â obfs_test_socket_vtab.close = obfs_test_win32_close;<br>
+}<br>
diff --git a/src/plugins/obfs-test/obfs-test.c b/src/plugins/obfs-test/obfs-test.c<br>
new file mode 100644<br>
index 00000000..27a3d21e<br>
--- /dev/null<br>
+++ b/src/plugins/obfs-test/obfs-test.c<br>
@@ -0,0 +1,94 @@<br>
+#include <stdlib.h><br>
+#include <string.h><br>
+#include <stdbool.h><br>
+#include "openvpn-plugin.h"<br>
+#include "openvpn-transport.h"<br>
+#include "obfs-test.h"<br>
+<br>
+struct openvpn_transport_bind_vtab1 obfs_test_bind_vtab = { 0 };<br>
+struct openvpn_transport_socket_vtab1 obfs_test_socket_vtab = { 0 };<br>
+<br>
+struct obfs_test_context<br>
+{<br>
+Â Â struct openvpn_plugin_callbacks *global_vtab;<br>
+};<br>
+<br>
+static void<br>
+free_context(struct obfs_test_context *context)<br>
+{<br>
+Â Â if (!context)<br>
+Â Â {<br>
+Â Â Â Â return;<br>
+Â Â }<br>
+Â Â free(context);<br>
+}<br>
+<br>
+OPENVPN_EXPORT int<br>
+openvpn_plugin_open_v3(int version, struct openvpn_plugin_args_open_in const *args,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â struct openvpn_plugin_args_open_return *out)<br>
+{<br>
+Â Â struct obfs_test_context *context;<br>
+<br>
+Â Â context = (struct obfs_test_context *) calloc(1, sizeof(struct obfs_test_context));<br>
+Â Â if (!context)<br>
+Â Â {<br>
+Â Â Â Â return OPENVPN_PLUGIN_FUNC_ERROR;<br>
+Â Â }<br>
+<br>
+Â Â context->global_vtab = args->callbacks;<br>
+Â Â obfs_test_initialize_vtabs_platform();<br>
+Â Â obfs_test_bind_vtab.parseargs = obfs_test_parseargs;<br>
+Â Â obfs_test_bind_vtab.argerror = obfs_test_argerror;<br>
+Â Â obfs_test_bind_vtab.freeargs = obfs_test_freeargs;<br>
+<br>
+Â Â out->type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TRANSPORT);<br>
+Â Â out->handle = (openvpn_plugin_handle_t *) context;<br>
+Â Â return OPENVPN_PLUGIN_FUNC_SUCCESS;<br>
+<br>
+err:<br>
+Â Â free_context(context);<br>
+Â Â return OPENVPN_PLUGIN_FUNC_ERROR;<br>
+}<br>
+<br>
+OPENVPN_EXPORT void<br>
+openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)<br>
+{<br>
+Â Â free_context((struct obfs_test_context *) handle);<br>
+}<br>
+<br>
+OPENVPN_EXPORT int<br>
+openvpn_plugin_func_v3(int version,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â struct openvpn_plugin_args_func_in const *arguments,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â struct openvpn_plugin_args_func_return *retptr)<br>
+{<br>
+Â Â /* We don't ask for any bits that use this interface. */<br>
+Â Â return OPENVPN_PLUGIN_FUNC_ERROR;<br>
+}<br>
+<br>
+OPENVPN_EXPORT void *<br>
+openvpn_plugin_get_vtab_v1(int selector, size_t *size_out)<br>
+{<br>
+Â Â switch (selector)<br>
+Â Â {<br>
+Â Â Â Â case OPENVPN_VTAB_TRANSPORT_BIND_V1:<br>
+Â Â Â Â Â Â if (obfs_test_bind_vtab.bind == NULL)<br>
+Â Â Â Â Â Â {<br>
+Â Â Â Â Â Â Â Â return NULL;<br>
+Â Â Â Â Â Â }<br>
+Â Â Â Â Â Â *size_out = sizeof(struct openvpn_transport_bind_vtab1);<br>
+Â Â Â Â Â Â return &obfs_test_bind_vtab;<br>
+<br>
+Â Â Â Â default:<br>
+Â Â Â Â Â Â return NULL;<br>
+Â Â }<br>
+}<br>
+<br>
+void<br>
+obfs_test_log(struct obfs_test_context *ctx,<br>
+Â Â Â Â Â Â Â openvpn_plugin_log_flags_t flags, const char *fmt, ...)<br>
+{<br>
+Â Â va_list va;<br>
+Â Â va_start(va, fmt);<br>
+Â Â ctx->global_vtab->plugin_vlog(flags, OBFS_TEST_PLUGIN_NAME, fmt, va);<br>
+Â Â va_end(va);<br>
+}<br>
diff --git a/src/plugins/obfs-test/obfs-test.exports b/src/plugins/obfs-test/obfs-test.exports<br>
new file mode 100644<br>
index 00000000..e7baada4<br>
--- /dev/null<br>
+++ b/src/plugins/obfs-test/obfs-test.exports<br>
@@ -0,0 +1,4 @@<br>
+openvpn_plugin_open_v3<br>
+openvpn_plugin_close_v1<br>
+openvpn_plugin_get_vtab_v1<br>
+openvpn_plugin_func_v3<br>
diff --git a/src/plugins/obfs-test/obfs-test.h b/src/plugins/obfs-test/obfs-test.h<br>
new file mode 100644<br>
index 00000000..b9a6f8b4<br>
--- /dev/null<br>
+++ b/src/plugins/obfs-test/obfs-test.h<br>
@@ -0,0 +1,42 @@<br>
+#ifndef OPENVPN_PLUGIN_OBFS_TEST_H<br>
+#define OPENVPN_PLUGIN_OBFS_TEST_H 1<br>
+<br>
+#include "openvpn-plugin.h"<br>
+#include "openvpn-transport.h"<br>
+<br>
+#define OBFS_TEST_PLUGIN_NAME "obfs-test"<br>
+<br>
+struct obfs_test_context;<br>
+<br>
+struct obfs_test_args<br>
+{<br>
+Â Â const char *error;<br>
+Â Â int offset;<br>
+};<br>
+<br>
+extern struct openvpn_transport_bind_vtab1 obfs_test_bind_vtab;<br>
+extern struct openvpn_transport_socket_vtab1 obfs_test_socket_vtab;<br>
+<br>
+void obfs_test_initialize_vtabs_platform(void);<br>
+<br>
+void obfs_test_munge_addr(struct sockaddr *addr, openvpn_transport_socklen_t len);<br>
+<br>
+size_t obfs_test_max_munged_buf_size(size_t clear_size);<br>
+<br>
+size_t obfs_test_munge_buf(struct obfs_test_args *how,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â Â char *out, const char *in, size_t len);<br>
+<br>
+ssize_t obfs_test_unmunge_buf(struct obfs_test_args *how,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â char *buf, size_t len);<br>
+<br>
+openvpn_transport_args_t obfs_test_parseargs(void *plugin_handle,<br>
+Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â const char *const *argv, int argc);<br>
+<br>
+const char *obfs_test_argerror(openvpn_transport_args_t args);<br>
+<br>
+void obfs_test_freeargs(openvpn_transport_args_t args);<br>
+<br>
+void obfs_test_log(struct obfs_test_context *ctx,<br>
+Â Â Â Â Â Â Â Â Â Â openvpn_plugin_log_flags_t flags, const char *fmt, ...);<br>
+<br>
+#endif /* !OPENVPN_PLUGIN_OBFS_TEST_H */<br>
-- <br>
2.19.2<br>
<br>
<br>
<br>
_______________________________________________<br>
Openvpn-devel mailing list<br>
<a href="mailto:Openvpn-devel@lists.sourceforge.net" target="_blank">Openvpn-devel@lists.sourceforge.net</a><br>
<a href="https://lists.sourceforge.net/lists/listinfo/openvpn-devel" rel="noreferrer" target="_blank">https://lists.sourceforge.net/lists/listinfo/openvpn-devel</a><br>
</blockquote></div>
@@ -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
@@ -12,4 +12,4 @@
MAINTAINERCLEANFILES = \
$(srcdir)/Makefile.in
-SUBDIRS = auth-pam down-root
+SUBDIRS = auth-pam down-root obfs-test
new file mode 100644
@@ -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
new file mode 100644
@@ -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.
new file mode 100644
@@ -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));
+ 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_;
+}
new file mode 100644
@@ -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)
+{
+ 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. */
+#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;
+}
new file mode 100644
@@ -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
+ * 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)
+{
+ 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,
+ rwflags, handle);
+ }
+}
+
+static bool
+obfs_test_posix_update_event(openvpn_transport_socket_t handle, void *arg, unsigned rwflags)
+{
+ obfs_test_log(((struct obfs_test_socket_posix *) handle)->ctx,
+ PLOG_DEBUG, "update-event: %p, %p, %d", handle, arg, rwflags);
+ 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);
+ 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,
+ 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;
+ }
+ if (*addrlen > 0)
+ {
+ obfs_test_munge_addr(addr, *addrlen);
+ }
+ if (result > 0)
+ {
+ struct obfs_test_args *how = &((struct obfs_test_socket_posix *) handle)->args;
+ 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);
+ return result;
+}
+
+static ssize_t
+obfs_test_posix_sendto(openvpn_transport_socket_t handle, const void *buf, size_t len,
+ 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;
+ 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;
+ }
+ /* 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;
+}
new file mode 100644
@@ -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 */
+ 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. */
+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,
+ 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?!");
+ 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. */
+ 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
+ * 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
+ * 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"
+ * 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");
+ 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)
+{
+ 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.
+ * 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. */
+ 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))
+ {
+ 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,
+ (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);
+
+ 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 */,
+ (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. */
+ 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,
+ 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)
+{
+ struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32 *)handle;
+ 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);
+ }
+}
+
+static bool
+obfs_test_win32_update_event(openvpn_transport_socket_t handle, void *arg, unsigned rwflags)
+{
+ obfs_test_log(((struct obfs_test_socket_win32 *) handle)->ctx, PLOG_DEBUG,
+ "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;
+ unsigned result = 0;
+
+ if ((sock->last_rwflags & OPENVPN_TRANSPORT_EVENT_READ) && complete_pending_read(sock))
+ {
+ result |= OPENVPN_TRANSPORT_EVENT_READ;
+ }
+ if ((sock->last_rwflags & OPENVPN_TRANSPORT_EVENT_WRITE)
+ && (sock->slot_write.status != IO_SLOT_PENDING || complete_pending_write(sock)))
+ {
+ 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)
+{
+ struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32 *)handle;
+ 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
+ * 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
+ * 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)
+{
+ struct obfs_test_socket_win32 *sock = (struct obfs_test_socket_win32 *)handle;
+ 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? */
+ 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);
+ }
+ 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.
+ * ... 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;
+}
new file mode 100644
@@ -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,
+ struct openvpn_plugin_args_open_return *out)
+{
+ struct obfs_test_context *context;
+
+ context = (struct obfs_test_context *) calloc(1, sizeof(struct obfs_test_context));
+ 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,
+ 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);
+}
new file mode 100644
@@ -0,0 +1,4 @@
+openvpn_plugin_open_v3
+openvpn_plugin_close_v1
+openvpn_plugin_get_vtab_v1
+openvpn_plugin_func_v3
new file mode 100644
@@ -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);
+
+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);
+
+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, ...);
+
+#endif /* !OPENVPN_PLUGIN_OBFS_TEST_H */