Skip to content

fix(airplay): unique per-zone RAOP identifier — zones collided, AirPlay hit wrong room (#356) - #357

Closed
tobsch wants to merge 1 commit into
sonn-audio:devfrom
tobsch:fix/airplay-unique-zone-id
Closed

tobsch wants to merge 1 commit into
sonn-audio:devfrom
tobsch:fix/airplay-unique-zone-id

Conversation

@tobsch

@tobsch tobsch commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #356 — root cause found and fix verified live (patched into the running container's dist).

Root cause

deriveHardwareAddress(sourceMac, zoneId) in airplayInstance.ts builds each zone's RAOP identifier by
adding zoneId to the last byte of the zone's sourceMac:

bytes[5] = ((bytes[5] ?? 0) + (zoneId & 0xff)) & 0xff;

But the per-zone sourceMac values are not unique — several zones share one (in this install:
…1067, …1068, …1069, …106A each appear on two zones). Adding zoneId on top then collides whenever
sourceMacLastByte + zoneId coincides:

zone sourceMac +zoneId identifier
5 Wohnzimmer …1068 +5 …106D
6 Elternbad …1067 +6 …106D ⚠️
9 Küche …106A +9 …1073
10 Außenbereich …1069 +10 …1073 ⚠️

So Wohnzimmer/Elternbad and Küche/Außenbereich each advertised the same _raop._tcp identifier (only the
port differed). An AirPlay client that selects by identifier lands on whichever resolves first — verified:
streaming to 88AEDD68106D played into Elternbad, not the intended Wohnzimmer.

Fix

zoneId is unique per zone, so derive the last byte from it directly rather than adding it to a non-unique
base:

bytes[5] = zoneId & 0xff;

Verification (dist-patched into the running container)

All 11 zones now advertise distinct identifiers, and every zone shows up in discovery (previously the
colliding ones shadowed each other):

…1001 Kinderzimmer Linda   …1005 Wohnzimmer     …1009 Küche
…1002 Kinderzimmer Louise  …1006 Elternbad      …100A Außenbereich
…1004 Esszimmer            …1007 Elternschlafz. …100B Gästebad
                           …1008 Backupküche    …100C Einliegerwohnung

Streaming an AirPlay tone to Wohnzimmer's id (…1005) now lands in the right room —
[Input|AirPlay][Wohnzimmer] airplay pcm chunk … zoneId=5.

(Deeper cleanup out of scope here: the per-zone sourceMac values probably shouldn't be assigned with
duplicates in the first place. Making the identifier derive from the unique zoneId fixes the user-visible
collision regardless.)

…d) (sonn-audio#356)

deriveHardwareAddress added zoneId to the last byte of the per-zone sourceMac, but
those sourceMacs are not unique across zones — so the sum collided: sourceMac ..68
+zoneId 5 and ..67 +zoneId 6 both produced ..6D. Wohnzimmer and Elternbad therefore
advertised the same RAOP/AirPlay identifier (and Küche/Außenbereich another), so an
AirPlay client selecting by identifier streamed into the wrong room.

zoneId is unique per zone, so set the last byte from it directly instead of adding
it to a non-unique base. Verified: all 11 zones now advertise distinct identifiers
(…1001…100C) and an AirPlay stream targeted at Wohnzimmer's id reaches zoneId=5.

Fixes sonn-audio#356.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rudyberends

Copy link
Copy Markdown
Collaborator

Went with a slightly different fix: the address is now derived from a SHA-256 hash of serial+zoneId, with the locally-administered bit set. Setting the last byte to zoneId alone fixes a single server, but two audioservers with near-adjacent serials (identical first five bytes) would still collide on equal player ids — hashing the full pair covers that case too, and the locally-administered bit keeps the synthetic address out of the real MAC space. Unit tests cover both collision classes, including your ..68+5 == ..67+6 case.

rudyberends added a commit that referenced this pull request Aug 29, 2026
deriveHardwareAddress added zoneId to the last byte of the zone's
sourceMac, but sourceMac is the serial of whichever device carries the
zone's outputs — shared by zones on one extension, with sibling serials
differing only in the last bytes. The sums collided (..68+5 == ..67+6),
so two rooms advertised the same AirPlay identifier and senders that
select by id landed in the wrong room.

Derive the address from a hash of serial+zoneId instead: unique for
every (device, zone) pair — also across audioservers with adjacent
serials, which setting the last byte to zoneId alone would still let
collide — and marked locally administered so it can never coincide
with a real device's MAC.

Fixes #356. Closes #357.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants