PiHole and DNS over WireGuard
This is part of my series on renovating my homelabs to ring in the roaring ’20s.
I previously wrote about setting up a WireGuard VPN that allows me to connect to my LAN while offsite. Today I’ll be setting up a PiHole and explaining how I can use this to block ads for any device on my network or connected to my VPN.
Sections:
Setting up the PiHole
I’m going to run my PiHole as a containerized process, using the official Docker image. But, I’m running Fedora 31 - which uses cgroups v2 (that Docker doesn’t support) - so I’ll be using Podman. You can read more about that in my (upcoming) post on F31, cgroups v2, and Kubernetes.
The pattern I’ll be using is the officially recommended container lifecycle for Podman workflows:
- create the container with
podman
- manage the runtime with
systemd
The first thing we need to is translate their official docker-compose example in to a podman compatible script. For reference, here that is:
# pihole-docker-compose.yaml
version: "3"
services:
pihole:
container_name: pihole
image: pihole/pihole:latest
ports:
- "53:53/tcp"
- "53:53/udp"
- "67:67/udp"
- "80:80/tcp"
- "443:443/tcp"
environment:
TZ: 'America/Chicago'
volumes:
- './etc-pihole/:/etc/pihole/'
- './etc-dnsmasq.d/:/etc/dnsmasq.d/'
restart: unless-stopped
Which I turned in to the following pihole-create.sh
script which creates the container using podman:
#!/bin/bash
podman create \
--name pihole \
-p 53:53/tcp \
-p 53:53/udp \
-p 67:67/udp \
-p 80:80/tcp \
-p 443:443/tcp \
-e TZ=America/Chicago \
-v /etc/pihole/pihole:/etc/pihole:z \
-v /etc/pihole/dnsmasq.d:/etc/dnsmasq.d:z \
pihole/pihole:latest
As we can see, the podman
syntax is mostly equivalent to the docker
CLI syntax. In fact, we could alias docker=podman
and use it just like we use Docker, most of the time ;)
Notice we are not running the PiHole yet, we’ve just created the container. To start it (and manage it for the future), we use systemd.
I wrote this straightforward systemd unit to run the container:
# /etc/systemd/system/pihole.service
[Unit]
Description=PiHole Podman container
Wants=syslog.service
[Service]
Restart=always
ExecStart=/usr/bin/podman start -a pihole
ExecStop=/usr/bin/podman stop -t 10 pihole
[Install]
WantedBy=multi-user.target
And we can now enable and start the containerized PiHole process:
$ sudo systemctl enable --now pihole
Using enable
instead of just start
means the service will be started automatically at boot, so I never have to think about it again.
Note: one drawback that I have noticed with running PiHole with Podman is that all of the incoming requests have their source IP rewritten to the Podman NAT source
10.88.0.1
. I have tried to turn of NATing with no luck. Further, PiHole will not run with--net=host
on my machine because I have left “predictable interface names” enabled and it is hardcoded to expecteth0
- my primary interface iseno1
. This does not affect usability and is only cosmetic (PiHole’s metrics show al requests come from10.88.0.1
instead of my real clients) so I left it alone.
Finally, let’s check that the PiHole is running and blocking ad-related domains as expected:
$ dig doubleclick.net @192.168.0.53
; <<>> DiG 9.11.13-RedHat-9.11.13-3.fc31 <<>> doubleclick.net @192.168.0.53
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7561
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;doubleclick.net. IN A
;; ANSWER SECTION:
doubleclick.net. 2 IN A 0.0.0.0
;; Query time: 0 msec
;; SERVER: 192.168.0.53#53(192.168.0.53)
;; WHEN: Sat Jan 11 13:52:45 CST 2020
;; MSG SIZE rcvd: 60
Note in the ANSWER SECTION
that the name resolved to 0.0.0.0
. That means everything is working!
Using the PiHole for DNS
Now that we’re running an ad-blocking DNS server, we need to configure our other devices to use it and enjoy the ad-free Internet.
We can easily make it so that any device on the LAN uses the PiHole, and also enable it on all of the devices on our WireGuard mesh. I’ll start with applying it to my LAN.
Configuring the Gateway/Router
To route DNS traffic through the PiHole for all devices on the same network as it, we set the LAN DHCP server to use the PiHole as its DNS server. Unless you have configured it otherwise, the LAN DHCP server is usually the Gateway or Router provided by your ISP, or that is attached to the pipe from your ISP and exposed to the Internet.
The DHCP server is responsible for a things like assigning IP addresses to all the clients on your LAN. It also tells those clients what DNS servers they should use.
My Gateway has a couple of options for configuring the DNS server settings. By default, it’s set to “ISP”, meaning when my router just passes along the DNS server IPs from my ISP to its DHCP clients. I changed this to “Static”, and then put in my PiHole’s IP address as the primary DNS server.
In case the PiHole service crashes, I set a backup DNS IP to 1.1.1.1
, Cloudflare’s public DNS server, so that the devices on my network won’t experience any interruptions to their network access.
Then, I told the gateway to renew all the client DHCP leases so that all my clients immediately picked up the new DNS settings. If your router does not have a function for that, you can disconnect and reconnect a device to the network to force it to get a new lease, or you can wait until the DHCP leases expire and are automatically renewed. This usually happens within 24 hours.
Once the clients have renewed their DHCP, they get the new DNS config pointed at the PiHole and start sending their DNS requests to it.
Configuring off-network WireGuard clients
I previously wrote about setting up LAN peering with WireGuard, which makes using the PiHole from off-network clients easy. I want to use the PiHole from my smartphone while not connected to my WiFi, or from my laptop while traveling - both are possible as long as I can establish a WireGuard tunnel.
To set this up, I need to make a small change in the /etc/wireguard/wg0.conf
on my laptop:
[Interface]
# Name = wg0
...
DNS = 192.168.0.53 # <- this is the LAN IP address of my PiHole
[Peer]
# this is a Peer on my PiHole's LAN - not necessarily the same machine
# if not, it needs to be configured to do IP forwarding and masquerading
...
AllowedIPs = 172.16.4.1, 192.168.0.0/24 # <- this is my LAN CIDR
After this change whenever I connect my WireGuard VPN this peer will send its DNS requests through my PiHole.
To set it up on my phone I make the same changes: adding the PiHole IP address to the DNS
field of the WireGuard config, and making sure that routing Peer has the LAN CIDR in it’s AllowedIPs
.
Now all of my devices are forever ad-free, at home or on the go!
Read the other articles in this series here:
- New Year, New Lab
- #TODO - Epyc EKWB liquid cooled server build
- ZFS on Linux, ZED, and Postfix
- Configuring Postfix with Gmail
- WireGuard VPN mesh
- PiHole and DNS over WireGuard
- Private DNS with CoreDNS
- #TODO - VFIO GPU Passthrough
- #TODO - Networking: Unifi, VLANs, and (Core)DNS localzones over WireGuard
- Rescuing a bad Fedora upgrade via chroot