Add host-to-npub static mapping with DNS hostname resolution

Add a HostMap that resolves human-readable hostnames to npubs,
enabling `gateway.fips` instead of the full `npub1...xyz.fips`.
Two sources populate the map: peer `alias` fields from the YAML
config and an operator-maintained hosts file at /etc/fips/hosts.

The DNS responder auto-reloads the hosts file on each request by
checking the file modification time, so operators can update
mappings without restarting the daemon.

- New src/upper/hosts.rs: HostMap, HostMapReloader, hostname
  validation, hosts file parser with auto-reload on mtime change
- DNS resolver checks host map before falling back to direct npub
- Node uses host map for peer display names
- Default hosts file added to both .deb and tarball packaging
- 26 new tests (789 total)
This commit is contained in:
Johnathan Corgan
2026-03-08 18:34:54 +00:00
parent ead91c75da
commit 0bb6e70fb5
10 changed files with 1033 additions and 28 deletions
+5 -2
View File
@@ -448,10 +448,13 @@ impl Node {
let dns_channel_size = self.config.node.buffers.dns_channel;
let (identity_tx, identity_rx) = tokio::sync::mpsc::channel(dns_channel_size);
let dns_ttl = self.config.dns.ttl();
let handle = tokio::spawn(crate::upper::dns::run_dns_responder(socket, identity_tx, dns_ttl));
let base_hosts = crate::upper::hosts::HostMap::from_peer_configs(self.config.peers());
let hosts_path = std::path::PathBuf::from(crate::upper::hosts::DEFAULT_HOSTS_PATH);
let reloader = crate::upper::hosts::HostMapReloader::new(base_hosts, hosts_path);
info!(bind = %bind, hosts = reloader.hosts().len(), "DNS responder started for .fips domain (auto-reload enabled)");
let handle = tokio::spawn(crate::upper::dns::run_dns_responder(socket, identity_tx, dns_ttl, reloader));
self.dns_identity_rx = Some(identity_rx);
self.dns_task = Some(handle);
info!(bind = %bind, "DNS responder started for .fips domain");
}
Err(e) => {
warn!(bind = %bind, error = %e, "Failed to start DNS responder");
+26 -4
View File
@@ -33,6 +33,7 @@ use crate::transport::tcp::TcpTransport;
#[cfg(target_os = "linux")]
use crate::transport::ethernet::EthernetTransport;
use crate::tree::TreeState;
use crate::upper::hosts::HostMap;
use crate::upper::icmp_rate_limit::IcmpRateLimiter;
use crate::upper::tun::{TunError, TunOutboundRx, TunState, TunTx};
use self::wire::{build_encrypted, build_established_header, prepend_inner_header, FLAG_CE, FLAG_KEY_EPOCH, FLAG_SP};
@@ -40,6 +41,7 @@ use crate::{Config, ConfigError, Identity, IdentityError, NodeAddr, PeerIdentity
use rand::Rng;
use std::collections::{HashMap, VecDeque};
use std::fmt;
use std::sync::Arc;
use std::thread::JoinHandle;
use thiserror::Error;
@@ -380,6 +382,11 @@ pub struct Node {
/// Human-readable names for configured peers (alias or short npub).
/// Populated at startup from peer config.
peer_aliases: HashMap<NodeAddr, String>,
// === Host Map ===
/// Static hostname → npub mapping for DNS resolution.
/// Built at construction from peer aliases and /etc/fips/hosts.
host_map: Arc<HostMap>,
}
impl Node {
@@ -433,6 +440,13 @@ impl Node {
let max_links = config.node.limits.max_links;
let coords_response_interval_ms = config.node.session.coords_response_interval_ms;
let mut host_map = HostMap::from_peer_configs(config.peers());
let hosts_file = HostMap::load_hosts_file(std::path::Path::new(
crate::upper::hosts::DEFAULT_HOSTS_PATH,
));
host_map.merge(hosts_file);
let host_map = Arc::new(host_map);
Ok(Self {
identity,
startup_epoch,
@@ -483,6 +497,7 @@ impl Node {
last_parent_reeval: None,
last_congestion_log: None,
peer_aliases: HashMap::new(),
host_map,
})
}
@@ -530,6 +545,8 @@ impl Node {
let max_links = config.node.limits.max_links;
let coords_response_interval_ms = config.node.session.coords_response_interval_ms;
let host_map = Arc::new(HostMap::new());
Self {
identity,
startup_epoch,
@@ -580,6 +597,7 @@ impl Node {
last_parent_reeval: None,
last_congestion_log: None,
peer_aliases: HashMap::new(),
host_map,
}
}
@@ -730,11 +748,15 @@ impl Node {
/// Return a human-readable display name for a NodeAddr.
///
/// Lookup order:
/// 1. Configured peer alias or short npub (from startup map)
/// 2. Active peer's short npub (e.g., inbound peer not in config)
/// 3. Session endpoint's short npub (end-to-end, may not be direct peer)
/// 4. Truncated NodeAddr hex (unknown address)
/// 1. Host map hostname (from peer aliases + /etc/fips/hosts)
/// 2. Configured peer alias or short npub (from startup map)
/// 3. Active peer's short npub (e.g., inbound peer not in config)
/// 4. Session endpoint's short npub (end-to-end, may not be direct peer)
/// 5. Truncated NodeAddr hex (unknown address)
pub(crate) fn peer_display_name(&self, addr: &NodeAddr) -> String {
if let Some(hostname) = self.host_map.lookup_hostname(addr) {
return hostname.to_string();
}
if let Some(name) = self.peer_aliases.get(addr) {
return name.clone();
}