iron #
Peer-to-peer network interface based on iroh.
iron creates a virtual IPv6 network over peer-to-peer QUIC connections, letting
endpoints reach each other directly through .iron DNS names.
Features #
- Virtual overlay network: each peer is addressed by its public key
- Encrypted P2P: all traffic is encrypted via iroh's QUIC protocol
- DNS resolution:
.irondomains resolve to peer IPv6 addresses - TUN interface: a standard network device, so any application can use it
- NAT traversal: automatic hole punching with relay fallback
Architecture #
An application opens a normal IPv6 socket. The operating system routes traffic
for fd69:726f::/32 to iron's TUN interface, which maps the destination IPv6
address to a peer EndpointId and forwards the packet over an encrypted iroh QUIC
connection. The receiving peer verifies the source address and writes the packet
to its own TUN interface, where the OS delivers it to the listening application.
Building #
Prerequisite: nix
# build for your system
nix build .#default
# build for an explicit target
nix build .#packages.x86_64-linux.default
The binary will be at result/bin/iron.
Testing #
nix flake check
Usage #
Starting iron #
Basic usage (requires root):
sudo iron serve
On first run, iron automatically configures DNS for .iron domains on macOS and
Linux with systemd-resolved. This only affects .iron domains; all other DNS
resolution is untouched.
Custom log level and DNS port:
sudo iron serve --log-level debug
sudo iron serve --dns-port 5353
When iron starts, it prints your node identity:
Node ID (hex): 74df87cccf7e0fead1370fc39f65be3de44f5069f5db87f3b08435ccdaf3b5b9
Node ID (base32): ot36ptgm67yp5vjt6b6dtz2l4ppejtggt5w3y64lqqrvztpl2wnq
DNS name: ot36ptgm67yp5vjt6b6dtz2l4ppejtggt5w3y64lqqrvztpl2wnq.iron
Use the base32 Node ID for DNS queries.
Identity persistence #
Your Node ID and .iron domain are persistent across restarts. iron generates
and saves a secret key on first run:
- Key location:
~/.config/iron/secret.key - Permissions: 0600 (owner read/write only)
Keep this key secure; it is your node's identity. Your domain name stays the same across restarts, so peers can always reach you at the same address.
To reset your identity:
iron key reset
# or manually:
rm ~/.config/iron/secret.key
sudo iron serve
DNS configuration #
iron configures DNS automatically on supported platforms:
- macOS: creates
/etc/resolver/iron - Linux with systemd-resolved: creates
/etc/systemd/resolved.conf.d/iron.conf - Other Linux: see DNS Setup Guide for manual configuration
Only .iron domains are routed to iron's DNS server (127.0.0.1:5333); all
other domains use your normal DNS. This works alongside Tailscale, VPNs, and
other DNS setups.
To remove iron's DNS configuration (for example, after a crash):
sudo iron --cleanup-dns
See DNS Setup Guide for advanced configuration and troubleshooting.
Command line options #
For complete CLI documentation, see CLI Reference.
Usage: iron [COMMAND]
Commands:
serve Start the iron daemon (TUN interface and DNS server)
self Show information about your node
convert Convert between node ID formats
key Key management utilities
resolve Test DNS resolution
vanity Generate vanity address with desired prefix
help Print this message or the help of the given subcommand(s)
Global Options:
-l, --log-level <LEVEL> Set the log level [default: info]
(trace, debug, info, warn, error)
--dns-port <PORT> DNS server port [default: 5333]
--cleanup-dns Remove DNS configuration for .iron domains
-h, --help Print help
-V, --version Print version
Environment variables #
RUST_LOG controls log levels per module and overrides --log-level:
RUST_LOG=iron::protocol=trace,iron=info sudo iron serve
Connecting two nodes #
Both machines need iron installed and reachable from each other (same network, or internet with NAT traversal).
iron uses IPv6 exclusively, so always use IPv6 when connecting to .iron
domains:
nc -6 <node>.iron 1234
curl -6 http://<node>.iron:8080
ping6 <node>.iron
Step 1: start iron on both machines #
sudo iron serve
Note the base32 Node ID displayed on each machine.
Step 2: test connectivity #
From Machine B, resolve and reach Machine A:
iron resolve <MACHINE_A_BASE32_ID>.iron
# or with dig:
dig <MACHINE_A_BASE32_ID>.iron AAAA
ping6 <MACHINE_A_BASE32_ID>.iron
To verify end-to-end traffic, run a service on Machine A and access it from Machine B:
# Machine A: start an HTTP server bound to IPv6
python3 -m http.server 8080 --bind ::
# Machine B: access the server
curl -6 http://[<MACHINE_A_BASE32_ID>.iron]:8080/
# Or with netcat
# Machine A (listen on IPv6):
nc -6 -l 1234
# Machine B (connect over IPv6):
nc -6 <MACHINE_A_BASE32_ID>.iron 1234
How it works #
DNS resolution #
When you access <endpoint_id>.iron:
- A DNS query is sent to iron's resolver (port 5333)
- The EndpointId is parsed from the base32-encoded domain
- An IPv6 address is derived:
fd69:726f::xxxx:xxxx:xxxx:xxxx - The application connects to that IPv6 address
Sending packets #
- The application sends to the IPv6 address
- The OS routes the packet to the TUN interface
- iron looks up the EndpointId from the IPv6 address
- The packet is sent to the peer over the iroh QUIC connection
- NAT traversal happens automatically
Receiving packets #
- The peer sends the packet via iroh
- iron receives it on a QUIC stream
- The source address is verified (anti-spoofing)
- The packet is written to the TUN interface
- The OS routes it to the listening application
IPv6 address derivation #
Each EndpointId (32 bytes) maps to a unique IPv6 address. The last 8 bytes of the EndpointId form the host part of the IPv6 address:
EndpointId: 197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d61
Last 8 bytes: 039b fa8b 3d36 8b3d
IPv6: fd69:726f:0000:0000:039b:fa8b:3d36:8b3d
|--ULA prefix--| |--from EndpointId--|
Troubleshooting #
"ERROR: iron must be run as root" #
TUN device creation requires elevated privileges. Use sudo:
sudo iron serve
"Failed to create TUN device" #
macOS:
- Ensure you have permission to create TUN devices
- Check system integrity protection settings
Linux:
- Verify the TUN kernel module is loaded:
lsmod | grep tun - Load it if needed:
sudo modprobe tun
DNS not resolving #
-
Verify iron configured DNS:
- macOS: check that
/etc/resolver/ironexists - Linux: check that
/etc/systemd/resolved.conf.d/iron.confexists
- macOS: check that
-
Test DNS directly:
iron resolve <node-id>.iron # or with dig: dig @127.0.0.1 -p 5333 <node-id>.iron AAAA -
Make sure you are using the base32 Node ID (52 chars), not the hex one (64 chars).
-
For advanced configuration, see DNS Setup Guide
"I can't ping myself" / "Loopback detected" #
This is expected. iron is a P2P network; you cannot connect to yourself. Self-ping would require protocol-specific packet rewriting (ICMP echo reply, TCP handshake, etc.), which is unnecessary complexity for a feature that does not test real P2P connectivity.
Use two separate nodes for testing. See Testing Limitations for details.
No connection to peer #
- Verify you are using the correct EndpointId
- Check the logs for connection attempts with
--log-level debug - Ensure firewalls allow UDP (QUIC runs over UDP)
- Check whether iroh can reach its relay servers
Performance #
- Check whether a direct connection was established instead of a relay
- Verify the MTU (default 1420)
- Monitor with
--log-level trace(very verbose)
Log levels #
error: only critical failureswarn: recoverable issues (failed sends, unknown destinations)info: high-level events (startup, connections, shutdown)debug: packet flow, DNS queries, mappingstrace: very detailed (stream operations, individual packets)
Per-module filtering:
RUST_LOG=iron::dns=debug,iron::tun=trace,iron=info sudo iron serve
Development #
A development shell with all build dependencies is provided via:
nix develop
Running tests #
nix flake check # includes linting and CVE checks
cargo test # unit and integration tests only
Building documentation #
cargo doc --open --no-deps
Technical details #
Components #
keys.rs: persistent identity storage and generationmapping.rs: bidirectional EndpointId to IPv6 mappingdns.rs: hickory-server based resolver for.irondomainsdns_config.rs: auto-configuration for system DNStun.rs: virtual network device for packet interceptionprotocol.rs: iroh QUIC transport with connection poolingnode.rs: component lifecycle management
Specifications #
- IPv6 ULA prefix:
fd69:726f::/32 - IPv6 only: the network operates exclusively over IPv6
- MTU: 1420 bytes (accounts for QUIC overhead)
- ALPN:
iron/packet/0 - DNS encoding: base32 (no padding), 52 characters
- Key storage:
~/.config/iron/secret.key(0600 permissions) - Platform: macOS (utun), Linux (iron0)
Security #
- Encryption: all traffic is encrypted via iroh's QUIC (TLS 1.3)
- Authentication: public key cryptography (EndpointId = PublicKey)
- Identity persistence: cryptographic keys stored with 0600 permissions
- Source verification: prevents IP spoofing between peers
- NAT traversal: hole punching with relay fallback
License #
MIT OR Apache-2.0
Contributing #
Contributions welcome. Follow the coding guidelines in AGENTS.md.