Skip to content

Quick start

The fastest way to get a device running is one command:

Terminal window
bnw setup

It creates the device identity if there is none, enables the web gateway, starts the node now and at every login, and opens the web client. Running it again repairs whatever is missing. Arguments after -- go to bnw node start, for example bnw setup -- --relay /dnsaddr/relay.example.net.

  • The login service. bnw service install|uninstall|status manages it on its own. It is a launchd agent on macOS and a systemd user unit on Linux, runs as you, and needs no administrator rights. Each data directory gets its own service, so several nodes can run side by side. The node logs to <data-dir>/logs/node.log and restarts if it crashes. On a Linux machine without a desktop login, run loginctl enable-linger so the node keeps running after you log out.
  • Windows. The login service is not supported yet; run bnw node start --web instead.

The rest of this section does the same steps by hand.

Initialize a device identity and local database:

Terminal window
bnw init
bnw id

By default, state is stored in ~/.bnw. Use --data-dir or BNW_DATA_DIR to keep independent node state elsewhere:

Terminal window
bnw --data-dir ./node-a init
BNW_DATA_DIR=./node-a bnw id

Identity files are created with restrictive permissions and are never silently overwritten. Each physical installation should run bnw init and create its own identity; do not copy .bnw between devices.

Start the node with informational logging:

Terminal window
RUST_LOG=info bnw node start

By default a node listens on random TCP and QUIC ports on IPv4, and on IPv6 when the host supports it. IPv6 is best-effort: a host without it just logs a warning. Many mobile and home networks give devices public IPv6 addresses, where peers can often connect directly without a relay. Link-local IPv6 addresses (fe80::) are not shared as routing hints, because they can’t be dialed from other machines. Explicit --listen addresses replace all defaults, and each of them must bind.

Nodes log their complete listening multiaddresses, including their peer ID. To use a stable address outside mDNS discovery, assign fixed TCP and QUIC ports on the reachable node:

Terminal window
RUST_LOG=info bnw node start \
--listen /ip4/0.0.0.0/tcp/4001 \
--listen /ip4/0.0.0.0/udp/4001/quic-v1

Then pass one of its advertised addresses to another node. The peer remains non-authoritative and the address is remembered locally:

Terminal window
RUST_LOG=info bnw node start \
--bootstrap /ip4/192.0.2.10/tcp/4001/p2p/12D3KooW...

Multiple addresses may be passed by repeating --bootstrap, or as a comma-separated BNW_BOOTSTRAP value.

For a controlled topology, disable LAN discovery and provide only the intended bootstrap links:

Terminal window
bnw node start --no-mdns \
--listen /ip4/0.0.0.0/tcp/4002 \
--bootstrap /ip4/192.0.2.10/tcp/4001/p2p/12D3KooW...

This is primarily useful for repeatable fault testing and static deployments. Normal LAN use should leave mDNS enabled.

By default a node uses BNW’s public relay, /dnsaddr/relay.cellmobs.com, pinned to its peer ID. A fresh install therefore reaches peers on other networks without configuration. The relay also serves as its first peer for discovery, so capabilities published anywhere can be found.

  • --relay replaces the default with the relays given.
  • --no-default-relay (or BNW_NO_DEFAULT_RELAY) uses no relay unless one is given, leaving only direct connections, the local network, and --bootstrap.
  • A node running --relay-server never uses the default.

A node advertises its capabilities and blobs in the DHT again whenever it gains a new relay reservation, and hourly. A node that started offline, or a relay that restarted and lost its in-memory records, is found again within minutes rather than hours.

Nodes on different networks can reach each other through a publicly reachable peer acting as a circuit relay. Any node can act as the relay. It gains no authority by doing so and cannot read or forge relayed traffic, which remains Noise-encrypted end to end. On the public host, assert its reachable address and enable relay service:

Terminal window
RUST_LOG=info bnw node start --relay-server \
--listen /ip4/0.0.0.0/tcp/4001 \
--listen /ip4/0.0.0.0/udp/4001/quic-v1 \
--external-address /ip4/203.0.113.5/tcp/4001 \
--external-address /ip4/203.0.113.5/udp/4001/quic-v1

A relay refuses reservations until it has at least one external address. --external-address sets it immediately; otherwise AutoNAT must first confirm one from connected peers’ dial-back probes.

On each node behind NAT, reserve a relayed address on the relay. Give both its QUIC and TCP addresses:

Terminal window
RUST_LOG=info bnw node start \
--relay /ip4/203.0.113.5/udp/4001/quic-v1/p2p/12D3KooWRelay... \
--relay /ip4/203.0.113.5/tcp/4001/p2p/12D3KooWRelay...

--relay may be repeated or given as a comma-separated BNW_RELAY value. The node dials relays itself and adds them to Kademlia routing, so they don’t also need to be passed as --bootstrap.

A relay can also be given by DNS name with /dnsaddr:

Terminal window
RUST_LOG=info bnw node start --relay /dnsaddr/relay.example.com

