4.2 STUN 与 TURN 部署 本节摘要:打洞不是凭空完成的,它站在两台服务器的肩膀上:STUN 帮终端「照镜子」看清自己的公网地址,TURN 在直连无望时化身中继替双方搬运加密数据。本节用事实上的标准实现 coturn 走完部署全流程——安装、配置、凭证、防火墙、验证——并把中继的带宽成本算清楚,让「要不要上 TURN」从拍脑袋变成算术题。 目标清单 阅读完本节,你应当能够: 说清 STUN 与 TURN 各自解决什么问题、为什么 TURN 必须有鉴权而 STUN 可以没有; 完成 coturn 的安装与最小可用配置,理解每一项关键配置的含义; 配置长期凭证机制,并在客户端正确填写 iceServers; 按协议清单打通防火墙端口,避免「部署成功但外网不可达」;
本节摘要:打洞不是凭空完成的,它站在两台服务器的肩膀上:STUN 帮终端「照镜子」看清自己的公网地址,TURN 在直连无望时化身中继替双方搬运加密数据。本节用事实上的标准实现 coturn 走完部署全流程——安装、配置、凭证、防火墙、验证——并把中继的带宽成本算清楚,让「要不要上 TURN」从拍脑袋变成算术题。
阅读完本节,你应当能够:
先明确分工。STUN 只做一件小事:你从陌生端口给它发个询问,它把「我看到的你的地址端口」原样告诉你——这就是 srflx 候选的来源。它不转发数据,所以负载极轻,公共免费的 STUN 服务到处都有。TURN 则重得多:直连失败时,双方的加密媒体都从 TURN 进出,它真实承担转发带宽。
一个常见误解是「STUN 与 TURN 二选一」。正确的关系是递进:先有 STUN 才知道公网映射;有映射才能尝试直连;直连探测失败才用 TURN。TURN 的分配请求本身也经由 STUN 协议扩展完成。部署视角的结论是:TURN 服务通常同时就是 STUN 服务(coturn 一个进程提供两者),但要按两者的不同负载特征规划——STUN 只吃包量,TURN 吃真实带宽。
为什么 TURN 必须鉴权?因为它替用户烧的是真金白银的带宽。无鉴权的开放中继会在几小时内被滥用成流量代理,这是运维事故名单上的常客。STUN 无所谓——它的回答只是「你长什么样」,没有可窃取的资源。
以最常见的 Linux 服务器为例,最小可用配置如下(配置文件通常在系统配置目录的 turnserver 主配置里):
# coturn 最小可用配置(示意,路径随发行版而异) listening-port=3478 # STUN 与 TURN 的默认监听端口 external-ip=203.0.113.9 # 本机公网地址,NAT 后部署时必须写 realm=turn.example.net # 域名,凭证计算的一部分,客户端可见 lt-cred-mech # 启用长期凭证机制 user=alice:0x82311c9b0f2ce4a3 # 用户与密钥散列(由账号密码加 realm 算出) # 或开发期明文:user=alice:somepassword min-port=49160 # 中继端口池下限 max-port=49200 # 中继端口池上限,按并发路数规划 fingerprint no-multicast-peers # 安全收紧项,禁止中继到组播 no-cli # 关闭管理端口,生产必做
客户端对应的写法:
const pc = new RTCPeerConnection({ iceServers: [ { urls: 'stun:turn.example.net:3478' }, { urls: [ 'turn:turn.example.net:3478?transport=udp', 'turn:turn.example.net:3478?transport=tcp', 'turns:turn.example.net:5349?transport=tcp', // TLS 版本,封锁 UDP 的网络救星 ], username: 'alice', credential: 'somepassword', }, ], });
部署完成后按四步验证,缺一步都可能「本地通、线上死」:
# 第一步:进程与端口 ss -ulnp | grep 3478 # 第二步:STUN 分配(输出应包含你的公网映射地址) turnutils_stun -p 3478 turn.example.net # 第三步:TURN 中继全链路(到另一台机器上跑对端更佳) turnutils_peer -L 127.0.0.1 -p 3500 & turnutils_uclient -T -u alice -w somepassword turn.example.net 127.0.0.1 # 第四步:外部可达性——从办公网外或手机热点重复第二、三步
防火墙侧的端口清单要背下来:UDP 与 TCP 的 3478(信令式访问)、TLS 的 5349(turns)、以及中继端口池的 UDP 段(49160 起)。中继端口池是最常被漏放行的一段—— symptoms 是 STUN 正常、TURN 分配地址也拿到了,但媒体死活不通。

凭证怎么发。长期凭证的密码散列由「用户名、realm、密码」拼接后做摘要运算,服务端可静态配置,也可动态生成。生产推荐动态化:业务服务签发带过期时间的临时凭证(用户名里编码过期时刻,密码由共享密钥对它做摘要),TURN 用 rest-api 风格的鉴权配置校验。这样凭证泄露的窗口被限制在几分钟内,且无需在 TURN 配置文件里维护用户表。
成本怎么算。中继带宽是双向的:一路音频约 40 千比特每秒、一路 720p 视频约 1.5 兆比特每秒,上行加下行双倍计。假设两成通话走中继、场均两人一路视频通话一小时,单路中继流量约 1.3 吉字节——乘上中继比例与日均通话量,就是 TURN 集群的出向流量账单。把中继比例压下来的手段(改善 STUN 可达性、优化候选探测顺序)都是真金白银。
容量怎么排。coturn 单进程可撑数千路中继,瓶颈通常在网卡与端口池:每路中继占用一个端口,端口池上限决定单机路数;多机部署时前置负载均衡(对 3478 与 5349 做 TCP 负载均衡,中继流量天然按分配地址分散)。
背景:某产品上线TURN 后出现怪象:约一成用户第一次连接必失败,重试又好了;换个网络环境又复现。
操作:对照失败用户的日志发现 srflx 正常、relay 候选收集超时;在测试环境复现时抓到 TURN 返回的凭证校验错误。根因是运维把「凭证有效期为当前时间加五分钟」的生成逻辑写成了「服务器启动时间加五分钟」——服务跑过五分钟后,签出的凭证天然过期;客户端首次连接用新凭证成功与否取决于哪台 TURN 实例接单,多实例时钟与启动时间参差,造成随机成败。
结果:修正为按当前时间动态签发、TURN 侧允许少量时钟偏移,故障消失;同时把凭证签发接入了监控(签发失败率、校验失败率两条曲线),此后同类问题五分钟内可见。
解读:凭证是 ICE 流程里少数「两端协作」的安全机制,也是极少数会随时间腐烂的配置。凡是带时间的凭证,必须同时监控「签发」与「校验失败」两侧——只在业务侧看成功率的团队,会漏掉这类随机性故障的最早信号。
变式:纯内网部署(企业会议内网一体机)可以反向操作:网络可达性有保证时,STUN 都可以省,host 候选直连即可,TURN 仅作为防火墙隔离区的过渡配置。基础设施跟着网络环境走,没有放之四海的必配清单。