Skip to main content

RFC 8835 - Transports for WebRTC

基本信息

  • RFC编号: 8835
  • 标题: Transports for WebRTC
  • 中文标题: WebRTC的传输层
  • 发布日期: 2021年1月
  • 状态: PROPOSED STANDARD (提案标准)
  • 作者: H. Alvestrand (Google)

摘要 (Abstract)

本规范描述了WebRTC使用的传输协议栈,包括ICE、DTLS、SRTP等核心传输组件。WebRTC通过UDP传输媒体和数据,使用ICE进行NAT穿透,使用DTLS建立安全连接,使用SRTP加密媒体流。

WebRTC传输栈概述

完整协议栈

WebRTC协议栈 (从上到下):

┌─────────────────────────────────────────┐
│ 应用层 (Application) │
│ - 音频/视频流 │
│ - 数据通道 (DataChannel) │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ RTP/RTCP (实时传输协议) │
│ - 媒体传输 │
│ - 控制信息 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ SRTP/SRTCP (安全RTP) │
│ - 加密媒体流 │
│ - 认证 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ DTLS (数据报TLS) │
│ - 密钥协商 │
│ - 端到端加密 │
│ - 证书验证 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ ICE (交互式连接建立) │
│ - NAT穿透 │
│ - 候选地址收集 │
│ - 连接性检查 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ UDP (用户数据报协议) │
│ - 底层传输 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ IP (互联网协议) │
│ - IPv4 / IPv6 │
└─────────────────────────────────────────┘

数据通道额外栈:

┌─────────────────────────────────────────┐
│ DataChannel API │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ SCTP (流控制传输协议) │
│ - 可靠传输 │
│ - 消息边界 │
└─────────────────────────────────────────┘

┌─────────────────────────────────────────┐
│ DTLS │
└─────────────────────────────────────────┘

(ICE + UDP + IP)

为什么选择UDP?

UDP vs TCP for WebRTC:

TCP的问题:
❌ 队头阻塞 (Head-of-Line Blocking)
- 一个丢包阻塞整个流
- 实时音视频不可接受

❌ 连接建立慢
- 三次握手延迟
- TLS握手延迟

❌ 拥塞控制不灵活
- 固定算法
- 无法针对实时媒体优化

UDP的优势:
✓ 无队头阻塞
- 独立数据包
- 丢包不影响后续包

✓ 低延迟
- 无需等待确认
- 立即发送

✓ 灵活控制
- 应用层拥塞控制
- 针对媒体优化

WebRTC选择:
UDP + 应用层可靠性
→ 媒体用SRTP (不可靠)
→ 数据用SCTP (可靠可选)

ICE (Interactive Connectivity Establishment)

ICE的作用

问题: NAT穿透

场景:
用户A (192.168.1.100) ← NAT1 ← 互联网 → NAT2 → 用户B (192.168.1.200)
私有IP 私有IP

问题:
- A不知道如何到达B的私有IP
- B不知道如何到达A的私有IP
- NAT阻止外部连接

ICE解决方案:
1. 收集候选地址 (Candidates)
- Host候选: 本地IP
- Server Reflexive候选: NAT公网IP (通过STUN)
- Relay候选: 中继地址 (通过TURN)

2. 交换候选地址 (通过信令)

3. 连接性检查 (Connectivity Checks)
- 尝试所有候选对
- 找到最佳路径

4. 建立连接

候选地址类型

// Host候选 (本地地址)
{
type: 'host',
ip: '192.168.1.100',
port: 54321,
protocol: 'udp',
priority: 2130706431,
foundation: '1',
component: 1
}

// Server Reflexive候选 (STUN获取的公网地址)
{
type: 'srflx',
ip: '203.0.113.1', // 公网IP
port: 12345, // NAT映射的端口
protocol: 'udp',
priority: 1694498815,
foundation: '2',
relatedAddress: '192.168.1.100', // 本地IP
relatedPort: 54321
}

