How iroh's BeforeConnectOutcome Connection Hooks Work: Pre-Connection Filtering in Rust
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, 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, 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.
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.
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
Acceptis returned – Processing continues to the next hook in the vector. - If
Rejectis returned – The iteration stops immediately, and the connection attempt is aborted. TheRejectoutcome propagates back to the caller ofconnect, 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:
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.rswithAcceptandRejectvariants that control connection flow. - Hooks implement the
EndpointHookstrait and overridebefore_connectto inspect outgoing connections. - The
EndpointHooksList::before_connectdispatcher (lines 44-57) executes hooks sequentially until one returnsRejector all returnAccept. - Zero network overhead occurs when
Rejectis returned, as the connection aborts before packet transmission. - Registration occurs via
Builder::hooks, which stores hooks in aVec<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 file demonstrates both patterns in a real-world scenario.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →