Using OpenVPN for Select Sites with OpenWRT

I love watching Hulu from time to time, but living in Japan means I need to use a proxy or VPN located in the US to view it. I used to use a PPTP VPN server on a VPS to view Hulu, routing all traffic through the VPN. This had some obvious drawbacks in that Hulu playback was slower, the VPN configuration had to be done on each computer, and I couldn't use things like BitTorrent at the same time as watching videos on Hulu. While this was fine when I'd just watch videos on my laptop once every few weeks, I wanted a more transparent solution once an HTPC and an OpenWRT-capable router entered the equation.

The solution I came up with is simply connecting to an OpenVPN server from my OpenWRT router and selectively routing certain IP addresses through the VPN. The great thing about this setup is that it's completely transparent for anyone on the LAN. Any computer can stream videos from Hulu without any configuration.

OpenVPN Server

I won't cover setting up an OpenVPN server in any detail, as that's already covered in countless other pages on the Internet. I will, however, offer a few hints from my experience setting up an OpenVPN server on FreeBSD.

The VPN doesn't have to be fast. I used an old FreeBSD server of mine still running in my parents house on a slow cable connection.

I used a combination of these two guides to create a bridged VPN. Here's my basic OpenVPN config file:

port 1194  
proto udp

dev tun

ca /usr/local/etc/openvpn/keys/ca.crt  
cert /usr/local/etc/openvpn/keys/example.crt  
key /usr/local/etc/openvpn/keys/example.key  
dh /usr/local/etc/openvpn/keys/dh1024.pem

server 10.8.0.0 255.255.255.0

duplicate-cn

keepalive 10 120

comp-lzo

persist-key  
persist-tun

status /var/log/openvpn/openvpn-status.log

verb 3  

I then set up pf using the following pf.conf to NAT the VPN clients and give them access to the Internet:

ext_if="vr0"  
vpn_if="tun0"  
vpn_network="10.8.0.0/24"

nat on $ext_if from $vpn_network to any -> ($ext_if)  

OpenVPN Client on OpenWRT

I mostly followed the guide here to set up the client. For reference, I listed all the changes to my config files on the router below.

/etc/config/network

config interface 'vpn'  
        option ifname 'tun0'
        option defaultroute '0'
        option peerdns '0'
        option proto 'none'

/etc/config/firewall

config zone  
        option input 'ACCEPT'
        option forward 'REJECT'
        option output 'ACCEPT'
        option name 'vpn'
        option masq '1'
        option network 'vpn'

config forwarding  
        option dest 'lan'
        option src 'vpn'

config forwarding  
        option dest 'vpn'
        option src 'lan'

/etc/config/openvpn

config openvpn 'example'  
        option enabled '1'
        option client '1'
        option dev 'tun'
        option proto 'udp'
        option nobind '1'
        option persist_tun '1'
        option persist_key '1'
        option ca '/etc/openvpn/example.ca'
        option cert '/etc/openvpn/example.cert'
        option key '/etc/openvpn/example.key'
        option comp_lzo '1'
        option verb '3'
        option float '1'
        option remote 'example.us.ostanin.org'
        option client '1'

Selective Routing

Now, we can route IP addresses used by Hulu through the VPN. There are two ways to do this, either have the OpenWRT client call route to add the routes, or have the OpenVPN server push the routes to the client. I choose to push the routes from the server, as this would allow me to connect to the server from outside my LAN and still enjoy Hulu without any additional configuration.

OpenVPN allows you to add routes to the client using the push "route X.X.X.X 255.255.255.255" directive in it's config file. So we just need to add all the necessary IP addresses for Hulu into the OpenVPN server's config file and we're good to go. I wrote a python script to automate the process:

#! /usr/bin/env python

SITES = {  
    'hulu': [
        'hulu.com',
        'www.hulu.com',
        'static.hulu.com',
        'ads.hulu.com',
        'assets.hulu.com',
        't2.hulu.com',
        'urlcheck.hulu.com',
        'secure.hulu.com',
        'a.hulu.com',
        'b.hulu.com',
        'c.hulu.com',
        'd.hulu.com',
        'e.hulu.com',
        'f.hulu.com',
        'g.hulu.com',
        'h.hulu.com',
        'i.hulu.com',
        'j.hulu.com',
        'k.hulu.com',
        'l.hulu.com',
        'm.hulu.com',
        'n.hulu.com',
        'o.hulu.com',
        'p.hulu.com',
        'q.hulu.com',
        'r.hulu.com',
        's.hulu.com',
        't.hulu.com',
        'u.hulu.com',
        'v.hulu.com',
        'w.hulu.com',
        'x.hulu.com',
        'y.hulu.com',
        'z.hulu.com',
    ]  
}

