Early-stage software. Shurli is experimental and built with AI assistance. It will have bugs. Not recommended for production or safety-critical use. Read the disclaimer.
Relay & NAT Traversal

Relay & NAT Traversal

What is Circuit Relay v2?

Circuit Relay v2 is libp2p’s protocol for routing traffic through an intermediary relay node when peers can’t connect directly (NAT, CGNAT, firewalls). It replaced v1 in 2021.

How it works

Circuit Relay v2 sequence: Peer A reserves slot on Relay, Peer B connects through Relay, streams bridged bidirectionally

The protocol splits into two sub-protocols:

  • Hop (/libp2p/circuit/relay/0.2.0/hop) - client <-> relay (reserve, connect)
  • Stop (/libp2p/circuit/relay/0.2.0/stop) - relay <-> target peer (deliver connection)

Why v1 was replaced

v1 had no resource reservation - relays got overloaded with no way to limit usage. v2 introduced explicit reservations with configurable limits (duration, data caps, bandwidth), making it cheap to run “an army of relays for extreme horizontal scaling.” Relays can reject connections with status codes like RESOURCE_LIMIT_EXCEEDED or RESERVATION_REFUSED.

Known limitations

LimitationDetail
Setup latency5-15 seconds (reservation + handshake + DHT lookup)
No persistent connectionsConnections have hard TTL; each dial requires new reservation
Reservation overheadEvery peer must explicitly reserve before receiving relayed connections
Throughput asymmetryLimited by relay’s aggregate bandwidth, not peer bandwidth
Default public limits128 KB data cap, 2-minute duration (configurable on self-hosted)

Is there a Circuit Relay v3?

No. No v3 exists or is planned. libp2p’s strategy is to reduce dependence on relays through better hole punching (DCUtR improvements, AutoNAT v2), not to replace the relay protocol itself.

The improvements come from upgrading everything around the relay - see the next FAQ entry.

Source: Circuit Relay v2 Specification


Why Circuit Relay v2 is the right choice for Shurli

  1. Symmetric NAT - Hole-punch success rates are irrelevant (all protocols fail against symmetric NAT, all fall back to relay)
  2. Self-hosted relay - You control limits, so the 128KB/2min public relay caps don’t apply
  3. No vendor dependency - Matches the self-sovereign philosophy
  4. Native to libp2p - No additional dependencies in the Go codebase
  5. Battle-tested - Millions of IPFS nodes use it daily
  6. Configurable - When you run your own relay, you set your own resource limits

The only area where alternatives genuinely outperform Circuit Relay v2:

  • Connection speed: Iroh (1-3s) and Tailscale (<1s) are faster than Circuit Relay v2 (5-15s) due to persistent relay connections
  • Hole-punch success for regular NAT: Iroh (~90%) and Tailscale (~92%) beat DCUtR (~70%) - but this doesn’t matter for symmetric NAT

For Starlink CGNAT with a self-hosted relay, Circuit Relay v2 is functionally equivalent to Iroh and Tailscale in relay quality.


Can I use public relay servers instead of my own?

Yes, public IPFS relays exist - thousands of them. Since Circuit Relay v2, every public IPFS node runs a relay by default. libp2p’s AutoRelay can discover and use them automatically.

But there’s a catch. Public relays have strict resource limits:

ConstraintPublic IPFS relay (v2 defaults)Your self-hosted relay
Duration2 minutes per connectionUnlimited (you configure)
Data cap128 KB per relay sessionUnlimited (you configure)
Bandwidth~1 Kbps (intentionally throttled)Your VPS bandwidth
PurposeCoordinate hole-punch, then disconnectFull traffic relay
UptimeRandom node, could disappearYour VPS, 99.9% uptime
SSH sessionDrops after 2 min or 128 KBWorks indefinitely

Public relays are designed as a trampoline - they help two peers find each other, attempt a hole-punch, and then drop off. They were never meant for sustained traffic like SSH sessions, XRDP, or LLM inference.


How do public relays differ from self-hosted relays?

Conceptually yes - both are “someone else’s relay you use for free.” But the implementation differs significantly:

IPFS public relaysIroh’s relays
OperatorThousands of random IPFS peersn0 team (Iroh’s company)
ArchitectureDecentralized - any public node can be a relayCentralized - Iroh runs them
Data limit128 KB per sessionNo hard cap
Time limit2 minutesPersistent connection
PurposeTrampoline for hole-punch coordinationActual traffic fallback (like Tailscale’s DERP)
ReliabilityRandom node could vanish anytimeOperated infrastructure
Protocollibp2p Circuit Relay v2Custom protocol (UDP-over-HTTP)

Iroh’s relays are essentially Tailscale’s DERP servers for the Iroh ecosystem - meant to carry real traffic when hole-punching fails. IPFS’s public relays are just for the initial handshake.


Why does Shurli use its own relay?

For Starlink/CGNAT (symmetric NAT) users, hole-punching always fails. Traffic must stay on the relay for the entire session. This means:

  1. Public IPFS relays - Connection drops after 2 minutes or 128 KB. Unusable.
  2. Iroh’s relays - Would work, but you depend on Iroh’s infrastructure and lose sovereignty.
  3. Tailscale’s DERP - Would work, but requires a Tailscale account and their control plane.
  4. Your own relay - Works indefinitely, unlimited data, you control everything.

Shurli’s self-hosted relay ($5/month VPS) is the only option that provides both unlimited traffic and full sovereignty.


Why does symmetric NAT break hole-punching?

Nebula uses lighthouse nodes (like STUN servers) to help peers discover each other’s public IP:port. Then it attempts direct hole-punching.

With symmetric NAT (CGNAT), the mapped port changes for every destination:

Why symmetric NAT breaks hole-punching: different mapped port per destination causes port mismatch

The port the lighthouse tells Peer B to use was allocated for the lighthouse connection, not Peer B. The hole-punch fails.

Nebula has no relay fallback. If hole-punching fails, the connection simply doesn’t work. Tailscale falls back to DERP. Shurli falls back to circuit relay. Nebula has nothing.


Is it safe for my home node to act as a relay?

This is the most important security question for Shurli’s future. The short answer: yes, with Circuit Relay v2’s built-in protections, a home node can safely relay traffic for authorized peers without increasing its attack surface.

Here’s the full breakdown - because “trust us” is not a security argument.

Home relay security layers: unauthenticated peers blocked at ConnectionGater (Layer 1), authorized peers pass through resource limits (Layer 2) and E2E encryption (Layer 3)

What “acting as a relay” actually means

When your home node enables relay service, it does one thing: accept a reservation from a peer (identified by their peer ID), then forward encrypted bytes between that peer and whoever connects to them through you. Your node never sees the content - it’s end-to-end encrypted with Noise protocol. Your node never authenticates the remote peer’s connections - that’s the target peer’s job.

Think of it as holding two tin cans connected by a string. You’re the string. You can feel vibrations but not hear words.

Resource limits are enforced by the protocol, not by trust

Circuit Relay v2 was specifically redesigned (from v1) because v1 relays had no resource controls and got overwhelmed. v2 has per-reservation resource budgets baked into the protocol:

ProtectionWhat it preventsDefault
Max reservationsTotal peers using your relayConfigurable (128 default)
Max circuits per peerOne peer consuming all relay capacity16
Max reservations per IPIP address hoarding reservations8
Max reservations per ASNOne ISP’s network flooding your relay32
Reservation TTLStale reservations consuming resources1 hour
Session durationIndefinite relay connectionsConfigurable (10 min default)
Session data limitBandwidth theftConfigurable (64 MB default)

When any limit is hit, the relay returns RESOURCE_LIMIT_EXCEEDED and the connection is refused. No crash, no OOM, no degradation. The peer simply can’t connect.

