Abstract
I am setting up a new server for my house, and thought I would take the opportunity to document setting up Bind 9 with ad blocking. Currently, my DNS is setup with Bind, but I did not make any notes or write anything down at the time of setting it up. Since I am re-doing much of that work anyway, I thought I might as well take some notes for myself and anyone else struggling with Bind like I know I did, both in the past and with this current iteration.
Basic OS
I went with a very standard FreeBSD bootonly install with ZFS on
root. Rebooting into the newly installed system, I installed some basic
things that I want on the system such as neovim
,
doas
, and git
:
pkg update
pkg install -y neovim doas git
# Symlinking nvim to vim because muscle memory
ln -s /usr/local/bin/nvim /usr/local/bin/vim
I then changed the configuration of the network adapter from DHCP to
a static IP. This is best done in /etc/rc.conf
to be
persistent between reboots. While I was editing my rc.conf
file, I went ahead and organized the parameters to be a bit more
logical, at least for me:
# Standard Host Config
clear_tmp_enable="YES"
syslogd_flags="-ss"
sendmail_enable="NONE"
powerd_enable="YES"
# Set dumpdev to "AUTO" to enable crash dumps, "NO" to disable
dumpdev="AUTO"
zfs_enable="YES"
moused_nondefault_enable="NO"
# Networking Stuff
hostname="server"
# ifconfig_re2="dhcp"
ifconfig_re2="inet 172.16.0.40 netmask 255.255.255.0"
defaultrouter="172.16.0.1"
# Setting Time
sshd_enable="YES"
ntpd_enable="YES"
ntpd_sync_on_start="YES"
# Bind Stuff
# Bhyve Stuff
kld_list="nmdm vmm"
# Jails
cloned_interfaces="lo1"
This file will be modified a few times during the deployment of the server, but that will be a later me problem. For now the lines that are relevant are the ones under the “Networking Stuff” section. In order:
hostname = "server"
: Sets the hostname to ‘server’#ifconfig_re2="dhcp"
: commented out, but would set up DHCP on interface re2 if uncommentedifconfig_re2="inet 172.16.0.40 netmask 255.255.255.0"
: sets a static IP of 172.16.0.40 with a /24 netmask.defaultrouter="172.16.0.1"
: sets the gateway to 172.16.0.1.
Setting Up Bind
pkg install -y bind-tools bind920
rndc-confgen -a
The bind-tools
package is what provides utilities such
as nslookup
and dig
, which are not strictly
required, but make troubleshooting much easier. After getting the
packages installed, we have to get the server configured:
// Comment out the following line to enable named for all interfaces
// listen-on { 127.0.0.1; };
// alternatively, an address can be specified for a listener address
...
// Enables recursive DNS
recursion yes
// Stealing the following comments from the original file:
// If you've got a DNS server around at your upstream provider, enter
// its IP address here, and enable the link below. This will make you
// benefit from its cache, thus reduce overall DNS traffic in the internet.
forwarders {
9.9.9.9; // Using quad 9
8.8.8.8; // Google DNS
1.1.1.1; // Cloudflare
// etc
}
// If you only want the DNS server to forward queries,
// then the following line should be uncommented.
// forward only;
Enabling recursive DNS is generally recommended as it caches the
results of DNS queries, boosting performance. We will be coming back to
the named.conf
file to enable the zones, however, let’s
look at the zone files for now.
Zone Files
While the zone files can technically be stored anywhere on the
system, I tend to make a master
directory in the
named
configuration directory
(/usr/local/etc/namedb
). Alternatively, the FreeBSD
implementation has a default primary
directory for the
localhost and ‘empty’ zone files.
Here is an example zone file for the domain
example.home.arpa
, with comments (starting with ‘;’) to
better explain what each line is:
$ORIGIN example.home.arpa. ; Base domain name for the zone file
$TTL 3H ; default time to live for the zone
@ IN SOA example admin.example.home.arpa. ( ; Setting the SOA record for the zone, this is a requirement for the zone configureation to be valid
6 ; Serial, number must increase on each edit for changes to be loaded. If number decreses, bind will give an error on the zone
3h ; Refresh
1h ; Retry
1w ; Expirey
1h ) ; Minimum TTL
IN A 172.16.0.40 ; A record for main domain
@ IN NS ns1.example.home.arpa. ; Name Server record for domain (recommended, but not strictly required)
ns1 IN A 172.16.0.40 ; A record for Name Server record (required for NS record to be valid)
Any domain can be added to these zone files, however, for local
domains it is generally recommended, per RFC 8375 to use
.home.arpa
for local domains rather than something like
.home
, .local
, or .lan
. This is
because the .home.arpa
domain is specifically reserved for
that purpose, and thus will not run into collision issues with external
services.
Now let’s setup a reverse zone for our domain:
$TTL 3h
@ SOA example.home.arpa. admin.exmaple.home.arpa. (
2 ; Serial
1d ; Refresh
12h ; Retry
1w ; Expire
3h ) ; TTL
NS ns1.home.arpa.
40 PTR ns1.home.arpa.
40 PTR home.arpa.
While this file looks very similar, it is not exactly the same, with
the most notable feature being the lack of full IP addresses. Rather, it
will only contain the last octet for each of the domain names, the rest
of the address will actually come from the named.conf
file
when we enable the zone(s) and reverse zone(s). I added those entries
right above the entries for the various RFCs:
/* Serving the following zones locally will prevent any queries
for these zones leaving your network and going to the root
name servers. This has two significat advantages:
1. Faster local resolution for your users
2. No spurious traffic will be sent from your network to the roots
*/
// Custom Forward Zone Files
zone "example.home.arpa" { type primary; file "/usr/local/etc/namedb/master/example-forward.db"; };
// Custom Reverse Zone Files
zone "0.16.172.in-addr.arpa" { type primary; file "/usr/local/etc/namedb/master/local-reverse.db"; };
...
// Comment (or change) the existing entry for '0.16.172.in-addr.arpa'
// zone "0.16.172.in-addr.arpa" { type primary; file "/usr/local/etc/namedb/primary/empty.db"; };
...
We can check for mis-configurations using the
named-checkconf
and named-checkzone
utilities
as follows:
# Checking for configuration issues (no output means no configuration issues were found)
named-checkconf
# Checking for configuration issues in the forward zone file
named-checkzone example.home.arpa master/example-forward.db
zone example.home.arpa/IN: loaded serial 6
OK
# Checking for configuration issues in the reverse zone file
named-checkzone 40.0.16.172.example.home.arpa master/local-reverse.db
zone 40.0.16.172.example.home.arpa/IN: loaded serial 2
OK
Now that everything is configured, let’s enable the service and see if it works:
# I tend to prefer to not fully enable the service until it is tested
# the one start command will allow it to run as if it were enabled,
# without it actually being enabled
service named onestart
# To fully enable the service:
sysrc named_start=YES
service named start
Now we need to add the DNS server to its own
/etc/resolv.conf
file (and I generally remove other
namesevers):
# The search line reaches out to the domain to check for connectivity to the Internet
search freebsd.org
nameserver 127.0.0.1
# Can also use the machine's actual network address
nameserver 172.16.0.40
Then we test:
# Checking to make sure the machine can get to the Internet,
# as well as checking which name server it is using:
nslookup foxide.xyz
Server: 172.16.0.40
Address: 172.16.0.40#53
Non-authoritative answer:
Name: foxide.xyz
Address: 172.245.181.191
nslookup example.home.arpa
# Now for our local domain
Server: 172.16.0.40
Address: 172.16.0.40#53
Name: example.home.arpa
Address: 172.16.0.40
# Now for the reverse address
nslookup 172.16.0.40
40.0.16.172.in-addr.arpa name = ns1.example.home.arpa.
40.0.16.172.in-addr.arpa name = example.home.arpa.
Assuming all the tests passed, the DNS server is basically setup, and more zones can be added as desired, each new domain will need a separate zone file, however, the reverse zones can live in one file as long as they are within the same subnet. I do also want to point out that the various config checks will not check that the paths within the config are valid, so, if the local addresses are not working, check that. I spend at least 30 minutes troubleshooting only to find that I missed a ‘b’ in the file path.
Ad Blocking
Now that we have the standard DNS stuff setup and working, let’s move on to ad blocking. This is something that can be done with things like pi-hole, but I want to block ads and use Internet grade DNS. Thankfully, this is a problem that has been tackled before. The following script was modified from the linked articled by bgstack15, which was adapted from Mueller-ma. That script had some other features such as block lists and allow lists, however, that is not a feature I care to use currently. Maybe I will update the script if I decide I need and/or want that feature. For now, let’s take a look at the script:
#!/bin/sh
# Create a workspace in /tmp to keep organized
mkdir /tmp/adblock
cd /tmp/adblock
# Setting some variables
blockedfile="/usr/local/etc/namedb/primary/empty.db"
zonefile="/usr/local/etc/namedb/named.conf"
# Fetching the desired domain list,
# checkout https://github.com/StevenBlack/hosts for all options
fetch https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts -o StevenBlack-hosts
# The original lists were meant to go in /etc/hosts, this prepares them to be turned into a zone entry
< StevenBlack-hosts awk '/^0\.0\.0\.0/ && ! /127.0.0.1|255.255.255.255|::1/ {print $2}' >> hosts
# This removes duplicates as well as commented lines
< hosts grep -vE '^\s*($|#)' | sort | uniq > uniq_hosts
# This turnes each domain into a zone entry
< uniq_hosts sed -r "s:(.*):zone \"\1\" { type master; file \"${blockedfile}\"; };:" > zone
# Copies the current named.conf minus adblocking entires for safety
sed "/Adblocking/q" ${zonefile} >> ${zonefile}.bk
# Moves just the normal configs back
cp ${zonefile}.bk ${zonefile}
# Add the adblocking zones to named
cat zone >> ${zonefile}
# Restart service for changes to take effect
service named restart
# Cleanup
rm /tmp/adblock/*
The only manual modification required for the named.conf
file is adding a comment containing the word “Adblocking” at the end of
the file. This is used to remove old entries that were being block via
the sed
command in the script.
It is generally best practice to copy the script to either the
/usr/local/bin
or /usr/local/sbin
directories,
then from there the script can be run via cron or other automation. The
source repo appears to be updated fairly often, so I plan on pulling a
new file every 2 weeks. It can also be run manually if needed
though.
General Notes
This section is a random collection of thoughts that didn’t really fit in anywhere else.
Issues with some tooling
The rndc
tool that is intended to manage bind is quite neat, however, I expect
that for most small bind instances, it is quite overkill. I also found
that it inconsistently works; there were many times in which I tried to
run rndc reload
, but the command would fail. To get around
this, I had to run service named onerestart
; this isn’t
particularly a problem as the workaround is was I would have done
without the other tool anyway, but it does make me want to use the tool
less. It is also possible there was a mis-configuration on my part, or
something else I did that made the tool operate this way. Certainly feel
free to try the tool, but issues may arise during that process.
Getting around ISP routers
I was in a situation where my router would not allow changing DNS servers. There are a few different solutions such as putting the router in bridge mode and bringing APs for WiFi, a router, and a firewall, but that was not something I could practically do right now. Another solution was buy a better router. While this solution is good and much less expensive than creating an enterprise grade network for my house, it is not free, and not something I was interesting in spending the money on right now. My solution ended up being creating a DHCP range of one static address, then implementing a DHCP server (on the same server as bind) that would set the DNS server as I wanted it. This got around the problem for all the machines I cared about.