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
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
| Transport | Protocol | Use Case | Status |
|---|---|---|---|
| QUIC | quic | Primary — low latency, multiplexed, encrypted | ✅ Primary |
| WebRTC | webrtc | Browser, NAT traversal | ✅ Secondary |
| TCP | tcp | Fallback for restricted networks | ✅ Fallback |
| WebTransport | webtransport | Browser (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
[network]
quic = true
quic_port = 0 # 0 = random ephemeral port
quic_max_idle = 30 # seconds
quic_keepalive = 10 # seconds
quic_congestion = "bbr" # bbr | cubicMultiaddr 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
[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 | noneSignaling
WebRTC requires signaling — IFM uses libp2p WebRTC Direct with:
- DHT-based signaling — peers exchange SDP via Kademlia
- Relay signaling — via libp2p circuit relay
- 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:
| Channel | Reliability | Ordering | Use Case |
|---|---|---|---|
ifm-voice | Unreliable | Unordered | Opus frames (low latency, loss-tolerant) |
ifm-video | Unreliable | Unordered | Video frames (real-time) |
ifm-chat | Reliable | Ordered | Text messages |
ifm-file | Reliable | Ordered | File transfer (integrity, retransmission) |
ifm-data | Reliable | Ordered | Generic 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
[network]
tcp = true
tcp_port = 0
tcp_nodelay = true
tcp_keepalive = 30Multiaddr 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
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 WELCOMEDHT Configuration
[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
[network]
lan = true
mdns = true
mdns_interval = 30 # seconds
broadcast_port = 4002
broadcast_interval = 10 # secondsStation & 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:
| Topic | Payload | Purpose |
|---|---|---|
/ifm/stations/v1 | DiscoveryOp (serde-tagged) | Station announce/update/search |
/ifm/relays/v1 | RelayMsg::Announce + Hello/Bye/Tuned | Relay 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
[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 peers3.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):
- A completely new node needs any one initial rendezvous source (a community relay seed, mDNS on the LAN, a cached peer, a QR link).
- 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.
- Every peer met is persisted in the peer cache (
peer-cache.jsonin the app data dir), so a later start reconnects with no seed list, rendezvous, or server at all. - 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).
bootstrap_resilience.rsruns 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.
4. QR Code / Invitation Links
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=base64secretInvitation Link
https://ifm.network/join?peer=12D3KooW...&freq=private.team&key=...Components
| Parameter | Description |
|---|---|
peer | Target Peer ID (base58btc) |
addrs | Multiaddrs (comma-separated, URL-encoded) |
freq | Frequency to auto-tune (optional) |
key | Encryption key for protected freq (optional, base64) |
5. Friend Invitation (Social Graph)
NAT Traversal
Techniques (in priority order)
| Technique | Transport | Success Rate | Latency |
|---|---|---|---|
| Direct (public IP) | QUIC/TCP | 100% | Lowest |
| Hole Punching | QUIC/UDP | ~85% | Low |
| UPnP/NAT-PMP | QUIC/TCP | ~60% | Medium |
| WebRTC ICE | WebRTC | ~95% | Low |
| Circuit Relay | Any | 100% | Higher |
| TURN Relay | WebRTC | 100% | 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:
- Entry point (optional, never required) — remote mode (the default) fetches the deployed rendezvous's live manifest (
IFM_MANIFEST_URL, defaulthttp://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. - 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.
- Reservations — every node requests a circuit-relay v2 reservation from each relay in the shared pool, making it reachable through that relay.
Viaannouncements — on reservation-accepted (and re-announced on the periodic control tick), a node publishesVia { node, relay, verifying_key }on the/ifm/relays/v1topic. Receivers derive the node's libp2p PeerId from the verifying key — no Identify exchange needed.- Automatic circuit dial — peers that share a relay dial each other through it (
<relay>/p2p/<relay>/p2p-circuit/p2p/<peer>); thedcutrbehaviour 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. - Disposable relays — pool members re-announce
Helloevery 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:
| Plane | Topics / 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 overlay | One WebRTC data channel per lane (ifm-voice … ifm-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
[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 messagesPeer 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
| Event | Trigger | Action |
|---|---|---|
Connected | Handshake complete | Add to peer set, start keepalive |
Disconnected | Error/timeout/close | Remove from peer set, cleanup streams |
Upgraded | Better transport available | Migrate streams, close old |
Degraded | Transport failed | Fallback to next transport |
Mermaid: Discovery & Connection Flow
Configuration Summary
[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.25Running a Bootstrap Node
# Minimal bootstrap node
ifm bootstrap --port 4001 --key bootstrap.key
# With custom config
ifm bootstrap --config bootstrap.tomlbootstrap.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 bootstrapRequirements:
- 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