Only relay for people you chose

Shurli’s relay service is restricted to peers in your authorized_keys file by default. The ConnectionGater blocks unauthenticated connections before any relay protocol runs - an anonymous internet scanner hitting your relay’s port gets rejected at the connection layer, and the reservation request never reaches the relay service logic.

Batch I-f shipped “every-peer-is-a-relay” - any peer with a detected global IP auto-enables circuit relay v2 with conservative resource limits (4 reservations, 16 circuits, 128KB per direction, 10-minute sessions). The ConnectionGater ensures only authorized peers can make reservations or create circuits through this relay.

Relay management uses the relay admin socket (Unix domain socket with cookie auth). Admins generate pairing codes via shurli relay pair, which talks to the running relay process directly. No SSH needed for peer onboarding, no manual peer ID exchange.

The attack surface increase from enabling relay is zero for unauthenticated peers - they are rejected at the same layer they would be rejected at today.

“But my IP address becomes visible”

This concern has two parts:

Part 1: Visible to peers you explicitly authorized. Yes - when a peer connects directly (via hole punch or IPv6), they see your IP. But you already authorized them in authorized_keys. They already know where you are conceptually. And your IP is visible in any direct TCP/QUIC connection regardless of whether relay service is enabled.

Part 2: Visible on the public DHT. No - Shurli uses a private Kademlia DHT (/shurli/kad/1.0.0), completely isolated from the public IPFS Amino DHT. Your node only talks to other Shurli nodes for discovery, not the broader IPFS network. Your addresses are only discoverable by peers running Shurli software. Additional mitigations:

  • Relay-only advertising: Advertise only your relay VPS address; your home IP only visible after authentication
  • IPv6 privacy extensions: Use temporary IPv6 addresses that rotate

The relay VPS model today already exposes its public IP. A home relay with the ConnectionGater is no more exposed than the VPS - and arguably less, since only authorized peers can connect.

What your relay CANNOT be used for

AttackWhy it fails
Traffic sniffingEnd-to-end Noise encryption - relay sees ciphertext only
Connection injectionBoth peers authenticate each other via peer ID (Ed25519)
DDoS amplificationQUIC source address verification; per-IP reservation limits
Resource exhaustionHard limits on reservations, circuits, duration, data
Open relay abuseConnectionGater allowlist - only authorized peers can reserve
Pivot to your LANRelay forwards bytes, doesn’t parse them. No routing to your network.

Why this is different from running an open service

Self-hosters know the pattern: expose a service on a home server, and within hours the port scanners find it. Firewall logs light up with probes from around the world. This happens because the service accepts connections from anyone - every participant on the network can reach it.

A Shurli relay with its ConnectionGater allowlist is fundamentally different:

Typical exposed serviceShurli relay
Who can connectAnyone who finds the portOnly peers in your authorized_keys
What they can doFull protocol interactionForward encrypted bytes (nothing else)
DiscoveryPort scanning, Shodan, service-specific gossipPrivate - only authorized peers know about it
Attack surfaceFull protocol parser (HTTP, SSH, etc.)ConnectionGater rejection (zero protocol parsing for unauthorized)

An open service is an open door with a bouncer inside. A Shurli relay is a door that only opens with the right key - and even then, it only passes sealed envelopes.

Comparison with other relay architectures

FeatureShurliTailscale DERPIPFS public relay
Who can use itExplicit allowlist (authorized_keys)Any Tailscale account holderAnyone
AuthenticationConnectionGater (peer ID)WireGuard keysNone
Resource limitsPer-peer, per-IP, per-ASNNot published128 KB / 2 min
E2E encryptionNoise (Ed25519)WireGuard (Curve25519)Noise
Relay sees contentNoNoNo
Self-hostedYesNo (Tailscale operates)N/A (public nodes)

The path forward

