Lightweight ad-blocking with dnsmasq and Raspberry Pi

Did you know that you can put that spare Raspberry Pi of yours to work as a caching DNS server and ad-blocker using off-the-shelf packages for Linux?

Image for post
Image for post
Stop adverts and the privacy invasion caused by behavioural trackers

Now, first of all let’s get something out of the way. Many of us will be aware of the open source project written in PHP called “PiHole.” PiHole automates dnsmasq and adds a UI dashboard on top, so that you can geek out over the how many adverts you may have blocked.

So why would we want to deal with dnsmasq, if PiHole is so great? Well, first of all, it means running PHP, a webserver, and a database on your poor little Raspberry Pi, and these are all unnecessary for the task of blocking ads. It also means that many users have little to no understanding of what PiHole is actually doing under the hood.

In this post I’ll show you what dnsmasq can do on its own. We will setup a Raspberry Pi as a caching DNS server, and then show how it can also be used with a block-list of domains, and finally, how you can get all of your devices on your home-network to use it.

Overview

There’ll be six parts to this post and along the way, I’ll provide my own insights and feedback as I set this up for myself.

  1. Pre-reqs and configuring the Operating System
  2. Setting up dnsmasq on RaspiOS (aka Raspbian) as a caching DNS server
  3. Testing out the caching DNS server
  4. Sourcing and configuring a block list for ads and privacy trackers
  5. Configuring our home network to use the new DNS server
  6. Wrapping up — usability and other approaches to blocking ads

1. Pre-reqs

Once this project is in place, all DNS requests from your devices will be made through the Raspberry Pi. For that reason, you do not want to run this over WiFi, so whether you’re using a Raspberry Pi Zero with a USB ethernet adapter, or a RPi4, may attention here. Half of the benefits of running a local DNS server are to reduce latency, using WiFi will only make webpages slower to open.

Image for post
Image for post
My Raspberry Pi Zero bought from https://pimoroni.com

If I was trying to get the absolute lowest latency, and highest performance, then I would go with an RPi4 with 2GB RAM for around 35USD. Given that I have some spare RPi Zeros and wanted to put them to work, it meant acquiring a USB ethernet adapter. Not all adapters work, so if you’re buying one, look out for the chipset in mine (AX88772B):

pi@zero-dns:~ $ lsusbBus 001 Device 002: ID 0b95:772b ASIX Electronics Corp. AX88772B

Flash the Operating System to your SD card

Download RaspiOS Lite, the 32-bit edition will do and works on all the devices at present.

Now use Etcher.io to flash the operating system to your SD card.

Then make a file named ssh in the boot directory before powering on the device.

2. Setting up dnsmasq on RaspiOS (aka Raspbian) as a caching DNS server

dnsmasq as described on Wikipedia:

So dnsmasq can also offer DHCP and netbooting features. We’ll come back to that later.

First install the package if you don’t already have it:

sudo apt update -qy
sudo apt install dnsmasq

dnsmasq can offer up IP addresses, so there is a risk that we could interfere with our local network, make sure that the service is stopped

sudo systemctl stop dnsmasq

Now make a copy of the config file:

sudo cp /etc/dnsmasq.conf /etc/dnsmasq.conf.bak
sudo rm /etc/dnsmasq.conf

Now create a new file /etc/dnsmasq.conf with the following contents:

no-dhcp-interface=eth0
cache-size=1000
domain-needed
bogus-priv
dns-forward-max=150
resolv-file=/etc/resolv.dnsmasq
no-poll
log-queries

Let’s walk through the config file:

  • no-dhcp-interface=eth0 — this disables the DHCP server, we don’t need it at this time. Check that your ethernet adapter is actually called eth0 by running ifconfig or ip addr and viewing the output
  • cache-size=1000 — since our DNS server can also cache requests, that will reduce latency for subsequent lookups. This sets the cache to 1000 entries.
  • dns-forward-max=150 — this line limits the concurrent DNS requests that can be made on behalf of your network
  • resolv-file=/etc/resolv.dnsmasq — usually RaspiOS will look up DNS entries from a “resolv” file at /etc/resolv.conf, so we instruct dnsmasq to use a separate file with the “upstream” DNS servers it will forward messages to
  • no-poll — this setting is for efficiency, you can have dnsmasq constantly poll for new DNS servers in the resolv-file, we don’t require this
  • log-queries— this is an optional setting, which writes any DNS lookups into the journal, so that you can view them as they happen. I find it useful

