For the last 3 years I have been using djbdns on SmartOS and it has all been working great. Recently however, I started looking into DNSSEC and DNSCrypt, which ended up leading me to the OpenNIC Project.

I decided to change my home DNS server setup to forward OpenNIC DNS servers over an encrypted channel as opposed to using OpenDNS like I did with djbdns.

To set this up, I have a zone with dnsmasq and dnscrypt-proxy running

  • dnsmasq - listens globally on port 53 for incoming DNS requests, answers local domain DNS requests for my network, and forwards the rest to dnscrypt-proxy
  • dnscrypt-proxy - listens locally on port 5300 for incoming DNS requests from dnsmasq and forwards them securely to an OpenNIC DNS server

Install

To start, install dnsmasq with the following command:

pkgin in dnsmasq

Installing dnscrypt-proxy requires a little bit more work as it is currently not in pkgsrc. To install it, we need to pull in some dependencies.

pkgin in go git

Now we can build dnscrypt-proxy

mkdir -p ~/dnscrypt-proxy
cd ~/dnscrypt-proxy &&
    wget https://github.com/jedisct1/dnscrypt-proxy/archive/2.0.16.tar.gz &&
    tar xf 2.0.16.tar.gz &&
    cd dnscrypt-proxy-2.0.16/dnscrypt-proxy &&
    go get
    go build

We can verify it built by running it with -version

$ ./dnscrypt-proxy -version
2.0.16

And finally we can install it with

cp ./dnscrypt-proxy /opt/local/bin

Configure dnscrypt-proxy

Use the following config to configure dnscrypt-proxy to work with OpenNIC DNS servers. This server will listen on 127.0.0.1:5300 for plaintext requests forwarded from dnsmasq which we'll configure next.

Save this configuration file to /opt/local/etc/dnscrypt.conf

listen_addresses = ['127.0.0.1:5300']
ipv4_servers = true
dnscrypt_servers = true
doh_servers = true
require_dnssec = true
require_nolog = true
require_nofilter = true
timeout = 2500
keepalive = 30
cert_refresh_delay = 240
fallback_resolver = '9.9.9.9:53'
ignore_system_dns = true

cache = true
cache_size = 512
cache_min_ttl = 600
cache_max_ttl = 86400
cache_neg_min_ttl = 60
cache_neg_max_ttl = 600

[sources]
    [sources.'opennic']
    url = 'http://download.dnscrypt.info/resolvers-list/v2/opennic.md'
    minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
    cache_file = '/var/tmp/opennic.md'

# log queries
#[query_log]
#    format = 'tsv'
#    file = '/dev/stdout'

Note that I personally don't log any DNS requests on my home network for privacy reasons, but I've left the configuration lines there commented-out as turning on logging can be valuable for debugging purposes when setting up these services.

Because dnscrypt-proxy wasn't in pkgsrc we will have to create the SMF service ourselves.

~/dnscrypt-proxy.xml

<?xml version='1.0'?>
<!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
<service_bundle type='manifest' name='export'>
  <service name='application/dnscrypt-proxy' type='service' version='0'>
    <create_default_instance enabled='true'/>
    <dependency name='dep0' grouping='require_all' restart_on='error' type='service'>
      <service_fmri value='svc:/milestone/multi-user:default'/>
    </dependency>
    <exec_method name='start' type='method' exec='dnscrypt-proxy -config /opt/local/etc/dnscrypt.conf &amp;' timeout_seconds='10'>
      <method_context working_directory='/var/empty'>
        <method_credential user='nobody' group='nobody'/>
        <method_environment>
          <envvar name='PATH' value='/opt/local/sbin:/opt/local/bin:/opt/custom/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'/>
        </method_environment>
      </method_context>
    </exec_method>
    <exec_method name='stop' type='method' exec=':kill' timeout_seconds='30'/>
    <template>
      <common_name>
        <loctext xml:lang='C'>dnscrypt-proxy</loctext>
      </common_name>
    </template>
  </service>
</service_bundle>

And then run the following command to import the service and start it.

svccfg import ~/dnscrypt-proxy.xml

You can verify it's running with:

$ svcs -p dnscrypt-proxy
STATE          STIME    FMRI
online         23:02:53 svc:/application/dnscrypt-proxy:default

Or check the logs with:

