Peer-to-peer network interface based on iroh
iron creates a virtual IPv6 network over peer-to-peer QUIC connections, enabling direct connectivity between endpoints using .iron DNS names.
- 🌐 Virtual Overlay Network: Each peer is addressed by their public key
- 🔒 Encrypted P2P: All traffic encrypted via iroh's QUIC protocol
- 🏷️ DNS Resolution:
.irondomain names resolve to peer IPv6 addresses - 🔌 TUN Interface: Standard network interface, works with any application
- 🚀 NAT Traversal: Automatic hole punching with relay fallback
- 📡 Direct Connections: Establishes direct peer connections when possible
┌─────────────┐
│ Application │ (browser, curl, any network app)
└──────┬──────┘
│ Standard IPv6 socket
┌──────▼────────────────────────────────┐
│ Operating System (IPv6 stack) │
└──────┬────────────────────────────────┘
│ fd69:726f::/32 routes to TUN
┌──────▼────────────────────────────────┐
│ iron (TUN Interface) │
│ • DNS: .iron → IPv6 │
│ • Registry: IPv6 ↔ EndpointId │
│ • Protocol: Packets over QUIC │
└──────┬────────────────────────────────┘
│ Encrypted QUIC (iroh)
▼
Internet / LAN
▼
┌────────────────────────────────────────┐
│ Peer iron node │
└────────────────────────────────────────┘
Prerequisites:
- nix
# build for your system
nix build .#default
# build for an explicit target
nix build .#packages.x86_64-linux.defaultThe binary will be at result/bin/iron.
nix flake checkBasic usage (requires root):
sudo iron serveOn first run, iron will automatically configure DNS for .iron domains (macOS and Linux with systemd-resolved only). This configuration only affects .iron domains - all other DNS resolution remains unchanged.
With custom log level:
sudo iron serve --log-level debugWith custom DNS port:
sudo iron serve --dns-port 5353When iron starts, you'll see:
Node ID (hex): 74df87cccf7e0fead1370fc39f65be3de44f5069f5db87f3b08435ccdaf3b5b9
Node ID (base32): ot36ptgm67yp5vjt6b6dtz2l4ppejtggt5w3y64lqqrvztpl2wnq
DNS name: ot36ptgm67yp5vjt6b6dtz2l4ppejtggt5w3y64lqqrvztpl2wnq.iron
The base32 Node ID is what you use for DNS queries.
Your Node ID and .iron domain name are persistent across restarts.
Iron automatically generates and saves a secret key on first run:
- Key location:
~/.config/iron/secret.key - Permissions: 0600 (owner read/write only)
- Important: Keep this key secure - it's your node's identity!
This means:
- ✅ Your
.irondomain name stays the same across restarts - ✅ You can share your domain name with others reliably
- ✅ Peers can always find you at the same address
To reset your identity (get a new Node ID and domain):
iron key reset
# or manually:
rm ~/.config/iron/secret.key
sudo iron serveIron automatically configures DNS on first run for 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
Manual DNS commands:
# Remove DNS configuration (cleanup if iron crashed)
sudo iron --cleanup-dnsWhat this does:
- Routes only
.irondomains to iron's DNS server (127.0.0.1:5333) - All other domains (google.com, github.com, etc.) use your normal DNS
- Works alongside Tailscale, VPNs, and other DNS systems
For advanced configuration or troubleshooting, see DNS Setup Guide.
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
RUST_LOG: Control log levels per module (overrides--log-level)RUST_LOG=iron::protocol=trace,iron=info sudo iron serve
- Two machines with iron installed
- Both machines can reach each other (same network, or internet with NAT traversal)
Iron uses IPv6 exclusively. When connecting to .iron domains, you must use IPv6:
nc -6 <node>.iron 1234
curl -6 http://<node>.iron:8080
ping6 <node>.ironMost applications will automatically fall back to IPv6, but using the -6 flag explicitly ensures immediate connection.
Machine A:
sudo iron serve
# DNS configured automatically on first run
# Note the base32 Node ID displayedMachine B:
sudo iron serve
# DNS configured automatically on first run
# Note the base32 Node ID displayedTest DNS resolution:
iron resolve <MACHINE_A_BASE32_ID>.iron
# or with dig:
dig <MACHINE_A_BASE32_ID>.iron AAAAPing Machine A:
ping6 <MACHINE_A_BASE32_ID>.ironRun a service on Machine A and access it from Machine B:
# On Machine A - start HTTP server (bind to IPv6)
python3 -m http.server 8080 --bind ::
# On Machine B - access the server (use -6 flag)
curl -6 http://[<MACHINE_A_BASE32_ID>.iron]:8080/
# Or with netcat
# Machine A (server, listen on IPv6):
nc -6 -l 1234
# Machine B (client, connect to IPv6):
nc -6 <MACHINE_A_BASE32_ID>.iron 1234If you see Machine A's directory listing, it works! 🎉
See DNS Setup Guide for DNS issues.
For P2P connection issues:
- Check both nodes show "TUN interface running" in logs
- Verify iroh endpoint is initialized on both
- Check firewalls allow UDP (QUIC uses UDP)
- Watch logs with
--log-level debugto see connection attempts
When you access <endpoint_id>.iron:
- DNS query sent to iron's resolver (port 5333)
- EndpointId parsed from base32-encoded domain
- IPv6 address derived:
fd69:726f::xxxx:xxxx:xxxx:xxxx - Application connects to IPv6 address
- Application sends to IPv6 address
- OS routes to TUN interface (iron)
- iron looks up EndpointId from IPv6
- Packet sent to peer via iroh QUIC connection
- NAT traversal handled automatically
- Peer sends packet via iroh
- iron receives on QUIC stream
- Source address verified (anti-spoofing)
- Packet written to TUN interface
- OS routes to listening application
Each EndpointId (32 bytes) maps to a unique IPv6:
EndpointId: 197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d61
└──────────┘
Last 8 bytes
▼
IPv6: fd69:726f:0000:0000:039b:fa8b:3d36:8b3d
└─ULA prefix─┘ └─from EndpointId─┘
TUN device creation requires elevated privileges. Use sudo:
sudo iron servemacOS:
- Ensure you have permission to create TUN devices
- Check system integrity protection settings
Linux:
- Verify TUN kernel module is loaded:
lsmod | grep tun - Load if needed:
sudo modprobe tun
Iron automatically configures DNS on first run for macOS and Linux with systemd-resolved.
If DNS is not working:
-
Verify iron configured DNS:
- macOS: Check if
/etc/resolver/ironexists - Linux: Check if
/etc/systemd/resolved.conf.d/iron.confexists
- macOS: Check if
-
Manually setup DNS if needed:
# DNS is auto-configured on first run of 'iron serve' # If cleanup is needed: sudo iron --cleanup-dns
-
Test DNS directly:
iron resolve <node-id>.iron # or with dig: dig @127.0.0.1 -p 5333 <node-id>.iron AAAA
-
Verify you're using the base32 Node ID (52 chars), not hex (64 chars)
-
For advanced configuration, see DNS Setup Guide
This is expected behavior. iron is a P2P network - you cannot connect to yourself.
Self-ping requires protocol-specific packet rewriting (ICMP echo reply, TCP handshake, etc.) which would add unnecessary complexity for a feature that doesn't test real P2P connectivity.
Solution: Use two separate machines/nodes for testing. See Testing Limitations for details.
- Verify Node ID: Ensure you're using the correct EndpointId
- Check logs: Look for connection attempts with
--log-level debugsudo iron serve --log-level debug
- Firewall: Ensure UDP traffic is allowed (iroh uses QUIC over UDP)
- Relay server: Check if iroh can reach relay servers
-
Direct connection: Check if direct connection established (vs relay)
RUST_LOG=iron::protocol=debug sudo iron serve
-
MTU: Verify MTU is set correctly (default 1420)
-
Network congestion: Monitor with
--log-level trace(very verbose)
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)
Example per-module filtering:
RUST_LOG=iron::dns=debug,iron::tun=trace,iron=info sudo iron serveA development shell with all build dependencies is provided via
nix develop# via nix:
nix flake check # this includes further checks like linting and CVE checks
# via cargo (unit and integration tests only):
cargo testcargo doc --open --no-deps- Key Management (
keys.rs): Persistent identity storage and generation - Registry (
mapping.rs): Bidirectional EndpointId ↔ IPv6 mapping - DNS Resolver (
dns.rs): Hickory-server based resolver for.irondomains - DNS Config (
dns_config.rs): Auto-configuration for system DNS - TUN Interface (
tun.rs): Virtual network device for packet interception - Protocol Handler (
protocol.rs): Iroh QUIC transport with connection pooling - Orchestrator (
node.rs): Component lifecycle management
- IPv6 ULA Prefix:
fd69:726f::/32(iron-branded) - IPv6 Only: Network operates exclusively over IPv6
- MTU: 1420 bytes (accounts for QUIC overhead)
- ALPN:
iron/packet/0(protocol identifier) - DNS Encoding: Base32 (no padding), 52 characters
- Key Storage:
~/.config/iron/secret.key(0600 permissions) - Platform: macOS (utun), Linux (iron0)
- Encryption: All traffic encrypted via iroh's QUIC (TLS 1.3)
- Authentication: Public key cryptography (EndpointId = PublicKey)
- Identity Persistence: Cryptographic keys stored securely (0600 permissions)
- Source Verification: Prevents IP spoofing between peers
- NAT Traversal: Secure hole punching with relay fallback
MIT OR Apache-2.0
Contributions welcome! Please follow the coding guidelines in AGENTS.md.
Built on iroh - a Rust library for peer-to-peer networking.