Local caching resolver in FreeBSD 10

As of a few hours ago, all it takes to set up a local caching resolver in FreeBSD 10 is:

# echo local_unbound_enable=yes >>/etc/rc.conf
# service local_unbound start

Yes, it really is that simple—and it works fine with DHCP, too. Hold my beer and watch this:

# pgrep -lf dhclient
1316 dhclient: vtnet0
1265 dhclient: vtnet0 [priv]
# cat /etc/resolv.conf
# Generated by resolvconf
search example.com
nameserver 192.0.2.53
# time host www.freebsd.org
www.freebsd.org is an alias for wfe0.ysv.freebsd.org.
wfe0.ysv.freebsd.org has address 8.8.178.110
wfe0.ysv.freebsd.org has IPv6 address 2001:1900:2254:206a::50:0
wfe0.ysv.freebsd.org mail is handled by 0 .
        0.02 real         0.00 user         0.01 sys

As you can see, we’re running DHCP on a VirtIO network interface. Let’s work our magic:

# echo local_unbound_enable=yes >>/etc/rc.conf
# service local_unbound start
Performing initial setup.
Extracting forwarders from /etc/resolv.conf.
/var/unbound/forward.conf created
/var/unbound/unbound.conf created
/etc/resolvconf.conf created
original /etc/resolv.conf saved as /etc/resolv.conf.20130923.075319
Starting local_unbound.

And presto:

# pgrep -lf unbound
3799 /usr/sbin/unbound -c/var/unbound/unbound.conf
# cat /var/unbound/unbound.conf 
# Generated by local-unbound-setup
server:
        username: unbound
        directory: /var/unbound
        chroot: /var/unbound
        pidfile: /var/run/local_unbound.pid
        auto-trust-anchor-file: /var/unbound/root.key

include: /var/unbound/forward.conf
# cat /var/unbound/forward.conf
# Generated by local-unbound-setup
forward-zone:
        name: .
        forward-addr: 192.0.2.53
# cat /etc/resolv.conf
# Generated by resolvconf
search example.com
# nameserver 192.0.2.53

nameserver 127.0.0.1
options edns0

We can see the cache at work; the first request takes significantly longer than before, but the second is served from cache:

# time host www.freebsd.org
www.freebsd.org is an alias for wfe0.ysv.freebsd.org.
wfe0.ysv.freebsd.org has address 8.8.178.110
wfe0.ysv.freebsd.org has IPv6 address 2001:1900:2254:206a::50:0
wfe0.ysv.freebsd.org mail is handled by 0 .
        0.07 real         0.01 user         0.00 sys
# time host www.freebsd.org
www.freebsd.org is an alias for wfe0.ysv.freebsd.org.
wfe0.ysv.freebsd.org has address 8.8.178.110
wfe0.ysv.freebsd.org has IPv6 address 2001:1900:2254:206a::50:0
wfe0.ysv.freebsd.org mail is handled by 0 .
        0.01 real         0.00 user         0.00 sys

Finally, let’s see how this interacts with DHCP:

# resolvconf -u
# cat /etc/resolv.conf
# Generated by resolvconf
search example.com
nameserver 127.0.0.1
nameserver 192.0.2.53
options edns0

# cat /var/unbound/forward.conf 
# Generated by resolvconf

forward-zone:
        name: "example.com"
        forward-addr: 192.0.2.53

forward-zone:
        name: "."
        forward-addr: 192.0.2.53

Note that resolvconf(8) re-added the 192.0.2.53 entry. It doesn’t really matter, as long as 127.0.0.1 comes first.

[ETA: it does matter—see Jakob Schlyter’s comment below and my reply.]

[ETA: see my followup about the motivation for importing Unbound.]

