Skip to content

IFM Transport & Discovery

Overview

IFM is transport-agnostic — the core protocol doesn't know or care whether the underlying transport is QUIC, WebRTC, or TCP. The ifm-transport crate provides a unified abstraction over multiple transports, with libp2p handling the heavy lifting.


Transport Abstraction

rust
trait Transport: Send + Sync {
    async fn listen(&self, addr: Multiaddr) -> Result<Listener>;
    async fn dial(&self, addr: Multiaddr) -> Result<Connection>;
    fn supported_protocols(&self) -> Vec<Protocol>;
    fn max_connections(&self) -> usize;
}

Supported Transports

TransportProtocolUse CaseStatus
QUICquicPrimary — low latency, multiplexed, encrypted✅ Primary
WebRTCwebrtcBrowser, NAT traversal✅ Secondary
TCPtcpFallback for restricted networks✅ Fallback
WebTransportwebtransportBrowser (future)🔄 Planned

QUIC Transport (Primary)

Why QUIC?

  • 0-RTT/1-RTT handshake — fast connection establishment
  • Multiplexed streams — no head-of-line blocking
  • Built-in encryption — TLS 1.3 / Noise
  • Connection migration — survives IP changes (mobile)
  • Congestion control — BBR/CUBIC, better than TCP

Configuration

toml
[network]
quic = true
quic_port = 0          # 0 = random ephemeral port
quic_max_idle = 30     # seconds
quic_keepalive = 10    # seconds
quic_congestion = "bbr"  # bbr | cubic

Multiaddr Format

/ip4/192.168.1.100/udp/4001/quic-v1/p2p/12D3KooW...
/ip6/::1/udp/4001/quic-v1/p2p/12D3KooW...

Stream Management

  • Control stream: Protocol negotiation, keepalive
  • Data streams: One per frequency (bidirectional)
  • Max streams: 256 per connection (configurable)

WebRTC Transport (Browser & NAT Traversal)

Why WebRTC?

  • Browser native — no WASM networking limitations
  • ICE/STUN/TURN — automatic NAT traversal
  • DataChannels — unreliable (voice) + reliable (data)
  • DTLS — encrypted by default

Configuration

toml
[network]
webrtc = true
stun_servers = [
    "stun:stun.l.google.com:19302",
    "stun:stun.cloudflare.com:3478"
]
turn_servers = []  # Optional, for restrictive NATs
ice_gathering = "all"  # all | relay | none

Signaling

WebRTC requires signaling — IFM uses libp2p WebRTC Direct with:

  1. DHT-based signaling — peers exchange SDP via Kademlia
  2. Relay signaling — via libp2p circuit relay
  3. Local signaling — mDNS for LAN peers

DataChannel Types (per traffic lane)

Browser↔browser (the WebRTC overlay) keeps one data channel per traffic lane — real-time lanes are unordered & loss-tolerant, reliable lanes are ordered:

ChannelReliabilityOrderingUse Case
ifm-voiceUnreliableUnorderedOpus frames (low latency, loss-tolerant)
ifm-videoUnreliableUnorderedVideo frames (real-time)
ifm-chatReliableOrderedText messages
ifm-fileReliableOrderedFile transfer (integrity, retransmission)
ifm-dataReliableOrderedGeneric data / telemetry

All lanes share a single RTCPeerConnection between two browsers (logical isolation, not one physical socket per lane) — see Independent Transport Lines below.


TCP Transport (Fallback)

When Used

  • Corporate firewalls blocking UDP
  • Networks with QUIC/WebRTC blocked
  • Debugging / development

Configuration

toml
[network]
tcp = true
tcp_port = 0
tcp_nodelay = true
tcp_keepalive = 30

Multiaddr Format

/ip4/192.168.1.100/tcp/4001/p2p/12D3KooW...
/ip6/::1/tcp/4001/p2p/12D3KooW...

Limitations

  • No multiplexing (single stream per connection)
  • Head-of-line blocking
  • No connection migration
  • Slower handshake (TCP + TLS/Noise)

Transport Selection Logic

rust
async fn select_transport(peer_addrs: Vec<Multiaddr>) -> Result<Connection> {
    // Priority order
    let priorities = [
        Protocol::Quic,
        Protocol::WebRTC,
        Protocol::Tcp,
    ];
    
    for protocol in priorities {
        for addr in peer_addrs.iter().filter(|a| a.supports(protocol)) {
            if let Ok(conn) = dial(addr).await {
                return Ok(conn);
            }
        }
    }
    
    Err(Error::NoTransportAvailable)
}

Connection Manager

  • Maintains connection pool per peer
  • Upgrades connections when better transport available
  • Load balances across transports
  • Health checks — pings, stream keepalive

Peer Discovery

IFM uses multiple discovery mechanisms in parallel:

1. Kademlia DHT (Primary)

Joining a Frequency via DHT

1. Compute Topic ID = BLAKE3(canonical_frequency)
2. DHT: GET_PROVIDERS(Topic ID)
3. Receive list of (Peer ID, Multiaddrs)
4. Dial top N peers (parallel, with timeout)
5. On success: SUBSCRIBE to GossipSub topic
6. Send HELLO, receive WELCOME

DHT Configuration

toml
[discovery]
dht = true
dht_mode = "client"  # client | server (server = full routing)
dht_replication = 20
dht_refresh_interval = 3600  # seconds
dht_bootstrap = [
    "/ip4/bootstrap.ifm.network/udp/4001/quic-v1/p2p/12D3KooWBootstrap1",
    "/ip4/bootstrap2.ifm.network/udp/4001/quic-v1/p2p/12D3KooWBootstrap2"
]

2. Local Network Discovery (mDNS/UDP Multicast)

Configuration

toml
[network]
lan = true
mdns = true
mdns_interval = 30  # seconds
broadcast_port = 4002
broadcast_interval = 10  # seconds

Station & Relay Discovery

The transport also carries the discovery plane (docs/protocol/discovery.md): station and relay records ride control topics on the same mesh as user traffic, so they inherit its encryption, dedup, and TTL rules:

TopicPayloadPurpose
/ifm/stations/v1DiscoveryOp (serde-tagged)Station announce/update/search
/ifm/relays/v1RelayMsg::Announce + Hello/Bye/TunedRelay presence + signed announcements
  • Records are verified before they are stored (§14): the ID must match the embedded public key and the Ed25519 signature must check out; unverifiable records are dropped.
  • The newest timestamp per station/relay wins.
  • A 30-second heartbeat tick re-broadcasts this node's own relay hello, relay announcement, and station record (liveness, §10).
  • The transport API exposes announce_station / station_records / announce_relay / relay_announcements; the core ingests them lazily on read (Node::stations(), Node::relay_ranked(), …).

The in-memory transports (InMemoryMesh, SharedMeshTransport) mirror the same API so tests and the headless core exercise identical discovery semantics without sockets.


3. Bootstrap Peers

Bootstrap Flow

Configuration

toml
[network]
bootstrap = true
bootstrap_peers = [
    "/ip4/bootstrap.ifm.network/udp/4001/quic-v1/p2p/12D3KooWBootstrap1",
    "/ip4/bootstrap2.ifm.network/udp/4001/quic-v1/p2p/12D3KooWBootstrap2",
    "/ip4/bootstrap3.ifm.network/udp/4001/quic-v1/p2p/12D3KooWBootstrap3"
]
bootstrap_interval = 300  # Re-bootstrap every 5 min if disconnected
bootstrap_min_peers = 3   # Minimum connected bootstrap peers

3.5 Discovery Sources — interchangeable, none authoritative

IFM treats every discovery mechanism as one entry point among several. No source is authoritative, and none is a dependency: bootstrap seeds are how you enter, the mesh itself becomes the infrastructure afterward.

The intended lifecycle (proved end-to-end by bootstrap_resilience.rs):

  1. A completely new node needs any one initial rendezvous source (a community relay seed, mDNS on the LAN, a cached peer, a QR link).
  2. Once it connects to one IFM peer, it discovers the rest through the DHT (provider records on the shared mesh key), the control topics, and the relay pool — no further manual input.
  3. Every peer met is persisted in the peer cache (peer-cache.json in the app data dir), so a later start reconnects with no seed list, rendezvous, or server at all.
  4. Bootstrap/relay peers are community-operated and disposable: if one disappears, the pool prunes it (90s Hello TTL) and paths re-route (DCUtR upgrades relayed connections to direct P2P when possible).
  5. bootstrap_resilience.rs runs the whole cycle: mesh forms → bootstrap dies → delivery continues → a new relay joins seeded only with community relays → a relay dies → delivery continues → a new listener joins seeded only with the surviving relay and receives the station's broadcast.

Multi-hop delivery note. GossipSub is configured with ValidationMode::Strict; the transport explicitly accepts every inbound message (report_message_validation_result(..., Accept)) after handling it. Without that verdict the behaviour would never forward messages, silently breaking station → relay → listener delivery (a relay that receives but never re-broadcasts). delivery.rs covers both the direct path and the through-relay path.


For protected/hidden frequencies and direct peer connection:

QR Code Format

ifm://connect?peer=12D3KooW...&addrs=/ip4/1.2.3.4/udp/4001/quic-v1&freq=protected/team.alpha&key=base64secret
https://ifm.network/join?peer=12D3KooW...&freq=private.team&key=...

Components

ParameterDescription
peerTarget Peer ID (base58btc)
addrsMultiaddrs (comma-separated, URL-encoded)
freqFrequency to auto-tune (optional)
keyEncryption key for protected freq (optional, base64)

5. Friend Invitation (Social Graph)


NAT Traversal

Techniques (in priority order)

TechniqueTransportSuccess RateLatency
Direct (public IP)QUIC/TCP100%Lowest
Hole PunchingQUIC/UDP~85%Low
UPnP/NAT-PMPQUIC/TCP~60%Medium
WebRTC ICEWebRTC~95%Low
Circuit RelayAny100%Higher
TURN RelayWebRTC100%Highest

Hole Punching (QUIC)

Circuit Relay (libp2p)

Server-Optional Relay Plane (automatic circuit dialing + DCUtR)

IFM's relay plane is server-optional: no IFM-operated VPS or central server is required. Any node with a public address can serve the shared pool (the IFM Relay Station app is exactly that), and the rest of the mesh is pure P2P:

  1. Entry point (optional, never required) — remote mode (the default) fetches the deployed rendezvous's live manifest (IFM_MANIFEST_URL, default http://ifm-rendezvous.fly.dev/bootstrap.ifm.json) and dials the listed address; LAN mode (IFM_LAN=1) uses zero-config mDNS instead. The two modes are exclusive.
  2. AutoNAT reachability — every node probes connected peers to learn whether it is publicly reachable and what its public address is. Station records prefer the public address (a LAN-bound private IP is undialable from the wider mesh), so any listener can reach the station directly.
  3. Reservations — every node requests a circuit-relay v2 reservation from each relay in the shared pool, making it reachable through that relay.
  4. Via announcements — on reservation-accepted (and re-announced on the periodic control tick), a node publishes Via { node, relay, verifying_key } on the /ifm/relays/v1 topic. Receivers derive the node's libp2p PeerId from the verifying key — no Identify exchange needed.
  5. Automatic circuit dial — peers that share a relay dial each other through it (<relay>/p2p/<relay>/p2p-circuit/p2p/<peer>); the dcutr behaviour then hole-punches the connection direct, so the relay carries only connectivity and steady traffic moves P2P. If punching fails (restrictive NAT), the circuit keeps carrying traffic — the network still works, just at relay cost.
  6. Disposable relays — pool members re-announce Hello every 30s; a relay that crashes or leaves drops out of the pool automatically after a lapsed heartbeat (90s), so no individual relay is ever required and the pool adapts on its own.

Independent Transport Lines

IFM keeps different traffic classes on independent transport lines. A line is dedicated to one traffic purpose and is never a general-purpose container for unrelated traffic:

Traffic classes (at minimum): AUDIO, VIDEO, CHAT, TELEMETRY, FILE, CONTROL. Each lane can have its own encoding, queue, buffering, priority, reliability requirements, congestion behavior, and transport implementation. A failure, congestion, or high-volume transfer on one line MUST NOT block or significantly degrade unrelated real-time lines (a large file transfer must not interfere with live audio).

Logical isolation, not necessarily physical sockets. Lanes share the underlying peer-to-peer network while keeping separate logical transport channels, queues, priorities, flow control, and failure domains. This leaves room to map lanes onto QUIC streams, WebRTC data channels, or TCP connections without coupling the application protocols together.

Implementation across IFM's transports:

PlaneTopics / channels
Control/ifm/stations/v1, /ifm/relays/v1, /ifm/telemetry/v1 (dedicated)
Frequency payloads/ifm/{topic_id}/voice, /video, /chat, /file, /data (GossipSub lanes on every transport)
Browser overlayOne WebRTC data channel per lane (ifm-voiceifm-data)

GossipSub (Mesh Broadcast)

Topic = Traffic Lane of a Frequency

/ifm/{topic_id}/voice   # live audio
/ifm/{topic_id}/video   # real-time video
/ifm/{topic_id}/chat    # text messages
/ifm/{topic_id}/file    # file transfer
/ifm/{topic_id}/data    # telemetry / generic data

(topic_id = BLAKE3 hash of the canonical frequency.) The payload plane of a frequency is split into these per-class lanes so no single application stream carries every traffic class — a tuned node subscribes to all of them.

Parameters

toml
[gossip]
ttl = 16              # Packet TTL (hops)
fanout = 6            # Forward to 6 peers
mesh_n = 6            # Target mesh degree
mesh_n_low = 4        # Min mesh degree
mesh_n_high = 12      # Max mesh degree
gossip_factor = 0.25  # Gossip to 25% of mesh
history_length = 5    # Message history for new peers
history_gossip = 3    # Gossip 3 history messages

Peer Scoring

Peers scored on:

  • Delivery reliability — do they forward valid packets?
  • Timeliness — low latency forwarding
  • Behavior — no spam, valid signatures
  • Connection quality — stable connections

Low-score peers → pruned from mesh.


Connection Lifecycle

Events

EventTriggerAction
ConnectedHandshake completeAdd to peer set, start keepalive
DisconnectedError/timeout/closeRemove from peer set, cleanup streams
UpgradedBetter transport availableMigrate streams, close old
DegradedTransport failedFallback to next transport

Mermaid: Discovery & Connection Flow


Configuration Summary

toml
[network]
# Transport enable/disable
quic = true
webrtc = true
tcp = true

# QUIC settings
quic_port = 0
quic_max_idle = 30
quic_keepalive = 10
quic_congestion = "bbr"

# WebRTC settings
stun_servers = ["stun:stun.l.google.com:19302"]
turn_servers = []
ice_gathering = "all"

# TCP settings
tcp_port = 0
tcp_nodelay = true

# Discovery
bootstrap = true
bootstrap_peers = [
    "/ip4/bootstrap.ifm.network/udp/4001/quic-v1/p2p/12D3KooWBootstrap1",
    "/ip4/bootstrap2.ifm.network/udp/4001/quic-v1/p2p/12D3KooWBootstrap2"
]
lan = true
mdns = true
mdns_interval = 30
broadcast_port = 4002

# DHT
dht = true
dht_mode = "client"
dht_replication = 20

# GossipSub
[gossip]
ttl = 16
fanout = 6
mesh_n = 6
mesh_n_low = 4
mesh_n_high = 12
gossip_factor = 0.25

Running a Bootstrap Node

bash
# Minimal bootstrap node
ifm bootstrap --port 4001 --key bootstrap.key

# With custom config
ifm bootstrap --config bootstrap.toml

bootstrap.toml:

toml
[node]
name = "bootstrap.ifm.network"
relay = true
cache = 0
connections = 1000

[network]
bootstrap = false  # Don't bootstrap from others
lan = false
quic = true
webrtc = false

[gossip]
fanout = 20  # Higher fanout for bootstrap

Requirements:

  • Static public IP or stable DNS
  • Open UDP 4001 (QUIC) and/or TCP 4001
  • High bandwidth (≥100 Mbps)
  • Low latency (<50ms to major regions)
  • DDoS protection recommended

Released under the MIT License.