// Relay候选 (TURN中继地址)
{
type: 'relay',
ip: '198.51.100.1', // TURN服务器IP
port: 3478,
protocol: 'udp',
priority: 16777215,
foundation: '3',
relatedAddress: '203.0.113.1',
relatedPort: 12345
}

优先级顺序:
Host > Server Reflexive > Relay
(优先直连,最后才用中继)

ICE连接流程

// WebRTC ICE示例
const pc = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302' // STUN服务器
},
{
urls: 'turn:turn.example.com:3478', // TURN服务器
username: 'user',
credential: 'pass'
}
]
});

// 监听ICE候选
pc.onicecandidate = (event) => {
if (event.candidate) {
console.log('New ICE candidate:', event.candidate);

// 通过信令发送给对方
signalingChannel.send({
type: 'ice-candidate',
candidate: event.candidate
});
} else {
console.log('All ICE candidates gathered');
}
};

// 监听ICE连接状态
pc.oniceconnectionstatechange = () => {
console.log('ICE状态:', pc.iceConnectionState);

switch (pc.iceConnectionState) {
case 'checking':
console.log('正在检查连接性...');
break;
case 'connected':
console.log('ICE连接成功!');
break;
case 'completed':
console.log('ICE连接完成!');
break;
case 'failed':
console.error('ICE连接失败!');
break;
case 'disconnected':
console.warn('ICE连接断开');
break;
case 'closed':
console.log('ICE连接关闭');
break;
}
};

// ICE状态转换:
// new → checking → connected → completed
// ↓
// failed / disconnected / closed

STUN vs TURN

STUN (Session Traversal Utilities for NAT):
作用: 发现公网IP和端口
原理:
1. 客户端向STUN服务器发送请求
2. STUN服务器返回客户端的公网IP:端口
3. 客户端获知NAT映射的地址

优点:
✓ 轻量级
✓ 低延迟
✓ 服务器负载小

缺点:
❌ 无法穿透对称NAT
❌ 某些企业防火墙阻止

成功率: ~85%

TURN (Traversal Using Relays around NAT):
作用: 中继媒体数据
原理:
1. 客户端连接到TURN服务器
2. 获得一个中继地址
3. 所有数据通过TURN服务器转发

优点:
✓ 100%成功 (后备方案)
✓ 绕过严格防火墙

缺点:
❌ 增加延迟
❌ 服务器带宽消耗大
❌ 运营成本高

使用场景:
- STUN失败时的后备
- 对称NAT环境
- 严格的企业网络

部署建议:
1. 总是提供STUN (必需)
2. 提供TURN作为后备 (推荐)
3. 优先使用直连 (性能最佳)

DTLS (Datagram Transport Layer Security)

DTLS的作用

为什么需要DTLS?

问题:
- UDP是不安全的
- RTP媒体流需要加密
- 需要密钥协商

DTLS解决:
✓ 建立安全连接
✓ 协商加密密钥
✓ 验证对方身份
✓ 完整性保护

DTLS vs TLS:
TLS (基于TCP):
- 可靠传输
- 有序交付
- 流式传输

DTLS (基于UDP):
- 不可靠传输
- 可能乱序
- 数据报传输
→ 需要额外处理丢包和重排序

DTLS版本:
WebRTC要求: DTLS 1.2 或更高
推荐: DTLS 1.3

DTLS握手流程

DTLS握手 (简化版):

客户端 服务器
| |
| ClientHello |
|----------------------------->|
| |
| ServerHello |
| Certificate |
| ServerKeyExchange |
| CertificateRequest |
| ServerHelloDone |
|<-----------------------------|
| |
| Certificate |
| ClientKeyExchange |
| CertificateVerify |
| [ChangeCipherSpec] |
| Finished |
|----------------------------->|
| |
| [ChangeCipherSpec] |
| Finished |
|<-----------------------------|
| |
| --- 安全连接建立 --- |
| |

握手后:
- 协商好加密算法
- 交换了密钥材料
- 验证了对方证书
- 可以开始加密通信

DTLS-SRTP