39 thoughts on “Local caching resolver in FreeBSD 10

  1. Beware! With DNSSEC validation turned on (which is the default above), you must not have your non-local DNS nameserver still in /etc/resolv.conf, or validation failures will not be handled correctly. This is because a validation failure results in SERVFAIL, and libresolv will happily continue to the next name server in resolv.conf (where validation might be turned off) and succeed.

    Hence, resolvconf(8) should really NOT re-add non-local name servers to resolv.conf.

  2. I just tried to set up unbound by
    1) stopping named
    2) adding local_unbound_enable=yes to /etc/rc.conf
    3) typing % service local_unbound start
    Now there is no dns-resolution anymore – even on my localhost.

    Did I miss anything?

    Greetings Peter

    1. I assume you typed “service local_unbound start” at a root prompt; “%” is the csh and zsh prompt character for unprivileged users.

      1. Is unbound running? (pgrep -lf unbound)
      2. What are the contents of your /etc/resolv.conf?
      3. What are the contents of your /var/unbound/forward.conf?
      4. Are you using DHCP?
  3. Hello. thanks for this. I have a question though:

    Does unbound have a method of using dhcpd like bind does? For example, let’s say i have a laptop that signs on to my network with the hostname “wonslung-lap” . On bind, with my setup, it dhcpd will insert an A record and a reverse DNS record into bind’s Zone files.

    Because of this, i can ssh in from any other client on the network.

    Does unbound have a similar option for local authoritative dns?

    1. If you’re talking about DDNS: the LDNS distribution includes a DDNS client, but we do not build it. There are several DDNS clients in the ports tree.

  4. Hi,
    I have installed FreeBSD 10 beta4 where unbound has replaced bind. I have installed unbound according to your instruction. Unfortunately unbound can’t forward requests from localhost to provider’s DNS servers. Can you help me fix this trouble?

    # pgrep -lf unbou
    2856 /usr/sbin/unbound -c/var/unbound/unbound.conf

    cat /etc/resolv.conf
    search beeline
    # nameserver 85.21.192.3
    # nameserver 213.234.192.8
    nameserver 127.0.0.1
    options edns0

    cat /var/unbound/forward.conf
    # Generated by local-unbound-setup
    forward-zone:
    name: .
    forward-addr: 85.21.192.3
    forward-addr: 213.234.192.8

    sockstat -4 | grep unbound
    unbound unbound 2856 3 udp4 127.0.0.1:53 *:*
    unbound unbound 2856 4 tcp4 127.0.0.1:53 *:*
    unbound unbound 2856 5 tcp4 127.0.0.1:953 *:*

    cat /etc/unbound/unbound.conf
    # Generated by local-unbound-setup
    server:
    username: unbound
    directory: /var/unbound
    chroot: /var/unbound
    pidfile: /var/run/local_unbound.pid
    auto-trust-anchor-file: /var/unbound/root.key
    access-control: 127.0.0.0/8 allow_snoop
    remote-control:
    control-enable: yes
    control-interface: 127.0.0.1
    control-port: 953
    include: /var/unbound/forward.conf

    ping tp.internet.beeline.ru
    ping: cannot resolve tp.internet.beeline.ru: Host name lookup failure

    And if
    cat /etc/resolv.conf
    search beeline
    nameserver 85.21.192.3
    # nameserver 213.234.192.8
    nameserver 127.0.0.1
    options edns0

    then
    ping tp.internet.beeline.ru
    PING tp.internet.beeline.ru (85.21.0.240): 56 data bytes
    64 bytes from 85.21.0.240: icmp_seq=0 ttl=251 time=0.796 ms
    64 bytes from 85.21.0.240: icmp_seq=1 ttl=251 time=0.772 ms

    1. I don’t know. Try running tcpdump on the loopback interface (sudo tcpdump -i lo0 udp port domain) to see DNS traffic between ping and unbound, and on the external interface to see DNS traffic between unbound and your forwarders.

      1. That would disable DNSSEC validation, which is the point of running Unbound locally.

  5. I had installed unbound on FreeBSD 10 beta RC 3 and then upgraded to RC5. Unbound still works for local requests but now longer serves requests by other machines.
    Do you have any idea what might be going wrong?

    1. What do you mean by “installed unbound”? Just enabled the local_unbound service?

      1. No I had it running really well then it logs a failed to prime trust anchor. I assumed it was answering for local host but I still had another name server in resolv.conf.

        1. A “failed to generate a trust anchor” error is harmless, as long as it has succeeded at least once. It just means that it was unable to retrieve a new copy of the DNS root public key, but that key should never change unless it’s been compromised, which would be all over the news.

  6. After upgrading to 10.0 I can’t get unbound to work. The “service local_unbound start” generates an error saying “su: unknown login: unbound”. I checked the passwd, master.passwd, and group files and they all have an ‘unbound’ entry. I can run “su -m bind -c whoami” however I get the same su error if I run “su -m unbound -c whoami”. Anyone know how to resolve this?

    1. Check the timestamp on /etc/*passwd and /etc/*pwd.db. If the former are more recent than the latter, you need to run pwd_mkdb(8).

      1. I found pwd_mkdb(8), but the man page was kind of intimidating so I ran adduser and set everything to the values already in passwd and that fixed the db. Now local_unbound is working. Thanks!

        1. Perhaps Someone™ should add an example section to the man page. The command you want is pwd_mkdb -p /etc/master.passwd. Either freebsd-update(8) or mergemaster(8) should have taken care of it for you.

  7. I had the same problem.
    I ran:
    pwd_mkdb -p /etc/master.passwd

    which fixed the problem. Thanks for pointing me the right direction.

  8. Nice explanation…

    What about local_unbound + dnscrypt-proxy?

    I was trying to run dnscrypt on 127.0.0.2 (lo0 alias) and then point local_unbound to it!

    But I’m having no luck…
    cat /etc/unbound/forward.conf
    forward-zone:
    name: "."
    forward-addr: 127.0.0.2

    % drill txt debug.opendns.com @127.0.0.2
    ;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 42930
    ;; flags: qr rd ra ; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 0
    ;; QUESTION SECTION:
    ;; debug.opendns.com. IN TXT

    ;; ANSWER SECTION:
    debug.opendns.com. 0 IN TXT "server 5.fra"
    debug.opendns.com. 0 IN TXT "flags 20 0 2f4 4000800000000000000"
    debug.opendns.com. 0 IN TXT "id 0"
    debug.opendns.com. 0 IN TXT "source 77.54.234.20:43972"
    debug.opendns.com. 0 IN TXT "dnscrypt enabled (################)"

    ;; AUTHORITY SECTION:

    ;; ADDITIONAL SECTION:

    ;; Query time: 54 msec
    ;; EDNS: version 0; flags: ; udp: 4096
    ;; SERVER: 127.0.0.2
    ;; WHEN: Fri Feb 28 00:42:45 2014
    ;; MSG SIZE rcvd: 221

    host debug.opendns.com 127.0.0.1
    Using domain server:
    Name: 127.0.0.1
    Address: 127.0.0.1#53
    Aliases:

    Host debug.opendns.com not found: 2(SERVFAIL)

    host -t txt debug.opendns.com 127.0.0.2
    Using domain server:
    Name: 127.0.0.2
    Address: 127.0.0.2#53
    Aliases:

    debug.opendns.com descriptive text "server 5.fra"
    debug.opendns.com descriptive text "flags 20 0 2f4 4000800000000000000"
    debug.opendns.com descriptive text "id 0"
    debug.opendns.com descriptive text "source 77.54.234.20:43972"
    debug.opendns.com descriptive text "dnscrypt enabled

    ....

    1. I don’t know anything about DNSCrypt. Does Unbound say anything interesting in the logs? Have you tried running it in debug mode?

      1. Well dnscrypt here would work only as a forwarder and it seems to work well now, the only problem is that it seems local-domain lookup is not working, but this is probably not related to dnscrypt, I’m probably doing something wrong in local-unbound config.

        this is my unbound.conf:
        server:
        username: unbound
        directory: /var/unbound
        chroot: /var/unbound
        pidfile: /var/run/local_unbound.pid
        auto-trust-anchor-file: /var/unbound/root.key
        private-domain: "geek.lan"

        include: /var/unbound/forward.conf

        and forward.conf
        forward-zone:
        name: "geek.lan"
        forward-addr: 10.10.50.254

        forward-zone:
        name: "."
        forward-addr: 127.0.0.2

        what would be wrong here?

        1. What do you mean when you say local domain lookup doesn’t work? You don’t get an answer when you try to look something up in geek.lan?

          Try adding something to your local zone file that resolved to a routable address, e.g.

          freebsd.geek.lan. IN CNAME www.freebsd.org.

          then look it up through local_unbound. If it works, there is a bug (or misfeature) in unbound relating to forwarding of zones with private addresses.

          1. I was trying to query with drill -D yoda.geek.lan now and I get this:
            drill -D yoda.geek.lan
            ;; ->>HEADER<<- opcode: QUERY, rcode: SERVFAIL, id: 59098
            ;; flags: qr rd ra ; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
            ;; QUESTION SECTION:
            ;; yoda.geek.lan. IN A

            ;; ANSWER SECTION:

            ;; AUTHORITY SECTION:

            ;; ADDITIONAL SECTION:

            ;; Query time: 7 msec
            ;; EDNS: version 0; flags: do ; udp: 4096
            ;; SERVER: 127.0.0.1
            ;; WHEN: Sat Mar 1 18:20:26 2014
            ;; MSG SIZE rcvd: 42

            However in the debug output from unbound:
            [1393698026] unbound[10467:0] info: 0RDd mod1 rep yoda.geek.lan. A IN [32/4683]
            [1393698026] unbound[10467:0] debug: cache memory msg=66424 rrset=66846 infra=3091 val=66401
            [1393698026] unbound[10467:0] debug: answer cb
            [1393698026] unbound[10467:0] debug: Incoming reply id = 7d92
            [1393698026] unbound[10467:0] debug: Incoming reply addr = ip4 10.10.50.254 port 53 (len 16)
            [1393698026] unbound[10467:0] debug: lookup size is 1 entries
            [1393698026] unbound[10467:0] debug: received udp reply.
            [1393698026] unbound[10467:0] debug: udp message[47:0] 7D928590000100010000000004796F6461046765656B036C616E0000010001C00C000100010000000000040A0A3232
            [1393698026] unbound[10467:0] debug: outnet handle udp reply
            [1393698026] unbound[10467:0] debug: measured roundtrip at 2 msec
            [1393698026] unbound[10467:0] debug: svcd callbacks start
            [1393698026] unbound[10467:0] debug: worker svcd callback for qstate 0x8026b5080
            [1393698026] unbound[10467:0] debug: mesh_run: start
            [1393698026] unbound[10467:0] debug: iterator[module 1] operate: extstate:module_wait_reply event:module_event_reply
            [1393698026] unbound[10467:0] info: iterator operate: query yoda.geek.lan. A IN
            [1393698026] unbound[10467:0] debug: process_response: new external response event
            [1393698026] unbound[10467:0] info: scrub for geek.lan. NS IN
            [1393698026] unbound[10467:0] info: response for yoda.geek.lan. A IN
            [1393698026] unbound[10467:0] info: reply from 10.10.50.254#53
            [1393698026] unbound[10467:0] info: incoming scrubbed packet: ;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 0
            ;; flags: qr aa rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
            ;; QUESTION SECTION:
            ;; yoda.geek.lan. IN A

            ;; ANSWER SECTION:
            yoda.geek.lan. 0 IN A 10.10.50.50

            ;; AUTHORITY SECTION:

            ;; ADDITIONAL SECTION:

            ;; Query time: 0 msec
            ;; WHEN: Thu Jan 1 01:00:00 1970
            ;; MSG SIZE rcvd: 47

            [1393698026] unbound[10467:0] debug: iter_handle processing q with state QUERY RESPONSE STATE
            [1393698026] unbound[10467:0] info: query response was ANSWER
            [1393698026] unbound[10467:0] debug: TTL 0: dropped msg from cache
            [1393698026] unbound[10467:0] debug: iter_handle processing q with state FINISHED RESPONSE STATE
            [1393698026] unbound[10467:0] info: finishing processing for yoda.geek.lan. A IN
            [1393698026] unbound[10467:0] debug: mesh_run: iterator module exit state is module_finished
            [1393698026] unbound[10467:0] debug: validator[module 0] operate: extstate:module_wait_module event:module_event_moddone
            [1393698026] unbound[10467:0] info: validator operate: query yoda.geek.lan. A IN
            [1393698026] unbound[10467:0] debug: validator: nextmodule returned
            [1393698026] unbound[10467:0] debug: val handle processing q with state VAL_INIT_STATE
            [1393698026] unbound[10467:0] debug: validator classification positive
            [1393698026] unbound[10467:0] info: no signer, using yoda.geek.lan. TYPE0 CLASS0
            [1393698026] unbound[10467:0] debug: val handle processing q with state VAL_FINISHED_STATE
            [1393698026] unbound[10467:0] debug: mesh_run: validator module exit state is module_finished
            [1393698026] unbound[10467:0] debug: query took 0.002532 sec
            [1393698026] unbound[10467:0] info: mesh_run: end 0 recursion states (0 with reply, 0 detached), 0 waiting replies, 2 recursion replies sent, 0 replies dropped, 0 states jostled out
            [1393698026] unbound[10467:0] info: average recursion processing time 0.170909 sec
            [1393698026] unbound[10467:0] info: histogram of recursion processing times
            [1393698026] unbound[10467:0] info: [25%]=0 median[50%]=0 [75%]=0
            [1393698026] unbound[10467:0] info: lower(secs) upper(secs) recursions
            [1393698026] unbound[10467:0] info: 0.002048 0.004096 1
            [1393698026] unbound[10467:0] info: 0.262144 0.524288 1
            [1393698026] unbound[10467:0] debug: cache memory msg=66424 rrset=66846 infra=3091 val=66401

          2. Yep, looks like it’s dropping the answer due to the RFC1918 address. Did you try the freebsd.geek.lan thing I suggested?

          3. Wait, I spoke too soon. I just noticed this bit:

            ;; QUESTION SECTION:
            ;; yoda.geek.lan. IN A

            ;; ANSWER SECTION:
            yoda.geek.lan. 0 IN A 10.10.50.50

            followed by

            [1393698026] unbound[10467:0] debug: TTL 0: dropped msg from cache

            so it looks like Unbound is dropping the answer it got from upstream because its TTL is 0. This raises a number of questions:

            • Did dnscrypt intentionally set the TTL to 0?
            • Does the DNS specification actually allow this?
            • Was it a conscious decision on the unbound developers’ part to ignore a response with a TTL of 0, or did they just not consider that scenario?

            Assuming that dnscrypt intentionally sets the TTL to 0 to prevent downstream resolvers from caching the response, it shouldn’t be too hard to make unbound accept that answer and pass it on without caching it. As a workaround, see if it is possible to configure dnscrypt to use a non-zero TTL.

          4. Grr, spoke too soon again. I didn’t notice that the TTL 0 issue is not related to dnscrypt, but to whatever you’re using for your local domain. What do you run on 10.10.50.254?

  9. Sorry for the late reply that’s my local router (running asus merlin firmware)

    I also just found a more weired thing... tcpdump (wireshark shows this):
    12 12.637094000 127.0.0.1 127.0.0.1 DNS 129 Standard query response 0x51d3 No such name
    13 21.769253000 127.0.0.1 127.0.0.1 DNS 65 Standard query 0xf10d A c3po.geek.local
    14 21.769856000 10.10.50.70 10.10.50.254 DNS 86 Standard query 0xc826 A c3po.geek.local
    15 21.771011000 10.10.50.254 10.10.50.70 DNS 91 Standard query response 0xc826 A 10.10.50.10

    So, it seems that the query DID retrun an ANWSER and its local_unbound that’s somehow …. not parsing it/Seeing it as ‘empty’?

  10. Sorry for the late reply, was having problems posting from smartphone!

    The ‘10.10.50.254’ machine is my local router, running Asus Merlin firmware… An interesting thing is that according to wireshark/tcpdump the rely did came

    No. Time Source Destination Protocol Length Info
    11 12.637054000 127.0.0.1 127.0.0.1 DNS 54 Standard query 0x51d3 A c3po
    12 12.637094000 127.0.0.1 127.0.0.1 DNS 129 Standard query response 0x51d3 No such name
    13 21.769253000 127.0.0.1 127.0.0.1 DNS 65 Standard query 0xf10d A c3po.geek.local
    14 21.769856000 10.10.50.70 10.10.50.254 DNS 86 Standard query 0xc826 A c3po.geek.local
    15 21.771011000 10.10.50.254 10.10.50.70 DNS 91 Standard query response 0xc826 A 10.10.50.10

    But local unbound log shows nothing after AWNSER.

    1. Like I said, Unbound receives the answer but discards it because it has a TTL of 0; at least, that’s what the log says. Unfortunately, neither tcpdump nor wireshark seem to display the DNS TTL (as opposed to the IP TTL). I would suggest asking unbound-users@unbound.net for advice.

  11. What you suggest does seem to be pointing to the problem, I’ll ask in the list and post feedback later.

    Another thing I noticed is that each time I reboot I have to update forwarders.conf with the dnscrypt-proxy ip for “.”, its always re-updating the file using resolvconf… has if it was running “setup” before the start… wonder if this is the expected behavior with local_unbound

    1. Yes, it is the expected behavior. You should place that part of your configuration either directly in unbound.conf or in a separate .conf file which you include in unbound.conf.

      1. Just follow the steps in “Unbound DNS Tutorial” in calomel.org, specifically “Authoritative, validating, recursive DNS caching” for not skip auto-trust-anchor-file in unbound.conf

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>