From charlesreid1

Up: Ubuntu/Bespin

Previous: Ubuntu/Bespin/DNS

Next: Ubuntu/Bespin/Iptables

Running PiHole via Docker on Ubuntu 18.04

System DNS

Note that this guide assumes you have already disabled your systemd-resolvd DNS service.

Notes on Networking and Ports

PiHole acts as a DNS server for Bespin, listening on port 53 by default. This complicates things for us:

  • We already set up dnsmasq to run as a DNS and DHCP server for the wireless AP hotspot
  • If we hadn't set up dnsmasq, Ubuntu already has a built-in DNS server (systemd-resolvd) running on port 53 (see Ubuntu/Bespin for instructions to disable)
  • Previously, we dealt with the problem of having two DNS servers trying to use port 53 by killing one of them (systemd-resolvd). But we don't have that option this time.

PiHole on Non-Standard Port

Given that dnsmasq is already using port 53 to handle DNS queries for the wifi AP, we need to figure out an alternative approach.

One possibility would be to run dnsmasq on port 53, and run the PiHole DNS server on port 5353, and set the PiHole DNS as an upstream DNS server. Pretty slick, huh?

Turns out that doesn't work. dnsmasq gets its upstream nameservers from /etc/resolv.conf, which does not allow any non-standard ports. Every IP address in that list has to have a DNS server running on port 53.

So, using a non-standard port is out.

PiHole on Non-Standard Loopback IP

Given that we need the PiHole to listen on port 53, but we don't want it clashing with dnsmasq, we can assign the PiHole to listen for DNS queries on a non-standard loopback IP address.

  • The standard loopback IP address is 127.0.0.1. That's set up and available by default, and works like "localhost".
  • But you can ALSO use any in the CIDR block 127.0.0.0/8 to create a new loopback IP address. That's a lot of IPs.

The plan is still the same: set up the PiHole running on 127.53.0.1, and set 127.53.0.1 as the only upstream DNS server for dnsmasq to use in /etc/resolv.conf.

Now, if a client on the AP requests "github.com", the request will go via port 53 to dnsmasq on bespin. dnsmasq will not find it in /etc/hosts so it will look up the upstream nameserver in /etc/resolv.conf (the PiHole at 127.53.0.1 port 53) and will pass the DNS request on to the PiHole. The PiHole will determine what to do, and will pass the DNS request upstream if it cannot resolve it.

Diagram

