feat: relay federation infrastructure — room bridging, loop prevention, peer connections
Some checks failed
Mirror to GitHub / mirror (push) Failing after 36s
Build Release Binaries / build-amd64 (push) Failing after 2m1s

Phase 1 of relay federation:

1. Signal messages: FederationRoomJoin/Leave/ParticipantUpdate added
   to SignalMessage enum for relay-to-relay room coordination.

2. Room changes: ParticipantOrigin (Local/Federated) tracking, loop
   prevention (federated media only forwards to local participants),
   ParticipantSender::Federation with 8-byte room-hash prefixed
   datagrams, merged participant lists (local + remote), new methods:
   join_federated(), update_federated_participants(), local_senders(),
   active_rooms(), local_participants().

3. FederationManager: connects to configured peers via QUIC with SNI
   "_federation", reconnects with exponential backoff (5s-300s),
   exchanges FederationRoomJoin signals, runs recv loops for both
   signals and media datagrams, creates virtual participants in rooms.

4. Accept-side: _federation SNI handling in main.rs, unknown peer
   gets helpful "add to relay.toml" log message, recognized peers
   handed off to FederationManager.

TODO: TLS fingerprint verification — currently outbound connections
use client_config() which doesn't present a cert, so inbound
verification fails. Need mutual TLS or URL-based peer matching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-07 22:30:18 +04:00
parent 2f2720802d
commit 6be36e43c2
8 changed files with 516 additions and 6 deletions

View File

@@ -320,6 +320,21 @@ async fn main() -> anyhow::Result<()> {
// Room manager (room mode only)
let room_mgr = Arc::new(Mutex::new(RoomManager::new()));
// Federation manager
let federation_mgr = if !config.peers.is_empty() {
let fm = Arc::new(wzp_relay::federation::FederationManager::new(
config.peers.clone(),
room_mgr.clone(),
endpoint.clone(),
tls_fp.clone(),
));
let fm_run = fm.clone();
tokio::spawn(async move { fm_run.run().await });
Some(fm)
} else {
None
};
// Session manager — enforces max concurrent sessions
let session_mgr = Arc::new(Mutex::new(SessionManager::new(config.max_sessions)));
@@ -375,6 +390,7 @@ async fn main() -> anyhow::Result<()> {
let trunking_enabled = config.trunking_enabled;
let presence = presence.clone();
let route_resolver = route_resolver.clone();
let federation_mgr = federation_mgr.clone();
tokio::spawn(async move {
let addr = connection.remote_address();
@@ -482,6 +498,38 @@ async fn main() -> anyhow::Result<()> {
return;
}
// Federation connections use SNI "_federation"
if room_name == "_federation" {
if let Some(ref fm) = federation_mgr {
// Check if we recognize this peer by TLS fingerprint
let peer_fp = wzp_transport::tls_fingerprint(
&transport.connection()
.peer_identity()
.and_then(|id| id.downcast::<Vec<rustls::pki_types::CertificateDer>>().ok())
.and_then(|certs| certs.first().cloned())
.map(|c| c.to_vec())
.unwrap_or_default()
);
if let Some(peer_config) = fm.find_peer_by_fingerprint(&peer_fp) {
let peer_config = peer_config.clone();
let fm = fm.clone();
info!(%addr, label = ?peer_config.label, "inbound federation connection accepted");
fm.handle_inbound(transport, peer_config).await;
} else {
warn!(%addr, "unknown relay wants to federate");
info!(" to accept, add to relay.toml:");
info!(" [[peers]]");
info!(" url = \"{addr}\"");
info!(" fingerprint = \"{peer_fp}\"");
transport.close().await.ok();
}
} else {
info!(%addr, "federation connection rejected (no peers configured)");
transport.close().await.ok();
}
return;
}
// Auth check: if --auth-url is set, expect first signal message to be a token
// Auth: if --auth-url is set, expect AuthToken as first signal
let authenticated_fp: Option<String> = if let Some(ref url) = auth_url {