Create a file for your upstream DNS servers: /etc/resolv.dnsmasq

Let’s add Google, OpenDNS, and Cloudflare with their primary and secondary DNS servers:

nameserver 8.8.8.8
nameserver 8.8.4.4
nameserver 208.67.222.222
nameserver 208.67.220.220
nameserver 1.1.1.1
nameserver 1.0.0.1

If you have a preference for one or the other, just comment them out as you see fit. This config gives us six separate high-speed DNS servers to send queries onto.

Start the server and test it out

sudo systemctl daemon-reload
sudo systemctl restart dnsmasq

3. Test out the DNS server

From your local computer, run nslookup. This is installed by default on MacOS, but you may need to add a package if you use Linux on your desktop.

Now use the IP of your Raspberry Pi in the following command:

export IP=192.168.0.65time nslookup medium.com $IP

You should see that the initial request takes slightly longer, and the subsequent one is faster:

Server:  192.168.0.65
Address: 192.168.0.65#53
Non-authoritative answer:
Name: google.com
Address: 172.217.169.14
real 0m0.259s

The second result is clearly cached:

Server:  192.168.0.65
Address: 192.168.0.65#53
Non-authoritative answer:
Name: google.com
Address: 172.217.169.14
real 0m0.008s

And faster than going direct, when cached:

Server:  192.168.0.65
Address: 192.168.0.65#53
Non-authoritative answer:
Name: google.com
Address: 172.217.169.14
real 0m0.038s

If you’re a MacOS user, then navigate to your System Preferences, and click on Network Settings.

If you’re working on a plugged-in connection, then pick Ethernet, otherwise click Wi-Fi, then Advanced:

Image for post
Image for post
Network Settings
Image for post
Image for post

Then type in the IP of your Raspberry Pi, and click OK

From then on, your web-browser, applications and CLI utilities will all use the default DNS server, your Raspberry Pi:

Image for post
Image for post

You can set up a live-view of the logs on your Raspberry Pi with the following command:

sudo journalctl -u dnsmasq -f

The -f command is optional, and tails the logs.

Image for post
Image for post

Since I have a tab open for Slack, and one open for Gmail, you can see the DNS lookup requests being made and subsequently cached.

4. Sourcing and configuring a block list for ads and privacy trackers

Now, you could stop here and get some benefit from running a private DNS server, that caches your results, but there’s more we can do.

We can block advertisements and websites that tracker our usage and behaviour. This kind of information is used to build an online persona and then to target us with advertisements in the future, the usage data is also often sold to third-parties.

The simplest way to block both ads and trackers, is to obtain a list of their domains, and make them resolve to 127.0.0.1, our local computer. It’s simple but effective.

Log onto your Raspberry Pi again.

Edit /etc/dnsmasq.conf and add:

conf-file=/home/pi/dnsmasq.blacklist.txt

Now download a blacklist. You can shop around here and find one you like the look of, or combine lists together. Simply put, they are lists of IPs and domains, so they are easy to read and review.

Head over to https://github.com/notracking/hosts-blocklists and checkout the list on offer there.

Now download the list and place it at /home/pi/dnsmasq.blacklist.txt

curl -SLso /home/pi/dnsmasq.blacklist.txt https://raw.githubusercontent.com/notracking/hosts-blocklists/master/dnsmasq/dnsmasq.blacklist.txt# Check it downloaded OKhead /home/pi/dnsmasq.blacklist.txt

This is a long file — around 6–7MB and contains lots of entries.

Now restart dnsmasq:

sudo systemctl daemon-reload
sudo systemctl restart dnsmasq

Finally, check it worked with ping and one of the sites in the dnsmasq.blacklist.txt file.

ping -c1 020business.comPING 020business.com (0.0.0.0): 56 data bytesping: sendto: No route to host

In this instance, the host was directed to 0.0.0.0. If you have another computer, you’ll be able to run the same command and see its actual IP address which I got as (154.210.22.147)

So if a web-page tries to download a tracking pixel from that domain it will fail, if a piece of JavaScript attempts to make a HTTP request to that domain, well it can’t.

5. Configuring our home network to use the new DNS server