// DTLS-SRTP密钥导出
// DTLS握手完成后,导出SRTP密钥

// 密钥材料:
{
clientWriteMasterKey: Buffer, // 客户端发送密钥
serverWriteMasterKey: Buffer, // 服务器发送密钥
clientWriteMasterSalt: Buffer, // 客户端盐值
serverWriteMasterSalt: Buffer // 服务器盐值
}

// SRTP使用这些密钥加密RTP包
// SRTCP使用这些密钥加密RTCP包

// 密钥长度 (取决于加密套件):
AES-128:
- 主密钥: 16字节
- 盐值: 14字节

AES-256:
- 主密钥: 32字节
- 盐值: 14字节

证书验证

// WebRTC使用自签名证书
// 通过指纹验证证书

// 生成证书指纹
function generateFingerprint(certificate) {
const der = certificate.toDER();
const hash = crypto.createHash('sha256');
hash.update(der);
return hash.digest('hex').match(/.{2}/g).join(':').toUpperCase();
}

// SDP中的指纹
a=fingerprint:sha-256 DF:FA:FB:08:3B:3C:54:1D:D7:D4:24:AB:CD:EF:12:34:...

// 验证流程:
1. 生成本地证书和私钥
2. 计算证书指纹
3. 通过信令交换指纹
4. DTLS握手时验证对方证书指纹
5. 指纹匹配 → 信任证书
6. 指纹不匹配 → 拒绝连接

// 示例代码
pc.onicecandidate = async (event) => {
// 获取本地证书指纹
const localDescription = pc.localDescription;
const fingerprint = extractFingerprint(localDescription.sdp);

// 发送给对方
signalingChannel.send({
type: 'offer',
sdp: localDescription.sdp,
fingerprint: fingerprint
});
};

// 验证对方指纹
function verifyFingerprint(remoteSdp, expectedFingerprint) {
const actualFingerprint = extractFingerprint(remoteSdp);

if (actualFingerprint !== expectedFingerprint) {
throw new Error('Fingerprint mismatch - possible MITM attack!');
}
}

SRTP (Secure RTP)

SRTP加密

SRTP = RTP + 加密 + 认证

RTP包结构:
┌──────────────────────────────────────┐
│ RTP Header (12+ bytes) │
│ - V, P, X, CC, M, PT, Seq, TS, SSRC │
├──────────────────────────────────────┤
│ RTP Payload (媒体数据) │
│ - 音频样本 或 视频帧 │
└──────────────────────────────────────┘

SRTP包结构:
┌──────────────────────────────────────┐
│ RTP Header (明文) │
├──────────────────────────────────────┤
│ Encrypted Payload (加密) │
│ - 加密的媒体数据 │
├──────────────────────────────────────┤
│ Authentication Tag (认证标签) │
│ - HMAC-SHA1 或 其他 │
└──────────────────────────────────────┘

加密内容:
✓ RTP payload (媒体数据)
✗ RTP header (不加密 - 用于路由)

认证内容:
✓ RTP header + encrypted payload

加密套件

常用SRTP加密套件:

1. SRTP_AES128_CM_HMAC_SHA1_80
加密: AES-128 Counter Mode
认证: HMAC-SHA1 (80位)
用途: 标准配置

2. SRTP_AES128_CM_HMAC_SHA1_32
加密: AES-128 Counter Mode
认证: HMAC-SHA1 (32位 - 截断)
用途: 低带宽场景

3. SRTP_AEAD_AES_128_GCM
加密+认证: AES-128-GCM (一体化)
用途: 现代推荐 (DTLS 1.3)

4. SRTP_AEAD_AES_256_GCM
加密+认证: AES-256-GCM
用途: 高安全要求

性能对比:
AES-128-GCM > AES-128-CTR+HMAC > AES-256-GCM
(GCM硬件加速,性能最好)

重放攻击防护

// SRTP使用滑动窗口防止重放攻击

