mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 16:24:45 +00:00
Add scoped mDNS / DNS-SD discovery for peers on the same local link, giving sub-second pairing without a relay or NAT-traversal roundtrip. A node advertises its npub, protocol version, and an optional network scope over link-local multicast, and browses for matching adverts to initiate Noise handshakes against same-LAN peers. LAN discovery is disabled by default; operators enable it with node.discovery.lan.enabled: true. Default-off avoids reintroducing a per-LAN identity broadcast on nodes that have deliberately disabled other discovery channels, and avoids any multicast surprise on upgrade. The startup advertised-port picker now excludes bootstrap transports and selects a non-bootstrap operational UDP transport with a stable lowest-id selector, so the advertised port is deterministic across restarts rather than dependent on HashMap iteration order. This matches the per-dial transport selection used for discovered peers. Co-authored-by: Johnathan Corgan <johnathan@corganlabs.com>
105 lines
3.8 KiB
Rust
105 lines
3.8 KiB
Rust
//! Bootstrap handoff types.
|
|
//!
|
|
//! These types model the boundary between an external rendezvous/bootstrap
|
|
//! runtime and the core FIPS transport/handshake stack. The rendezvous side
|
|
//! owns Nostr/STUN/UDP hole punching; once a direct UDP path is established,
|
|
//! it hands the live socket and selected remote endpoint to FIPS so the
|
|
//! existing Noise/FMP transport path can take over.
|
|
|
|
pub mod lan;
|
|
pub mod nostr;
|
|
|
|
use crate::config::UdpConfig;
|
|
use crate::{NodeAddr, TransportId};
|
|
use std::net::{SocketAddr, UdpSocket};
|
|
|
|
/// Punch-probe magic ("NPTC", network byte order). First byte `0x4E`
|
|
/// collides with FMP's prefix-version high-nibble check, so the UDP
|
|
/// transport silently filters packets carrying this magic to keep
|
|
/// post-adoption handshake logs clean. Defined at the top-level
|
|
/// `discovery` module so the UDP filter and the nostr submodule's
|
|
/// punch sender share the same constant.
|
|
pub const PUNCH_MAGIC: u32 = 0x4E505443;
|
|
|
|
/// Punch-probe-ack magic ("NPTA", network byte order). Same filter as
|
|
/// [`PUNCH_MAGIC`].
|
|
pub const PUNCH_ACK_MAGIC: u32 = 0x4E505441;
|
|
|
|
/// Returns `true` if the first four bytes of `data` match a punch-probe or
|
|
/// punch-ack magic. Used by the UDP transport's receive loop to silently
|
|
/// drop stray probes that arrive on an adopted socket after the remote
|
|
/// peer's punch attempt has already timed out.
|
|
pub fn is_punch_packet(data: &[u8]) -> bool {
|
|
if data.len() < 4 {
|
|
return false;
|
|
}
|
|
let magic = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
|
|
magic == PUNCH_MAGIC || magic == PUNCH_ACK_MAGIC
|
|
}
|
|
|
|
/// Result of handing an established traversal session into FIPS.
|
|
#[derive(Debug, Clone)]
|
|
pub struct BootstrapHandoffResult {
|
|
/// Newly allocated transport ID used for the adopted UDP socket.
|
|
pub transport_id: TransportId,
|
|
/// Local socket address now owned by the FIPS UDP transport.
|
|
pub local_addr: SocketAddr,
|
|
/// Confirmed remote UDP endpoint selected by traversal.
|
|
pub remote_addr: SocketAddr,
|
|
/// Peer node address derived from the supplied peer identity.
|
|
pub peer_node_addr: NodeAddr,
|
|
/// Nostr session identifier used by the bootstrap runtime.
|
|
pub session_id: String,
|
|
}
|
|
|
|
/// Established UDP traversal ready to be handed into FIPS.
|
|
///
|
|
/// The socket must already be bound and must be the same socket used for the
|
|
/// traversal runtime's STUN and punch traffic so the NAT mapping is preserved.
|
|
#[derive(Debug)]
|
|
pub struct EstablishedTraversal {
|
|
/// Rendezvous session identifier for logging/correlation.
|
|
pub session_id: String,
|
|
/// Remote peer identity in `npub` form.
|
|
pub peer_npub: String,
|
|
/// The selected remote UDP endpoint to use for the FIPS handshake.
|
|
pub remote_addr: SocketAddr,
|
|
/// The live UDP socket carrying the established mapping.
|
|
pub socket: UdpSocket,
|
|
/// Optional name for the adopted UDP transport.
|
|
pub transport_name: Option<String>,
|
|
/// Optional UDP transport tuning overrides.
|
|
pub transport_config: Option<UdpConfig>,
|
|
}
|
|
|
|
impl EstablishedTraversal {
|
|
/// Construct an established traversal handoff.
|
|
pub fn new(
|
|
session_id: impl Into<String>,
|
|
peer_npub: impl Into<String>,
|
|
remote_addr: SocketAddr,
|
|
socket: UdpSocket,
|
|
) -> Self {
|
|
Self {
|
|
session_id: session_id.into(),
|
|
peer_npub: peer_npub.into(),
|
|
remote_addr,
|
|
socket,
|
|
transport_name: None,
|
|
transport_config: None,
|
|
}
|
|
}
|
|
|
|
/// Attach an explicit transport name to the adopted UDP transport.
|
|
pub fn with_transport_name(mut self, name: impl Into<String>) -> Self {
|
|
self.transport_name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
/// Override UDP transport tuning for the adopted socket.
|
|
pub fn with_transport_config(mut self, config: UdpConfig) -> Self {
|
|
self.transport_config = Some(config);
|
|
self
|
|
}
|
|
}
|