# How iroh's BeforeConnectOutcome Connection Hooks Work: Pre-Connection Filtering in Rust

> Learn how iroh's BeforeConnectOutcome connection hooks enable pre-connection filtering in Rust. Inspect or reject outgoing connections before packets are sent with Accept or Reject.

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

---

**BeforeConnectOutcome connection hooks allow applications to inspect or reject outgoing connections before any network packets are transmitted, returning either `Accept` to proceed or `Reject` to abort the attempt immediately.**

The iroh networking library provides a robust hook system for intercepting connection attempts at the application layer. Understanding how **BeforeConnectOutcome connection hooks** work enables developers to implement security policies, rate limiting, or address-based filtering without wasting network resources. These hooks execute during the `connect` call in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs), ensuring zero packets leave the host if the connection is rejected.

## What is BeforeConnectOutcome?

`BeforeConnectOutcome` is a Rust enum that defines the possible results of a pre-connection inspection. Located in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs), this type enables the hook system to communicate whether an outgoing connection should proceed or be blocked.

### The Enum Definition in hooks.rs

According to the source code in lines 9-16, the enum defines two variants:

- **`Accept`** – Signals that the connection attempt should continue to the next hook or proceed to the network layer.
- **`Reject`** – Immediately aborts the connection attempt without transmitting any packets.

### The EndpointHooks Trait Interface

The trait definition in the same file (lines 69-85) specifies the contract that all hooks must implement. The `before_connect` method receives the remote address and ALPN protocol identifier, then returns a future resolving to a `BeforeConnectOutcome`. The default implementation simply returns `Accept`, meaning you only need to override this method when you require custom filtering logic.

```rust
fn before_connect<'a>(
    &'a self,
    remote_addr: &'a EndpointAddr,
    alpn: &'a [u8],
) -> impl Future<Output = BeforeConnectOutcome> + Send + 'a;

```

## Hook Registration and Storage

Hooks are installed during endpoint construction using `Builder::hooks`. The builder stores hooks in an `EndpointHooksList` structure, which internally maintains a `Vec<Box<dyn DynEndpointHooks>>` as defined in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs).

When you call `Builder::hooks(Box::new(your_hook))`, the hook is boxed and appended to this vector. The order of registration determines the execution order, with hooks running sequentially until one returns `Reject` or all have returned `Accept`.

## The Connection Flow and Dispatcher Logic

When `Endpoint::connect` or `connect_with_opts` is invoked, the endpoint delegates to `EndpointHooksList::before_connect` (lines 44-57) before any network traffic occurs.

The dispatcher iterates over the registered hooks in registration order. For each hook, it awaits the future returned by `hook.before_connect(remote_addr, alpn)` and evaluates the outcome:

- **If `Accept` is returned** – Processing continues to the next hook in the vector.
- **If `Reject` is returned** – The iteration stops immediately, and the connection attempt is aborted. The `Reject` outcome propagates back to the caller of `connect`, and no packets are transmitted to the remote peer.

This short-circuit behavior ensures that expensive or unwanted connections are terminated at the application layer, conserving both network bandwidth and computational resources.

## Implementing a Custom Pre-Connection Hook

The following example demonstrates how to implement a blacklist hook that rejects connections to specific addresses before they reach the network layer:

```rust
use iroh_base::EndpointAddr;
use iroh::endpoint::{Builder, EndpointHooks, BeforeConnectOutcome};

/// A simple hook that rejects connections to a black-listed address.
#[derive(Debug)]
struct BlacklistHook;

impl EndpointHooks for BlacklistHook {
    fn before_connect<'a>(
        &'a self,
        remote_addr: &'a EndpointAddr,
        _alpn: &'a [u8],
    ) -> impl Future<Output = BeforeConnectOutcome> + Send + 'a {
        async move {
            if remote_addr.to_string().contains("bad.peer") {
                // Abort the connection attempt.
                BeforeConnectOutcome::Reject
            } else {
                BeforeConnectOutcome::Accept
            }
        }
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Build an endpoint and install the hook.
    let endpoint = Builder::default()
        .hooks(Box::new(BlacklistHook))
        .bind_ephemeral()?
        .await?;

    // This connect will be rejected by the hook.
    let _ = endpoint
        .connect("bad.peer:12345".parse()?, b"iroh/alpn")
        .await
        .expect_err("connection should be rejected");

    Ok(())
}

```

This implementation demonstrates three critical patterns: registering a custom hook with `Builder::hooks`, examining the `remote_addr` parameter, and returning `Reject` to abort the connection before any packets leave the host.

## Summary

- **BeforeConnectOutcome** is an enum in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs) with `Accept` and `Reject` variants that control connection flow.
- Hooks implement the `EndpointHooks` trait and override `before_connect` to inspect outgoing connections.
- The `EndpointHooksList::before_connect` dispatcher (lines 44-57) executes hooks sequentially until one returns `Reject` or all return `Accept`.
- **Zero network overhead** occurs when `Reject` is returned, as the connection aborts before packet transmission.
- Registration occurs via `Builder::hooks`, which stores hooks in a `Vec<Box<dyn DynEndpointHooks>>` preserving insertion order.

## Frequently Asked Questions

### What happens when multiple BeforeConnectOutcome hooks are registered?

Hooks execute in the order they were added to the builder. The system evaluates each hook sequentially, and if any hook returns `Reject`, the remaining hooks are skipped and the connection aborts immediately. If all hooks return `Accept`, the connection proceeds to the network layer.

### Does the before_connect hook support async operations?

Yes, the `before_connect` method returns an `impl Future`, allowing you to perform asynchronous operations such as database lookups or external API calls to determine whether to accept or reject a connection. The endpoint awaits this future before proceeding.

### What is the performance impact of using these hooks?

The overhead is minimal and proportional to the logic inside your hook. Since the hook executes before any network packets are sent, rejecting a connection actually saves resources by avoiding unnecessary network I/O. However, expensive async operations in hooks will delay the connection attempt until they complete.

### How does BeforeConnectOutcome differ from after_handshake hooks?

`BeforeConnectOutcome` hooks execute **before** any network traffic occurs, allowing you to reject connections based on static information like the remote address or ALPN. In contrast, `after_handshake` hooks execute **after** the cryptographic handshake completes, enabling inspection of authenticated peer identities but consuming network resources to establish the connection first. The [`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs) file demonstrates both patterns in a real-world scenario.