Skip to main content

Command Palette

Search for a command to run...

Part 2 - Network Design, Two Networks, and the IoT Problem

When your IoT devices ask for network access, you give them a subnet, a firewall, a DNS policy, and the kind of trust normally reserved for raccoons near open trash cans.

Updated
6 min read
Part 2 - Network Design, Two Networks, and the IoT Problem

Two Networks

My home network is not one network. It is two separate LAN segments on a Firewalla Gold Plus. Technically, there is also a third guest network, but guests do not need access to anything internal and I dont need to block ads and trackers for them either!

The main network (xxx.xxx.100.x) is where all the computers, phones, and everything that needs to talk to the cluster lives. The IoT network (xxx.xxx.36.x) is a separate physical LAN port on the Firewalla which is isolated from the main network by design. On this network, I have several cameras, smart TVs, smart doorbell, a treadmill, various speakers; basically the usual assortment of devices that have no business talking to anything on the main network.

The Firewalla routes traffic between them with a hard rule: .100 can reach into .36 (for management), but .36 cannot reach .100. Not by default, and definitely not by accident.

This is the correct way to run an IoT network. It's also the setup that immediately complicated everything when it came the time for me to point all devices at Pi-hole.


The Problem with IoT Isolation

I was going to deploy Pi-hole on my K3s cluster, which was on the .100 network. The MetalLB VIP (xxx.xxx.100.232) also was on the .100 network.

But, none of the .36 devices could reach it. Thankfully, that was the whole point of the isolation, but it did pose an annoying problem to solve.

My first approach was to configure the Firewalla to forward DNS queries from .36 network to xxx.xxx.100.232. Firewalla intercepts the queries, sends them to Pi-hole, and returns the answers, clean and simple. Except for one problem: every query arriving at Pi-hole from .36 devices showed the same source IP, which was Firewalla's .100 interface address. Close to 45 IoT devices, all appearing as one client. Poor Firewalla. With no per-device logging, I had no way of knowing which camera was querying that special server in Belarus!

This is DNS forwarding in a nutshell: the forwarder becomes the visible client. Unless you preserve or pass along the original source somehow, Pi-hole only sees the forwarder.

FYI: I deployed MetalLB using the official manifest because (according to the internet MetalLB experts) that’s the preferred path. MetalLB’s own documentation leads with kubectl apply, not Helm. Also, since there are no install-time config settings to manage, a values.yaml would mostly be just decorative YAML. So, I took the easy route for once and stuck with one command, the official source, and no separate Helm chart to babysit alongside the MetalLB version.


Firewalla DNS Booster: A Red Herring

Firewalla has a feature called DNS Booster. It intercepts DNS so Firewalla can apply its DNS features and forwarding behavior. The idea behind this feature is to provide faster, encrypted DNS for all devices without any configuration on the devices themselves.

But, this also introduced another challenge. DNS Booster was intercepting queries even after pointing devices at Pi-hole. Every query from a .100 device would go to xxx.xxx.100.232, then Firewalla would catch it anyway, and the query would appear in Pi-hole as coming from firewalla.home and not from the actual device. DNS Booster and its impacts were invisible to me until I started looking closely at the query log and saw that every single query came from the same source.

The fix was to turn DNS Booster off. Completely, for all networks.

This also meant disabling IPv6 on the LAN networks. IPv6 router advertisements were handing out IPv6 DNS server addresses to devices regardless of what Firewalla's DHCP was saying. Devices would use the IPv6 DNS server, bypass Pi-hole entirely, and ads would end up loading. In my setup, turning off IPv6 on the LAN while leaving it enabled on the WAN (my incoming FIOS) fixed this without breaking anything.


The Solution: A Narrow Firewall Rule

After a lot of head-scratching, the best solution turned out to be the simplest one. My Firewalla already routes between the two networks because it sits in the path between port 1 (.100 network) and port 2 (.36 network). So, I added a single firewall rule:

Source:      xxx.xxx.36.0/24
Destination: xxx.xxx.100.232
Port:        53 (TCP + UDP)
Action:      Allow

This punches one very specific hole in the isolation. .36 devices can reach Pi-hole on TCP/UDP port 53, and nothing else on the .100 network. They still cannot reach any computer, phone, or other device on .100. This way, my network isolation settings hold, but DNS works the way I wanted it to work.

Once the rule was in place, I updated my Firewalla's DHCP settings for the .36 network so that it started to hand out xxx.xxx.100.232 as the DNS server. Devices on .36 network now query Pi-hole directly. Their real .36 IP addresses show in the query log. Per-device logging works on both networks.


What We Have Now

Both networks route DNS through a single MetalLB VIP at xxx.xxx.100.232:53:

  • .100 devices query Pi-hole directly via DHCP DNS setting

  • .36 IoT devices reach Pi-hole through a narrow Firewalla firewall rule (port 53 only)

  • externalTrafficPolicy: Local ensures that real client source IPs are preserves end-to-end

  • Pi-hole will run as two pods on the k3s cluster, on different physical nodes

  • Pi-hole will forward upstream to DNSCrypt, which will use Cloudflare over encrypted DNS.

Both networks will Pi-hole, and Pi-hole will see the real client IPs from both sides. IoT isolation is still intact: .36 devices can reach exactly one .100 address, on one port, and nothing else.


Firewalla Configuration Checklist

For anyone with a similar setup:

  • DNS Booster: Off on all networks

  • IPv6: Disabled on all LAN networks (leave WAN enabled)

  • .100 DHCP DNS: xxx.xxx.100.232

  • .36 DHCP DNS: xxx.xxx.100.232

  • Firewall rule: Allow xxx.xxx.36.0/24xxx.xxx.100.232:53 (TCP & UDP)


No Firewalla? No Problem!

If you don't have a Firewalla device:

  • Option 1: Use a router that supports VLANs. Many prosumer routers support VLANs natively. Create two VLANs - one for main devices, one for IoT - and configure inter-VLAN firewall rules on the router. Same result as Firewalla, just more configuration.

  • Option 2: A managed switch + capable router. A managed switch can handle VLAN tagging and assign ports to VLANs. The router handles routing between VLANs with firewall rules. Classic enterprise approach scaled down to homelab.

An unmanaged switch will not help with creating or enforcing VLAN segmentation. Some may pass VLAN-tagged frames, but they cannot assign ports to VLANs or enforce inter-VLAN policy. You can also rule out most budget consumer routers, including nearly all ISP-provided ones, because they typically lack VLAN support and inter-network firewall rules.


Next: DNSCrypt: Encrypting the Upstream


Cover image generated by AI. The fortress may be fake, but the over-engineering is very real.

All YAML files referenced in this series are available in the companion GitHub repository.

More from this blog

N

Needless complexity, production-grade architecture!

11 posts

Came for the answer, stayed for the rabbit hole.

Accidental Complexity is my take on building things that probably didn't need to be this complicated and being completely fine with that. In other words, over-engineered solutions to under-complicated problems. A DNS server became a distributed cluster. A network monitor became a full observability stack. A 15-minute task became a multi-weekend yak shave.

No fluff, no "now, we will explore." Just the work and over-engineering.