The experience of masking an OpenVPN tunnel using obfsproxy
- Tutorial
Preamble
In connection with the emerging trends, I decided to obfuscate my modest OpenVPN tunnel, just to get my hand in it - it’s not very useful ...
Given: a cheap VPS with white IP running under the Ubuntu Trusty Server Edition and serving as an OpenVPN server.
Required: if possible, hide the OpenVPN tunnel, preferably without the invention of bicycles.
obfsproxy
Since the Tor node was already raised on VPS, the thought of obfsproxy came naturally . As it turned out, the utility really knows how to pretend to be SOCKS-proxy, which means that it will obfuscate not only Tor connections, but also almost arbitrary traffic. Rummaging around the Web, I found several manuals, but many were either outdated or overlooked the necessary details.
Important! Versions.
- OpenVPN versions earlier than 2.3.4 contain a bug that interferes with the normal work with SOCKS proxies. If you want to use socks mode, make sure you have the right version of OpenVPN installed.
- Apparently, obfsproxy is still not included in the Tor repository, and older versions without scramblesuit support may be in the Debian / Ubuntu repositories. In this case, obfsproxy must be installed using the pip utility ( python-pip package ):
pip install obfsproxy
After the installation is complete, make sure that you are using the obfsproxy version with scramblesuit support:obfsproxy scramblesuit -h
- The Windows version of obfsproxy can be extracted from the Tor Browser package by finding the Tor \ PluggableTransports subdirectory .
From man obfsproxy you can learn the following
obfsproxy [logging_options] [--data-dir path] transport [--dest destination] [--password BASE32PASS] mode listen_addr
More details:
- --data-dir statepath - directory for temporary data. Required if obfsproxy is not in managed mode.
- transport - selects the traffic obfuscation method and its transmission mode. We are interested in the following transports:
- scramblesuit is a relatively new method that we will use.
- dummy - a debugging method in which traffic is transmitted as is, without obfuscation.
- mode - selects the proxy mode of operation. We are interested in the following modes:
- server - accepts client connections, deobfuscates and redirects to the destination address . This mode is useful on the server side.
- client - transparent proxying: receives data, obfuscates and sends it to the server at destination address . What we will use on the client side.
- socks - imitation of socks proxies.
The difference between client and socks modes is that in the first case the address of the remote host to which the target application is trying to connect must be replaced with the address of the opbfsproxy client, while in the second case the target application uses obfsproxy as a SOCKS proxy. Client is also suitable for our purposes. - --dest destination - for the server: sets the address-port pair to which deobfuscated connections will be redirected. For the client: sets the address of the obfsproxy server.
- --password BASE32PASS - pre-shared key for the scramblesuit method. The key must be a string with a length of at least 20 characters, encoded in base32, and match on the client and on the server. A suitable key can be generated as follows:
python -c "import os,base64; print base64.b32encode(os.urandom(20))"
- --password-file filepath - an alternative key setting method for the scramblesuit method, in case you do not want to write pre-shared key in clear text.
- log_options includes the following keys:
- --log-file path - sets the location of the log.
- --log-min-severity level - Events of what level (debug, info, warning, error) to record.
- --no-safe-logging - by default obfsproxy cuts addresses from the log, but this key allows you to change this behavior.
- --no-log - disables logging altogether (by default).
The VPN server itself can either be hidden behind obfsproxy completely (making it listen to 127.0.0.1), or left on the external network interface in case of obfsproxy failure. I chose the second option as more reliable.
Thus, to run obfsproxy on the server side, it is enough to do something like:
obfsproxy --log-file /var/log/obfsproxy-openvpn.log --log-min-severity info scramblesuit --password BASE32LONGPASS --dest 1.2.3.4:1800 server 1.2.3.4:81
where 1.2.3.4 is the external IP of the host, 1800 is the port that listens to OpenVPN, 81 is the port that obfsproxy will listen to.
A serious drawback is the inability of obfsproxy to start in daemon mode, but this can be circumvented.
Running on the client is almost identical to the server:
obfsproxy scramblesuit --password BASE32LONGPASS --dest 1.2.3.4:81 {client|socks} 127.0.0.1:81
where 1.2.3.4:81 is the address that the obfsproxy server listens to, 127.0.0.1:81 is the address that the client is listening to.
Configure OpenVPN
Important : OpenVPN must use the TCP protocol.
If we use client mode, we simply replace the address of the remote server in the openvpn configuration file with the address of the local obfsproxy:
#remote 1.2.3.4 1800 #заменяем старый адрес сервера
remote 127.0.0.1 81 #вместо этого пытаемся подключиться к клиентской части obfsproxy
Socks mode
To work in socks mode, configure OpenVPN a little differently:
Considering the need to replace the address of the OpenVPN server with the address of the obfsproxy server, I don’t see much benefit from the proxy mode - applications in which the connection addresses are wired tightly, so you won’t block it anyway.
#remote 1.2.3.4 1800 #заменяем старый адрес сервера
remote 1.2.3.4 81 #на адрес obfsproxy-сервера (важно! иначе работать не будет)
socks-proxy-retry
socks-proxy 127.0.0.1 81 #пытаемся задействовать socks proxy
Considering the need to replace the address of the OpenVPN server with the address of the obfsproxy server, I don’t see much benefit from the proxy mode - applications in which the connection addresses are wired tightly, so you won’t block it anyway.
If openvpn traffic is blocked only occasionally (for example, only on a working network), you can use the following trick. In the OpenVPN configuration file, write the following:
remote 1.2.3.4 1800 #сначала пытаемся подключиться напрямую
remote 127.0.0.1 81 #если не удалось, подключаемся через obfsproxy
OpenVPN will try to use every section
Regarding the launch of obfsproxy, unfortunately, OpenVPN itself does not support pre-connect scripts (running the command before trying to connect). You can try to install one of the unofficial patches , run obfsproxy manually, or use one of the following methods.
openvpn-gui for windows
If you use the openvpn-gui wrapper to connect manually (or automatically with the --connect switch ), then the easiest way is to create the XXXXXX_pre.bat file in the config folder , where XXXXXX is the name of the configuration file (without the .ovpn extension). This file will be automatically executed before openvpn-gui tries to connect. The contents of the file will look something like this:
start "window title" /MIN "%USERPROFILE%\Tor Browser\Tor\PluggableTransports\obfsproxy.exe" --log-min-severity info --data-dir "%TEMP%\obfs-openvpn" scramblesuit --password-file obfsproxy.key --dest 1.2.3.4:81 client 127.0.0.1:81
- window title - window title required.
- % USERPROFILE% \ Desktop \ Tor Browser \ Tor \ PluggableTransports \ obfsproxy.exe - path to the obfsproxy executable file. By default, Tor Browser is installed on the desktop, although this is not necessary.
- % TEMP% \ obfs-openvpn - path for placing obfsproxy status files.
- obfsproxy.key - path to the file with the key, if you use it.
- 1.2.3.4:81 - the address that the obfsproxy server side is listening on.
- 127.0.0.1:81 - the address that the client part will listen to.
Using the start command (without the / wait switch, it works as an analogue of the & operator in the shell) is necessary, since openvpn-gui expects the pre-up script to complete before trying to connect. The disadvantage of this method is the console window, constantly hanging in the background. If desired, it can be hidden by the hidcon utility or similar.
obfsproxy as a service
If OpenVPN starts as a service, then it makes sense to organize obfsproxy in the same way.
You can create a service using srvany utility from MS or some alternative utility like NSSM . The issue of launching the application as a Windows service is beyond the scope of the article, so I’ll just point out that this approach allows you to set dependencies on and for other services, thereby ensuring that obfsproxy will be available at the time of launching OpenVPN.
This approach can be combined with the previous one using the bat-file launched by openvpn-gui to start the created service with the net start command .
Speed Impact
The last check was carried out on February 7, 2016 through the good old speedtest.net, using the same server in London (OpenVPN server is hosted in Latvia, the client is in the European part of Russia) from a client machine running Windows 7. Measurements were made in tenfold repetition. From the results it follows that at least in my case, obfsproxy does not significantly affect the speed. So, if there is a good channel to your VPN server, then it will not be worse.
Mode | Ping ms | Download, MB / s | Upload, MB / s | |
---|---|---|---|---|
direct | average | 56 | 20.18 | 21.03 |
coefficient variations | 3.6% | 0.6% | 1.2% | |
openvpn | average | 56 | 20.21 | 21.29 |
coefficient variations | 2.3% | 1,0% | 1,0% | |
openvpn + obfsproxy | average | 55 | 20.24 | 21.15 |
coefficient variations | 4.0% | 0.73% | 1.34% |
Running obfs-proxy as init.d service
init.d / obfs-openvpn
#! / bin / sh
### BEGIN INIT INFO
# Provides: obfsproxy-openvpn
# Required-Start: $ remote_fs $ syslog
# Required-Stop: $ remote_fs $ syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: obfsproxy wrapper for openvpn
# Description: This file starts up obfsproxy configured to process obfuscated openvpn client connections.
### END INIT INFO
# Author: Vindicar
# Do NOT “set -e"
# PATH should only include / usr / * if it runs after the mountnfs.sh script
PATH = "/ sbin: / usr / sbin: / bin: / usr / bin: / usr / local / bin "
DESC =" obfsproxy wrapper for openvpn "
NAME = obfs-openvpn
DAEMON =" / usr / local / bin / obfsproxy "
OBFSPROXY_KEYFILE =" / etc / obfsproxy-openvpn.key "
SERVER_IP =?.?.?.?.?.?.?.??? # <<< TODO: insert server address here
OPENVPN_PORT = 443 #port 443 due to use of sslh
OBFSPROXY_PORT = 81
DAEMON_ARGS = "- log-file /var/log/obfs-openvpn.log --log-min-severity warning \
--data-dir / tmp / obfs-openvpn \
scramblesuit --password-file $ OBFSPROXY_KEYFILE \
--dest $ SERVER_IP: $ OPENVPN_PORT server $ SERVER_IP: $ OBFSPROXY_PORT "
SCRIPTNAME = / etc / init.d / $ NAME
# Exit if the package is not installed
[-x "$ DAEMON"] || exit 0
# Read configuration variable file if it is present
[-r / etc / default / $ NAME] &&. / etc / default / $ NAME
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_ * functions.
# Depend on lsb-base (> = 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. / lib / lsb / init-functions
#
# Function that starts the daemon / service
#
do_start ()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon --start --quiet --pidfile $ PIDFILE --exec $ DAEMON --test> / dev / null \
|| return 1
start-stop-daemon --start --background --quiet --make-pidfile --pidfile $ PIDFILE --exec $ DAEMON - \
$ DAEMON_ARGS \
|| return 2
# Add code here, if necessary, that waits for the process to be ready
# to handle requests from services started subsequently which depend
# on this one. As a last resort, sleep for some time.
}
#
# Function that stops the daemon / service
#
do_stop ()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
# EDIT: removed --name parameter since ssd matches it against process name which is 'python'
# will be relying on the pidfile to stop it.
start-stop-daemon --stop --quiet --retry = TERM / 30 / KILL / 5 --pidfile $ PIDFILE
RETVAL = "$?"
["$ RETVAL" = 2] && return 2
# Wait for children to finish too if this is a daemon that forks
# and if the daemon is only ever run from this initscript.
# If the above conditions are not satisfied then add some other code
# that waits for the process to drop all resources that could be
# needed by services started subsequently. A last resort is to
# sleep for some time.
# start-stop-daemon --stop --quiet --oknodo --retry = 0/30 / KILL / 5 --exec $ DAEMON
# ["$?" = 2] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $ PIDFILE
return "$ RETVAL"
}
#
# Function that sends a SIGHUP to the daemon / service
#
do_reload () {
#
# If the daemon can reload its configuration without
# restarting (for example, when it is sent a SIGHUP ),
# then implement that here.
#
start-stop-daemon --stop --signal 1 --quiet --pidfile $ PIDFILE
return 0
}
case "$ 1" in
start)
["$ VERBOSE"! = no] &&
do_start
case "$?" in
0 | 1) ["$ VERBOSE"! = no] && log_end_msg 0 ;;
2) ["$ VERBOSE"! = No] && log_end_msg 1 ;;
esac
;;
stop)
["$ VERBOSE"! = no] && log_daemon_msg "Stopping $ DESC" "$ NAME"
do_stop
case "$?" in
0 | 1) ["$ VERBOSE"! = no] && log_end_msg 0 ;;
2) ["$ VERBOSE"! = No] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$ DAEMON" "$ NAME" && exit 0 || exit $?
;;
#reload | force-reload)
#
# If do_reload () is not implemented then leave this commented out
# and leave 'force-reload' as an alias for 'restart'.
#
#log_daemon_msg "Reloading $ DESC" "$ NAME"
#do_reload
#log_end_msg $?
# ;;
restart | force-reload)
#
# If the “reload” option is implemented then remove the
# 'force-reload' alias
#
log_daemon_msg "Restarting $ DESC" "$ NAME"
do_stop
case "$?" in
0 | 1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
#echo "Usage: $ SCRIPTNAME {start | stop | restart | reload | force-reload}"> & 2
echo "Usage: $ SCRIPTNAME {start | stop | status | restart | force-reload}"> & 2
exit 3
;;
esac
### BEGIN INIT INFO
# Provides: obfsproxy-openvpn
# Required-Start: $ remote_fs $ syslog
# Required-Stop: $ remote_fs $ syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: obfsproxy wrapper for openvpn
# Description: This file starts up obfsproxy configured to process obfuscated openvpn client connections.
### END INIT INFO
# Author: Vindicar
# Do NOT “set -e"
# PATH should only include / usr / * if it runs after the mountnfs.sh script
PATH = "/ sbin: / usr / sbin: / bin: / usr / bin: / usr / local / bin "
DESC =" obfsproxy wrapper for openvpn "
NAME = obfs-openvpn
DAEMON =" / usr / local / bin / obfsproxy "
OBFSPROXY_KEYFILE =" / etc / obfsproxy-openvpn.key "
SERVER_IP =?.?.?.?.?.?.?.??? # <<< TODO: insert server address here
OPENVPN_PORT = 443 #port 443 due to use of sslh
OBFSPROXY_PORT = 81
DAEMON_ARGS = "- log-file /var/log/obfs-openvpn.log --log-min-severity warning \
--data-dir / tmp / obfs-openvpn \
scramblesuit --password-file $ OBFSPROXY_KEYFILE \
--dest $ SERVER_IP: $ OPENVPN_PORT server $ SERVER_IP: $ OBFSPROXY_PORT "
SCRIPTNAME = / etc / init.d / $ NAME
# Exit if the package is not installed
[-x "$ DAEMON"] || exit 0
# Read configuration variable file if it is present
[-r / etc / default / $ NAME] &&. / etc / default / $ NAME
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_ * functions.
# Depend on lsb-base (> = 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. / lib / lsb / init-functions
#
# Function that starts the daemon / service
#
do_start ()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon --start --quiet --pidfile $ PIDFILE --exec $ DAEMON --test> / dev / null \
|| return 1
start-stop-daemon --start --background --quiet --make-pidfile --pidfile $ PIDFILE --exec $ DAEMON - \
$ DAEMON_ARGS \
|| return 2
# Add code here, if necessary, that waits for the process to be ready
# to handle requests from services started subsequently which depend
# on this one. As a last resort, sleep for some time.
}
#
# Function that stops the daemon / service
#
do_stop ()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
# EDIT: removed --name parameter since ssd matches it against process name which is 'python'
# will be relying on the pidfile to stop it.
start-stop-daemon --stop --quiet --retry = TERM / 30 / KILL / 5 --pidfile $ PIDFILE
RETVAL = "$?"
["$ RETVAL" = 2] && return 2
# Wait for children to finish too if this is a daemon that forks
# and if the daemon is only ever run from this initscript.
# If the above conditions are not satisfied then add some other code
# that waits for the process to drop all resources that could be
# needed by services started subsequently. A last resort is to
# sleep for some time.
# start-stop-daemon --stop --quiet --oknodo --retry = 0/30 / KILL / 5 --exec $ DAEMON
# ["$?" = 2] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $ PIDFILE
return "$ RETVAL"
}
#
# Function that sends a SIGHUP to the daemon / service
#
do_reload () {
#
# If the daemon can reload its configuration without
# restarting (for example, when it is sent a SIGHUP ),
# then implement that here.
#
start-stop-daemon --stop --signal 1 --quiet --pidfile $ PIDFILE
return 0
}
case "$ 1" in
start)
["$ VERBOSE"! = no] &&
do_start
case "$?" in
0 | 1) ["$ VERBOSE"! = no] && log_end_msg 0 ;;
2) ["$ VERBOSE"! = No] && log_end_msg 1 ;;
esac
;;
stop)
["$ VERBOSE"! = no] && log_daemon_msg "Stopping $ DESC" "$ NAME"
do_stop
case "$?" in
0 | 1) ["$ VERBOSE"! = no] && log_end_msg 0 ;;
2) ["$ VERBOSE"! = No] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$ DAEMON" "$ NAME" && exit 0 || exit $?
;;
#reload | force-reload)
#
# If do_reload () is not implemented then leave this commented out
# and leave 'force-reload' as an alias for 'restart'.
#
#log_daemon_msg "Reloading $ DESC" "$ NAME"
#do_reload
#log_end_msg $?
# ;;
restart | force-reload)
#
# If the “reload” option is implemented then remove the
# 'force-reload' alias
#
log_daemon_msg "Restarting $ DESC" "$ NAME"
do_stop
case "$?" in
0 | 1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
#echo "Usage: $ SCRIPTNAME {start | stop | restart | reload | force-reload}"> & 2
echo "Usage: $ SCRIPTNAME {start | stop | status | restart | force-reload}"> & 2
exit 3
;;
esac