Tunnel Vision

Share
Tunnel Vision
Photo by Joshua Rawson-Harris / Unsplash
Networking · OpenWrt · VPN · Self-Hosted

A retired UDM, a dusty Raspberry Pi, and three VPN protocols walked into a hotel room. One of them eventually worked.

3 → 1
VPN Protocols Tried
5
Home VLANs Reachable
$0
New Hardware

The tunnel works. A sentence that took—I'm going to estimate conservatively here—several hours, three VPN protocol attempts, one phone hotspot, and the kind of patient troubleshooting that makes you question whether you should have just bought a GL.iNet travel router for sixty bucks.

I should back up.


01 The Hardware Heap

I have a retired UDM sitting in a drawer. One of those original Dream Machines from 2020, replaced by a UDM-Pro that now runs my home network. I also have a Raspberry Pi 3B+ that hasn't done anything useful since—actually I'm not sure it ever did anything useful. The idea was simple enough: take these two perfectly good pieces of hardware that are doing nothing, and turn them into a portable travel network that tunnels back home.

The architecture looks like this. Hotels with Ethernet: plug the UDM straight in. Hotels with only Wi-Fi: the Pi joins the hotel Wi-Fi as a client, NATs the traffic down its Ethernet port, and hands it to the UDM's WAN. Either way, the UDM runs the travel LAN, the firewall, and the VPN back home.

The Pi is just an upstream adapter. A $35 Wi-Fi-to-Ethernet translator running OpenWrt. It does not need to be smart. It needs to be reliable.

02 Flashing the Pi

OpenWrt 25.12.5 on a Raspberry Pi 3B+. Downloaded the image, verified the SHA256 like a responsible adult, and flashed it with Raspberry Pi Imager. None of the Imager's customization features—Wi-Fi credentials, SSH keys, hostname—work with OpenWrt images. Those inject Raspberry Pi OS config files that OpenWrt has never heard of. So the first boot was old school: Ethernet cable, SSH to 192.168.1.1, no password.

The recon was straightforward. Broadcom brcmfmac Wi-Fi driver, 2.4 GHz only, 100 Mbps Ethernet, 938 MB of RAM, and a 104 MB root partition that's more than enough for what amounts to a NAT box. All hardware limitations of the Pi 3B+. All acceptable for the job.

The configuration required some care. eth0 needed to come out of the default LAN bridge and become the downstream transit interface at 172.31.253.1/24. wlan0 needed to become the upstream WAN, joining my home Wi-Fi as a station client. DHCP server on eth0 only—because leaking DHCP onto a hotel Wi-Fi network is the kind of mistake that earns you a phone call from the front desk. NAT from eth0 toward wlan0. Firewall blocking all management access from the hotel side.

Wrote all the config files over SSH, issued a network restart, and the SSH session died. Expected. The Pi needed a full power cycle because the bridge didn't cleanly release eth0 during a soft restart. All to assign an IP address to a router sitting three feet from my keyboard.
03 The Chain

Once the Pi was working, connecting the travel UDM was almost anticlimactic. After quick factory reset, I changed the UDM's default LAN to 10.85.85.0/24, plugged its WAN into the Pi's eth0, and it picked up 172.31.253.128 via DHCP. Connected to the UDM's Wi-Fi from a MacBook Air, loaded a website, and the full chain was live.

Laptop, to UDM, to Pi, to home Wi-Fi, to internet. Four hops, two NATs, and it all just worked.

The conntrack table on the Pi told the whole story—TCP sessions established through to external hosts, DNS queries flowing to Quad9 and Cloudflare, NAT doing its thing. My other Mac was dual-homed on both the home network and the travel UDM, which caused some routing confusion that was entertaining to debug but ultimately irrelevant to the actual setup.

04 The VPN Saga

This is where it got educational. And by educational I mean fun: the kind of educational where you try three different VPN protocols before one of them agrees to cooperate.em>

I started with Unifi's SD-WAN Site Magic solution but was discqualified from that because I'm already running an instance of OSPF (Spoiler: I'm pretty certain that means Site Magic is running OSPF under the hood somehow, but I'd have to get it set up and trun a packet capture to verify.). IPsec was next because it's built into the UniFi UI on both devices. Auto IPsec, site-to-site—should be straightforward. Except UniFi's site-to-site VPN is a peer-to-peer model. Both sides need to know the other's IP or hostname. The home UDM-Pro has a public IP, but it's dynamic, so we set up DDNS through Cloudflare. home.cerasani.net, API token with zone edit permissions, and after some initial confusion about field names (hostname vs. zone name vs. the six other things Cloudflare calls things), the DDNS was updating correctly.

But the travel UDM will never have a public IP. It sits behind the Pi's NAT, which sits behind hotel Wi-Fi NAT. There is no address to point a DNS record at. Setting up traveler.cerasani.net would resolve to 172.31.253.128—a private address that means absolutely nothing to anyone on the internet.

OpenVPN was the next attempt. It wanted a 512-character pre-shared key and remote tunnel addresses. Got those configured, but hit the same fundamental wall: site-to-site means both sites need addresses.

The breakthrough was realizing we needed a client/server model, not peer-to-peer (no two-way key exchange necessary). WireGuard VPN Server on the home UDM-Pro—it just listens and accepts connections from anyone with the right key. WireGuard VPN Client on the travel UDM—it connects outbound to home.cerasani.net. The home side never needs to know the travel side's IP. The travel side initiates. NAT is irrelevant.

I couldn't even test the tunnel initially because the travel UDM was getting its internet through the Pi, which was on the home Wi-Fi, which meant the VPN endpoint was the same network we were sitting on. Had to tether the Pi to a phone hotspot to create a genuinely separate internet path. A sentence I did not anticipate writing today.

The tunnel came up. Status: Established. But nothing worked. Couldn't ping anything. Thanks to stale IPsec configurations still present, the routing table on the travel UDM showed all the right routes—interface "travel," type "static"—but every single one said "not installed" and listed no "next hop." The routes existed in configuration and refused to participate in reality. They were vapor routes.

Static routes pointing to the tunnel interface. That was it. Added them, and five home VLANs became reachable from a MacBook Air connected to a UDM connected to a Pi connected to a phone in my pocket.

05 What Actually Matters

The hard part was never the happy path. Flashing OpenWrt, configuring NAT, setting up DHCP—that's a 45-minute endevour. The learning part was understanding that IPsec assumes both endpoints are reachable, and that assumption falls apart the moment one side is permanently behind NAT without a public identity. The difficulty was realizing you can't test a VPN tunnel to your own network from inside your own network. The hurdle was a routing table full of correct entries that were on an extended coffee break, declining to do their jobs.

Each wrong turn taught something a tutorial never would have. IPsec showed me about its fundamental assumption of static, reachable endpoints—an assumption so baked into the protocol that UniFi's UI literally won't let you save the config without filling in both sides. The DDNS detoured me through the exact Cloudflare field mapping on UniFi. The hotspot pivot revealed that sometimes the testing environment is the problem, not the configuration.

The whole setup fits in a bag. A Pi, a UDM, two power supplies, and an Ethernet cable. Plug it in at any hotel, join the Wi-Fi from the Pi's LuCI interface, and every device on the travel LAN has a tunnel home. Five VLANs, reachable from anywhere with an internet connection and a 2.4 GHz radio.

A retired UDM from my drawer and a Pi that was collecting dust. Total new hardware cost: zero. Total hours invested in making it work: let's not.