How it works

Overview

Play2P is a browser-based peer-to-peer game console. Two players open the same URL; their browsers connect via WebRTC and play. No game server. No account. No relay.

The site is static. After page load, traffic flows between the two players, plus short STUN queries during the handshake.

The remaining slides cover the handshake in order.

Player 1 starts a session

Player 1 clicks New Game. The script runs locally; the site is not contacted again.

The browser builds an offer

The page creates an RTCPeerConnection, opens a data channel on it, and generates a session description: the offer.

The offer, as JSON

The offer is a JSON-encoded RTCSessionDescription:

{ "type": "offer", "sdp": "v=0 m=application 9 UDP/DTLS/SCTP webrtc-datachannel a=fingerprint:sha-256 11:22:33:... a=candidate:2 1 udp 1686 81.92.140.7 54812 typ srflx ..." }

Typical size: 2–4 KB.

Key fields

type
"offer" from the host, "answer" from the guest.
m=application
One data channel. No audio, no video.
a=fingerprint
SHA-256 of the browser's DTLS certificate. Binds encryption to this peer.
a=candidate
A reachable address. srflx is the public address discovered via STUN.

STUN provides the public address

During ICE gathering, the browser queries public STUN servers for its public IP and port. Each response becomes an a=candidate line of type srflx.

Servers in use

Google
stun:stun.l.google.com:19302
Cloudflare
stun:stun.cloudflare.com:3478

Scope

STUN does not relay traffic. One UDP query, one response. Out of the path once the data channel opens.

Out-of-band exchange

Player 1 sends the code to Player 2 through any messaging channel: SMS, email, Discord. The channel does not need to be encrypted.

What the payload contains

Routing data, not a credential. Network candidates, a DTLS fingerprint, the data channel description. No account or device identity.

Player 2 joins

Player 2 opens the same URL, clicks Join Game, and pastes the offer.

The browser builds an answer

Player 2's browser parses the offer, creates its own RTCPeerConnection, and generates an SDP answer. STUN runs again to gather Player 2's own public address.

The answer mirrors the offer

Same fields. type is now "answer"; candidates describe Player 2's network:

{ "type": "answer", "sdp": "v=0 m=application 9 UDP/DTLS/SCTP webrtc-datachannel a=fingerprint:sha-256 88:99:AA:... a=candidate:1 1 udp 1686 203.0.113.42 51820 typ srflx ..." }

Player 2 returns the answer to Player 1 over the same channel.

Player 1 applies the answer

Player 1 pastes the answer and clicks Start match. The browser applies it as the remote description.

ICE picks a route

Each browser probes candidate pairs until it finds one reachable in both directions. The result is typically a direct path, including across NATs.

The data channel opens

The RTCDataChannel opens with DTLS-SRTP encryption. Game traffic flows between the two browsers. The site and STUN servers leave the path. Encryption keys never leave the two endpoints.

Source

The library, the games, and this page live in one repository.