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的关键!