How to Implement a Custom Address Filter in iroh

To implement a custom address filter in iroh, create an AddrFilter using AddrFilter::custom with a closure that evaluates TransportAddr items, then attach it via Builder::addr_filter during endpoint construction or AddressLookupServices::set_addr_filter at runtime.

The iroh networking stack uses address filters to control which transport addresses a node publishes or accepts when connecting to peers. This mechanism lives in the core iroh and iroh-dns crates, allowing you to restrict endpoint visibility to specific address types such as relay URLs, IPv6-only sockets, or custom combinations.

Understanding the AddrFilter Type

According to the iroh source code, an AddrFilter is defined in iroh-dns/src/endpoint_info.rs as a type that stores an optional boxed predicate with the signature Fn(&TransportAddr) -> bool. When the endpoint publishes address data, it calls EndpointData::apply_filter(&filter), which runs this predicate against every TransportAddr and retains only those returning true.

The crate provides three built-in presets:

  • AddrFilter::relay_only() – keeps only TransportAddr::Relay(_) variants
  • AddrFilter::ip_only() – keeps only direct IP socket addresses
  • AddrFilter::unfiltered() – allows all addresses (no filtering)

Creating a Custom Address Filter

A custom filter is constructed by providing a closure to AddrFilter::custom. The closure receives a &TransportAddr and must return a boolean indicating whether to keep that address.

The TransportAddr enum distinguishes between relay-based and direct IP addresses:

  • TransportAddr::Relay(_) for relay URLs
  • TransportAddr::Ip(socket_addr) for direct socket addresses
use iroh::address_lookup::AddrFilter;
use iroh_base::TransportAddr;

// Custom filter keeping only relay URLs and IPv6 addresses
let my_filter = AddrFilter::custom(|addr: &TransportAddr| match addr {
    TransportAddr::Relay(_) => true,
    TransportAddr::Ip(ip) => ip.is_ipv6(),
    _ => false,
});

Applying Filters During Endpoint Construction

The most common approach attaches the filter during endpoint setup via Builder::addr_filter in iroh/src/endpoint.rs. This applies the filter globally to both publishing and lookup operations.

use iroh::{
    Endpoint,
    address_lookup::AddrFilter,
    endpoint::{presets, Builder},
    transport::TransportAddr,
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let filter = AddrFilter::custom(|addr| match addr {
        TransportAddr::Relay(_) => true,
        TransportAddr::Ip(ip) => ip.is_ipv6(),
        _ => false,
    });

    let ep: Endpoint = Builder::new(presets::Minimal)
        .addr_filter(filter)
        .bind()
        .await?;

    Ok(())
}

Once bound, the endpoint automatically applies this filter whenever publishing endpoint data, ensuring only IPv6 and relay addresses are visible to peers.

Updating Filters at Runtime

You can modify the filter on an existing endpoint via AddressLookupServices::set_addr_filter, defined in iroh/src/address_lookup.rs. This replaces the global filter for all subsequent publish operations without affecting existing published data.

use iroh::{Endpoint, address_lookup::AddrFilter};

async fn update_filter(ep: &Endpoint) -> anyhow::Result<()> {
    let ipv4_only = AddrFilter::custom(|addr| match addr {
        iroh_base::TransportAddr::Ip(ip) => ip.is_ipv4(),
        _ => false,
    });

    ep.address_lookup()
        .expect("endpoint still open")
        .set_addr_filter(ipv4_only);

    Ok(())
}

Note that existing published data is not retroactively filtered; only future publish calls use the new filter.

Service-Specific Address Filtering

Individual address-lookup services support independent filters. For example, PkarrPublisher in iroh-dns/src/pkarr.rs exposes its own addr_filter method in the builder, allowing fine-grained control per service.

use iroh::{
    address_lookup::{AddrFilter, PkarrPublisherBuilder},
    Endpoint,
};

let pkarr_pub = PkarrPublisherBuilder::new(pkarr_url)
    .addr_filter(AddrFilter::custom(|addr| {
        matches!(addr, iroh_base::TransportAddr::Relay(_))
    }))
    .build(secret_key, tls_config)?;

This configuration ensures the Pkarr service publishes only relay URLs, even if the global endpoint filter allows direct IPs.

Complete Minimal Example

This example demonstrates a fully configured endpoint with a custom filter that blocks all direct IP addresses, allowing only relay URLs:

use iroh::{
    address_lookup::{AddrFilter, DnsResolver, PkarrPublisher},
    endpoint::{presets, Builder},
    transport::TransportAddr,
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let filter = AddrFilter::custom(|addr| matches!(addr, TransportAddr::Relay(_)));

    let ep = Builder::new(presets::Minimal)
        .addr_filter(filter)
        .address_lookup(PkarrPublisher::n0_dns())
        .address_lookup(DnsResolver::default())
        .bind()
        .await?;

    // Endpoint now publishes only relay URLs
    Ok(())
}

Summary

  • AddrFilter is defined in iroh-dns/src/endpoint_info.rs and stores a predicate Fn(&TransportAddr) -> bool.
  • Create custom filters using AddrFilter::custom with a closure that inspects TransportAddr variants.
  • Attach global filters during construction via Builder::addr_filter in iroh/src/endpoint.rs.
  • Modify filters at runtime using AddressLookupServices::set_addr_filter in iroh/src/address_lookup.rs.
  • Apply service-specific filters via individual builder methods like PkarrPublisherBuilder::addr_filter.
  • Built-in presets relay_only, ip_only, and unfiltered cover common use cases.

Frequently Asked Questions

How does the AddrFilter actually remove addresses?

The filter applies during the publish workflow. When EndpointData::apply_filter is called, it iterates through all candidate addresses and retains only those where the predicate returns true. This filtered set is then published to the network via the configured lookup services.

Can I use multiple filters simultaneously?

You cannot chain multiple AddrFilter instances directly. Instead, combine logic inside a single closure passed to AddrFilter::custom. For example, check both is_ipv6() and port ranges within one closure to achieve complex filtering rules.

Will changing the filter affect existing connections?

No. Changing the filter via set_addr_filter only affects future address publications. Existing published endpoint data remains cached by remote peers until it expires, and active connections remain unaffected because they are established before the filter change.

Where are the built-in filter methods implemented?

The built-in convenience methods relay_only(), ip_only(), and unfiltered() are implemented in iroh-dns/src/endpoint_info.rs alongside the AddrFilter struct definition. These methods return pre-configured AddrFilter instances with standard closures for common filtering scenarios.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →