Skip to content
Go back

IoT VLANs That Don't Break Chromecast

By KingPin 15 min read
IoT VLANs That Don't Break Chromecast
Contents

You Built the VLAN and Now Nothing Can Cast

You put the smart plugs, TVs, and Chromecast on VLAN 20. You wrote a firewall rule that keeps them away from your NAS. You felt great for about ninety seconds. Then you opened YouTube on your phone and the cast button was gone.

The VLAN did its job. The discovery protocol did not get the memo. Chromecast, AirPlay, Sonos, HomeKit, and most printers find each other with multicast packets that never cross a router. To keep both the segmentation and the casting, you need two separate fixes: firewall rules that let trusted devices start connections into the IoT VLAN, and a reflector that copies discovery packets between the two networks. Skip either one and something breaks at 2 AM when a guest wants to cast a video.

The addresses below are examples. Trusted LAN is 10.0.10.0/24 on VLAN 10. IoT is 10.0.20.0/24 on VLAN 20.

Full example: Clone the working files at github.com/KingPin/sumguy-examples/networking/iot-vlans-without-breaking-chromecast

Why Discovery Dies at the VLAN Edge

Two protocols do most of the work.

Neither packet crosses a router, but not because of the TTL. mDNS goes to 224.0.0.251, inside the 224.0.0.0/24 local network control block that routers never forward off the link (RFC 6762 asks for an IP TTL of 255, which old receivers use to check the packet came from the local link). SSDP goes to 239.255.255.250 with a default TTL of 2, and no home router runs multicast routing for it. So your phone on VLAN 10 shouts “who is a Chromecast?” and the shout never leaves VLAN 10.

Routing the multicast is not the fix. A reflector is a small daemon that sits on both VLANs, hears a query on one, and repeats it on the other. It also repeats the answers. The devices never know they are on different networks. They just see a neighbor.

Half One: The VLAN and the Firewall Rules

First, the VLAN. On a Linux router or server, a sub-interface is one command:

Terminal window
sudo ip link add link eth0 name eth0.20 type vlan id 20
sudo ip addr add 10.0.20.1/24 dev eth0.20
sudo ip link set eth0.20 up

That does not survive a reboot. For a permanent setup, use systemd-networkd with three small files:

/etc/systemd/network/10-eth0.network
[Match]
Name=eth0
[Network]
VLAN=eth0.20
/etc/systemd/network/20-eth0.20.netdev
[NetDev]
Name=eth0.20
Kind=vlan
[VLAN]
Id=20
/etc/systemd/network/30-eth0.20.network
[Match]
Name=eth0.20
[Network]
Address=10.0.20.1/24

If you run OPNsense, pfSense, or UniFi, you create the VLAN in the UI instead and skip all of this. The rules are the same idea.

The rule set

The policy is short:

  1. Trusted to IoT: allow new connections.
  2. IoT to trusted: allow only replies to connections trusted started.
  3. IoT to internet: allow (most cloud devices need it).
  4. Everything else from IoT: drop.

Here it is as nftables on a Linux router. The WAN interface is eth1.

/etc/nftables.conf
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
# DHCP and DNS from both LAN segments
iifname { "eth0.10", "eth0.20" } udp dport { 53, 67 } accept
iifname { "eth0.10", "eth0.20" } tcp dport 53 accept
# mDNS to the router itself (needed for the reflector, see below)
iifname { "eth0.10", "eth0.20" } udp dport 5353 accept
}
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
# trusted -> IoT: new connections allowed
iifname "eth0.10" oifname "eth0.20" accept
# IoT -> Home Assistant only (single host on trusted side)
iifname "eth0.20" oifname "eth0.10" ip daddr 10.0.10.50 tcp dport 8123 accept
# trusted and IoT -> internet
iifname { "eth0.10", "eth0.20" } oifname "eth1" accept
}
}

Three things to notice.

The ct state established,related accept line is the one that lets the Chromecast answer your phone. Without it, your phone’s connection goes out and the replies die.

