fix: multi-hop federation — hub relay forwards without local participants
Some checks failed
Mirror to GitHub / mirror (push) Failing after 36s
Build Release Binaries / build-amd64 (push) Failing after 2m18s

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) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-08 13:33:44 +04:00
parent f4b5996bdf
commit 3d76acf528
3 changed files with 13 additions and 9 deletions

View File

@@ -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();

View File

@@ -76,4 +76,5 @@ pub struct CallStats {
pub struct RoomMember {
pub fingerprint: String,
pub alias: Option<String>,
pub relay_label: Option<String>,
}

View File

@@ -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);
}
}
}