How I bypassed my university's domain blocker to watch movies on hdviet.com

TL;DR: Clone my script from GitHub, run it with python2 server.py 8080, configure your browser to use localhost:8080 as HTTP and HTTPS proxy, profit.

Disclaimer: The sole reason I came up with this trick and documented it was to satisfy my curiosity. I don’t come to campus often anymore so it’s not like I’m going to spend 8 hours a day wasting the university’s internet bandwidth for “Two and a half men” anyway…

Another Note (last one, promise!): If you’re using Mac OS X or Windows, Proxifier will probably do the trick way better and without any hassle. If you’re using Linux or you simply want to learn more about this stuff, read on!

The problem#

This semester the RMIT-WPA wifi network no longer requires manual proxy configuration (probably because it makes Web Programming students miserable - they have to use Google App Engine), which is good news. Nevertheless, that annoying domain filter is still up and running, meaning we still can’t go to certain blacklisted websites. (mediafire, fshare, gamevn, vnsharing, etc.)

Hdviet’s case is a bit special: the domain hdviet.com itself is not blocked, but the domain of the actual server hosting its playlists & videos, v-01.vn-hd.com, is. A quick look at Firefox’s excellent Network inspector confirmed that:

If you request the file directly:

Going for the IP#

Naturally, I wanted to check if I could access the resource directly via the IP. An easy way to look up a domain’s IP is using ping.eu. Once you’ve got the IP, try replacing the domain with it in the failed request:

This time it works, which means only the domain is blocked, not the IP.

One thing worth noting about hdviet: The video is not served as 1 single file, it is instead chopped into multiple parts, which are loaded in order. Therefore, our first job is to automatically replace v-01.vn-hd.com with the IP in all of the requests.

Twisted proxy#

Since changing the request destination directly in the browser is probably difficult (I don’t think Google Chrome even allows that), we’ll use an HTTP(S) proxy. This is when Twisted comes in handy.

Twisted is a battery-included framework to build robust network applications. By “battery-included” they mean that most of the common functionalities have already been implemented so we can use them out of the box. For the purpose of this tutorial, we are only interested in its HTTP proxy library.

To install twisted, use pip:

sudo pip install twisted

Since the default implementation doesn’t support HTTPS, we’ll use a powered-up one I found on GitHub, written by Peter Ruibal. Let’s clone this thing:

git clone https://github.com/fmoo/twisted-connect-proxy.git

Now let’s try running the proxy server: cd into the cloned directory and run it with python2:

cd twisted-connect-proxy
python2 server.py 8080

Then configure your browser to use localhost:8080 as the proxy. For Firefox it’s easy:

You should now be able to surf the web through the running proxy. But hey, you still can’t visit any blocked site! Of course you can’t, since we haven’t replaced the domains with IPs. Let’s do that.

Domain to IP#

Open server.py, look for this part:

class ConnectProxyRequest(ProxyRequest):
    """HTTP ProxyRequest handler (factory) that supports CONNECT"""
    connectedProtocol = None

    def process(self):
        if self.method == 'CONNECT':
            self.processConnectRequest()
        else:
            ProxyRequest.process(self)

The process() method is in charge of forwarding whatever request the proxy receives to the actual target server. Let’s intercept it with our own redirect() function:

redirects = {
    'v-01.vn-hd.com': '125.212.216.93',  # video
    's.vn-hd.com': '210.211.120.146',  # sub
}

def redirect(req):
    for domain, ip in redirects.items():
        if req.path.find(domain) != -1:  # check if we're requesting a blocked domain
            req.uri = req.uri.replace(domain, ip, 1)
            req.path = req.path.replace(domain, ip, 1)
            req.requestHeaders.setRawHeaders('host', [ip])  # replace "Host" header too
            return

class ConnectProxyRequest(ProxyRequest):
    """HTTP ProxyRequest handler (factory) that supports CONNECT"""
    connectedProtocol = None

    def process(self):
        redirect(self)  # intercept request processing
        if self.method == 'CONNECT':
            self.processConnectRequest()
    # the rest of the file ...

