1

I am trying to setup a Wireguard VPN on a remote Debian server and use Pi-hole on that same server. I installed both of them as Docker containers. For Wireguard I have used wg-easy and for Pi-hole I have used the install script from the docker-pi-hole repository.

I have used the following commands in order to configure the firewall, using the instructions from this website.

# Clear out the entire firewall

iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT iptables -t nat -F iptables -t mangle -F iptables -F iptables -X

ip6tables -P INPUT ACCEPT ip6tables -P FORWARD ACCEPT ip6tables -P OUTPUT ACCEPT ip6tables -t nat -F ip6tables -t mangle -F ip6tables -F ip6tables -X

iptables-legacy -P INPUT ACCEPT iptables-legacy -P FORWARD ACCEPT iptables-legacy -P OUTPUT ACCEPT iptables-legacy -t nat -F iptables-legacy -t mangle -F iptables-legacy -F iptables-legacy -X

Add rules for IPv4

iptables -A INPUT -i wg0 -p tcp --destination-port 53 -j ACCEPT iptables -A INPUT -i wg0 -p udp --destination-port 53 -j ACCEPT iptables -A INPUT -i wg0 -p tcp --destination-port 80 -j ACCEPT iptables -A INPUT -p tcp --destination-port 22 -j ACCEPT iptables -A INPUT -p tcp --destination-port 51821 -j ACCEPT iptables -A INPUT -p udp --destination-port 51820 -j ACCEPT iptables -I INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -I INPUT -i lo -j ACCEPT iptables -A INPUT -p udp --dport 80 -j REJECT --reject-with icmp-port-unreachable iptables -A INPUT -p tcp --dport 443 -j REJECT --reject-with tcp-reset iptables -A INPUT -p udp --dport 443 -j REJECT --reject-with icmp-port-unreachable iptables -P INPUT DROP

Add rules for IPv6

ip6tables -A INPUT -i wg0 -p tcp --destination-port 53 -j ACCEPT ip6tables -A INPUT -i wg0 -p udp --destination-port 53 -j ACCEPT ip6tables -A INPUT -i wg0 -p tcp --destination-port 80 -j ACCEPT ip6tables -A INPUT -p tcp --destination-port 22 -j ACCEPT ip6tables -A INPUT -p tcp --destination-port 51821 -j ACCEPT ip6tables -A INPUT -p udp --destination-port 51820 -j ACCEPT ip6tables -I INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT ip6tables -I INPUT -i lo -j ACCEPT ip6tables -A INPUT -p udp --dport 80 -j REJECT --reject-with icmp6-port-unreachable ip6tables -A INPUT -p tcp --dport 443 -j REJECT --reject-with tcp-reset ip6tables -A INPUT -p udp --dport 443 -j REJECT --reject-with icmp6-port-unreachable ip6tables -P INPUT DROP

Restart Docker in order to re-create the Docker iptables rules

service docker restart

The output of iptables -L --line-numbers && ip6tables -L --line-numbers is :

# Warning: iptables-legacy tables present, use iptables-legacy to see them
Chain INPUT (policy DROP)
num  target     prot opt source               destination         
1    ACCEPT     all  --  anywhere             anywhere            
2    ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
3    ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:domain
4    ACCEPT     udp  --  anywhere             anywhere             udp dpt:domain
5    ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:http
6    ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh
7    ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:51821
8    ACCEPT     udp  --  anywhere             anywhere             udp dpt:51820
9    REJECT     udp  --  anywhere             anywhere             udp dpt:80 reject-with icmp-port-unreachable
10   REJECT     tcp  --  anywhere             anywhere             tcp dpt:https reject-with tcp-reset
11   REJECT     udp  --  anywhere             anywhere             udp dpt:https reject-with icmp-port-unreachable

Chain FORWARD (policy ACCEPT) num target prot opt source destination
1 DOCKER-USER all -- anywhere anywhere
2 DOCKER-ISOLATION-STAGE-1 all -- anywhere anywhere
3 ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED 4 DOCKER all -- anywhere anywhere
5 ACCEPT all -- anywhere anywhere
6 ACCEPT all -- anywhere anywhere

Chain OUTPUT (policy ACCEPT) num target prot opt source destination

Chain DOCKER (1 references) num target prot opt source destination
1 ACCEPT tcp -- anywhere 172.17.0.2 tcp dpt:51821 2 ACCEPT udp -- anywhere 172.17.0.2 udp dpt:51820 3 ACCEPT tcp -- anywhere 172.17.0.3 tcp dpt:http 4 ACCEPT tcp -- anywhere 172.17.0.3 tcp dpt:domain 5 ACCEPT udp -- anywhere 172.17.0.3 udp dpt:domain

Chain DOCKER-ISOLATION-STAGE-1 (1 references) num target prot opt source destination
1 DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
2 RETURN all -- anywhere anywhere

Chain DOCKER-ISOLATION-STAGE-2 (1 references) num target prot opt source destination
1 DROP all -- anywhere anywhere
2 RETURN all -- anywhere anywhere

Chain DOCKER-USER (1 references) num target prot opt source destination
1 RETURN all -- anywhere anywhere

Both containers have been installed properly and I can access the WebUI for both of them. I am able to connect to the VPN on my phone using the Wireguard. As expected the static IP of the remote server is shown when I look up my IP. However, the Wireguard client is using 1.1.1.1 as the DNS server. What should I do to use Pi-hole as my DNS server instead ? If I use the server static IP as the DNS server on the client I can't access the Internet.


Output of ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 56:00:03:e6:2e:b0 brd ff:ff:ff:ff:ff:ff
    inet 102.3.4.5/23 brd 102.3.4.255 scope global dynamic enp1s0
       valid_lft 58212sec preferred_lft 58212sec
    inet6 fe80::5400:3ff:fee6:2eb0/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:b9:8f:2e:48 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:b9ff:fe8f:2e48/64 scope link 
       valid_lft forever preferred_lft forever
87: veth45fdacb@if86: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 4a:1a:46:3d:dc:1c brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::481a:46ff:fe3d:dc1c/64 scope link 
       valid_lft forever preferred_lft forever
91: veth87dce69@if90: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 42:fb:a1:a8:eb:ff brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::40fb:a1ff:fea8:ebff/64 scope link 
       valid_lft forever preferred_lft forever

Output of iptables-save

# Generated by iptables-save v1.8.7 on Sat Mar 12 16:57:54 2022
*filter
:INPUT DROP [2055:137031]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [4530:715832]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i wg0 -p tcp -m tcp --dport 53 -j ACCEPT
-A INPUT -i wg0 -p udp -m udp --dport 53 -j ACCEPT
-A INPUT -i wg0 -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 51821 -j ACCEPT
-A INPUT -p udp -m udp --dport 51820 -j ACCEPT
-A INPUT -p udp -m udp --dport 80 -j REJECT --reject-with icmp-port-unreachable
-A INPUT -p tcp -m tcp --dport 443 -j REJECT --reject-with tcp-reset
-A INPUT -p udp -m udp --dport 443 -j REJECT --reject-with icmp-port-unreachable
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 51821 -j ACCEPT
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p udp -m udp --dport 51820 -j ACCEPT
-A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
-A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 53 -j ACCEPT
-A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p udp -m udp --dport 53 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
COMMIT
# Completed on Sat Mar 12 16:57:54 2022
# Generated by iptables-save v1.8.7 on Sat Mar 12 16:57:54 2022
*nat
:PREROUTING ACCEPT [4049:393443]
:INPUT ACCEPT [129:8227]
:OUTPUT ACCEPT [165:11159]
:POSTROUTING ACCEPT [1177:81133]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 51821 -j MASQUERADE
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p udp -m udp --dport 51820 -j MASQUERADE
-A POSTROUTING -s 172.17.0.3/32 -d 172.17.0.3/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A POSTROUTING -s 172.17.0.3/32 -d 172.17.0.3/32 -p tcp -m tcp --dport 53 -j MASQUERADE
-A POSTROUTING -s 172.17.0.3/32 -d 172.17.0.3/32 -p udp -m udp --dport 53 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 51821 -j DNAT --to-destination 172.17.0.2:51821
-A DOCKER ! -i docker0 -p udp -m udp --dport 51820 -j DNAT --to-destination 172.17.0.2:51820
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.3:80
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 53 -j DNAT --to-destination 172.17.0.3:53
-A DOCKER ! -i docker0 -p udp -m udp --dport 53 -j DNAT --to-destination 172.17.0.3:53
COMMIT
# Completed on Sat Mar 12 16:57:54 2022
# Warning: iptables-legacy tables present, use iptables-legacy-save to see them

