From 3d76acf528b364544749370fd96a8f3af6853b8a Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Wed, 8 Apr 2026 13:33:44 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20multi-hop=20federation=20=E2=80=94=20hub?= =?UTF-8?q?=20relay=20forwards=20without=20local=20participants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes for 3-relay chain (R1→R2→R3): 1. Room lookup in handle_datagram: hub relay (R2) has no local participants, so active_rooms() was empty and datagrams were silently dropped. Now also checks global_rooms config directly, allowing hub relays to forward without local clients. 2. Multi-hop forwarding: removed active_rooms filter — forward to ALL connected peers except source. The receiving peer decides whether to deliver or forward further. 3. Android relay_label: native RoomMember now includes relay_label from RoomUpdate signal. Kotlin UI reads it for relay grouping. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/wzp-android/src/engine.rs | 1 + crates/wzp-android/src/stats.rs | 1 + crates/wzp-relay/src/federation.rs | 20 +++++++++++--------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/wzp-android/src/engine.rs b/crates/wzp-android/src/engine.rs index 4f8c814..cfb1812 100644 --- a/crates/wzp-android/src/engine.rs +++ b/crates/wzp-android/src/engine.rs @@ -852,6 +852,7 @@ async fn run_call( .map(|p| crate::stats::RoomMember { fingerprint: p.fingerprint.clone(), alias: p.alias.clone(), + relay_label: p.relay_label.clone(), }) .collect(); let mut stats = state_signal.stats.lock().unwrap(); diff --git a/crates/wzp-android/src/stats.rs b/crates/wzp-android/src/stats.rs index 8fee1e2..07aae39 100644 --- a/crates/wzp-android/src/stats.rs +++ b/crates/wzp-android/src/stats.rs @@ -76,4 +76,5 @@ pub struct CallStats { pub struct RoomMember { pub fingerprint: String, pub alias: Option, + pub relay_label: Option, } diff --git a/crates/wzp-relay/src/federation.rs b/crates/wzp-relay/src/federation.rs index b34b2eb..58f4dfa 100644 --- a/crates/wzp-relay/src/federation.rs +++ b/crates/wzp-relay/src/federation.rs @@ -688,19 +688,22 @@ async fn handle_datagram( } } - // Find room by hash + // Find room by hash — check local rooms AND global room config let room_name = { let mgr = fm.room_mgr.lock().await; - { let active = mgr.active_rooms(); + // First: check local rooms (has participants) active.iter().find(|r| room_hash(r) == rh).cloned() .or_else(|| active.iter().find(|r| fm.global_room_hash(r) == rh).cloned()) - } + // Second: check global room config (hub relay may have no local participants) + .or_else(|| { + fm.global_rooms.iter().find(|name| room_hash(name) == rh).cloned() + }) }; let room_name = match room_name { Some(r) => r, - None => return, // room not active locally + None => return, // not a known room }; // Rate limit per room @@ -725,16 +728,15 @@ async fn handle_datagram( } } - // Multi-hop: forward to OTHER active peers (not the source) + // Multi-hop: forward to ALL other connected peers (not the source) + // Don't filter by active_rooms — the receiving peer decides whether to deliver let links = fm.peer_links.lock().await; for (fp, link) in links.iter() { - if fp != source_peer_fp && link.active_rooms.contains(&room_name) { + if fp != source_peer_fp { let mut tagged = Vec::with_capacity(8 + media_bytes.len()); tagged.extend_from_slice(&rh); tagged.extend_from_slice(&media_bytes); - if let Err(e) = link.transport.send_raw_datagram(&tagged) { - warn!(peer = %link.label, "multi-hop forward error: {e}"); - } + let _ = link.transport.send_raw_datagram(&tagged); } } }