+--------------------------------------------------------------------------------------------------------------------------------+
| bespin - host machine                                                                                                          |
|                                                                                                                                |
|                                                                                                                                |
|                                                                                                                                |
|        +-----------------------+                                                                                               |
|        |                       |                                                                                               |
|        |         PiHole        |                                                                                               |
|        |                       |                                                                                               |
|        |                +------+                                                                                               |
|        |                | port |   4a. PiHole is bound to the loopback interface,                                              |
|        |                | 53   |       address 127.53.0.1 port 53, so the client                                               |
|        +----------------+---+-++       DNS request is received by the PiHole.                                                  |
|                             ^ |                                                                                                |
|                             | |    4b. PiHole will decide whether to filter the request,                                       |
|   +-------------------------+ |        and will forward any DNS requests it cannot serve                                       |
|   |                           |        to other upstream servers.                                                              |
|   |                           |                                                                                                |
|   |                           +---------------------------------------------------------------------------------+              |
|   |                                                                                                             |              |
|   |    +------------------------+                                                         5. DNS requests to    |              |
|   |    |                        |                                                            upstream servers   |              |
|   |    |   dnsmasq              |                                                            are sent out over  |              |
|   |    |                        |                                                            the VPN tunnel     |              |
|   |    |                +-------+  2a. dnsmasq is bound to the wlan1 (and loopback)                             |              |
|   |    |                |  port |      interfaces, so DNS request is received on                                v              |
|   |    |                |    53 |      port 53                                                     +------------+-------+      |
|   |    +----------------+---+--++                                                                  |                    |      |
|   |                         |  ^   2b. dnsmasq cannot fulfill the request, so it                   |  OpenVPN           |      |
|   |                         |  |       forwards the request to the upstream server                 |                    |      |
|   |                         |  |       in /etc/resolv.conf, 127.53.0.1                             |  (+encryption)     |      |
|   |                         |  |                                                                   |                    |      |
|   |                         |  +-------------------------+                                         +------------+-------+      |
|   +-----------+             |                            |                                                      |              |
|               |             |                            |                                                      |              |
|               |             |                            |                                 +----------+         |              |
|               |             |                            |                                 |          |         |              |
|          +---------------------------+        +-----------------+         +------------------+     +-----------------+         |
|          |    |             |        |        |   wlan1  |      |         |    wlan0       | |     |  | tun0    |    |         |
|          |    |    loopback |        |        |   AP wifi|card  |         |    internet gw | |     |  | VPN tunnel   |         |
|          |    |             v        |        |          |      |         |    wifi card   v |     |  |         |    |         |
|          +----+-------+-----+--------+        +----------+------+         +----------------+-+     +--+---------+----+         |
|          | 127.53.0.1 |  127.0.0.1   |        |  192.168.10.1   |         |  192.168.0.199   |     |   10.8.0.17     |         |
+----------+------------+--------------+--------+---------+-------+---------+----------------+-+-----+-----------------+---------+
                                                          ^                                  |
                3. The request is passed                  |                                  |
                   from loopback 127.0.0.1                |                                  | 6. VPN traffic is encrypted and sent
                   to loopback 127.53.0.1                 | 1. A client on the AP asks       |    out via wlan0 to the VPN server,
                                                          |    for DNS info for a domain,    |    which handles the DNS requests on
                                                          |    e.g., "github.com"            |    the other side of the tunnel
                                              +-----------+-----------+                      |
                                              |                       |                      |
                                              |   client DNS request  |                      |
                                              |   from 192.168.10.5   |                      |
                                              |         for           |                      |
                                              |     "github.com"      |                      |
                                              |                       |                      |
                                              +-----------------------+                      |
                                                                                             v
                                                                               +-------------+------------+
                                                                               |                          |
                                                                               |     OpenVPN Server       |
                                                                               |     + External DNS       |
                                                                               |                          |
                                                                               |                          |
                                                                               +--------------------------+

Install Stuff

Docker

Thanks to the Ansible step covered on the Ubuntu/Bespin page, Docker is already installed on Bespin.

$ which docker
/usr/bin/docker

$ which docker-compose
/usr/local/bin/docker-compose

PiHole Docker Image

Pull the latest pihole docker image:

docker pull pihole/pihole:latest

Create Loopback Device

Define a non-standard loopback IP address 127.53.0.1 in /etc/network/interfaces so it will be persistent across reboots.

There are two ways to create a new non-standard loopback IP address:

1. Reuse the loopback device, adding a second IP address to it using an up/down directive in the loopback entry of the network interfaces file

2. Create a whole new loopback device, called lo:0, that has the specified loopback IP address

We use approach 2

Approach 2

Create a new loopback device called lo:0 in the network interfaces file:

/etc/network/interfaces

# Loopback interfaces.
#
# We define standard lo loopback interface
# as well as an alias, lo:1, to run a second
# DNS server.
auto lo lo:1
iface lo inet loopback
iface lo:1 inet static
    address 127.53.0.1
    netmask 255.255.255.0
    network 127.53.0.0

Reboot and verify the new IP address on the loopback shows up with this command:

$ /sbin/ip addr show dev lo
1: lo: 

    ...snip...

    inet 127.53.0.1/24 brd 127.53.0.255 scope host lo:1

    ...snip...

Create Docker Compose File

Requirements

We assume the following directory setup with the following files present:

  • The root directory for pihole is /home/ubuntu/pihole
  • A docker-compose.yml file is present in the root directory
  • An etc-pihole directory that is bind-mounted to /etc/pihole/
  • An etc-dnsmasq.d directory that is bind-mounted to /etc/dnsmasq.d

