Transparent DNS Proxy

I found this Github project called Unblock-Youku a few months ago. Besides obvious usefulness of proxying via Chinese server (for watching Chinese TV shows on the web). The cool thing that it does is that it allows you to use its DNS server, which would also act as a HTTP transparent proxy, so all you HTTP requests are automatically proxied, without setting up a proxy.

The code for the DNS server implementation is here. There's no Dockerfile for it yet, let's create one.

We will start by using a Node base image, let's use the latest one:

FROM node:9.8

RUN npm install -g rapydscript http-proxy optimist

# ADD should be put as behind as possible, bc. it always rebuild here
ADD . /home
WORKDIR /home
# RUN pipenv --python 3.5
RUN make
CMD nodejs droxy.js --help

Unfortunately, I tried to tweak it to make it work but it has no luck. The last error is TypeError: Cannot read property 'constructor' of undefined.

Let's pivot.


After doing some research online. I found out that this technology is called transarent DNS proxy. It is used by many ISPs for their DNS services.

There are a few github projects related to TDNSP. For example, this one. netflix-proxy uses two softwares under the hood: dnsmasq and sniproxy. The first is a small DNS server and the second is an HTTP(S) proxy based on hostname.

Let's give them a try in a Docker container on the server: docker run -it --rm -P ubuntu:xenial /bin/bash. Follow the debian instruction on SNI proxy github page. No problem!

Glance over the config file /etc/sniproxy.conf shows that it listens on port 80 for HTTP proxy and 443 for HTTPS. Then there is the table directive, which is basically a mapping from hostname to ip address.

# if no table specified the default 'default' table is defined
table {
    # if no port is specified default HTTP (80) and HTTPS (443) ports are
    # assumed based on the protocol of the listen block using this table
    example.com 192.0.2.10
    example.net 192.0.2.20
}

However, it takes me a while to figure out the syntax for forward proxying all requests, with the help of this blog: .* *. Now it should forwards all http and https requests without problem. fallback directive is effective when http request has no Host field, let's ignore this senario for now and assume all http requests are made with hostname instead of ip. But to resolve it would need something like this:

Using Nginx as forward proxy:

        location / {
                # resolver is the dns address used for resolving http requests without Host header.
                resolver 8.8.8.8;
                proxy_pass http://$http_host$uri$is_args$args;
        }

Let's test it works by running sniproxy with sniproxy, tailing the sniproxy log with tail -f /var/log/sniproxy/*, and edit the hosts file to add 127.0.0.1 ifconfig.co. Then curl it:

$ curl https://ifconfig.co
$ curl http://ifconfig.co

Both works! The hit shows up in the log. Now we use dnsmasq as a dns server to resolve some domains to our proxy server.

Oh wait, I just realized that I don't need to use sniproxy as a catch-all forward proxy, because I can just set the domain list in dnsmasq. Oh well...

Set up the dnsmasq following the blog above.

domain-needed
bogus-priv
no-resolv
user=root
group=root
conf-dir=/etc/dnsmasq.d
server=8.8.4.4
server=8.8.8.8
address=/ifconfig.co/138.197.172.8

Run docker run --rm -p 53:53 -p 80:80 -p 443:443 dohsimpson/transpare-dns-proxy on the server. But unfortunately, dnsmasq is not taking queries from outside world, even though it's listening on *:53. It's been too long (> 5hrs). I give up on this project...

Lesson learned

  • Every project needs at least 5 hours. This is too much. I'm getting headaches just for working so many hours. From tomorrow, we will do 1 project per 2 days.

  • It is much easier to work with technology I am already familiar with than softwares not used.