【ZeroRange WebRTC】WebRTC 基于 STUN 的 srflx 直连原理与实现
WebRTC 基于 STUN 的 srflx 直连原理与实现
本文系统梳理在手机 App 与 IPC 设备之间,基于 STUN 获取到 srflx(Server Reflexive)候选后如何实现点对点直连进行音视频传输的完整技术路径,包括 NAT 出站映射、ICE 连通性检查、候选选择与提名、DTLS-SRTP 加密、保活与 Consent Freshness 等关键机制,并结合 Amazon Kinesis WebRTC C SDK 的落地细节与排错要点。
1. 核心概念
srflx候选:通过向公网 STUN 服务器发送Binding Request后,服务器在响应中返回请求的源地址(XOR-MAPPED-ADDRESS),即“外界看到的你的公网 IP:端口”。这对应 NAT 为该本地 socket 建立的出站映射。- 5‑tuple:唯一标识一条传输层流的五元组:
源IP、源端口、目的IP、目的端口、协议(UDP/TCP)。NAT/防火墙/内核conntrack都按 5‑tuple 维护状态。 - ICE 候选类型与优先:一般优先级
host > srflx > relay(TURN);ICE 通过对“本地候选 ↔ 远端候选”进行 STUN 连通性检查,选出最优且可达的候选对。 - NAT 出站映射:当内网端向外发 UDP 包时,NAT 为
内网IP:内端口分配一个公网IP:外端口并建立转换表项与过滤规则;不同 NAT 类型(全锥/受限锥/端口受限锥/对称)对入站放行的条件不同。
参考:RFC 5389(STUN)、RFC 8445(ICE)、RFC 5766(TURN)。
2. 形成直连的完整过程
- 信令交换(WSS/HTTPS):双方通过信令通道交换 SDP(编解码、指纹、
a=setup等)以及 Trickle ICE 候选。 - 收集
srflx:每端向 STUN 发送Binding Request,NAT 建立出站映射,STUN 在Binding Success Response中返回XOR-MAPPED-ADDRESS,形成srflx候选。 - 传播候选:双方将
host/srflx/(必要时)relay候选通过信令发送给对端。 - 连通性检查(双向):每端用自己的本地候选对远端候选发 STUN 检查。双向检查满足受限锥/端口受限锥 NAT 的“我曾向你发过包”过滤条件。
- 候选对通过:某个候选对检查成功,双方 NAT 都建立并允许该 5‑tuple 的入站/出站,路径打通。
- 提名与固定:ICE
controlling端发送USE-CANDIDATE提名该候选对,controlled端确认后固定此 5‑tuple 为媒体路径。 - DTLS 握手与 SRTP:在固定的 5‑tuple 上完成 DTLS 握手(角色由 SDP
a=setup协商),导出 SRTP 密钥(RFC 5764),后续用 SRTP 加密 RTP/RTCP。 - 直接传输媒体:音视频 RTP/RTCP 包沿该 5‑tuple 点对点传输,通常无需经任何服务器中继。
3. NAT 打洞的技术细节
- 出站映射与端口复用:ICE 复用同一个本地 socket/端口向 STUN 与对端发包,确保继续使用已在 NAT 上建立的映射,避免新端口导致重新建映射。
- NAT 过滤策略:
- 全锥:任何对端都可向“映射的公网端口”入站。
- 受限锥:仅允许“我曾向其发过包的对端 IP”入站。
- 端口受限锥:进一步受限到“IP+端口”。
- 对称 NAT:外部端口与目标地址绑定,给 STUN 的映射对其他对端不可用,
srflx直连常失败。
- 映射闲置超时:NAT 对 UDP 映射设定超时(常见 30–120 秒),若无流量则清除;需保活维持。
prflx候选:检查过程中可能出现对端未声明但可达的本地地址,形成peer reflexive候选,并可能被选中。
4. 候选选择与提名(ICE 角色)
- 角色划分:ICE 有
controlling/controlled两个逻辑角色,仅影响提名流程;两端都会并行发 STUN 检查。 - 提名:
controlling端在某个候选对通后发送USE-CANDIDATE属性提名,controlled端确认后固定路径。 - 路径复用:提名后的所有流量(STUN 保活与 consent、DTLS、RTP/RTCP、SCTP 数据通道)均复用同一 5‑tuple,保证 NAT 状态一致与稳定。
5. 保活与 Consent Freshness 的底层原理
- 保活(Keepalive):周期发送小报文维持 NAT 映射不超时。常用 STUN
Binding Indication(不需要响应,轻量),或小体积RTCP/RTP心跳。典型间隔 15–20 秒以适配保守 NAT。 - Consent Freshness(同意新鲜度):周期性在“已选候选对”的 5‑tuple 上发送 STUN
Binding Request,要求对端返回成功响应并校验MESSAGE-INTEGRITY(基于ice-pwd)。若在规定窗口内未获得响应(例如连续失败或超过约 30 秒),必须停止在该 5‑tuple 上发送媒体,避免向无效或非同意的地址持续发流量。参考 RFC 7675。 - 二者差异:保活可用不需响应的 Indication,只维持映射;Consent Freshness需可验证的响应,确保对端仍“同意”接收。
6. 安全与加密
- DTLS 握手:在选定 5‑tuple 上进行 DTLS 握手,角色由 SDP
a=setup(如actpass/active/passive)协商。证书指纹在 SDP 中交换防止中间人攻击。 - SRTP:DTLS 握手后按照 RFC 5764 导出 SRTP 密钥,后续 RTP/RTCP 全程加密与鉴别。
- STUN 完整性:STSUN 请求/响应带
MESSAGE-INTEGRITY与FINGERPRINT,防止伪造与损坏。
7. 何时需要 TURN 回落
- 对称 NAT:
srflx通常不可用;需使用 TURNrelay候选。 - UDP 被阻断或强审计:企业网络/移动网络可能限制 UDP;TURN 可走 TCP 或 TLS(
turns)。 - 防火墙策略:目的端口或外联受限导致检查失败;允许 TURN 中继可显著提高成功率。
8. 在 Amazon Kinesis WebRTC C SDK 中的对应
- 端点发现:先通过
HTTPS端点获取 STUN/TURN 配置(GetIceServerConfig),通过WSS建立信令。你可能在日志中看到形如:{"ResourceEndpointList":[{"Protocol":"HTTPS"...},{"Protocol":"WSS"...}]}。 - ICE 收集与检查:ICE 模块向 STUN 发
Binding Request生成srflx,随后对候选对做连通性检查。 - 候选选择与媒体:日志会打印“selected candidate pair”的类型(
host/srflx/relay)。选中srflx即为直连;之后完成 DTLS→SRTP,在该 5‑tuple 上传输媒体。 - 状态机位置:信令状态推进逻辑位于
src/source/Signaling/StateMachine.c(如SIGNALING_STATE_NONE为起始/占位),具体 ICE/DTLS/RTP 模块位于src/source/Ice/,Rtp/,Srtp/,PeerConnection/等目录。
9. 日志定位与排错清单
- 关注关键词:
GetIceServerConfig、OnIceCandidate、Connectivity check、selected candidate pair、USE-CANDIDATE、DTLS handshake、SRTP established、Binding Indication/Request。 - 常见问题:
WSS握手失败:检查系统时间、根证书、代理/防火墙。GetIceServerConfig403:核查 IAM 权限与区域/通道 ARN。- 连通性检查失败:可能为对称 NAT 或 UDP 阻断;启用 TURN 并允许 TCP/TLS。
- 映射超时掉线:增大保活频率(15–20 秒),确保 Consent Freshness 响应正常。
- 编解码/带宽问题:检查 RTP/RTCP 反馈(PLI/NACK/TCC/REMB),适配码率与抖动缓冲。
10. FAQ(角色与连接)
- 谁是“客户端/服务端”?在 UDP 传输层,两端都是对等体;只有 ICE(
controlling/controlled)与 DTLS(握手的client/server)存在逻辑角色。 - 为何必须“双向检查”?受限锥/端口受限锥 NAT 要求“我曾向该对端发过包”才放行入站;双向 STUN 检查满足双方 NAT 的过滤条件。
- 5‑tuple 为什么重要?它是 NAT/防火墙的状态键;提名后复用同一 5‑tuple 能保证 DTLS/SRTP/STUN 与媒体路径一致、稳定。
11. 一个简化的时序示例
- IPC:
192.168.1.10:5000→ STUN(UDP)→ NAT 建映射203.0.113.5:62000→ 获得srflx。 - 手机:
10.0.0.7:4000→ STUN(UDP)→ NAT 建映射198.51.100.7:55000→ 获得srflx。 - 互发检查:手机向
203.0.113.5:62000发 STUN;IPC 向198.51.100.7:55000发 STUN → 双侧 NAT 放行。 - 提名与固定:ICE 选中并提名该候选对 → 在此 5‑tuple 上完成 DTLS → 导出 SRTP → 媒体直连传输。
12. 进一步参考
- RFC 5389: Session Traversal Utilities for NAT (STUN)
- RFC 8445: Interactive Connectivity Establishment (ICE)
- RFC 5766: Traversal Using Relays around NAT (TURN)
- RFC 5764: SRTP Keying via DTLS-SRTP
- RFC 7675: STUN Usage for Consent Freshness
- 本仓库文档:
docs/ice-srflx-connectivity.zh.md、docs/https-wss-webrtc.zh.md、docs/webrtc-ice-paths.svg
若你需要将具体运行日志(如“selected candidate pair”的类型、Consent 检查的响应情况)映射到上述步骤,我可以继续按日志时间线为你标注与定位。
13. 技术原理补充(更深入)
- NAT 转换表项与出站映射:当内网端(如 IPC 或手机)发出 UDP 报文时,NAT 会为该本地 5‑tuple 建立一条“转换/过滤”状态,并分配一个外网端口。典型形态:
(内网IP, 内端口, 协议[, 目标IP/端口约束]) → (公网IP, 外端口),并带有闲置超时(常见 30–120 秒)。 - STUN
XOR-MAPPED-ADDRESS:向公网 STUN 发送Binding Request后,服务器在成功响应中回显“请求的源地址”,这就是你的srflx公网地址,直接对应 NAT 刚建立的出站映射。 - 对称 NAT 的限制:对称 NAT 的映射与“目标地址”绑定,给 STUN 建的外网端口仅对 STUN 目的地址有效,换到对端地址时会使用不同外网端口,导致
srflx直连常失败,需要回落到 TURN。 - 双向检查的必要性:受限锥/端口受限锥 NAT 会要求“我曾向该对端(IP/端口)发过包”才放行入站。因此 ICE 需要双方都向对端候选发 STUN 检查,才能同时满足两侧 NAT 的过滤条件。
Binding RequestvsBinding Indication:- Request 需要对端响应,用于连通性检查与 Consent Freshness(确认对端仍“同意”接收)。
- Indication 不需要响应,负载更轻,常用于保活以维持 NAT 映射不过期。
- 5‑tuple 复用:提名后将 STUN/DTLS/RTP/RTCP/SCTP 全部复用同一 5‑tuple,保证 NAT 状态一致、握手与媒体路径不脱节;若 5‑tuple 变化(如端口或网络切换),需重新检查或 ICE 重启。
14. SDK 实现位置与关键函数(Amazon Kinesis WebRTC C SDK)
- 收集与初始化
srflx:src/source/Ice/IceAgent.c中的iceAgentInitSrflxCandidate、iceAgentSendSrflxCandidateRequest负责初始化与向 STUN 发送请求以生成srflx候选。- STUN 成功响应处理(
STUN_PACKET_TYPE_BINDING_RESPONSE_SUCCESS)中包含识别srflx的逻辑与诊断更新(同文件)。
- 连通性检查与提名:
- 候选对状态与“提名”标志
nominated的维护在IceAgent.c与src/source/Ice/IceAgentStateMachine.c(状态ICE_AGENT_STATE_NOMINATING)。 - 一旦找到“已提名且检查成功”的候选对,会将其设置为数据发送对(
pDataSendingIceCandidatePair)。
- 候选对状态与“提名”标志
- 保活与 Consent:
src/source/Ice/IceAgent.c的iceAgentSendKeepAliveTimerCallback配合定时器在已选候选对上发送保活(日志中常见Received STUN binding indication)。- 统计结构中包含“用于验证 consent 的请求往返”指标,见
src/include/com/amazonaws/kinesis/video/webrtcclient/Stats.h与src/source/Metrics/Metrics.c的getIceCandidatePairStats。
- SDP 与候选类型:
src/source/Sdp/Sdp.h定义SDP_CANDIDATE_TYPE_SERFLX("srflx")。 - 入口与回调:
src/source/PeerConnection/PeerConnection.c负责创建 ICE Agent 与注册回调(如onIceConnectionStateChange、onNewIceLocalCandidate)。 - 样例与测试:
- 测试:
tst/PeerConnectionFunctionalityTest.cpp中的connectTwoPeersWithHostAndStun验证主机与 STUN 候选可连接;multipleCandidateSuccessOneDTLSCheck验证 DTLS 仅在选定对上进行一次。 - 样例:
samples/Common.c展示配置 STUN/TURN 与初始化 PeerConnection 的流程。
- 测试:
15. 调试日志样例与关键词对照
- 端点发现(信令):
Received client http read response: {"ResourceEndpointList":[{"Protocol":"HTTPS"...},{"Protocol":"WSS"...}]}
- 收集与检查:
new local candidate: type=srflx ...(不同日志级别实现可能有差)Connectivity check success、Selected candidate pair: local=..., remote=..., type=srflx(或host/relay)
- 提名与固定:
nominated=true、ICE_AGENT_STATE_NOMINATING → READY(状态机推进)
- 保活与 Consent:
Received STUN binding indication(保活)Binding Request成功/失败与往返统计(Consent Freshness)
- 安全与媒体:
DTLS handshake complete、SRTP established、随后开始 RTP/RTCP 传输。
16. srflx 直连 Checklist(实践建议)
- STUN 可达:确保公网 STUN 服务器可访问,DNS/TLS(若走
turns/wss)与路由正常。 - UDP 放行:运营商/企业网络允许 UDP;若受限,配置 TURN 并允许 TCP/TLS 中继。
- NAT 友好:对称 NAT 环境预置 TURN;优先使用 Trickle ICE,延长候选收集时间以提高直连概率。
- 同一 socket 复用:确保实现复用同一本地端口进行 STUN 与检查,避免端口切换导致映射重建。
- 保活频率:设置保守的保活间隔(15–20 秒);监控 Consent Freshness 响应,超时即停发并重试/重选候选。
- 编解码与拥塞:关注 RTCP/TCC/REMB/NACK/PLI 指标,合理配置码率与抖动缓冲以保障体验。
17. 术语速查
srflx:Server Reflexive 候选,来自 STUN 响应的公网映射地址。prflx:Peer Reflexive 候选,检查中发现的对端可达本地地址。nominated:被controlling端提名且检查成功的候选对标志。- 5‑tuple:
源IP/源端口/目的IP/目的端口/协议,网络状态的唯一键。 - Consent Freshness:通过需响应的 STUN
Binding Request周期确认对端仍“同意”接收。
