From patchwork Sun Dec 30 00:28:58 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 651 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director11.mail.ord1d.rsapps.net ([172.30.191.6]) by backend30.mail.ord1d.rsapps.net with LMTP id 2LXuN5KsKFyRSAAAIUCqbw for ; Sun, 30 Dec 2018 06:31:30 -0500 Received: from proxy7.mail.ord1d.rsapps.net ([172.30.191.6]) by director11.mail.ord1d.rsapps.net with LMTP id cHyuN5KsKFx7SwAAvGGmqA ; Sun, 30 Dec 2018 06:31:30 -0500 Received: from smtp6.gate.ord1d ([172.30.191.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy7.mail.ord1d.rsapps.net with LMTP id uL4eOJKsKFx2ZAAAMe1Fpw ; Sun, 30 Dec 2018 06:31:30 -0500 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp6.gate.ord1d.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=unstable.cc X-Suspicious-Flag: YES X-Classification-ID: 7397c4a4-0c26-11e9-bebd-52540050e3e0-1-1 Received: from [216.105.38.7] ([216.105.38.7:11388] helo=lists.sourceforge.net) by smtp6.gate.ord1d.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id CB/5D-19378-29CA82C5; Sun, 30 Dec 2018 06:31:30 -0500 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1gdZIY-0004aU-8n; Sun, 30 Dec 2018 11:30:26 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1gdZIW-0004aM-G9 for openvpn-devel@lists.sourceforge.net; Sun, 30 Dec 2018 11:30:24 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=VdxM0Xs6rT/V9RELsyWcs2SpivIN+Zy+6NwWZVq6KwM=; b=NE5Ezyzes2CcteAWmhiYgKbLZn xHQt6BxBllkq/TqEP+W0z3v/3A/vR+oF5xJZ7bBKCWMToVFR/BovBtFp5l6LbnWRu70kHum5EzM6S FOXX4Tysa4i8+xKkqCEbUSRXX54ibiM56WyWwzINy8bKdmq72Esy8qIj1VXNdY4N5PPo=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=VdxM0Xs6rT/V9RELsyWcs2SpivIN+Zy+6NwWZVq6KwM=; b=LOc8OVoJbXe/hiq0HJT1z9Y+d6 RImSKwGYnMqOGPTfio+a+Ljd1l2ujhKW/HMfLhWw83G8GLWkz+4x5E/uVCTiQ7TH9Es1K/4LGg4qK M5xAJz5Ucz6v9mUTBdlgYBJ5PhaYXz/sbtylC5K08DFZFZ/7njaF0rtDL0cLjvLEUF9Y=; Received: from s2.neomailbox.net ([5.148.176.60]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1.2:DHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1gdZIT-006XEx-Hz for openvpn-devel@lists.sourceforge.net; Sun, 30 Dec 2018 11:30:24 +0000 From: Antonio Quartulli To: openvpn-devel@lists.sourceforge.net Date: Sun, 30 Dec 2018 21:28:58 +1000 Message-Id: <20181230112901.29241-2-a@unstable.cc> In-Reply-To: <20181230112901.29241-1-a@unstable.cc> References: <20181230112901.29241-1-a@unstable.cc> MIME-Version: 1.0 X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [5.148.176.60 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1gdZIT-006XEx-Hz Subject: [Openvpn-devel] [PATCH 1/4] transport: introduce tranport API plugin codebase X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: antonio@openvpn.net, Robin Tarsiger Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Robin Tarsiger This API will allow plugins to provide virtual socket implementations with their own table of functions for operations on these sockets, with limited but functional integration with the main OpenVPN event loop. This new mechanism can be used to implement alternative transport protocols via external plugins, without interfering with the OpenVPN core codebase. A sample user of this API might be an obsufcation schema which manipulates the traffic before sending/receiving it. Signed-off-by: Robin Tarsiger [antonio@openvpn.net: refactored commits, restyled code] --- include/Makefile.am | 1 + include/openvpn-plugin.h.in | 31 +++- include/openvpn-transport.h | 240 ++++++++++++++++++++++++++++ src/openvpn/Makefile.am | 1 + src/openvpn/plugin.c | 4 + src/openvpn/plugin.h | 1 + src/openvpn/socket.h | 3 + src/openvpn/transport.c | 303 ++++++++++++++++++++++++++++++++++++ src/openvpn/transport.h | 94 +++++++++++ 9 files changed, 677 insertions(+), 1 deletion(-) create mode 100644 include/openvpn-transport.h create mode 100644 src/openvpn/transport.c create mode 100644 src/openvpn/transport.h diff --git a/include/Makefile.am b/include/Makefile.am index 484e4e12..5c0ffcc4 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -15,4 +15,5 @@ MAINTAINERCLEANFILES = \ include_HEADERS = \ openvpn-plugin.h \ + openvpn-transport.h \ openvpn-msg.h diff --git a/include/openvpn-plugin.h.in b/include/openvpn-plugin.h.in index 103844f7..3539e534 100644 --- a/include/openvpn-plugin.h.in +++ b/include/openvpn-plugin.h.in @@ -129,7 +129,8 @@ extern "C" { #define OPENVPN_PLUGIN_TLS_FINAL 10 #define OPENVPN_PLUGIN_ENABLE_PF 11 #define OPENVPN_PLUGIN_ROUTE_PREDOWN 12 -#define OPENVPN_PLUGIN_N 13 +#define OPENVPN_PLUGIN_TRANSPORT 13 +#define OPENVPN_PLUGIN_N 14 /* * Build a mask out of a set of plug-in types. @@ -852,6 +853,34 @@ OPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_select_initialization_ OPENVPN_PLUGIN_DEF int OPENVPN_PLUGIN_FUNC(openvpn_plugin_min_version_required_v1) (void); +/* + * FUNCTION: openvpn_plugin_get_vtab_v1 + * + * This is only used for TRANSPORT plugins presently. It is called to + * retrieve a vtable structure to be used for binding virtual sockets + * which use the transport provided by the plugin. The selector is an + * OPENVPN_VTAB constant. *size_out must be set to the size of the + * structure returned. + * + * REQUIRED: only for TRANSPORT plugins + * + * RETURN VALUE + * + * A pointer to a vtable of the requested type, or NULL if no + * such vtable is provided by this plugin. + */ + +enum { + /* Return type: struct openvpn_transport_bind_vtab1 * + Plugin should provide OPENVPN_PLUGIN_TRANSPORT at open time. + */ + OPENVPN_VTAB_TRANSPORT_BIND_V1 = 0x54726e31 /* 'Trn1' */ +}; + + +OPENVPN_PLUGIN_DEF void *OPENVPN_PLUGIN_FUNC(openvpn_plugin_get_vtab_v1) + (int selector, size_t *size_out); + /* * Deprecated functions which are still supported for backward compatibility. */ diff --git a/include/openvpn-transport.h b/include/openvpn-transport.h new file mode 100644 index 00000000..72872d9c --- /dev/null +++ b/include/openvpn-transport.h @@ -0,0 +1,240 @@ +/* + * Transport API handling code + * + * Copyright (C) 2018 Robin Tarsiger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef OPENVPN_TRANSPORT_H_ +#define OPENVPN_TRANSPORT_H_ + +/* PLATFORM: only POSIX-y platforms or Win32 here */ + +#ifdef _WIN32 + +/* Win32 */ +#define OPENVPN_TRANSPORT_PLATFORM_WIN32 +#include +#include +#include + +/* On Windows, platform-native events to wait on are provided to OpenVPN core as + * pairs of system events, normally corresponding to one potentially queued I/O + * operation in each direction. The read event is waited on if read events are + * requested, and the write event is waited on if write events are + * requested. Events need not be distinct, but usually will be. Two event + * handles must always be provided; neither is permitted to be NULL. */ +typedef const struct openvpn_transport_win32_event_pair { + HANDLE read; + HANDLE write; +} *openvpn_transport_native_event_t; + +/* Windows doesn't have socklen_t; it uses int. */ +typedef int openvpn_transport_socklen_t; + +#else /* ifdef _WIN32 */ + +/* POSIX-y */ +#define OPENVPN_TRANSPORT_PLATFORM_POSIX +#include +#include +#include +#include + +/* On POSIX-y platforms, platform-native events to wait on are provided to + * OpenVPN core as file descriptors. Readiness for read and write are defined + * the same way as in poll, epoll, and similar system APIs. The file descriptor + * may be of any type which can be waited on. */ +typedef int openvpn_transport_native_event_t; + +/* Alias natural socklen_t definition. */ +typedef socklen_t openvpn_transport_socklen_t; + +#endif + +/* rwflags values are a bitwise OR of zero or more of these values, indicating + * what types of events are being requested or returned for a given object. */ +#define OPENVPN_TRANSPORT_EVENT_READ (1<<0) +#define OPENVPN_TRANSPORT_EVENT_WRITE (1<<1) + +/* Handle to an object that can accumulate requests to wait on platform-native + * event sources. Use the functions in the vtable to operate on it. */ +typedef struct openvpn_transport_event_set_handle *openvpn_transport_event_set_handle_t; + +struct openvpn_transport_event_set_vtab { + /* Request to be notified when ev becomes ready in any of the ways specified + * by the bitmask rwflags. Incoming notifications will have arg passed + * through as-is. See above for the definition of native events on different + * platforms. + * + * Note that arg must be a distinct, owned pointer and not NULL; see + * update_event below. */ + void (*set_event)(openvpn_transport_event_set_handle_t handle, + openvpn_transport_native_event_t ev, unsigned rwflags, + void *arg); +}; + +/* Implementation extends this structure with state. Implementation is normally + * provided by OpenVPN core; see request_event in struct + * openvpn_transport_vtab. */ +struct openvpn_transport_event_set_handle { + const struct openvpn_transport_event_set_vtab *vtab; +}; + +/* Handle to a virtual datagram socket, non-connection-oriented. */ +typedef struct openvpn_transport_socket *openvpn_transport_socket_t; + +/* Implementation extends this structure with state. Implementation is normally + * provided by a plugin providing an indirect socket mechanism. */ +struct openvpn_transport_socket { + const struct openvpn_transport_socket_vtab1 *vtab; +}; + +/* Handle to opaque per-connection parameters. */ +typedef void *openvpn_transport_args_t; + +/* Virtual socket implementations should expect (and consumers must + * provide) vtable calls in the following order: + * + * - bind must occur first, to create the virtual socket. + * - request_event is followed by zero or more update_event calls, and then a + * pump call, before any further request_event calls occur. + * - recvfrom/sendto may occur at any time between bind and close, including + * interleaved with the event request cycle above. + * - close must occur last. + * + * Error reporting is platform-native: errno on POSIX-y systems, or + * Winsock errors on Windows systems. + */ + +struct openvpn_transport_bind_vtab1 { + /* Producer should set to 0, consumer should ignore. May indicate + * extended functions in the future. */ + unsigned features; + + /* Parse connection-specific arguments as provided by a + * "transport-plugin" configuration line. argv[0] is normally the + * plugin shared object pathname. argc is the total number of + * valid strings in argv; argv[argc] is also NULL. plugin_handle + * is an openvpn_plugin_handle_t. Memory for argv and its strings + * is borrowed and may not be retained by the plugin. Any syntax + * checking of text arguments should be done in this function. + * + * The value returned points to either opaque, valid parameters, + * or an error value. The distinction between the two is defined + * by the plugin, and can only be evaluated via the argerror + * function below. + * + * This function pointer is allowed to be NULL. In this case, + * argerror and freeargs must also be NULL, a nonempty + * connection-specific argument list will be rejected with an + * error, and the value of args in the subsequent bind call will + * always be NULL. + */ + openvpn_transport_args_t (*parseargs)(void *plugin_handle, + const char *const *argv, + int argc); + + /* If args is an error value, as defined by this plugin, then + * return a string describing the error. The string must remain + * valid until the next call to argerror or freeargs on the same + * value of args. If args is not an error value, return NULL. + * This function pointer must be NULL if and only if parseargs is + * NULL. */ + const char *(*argerror)(openvpn_transport_args_t args); + + /* Destroy any resources associated with args, which may be any + * return value of parseargs: either parsed parameters or an error + * value. This function pointer must be NULL if and only if + * parseargs is NULL. */ + void (*freeargs)(openvpn_transport_args_t args); + + /* Bind a new virtual socket to addr/len, given a plugin handle + * and any connection-specific parameters. addr must not be NULL. + * plugin_handle is actually of type openvpn_plugin_handle_t. + * args is any value returned by parseargs for which argerror + * would return NULL; however, it is still borrowed, and the + * caller may call freeargs after bind while the socket is still + * in use. */ + openvpn_transport_socket_t (*bind)(void *plugin_handle, + openvpn_transport_args_t args, + const struct sockaddr *addr, + openvpn_transport_socklen_t len); +}; + +struct openvpn_transport_socket_vtab1 { + /* Producer should set to 0, consumer should ignore. May indicate + * extended functions in the future. */ + unsigned features; + + /* Given the bitmask rwflags, request that event_set be provided with all + * native events that should be waited on such that whenever this virtual + * socket may become ready in a way specified by rwflags, one of the + * native events will become ready. This function should call + * + * event_set->vtab->set_event(event_set, ...) + * + * zero or more times for this purpose. + * + * The state of event_set should be assumed not to persist between calls to + * request_event; every native event must be provided every time. Currently, + * only one native event may be supplied (i.e., one call above). */ + void (*request_event)(openvpn_transport_socket_t handle, + openvpn_transport_event_set_handle_t event_set, + unsigned rwflags); + + /* Indicate to the virtual socket that a native event for which arg was + * provided to a set_event call above became ready in a manner indicated by + * the bitmask rwflags. This function _must_ test for whether arg + * corresponds to an actual requested event from this virtual socket. + * + * If arg corresponds to a requested event, update_event does any necessary + * internal state updates and _returns true_ to consume the event. + * + * If arg does not correspond to a requested event, update_event + * does nothing and _returns false_. */ + bool (*update_event)(openvpn_transport_socket_t handle, void *arg, + unsigned rwflags); + + /* Perform any pending processing that can be performed + * immediately, and return a bitmask of rwflags indicating whether + * this virtual socket is ready to receive/send more packets. */ + unsigned (*pump)(openvpn_transport_socket_t handle); + + /* Receive a packet into buf/len, storing the address into addr/(*addrlen) + * and updating *addrlen to match. Returns -1 on error, or the number of + * bytes received. Must not block; signals an error if there is nothing to + * receive. */ + ssize_t (*recvfrom)(openvpn_transport_socket_t handle, + void *buf, size_t len, + struct sockaddr *addr, + openvpn_transport_socklen_t *addrlen); + + /* Send a packet from buf/len to the address addr/addrlen. Returns -1 on + * error, or the number of bytes sent. Must not block; signals an error if + * there is no room to send. */ + ssize_t (*sendto)(openvpn_transport_socket_t handle, + const void *buf, size_t len, + const struct sockaddr *addr, + openvpn_transport_socklen_t addrlen); + + /* Destroy this virtual socket and free all resources allocated for it. The + * virtual socket must not be used afterward. */ + void (*close)(openvpn_transport_socket_t handle); +}; + +#endif diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 197e62ba..41cd90aa 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -119,6 +119,7 @@ openvpn_SOURCES = \ status.c status.h \ syshead.h \ tls_crypt.c tls_crypt.h \ + transport.c transport.h \ tun.c tun.h \ win32.h win32.c \ cryptoapi.h cryptoapi.c diff --git a/src/openvpn/plugin.c b/src/openvpn/plugin.c index b9708342..2ab81842 100644 --- a/src/openvpn/plugin.c +++ b/src/openvpn/plugin.c @@ -119,6 +119,9 @@ plugin_type_name(const int type) case OPENVPN_PLUGIN_ROUTE_PREDOWN: return "PLUGIN_ROUTE_PREDOWN"; + case OPENVPN_PLUGIN_TRANSPORT: + return "PLUGIN_TRANSPORT"; + default: return "PLUGIN_???"; } @@ -295,6 +298,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o) PLUGIN_SYM(client_destructor, "openvpn_plugin_client_destructor_v1", 0); PLUGIN_SYM(min_version_required, "openvpn_plugin_min_version_required_v1", 0); PLUGIN_SYM(initialization_point, "openvpn_plugin_select_initialization_point_v1", 0); + PLUGIN_SYM(get_vtab, "openvpn_plugin_get_vtab_v1", 0); if (!p->open1 && !p->open2 && !p->open3) { diff --git a/src/openvpn/plugin.h b/src/openvpn/plugin.h index 791d476b..435c4763 100644 --- a/src/openvpn/plugin.h +++ b/src/openvpn/plugin.h @@ -76,6 +76,7 @@ struct plugin { openvpn_plugin_client_destructor_v1 client_destructor; openvpn_plugin_min_version_required_v1 min_version_required; openvpn_plugin_select_initialization_point_v1 initialization_point; + openvpn_plugin_get_vtab_v1 get_vtab; openvpn_plugin_handle_t plugin_handle; }; diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h index 17801418..f49e6315 100644 --- a/src/openvpn/socket.h +++ b/src/openvpn/socket.h @@ -572,6 +572,9 @@ enum proto_num { PROTO_TCP, PROTO_TCP_SERVER, PROTO_TCP_CLIENT, +#ifdef ENABLE_PLUGIN + PROTO_INDIRECT, +#endif PROTO_N }; diff --git a/src/openvpn/transport.c b/src/openvpn/transport.c new file mode 100644 index 00000000..427e0062 --- /dev/null +++ b/src/openvpn/transport.c @@ -0,0 +1,303 @@ +/* + * Transport API handling code + * + * Copyright (C) 2018 Robin Tarsiger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#ifdef ENABLE_PLUGIN + +#include "error.h" +#include "event.h" +#include "plugin.h" +#include "socket.h" +#include "transport.h" +#include "openvpn-transport.h" + +bool +transport_prepare(const struct plugin_list *plugins, + const char **transport_plugin_argv, + struct openvpn_transport_bind_vtab1 **vtabp, + openvpn_plugin_handle_t *handlep, + openvpn_transport_args_t *argsp) +{ + const char *expected_so_pathname = transport_plugin_argv[0]; + int argc = 0; + + while (transport_plugin_argv[argc]) + { + argc++; + } + + for (int i = 0; i < plugins->common->n; i++) + { + struct plugin *p = &plugins->common->plugins[i]; + if (p->so_pathname && !strcmp(p->so_pathname, expected_so_pathname)) + { + /* Pathname matches; this is the plugin requested. */ + if (!(p->plugin_type_mask + & OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TRANSPORT))) + { + msg(M_FATAL, + "INDIRECT: plugin %s does not indicate TRANSPORT functionality", + p->so_pathname); + } + + size_t size; + struct openvpn_transport_bind_vtab1 *vtab = NULL; + if (p->get_vtab) + { + vtab = p->get_vtab(OPENVPN_VTAB_TRANSPORT_BIND_V1, &size); + } + + if (!vtab) + { + msg(M_FATAL, + "INDIRECT: plugin %s has no TRANSPORT_BIND_V1 table", + p->so_pathname); + } + + /* Sanity checks on the vtable. */ + if (!(size == sizeof(*vtab) && vtab->bind + && ((vtab->parseargs && vtab->argerror && vtab->freeargs) + || (!vtab->parseargs && !vtab->argerror + && !vtab->freeargs)))) + { + msg(M_FATAL, + "INDIRECT: plugin %s returned a faulty TRANSPORT_BIND_V1 table", + p->so_pathname); + } + + openvpn_transport_args_t args = NULL; + if (vtab->parseargs) + { + args = vtab->parseargs(p->plugin_handle, transport_plugin_argv, + argc); + + const char *argerror = vtab->argerror(args); + if (argerror) + { + msg(M_FATAL, + "INDIRECT: invalid arguments to transport-plugin %s: %s", + p->so_pathname, argerror); + } + } + + *vtabp = vtab; + *handlep = p->plugin_handle; + *argsp = args; + + return true; + } + } + + return false; +} + +openvpn_transport_socket_t +transport_bind(const struct plugin_list *plugins, + const char **transport_plugin_argv, sa_family_t ai_family, + struct addrinfo *bind_addresses) +{ + openvpn_plugin_handle_t handle; + openvpn_transport_args_t args; + openvpn_transport_socket_t indirect; + struct openvpn_transport_bind_vtab1 *vtab; + struct addrinfo *cur = NULL; + struct openvpn_sockaddr zero; + + if (!transport_prepare(plugins, transport_plugin_argv, &vtab, &handle, + &args)) + { + msg(M_FATAL, "INDIRECT: Socket bind failed: provider plugin not found"); + } + + /* Partially replicates the functionality of socket_bind. No bind_ipv6_only + * or other such options, presently. + */ + if (bind_addresses) + { + for (cur = bind_addresses; cur; cur = cur->ai_next) + { + if (cur->ai_family == ai_family) + { + break; + } + } + + if (!cur) + { + msg(M_FATAL, "INDIRECT: Socket bind failed: Addr to bind has no %s record", + addr_family_name(ai_family)); + } + } + + if (cur) + { + indirect = vtab->bind(handle, args, cur->ai_addr, cur->ai_addrlen); + } + else if (ai_family == AF_UNSPEC) + { + msg(M_ERR, "INDIRECT: cannot bind with unspecified address family"); + } + else + { + memset(&zero, 0, sizeof(zero)); + zero.addr.sa.sa_family = ai_family; + addr_zero_host(&zero); + indirect = vtab->bind(handle, args, &zero.addr.sa, af_addr_size(ai_family)); + } + + if (!indirect) + { + msg(M_ERR, "INDIRECT: Socket bind failed"); + } + + if (vtab->freeargs) + { + vtab->freeargs(args); + } + + return indirect; +} + +struct encapsulated_event_set +{ + struct openvpn_transport_event_set_handle handle; + struct event_set *real; +}; + +#if EVENT_READ == OPENVPN_TRANSPORT_EVENT_READ \ + && EVENT_WRITE == OPENVPN_TRANSPORT_EVENT_WRITE +#define TRANSPORT_EVENT_BITS_IDENTICAL 1 +#else +#define TRANSPORT_EVENT_BITS_IDENTICAL 0 +#endif + +static inline unsigned +translate_rwflags_in(unsigned vrwflags) +{ +#if TRANSPORT_EVENT_BITS_IDENTICAL + return vrwflags; +#else + unsigned rwflags = 0; + if (vrwflags & OPENVPN_TRANSPORT_EVENT_READ) + { + rwflags |= EVENT_READ; + } + if (vrwflags & OPENVPN_TRANSPORT_EVENT_WRITE) + { + rwflags |= EVENT_WRITE; + } + return rwflags; +#endif +} + +static inline unsigned +translate_rwflags_out(unsigned rwflags) +{ +#if TRANSPORT_EVENT_BITS_IDENTICAL + return rwflags; +#else + unsigned vrwflags = 0; + if (rwflags & EVENT_READ) + { + vrwflags |= OPENVPN_TRANSPORT_EVENT_READ; + } + if (rwflags & EVENT_WRITE) + { + vrwflags |= OPENVPN_TRANSPORT_EVENT_WRITE; + } + return vrwflags; +#endif +} + +static void +encapsulated_event_set_set_event(openvpn_transport_event_set_handle_t handle, + openvpn_transport_native_event_t vev, + unsigned vrwflags, void *arg) +{ + unsigned rwflags = translate_rwflags_in(vrwflags); + event_t ev; +#ifdef _WIN32 + struct rw_handle rw; + rw.read = vev->read; + rw.write = vev->write; + ev = &rw; +#else + ev = vev; +#endif + + struct event_set *es = ((struct encapsulated_event_set *) handle)->real; + /* If rwflags == 0, we do nothing, because this is always one-shot mode. */ + if (rwflags != 0) + { + event_ctl(es, ev, rwflags, arg); + } +} + +static const struct openvpn_transport_event_set_vtab encapsulated_event_set_vtab = { + encapsulated_event_set_set_event +}; + +unsigned +transport_pump(openvpn_transport_socket_t indirect, + struct event_set_return *esr, int *esrlen) +{ + int i = 0; + while (i < *esrlen) + { + unsigned vrwflags = translate_rwflags_out(esr[i].rwflags); + if (indirect->vtab->update_event(indirect, esr[i].arg, vrwflags)) + { + /* Consume the event; move the last one in place of it. */ + if (i != *esrlen - 1) + { + memcpy(&esr[i], &esr[*esrlen-1], sizeof(*esr)); + } + (*esrlen)--; + } + else + { + /* Don't consume the event; move to the next one. */ + i++; + } + } + + return translate_rwflags_in(indirect->vtab->pump(indirect)); +} + +void +transport_request_events(openvpn_transport_socket_t indirect, + struct event_set *es, unsigned rwflags) +{ + unsigned vrwflags = translate_rwflags_out(rwflags); + struct encapsulated_event_set encapsulated_es; + encapsulated_es.handle.vtab = &encapsulated_event_set_vtab; + encapsulated_es.real = es; + indirect->vtab->request_event(indirect, &encapsulated_es.handle, vrwflags); +} + +#endif /* ENABLE_PLUGIN */ diff --git a/src/openvpn/transport.h b/src/openvpn/transport.h new file mode 100644 index 00000000..344ce44b --- /dev/null +++ b/src/openvpn/transport.h @@ -0,0 +1,94 @@ +/* + * Transport API handling code + * + * Copyright (C) 2018 Robin Tarsiger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#ifndef OPENVPN_TRANSPORT_H +#define OPENVPN_TRANSPORT_H + +#ifdef ENABLE_PLUGIN + +#include "plugin.h" +#include "openvpn-transport.h" + +/* Given a list of plugins and an argument list for a desired + * transport plugin instance, prepare to bind new link sockets using + * that transport plugin and args. If all succeeds, return true, and: + * + * *vtabp is set to the vtable by which to bind new link sockets. + * *handlep is set to the plugin handle to use in bind calls. + * *argsp is set to the args value to use in bind calls. It is + * the caller's responsibility to call freeargs on it later. + * + * Otherwise, return false, and the values of all of the above are + * undefined. None of the output pointers may be NULL. + */ +bool transport_prepare(const struct plugin_list *plugins, + const char **transport_plugin_argv, + struct openvpn_transport_bind_vtab1 **vtabp, + openvpn_plugin_handle_t *handlep, + openvpn_transport_args_t *argsp); + +/* Bind a virtual socket given an address family and list of potential + * bind addresses. bind_addresses may be NULL, in which case an + * unspecified address of the correct family is used. The virtual + * socket comes from a transport plugin in the list of plugins which + * matches transport_plugin_argv, which is used for any + * connection-specific parameters the plugin may require. + * + * Raises a fatal error if the socket cannot be bound. + */ +openvpn_transport_socket_t +transport_bind(const struct plugin_list *plugins, + const char **transport_plugin_argv, sa_family_t ai_family, + struct addrinfo *bind_addresses); + +/* Mutates esr/esrlen to consume events. */ +unsigned transport_pump(openvpn_transport_socket_t vsocket, + struct event_set_return *esr, int *esrlen); + +void transport_request_events(openvpn_transport_socket_t indirect, + struct event_set *es, unsigned rwflags); + +/* NOTE: transport_write and transport_read implicitly downcast from a + * ssize_t to an int on return. Various link_socket_* functions + * already do this, under the assumption that the return values will + * always fit in an int, because the requested lengths always fit in + * an int, otherwise the buffer structure would already be corrupted. + */ + +static inline int +transport_write(openvpn_transport_socket_t indirect, + struct buffer *buf, struct sockaddr *addr, socklen_t addrlen) +{ + return indirect->vtab->sendto(indirect, BPTR(buf), BLEN(buf), addr, + addrlen); +} + +static inline int +transport_read(openvpn_transport_socket_t indirect, + struct buffer *buf, struct sockaddr *addr, socklen_t *addrlen) +{ + return indirect->vtab->recvfrom(indirect, BPTR(buf), + buf_forward_capacity(buf), addr, addrlen); +} + +#endif /* ENABLE_PLUGIN */ + +#endif /* !OPENVPN_TRANSPORT_H */ From patchwork Sun Dec 30 00:28:59 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 652 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director9.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id 6KogOJKsKFyAbwAAIUCqbw for ; Sun, 30 Dec 2018 06:31:31 -0500 Received: from proxy4.mail.iad3b.rsapps.net ([172.31.255.6]) by director9.mail.ord1d.rsapps.net with LMTP id CNagNZKsKFzZagAAalYnBA ; Sun, 30 Dec 2018 06:31:30 -0500 Received: from smtp8.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy4.mail.iad3b.rsapps.net with LMTP id EOr4L5KsKFxSXgAA9crAow ; Sun, 30 Dec 2018 06:31:30 -0500 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp8.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=unstable.cc X-Suspicious-Flag: YES X-Classification-ID: 7377742e-0c26-11e9-8cdd-5254005eee35-1-1 Received: from [216.105.38.7] ([216.105.38.7:20212] helo=lists.sourceforge.net) by smtp8.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 78/12-21509-29CA82C5; Sun, 30 Dec 2018 06:31:30 -0500 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1gdZIa-0003dy-PN; Sun, 30 Dec 2018 11:30:28 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1gdZIZ-0003de-Cn for openvpn-devel@lists.sourceforge.net; Sun, 30 Dec 2018 11:30:27 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=EcrMmZStxLRAElsNwrjMxLShNsudnS54t29TfkfV9MY=; b=PMwJSrMYJ20mCvMltMr0BP1StA k8u7lswtj/vSTBs9WXYS1sP9WWCmz0Vnc38ho5hT10JSlZkWui0iyN+n6QdNshFlvS+HH1RUS19EZ uSpx9nj351y8A9Mp6OpmMb+X/rXohCb3yVKIc9QWra/L1Bhj7yE7jh2KgC/4dWWuUVtw=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=EcrMmZStxLRAElsNwrjMxLShNsudnS54t29TfkfV9MY=; b=iTd0/4J6yduuNK6f+DDI+G8KHH 3Yi2iNuztDa3cpPJ9XBBGDIr983T2f13cFWn2sxX6bp4yfZHb5JwDXgDUD3C4/qtEp7ArDvX10b4y +ciyTCRs9QNWjjt6TnVg9oK3typ2yaE0HDnAr5yH3mCQ/DKasZlxbOsxe4HQY/IC4nZ8=; Received: from s2.neomailbox.net ([5.148.176.60]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1.2:DHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1gdZIX-006XFG-BT for openvpn-devel@lists.sourceforge.net; Sun, 30 Dec 2018 11:30:27 +0000 From: Antonio Quartulli To: openvpn-devel@lists.sourceforge.net Date: Sun, 30 Dec 2018 21:28:59 +1000 Message-Id: <20181230112901.29241-3-a@unstable.cc> In-Reply-To: <20181230112901.29241-1-a@unstable.cc> References: <20181230112901.29241-1-a@unstable.cc> MIME-Version: 1.0 X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [5.148.176.60 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1gdZIX-006XFG-BT Subject: [Openvpn-devel] [PATCH 2/4] socket: introduce INDIRECT transport protocol abstraction X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: antonio@openvpn.net, Robin Tarsiger Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Robin Tarsiger This new transport protocol is used to tell the core code that traffic should not be directly processed, but should rather be rerouted to a transport plugin. It is basically an abstraction as it does not say tell the code how to process the data, but simply forces its redirection to the external code. Signed-off-by: Robin Tarsiger [antonio@openvpn.net: refactored commits, restyled code] --- src/openvpn/forward.c | 5 ++ src/openvpn/socket.c | 146 ++++++++++++++++++++++++++++++++++++++-- src/openvpn/socket.h | 70 +++++++++++++++++++ src/openvpn/transport.h | 5 ++ 4 files changed, 222 insertions(+), 4 deletions(-) diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c index 0a90fff0..a7092c7e 100644 --- a/src/openvpn/forward.c +++ b/src/openvpn/forward.c @@ -2150,6 +2150,11 @@ io_wait_dowork(struct context *c, const unsigned int flags) { int i; c->c2.event_set_status = 0; +#ifdef ENABLE_PLUGIN + c->c2.event_set_status |= + (socket_indirect_pump(c->c2.link_socket, esr, &status) & 3) + << socket_shift; +#endif for (i = 0; i < status; ++i) { const struct event_set_return *e = &esr[i]; diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c index db944245..b548ab7a 100644 --- a/src/openvpn/socket.c +++ b/src/openvpn/socket.c @@ -41,6 +41,7 @@ #include "manage.h" #include "openvpn.h" #include "forward.h" +#include "transport.h" #include "memdbg.h" @@ -49,6 +50,9 @@ const int proto_overhead[] = { /* indexed by PROTO_x */ IPv4_UDP_HEADER_SIZE, /* IPv4 */ IPv4_TCP_HEADER_SIZE, IPv4_TCP_HEADER_SIZE, +#ifdef ENABLE_PLUGIN + INDIRECT_HEADER_SIZE, +#endif IPv6_UDP_HEADER_SIZE, /* IPv6 */ IPv6_TCP_HEADER_SIZE, IPv6_TCP_HEADER_SIZE, @@ -1103,9 +1107,46 @@ bind_local(struct link_socket *sock, const sa_family_t ai_family) } } +#ifdef ENABLE_PLUGIN + +static void +create_socket_indirect(struct link_socket *sock, sa_family_t ai_family) +{ + struct addrinfo *bind_addresses = NULL; + if (sock->bind_local) + { + bind_addresses = sock->info.lsa->bind_local; + } + + sock->indirect = transport_bind(sock->info.plugins, + sock->info.transport_plugin_argv, + ai_family, + bind_addresses); +} + +bool +proto_is_indirect(int proto) +{ + return proto == PROTO_INDIRECT; +} + +#else /* ifdef ENABLE_PLUGIN */ + +static void +create_socket_indirect(struct link_socket *sock, sa_family_t ai_family) +{ +} + +#endif /* ENABLE_PLUGIN */ + static void create_socket(struct link_socket *sock, struct addrinfo *addr) { + if (proto_is_indirect(sock->info.proto)) + { + create_socket_indirect(sock, addr->ai_family); + } + if (addr->ai_protocol == IPPROTO_UDP || addr->ai_socktype == SOCK_DGRAM) { sock->sd = create_socket_udp(addr, sock->sockflags); @@ -2279,7 +2320,11 @@ link_socket_init_phase2(struct link_socket *sock, } /* If socket has not already been created create it now */ - if (sock->sd == SOCKET_UNDEFINED) + if (sock->sd == SOCKET_UNDEFINED +#ifdef ENABLE_PLUGIN + && !sock->indirect +#endif + ) { /* If we have no --remote and have still not figured out the * protocol family to use we will use the first of the bind */ @@ -2300,7 +2345,11 @@ link_socket_init_phase2(struct link_socket *sock, } /* Socket still undefined, give a warning and abort connection */ - if (sock->sd == SOCKET_UNDEFINED) + if (sock->sd == SOCKET_UNDEFINED +#ifdef ENABLE_PLUGIN + && !sock->indirect +#endif + ) { msg(M_WARN, "Could not determine IPv4/IPv6 protocol"); sig_info->signal_received = SIGUSR1; @@ -2338,7 +2387,10 @@ link_socket_init_phase2(struct link_socket *sock, } } - phase2_set_socket_flags(sock); + if (sock->sd != SOCKET_UNDEFINED) + { + phase2_set_socket_flags(sock); + } linksock_print_addr(sock); done: @@ -2362,6 +2414,14 @@ link_socket_close(struct link_socket *sock) const int gremlin = 0; #endif +#ifdef ENABLE_PLUGIN + if (sock->indirect) + { + sock->indirect->vtab->close(sock->indirect); + sock->indirect = NULL; + } +#endif + if (socket_defined(sock->sd)) { #ifdef _WIN32 @@ -3143,16 +3203,25 @@ static const struct proto_names proto_names[] = { {"tcp-server", "TCP_SERVER", AF_UNSPEC, PROTO_TCP_SERVER}, {"tcp-client", "TCP_CLIENT", AF_UNSPEC, PROTO_TCP_CLIENT}, {"tcp", "TCP", AF_UNSPEC, PROTO_TCP}, +#ifdef ENABLE_PLUGIN + {"indirect", "INDIRECT", AF_UNSPEC, PROTO_INDIRECT}, +#endif /* force IPv4 */ {"udp4", "UDPv4", AF_INET, PROTO_UDP}, {"tcp4-server","TCPv4_SERVER", AF_INET, PROTO_TCP_SERVER}, {"tcp4-client","TCPv4_CLIENT", AF_INET, PROTO_TCP_CLIENT}, {"tcp4", "TCPv4", AF_INET, PROTO_TCP}, +#ifdef ENABLE_PLUGIN + {"indirect4", "INDIRECT_IPv4", AF_INET, PROTO_INDIRECT}, +#endif /* force IPv6 */ {"udp6","UDPv6", AF_INET6, PROTO_UDP}, {"tcp6-server","TCPv6_SERVER", AF_INET6, PROTO_TCP_SERVER}, {"tcp6-client","TCPv6_CLIENT", AF_INET6, PROTO_TCP_CLIENT}, {"tcp6","TCPv6", AF_INET6, PROTO_TCP}, +#ifdef ENABLE_PLUGIN + {"indirect6", "INDIRECT_IPv6", AF_INET6, PROTO_INDIRECT}, +#endif }; bool @@ -3167,6 +3236,10 @@ proto_is_net(int proto) bool proto_is_dgram(int proto) { + if (proto_is_indirect(proto)) + { + return true; + } return proto_is_udp(proto); } @@ -3301,6 +3374,18 @@ proto_remote(int proto, bool remote) return "TCPv4_CLIENT"; } +#ifdef ENABLE_PLUGIN + if (proto == PROTO_INDIRECT) + { + /* FIXME: the string reported here should match the actual transport + * protocol being used, however in this function we have no knowledge of + * what protocol is exactly being used by the transport-plugin. + * Therefore we simply return INDIRECT for now. + */ + return "INDIRECT"; + } +#endif + ASSERT(0); return ""; /* Make the compiler happy */ } @@ -3360,6 +3445,29 @@ link_socket_read_tcp(struct link_socket *sock, } } +#ifdef ENABLE_PLUGIN + +int +link_socket_read_indirect(struct link_socket *sock, + struct buffer *buf, + struct link_socket_actual *from) +{ + ASSERT(sock->indirect); + socklen_t fromlen = sizeof(from->dest.addr); + socklen_t expectedlen = af_addr_size(sock->info.af); + addr_zero_host(&from->dest); + int len = transport_read(sock->indirect, buf, + &from->dest.addr.sa, &fromlen); + if (len >= 0 && expectedlen && fromlen != expectedlen) + { + bad_address_length(fromlen, expectedlen); + } + + return buf->len = len; +} + +#endif /* ENABLE_PLUGIN */ + #ifndef _WIN32 #if ENABLE_IP_PKTINFO @@ -3492,6 +3600,21 @@ link_socket_write_tcp(struct link_socket *sock, #endif } +#ifdef ENABLE_PLUGIN + +int +link_socket_write_indirect(struct link_socket *sock, + struct buffer *buf, + struct link_socket_actual *to) +{ + ASSERT(sock->indirect); + struct sockaddr *addr = (struct sockaddr *) &to->dest.addr.sa; + socklen_t addrlen = (socklen_t) af_addr_size(to->dest.addr.sa.sa_family); + return transport_write(sock->indirect, buf, addr, addrlen); +} + +#endif /* ENABLE_PLUGIN */ + #if ENABLE_IP_PKTINFO size_t @@ -3580,6 +3703,12 @@ link_socket_write_udp_posix_sendmsg(struct link_socket *sock, int socket_recv_queue(struct link_socket *sock, int maxsize) { + if (proto_is_indirect(sock->info.proto)) + { + /* Indirect handler will take care of this, so do nothing. */ + return IOSTATE_QUEUED; + } + if (sock->reads.iostate == IOSTATE_INITIAL) { WSABUF wsabuf[1]; @@ -3952,7 +4081,16 @@ socket_set(struct link_socket *s, /* if persistent is defined, call event_ctl only if rwflags has changed since last call */ if (!persistent || *persistent != rwflags) { - event_ctl(es, socket_event_handle(s), rwflags, arg); +#ifdef ENABLE_PLUGIN + if (s->indirect) + { + transport_request_events(s->indirect, es, rwflags); + } + else +#endif + { + event_ctl(es, socket_event_handle(s), rwflags, arg); + } if (persistent) { *persistent = rwflags; diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h index f49e6315..73a4ab6f 100644 --- a/src/openvpn/socket.h +++ b/src/openvpn/socket.h @@ -34,6 +34,7 @@ #include "proxy.h" #include "socks.h" #include "misc.h" +#include "transport.h" /* * OpenVPN's default port number as assigned by IANA. @@ -115,6 +116,7 @@ struct link_socket_info bool connection_established; const char *ipchange_command; const struct plugin_list *plugins; + const char **transport_plugin_argv; bool remote_float; int proto; /* Protocol (PROTO_x defined below) */ sa_family_t af; /* Address family like AF_INET, AF_INET6 or AF_UNSPEC*/ @@ -175,6 +177,11 @@ struct link_socket struct rw_handle listen_handle; /* For listening on TCP socket in server mode */ #endif +#ifdef ENABLE_PLUGIN + /* only valid when info.proto == PROTO_INDIRECT */ + openvpn_transport_socket_t indirect; +#endif + /* used for printing status info only */ unsigned int rwflags_debug; @@ -1049,12 +1056,53 @@ int link_socket_read_udp_posix(struct link_socket *sock, #endif +#ifdef ENABLE_PLUGIN + +int link_socket_read_indirect(struct link_socket *sock, + struct buffer *buf, + struct link_socket_actual *from); + +int link_socket_write_indirect(struct link_socket *sock, + struct buffer *buf, + struct link_socket_actual *from); + +bool proto_is_indirect(int proto); + +#else /* ifdef ENABLE_PLUGIN */ + +static int +link_socket_read_indirect(struct link_socket *sock, + struct buffer *buf, struct link_socket_actual *from) +{ + return -1; +} + +static int +link_socket_write_indirect(struct link_socket *sock, + struct buffer *buf, struct link_socket_actual *from) +{ + return -1; +} + +static bool +proto_is_indirect(int proto) +{ + return false; +} + +#endif /* ENABLE_PLUGIN */ + /* read a TCP or UDP packet from link */ static inline int link_socket_read(struct link_socket *sock, struct buffer *buf, struct link_socket_actual *from) { + if (proto_is_indirect(sock->info.proto)) + { + return link_socket_read_indirect(sock, buf, from); + } + if (proto_is_udp(sock->info.proto)) /* unified UDPv4 and UDPv6 */ { int res; @@ -1169,6 +1217,11 @@ link_socket_write(struct link_socket *sock, struct buffer *buf, struct link_socket_actual *to) { + if (proto_is_indirect(sock->info.proto)) + { + return link_socket_write_indirect(sock, buf, to); + } + if (proto_is_udp(sock->info.proto)) /* unified UDPv4 and UDPv6 */ { return link_socket_write_udp(sock, buf, to); @@ -1264,6 +1317,23 @@ socket_reset_listen_persistent(struct link_socket *s) #endif } +#ifdef ENABLE_PLUGIN + +static inline unsigned +socket_indirect_pump(struct link_socket *s, struct event_set_return *esr, int *esrlen) +{ + if (s->indirect) + { + return transport_pump(s->indirect, esr, esrlen); + } + else + { + return 0; + } +} + +#endif /* ENABLE_PLUGIN */ + const char *socket_stat(const struct link_socket *s, unsigned int rwflags, struct gc_arena *gc); #endif /* SOCKET_H */ diff --git a/src/openvpn/transport.h b/src/openvpn/transport.h index 344ce44b..37050eaf 100644 --- a/src/openvpn/transport.h +++ b/src/openvpn/transport.h @@ -27,6 +27,11 @@ #include "plugin.h" #include "openvpn-transport.h" +/* INDIRECT does not have any overhead per se, but it depends on what is + * implemented by the transport plugin + */ +#define INDIRECT_HEADER_SIZE 0 + /* Given a list of plugins and an argument list for a desired * transport plugin instance, prepare to bind new link sockets using * that transport plugin and args. If all succeeds, return true, and: From patchwork Sun Dec 30 00:29:00 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 653 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id WC+WHpWsKFyRSAAAIUCqbw for ; Sun, 30 Dec 2018 06:31:33 -0500 Received: from proxy19.mail.iad3b.rsapps.net ([172.31.255.6]) by director7.mail.ord1d.rsapps.net with LMTP id uJCqG5WsKFzLPAAAovjBpQ ; Sun, 30 Dec 2018 06:31:33 -0500 Received: from smtp17.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy19.mail.iad3b.rsapps.net with LMTP id oPYXFZWsKFxIUAAAIG4riQ ; Sun, 30 Dec 2018 06:31:33 -0500 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp17.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=unstable.cc X-Suspicious-Flag: YES X-Classification-ID: 75208cf2-0c26-11e9-811f-52540094e46f-1-1 Received: from [216.105.38.7] ([216.105.38.7:51135] helo=lists.sourceforge.net) by smtp17.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id BA/35-11247-49CA82C5; Sun, 30 Dec 2018 06:31:33 -0500 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1gdZIi-0003fn-SZ; Sun, 30 Dec 2018 11:30:36 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1gdZIh-0003fQ-Dc for openvpn-devel@lists.sourceforge.net; Sun, 30 Dec 2018 11:30:35 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=gC4sxUA6ghjx/oGPHwdkvdRytdpaYi1RVgSc6YOSiC0=; b=iduqm7cImoUv5TkEGG1kF3aJIr geeeInDf5EepvAkM1hWR7dpGVveh4Ic3ViXH0V6ITI5/jNimRxhHBqSfk+bigqS9p/Pz93+hEddwL akU+qxy1JODrUw16oJVnf4gYCyGEnLGyIe4m6YADa2UdT2l8eVbUm2x3pbXiKxelo8mk=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=gC4sxUA6ghjx/oGPHwdkvdRytdpaYi1RVgSc6YOSiC0=; b=bZuTWoVgZjl2Fk5NQTUJIR4yIx 4hXCnKvihAgtlcr7ahwpTSefxLu2Sk/qfIRsJ4ek76uez7bFZ+0RIX4itVXKrX4+HOanVMsemqAdO BProKHvQjRqjA82B/Z4ZMg1oIGn5GofHN+bii1hXyvtzTUYmypqNSiQgO9XrcRQ9HRmU=; Received: from s2.neomailbox.net ([5.148.176.60]) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1.2:DHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1gdZIf-005zfg-Oz for openvpn-devel@lists.sourceforge.net; Sun, 30 Dec 2018 11:30:35 +0000 From: Antonio Quartulli To: openvpn-devel@lists.sourceforge.net Date: Sun, 30 Dec 2018 21:29:00 +1000 Message-Id: <20181230112901.29241-4-a@unstable.cc> In-Reply-To: <20181230112901.29241-1-a@unstable.cc> References: <20181230112901.29241-1-a@unstable.cc> MIME-Version: 1.0 X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [5.148.176.60 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1gdZIf-005zfg-Oz Subject: [Openvpn-devel] [PATCH 3/4] options: add support for --transport-plugin X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: antonio@openvpn.net, Robin Tarsiger Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Robin Tarsiger Add a new config option to allow the user to specify a transport plugin implementing the new API. This plugin can be used to manipulate traffic in any way, as designed by the plugin developer. The fondamental advantage of this plugin is that the core codebase does not need to know anything about its implementation, as soon as it implements the transport API properly. A plugin specified with --transport-plugin must be already loaded via --plugin. --transport-plugin is a per-connection-block option and specifies which plugin to use for this particular connection. It can take additional arguments, if required by the specific plugin. The manpage has been extended accordingly. Signed-off-by: Robin Tarsiger [antonio@openvpn.net: refactored commits, restyled code] --- doc/openvpn.8 | 40 ++++++++++++++++++++++++++++++++++++++++ src/openvpn/init.c | 1 + src/openvpn/options.c | 31 +++++++++++++++++++++++++++++++ src/openvpn/options.h | 1 + src/openvpn/socket.c | 2 ++ src/openvpn/socket.h | 1 + 6 files changed, 76 insertions(+) diff --git a/doc/openvpn.8 b/doc/openvpn.8 index 7abcaf1e..9325dabd 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -2847,6 +2847,46 @@ every module and script must return success (0) in order for the connection to be authenticated. .\"********************************************************* .TP +.B \-\-transport-plugin module-pathname [connection-args] +Use the loaded plugin module identified by +.B module-pathname +to provide a transport layer for the connection. The +.B module-pathname +must be exactly equivalent to a pathname supplied to a +.B \-\-plugin +option. The same transport plugin may be used for +multiple connections, in which case the +.B \-\-plugin +option which loads it should only occur once. However, +only one transport plugin may be specified per +connection. + +If +.B connection-args +are present, these arguments are passed to the transport +plugin when establishing this connection specifically; this +is distinct from any per-plugin arguments which may have +been specified using the +.B \-\-plugin +option. Documentation for possible +.B connection-args +may be provided along with the plugin in use. + +When a transport plugin is in use, the +.B \-\-proto +option should not normally be used and will usually result in +an error, as the transport plugin takes over from the native +transport protocol that would otherwise be specified. The +rest of OpenVPN will operate in a manner similar to that of +UDP mode, using the pseudo-protocol "indirect". There is one +remaining rare use for +.B \-\-proto +in this case, which is to force a specific address family for +transport plugins for which this is still meaningful. This can +be done by specifying "indirect4" or "indirect6" as the +protocol. +.\"********************************************************* +.TP .B \-\-keying\-material\-exporter label len Save Exported Keying Material [RFC5705] of len bytes (must be between 16 and 4095 bytes) using label in environment diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 560d87db..9f7b5fdd 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -3335,6 +3335,7 @@ do_init_socket_1(struct context *c, const int mode) &c->c1.link_socket_addr, c->options.ipchange, c->plugins, + c->options.ce.transport_plugin_argv, c->options.resolve_retry_seconds, c->options.ce.mtu_discover_type, c->options.rcvbuf, diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 0cf8db76..7e905532 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -403,6 +403,9 @@ static const char usage_message[] = #ifdef ENABLE_PLUGIN "--plugin m [str]: Load plug-in module m passing str as an argument\n" " to its initialization function.\n" + "--transport-plugin m [args]: Use plug-in module m to provide the transport\n" + " layer, with optional per-connection args. The\n" + " module must already be loaded with --plugin.\n" #endif #if P2MP #if P2MP_SERVER @@ -2005,6 +2008,22 @@ options_postprocess_verify_ce(const struct options *options, const struct connec msg(M_USAGE, "--proto tcp is ambiguous in this context. Please specify --proto tcp-server or --proto tcp-client"); } +#ifdef ENABLE_PLUGIN + /* + * "proto indirect" may not be specified directly without a + * transport-plugin, and vice versa. + */ + if (ce->proto == PROTO_INDIRECT && !ce->transport_plugin_argv) + { + msg(M_USAGE, "--proto indirect may not be used without a transport-plugin line"); + } + + if (ce->transport_plugin_argv && ce->proto != PROTO_INDIRECT) + { + msg(M_USAGE, "--transport-plugin must be used with --proto indirect"); + } +#endif + /* * Sanity check on daemon/inetd modes */ @@ -5190,6 +5209,18 @@ add_option(struct options *options, goto err; } } + else if (streq(p[0], "transport-plugin") && p[1]) + { + VERIFY_PERMISSION(OPT_P_PLUGIN|OPT_P_CONNECTION); + + /* p[1] is the shared object name, which becomes + * argv[0]. p[2..] are connection-specific transport + * parameters, which become argv[1..]. + */ + options->ce.transport_plugin_argv = make_extended_arg_array(&p[1], + &options->gc); + options->ce.proto = PROTO_INDIRECT; + } #endif else if (streq(p[0], "mode") && p[1] && !p[2]) { diff --git a/src/openvpn/options.h b/src/openvpn/options.h index e2b38939..c2d0e9ac 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -85,6 +85,7 @@ struct options_pre_pull struct connection_entry { + const char **transport_plugin_argv; int proto; sa_family_t af; const char *local_port; diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c index b548ab7a..e8f790ea 100644 --- a/src/openvpn/socket.c +++ b/src/openvpn/socket.c @@ -1916,6 +1916,7 @@ link_socket_init_phase1(struct link_socket *sock, struct link_socket_addr *lsa, const char *ipchange_command, const struct plugin_list *plugins, + const char **transport_plugin_argv, int resolve_retry_seconds, int mtu_discover_type, int rcvbuf, @@ -1955,6 +1956,7 @@ link_socket_init_phase1(struct link_socket *sock, sock->info.bind_ipv6_only = bind_ipv6_only; sock->info.ipchange_command = ipchange_command; sock->info.plugins = plugins; + sock->info.transport_plugin_argv = transport_plugin_argv; sock->server_poll_timeout = server_poll_timeout; sock->mode = mode; diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h index 73a4ab6f..eb0b2a73 100644 --- a/src/openvpn/socket.h +++ b/src/openvpn/socket.h @@ -327,6 +327,7 @@ link_socket_init_phase1(struct link_socket *sock, struct link_socket_addr *lsa, const char *ipchange_command, const struct plugin_list *plugins, + const char **transport_plugin_argv, int resolve_retry_seconds, int mtu_discover_type, int rcvbuf, From patchwork Sun Dec 30 00:29:01 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Antonio Quartulli X-Patchwork-Id: 655 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director8.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id EHRhBqWsKFzHbgAAIUCqbw for ; Sun, 30 Dec 2018 06:31:49 -0500 Received: from proxy13.mail.iad3b.rsapps.net ([172.31.255.6]) by director8.mail.ord1d.rsapps.net with LMTP id cPCvA6WsKFybNAAAfY0hYg ; Sun, 30 Dec 2018 06:31:49 -0500 Received: from smtp28.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy13.mail.iad3b.rsapps.net with LMTP id 4BljOaSsKFwxNgAAvUvv+w ; Sun, 30 Dec 2018 06:31:48 -0500 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp28.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=unstable.cc X-Suspicious-Flag: YES X-Classification-ID: 7e31a5f6-0c26-11e9-90f8-525400c8cd63-1-1 Received: from [216.105.38.7] ([216.105.38.7:1364] helo=lists.sourceforge.net) by smtp28.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 21/05-09767-4ACA82C5; Sun, 30 Dec 2018 06:31:48 -0500 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1gdZIo-0003hd-Ut; Sun, 30 Dec 2018 11:30:42 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1gdZIn-0003hI-OR for openvpn-devel@lists.sourceforge.net; Sun, 30 Dec 2018 11:30:41 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=ZWYnsjgO6nBkqWyvJsc6HW1Xl0ceqXoPBBEXoT5OXLE=; b=LvYCurORykTOerWaLDPfES39y+ MwahM88YuM8ZOqXaAwy4wtBWhOyGyhiOp8qOnyav953yWywMT/3IDd9SIX+nycvtUm0DjGZMoomYp Fkbq9QNNxHvNyXg8M/BSlCBg6xBEiF9yT1FVv2xJT1SUpKfM/vqKGhOR7k0oya5hPCvA=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=ZWYnsjgO6nBkqWyvJsc6HW1Xl0ceqXoPBBEXoT5OXLE=; b=ITKknkVXiRYJXT+cBK3JWtXUZo Y+3Mg4IcpZPxg5xt/J+HTeTYqFkY8yA6+0dJMG/3vdC/cObqU2PdFmyGyByP5kodAo70dwU8MBjQz 0dNiJShTcsMrbbsdkK+9JVv4Av74ZE1QbFcCBkfZfGyxPevthT/oY1d3xr5F2RuD2lmI=; Received: from s2.neomailbox.net ([5.148.176.60]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLSv1.2:DHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1gdZIk-007dzS-Oa for openvpn-devel@lists.sourceforge.net; Sun, 30 Dec 2018 11:30:41 +0000 From: Antonio Quartulli To: openvpn-devel@lists.sourceforge.net Date: Sun, 30 Dec 2018 21:29:01 +1000 Message-Id: <20181230112901.29241-5-a@unstable.cc> In-Reply-To: <20181230112901.29241-1-a@unstable.cc> References: <20181230112901.29241-1-a@unstable.cc> MIME-Version: 1.0 X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [5.148.176.60 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1gdZIk-007dzS-Oa Subject: [Openvpn-devel] [PATCH 4/4] transport-plugin: add sample obfs-test plugin X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: antonio@openvpn.net, Robin Tarsiger Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Robin Tarsiger 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 [antonio@openvpn.net: refactored commits, restyled code] Signed-off-by: Robin Tarsiger <rtt@dasyatidae.com>
--- 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 +#include +#include +#include "obfs-test.h" +#ifdef OPENVPN_TRANSPORT_PLATFORM_POSIX +#include +#include +typedef in_port_t obfs_test_in_port_t; +#else +#include +#include +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 +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#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 */