use super::Client;
impl Client {
pub fn supports_native_ip_exclusions(&self) -> bool {
cfg!(feature = "native-ip-exclusions")
}
pub async fn set_native_ip_exclusions(
&self,
networks: Vec<ipnet::IpNet>,
) -> anyhow::Result<()> {
anyhow::ensure!(
self.supports_native_ip_exclusions(),
"native IP exclusions are unsupported by this build"
);
let _init_lock = self.iroh_init_guard.lock().await;
*self.native_ip_exclusions.write().await = Some(networks.clone());
#[cfg(feature = "native-ip-exclusions")]
if let Some(endpoint) = self.iroh_endpoint.read().await.as_ref() {
endpoint.set_excluded_ip_networks(networks).await?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::client::ClientBuilder;
#[cfg(not(feature = "native-ip-exclusions"))]
use crate::client::TransportConfig;
#[cfg(not(feature = "native-ip-exclusions"))]
#[tokio::test]
async fn unsupported_native_ip_input_refuses_endpoint_creation() {
for networks in [vec!["127.0.0.0/8".parse().unwrap()], Vec::new()] {
let client =
ClientBuilder::new_provider_neutral("ip-policy-test".into(), Box::new(|| None))
.transport_config(TransportConfig {
relay: false,
..Default::default()
})
.native_ip_exclusions(networks.clone())
.build();
let live_update = client.set_native_ip_exclusions(networks).await;
assert!(
live_update.is_err(),
"an unsupported policy update cannot be acknowledged, including explicit clear"
);
let result = client
.init_iroh_without_internal_router(None, Vec::new())
.await;
assert!(
result.is_err(),
"an explicitly configured unsupported policy cannot bind an endpoint"
);
assert!(client.iroh_endpoint.read().await.is_none());
assert!(client.current_node_id().await.is_none());
}
}
#[cfg(feature = "native-ip-exclusions")]
#[tokio::test]
async fn native_ip_constructor_and_live_updates_use_the_same_endpoint() {
for supplied_by_sdk in [true, false] {
let networks = vec!["127.0.0.0/8".parse().unwrap()];
let builder =
ClientBuilder::new_provider_neutral("ip-policy-test".into(), Box::new(|| None));
let client = if supplied_by_sdk {
builder.native_ip_exclusions(networks.clone()).build()
} else {
builder.build()
};
let builder = iroh::Endpoint::builder(iroh::endpoint::presets::Minimal)
.bind_addr("127.0.0.1:0")
.unwrap()
.bind_addr("[::1]:0")
.unwrap();
let builder = if supplied_by_sdk {
builder
} else {
builder.excluded_ip_networks(networks)
};
let endpoint = builder.bind().await.unwrap();
assert_eq!(
endpoint
.addr()
.ip_addrs()
.any(|addr| addr.ip().to_canonical().is_ipv4()),
supplied_by_sdk,
"verify the fixture's initial candidate policy"
);
let node = endpoint.id();
client
.adopt_endpoint_with_router_mode(endpoint.clone(), false)
.await
.unwrap();
assert!(endpoint.addr().ip_addrs().all(|addr| !addr.ip().to_canonical().is_ipv4()),
"constructor input must be committed before publishing; unspecified input must preserve a host exclusion");
client.set_native_ip_exclusions(Vec::new()).await.unwrap();
assert!(
endpoint
.addr()
.ip_addrs()
.any(|addr| addr.ip().to_canonical().is_ipv4()),
"clearing policy must refresh native candidates on the retained endpoint"
);
client
.set_native_ip_exclusions(vec!["127.0.0.0/8".parse().unwrap()])
.await
.unwrap();
assert!(endpoint
.addr()
.ip_addrs()
.all(|addr| !addr.ip().to_canonical().is_ipv4()));
assert_eq!(
client.iroh_endpoint.read().await.as_ref().unwrap().id(),
node
);
assert!(!endpoint.is_closed());
endpoint.close().await;
}
}
}