Random Notes‎ > ‎

Bypassing Internet censorship with OpenVPN on OpenWRT and proxy autoconfig

posted Aug 14, 2014, 12:45 PM by William Shallum   [ updated Aug 14, 2014, 12:46 PM ]
The local ISP has blocked several sites. They have a transparent HTTP proxy so even if you used alternate DNS servers, HTTP traffic to those sites will still be intercepted. To get past this I choose to use a proxy located on a VPS I have elsewhere. However not all traffic should be routed via the VPN since it will slow things down. Also the bandwidth for the VPS is not "unlimited" like the local ISP offers.

[[ TODO CREATE AND INSERT IMAGE ]]

Overview


Router will connect to VPS using OpenVPN in a site-to-site configuration. A dummy network will be set up on the VPS side to act as the other "site". A SOCKS proxy running on the VPS will listen for requests from clients at home coming through the tunnel. A proxy autoconfig (PAC) file will be used to direct access to blocked sites only through the SOCKS proxy. Other sites will just go through the ISP as usual. As a bonus, there will be an extra SSID added to the wireless router where clients connecting to that SSID have their traffic automatically tunneled through the VPN.

OpenVPN certificate setup


Actually I used the startssl.com certificates (client S/MIME and web server certs) for this, but readers may find the instructions in the OpenVPN howto less troublesome: https://openvpn.net/index.php/open-source/documentation/howto.html#pki

Setting up OpenVPN - VPS


I installed OpenVPN from pkgsrc on a NetBSD VPS. Configuration follows. This routes 10.1.1.0/24 to the VPS (this additional address was added to the loopback interface) and tells the VPS that 192.168.1.0/24 (home network) and 172.16.0.0/24 (home "always VPN" SSID network) are to be reached through OpenVPN.

local **server-ip-address**
port 1194
proto udp
dev tun0
ca /usr/pkg/etc/openvpn/ca.crt
cert /usr/pkg/etc/openvpn/server.crt
key /usr/pkg/etc/openvpn/server.key
dh /usr/pkg/etc/openvpn/dh2048.pem
server 10.11.12.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "route 10.1.1.0 255.255.255.0"
client-config-dir clients
ccd-exclusive
route 192.168.1.0 255.255.255.0
route 172.16.0.0 255.255.255.0
keepalive 10 120
ping-timer-rem
comp-lzo
persist-key
persist-tun
status openvpn-status.log
verb 3

and for the client config in clients/certificate-cn, this says that this particular client will handle the 192.168.1.0/24 and 172.16.0.0/24 network:

iroute 192.168.1.0 255.255.255.0
iroute 172.16.0.0 255.255.255.0

Setting up OpenVPN - OpenWRT router

OpenVPN is installed from opkg. There is probably a luci app to configure it, but I used the config files.

config openvpn 'to_vps'
        option client '1'
        option dev 'tun1'
        option proto 'udp'
        option remote '**server-host-name** 1194'
        option resolv_retry 'infinite'
        option nobind '1'
        option persist_key '1'
        option persist_tun '1'
        option comp_lzo '1'
        option keepalive '10 60'
        option ping_timer_rem '1'
        option enabled '1'
        option verb '3'
        option ca '/etc/openvpn/ca.crt'
        option cert '/etc/openvpn/client.crt'
        option key '/etc/openvpn/client.key'
        option remote_cert_eku '1.3.6.1.5.5.7.3.1'
        option tls_remote '**server-common-name**'
        option up '/etc/openvpn/up-vps.sh'
        option script_security '2'

On up this will run /etc/openvpn/up-vps.sh which is:

#!/bin/sh
if [ "$1" == "tun1" ] ; then
ip rule del from 172.16.0.0/24
ip rule add from 172.16.0.0/24 table usvpn
ip route del table usvpn
ip route add table usvpn 172.16.0.0/24 dev br-brvir  proto kernel  scope link  src 172.16.0.1
ip route add default table usvpn via $5 dev $1
ip route flush cache
fi

That is done so the default gateway for the "always VPN" SSID can pass through the VPN (TODO document this more).

Setting up nylon SOCKS proxy


Config file:

[General]
No-Simultaneous-Conn=10
Verbose=0
[Server]
Port=1080
Binding-Interface=10.1.1.1
Connecting-Interface=**vps-outgoing-interface**
Allow-IP=192.168.1.0/24

Startup rc.d script (taken from FreeBSD port (?) not sure anymore):

#!/bin/sh

# PROVIDE: nylon
# REQUIRE: DAEMON
# KEYWORD: shutdown

. /etc/rc.subr

name="nylon"
rcvar="${name}"
command="/usr/local/bin/${name}"
config_file="/usr/local/etc/nylon.conf"
required_files="$config_file"
pidfile="/var/run/${name}.pid"
command_args="-c $config_file -P $pidfile"

load_rc_config $name
run_rc_command "$1"

Proxy PAC file


Luckily the ISP's DNS servers returns a different address for blocked sites. Thus we can assume that any answer that lies within the ISP's network means the site is blocked.

We can use OpenWRT's web server to serve PAC files. They are automatically served with the correct MIME type if saved with the extension .pac. The document root is /www (e.g. /www/proxy.pac = http://router.ip/proxy.pac).

The function itself is relatively clean, with just some hacks to skip youtube and home domain. Also Apple's CFNetwork implementation is detected and a more appropriate proxy string is returned. PAC has an "isInNet" function that is useful. However, you can also choose to match it using a regex if that feels better. One regex can match multiple disjoint ranges with the alternation operator. The IP ranges for your ISP can be obtained from APNIC's WHOIS (if in Asia-Pacific region).

function FindProxyForURL(url, host) {
        var noproxy = "DIRECT";
        var proxy = "SOCKS5 10.1.1.1:1080";
        if (typeof __Apple_dsn_cache != "undefined") {
                /* apple prefers SOCKS not SOCKS5 http://www.opensource.apple.com/source/CFNetwork/CFNetwork-129.9/Proxies/ProxySupport.c */
                proxy = "SOCKS 10.1.1.1:1080";
        }
        if (!shExpMatch(url, "http://*") && !shExpMatch(url, "https://*")) {
                return noproxy;
        }
        if (isPlainHostName(host)) {
                return noproxy;
        }
        if (dnsDomainIs(host, ".my.home.domain")) {
                return noproxy;
        }
        if (shExpMatch(url, "https://*")) {
                if (shExpMatch(host, "*.youtube.com") || shExpMatch(host, "*.ytimg.com")) {
                        return noproxy;
                }
        }
        var ip = dnsResolve(host);
        
        /* NOTE: not real IP ranges, these are only examples. use APNIC's whois to find IP ranges */

        var re = /^192\.0\.2\.(2|4|6)/;
        if (re.test(ip)) {
                return proxy;
        }
        if (
                        isInNet(ip, "198.51.100.0",   "255.255.255.0") ||
                        isInNet(ip, "203.0.113.0", "255.255.255.0")
           ) {
                return proxy;
        }
        return noproxy;
}


Browser config


Browsers can be configured to use the PAC file. Chrome follows the Windows system settings for this. Since sometimes things don't work properly, there should be another browser that can be used if the proxy is to be bypassed e.g. Firefox with Proxy Selector.

References


Comments