2015-01-04

The DIY DNS Recursive Resolver: Your Queries Belong to You

A while ago there were numerous reports that Google Public DNS was having performance problems. I wasn't surprised because there is no web service with 100% uptime and even the major online players with countless servers and comprehensive failover strategies in place have to contend with the occasional Very Bad Thing happening at Exactly The Worst Possible Time. Perhaps this outage affected you. Shame on you, you ought to know better than to trust your DNS resolution to a third party, let alone one who's profit model is based on advanced analysis of your Internet usage patterns. If it's free, you have to ask yourself what the real price is that you're paying for it.

During the reported Google Public DNS outage, it occurred to me that most people don't really understand how the Domain Name System works, or how they can take advantage of it to make it work for them. Many folks don't want to be bothered with learning this stuff and would rather that Google do all the thinking for them.

This is not for them.

For the rest of us, there are easy ways to set up DNS resolution on our own home networks and on our laptops. What follows is a modest tutorial that aims to give a basic overview of how to set up a DNS recursive resolver that you can use to get away from having to rely on Google, or OpenDNS, or your ISP for name lookup services.

Why bother running a DNS server?

DNS is important. It's the mechanism used by computers to turn a domain name, like www.cnn.com, into an IP address, like 157.166.240.13. One of these things is easier to remember than the other. More importantly, the people who do the conversion from domain name to IP address control where your Internet traffic goes. Some ISPs and private networks, for whatever reason, can redirect your traffic wheresoever they choose by hijacking your DNS lookups: you see this often in airports and other public places where there are for-pay wireless Internet services. If you open up your browser and ask it to load "www.cnn.com", it will instead take you to the wifi provider's sign-up page to try to sell you their services. Your browser is not getting the right answers to the question you've asked because it's trusting a strange server that wants you to pay first before it hands out the right answers. In other cases, the network is openly hostile to the user and will redirect or block queries to domains that are deemed questionable or dangerous. For now, the IP for falundafa.org is 4.53.82.215.

So in order to use the Internet freely you need to trust a DNS server, just not necessarily someone else's DNS server. Setting up your own resolver is not as complicated as it may seem.

Why yet another howto on DNS?

This one is going to be a little bit different. There are plenty of tutorials out there on how to build a DNS server, but my preferred setup is currently not documented in any single place. I use a combination of DNS software written in 2001 and patches to improve its security and performance, none of which are kept in a central location for easy reference. What's more, the old era of starting services on most Unix and Linux operating systems has ended and there are now a plethora of new service management technologies that control when and how your system's services start. Appending a line to the end of /etc/inittab doesn't cut it any more. These are the steps learned through years of working with these packages, and picking up the tips and tricks that are never as well-documented as you'd like them when you're sitting in front of an unfamiliar distro wondering what they changed between versions v-dot-now and v-dot-last-time. OpenBSD changed their base service rc.d startup model in version 5.0, so I don't even know what's real and what's fantasy anymore.

Will DNSSEC help me?

DNSSEC can help you if and only if your concern is validating that any of your signed DNS responses are, in fact, signed DNS responses and if you're OK with trusting 1024-bit RSA keys. This can help prevent poisoning your cache, but it doesn't help you if you are interested in privacy or maintaining availability of DNS lookups. This tutorial doesn't cover DNSSEC, but you can still independently use DNSSEC after completing this tutorial. DNSSEC is complicated and error-prone, even for DNSSEC masters.

What do I need to get started?

