We want to serve DNS for LAN clients, but block or redirect results for certain domains (advertisements, maleware, etc.).
(See how to do something similar in a slightly lighter-weight way with dnsmasq.)
A DNS server can be either authoritative or recursive.
An authoritative DNS server returns DNS records for its domain of authority (its zone). Authoritative DNS servers are hierarchical. The ICANN root DNS servers sit at the top of the hierarchy, followed by the TLD nameservers (e.g. “.com”), followed by authoritative nameservers for second-level domains (“example.com”), etc.
A recursive DNS server queries other nameservers (moving as far through the hierarchy of authoritative nameservers as necessary) on behalf of clients. It caches results, only hitting other nameservers for new lookups or when the TTL of a cached record expires.
On Debian, we install Unbound as a package:
# apt-get install unbound
…and edit /etc/unbound/unbound.conf
.
On FreeBSD 10.1 and later, Unbound is installed by default.
% uname -sr
FreeBSD 10.3-RELEASE-p7
% whereis unbound
unbound: /usr/sbin/unbound /usr/share/man/man8/unbound.8.gz /usr/ports/dns/unbound
# sysrc local_unbound_enable=YES
local_unbound_enable: NO -> YES
# service local_unbound start
Performing initial setup.
Extracting forwarders from /etc/resolv.conf.
/var/unbound/forward.conf created
/var/unbound/lan-zones.conf created
/var/unbound/control.conf created
/var/unbound/unbound.conf created
/etc/resolvconf.conf created
original /etc/resolv.conf saved as /etc/resolv.conf.20160826.095904
Starting local_unbound.
FreeBSD has the shell script local-unbound-setup
that does magic things to auto generate unbound.conf
.
Double-check its output, especially since it sets up any nameservers found in /etc/resolv.conf
as forwarders, which is often not what we want; it may be necessary to edit any nameservers apart from 127.0.0.1
out of /etc/resolv.conf
, delete /var/unbound/forwarders.conf
, and re-run /usr/sbin/local-unbound-setup
.
Because we want to provide name service to our LAN(s), create /var/unbound/conf.d/lan-allow.conf
:
server:
interface: 10.0.1.1
interface: 10.0.2.1
access-control: 10.0.1.0/24 allow
access-control: 10.0.2.0/24 allow
If the box gets its address via DHCP, resolvconf
might overwrite /etc/resolv.conf
. In this case, override the DHCP nameservers in /etc/dhcpclient.conf
:
interface "em0" {
supersede domain-name-servers 127.0.0.1
}
On FreeBSD the service is called local_unbound
, so:
# service local_unbound restart
If we’re handing out DHCP leases from FreeBSD, we may want to edit /usr/local/etc/dhcpd.conf
to hand out our new nameserver:
option domain-name-servers 10.0.1.1;
and restart the daemon:
# service isc-dhcpd restart
In unbound.conf
:
include: /etc/unbound/blacklist.conf
The format of the blacklist file:
local-zone: "example.com" refuse
The first line of the blacklist.conf
file should be:
server:
To periodically update our blacklist, we can have cron run something like this script:
#!/bin/sh
curl --silent --user username:*********** --output /tmp/dg-ads.tar.gz http://www.squidblacklist.org/downloads/squidblacklists/dg/dg-ads.tar.gz
curl --silent --user username:*********** --output /tmp/dg-malicious.tar.gz http://www.squidblacklist.org/downloads/squidblacklists/dg/dg-malicious.tar.gz
touch /var/unbound/whitelist
if [ -s /tmp/dg-ads.tar.gz ]
then
cd /tmp
tar -xvf /tmp/dg-ads.tar.gz
fi
if [ -s /tmp/dg-malicious.tar.gz ]
then
cd /tmp
tar -xvf /tmp/dg-malicious.tar.gz
fi
if [ -s /tmp/dg-ads.acl -a -s /tmp/dg-malicious.acl -a -e /var/unbound/whitelist ]
then
cat /tmp/dg-ads.acl /tmp/dg-malicious.acl | sort | uniq \
| cat - /var/unbound/whitelist /var/unbound/whitelist | sort | uniq -u \
| sed '/^#.*/d' `# Delete comments` \
| sed 's/
//g' `# Delete Windows newlines` \
| sed -n -E '/^[a-z0-9\-\.]+/p' `# Strip any chars not valid in a domain name` \
| sed 's/^\.//' `# Strip starting dots (e.g. ".example.com")` \
| awk '{print "local-zone: \""$1"\" refuse"}' \
| sed '1 i\
server:
' > /var/unbound/conf.d/blacklist.conf
if unbound-checkconf /var/unbound/conf.d/blacklist.conf ; then
unbound-control reload
fi
fi
If we want to redirect to 127.0.0.1 rather than returning REFUSED:
cat /tmp/dg-ads.acl /tmp/dg-malicious.acl /tmp/dg-porn.acl | sort | uniq \
| cat - /var/unbound/whitelist /var/unbound/whitelist | sort | uniq -u \
| sed '/^#.*/d' `# Delete comments` \
| sed 's/
//g' `# Delete Windows newlines` \
| sed -n -E '/^[a-z0-9\-\.]+/p' `# Strip any chars not valid in a domain name` \
| sed 's/^\.//' `# Strip starting dots (e.g. ".example.com")` \
| awk '{print "local-zone: \""$1"\" redirect\nlocal-data: \""$1" A 127.0.0.1\""}' \
| sed '1 i\
server:
' > /var/unbound/conf.d/blacklist.conf
See unbound-control(8)
.
If not using the default config file location, it may be necessary to tell unbound-control
the location of the config file (-c /var/unbound/unbound.conf
).
# unbound-control status
version: 1.5.9
verbosity: 1
threads: 1
modules: 2 [ validator iterator ]
uptime: 419 seconds
options: control(ssl)
unbound (pid 22087) is running...
# unbound-control stats_noreset
thread0.num.queries=5
thread0.num.cachehits=1
thread0.num.cachemiss=4
thread0.num.prefetch=0
thread0.num.recursivereplies=4
thread0.requestlist.avg=0
thread0.requestlist.max=0
thread0.requestlist.overwritten=0
thread0.requestlist.exceeded=0
thread0.requestlist.current.all=0
thread0.requestlist.current.user=0
thread0.recursion.time.avg=0.293604
thread0.recursion.time.median=0.349525
thread0.tcpusage=0
total.num.queries=5
total.num.cachehits=1
total.num.cachemiss=4
total.num.prefetch=0
total.num.recursivereplies=4
total.requestlist.avg=0
total.requestlist.max=0
total.requestlist.overwritten=0
total.requestlist.exceeded=0
total.requestlist.current.all=0
total.requestlist.current.user=0
total.recursion.time.avg=0.293604
total.recursion.time.median=0.349525
total.tcpusage=0
time.now=1472176370.069576
time.up=235.308012
time.elapsed=235.308012
A recursive DNS server can in some cases be tricked into caching bad records, thereby diverting clients to incorrect and potentially harmful destinations.
DNSSEC prevents many cache poisoning exploits.
We might need Windows DNS servers. We can configure them to use our Unbound DNS server as a forwarder.
In the Windows DNS Manager, view the Properties of the DNS server. Go to the Forwarders tab, and enter the IP address of our Unbound DNS server.
https://technet.microsoft.com/en-us/library/cc754941%28v=ws.11%29.aspx https://technet.microsoft.com/en-us/library/cc730756%28v=ws.11%29.aspx
If anything on the LAN will be querying our Unbound server directly, we may want to include this in our unbound.conf
:
forward-zone:
name: "mylan.example.com"
forward-addr: 10.0.0.2
forward-addr: 10.0.0.3
Note that FreeBSD has drill
instead of dig
. On Debian, drill is available in the ldnsutils
package.
Just the IP:
% dig +short example.com
93.184.216.34
Find nameservers for a domain:
% dig ns +short example.com
b.iana-servers.net.
a.iana-servers.net.
Query a particular nameserver:
% dig +noall +answer example.com @8.8.8.8
example.com. 20817 IN A 93.184.216.34
See the TTL for a record:
% dig +noall +answer example.com
example.com. 15313 IN A 93.184.216.34
Reverse lookup (PTR):
% dig +short -x 8.8.8.8
google-public-dns-a.google.com.
Compare the TTL for a cached record at different recursive nameservers:
% dig @10.0.0.1 +noall +answer example.com
example.com. 86400 IN A 93.184.216.34
% dig @8.8.8.8 +noall +answer example.com
example.com. 15277 IN A 93.184.216.34
Look at any record type for a domain:
% dig +noall +answer any example.com
example.com. 15423 IN A 93.184.216.34
example.com. 19191 IN NS b.iana-servers.net.
example.com. 19191 IN NS a.iana-servers.net.
example.com. 1191 IN SOA sns.dns.icann.org. noc.dns.icann.org. 2015082641 7200 3600 1209600 3600
example.com. 17 IN TXT "v=spf1 -all"
example.com. 17 IN TXT "$Id: example.com 4415 2015-08-24 20:12:23Z davids $"
example.com. 19191 IN AAAA 2606:2800:220:1:248:1893:25c8:1946
One of the points of interest in the above is the Start Of Authority record, which we see more clearly with dig’s multiline
switch:
% dig +noall +answer +multiline soa example.com
example.com. 1326 IN SOA sns.dns.icann.org. noc.dns.icann.org. (
2015082641 ; serial
7200 ; refresh (2 hours)
3600 ; retry (1 hour)
1209600 ; expire (2 weeks)
3600 ; minimum (1 hour)
)
The SOA record includes: - the primary nameserver for the domain (sns.dns.icann.org) - the party responsible for the domain (noc.dns.icann.org) - the timestamp when the record was last modified (2015082641 ; serial) - seconds till the zone refresh (7200 ; refresh) - seconds to wait until retrying a failed zone refresh (3600 ; retry) - seconds until a zone result should not be considered authoritative (1209600 ; expire) - seconds a resolver should consider a negative result valid before requerying (3600 ; minimum)
Find which nameservers in a zone have synchronized which version of the name record (all servers are synced in this case):
% dig google.com +nssearch | awk '{ print $2, $4 }'
ns2.google.com. 131206105
ns3.google.com. 131206105
ns1.google.com. 131206105
ns2.google.com. 131206105
Bypass the resolver’s cache to see a worst-case full query path (results abridged):
% dig +trace example.com
; <<>> DiG 9.10.3-P4-Debian <<>> +trace example.com
;; global options: +cmd
. 3600 IN NS k.root-servers.net.
. 3600 IN NS e.root-servers.net.
;; Received 768 bytes from 10.0.0.2#53(10.0.0.2) in 1 ms
com. 172800 IN NS f.gtld-servers.net.
com. 172800 IN NS d.gtld-servers.net.
;; Received 735 bytes from 198.41.0.4#53(a.root-servers.net) in 27 ms
example.com. 172800 IN NS a.iana-servers.net.
example.com. 172800 IN NS b.iana-servers.net.
;; Received 591 bytes from 192.35.51.30#53(f.gtld-servers.net) in 51 ms
example.com. 86400 IN A 93.184.216.34
example.com. 86400 IN NS a.iana-servers.net.
example.com. 86400 IN NS b.iana-servers.net.
;; Received 452 bytes from 199.43.135.53#53(a.iana-servers.net) in 24 ms
Look for TXT records:
% dig +noall +answer txt google.com
google.com. 3598 IN TXT "v=spf1 include:_spf.google.com ~all"
(An spf record indicates which servers are allowed to send mail for the domain.)
What if we want to log queries for debugging purposes?
# unbound-control -c /var/unbound/unbound.conf set_option verbosity: 1
ok
# unbound-control -c /var/unbound/unbound.conf set_option log-queries: yes
ok
This logs a lot of stuff, so when we’re done looking for a specific thing, turn down logging:
# unbound-control -c /var/unbound/unbound.conf set_option log-queries: no
ok
# unbound-control -c /var/unbound/unbound.conf set_option verbosity: 0
ok
By default, it logs to syslog.