How to Configure Portmapper for NAT Traversal in iroh

Use PortMapper::new() to connect to a relay, then call map_port() with your protocol and private address to expose a local service behind NAT through a public port allocated by the relay, and release it with unmap() when done.

Configuring portmapper for NAT traversal in iroh allows nodes behind restrictive firewalls to expose local services to the public internet without manual router configuration. The n0-computer/iroh repository provides a built-in Portmapper service that handles dynamic port allocation and encrypted traffic forwarding. This guide explains how to use the client API defined in iroh/src/portmapper.rs to create and manage NAT mappings.

Understanding iroh's Portmapper Architecture

The portmapper consists of a client-side API and a relay service that work together to punch holes through NAT.

Core Components

  • PortMapper struct – Defined in iroh/src/portmapper.rs, this is the client interface that manages connections to relay nodes and tracks active mappings.
  • Protocol enum – Supports Protocol::Tcp and Protocol::Udp for creating transport-specific mappings.
  • PortMappingRequest and PortMappingResponse – Serialization structs that handle the negotiation between client and relay using bincode for low-overhead messaging.
  • Relay service – A publicly reachable iroh node that allocates public ports and forwards traffic over the encrypted iroh mesh.

How NAT Traversal Works

When you configure portmapper for NAT traversal, the client sends a PortMappingRequest to a relay containing the desired protocol and private socket address. The relay checks its available port pool, reserves an unused public port, and stores the mapping in an internal HashMap<PublicPort, MappingInfo> protected by a RwLock. The relay then returns a PortMappingResponse with the public endpoint. Incoming traffic on the relay's public port is forwarded through the iroh transport layer to your local service, and replies travel back through the same encrypted channel.

Configuring the Portmapper Client

To begin, instantiate the PortMapper with the URL of a relay running the portmapper service.

use iroh::portmapper::{PortMapper, Protocol};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Connect to a public relay
    let mut mapper = PortMapper::new("https://relay.example.com:443").await?;
    
    // Map a local TCP service
    let public_addr = mapper
        .map_port(Protocol::Tcp, "127.0.0.1:8080".parse()?)
        .await?;
    
    println!("Public address: {}", public_addr);
    Ok(())
}

The PortMapper::new method establishes the connection to the relay, while map_port registers your local service. According to the source in iroh/src/portmapper.rs, map_port is typically found around line 78 and handles the async negotiation with the relay.

Mapping UDP Services

For UDP-based applications like DNS or VoIP, use the same pattern with Protocol::Udp:

let public_addr = mapper
    .map_port(Protocol::Udp, "127.0.0.1:5353".parse()?)
    .await?;

The implementation in iroh/src/portmapper.rs supports both protocols through the same async interface, spawning appropriate forwarding tasks for each transport type.

Managing Port Mappings with map_port and unmap

Creating a mapping reserves a public port for the lifetime of the connection or until explicitly released. To free the port on the relay and stop the forwarding task, call unmap.

// Release the specific port when no longer needed
mapper.unmap(public_addr.port()).await?;

The unmap method, located in iroh/src/portmapper.rs around line 102, sends a teardown request to the relay, which removes the entry from its internal mapping table and closes the public socket.

Complete Example: Exposing a Local HTTP Server Behind NAT

This full example demonstrates running a Hyper HTTP server locally and exposing it through the portmapper.

use iroh::portmapper::{PortMapper, Protocol};
use hyper::{
    service::{make_service_fn, service_fn},
    Body, Request, Response, Server,
};

async fn hello(_req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
    Ok(Response::new(Body::from("Hello from behind NAT!")))
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Start local HTTP server on 127.0.0.1:8080
    let make_svc = make_service_fn(|_| async { 
        Ok::<_, hyper::Error>(service_fn(hello)) 
    });
    let server = Server::bind(&"127.0.0.1:8080".parse()?).serve(make_svc);
    tokio::spawn(server);

    // Configure portmapper for NAT traversal
    let mut mapper = PortMapper::new("https://relay.example.com:443").await?;
    let public_addr = mapper
        .map_port(Protocol::Tcp, "127.0.0.1:8080".parse()?)
        .await?;
    
    println!("Service available at: http://{}", public_addr);
    
    // Keep running until interrupted
    tokio::signal::ctrl_c().await?;
    
    // Clean up the mapping
    mapper.unmap(public_addr.port()).await?;
    Ok(())
}

This example combines the local service binding with the portmapper client, creating a complete NAT traversal solution using only the iroh SDK.

Summary

  • Instantiate the client with PortMapper::new(relay_url) to connect to a relay node.
  • Create mappings using map_port(Protocol, SocketAddr) to expose local TCP or UDP services through a public port allocated by the relay.
  • Traffic forwarding happens automatically over iroh's encrypted transport layer, with the relay handling the NAT traversal.
  • Clean up resources by calling unmap(port) to release public ports and stop forwarding tasks.
  • All client functionality is implemented in iroh/src/portmapper.rs, with supporting relay logic in iroh/src/relay.rs and transport encryption in iroh/src/transport.rs.

Frequently Asked Questions

What protocols does the iroh portmapper support?

The iroh portmapper supports both TCP and UDP protocols through the Protocol enum defined in iroh/src/portmapper.rs. You can specify Protocol::Tcp for HTTP servers or Protocol::Udp for DNS, VoIP, or gaming applications.

How do I choose a relay node for port mapping?

Any iroh node running the portmapper service can act as a relay. In practice, you should use a publicly reachable node with a stable internet connection and static IP address. The n0-computer/iroh repository typically provides documentation on public relay endpoints, or you can run your own relay by enabling the portmapper service in the node configuration.

Is traffic forwarded through the portmapper encrypted?

Yes. While the portmapper handles the allocation of public ports and packet forwarding, the actual data flows through iroh's existing encrypted transport layer defined in iroh/src/transport.rs. This means connections benefit from end-to-end encryption without requiring additional TLS configuration on the mapped ports.

Can I use multiple port mappings simultaneously?

Yes. A single PortMapper instance can manage multiple concurrent mappings. Simply call map_port() multiple times with different local socket addresses or protocols. Each call returns a unique public address, and the client maintains an internal mapping table to route traffic correctly. Remember to call unmap() for each port when shutting down services.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →