Friday 12 December 2014

Run your own proxy server to watch BBC iPlayer outside the UK



TL;DR head to netflix-proxy on GitHub or subscribe to Unzoner VPN service to un-block:



If you are located outside of the UK and you want to watch a subset of content the British Broadcast Corporation (BBC) makes available online via BBC iPlayer, you are out of luck. This content is only available to users based in the UK (or you could pay BBC for it).

You could also pay for commercial services, such as HolaUnblock-Us and 
Smart DNS Proxy to name a few, which allow you to bypass the geo restriction.

Alternatively, or you can knock together a similar solution quickly yourself for a cost of a cheap Virtual Private Server (VPS). How cheap, depends on where you decide to host your VPS. For my hosting I choose Digital Ocean with the VPS hosted in a London data center for $5 USD per month. If you sign up with the link above, you will receive $10 USD credit, which is enough for two months of hosting. Also, the more friends you have sharing a solution like this, the more cost effective it becomes, since a single VPS can support many individuals watching BBC iPlayer at once.

Update 03/2015: I've now packaged the entire system using Docker images. This reduced the deployment to three easy steps. Head over to GitHub and follow the instructions there: https://github.com/ab77/iplayer-proxy. 

However if you would still like to create this system from scratch, please read on..

In essence, the following solution works by selectively proxying certain requests (*.bbc.co.uk and *.bbci.co.uk) via a server located in the UK, thus bypassing BBC iPlayer application level geo-restrictions. This solution, unlike VPN solutions, does not proxy the actual video/audio delivery, which uses different domain(s), so you should still get the benefit of decent bitrates from your local Content Distribution Network PoP.

Note, this won't work on devices which don't support the use of TLS Server Name Indication during SSL handshake (such as WD TV), you'll have to use a more complex system described in this guide or pay for Hola/Unblock-Us/Smart DNS Proxy/etc.



Prerequisites


You'll need the following:
  • A working Internet connection at home
  • A UK based VPS with root access and clean CentOS x86 6.x image
  • Basic knowledge in navigating a Linux console


Configuration


The configuration depends on the following two components:

  • DNS server
  • HTTP/SSL proxy

For the purpose of this demo, we assume your VPS's public Internet IP is 178.62.x.y and your host is called myhost.

We also assume your ISP has assigned 86.50.x.y to your home router. You can check what your IP is by going to http://www.whatismyip.com/.

Note, if you ISP assigns you a dynamic IP, you will need to update your configuration every time this changes. Better ask them for a static IP address.

When creating your configuration files, please change the place holder IPs to your actual IPs.


Base VPS Deployment


This step depends on the VPS provider you choose, but broadly consists of the following steps:
  • Create account with VPS provider
  • Deploy a CentOS x64 6.x image to a UK based data centre.

Once your VPS server is created, you will get a public IP address and SSH credentials to use to login. First, establish an SSH session to your server and install additional repositories:
[root@myhost ~]# yum -y install epel-release
[root@myhost ~]# yum -y update

Install certain pre-requisites to make your life a little easier:
[root@myhost ~]# yum install openssl vim git wget curl bind-utils telnet -y

Install development tools to enable you to build packages later:
[root@myhost ~]# yum groupinstall "Development Tools" -y
[root@myhost ~]# yum install rpmbuild autoconf automake curl libev libev-devel pcre pcre-devel perl udns-devel -y


IP Tables (firewall)


Since our VPS is on a public Internet, we are going to firewall it off from all the unpleasantness out there.

Add rules to allow DNS, HTTP and HTTPS inbound from your home's public IP:
[root@myhost ~]# iptables -A INPUT -s 86.50.x.y/32 -p udp -m udp --dport 53 -j ACCEPT
[root@myhost ~]# iptables -A INPUT -s 86.50.x.y/32 -p tcp -m tcp --dport 80 -j ACCEPT
[root@myhost ~]# iptables -A INPUT -s 86.50.x.y/32 -p tcp -m tcp --dport 443 -j ACCEPT
[root@myhost ~]# service iptables save
[root@myhost ~]# service iptables restart



BIND (DNS server)


BIND is going to be our DNS server. It will respond with our VPS IP to DNS requests for bbc.co.uk and bbci.co.uk domains.

Note, we are configuring the DSN server to run in a chroot jail and as a non-root user, to provide added security and minimise the potential effects of a security compromise.

Install BIND:
[root@myhost ~]# yum install bind -y


Edit your /etc/named.conf and make it look as follows:
options {
        listen-on port 53 { any; };
        listen-on-v6 port 53 { any; };
        directory       "/var/named";
        dump-file       "/var/named/data/cache_dump.db";
        statistics-file "/var/named/data/named_stats.txt";
        memstatistics-file "/var/named/data/named_mem_stats.txt";
        allow-recursion { none; };
        allow-transfer  { none; };
        recursion no;
        additional-from-auth no;
        additional-from-cache no;

        auth-nxdomain no;    # conform to RFC1035
        dnssec-enable yes;
        dnssec-validation yes;
        dnssec-lookaside auto;

        /* Path to ISC DLV key */
        bindkeys-file "/etc/named.iscdlv.key";
        managed-keys-directory "/var/named/dynamic";

        forwarders {
                8.8.8.8;
                8.8.4.4;
        };
};

acl "trusted" {
        any;
};

