Skip to content
Open
29 changes: 28 additions & 1 deletion iroh/src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
//!
//! [module docs]: crate

use std::{collections::BTreeSet, net::SocketAddr, pin::Pin, sync::Arc};
use std::{
collections::BTreeSet,
fmt::Debug,
net::{IpAddr, SocketAddr},
pin::Pin,
sync::Arc,
};

#[cfg(not(wasm_browser))]
use ipnet::{Ipv4Net, Ipv6Net};
Expand Down Expand Up @@ -150,6 +156,17 @@ pub struct Builder {
net_report_config: NetReportConfig,
crypto_provider: Option<Arc<rustls::crypto::CryptoProvider>>,
configured_addrs: BTreeSet<SocketAddr>,
direct_addr_filter: Option<Box<dyn DirectAddrFilter>>,
}

/// Filters the endpoint's NAT traversal address candidates.
///
/// An address for which [`Self::use_nat_candidate`] returns `false` is dropped.
/// Set the filter with
/// [`Builder::direct_addr_filter`].
pub trait DirectAddrFilter: Send + Sync + Debug + 'static {
/// Returns whether `ip` should be used as a NAT traversal candidate.
fn use_nat_candidate(&self, ip: IpAddr) -> bool;
}

impl From<RelayMode> for Option<TransportConfig> {
Expand Down Expand Up @@ -218,6 +235,7 @@ impl Builder {
net_report_config: Default::default(),
crypto_provider: None,
configured_addrs: Default::default(),
direct_addr_filter: None,
}
}

Expand Down Expand Up @@ -284,6 +302,7 @@ impl Builder {
net_report_config: self.net_report_config,
static_config,
configured_addrs: self.configured_addrs,
direct_addr_filter: self.direct_addr_filter,
};

let inner = socket::EndpointInner::bind(sock_opts)
Expand Down Expand Up @@ -637,6 +656,14 @@ impl Builder {
self
}

/// Uses a filter for NAT traversal address candidates.
///
/// See [`DirectAddrFilter`] for details.
pub fn direct_addr_filter(mut self, filter: impl DirectAddrFilter) -> Self {
self.direct_addr_filter = Some(Box::new(filter));
self
}

/// Sets the initial user-defined data to be published in Address Lookup's for this node.
///
/// When using Address Lookup's, this string of [`UserData`] will be published together
Expand Down
58 changes: 55 additions & 3 deletions iroh/src/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ use crate::{
address_lookup::{self, AddressLookupFailed, EndpointData, UserData},
defaults::timeouts::NET_REPORT_TIMEOUT,
endpoint::{
LocalTransportAddr, RelayStatus, hooks::EndpointHooksList, quic::QuicTransportConfig,
DirectAddrFilter, LocalTransportAddr, RelayStatus, hooks::EndpointHooksList,
quic::QuicTransportConfig,
},
metrics::EndpointMetrics,
net_report::{self, IfStateDetails, Report},
Expand Down Expand Up @@ -196,6 +197,9 @@ pub(crate) struct Options {

/// Explicitly configured external addresses to advertise.
pub(crate) configured_addrs: BTreeSet<SocketAddr>,

/// Optional filter for NAT traversal address candidates.
pub(crate) direct_addr_filter: Option<Box<dyn DirectAddrFilter>>,
}

/// Inner state for an iroh [`crate::Endpoint`].
Expand Down Expand Up @@ -887,6 +891,7 @@ impl EndpointInner {
net_report_config,
static_config,
configured_addrs,
direct_addr_filter,
} = opts;

let address_lookup =
Expand Down Expand Up @@ -1082,6 +1087,7 @@ impl EndpointInner {
periodic_re_stun_timer: new_re_stun_timer(false),
network_monitor,
local_interfaces_watcher,
direct_addr_filter,
direct_addr_update_state,
transports_network_change,
direct_addr_done_rx,
Expand Down Expand Up @@ -1464,6 +1470,8 @@ struct Actor {
network_monitor: netmon::Monitor,
/// Watcher for changes to the local network interfaces, IP addresses and routes.
local_interfaces_watcher: n0_watcher::Direct<netmon::State>,
/// Optional filter for NAT traversal address candidates.
direct_addr_filter: Option<Box<dyn DirectAddrFilter>>,
transports_network_change: transports::NetworkChangeSender,
/// Indicates the direct addr update state.
direct_addr_update_state: DirectAddrUpdateState,
Expand Down Expand Up @@ -1872,6 +1880,7 @@ impl Actor {
}

// Finally create and store store all these direct addresses
let direct_addr_filter = self.direct_addr_filter.as_deref();
let stored_addrs = addrs
.into_iter()
.filter_map(|(addr, (typ, flags))| {
Expand All @@ -1880,6 +1889,11 @@ impl Actor {
if is_deprecated {
return None;
}
if let Some(f) = direct_addr_filter
&& !f.use_nat_candidate(addr.ip())
{
return None;
}
Some(DirectAddr { addr, typ })
})
.collect();
Expand Down Expand Up @@ -2113,7 +2127,11 @@ impl Display for DirectAddrType {

#[cfg(all(test, with_crypto_provider))]
mod tests {
use std::{net::SocketAddrV4, sync::Arc, time::Duration};
use std::{
net::{IpAddr, SocketAddrV4},
sync::Arc,
time::Duration,
};

use data_encoding::HEXLOWER;
use iroh_base::{EndpointAddr, EndpointId, TransportAddr};
Expand All @@ -2131,7 +2149,7 @@ mod tests {
Endpoint, SecretKey,
address_lookup::memory::MemoryLookup,
dns::DnsResolver,
endpoint::{QuicTransportConfig, presets},
endpoint::{DirectAddrFilter, QuicTransportConfig, presets},
socket::{
EndpointInner, StaticConfig, TransportConfig,
biased_rtt_path_selector::BiasedRttPathSelector,
Expand Down Expand Up @@ -2180,6 +2198,7 @@ mod tests {
net_report_config: Default::default(),
static_config,
configured_addrs: Default::default(),
direct_addr_filter: None,
}
}

Expand Down Expand Up @@ -2550,6 +2569,38 @@ mod tests {
assert_eq!(eps0, eps1);
}

#[tokio::test]
#[traced_test]
async fn direct_addr_filter_drops_local_addresses() {
// Baseline: without a filter, local interface addresses are gathered.
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
let sock = EndpointInner::bind(default_options(&mut rng))
.await
.unwrap();
assert!(
!sock.ip_addrs().get().is_empty(),
"expected some local addresses without a filter"
);

// A reject-all filter drops every gathered local interface address, so
// `collect_local_addresses` contributes nothing.
#[derive(Debug)]
struct DropAll;
impl DirectAddrFilter for DropAll {
fn use_nat_candidate(&self, _ip: IpAddr) -> bool {
false
}
}
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
let mut opts = default_options(&mut rng);
opts.direct_addr_filter = Some(Box::new(DropAll));
let sock = EndpointInner::bind(opts).await.unwrap();
assert!(
sock.ip_addrs().get().is_empty(),
"a reject-all direct_addr_filter should drop every local address"
);
}

/// Creates a new [`noq::Endpoint`] hooked up to a [`Socket`].
///
/// This is without involving [`crate::endpoint::Endpoint`]. The socket will accept
Expand Down Expand Up @@ -2596,6 +2647,7 @@ mod tests {
net_report_config: Default::default(),
static_config,
configured_addrs: Default::default(),
direct_addr_filter: None,
};
let sock = EndpointInner::bind(opts).await?;
Ok(sock)
Expand Down