class SRTPReplayProtection {
constructor(windowSize = 64) {
this.windowSize = windowSize;
this.highestSeq = 0;
this.bitmap = new BigInt(0);
}

checkAndUpdate(seq) {
const delta = seq - this.highestSeq;

if (delta > 0) {
// 新包,序号更大
this.bitmap = this.bitmap << BigInt(delta);
this.bitmap |= 1n;
this.highestSeq = seq;
return true;

} else if (delta > -this.windowSize) {
// 在窗口内
const bitPos = -delta;
const mask = 1n << BigInt(bitPos);

if (this.bitmap & mask) {
// 重复包!
return false;
}

this.bitmap |= mask;
return true;

} else {
// 太旧的包
return false;
}
}
}

// 使用
const replayCheck = new SRTPReplayProtection();

function processSRTPPacket(packet) {
const seq = packet.sequenceNumber;

if (!replayCheck.checkAndUpdate(seq)) {
console.warn('Replay attack detected!', seq);
return; // 丢弃重放包
}

// 解密并处理包
decryptAndProcess(packet);
}

数据通道 (DataChannel)

SCTP over DTLS over UDP

数据通道协议栈:

应用层: DataChannel API

传输层: SCTP (Stream Control Transmission Protocol)

安全层: DTLS

网络层: ICE + UDP + IP

为什么用SCTP?
✓ 消息边界保留 (不像TCP流式)
✓ 可选可靠性 (配置是否重传)
✓ 可选有序性 (配置是否排序)
✓ 多流支持 (一个连接多个流)
✓ 拥塞控制

SCTP特性:
1. 可靠/不可靠 可选
2. 有序/无序 可选
3. 消息边界
4. 多路复用

DataChannel配置

// 创建数据通道
const dataChannel = pc.createDataChannel('chat', {
// 可靠性
ordered: true, // 有序交付
maxRetransmits: 3, // 最多重传3次
// 或
maxPacketLifeTime: 1000, // 最多存活1秒

// 协议
protocol: '', // 自定义协议标识

// 其他
negotiated: false, // 是否协商
id: null // 通道ID
});

// 配置组合:

// 1. 可靠有序 (类似TCP)
{
ordered: true,
maxRetransmits: undefined // 无限重传
}

// 2. 可靠无序
{
ordered: false,
maxRetransmits: undefined
}

// 3. 不可靠有序
{
ordered: true,
maxRetransmits: 0 // 不重传
}

// 4. 不可靠无序 (类似UDP)
{
ordered: false,
maxRetransmits: 0
}

// 5. 部分可靠
{
ordered: true,
maxRetransmits: 3, // 重传3次
maxPacketLifeTime: 500 // 或存活500ms
}

DataChannel使用示例

// 发送方
const pc1 = new RTCPeerConnection();
const dc1 = pc1.createDataChannel('chat');

dc1.onopen = () => {
console.log('DataChannel opened');

// 发送文本
dc1.send('Hello World!');

// 发送二进制 (ArrayBuffer)
const buffer = new ArrayBuffer(1024);
dc1.send(buffer);

// 发送Blob
const blob = new Blob(['data'], { type: 'text/plain' });
dc1.send(blob);
};

dc1.onmessage = (event) => {
console.log('Received:', event.data);
};

dc1.onerror = (error) => {
console.error('DataChannel error:', error);
};

dc1.onclose = () => {
console.log('DataChannel closed');
};

// 接收方
const pc2 = new RTCPeerConnection();

pc2.ondatachannel = (event) => {
const dc2 = event.channel;
console.log('DataChannel received:', dc2.label);

dc2.onmessage = (event) => {
console.log('Received:', event.data);

// 回复
dc2.send('Response: ' + event.data);
};
};

// 实际应用:

// 1. 聊天消息 (可靠有序)
const chat = pc.createDataChannel('chat', {
ordered: true
});

chat.send(JSON.stringify({
type: 'message',
text: 'Hello!',
timestamp: Date.now()
}));

// 2. 游戏状态同步 (不可靠无序,低延迟优先)
const gameState = pc.createDataChannel('game-state', {
ordered: false,
maxRetransmits: 0 // 不重传旧状态
});