We’ve now configured our Mac to use the new caching DNS server, we’ve seen it working and speeding up cached lookups. We can even see that it blocks domains in our downloaded blacklist.

So what’s left?

If you are fortunate, then your home router will allow you to simply set a DNS override for any devices on the network, or you can go into each device and set it manually.

However if you have a Virgin Media SuperHub 3.0, you cannot edit the DNS settings on your router, and I suspect that’s the same for many other devices. On an Android phone, I couldn’t even set a manual DNS entry.

Having a DNS server that we can only use on our PC is of limited use, so how do we overcome that problem?

Unfortunately, it’s not pretty. We have to disable the DHCP server on our router, and then call upon dnsmasq’s special ability to give our IP addresses since it can also set a custom DNS entry.

Check carefully what your IP range is at home, it should be 192.168.0.0/24 or 192.168.1.24/24 with a subnet mask of 255.255.255.0 (aka /24). Also note down your router’s IP address, this is usually 192.168.0.1 or 192.168.0.254.

On a Mac, you can find this info in the Network Settings page, see the screenshot from earlier.

Edit the dnsmasq.conf file and restart dnsmasq:

interface=eth0
listen-address=192.168.0.65
dhcp-range=192.168.0.100,192.168.0.250,1h
dhcp-option=option:router,192.168.0.1

Update listen-address with the IP you have on your Raspberry Pi. You will also need to make this a static IP for the device, since the router won’t be able to issue it anymore after these steps.

This config gives you a range of 192.168.0.100–250 for the client devices, feel free to edit it.

Finally, you can also test your new DHCP server before turning the one on your router off, by making dnsmasq only answer for the MAC address of certain clients. You can usually find this address using ifconfig, ip addr, or via your Settings menu.

One of my PCs has the MAC address of DC:A6:32:03:33:7B so I just added the below:

dhcp-ignore=tag:!known
dhcp-host=DC:A6:32:03:33:7B,set:local

You can remove this section once you are sure that your DHCP server is working as expected.

Add to /etc/dhcpcd.conf:

interface eth0
profile static_eth0
static ip_address=192.168.0.65/24
static routers=192.168.0.1
static domain_name_servers=127.0.0.1

Now restart dnsmasq:

sudo systemctl daemon-reload
sudo systemctl restart dnsmasq

Next, disable DHCP on your router.

You can now check your DNS settings on your iPhone, Android device, or other laptops and Raspberry Pis, they should all be using your new DNS server and benefiting from caching and ad-blocking.

If you would like to check that everything’s working as expected, just run the log command from earlier:

sudo journalctl -u dnsmasq -f

6. Wrapping up

In this post we’ve done everything ourselves to set up a very minimal DNS server using a standard package often included with Linux called dnsmasq.

Rather than trusting in automation, and heavy-weight flashy UIs, statistics and databases, we were able to run a few commands, understand exactly what was happening and still get the same benefits.

So is ad-blocking for you?

One of the first things I found when I switched on the ad-blocker, was that Google Search became slightly less useful. The links at the top of a search, which are adverts, and part of the shopping experience, are blocked. This is because Google was using them to track our behaviour.

Image for post
Image for post
Image for post
Image for post

You will find the same happens with other search engines, even those which do not track your behaviour, like DuckDuckGo.

One of my blog posts that uses image links from Amazon, and referral links entitled: Building a Linux Desktop for Cloud Native Development, no longer works or displays as it should:

Image for post
Image for post

Can you see the broken image?

So with ad-blocking at the DNS level, we do get a global block across our whole network, but when using a browser-level ad-blocker or plugin, we can fine-tune which ads we allow through, and whether we want to see referral images on a certain page.

FireFox is also making strides into “Do Not Track” technology, in a post on the Verge about the browser, the author shows how to use some of the features including anti-fingerprinting techniques.

So for me, I think I may revert back to browser-level ad-blocking, but retain the caching DNS server — and I’m completely free to switch back again at any time, thanks to being able to host everything on a tiny Raspberry Pi.

You may also enjoy my other tutorials on Medium:

Get a public IP and endpoint for your Raspberry Pi using an open-source, cloud-native tunnel:

Upgrade your home-office, sound professional, and look the part.

Dust off another Raspberry Pi, and find out what you can do with clustering, in 15 mins.

Written by

CNCF Ambassador. OpenFaaS & Inlets founder — https://www.alexellis.io

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store