How to Implement Custom Address Filters in iroh for Publishing Addresses
Implement custom address filters in iroh by creating an AddrFilter with AddrFilter::custom and a closure that evaluates TransportAddr instances, then apply it via EndpointBuilder::addr_filter during construction or AddressLookupServices::set_addr_filter at runtime to control which addresses your node publishes to peers.
The iroh networking stack from n0-computer/iroh allows nodes to control exactly which transport addresses they advertise to the network using customizable filters. When publishing endpoint information to DNS, Pkarr, or other discovery services, implementing custom address filters ensures your application exposes only intended addresses—whether relay URLs, specific IP versions, or domain-specific criteria. This guide covers the architecture, implementation patterns, and specific source locations you need to customize address publication behavior in the iroh codebase.
Understanding iroh Address Filter Architecture
Address filters in iroh are represented by the AddrFilter type defined in iroh-dns/src/endpoint_info.rs. This type stores an optional boxed predicate with the signature Fn(&TransportAddr) -> bool. When publishing endpoint data, the system internally calls EndpointData::apply_filter(&filter), which iterates over every available TransportAddr and retains only those where the predicate returns true.
The same filter applies globally across all address lookup services when set via AddressLookupServices::set_addr_filter, ensuring consistent address visibility whether using DNS, Pkarr, or memory-based discovery implementations. According to the iroh source code, the filter evaluation happens during the publish phase, before addresses reach any remote resolver.
Creating a Custom Address Filter
Using the Custom Constructor
Create a custom filter using AddrFilter::custom, which accepts a closure that receives a reference to a TransportAddr and returns a boolean. The closure evaluates each address candidate during the publishing phase, allowing fine-grained control over address visibility.
use iroh::address_lookup::AddrFilter;
use iroh::transport::TransportAddr;
// Custom filter: allow relay URLs and IPv6 addresses only
let my_filter = AddrFilter::custom(|addr: &TransportAddr| match addr {
TransportAddr::Relay(_) => true,
TransportAddr::Ip(ip) => ip.is_ipv6(),
_ => false,
});
Built-in Filter Presets
iroh provides three built-in presets for common filtering scenarios:
AddrFilter::relay_only()– Retains onlyTransportAddr::Relay(_)variants, hiding all direct IP addressesAddrFilter::ip_only()– Retains only direct IP socket addresses, excluding relay URLsAddrFilter::unfiltered()– Retains all addresses (default behavior when no filter is set)
Applying Custom Filters to iroh Endpoints
During Endpoint Construction
Attach filters at build time using EndpointBuilder::addr_filter defined in iroh/src/endpoint.rs. This method integrates the filter into the endpoint construction flow, applying it to both publishing and lookup operations automatically.
use iroh::{
Endpoint,
address_lookup::AddrFilter,
endpoint::{presets, Builder},
transport::TransportAddr,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Define filter: keep relay URLs and IPv6 addresses only
let my_filter = AddrFilter::custom(|addr: &TransportAddr| match addr {
TransportAddr::Relay(_) => true,
TransportAddr::Ip(ip) => ip.is_ipv6(),
_ => false,
});
// Build endpoint with the filter installed
let ep: Endpoint = Builder::new(presets::Minimal)
.addr_filter(my_filter)
.bind()
.await?;
// Published data now contains only the filtered addresses
Ok(())
}
Runtime Filter Updates
Modify filters on already-running endpoints via AddressLookupServices::set_addr_filter implemented in iroh/src/address_lookup.rs. Access the lookup registry through endpoint.address_lookup() to replace the global filter for all subsequent publish operations.
use iroh::{Endpoint, address_lookup::AddrFilter};
async fn set_runtime_filter(ep: &Endpoint) -> anyhow::Result<()> {
// Keep only IPv4 addresses
let ipv4_filter = AddrFilter::custom(|addr| match addr {
iroh_base::TransportAddr::Ip(ip) => ip.is_ipv4(),
_ => false,
});
// Access the lookup registry and replace the global filter
ep.address_lookup()
.expect("endpoint still open")
.set_addr_filter(ipv4_filter);
Ok(())
}
Important: As implemented in the iroh source code, existing published data is not retroactively filtered. Only future calls to publish reflect the new filter criteria.
Per-Service Filter Configuration
Individual lookup services support their own addr_filter methods for fine-grained control. In iroh-dns/src/pkarr.rs, the PkarrPublisherBuilder::addr_filter method allows service-specific filtering that applies only to that particular discovery mechanism.
use iroh::{
address_lookup::{AddrFilter, PkarrPublisherBuilder},
Endpoint,
};
// Build a Pkarr publisher that never leaks private IPs
let pkarr_pub = PkarrPublisherBuilder::new(pkarr_url)
.addr_filter(AddrFilter::custom(|addr| {
// Disallow any address that is not a relay
matches!(addr, iroh_base::TransportAddr::Relay(_))
}))
.build(secret_key, tls_config)?;
Complete Implementation Examples
Full Integration with Multiple Services
Combine global filters with specific lookup services for comprehensive address control:
use iroh::{
address_lookup::{AddrFilter, DnsResolver, PkarrPublisher},
endpoint::{presets, Builder},
transport::TransportAddr,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Global filter: keep only relays, discard all direct IPs
let filter = AddrFilter::custom(|addr| matches!(addr, TransportAddr::Relay(_)));
// Build endpoint with global filter and specific lookup services
let ep = Builder::new(presets::Minimal)
.addr_filter(filter) // applies to all services
.address_lookup(PkarrPublisher::n0_dns()) // publishing via pkarr
.address_lookup(DnsResolver::default()) // lookup via DNS
.bind()
.await?;
// The endpoint now publishes only relay URLs across all configured services
Ok(())
}
Practical Filter from iroh Examples
The repository demonstrates switching between filter presets in iroh/examples/transfer.rs, showing how to toggle between AddrFilter::ip_only() and AddrFilter::relay_only() based on runtime configuration flags.
Key Source Files for Address Filtering
| File (relative to repository) | Implementation Details |
|---|---|
iroh/src/endpoint.rs |
Builder::addr_filter method that integrates AddrFilter into endpoint construction |
iroh/src/address_lookup.rs |
Global filter storage (addr_filter: Arc<RwLock<Option<AddrFilter>>>) and set_addr_filter method |
iroh-dns/src/endpoint_info.rs |
AddrFilter type definition, custom constructor, and apply_filter logic |
iroh-dns/src/pkarr.rs |
PkarrPublisherBuilder::addr_filter for per-service filtering |
iroh/examples/transfer.rs |
Practical demonstrations of preset filter usage |
Summary
AddrFilteriniroh-dns/src/endpoint_info.rsrepresents address filtering logic as a boxed predicateFn(&TransportAddr) -> bool- Create custom filters using
AddrFilter::customwith a closure that evaluates eachTransportAddrvariant - Apply filters globally during endpoint construction via
EndpointBuilder::addr_filteror at runtime viaAddressLookupServices::set_addr_filter - Use per-service filters like
PkarrPublisherBuilder::addr_filterfor mechanism-specific address control - Runtime filter changes only affect future publish operations; existing published data remains unchanged
Frequently Asked Questions
What is the default behavior if no address filter is set in iroh?
If no filter is configured, iroh behaves as if AddrFilter::unfiltered() is active, publishing all available transport addresses including direct IPs and relay URLs. The apply_filter method in iroh-dns/src/endpoint_info.rs passes through all addresses when the filter predicate is None.
Can I change the address filter after the iroh endpoint has started?
Yes, you can update the filter at runtime using endpoint.address_lookup().set_addr_filter(filter) as defined in iroh/src/address_lookup.rs. This replaces the global filter stored in an Arc<RwLock<Option<AddrFilter>>>, affecting all subsequent publish operations across configured lookup services.
Do custom filters retroactively remove already-published addresses?
No, custom filters do not retroactively filter existing published data. As implemented in the iroh source code, the filter only applies to future calls to publish. To remove previously published addresses, you must trigger a new publish operation after setting the filter, allowing the updated EndpointData::apply_filter logic to generate new filtered data.
What's the difference between global and per-service address filters in iroh?
Global filters set via Builder::addr_filter or AddressLookupServices::set_addr_filter apply to all address lookup services uniformly. Per-service filters, such as those set via PkarrPublisherBuilder::addr_filter in iroh-dns/src/pkarr.rs, apply only to that specific discovery mechanism. When both are present, the system applies the most restrictive combination or the service-specific filter, depending on the implementation context.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →