Why not Pi-hole?
Pi-hole needs no introduction; it has been a homelab staple for years. However, its popularity has also led to it being used in situations where it is not needed. If your goal is to self-host a recursive DNS resolver with ad blocking, you’ll need to run Unbound too. Instead of playing with cute web interfaces, we can simplify our networks by leveraging Response Policy Zones (RPZ). On top of that, Pi-hole only runs on Linux.
This isn’t for everyone; it requires familiarity with the command line. My goal is just to show a better integrated alternative to Pi-Hole for OpenBSD routers.
Configuring Unbound
Since Unbound is included in base, we do not need to install anything. The configuration takes place in /var/unbound/etc/unbound.conf because it runs in a chroot for extra separation.
Modify the config to something like:
remote-control:
control-enable: yes
control-interface: /var/run/unbound.sock
server:
interface: 127.0.0.1
interface: ::1
hide-identity: yes
hide-version: yes
root-hints: "/var/unbound/db/named.cache"
prefetch: yes
auto-trust-anchor-file: "/var/unbound/db/root.key"
prefetch-key: yes
# The order of these matters!
module-config: "respip validator iterator"
define-tag: "ads nsfw"
# You might want to change these to your subnets
access-control-tag: 0.0.0.0/0 "ads nsfw"
access-control-tag: ::/0 "ads nsfw"
rpz:
name: oisd-ads
url: https://small.oisd.nl/rpz
tags: "ads"
rpz:
name: oisd-nsfw
url: https://nsfw-small.oisd.nl/rpz
tags: "nsfw"
I’m not going to break this down line by line. If you need help understanding the config, start with the man page.
Validate the config with unbound-checkconf, because rcctl
configtest unbound doesn’t work for whatever reason.
Add the root hints to the chroot
Recursive resolvers need to know where to start looking for DNS records. The root hints file provides the IP addresses of the 13 authoritative root nameservers that anchor the global DNS hierarchy.
cd /var/unbound/db/
ftp https://www.internic.net/domain/named.cache
Starting Unbound
Enable the service and increase the startup timeout. Larger RPZ files will increase the time it takes for the daemon to initialize, so we tell rc that the wait is expected.
rcctl set unbound timeout 60
rcctl enable unbound
rcctl start unbound
Redirect all DNS traffic on your network to Unbound
Then add this little trick to /etc/pf.conf so that all incoming
local traffic on port 53 is routed to unbound. This doesn’t include
the router itself.
pass in on !egress inet proto { udp tcp } from any to self \
port domain rdr-to 127.0.0.1
pass in on !egress inet6 proto { udp tcp } from any to self \
port domain rdr-to ::1
Now run pfctl -f /etc/pf.conf and you should be done!
Alternatively, you could advertise the resolver in dhcpd.conf and rad.conf like a normal person.
Testing
Modern web browsers often try to handle DNS internally (DoH), which can make command-line tests deceiving. So I recommend using web-based tests check to see if DNSSEC and the RPZ for ad blocking are working.
If these tests fail, check your browser’s “Secure DNS” settings; it might be bypassing the system resolver.
RPZ files
If you have the available memory, I suggest using the big lists from OISD.nl. Another source I’ve found is hagezi/dns-blocklists. Writing your own RPZ should also be self explanatory after you look at the syntax of an already existing zone.
Additional notes
- BIND supports RPZ too but I’ve never used it
- If you want remote access take a look at wg
- YouTube video of Paul Vixie explaining the current state of DNS
- This article was inspired by this guy using Pi-hole
- I found the pf trick on Tedu’s blog