setInterval(() => {
gameState.send(JSON.stringify({
playerX: player.x,
playerY: player.y,
timestamp: Date.now()
}));
}, 50); // 20 FPS

// 3. 文件传输 (可靠有序)
const fileTransfer = pc.createDataChannel('file-transfer', {
ordered: true
});

function sendFile(file) {
const chunkSize = 16384; // 16KB chunks
let offset = 0;

const reader = new FileReader();

reader.onload = (e) => {
fileTransfer.send(e.target.result);
offset += chunkSize;

if (offset < file.size) {
readSlice(offset);
} else {
console.log('File sent completely');
}
};

function readSlice(o) {
const slice = file.slice(o, o + chunkSize);
reader.readAsArrayBuffer(slice);
}

readSlice(0);
}

拥塞控制

Google Congestion Control (GCC)

WebRTC拥塞控制算法:

目标:
- 最大化吞吐量
- 最小化延迟
- 公平分享带宽

组件:

1. 发送端估计
- 基于RTCP反馈
- 测量丢包率
- 测量RTT

2. 接收端估计
- 基于到达时间
- 检测延迟增加
- 估计可用带宽

3. 速率控制
- 调整发送速率
- 平滑变化
- 快速反应

算法流程:

接收端:
┌──────────────────────────────┐
│ 1. 记录包到达时间 │
│ 2. 计算包组延迟变化 │
│ 3. 检测网络拥塞 │
│ 4. 发送REMB (接收端估计) │
└──────────────────────────────┘

发送端:
┌──────────────────────────────┐
│ 1. 接收REMB反馈 │
│ 2. 监测丢包率 │
│ 3. 综合评估网络状态 │
│ 4. 调整码率 │
└──────────────────────────────┘

带宽探测

// 带宽探测策略

class BandwidthProbe {
constructor() {
this.currentBitrate = 300000; // 300 kbps起始
this.maxBitrate = 2000000; // 2 Mbps上限
this.state = 'increase';
}

update(feedback) {
const { lossRate, rtt, remb } = feedback;

if (lossRate > 0.1) {
// 丢包率 > 10% → 降低码率
this.state = 'decrease';
this.currentBitrate *= 0.85; // 减少15%

} else if (rtt > 200) {
// RTT > 200ms → 保持
this.state = 'hold';

} else if (remb && remb < this.currentBitrate) {
// 接收端估计低于当前 → 降低
this.currentBitrate = remb * 0.95;

} else if (lossRate < 0.02 && rtt < 100) {
// 网络良好 → 增加码率
this.state = 'increase';
this.currentBitrate *= 1.05; // 增加5%

// 不超过上限
this.currentBitrate = Math.min(
this.currentBitrate,
this.maxBitrate
);
}

return Math.floor(this.currentBitrate);
}
}

// 使用
const probe = new BandwidthProbe();

setInterval(() => {
const stats = getConnectionStats();
const newBitrate = probe.update({
lossRate: stats.packetsLost / stats.packetsSent,
rtt: stats.currentRoundTripTime,
remb: stats.availableOutgoingBitrate
});

console.log('New target bitrate:', newBitrate);
updateEncoderBitrate(newBitrate);
}, 1000); // 每秒更新

性能优化

1. ICE优化

// 使用ICE候选过滤
const pc = new RTCPeerConnection({
iceServers: [...],

// ICE传输策略
iceTransportPolicy: 'all', // 'all' | 'relay'
// 'relay': 强制使用TURN (隐藏IP)

// ICE候选池大小
iceCandidatePoolSize: 10, // 预先收集候选

// Bundle策略
bundlePolicy: 'max-bundle', // 所有媒体共用一个传输

// RTCP复用
rtcpMuxPolicy: 'require' // RTP和RTCP共用端口
});

// Trickle ICE (增量候选)
// 不等待所有候选收集完,立即发送
pc.onicecandidate = (event) => {
if (event.candidate) {
// 立即发送
sendCandidateToRemote(event.candidate);
}
};

