Test results
These are Freehop's own network-test results, produced by its developer in a home lab—not an independent testing laboratory or tests on live ISP, mobile, corporate or physical home-router networks. The revision tested on 2 October 2026 was run in an isolated Linux environment with simulated NAT profiles and real browser engines. Browser and desktop audio and video connected in every simulated network where a route exists inside the session, including cases direct-only WebRTC cannot connect.
This is a repeatable kernel-NAT test in Linux network namespaces, not a certification, independent evaluation or test on physical ISP, carrier or corporate networks. The 40-run matrix was run on 2 October 2026 after the security fixes: gateways and host nodes now relay only inside their session, and every simulated scenario still passed. On 9 October 2026 every scenario ran once more with Chromium for alpha.5, in the same lab packaged as a Docker container (lab/docker.sh), and took the same route; the three-trial, three-engine matrix has not been rerun. Unit, local-browser and TypeScript checks below ran for alpha.5 on 9 October 2026 unless marked otherwise. See Not yet verified below.
How the home-lab test environment works
Real Chromium 151, Firefox 153 and WebKit 26.5 ran in separate Linux network namespaces, each behind its own router namespace with a Linux kernel NAT profile, using fake cameras and microphones. The gate ran on a simulated internet. Desktop gateways and host nodes ran next to their peers. This setup exercises controlled, repeatable network profiles; it does not reproduce the full behavior or variability of real routers and internet providers. Every run asserts:
- the path that each side of every pair reports;
- that media actually flows: more than 50 audio packets and more than 30 decoded video frames per origin peer;
- that the gate's traffic stays signalling-sized.
| Profile | Emulates |
|---|---|
eim | A home router: endpoint-independent mapping, port-restricted filtering |
random | A hard NAT: symmetric, a fully random port for every destination |
udpblock | A firewall that drops all UDP |
upnp | A home router running miniupnpd 2.3.4 with PCP, NAT-PMP and UPnP IGD |
public | No NAT: a public address |
gateonly | A network that only forwards TCP to and from the gate host |
v6 | IPv6 behind a stateful firewall, with IPv4 UDP blocked |
Scenario results
The measurements below are from the original 2 October run; all 40 runs also passed the security-hardening rerun that day. Peers are named by letter: a, b and c are browser participants, g is a desktop participant running a gateway, and h is a host node (a gateway member without media). Gate traffic is the gate's total for the whole run, including retries of pairs that stay unreachable. It is signalling only.
| Scenario | Networks | Browsers | Passed | Paths per pair | Gate traffic per run |
|---|---|---|---|---|---|
direct-eim | a=eim, b=eim | Chromium | 3/3 | a–b direct | 31–32 KB |
direct-eim | a=eim, b=eim | Firefox, WebKit | 1/1 | a–b direct | 34 KB |
ipv6-direct | a=v6, b=v6 | Chromium | 3/3 | a–b direct over IPv6 | 34 KB |
ipv6-direct | a=v6, b=v6 | Firefox, WebKit | 1/1 | a–b direct | 36 KB |
hard-pair | a=random, b=random | Chromium | 3/3 | a–b unreachable (expected: nobody else in the session) | 93 KB |
hard-pair-bridge | a=random, b=random, c=public | Chromium | 3/3 | a–c, b–c direct, a–b bridgedvia c | 91 KB |
hard-pair-bridge | as above | Firefox, WebKit, Chromium as c | 1/1 | as above | 80 KB |
two-peer-desktop-host | a=random, g=upnp | Chromium | 3/3 | a–g gatewayvia g | 37–66 KB |
two-peer-desktop-host | as above | WebKit, Firefox as g | 1/1 | a–g gatewayvia g | 34 KB |
hard-pair-gateway | a=random, b=random, g=upnp | Chromium | 3/3 | a–g, b–g gatewayvia g, a–b relayvia g | 143–201 KB |
hard-pair-gateway | as above | Firefox, WebKit, Chromium as g | 1/1 | as above | 203 KB |
udpblock-gateway | a=udpblock, g=upnp | Chromium | 3/3 | a–g gatewayvia g (TURN over TCP) | 63 KB |
udpblock-gateway | as above | Firefox, Chromium as g | 1/1 | a–g gatewayvia g | 54 KB |
udpblock-pair-gateway | a=udpblock, b=udpblock, g=upnp | Chromium | 3/3 | a–g, b–g gatewayvia g, a–b relayvia g | 186 KB |
hard-pair-host-node | a=random, b=random, h=public | Chromium | 3/3 | a–b relayvia h (no desktop participant) | 73 KB |
hard-pair-host-node | as above | Firefox, WebKit | 1/1 | a–b relayvia h | 62 KB |
udpblock-pair-host-node | a=udpblock, b=udpblock, h=public | Chromium | 3/3 | a–b relayvia h | 65 KB |
gate-only | a=gateonly, b=eim, c=public | Chromium | 3/3 | a–b, a–c unreachable (the boundary), b–c direct | 207 KB |
Total: 40 of 40 home-lab network runs passed, 0 failed. The cross-engine runs put Firefox and WebKit behind the simulated hard profiles, and use Chromium only where a third party is needed.
What this answers
Earlier research that only tried direct routes proved its own boundary: two fully random symmetric NATs connected in 0 of 3 attempts, and UDP-blocked peers had no route at all. In these simulated network tests Freehop connects both cases, through a desktop participant's gateway, the session's host node or a participant. The same test setup leaves a hard-NAT pair without a helper unreachable. A network profile that reaches nothing but the gate (gate-only) remains unreachable even with session helpers. There, media is impossible without the operator carrying it, and Freehop reports unreachable.
Opt-in last resorts (alpha.5, 9 October 2026)
One Chromium run each, in the Docker-packaged lab. The first two rows use the default options and document limits; the rest use the opt-in turn and portPrediction options.
| Scenario | Profiles | Result | Path per pair |
|---|---|---|---|
eim-random | a=eim, b=random | 1/1 | a–b unreachable (home router against a strict NAT) |
udpblock-eim | a=udpblock, b=eim | 1/1 | a–b unreachable (UDP-blocked browser against a home router) |
hard-pair-turn | a=random, b=random, t=application TURN | 1/1 | a–b relay via turn, over UDP |
udpblock-pair-turn | a=udpblock, b=udpblock, t=application TURN | 1/1 | a–b relay via turn, over TCP |
random-eim-predict | a=random, b=eim, portPrediction | 1/1 | a–b unreachable, no prediction attempted |
hard-pair-predict | a=random, b=random, portPrediction | 1/1 | a–b unreachable, no prediction attempted |
Port prediction's success case, a NAT that hands out ports in order, cannot be simulated with Linux NAT and has not yet been tested on real networks; the option stays off by default.
Leaving the TURN relay (alpha.6, 10 October 2026)
Two Chromium peers on one machine whose pages first accept only relayed candidates (test/browser/turn-upgrade.mjs). Same machine and LAN: not a NAT or WAN test.
| Step | Result |
|---|---|
| Connect with the application TURN relay | relay via turn |
| A check for a cheaper route while none exists | stays on the relay, no gap in the audio |
| Every candidate accepted again | both peers move to direct by themselves, no gap in the audio |
| After the move | the relay forwards 0 bytes; its allocations end about 70 s later |
A variant without the final restart left one allocation open for the rest of the run, so that restart is what releases the relay.
Other evidence
Unit tests: 208 of 208
Passed for alpha.6 on 10 October 2026, including the audio-recovery and adaptive-video regressions, the opt-in route tests (NAT classification, prediction bounds, TURN rung order and fallback, and TURN servers in tickets) and leaving the TURN relay: checks with the relay still configured, its release, the polite side, and TURN as the last rung again afterwards.
- STUN codec with the RFC 5769 test vectors, plus fuzzing.
- TURN server: integrity, framing, quotas, room bandwidth, IPv6 destination policy and TCP deadlines.
- Port mapper: 30 cases against simulated PCP, NAT-PMP and UPnP routers.
- Gate: routing, isolation, floods, tokens, capacity, STUN, keepalive and proxy trust.
- Gateway: bounded room-scoped credentials, revocation retention, renderer isolation and loopback refusal.
- Protocol regressions: gate audiences, STUN pinning, discovery churn, both-endpoint forwarding consent and cancellation, key rotation and copied aliases.
TURN conformance-tool run (original home-lab test run, rerun 9 October 2026)
coturn's turnutils_uclient against Freehop's TURN server: UDP 800/800 and TCP 800/800 messages, 0 lost, in both runs. It runs in Send/Data mode, because coturn's client uses obsolete RFC 5766 channel numbers. ChannelData is covered by the browser tests.
TURN browser interop (macOS, original test run)
20 passed, 0 failed, 6 blocked. All six blocked cases are WebKit over TCP, caused by a TURN URL bug in Playwright's WebKit build (WebKit bug 320931). The client detects it and degrades to UDP TURN for that engine.
Local browser suites (macOS)
On 10 October 2026, npm run ship passed the three-engine TLS mesh, multi-gate outage, kick/rekey, SDK reference app, application-TURN and TURN-upgrade suites for alpha.6. The list below also includes the original three-Chromium and public-tracker checks; tracker connectivity was separately exercised in live demo checks.
- A mesh of three Chromium browsers.
- Chromium, Firefox and WebKit in one mesh over TLS.
- Multi-gate: peers on disjoint gates introduced through the mesh, then every gate shut down while media continued (about 600 audio packets per peer in 6 seconds).
- Kick and rekey.
- A public WebTorrent tracker as the only gate (
wss://tracker.openwebtorrent.com): no server of our own. - The SDK reference app driven through its own interface and API, including a kick rotation.
- Two Chromium peers that accept only relayed candidates:
unreachablewithout theturnoption, relay viaturnwith it. - Two Chromium peers on the application TURN relay leave it for a direct route once one exists, without a gap in the audio, and release it.
TypeScript consumers
Declarations cover all 15 public entry points since alpha.4. For alpha.6 on 10 October 2026, strict browser and backend consumers passed with the repository's TypeScript 7 compiler; the option types are from alpha.5 plus timing.turnUpgradeMs; the TypeScript 6 and Electron 44.6 checks are from alpha.4 on 7 October 2026. After publication on 9 October, a fresh install of freehop@0.1.0-alpha.5 from npm passed the same compile and runtime smoke checks. Run npm run typesgate from a checkout for the maintained consumer fixtures.
Security review
The earlier source and test review recorded 11 findings. The subsequent source review and security regressions cover key epochs, alias revocation, tickets, authority races, forwarding authorization, gate input limits, UDP response budgets and Electron IPC. See the security model for the current trust limits.
Not yet verified
- Physical networks: real home routers, 4G and 5G carrier NAT (usually with IPv6), a UDP-blocking corporate network, and routers without UPnP, PCP or NAT-PMP.
- Application performance under load: CPU, rendering and responsiveness when calls run alongside your meeting UI, shared workspace or game.
- iOS Safari and Android browsers. Only desktop engines were tested.
- Media quality under real load. Forwarded (bridged) media is re-encoded by the participant, and the upstream budget of a forwarding participant on home broadband is still unmeasured.
- Port prediction on real networks. Its success case, a NAT that hands out ports in order, cannot be simulated with Linux NAT, so
portPredictionstays off by default. - The application TURN relay in Firefox (work in progress) and WebKit, and leaving it on real networks. In the relay-only browser setup, Firefox gets no audio in one direction through the application relay, also with the alpha.5 code. Leaving the relay was tested with Chromium on one machine.
Reproduce
npm run ship # unit tests, release gates and browser suites
node test/browser/tracker.mjs # a public WebTorrent tracker as the only gate
The home-lab NAT test harness builds network namespaces and kernel NAT rules, so its scripts refuse to run anywhere except a disposable, privileged Linux container (FREEHOP_DISPOSABLE_LAB=yes, as root). lab/docker.sh builds that container from lab/Dockerfile and runs it with Docker, including Docker Desktop on macOS:
lab/docker.sh qualify # the 11-scenario matrix three times, cross-engine runs and coturn conformance
lab/docker.sh hard-pair-turn # one scenario, including the opt-in ones
Every run writes a JSON report with the source hashes, paths, media counters and gate statistics.