Skip to main content

dig_nat/
config.rs

1//! Connection configuration — the local identity, relay endpoint, enabled methods, and timeouts
2//! that shape a [`crate::connect`] call. Built with a fluent builder; the caller never selects the
3//! traversal method (only which ones are *enabled*).
4
5use std::time::Duration;
6
7use zeroize::Zeroizing;
8
9use crate::identity::PeerId;
10use crate::method::TraversalKind;
11
12/// The local node's mTLS identity: its certificate + private key (both DER) and the derived
13/// [`PeerId`] the remote will verify.
14///
15/// The certificate is self-signed and its public key IS the identity (see [`crate::mtls`]). Callers
16/// typically load a persisted `ChiaCertificate`-style pair; [`LocalIdentity::from_der`] derives the
17/// `peer_id` from the cert's SPKI so it always matches what a remote computes.
18#[derive(Clone)]
19pub struct LocalIdentity {
20    /// This node's leaf certificate, DER-encoded.
21    pub cert_der: Vec<u8>,
22    /// The matching private key, DER-encoded (PKCS#8).
23    ///
24    /// #179 finding 4: wrapped in [`Zeroizing`] (rather than a plain `Vec<u8>`) so every clone
25    /// (`LocalIdentity` is cloned per dial, see `dialer.rs`) and every drop scrubs the private-key
26    /// bytes from memory instead of leaving them in freed heap. Derefs transparently to `&[u8]` for
27    /// existing call sites (e.g. building a `rustls::pki_types::PrivateKeyDer`).
28    pub key_der: Zeroizing<Vec<u8>>,
29    /// This node's own `peer_id` = SHA-256(cert SPKI DER).
30    pub peer_id: PeerId,
31}
32
33impl LocalIdentity {
34    /// Build a local identity from a DER cert + PKCS#8 key, deriving `peer_id` from the cert SPKI.
35    /// Returns `None` if the certificate cannot be parsed.
36    pub fn from_der(cert_der: Vec<u8>, key_der: Vec<u8>) -> Option<Self> {
37        let peer_id = crate::identity::peer_id_from_leaf_cert_der(&cert_der)?;
38        Some(LocalIdentity {
39            cert_der,
40            key_der: Zeroizing::new(key_der),
41            peer_id,
42        })
43    }
44}
45
46impl std::fmt::Debug for LocalIdentity {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_struct("LocalIdentity")
49            .field("peer_id", &self.peer_id)
50            .field("cert_der", &format!("<{} bytes>", self.cert_der.len()))
51            .field("key_der", &"<redacted>")
52            .finish()
53    }
54}
55
56/// Which traversal methods are enabled + the per-method deadline and relay settings for a connect.
57///
58/// The DEFAULT enables every method in the canonical order with sane timeouts and the canonical
59/// [`dig_constants::DIG_RELAY_URL`] relay. The caller tweaks via the builder but never picks *which*
60/// method wins — the strategy does, first-success-wins, relay-last.
61#[derive(Debug, Clone)]
62pub struct NatConfig {
63    /// The traversal techniques permitted, by kind. The strategy always tries them in
64    /// [`TraversalKind::rank`] order regardless of the order here.
65    pub enabled_methods: Vec<TraversalKind>,
66    /// Per-method hard timeout — a method that does not complete in this window is abandoned and the
67    /// strategy moves on (this is the guarantee that a hung method can never block `connect`).
68    pub per_method_timeout: Duration,
69    /// The relay WebSocket endpoint for the relay-coordinated + relayed methods. Defaults to the
70    /// canonical relay; honour the `DIG_RELAY_URL` env override / `=off` opt-out via
71    /// [`crate::relay::relay_url_from_env`] / [`crate::relay::relay_enabled`].
72    pub relay_endpoint: String,
73    /// A STUN server used to discover this node's reflexive address for candidate advertisement +
74    /// hole-punch. `None` = derive from the relay host on the standard STUN port
75    /// [`STUN_PORT`](crate::config::STUN_PORT) (the relay co-locates a STUN server, L7 spec §3):
76    /// point a node at a private relay and its STUN follows.
77    pub stun_server: Option<std::net::SocketAddr>,
78}
79
80/// The standard STUN port (RFC 5389). The DIG relay serves STUN here, co-located with the relay host
81/// (`relay.dig.net:3478`); a node derives its STUN host from `DIG_RELAY_URL` (L7 spec §3).
82pub const STUN_PORT: u16 = 3478;
83
84impl Default for NatConfig {
85    fn default() -> Self {
86        NatConfig {
87            enabled_methods: vec![
88                TraversalKind::Direct,
89                TraversalKind::Upnp,
90                TraversalKind::NatPmp,
91                TraversalKind::Pcp,
92                TraversalKind::HolePunch,
93                TraversalKind::Relayed,
94            ],
95            per_method_timeout: Duration::from_secs(5),
96            relay_endpoint: dig_constants::DIG_RELAY_URL.to_string(),
97            stun_server: None,
98        }
99    }
100}
101
102impl NatConfig {
103    /// Start from the default config.
104    pub fn builder() -> NatConfigBuilder {
105        NatConfigBuilder {
106            cfg: NatConfig::default(),
107        }
108    }
109
110    /// Whether `kind` is enabled in this config.
111    pub fn is_enabled(&self, kind: TraversalKind) -> bool {
112        self.enabled_methods.contains(&kind)
113    }
114}
115
116/// Fluent builder for [`NatConfig`].
117#[derive(Debug, Clone)]
118pub struct NatConfigBuilder {
119    cfg: NatConfig,
120}
121
122impl NatConfigBuilder {
123    /// Restrict the enabled methods (they are still tried in canonical rank order).
124    pub fn enabled_methods(mut self, methods: Vec<TraversalKind>) -> Self {
125        self.cfg.enabled_methods = methods;
126        self
127    }
128
129    /// Disable a single method (e.g. turn off the relay fallback for an air-gapped node).
130    pub fn disable(mut self, kind: TraversalKind) -> Self {
131        self.cfg.enabled_methods.retain(|k| *k != kind);
132        self
133    }
134
135    /// Set the per-method timeout.
136    pub fn per_method_timeout(mut self, timeout: Duration) -> Self {
137        self.cfg.per_method_timeout = timeout;
138        self
139    }
140
141    /// Override the relay endpoint (defaults to the canonical relay).
142    pub fn relay_endpoint(mut self, endpoint: impl Into<String>) -> Self {
143        self.cfg.relay_endpoint = endpoint.into();
144        self
145    }
146
147    /// Set the STUN server used for reflexive-address discovery.
148    pub fn stun_server(mut self, addr: std::net::SocketAddr) -> Self {
149        self.cfg.stun_server = Some(addr);
150        self
151    }
152
153    /// Finalize the config.
154    pub fn build(self) -> NatConfig {
155        self.cfg
156    }
157}