logo
Jiff Slater
🤔 About
✍️ Contact
📚Knowledge
30 Jul 2021
These articles have been archived. You may find them useful but I am no longer offering support for them. Check out my latest articles on plkt.io.
Setting up a Docker Pi-hole DNS server for wired and wireless clients
21 November 2018

Pi-Hole is a DNS resolver that prevents the resolution of common ad-hosting networks. I have a server in my household that I wanted to run as a Pi-hole server for both Ethernet and wireless clients. Here’s how I did it. Keep in mind that when changing the network configuration it’s wise to do it incrementally and test each step to avoid making a mistake and not being able to troubleshoot. In addition, Pi-hole was originally designed to be the only thing installed on a Raspberry Pi so to make the configuration less invasive on my existing system, I’ll be using the official Docker container. For a much simpler installation, go ahead and run the curl | bash command on their home page.

Network topology

You’ll need to get a good idea of your current network topology before continuing. In my case, I wanted to let this be opt-in for other clients on the network because I didn’t want to cache other people’s DNS requests. This means I wouldn’t alter the DNS settings on the router.
First, I mapped out my current network topology. This is pretty easy to do if you just trace the cables in the house. Your set up will probably match mine:

A simpler home set up might only have wireless clients.

My configuration mirrors the above and my server is connected to the switch mentioned. Next step is to look at the current configuration according to your devices. You’ll need to gather the interface settings for your router and your server.

In my case,

With this in mind, we want to configure the server to act as a wireless hotspot for the Ethernet connection while also providing DNS for both wireless and wired clients. Fortunately, this is pretty simple to do, once you know which apps and files are needed.
This guide uses Debian 9 and NetworkManager.
First, we’ll configure the wireless access point and make sure clients can connect. Look at your current configuration:

$ nmcli
eno1: connected to Wired connection 1
"Intel Ethernet Connection I217-LM"
ethernet (e1000e), AA:BB:CC:DD:EE:FF, hw, mtu 1500
ip4 default
inet4 192.168.0.2/24
route4 169.254.0.0/16

wlp3s0: disconnected
"Intel Wireless"
wifi (iwlwifi), AA:BB:CC:DD:EE:FF, hw

lo: unmanaged
loopback (unknown), 00:00:00:00:00:00, sw, mtu 65536

DNS configuration:
servers: 194.168.4.100 194.168.8.100
interface: eno1
Next, create a wireless hotspot, confirm you can connect, and then delete it.
$ sudo nmcli --show-secrets dev wifi hotspot
Hotspot password: xMNUYLGH
Device 'wlp3s0' successfully activated with '95f843c0-18b4-4133-a27f-9d3eb12be8e7'.
[.. connect to the device ..]
$ sudo nmcli connection down uuid 95f843c0-18b4-4133-a27f-9d3eb12be8e7
$ sudo nmcli connection delete uuid 95f843c0-18b4-4133-a27f-9d3eb12be8e7

Now that we’re certain we can create a hotspot we can configure it to our preferences.

Pi-hole with Docker

Installing Docker is relatively simple. We’ll enable the HTTPS functionality for their repository and then download the Community Edition of Docker.

$ sudo apt install gnupg2 curl ca-certificates apt-transport-https software-properties-common

Install their GPG key. You can verify the fingerprint by comparing the output from the below command with their official documentation [link]. Last time I checked, the fingerprint’s last 8 characters were: 0x0EBFCD88.

$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

Next, enable the stable repository for this release. In my case I’m using Debian Stretch.

$ sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/debian \
$(lsb_release -cs) \
stable"

Finally, download Docker.

$ sudo apt update
$ sudo apt install docker-ce

Confirm that it works.

$ sudo docker run hello-world

If this works, add yourself to the Docker group and log out and then log in.

$ sudo usermod -aG docker `whoami`

Now we can launch the Pi-hole Docker container and configure it to act as a DNS server. We’ll use the following configuration settings.

$ docker pull pihole/pihole
$ mkdir -p ~/local/docker/pihole/pihole/etc/{pihole,dnsmasq.d}
$ docker run \
--name pihole \
-p 80:80 \
-p 53:53/tcp \
-p 53:53/udp \
-p 443:443/tcp \
-p 443:443/udp \
-v ~/local/docker/pihole.pihole/etc/pihole:/etc/pihole \
-v ~/local/docker/pihole.pihole/etc/dnsmasq.d:/etc/dnsmasq.d \
--dns=127.0.0.1 \
--dns=1.1.1.1 \
-e ServerIP=192.168.0.2 \
-e IPv6=False \
-e DNS1=192.168.4.100 \
-e DNS1=192.168.8.100 \
-e WEBPASSWORD=password \
pihole/pihole:latest

If you get some sort of error such as “Couldn’t bind to :80 because already in use”, correct the error, delete the container, and try again.

$ sudo systemctl stop apache2
$ sudo systemctl disable apache2
$ docker container list -a
$ docker container rm <container>

Now finally, connect to your container by navigating to http://<server_ip> on a different computer.

You can also check that your container has network access by:

$ docker container exec pihole ping www.google.com

Now the Docker container is up and running, go ahead and change the settings on your wired interface to use the IP address of your server as the DNS address.

For wireless clients, we’ll go ahead and configure the hotspot again, this time setting the DNS to use our server. Notice that due to installing Docker our networking configuration has changed.

