mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-12 09:33:23 +00:00
Merge branch 'master' into next
# Conflicts: # src/node/lifecycle.rs # testing/static/scripts/rekey-test.sh
This commit is contained in:
+194
-6
@@ -18,7 +18,7 @@
|
||||
//! nsec: "nsec1..."
|
||||
//! ```
|
||||
|
||||
#[cfg(feature = "gateway")]
|
||||
#[cfg(target_os = "linux")]
|
||||
mod gateway;
|
||||
mod node;
|
||||
mod peer;
|
||||
@@ -30,12 +30,12 @@ use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(feature = "gateway")]
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use gateway::{ConntrackConfig, GatewayConfig, GatewayDnsConfig, PortForward, Proto};
|
||||
pub use node::{
|
||||
BloomConfig, BuffersConfig, CacheConfig, ControlConfig, DiscoveryConfig, LimitsConfig,
|
||||
NodeConfig, RateLimitConfig, RekeyConfig, RetryConfig, SessionConfig, SessionMmpConfig,
|
||||
TreeConfig,
|
||||
NodeConfig, NostrDiscoveryConfig, NostrDiscoveryPolicy, RateLimitConfig, RekeyConfig,
|
||||
RetryConfig, SessionConfig, SessionMmpConfig, TreeConfig,
|
||||
};
|
||||
pub use peer::{ConnectPolicy, PeerAddress, PeerConfig};
|
||||
pub use transport::{
|
||||
@@ -337,6 +337,9 @@ pub enum ConfigError {
|
||||
|
||||
#[error("identity error: {0}")]
|
||||
Identity(#[from] IdentityError),
|
||||
|
||||
#[error("invalid configuration: {0}")]
|
||||
Validation(String),
|
||||
}
|
||||
|
||||
/// Identity configuration (`node.identity.*`).
|
||||
@@ -378,7 +381,7 @@ pub struct Config {
|
||||
pub peers: Vec<PeerConfig>,
|
||||
|
||||
/// Gateway configuration (`gateway`).
|
||||
#[cfg(feature = "gateway")]
|
||||
#[cfg(target_os = "linux")]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub gateway: Option<GatewayConfig>,
|
||||
}
|
||||
@@ -500,7 +503,7 @@ impl Config {
|
||||
self.peers = other.peers;
|
||||
}
|
||||
// Merge gateway section — higher-priority config replaces entirely
|
||||
#[cfg(feature = "gateway")]
|
||||
#[cfg(target_os = "linux")]
|
||||
if other.gateway.is_some() {
|
||||
self.gateway = other.gateway;
|
||||
}
|
||||
@@ -552,6 +555,69 @@ impl Config {
|
||||
self.peers.iter().filter(|p| p.is_auto_connect())
|
||||
}
|
||||
|
||||
/// Validate cross-field configuration invariants.
|
||||
pub fn validate(&self) -> Result<(), ConfigError> {
|
||||
let nostr = &self.node.discovery.nostr;
|
||||
|
||||
let any_transport_advertises_on_nostr = self
|
||||
.transports
|
||||
.udp
|
||||
.iter()
|
||||
.any(|(_, cfg)| cfg.advertise_on_nostr())
|
||||
|| self
|
||||
.transports
|
||||
.tcp
|
||||
.iter()
|
||||
.any(|(_, cfg)| cfg.advertise_on_nostr())
|
||||
|| self
|
||||
.transports
|
||||
.tor
|
||||
.iter()
|
||||
.any(|(_, cfg)| cfg.advertise_on_nostr());
|
||||
|
||||
if any_transport_advertises_on_nostr && !nostr.enabled {
|
||||
return Err(ConfigError::Validation(
|
||||
"at least one transport has `advertise_on_nostr = true`, but `node.discovery.nostr.enabled` is false".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if self.peers.iter().any(|peer| peer.via_nostr) && !nostr.enabled {
|
||||
return Err(ConfigError::Validation(
|
||||
"at least one peer has `via_nostr = true`, but `node.discovery.nostr.enabled` is false".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
for (i, peer) in self.peers.iter().enumerate() {
|
||||
if peer.addresses.is_empty() && !peer.via_nostr {
|
||||
return Err(ConfigError::Validation(format!(
|
||||
"peers[{i}] ({}): must specify at least one address, or set `via_nostr = true` to resolve endpoints from the Nostr advert",
|
||||
peer.npub
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let has_nat_udp_advert = self
|
||||
.transports
|
||||
.udp
|
||||
.iter()
|
||||
.any(|(_, cfg)| cfg.advertise_on_nostr() && !cfg.is_public());
|
||||
|
||||
if nostr.enabled && has_nat_udp_advert {
|
||||
if nostr.dm_relays.is_empty() {
|
||||
return Err(ConfigError::Validation(
|
||||
"NAT UDP advert publishing requires `node.discovery.nostr.dm_relays` to be non-empty".to_string(),
|
||||
));
|
||||
}
|
||||
if nostr.stun_servers.is_empty() {
|
||||
return Err(ConfigError::Validation(
|
||||
"NAT UDP advert publishing requires `node.discovery.nostr.stun_servers` to be non-empty".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serialize this configuration to YAML.
|
||||
pub fn to_yaml(&self) -> Result<String, serde_yaml::Error> {
|
||||
serde_yaml::to_string(self)
|
||||
@@ -1122,4 +1188,126 @@ peers:
|
||||
assert_eq!(peer.addresses.len(), 2);
|
||||
assert!(peer.is_auto_connect());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_nostr_discovery_config() {
|
||||
let yaml = r#"
|
||||
node:
|
||||
discovery:
|
||||
nostr:
|
||||
enabled: true
|
||||
advertise: false
|
||||
policy: configured_only
|
||||
open_discovery_max_pending: 12
|
||||
app: "fips.nat.test.v1"
|
||||
signal_ttl_secs: 45
|
||||
advert_relays:
|
||||
- "wss://relay-a.example"
|
||||
dm_relays:
|
||||
- "wss://relay-b.example"
|
||||
stun_servers:
|
||||
- "stun:stun.example.org:3478"
|
||||
peers:
|
||||
- npub: "npub1peer"
|
||||
via_nostr: true
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "nat"
|
||||
"#;
|
||||
let config: Config = serde_yaml::from_str(yaml).unwrap();
|
||||
assert!(config.node.discovery.nostr.enabled);
|
||||
assert!(!config.node.discovery.nostr.advertise);
|
||||
assert_eq!(config.node.discovery.nostr.app, "fips.nat.test.v1");
|
||||
assert_eq!(config.node.discovery.nostr.signal_ttl_secs, 45);
|
||||
assert_eq!(
|
||||
config.node.discovery.nostr.policy,
|
||||
NostrDiscoveryPolicy::ConfiguredOnly
|
||||
);
|
||||
assert_eq!(config.node.discovery.nostr.open_discovery_max_pending, 12);
|
||||
assert_eq!(
|
||||
config.node.discovery.nostr.advert_relays,
|
||||
vec!["wss://relay-a.example".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
config.node.discovery.nostr.dm_relays,
|
||||
vec!["wss://relay-b.example".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
config.node.discovery.nostr.stun_servers,
|
||||
vec!["stun:stun.example.org:3478".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
config.peers[0].addresses[0].addr, "nat",
|
||||
"udp:nat address should parse without special-casing in YAML"
|
||||
);
|
||||
assert!(config.peers[0].via_nostr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_transport_advert_requires_nostr_enabled() {
|
||||
let mut config = Config::default();
|
||||
config.transports.udp = TransportInstances::Single(UdpConfig {
|
||||
advertise_on_nostr: Some(true),
|
||||
..Default::default()
|
||||
});
|
||||
config.node.discovery.nostr.enabled = false;
|
||||
|
||||
let err = config.validate().expect_err("validation should fail");
|
||||
assert!(err.to_string().contains("advertise_on_nostr"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
fn test_validate_peer_via_nostr_requires_nostr_enabled() {
|
||||
let mut config = Config::default();
|
||||
config.peers = vec![PeerConfig {
|
||||
npub: "npub1peer".to_string(),
|
||||
via_nostr: true,
|
||||
..Default::default()
|
||||
}];
|
||||
config.node.discovery.nostr.enabled = false;
|
||||
|
||||
let err = config.validate().expect_err("validation should fail");
|
||||
assert!(err.to_string().contains("via_nostr"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
fn test_validate_peer_addresses_required_unless_via_nostr() {
|
||||
// Empty addresses + via_nostr=false → error.
|
||||
let mut config = Config::default();
|
||||
config.peers = vec![PeerConfig {
|
||||
npub: "npub1peer".to_string(),
|
||||
..Default::default()
|
||||
}];
|
||||
let err = config.validate().expect_err("validation should fail");
|
||||
assert!(err.to_string().contains("at least one address"));
|
||||
|
||||
// Empty addresses + via_nostr=true + nostr.enabled=true → ok.
|
||||
config.peers[0].via_nostr = true;
|
||||
config.node.discovery.nostr.enabled = true;
|
||||
config
|
||||
.validate()
|
||||
.expect("via_nostr should allow empty addresses");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_nat_udp_advert_requires_relays_and_stun() {
|
||||
let mut config = Config::default();
|
||||
config.node.discovery.nostr.enabled = true;
|
||||
config.node.discovery.nostr.dm_relays.clear();
|
||||
config.transports.udp = TransportInstances::Single(UdpConfig {
|
||||
advertise_on_nostr: Some(true),
|
||||
public: Some(false),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let err = config.validate().expect_err("validation should fail");
|
||||
assert!(err.to_string().contains("dm_relays"));
|
||||
|
||||
config.node.discovery.nostr.dm_relays = vec!["wss://relay.example".to_string()];
|
||||
config.node.discovery.nostr.stun_servers.clear();
|
||||
let err = config.validate().expect_err("validation should fail");
|
||||
assert!(err.to_string().contains("stun_servers"));
|
||||
}
|
||||
}
|
||||
|
||||
+228
-27
@@ -192,14 +192,20 @@ pub struct DiscoveryConfig {
|
||||
/// Hop limit for LookupRequest flood (`node.discovery.ttl`).
|
||||
#[serde(default = "DiscoveryConfig::default_ttl")]
|
||||
pub ttl: u8,
|
||||
/// Lookup completion timeout in seconds (`node.discovery.timeout_secs`).
|
||||
#[serde(default = "DiscoveryConfig::default_timeout_secs")]
|
||||
pub timeout_secs: u64,
|
||||
/// Per-attempt timeouts in seconds (`node.discovery.attempt_timeouts_secs`).
|
||||
/// Each entry is the time to wait for a response before sending the next
|
||||
/// LookupRequest (with a fresh request_id). Sequence length determines the
|
||||
/// total number of attempts before declaring the destination unreachable.
|
||||
/// Default `[1, 2, 4, 8]` gives 4 attempts and a 15s total budget.
|
||||
#[serde(default = "DiscoveryConfig::default_attempt_timeouts_secs")]
|
||||
pub attempt_timeouts_secs: Vec<u64>,
|
||||
/// Dedup cache expiry in seconds (`node.discovery.recent_expiry_secs`).
|
||||
#[serde(default = "DiscoveryConfig::default_recent_expiry_secs")]
|
||||
pub recent_expiry_secs: u64,
|
||||
/// Base backoff after first lookup failure in seconds (`node.discovery.backoff_base_secs`).
|
||||
/// Doubles per consecutive failure up to `backoff_max_secs`.
|
||||
/// Base backoff after lookup failure in seconds (`node.discovery.backoff_base_secs`).
|
||||
/// Doubles per consecutive failure up to `backoff_max_secs`. Defaults to 0
|
||||
/// (no post-failure suppression); the per-attempt sequence in
|
||||
/// `attempt_timeouts_secs` provides the only retry pacing.
|
||||
#[serde(default = "DiscoveryConfig::default_backoff_base_secs")]
|
||||
pub backoff_base_secs: u64,
|
||||
/// Maximum backoff cap in seconds (`node.discovery.backoff_max_secs`).
|
||||
@@ -210,28 +216,21 @@ pub struct DiscoveryConfig {
|
||||
/// Defense-in-depth against misbehaving nodes.
|
||||
#[serde(default = "DiscoveryConfig::default_forward_min_interval_secs")]
|
||||
pub forward_min_interval_secs: u64,
|
||||
/// Retry interval within the timeout window in seconds
|
||||
/// (`node.discovery.retry_interval_secs`).
|
||||
/// After this interval without a response, resend the lookup.
|
||||
#[serde(default = "DiscoveryConfig::default_retry_interval_secs")]
|
||||
pub retry_interval_secs: u64,
|
||||
/// Maximum attempts per lookup (`node.discovery.max_attempts`).
|
||||
/// 1 = no retry, 2 = one retry, etc.
|
||||
#[serde(default = "DiscoveryConfig::default_max_attempts")]
|
||||
pub max_attempts: u8,
|
||||
/// Nostr-mediated overlay endpoint discovery.
|
||||
#[serde(default = "DiscoveryConfig::default_nostr")]
|
||||
pub nostr: NostrDiscoveryConfig,
|
||||
}
|
||||
|
||||
impl Default for DiscoveryConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ttl: 64,
|
||||
timeout_secs: 10,
|
||||
attempt_timeouts_secs: vec![1, 2, 4, 8],
|
||||
recent_expiry_secs: 10,
|
||||
backoff_base_secs: 30,
|
||||
backoff_max_secs: 300,
|
||||
backoff_base_secs: 0,
|
||||
backoff_max_secs: 0,
|
||||
forward_min_interval_secs: 2,
|
||||
retry_interval_secs: 5,
|
||||
max_attempts: 2,
|
||||
nostr: NostrDiscoveryConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,26 +239,228 @@ impl DiscoveryConfig {
|
||||
fn default_ttl() -> u8 {
|
||||
64
|
||||
}
|
||||
fn default_timeout_secs() -> u64 {
|
||||
10
|
||||
fn default_attempt_timeouts_secs() -> Vec<u64> {
|
||||
vec![1, 2, 4, 8]
|
||||
}
|
||||
fn default_recent_expiry_secs() -> u64 {
|
||||
10
|
||||
}
|
||||
fn default_backoff_base_secs() -> u64 {
|
||||
30
|
||||
0
|
||||
}
|
||||
fn default_backoff_max_secs() -> u64 {
|
||||
300
|
||||
0
|
||||
}
|
||||
fn default_forward_min_interval_secs() -> u64 {
|
||||
2
|
||||
}
|
||||
fn default_retry_interval_secs() -> u64 {
|
||||
5
|
||||
fn default_nostr() -> NostrDiscoveryConfig {
|
||||
NostrDiscoveryConfig::default()
|
||||
}
|
||||
fn default_max_attempts() -> u8 {
|
||||
2
|
||||
}
|
||||
|
||||
/// Nostr advert discovery policy.
|
||||
///
|
||||
/// Controls how overlay endpoint adverts are consumed:
|
||||
/// - `disabled`: ignore advert-derived endpoints for all peers
|
||||
/// - `configured_only`: allow advert fallback only for configured peers with
|
||||
/// `peers[].via_nostr = true`
|
||||
/// - `open`: also consider adverts for non-configured peers
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NostrDiscoveryPolicy {
|
||||
Disabled,
|
||||
#[default]
|
||||
ConfiguredOnly,
|
||||
Open,
|
||||
}
|
||||
|
||||
/// Nostr-mediated overlay endpoint discovery (`node.discovery.nostr.*`).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NostrDiscoveryConfig {
|
||||
/// Enable Nostr-signaled traversal bootstrap.
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
/// Publish service advertisements so remote peers can bootstrap inbound.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_advertise")]
|
||||
pub advertise: bool,
|
||||
/// Relay URLs used for service advertisements.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_advert_relays")]
|
||||
pub advert_relays: Vec<String>,
|
||||
/// Relay URLs used for encrypted signaling events.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_dm_relays")]
|
||||
pub dm_relays: Vec<String>,
|
||||
/// STUN servers used for local reflexive address discovery.
|
||||
/// Outbound observation uses only this local list; peer-advertised STUN
|
||||
/// values are informational and are not treated as egress targets.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_stun_servers")]
|
||||
pub stun_servers: Vec<String>,
|
||||
/// Whether to advertise local (RFC 1918 / ULA) interface addresses as
|
||||
/// host candidates in the traversal offer.
|
||||
///
|
||||
/// Off by default: in most deployments the relevant peers are not on the
|
||||
/// same broadcast domain, and sharing private host candidates causes
|
||||
/// misleading punch successes when an asymmetric L3 path (corporate VPN,
|
||||
/// Tailscale subnet route, overlapping address space, etc.) makes a
|
||||
/// peer's private IP one-way reachable from this node. Enable only when
|
||||
/// peers are on the same physical LAN and same-LAN punching is wanted.
|
||||
#[serde(default)]
|
||||
pub share_local_candidates: bool,
|
||||
/// Traversal application namespace and advert identifier suffix.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_app")]
|
||||
pub app: String,
|
||||
/// Signaling TTL in seconds.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_signal_ttl_secs")]
|
||||
pub signal_ttl_secs: u64,
|
||||
/// Policy for advert-derived endpoint discovery.
|
||||
#[serde(default)]
|
||||
pub policy: NostrDiscoveryPolicy,
|
||||
/// Max number of open-discovery peers queued for outbound retry/connection
|
||||
/// at once. Prevents unbounded queue growth from ambient advert traffic.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_open_discovery_max_pending")]
|
||||
pub open_discovery_max_pending: usize,
|
||||
/// Max concurrent inbound traversal offers processed at once.
|
||||
/// Acts as a rate limit against offer spam from relays.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_max_concurrent_incoming_offers")]
|
||||
pub max_concurrent_incoming_offers: usize,
|
||||
/// Max cached overlay adverts retained from relay traffic.
|
||||
/// Bounds memory under ambient advert volume.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_advert_cache_max_entries")]
|
||||
pub advert_cache_max_entries: usize,
|
||||
/// Max seen-session IDs retained for replay detection.
|
||||
/// Oldest entries are evicted when the cap is exceeded.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_seen_sessions_max_entries")]
|
||||
pub seen_sessions_max_entries: usize,
|
||||
/// Overall punch attempt timeout in seconds.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_attempt_timeout_secs")]
|
||||
pub attempt_timeout_secs: u64,
|
||||
/// Replay tracking retention window in seconds.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_replay_window_secs")]
|
||||
pub replay_window_secs: u64,
|
||||
/// Delay before punch traffic starts.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_punch_start_delay_ms")]
|
||||
pub punch_start_delay_ms: u64,
|
||||
/// Interval between punch packets.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_punch_interval_ms")]
|
||||
pub punch_interval_ms: u64,
|
||||
/// How long to keep punching before failure.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_punch_duration_ms")]
|
||||
pub punch_duration_ms: u64,
|
||||
/// Advert TTL in seconds.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_advert_ttl_secs")]
|
||||
pub advert_ttl_secs: u64,
|
||||
/// How often adverts are refreshed in seconds.
|
||||
#[serde(default = "NostrDiscoveryConfig::default_advert_refresh_secs")]
|
||||
pub advert_refresh_secs: u64,
|
||||
}
|
||||
|
||||
impl Default for NostrDiscoveryConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
advertise: Self::default_advertise(),
|
||||
advert_relays: Self::default_advert_relays(),
|
||||
dm_relays: Self::default_dm_relays(),
|
||||
stun_servers: Self::default_stun_servers(),
|
||||
share_local_candidates: false,
|
||||
app: Self::default_app(),
|
||||
signal_ttl_secs: Self::default_signal_ttl_secs(),
|
||||
policy: NostrDiscoveryPolicy::default(),
|
||||
open_discovery_max_pending: Self::default_open_discovery_max_pending(),
|
||||
max_concurrent_incoming_offers: Self::default_max_concurrent_incoming_offers(),
|
||||
advert_cache_max_entries: Self::default_advert_cache_max_entries(),
|
||||
seen_sessions_max_entries: Self::default_seen_sessions_max_entries(),
|
||||
attempt_timeout_secs: Self::default_attempt_timeout_secs(),
|
||||
replay_window_secs: Self::default_replay_window_secs(),
|
||||
punch_start_delay_ms: Self::default_punch_start_delay_ms(),
|
||||
punch_interval_ms: Self::default_punch_interval_ms(),
|
||||
punch_duration_ms: Self::default_punch_duration_ms(),
|
||||
advert_ttl_secs: Self::default_advert_ttl_secs(),
|
||||
advert_refresh_secs: Self::default_advert_refresh_secs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NostrDiscoveryConfig {
|
||||
fn default_advertise() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_advert_relays() -> Vec<String> {
|
||||
vec![
|
||||
"wss://relay.damus.io".to_string(),
|
||||
"wss://nos.lol".to_string(),
|
||||
"wss://offchain.pub".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn default_dm_relays() -> Vec<String> {
|
||||
vec![
|
||||
"wss://relay.damus.io".to_string(),
|
||||
"wss://nos.lol".to_string(),
|
||||
"wss://offchain.pub".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn default_stun_servers() -> Vec<String> {
|
||||
vec![
|
||||
"stun:stun.l.google.com:19302".to_string(),
|
||||
"stun:stun.cloudflare.com:3478".to_string(),
|
||||
"stun:global.stun.twilio.com:3478".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
fn default_app() -> String {
|
||||
"fips-overlay-v1".to_string()
|
||||
}
|
||||
|
||||
fn default_signal_ttl_secs() -> u64 {
|
||||
120
|
||||
}
|
||||
|
||||
fn default_open_discovery_max_pending() -> usize {
|
||||
64
|
||||
}
|
||||
|
||||
fn default_max_concurrent_incoming_offers() -> usize {
|
||||
16
|
||||
}
|
||||
|
||||
fn default_advert_cache_max_entries() -> usize {
|
||||
2048
|
||||
}
|
||||
|
||||
fn default_seen_sessions_max_entries() -> usize {
|
||||
2048
|
||||
}
|
||||
|
||||
fn default_attempt_timeout_secs() -> u64 {
|
||||
10
|
||||
}
|
||||
|
||||
fn default_replay_window_secs() -> u64 {
|
||||
300
|
||||
}
|
||||
|
||||
fn default_punch_start_delay_ms() -> u64 {
|
||||
2_000
|
||||
}
|
||||
|
||||
fn default_punch_interval_ms() -> u64 {
|
||||
200
|
||||
}
|
||||
|
||||
fn default_punch_duration_ms() -> u64 {
|
||||
10_000
|
||||
}
|
||||
|
||||
fn default_advert_ttl_secs() -> u64 {
|
||||
3_600
|
||||
}
|
||||
|
||||
fn default_advert_refresh_secs() -> u64 {
|
||||
1_800
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-1
@@ -94,7 +94,11 @@ pub struct PeerConfig {
|
||||
pub alias: Option<String>,
|
||||
|
||||
/// Transport addresses for reaching this peer.
|
||||
/// At least one address is required.
|
||||
///
|
||||
/// At least one address is required unless `via_nostr` is `true`,
|
||||
/// in which case the address list may be empty and endpoints are
|
||||
/// resolved from the peer's Nostr advert at dial time.
|
||||
#[serde(default)]
|
||||
pub addresses: Vec<PeerAddress>,
|
||||
|
||||
/// Connection policy for this peer.
|
||||
@@ -106,6 +110,13 @@ pub struct PeerConfig {
|
||||
/// backoff after MMP removes this peer due to liveness timeout.
|
||||
#[serde(default = "default_auto_reconnect")]
|
||||
pub auto_reconnect: bool,
|
||||
|
||||
/// Whether to append Nostr-advertised endpoints when dialing this peer.
|
||||
///
|
||||
/// Static addresses are still attempted first; advert-derived endpoints are
|
||||
/// appended as fallback candidates.
|
||||
#[serde(default)]
|
||||
pub via_nostr: bool,
|
||||
}
|
||||
|
||||
impl Default for PeerConfig {
|
||||
@@ -116,6 +127,7 @@ impl Default for PeerConfig {
|
||||
addresses: Vec::new(),
|
||||
connect_policy: ConnectPolicy::default(),
|
||||
auto_reconnect: default_auto_reconnect(),
|
||||
via_nostr: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,6 +145,7 @@ impl PeerConfig {
|
||||
addresses: vec![PeerAddress::new(transport, addr)],
|
||||
connect_policy: ConnectPolicy::default(),
|
||||
auto_reconnect: default_auto_reconnect(),
|
||||
via_nostr: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,19 @@ pub struct UdpConfig {
|
||||
/// UDP send buffer size in bytes (`send_buf_size`). Defaults to 2 MB.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub send_buf_size: Option<usize>,
|
||||
|
||||
/// Whether this transport should be advertised on Nostr overlay discovery.
|
||||
/// Default: false.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub advertise_on_nostr: Option<bool>,
|
||||
|
||||
/// Whether UDP should be advertised as directly reachable (`host:port`) on
|
||||
/// Nostr. When false and advertised, UDP is emitted as `addr: "nat"` to
|
||||
/// trigger rendezvous traversal.
|
||||
///
|
||||
/// Default: false.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub public: Option<bool>,
|
||||
}
|
||||
|
||||
impl UdpConfig {
|
||||
@@ -60,6 +73,16 @@ impl UdpConfig {
|
||||
pub fn send_buf_size(&self) -> usize {
|
||||
self.send_buf_size.unwrap_or(DEFAULT_UDP_SEND_BUF)
|
||||
}
|
||||
|
||||
/// Whether this UDP transport should be advertised on Nostr discovery.
|
||||
pub fn advertise_on_nostr(&self) -> bool {
|
||||
self.advertise_on_nostr.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Whether this UDP transport should be advertised as directly reachable.
|
||||
pub fn is_public(&self) -> bool {
|
||||
self.public.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Transport instances - either a single config or named instances.
|
||||
@@ -293,6 +316,11 @@ pub struct TcpConfig {
|
||||
/// Maximum simultaneous inbound connections. Defaults to 256.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub max_inbound_connections: Option<usize>,
|
||||
|
||||
/// Whether this transport should be advertised on Nostr overlay discovery.
|
||||
/// Default: false.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub advertise_on_nostr: Option<bool>,
|
||||
}
|
||||
|
||||
impl TcpConfig {
|
||||
@@ -332,6 +360,11 @@ impl TcpConfig {
|
||||
self.max_inbound_connections
|
||||
.unwrap_or(DEFAULT_TCP_MAX_INBOUND)
|
||||
}
|
||||
|
||||
/// Whether this TCP transport should be advertised on Nostr discovery.
|
||||
pub fn advertise_on_nostr(&self) -> bool {
|
||||
self.advertise_on_nostr.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -420,6 +453,11 @@ pub struct TorConfig {
|
||||
/// in torrc; fips reads the .onion hostname from a file.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub directory_service: Option<DirectoryServiceConfig>,
|
||||
|
||||
/// Whether this transport should be advertised on Nostr overlay discovery.
|
||||
/// Default: false.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub advertise_on_nostr: Option<bool>,
|
||||
}
|
||||
|
||||
/// Directory-mode onion service configuration.
|
||||
@@ -507,6 +545,11 @@ impl TorConfig {
|
||||
self.max_inbound_connections
|
||||
.unwrap_or(DEFAULT_TOR_MAX_INBOUND)
|
||||
}
|
||||
|
||||
/// Whether this Tor transport should be advertised on Nostr discovery.
|
||||
pub fn advertise_on_nostr(&self) -> bool {
|
||||
self.advertise_on_nostr.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user