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 "\nTimed out waiting for Pi-hole start, consult your container logs for more info (\`docker logs pihole\`)"
        exit 1
    fi
done;
4
  • It would be easier to configure the VM running piHole to connect to Wireguard then the client connected to Wireguard to use piHole. However, it should be as simple, as configuring your client to use pihole as the DNS provider.
    – Ramhound
    Commented Mar 12, 2022 at 16:03
  • 1
    Normally you use the WG IP of the WG server on the WG clients as DNS server if you have a DNS forwarder or DNAT rules on the WG server. However, since your WG server is apparently run in a separate container, most likely you should use the IP of the docker bridge (that the WG container is attached to), so that the DNS queries will be forwarded out to the container host and then DNAT'd into the pi-hole container. If the containers share the same bridge, then you can probably use the IP of pi-hole container directly (MASQUERADE / return route is likely needed in the corresponding container then)
    – Tom Yan
    Commented Mar 12, 2022 at 16:23
  • It would be best for you to share ip a and iptables-save output from the container host. (I doubt that you have a wg0 on it btw, so some of the INPUT rules you added are unnecessary.)
    – Tom Yan
    Commented Mar 12, 2022 at 16:32
  • Thank you @TomYan, I am going to try to look into that although I don't know very much about networking yet. I have updated my post with the output of ip a and iptables-save. Commented Mar 12, 2022 at 17:02

1 Answer 1

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).

1
  • Thank you for your help, statically setting the IP of both containers worked. I have updated the OP accordingly. Commented Mar 12, 2022 at 23:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .