pi-hole / docker-pi-hole

Pi-hole in a docker container
https://pi-hole.net
Other
8.57k stars 1.13k forks source link

DHCP instructions #219

Closed Tatsh closed 3 years ago

Tatsh commented 6 years ago

Would you accept a pull request to update the README on how to make the DHCP server work?

I got it working on a regular Gentoo install with systemd and only having to adjust iptables.

diginc commented 6 years ago

I'd like to see the instructions. If it's long maybe we make it a separate document that gets linked to from the README.

Tatsh commented 6 years ago

Posting a rough draft.

Tatsh commented 6 years ago

This setup is for those who have two NICs on their machine. One will take in the internet (WAN) connection and the other will provide routing, usually with switches. There will be no other devices connected.

Some ISPs provide modems that also function as routers. You need to switch this device into bridge mode before it will correctly give a non-private IP address, usually out of the first ethernet port if there is more than one. This is the case with Comcast Xfinity and Spectrum in the US.

Every command except the two Docker commands in this document require using the root user, or sudo (easier if you use sudo su -). This guide assumes a system booting with systemd.

You need to know which NIC will provide which service, either internet or routing. Use ifconfig -a (manpage) to see all connections. Output will be similar to below:

docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 0.0.0.0
        ether 02:42:6f:02:85:a1  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

enp1s0f0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet xx.xx.xx.xx  netmask 255.255.248.0  broadcast xx.xx.xx.xx
        inet6 xx  prefixlen 64  scopeid 0x20<link>
        ether 00:1b:21:36:88:00  txqueuelen 1000  (Ethernet)
        RX packets 81765649  bytes 108457262437 (101.0 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 86576734  bytes 94204952379 (87.7 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device memory 0xfdc20000-fdc3ffff  

enp1s0f1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        ether 00:1b:21:36:88:01  txqueuelen 1000  (Ethernet)
        RX packets 88680977  bytes 94356583993 (87.8 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 84016023  bytes 111055124531 (103.4 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device memory 0xfdc00000-fdc1ffff  

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 57902  bytes 4144373 (3.9 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 57902  bytes 4144373 (3.9 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

We can ignore lo and docker0 as these are always present. We can see two devices enp1s0f0 and enp1s0f1. enp1s0f0 is connected to the internet and has an IP from the ISP. enp1s0f1 is connected to a switch but does not have an IP because we have not given it one yet.

Before we do that, we need to create a bridge interface so the internet can be shared between enp1s0f0 and enp1s0f1.

On my machine I am using systemd's service to create a bridge and set up enp1s0f1 with a static IP. This has to be an IP you will not allow to be taken with DHCP, so it should out of range of the DHCP range. I am using 192.168.1.5 in my case which is below the DHCP starting IP I have set 192.168.1.100. You need to create /etc/systemd/network/ if it does not exist (mkdir -p /etc/systemd/network/).

Here is the configuration for the input from the ISP, located at /etc/systemd/network/20-isp.network (replace enp1s0f0 with your interface name).

[Match]
Name=enp1s0f0

[Network]
DHCP=yes

We are saying to get an IP via DHCP from the ISP here. If your ISP assigns you a static IP you need to set this correctly here. Most ISPs assign with DHCP. yes means get IPv4 and IPv6. You can write ipv4 and ipv6 if you only want those.

This is the configuration to set a static IP on the NIC providing LAN routing, located at /etc/systemd/network/21-lan-nic.network:

[Match]
Name=enp1s0f1

[Network]
Address=192.168.1.5/24

This is the configuration to create a bridge between these devices. Three files in total: describe the device, set its static IP (this will be the pi.hole IP), and set up which devices connect to this bridge. We are naming this bridge br0. Note the extensions.

/etc/systemd/network/30-bridge.netdev:

[NetDev]
Name=br0
Kind=bridge

/etc/systemd/network/31-bridge.network:

[Match]
Name=br0

[Network]
Address=192.168.1.254/24

/etc/systemd/network/90-br0-devices.network:

[Match]
; mask is used here to match any device starting with 'en'
Name=en*

[Network]
Bridge=br0

Once you have created these files, restart the network service: systemctl restart systemd-networkd.service.

ifconfig br0 should show a device with the IP you set:

br0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.254  netmask 255.255.255.0  broadcast 192.168.1.255
...

You can also verify the LAN routing NIC got an IP as well with a similar command.

Now you can start the Pi-hole container with some very specific options. Note that --net=host is being used, which means anything using port 80 or port 53 should be turned off. Note the --cap-add, -e ServerIP, -e DNSMASQ_LISTENING and -e INTERFACE arguments.

This is not done as root.

docker container rm -f 'pihole'

docker run \
    --net=host \
    --cap-add=NET_ADMIN \
    -e ServerIP="192.168.1.254'" \
    -e WEBPASSWORD='the-password' \
    -e DNSMASQ_LISTENING=all \
    -e INTERFACE=br0 \
    --restart=always \
    --name pihole \
    -v '/etc/resolv.conf:/etc/resolv.conf:ro' \
    -v '/home/tatsh/dev/hosts:/etc/hosts:ro' \
    -v '/home/tatsh/dev/pihole-config/:/etc/pihole/' \
    -v '/home/tatsh/dev/pihole-dnsmasq.d/:/etc/dnsmasq.d' \
    -v /sys/fs/cgroup:/sys/fs/cgroup:ro \
    -d \
    diginc/pi-hole:debian

Verify this works by running docker logs -f pihole to watch the logs. Pi-hole should be able to start normally.

Now you need another computer with a web browser and ethernet. Connect this computer to a switch, then connect that switch to the LAN ethernet port. Set the IP of the computer statically to something like 192.168.1.33, with a netmask of 255.255.255.0 and the router set to 192.168.1.254. Set the DNS to have primary as 192.168.1.254.

Alternatively you can plug the client system directly into the LAN NIC but you might need a crossover cable as older NICs may not support using a patch cable for this purpose. Most newer NICs automatically adjust.

You should now be able to go to pi.hole/admin in the web browser of the client computer. If this does not work, temporarily set pi.hole in /etc/hosts or similar to route to 192.168.1.254. Log in and head over to settings and enable DHCP. Be sure to set the router gateway IP address to the correct one, in this case it is 192.168.1.254. Click the Save button.

On the server, you need to make the following changes to the firewall. I do this with iptables commands.

Do not run iptables -P INPUT DROP until the very end, when you are certain you have not blocked yourself out. If you are using SSH to manage this server, you must enable access to the SSH port. If you find yourself locked out, you will have to use something else to get on the machine (keyboard and monitor, IPMI/AMT/etc, VNC/RDP (usually provided by a web host), etc).

iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# Allow SSH
iptables -A INPUT -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

# Needed to access the web interface and API
iptables -A INPUT -p tcp -m tcp --dport 80 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

# Needed to correctly receive DHCP information from clients
iptables -A INPUT -p udp -m udp --dport 67 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

# Needed for DNS
iptables -A INPUT -p udp -m udp --dport 53 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT

# Needed to allow internet access (br0 should be last)
iptables -A FORWARD -i enp1s0f1 -j ACCEPT
iptables -A FORWARD -i enp1s0f0 -j ACCEPT
iptables -A FORWARD -i br0 -j ACCEPT
iptables -P FORWARD DROP

# Allow any output (you could also limit to 80 and 443 only if you wanted only HTTP web access on your network)
# If you decide to use DROP, you must allow UDP port 68 here for DHCP to work
iptables -P OUTPUT ACCEPT

# Disallow input except for what was listed above
iptables -P INPUT DROP

# Just in case it is not set
echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf
sysctl -p /etc/sysctl.conf

On the client system you can now disable static routing and set the NIC to use DHCP. You should receive a new IP and internet should be working.

Wi-fi

If you are replacing your router that has wi-fi, you can probably still use this router as wi-fi access point. Plug this router's 1st LAN port into a switch port connected to your new routing system. Disable DHCP and set the router in its configuration to simply be an access point (it may say something else like DHCP forwarder).

Troubleshooting

References

systemd networking