WebRTC源码研究(27)TURN协议
文章目录
- WebRTC源码深入分析(27)TURN协议
- 第一节 TURN协议概述
- 第二节 TURN客户端与服务器的处理流程
- 第三节 TURN消息处理过程
- 第三章(turn)消息发送过程
- 第四章(turn)信道传输机制
第3章 TURN服务器部署方案
WebRTC源码研究(27)TURN协议
在之前的章节中已经介绍了STUN协议,在讲述P2P打洞原理的章节中也提到了TURN协议,在我们面对对称性NAT这种特性限制时,默认依赖常规穿透方式已经变得不可行的情况下,则不得不借助于STUN机制进行转发。这样的做法虽然有效但也会带来一定的性能开销。STUN/RFC5389协议里能处理的也只有市场主流的Cone NAT配置(关于NAT类型的具体内容请参见《P2P通信原理与实现》)。而对于对称性NAT这种特殊场景而言传统的穿透方法已经无法满足需求因此我们需要专门针对这种情况设计一种保证通信成功的中继机制(Relaying)。虽然这种方法会增加服务器的工作负担但至少能够突破最坏情况下信道连接受限的问题从而真正解除对NAT类型的制约限制了网络架构的发展方向TURN/RFC5766正是为了应对这一挑战而被提出并得到了广泛的应用
本篇深入介绍TURN协议的技术细节,并引导读者访问官方网站获取电子版文档阅读。
TURNServer提供了丰富的开源代码库供研究人员深入研究。访问该项目地址获取最新版本:TURNServer


1. TURN协议简介
TURN 协议支持 NAT 后端对象经由 TCP/UDP 接收网络流量。当采用对称式 NAT(或防火墙配置)时,在特定应用场景下该方法表现尤为出色。
采用TURN方式解决NAT问题的过程与STUN机制具有相似性。它基于私网接入用户通过特定机制预先获取其私有IP地址对应的公网IP地址(其中,在出口端网(export NAT endpoint)上对应的公网IP地址是出口NAT上使用的端口映射关系所映射的结果,在turn server上则是该用户的分配目标),然后在报文负载中所描述的IP信息直接填写该预处理后的公网IP address的方式。其工作原理实质上与 STUN 机制一致,并且这种机制确保了网络内部私有IP资源的有效利用。
Turn (简称TURN)的全称是Traversal Using Relay NAT(TURbo NAT),即采用Relay方式进行穿透。TURN应用模型通过将TURNServer分配的地址及端口作为客户端对外提供的接收地址及端口。这种应用模型不仅继承了STUN方式的优点(如可实现NAT穿透),还解决了STUN方案无法穿透对称NAT(SymmetricNAT)以及类似Firewall设备的问题(无论企业网/驻地网出口为哪种类型的NAT/FW都能实现穿透)。此外TURN还支持基于TCP协议的应用(如H323协议)。 TURN服务器负责将相关地址及端口分配给客户端使用,并能指定RTP/RTCP地址对(其中RTCP端口号为RTP端口号加1)作为本地客户的接收地址以避免出口NAT下任意分配RTCP地址的问题。这样一来客户端就不会收到来自对端发送来的RTCP报文(当对端发送RTCP报文时目的端口号缺省按RTP端口号加1发送)。
TURN的一个局限性在于任何报文都需要通过TURNServer进行转发,在这种情况下可能会增加传输过程中的延迟以及可能导致数据丢失的风险。
2. TURN 客户端,服务器处理流程
我在前面的文章中对TURN相关内容进行了介绍。如果需要更深入的理解,请参阅官方文档 RFC 5766。如需进一步了解,请参阅官方文档 RFC 5766。
通常情况下,在内网内部运行的(turn)客户端会借助一个或多个(nat)设备将流量转发至公网上运行的(turn server)作为中继节点。位于公网上运行的(turn server)作为中继节点与各个客户端建立联系
Peer A
Server-Reflexive +---------+
Transport Address | |
192.0.2.150:32102 | |
|/||
TURN | / ^| Peer A |
Client’s Server | / || |
Host Transport Transport | // || |
Address Address | // |+---------+
10.1.1.2:49721 192.0.2.15:3478 |+-+ // Peer A
| | ||N| / Host Transport
| +-+ | ||A|/ Address
| | | | v|T| 192.168.100.2:49582
| | | | /+-+
+---------+| | | |+---------+ / +---------+
|N|//|||||||||
|---|---|---|---|---|---|---|---|---|---|
|Client|----|A|----------|Server|------------------|Peer B||||
|^|^ ^|||||||||
|T||||||||||
+---------+ | || +---------+| |+---------+
|||||
|---|---|---|---|
+-+| | |
|||
|---|---|
Client’s | Peer B
Server-Reflexive Relayed Transport
Transport Address Transport Address Address
192.0.2.1:7000 192.0.2.15:50000 192.0.2.210:49191
shell

在上图中可以看到左侧放置了一个位于NAT后的 TURN 客户端(内网IP为10.1.1.2:49721),该客户端通过TCP/IP 连接到公网上的 TURN 服务器(默认端口3478)。当该 TURN 服务器接收到请求后会返回一个 Client 的反射地址(即NAT赋予的公网上IP及端口)192.0.2.1:7000,并将此反射地址赋值给 Client 的 ALLOCATION 数据结构。随后(turn server)会根据需求自动生成 Client 的中继地址(图示为192.0.2.15:50000),两个对等端若要通过TURN 协议与 Client 实现通信则可直接发送至该中继地址进行数据收发即可(turn server)会将发送至指定中继地址的数据自动转发至对应的 Client (其反射地址为此处所指)。
在每一个Server上都有一个独特的allocation与每个client一一对应,并且仅有一个中继地址。当数据包抵达某个中继地址时,在这种情况下服务器总是能够明确地确定其去向。值得注意的是,在同一个Server上可能存在多个allocation供同一Client使用
按照协议规定,在 peer 和 TURN 服务器之间采用 UDP 方式建立连接。然而,在不同设备之间传输 STUN 报文时可选的方式不仅限于 UDP 而是包括 TCP、UDP 以及 TLS-over-TCP 等多种方式。当多个客户端通过中继节点传递数据时,在某些情况下若使用了 TCP 协议,在服务端可能会被转换为 UDP 格式。因此为了确保高效性与可靠性,建议客户端始终采用 UDP 协议进行通信。值得注意的是,尽管如此,在实际应用中发现部分防火墙会对所有 UDP 数据进行过滤拦截。而针对那些需要三次握手建立连接的 TCP 协议而言,则采取了更加灵活的安全策略。
分配(Allocations)
要在服务器端获得一个中继分配,客户端须使用分配事务. 客户端发送分配请求(Allocate request)到服务器,然后服务器返回分配成功响应,并包含了分配的地址.客户端可以在属性字段描述其想要的分配类型(比如生命周期).由于中继数据实现了安全传输,服务器会要求对客户端进行验证,主要使用STUN的long-term credential mechanism.
一旦中继传输地址分配好,客户端必须要将其保活.通常的方法是发送刷新请求(Refresh request)到服务端.这在TURN中是一个标准的方法.刷新频率取决于分配的生命期,默认为10分钟.客户端也可以在刷新请求里指定一个更长的生命期,而服务器会返回一个实际上分配的时间. 当客户端想中指通信时,可以发送一个生命期为0的刷新请求.服务器和客户端都保存有一个成为五元组(5-TUPLE)的信息,比如对于客户端来说,五元组包括客户端本地地址/端口,服务器地址/端口,和传输协议;服务器也是类似,只不过将客户端的地址变为其反射地址,因为那才是服务器所见到的. 服务器和客户端在分配请求中都带有5-TUPLE信息,并且也在接下来的信息传输中使用,因此彼此都知道哪一次分配对应哪一次传输.
如下图所示,客户端首先发送Allocate请求,但是没带验证信息,因此STUN服务器会返回error response,客户端收到错误后加上所需的验证信息再次请求,才能进行成功的分配.
TURN TURN Peer Peer
client server A B
|-- Allocate request --------------->|||
|---|---|---|
|<--------------- Allocate failure --|||
|(401 Unauthorized)|||
||||
|-- Allocate request --------------->|||
||||
|<---------- Allocate success resp --|||
|(192.0.2.15:50000)|||
// // // //
||||
|---|---|---|
||||
|<----------- Refresh success resp --|||
||||
shell

