Edit : added the 15/01/2016 the dns64, unbound bad ip listing and modification of ports between dnsmasq and NSD.

In this article, I want to show you the dns installation I made in my local network, in hope it will be useful to somebody. It's also a way for me to check the setup, be sure everything works as supposed.

Here is the base of the config and what I want :

  1. Openbsd server on a lan, setup as DMZ, so it listens directly to internet AND the lan.
  2. Ipv6 enabled and used as often as possible.
  3. I host my own domain name server, with dnssec.
  4. I intend to do dnssec validation as well.
  5. I want to be able to resolve all my hosts address, so that a new host can enter the network without problem. He will have ip + ipv6 address and hostname.

As you can see, it may be a bit complicated !

Solutions

I see two solutions :

  1. The venerable Bind and its cousin Dhcp which update the local zone. You use the Bind view function to answer authoritative  for your own domain. Problem : Bind has its critics. It is considered heavy and bogus. Depending on your dhcp server, you may have problems with dhcpv6 and dns updating (maybe). And you have to launch a r(t)advd [1] for people not using dhcpv6.
  2. You use Dnsmasq as a local dns + dhcp +dhcpv6 +r(t)advd daemon. At the same time, you  use unbound as a deep resolver, and you answer autoritative side (your domain) with nsd.

In the second solution, the resolver and the authoritative server can be other softwares of course. Yet, NSD and Unbound are in the base of OpenBSD, so I won't argue.

Compare the two solutions :

Bind + dhcpv4 + dhcpv6 +r(t)advd

