Wednesday, January 25, 2012

Seamless, Secure Routing to Blocked Sites

Where I live, Internet access is restricted. Specific sites are blocked, as well as sections of some sites depending on the content. Blocking is done primarily in two ways:
  • False DNS results
  • Broken connections, including unacknowledged SYNs, high packet loss, and injected RSTs.
With access to a computer outside the country (such as a micro instance on EC2), these problems may be overcome by using the outside computer as a proxy. One simple solution is to use SSH as a SOCKS proxy. This is what I had been using for about a year. It works fine, but not all devices can use a SOCKS proxy, and performance is degraded by layering TCP inside TCP.

As a weekend project, I decided to implement a more sophisticated system, with the following goals:

  • Only route data destined for blocked networks through the proxy so that domestic sites remain fast
  • All devices connected to my home network should automatically be able to access blocked sites with zero configuration
With these goals in mind, I designed the following system:

  • Router maintains a secure VPN tunnel to the proxy (OpenVPN)
  • Routing table has entries for all IP blocks owned by blocked sites, routes this traffic over the tunnel
  • Local DNS server is configured to forward requests for blocked domains to Google's public DNS server over the VPN tunnel
  • Local DNS server forwards other requests to the ISP's DNS server
My router is a Mikrotik RouterOS device, which supports OpenVPN tunnels. I configured a VPN tunnel at the router level to a linux instance on EC2. This is pretty standard so I'll skip the details.

Setting up the routing table is where things get a little tricky. Fortunately the blocked sites are all rather large companies, and they all have their own AS number. Using radb it is possible to query which address blocks are advertised by an ASN. I wrote a script to parse this information and print out router commands for each block:

#!/bin/bash

if [ $# -ne 2 ]; then
  echo "Usage: `basename $0` [AS number] [friendly name]"
  exit 1
fi

asNumber=$1
asName=$2
whois -h whois.radb.net "!gAS$asNumber" | head -n -1 |awk 'NR>1' | tr -d '\n' | tr ' ' '\n' | sort | uniq | tr '\n' ' ' | awk "{for(i=1;i<=NF;i++)print \"add dst-address=\"\$i\" gateway=ovpn-out1 comment=\\\"AS$asNumber $asName\\\"\"}"
Here's the results for Twitter (AS13414).
add dst-address=199.16.156.0/22 gateway=ovpn-out1 comment="AS13414 Twitter"
add dst-address=199.59.148.0/22 gateway=ovpn-out1 comment="AS13414 Twitter"
add dst-address=24.75.96.0/21 gateway=ovpn-out1 comment="AS13414 Twitter"
I also did the same for Facebook (AS32934) and Google (AS15169), both of which have considerably more blocks. Unfortunately the data from radb contains redundant blocks, for example it has some CIDR blocks which are both a /24 and a /23, so the /24 is useless since it is already contained in the /23. The total number of routes is a bit over a thousand, which the Mikrotik can handle easily, but with some logic or a better data source it could be shrunk to a few hundred.

That was the hard bit. Next up is DNS. I installed Unbound and configured it to forward to Google's public DNS server (8.8.8.8) for the blocked domains (which, recall, will be routed over the VPN tunnel and so not subject to poisoning), and my ISP's DNS server for the rest:

server:
 verbosity: 1
 interface: 0.0.0.0
        access-control: 0.0.0.0/0 allow
 msg-cache-size: 16m
        rrset-cache-size: 32m
 chroot: ""

forward-zone:
    name: "facebook.com"
    forward-addr: 8.8.8.8
forward-zone:
    name: "fbcdn.net"
    forward-addr: 8.8.8.8
forward-zone:
    name: "facebook.net"
    forward-addr: 8.8.8.8
forward-zone:
    name: "facebook.com.edgekey.net"
    forward-addr: 8.8.8.8
forward-zone:
    name: "facebook.com.edgesuite.net"
    forward-addr: 8.8.8.8
forward-zone:
    name: "akamaiedge.net"
    forward-addr: 8.8.8.8
forward-zone:
    name: "google.com"
    forward-addr: 8.8.8.8
forward-zone:
    name: "gmail.com"
    forward-addr: 8.8.8.8
forward-zone:
    name: "googleusercontent.com"
    forward-addr: 8.8.8.8
forward-zone:
    name: "blogspot.com"
    forward-addr: 8.8.8.8
forward-zone:
    name: "blogger.com"
    forward-addr: 8.8.8.8
forward-zone:
    name: "youtube.com"
    forward-addr: 8.8.8.8
forward-zone:
    name: "twitter.com"
    forward-addr: 8.8.8.8
forward-zone:
    name: "."
    forward-addr: 192.168.88.1
And, we're done. Now any device can log onto the wifi in my apartment and have unrestricted access to all the normally blocked sites, and still have fast connectivity to domestic sites.