# How Iroh Endpoint Hooks Filter Incoming Connections: A Complete Guide

> Learn how Iroh endpoint hooks filter incoming connections using the after_handshake method to accept or reject. Understand immediate connection termination and hook execution.

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

---

**Iroh endpoint hooks filter incoming connections by implementing the `after_handshake` method to return either `Accept` or `Reject`, where the first rejection immediately terminates the connection and prevents subsequent hooks from executing.**

The n0-computer/iroh repository provides a Rust networking stack that allows developers to intercept incoming connections at the endpoint level. Understanding how iroh endpoint hooks filter incoming connections enables you to implement custom authentication, blocklists, and access control policies before the application layer processes any data.

## The Connection Establishment Flow

When a remote peer attempts to connect to your iroh endpoint, the networking stack executes a specific sequence before allowing the connection to proceed. First, the TLS handshake completes, making the remote's endpoint ID and Application-Layer Protocol Negotiation (ALPN) available to the local node.

Immediately after handshake completion, the endpoint invokes the `EndpointHooks::after_handshake` method for every hook registered on the endpoint. According to the implementation in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs) (lines 87-94), this occurs at a critical decision point where the connection exists but no application data has been exchanged.

## How Filtering Decisions Work

Each endpoint hook must return an `AfterHandshakeOutcome` that determines the connection's fate. The enum provides two variants for filtering:

- **`AfterHandshakeOutcome::Accept`** – Allows the connection to proceed normally.
- **`AfterHandshakeOutcome::Reject { error_code, reason }`** – Immediately closes the connection with the specified QUIC error code and reason string.

The filtering logic implements a **short-circuit pattern**: if any hook returns `Reject`, the endpoint closes the connection instantly and skips all remaining hooks. This design ensures that security-critical hooks—such as those handling authentication or blocklists—can terminate unwanted connections without wasting resources on additional validation.

The default implementation of `after_handshake` simply returns `Accept`, meaning connections are allowed unless explicitly overridden by custom hook logic.

## Implementing a Custom Connection Filter

To filter incoming connections, implement the `EndpointHooks` trait and override the `after_handshake` method. The following example demonstrates a blocklist hook that rejects connections from specific endpoint IDs:

```rust
use iroh::endpoint::{Builder, EndpointHooks, AfterHandshakeOutcome};
use iroh::endpoint::connection::Connection;
use iroh::endpoint::quic::VarInt;

/// A simple hook that rejects connections from a black‑listed endpoint ID.
#[derive(Debug)]
struct BlocklistHook {
    blocked_id: iroh_base::EndpointId,
}

impl EndpointHooks for BlocklistHook {
    fn after_handshake<'a>(
        &'a self,
        conn: &'a Connection,
    ) -> impl Future<Output = AfterHandshakeOutcome> + Send + 'a {
        async move {
            if conn.remote_id() == self.blocked_id {
                // 0x01 = "Application error" (QUIC error code)
                AfterHandshakeOutcome::reject(VarInt::from_u32(0x01), b"blocked endpoint")
            } else {
                AfterHandshakeOutcome::Accept
            }
        }
    }
}

// Build an endpoint with the hook installed
let endpoint = Builder::default()
    .hooks(BlocklistHook { blocked_id: ... })
    .bind()?;

```

In this implementation, the hook inspects `conn.remote_id()` after the TLS handshake completes. If the remote endpoint ID matches the blocklist entry, the hook returns `Reject` with a QUIC error code `0x01`, causing the connection to close immediately with the reason string "blocked endpoint".

## Higher-Level Incoming Filters

While `EndpointHooks` provide low-level control, iroh also offers higher-level abstractions in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs). The `IncomingFilter` trait wraps the low-level hook outcomes and provides the `IncomingFilterOutcome` enum with four states:

- **`Accept`** – Proceed with the connection.
- **`Reject`** – Close the connection immediately.
- **`Retry`** – Request the remote to retry (useful for address validation).
- **`Ignore`** – Silently drop the connection.

The repository includes practical examples demonstrating these patterns:
- **[`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs)** – Shows authentication token validation that rejects connections with custom error codes.
- **[`iroh/examples/incoming-filter.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/incoming-filter.rs)** – Demonstrates address validation using the retry mechanism.

## Summary

- **Iroh endpoint hooks** intercept connections after the TLS handshake via the `after_handshake` method defined in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs).
- Hooks return `AfterHandshakeOutcome::Accept` to allow connections or `AfterHandshakeOutcome::Reject` to close them with specific QUIC error codes.
- The **first rejection wins**: subsequent hooks do not execute once a hook returns `Reject`.
- The default implementation permits all connections, requiring custom logic to implement filtering.
- Higher-level **IncomingFilter** abstractions in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) provide additional `Retry` and `Ignore` outcomes for complex handshaking scenarios.

## Frequently Asked Questions

### What happens when multiple hooks are registered on an iroh endpoint?

When multiple hooks are registered, iroh executes them sequentially in the order they were added. However, the system short-circuits on the first `Reject` outcome, meaning if Hook A rejects the connection, Hook B never executes. This behavior ensures that security-critical filters can block malicious connections before resource-intensive validation logic runs.

### Can iroh endpoint hooks modify connections instead of just filtering them?

No, the `EndpointHooks` trait is designed strictly for accept/reject decisions immediately after the handshake. The `after_handshake` method signature allows inspecting the `Connection` object to read remote endpoint IDs and ALPN information, but it does not provide mutable access to modify connection parameters. For protocol-level modifications, implement custom logic in the application layer after the connection is accepted.

### What is the difference between EndpointHooks and IncomingFilter in iroh?

**EndpointHooks** provide low-level QUIC connection control with binary Accept/Reject outcomes, operating immediately after the TLS handshake in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs). **IncomingFilter**, defined in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs), offers a higher-level abstraction with four outcomes: Accept, Reject, Retry, and Ignore. IncomingFilter is typically used for application-layer protocol handlers that need to request retries or silently ignore connections rather than just accepting or rejecting them.

### How do I provide a custom error code when rejecting a connection in iroh?

When returning `AfterHandshakeOutcome::Reject`, specify a QUIC error code using `VarInt::from_u32()` and provide a byte string reason. For example: `AfterHandshakeOutcome::reject(VarInt::from_u32(0x01), b"blocked endpoint")`. The error code `0x01` typically represents an application-level error, while custom codes in the range reserved for private use can be defined for your specific protocol.