DNSCrypt on the EdgeRouter Lite

Introduction:

ISP: Comcast
Modem: Motorola SB6141
Router: Ubiquiti EdgeMax Router Lite
EdgeOS System Image: v1.9.7+hotfix.3
WAN Interface: eth0
LAN Interface: eth1 (172.16.0.1)

For this guide, I use the IP address 172.16.0.1 as the one I want dnscrypt-proxy bind to. So far, I have not found a way to configure dnscrypt-proxy to natively bind to only eth1 and eth2.

Installation:

  1. ssh into your EdgeRouter Lite and sudo to root
  2. mkdir ~/dnscrypt-proxy && cd ~/dnscrypt-proxy
  3. curl --remote-name https://download.dnscrypt.org/dnscrypt-proxy/binaries/linux/dnscrypt-proxy-mips-linux-musl.tar.gz
  4. curl --remote-name https://download.dnscrypt.org/dnscrypt-proxy/binaries/linux/dnscrypt-proxy-mips-linux-musl.tar.gz.minisig
  5. TODO: Figure out how to get minisign working on the ERL, https://jedisct1.github.io/minisign/
  6. tar xvfz dnscrypt-proxy-mips-linux-musl.tar.gz
  7. cd dnscrypt-proxy
  8. ./installer.sh
  9. cd ~ && rm -rf ~/dnscrypt-proxy

Configuration:

First off, run /opt/dnscrypt-proxy/mips-linux-musl/bin/dnscrypt-update-resolvers.sh to update your local cache of DNSCrypt Resolvers.

Create the dnscrypt-proxy system user which will run the actual proxy:

useradd --comment 'dnscrypt-proxy system user' --no-create-home --system --user-group --shell /usr/sbin/nologin --home-dir /opt/dnscrypt-proxy dnscrypt-proxy

The following config will start the dnscrypt-proxy on port 153 for us to verify everything works.
/opt/dnscrypt-proxy/dnscrypt-proxy.conf:

ResolverName random
ResolversList /opt/dnscrypt-proxy/mips-linux-musl/.sw/dnscrypt-proxy/share/dnscrypt-proxy/dnscrypt-resolvers.csv
Daemonize no
PidFile /var/run/dnscrypt-proxy.pid
User dnscrypt-proxy
LocalAddress 172.16.0.1:153
LocalCache on
EphemeralKeys off
MaxActiveRequests 250
EDNSPayloadSize 1252
IgnoreTimestamps no
TCPOnly no
QueryLogFile /var/log/dnscrypt-proxy/queries.log
LogFile /var/log/dnscrypt-proxy/proxy.log
LogLevel 6
# Syslog       off
# SyslogPrefix dnscrypt
BlockIPv6 yes

Note that this is going to run dnscrypt-proxy on port 153, if you've configured strict firewall rules on your ERL, temporarily add a new rule to allow access to 153 from your test client (or your entire internal network).

chown -R dnscrypt-proxy:dnscrypt-proxy /opt/dnscrypt-proxy/
/opt/dnscrypt-proxy/mips-linux-musl/bin/dnscrypt-proxy /opt/dnscrypt-proxy/dnscrypt-proxy.conf

Now, on a client host that can hit the ERL on port 153, issue the following:

dig techsmix.net @172.16.0.1 -p 153

You should see a completed response:

❯ dig techsmix.net @172.16.0.1 -p 153

; <<>> DiG 9.8.3-P1 <<>> techsmix.net @172.16.0.1 -p 153
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55928
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1252
;; QUESTION SECTION:
;techsmix.net.			IN	A

;; ANSWER SECTION:
techsmix.net.		300	IN	A	192.241.192.75

;; Query time: 318 msec
;; SERVER: 172.16.0.1#153(172.16.0.1)
;; WHEN: Sun Sep 17 12:19:55 2017
;; MSG SIZE  rcvd: 57

And in /var/log/dnscrypt-queries.log see a line like:

Sun Sep 17 16:36:16 2017	172.16.0.65	techsmix.net	A

Now, that the test is working let's reconfigure our ERL to only expose dnscrypt-proxy as the internal DNS server.

Edit /opt/dnscrypt-proxy/dnscrypt-proxy.conf so that LocalAddress is 172.16.0.1:53 and Daemonize is yes.

At this point, you should see that the ERL has a dnsmasq process running if you've enabled the DNS service:

root@main-router:~/dnscrypt-proxy# netstat -tulnp | grep 53
tcp        0      0 0.0.0.0:53              0.0.0.0:*               LISTEN      1489/dnsmasq
tcp        0      0 172.16.0.1:153          0.0.0.0:*               LISTEN      12584/dnscrypt-prox
tcp6       0      0 :::53                   :::*                    LISTEN      1489/dnsmasq
tcp6       0      0 :::49153                :::*                    LISTEN      1497/upnpd
udp        0      0 0.0.0.0:53              0.0.0.0:*                           1489/dnsmasq
udp        0      0 172.16.0.1:153          0.0.0.0:*                           12584/dnscrypt-prox
udp6       0      0 :::53                   :::*                                1489/dnsmasq

Remove all DNS configuration via the ERL's WebUI: https://address.to.erl.here/#Services/DNS

Verify that there's no processes holding port 53 open:

root@main-router:~/dnscrypt-proxy# netstat -tulnp | grep 53
tcp        0      0 172.16.0.1:153          0.0.0.0:*               LISTEN      12584/dnscrypt-prox
tcp6       0      0 :::49153                :::*                    LISTEN      1497/upnpd
udp        0      0 172.16.0.1:153          0.0.0.0:*                           12584/dnscrypt-prox

Kill the old test process and start the real one

ps aux | grep dnscrypt && kill $(cat /var/run/dnscrypt-proxy.pid) &&  /opt/dnscrypt-proxy/mips-linux-musl/bin/dnscrypt-proxy /opt/dnscrypt-proxy/dnscrypt-proxy.conf

And now, verify that dnscrypt-proxy has taken over port 53:

root@main-router:~/dnscrypt-proxy# netstat -tulnp | grep 53
tcp        0      0 172.16.0.1:53           0.0.0.0:*               LISTEN      13493/dnscrypt-prox
tcp6       0      0 :::49153                :::*                    LISTEN      1497/upnpd
udp        0      0 172.16.0.1:53           0.0.0.0:*                           13493/dnscrypt-prox

Assuming your clients on your network are already using your router for DNS, you should immediatly start seeing the queries in /var/log/dnscrypt-queries.log. If that's not the case, navigate to https://your.erl.address.here/#Services/DHCP/Server and configure the "DNS 1" option to be your ERL's internal IP (in this example, it would be 172.16.0.1).

It's reccomended that you add the following cronjob to keep the local list of DNSCrypt servers updated:

0 * * * * /opt/dnscrypt-proxy/mips-linux-musl/bin/dnscrypt-update-resolvers.sh > /dev/null 2>&1

Finally, to ensure that dnscrypt-proxy starts automatically on your ERL, you'll need to create and enable the SysVinit script. I've used please-run to generate the following init script

/etc/init.d/dnscrypt-proxy: https://gist.github.com/jaredledvina/f76d72404df3fb5e3fe9ba978755ac7f

Now, you'll want to make sure the new init script works so, let's kill the manually run daemon and start it cleanly with this init script:

kill $(cat /var/run/dnscrypt-proxy.pid)
service dnscrypt-proxy start

Again, you should see the dnscrypt-proxy service running and listening on the configured interface and port.

Finally, I use chkconfig to managing the services to run at boot (install it with apt-get install chkconfig):

root@main-router:~# chkconfig -l dnscrypt-proxy
dnscrypt-proxy            0:off  1:off  2:off  3:off  4:off  5:off  6:off

Enable it to start at boot:

root@main-router:~# chkconfig --add dnscrypt-proxy
dnscrypt-proxy            0:off  1:off  2:on   3:on   4:on   5:on   6:off

Optionally, if you'd like some simple Ad blocking via dnscrypt-proxy perform the following:

cd /opt/dnscrypt-proxy && curl --remote-name https://download.dnscrypt.org/blacklists/domains/mybase.txt'

Add this line to /opt/dnscrypt-proxy/dnscrypt-proxy.conf:

'BlackList domains:/opt/dnscrypt-proxy/mybase.txt logfile:/var/log/dnscrypt-proxy/blocked.log

Finally, restart dnscrypt-proxy and you should start seeing the deny actions logged to /var/log/dnscrypt-proxy/blocked.log.
/var/log/dnscrypt-proxy/blocked.log:

root@main-router:/opt/dnscrypt-proxy# tail /var/log/dnscrypt-blocked.log
Sun Sep 17 19:01:55 2017	172.16.0.65	msec.xp1.ru4.com	*ru4.com
4Sun Sep 17 19:01:55 2017	172.16.0.65	msec.xp1.ru4.com	*ru4.com
5Sun Sep 17 19:01:55 2017	172.16.0.65	msec.xp1.ru4.com	*ru4.com
6Sun Sep 17 19:01:55 2017	172.16.0.65	sync-tm.everesttech.net	*everesttech.net
7Sun Sep 17 19:01:55 2017	172.16.0.65	sync-tm.everesttech.net	*everesttech.net
8Sun Sep 17 19:01:55 2017	172.16.0.65	aa.agkn.com	*agkn.com
9Sun Sep 17 19:01:55 2017	172.16.0.65	ag.innovid.com	*innovid.com
10Sun Sep 17 19:01:55 2017	172.16.0.65	ag.innovid.com	*innovid.com

You can add a simple cronjob like the following to keep that file always up to date:

0 1 * * * cd /opt/dnscrypt-proxy/ && /usr/bin/curl -s --remote-name https://download.dnscrypt.org/blacklists/domains/mybase.txt > /dev/null 2>&1 && service dnscrypt-proxy restart > /dev/null 2>&1

There you have it! All of your local network DNS queries are automatically proxied securely using DNSCrypt. Enjoy!

Some followups to this article that I will hopefully find time to track down and implement:

  • Build ERL .deb packages that include a proper SysVinit script and manages all of the install
  • Configure dnsproxy-crypt to bind to multiple internal only interfaces
  • Locking down DNS traffic such that it must use dnscrypt-proxy to leave the internal network
  • Deploying something like https://github.com/Phillipmartin/gopassivedns to monitor all DNS traffic for anything that doesn't use dnscrypt-proxy.
  • Setup logrotate for the various dnscrypt-proxy log files.

/etc/logrotate.d/dnscrypt-proxy:

/var/log/dnscrypt-proxy/blocked.log {
    daily
    missingok
    notifempty
    compress
    copytruncate
    size 1M
    rotate 2
    postrotate
        /usr/sbin/service dnscrypt-proxy restart
    endscript
}
/var/log/dnscrypt-proxy/proxy.log {
    daily
    missingok
    notifempty
    compress
    copytruncate
    size 1M
    rotate 2
    postrotate
        /usr/sbin/service dnscrypt-proxy restart
    endscript
}
/var/log/dnscrypt-proxy/queries.log {
    daily
    missingok
    notifempty
    compress
    copytruncate
    size 1M
    rotate 2
    postrotate
        /usr/sbin/service dnscrypt-proxy restart
    endscript
}