The docker compose file needs to specify the following port setup for PiHole:

  • PiHole needs to bind port 53 inside the container to 127.53.0.1:53 on the host so the container can receive requests
  • iptables on bespin needs to forward packets from the lo:1 interface with IP 127.53.0.1 to the VPN tunnel device tun0 so that the DNS requests that the PiHole does not fulfill will be sent through the VPN tunnel
  • Protect the web interface by binding ports 80 and 443 to 127.0.0.1:80 and 127.0.0.1:443 (or 8080 and 8443, as I did below)
  • We don't need port 67, which is the PiHole's DHCP server, so don't create a port mapping for it

Setup

Before creating the docker compose file, create the necessary files and folders:

cd
mkdir -p phiole pihole/etc-pihole pihole/etc-dnsmasq.d
cd pihole

Compose File

In the folder /home/ubuntu/pihole, create a docker-compose.yml file:

/home/ubuntu/pihole/docker-compose.yml

version: "3.3"
services:
  pihole:
    container_name: pihole
    domainname: docker
    hostname: pihole
    image: pihole/pihole:latest
    ports:
      - '127.53.0.1:53:53/tcp'
      - '127.53.0.1:53:53/udp'
      - '127.0.0.1:8080:80'
      - '127.0.0.1:8443:443'
    volumes:
      - './etc-pihole/:/etc/pihole/'
      - './etc-dnsmasq.d/:/etc/dnsmasq.d/'
    environment:
      - TZ="America/Vancouver"
      - WEBPASSWORD=chicken5
    dns:
      - 127.0.0.1
      - 8.8.8.8
    cap_add:
      - NET_ADMIN
    restart: unless-stopped

Starting Docker Compose

To start docker compose with this compose file, run this command from the directory containing docker-compose.yml:

docker-compose up

This will run in the foreground, so have another shell open to test some things.

Configure Packet Forwarding for Outbound PiHole DNS

Now we have a PiHole service listening on the right port 53, device lo:1, and interface 127.53.0.1

Next we want to configure the PiHole loopback device lo:1 to be able to forward packets to the VPN tunnel, so that DNS requests will be sent through the VPN tunnel and kept secure.

If we leave out this step, then dnsmasq will forward DNS queries to the PiHole DNS service, but the PiHole will have no way of sending DNS queries to its upstream servers, so it will be a DNS dead end.

We will use iptables rules to forward packets from the loopback network interface that the PiHole is using, on to the VPN tunnel network interface.

Here is the stanza we'll add to our existing iptables script:

# Forward outgoing DNS traffic from lo:1 (PiHole) through PIA tunnel
DNS="lo:1"
TUN="tun1"
PROTOCOLS="udp tcp"
for PROTOCOL in PROTOCOLS; do
    $ipt -A FORWARD -p ${PROTOCOL} -i ${DNS} -o ${TUN} --dport 53 -j ACCEPT
    $ipt -A FORWARD -p ${PROTOCOL} -i ${TUN} -o ${DNS} --dport 53 -m state --state ESTABLISHED,RELATED -j ACCEPT
done

Configure dnsmasq to use PiHole as Upstream DNS

Note: the improved version of a dnsmasq config file is covered in Ubuntu/Bespin/Dnsmasq

Edit /etc/resolv.conf to contain one DNS server, the PiHole:

/etc/resolv.conf

nameserver 127.53.0.1

Edit the dnsmasq configuration file and update the server= option:

/etc/dnsmasq.conf

server=127.53.0.1

Create a PiHole Startup Service

Create a systemd startup service file:

/etc/systemd/system/pihole.service

[Unit]
Description=PiHole Docker Pod
Requires=docker.service
After=docker.service

[Service]
Restart=always
StandardError=null
StandardOutput=null
ExecStart=/usr/local/bin/docker-compose -f /home/ubuntu/pihole/docker-compose.yml up
ExecStop=/usr/local/bin/docker-compose  -f /home/ubuntu/pihole/docker-compose.yml stop

[Install]
WantedBy=default.target

Enable and start the service:

sudo systemctl enable pihole
sudo systemctl start pihole

Related Flags