Main Page

DNS ad sinkhole on a Raspberry Pi 3 with FreeBSD

20 October 2021

This is similar in concept to a Pi-Hole, only much lighter weight. We'll be using:

Start by grabbing the FreeBSD-13.0-RELEASE-arm64-aarch64-RPI.img.xz image from the link above and verifying the SHA512 checksum. Then, uncompress it and write it to the microSD card you'll be using in your RPI3. I won't go over any of this here because the exact process depends on your OS.

Once your RPI3 is booted up, SSH in with user freebsd and password freebsd. The first thing you should do is change the root password and the password for the default user (freebsd). The default root password is root.

        $ passwd
        $ su
        # passwd

I also suggest using keys for SSH instead of passwords, but you can find tons of guides on setting that up elsewhere, so I won't go into it.

Let's set up a static IP address and change the hostname, and also enable ntpd for time. You don't want the IP to change, because then everything will explode. Open up /etc/rc.conf

        # vi /etc/rc.conf

Either add these lines if they're missing or change them if they already exist and say something different. You'll want to set defaultrouter to the IP of your router/gateway, and then set the value after inet on the ifconfig_ue0 line to something outside of your router/gateway's DHCP pool. Be sure to comment out that DHCP line.

        ifconfig_ue0="inet netmask"

Now, we need to install all the fun latest patches and whatnot. This will take a minute, and you'll be prompted to respond a couple of times. Then, reboot the pi after it's completed. When it comes back up, ssh back in (to the IP you put above) and su back to the root user.

        # freebsd-update fetch install

Once we're back up and you're root again, install wget, git, and doas. It's going to prompt you to install pkg, say y and let it do its thing.

        # pkg install wget git doas

To set up doas, which is an alternative to sudo developed by the OpenBSD project, we need to create doas.conf and just add two lines to it.

        # vi /usr/local/etc/doas.conf

        permit nopass keepenv root
        permit persist keepenv freebsd

Now we're going to grab the adblock-unbound sources, then move it to where it's going to live. We don't really need --depth=1 here because there's a whopping 5 commits in the adblock-unbound repo, but I do it out of habit when I'm just grabbing something and won't be actually working on it (and don't need the full history).

        # git clone --depth=1
        # chmod +x adblock-unbound/
        # mv adblock-unbound /usr/local/bin/

Okay, time to set up local-unbound. First, we're going to switch to local-unbound's config directory. Next, we'll grab the root hints file named.cache. Then we'll grab the root key so we can deal with DNSSEC.

        # cd /var/unbound
        # ftp
        # local-unbound-anchor -a root.key

Time to add the actual configuration for local-unbound.

        # vi unbound.conf

There's a lot of options here, basically we're telling it to hide info about our server here, send the minimum amount of info with DNS queries, and only allow connections from the local network. We're also telling it where the blocklist will live (we'll get to that next). Put this in the file and save it. If you want more info about what everything means, you can check man unbound.conf (or look at the documentation linked at the bottom of this post).

            username: unbound
            directory: /var/unbound
            chroot: /var/unbound
            pidfile: /var/run/
            auto-trust-anchor-file: /var/unbound/root.key
            root-hints: /var/unbound/named.cache
            access-control: refuse
            access-control: allow
            access-control: allow
            logfile: /var/unbound/unbound.log
            port: 53
            do-ip4: yes
            do-ip6: yes
            do-tcp: yes
            do-udp: yes
            hide-identity: yes
            hide-version: yes
            qname-minimisation: yes
            unblock-lan-zones: yes
            insecure-lan-zones: yes

        include: /var/unbound/conf.d/ads.conf

            control-enable: yes
            control-interface: /var/run/local_unbound.ctl
            control-use-cert: no

We need to fetch the initial blocklist. Run these three commands:

        # mkdir conf.d
        # RECIPE=master WITH_IPV6=false /usr/local/bin/adblock-unbound/ > /var/unbound/conf.d/ads.conf
        # chown -R unbound:unbound conf.d

Let's add the script that cron will call to update our block list. I know it doesn't really need to be executable because we can always just call it with sh. Add the lines to the file and save it.

        # touch
        # chmod +x
        # chown unbound:unbound
        # vi
        RECIPE=master WITH_IPV6=false /usr/local/bin/adblock-unbound/ > /var/unbound/conf.d/ads.conf
        local-unbound-control reload

Now let's set local_unbound to start at boot and actually start it up.

        # sysrc local_unbound_enable="YES"
        # service local_unbound start

Now let's try to see if it works. Run this from your personal computer, not the Pi.

        $ dig @ +dnssec

Did it work? Cool. Now we need to tell cron to update our fun little block list. I have it set to run at 10:00 UTC which is currently 6AM for me here on the east coast of the US, since we're in DST. Crack open a cold one the crontab, then add the magic line to it and save.

        # doas -u unbound crontab -e

        0 10 * * * /bin/sh /var/unbound/ >/dev/null 2>&1

And, uh, yeah. We're basically done with the setup on the Pi. Now you need to go into your router's admin panel, go to the DHCP settings, and set the IP of your Pi, such as, as the DNS server to advertise to connected clients. I use an Asus RT-AC66U, if you use the same or similar, log in to the admin panel and then:

        Advanced Settings > LAN
        DHCP Server tab
        DNS and WINS Server Setting
        DNS Server -->

Then disconnect whatever devices you have and reconnect them to the network.