b2p moves files between two machines through a small relay that both sides
reach with an ordinary outbound HTTPS/WebSocket connection on port 443 β the
same kind of connection a browser makes, and on many networks the only kind
allowed out. That is the design center: corporate networks, guest Wi-Fi,
CGNAT'd home lines, mobile hotspots β places where hole-punching, UDP, and
high TCP ports are dead on arrival, but the web works. TLS-inspecting proxies
are supported too (--cafile). The relay is yours: deploy it once, for
free, on Cloudflare Workers, or self-host it anywhere.
flowchart LR
A["π» sender"] -- "outbound wss / 443" --> R{{"βοΈ relay"}}
B["π» receiver"] -- "outbound wss / 443" --> R
The relay pairs the two connections and forwards bytes β it sees ciphertext only. A SPAKE2 key exchange over that pipe proves both sides know the one-time code, then the file streams end-to-end encrypted with XChaCha20-Poly1305. Interrupted transfers resume from the staged chunks instead of restarting.
Linux Β· macOS
curl -fsSL https://raw.githubusercontent.com/campiohe/b2p/main/scripts/install.sh | bashWindows (PowerShell)
irm https://raw.githubusercontent.com/campiohe/b2p/main/scripts/install.ps1 | iexBoth scripts download the binary for your platform from the
latest release, install it
(~/.local/bin Β· %LOCALAPPDATA%\Programs\b2p), and tell you what to do
about your PATH. Set B2P_INSTALL_DIR to install elsewhere, or
B2P_VERSION (e.g. v0.6.0) to pin a version. No runtime dependencies,
~8 MB.
Other ways to install
Prebuilt archives β Linux x86_64 (static musl), macOS arm64, Windows
x86_64 β are attached to every
release; unpack and put b2p
on your PATH. For anything else (Intel Macs, ARM Linux), build from
source:
cargo install --git https://github.com/campiohe/b2pThe relay also ships as a container image:
ghcr.io/campiohe/b2p:latest.
1. Install b2p on both machines (above).
2. Point it at a relay (deploy your own below):
$ b2p relay set wss://b2p-relay.<account>.workers.dev3. Transfer. On the sending machine:
$ b2p send path/to/file-or-folder
$ b2p send --text "the wifi password is hunter2"It prints the code two ways: a short human code like 7-otter-zebra
(works when the receiver has the same relay configured) and a long
b2p://β¦ form that embeds the relay address, so a freshly-installed
receiver needs no configuration at all. On the receiving machine:
$ b2p receive 7-otter-zebra
$ b2p receive 'b2p://b2p-relay.you.workers.dev/β¦#β¦'All flags
receive <code> --out DIR (destination) Β· --yes (no accept prompt) Β·
--overwrite Β· --relay URL (override the configured relay, both
commands) Β· --cafile FILE (extra root CA, all commands) Β· env
B2P_RELAY / B2P_RELAY_TOKEN override the config file Β· b2p relay show prints the configured relay.
The relay is a ~100-line Cloudflare Worker in relay-worker/.
You need a free Cloudflare account and Node.js:
$ cd relay-worker
$ npx wrangler login # opens the browser once
$ npx wrangler deploy # prints https://b2p-relay.<account>.workers.devOptionally restrict it to holders of a shared token (recommended once your relay URL circulates β short human codes use a small, enumerable room namespace, so the token is what keeps strangers from squatting rooms on your relay; your data is end-to-end encrypted either way):
$ npx wrangler secret put RELAY_TOKENThe token stays private to your own relay: b2p never sends it to a relay
address that came from someone else's b2p:// code. Then, on each machine:
$ b2p relay set wss://b2p-relay.<account>.workers.dev --token <T>The free tier comfortably covers personal use β dozens of multi-GB transfers a day; the relay never stores data.
The same b2p binary can be the relay β any VPS, home server, or container
platform works:
$ b2p relay serve # plain ws on 0.0.0.0:9009
$ b2p relay serve --token S3CR3T # require a bearer token
$ b2p relay serve --tls-cert c.pem --tls-key k.pem # built-in TLSor with Docker:
$ docker run -p 9009:9009 -e RELAY_TOKEN=S3CR3T ghcr.io/campiohe/b2p:latestFor internet use put TLS in front (unless using --tls-cert) β port 443 is
the whole point. Caddy does it in two lines with automatic Let's Encrypt
certificates:
relay.example.com {
reverse_proxy 127.0.0.1:9009
}(Kubernetes: terminate TLS at the ingress and point it at port 9009.) Then on
each machine: b2p relay set wss://relay.example.com.
The Cloudflare Worker and b2p relay serve implement the same protocol and
are interchangeable; relay-worker/test.mjs is the
conformance suite for both β CI runs it against b2p relay serve on every
push.
If the connection drops mid-transfer, the sender keeps waiting and the
code stays valid β re-run the same receive command and only the missing
chunks are sent (the receiver reports what it already staged, matched by
content fingerprint).
$ b2p doctor # DNS filtering, TLS inspection, UDP/STUN, relay reachability
$ b2p doctor <host> # same checks, aimed at a specific host or URLEvery check names the layer and ends with a one-line verdict; the relay check
does a real WebSocket connect + ping round-trip. b2p send and b2p receive
run the doctor automatically when they cannot establish a connection.
What the encryption covers. The relay sees ciphertext, sizes, and timing β never plaintext or keys. The code (and the SPAKE2 exchange derived from it) never travels through the relay in a usable form.
Wrong-code attempts are one-strike. A parked send offer is
cancelled by the first connection that fails the code confirmation β
your file is never sent to a peer that didn't prove the code, and a
guesser gets exactly one try per offer (like wormhole and croc). Anyone
who can reach the room can burn an offer this way, which is one more
reason to set a relay token.
Current limits β and the plan. Metadata: the relay operator can see
connecting IPs, timing, and transfer sizes. Reachability under filtering:
today b2p makes no attempt to hide its traffic, so a network that filters your
relay's hostname at the DNS or SNI layer stops transfers (b2p doctor names
which layer, and a custom domain in front of the Worker sidesteps category
blocks like *.workers.dev). Both areas have designed roadmap items β
proxy/Tor support, DNS-over-HTTPS resolution, Encrypted Client Hello β in
todo.md.
Practical notes.
- b2p trusts the operating system's certificate store (plus
SSL_CERT_FILE/SSL_CERT_DIR/--cafile); networks with TLS inspection work as long as the proxy's root CA is installed. - Folder transfers briefly need ~2Γ the transfer size free on both sides (tar spool on the sender, staging area on the receiver).
- Proxies that require explicit HTTP CONNECT configuration are not supported yet.
$ cargo test # full offline suite β transfers run against the real
# `relay serve` implementation on an ephemeral port
$ B2P_TEST_RELAY_URL=wss://β¦ cargo test --test relay_live # live smokeCI (ci.yml) runs cargo fmt --check,
cargo clippy -- -D warnings, cargo test, and the relay conformance suite
on every push and pull request to main.
Cutting a release
-
Bump
versioninCargo.tomland commit. -
Tag and push:
$ git tag v0.5.0 $ git push origin v0.5.0
The release workflow (release.yml)
cross-compiles Linux x86_64 (musl), macOS arm64, and Windows x86_64 binaries,
publishes them to a GitHub Release named after the tag, and pushes the Docker
image to ghcr.io/campiohe/b2p. Tags containing a hyphen (v0.5.0-rc.1)
publish as pre-releases and don't move the :latest image tag.