Below is a small tutorial on how you can create your own recursive DNS server using Unbound, adding custom records to block ads (plus fakenews, porn and/or social websites), on Apple macOS. Also, you can use DNS over TLS if needed/wanted.
Installation
Unbound is already in Homebrew so installing it is just a matter of running:
$ brew install unbound
Configuration
Find a free unique id for the unbound
user in the 1-500 range (reserved for system accounts). For example, 444
.
$ dscl . -list /Groups PrimaryGroupID | grep 444
$ dscl . -list /Users PrimaryGroupID | grep 444
If you have no output for those two commands, you can proceed to actually create the user and group.
$ sudo dscl . -create /Groups/_unbound
$ sudo dscl . -create /Groups/_unbound PrimaryGroupID 444
$ sudo dscl . -create /Users/_unbound
$ sudo dscl . -create /Users/_unbound RecordName _unbound unbound
$ sudo dscl . -create /Users/_unbound RealName "Unbound DNS server"
$ sudo dscl . -create /Users/_unbound UniqueID 444
$ sudo dscl . -create /Users/_unbound PrimaryGroupID 444
$ sudo dscl . -create /Users/_unbound UserShell /usr/bin/false
$ sudo dscl . -create /Users/_unbound Password '*'
$ sudo dscl . -create /Groups/_unbound GroupMembership _unbound
Fetch the root key required for DNSSEC validation:
$ sudo unbound-anchor -a /usr/local/etc/unbound/root.key
Create the certificates needed:
$ sudo unbound-control-setup -d /usr/local/etc/unbound
Here is a single-line command that will download the StevenBlack hosts list (fakenews + gambling + porn + social, so keep that in mind), convert it for unbound and save it in /usr/local/etc/unbound/zone-block-general.conf
. Unbound will respond with NXDOMAIN to all the domains in this list.
$ (curl --silent https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn-social/hosts | grep '^0\.0\.0\.0' | sort) | awk '{print "local-zone: \""$2"\" refuse"}' > /usr/local/etc/unbound/zone-block-general.conf
Let’s configure unbound
as an authoritative, validating, recursive caching DNS server. The lines below go to your /usr/local/etc/unbound/unbound.conf
:
server:
# log verbosity
verbosity: 1
interface: 127.0.0.1
access-control: 127.0.0.1/8 allow
chroot: ""
username: "_unbound"
auto-trust-anchor-file: "/usr/local/etc/unbound/root.key"
# answer DNS queries on this port
port: 53
# enable IPV4
do-ip4: yes
# disable IPV6
do-ip6: no
# enable UDP
do-udp: yes
# enable TCP, you could disable this if not needed, UDP is quicker
do-tcp: yes
# which client IPs are allowed to make (recursive) queries to this server
access-control: 10.0.0.0/8 allow
access-control: 127.0.0.0/8 allow
access-control: 192.168.0.0/16 allow
root-hints: "/usr/local/etc/unbound/root.hints"
# do not answer id.server and hostname.bind queries
hide-identity: yes
# do not answer version.server and version.bind queries
hide-version: yes
# will trust glue only if it is within the servers authority
harden-glue: yes
# require DNSSEC data for trust-anchored zones, if such data
# is absent, the zone becomes bogus
harden-dnssec-stripped: yes
# use 0x20-encoded random bits in the query to foil spoof attempts
use-caps-for-id: yes
# the time to live (TTL) value lower bound, in seconds
cache-min-ttl: 3600
# the time to live (TTL) value cap for RRsets and messages in the cache
cache-max-ttl: 86400
# perform prefetching of close to expired message cache entries
prefetch: yes
num-threads: 4
msg-cache-slabs: 8
rrset-cache-slabs: 8
infra-cache-slabs: 8
key-cache-slabs: 8
rrset-cache-size: 256m
msg-cache-size: 128m
so-rcvbuf: 1m
private-address: 192.168.0.0/16
private-address: 172.16.0.0/12
private-address: 10.0.0.0/8
private-domain: "home.lan"
unwanted-reply-threshold: 10000
val-clean-additional: yes
# additional blocklist (Steven Black hosts file, read above)
include: /usr/local/etc/unbound/zone-block-general.conf
remote-control:
control-enable: yes
control-interface: 127.0.0.1
server-key-file: "/usr/local/etc/unbound/unbound_server.key"
server-cert-file: "/usr/local/etc/unbound/unbound_server.pem"
control-key-file: "/usr/local/etc/unbound/unbound_control.key"
control-cert-file: "/usr/local/etc/unbound/unbound_control.pem"
If you want to use DNS over TLS, you can forward requests to a TLS-capable recursive server, for example Cloudflare (1.1.1.1) or Quad9 (9.9.9.9). Add the lines below to your /usr/local/etc/unbound.conf
:
forward-zone:
name:"."
# use Quad9
forward-addr:9.9.9.9@853
# or Cloudflare
# forward-addr:1.1.1.1@853
forward-ssl-upstream:yes
The unbound
process needs read and write permissions for the configuration directory, use staff
as group so the user can use unbound-control
:
$ sudo chown -R _unbound:staff /usr/local/etc/unbound
$ sudo chmod 640 /usr/local/etc/unbound/*
If you want to start unbound
at boot, you need to create the /Library/LaunchDaemons/net.unbound.plist
file and place those lines in it:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.unbound</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/sbin/unbound</string>
<string>-d</string>
<string>-c</string>
<string>/usr/local/etc/unbound/unbound.conf</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Start the Unbound daemon:
$ sudo launchctl load /Library/LaunchDaemons/net.unbound.plist
Stop (when needed) the Unbound daemon:
$ sudo launchctl unload /Library/LaunchDaemons/net.unbound.plist
Set your local DNS server as default for the Wi-Fi connection:
$ networksetup -setdnsservers Wi-Fi 127.0.0.1
Check if DNS was set and everything is ok:
$ networksetup -getdnsservers Wi-Fi
127.0.0.1
Testing DNSSEC for your new DNS resolver is easy, using dig
:
$ dig org. SOA +dnssec @127.0.0.1
; <<>> DiG 9.10.6 <<>> org. SOA +dnssec @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8381
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 7, ADDITIONAL: 1
...
The ad
flag is short for Authenticated Data and means DNSSEC is working.