Thanks to Tom Yan's answer I was able to properly configure both Pi-hole and Wireguard using the following scripts :

Wireguard

docker run -d \
  --name=wg-easy \
  -e WG_HOST=my-server-static-ip \
  -e PASSWORD=my-password \
  -e WG_DEFAULT_DNS=172.17.0.3 \
  -v ~/.wg-easy:/etc/wireguard \
  -p 51820:51820/udp \
  -p 51821:51821/tcp \
  --ip 172.17.0.2 \
  --cap-add=NET_ADMIN \
  --cap-add=SYS_MODULE \
  --sysctl="net.ipv4.conf.all.src_valid_mark=1" \
  --sysctl="net.ipv4.ip_forward=1" \
  --restart unless-stopped \
  weejewel/wg-easy

Pi-hole

#!/bin/bash

https://github.com/pi-hole/docker-pi-hole/blob/master/README.md

PIHOLE_BASE="${PIHOLE_BASE:-$(pwd)}" [[ -d "$PIHOLE_BASE" ]] || mkdir -p "$PIHOLE_BASE" || { echo "Couldn't create storage directory: $PIHOLE_BASE"; exit 1; }

Note: ServerIP should be replaced with your external ip.

docker run -d
--name pihole
-p 53:53/tcp -p 53:53/udp
-p :80:80
-e TZ="Europe/Paris"
-e WEBPASSWORD="my-password"
-v "${PIHOLE_BASE}/etc-pihole:/etc/pihole"
-v "${PIHOLE_BASE}/etc-dnsmasq.d:/etc/dnsmasq.d"
--dns=127.0.0.1 --dns=1.1.1.1
--restart=unless-stopped
--hostname pi.hole
--ip 172.17.0.3
-e VIRTUAL_HOST="pi.hole"
-e PROXY_LOCATION="pi.hole"
-e ServerIP="my-server-static-ip"
pihole/pihole:latest

printf 'Starting up pihole container ' for i in $(seq 1 20); do if [ "$(docker inspect -f "{{.State.Health.Status}}" pihole)" == "healthy" ] ; then printf ' OK' echo -e "\n$(docker logs pihole 2> /dev/null | grep 'password:') for your pi-hole: https://${IP}/admin/" exit 0 else sleep 3 printf '.' fi

if [ $i -eq 20 ] ; then
    echo -e &quot;\nTimed out waiting for Pi-hole start, consult your container logs for more info (\`docker logs pihole\`)&quot;
    exit 1
fi

done;

1 Answers1

3

You should be able to use 172.17.0.3, which is the IP assigned to the Pi-Hole container, as DNS server on the Wireguard clients, since your Wireguard container and Pi-Hole container are connected to the same bridge. Because source NAT has been set up inside the Wireguard container, it should work out-of-the-box.

Note that apparently the IPs assigned to the docker containers are enumerated by default, so unless you can guarantee that the containers are always started in the same order (e.g. they are started synchronously with a script or with ordered systemd services), the IPs assigned to the containers could be swapped in the next boot. Therefore it would probably be better if you statically set their IPs with the --ip option of docker run.


P.S. One may think that 172.17.0.1, which should save you from the enumerated IP problem I just mentioned, can be used instead. However, as you can see from the iptables-save dump, for reasons docker by default does not make the host redirect traffics from the docker bridge to corresponding docker container as per the mapped/forwarded ports:

...
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
...
-A DOCKER -i docker0 -j RETURN
...
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 53 -j DNAT --to-destination 172.17.0.3:53
-A DOCKER ! -i docker0 -p udp -m udp --dport 53 -j DNAT --to-destination 172.17.0.3:53
...

Therefore you'll need to add extra DNAT rule manually if you use 172.17.0.1 instead, and the rule will require specification of the corresponding container IP, as you can see. In other words, the approach can't actually help, let alone that it is ugly anyway with the current setup.

It is also why using the public IP didn't work btw (given the fact that it is apparently configured on the container host directly; in other words, it wasn't a hairpin problem or so).

Tom Yan
  • 10,996