use super::{ClientBuilder, TransportConfig};
fn builder() -> ClientBuilder {
ClientBuilder::new_provider_neutral("udp-initializer-proof".into(), Box::new(|| None))
.transport_config(TransportConfig {
relay: false,
..Default::default()
})
}
#[cfg(feature = "native-udp-socket-initializer")]
#[tokio::test]
async fn constructor_udp_initializer_refusal_prevents_native_publication() {
let client = builder()
.native_udp_socket_initializer(|_, _| {
Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"sdk-initializer-refusal",
))
})
.build();
let error = client
.init_iroh_without_internal_router(None, Vec::new())
.await
.unwrap_err();
assert!(format!("{error:#}").contains("sdk-initializer-refusal"));
assert!(
client.iroh_endpoint.read().await.is_none(),
"failed native policy must not publish an endpoint"
);
assert!(
client.current_node_id().await.is_none(),
"failed native policy must not publish node identity"
);
}
#[tokio::test]
async fn constructor_socket_initializers_cannot_be_claimed_for_an_already_bound_host_endpoint() {
let endpoint = iroh::Endpoint::builder(iroh::endpoint::presets::Minimal)
.bind_addr("127.0.0.1:0")
.unwrap()
.bind()
.await
.unwrap();
let mut policies = Vec::new();
#[cfg(feature = "native-udp-socket-initializer")]
policies.push(builder().native_udp_socket_initializer(|_, _| Ok(())));
#[cfg(feature = "native-relay-tcp-socket-initializer")]
policies.push(builder().native_relay_tcp_socket_initializer(|_| Ok(())));
#[cfg(feature = "native-dns-socket-initializer")]
policies.push(builder().native_dns_socket_initializer(|_, _| Ok(())));
for policy in policies {
let client = policy.build();
let error = client
.adopt_endpoint_with_router_mode(endpoint.clone(), false)
.await
.unwrap_err();
assert!(error
.to_string()
.contains("requires SDK-owned endpoint construction"));
assert!(client.iroh_endpoint.read().await.is_none());
assert!(client.current_node_id().await.is_none());
assert!(
!endpoint.is_closed(),
"refused host policy must preserve the external owner's endpoint"
);
}
let ordinary = builder().build();
ordinary
.adopt_endpoint_with_router_mode(endpoint.clone(), false)
.await
.unwrap();
assert_eq!(
ordinary.current_node_id().await,
Some(endpoint.id().to_string())
);
endpoint.close().await;
}
#[cfg(all(
unix,
feature = "native-relay-tcp-socket-initializer",
feature = "test-harness"
))]
#[tokio::test]
async fn constructor_relay_tcp_policy_reaches_live_sdk_relay() {
use std::{
os::fd::AsFd,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
};
let (_map, relay_url, _server) = iroh::test_utils::run_relay_server_with(false)
.await
.unwrap();
let url = relay_url.to_string();
let calls = Arc::new(AtomicUsize::new(0));
let observed = calls.clone();
let client = builder()
.transport_config(TransportConfig {
relay: true,
iroh_relay_only: true,
..Default::default()
})
.native_relay_tcp_socket_initializer(move |socket| {
let duplicate = std::net::TcpStream::from(socket.as_fd().try_clone_to_owned()?);
assert!(
duplicate.peer_addr().is_err(),
"SDK policy ran after relay TCP connect"
);
duplicate.set_ttl(31)?;
assert_eq!(duplicate.ttl()?, 31);
observed.fetch_add(1, Ordering::SeqCst);
Ok(())
})
.build();
client
.init_test_endpoint(None, Vec::new(), Some(&url))
.await
.unwrap();
let endpoint = client.get_endpoint().await.unwrap();
let online = tokio::time::timeout(std::time::Duration::from_secs(3), endpoint.online()).await;
endpoint.close().await;
online.expect("SDK-owned endpoint must complete its actual local relay handshake");
assert!(
calls.load(Ordering::SeqCst) > 0,
"SDK constructor policy did not reach the live relay TCP factory"
);
}
#[cfg(feature = "native-dns-socket-initializer")]
#[tokio::test]
async fn constructor_dns_policy_reaches_sdk_owned_resolver() {
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
let calls = Arc::new(AtomicUsize::new(0));
let observed = calls.clone();
let client = builder()
.native_dns_socket_initializer(move |_, _| {
observed.fetch_add(1, Ordering::SeqCst);
Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"sdk-dns-policy-refusal",
))
})
.build();
client
.init_iroh_without_internal_router(None, Vec::new())
.await
.unwrap();
let endpoint = client.get_endpoint().await.unwrap();
let result = endpoint
.dns_resolver()
.unwrap()
.lookup_ipv4("refused.invalid.", std::time::Duration::from_secs(1))
.await;
endpoint.close().await;
assert!(matches!(
result,
Err(iroh::dns::DnsError::SocketPolicy { .. })
));
assert!(
calls.load(Ordering::SeqCst) > 0,
"SDK policy must reach actual resolver socket construction"
);
}