mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
feat(node): warm routes to direct peers at the mesh edge + app-owned DNS seam
A mesh-edge node (only direct BLE / Wi-Fi Aware / LAN peers, sparse bloom filters) could not route to its own directly-connected neighbours: a neighbour is never in another peer's bloom filter, so discovery was gated off and find_next_hop had no coordinates. Traffic to such a peer failed with "no route to destination" even with an established session. - discovery: don't suppress a lookup on a bloom miss unless we have >2 peers (trustworthy blooms); and when no tree peer advertises the target, flood the LookupRequest to every sendable peer — including the target itself, which answers a lookup for its own address, so the querier learns its coordinates and can route. - session: when an established-session send hits no-route, trigger discovery (previously only session *initiation* did), so a coordless session — e.g. a platform-pushed peer whose Noise handshake came up before tree discovery — self-warms and the app's retransmit succeeds. - node: add Node::enable_app_owned_dns() — returns the DnsIdentityTx an embedder (Android VpnService pump) uses to register identities it resolved itself, giving the same identity-cache/route-warming the built-in DNS responder provides.
This commit is contained in:
@@ -439,14 +439,30 @@ impl Node {
|
||||
let origin_coords = self.tree_state().my_coords().clone();
|
||||
let request = LookupRequest::generate(*target, origin, origin_coords, ttl, 0);
|
||||
|
||||
// Send only to tree peers whose bloom filter contains the target
|
||||
let peer_addrs: Vec<NodeAddr> = self
|
||||
// Prefer tree peers whose bloom filter contains the target.
|
||||
let mut peer_addrs: Vec<NodeAddr> = self
|
||||
.peers
|
||||
.iter()
|
||||
.filter(|(addr, peer)| self.is_tree_peer(addr) && peer.may_reach(target))
|
||||
.map(|(addr, _)| *addr)
|
||||
.collect();
|
||||
|
||||
// Edge fallback: if no tree peer advertises the target — always the case
|
||||
// for a *directly connected* target, since a neighbour is never in another
|
||||
// peer's bloom — flood the request to EVERY peer we can send to, not just
|
||||
// tree peers. Crucially this includes the target itself when it's a direct
|
||||
// (non-tree) neighbour: a node answers a lookup for its own address, so the
|
||||
// querier learns its coordinates and can route to it. Without this a
|
||||
// mesh-edge node can't reach its own neighbours.
|
||||
if peer_addrs.is_empty() {
|
||||
peer_addrs = self
|
||||
.peers
|
||||
.iter()
|
||||
.filter(|(_, peer)| peer.can_send())
|
||||
.map(|(addr, _)| *addr)
|
||||
.collect();
|
||||
}
|
||||
|
||||
let peer_count = peer_addrs.len();
|
||||
|
||||
debug!(
|
||||
@@ -509,14 +525,22 @@ impl Node {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bloom filter pre-check: if no peer's filter contains the target,
|
||||
// it's not in the mesh — skip the lookup and record as failure.
|
||||
// Bloom filter pre-check: normally, if no peer's filter contains the
|
||||
// target we skip. But a *directly connected* peer is never in any other
|
||||
// peer's bloom (you reach it directly, not through them), so a mesh-edge
|
||||
// node — one whose only peers are direct links (BLE / Wi-Fi Aware / LAN),
|
||||
// with sparse blooms — could never discover coordinates for its own
|
||||
// neighbours and would fail to route to them. Only suppress on a bloom
|
||||
// miss when we have several peers whose blooms we can actually trust;
|
||||
// otherwise fall through and flood the lookup (bounded by TTL + the
|
||||
// `sent == 0` guard below).
|
||||
let reachable = self.peers.values().any(|peer| peer.may_reach(dest));
|
||||
if !reachable {
|
||||
if !reachable && self.peers.len() > 2 {
|
||||
self.metrics().discovery.req_bloom_miss.inc();
|
||||
self.discovery_backoff.record_failure(dest);
|
||||
debug!(
|
||||
target_node = %self.peer_display_name(dest),
|
||||
peers = self.peers.len(),
|
||||
"Discovery skipped, target not in any peer bloom filter"
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -2140,6 +2140,15 @@ impl Node {
|
||||
}
|
||||
if let Err(e) = self.send_ipv6_packet(&dest_addr, &ipv6_packet).await {
|
||||
debug!(dest = %self.peer_display_name(&dest_addr), error = %e, "Failed to send TUN packet via session");
|
||||
// An established session can still have no route when its
|
||||
// coordinates were never learned or went stale — e.g. a
|
||||
// platform-pushed peer (Wi-Fi Aware / LAN) whose Noise session
|
||||
// came up before tree discovery. Unlike session initiation
|
||||
// (below), this path didn't trigger a lookup, so the route
|
||||
// never warmed and every packet was silently dropped. Kick
|
||||
// discovery (rate-limited internally); the app's retransmit
|
||||
// then succeeds once coordinates arrive.
|
||||
self.maybe_initiate_lookup(&dest_addr).await;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2910,6 +2910,26 @@ impl Node {
|
||||
(outbound_tx, tun_rx)
|
||||
}
|
||||
|
||||
/// Set up an **app-owned DNS resolver**: an embedder that answers `.fips`
|
||||
/// queries itself (e.g. the Android VpnService packet pump, which has no
|
||||
/// system DNS socket) returns each resolved identity through the sender this
|
||||
/// returns. The `run_rx_loop` consumes them and calls
|
||||
/// [`Self::register_identity`] — the same identity-cache population (and hence
|
||||
/// route warming) the built-in [`crate::upper::dns::run_dns_responder`] does.
|
||||
///
|
||||
/// Without this the embedder's answers resolve the AAAA but leave the node's
|
||||
/// identity cache empty, so the first outbound packet to a freshly-resolved
|
||||
/// `<npub>.fips` has no cached pubkey to open a session with and is dropped.
|
||||
///
|
||||
/// Call after [`Node::new`] and **before** [`Self::start`], like
|
||||
/// [`Self::enable_app_owned_tun`].
|
||||
pub fn enable_app_owned_dns(&mut self) -> crate::upper::dns::DnsIdentityTx {
|
||||
let size = self.config().node.buffers.tun_channel.max(1);
|
||||
let (identity_tx, identity_rx) = tokio::sync::mpsc::channel(size);
|
||||
self.dns_identity_rx = Some(identity_rx);
|
||||
identity_tx
|
||||
}
|
||||
|
||||
// === Sending ===
|
||||
|
||||
/// Encrypt and send a link-layer message to an authenticated peer.
|
||||
|
||||
Reference in New Issue
Block a user