The Home Assistant line is the “allow IoT to reach only one host” pattern. Devices that push data to HA (a webhook, the API on 8123) can reach ha.lan at 10.0.10.50 and nothing else on the trusted side. Add one line per service. Do not widen it to the whole /24 because it is Tuesday and you are tired.

The input chain accepts UDP 5353 from both VLANs. The multicast address 224.0.0.251 is delivered to the router itself, so a default-deny input policy blocks your own reflector if you forget this line.

On OPNsense or pfSense the same policy is a table of rules on the IoT interface, evaluated top to bottom:

IoT interface rules (first match wins)
1. Pass IoT net -> ha.lan (10.0.10.50) TCP 8123
2. Pass IoT net -> This Firewall UDP 53, UDP 67 (DNS, DHCP)
3. Block IoT net -> RFC1918 networks any
4. Pass IoT net -> any any (internet)
Trusted interface rules
1. Pass Trusted net -> any any

The block on private ranges in rule 3 is what makes IoT unable to reach the trusted VLAN. Because firewalls are stateful, the replies to trusted-initiated connections still come back. Rule order matters: the Home Assistant pass sits above the private-range block, or it never matches.

For cloud-free devices (a Shelly running local-only, a Zigbee hub), you can drop rule 4 and give them no internet at all. Test one device first. A lot of “local” gear phones home for NTP.

Half Two: Make Discovery Cross the Gap

Three options exist. They all do the same job.

Option A: Avahi in reflector mode

Avahi works anywhere you have a Linux box with a leg on both VLANs. The router itself, a mini PC with a trunk port, or a container with host networking.

/etc/avahi/avahi-daemon.conf
[server]
use-ipv6=no
allow-interfaces=eth0.10,eth0.20
enable-dbus=no
[reflector]
enable-reflector=yes

allow-interfaces= limits Avahi to the listed interfaces. enable-reflector=yes is the switch, and it defaults to no. The reflector copies mDNS from each interface to all the others it listens on. enable-dbus=no lets the daemon run in a container with no D-Bus daemon. The docs say the default is yes when Avahi was built with D-Bus support.

Two more [reflector] keys exist: reflect-ipv (bridges IPv4 and IPv6, default no) and reflect-filters, a comma-separated list of service names to reflect. The default reflects everything. Use reflect-filters when you want Chromecast discovery and nothing else:

avahi-daemon.conf (reflector section, narrowed)
[reflector]
enable-reflector=yes
reflect-filters=_googlecast._tcp.local,_airplay._tcp.local

Check the exact service names against what avahi-browse -art shows on your network before you trust that list. The filter is a substring match against the service instance name (the PTR target and the SRV/TXT owner name), so _googlecast._tcp.local matches Chromecast-abc._googlecast._tcp.local, and a hostname fragment works too.

Here is a Compose file with host networking, which Avahi needs so it can see the VLAN interfaces on the host. It builds on a plain Alpine image and installs Avahi at start. Pin your own image if you want it reproducible.