logging {
        channel default_debug {
                file "data/named.run";
                severity dynamic;
        };
};

zone "." IN {
        type hint;
        file "named.ca";
};

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";
include "/etc/named/zones.override";


Edit your /etc/named/db.override and make it look as follows:
$TTL  86400

@   IN  SOA ns1 root (
            YYYYMMDD01  ; serial
            604800      ; refresh 1w
            86400       ; retry 1d
            2419200     ; expiry 4w
            86400       ; minimum TTL 1d
            )

    IN  NS  ns1
ns1 IN  A   178.62.x.y
@   IN  A   178.62.x.y
*   IN  A   178.62.x.y

* Note, change the above IP address to your the public IP of your VPS and update the datestamp.
Edit your /etc/named/zones.override and make it look as follows:
zone "bbc.co.uk." {
        type master;
        file "/etc/named/db.override";
};

zone "bbci.co.uk." {
        type master;
        file "/etc/named/db.override";
};


Note, you may need to change the ownership of the new files you created from root to named:
[root@myhost ~]# chown named:named /etc/named/zones.override
[root@myhost ~]# chown named:named /etc/named/db.override


Install BIND (chroot package):
[root@myhost ~]# yum install bind-chroot -y


Finally, start BIND:
[root@myhost ~]# rndc-confgen -a -r /dev/urandom -t /var/named/chroot
[root@myhost ~]# chkconfig named on
[root@myhost ~]# service named start


From one of your home machines, run the following quick test:
$ nslookup www.bbc.co.uk 178.62.x.y
Server:         178.62.x.y
Address:        178.62.x.y#53

Name:   www.bbc.co.uk
Address: 178.62.x.y

$ nslookup www.bbci.co.uk 178.62.x.y
Server:         178.62.x.y
Address:        178.62.x.y#53

Name:   www.bbci.co.uk
Address: 178.62.x.y


You should get the IP address of your VPS in the DNS response.


SNI Proxy (HTTP/SSL proxy)


SNI Proxy is going to be our HTTP/SSL proxy, which supports for TLS SNI extensions.

Build and install SNI Proxy:
[root@myhost ~]# cd /opt
[root@myhost ~]# git clone https://github.com/dlundquist/sniproxy.git
[root@myhost ~]# cd ./sniproxy
[root@myhost ~]# ./autogen.sh && ./configure && make dist
[root@myhost ~]# rpmbuild --define "_sourcedir `pwd`" -ba --nodeps redhat/sniproxy.spec
[root@myhost ~]# yum install $(ls /root/rpmbuild/RPMS/x86_64/sniproxy-[0-9]*.rpm) -y


Note: if the RPM fails to install, check the exact name of the file in the /root/rpmbuild/RPMS/x86_64/ directory.


Also, please review the ./configure command output to make sure LIBUDNS or libudns  detected, if it hasn't, you will not be able to get SNI Proxy to work with the below configuration file. On a Red Hat/CentOS server, the library should be available from EPEL repository (yum install udns-devel), however for other distributions, you will need to build and install it manually.

Edit your sniproxy.conf and make it look as follows:
[root@myhost ~]# vim /etc/sniproxy.conf

user daemon
pidfile /var/tmp/sniproxy.pid

resolver {
        nameserver 127.0.0.1
}

error_log {
        filename /var/log/sniproxy_error.log
        priority notice
}

listener 178.62.x.y 80 {
        proto http
        access_log {
                filename /var/log/sniproxy_access.log
        }
}

listener 178.62.x.y 443 {
        proto tls
        access_log {
                filename /var/log/sniproxy_access.log
        }
}

table {
        .*\.bbc\.co\.uk *
        bbc\.co\.uk *
        .*\.bbci\.co\.uk *
        bbci\.co\.uk *
}

Finally, install the start-up script and start SNI Proxy:
[root@myhost ~]# cp ./redhat/sniproxy.init /etc/init.d/sniproxy
[root@myhost ~]# chmod +x /etc/init.d/sniproxy
[root@myhost ~]# chkconfig sniproxy on
[root@myhost ~]# service sniproxy start


Note, if the proxy complains about ports being in use, check if you got another web server already running and if so, disable it or move it to a different port.

From one of your home machines, run the following quick test:
$ telnet 178.62.x.y 80
Trying 178.62.x.y...
Connected to 178.62.x.y.
Escape character is '^]'.
^C
Connection closed by foreign host.

$ openssl s_client -servername www.bbc.co.uk -connect 178.62.x.y:443
CONNECTED(00000003)
depth=1 C = BE, O = GlobalSign nv-sa, CN = GlobalSign Organization Validation CA - SHA256 - G2
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
 0 s:/C=GB/ST=London/L=London/O=British Broadcasting Corporation/CN=*.bbc.co.uk
   i:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
 1 s:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
   i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
---
Server certificate
-----BEGIN CERTIFICATE-----
MII...


You should get a socket connection on port 80 and get an SSL handshake with the BBC.


Putting It All Together


The last step is to change the DNS server on the device you are planning to watch BBC iPlayer on. Set it to your VPS public IP (i.e. 178.62.x.y)

Fire up BBC iPlayer and you should be able to play back all the wonderful content they make available - enjoy!


Note, this won't work on devices which don't support the use of SNI during SSL handshake, you'll have no choice but to use a system described in this guide.


References


I've used the following reference material to prepare the solution described in this article. Many thanks to the respective authors.