# How to Implement a Custom Address Filter in iroh

> Learn to implement a custom address filter in iroh by creating an AddrFilter with a closure and attaching it during endpoint construction or at runtime. Enhance your network control.

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

---

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

```rust
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`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs). This applies the filter globally to both publishing and lookup operations.

```rust
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`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs). This replaces the global filter for all subsequent publish operations without affecting existing published data.

```rust
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`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs) exposes its own `addr_filter` method in the builder, allowing fine-grained control per service.

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

```rust
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`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs).
- Modify filters at runtime using **`AddressLookupServices::set_addr_filter`** in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/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`](https://github.com/n0-computer/iroh/blob/main/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.