You're going to want a Unix or Linux system. I assume that this is going to be a home server sort of device you keep on your home network, but you can easily adapt this to run on a laptop. In fact, the author of djbdns recommends a separate cache on every host. I'm sure this can be ported to OS X, and with the right precautions it can even be run on Windows through Cygwin with libsodium. This is all outside the scope of the tutorial though. Get a Unix or Linux system and ensure it has a C compiler and a working network configuration: (sudo apt-get update && sudo apt-get install build-essential gcc make is probably a good start). I assume most source code here will be kept in /usr/local/src. You may need to alter these paths if you keep your source in a different location.

  1. Get yourself a copy of NaCl, the Networking and Cryptography library.

    nacl-20110221.tar.bz2 has the following checksums:

    MD5 = 7efb5715561c3d10dafd3fa97b4f2d20

    SHA1 = 6007a6aee249f5a534ec53fddfc364601fba9629

    SHA256 = 4f277f89735c8b0b8a6bbd043b3efb3fa1cc68a9a5da6a076507d067fc3b3bf8

    SHA512 = 4c031ceffe6a28dc74b46ac003d485531f78de467c802df73c8b22ca53644dabb7d2e3080b7bdd6583f0d07ad76b6d95bc0ffdce319ca2f80ee041e6fe618656

    Fetch and compile it. This can take some time to compile, so I do this in a GNU screen or tmux session and keep working.

    wget -N http://hyperelliptic.org/nacl/nacl-20110221.tar.bz2
    openssl dgst -sha256 ./nacl-20110221.tar.bz2
    # SHA256(./nacl-20110221.tar.bz2)= 4f277f89735c8b0b8a6bbd043b3efb3fa1cc68a9a5da6a076507d067fc3b3bf8
    mkdir -m02755 ./src.new && mv ./src.new ./src
    bunzip2 < nacl-20110221.tar.bz2 | (cd ./src; tar -xf -)
    [ -d /usr/local/src ] || sudo mv ./src /usr/local/
    cd /usr/local/src/nacl-20110221
    time ./do
  2. While NaCl compiles, fetch more of the necessary components.

    daemontools-0.76.tar.gz has the following checksums:

    MD5 = 1871af2453d6e464034968a0fbcb2bfc

    SHA1 = 70a1be67e7dbe0192a887905846acc99ad5ce5b7

    SHA256 = a55535012b2be7a52dcd9eccabb9a198b13be50d0384143bd3b32b8710df4c1f

    SHA512 = e4a7938352b745a03ccc41acdddba1e6782f0ca245e5cae060de62ab6c5a23c841a994c30140298d274405a7f26d53ba7e84e5810a3d185b2c01e4c0feffe6c7

    Fetch it and extract it:

    wget -N http://cr.yp.to/daemontools/daemontools-0.76.tar.gz
    openssl dgst -sha256 ./daemontools-0.76.tar.gz
    # SHA256(./daemontools-0.76.tar.gz)= a55535012b2be7a52dcd9eccabb9a198b13be50d0384143bd3b32b8710df4c1f
    mkdir -m01755 ./package
    gzip -d < daemontools-0.76.tar.gz | (cd ./package; tar -xpf -)
    sudo mv ./package /

    On Linux systems, you have to work around their errno bug. This can be skipped on BSD:

    cd /package/admin/daemontools-0.76
    echo gcc -O2 -Wimplicit -Wunused -Wcomment -Wchar-subscripts -Wuninitialized -Wshadow -Wcast-qual -Wcast-align -Wwrite-strings -include /usr/include/errno.h > ./src/conf-cc
  3. Compile and install daemontools. I don't use the default ./package/install script directly anymore because I can't guarantee its modifications to /etc/inittab or /etc/rc.local will work as desired.

    cd /package/admin/daemontools-0.76
    ./package/compile
    sudo ./package/upgrade
    [ -d /service ] || sudo mkdir /service
    cd

    daemontools must be configured to run when the system starts. This is where the problem of which default service-starting mechanism your operating system enters the equation. These are some basic config files for popular init replacements. Your actual mileage may vary.

    For Linux systems running Upstart:

    echo description "daemontools" > ./daemontools.conf
    echo start on filesystem >> ./daemontools.conf
    echo exec /command/svscanboot >> ./daemontools.conf
    chmod 0644 ./daemontools.conf
    sudo cp -p daemontools.conf /etc/init/
    sudo service daemontools start

    For Linux systems running systemd:

    echo [Unit] > ./daemontools.service
    echo Description=daemontools >> ./daemontools.service
    echo After=local-fs.target >> ./daemontools.service
    echo [Service] >> ./daemontools.service
    echo ExecStart=/command/svscanboot >> ./daemontools.service
    echo [Install] >> ./daemontools.service
    echo WantedBy=multi-user.target >> ./daemontools.service
    chmod 0644 ./daemontools.service
    sudo cp -p ./daemontools.service /etc/systemd/system/
    sudo systemctl start daemontools
    sudo systemctl enable daemontools

    For OpenBSD systems v5.0 and up:

    echo #!/bin/sh > ./daemontools
    echo daemon="csh -cf '/command/svscanboot &'" >> ./daemontools
    echo . /etc/rc.d/rc.subr >> ./daemontools
    echo rc_cmd \$1 >> ./daemontools
    sudo cp ./daemontools /etc/rc.d/daemontools
    sudo chmod 0555 /etc/rc.d/daemontools

    Manually add " daemontools" to the "start_daemon" line of services in /etc/rc.

  4. Download djbdns-1.05.

    djbdns-1.05.tar.gz has the following checksums:

    MD5 = 3147c5cd56832aa3b41955c7a51cbeb2

    SHA1 = 2efdb3a039d0c548f40936aa9cb30829e0ce8c3d

    SHA256 = 3ccd826a02f3cde39be088e1fc6aed9fd57756b8f970de5dc99fcd2d92536b48

    SHA512 = 20f066402801d7bec183cb710a5bc51e41f1410024741e5803e26f68f2c13567e48eba793f233dfab903459c3335bc169e24b99d66a4c64e617e1f0779732fa9

    wget -N http://cr.yp.to/djbdns/djbdns-1.05.tar.gz
    openssl dgst -sha256 ./djbdns-1.05.tar.gz
    # SHA256(./djbdns-1.05.tar.gz)= 3ccd826a02f3cde39be088e1fc6aed9fd57756b8f970de5dc99fcd2d92536b48
    gzip -d < djbdns-1.05.tar.gz | (cd /usr/local/src; tar -xf -)
  5. Update the compiling and linking scripts. On Linux, this will contain the -include /usr/include/errno.h errno workaround. On BSD, the errno fix can be omitted but you still have to add the NaCl include and linker files. Be sure to change the value of ARCH to match your machine's architecture. Usually this is the output of uname -m but not always.

    cd /usr/local/src/djbdns-1.05
    ARCH=amd64
    echo gcc -O2 -Wimplicit -Wunused -Wcomment -Wchar-subscripts -Wuninitialized -Wshadow -Wcast-qual -Wcast-align -Wwrite-strings -include /usr/include/errno.h -I/usr/local/src/nacl-20110221/build/`hostname -s`/include/${ARCH} > /usr/local/src/djbdns-1.05/conf-cc
    echo gcc -s -L/usr/local/src/nacl-20110221/build/`hostname -s`/lib/${ARCH} > /usr/local/src/djbdns-1.05/conf-ld

    On systems that do not prefer gcc, replace these lines with the preferred C compiler for that system (possibly clang). I'm looking at you, FreeBSD.

  6. Patch djbdns-1.05 with Matthew Dempsky's DNSCurve patch.

    djbdns-dnscurve-20090602.patch has the following checksums:

    MD5 = 52d7d77015ee7ee6f90c2dc96a303f76

    SHA1 = 641a44435230991af26cfa93ed0b38f8ff5df409

    SHA256 = 7efc54bd1981d0eb920de02b97f9b152c57e6add8023c9b82566358dc9525bba

    SHA512 = 986155a0ac7b866e87ce51005ffc15057c2a2d63778a4071b176f0d618e06abca56b28d0391337ca7db181a972616cf84a2c65717b215056b84c371e20d9ec64

    wget -N http://shinobi.dempsky.org/~matthew/patches/djbdns-dnscurve-20090602.patch
    openssl dgst -sha256 ./djbdns-dnscurve-20090602.patch
    # SHA256(./djbdns-dnscurve-20090602.patch)= 7efc54bd1981d0eb920de02b97f9b152c57e6add8023c9b82566358dc9525bba
    mv djbdns-dnscurve-20090602.patch /usr/local/src/djbdns-1.05/
    cd /usr/local/src/djbdns-1.05
    patch -p1 < djbdns-dnscurve-20090602.patch
  7. Fix the AXFR bug in response_addname().

    --- a/response.c
    +++ b/response.c
    @@ -34,7 +34,7 @@ int response_addname(const char *d)
             uint16_pack_big(buf,49152 + name_ptr[i]);
             return response_addbytes(buf,2);
           }
    -    if (dlen <= 128)
    +    if ((dlen <= 128) && (response_len < 16384)) /* <URL:http://www.securityfocus.com/archive/1/501294/30/0/threaded> and <URL:http://marc.info/?l=djbdns&m=123613000920446&w=2> */
           if (name_num < NAMES) {
            byte_copy(name[name_num],dlen,d);
            name_ptr[name_num] = response_len;
  8. Wait for NaCl to finish compiling before you proceed.

  9. Compile djbdns-1.05 and install it.

    make
    sudo make setup check

  10. Create a new group and two users.

    sudo groupadd _dns
    sudo useradd -d /dev/null -s /usr/bin/false -g _dns _dnscache
    sudo useradd -d /dev/null -s /usr/bin/false -g _dns _dnslog

    Official documentation recommends "Gdnscache" and "Gdnslog". Use whatever works for you.

  11. The installation of dnscache is automated by the utility dnscache-conf. It requires a specific set of arguments.

    sudo dnscache-conf _dnscache _dnslog /etc/dnscache 127.0.0.1

    This creates a /etc/dnscache directory with the necessary startup files. You may need to edit some of the defaults:

    echo 5000000 > ./CACHESIZE
    echo 5500000 > ./DATALIMIT
    chmod 0600 ./CACHESIZE ./DATALIMIT
    sudo mv ./CACHESIZE ./DATALIMIT /etc/dnscache/env/

  12. Make a symlink from the dnscache directory to /service.

    sudo ln -s /etc/dnscache /service/dnscache
    

  13. Update /etc/resolv.conf and any resolv.conf-generating scripts. This is seldom as simple as editing /etc/resolv.conf anymore. In Ubuntu, this usually works.

    cd /etc/dhcp/dhclient-enter-hooks.d
    sudo cp -p resolvconf resolvconf.orig
    sudo cp -p resolvconf resolvconf.new
    sudo vi resolvconf.new
    

    Make the following change:

    --- resolvconf.orig
    +++ resolvconf.new
    @@ -23,20 +23,7 @@
                    # It gets run later (or, in the TIMEOUT case, MAY get run later)
                    make_resolv_conf() {
                            local R
    -                       local nameserver
    -                       R=""
    -                       if [ "$new_domain_name_servers" ] && [ "$new_domain_name" ] ; then
    -                               R="${R}domain $new_domain_name
    -"
    -                       fi
    -                       if [ "$new_domain_name_servers" ] && [ "$new_domain_search" ] ; then
    -                               R="${R}search $new_domain_search
    -"
    -                       fi
    -                       for nameserver in $new_domain_name_servers ; do
    -                               R="${R}nameserver $nameserver
    -"
    -                       done
    +                       R="nameserver 127.0.0.1"
                            [ ! "$interface" ] || echo -n "$R" | /sbin/resolvconf -a "${interface}.dhclient"
                    }
                    ;;
    
    

    Move the new resolvconf file in place

    sudo mv resolvconf.new resolvconf

  14. EDIT: I've also had success appending the line "dns-nameservers 127.0.0.1" into /etc/network/interfaces, though your actual mileage may vary. This requires a restart, which happens to be the next step.

  15. EDIT: I've locked up cloud-based VMs by editing /etc/network/interfaces. So to work around that, I manually edit /etc/resolv.conf and then use chflags or chattr to make it read-only or immutable because resolvconf is stupid and doesn't belong on servers.

  16. Reboot the system. Test that daemontools runs at system startup and that dnscache is up and can run for more than 1 or 2 seconds without restarting.

    ps -ef | grep svscanboot
    sudo svstat /service/dnscache

    Other systems may require additional pokery jiggery. Your actual mileage may vary.

By following these steps, you should have constructed a DNS resolver built with security in mind. You've patched it with a state-of-the-art cryptography library and it now supports sites that utilize DNSCurve-based ECC encryption on their DNS endpoints. Queries to and responses from those DNSCurve-enabled sites are encrypted — really encrypted, not just plaintext with a signature — and you don't have a dependency on an external agency to provide you with a fundamental Internet service. We still have a very long way to go before we have a safe, secure, and reliable DNS infrastructure that we know we can trust. This is only the beginning.

No comments: