Paths and escalation
direct → gateway → relay → bridged → unreachable
Every pair of peers in a call finds its own media route. Freehop tries the cheapest route first and only moves to the next step after the current attempt exhausts its time budget, so the cost stays with the two people talking whenever possible.
The ladder
| Step | Path | What carries the media | Typical case |
|---|---|---|---|
| 1 | direct | Nothing in between | Same LAN, IPv6 on both sides, or addresses discovered with STUN through ordinary home routers |
| 2 | gateway | One endpoint's own gateway | Ben's carrier NAT cannot reach Dani directly, so he uses the front door of Dani's desktop app |
| 3 | relay | The gateway of another session member | Ben and Kai both sit behind random-port NATs; the session's host node relays for them |
| 4 | bridged | Another participant, forwarding the media | Ben and Kai cannot reach each other, but both reach Cleo, who is in the call anyway |
| unreachable | Nothing | No route exists inside the session; Freehop says so and keeps retrying slowly |
The cost rule behind the ladder: a byte of media only crosses machines that belong to the call. Steps 2 to 4 use machines of the session itself (a participant's gateway, the host node, a participant), never the signalling gate. If you operate the session gateway, its media bandwidth is your cost.
When each step is tried
Freehop follows the W3C perfect negotiation pattern. Each pair escalates through phases, and each phase has a time budget:
| Phase | ICE servers in use | Expected route | Moves on when |
|---|---|---|---|
| 0, endpoint | Application-approved STUN, the peer's own gateway, the other peer's gateway | direct or gateway | Not connected 5 s after the description; one more 5 s if connectivity checks are getting answers |
| 1, session | Plus up to 2 gateways of connected participants or gateway members | relay | 7 s without a connection; one extra 7 s if checks get answers |
| 2, bridged | Unchanged | bridged | The direct or session route later succeeds |
| no route | Unchanged | unreachable | Never gives up: ICE restarts after 30 s, 60 s, 120 s, 240 s, then every 300 s |
A few rules keep the escalation stable:
- One side restarts. The peer with the lexicographically greater id is polite. Only the other (impolite) side restarts ICE. The polite side applies the same servers and takes over after 2.5 s if no restart offer arrived. Simultaneous restarts collide: home-lab browser tests observed that a rolled-back restart offer could leave Chromium's senders silent.
- Both sides gather against the same relays. A description names the extra gateways in use, and the receiver adds those servers before it answers.
- Nothing deadlocks on a lost envelope. An offer that gets no answer within 8 s is rolled back and sent again. Candidates for an ICE generation that has not arrived yet wait in a buffer of 128.
- Media is watched. If a connected link's live track sends no packets for two 5-second checks, the link restarts (the polite side asks the impolite side to do it).
- Networks change. On
disconnecteda link waits 4 s, then restarts; onfailedit restarts at once.
Optional rungs before "no route"
Applications can opt into two more attempts at exactly the point where a pair would report unreachable. With neither option set, nothing above changes.
- Port prediction (
portPrediction: true). Some strict NATs hand out ports in order. When one side's NAT does and the other keeps one mapping (or also allocates in order), the link restarts once and probes the peer's next ports for up to 10 s, aiming for direct. Random NATs are never predicted. - Your own TURN relay (
turn). Tried last, for up to 10 s; the path reports relay withvia: 'turn'. The media stays encrypted end to end, but it crosses a relay you run or rent, so its bandwidth is yours. It stays the last resort after it connects: the pair checks for a cheaper route after a minute, then less often, and within seconds when a gateway it could use appears. When one works, the pair moves to it and releases the relay. A network that can only reach your gate still needs that relay to be reachable through what the network allows.
Play the timeline
Step through the phases of one pair, or let it run. Each outcome stops at the phase where it connects.
Escalation timeline
- Phase 0: endpoints5 s
- ICE servers
- Application-approved STUN, your own gateway and the other side's gateway.
- Route
- Direct (LAN, IPv6, STUN reflexive) or one endpoint’s own gateway.
- Moves on when
- Not connected 5 s after the description, plus one 5 s grace period if connectivity checks get answers.
- Phase 1: session gateways7 s
- ICE servers
- Adds up to two gateways of other members: a desktop participant or the host node.
- Route
- Relay through a session gateway, still encrypted end to end.
- Moves on when
- Only the impolite peer restarts ICE; the polite one takes over after 2.5 s. Not connected after 7 s, plus one 7 s grace period if checks get answers.
- Phase 2: bridginguntil a route appears
- ICE servers
- Unchanged.
- Route
- A connected participant forwards the media. The lower peer id asks; the other side takes over after 4 s.
- Moves on when
- When the direct or session route later connects, the bridge is released.
- Unreachable30 s to 5 min
- ICE servers
- Unchanged.
- Route
- None inside the session. By default Freehop reports it instead of renting a relay; an app can opt into its own.
- Moves on when
- ICE restarts after 30 s, 60 s, 120 s, 240 s, then every 300 s. New members or a network change can open a route.
Phase budgets come from the client's defaults; connection times here are illustrative. The 4-second bridge wait is an initiator fallback, not a deadline that proves a pair unreachable. A phase with no eligible gateway or bridge can be skipped. Working routes are used as soon as they connect.
Worked examples
Each row below comes from the original home-lab network tests, before the later security fixes, and passed three out of three Chromium runs; the results page also lists the Firefox and WebKit runs. The home-built test environment simulates each network with a Linux kernel NAT profile; physical carrier networks are not yet verified.
| People in the call | Simulated networks | What happens | Result | Test scenario |
|---|---|---|---|---|
| Two participants like Ana | Two ordinary home routers | STUN-discovered addresses work, because each router keeps the same public port for every destination | direct | direct-eim |
| Ivy and a friend on the same kind of network | IPv6 on both sides, IPv4 UDP blocked | Global IPv6 addresses; the stateful firewalls let the replies in once both sides send | direct | ipv6-direct |
| Ben and Kai, alone | Two random-port (symmetric) NATs | Neither NAT keeps a port the other side can predict, and nobody else is in the session | unreachable | hard-pair |
| Ben, Kai and Cleo | Two random-port NATs and a public address | Ben and Kai each reach Cleo directly. Cleo forwards their media to each other | Ben–Kai bridgedvia Cleo | hard-pair-bridge |
| Ben and Dani | Random-port NAT and a desktop app behind a UPnP router | Ben connects to Dani's own gateway through the port her router opened | gatewayvia Dani | two-peer-desktop-host |
| Ben, Kai and Dani | Two random-port NATs and a desktop app | Both reach Dani's gateway. Ben and Kai then relay through it | Ben–Kai relayvia Dani | hard-pair-gateway |
| Omar and Dani | UDP-blocking office and a desktop app | Omar reaches Dani's gateway with TURN over TCP | gatewayvia Dani | udpblock-gateway |
| Omar, a colleague in another UDP-blocking office, and Dani | Two UDP-blocking offices and a desktop app | Both use Dani's gateway over TCP, and relay through it to each other | relayvia Dani | udpblock-pair-gateway |
| Ben and Kai in a hosted session | Two random-port NATs, a host node with a public address | The host node joins without media and relays for them | relayvia host node | hard-pair-host-node |
| Omar and a colleague in a hosted session | Two UDP-blocking offices, a host node | Both reach the host node over TCP | relayvia host node | udpblock-pair-host-node |
| Gus with Ana and Cleo | A guest network that only reaches the gate host | Ana and Cleo connect directly. Gus can reach nothing but the gate | Gus unreachable, Ana–Cleo direct | gate-only |
The last row is the honest boundary. A network that can only reach the gate has no media route. A separately configured session gateway must be reachable over a transport the network permits; the gate cannot relay media. See Limits.
Find a path
Pick a network for Ana and for Ben, and choose who else is in the session. The model shows a possible route under those assumptions, why, and what your own server carries.
Path finder
Illustrative network model, not a test of your connection. Figures below come from Freehop's original home-lab network tests, before the later security fixes.
Relayed by the session's host node.
Both strict NATs pick a new random port for every destination, so neither side can predict where to send. After the 5-second endpoint budget (plus a grace period if checks get answers), Freehop adds the gateways of other session members (up to two at a time) and restarts ICE. The host node's gateway is reachable from both of you, so media relays there. It stays encrypted end to end: the relay only moves ciphertext.
- When
- Phase 1: session gateways, next 7 s
- Your servers
- Sealed signalling only. Media bytes: 0.
- Home-lab test evidence
- hard-pair-host-node 3/3 Chromium, 1/1 Firefox + WebKit passed, gate traffic 62–73 KB per run.
How the path is classified
When a link connects, Freehop reads the selected ICE candidate pair:
- no relay candidate on either side: direct;
- a relay owned by one of the two endpoints: gateway;
- a relay owned by another member of the session: relay;
- media arriving through a forwarding participant: bridged;
- nothing works and no bridge candidate exists: unreachable.
A local peer-reflexive candidate whose relayProtocol is set still rides a relay, and is classified as one. The SDK reports every change as a path event, with via naming the gateway owner or forwarder:
session.on('path', ({ peer, kind, via }) => {
label(peer).textContent = via ? `${kind} via ${nameOf(via)}` : kind;
});
While a pair is bridged or unreachable, it keeps trying the cheaper routes with cheap ICE restarts. When a direct or session route connects later, the bridge is released.
Next
Gateways and port mapping explains step 2, the participant's own front door.