import socket  
import sys

sites = sys.argv[1:]

for site in sites:  
    if site in SITES:
        print '###', site
        for host in SITES[site]:
            try:
                addrs = socket.gethostbyname_ex(host)[2]
                print '#', host
                for addr in addrs:
                    print 'push "route', addr, '255.255.255.255"'
            except:
                pass

When run with ./routemaker.py hulu, it will print out a list of all push directives needed to watch Hulu. Here is an example run:

$ ./routemaker.py hulu
### hulu
# hulu.com
push "route 60.254.153.10 255.255.255.255"  
push "route 60.254.153.25 255.255.255.255"  
# www.hulu.com
push "route 210.149.135.22 255.255.255.255"  
push "route 210.149.135.45 255.255.255.255"  
# static.hulu.com
push "route 23.3.104.33 255.255.255.255"  
push "route 23.3.104.8 255.255.255.255"  
# ads.hulu.com
push "route 23.3.104.65 255.255.255.255"  
push "route 23.3.104.59 255.255.255.255"  
# assets.hulu.com
push "route 23.3.104.42 255.255.255.255"  
push "route 23.3.104.75 255.255.255.255"  
# t2.hulu.com
push "route 23.3.104.32 255.255.255.255"  
push "route 23.3.104.9 255.255.255.255"  
# urlcheck.hulu.com
push "route 208.91.157.69 255.255.255.255"  
# secure.hulu.com
push "route 118.215.182.31 255.255.255.255"  
# a.hulu.com
push "route 23.3.104.59 255.255.255.255"  
push "route 23.3.104.65 255.255.255.255"  
# c.hulu.com
push "route 210.149.135.21 255.255.255.255"  
push "route 210.149.135.14 255.255.255.255"  
# g.hulu.com
push "route 208.91.157.28 255.255.255.255"  
# i.hulu.com
push "route 208.91.157.16 255.255.255.255"  
# m.hulu.com
push "route 23.3.104.66 255.255.255.255"  
push "route 23.3.104.56 255.255.255.255"  
# p.hulu.com
push "route 23.3.104.43 255.255.255.255"  
push "route 23.3.104.81 255.255.255.255"  
# r.hulu.com
push "route 210.149.135.29 255.255.255.255"  
push "route 210.149.135.44 255.255.255.255"  
# s.hulu.com
push "route 23.3.104.65 255.255.255.255"  
push "route 23.3.104.33 255.255.255.255"  
# t.hulu.com
push "route 208.91.157.68 255.255.255.255"  
# u.hulu.com
push "route 208.91.157.20 255.255.255.255"  

You can now copy and paste that to your OpenVPN server config and restart the server. You could also automate it to run once a day and restart OpenVPN automatically if you so wish.

If everything worked, the Hulu routes should have been added to your client:

[email protected]:~# route  
Kernel IP routing table  
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface  
default         182.236.18.15   0.0.0.0         UG    0      0        0 pppoe-wan  
10.8.0.1        10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
10.8.0.5        *               255.255.255.255 UH    0      0        0 tun0  
23.3.104.8      10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.9      10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.32     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.33     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.34     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.42     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.43     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.56     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.59     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.65     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.66     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.73     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.75     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.3.104.81     10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
23.48.134.31    10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
60.254.153.10   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
60.254.153.25   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
182.236.18.15   *               255.255.255.255 UH    0      0        0 pppoe-wan  
192.168.1.0     *               255.255.255.0   U     0      0        0 br-lan  
208.91.157.16   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
208.91.157.20   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
208.91.157.28   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
208.91.157.68   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
208.91.157.69   10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
210.149.135.30  10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  
210.149.135.53  10.8.0.5        255.255.255.255 UGH   0      0        0 tun0  

And there you have it! Hulu should now work on all devices within the LAN. You can make other sites work too by adding their hostnames to the script as well. Keep in mind that ads from Hulu will be sent through the VPN while videos themselves will not, resulting in very laggy ads if your VPN is slow like mine. If your Hulu client allows choosing a quality setting for ads (like the XBMC client does), set them to the lowest quality.