Skip to content

Relay and AutoNAT Infrastructure

lattice infrastructure run operates a combined Circuit Relay v2 and AutoNAT v2 service. It improves connectivity for online Lattice clients but stores no vault data, membership data, reservations, circuits, or probe results.

The service intentionally does not provide rendezvous, mailbox, object storage, DHT, or gossipsub. Deploy those as separate services when a product mode calls for them. The optionally allowlisted rendezvous service is documented in docs/services/rendezvous.md; mailbox and object storage also retain their own process, identity, state, config, and service account.

Terminal window
lattice infrastructure init --host relay.example.net --print-firewall

init writes a validated config to the default service path, creates the stable identity under lock, and prints the client [network] entry. Use --external-ip for a raw address and --out/--state-dir to place the config and identity explicitly. The installer and containers wrap this; see Self-Hosting the Edge Services.

Start from packaging/lattice-infrastructure.toml. Replace every relay.example.net address with the public DNS name or IP address that routes to this host, then validate the file:

Terminal window
lattice infrastructure config-check --config ./lattice-infrastructure.toml
lattice infrastructure peer-id

Clients configure the printed Peer ID together with one advertised address:

[network]
relay_nodes = ["/dns4/relay.example.net/tcp/4001/p2p/12D3Koo..."]

The default peer-id command uses the current user’s infrastructure state directory. For a system service, query the explicit system identity path after the service has created it, as shown below.

The production config requires nonzero TCP and QUIC listeners and at least one matching routable external address. Unknown fields, duplicate addresses, unbounded values, and per-peer limits above global limits are rejected.

The default policy permits 128 reservations, 64 active circuits, 256 MiB and 10 minutes per circuit, 64 pending AutoNAT probes, 8 concurrent probes per source IP, 16 probe starts per source IP per 30 seconds, 512 established connections, and at most 8 established connections per peer. The packaged file is the authoritative list of defaults and can be tightened for smaller deployments.

The stable libp2p identity key is the only durable service state. Clients pin its Peer ID, so deleting the key changes the service identity and requires updating every client configuration. Reservations, circuits, probes, and metrics reset on restart.

Without systemd, paths follow the platform’s per-user config, data, and runtime directories under lattice-infrastructure. --config overrides only the config file path. The state and lock paths remain in the dedicated infrastructure data directory.

The sample listens on TCP 4001 and UDP 4001 for QUIC. Expose both through the host firewall and any upstream NAT. Lattice does not alter firewall rules. The metrics listener defaults to 127.0.0.1:9474 and should remain private.

Endpoints:

  • /healthz: the metrics HTTP task is alive.
  • /readyz: both libp2p listeners reached readiness.
  • /metrics: OpenMetrics relay, AutoNAT, connection, swarm, and bandwidth data.

Metrics do not use Peer IDs, IP addresses, multiaddresses, vault IDs, or rendezvous namespaces as labels.

Install the release binary, sample config, service user declaration, and unit:

Terminal window
sudo install -Dm0755 target/release/lattice /usr/bin/lattice
sudo install -Dm0644 packaging/systemd/lattice.sysusers.conf /usr/lib/sysusers.d/lattice.conf
sudo systemd-sysusers /usr/lib/sysusers.d/lattice.conf
sudo install -Dm0644 packaging/systemd/lattice-infrastructure.service /etc/systemd/system/lattice-infrastructure.service
sudo install -Dm0644 packaging/lattice-infrastructure.toml /etc/lattice-infrastructure/config.toml
sudo systemctl daemon-reload
sudo systemctl enable --now lattice-infrastructure.service

The infrastructure config contains public listener addresses and bounded policy, not secrets. It is root-owned and world-readable because systemd keeps ConfigurationDirectory root-owned. The public service runs as the dedicated lattice-infrastructure account and cannot read the password-vault daemon’s private /var/lib/lattice state.

Verify startup and scrape locally:

Terminal window
systemctl status lattice-infrastructure.service
journalctl -u lattice-infrastructure.service -f
sudo -u lattice-infrastructure /usr/bin/lattice infrastructure peer-id \
--identity /var/lib/lattice-infrastructure/infrastructure-identity.key
curl --fail http://127.0.0.1:9474/readyz
curl --fail http://127.0.0.1:9474/metrics

The unit uses systemd readiness, watchdog, restart-on-failure, dedicated configuration/state/runtime directories, a restrictive umask, and an empty capability bounding set. A listener or runtime failure after readiness exits the process so systemd can restart it.

Install the binary, user unit, and config without root access:

Terminal window
install -Dm0755 target/release/lattice ~/.local/bin/lattice
install -Dm0644 packaging/systemd/user/lattice-infrastructure.service ~/.config/systemd/user/lattice-infrastructure.service
install -Dm0600 packaging/lattice-infrastructure.toml ~/.config/lattice-infrastructure/config.toml
systemctl --user daemon-reload
systemctl --user enable --now lattice-infrastructure.service

Enable user lingering separately when the service must survive logout:

Terminal window
loginctl enable-linger "$USER"

Other Unix supervisors should run the process in the foreground:

Terminal window
lattice infrastructure run --config /path/to/lattice-infrastructure.toml

Send SIGTERM or Ctrl-C for a bounded graceful shutdown. Do not wrap this command in classic double-fork daemonization when a supervisor owns the process.

The release-only Linux topology gate uses isolated network namespaces and does not modify the host firewall ruleset:

Terminal window
bash xtask/cross-nat-e2e.sh

It requires ip, nft, sysctl, and unshare. Compilation runs as the invoking user. By default the already-built test runs as root only inside a new unprivileged user, mount, and network namespace. Its nested namespaces and nftables rules therefore cannot modify the host network or firewall. If the kernel disables unprivileged user namespaces, the wrapper prints an explicit sudo command for operator review and exits; it never invokes sudo itself.

The test creates a temporary public segment, two independent stateful NAT routers, two private client networks, and a namespace running the real infrastructure command. It uses deterministic source-port translation without static destination NAT, so an inbound direct connection is available only after the clients perform the DCUtR simultaneous-open exchange. Two complete Lattice daemons then sync an attachment over the direct path, restart, converge concurrent edits to one conflict, propagate a signed resolution, lose all node-to-node reachability, and propagate a deletion over the retained relay. All nested namespaces and nftables state disappear with the disposable user namespace. A directly root-run fallback also removes its exact per-run namespace prefix on normal exit, failure, SIGINT, or SIGTERM.