From dfbce70c6339830584579262f61622333746edec Mon Sep 17 00:00:00 2001 From: Ahmed Abdelhalim Date: Thu, 13 Nov 2025 17:51:53 +0100 Subject: Update and publish the VPN setup blog post --- .../images/wg-portal-demo.gif | Bin 0 -> 166585 bytes content/blog/my-home-network-setup/index.md | 138 ++++++++++++--------- 2 files changed, 78 insertions(+), 60 deletions(-) create mode 100644 content/blog/my-home-network-setup/images/wg-portal-demo.gif (limited to 'content/blog') diff --git a/content/blog/my-home-network-setup/images/wg-portal-demo.gif b/content/blog/my-home-network-setup/images/wg-portal-demo.gif new file mode 100644 index 0000000..62d90fc Binary files /dev/null and b/content/blog/my-home-network-setup/images/wg-portal-demo.gif differ diff --git a/content/blog/my-home-network-setup/index.md b/content/blog/my-home-network-setup/index.md index e32cfd1..4a3adbe 100644 --- a/content/blog/my-home-network-setup/index.md +++ b/content/blog/my-home-network-setup/index.md @@ -1,28 +1,27 @@ --- -title: My Home Network Setup -date: 2025-11-12 -draft: true +title: My Home (VPN) Network Setup +date: 2025-11-13 +draft: false --- # 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. +The LG TV is great, but since it's based on webOS it doesn't have native 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, +Fortunately, I had a Raspberry Pi lying around, which I got a while ago to tinker with. +So I decided to purpose 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).
+functionalities). _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. +This way, I could ensure that all devices on my network would benefit from the DNS filtering/caching, +VPN connection -when enabled-, 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. +From a network topology perspective, the Raspberry Pi would sit between the router and the rest of my home network, +and intercept all traffic going in and out of the network. # The Draft @@ -33,18 +32,18 @@ config: --- 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] + Internet <--> VPN[VPN Provider: 10.2.0.2] + ISP <--> Router[Router: 192.168.0.1] + Router <--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: +For this to work, the idea was to have the following criteria fulfilled: -1. The modem needs to be configured to use the Raspberry Pi as its default gateway. +1. The router 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. @@ -52,32 +51,33 @@ For this to work, the following steps needed to be implemented: ## 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, +My Raspberry Pi 5 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. +The Pi-Hole official setup guide describes how to configure 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 +This poses a problem for setting a VPN up, 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. +the VPN tunnel; the leak is of the VPN exit node IP by the DNS upstream servers, 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. +Finally, I also needed a simpler way to monitor the VPN connection status and to enable/disable +the VPN connections without requiring to ssh into the Raspberry Pi and running some shell commands. ## 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] +easiest to automate routers out there. /s_[^1] -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, +That means that many of the commonly used features in other routers, or could help automating this setup didn't work +on the Fritz!Box, so after many hours of trial and error, reading forums, and occasionally 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: @@ -95,30 +95,30 @@ 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. +Luckily, I had already started using Ansible to [automate my whole home setup a while ago][^2], and +I had the perfect place to implement this project in a way that I could debug and maintain. ## 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 RPI Pi-Hole role depends on [`systemd`][10] and [`network`][11] roles. +`systemd` is required to manage the Pi-Hole FTL DNS service, and the `network` role is used to manage the whole +network configuration-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`. +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, +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]) +installation script, after creating the required custom configurations 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.
+This role is completely independent of the VPN setup, so it can be used standalone if needed.
Here is the full [Pi-Hole role implementation][13]. ## The Gateway installation @@ -130,12 +130,13 @@ 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. Configure (event-driven) `udev` rules 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. +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 edit the WireGuard +configuration files manually, despite the fact that `systemd` approach is (IMHO) way more complex, but it's also +possible to extend it to eliminate the DNS leak problem mentioned before.[^3] 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. @@ -176,31 +177,50 @@ Here is the full [Gateway role implementation][14], but in a nutshell, the key p Same rules, but routes through router (`eth0`) instead of VPN (`wg+`) interfaces. -# The Extras +# The Web Interface + +Because I didn't find exactly what I wanted, which is a simple web interface to check and toggle +the VPN connection status, which can be installed and configured via code, +I created the [wg-portal][17], which is a simple web application built with Go 🤩, and a +little bit of JavaScript 🤢🤮. + +# The Results -- [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. +I started this project out of curiosity and to learn more about networking concepts, Go web development, ansible +automation, I could have just set up only the VPN or DNS sinkhole, but I wanted to explore, experiment, and challenge +myself a little to make this different tools work together, so it was to my extreme surprise that not only +did everything work as expected, but that it also worked reliably and stably for months now. -# The Conclusion +Here is a GIF of the web interface in action:
+wg-portal in action -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. +# The Problems -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 +A part from rebooting the Raspberry Pi every once in a while, 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! +turn off the VPN before checking for flights and realizing that the prices were in GBP instead of EUR 😅! + +# The Future + +I think this has been functioning pretty well, and I don't see any immediate need to change anything, +but maybe in the future I would work on the wg-portal project to try to remove the JavaScript dependency, and +use HTMX (since I don't have much experience with it yet), and maybe add more documentation about the whole Linux +network setup and the assumptions/decisions made during the development of the Ansible roles. --- -[^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 +[^1]:_Fun fact: The Fritz!Box has already implemented [VPN support][8] in the FRITZ!OS 7.50 Update -I think-, but +it wasn't stable, therefore leaked all the time, which disrupted the internet connection for all devices on the network, +and I couldn't get it to work with more than one WireGuard configuration at a time. +[^2]: 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. +[^3]: By modifying the [script][23] to also change the Pi-Hole configuration file when switching modes, +and restarting the Pi-Hole FTL service, it would be possible to have the Pi-Hole use the VPN DNS servers when in VPN mode.
+It would be a bit slow (due to Pi-Hole FTL restarts), but it also would mix the responsibilities of +the gateway and Pi-Hole roles (which I didn't want to implement to preserve the modularity, and locality of the roles). [2]: https://en.wikipedia.org/wiki/DNS_sinkhole [3]: https://pi-hole.net/ @@ -215,8 +235,6 @@ of all of my home devices, and the [configure project repository][22] for all of [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 +[23]: https://git.sr.ht/~a14m/ansible-distro-configure/tree/358b8577af5feae702cb80bdc9b61ded0ae2c458/item/roles/gateway/templates/gateway-apply-rules.sh.j2 -- cgit v1.2.3