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