Tenzro
Operability

NAT traversal.

Tenzro nodes form a mesh across home wifi, mobile, residential ISPs, and corporate NATs without operator action. The stack composes Identify observed-address tally, AutoNAT v2, Circuit-Relay v2, and DCUtR hole punching.
STATUS
Testnet
CRATE
tenzro-network
TRANSPORTS
TCP + QUIC (port 9000)
DEFAULT
Permissionless
01

Universal listen-addr default

tenzro-node binds both /ip4/0.0.0.0/tcp/9000 and /ip4/0.0.0.0/udp/9000/quic-v1 by default when --listen-addr is omitted. This is the universal transport set: any device — cloud VM, residential wifi, mobile tether, Raspberry Pi, ESP32 — can reach the node via whichever transport NAT permits.

QUIC's structural port-reuse gives Identify observed_addr a stable listening UDP port that AutoNAT v2 can probe back, which is what makes hole punching reliable.

02

Four behaviours composed

The NAT stack is built into TenzroBehaviour via libp2p's SwarmBuilder::with_relay_client:

  • Identify — peers exchange listen addresses + observed-address feedback.
  • AutoNAT v2 — dial-back probes confirm public reachability.
  • Circuit-Relay v2 — public validators serve as relay hops for joiners.
  • DCUtR — direct connection upgrade through relay, i.e. hole punching.

Validators with a confirmed public address run the server halves (relay::Behaviour, autonat::v2::server::Behaviour). Joiner-class roles (LightClient, ModelProvider, TeeProvider) run the client halves. All five sub-behaviours are wrapped in Toggle<> so disabled halves are no-ops.

03

Config knobs

[network]
enable_relay = true              # server-side; default-on for Validator
enable_hole_punching = true      # client-side; default-on for all roles
04

Permissionless observed_addr

We never require --external-p2p-addr. The node uses Identify observed-address tally (with quorum N≥3) to learn its public address, AutoNAT v2 to confirm it, and falls back to Circuit-Relay v2 + DCUtR when direct reachability fails. This matches libp2p-class permissionless discovery.

05

Runtime-adaptive DHT + relay

The stack promotes itself as reachability improves rather than reading a fixed role from config. A node starts Kademlia in client mode and promotes to server only once its reachability tier holds at direct — so a node advertises itself as a routable DHT server after it has confirmed it is dialable, and a node behind a NAT never pollutes the routing table with an address that cannot be reached.

A joiner that learns a peer is relay-capable during the Identify handshake books a Circuit-Relay v2 reservation on it right away, so a relayed /p2p-circuit address exists before the first DCUtR hole-punch attempt — the fallback path is ready ahead of the direct path failing.

06

Bootstrap fallback

On testnet, bootstrap peers are advertised in both /ip4/.../tcp/9000/p2p/... and /ip4/.../udp/9000/quic-v1/p2p/... forms; the dialer picks whichever its NAT permits. --boot-nodes accepts a comma-separated list. The built-in testnet set additionally carries both DNS and raw fleet-IP multiaddrs for the same peers, so a node whose resolver cannot answer the DNS names still reaches the fleet over the pinned IP addresses — a first dial does not depend on DNS.

07

Cloud firewalls are operator runbook

Cloud NAT/security-group/network-policy rules are not part of the protocol. Open TCP/9000 and UDP/9000 in whatever firewall your host provides (cloud security group, VM firewall, K8s NetworkPolicy). Some container-optimized host images default their local iptables to DROP for everything except lo, icmp, tcp:22, and ESTABLISHED — on those, also append an ACCEPT rule for TCP/UDP 9000 via cloud-init.

Related
← All docs