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:
Arjen
2026-07-25 10:00:26 +01:00
parent 527514689f
commit e8270970ee
3 changed files with 58 additions and 5 deletions
+20
View File
@@ -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.