# How to Implement Custom Address Filters in iroh for Publishing Addresses

> Learn to implement custom address filters in iroh. Control address publishing with `AddrFilter` and `EndpointBuilder` for the n0-computer/iroh repository.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-07-05

---

**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`](https://github.com/n0-computer/iroh/blob/main/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.

```rust
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 only `TransportAddr::Relay(_)` variants, hiding all direct IP addresses
- **`AddrFilter::ip_only()`** – Retains only direct IP socket addresses, excluding relay URLs
- **`AddrFilter::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`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs). This method integrates the filter into the endpoint construction flow, applying it to both publishing and lookup operations automatically.

```rust
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`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs). Access the lookup registry through `endpoint.address_lookup()` to replace the global filter for all subsequent publish operations.

```rust
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`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs), the **`PkarrPublisherBuilder::addr_filter`** method allows service-specific filtering that applies only to that particular discovery mechanism.

```rust
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:

```rust
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`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) | **`Builder::addr_filter`** method that integrates `AddrFilter` into endpoint construction |
| [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/endpoint_info.rs) | **`AddrFilter`** type definition, **`custom`** constructor, and **`apply_filter`** logic |
| [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs) | **`PkarrPublisherBuilder::addr_filter`** for per-service filtering |
| [`iroh/examples/transfer.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/transfer.rs) | Practical demonstrations of preset filter usage |

## Summary

- **`AddrFilter`** in [`iroh-dns/src/endpoint_info.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/endpoint_info.rs) represents address filtering logic as a boxed predicate `Fn(&TransportAddr) -> bool`
- Create custom filters using **`AddrFilter::custom`** with a closure that evaluates each `TransportAddr` variant
- Apply filters globally during endpoint construction via **`EndpointBuilder::addr_filter`** or at runtime via **`AddressLookupServices::set_addr_filter`**
- Use per-service filters like **`PkarrPublisherBuilder::addr_filter`** for 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`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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.