diff options
| author | Ahmed Abdelhalim <[email protected]> | 2025-11-13 00:51:31 +0100 |
|---|---|---|
| committer | Ahmed Abdelhalim <[email protected]> | 2025-11-13 00:51:31 +0100 |
| commit | 964982925f527173c8d92ecf41cca50db72b0bce (patch) | |
| tree | 8ede02a98d304c71bcb9006c5909228be385678b /content | |
| parent | b4307e276b04aa270f213c6c932a666e486dd1ad (diff) | |
First draft of home network setup
Diffstat (limited to 'content')
| -rw-r--r-- | content/blog/my-home-network-setup/index.md | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/content/blog/my-home-network-setup/index.md b/content/blog/my-home-network-setup/index.md new file mode 100644 index 0000000..e32cfd1 --- /dev/null +++ b/content/blog/my-home-network-setup/index.md @@ -0,0 +1,222 @@ +--- +title: My Home Network Setup +date: 2025-11-12 +draft: true +--- +# The Beginning + +A little over a year ago, I replaced my Sony Android TV with an LG smart TV. +The LG TV is great in so many different ways, but since it's based on webOS it doesn't have VPN support. + +Fortunately, I had a Raspberry Pi lying around, which I got years ago in a sale to tinker with. + +After some time, I decided to repurpose the Raspberry Pi to function as my home network gateway, +providing both a [DNS sinkhole][2] and a VPN exit point to all devices on my home network (plus some other +functionalities).<br> +_After all, one can only survive so many German YouTube ads before "einen Dachschaden haben"._ + +# The Idea + +The idea was to have a single point of entry and exit for all my home network traffic. +This way, I could ensure that all devices on my network would benefit from the VPN connection, +DNS filtering/caching, and other network services I wanted to implement. + +From a network topology perspective, the Raspberry Pi would sit between the modem and the rest of my home network, +and be the only, default, and automatically configured gateway for all home network devices. + +# The Draft + +```mermaid +--- +config: + theme: neutral +--- +graph TD + Internet[Cat Memes] <--> ISP[ISP: 2.213.76.114] + Internet <--VPN mode--> VPN[VPN Provider: 10.2.0.2] + ISP <--> Modem[Modem: 192.168.0.1] + Modem <--direct mode--> RPI[Raspberry Pi: 192.168.0.254] + VPN <--VPN mode--> RPI + RPI <--> D1[TV: 192.168.0.100] + RPI <--> D2[Laptop: 192.168.0.151] + RPI <--> D3[Phone: 192.168.0.200] +``` + +For this to work, the following steps needed to be implemented: + +1. The modem needs to be configured to use the Raspberry Pi as its default gateway. +1. A VPN client needs to be installed and configured on the Raspberry Pi. +1. A DNS sinkhole service needs to be set up on the Raspberry Pi. +1. The Raspberry Pi needs to be set up to forward traffic between its network/VPN interfaces. +1. Additional services like DHCP, firewall rules, and monitoring tools can be configured as needed. + +## 1. The Raspberry Pi Setup + +My Raspberry Pi is running Raspberry Pi OS Lite. I decided to use [Pi-Hole][3] as a DNS sinkhole, +[WireGuard][5] as a VPN client, and ProtonVPN as a VPN provider. + +The Pi-Hole official setup automatically configures the Raspberry Pi as a DNS resolver for the whole network. + +This poses a problem for the VPN, since VPN providers often use their own DNS servers to prevent DNS leaks, but +the Pi-Hole would intercept these requests and resolve them using the Pi-Hole's upstream DNS servers. + +**This is a DNS leak**, albeit not in the traditional sense, since the DNS requests are still going through +the VPN tunnel; the leak is of the VPN exit node IP, which is not ideal, but _acceptable_ for my use case. + +There are solutions to this problem: + +- Use WireGuard's `PostUp` and `PostDown` scripts to change the Pi-Hole DNS servers when the VPN connects/disconnects. +- Use `systemd` services to automatically manage and switch the Pi-Hole DNS configuration based on the VPN state. + +Finally, I also needed a simpler[^1] way to monitor the VPN connection status and to easily enable/disable +the VPN connections. + +## 2. The Fritz!Box Setup + +_It's a commonly known fact that Fritz!Box routers are some of the most user-friendly and +easiest to automate routers out there. /s_[^2] + +That means that many of the commonly used features in other routers that would help secure this setup won't work +on the Fritz!Box, so after many hours of trial and error, reading forums and sometimes German documentation PDFs, +I disabled as many features as possible on the Fritz!Box, to avoid conflicts and weird behavior +that were extremely hard to debug, and sometimes were exactly the opposite of what was shown on the Fritz!Box UI, +which included: + +- Disable the DHCP server on the Fritz!Box to avoid IP conflicts (both IPv4 and IPv6). +- Disable "Fallback to public DNS servers". +- Disable DNS over TLS (DoT). +- Remove all static routes configurations. +- Configure the standard profile filtering to block DNS. +- Configure the unrestricted profile to allow the Raspberry Pi unrestricted internet access. +- Set the DNS server to the Raspberry Pi's IP address (both IPv4 and IPv6). + +This setup is highlighted in the [official Pi-Hole tutorial][4]. + +# The Implementation + +Since I have pretty much a less-than-average fish memory, I needed to have everything either documented or automated. +Luckily, I had already started using Ansible to [automate my whole home setup a while ago][^3], and +I had the perfect place to implement this project in a way that I could work with. + +## The Pi-Hole installation + +The bootstrapped RPI Pi-Hole role depends on [`systemd`][10] and [`network`][11] roles. +The `systemd` is required to manage the Pi-Hole FTL DNS service, and the `network` role is used to manage the whole +network configurations as a code. + +The `network` role ensures that `network-manager`, `systemd-resolved` and `dnsutils` are installed, and ensures that +`avahi-daemon`, `avahi-utils` and `dhcpcd` are removed to avoid conflicts with `systemd-resolved`. + +Additionally, the `network` role configures the static IP address for the Raspberry Pi, and configures +the `network-manager` DNS resolver (using `systemd-resolved` as a backend), and the default upstream DNS servers. + +Other than that, the Pi-Hole installation is pretty "standard", following the official Pi-Hole installation script, +I created the `pihole` user, and the required setup directories manually for clarity, downloaded and ran the +installation script, after creating the required configuration file ([`/etc/pihole/pihole.toml`][12]) + +And to avoid DNS conflicts between the Pi-Hole and network services (`NetworkManager` and `systemd-resolved`), +the role disables the default `NetworkManager` DNS configurations and configures `systemd-resolved` to use the Pi-Hole +as the default DNS resolver for the whole system. + +This role is completely independent from the VPN setup, so it can be used standalone if needed.<br> +Here is the full [Pi-Hole role implementation][13]. + +## The Gateway installation + +The [gateway][14] role was a bit more complex to develop, since I didn't have much experience with managing networks or +devices, but after some research, trial and error, and some snippets of code from different internet posts about +different topics, I managed to create a role that does the following: + +1. Ensures that `iptables`, `iproute2`, and `udev` are installed. +1. Configures `sysctl` to enable IP forwarding. +1. Template the script that toggles the direct/VPN gateway mode. +1. Configure `udev` roles to trigger the `systemd` services when the VPN interface goes up/down. +1. Adds the `systemd` services that respond to the different `udev` events to switch the gateway mode. + +I opted to use this approach instead of using WireGuard's `PostUp` and `PostDown` scripts, because I wanted to have +the whole process automated and developed once but deployed everywhere, without the need to manually edit the WireGuard +configuration files, despite that this approach is (IMHO) way more complex. + +But what was the most complicated part of this process was to figure out the exact `iptables` rules required +to properly forward the traffic between the different interfaces, and being able to debug when something went wrong. + +Because I also wanted to preserve the [Locality of Behavior principle][15], I created the bash script that toggles +which mode is active (direct/VPN), and ensured that the setup is almost exclusively managed by `systemd` services. + +Here is the full [Gateway role implementation][14], but in a nutshell, the key parts are: + +1. Flush all existing `iptables` rules before applying new ones. +2. `apply_vpn_mode()` - Routes local network through VPN +3. `apply_direct_mode()` - Routes local network directly to the internet + +### VPN mode `iptables` rules explained + +`iptables -t nat -A POSTROUTING -s "$GATEWAY_SUBNET" -o wg+ -j MASQUERADE` + +- NAT rule: Rewrites source IP of packets from `192.168.0.0/24` going out through WireGuard (`wg0`, `wg-US`, etc.) interfaces. +- Changes: `192.168.0.151` → `10.2.0.2` (VPN IP) +- Why: So remote servers respond back through the VPN tunnel + +`iptables -A OUTPUT -o wg+ -j ACCEPT` + +- Allows Raspberry Pi's own traffic to go out through WireGuard interfaces. +- For: Pi's own DNS queries, updates, etc. + +`iptables -A FORWARD -s "$GATEWAY_SUBNET" -o wg+ -j ACCEPT` + +- Allows forwarding packets from local network (`192.168.0.0/24`) to WireGuard. +- For: Other devices' traffic being routed through the Pi. + +`iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT` + +- Allows return traffic (responses) back through. +- For: When [http.cat][16] responds, let the packets come back. + +### Direct mode `iptables` rules explained + +Same rules, but routes through router (`eth0`) instead of VPN (`wg+`) interfaces. + +# The Extras + +- [wg-portal][17]: A simple web app to monitor and control the VPN connection status. +- [Prometheus][18] + [Grafana][19]: For monitoring the Raspberry Pi's performance and network statistics. +- [Nginx][20]: To serve all the web applications and dashboards. + +# The Conclusion + +I started this project out of curiosity and to learn more about networking, administration, and automation. +I could have just set up only the VPN or DNS sinkhole, but wanted to explore, experiment and challenge myself a little, +so it was to my extreme surprise that not only everything worked as expected, but that it also worked reliably and +stably for more than a few months now. + +A part from rebooting the Raspberry Pi every once in a while to apply updates, I haven't had to do much of any manual +work to keep the setup running, and the only problems I've had -a part from the occasional ISP issues- was forgetting to +turn the VPN off while checking for flights and realizing that the prices were in GBP instead of EUR! + +--- +[^1]: Simpler in this case meaning not needing to go to my computer, open a terminal, ssh into the Raspberry Pi, +run some commands, check the status, and then run more commands to enable/disable the VPN, etc. +[^2]:_Fun fact: The Fritz!Box has already implemented [VPN support][8] in the FRITZ!OS 7.50 Update -I think-, but +I couldn't get it to work with more than one configuration, and it wasn't stable, and leaked all the time, which made +it pretty much useless for my use case._ +[^3]: Feel free to check the full [install project repository][21] for all of the code about automating the installation +of all of my home devices, and the [configure project repository][22] for all of the code about configuring the devices. + +[2]: https://en.wikipedia.org/wiki/DNS_sinkhole +[3]: https://pi-hole.net/ +[4]: https://docs.pi-hole.net/routers/fritzbox/ +[5]: https://www.wireguard.com/ +[8]: https://fritz.com/en/apps/knowledge-base/FRITZ-Box-7583/3448_VPN-with-FRITZ-Box +[10]: https://git.sr.ht/~a14m/ansible-distro-configure/tree/358b8577af5feae702cb80bdc9b61ded0ae2c458/item/roles/systemd/tasks/main.yml +[11]: https://git.sr.ht/~a14m/ansible-distro-configure/tree/358b8577af5feae702cb80bdc9b61ded0ae2c458/item/roles/network/tasks/main.yml +[12]: https://git.sr.ht/~a14m/ansible-distro-configure/tree/358b8577af5feae702cb80bdc9b61ded0ae2c458/item/roles/pihole/templates/pihole.toml.j2 +[13]: https://git.sr.ht/~a14m/ansible-distro-configure/tree/358b8577af5feae702cb80bdc9b61ded0ae2c458/item/roles/pihole +[14]: https://git.sr.ht/~a14m/ansible-distro-configure/tree/358b8577af5feae702cb80bdc9b61ded0ae2c458/item/roles/gateway +[15]: https://htmx.org/essays/locality-of-behaviour/ +[16]: https://http.cat/ +[17]: https://git.sr.ht/~a14m/wg-portal +[18]: https://git.sr.ht/~a14m/ansible-distro-configure/tree/358b8577af5feae702cb80bdc9b61ded0ae2c458/item/roles/prometheus +[19]: https://git.sr.ht/~a14m/ansible-distro-configure/tree/358b8577af5feae702cb80bdc9b61ded0ae2c458/item/roles/grafana +[20]: https://git.sr.ht/~a14m/ansible-distro-configure/tree/358b8577af5feae702cb80bdc9b61ded0ae2c458/item/roles/nginx +[21]: https://git.sr.ht/~a14m/ansible-distro-install +[22]: https://git.sr.ht/~a14m/ansible-distro-configure |
