summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAhmed Abdelhalim <[email protected]>2025-11-13 00:51:31 +0100
committerAhmed Abdelhalim <[email protected]>2025-11-13 00:51:31 +0100
commit964982925f527173c8d92ecf41cca50db72b0bce (patch)
tree8ede02a98d304c71bcb9006c5909228be385678b
parentb4307e276b04aa270f213c6c932a666e486dd1ad (diff)
First draft of home network setup
-rw-r--r--content/blog/my-home-network-setup/index.md222
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` &rarr; `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