# WebRTC

> This topic assumes the Browser Request Lifecycle and CORS topics from this group for general network/browser context. WebRTC is a genuinely different transport model from everything else covered in this repo — peer-to-peer, not client-server — so this topic builds its own vocabulary rather than reusing HTTP request/response framing.

## 1. What WebRTC Actually Is: Browser-to-Browser, No Server in the Data Path

Every other topic in this group assumes a request goes to a server. WebRTC (Web Real-Time Communication) is different: it's a browser API for establishing a **direct peer-to-peer connection** between two browsers (or a browser and another WebRTC-capable endpoint), so audio, video, or arbitrary data can flow between them without transiting an application server at all.

```
Normal request:     Browser A  <──HTTP──>  Server
WebRTC (after setup): Browser A  <──direct P2P──>  Browser B
```

This matters for latency (no server hop for the actual media/data) and for server cost (the server doesn't have to relay potentially large volumes of audio/video traffic) — but it also means the two peers need a way to find and describe themselves to each other *before* a direct connection exists, which is the core problem the rest of this topic solves.

## 2. Signaling: WebRTC Deliberately Doesn't Define It

Before two peers can connect directly, they need to exchange:
- **Session descriptions** (SDP — what media/data they want to send, codecs supported, etc.)
- **Network candidates** (ICE candidates — see §3, the actual IP/port combinations each peer might be reachable at)

This exchange is called **signaling**, and the WebRTC spec deliberately does **not** define how it happens — it only defines the browser-side API for *producing* and *consuming* these messages (`RTCPeerConnection.createOffer()`, `.setLocalDescription()`, etc.). The actual transport for getting an offer from Peer A to Peer B is entirely up to the application: commonly a WebSocket connection to a signaling server, but it could just as well be a REST API with polling, or even manually copy-pasted text for a toy demo.

```js
// Peer A: create an offer and send it to Peer B via SOME signaling channel
// (here, an already-connected WebSocket to a signaling server)
const pc = new RTCPeerConnection({ iceServers: [{ urls: "stun:stun.example.com:19302" }] });
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signalingSocket.send(JSON.stringify({ type: "offer", sdp: offer }));

// Peer B: receives the offer over the signaling channel, responds with an answer
pc.setRemoteDescription(receivedOffer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
signalingSocket.send(JSON.stringify({ type: "answer", sdp: answer }));
```

Once both sides have exchanged descriptions and enough ICE candidates, the signaling channel's job is done — the actual media/data connection that follows is direct, peer-to-peer, and independent of it.

## 3. ICE, STUN, and TURN: Getting Through NATs

Almost every real device sits behind Network Address Translation (NAT) — it doesn't have a publicly routable IP address of its own. Two peers behind two different NATs can't simply connect to each other's private IPs; they need to discover what address is actually reachable from the outside. This discovery-and-connection process is **ICE** (Interactive Connectivity Establishment), and it uses two kinds of helper servers:

- **STUN** (Session Traversal Utilities for NAT): a lightweight server a peer asks "what's my public IP and port, as seen from outside my NAT?" The peer includes this reflexive address as one of its **ICE candidates** — a possible way to reach it.
- **TURN** (Traversal Using Relays around NAT): when direct connectivity genuinely isn't possible (symmetric NATs, restrictive firewalls), a TURN server acts as a relay — both peers connect *to the TURN server*, which forwards traffic between them. This sacrifices the "no server in the data path" property but guarantees connectivity as a fallback.

```js
const pc = new RTCPeerConnection({
  iceServers: [
    { urls: "stun:stun.example.com:19302" },              // try direct first
    { urls: "turn:turn.example.com:3478", username: "u", credential: "p" }, // fallback relay
  ],
});
```

Each peer gathers multiple ICE candidates (its local network interface address, its STUN-reflexive public address, and a TURN-relayed address if configured), exchanges the full candidate list with the other peer over signaling, and ICE tries each pairing until it finds one that actually works — preferring the most direct path (local > STUN-reflexive > TURN-relayed) since each step down adds latency and, for TURN, server bandwidth cost.

![Peers exchange SDP offers and answers through a signaling server, discover public addresses with STUN, and connect directly or through a TURN relay](diagrams/01-signaling-ice.svg)

## 4. Data Channels vs. Media Streams

Once a peer connection is established, it can carry two different kinds of content:

- **Media streams** (`MediaStreamTrack`, via `getUserMedia()` for camera/mic, added to the connection with `pc.addTrack()`) — audio/video, the classic video-call use case.
- **Data channels** (`pc.createDataChannel()`) — arbitrary binary/text messages over the same peer connection, usable for anything (game state sync, file transfer, chat) without any media involved at all.

```js
const channel = pc.createDataChannel("chat");
channel.onopen = () => channel.send("hello peer");
channel.onmessage = (event) => console.log("received:", event.data);
```

Data channels are built on **SCTP** (layered over the same encrypted transport as media — see §5), and can be configured ordered or unordered, reliable or unreliable (similar tradeoffs to TCP vs UDP), which is one reason WebRTC data channels are sometimes chosen over WebSockets for latency-sensitive use cases like real-time multiplayer games.

## 5. Security Surface: Encryption Is Mandatory, but IP Exposure Is a Real Concern

**Encryption**: unlike plain WebSockets (covered in the next topic, where TLS is optional/`wss://` vs `ws://`), WebRTC **mandates** encryption for all media and data channel traffic (DTLS for the handshake, SRTP for media) — there is no unencrypted WebRTC transport mode. This is a spec-level requirement, not an opt-in.

**IP address leakage**: this is WebRTC's most-cited privacy concern. Because ICE candidate gathering involves discovering a peer's local network interface addresses (and, via STUN, their public IP), a malicious or tracking-focused webpage can use WebRTC's candidate-gathering step to learn a visitor's real IP address — **even if that visitor is behind a VPN** that would otherwise hide it from ordinary HTTP requests. This works because ICE candidate gathering happens at the browser/OS network-stack level, independent of whatever the page's outbound HTTP traffic is routed through.

```js
// A page can trigger ICE gathering with no actual peer on the other end,
// purely to observe the candidates and extract IP addresses:
const pc = new RTCPeerConnection({ iceServers: [{ urls: "stun:stun.example.com:19302" }] });
pc.createDataChannel(""); // needed to trigger candidate gathering at all
pc.onicecandidate = (event) => {
  if (event.candidate) console.log("leaked candidate:", event.candidate.candidate); // may contain real local/public IPs
};
pc.createOffer().then((offer) => pc.setLocalDescription(offer));
```

This is exactly why privacy-focused browsers and browser extensions offer a "disable WebRTC IP leak" setting, and why some VPN providers specifically warn about it — it's not a bug in any individual site, it's an inherent consequence of what ICE candidate gathering has to expose to function at all.

## Common Pitfalls

1. **Assuming WebRTC defines its own signaling transport.** It doesn't — you must build or choose one (commonly WebSockets) yourself; "WebRTC isn't connecting" is very often actually a signaling-delivery bug, not a peer-connection bug.
2. **Forgetting a TURN server fallback in production.** STUN-only setups fail for a meaningful fraction of real-world users behind symmetric NATs or restrictive corporate firewalls — TURN is not optional for a production-quality connection rate, even though it's "just a fallback."
3. **Treating the data channel as guaranteed-ordered/reliable by default without checking.** `createDataChannel` accepts options (`ordered`, `maxRetransmits`) that change these guarantees — the defaults are ordered+reliable (TCP-like), but many latency-sensitive use cases deliberately want unordered/unreliable (UDP-like) and must opt in explicitly.
4. **Not accounting for the IP-leakage privacy surface** when embedding any third-party WebRTC-based widget (video chat, VoIP) on a page — the widget's ICE gathering can expose visitor IPs regardless of what the rest of the page's network security posture looks like.
5. **Confusing "encrypted" with "the media path stays private from the app's own servers."** DTLS/SRTP encrypt the peer-to-peer transport itself, but the application still controls the signaling channel and can see SDP/candidate metadata — encryption protects the wire, not the application layer's visibility into connection setup.

## Quick Reference

```
WebRTC = direct browser-to-browser (P2P) audio/video/data, no server in the data path
Signaling = NOT defined by WebRTC — you choose the transport (commonly WebSockets)
  RTCPeerConnection.createOffer/createAnswer/setLocalDescription/setRemoteDescription

ICE = the process of finding a working address pair between two peers
  STUN = "what's my public IP?" (lightweight, no relay)
  TURN = relay server fallback when direct connectivity is impossible

MediaStreamTrack  -> audio/video (getUserMedia + pc.addTrack)
RTCDataChannel    -> arbitrary data, ordered/reliable configurable, SCTP-based

Security: DTLS/SRTP encryption is MANDATORY, not optional
Privacy risk: ICE candidate gathering can leak real local/public IPs, even behind a VPN
```