You need ddns to be able to add hosts in the lan without thinking of it. And you need to setup Bind to answer dns queries depending on the origin of the requests (it's the view function). So you have a lot of work to setup Bind correctly. Both Dhcp and Bind have to share some common settings (the ddns key).

Dnsmasq

Dnsmasq works almost out of the box for local matters. But you need to setup a proper resolver for dnssec validation and recursion. And you have to take care all this daemons won't collapse each others, as they are suppose to use the same port and address !

As said above, it will be complicated !

I will use the Dnsmasq solution. At first, I though Dnsmasq was not a good thing. Its configuration looks like a mess, or a draft, as you want. But I really like the fact that it is possible to run completely a lan on its behalf ! If you don't want to browse the web, nor need to host an authoritative domain server, it's perfect ! It completely replace three softwares at once (local and caching dns server, dhcp 4 and 6, both coordinated, and r(t)advd).

Design

Actually, as you have noted, we will have three (!) dns servers running on the same machine. That may lead to huge problems ! So I am going to launch all this servers on different ports, and use pf to forward queries coming from the lan to Dnsmasq, which will ask Unbound to resolve the deep internet with dnssec.

I could use one address per server (after all, we are in a lan, we can use ten addresses with not much problem, and ipv6 allows so much addresses) but Dnsmasq doesn't like to be restricted in address listening. So I prefer forward ports.

I will use mainly ipv6 local address when needed. By the way, I use now DNS64. When ipv4 will be out of order (in years) you should have switched every service to ipv6 at each configuration upgrade, and, voila... you can now shutdown the ipv4 stack (if you want so).

Everybody understand and ready ? Let's go !

Installation

NSD

Let's start with the autoritative dns server. He will listen to the internet, on the port 5355, unaware of the two other daemon, and DNS requests from the internet world are forwarded to NSD by PF.

I followed this tutorial to setup NSD properly. It serves well this domain, signed with dnssec.

Here is the conf':

server:
    server-count: 4
    hide-version: yes
    zonesdir: "/var/nsd/"
    ip-address: 127.0.0.1
    ip-address: ::1
    port: 5355

## master zones
zone:
    name: "22decembre.eu"
    zonefile: "22decembre.eu/zone.db.signed"
    # gandi slave servers
    notify:         217.70.177.40 NOKEY
    provide-xfr:    217.70.177.40 NOKEY

zone:
    name: "7.0.2.8.0.0.d.d.8.d.6.1.1.0.0.2.ip6.arpa"
    zonefile: "7.0.2.8.0.0.d.d.8.d.6.1.1.0.0.2.ip6.arpa/zone.db.signed"

## control

remote-control:
    control-enable: yes

Remember to add nsd to rc.conf.local :
nsd_flags=""

Dnsmasq

Dnsmasq is available in Openbsd packages or ports. Bad news is that it's buggy at present. I will use the git version.

cd /usr/local/src/ git clone git://thekelleys.org.uk/dnsmasq.git
cd dnsmasq
make install

The config is default or almost. You should have a look at dnsmasq doc ! Here is my config :

# Configuration file for dnsmasq.
# # Format is one option per line, legal options are the same
# as the long options legal on the command line. 
# See "/usr/local/sbin/dnsmasq --help" or "man 8 dnsmasq" for details.
# Listen on this specific port instead of the standard DNS port (53).
# Setting this to zero completely disables DNS function, 
# leaving only DHCP and/or TFTP. I set 5353 here and pf will forward local 
# dns queries to this port. 
#
# Before august 2015
# port=5353 
#
## Never forward plain names (without a dot or domain part) domain-needed
# Never forward addresses in the non-routed address spaces.
#
bogus-priv
#
# Add local-only domains here, queries in these domains are answered
# from /etc/hosts or DHCP only. 
#
local=/example.com/
#
# talk with unbound on port 5354
#
server=::1#5354
#
# Set this (and domain: see below) if you want to have a domain
# automatically added to simple names in a hosts-file.
#
expand-hosts 
#
# Set the domain for dnsmasq. this is optional, but if it is set, it 
# does the following things. 
# 1) Allows DHCP hosts to have fully qualified domain names, as long 
# as the domain part matches this setting.
# 2) Sets the "domain" DHCP option thereby potentially setting the
# domain of all systems configured by DHCP
# 3) Provides the domain part for "expand-hosts"
# domain=example.com 
#
dhcp-range=192.168.0.50,192.168.0.200,255.255.255.0
#
# Specify a subnet which can't be used for dynamic address allocation,
# is available for hosts with matching --dhcp-host lines. Note that
# dhcp-host declarations will be ignored unless there is a dhcp-range
# of some type for the subnet in question.
# In this case the netmask is implied (it comes from the network 
# configuration on the machine running dnsmasq) it is possible to give
# an explicit netmask instead.
#
#dhcp-range=192.168.0.0,static
#
# Do DHCP and Router Advertisements for this subnet. Set the A bit in 
# the RA so that clients can use SLAAC addresses as well as DHCP ones.
#
# dhcpv6 range, default netmask is 64 
#
dhcp-range=2001:DB8::100, 2001:DB8::8000, ra-names
enable-ra 
#
# Send options to hosts which ask for a DHCP lease.
# See RFC 2132 for details of available options.
# Common options can be given to dnsmasq by name:
# run "dnsmasq --help dhcp" to get a list.
# Note that all the common settings, such as netmask and
# broadcast address, DNS server and default route, are given
# sane defaults by dnsmasq. You very likely will not need
# any dhcp-options. If you use Windows clients and Samba, there
# are some options which are recommended, they are detailed at the
# end of this section.
#
# my ipv4 router is not the machine running dnsmasq !
#
dhcp-option=option:router,192.168.0.1
#
# 0.0.0.0 means you say a dhcp option with the dnsmasq address.
# Here the ntp server 
#
dhcp-option=option:ntp-server,0.0.0.0
#
# I use the dns-server running on the server (dnsmasq itself !)
# plus opendns server
dhcp-option=option:dns-server,0.0.0.0,208.67.222.222
#
# domain to pass to the dhcp client (same as the one in domain=...).
#
dhcp-option=option:domain-search,example.com
#
# same options as ipv4, with ipv6 notion.
# [::] means dnsmasq address.
#
dhcp-option=option6:dns-server,[::],[2620:0:ccd::2]
dhcp-option=option6:ntp-server,[::]
dhcp-option=option6:domain-search,example.com
dhcp-authoritative
#
# Set the cachesize here.
cache-size=250
#
log-dhcp

You obviously have to adapt your settings : domain name, ip address range and ipv6 address range. In the configuration above, I use opendns servers and the ipv6 router is the same machine as the one running dnsmasq. But the ipv4 router is different.

You have to have a rc script. Here is mine :

1
2
3
4
#!/bin/sh
daemon="/usr/local/sbin/dnsmasq --proxy-dnssec"
. /etc/rc.d/rc.subr
rc_cmd $1

Dnsmasq will forward recursive dns to unbound, with a dnssec proxy.
And add dnsmasq to pkg_script in /etc/rc.conf.local

Unbound

Unbound is installed by default in OpenBSD base. It's a good idea to have a look at its documentation. In order to resolv dnssec, you need to install root keys before it starts. So run this command :

unbound-anchor

This command is also runned on a weekly basis with a crontask.

Then goes the configuration of the daemon. You don't have much to change. Just take care of the port, the address you allow control, and the anchors (I installed the root anchor with the previous command and the dlv anchor) :

# The server clause sets the main parameters. 
server: # verbosity number, 0 is least verbose. 1 is default. 
    verbosity: 1 
    #
    # port to discuss with dnsmasq
    port: 5354 
    #
    # Enable IPv4, IPv6, Udp,Tcp,"yes" or "no".
    do-ip4: yes
    do-ip6: yes
    do-udp: yes
    do-tcp: yes
    #
    access-control: 0.0.0.0/0 refuse
    access-control: 127.0.0.0/8 allow
    access-control: 192.168.0.0/25 allow
    #
    access-control: ::0/0 refuse
    access-control: ::1 allow
    access-control: fe80::/64
    allow access-control: 2001:DB8::/64 allow
    #
    chroot: "/var/unbound"

    include:"/var/unbound/db/list"

    log-queries: no
    root-hints: "named.cache"
    module-config: "dns64 validator iterator"

    dns64-prefix: 64:FF9B::/96

    auto-trust-anchor-file: "root.key"
    dlv-anchor-file: "dlv.isc.org.key"

In order to optimize my web bandwidth consumption, avoid tracking, advertisement or other bad ip, I use also http://someonewhocares.org/hosts/. I have a crontask that runs weekly this script.

#
SHELL=/bin/sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin
HOME=/var/log
#
#minute hour    mday    month   wday    command
#
# weekly update of unbound roots keys
05     1       *       *       1        /usr/sbin/unbound-anchor -v && /usr/sbin/unbound-control reload

# weekly update of bad ip list from http://someonewhocares.org
05      1       *       *       2       /usr/local/bin/unbound-anti-ads --file=/var/unbound/db/list && /usr/sbin/unbound-control reload

You see also that unbound furnish DNS64.

Remember to make unbound start on boot. You should have this in /etc/rc.conf.local, here both dnsmasq and unbound. Now, your rc.conf.local should have this :

pkg_scripts="... dnsmasq unbound ..."
nsd_flags=""

PF

Edit : before august 2015, NSD was running on the casual domain port, and I derivated local dns querries to dnsmasq port (5353 by the time). After august 2015, I reversed this behavior. If you want to know the way, mail me or find an internet archive. I am sure there are tons of them.

In my pf.conf, I have a localnet table, very useful. Then I allow the server to talk friendly with the neighbourhood (general rule no purpose with this article). But every dns request that doesn't come from this localnet is redirected to NSD port.

It's there also I have nat64 (to work with unbound dns64).

table «localnet» { 192.168.0.0/24, 2001:DB8::/64, fe80::/64, 127.0.0.1, ::1 }
#
# let's talk with the neighbours
pass in from <localnet> to any pass out from any to <localnet>

# dns redirection on nsd
pass in log quick inet  proto {udp,tcp} from !<localnet> to any port domain rdr-to 127.0.0.1    port 5355
pass in log quick inet6 proto {udp,tcp} from !<localnet> to any port domain rdr-to ::1          port 5355

# nat64
# Found here : http://firstyear.id.au/entry/19
pass in log     quick   inet6 from any to 64:ff9b::/96 af-to inet from (egress:0) keep state rtable 0

Take care of the rules interaction ! You may have a look on the pf.conf manual and search for the tutorials on internet... The orders the rules apply is really important !

Resolv.conf

In order for the server itself to resolv its network and companions (via dnsmasq), and the internet outside (via unbound), we have to change the content of the /etc/resolv.conf file on the server.

We request the two dns daemons. To use the non standard port, you must use brackets [ ], with or without ipv6. Finally, I indicate also opendns, in order to prevent failure from unbound which will arrive, one day or an other !

search example.com
domain exemple.com
lookup file bind
nameserver [::1]:5354
nameserver [::1]:5353
nameserver 2620:0:ccc::2
nameserver 208.67.220.220

In use...

I have this config running quite correctly, with dnssec validation and local hosts resolving. Remember to ask your network client to work with dhcp (v4 and v6). This works perfect on debian and windows. I have only troubles because my isp forces the dns resolution on its dns servers. There seems to be a debian bug on that.

Dnsmasq act also as r(t)advd and furnish dns info as well as route, it's useful for temporary hosts. But when I began setting this up, it worked better with dhcpv6 - at least for hosts resolution.

You can install a dnssec validator in firefox. I have now quite often a green key in my address bar. That's fine.

[1] Under OpenBSD, the router advertisement daemon is called rtadvd. Under debian it's called radvd. So I use the r(t)advd word instead.

PS: If you liked the article, or found it useful, or if you simply like me, you are very much welcome to tip or give via liberapay.