OpenWRT encrypted DNS lookup using multiple DNSCrypt servers

Want to setup OpenWRT to query specific DNSCrypt server depending on domain name to resolve? Read on.

Overview

It is assumed that you know what DNSCrypt is. Long story short, it is a protocol for encrypting DNS lookup traffic.

I installed dnscrypt-proxy 1.4.3-1 from OpenWRT package repository. The proxy serves to receive incoming DNS requests then it makes an outbound request to DNSCrypt servers. It could load one proxy just fine but it could not initiate multiple instances until I applied a patch (more details below).

You may ask why do I need multiple when a single Anycast DNS server can give you the closest server? Sometimes the closest server does not have the content that you want; one way of geo-blocking by content providers. Solution is to use a UK DNS server to lookup a UK address, a US DNS server for US address, etc.

How to setup

Install dnscrypt-proxy if you have not already. As mentioned earlier, the version I have at time of writing is 1.4.3-1.

Stop the service:

/etc/init.d/dnscrypt-proxy stop

This version needs to be patched to support multiple instances. Patch the file at /etc/init.d/dnscrypt-proxy with the following, copied from here:

#!/bin/sh /etc/rc.common
# dnscrypt-proxy init-script utilizing procd to support multiple instances at once

START=50
USE_PROCD=1

dnscrypt_instance() {
    config_get address         "$1" 'address'
    config_get port            "$1" 'port'
    config_get resolver        "$1" 'resolver'
    config_get resolvers_list  "$1" 'resolvers_list'

    procd_open_instance
    procd_set_param command "/usr/sbin/dnscrypt-proxy"
    procd_append_param command -a "${address}:${port}"
    procd_append_param command -u nobody
    procd_append_param command -L "${resolvers_list:-'/usr/share/dnscrypt-proxy/dnscrypt-resolvers.csv'}"
    procd_append_param command -R "${resolver:-'opendns'}"
    procd_close_instance
}

start_service () {
    config_load dnscrypt-proxy
    config_foreach dnscrypt_instance dnscrypt-proxy
}

service_triggers() {
    procd_add_reload_trigger "dnscrypt-proxy"
}

Overwrite /usr/share/dnscrypt-proxy/dnscrypt-resolvers.csv with a newer version from GitHub. This is to give us more DNSCrypt servers to choose from.

Next, edit /etc/config/dnscrypt-proxy to configure the instances. Below is a sample with 2 instances; one with Anycast support listening on port 5353, another without located in the UK listening on port 5354:

config dnscrypt-proxy
    option address '127.0.0.1'
    option port '5353'
    option resolver 'cisco'
    option resolvers_list '/usr/share/dnscrypt-proxy/dnscrypt-resolvers.csv'

config dnscrypt-proxy
    option address '127.0.0.1'
    option port '5354'
    option resolver 'fvz-rec-gb-lon-01'
    option resolvers_list '/usr/share/dnscrypt-proxy/dnscrypt-resolvers.csv'

Start DNSCrypt:

/etc/init.d/dnscrypt-proxy start

Testing

Install bind-tools so that we can use dig command to lookup DNS information:

opkg update
opkg install bind-tools

Lookup DNS record of www.google.com using:

dig 127.0.0.1 -p [port] www.google.com

Substitute [port] with whatever that you have defined in /etc/config/dnscrypt-proxy. Below is an example of the output from UK DNS server:

...
;; QUESTION SECTION:
;www.google.com.                        IN      A

;; ANSWER SECTION:
www.google.com.         144     IN      A       216.58.213.164
...

As shown above, the IP address of www.google.com is 216.58.213.164 whereas if I lookup against an Anycast DNS server, the response I get is  172.217.24.196, somewhere much closer to me than the former.

Use specific DNS server to lookup one or more host names

Edit /etc/config/dhcp. The gist is list server ... lines.

config dnsmasq
    ...
    option noresolv '1'
    list server '/[UK server host name]/127.0.0.1#5354'
    list server '127.0.0.1#5353'
    list server '/pool.ntp.org/8.8.8.8'

noresolv '1' is to prevent using any upstream DNS server other than those specified in this file. Substitute [UK server host name] with a real one of course. You may add multiple lines. Just keep them above the list server '127.0.0.1#5353' line.

IMPORTANT: Note that DNSCrypt requires time to be synchronised. Time synchronisation is performed against a host name but your DNS server is unavailable to perform lookups. This presents a chicken and egg problem. To mitigate this problem, a bypass is required for pool.ntp.org (assuming this is the NTP server configured in your OpenWRT) such that the lookup will be performed by a public DNS server i.e. 8.8.8.8, a Google DNS server. Rules are parsed top-down so subsequent lookups of pool.ntp.org will not reach Google DNS.

On boot, in case DNSCrypt fails to start

This is very likely due to Internet connection not available yet at time of starting DNSCrypt. In such a case, the workaround is to wait for Internet connection to be available before restarting DNSCrypt. Add the following lines into /etc/rc.local:

# Wait until Internet connection is available
for i in {1..60}; do ping -c1 -W1 8.8.8.8 &> /dev/null && break; done

# Restart DNSCrypt proxy as it requires a successful time sync for its encryption to work
/etc/init.d/dnscrypt-proxy restart

I am using ping to test whether Internet connection is available. This is not the best option but I did not want to invest too much time on this. Others have suggested using sleep command. If you know a better way, please share that as a comment below.