Phân tích toàn bộ WebRTC protocol từ lớp network đến JavaScript API — dành cho bạn muốn hiểu tại sao chứ không chỉ biết cách dùng. Tài liệu offline, không cần internet.
Toàn bộ quá trình WebRTC — từ lúc bắt đầu đến khi media chạy (click vào từng phase)
WebRTC (Web Real-Time Communication) là tập hợp các API và protocol mở cho phép trình duyệt trao đổi audio, video, và dữ liệu tùy ý trực tiếp với nhau — không cần plugin, không cần server trung gian cho media. Được chuẩn hóa bởi W3C và IETF, WebRTC được Chrome, Firefox, Safari, Edge hỗ trợ đầy đủ.
Kiến trúc tổng thể
Tại sao WebRTC phức tạp?
Ba thách thức cốt lõi:
- NAT traversal — hầu hết thiết bị không có IP công khai, nằm sau NAT. WebRTC phải "đục lỗ" qua NAT để 2 peer tìm thấy nhau.
- Bảo mật bắt buộc — DTLS và SRTP không thể tắt. Mọi kết nối WebRTC đều phải mã hóa. Đây là yêu cầu của spec, không phải tùy chọn.
- Codec negotiation — mỗi browser/thiết bị hỗ trợ codec khác nhau. SDP offer/answer là quá trình "thương lượng" codec, resolution, bitrate.
NAT (Network Address Translation) là kỹ thuật cho phép nhiều thiết bị trong mạng LAN
chia sẻ một IP công khai. Router NAT duy trì một bảng ánh xạ
(private IP, private port) ↔ (public IP, public port).
Tại sao NAT chặn WebRTC
Hai peer đằng sau NAT — không thể kết nối trực tiếp nếu không có STUN/ICE
4 loại NAT và khả năng traversal
| Loại NAT | Quy tắc inbound | ICE vượt được? |
|---|---|---|
| Full Cone | Bất kỳ ai biết public IP:port đều gửi được vào | Có — dùng srflx |
| Restricted Cone | Chỉ IP đã từng gửi outbound mới vào được | Có — ICE tạo "hole" trước |
| Port Restricted | Chỉ IP:port đã từng gửi outbound mới vào được | Có — ICE tạo "hole" trước |
| Symmetric | Mỗi đích đến nhận port ngoại khác nhau; không đoán được port | Khó — cần TURN relay |
Hole Punching — kỹ thuật vượt NAT
ICE dùng kỹ thuật hole punching: cả 2 peer gửi packet đến nhau đồng thời. Packet outbound từ A tạo entry trong NAT table của Router A, cho phép packet từ B qua Router A đến A.
Ví dụ từng bước — Port Restricted Cone NAT (phổ biến nhất)
Ví dụ thực tế — Port Restricted Cone NAT:
Signaling đã trao đổi srflx:
A biết B tại: 5.6.7.8:62100
B biết A tại: 1.2.3.4:48291
T=0ms A gửi: src=1.2.3.4:48291 dst=5.6.7.8:62100
Router A tạo: {192.168.1.5:5000 → 5.6.7.8:62100} ← NAT entry A
Packet đến Router B → DROP (chưa có entry cho 1.2.3.4:48291)
T=0ms B gửi: src=5.6.7.8:62100 dst=1.2.3.4:48291
Router B tạo: {10.0.0.8:6000 → 1.2.3.4:48291} ← NAT entry B
Packet đến Router A → DROP (chưa có entry cho 5.6.7.8:62100)
T=1ms Packet của A đến Router B lần 2 (ICE retransmit)
Router B tra bảng: có entry cho 1.2.3.4:48291 → FORWARD → Peer B nhận được ✓
T=1ms Packet của B đến Router A lần 2 (ICE retransmit)
Router A tra bảng: có entry cho 5.6.7.8:62100 → FORWARD → Peer A nhận được ✓
→ STUN Binding Response gửi về → ICE marks pair as "valid" → Nomination → P2P ✓
ICE (Interactive Connectivity Establishment — RFC 8445) là framework tổng hợp giải quyết bài toán NAT traversal. ICE không phải một protocol đơn lẻ mà là quy trình phối hợp STUN, TURN, và hole punching để tìm đường kết nối tốt nhất.
Ba giai đoạn ICE
Giai đoạn 1 — Candidate Gathering
Mỗi peer thu thập tất cả địa chỉ có thể kết nối — gọi là candidates:
Mỗi candidate có priority số — host > srflx > relay. ICE thử candidates theo thứ tự priority giảm dần.
Ba loại ICE candidate — priority từ cao xuống thấp, và ma trận candidate pairs
Giai đoạn 2 — Connectivity Checks
Hai peer trao đổi candidate lists qua signaling, sau đó tạo tất cả candidate pairs (candidate của A × candidate của B). Mỗi pair được check bằng STUN Binding Request theo cả 2 chiều.
Peer A candidates: [host-A, srflx-A, relay-A] Peer B candidates: [host-B, srflx-B, relay-B] Candidate pairs (sắp theo priority): 1. host-A ↔ host-B (ưu tiên cao nhất) 2. host-A ↔ srflx-B 3. srflx-A ↔ host-B 4. host-A ↔ relay-B 5. srflx-A ↔ srflx-B ... Mỗi pair: A gửi STUN request → B, B gửi STUN request → A Pair nào nhận được response hợp lệ → "valid pair"
Giai đoạn 3 — Nomination
Controlling peer (bên tạo offer) chọn valid pair tốt nhất và gửi
STUN request với flag USE-CANDIDATE. Đây là "nominated pair" — traffic
chuyển sang pair này.
Trickle ICE — tại sao quan trọng
Trickle ICE cho phép gửi candidates ngay khi tìm thấy, không cần chờ gather xong tất cả. Điều này giảm thời gian kết nối đáng kể vì:
Không có Trickle (slow)
- Gather tất cả candidates (có thể mất 2–5s)
- Gửi toàn bộ qua signaling
- Bắt đầu checks
Trickle ICE (fast)
- Tìm được host candidate → gửi ngay
- Check bắt đầu ngay, song song với gathering
- Thường connected trước khi gather xong
a=ice-options:trickle.
Candidates được gửi qua signaling channel dưới dạng các message riêng biệt, không cần gửi lại SDP.
ICE Restart
Khi kết nối bị disconnected (ví dụ đổi WiFi sang 4G), ICE có thể
restart bằng cách tạo offer mới với iceRestart: true.
Credentials ICE mới được tạo, quá trình gathering/checking lặp lại.
Media stream không bị gián đoạn nếu restart thành công đủ nhanh.
STUN (RFC 8489) là protocol đơn giản: client gửi request đến STUN server, server trả về IP:port mà server thấy — tức là IP:port công khai sau NAT.
[Peer A] [STUN Server]
private: 192.168.1.5:52341
public: ???
| |
|-- STUN Binding Request ------->|
| | Server thấy packet đến từ:
|<-- STUN Binding Response ------| 203.0.113.1:48291
| XOR-MAPPED-ADDRESS: |
| 203.0.113.1:48291 |
| |
→ Peer A biết public IP:port của mình là 203.0.113.1:48291
→ Tạo srflx candidate: "candidate:2 1 UDP ... 203.0.113.1 48291 typ srflx"
STUN Message Format
| Field | Size | Mô tả |
|---|---|---|
Message Type | 2 bytes | Binding Request (0x0001) hoặc Binding Response (0x0101) |
Message Length | 2 bytes | Độ dài phần attributes |
Magic Cookie | 4 bytes | Cố định: 0x2112A442 — dùng để phân biệt STUN với RTP |
Transaction ID | 12 bytes | Random ID để match request với response |
Attributes | variable | Key-value: XOR-MAPPED-ADDRESS, USERNAME, MESSAGE-INTEGRITY... |
STUN cho ICE Connectivity Checks
STUN không chỉ dùng để discover IP. Trong ICE, STUN Binding Request còn là
cơ chế connectivity check — peer gửi STUN request có chứa
USERNAME (ice-ufrag kết hợp) và MESSAGE-INTEGRITY (HMAC-SHA1).
Điều này vừa verify kết nối, vừa authenticate peer đang kết nối.
stun:stun.l.google.com:19302 — Google cung cấp free cho development.
Production nên tự host hoặc dùng dịch vụ như Twilio / Cloudflare Calls để có SLA.
TURN (RFC 8656) là fallback cuối cùng khi direct P2P và STUN-based connection đều thất bại. TURN server đứng giữa, relay toàn bộ traffic. Peer gửi data đến TURN, TURN forward đến peer kia.
[Peer A] [TURN Server] [Peer B]
| | |
|-- Allocate Request --→ | |
|←- Allocate Response -- | relay addr: 1.2.3.4:50000|
|-- CreatePermission --> | (allow B to send) |
| |←-- Connect/Send from B --|
|←- Data from B ---------| |
|-- Send to B --------→ |-- Forward to B ----------→|
Mọi packet đều đi qua TURN server.
Không còn P2P, nhưng luôn hoạt động kể cả Symmetric NAT.
Khi nào cần TURN?
- Một hoặc cả hai peer đằng sau Symmetric NAT
- Mạng doanh nghiệp có firewall chặn UDP outbound
- Carrier-grade NAT (CGN) trên mạng mobile 4G/5G
Chi phí TURN
Latency
Thêm 1 hop → tăng RTT. Nếu TURN server đặt gần người dùng, overhead chỉ ~10–30ms thêm vào.
Bandwidth
TURN server phải xử lý toàn bộ traffic media. 1 video call 1080p30 ≈ 3–5 Mbps qua TURN.
Cấu hình ICEServer với TURN
{
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
{
urls: "turn:turn.example.com:3478",
username: "user",
credential: "password"
},
{
urls: "turns:turn.example.com:5349", // TURN over TLS (TCP)
username: "user",
credential: "password"
}
]
}
SDP (RFC 8866) là định dạng text mô tả "tôi có thể làm gì" —
không phải protocol truyền tải, chỉ là metadata được trao đổi qua signaling.
SDP dùng format type=value, mỗi dòng 1 attribute.
SDP Offer thực tế — annotated
Offer/Answer Model
SDP negotiation dùng mô hình Offer/Answer (RFC 3264):
- Peer A gọi
createOffer()→ browser tạo SDP liệt kê tất cả capability - Peer A gọi
setLocalDescription(offer)→ ICE gathering bắt đầu - Peer A gửi offer qua signaling đến Peer B
- Peer B gọi
setRemoteDescription(offer) - Peer B gọi
createAnswer()→ chọn subset capability từ offer - Peer B gọi
setLocalDescription(answer) - Peer B gửi answer về Peer A
- Peer A gọi
setRemoteDescription(answer)→ negotiation hoàn tất
setLocalDescription trước khi ICE gathering,
setRemoteDescription trước khi createAnswer.
Sai thứ tự → InvalidStateError.
Signaling là quá trình 2 peer trao đổi thông tin để thiết lập kết nối — cụ thể là SDP offer/answer và ICE candidates. WebRTC chủ động không định nghĩa cơ chế signaling.
Signaling Sequence Diagram — toàn bộ flow
Từ WebSocket connect đến P2P video stream — mọi message theo đúng thứ tự thực tế
Demo App Architecture
Kiến trúc hai giai đoạn của ứng dụng demo
Signaling chỉ truyền 2 loại data
SDP Offer / Answer
Chuỗi text mô tả capability. Gửi 1 lần duy nhất khi bắt đầu negotiation.
{
"type": "offer",
"sdp": "v=0\r\no=-..."
}
ICE Candidates
Địa chỉ mạng tìm được. Gửi từng cái khi tìm thấy (Trickle ICE).
{
"type": "ice",
"candidate": {
"candidate": "candidate:...",
"sdpMid": "0"
}
}
Signaling server tối giản — WebSocket relay
// server.js — 30 dòng là đủ
const http = require('http');
const { WebSocketServer } = require('ws');
const server = http.createServer((req, res) => {
// serve static files
});
const wss = new WebSocketServer({ server });
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
ws.on('message', (data) => {
// forward message đến tất cả clients còn lại
for (const client of clients) {
if (client !== ws && client.readyState === 1) {
client.send(data);
}
}
});
ws.on('close', () => clients.delete(ws));
});
server.listen(3000);
Sau khi signaling xong
Khi iceConnectionState === "connected", media flow đi P2P.
Signaling server không liên quan đến media nữa.
Có thể đóng WebSocket connection mà video stream vẫn chạy bình thường.
DTLS (Datagram TLS — RFC 6347) là TLS chạy trên UDP. Sau khi ICE tìm được candidate pair, DTLS handshake thiết lập channel mã hóa và xác thực 2 peer với nhau.
DTLS vs TLS
| TLS | DTLS | |
|---|---|---|
| Transport | TCP (reliable) | UDP (unreliable) |
| Retransmission | TCP tự handle | DTLS tự retransmit handshake packets |
| Record layer | Sequential | Có epoch + sequence number để handle reorder |
| Latency | Cao hơn (TCP 3-way handshake) | Thấp hơn (UDP) |
DTLS Handshake Flow
Peer A (DTLS Client) Peer B (DTLS Server)
| |
|--- ClientHello ------------------>| (cipher suites, random)
|<-- HelloVerifyRequest ------------| (cookie, tránh DoS)
|--- ClientHello (with cookie) ---->|
|<-- ServerHello --------------------| (chosen cipher)
|<-- Certificate --------------------| (self-signed cert)
|<-- ServerKeyExchange --------------| (ECDH params)
|<-- CertificateRequest ------------|
|<-- ServerHelloDone ----------------|
|--- Certificate ------------------->| (Peer A's self-signed cert)
|--- ClientKeyExchange ------------->| (ECDH public key)
|--- CertificateVerify ------------->| (signature với private key)
|--- ChangeCipherSpec -------------->|
|--- Finished ---------------------->| (HMAC của toàn bộ handshake)
|<-- ChangeCipherSpec ---------------|
|<-- Finished -----------------------|
| |
|====== DTLS Channel Established =====|
Xác thực Certificate qua Fingerprint
Browser tự tạo self-signed certificate cho mỗi RTCPeerConnection — không cần CA (Certificate Authority). Certificate được xác thực qua fingerprint trong SDP:
- Peer A tạo self-signed cert, hash nó → fingerprint
- Đặt fingerprint vào SDP:
a=fingerprint:sha-256 4A:3B:... - SDP được gửi qua signaling channel (WebSocket — có thể trust)
- Trong DTLS handshake, Peer A gửi cert
- Peer B verify: hash của cert nhận được phải khớp fingerprint trong SDP
- Nếu không khớp → reject kết nối (man-in-the-middle attack)
Media (audio/video) không đi qua DTLS channel mà đi qua SRTP (RFC 3711). Lý do: DTLS record layer có overhead lớn, không phù hợp cho hàng nghìn packets/giây của video.
DTLS-SRTP Key Derivation
Keys cho SRTP được derive từ DTLS handshake qua cơ chế DTLS-SRTP (RFC 5764):
DTLS Master Secret
|
↓ DTLS-SRTP key derivation (RFC 5705 exporter)
|
┌────┴────────────────────────────────┐
│ Client SRTP Master Key (16 bytes) │
│ Server SRTP Master Key (16 bytes) │
│ Client SRTP Master Salt (14 bytes) │
│ Server SRTP Master Salt (14 bytes) │
└─────────────────────────────────────┘
|
↓ KDF (AES Counter Mode)
|
┌────┴──────────────────────────────────────┐
│ Cipher Key (AES-128-CM hoặc AES-256-CM) │
│ Authentication Key (HMAC-SHA1, 160-bit) │
│ Salting Key (thêm vào IV) │
└────────────────────────────────────────────┘
SRTP Packet Structure
┌─────────────────────────────────────────────────────┐ │ RTP Header (12+ bytes) — KHÔNG MÃ HÓA │ │ [V=2][P][X][CC][M][PT][Sequence Number][Timestamp] │ │ [SSRC] [CSRC list] │ ├─────────────────────────────────────────────────────┤ │ RTP Payload — MÃ HÓA (AES-128-CTR) │ │ (H264 NAL units, VP8 frames, Opus frames...) │ ├─────────────────────────────────────────────────────┤ │ Authentication Tag (HMAC-SHA1, 80-bit) — THÊM VÀO │ └─────────────────────────────────────────────────────┘
- Sequence Number trong header không mã hóa → receiver có thể detect reorder/replay
- Payload mã hóa bằng AES-CTR → TURN server không đọc được nội dung
- Auth Tag verify integrity — detect packet tampering
SRTP vs DTLS cho Media
Nếu dùng DTLS cho media
- Record layer overhead: ~13 bytes/packet
- Phải track sequence riêng
- Fragmentation phức tạp
- ~1000 packets/s × 13 bytes = 13 KB/s overhead không cần thiết
DTLS-SRTP (thực tế)
- DTLS chỉ dùng để exchange keys 1 lần
- SRTP dùng RTP header sẵn có
- Overhead chỉ thêm auth tag 10 bytes
- Latency thấp, throughput cao
Codec negotiation xác định ngôn ngữ chung giữa 2 peer. Offerer liệt kê tất cả codec hỗ trợ theo thứ tự ưu tiên. Answerer chọn codec đầu tiên trong danh sách mà cả hai đều hỗ trợ.
Video Codecs
Audio Codecs
| Codec | Bitrate | Đặc điểm |
|---|---|---|
| Opus | 6–510 kbps | Bắt buộc trong WebRTC. Adaptive bitrate, FEC, DTX. Tốt nhất cho mọi use case. |
| G.711 (PCMU/PCMA) | 64 kbps | Legacy, dùng cho interop với SIP/PSTN. |
| G.722 | 64 kbps | Wideband, chất lượng tốt hơn G.711. |
Force codec trong JavaScript
// Ưu tiên H264 cho drone streaming
async function preferH264(pc) {
const offer = await pc.createOffer();
const sdp = offer.sdp.replace(
/m=video (\d+) UDP\/TLS\/RTP\/SAVPF ([\d ]+)/,
(match, port, payloads) => {
const lines = offer.sdp.split('\n');
// tìm payload type của H264
const h264 = lines
.filter(l => l.includes('H264'))
.map(l => l.match(/a=rtpmap:(\d+)/)?.[1])
.filter(Boolean);
const rest = payloads.split(' ').filter(p => !h264.includes(p));
return `m=video ${port} UDP/TLS/RTP/SAVPF ${[...h264, ...rest].join(' ')}`;
}
);
await pc.setLocalDescription({ type: 'offer', sdp });
}
DataChannel cho phép truyền arbitrary data (text, binary) qua WebRTC. Không dùng RTP — thay vào đó dùng SCTP (RFC 4960) chạy trên DTLS channel.
SCTP vs TCP vs UDP
| Tính năng | TCP | UDP | SCTP (DataChannel) |
|---|---|---|---|
| Ordered delivery | Luôn có | Không | Tùy chọn |
| Reliable delivery | Luôn có | Không | Tùy chọn |
| Multi-stream | Không | Không | Có — nhiều channels, không block nhau |
| Head-of-line blocking | Có (giữa messages) | Không | Không (giữa channels) |
| Partial reliability | Không | Không | Có — maxRetransmits / maxPacketLifeTime |
| Message boundaries | Stream, không có boundary | Có | Có — message framing |
Các chế độ DataChannel
// Reliable + Ordered (mặc định) — dùng cho: chat, commands, file transfer
const channel = pc.createDataChannel("chat");
// Unreliable + Unordered — dùng cho: game state, telemetry (giống UDP)
const channel = pc.createDataChannel("telemetry", {
ordered: false,
maxRetransmits: 0
});
// Reliable nhưng có timeout — dùng cho: sensor data có deadline
const channel = pc.createDataChannel("sensor", {
ordered: false,
maxPacketLifeTime: 100 // ms — discard nếu chưa gửi được sau 100ms
});
File Transfer qua DataChannel
// Sender
async function sendFile(channel, file) {
const CHUNK = 16384; // 16KB — max chunk size an toàn
channel.send(JSON.stringify({ type:'file-start', name:file.name, size:file.size }));
const buffer = await file.arrayBuffer();
for (let offset = 0; offset < buffer.byteLength; offset += CHUNK) {
// throttle nếu buffer đầy
if (channel.bufferedAmount > channel.bufferedAmountLowThreshold) {
await new Promise(r => channel.onbufferedamountlow = r);
}
channel.send(buffer.slice(offset, offset + CHUNK));
}
channel.send(JSON.stringify({ type:'file-end' }));
}
// Receiver
let chunks = [], meta = null;
channel.onmessage = (e) => {
if (typeof e.data === 'string') {
const msg = JSON.parse(e.data);
if (msg.type === 'file-start') meta = msg;
if (msg.type === 'file-end') {
const blob = new Blob(chunks);
const url = URL.createObjectURL(blob);
const a = Object.assign(document.createElement('a'), { href:url, download:meta.name });
a.click();
chunks = []; meta = null;
}
} else {
chunks.push(e.data); // ArrayBuffer chunk
}
};
bufferedAmount
sẽ làm tràn buffer → browser crash hoặc data loss.
Luôn throttle bằng bufferedAmountLowThreshold.
RTCPeerConnection.getStats() trả về Map của hàng chục
RTCStats objects, mỗi object là một "report" về một khía cạnh
của kết nối. Đây là công cụ chẩn đoán mạnh nhất của WebRTC.
Các report type quan trọng
| Report Type | Metrics quan trọng | Dùng để |
|---|---|---|
| inbound-rtp | bytesReceived, packetsLost, jitter, framesDecoded, framesPerSecond |
Tính bitrate nhận, packet loss %, framerate thực tế |
| outbound-rtp | bytesSent, packetsSent, retransmittedPacketsSent, qualityLimitationReason |
Bitrate gửi, retransmit rate, lý do giảm chất lượng |
| candidate-pair | currentRoundTripTime, availableOutgoingBitrate, bytesDiscardedOnSend |
RTT, estimated bandwidth, dropped packets |
| local-candidate | candidateType, ip, port, protocol |
Xem đang dùng host/srflx/relay, UDP hay TCP |
| codec | mimeType, clockRate, sdpFmtpLine |
Verify codec đang dùng (video/H264, audio/opus) |
| media-source | frameWidth, frameHeight, framesPerSecond |
Resolution và fps của local camera/source |
Tính bitrate thực tế
let prevBytes = 0, prevTime = 0;
setInterval(async () => {
const stats = await pc.getStats();
const now = Date.now();
for (const [, report] of stats) {
if (report.type === 'inbound-rtp' && report.kind === 'video') {
const bytes = report.bytesReceived;
const dt = (now - prevTime) / 1000; // seconds
const bitrateKbps = ((bytes - prevBytes) * 8 / dt / 1000).toFixed(1);
console.log(`Video bitrate: ${bitrateKbps} kbps`);
console.log(`Packet loss: ${report.packetsLost}`);
console.log(`Jitter: ${(report.jitter * 1000).toFixed(1)} ms`);
console.log(`FPS: ${report.framesPerSecond}`);
prevBytes = bytes;
prevTime = now;
}
if (report.type === 'candidate-pair' && report.nominated) {
console.log(`RTT: ${(report.currentRoundTripTime * 1000).toFixed(0)} ms`);
console.log(`Available bandwidth: ${(report.availableOutgoingBitrate/1000).toFixed(0)} kbps`);
}
}
}, 1000);
qualityLimitationReason — tại sao video giảm chất lượng
| Value | Nghĩa |
|---|---|
"none" | Đang gửi ở chất lượng tối đa |
"bandwidth" | Mạng không đủ — encoder giảm bitrate/resolution |
"cpu" | CPU không đủ — encoder giảm fps/resolution |
"other" | Lý do khác (ví dụ: encoder constraint) |
Connection State Machine
RTCPeerConnection state machine — tất cả transitions và nguyên nhân
Theo dõi bằng pc.oniceconnectionstatechange và pc.onconnectionstatechange.
Hai event này độc lập — ICE có thể connected trong khi DTLS vẫn đang handshake.
chrome://webrtc-internals —
hiển thị toàn bộ getStats() theo real-time với biểu đồ. Công cụ chẩn đoán mạnh nhất
để phân tích WebRTC issues trong production.
Các điểm cốt lõi cần nhớ
Bản chắt lọc từ 12 sections — những điểm hay bị nhầm, hay gặp trong interviews và hay phát sinh bug trong production WebRTC.
Connection Flow
- Thứ tự: getUserMedia → createOffer → setLocalDescription → ICE gathering → signaling → setRemoteDescription → DTLS → SRTP
- ICE gathering bắt đầu ngay khi setLocalDescription được gọi — không phải sau
- ICE connected ≠ DTLS complete — hai state machine độc lập, dùng cả
oniceconnectionstatechangevàonconnectionstatechange - Trickle ICE: gửi candidates ngay khi gather được → giảm latency đáng kể vs batch
NAT & ICE
- 4 loại NAT: Full Cone, Restricted, Port Restricted, Symmetric — chỉ Symmetric không thể hole-punch
- Candidate priority: host > srflx > relay — ICE chọn pair có priority tổng cao nhất
- Symmetric NAT → STUN không đủ → bắt buộc TURN relay
- TURN credentials: dùng time-limited HMAC token (RFC 5766) — không expose static credentials
- ICE restarts khi network thay đổi —
pc.restartIce()
SDP
- SDP = negotiate parameters, không truyền data — chỉ là text description
a=fingerprinttrong SDP → verify DTLS certificate (chống MITM)sdpMid+sdpMLineIndex: định vị candidate thuộc media line nào- Offer/answer phải match: codec PT, direction (sendrecv/sendonly/recvonly), ice-ufrag/ice-pwd
- Unified Plan (mặc định): mỗi track = 1 m-line; Plan B (deprecated): nhiều tracks/m-line
DTLS & SRTP
- DTLS chạy sau ICE connected, trước khi media flow — không bỏ qua được
- DTLS fingerprint = SHA-256 của cert, so sánh với
a=fingerprinttrong SDP → chống MITM - SRTP key material được derive từ DTLS handshake (DTLS-SRTP, RFC 5763)
- RTCP-MUX: RTP và RTCP dùng chung port → giảm số port cần mở qua NAT
- Cipher suites: AES-128-GCM (mới) hoặc AES-128-CM-HMAC-SHA1-80 (legacy)
Codec & DataChannel
- Opus bắt buộc cho audio (RFC 7874) — tất cả browsers support
- Video codecs: VP8/VP9/H.264/AV1 — dùng
RTCRtpSender.getCapabilities('video')để kiểm tra - DataChannel = SCTP trên DTLS —
ordered: false+maxRetransmits: 0cho unreliable (gaming) - DataChannel không cần media tracks — có thể dùng độc lập
- Simulcast: gửi nhiều resolutions (rid: low/mid/high) → receiver chọn layer
Debugging & Stats
- chrome://webrtc-internals — real-time graphs, ICE candidates, SDP history — công cụ mạnh nhất
RTCInboundRtpStreamStats:jitter,packetsLost,bytesReceived,framesDecoded- Packet loss =
packetsLost / (packetsReceived + packetsLost)— >5% là degraded RTCIceCandidatePairStats:currentRoundTripTime,availableOutgoingBitrate- Jitter target < 30ms cho audio — jitter buffer sẽ tăng delay khi jitter cao
- Tại sao cần TURN server? — Symmetric NAT block hole-punching; TURN relay traffic qua server, đảm bảo kết nối được nhưng tốn bandwidth
- SDP offer/answer exchange xảy ra ở đâu? — Qua signaling channel tùy chọn (WebSocket, HTTP) — WebRTC không định nghĩa signaling protocol
- Khác nhau giữa ICE connected và connection state connected? — ICE connected = transport layer OK; connection state connected = ICE + DTLS đều xong
- Tại sao DTLS fingerprint quan trọng? — Chống MITM attack: attacker có thể relay ICE nhưng không thể forge certificate khớp fingerprint trong SDP
- DataChannel có thể dùng mà không cần video/audio không? — Có — createDataChannel() tạo RTCPeerConnection chỉ với SCTP transport, không cần addTrack()