2. 连接优先级

// ICE候选优先级计算
function calculatePriority(type, localPref, componentId) {
const typePref = {
'host': 126,
'srflx': 100,
'relay': 0
}[type];

// RFC 5245公式
return (2^24) * typePref +
(2^8) * localPref +
(256 - componentId);
}

// 本地偏好:
// - IPv6 > IPv4
// - 快速接口 > 慢速接口
// - VPN后 < 直连

// 组件ID:
// - RTP: 1
// - RTCP: 2

3. 网络质量监控

// 实时监控连接质量
async function monitorConnectionQuality(pc) {
const stats = await pc.getStats();

stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'video') {
console.log('视频接收质量:');
console.log('- 接收码率:', report.bytesReceived * 8 / report.timestamp, 'bps');
console.log('- 丢包率:', report.packetsLost / report.packetsReceived * 100, '%');
console.log('- Jitter:', report.jitter, 'seconds');
console.log('- 帧率:', report.framesPerSecond, 'fps');
}

if (report.type === 'candidate-pair' && report.state === 'succeeded') {
console.log('连接质量:');
console.log('- RTT:', report.currentRoundTripTime * 1000, 'ms');
console.log('- 可用带宽:', report.availableOutgoingBitrate / 1000, 'kbps');
console.log('- 本地候选类型:', report.localCandidateId);
console.log('- 远程候选类型:', report.remoteCandidateId);
}
});
}

setInterval(() => {
monitorConnectionQuality(peerConnection);
}, 1000);

安全考虑

1. 强制加密

WebRTC安全要求:

必需:
✓ DTLS加密 (所有传输)
✓ SRTP加密 (媒体)
✓ 证书指纹验证
✓ 安全信令通道

禁止:
❌ 明文RTP
❌ 未加密数据通道
❌ 跳过证书验证

最佳实践:
1. 使用DTLS 1.2+
2. 强加密套件
3. 定期更新证书
4. 验证远程指纹

2. 防御攻击

// 防止DoS攻击
class RateLimiter {
constructor(maxRequestsPerSecond) {
this.maxRequests = maxRequestsPerSecond;
this.requests = [];
}

allow(clientId) {
const now = Date.now();

// 清理旧请求
this.requests = this.requests.filter(r =>
now - r.timestamp < 1000
);

// 检查速率
const clientRequests = this.requests.filter(r =>
r.clientId === clientId
);

if (clientRequests.length >= this.maxRequests) {
return false; // 超过限制
}

this.requests.push({ clientId, timestamp: now });
return true;
}
}

// 使用
const limiter = new RateLimiter(10); // 每秒最多10个请求

app.post('/api/ice-candidate', (req, res) => {
const clientId = req.session.id;

if (!limiter.allow(clientId)) {
return res.status(429).json({ error: 'Too many requests' });
}

// 处理ICE候选...
});

总结

关键要点

✓ WebRTC使用UDP传输
✓ ICE处理NAT穿透
✓ DTLS提供加密
✓ SRTP保护媒体
✓ SCTP支持数据通道

传输栈:
应用 → RTP/SCTP → SRTP/DTLS → ICE → UDP → IP

候选类型:
Host > Server Reflexive > Relay

加密:
DTLS协商密钥 → SRTP加密媒体

数据通道:
可靠/不可靠可配置
有序/无序可配置

拥塞控制:
GCC算法
带宽探测
速率自适应

参考文献

相关RFC:

  • [RFC 8835] Transports for WebRTC ← 本文档
  • [RFC 8445] ICE
  • [RFC 6347] DTLS 1.2
  • [RFC 3711] SRTP
  • [RFC 4960] SCTP
  • [RFC 8831] WebRTC Data Channels

总结: WebRTC传输栈通过ICE、DTLS、SRTP的精妙组合,在不可靠的UDP之上构建了安全、高效的实时通信基础设施。理解这些传输层协议是掌握WebRTC的关键!