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.



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 to the VPS (this additional address was added to the loopback interface) and tells the VPS that (home network) and (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
ifconfig-pool-persist ipp.txt
push "route"
client-config-dir clients
keepalive 10 120
status openvpn-status.log
verb 3

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


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 ''
        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:

if [ "$1" == "tun1" ] ; then
ip rule del from
ip rule add from table usvpn
ip route del table usvpn
ip route add table usvpn dev br-brvir  proto kernel  scope link  src
ip route add default table usvpn via $5 dev $1
ip route flush cache

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:


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


# PROVIDE: nylon
# KEYWORD: shutdown

. /etc/rc.subr

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";
        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";
        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, "",   "") ||
                        isInNet(ip, "", "")
           ) {
                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.