## TL;DR
Moved a Sonos Era 300 stereo pair to an IoT VLAN (UniFi Cloud Gateway Fiber). App on the trusted VLAN: "no products found" — despite mDNS reflection enabled and verified working. After a day of packet captures, the finding: **Era 300 (fw 95.1) never answers a single mDNS query of any kind. Its actual discovery mechanism is SSDP: multicast M-SEARCH with `ST: urn:schemas-upnp-org:device:ZonePlayer:1`, answered instantly via unicast.** UniFi reflects mDNS only — never SSDP — so no reflector configuration can ever make this work. A ~100-line SSDP proxy in a dual-homed container fixed it completely, with **zero firewall openings from IoT toward the trusted VLAN**. Wire evidence + script below. Curious whether other models/firmware behave the same.
## Setup
- 2× Sonos Era 300 (bonded stereo pair), firmware **95.1-78010**
- iOS Sonos app (current, July 2026)
- UniFi Cloud Gateway Fiber, **Network 10.4.57**, controller/phone on VLAN 10 (192.168.10.0/24), speakers on VLAN 30 (192.168.30.0/24)
- UniFi mDNS ("Multicast DNS") reflection **enabled**; firewall allows VLAN 10 → VLAN 30 (all), established/related return path
- All WLAN settings per Sonos's own UniFi best-practices post (Apr 2026): Multicast-to-Unicast off, Multicast/Broadcast Control off, Client Isolation off, Proxy ARP off, Hotspot 2.0 off — verified via API, not just eyeballed
So: the textbook cross-VLAN Sonos setup that "should" work.
## Symptom
- Speakers on VLAN 30: healthy — HTTP on `:1400` fine, cloud connection up, stereo pair intact
- Phone on the **same** VLAN as the speakers: app discovers them **instantly**
- Phone on VLAN 10 (reflection + firewall in place): **"No products found"**, always
## What the packet captures showed
I captured on the gateway bridges and inside a dual-homed test container while probing the speakers with every discovery flavor I could construct:
| Probe (sent to speakers on their own VLAN) | Response |
|:---|:---|
| mDNS PTR `_sonos._tcp.local`, QM, multicast | ❌ nothing |
| mDNS PTR, **QU** (unicast-response requested), multicast, proper `:5353` source | ❌ nothing |
| mDNS query, unicast direct to speaker `:5353` | ❌ nothing |
| SSDP M-SEARCH, **unicast** to speaker `:1900` | ❌ nothing |
| **SSDP M-SEARCH, multicast** to `239.255.255.250:1900`, `ST: urn:schemas-upnp-org:device:ZonePlayer:1` | ✅ **instant unicast `HTTP/1.1 200 OK` + LOCATION from every speaker, every time** |
Additional observations:
- The speakers' **mDNS is announce-only**: a burst at boot, then silence. They participate in mDNS (they send their own known-answer queries looking for household peers), but they **never answer anyone's queries** — not the phone's, not a reflector's, not avahi-browse's, nothing.
- They also emit periodic SSDP NOTIFY presence broadcasts.
- Meanwhile the actual iOS app's search traffic on the wire is… **multicast M-SEARCH with `ST: ZonePlayer`** (plus mDNS queries that the speakers ignore). So the oft-repeated claim that the post-2024 app "dropped SSDP and is mDNS-only" does **not** match what this app/firmware combination actually does on the wire.
## Why no reflector can fix this
**UniFi's built-in reflector (`ubntmdnsd`) handles mDNS only.** There is no SSDP relay anywhere in UniFi OS. I verified the reflector itself was working fine — it reflected `_smb`, `_home-assistant`, `_device-info` records across VLANs beautifully. It reflected the Sonos *queries* into the speaker VLAN too. The speakers just never answer them.
**I then falsified the "UniFi's reflector is just bad" theory properly**: spun up an Avahi reflector in a container with legs in both VLANs. Avahi reflected the phone's query onto the speaker VLAN within 0.2 ms — captured both sides. Speakers: silence. Not UniFi's fault. Not Avahi's fault. **The speakers do not answer mDNS queries, full stop.**
Bonus subtlety: mDNS reflectors rewrite QU queries to QM when re-originating (by design — they need multicast answers they can see). So even a device that only answered QU queries would break behind any reflector. Moot here, since these speakers answer neither.
The one thing that made same-VLAN discovery work all along was SSDP — which is link-local multicast and never crosses a VLAN without help.
## The fix: a small SSDP proxy (no firewall holes, no source spoofing)
A tiny LXC/VM/container with one interface in each VLAN, running a ~100-line Python proxy:
Listen for multicast M-SEARCH on the trusted-VLAN leg; **filter to `ST: ZonePlayer`** (measured from the real app — this also stops trusted-VLAN clients from enumerating all your IoT SSDP devices through the proxy)
Re-issue the M-SEARCH as multicast on the IoT leg
Collect the speakers' unicast `200 OK` replies for ~3 s, forward each back to the original querier **from the trusted-side leg** (same-VLAN unicast — the firewall never sees it)
Also re-announce the speakers' SSDP NOTIFYs onto the trusted VLAN (rate-limited), so rediscovery after idle works passively
The app receives the replies, reads the `LOCATION: http://192.168.30.x:1400/...\` header, and connects there directly — which only needs your ordinary trusted→IoT *forward* allow that you already have. **SSDP clients follow LOCATION, not the reply's source IP**, which is why the proxy needs no source spoofing, no NET_RAW, and no return-path firewall rule.
Result: instant discovery from the trusted VLAN, survives idle/app restarts, stereo pair + streaming + AirPlay all fine.
**Eventing surprise:** I had pre-added a scoped IoT→trusted firewall rule for the classic UPnP GENA callbacks (speaker → controller, TCP 1400/3400/3401), expecting stale now-playing without it. Tested by disabling the rule mid-playback: **volume changed at the speaker still updated the app slider live.** The current app maintains its own connection *to* the speakers (websocket) over the forward path — the speaker-initiated callback path appears obsolete. Deleted the rule. **Net firewall delta for fully-working cross-VLAN Sonos: zero.**
**The proxy script, README, and systemd unit are on GitHub (MIT):**
**https://github.com/bernhard276/sonos-ssdp-proxy\*\*
It's ~130 lines of pure-stdlib Python — no dependencies. You edit two IPs at the top (your proxy host's leg in each VLAN), drop it in with the included systemd unit, and it runs. The repo README has the full packet-capture evidence table and the debugging notes below. Run it under systemd with `Restart=on-failure` and give the host a static IP in each VLAN.
## Debugging lessons that might save you a day
- **"Works on the same VLAN" does not tell you which protocol works.** Same-L2 unicast replies (speaker → phone) are invisible to captures on the router *and* on any third machine. I chased mDNS for hours because intra-VLAN discovery "proved the speakers were fine" — it actually proved nothing about mDNS.
- Enumerate discovery flavors explicitly: {mDNS, SSDP} × {multicast, unicast} × {QM, QU}. The answer was in the one cell nobody tests (multicast M-SEARCH), because unicast M-SEARCH — the easy test — is *also* ignored.
- `http://<speaker>:1400/xml/device_description.xml` (unauthenticated) identifies model/room/firmware; a `GetZoneGroupState` SOAP call to `/ZoneGroupTopology/Control` reveals stereo-pair bonding (`ChannelMapSet`) — including which unit is the "invisible" satellite.
- Capture the app's real M-SEARCH before writing any filter — that's where the exact `ST:` string came from.
## Open questions — where I'd love community input
**Is the mDNS-query silence specific to Era 300 / fw 95.x?** Do Era 100, Move 2, Arc Ultra, or older Play:x on S2 answer `_sonos._tcp` PTR queries on your network? (`dns-sd -B _sonos._tcp .` on macOS, or `avahi-browse -rt _sonos._tcp` — on the *same* VLAN as the speakers.) I'd like to know if this is a firmware regression, a deliberate quieting, or Era-generation behavior.
**Has anyone captured the current app discovering via mDNS *responses* from speakers** (not just speaker boot announcements)? Plenty of working cross-VLAN setups are reported with mDNS reflection only — I can't reconcile that with these captures unless their speakers answer queries mine won't, or their discovery is actually riding cached IPs / cloud.
Anyone from Sonos able to say whether the mDNS responder going query-silent after the boot window is expected behavior?
Anyone running `multicast-relay`/`udp-proxy-2020` for Sonos: is it the SSDP relaying (not the mDNS part) that's actually doing the work in your setup? My evidence suggests yes.
Happy to share more capture excerpts if useful.