feat: P3-T5 route resolution — find relay path to any fingerprint

RouteResolver queries PresenceRegistry to determine how to reach a target:
- Route::Local — connected to this relay
- Route::DirectPeer(addr) — on a directly connected peer relay
- Route::Chain(addrs) — multi-hop (structure ready, single-hop for now)
- Route::NotFound — not in any known relay

Protocol: added SignalMessage::RouteQuery { fingerprint, ttl } and
RouteResponse { fingerprint, found, relay_chain } for peer-to-peer
route queries over probe connections.

HTTP API: GET /route/:fingerprint returns JSON with route type + chain.

Relay handles incoming RouteQuery on probe connections: looks up locally,
replies with RouteResponse. TTL decremented for future multi-hop forwarding.

55 relay tests + 42 proto tests passing (7 new route tests).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-29 18:38:24 +04:00
parent 464e95a4bd
commit 326aa491cc
6 changed files with 349 additions and 3 deletions

View File

@@ -180,12 +180,16 @@ async fn main() -> anyhow::Result<()> {
// Presence registry
let presence = Arc::new(Mutex::new(PresenceRegistry::new()));
// Route resolver
let route_resolver = Arc::new(wzp_relay::route::RouteResolver::new(config.listen_addr));
// Prometheus metrics
let metrics = Arc::new(RelayMetrics::new());
if let Some(port) = config.metrics_port {
let m = metrics.clone();
let p = Some(presence.clone());
tokio::spawn(wzp_relay::metrics::serve_metrics(port, m, p));
let rr = Some(route_resolver.clone());
tokio::spawn(wzp_relay::metrics::serve_metrics(port, m, p, rr));
}
// Generate ephemeral relay identity for crypto handshake
@@ -251,6 +255,7 @@ async fn main() -> anyhow::Result<()> {
let metrics = metrics.clone();
let trunking_enabled = config.trunking_enabled;
let presence = presence.clone();
let route_resolver = route_resolver.clone();
tokio::spawn(async move {
let addr = connection.remote_address();
@@ -301,6 +306,39 @@ async fn main() -> anyhow::Result<()> {
break;
}
}
Ok(Some(wzp_proto::SignalMessage::RouteQuery { fingerprint, ttl })) => {
// Look up the fingerprint in our local registry
let reg = presence.lock().await;
let route = route_resolver.resolve(&reg, &fingerprint);
drop(reg);
let (found, relay_chain) = match route {
wzp_relay::route::Route::Local => {
(true, vec![route_resolver.local_addr().to_string()])
}
wzp_relay::route::Route::DirectPeer(peer_addr) => {
(true, vec![route_resolver.local_addr().to_string(), peer_addr.to_string()])
}
_ => {
// Not found locally; if ttl > 0 we could forward
// to other peers (future multi-hop). For now, reply not found.
if ttl > 0 {
// TODO: forward RouteQuery to other peers with ttl-1
}
(false, vec![])
}
};
let reply = wzp_proto::SignalMessage::RouteResponse {
fingerprint,
found,
relay_chain,
};
if let Err(e) = transport.send_signal(&reply).await {
error!(%addr, "route response send error: {e}");
break;
}
}
Ok(Some(_)) => {
// Ignore other signals on probe connections
}