mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-10 08:37:02 +00:00
node: introduce Reloadable trait and migrate host map to a lock-free snapshot
Add a `Reloadable` trait that normalizes the node's reloadable configuration/resource pattern onto a single contract built around an `arc_swap::ArcSwap` snapshot: a lock-free `load()` for the hot read path and an async `reload()` that re-reads the backing source and atomically swaps in a fresh snapshot. The trait carries the canonical Arc-wrapper template documentation (single-writer node tick, many-reader hot path, whole-snapshot swap so readers never observe a partial update). Migrate the host map to this trait via a new `HostMapReloadable` that reuses the existing load/merge/mtime helpers in upper::hosts. The Node `host_map` field changes from `Arc<HostMap>` to `HostMapReloadable`, and `peer_display_name` reads through a lock-free guard. The initial snapshot is byte-identical to the previous construction, so behavior is unchanged. The host map is still snapshotted once at construction and not polled; `reload()` is exercised only by unit tests for now. Wiring the periodic poll into the node tick, and deduplicating the hosts-file stat against the ACL reloader's embedded copy, is left as a follow-up. Add `arc-swap` as a dependency. Unit tests cover initial load (base + file, base only), change/no-change/deletion/creation detection, base-preserved-on-reload, and equivalence of the initial snapshot to the pre-migration construction.
This commit is contained in:
+13
-15
@@ -15,6 +15,7 @@ mod handlers;
|
||||
mod lifecycle;
|
||||
mod rate_limit;
|
||||
pub(crate) mod reject;
|
||||
mod reloadable;
|
||||
mod retry;
|
||||
mod routing_error_rate_limit;
|
||||
pub(crate) mod session;
|
||||
@@ -28,6 +29,7 @@ pub(crate) mod wire;
|
||||
|
||||
use self::discovery_rate_limit::{DiscoveryBackoff, DiscoveryForwardRateLimiter};
|
||||
use self::rate_limit::HandshakeRateLimiter;
|
||||
use self::reloadable::Reloadable;
|
||||
use self::routing_error_rate_limit::RoutingErrorRateLimiter;
|
||||
|
||||
/// Half-range of the symmetric jitter applied to the per-session rekey timer.
|
||||
@@ -501,8 +503,9 @@ pub struct Node {
|
||||
|
||||
// === Host Map ===
|
||||
/// Static hostname → npub mapping for DNS resolution.
|
||||
/// Built at construction from peer aliases and /etc/fips/hosts.
|
||||
host_map: Arc<HostMap>,
|
||||
/// Built at construction from peer aliases and /etc/fips/hosts, and
|
||||
/// published through a lock-free snapshot for the display path.
|
||||
host_map: reloadable::HostMapReloadable,
|
||||
|
||||
/// Off-task FMP-encrypt + UDP-send worker pool. Unix-only —
|
||||
/// the worker issues direct sendmmsg(2) / sendmsg+UDP_GSO calls
|
||||
@@ -595,13 +598,9 @@ impl Node {
|
||||
let forward_min_interval_secs = config.node.discovery.forward_min_interval_secs;
|
||||
|
||||
let base_host_map = HostMap::from_peer_configs(config.peers());
|
||||
let mut host_map = base_host_map.clone();
|
||||
let hosts_path = std::path::PathBuf::from(crate::upper::hosts::DEFAULT_HOSTS_PATH);
|
||||
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);
|
||||
let host_map =
|
||||
reloadable::HostMapReloadable::new(base_host_map.clone(), hosts_path.clone());
|
||||
let peer_acl = acl::PeerAclReloader::with_alias_sources(
|
||||
std::path::PathBuf::from(acl::DEFAULT_PEERS_ALLOW_PATH),
|
||||
std::path::PathBuf::from(acl::DEFAULT_PEERS_DENY_PATH),
|
||||
@@ -744,16 +743,14 @@ impl Node {
|
||||
let coords_response_interval_ms = config.node.session.coords_response_interval_ms;
|
||||
|
||||
let base_host_map = HostMap::from_peer_configs(config.peers());
|
||||
let mut host_map = base_host_map.clone();
|
||||
host_map.merge(HostMap::load_hosts_file(std::path::Path::new(
|
||||
crate::upper::hosts::DEFAULT_HOSTS_PATH,
|
||||
)));
|
||||
let host_map = Arc::new(host_map);
|
||||
let hosts_path = std::path::PathBuf::from(crate::upper::hosts::DEFAULT_HOSTS_PATH);
|
||||
let host_map =
|
||||
reloadable::HostMapReloadable::new(base_host_map.clone(), hosts_path.clone());
|
||||
let peer_acl = acl::PeerAclReloader::with_alias_sources(
|
||||
std::path::PathBuf::from(acl::DEFAULT_PEERS_ALLOW_PATH),
|
||||
std::path::PathBuf::from(acl::DEFAULT_PEERS_DENY_PATH),
|
||||
base_host_map,
|
||||
std::path::PathBuf::from(crate::upper::hosts::DEFAULT_HOSTS_PATH),
|
||||
hosts_path,
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -1084,7 +1081,8 @@ impl Node {
|
||||
/// 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) {
|
||||
let hosts = self.host_map.load();
|
||||
if let Some(hostname) = hosts.lookup_hostname(addr) {
|
||||
return hostname.to_string();
|
||||
}
|
||||
if let Some(name) = self.peer_aliases.get(addr) {
|
||||
|
||||
Reference in New Issue
Block a user