3. TURN 消息处理机制
3.1 TURN 消息发送机制
Client和Peer之间有两种方法通过TURN server交换应用信息:
- 第一种: 是使用Send和Data方法(method)
- 第二种: 是使用通道(channels)
两个方案均以特定的方式向服务器指定接收数据的peers, 同时, 服务器会通知客户端所收到的数据来自哪个peers.
该系统实现了数据传递功能(Indication),其中主要包括Send指令与Data指令两部分的工作流程。具体而言,在发送操作中(Send Instruction),客户端会向服务端发送一个Send Indication信息(Indication),该信息用于触发数据传输过程并携带必要的传输参数信息(Parameter)
当系统执行发送操作时(Send Operation),客户端会向服务端发送一个完整的数据包(Data Packet),其包含两个关键字段:源地址字段(Source Address)与目标地址字段(Destination Address)。这两个字段分别标识了当前的数据包的目的接收方以及最终的接收位置(Position)。
- XOR-PEER-ADDRESS属性定义了对等端(服务器侧)的地址。
- DATA属性用于传递给对等端的信息。
当服务器接收到发送指示后,负责将DATA字段中的数据进行解码,并按照UDP协议将其打包传输至相应的接收点.同时,在打包数据包时,指定该客户端作为源地址.这样做的结果是,来自对等端发送至中继节点的数据也会被服务器按照UDP协议转发回客户端.
值得注意的是,在与 peers 连接时若客户端未配置授权则指示包会被丢弃。
具体而言,
SEND/DATA INDICATION字段不具备认证功能,
原因在于长期验证机制无法验证 indication字段的存在。
为了避免潜在的安全威胁,
TURN机制要求客户端在向对等端发送指示包之前必须先获取并安装相应的授权(permission)。
如图所示,
在与 peers 连接时若客户端未配置授权则指示包会被丢弃。
同样地,
在 peer 端也会遇到同样的问题。
TURN TURN Peer Peer
client server A B
||||
|---|---|---|
|<-- CreatePermission success resp --|||
||||
|--- Send ind (Peer A)-------------->|||
|=== data ===>|||
||||
|<== data ====|||
|<-------------- Data ind (Peer A) --|||
||||
||||
|--- Send ind (Peer B)-------------->|||
|dropped|||
||||
|<== data ==================|||
|dropped|||
||||
shell

3.2 TURN 信道机制
对于某些应用,比如VOIP(Voice over IP),在Send/Data Indication中增加的36字节 数据格式信息会增加客户端和服务端之间的带宽负担.
以改善这种情况为例, Turn 采用了第二种方法来实现客户与 peer 之间的数据传输. 这一方法采用了另一种数据包格式, 即 ChannelData message 和 信道数据报文.
ChannelData消息未采用STUN头部,而是引入了一个4字节的新头部,该头部包含了称为信道编号的一个值(channel number).每个信道编号都与特定的一个 peer 联系起来,即用于标识一个对等端地址.
为了实现通道与对等端的绑定配置,客户端应当发起一次通道绑定请求(ChannelBind Request),并指定一个当前未被配对的信道编号以及对应的端点地址信息。
在绑定完成后, client 和 server 均可通过 ChannelData message 实现数据发送与转发功能。信道绑定的默认时长设定为10分钟,并且可以通过重发 ChannelBind Request 来延长其持续时间。与 Allocation 不同, 无法直接取消或删除已建立的绑定关系; 只有当连接超时才会自然失效.]
TURN TURN Peer Peer
client server A B
||||
|---|---|---|
|(Peer A to 0x4001)|||
||||
|<---------- ChannelBind succ resp --|||
||||
|-- [0x4001] data ------------------>|||
|=== data ===>|||
||||
|<== data ====|||
|<------------------ [0x4001] data --|||
||||
|--- Send ind (Peer A)-------------->|||
|=== data ===>|||
||||
|<== data ====|||
|<------------------ [0x4001] data --|||
||||
shell

