Skip to content

Latest commit

 

History

History
193 lines (146 loc) · 7.06 KB

File metadata and controls

193 lines (146 loc) · 7.06 KB

Peer Discovery

Two mechanisms for discovering other iroh-http nodes:

  • DNS discovery — global, always-on. Any node that publishes its address via Pkarr can be resolved by public key using standard DNS.
  • mDNS — local network. Nodes announce their presence on the LAN and can find each other without internet connectivity, via node.advertisePeer() and node.browsePeers().

DNS discovery

DNS discovery is enabled by default and configured at node creation:

await createNode({
  discovery: {
    // Default: true — uses n0's hosted DNS infrastructure.
    dns: true,

    // Custom resolver:
    // dns: { serverUrl: 'https://dns.example.com' },

    // Disable entirely (air-gapped / embedded):
    // dns: false,
  },
});

When enabled, node startup automatically publishes a signed Pkarr record containing the node's relay URL and direct socket addresses. On node.fetch, if the peer's address isn't already known, Iroh resolves it via DNS before the QUIC handshake — transparently, with no extra code.

node.advertisePeer()

Announce this node as a discoverable iroh-http peer on the local network via mDNS until the signal fires:

const controller = new AbortController();
node.advertisePeer({ serviceName: 'my-app', signal: controller.signal });

// Stop advertising:
controller.abort();

Returns a Promise<void> that resolves when advertising stops. Calling it without a signal advertises until the node is closed.

node.browsePeers()

Discover peers on the local network as an async iterable:

for await (const event of node.browsePeers({ serviceName: 'my-app' })) {
  if (event.isActive) {
    console.log('found peer:', event.nodeId, event.addrs);
  } else {
    console.log('peer left:', event.nodeId);
  }
}

See PeerDiscoveryEvent in the specification for the event shape.

Cancel by passing an AbortSignal or by breaking from the loop — both clean up the underlying mDNS listener:

const controller = new AbortController();
for await (const event of node.browsePeers({ signal: controller.signal })) { ... }
controller.abort();

// Or just break:
for await (const event of node.browsePeers({ serviceName: 'my-app' })) {
  if (done) break;
}

mDNS options

See MdnsOptions in the specification for the option shape.

browsePeers and advertisePeer accept MdnsOptions. Both can run simultaneously on the same node — they are independent.

Generic DNS-SD (node.advertise() / node.browse())

node.advertisePeer() / node.browsePeers() are the ergonomic, zero-config path for finding iroh-http peers: the instance name is the node id, the port comes from the endpoint, and the TXT set is fixed (pk + optional relay).

To advertise or browse any DNS-SD service — a printer, a game lobby, a non-iroh daemon — use the generic node.advertise() / node.browse() primitives. They are the same wire protocol and the same underlying engine, but lossless and fully caller-controlled:

const ac = new AbortController();

// Advertise an arbitrary service.
await node.advertise({
  serviceName: "printers",     // → _printers._tcp.local.
  instanceName: "Front Desk",
  port: 9100,
  protocol: "tcp",             // "udp" (default) | "tcp"
  txt: { model: "LaserJet 9000", color: "true" },
  signal: ac.signal,
});

// Browse — records are lossless: instance, host, port, addrs, and every TXT key.
for await (const rec of node.browse({ serviceName: "printers", protocol: "tcp" })) {
  console.log(rec.isActive ? "up" : "down", rec.instanceName, rec.port, rec.txt);
}

node.advertisePeer() / node.browsePeers() are defined as the iroh-http specialization of these generic primitives — there is one bridge, not two. The generic surface lives on the node because the native discovery FFI is loaded through the node addon (per ADR-018); it does not otherwise use the node's identity or endpoint.

Recognizing iroh-http peers in a generic browse

iroh-http advertises under a known service name with the node id in a pk TXT property. asIrohPeer(record) reinterprets a generic ServiceRecord as a DiscoveredPeer when it carries one, and returns null otherwise:

import { asIrohPeer, IROH_HTTP_SERVICE } from "@momics/iroh-http-node";

for await (const rec of node.browse({ serviceName: IROH_HTTP_SERVICE })) {
  const peer = asIrohPeer(rec);
  if (peer) await node.fetch(`httpi://${peer.nodeId}/api`);
}

Interop with iroh's built-in mDNS

iroh-http discovery is not interoperable with plain-iroh's built-in mDNS (swarm-discovery / iroh-mdns-address-lookup). The two use different wire formats: iroh-http speaks standard DNS-SD with a PTR record so it is browsable by Bonjour, iOS NWBrowser, and Android NsdManager, whereas iroh's own backend omits PTR and is invisible to those browsers. This trade — dropping plain-iroh mDNS interop to gain standards-compliant, mobile-visible discovery — is the whole point of the change; see ADR-017 for the wire-format decision.

Nodes on the same LAN still connect fine regardless: once a peer's node id and addresses are known (via DNS discovery, a ticket, or a direct address), the QUIC handshake is identical. Only the mDNS enumeration differs.

Without discovery

When DNS is disabled and neither browse nor advertise is called, the node operates in explicit-address mode. Connections must use direct hints (directAddrs), a home-relay hint (relayUrl), or ticket strings (see tickets).

Appropriate for embedded targets, air-gapped networks, and integration tests.

Platform support

Feature Node / Deno / Tauri
DNS discovery (auto-resolve by public key)
advertise() ✅ (AbortSignal)
browse() ✅ (async iterable + AbortSignal)

Feature flag: mDNS browse and advertise require the mdns compile-time feature in all Rust adapters.

Mobile setup (iOS & Android)

Discovery speaks standard DNS-SD over mDNS (PTR + SRV + TXT + A/AAAA), so a desktop node is browsable by Apple's NWBrowser and Android's NsdManager — see ADR-017 for why this wire format was chosen.

Tauri apps must declare local-network permissions and Bonjour service types before mDNS works on a device. See Mobile mDNS / DNS-SD setup for the exact iOS Info.plist and Android AndroidManifest.xml entries.

Mobile has one native generic browse engine and one native generic advertisement engine. The peer APIs use those same adapters, then apply the iroh-specific TXT, identity, and endpoint-lookup projection in Rust. Android resolves full generic records (host, port, TXT, addresses); iOS surfaces the instance name, service type and TXT but leaves host/port/addresses unresolved (NWBrowser does not resolve endpoints without an NWConnection). A single iroh-http:discovery capability permission grants both paths.