$ tail "$(svcs -L dnscrypt-proxy)"
[ Jul  7 03:02:53 Executing start method ("dnscrypt-proxy -config /opt/local/etc/dnscrypt.conf &"). ]
[ Jul  7 03:02:53 Method "start" exited with status 0. ]
[2018-07-07 03:02:53] [NOTICE] Source [/var/tmp/opennic.md] loaded
[2018-07-07 03:02:53] [NOTICE] dnscrypt-proxy 2.0.16
[2018-07-07 03:02:53] [NOTICE] Now listening to 127.0.0.1:5300 [UDP]
[2018-07-07 03:02:53] [NOTICE] Now listening to 127.0.0.1:5300 [TCP]
[2018-07-07 03:02:53] [NOTICE] [opennic-onic] OK (crypto v1) - rtt: 32ms
[2018-07-07 03:02:55] [NOTICE] [publicarray-au] TIMEOUT
[2018-07-07 03:02:55] [NOTICE] Server with the lowest initial latency: opennic-onic (rtt: 32ms)
[2018-07-07 03:02:55] [NOTICE] dnscrypt-proxy is ready - live servers: 1

And finally you can query the server directly with:

$ dig @127.0.0.1 -p 5300 +short daveeddy.com A
165.225.131.147

Configure dnsmasq

Now that dnscrypt-proxy is setup to securely forward requests to OpenNIC DNS servers, we can configure dnsmasq to answer local requests and forward requests to dnscrypt-proxy.

Save this configuration file to /opt/local/etc/dnsmasq.conf

port=53
listen-address=0.0.0.0
bind-interfaces

domain-needed
proxy-dnssec
filterwin2k
no-resolv
no-poll
no-hosts
neg-ttl=3600

# Rapture hosts
domain=rapture.com
local=/rapture.com/

# A records
address=/host1.rapture.com/10.0.1.1
address=/host2.rapture.com/10.0.1.2

# PTR records
ptr-record=1.1.0.10.in-addr.arpa,"host1.rapture.com"
ptr-record=2.1.0.10.in-addr.arpa,"host2.rapture.com"

# Forward everything else to dnscrypt
server=127.0.0.1#5300

# log queries to stderr
#log-queries
#log-facility=-

rapture.com is my personal home domain - swap this out for your own personal domain. The address lines will become local A records and the ptr-record lines will become local PTR records. Again, I personally don't log any queries on my network but the config lines are there commented-out to help with debugging when setting up the service initially.

Start the service with

svcadm enable dnsmasq

You can verify it's running with:

$ svcs -p dnsmasq
STATE          STIME    FMRI
online         Jul_05   svc:/pkgsrc/dnsmasq:default
               Jul_05      39838 dnsmasq

To check that this is working, we can run the same command from configuring dnscrypt-proxy, but this time using port 53 instead of 5300.

$ dig @127.0.0.1 -p 53 +short daveeddy.com A
165.225.131.147
$ dig @127.0.0.1 -p 53 +short host1.rapture.com A
10.0.1.1
$ dig @127.0.0.1 -p 53 +short host2.rapture.com A
10.0.1.2

Conclusion

With these two services in place, any server on the network can set their DNS servers to this zone and get local records as well as public records over an encrypted channel. I have 2 zones (on 2 different physical machines) on my network running these DNS services, and I give out both of their IP addresses as part of DHCP. If you want to test out these servers manually on a different machine you'd have to edit /etc/resolv.conf to look like:

search rapture.com
domain rapture.com
nameserver 10.0.1.2
nameserver 10.0.1.3

Where 10.0.1.2 and 10.0.1.3 are the IP addresses of the DNS zones on the network. With this config in place your local machine will use these zones as their default resolvers.

Lookup local addresses

$ nslookup host1.rapture.com
Server:     10.0.1.2
Address:    10.0.1.2#53

Name:   host1.rapture.com
Address: 10.0.1.2

$ nslookup 10.0.1.2
2.1.0.10.in-addr.arpa   name = host1.rapture.com.

Lookup a bogus local address

$ nslookup foo.rapture.com
Server:     10.0.1.2
Address:    10.0.1.2#53

** server can't find foo.rapture.com: NXDOMAIN

Lookup a public address

$ nslookup daveeddy.com
Server:     10.0.1.2
Address:    10.0.1.2#53

Non-authoritative answer:
Name:   daveeddy.com
Address: 165.225.131.147

Lookup an OpenNIC specific address

$ nslookup grep.geek
Server:     10.0.1.2
Address:    10.0.1.2#53

Name:   grep.geek
Address: 161.97.219.84
Name:   grep.geek
Address: 2001:470:4212:1::254