chore(vendor): report the #127 SSRC-tombstone race (and the #36 trailer fix) upstream to serenity-rs/songbird, then unwind the vendor #128

Open
opened 2026-07-26 04:29:39 +00:00 by robocub · 0 comments
Collaborator

Tracking issue for reporting the #127 SSRC-tombstone race upstream to serenity-rs/songbird (and deciding what to do about our vendor divergence). The operator files this by hand — agent PRs/issues to upstream are out.

What to report

Title suggestion: Receive side goes permanently deaf to a user who leaves and rejoins voice within one SSRC-cleanup interval (DAVE packets silently dropped)

Mechanism (all paths as of upstream current, present since the DAVE PR #291; our tree reproduces it at v0.5.x-vendored):

  1. ws.rs: ClientDisconnectssrc_signalling.disconnected_users.insert(user_id) — a lazy tombstone.
  2. udp_rx/mod.rs: the periodic cleanup (every 5 s) consumes tombstones: user_ssrc_map.remove(&id) → removes the returned SSRC from ssrc_user_map.
  3. ws.rs: Speakinguser_ssrc_map.insert(user, new_ssrc) (re-keying the same user).

If the user leaves and rejoins between two sweeps (a moderator kick + a bot's auto-rejoin, a fast channel hop, a voice-server blip), step 3 lands before step 2 — so the sweep removes the live mapping for the new SSRC. From then on every DAVE-encrypted packet from that SSRC fails the ssrc_user_map lookup in the decrypt path and is silently dropped (that branch has no logging), permanently: the next Speaking for that user won't re-fire (state didn't change), so the mapping is never restored. The receiving client is deaf to that user until the voice connection is rebuilt. Non-DAVE receive is degraded too (SSRC no longer attributable to a user), but DAVE turns it into total loss.

Reproduction: two clients in a DAVE voice channel; kick and rejoin client B within ~5 s of a sweep; observe client A's VoiceTick never carries B's audio again (B's new SSRC in neither speaking nor mapped). Our live CI hit this at ~60 % per attempt; timing-sensitivity tracks Discord's voice-state propagation latency.

Fix (what we vendored — commit a4a94b4 in this repo, vendor/songbird/src/driver/tasks/ws.rs): on Speaking, (a) disconnected_users.remove(user_id) — a re-speaking user is not disconnected, so a pending tombstone must not reap the fresh mapping; (b) eagerly remove the old ssrc_user_map reverse entry when the SSRC changed. ~10 lines, no API change. Optionally the old SSRC's decoder state could also be retired eagerly (it currently lingers in VoiceTick.silent until decode_state_timeout, which is what exposed our recorder artifact — harmless to songbird itself).

Also worth mentioning upstream: the three silent-drop branches in the DAVE receive path (unmapped SSRC, !is_ready(), NoDecryptorForUser) made this bug invisible; we added debug! lines to each (same commit) and would suggest upstream do likewise.

Also pending upstream (same vendor tree)

  • #36 DAVE-trailer corruption fix — the original reason this songbird is vendored (with davey 0.1.4). Still not reported/PR'd upstream. Filing both together makes one coherent story: two DAVE bugs + diagnostics, then we can unwind the vendor once both land in a release (constraint #7: re-pin to the fixed release, never float).

Checklist

  • File the tombstone-race issue upstream (serenity-rs/songbird), evidence above; link back here.
  • File / attach the #36 trailer fix (see docs/+ #36 history for the diff and analysis).
  • Offer the fixes as PRs (or confirm maintainers prefer their own patch).
  • When released upstream: re-pin, drop vendor/songbird, close this + #36 vendor-unwind TODO.
Tracking issue for reporting the #127 SSRC-tombstone race upstream to **serenity-rs/songbird** (and deciding what to do about our vendor divergence). The operator files this by hand — agent PRs/issues to upstream are out. ## What to report **Title suggestion:** *Receive side goes permanently deaf to a user who leaves and rejoins voice within one SSRC-cleanup interval (DAVE packets silently dropped)* **Mechanism** (all paths as of upstream `current`, present since the DAVE PR #291; our tree reproduces it at v0.5.x-vendored): 1. `ws.rs`: `ClientDisconnect` → `ssrc_signalling.disconnected_users.insert(user_id)` — a lazy tombstone. 2. `udp_rx/mod.rs`: the periodic cleanup (every 5 s) consumes tombstones: `user_ssrc_map.remove(&id)` → removes the returned SSRC from `ssrc_user_map`. 3. `ws.rs`: `Speaking` → `user_ssrc_map.insert(user, new_ssrc)` (re-keying the same user). If the user leaves **and** rejoins between two sweeps (a moderator kick + a bot's auto-rejoin, a fast channel hop, a voice-server blip), step 3 lands before step 2 — so the sweep removes the **live** mapping for the new SSRC. From then on every DAVE-encrypted packet from that SSRC fails the `ssrc_user_map` lookup in the decrypt path and is **silently dropped** (that branch has no logging), permanently: the next `Speaking` for that user won't re-fire (state didn't change), so the mapping is never restored. The receiving client is deaf to that user until the voice connection is rebuilt. Non-DAVE receive is degraded too (SSRC no longer attributable to a user), but DAVE turns it into total loss. **Reproduction:** two clients in a DAVE voice channel; kick and rejoin client B within ~5 s of a sweep; observe client A's `VoiceTick` never carries B's audio again (B's new SSRC in neither `speaking` nor mapped). Our live CI hit this at ~60 % per attempt; timing-sensitivity tracks Discord's voice-state propagation latency. **Fix (what we vendored — commit `a4a94b4` in this repo, `vendor/songbird/src/driver/tasks/ws.rs`):** on `Speaking`, (a) `disconnected_users.remove(user_id)` — a re-speaking user is not disconnected, so a pending tombstone must not reap the fresh mapping; (b) eagerly remove the *old* `ssrc_user_map` reverse entry when the SSRC changed. ~10 lines, no API change. Optionally the old SSRC's decoder state could also be retired eagerly (it currently lingers in `VoiceTick.silent` until `decode_state_timeout`, which is what exposed our recorder artifact — harmless to songbird itself). Also worth mentioning upstream: the three silent-drop branches in the DAVE receive path (`unmapped SSRC`, `!is_ready()`, `NoDecryptorForUser`) made this bug invisible; we added `debug!` lines to each (same commit) and would suggest upstream do likewise. ## Also pending upstream (same vendor tree) - **#36 DAVE-trailer corruption fix** — the original reason this songbird is vendored (with davey 0.1.4). Still not reported/PR'd upstream. Filing both together makes one coherent story: two DAVE bugs + diagnostics, then we can unwind the vendor once both land in a release (constraint #7: re-pin to the fixed release, never float). ## Checklist - [ ] File the tombstone-race issue upstream (serenity-rs/songbird), evidence above; link back here. - [ ] File / attach the #36 trailer fix (see `docs/`+ #36 history for the diff and analysis). - [ ] Offer the fixes as PRs (or confirm maintainers prefer their own patch). - [ ] When released upstream: re-pin, drop `vendor/songbird`, close this + #36 vendor-unwind TODO.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
dark/nether-voicebridge#128
No description provided.