This work allows obfuscation of the OpenVPN header to make it harder for layer 7 inspection to identify such traffic, which may come with blocking or recording actions in certain territories of the world. This patch, in a nutshell, can increase privacy and range of communication for its users. The `scramble' option introduced hereby is off by default. The option's usage, history and controversy of the patch is explained in detail on the following wiki page: https://tunnelblick.net/cOpenvpn_xorpatch.html --- src/openvpn/forward.c.orig 2016-08-23 14:16:28 UTC +++ src/openvpn/forward.c @@ -674,7 +674,10 @@ read_incoming_link (struct context *c) status = link_socket_read (c->c2.link_socket, &c->c2.buf, - &c->c2.from); + &c->c2.from, + c->options.ce.xormethod, + c->options.ce.xormask, + c->options.ce.xormasklen); if (socket_connection_reset (c->c2.link_socket, status)) { @@ -1151,7 +1154,10 @@ process_outgoing_link (struct context *c /* Send packet */ size = link_socket_write (c->c2.link_socket, &c->c2.to_link, - to_addr); + to_addr, + c->options.ce.xormethod, + c->options.ce.xormask, + c->options.ce.xormasklen); #ifdef ENABLE_SOCKS /* Undo effect of prepend */ --- src/openvpn/options.c.orig 2016-08-23 14:16:22 UTC +++ src/openvpn/options.c @@ -792,6 +792,9 @@ init_options (struct options *o, const b o->max_routes = MAX_ROUTES_DEFAULT; o->resolve_retry_seconds = RESOLV_RETRY_INFINITE; o->proto_force = -1; + o->ce.xormethod = 0; + o->ce.xormask = "\0"; + o->ce.xormasklen = 0; #ifdef ENABLE_OCC o->occ = true; #endif @@ -907,6 +910,9 @@ setenv_connection_entry (struct env_set setenv_int_i (es, "local_port", e->local_port, i); setenv_str_i (es, "remote", e->remote, i); setenv_int_i (es, "remote_port", e->remote_port, i); + setenv_int_i (es, "xormethod", e->xormethod, i); + setenv_str_i (es, "xormask", e->xormask, i); + setenv_int_i (es, "xormasklen", e->xormasklen, i); #ifdef ENABLE_HTTP_PROXY if (e->http_proxy_options) @@ -1366,6 +1372,9 @@ show_connection_entry (const struct conn SHOW_INT (connect_retry_seconds); SHOW_INT (connect_timeout); SHOW_INT (connect_retry_max); + SHOW_INT (xormethod); + SHOW_STR (xormask); + SHOW_INT (xormasklen); #ifdef ENABLE_HTTP_PROXY if (o->http_proxy_options) @@ -5131,6 +5140,46 @@ add_option (struct options *options, options->proto_force = proto_force; options->force_connection_list = true; } + else if (streq (p[0], "scramble") && p[1]) + { + VERIFY_PERMISSION (OPT_P_GENERAL|OPT_P_CONNECTION); + if (streq (p[1], "xormask") && p[2] && (!p[3])) + { + options->ce.xormethod = 1; + options->ce.xormask = p[2]; + options->ce.xormasklen = strlen(options->ce.xormask); + } + else if (streq (p[1], "xorptrpos") && (!p[2])) + { + options->ce.xormethod = 2; + options->ce.xormask = NULL; + options->ce.xormasklen = 0; + } + else if (streq (p[1], "reverse") && (!p[2])) + { + options->ce.xormethod = 3; + options->ce.xormask = NULL; + options->ce.xormasklen = 0; + } + else if (streq (p[1], "obfuscate") && p[2] && (!p[3])) + { + options->ce.xormethod = 4; + options->ce.xormask = p[2]; + options->ce.xormasklen = strlen(options->ce.xormask); + } + else if (!p[2]) + { + msg (M_WARN, "WARNING: No recognized 'scramble' method specified; using 'scramble xormask \"%s\"'", p[1]); + options->ce.xormethod = 1; + options->ce.xormask = p[1]; + options->ce.xormasklen = strlen(options->ce.xormask); + } + else + { + msg (msglevel, "No recognized 'scramble' method specified or extra parameters for 'scramble'"); + goto err; + } + } #ifdef ENABLE_HTTP_PROXY else if (streq (p[0], "http-proxy") && p[1]) { --- src/openvpn/options.h.orig 2016-08-23 14:16:22 UTC +++ src/openvpn/options.h @@ -100,6 +100,9 @@ struct connection_entry int connect_retry_max; int connect_timeout; bool connect_timeout_defined; + int xormethod; + const char *xormask; + int xormasklen; #ifdef ENABLE_HTTP_PROXY struct http_proxy_options *http_proxy_options; #endif --- src/openvpn/socket.c.orig 2016-08-23 14:16:22 UTC +++ src/openvpn/socket.c @@ -52,6 +52,53 @@ const int proto_overhead[] = { /* indexe IPv6_TCP_HEADER_SIZE, }; +int buffer_mask (struct buffer *buf, const char *mask, int xormasklen) { + int i; + uint8_t *b; + if ( xormasklen > 0 ) { + for (i = 0, b = BPTR (buf); i < BLEN(buf); i++, b++) { + *b = *b ^ mask[i % xormasklen]; + } + } + return BLEN (buf); +} + +int buffer_xorptrpos (struct buffer *buf) { + int i; + uint8_t *b; + for (i = 0, b = BPTR (buf); i < BLEN(buf); i++, b++) { + *b = *b ^ i+1; + } + return BLEN (buf); +} + +int buffer_reverse (struct buffer *buf) { +/* This function has been rewritten for Tunnelblick. The buffer_reverse function at + * https://github.com/clayface/openvpn_xorpatch + * makes a copy of the buffer and it writes to the byte **after** the + * buffer contents, so if the buffer is full then it writes outside of the buffer. + * This rewritten version does neither. + * + * For interoperability, this rewritten version preserves the behavior of the original + * function: it does not modify the first character of the buffer. So it does not + * actually reverse the contents of the buffer. Instead, it changes 'abcde' to 'aedcb'. + * (Of course, the actual buffer contents are bytes, and not necessarily characters.) + */ + int len = BLEN(buf); + if ( len > 2 ) { /* Leave '', 'a', and 'ab' alone */ + int i; + uint8_t *b_start = BPTR (buf) + 1; /* point to first byte to swap */ + uint8_t *b_end = BPTR (buf) + (len - 1); /* point to last byte to swap */ + uint8_t tmp; + for (i = 0; i < (len-1)/2; i++, b_start++, b_end--) { + tmp = *b_start; + *b_start = *b_end; + *b_end = tmp; + } + } + return len; +} + /* * Convert sockflags/getaddr_flags into getaddr_flags */ --- src/openvpn/socket.h.orig 2016-08-23 14:16:22 UTC +++ src/openvpn/socket.h @@ -245,6 +245,10 @@ struct link_socket #endif }; +int buffer_mask (struct buffer *buf, const char *xormask, int xormasklen); +int buffer_xorptrpos (struct buffer *buf); +int buffer_reverse (struct buffer *buf); + /* * Some Posix/Win32 differences. */ @@ -873,30 +877,56 @@ int link_socket_read_udp_posix (struct l static inline int link_socket_read (struct link_socket *sock, struct buffer *buf, - struct link_socket_actual *from) + struct link_socket_actual *from, + int xormethod, + const char *xormask, + int xormasklen) { + int res; if (proto_is_udp(sock->info.proto)) /* unified UDPv4 and UDPv6 */ { - int res; #ifdef WIN32 res = link_socket_read_udp_win32 (sock, buf, from); #else res = link_socket_read_udp_posix (sock, buf, from); #endif - return res; } else if (proto_is_tcp(sock->info.proto)) /* unified TCPv4 and TCPv6 */ { /* from address was returned by accept */ addr_copy_sa(&from->dest, &sock->info.lsa->actual.dest); - return link_socket_read_tcp (sock, buf); + res = link_socket_read_tcp (sock, buf); } else { ASSERT (0); return -1; /* NOTREACHED */ } + switch(xormethod) + { + case 0: + break; + case 1: + buffer_mask(buf,xormask,xormasklen); + break; + case 2: + buffer_xorptrpos(buf); + break; + case 3: + buffer_reverse(buf); + break; + case 4: + buffer_mask(buf,xormask,xormasklen); + buffer_xorptrpos(buf); + buffer_reverse(buf); + buffer_xorptrpos(buf); + break; + default: + ASSERT (0); + return -1; /* NOTREACHED */ + } + return res; } /* @@ -980,8 +1010,34 @@ link_socket_write_udp (struct link_socke static inline int link_socket_write (struct link_socket *sock, struct buffer *buf, - struct link_socket_actual *to) + struct link_socket_actual *to, + int xormethod, + const char *xormask, + int xormasklen) { + switch(xormethod) + { + case 0: + break; + case 1: + buffer_mask(buf,xormask,xormasklen); + break; + case 2: + buffer_xorptrpos(buf); + break; + case 3: + buffer_reverse(buf); + break; + case 4: + buffer_xorptrpos(buf); + buffer_reverse(buf); + buffer_xorptrpos(buf); + buffer_mask(buf,xormask,xormasklen); + break; + default: + ASSERT (0); + return -1; /* NOTREACHED */ + } if (proto_is_udp(sock->info.proto)) /* unified UDPv4 and UDPv6 */ { return link_socket_write_udp (sock, buf, to);