stunnel: SOCKS VPN

SOCKS VPN Overview

The following example illustrates using stunnel for a transparent VPN based on the SSL-encrypted SOCKS protocol with the Tor RESOLVE [F0] extension.

Unlike most other VPNs, SOCKS-based VPNs do not introduce any persistent control connection. This is highly preferable for battery-powered clients, as there are no keepalives. This also performs as good as direct TCP connections when clients frequently change their IP addresses, which is common in mobile environments.

Server Prerequisites

  • stunnel 5.24b1 or later on any platform supported by stunnel

The server configuration does not require any specific operating systems nor administrative privileges. Consequently, it is possible to setup VPN servers on most shared hosting platforms.

Client Prerequisites

  • stunnel 5.23 or later on the Linux platform
  • stunnel 5.24b2 or later on the FreeBSD, OpenBSD or OSX platform
  • Administrative (root) privileges
  • Tor-DNS for optional encrypted DNS support
The socksvpn client is not supported on the Windows platform.

Create Shared Secrets

Create the secrets.txt file containing long pre-shared secrets. The secrets.txt file on each client needs to contain just one username/secret pair. The secrets.txt on the server needs to contain the secrets of all permitted clients, for example:

user1:hooxaa4bohFa9booNo1meZaishie3e
user2:this is a very long and sufficiently secure passphrase
It is also possible, although more complex, to use PKI for authentication.

Setup the Server

The configuration file (stunnel.conf) template:

[SOCKS Server]
PSKsecrets = secrets.txt
accept = :::9080
protocol = socks

Setup the Client

The VPN client can be either a Linux gateway routing the traffic for an internal network (which needs the IP forwarding to be enabled), or a single Linux host (server or workstation).

Setup stunnel and run it as root

The configuration file (stunnel.conf) template:

[SOCKS Client Direct]
client = yes
PSKsecrets = secrets.txt
accept = :::9050
connect = <server_address>:9080

[SOCKS Client Transparent IPv4]
client = yes
PSKsecrets = secrets.txt
accept = 127.0.0.1:9051
connect = <server_address>:9080
protocol = socks

[SOCKS Client Transparent IPv6]
client = yes
PSKsecrets = secrets.txt
accept = ::1:9051
connect = <server_address>:9080
protocol = socks

Setup the firewall (as root)

On Linux the Netfilter is used:
VPN_HOST=<server_address>
iptables -t nat -A OUTPUT -p tcp -d $VPN_HOST --dport 9080 -j ACCEPT 2>/dev/null
iptables -t nat -A OUTPUT -o lo -j ACCEPT # internal OS IPC
iptables -t nat -A OUTPUT -p tcp --dport 9050 -j ACCEPT # non-transparent SOCKS
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports 9051
iptables -t nat -A PREROUTING -p tcp --dport 9050 -j ACCEPT # non-transparent SOCKS
iptables -t nat -A PREROUTING -p tcp -j REDIRECT --to-ports 9051
ip6tables -t nat -A OUTPUT -p tcp -d $VPN_HOST --dport 9080 -j ACCEPT 2>/dev/null
ip6tables -t nat -A OUTPUT -o lo -j ACCEPT # internal OS IPC
ip6tables -t nat -A OUTPUT -p tcp --dport 9050 -j ACCEPT # non-transparent SOCKS
ip6tables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports 9051
ip6tables -t nat -A PREROUTING -p tcp --dport 9050 -j ACCEPT # non-transparent SOCKS
ip6tables -t nat -A PREROUTING -p tcp -j REDIRECT --to-ports 9051
On FreeBSD, OpenBSD or OS X a PF configuration is required. Please contribute your configuration if you use one of these operating systems.

Setup DNS

Setup Tor-DNS to resolve DNS requests with SOCKS service on port 9050.

Configure your resolver configuration with DHCP, or by editing /etc/resolv.conf if DHCP is not used.

Client Configuration Script

The following script can be used to automate the client configuration on Linux (FreeBSD, OpenBSD and OSX support is also planned):

#!/bin/bash

# socksvpn      SOCKS VPN start/stop script
# Copyright (C) 2015 Michal Trojnara 
# Version:      1.03
# Release date: 2015.09.07

VPN_HOST=example.com
VPN_PORT=9080
SECRETS=/usr/local/etc/stunnel/secrets.txt
PID_DIR=/run
PID_STUNNEL=$PID_DIR/socksvpn.pid
PID_TOR_DNS=$PID_DIR/tor-dns.pid

stunnel_start() {
    stunnel -fd 0 << EOT
pid = $PID_STUNNEL
client = yes
PSKsecrets = $SECRETS
connect = $VPN_HOST:$VPN_PORT

[SOCKS Client Direct]
accept = :::9050

[SOCKS Client Transparent IPv4]
accept = 127.0.0.1:9051
protocol = socks

[SOCKS Client Transparent IPv6]
accept = ::1:9051
protocol = socks
EOT
}

do_netfilter() {
    $1 -t nat -F $2
    if [[ $2 = OUTPUT ]]; then # traffic of local processes
        $1 -t nat -A $2 -p tcp -d $VPN_HOST --dport $VPN_PORT -j ACCEPT 2>/dev/null
        $1 -t nat -A $2 -o lo -j ACCEPT # internal OS IPC
    fi
    $1 -t nat -A $2 -p tcp --dport 9050 -j ACCEPT # non-transparent SOCKS
    $1 -t nat -A $2 -p tcp -j REDIRECT --to-ports 9051
}

netfilter_start() {
    for PROG in iptables ip6tables; do
        for TABLE in PREROUTING OUTPUT; do
            do_netfilter $PROG $TABLE
        done
    done
}

netfilter_stop() {
    for PROG in iptables ip6tables; do
        for TABLE in PREROUTING OUTPUT; do
            $PROG -t nat -F $TABLE
        done
    done
}

pf_start() {
    sysctl -w net.inet.ip.forwarding=1
    # the PF configuration needs to be implemented:
    # echo "rdr on en2 inet proto tcp to any port 443 -> 127.0.0.1 port 9051" >"$0.pf"
    # pfctl -f "$0.pf"
    pfctl -e
}

pf_stop() {
    pfctl -d
}

do_start() {
    stunnel_start
    netfilter_start
    rm -f $PID_TOR_DNS
    nohup tor-dns >/dev/null 2>&1 &
    echo $! >$PID_TOR_DNS
    cp /etc/resolv.conf /etc/resolv.conf.socksvpn-backup
    echo "nameserver 127.0.0.1" >/etc/resolv.conf
    echo "$0 started"
}

do_stop() {
    cp /etc/resolv.conf.socksvpn-backup /etc/resolv.conf
    netfilter_stop
    kill -TERM $(cat $PID_STUNNEL)
    kill -TERM $(cat $PID_TOR_DNS)
    rm -f $PID_TOR_DNS
    echo "$0 stopped"
}

if [[ $EUID -ne 0 ]]; then
   echo "$0 must be run as root" >&2
   exit 1
fi

case "$1" in
    start)
        do_start
        ;;
    restart|reload|force-reload)
        do_stop
        sleep 3
        do_start
        ;;
    stop)
        do_stop
        ;;
    *)
        echo "Usage: $0 start|stop|restart|reload|force-reload" >&2
        exit 3
        ;;
esac

View Michal Trojnara's profile on LinkedIn

Valid HTML 4.01 Transitional