The node looks up TXT records at _dnsaddr.relay.example.com, each of the form dnsaddr=<multiaddr ending in /p2p/<peer-id>>, and uses every address found. Nested /dnsaddr records are followed, bounded to 8 lookups and 16 addresses. Appending /p2p/<peer-id> keeps only that relay’s addresses. Moving a relay, adding one, or replacing its key then becomes a DNS change instead of a client configuration change. With a bare /dnsaddr/<host>, the relay’s peer ID also comes from DNS, so whoever controls those records chooses the relay clients reserve on. Relayed traffic stays end-to-end encrypted between peers either way. But a substituted relay could see which peers connect and when, and could refuse service. To use DNS only as a locator, pin the relay: --relay /dnsaddr/relay.example.com/p2p/<peer-id>. The handshake then rejects any other key. Lookups run in the background: a failed lookup, for example when a laptop starts offline, is retried every 30 seconds and after a network change. bnw node status lists an unresolved relay by its /dnsaddr address until it resolves. Records are read when the node resolves them, so later DNS changes apply after a restart.

Relaying is QUIC-first. A relay’s addresses are tried one at a time, QUIC before TCP. TCP is used only if QUIC fails, for example on a network that blocks UDP. The node keeps a single connection per relay: a later TCP connection is closed in favour of QUIC, and the reservation moves with it. QUIC matters for NAT traversal for three reasons:

  • Its own keepalives hold the NAT mapping open.
  • A dead link is detected in about 10 seconds.
  • The relay connection lets identify learn this node’s public UDP address, which DCUtR needs to hole punch over UDP. That works far more often than TCP.

bnw node status shows which transport each relay connection uses.

The node logs relay reservation accepted and a /p2p-circuit listening address. Identify spreads that address to connected peers, and they add it to Kademlia routing. Peers that dial it connect through the relay, and DCUtR then attempts a direct hole-punched connection. The log reports whether the upgrade succeeded. AutoNAT logs the node’s current Public, Private, or Unknown reachability estimate.

deploy/relay/ packages a relay as a container image and runs it on a small EC2 instance with an Elastic IP and a persistent identity.

Every node limits the connections it accepts, so other peers cannot exhaust its memory or file descriptors:

Limit Node Relay server (--relay-server)
Handshakes in progress, incoming 32 128
Handshakes in progress, outgoing 64 64
Established incoming connections 128 1024
Established connections in total 256 1100
Connections per peer 4 4

New connections are also refused while the process uses more than 80% of system memory. Refused connections are counted in the connections-denied line of bnw node status.

GossipSub announcements are validated before a node delivers or forwards them:

  • Invalid: malformed announcements, ones larger than 16 KiB or naming more than 64 scopes, and ones published on a topic outside their own scopes are rejected. Rejection lowers the sender’s peer score. One invalid message stops gossip exchange with a new peer, and three graylist any peer, so all its messages are ignored. The penalty halves about every 70 seconds.
  • Rate-limited: each peer may deliver a burst of 200 announcements, refilled at 50 per second. Excess messages are ignored without penalty, because an honest peer may be forwarding someone else’s burst.
  • Not scored: delivery-rate penalties and the IP-colocation penalty stay off. BNW topics are quiet, and many honest peers can share one NAT address.

The gossip line of bnw node status counts rejected and rate-limited messages.

Relay circuits are bounded to 64 concurrent circuits, 10 minutes, and 16 MiB each. They are a rendezvous and fallback path, not bulk transfer infrastructure. When hole punching fails, as it can between two symmetric NATs, sync and resumable blob transfer continue over successive circuits more slowly.

Connected peers reconcile author summaries every 30 seconds in addition to synchronizing immediately after connection. This repairs a missed GossipSub announcement without requiring either node to restart. The interval can be adjusted for testing or constrained environments:

Terminal window
bnw node start --sync-interval-secs 5
# or: BNW_SYNC_INTERVAL_SECS=5 bnw node start

On macOS and Linux, successful CLI commands also wake the node through bnw-node.sock in the selected data directory. New subscriptions, announcements, and blob-provider changes are therefore processed immediately. The node also exposes the bounded catalog API through bnw-api.sock; both sockets are mode 0600 and local to the host. bnw-node.lock prevents two node processes from using the same directory. These files are runtime coordination state, not replicated data. A 30-second fallback scan preserves recovery after a missed notification or when an older CLI is used.

Query live state from the running process rather than persisted peer history:

Terminal window
bnw node status

The command reports the libp2p peer ID, connected-peer count, and process uptime on its first three lines. It then reports:

  • direct and relayed connection counts
  • AutoNAT reachability (public, private, or unknown until probes complete)
  • each configured relay and whether it holds a reservation
  • the complete relayed-address other peers can dial
  • local listen and confirmed external addresses
  • hole-punching results, pending peer redials, connections refused by limits, and GossipSub messages rejected or rate-limited

Networking state comes from the daemon’s local API socket and is refreshed about once per second. Nodes older than this API still answer with the first three lines. Requests use a nonce-bound response and a two-second local timeout.

Milestone 6 changes synchronization to /bnw/sync/6 and moves live announcements to opaque scope topics. Upgrade all peers together; Milestone 5 binaries do not exchange scoped history with Milestone 6 binaries.

On macOS, allow incoming network connections if the system firewall prompts you.