图中所示的数值`0x4001$代表信道标识码,该标识码位于ChannelData message消息头中的前两个字节位置。值得注意的是,该信道号需满足以下条件:
...: 该代码范围内的值不得用于信道标识...: 该区间内共有16383个不同的赋值空间,在当前系统设计中已被充分应用...: 该区域已经被保留下来以便在未来开发过程中进行扩展
3. TURN服务器搭建
3.1 编译安装
首先配置OpenSSL进行安装,适用于 macOS 旧版本(至 10.12 版本之前)。从 macOS 10.12 开始需手动完成 Open SSL 安装过程,并且 Open SSL 覆盖了多种加密与解密处理功能。
打开终端,直接命令:
linux下:
sudo apt-get install libssl-dev
shell
Mac下可以用brew install 安装:
brew install openssl
shell
推荐你自己下载源码编译安装。
- 编译安装 libevent 最新版;
wget https://github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz
tar xvfz libevent-2.0.21-stable.tar.gz
cd libevent-2.0.21-stable
./configure
make
sudo make install
shell
- 安装 coturn :
coturn可以选择使用多种数据库,这里使用的是SQLite,使用命令sudo apt-get install sqlite (or sqlite3)和sudo apt-get install libsqlite3-dev (or sqlite3-dev)安装;
编译coturn: 在 coturn 下载页 下载最新正式版本的 cotrun 源码,下载完成后使用tar xvfz turnserver-<...>.tar.gz命令解压;
依次使用./configure、make和make install编译安装 coturn 。
tar xvfz turnserver-<...>.tar.gz
./configure
make
sudo make install
shell
你也可以直接命令下载编译coturn:
git clone https://github.com/coturn/coturn
cd coturn
./configure
make
make install
shell
输入which turnserver,如果打印出路径,说明安装成功
安装完成后在 bin 目录下生成六个可执行文件:
- turnserver - 配置与管理 STUN/TURN 服务
- turnadmin - 提供账户配置与管理功能
- turnutils_stunclient - 包含用于 STUN 测试的一个客户端
- turnutils_uclient - 模拟多个 UDP、TCP、TLS 或 DTLS 类型客户端以测试 TURN 服务
- turnutils_peer - 支持 peer 对接功能
- turnutils_rfc5769check - 能够验证 RFC 5769 标准
3.2 配置使用
coturn 提供了命令行界面、配置文件以及数据库管理选项。该软件包支持以下几种数据库类型:SQLite、MySQL、PostgreSQL、MongoDB 和 Redis。安装完成后,默认情况下 SQLite 数据库会存储在 /usr/local/var/db 或 /var/db/turndb 目录中,并以 turndb 为名称。
该认证机制指出了两类认证途径:Long-Term Credential 和 Short-Term Credential。
具体来说,则需遵循 STUN 规范 http://tools.ietf.org/html/rfc5389#section-15.4 。然而,在 WebRTC 应用中,则主要支持 Long-Term Credential
- 配置coturn
使用turnadmin生成安全访问密码
#使用turnadmin生成安全访问密码
turnadmin -k -u username -r north.gov -p password
shell
/usr/local/etc/turnserver.conf配置
#listening-ip与relay-ip采用内网ip,external-ip是外网的ip
listening-port=444 #监听端口
external-ip=210.21.53.158 #外网IP
verbose
fingerprint
lt-cred-mech
realm=test
user=username:生成的加密密码 #修改成自己的
user=username:password #修改成自己的
stale-nonce
no-loopback-peers
no-multicast-peers
mobility
no-cli
shell

- 启动coturn
turnserver -o -a
shell
3.2.1 配置 Long-Term 用户
首先使用下列命令添加一个 Long-Term 用户:
sudo turnadmin -a -u you_name -p you_password -r you_realm
shell
这里默认使用的是 SQLite 数据库,在启动 turnserver 系统时,请务必指定 realm 参数;只有拥有该 realm 权限的用户才能成功登录。其中 '-a' 参数用于添加长期用户,在数据库配置中 '-a' 标识符表示将新增记录存储到 long-term table 中;'-u' 是用户名标识符;'-p' 是用户密码字段;'-r' 则指示该用户的 realm 配置。
注意一定要使用 root 权限配置,否则会配置失败,但是还没有错误提示。
3.2.2 启动服务器
完成用户的配置后即可正常运行turnserver服务。在初次启动时必须提供一个配置文件以支持其生成过程,请确保所有必要的设置已完成后再进行下一步操作。
sudo cp /usr/local/etc/turnserver.conf.default /usr/local/etc/turnserver.conf
sudo turnserver -a -f -v -r you_realm
shell
在其中,在-a处表示采用了基于长机制的技术实现持久化功能;而在-r处则指定了特定的Realms;仅限于这些被指定Realms中的用户才有资格调用服务器
3.2.3 测试 STUN
可以通过运行这些命令来验证 STUN 连接状态;该选项仅适用于 STUN 服务器提供的 IP 或域名称
# 测试 STUN
turnutils_stunclient 123.166.110.69
shell
3.2.4 测试 TURN
通过以下指令可以验证 TURN 服务是否可用;须在 turnserver 启动时指定 Realm 下的用户
# 测试 TURN
turnutils_uclient -u cjx -w 123456 123.166.110.69
shell
另外一种方式是基于 Google 的 WebRTC STUN/TURN 技术用于测试:链接地址为 https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/。
3.2.5 开机启动服务
为了使 turnserver 自动启动,必须先编辑配置文件 named_turnserver。通常会更改以下几个相关参数设置。
listening-ip=127.0.0.1
listening-ip=172.16.0.99 # 内网ip
external-ip=221.208.117.45 # 公网ip,如果服务器在NAT后需要指定该参数
fingerprint
lt-cred-mech
realm=<you_realm_name>
shell
在对 turnserver.config 进行优化后手动操作,在 Ubuntu 14.04 系统中通过以下命令启用开机启动项:
sudo update-rc.d turnserver defaults
shell
如果遇到 update-rc.d: /etc/init.d/turnserver 这个问题时,请可以直接运行以下命令来建立一个软连接
sudo ln -sf /usr/local/bin/turnsever /etc/init.d/turnserver
shell
添加/etc/systemd/system/turnserver.service
[Unit]
Description=coturn
Documentation=man:coturn(1) man:turnadmin(1) man:turnserver(1)
After=syslog.target network.target
[Service]
Type=forking
PIDFile=/var/run/turnserver.pid
ExecStart=/usr/local/bin/turnserver --daemon --pidfile /var/run/turnserver.pid -c /etc/turnserver.conf
ExecStopPost=/usr/bin/rm -f /var/run/turnserver.pid
Restart=on-abort
LimitCORE=infinity
LimitNOFILE=999999
LimitNPROC=60000
LimitRTPRIO=infinity
LimitRTTIME=7000000
CPUSchedulingPolicy=other
shell

然后执行以下命令:
# 使服务自动启动
sudo systemctl enable turnserver.service
# 启动服务
sudo systemctl start turnserver
# 停止服务
sudo systemctl stop turnserver
shell
3.2.6 负载均衡
coturn 可以通过配置一个 master turn server 并行部署多个 slave turn servers 以实现负载均衡。
对于 master 服务器来说,在启动时仅需配置 --alternate-server 和 --tls-alternate-server 这两个命令参数即可实现负载均衡功能。具体操作步骤,请参考 source/examples/scripts/loadbalance 文件夹中的多个文档。
参考文章:
https://github.com/rainzhaojy/blogs/issues/4
