Message ID | 20181230112901.29241-5-a@unstable.cc |
---|---|
State | Changes Requested |
Headers | show |
Series | Transport API: offload traffic manipulation to plugins | expand |
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>
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 */