using unbound and dnsmasq

After some time of using an Almond as our router and always having trouble with disconnects, I bought a small apu1d4, an AMD low power board, as our new router. It is now running FreeBSD and is very stable. Not a single connection was dropped yet.

As we have some services in our network, like a fileserver and a printer, we always wanted to use names instead of IPs, but not a single router yet could provide that. So this was the first problem I solved.

FreeBSD comes with unbound preinstalled. Unbound is a caching DNS resolver, which helps answer DNS queries faster, when they were already queried before. I wanted to use unbound as the primary source for DNS queries, as the caching functionality is pretty nice. Further I wanted an easy DHCP server, which would also function as a DNS server. For that purpose dnsmasq fits best. There are also ways to use dhcpd, bind and some glue to get the same result, but I wanted as few services as possible.

So my setup constellation looks like this:

client -> unbound -> dnsmasq
             +-----> ISP dns server

For my internal tld, I will use zero. The dns server is called cerberus.zero and has the IP 192.168.42.2. The network for this setup is 192.168.42.0/24.

configuring unbound

For this to work, first we configure unbound to make name resolution work at all. Most files already have pretty good defaults, so we will overwrite these with a file in /etc/unbound/conf.d/, in my case /etc/unbound/conf.d/zero.conf.

server:
  interface: 127.0.0.1
  interface: 192.168.42.2
  do-not-query-localhost: no
  access-control: 192.168.42.0/24 allow
  local-data: "cerberus. 86400 IN A 192.168.42.2"
  local-data: "cerberus.zero. 86400 IN A 192.168.42.2"
  local-data: "2.42.168.192.in-addr.arpa 86400 IN PTR cerberus.zero."
  local-zone: "42.168.192.in-addr.arpa" nodefault
  domain-insecure: "zero"

forward-zone:
  name: "zero"
  forward-addr: 127.0.0.1@5353

forward-zone:
  name: "42.168.192.in-addr.arpa."
  forward-addr: 127.0.0.1@5353

So what happens here is the following. First we tell unbound, on which addresses it should listen for incoming queries. Next we staate, that querying dns servers in localhost is totally okay. This is needed to later be able to resolve addresses on the local dnsmasq. If your dnsmasq is running on a different machine, you can leave this out. With access-control we allow the network 192.168.42.0/24 to query the dns server. The next three lines tell unbound, that the name cerberus and cerberus.zero are one and the same machine, the DNS server. Without these two lines unbound would not resolve the name of the local server, even if its name would be stated in /etc/hosts. With the last line we enable name resolution for the local network. The key domain-insecure tells unbound, that this domain has no support for DNSSEC. DNSSEC is enabled by default on unbound.

The two forward-zone entries tell unbound, where it should ask for queries regarding the zero tld and the reverse entries of the network. The address in this case points to the dnsmasq instance. In my case, that is running on localhost and port 5353.

Now we can add unbound to /etc/rc.conf and start unbound for the first time with the following command

$ sysrc local_unbound_enable=YES && service local_unbound start

Now you should be able to resolve the local hostname already

$ host cerberus.zero
cerberus.zero has address 192.168.42.2

configuring dnsmasq

The next step is to configure dnsmasq, so that it provides DHCP and name resolution for the network. When adjusting the config, please read the comments for each option in your config file carefully. You can find an example config in /usr/local/etc/dnsmasq.conf.example. Copy it to /usr/local/etc/dnsmasq.conf and open it in your editor:

port=5353
domain-needed
bogus-priv
no-resolv
no-hosts
local=/zero/
except-interface=re0
bind-interfaces
local-service
expand-hosts
domain=zero
dhcp-range=192.168.42.11,192.168.42.200,255.255.255.0,48h
dhcp-option=option:router,192.168.42.2
dhcp-option=option:dns-server,192.168.42.2
dhcp-host=00:90:f5:f0:fc:13,0c:8b:fd:6b:04:9a,sodium,192.168.42.23,96h

First we set the port to 5353, as defined in the unbound config. On this port dnsmasq will listen for incoming dns requests. The next two options are to avoid forwarding dns requests needlessly. The option no-resolv avoids dnsmasq knowning of any other dns server. no-hosts does the same for /etc/hosts. Its sole purpose is to provide DNS for the local domain, so it needn’t to know.

The next option tells dnsmasq for which domain it is responsible. It will also avoid answering requests for any other domain.

except-interfaces tells dnsmasq on which interfaces not to listen on. You should enter here all external interfaces to avoid queries from the wide web detecting hosts on your internal network. The option bind-interfaces will try to listen only on the interfaces allowed instead of listening on all interfaces and filtering the traffic. This makes dnsmasq a bit more secure, as not listening at all is better than listening.

The two options expand-hosts and domain=zero will expand all dns requests with the given domain part, if it is missing. This way, it is easier to resolv hosts in the local domain.

The next three options configure the DHCP part of dnsmasq. First is the range. In this example, the range starts from 192.168.42.11 and ends in 192.168.42.200 and all IPs get a 48h lease time. So if a new hosts enters the network, it will be given an IP from this range. The next two lines set options sent with the DHCP offer to the client, so it learns the default route and dns server. As both is running on the same machine in my case, it points to the same IP.

Now all machines which should have a static name and/or IP can be set through dhcp-host lines. You have to give the mac address, the name, the IP and the lease time. There are many examples in the example dnsmasq config, so the best is to read these.

When your configuration is done, you can enable the dnsmasq service and start it

$ sysrc dnsmasq_enable=YES && service dnsmasq start

When you get your first IP, do the following request and it should give you your IP

$ host $(hostname)
sodium.zero has address 192.168.42.23

With this, we have a running DNS server setup with DHCP.