Skip to main content

dig_nat/
lib.rs

1//! # dig-nat — abstract NAT traversal for DIG Node peer connections
2//!
3//! One API, [`connect`], establishes a **mutually-authenticated (mTLS)** connection to a peer using
4//! the best available NAT-traversal method, transparently. **The caller never chooses the method** —
5//! they describe the peer once and get back a verified [`PeerConnection`]; which technique got there
6//! is reported for observability but is not something the caller handles.
7//!
8//! ## Traversal order (first success wins, relay last)
9//!
10//! Internally the [`strategy`] attempts, in this order:
11//! 1. **Direct** — peer publicly reachable / already port-forwarded ([`method::direct`])
12//! 2. **UPnP/IGD** port mapping ([`method::upnp`])
13//! 3. **NAT-PMP** (RFC 6886, [`method::natpmp`])
14//! 4. **PCP** (RFC 6887, [`method::pcp`])
15//! 5. **Relay-coordinated hole-punch** (RLY-007, [`method::hole_punch`])
16//! 6. **Relayed transport** via `relay.dig.net` — the LAST resort ([`relay`])
17//!
18//! [`stun`] (RFC 5389) discovers this node's reflexive address for candidate advertisement +
19//! hole-punch coordination.
20//!
21//! ## Streaming-first + multiplexed transport
22//!
23//! Whatever tier establishes the connection, the result is uniform: a [`PeerConnection`] wrapping a
24//! single mTLS byte stream in [`yamux`](mux) multiplexing. The caller opens **many cheap concurrent
25//! logical streams** ([`PeerConnection::open_stream`]) with no head-of-line blocking, and
26//! **byte-range streams** ([`PeerConnection::open_range_stream`]) scoped to `[offset, len)` of a
27//! resource — so a downloader fetches DIFFERENT ranges from DIFFERENT peers in parallel and
28//! reassembles. The API is streaming (read bytes as they arrive), never buffer-the-whole-response.
29//!
30//! ## Identity + mTLS — delegated to `dig-tls`
31//!
32//! Every peer connection is mutual TLS, and the entire certificate model is owned by the canonical
33//! [`dig-tls`](dig_tls) crate (L00): the shipped public DigNetwork CA, the per-peer CA-signed
34//! [`NodeCert`], `peer_id = SHA-256(TLS SPKI DER)`, the #1204 BLS-G1 cert binding, and the ready
35//! rustls mutual-auth configs. dig-nat presents this node's [`NodeCert`] and uses
36//! [`dig_tls::client_config`] to pin the remote's `peer_id` to the [`peer::PeerTarget::peer_id`] the
37//! caller asked for — so the transport is self-authenticating. dig-nat holds NO cert/binding/peer_id
38//! code of its own (it was extracted to dig-tls in 0.6.0); the names below are re-exports for
39//! convenience.
40//!
41//! ## Graceful fallback + relay resilience
42//!
43//! Each method is bounded by a per-method timeout; if ALL fail, [`connect`] returns a clear
44//! [`NatError::AllMethodsFailed`] (never panics, never hangs). The [`relay`] client — used both as
45//! the last-resort transport and as a node's persistent reachability channel — establishes and
46//! maintains its session with keepalive + capped-exponential-backoff reconnect, tolerates the relay
47//! being down (retries in the background, never crashes the node), logs once per state change, and
48//! honours the `DIG_RELAY_URL=off` opt-out. See [`relay::RelayStatus`].
49//!
50//! ## Example
51//!
52//! ```no_run
53//! # use std::sync::Arc;
54//! # use dig_nat::{connect, NatConfig, NodeCert, PeerTarget, PeerId};
55//! # async fn run(node: Arc<NodeCert>, peer_id: PeerId, addr: std::net::SocketAddr) -> Result<(), dig_nat::NatError> {
56//! let peer = PeerTarget::with_addr(peer_id, addr, "DIG_MAINNET");
57//! let conn = connect(&peer, &node, &NatConfig::default()).await?;
58//! println!("connected to {} via {:?}", conn.peer_id, conn.method);
59//! # Ok(()) }
60//! ```
61
62#![forbid(unsafe_code)]
63#![warn(missing_docs)]
64
65pub mod config;
66pub mod dialer;
67pub mod error;
68pub mod method;
69pub mod mux;
70pub mod peer;
71pub mod relay;
72pub mod relay_descriptor;
73pub mod strategy;
74pub mod stun;
75pub mod wire;
76
77use std::sync::Arc;
78
79// --- Certificate / mTLS / identity model — re-exported from the canonical `dig-tls` crate (L00).
80// dig-nat CONSUMES dig-tls for ALL of these (it holds no copy of its own), so a single source of
81// truth means the DIG cert shape can never byte-drift between crates. ---
82pub use dig_tls::binding::{verify_binding_from_leaf_cert, BindingOutcome};
83pub use dig_tls::verify::{CapturedBlsPub, CapturedPeerId};
84pub use dig_tls::{
85    peer_id_from_leaf_cert_der, peer_id_from_tls_spki_der, BindingPolicy, NodeCert, PeerId,
86};
87
88pub use config::{NatConfig, NatConfigBuilder};
89pub use error::{MethodError, NatError};
90pub use method::{TraversalKind, TraversalMethod};
91pub use mux::{
92    AvailabilityAnswer, AvailabilityItem, AvailabilityRequest, AvailabilityResponse, PeerSession,
93    PeerStream, RangeFrame, RangeRequest,
94};
95pub use peer::{PeerConnection, PeerTarget};
96pub use relay_descriptor::{verify_relay_descriptor, RelayDescriptor, RelayDescriptorError};
97
98use dialer::MtlsDialer;
99use method::direct::DirectMethod;
100
101/// Establish a mutually-authenticated connection to `peer`, choosing the traversal method
102/// transparently (first success wins; relay is the last resort).
103///
104/// `node` is this node's [`NodeCert`] — its CA-signed mTLS identity from [`dig-tls`](dig_tls),
105/// presented as the client certificate; `config` selects which methods are enabled, the per-method
106/// timeout, the relay/STUN endpoints, and the [`BindingPolicy`] applied to the peer's #1204 cert
107/// binding. On success the returned [`PeerConnection`] carries the verified remote `peer_id`, the
108/// [`TraversalKind`] that established it, the peer's bound BLS pubkey (when present), and the
109/// authenticated stream.
110///
111/// # Errors
112/// - [`NatError::NoMethodsEnabled`] — the config enabled no methods.
113/// - [`NatError::AllMethodsFailed`] — every enabled method failed (with per-method reasons).
114/// - [`NatError::InvalidConfig`] — the relay/STUN config could not be used.
115///
116/// This function never panics and never hangs: every method (and its dial) is bounded by
117/// [`NatConfig::per_method_timeout`].
118pub async fn connect(
119    peer: &PeerTarget,
120    node: &Arc<NodeCert>,
121    config: &NatConfig,
122) -> Result<PeerConnection, NatError> {
123    let methods = build_enabled_methods(config);
124    if methods.is_empty() {
125        return Err(NatError::NoMethodsEnabled);
126    }
127    let dialer = MtlsDialer::new(Arc::clone(node)).with_binding_policy(config.binding_policy);
128    strategy::connect_with_strategy(peer, methods, &dialer, config.per_method_timeout).await
129}
130
131/// Assemble the enabled [`TraversalMethod`] trait objects for a config.
132///
133/// NOTE: the UPnP/NAT-PMP/PCP/hole-punch methods need runtime inputs the caller has not yet supplied
134/// through the current minimal config surface (gateway address, this node's local port + reflexive
135/// address, a live relay coordinator). Until those are wired through the builder, `connect` composes
136/// the methods it can construct from the config alone — currently the always-available **Direct**
137/// method. The other methods are fully implemented + tested and are composed explicitly by callers
138/// (e.g. `dig-node`) that hold that runtime context, via [`strategy::connect_with_strategy`]. This
139/// keeps `connect` honest (it never claims a method it cannot actually run) while the richer
140/// auto-composition lands with the discovery inputs.
141fn build_enabled_methods(config: &NatConfig) -> Vec<Arc<dyn TraversalMethod>> {
142    let mut methods: Vec<Arc<dyn TraversalMethod>> = Vec::new();
143    let direct = DirectMethod;
144    if config.is_enabled(direct.kind()) {
145        methods.push(Arc::new(direct));
146    }
147    methods
148}