$ sudo nmcli
docker0: connected to docker0
bridge, 02:42:FB:FA:35:DE, sw, mtu 1500
inet4 172.17.0.1/16
inet6 fe80::42:fbff:fefa:35de/64

veth9259d68: unmanaged
ethernet (veth), 72:FD:6C:AD:CE:D9, sw, mtu 1500

DNS configuration:
servers: 194.168.4.100 194.168.8.100
interface: eno1

Now we have two more interfaces: docker0 and veth9259d68. Unfortunately, on my end when I create the hotspot, clients aren’t issued an IP address. Let’s debug NetworkManager and see what routes are being created.

Create the hotspot with nmcli

$ sudo nmcli --show-secrets dev wifi hotspot

Now, we’ll use the lower level networking tools to see what’s happening.

$ ip r
default via 192.168.0.1 dev eno1 proto static metric 100
10.42.0.0/24 dev wlp3s0 proto kernel scope link src 10.42.0.1 metric 600
169.254.0.0/16 dev eno1 scope link metric 1000
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.0.0/24 dev eno1 proto kernel scope link src 192.168.0.2 metric 100

Next, let’s look at the configuration file NetworkManager creates for the hotspot.

$ cat /etc/NetworkManager/system-connections/Hotspot
[connection]
id=Hotspot
uuid=2473d7a3-4e0f-40d9-b239-72e52c6fad63
type=wifi
autoconnect=false
permissions=

[wifi]
hidden=true
mac-address=AC:FD:CE:87:84:D0
mac-address-blacklist=
mode=ap
ssid=Hotspot-luv

[wifi-security]
group=ccmp;
key-mgmt=wpa-psk
pairwise=ccmp;
proto=rsn;
psk=ZoKpIEU4

[ipv4]
dns-search=
method=shared

[ipv6]
addr-gen-mode=stable-privacy
dns-search=
method=ignore

Here, the culprit is the [ipv4] method=shared line. In the nm-setting-ip4-config.c file, we can see the following description for this setting.

* NetworkManager supports 5 values for the #NMSettingIPConfig:method property
* for IPv4. If “auto” is specified then the appropriate automatic method
* (DHCP, PPP, etc) is used for the interface and most other properties can be
* left unset. If “link-local” is specified, then a link-local address in the
* 169.254/16 range will be assigned to the interface. If “manual” is
* specified, static IP addressing is used and at least one IP address must be
* given in the “addresses” property. If “shared” is specified (indicating that
* this connection will provide network access to other computers) then the
* interface is assigned an address in the 10.42.x.1/24 range and a DHCP and
* forwarding DNS server are started, and the interface is NAT-ed to the current
* default network connection. “disabled” means IPv4 will not be used on this
* connection.

So from this description, it seems like the problem is the DHCP and forwarding DNS server aren’t starting correctly. Let’s look at the NetworkManager logs and see if anything is awry. We’ll also stop the Pi-hole container to avoid any other issues.

$ docker stop pihole
$ sudo journalctl -u NetworkManager --since "1 hour ago"

Walking through the logs is quite enlightening. (1) We see that NetworkManager creates IPtables entries for the interface, including to forward DNS and DHCP ports to the local DNSmasq instance. (2) We see that dnsmasq-manager failed to create a listening socket due to the address already in use by the Docker container.

Now – before rushing ahead and trying to fix this, it’s important to restate what we’re trying to accomplish here. Approaching the problem with the mindset of “how do I fix this” is wrong and will lead you down a DuckDuckGo / StackOverflow rabbit hole. In this scenario, we’re trying to issue an IP address to clients on the wlp3s0 interface. In addition, we want these clients to use the server as the DNS server so their DNS requests go through the Pi-hole Docker container.

Modify the default settings for shared IP interfaces.

$ sudo vim /etc/NetworkManager/dnsmasq-shared.d/default.conf
# Disable local DNS server
port=0

# Use Pi-hole for DNS requests
dhcp-option=option:dns-server,192.168.0.2,192.168.4.100

Now try restarting the docker container and the wireless hotspot. Check the log for errors.

$ docker start pihole
$ sudo nmcli --show-secrets dev wifi hotspot
$ sudo journalctl --since "1 minute ago" -u NetworkManager

No errors should be seen. Connect via your wireless device and confirm that new blocked entries are being inserted into the Pi-hole dashboard by going to your server IP address.

So in summary, we set up Pi-hole on Docker in Debian Stretch to block common adhosting networks for both wired and wireless clients on our home network. For me, this was a good test scenario to become more familiar with Docker.

Overall, I think that host based ad-blocking won’t be effective much longer as more and more content gets bundled with ads behind content delivery networks. The best practice regarding ads, in my opinion, is to only visit sites with acceptable ad practices. This means no pop-overs/pop-unders or stealing focus as well as not tracking you incessantly across the web. I suspect that ad-blocking has and will continue to move client-side. A simple way to avoid the most nefarious of ads is to use the Mozilla multi-container extension which lets you separate your online life into separate entities.

 

 

Sources

https://wireless.wiki.kernel.org/en/users/Documentation/rfkill

https://unix.stackexchange.com/questions/234552/create-wireless-access-point-and-share-internet-connection-with-nmcli

https://docs.docker.com/install/linux/docker-ce/debian/#set-up-the-repository

https://addons.mozilla.org/en-US/firefox/addon/multi-account-containers/

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/tags/1.6.2

https://github.com/jwilder/nginx-proxy