In the snippet above, we defined a dictionary redirects that stores the blocked domains that we need to replace. Note that I added s.vn-hd.com as well, which is the host that stores subtitles. In our actual redirect() function, we check if the request being processed is pointing to any of the blocked domains defined earlier, then replace domain with its corresponding IP if there is a match:

req.uri = req.uri.replace(domain, ip, 1)
req.path = req.path.replace(domain, ip, 1)
req.requestHeaders.setRawHeaders('host', [ip])

Note that the 3rd line also changes the “Host” HTTP header. Yes, our beloved people from IT Services do inspect HTTP headers to block stuff too. This line will introduce another problem that I will explain later in this post.

Now restart our proxy server and check the link again. It should work. You can now watch stuff, but you’ll notice that English subtitles are not shown even if you turn them on:

If you open the browser’s network inspector, reload the page and try to enable English subtitles again, you’ll see the problem:

The link in question is:

http://s.vn-hd.com/store6/21042013/Two_and_a_Half_Men_S02/E001/Two_and_a_Half_Men_S02_E001_ENG.srt

Since s.vn-hd.com is in our blocked domain dictionary (redirects), the proxy server will request this:

http://210.211.120.146/store6/21042013/Two_and_a_Half_Men_S02/E001/Two_and_a_Half_Men_S02_E001_ENG.srt

If you try to open it directly in a browser (that isn’t using our proxy server), you’ll get a 404 too. Why is that? This is because the Host header is also changed to 210.211.120.146 instead of the original domain s.vn-hd.com. Normally a single web server can be serving multiple domains at a time, and when we send an HTTP request, we need to specify Host: <domain> for the server to know which domain we want to get the resource from. When the Host header is simply the IP, the server may get confused and therefore cannot serve the correct resource. As for v-01.vn-hd.com, we got lucky in that case.

On the other hand, if we keep Host: s.vn-hd.com as-is, RMIT will be able to block our request. This leads to our final trick:

Google App Engine to the rescue!#

Because a subtitle file is just plain text, its size is negligible. We can set up an external website that receives our original request, fetches the requested file on hdviet’s server and returns the requested file’s content back to us. I have already set up a proof-of-concept Google App Engine website at hdviet-proxy.appspot.com. It works like this:

Now we need to edit our server code to redirect any s.vn-hd.com request to hdviet-proxy.appspot.com/?url=original_url.

import urllib

sub_server = 's.vn-hd.com'
remote_server = 'hdviet-proxy.appspot.com'
redirects = {
    'v-01.vn-hd.com': '125.212.216.93',  # video
}

def redirect(req):
    for domain, ip in redirects.items():
        if req.path.find(domain) != -1:
            req.uri = req.uri.replace(domain, ip, 1)
            req.path = req.path.replace(domain, ip, 1)
            req.requestHeaders.setRawHeaders('host', [ip])
            return
        elif req.path.find(sub_server) != -1:
            proxied_url = 'http://%s/?%s' % (remote_server,
                                                urllib.urlencode({'url': req.uri}))
            req.uri = proxied_url
            req.path = req.path.replace(sub_server, remote_server)
            req.requestHeaders.setRawHeaders('host', [remote_server])
            return

You can view my finished script on github and clone it to use right away.

If you want to set up your own website instead of using mine, it’s really simple. Just use the new site template provided with GAE SDK and edit main.py like so:

import webapp2
from google.appengine.api import urlfetch

class MainHandler(webapp2.RequestHandler):
    def get(self):
        url = self.request.get('url')
        resp = urlfetch.fetch(url).content
        self.response.write(resp)

app = webapp2.WSGIApplication([
    ('/', MainHandler)
], debug=True)

Remember to change the remote_server variable in server.py to match your appspot link.

Restart the server script, now when the browser requests for this:

http://s.vn-hd.com/store6/21042013/Two_and_a_Half_Men_S02/E001/Two_and_a_Half_Men_S02_E001_ENG.srt

server.py will redirect to this:

http://hdviet-proxy.appspot.com/?url=http%3A%2F%2Fs.vn-hd.com%2Fstore6%2F21042013%2FTwo_and_a_Half_Men_S02%2FE001%2FTwo_and_a_Half_Men_S02_E001_ENG.srt

And the appspot site will get the original url, fetch its content, and give it right back to us:

You should now be able to watch movies with subtitles. Congratulations!