# How iroh Endpoint Hooks Filter Incoming Connections

> Learn how Iroh endpoint hooks filter incoming connections using the after_handshake method. Accept or reject connections instantly with QUIC error codes for enhanced network control.

- 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 on the `EndpointHooks` trait, returning `AfterHandshakeOutcome::Accept` to allow the connection or `AfterHandshakeOutcome::Reject` to terminate it immediately with a QUIC error code.**

The n0-computer/iroh repository provides a flexible networking stack where **endpoint hooks** act as gatekeepers during connection establishment. When a remote peer attempts to connect, these hooks inspect the connection details after the TLS handshake completes and decide whether to keep or discard the session. This mechanism allows developers to implement custom authentication, blocklisting, or rate limiting directly within the endpoint configuration.

## The Connection Filtering Flow

When an incoming connection arrives at an iroh `Endpoint`, the filtering process follows a strict sequence defined in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs). The hooks are invoked synchronously during the handshake completion phase.

### 1. Handshake Completion

First, the TLS handshake completes. At this point, the remote peer’s **endpoint ID** and **ALPN** (Application-Layer Protocol Negotiation) are known and available to the hook implementation.

### 2. Hook Invocation

The `EndpointHooks::after_handshake` method is called for every hook registered on the endpoint. According to the source code in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs) (lines 87-94), the method signature allows asynchronous inspection of the connection:

```rust
fn after_handshake<'a>(
    &'a self,
    conn: &'a Connection,
) -> impl Future<Output = AfterHandshakeOutcome> + Send + 'a

```

### 3. Outcome Determination

Each hook returns an `AfterHandshakeOutcome`. The enum defines two critical variants:

- **`Accept`** – Signals that the connection should proceed normally.
- **`Reject { error_code, reason }`** – Immediately closes the connection with the specified QUIC error code (as a `VarInt`) and reason string.

If any hook returns `Reject`, the processing stops immediately and subsequent hooks are **not invoked**. This short-circuit behavior means the first rejection effectively filters the connection without incurring the cost of running additional hooks.

The default implementation of `after_handshake` simply returns `Accept`, meaning connections are allowed unless a custom hook explicitly overrides this behavior.

## Implementing a Custom Filter Hook

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 hook that rejects connections from blacklisted endpoint IDs.
#[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 handshake. If the ID matches the blocklist, it returns `Reject`, causing the connection to close instantly with error code `0x01` and the reason "blocked endpoint".

## Common Hook Patterns

The iroh repository provides several examples demonstrating different filtering strategies:

- **Authentication Hook** ([`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs)): Verifies tokens presented by the remote after the TLS handshake. Returns `Reject` if the authentication token mismatches or is missing.
- **Remote Information Hook** ([`iroh/examples/remote-info.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/remote-info.rs)): Records remote endpoint details for logging or metrics. Typically returns `Accept` unless recording fails.
- **Incoming Filter** ([`iroh/examples/incoming-filter.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/incoming-filter.rs)): Implements higher-level filtering using `IncomingFilterOutcome` (defined in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs)). This wrapper provides `Accept`, `Reject`, `Retry`, or `Ignore` decisions based on remote address validation status.

## Key Source Files

| File | Purpose |
|------|---------|
| [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs) | Defines `EndpointHooks`, `BeforeConnectOutcome`, `AfterHandshakeOutcome`, and the hook-invocation logic. |
| [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) | Implements `IncomingFilter` and `IncomingFilterOutcome` for higher-level filtering abstractions. |
| [`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs) | Demonstrates rejecting connections based on custom authentication logic. |
| [`iroh/examples/incoming-filter.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/incoming-filter.rs) | Shows retry logic for connections with unvalidated remote addresses. |

## Summary

- **Endpoint hooks** intercept incoming connections after the TLS handshake in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs).
- The **`after_handshake`** method returns `AfterHandshakeOutcome::Accept` to allow connections or `Reject` to close them with a QUIC error code.
- Hooks process **sequentially**, and the first `Reject` outcome stops the chain, preventing later hooks from executing.
- The `Connection` object provides access to the remote endpoint ID and ALPN for inspection.
- Default behavior is permissive (`Accept`), requiring explicit override for filtering.

## Frequently Asked Questions

### How do endpoint hooks differ from incoming filters?

**Incoming filters** are a higher-level abstraction defined in [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) that wrap the low-level hook outcomes. While `AfterHandshakeOutcome` provides binary `Accept/Reject` decisions, `IncomingFilterOutcome` adds `Retry` and `Ignore` variants for more nuanced handling of connection attempts. The underlying mechanism still relies on the `EndpointHooks` trait.

### Can multiple hooks be registered on a single endpoint?

Yes. The `Builder` accepts hooks via the `.hooks()` method, and multiple hooks can be chained. However, if any hook returns `Reject`, the connection is terminated immediately and remaining hooks are skipped. This design ensures that security-critical hooks (like authentication) can prevent wasted computation on subsequent filters.

### What error codes should be used when rejecting connections?

The `Reject` variant accepts a `VarInt` error code and a byte slice reason string. While iroh uses standard QUIC error codes internally, application-specific hooks typically use codes in the range reserved for application errors (e.g., `0x01` through `0x3fff`). The [`auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/auth-hook.rs) example demonstrates using custom error codes to signal specific failure modes to the connecting peer.

### Is there a hook for filtering outgoing connections?

Yes. The same [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs) file defines `BeforeConnectOutcome` for filtering **outgoing** connections before they are established. While the `after_handshake` flow handles incoming connections, outgoing filtering uses the `before_connect` method to decide whether to initiate a connection to a remote endpoint based on local policy.