Decentralization path: from single VPS relay to peer relays to DHT discovery to fully distributed where every public peer relays for authorized peers

The ConnectionGater-protected relay model is how Shurli eliminates dependency on the central relay VPS:

  1. Today: One relay VPS + every-peer-is-a-relay (Batch I-f) for peers with public IPs
  2. Near-term: Home nodes with public IPv6 or port-forwarding serve as additional relays -> multiple relays across the network
  3. Medium-term: Peers discover authorized relays via DHT (not a central endpoint) -> no single point of failure
  4. End state: Every publicly-reachable Shurli node relays for its authorized peers -> relay VPS becomes obsolete (not just optional)

This follows the same decentralization path as Bitcoin: hardcoded seeds -> DNS seeds -> peer exchange -> fully self-sustaining network.

Sources:


Can NAT traversal improve without changes to Shurli?

Yes. NAT traversal success depends heavily on what the NAT device does, and router/OS vendors are starting to make NATs friendlier.

FreeBSD PF: Endpoint-Independent Mapping (Sep 2024)

FreeBSD’s packet filter (PF) now has an endpoint-independent NAT option for UDP. This makes the NAT behave as “full cone” - the mapped port stays the same regardless of destination. Full-cone NATs have near-100% hole-punch success because both peers can predict each other’s mapped ports.

Why this matters: OPNsense (a popular firewall/router OS) is FreeBSD-based. If OPNsense adopts this option, a significant number of home and SMB routers get friendlier NAT behavior - and Shurli’s DCUtR success rate improves automatically without any code changes.

What to watch: OPNsense releases, pfSense updates, and any Linux nftables equivalent. If this pattern spreads to consumer routers, the percentage of “hard NAT” cases (endpoint-dependent mapping) shrinks organically.

Source: FreeBSD Status Report - Endpoint-Independent Mapping NAT

IPv6 eliminates NAT entirely

Many ISPs now provide globally routable public IPv6 addresses. IPv6 has no NAT - every device gets a public address. When two peers both have IPv6, they connect directly with zero NAT traversal, zero hole punching, zero relay dependency.

Shurli already supports IPv6 through libp2p’s transport layer. AutoNAT v2 tests IPv4 and IPv6 reachability independently, so a node behind IPv4 CGNAT but with public IPv6 will correctly identify that its IPv6 addresses are directly reachable.

Expected impact: As IPv6 adoption grows (currently ~45% globally, higher on mobile networks), the percentage of connections requiring relay will decrease. For networks where both peers have IPv6, relay is already unnecessary today.


How does CGNAT detection work?

CGNAT (Carrier-Grade NAT) is a second layer of NAT applied by mobile carriers and some ISPs. It makes hole-punching unreliable because the outer NAT drops unsolicited inbound packets regardless of inner NAT type.

Shurli detects CGNAT by checking local interfaces for RFC 6598 addresses (100.64.0.0/10). When found, the reachability grade is capped at D regardless of STUN results. This prevents false optimism: STUN sees only the inner NAT and might report “hole-punchable” when the outer CGNAT will actually block it.

Detection methodWhat it catchesWhat it misses
RFC 6598 (100.64.0.0/10) on local interfaceStandard CGNAT deploymentsMobile carriers using RFC 1918 (172.x.x.x) for CGNAT
STUN external IP differs from local IPAny NAT layerCan’t distinguish CGNAT from regular NAT

The limitation: mobile carriers that assign RFC 1918 addresses (like 172.20.x.x) for CGNAT look identical to regular home networks. There’s no clean way to detect this without active probing against a known endpoint. In testing, a 5G carrier using 172.20.10.x was graded as “hole-punchable” even though most hole-punch attempts failed.

The grade is honest about what it knows and what it doesn’t. When CGNAT is detected, the grade says so. When it can’t be detected, the grade reflects STUN results and the user sees the outcome in their actual connection path (direct vs relayed).


Last Updated: 2026-02-25