libp2p Host

The libp2p networking host

The XE host is a standard libp2p node configured for TCP transport with persistent identity, connection management, and multiple discovery mechanisms.

Host Creation

func NewHost(
    ctx context.Context,
    port int,
    dataDir string,
    version string,
    enableRelay bool,
    gater connmgr.ConnectionGater,
    maxConnsPerIP int,
) (host.Host, error)
ParameterDescription
portTCP listen port (default: 9000)
dataDirDirectory for persistent data. If non-empty, the identity key is persisted here.
versionNode version string, used in the User-Agent header
enableRelayEnable relay and hole-punching support
gaterOptional ConnectionGater — used by the netcheck handshake to reject banned peers before stream negotiation
maxConnsPerIPInbound connection cap per source IP (defaults to 8 when zero or negative)

The host listens on all interfaces:

/ip4/0.0.0.0/tcp/{port}

Connection Manager

The connection manager keeps the number of active peer connections within bounds:

SettingValue
Low watermark100 peers
High watermark400 peers
Grace period1 minute

When the number of connections exceeds the high watermark, the connection manager begins pruning connections down toward the low watermark. New connections are not pruned within the grace period.

cm, err := connmgr.NewConnManager(100, 400, connmgr.WithGracePeriod(time.Minute))

Resource Manager

Separately from the connection manager, a libp2p resource manager caps connections and connection rate per source network, so one host cannot occupy the whole peer slate:

LimitValue
IPv4 per /32 (single address)maxConnsPerIP connections
IPv6 per /56maxConnsPerIP connections
IPv6 per /484 × maxConnsPerIP connections
IPv4 loopback (127.0.0.0/8)Unlimited connections
Loopback (127.0.0.0/8, ::1/128)Exempt from connection rate limiting

Connection rate limiting allows roughly 1 new connection per second per IPv4 /32, with a burst of 2 × maxConnsPerIP and a one-minute grace period.

[!TIP] Multiple nodes behind one address The per-IP cap is the reason several nodes sharing an egress address (or several nodes on one host) can fail to peer. Raise it with --max-conns-per-ip.

Persistent Identity

When dataDir is provided, the host generates an Ed25519 keypair on first run and persists it to {dataDir}/host.key. On subsequent starts, the key is loaded from disk, giving the node a stable peer ID across restarts.

{dataDir}/host.key    # Ed25519 private key (libp2p marshaled format, mode 0600)

[!WARNING] Key Protection The host key file is written with mode 0600 (owner read/write only). The data directory is created with mode 0700. Losing this key means the node gets a new peer ID on next start.

If dataDir is empty (e.g., in tests), a new ephemeral identity is generated each time.

User-Agent

The host identifies itself with:

xe/{version}

This appears in the libp2p identify protocol and can be used for version-aware peer selection.

Relay Support

When enableRelay is true, the host enables three capabilities:

FeaturePurpose
EnableRelay()Accept relayed connections through other peers
EnableRelayService()Act as a relay for other peers
EnableHolePunching()NAT traversal via hole-punching

This allows nodes behind NAT or firewalls to participate in the network by routing connections through relay-capable peers.

[!NOTE] Note Relay is optional and disabled by default. Enable it for nodes that need to be reachable behind NAT without port forwarding.

Discovery

mDNS (Local Network)

mDNS discovery is started automatically via SetupDiscovery(). It uses the service tag xe-poc and connects to any discovered peer on the local network.

func SetupDiscovery(ctx context.Context, h host.Host) error

When a peer is found via mDNS, the node connects automatically. Peers with the same peer ID (self) are ignored.

Bootstrap Peers

Bootstrap peers are specified via the --dial flag as comma-separated multiaddrs:

xe node --dial "/ip4/1.2.3.4/tcp/9000/p2p/12D3KooW...,/ip4/5.6.7.8/tcp/9000/p2p/12D3KooW..."

The node dials each address on startup with a 10-second timeout per peer. A watchdog goroutine then re-dials any bootstrap peer that is not currently connected every 30 seconds, for the lifetime of the node — it is a permanent reconnection loop, not a one-off retry until first success.

Startup

  ├─ Dial peer A ─── success
  ├─ Dial peer B ─── fail

  └─ Watchdog (every 30s, forever)
       ├─ A connected? yes, skip
       └─ B connected? no, dial again

[!TIP] Multiaddr Format A full multiaddr includes the transport and peer ID:

/ip4/\{host\}/tcp/\{port\}/p2p/\{peerID\}

Example: /ip4/192.168.1.10/tcp/9000/p2p/12D3KooWRnBKUEkAgYBMNR...

Kademlia DHT

The DHT provides network-wide peer discovery beyond the local network and bootstrap list. See DHT below.

DHT Setup

func SetupDHT(ctx context.Context, h host.Host) (*dht.IpfsDHT, error)

The DHT is configured with:

Setting

Value

Mode

Server (participates in routing)

Protocol prefix

/xe

The /xe protocol prefix ensures XE nodes form their own DHT, isolated from the public IPFS DHT. Server mode means the node stores and serves routing records, not just queries them.

After creation, Bootstrap() is called to populate the routing table from the existing peerstore.

The DHT is used by the Messenger to resolve peer IDs to addresses via FindPeer() when a target peer is not already known.

Peer Dialing

The DialPeer function connects to a peer given a multiaddr string:

func DialPeer(ctx context.Context, h host.Host, addr string) error

The ParsePeerAddr helper extracts peer address info from a multiaddr:

func ParsePeerAddr(addr string) (*peer.AddrInfo, error)

Configuration Summary

These are flags of xe node:

FlagDefaultDescription
--port9000TCP listen port for libp2p
--data./dataData directory (identity key stored here)
--dial(none)Comma-separated bootstrap multiaddrs
--max-conns-per-ip8Max inbound connections per source IP; raise for multi-node-per-host setups

Lifecycle

  1. Create host -- NewHost() sets up TCP transport, connection manager, resource manager, and identity
  2. Start mDNS -- SetupDiscovery() enables local network discovery
  3. Bootstrap DHT -- SetupDHT() creates and bootstraps the Kademlia DHT
  4. Dial bootstrap peers -- the node dials each --dial address and starts the reconnection watchdog
  5. Register protocols -- netcheck, sync, messaging, tunnel and gossip handlers are registered on the host
  6. Shutdown -- in-flight sync goroutines are drained, then the host is closed and all streams and connections are terminated