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 <r...@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 <r...@dasyatidae.com>
> [anto...@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
>
_______________________________________________
Openvpn-devel mailing list
Openvpn-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to