compose.yaml
services:
avahi-reflector:
image: alpine:3
network_mode: host
restart: unless-stopped
volumes:
- ./avahi-daemon.conf:/etc/avahi/avahi-daemon.conf:ro
command: >
sh -c "apk add --no-cache avahi &&
rm -f /etc/avahi/services/*.service &&
exec avahi-daemon --no-drop-root --no-chroot
-f /etc/avahi/avahi-daemon.conf"

The rm -f line deletes the package’s default ssh and sftp-ssh service files. Without it, the reflector host advertises its own SSH to every VLAN it listens on. -f picks the config file. --no-chroot and --no-drop-root skip the chroot and the privilege drop. The man page recommends against --no-drop-root. In a throwaway container with a read-only config it is a fair trade, but it is a trade. Do not run a second Avahi on the same host, and stop the host’s own avahi-daemon.service first, or the two fight over port 5353.

Option B: OPNsense and pfSense

On OPNsense, install the os-mdns-repeater plugin. A new “MDNS Repeater” entry appears under Services. Tick Enabled and pick the interfaces. The docs list LAN, OPT1, and OPT2 as the example. The page also warns that the repeater needs at least 2 interfaces and works with no more than 5.

That is all the configuration there is. It is the lowest-effort fix if you already run OPNsense. It repeats mDNS only, and the plugin page documents no per-service filtering. On pfSense, the equivalent is the Avahi package, which has its own repeater settings. I am not going to quote its menu names from memory. Check the package page for your version.

Option C: UniFi

UniFi gateways ship a built-in mDNS proxy. Ubiquiti’s help center describes three modes: Auto (relays a predefined list of common services between VLANs), Off, and Custom (you choose services and networks). A newer help center article labels them All, Auto, and Custom and says all mDNS traffic is forwarded by default. Both say the proxy is enabled on newly created networks and can be turned off per network, so look under the network’s settings in your version and read the labels you actually have.

For a typical home setup, Auto does what you want. Then fix the firewall, because the proxy does nothing about that.

Which one

If you run OPNsense or UniFi, use the built-in one. Do not stand up Avahi because it sounds fun. If your router is a Linux box, a cheap router with VLAN support, or you need reflect-filters, use Avahi. Hiring a forklift to move a couch works, but your neighbors will have questions.

Discovery Is Only Half of Casting

The 2 AM version of this failure: mDNS works, the Chromecast shows up in the app, you tap it, and nothing plays.

Discovery tells the phone where the Chromecast is. After that, the phone opens a direct connection to it. A community write-up that tested casting across networks (linked below) lists these flows from the sender to the Chromecast:

The only Google page I found that names these ports is the Nest and Chromecast security tips page, which tells you to delete port forwards for 8008, 8009, and 8443. It is not a firewall list, so treat the list above as community-measured. It can change when Google changes the protocol.

With the nftables policy above, trusted to IoT is already open, so every one of those works. If you tightened trusted-to-IoT to specific ports, add these. The Chromecast’s replies need no rule, because ct state established,related handles them.

A few apps differ. VLC, for example, makes the Chromecast pull the video file from the VLC host, so the connection starts from IoT toward trusted. The same write-up documents TCP 8010 for that case. You will need a narrow rule from the Chromecast to your PC on that port, or you will need to use another casting source.

Casting from a phone on the guest Wi-Fi is a different problem, and you probably do not want to solve it. See the last section.

Sonos Hates You a Little

Sonos adds its own port list. Sonos support documents TCP 1400 and a long set of control ports, UDP 1900 and 1901 for app control, UDP 6969 for setup, TCP 4070 and UDP 5353 for Spotify Connect, and TCP 7000-7003 for AirPlay, among others. The full list is on Sonos’s firewall page (linked below).

Two consequences follow. Avahi does not reflect SSDP, so the UDP 1900 side needs a separate SSDP relay, and I will not recommend one I have not tested. And your Sonos app, your phone, and the speakers all want to be on one network.

My advice is blunt. Leave Sonos on the trusted VLAN. Speakers with a vendor app carry far less risk than a random Tuya plug. If you insist on isolating them, expect an afternoon of rule tuning and the occasional “no speakers found” screen after an app update.

Switches, IGMP, and Wi-Fi

If your managed switch has IGMP snooping on, it prunes multicast to ports that joined the group. A switch that follows RFC 4541 still floods 224.0.0.0/24, which covers mDNS, but plenty of cheap switches prune it anyway, and SSDP on 239.255.255.250 is always subject to pruning. Snooping needs a querier on the VLAN to keep the memberships alive, and a VLAN with no router interface running one can get flaky discovery after a few minutes. On a small network, turn snooping off on the IoT VLAN or enable a querier. Wi-Fi access points add their own multicast tricks (rate limits, multicast-to-unicast conversion), so if discovery works wired and fails on Wi-Fi, look at the AP settings first.

Thread and Matter Make This Weird

Matter devices are found with mDNS too. Thread devices sit on a separate low-power IPv6 mesh, and a Thread border router (some Apple TV, Google Nest, and Home Assistant setups act as one) bridges that mesh to your IP network. The border router advertises itself with mDNS and passes IPv6 traffic.

That means your IoT VLAN design now has one more thing that must sit somewhere. The simplest layout is to keep the border router and the Matter controller (Home Assistant) on the same VLAN. Splitting them forces the reflector to carry Matter and Thread service records, and IPv6 routing has to work between the two. Matter and Thread lean on IPv6, so drop use-ipv6=no from the Avahi config if you split them. I have not verified every Matter path across a reflector, so test with one device before you buy ten.

The Opposite Failure: Reflecting Into Guest

A reflector copies traffic between every interface it listens on. Add your guest VLAN to allow-interfaces=, or to the OPNsense interface list, and every guest can now see your printers, your Chromecast, your NAS shares, and any service that advertises over mDNS. Guests can also cast to your living-room TV at midnight.

The fix is boring. List only the VLANs that need discovery. Never list guest. In Avahi, an interface that is not in allow-interfaces= is ignored. On OPNsense, an interface you do not add to the repeater gets nothing. If you want a guest to cast, give them a separate temporary Chromecast on the guest network, not a bridge into your house.

Troubleshooting

Work from the bottom of the stack up.

Terminal window
# On the router or reflector host: do packets arrive on the IoT side?
sudo tcpdump -ni eth0.20 udp port 5353
# Is the Chromecast advertising? (run on a host in the IoT VLAN)
avahi-browse -art
# On a Mac in the trusted VLAN: can you see it across the gap?
dns-sd -B _googlecast._tcp
  1. Run tcpdump on eth0.20. If you see no 5353 traffic, the Chromecast is not speaking, or the switch is pruning it.
  2. Run tcpdump on eth0.10 while you open the Cast menu on your phone. You should see the query arrive and the reflected answer leave.
  3. Run avahi-browse -art from the trusted side. Entries for _googlecast._tcp should appear.
  4. Run dns-sd -B _googlecast._tcp on macOS. The Chromecast should be listed with its friendly name.
  5. If discovery works but casting fails, it is the firewall. Check the counters on your trusted-to-IoT rule and the established/related rule.

Common Questions

Do I need a VLAN to keep Chromecast away from my other devices?

No, a VLAN is not required, but it is the cleanest option. Network gear with client isolation or per-device firewall rules can also restrict a Chromecast. A VLAN with a default-deny rule toward the trusted LAN is easier to audit than per-device rules, and it keeps working when new gadgets arrive.

Why does Chromecast show up in the app but won’t play?

Chromecast shows up because discovery worked, and it fails to play because the direct connection is blocked. The phone must reach the Chromecast on TCP 8008-8009 and send media over UDP in the 32768-61000 range. Allow trusted to IoT for new connections and allow established and related replies back.

Does the Avahi reflector work with AirPlay and HomeKit?

Yes, Avahi reflects any mDNS service, so AirPlay and HomeKit advertisements cross VLANs the same way Chromecast ones do. The Avahi reflector does not carry SSDP, which some Sonos functions use. HomeKit accessories advertise _hap._tcp, and an Apple TV or HomePod home hub must reach them too, so test one accessory first.

Can I roll back if the reflector breaks something?

Yes. Set enable-reflector=no in avahi-daemon.conf and restart the service, or untick Enabled in the OPNsense MDNS Repeater page, or switch the UniFi mDNS mode to Off. Existing devices keep working on their own VLAN. Only cross-VLAN discovery stops, and the firewall rules stay as they are.

Is Chromecast still a thing in 2026?

Google replaced the Chromecast hardware with the Google TV Streamer in 2024. The Cast protocol still discovers devices over mDNS, so the reflector steps apply to any Cast receiver. Verify the ports for your own device with a packet capture, because the port list above is community-measured.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
Cloud Gaming Latency: It's Not Just Ping

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts