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.
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
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: "18.104.22.168.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.
access-control we allow the network
192.168.42.0/24 to query the dns
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
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.
forward-zone entries tell unbound, where it should ask for queries regarding
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
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 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.
no-resolv avoids dnsmasq knowning of any other dns server.
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.
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
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
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.