文章目录
WebRTC源码研究(27)TURN协议
前面的章节中有讲到STUN
协议,P2P打洞原理,其实在P2P打洞这个章节中有讲解到TURN
协议,就是我们在NAT类型的四种类型中,对称性NAT我们是无法穿透的,在我们P2P 用常规的方式无法穿透时,我们才借助于STUN 转发,这样的代价是很大的。
由于STUN/RFC5389协议里能处理的也只有市面上大多数的Cone NAT(关于NAT类型可以参照P2P通信原理与实现),
对于对称性 NAT,传统的P2P打洞方法是不适用的。因此为了保证通信能够建立,我们可以在没办法的情况下用保证成功的中继方法(Relaying),虽然使用中继会对服务器负担加重,而且也算不上P2P,但是至少保证了最坏情况下信道的通畅,从而不至于受NAT类型的限制。TURN/RFC5766就是为此目的而进行的拓展。
本篇主要详细讲解TURN
协议,具体更多细节可以参考官方文档:TURN/RFC5766 下载相关pdf细细品阅。
关于TURN Server有很多现成的开源代码,你可以从这里下载一个研究研究:TURN Server
1. TURN协议简介
TURN
协议允许NAT
或者防火墙后面的对象可以通过TCP
或者UDP
接收到数据。这在使用了对称式的NAT(或者防火墙)
的网络中尤其具有实用价值 。
TURN
方式解决NAT
问题的思路与STUN
相似,是基于私网接入用户通过某种机制预先得到其私有地址对应在公网的地址(STUN方式得到的地址为出口NAT上的地址,TURN方式得到地址为TURNServer上的地址
),然后在报文负载中所描述的地址信息直接填写该公网地址的方式,实际应用原理也是一样的。
TURN
的全称为Traversal Using Relay NAT
,即通过Relay方式穿越NAT,TURN
应用模型通过分配TURNServer
的地址和端口作为客户端对外的接受地址和端口,即私网用户发出的报文都要经过TURNServer
进行Relay转发,这种方式应用模型除了具有STUN
方式的优点外,还解决了STUN
应用无法穿透对称NAT(SymmetricNAT)以及类似的Firewall设备的缺陷,即无论企业网/驻地网出口为哪种类型的NAT/FW,都可以实现NAT的穿透,同时TURN
支持基于TCP
的应用,如H323
协议。此外TURNServer
控制分配地址和端口,能分配RTP/RTCP
地址对(RTCP
端口号为RTP
端口号加1)作为本端客户的接受地址,避免了STUN
应用模型下出口NAT
对RTP/RTCP
地址端口号的任意分配,使得客户端无法收到对端发过来的RTCP
报文(对端发RTCP
报文时,目的端口号缺省按RTP
端口号加1发送)
TURN
的局限性在于所有报文都必须经过TURNServer
转发,增大了包的延迟和丢包的可能性。
2. TURN 客户端,服务器处理流程
我在前面的博客“[WebRTC源码研究(25)NAT打洞原理]” 中有讲解到TURN 相关的流程,这里再详细讲解一下,更多细节可以参考官方文档:TURN/RFC5766
在典型的情况下,TURN
客户端连接到内网中,并且通过一个或者多个NAT
到达公网,TURN服务器
架设在公网中,不同的客户端以TURN服务器
为中继和其他peer进行通信,如下图所示:
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| || | // | |
| TURN |v | | v| TURN |/ | |
| 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
在上图中,左边的TURN Client
是位于NAT
后面的一个客户端(内网地址是10.1.1.2:49721),连接公网的TURN服务器
(默认端口3478)后,
服务器会得到一个Client
的反射地址(Reflexive Transport Address
, 即NAT分配的公网IP和端口)192.0.2.1:7000,此时Client
会通过TURN
命令创建或管理ALLOCATION
,allocation
是服务器上的一个数据结构,包含了中继地址的信息。
服务器随后会给Client
分配一个中继地址,即图中的192.0.2.15:50000,另外两个对等端若要通过TURN
协议和Client
进行通信,可以直接往中继地址收发数据即可,TURN服务器
会把发往指定中继地址的数据转发到对应的Client
,这里是其反射地址。
Server
上的每一个allocation
都唯一对应一个client
,并且只有一个中继地址,因此当数据包到达某个中继地址时,服务器总是知道应该将其转发到什么地方。
但值得一提的是,一个Client
可能在同一时间在一个Server
上会有多个allocation
,这和上述规则是并不矛盾的。
-
传输
在协议中,TURN
服务器与peer
之间的连接都是基于UDP
的,但是服务器和客户端之间可以通过其他各种连接来传输STUN报文,比如TCP/UDP/TLS-over-TCP.
客户端之间通过中继传输数据时候,如果用了TCP
,也会在服务端转换为UDP
,因此建议客户端使用UDP
来进行传输. 至于为什么要支持TCP
,那是因为一部分防火墙会完全阻挡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 request ---------------->| | |
| | | |
|<----------- Refresh success resp --| | |
| | | |
3. TURN 消息处理机制
3.1 TURN 消息发送机制
Client
和Peer
之间有两种方法通过TURN server
交换应用信息:
- 第一种: 是使用Send和Data方法(method)
- 第二种: 是使用通道(channels)
两种方法都通过某种方式告知服务器哪个peer应该接收数据,以及服务器告知client数据来自哪个peer.
Send Mechanism
使用了Send和Data指令(Indication
).其中Send指令用来把数据从client
发送到server
,而Data指令用来把数据从
server
发送到client
.当使用Send指令时,客户端发送一个Send Indication
到服务端,其中包含:
XOR-PEER-ADDRESS
属性,指定对等端的(服务器反射)地址.- DATA属性,包含要传给对等端的信息.
当服务器收到Send Indication
之后,会将DATA
部分的数据解析出来,并将其以UDP
的格式转发到对应的端点去,并且在封装数据包的时候把client
的中继地址作为源地址.从而从对等端发送到中继地址的数据也会被服务器转发到client
上.
值得一提的是,Send/Data Indication
是不支持验证的,因为长效验证机制不支持对indication
的验证,因此为了防止攻击,TURN
要求client
在给对等端发送indication
之前先安装一个到对等端的许可(permission
),
如下图所示,client到Peer B
没有安装许可,导致其indication
数据包将被服务器丢弃,对于peer B
也是同样:
TURN TURN Peer Peer
client server A B
| | | |
|-- CreatePermission req (Peer A) -->| | |
|<-- CreatePermission success resp --| | |
| | | |
|--- Send ind (Peer A)-------------->| | |
| |=== data ===>| |
| | | |
| |<== data ====| |
|<-------------- Data ind (Peer A) --| | |
| | | |
| | | |
|--- Send ind (Peer B)-------------->| | |
| | dropped | |
| | | |
| |<== data ==================|
| dropped | | |
| | | |
3.2 TURN 信道机制
对于一些应用程序,比如VOIP(Voice over IP),
在Send/Data Indication
中多加的36字节格式信息会加重客户端和服务端之间的带宽压力.
为改善这种情况,TURN
提供了第二种方法来让client
和peer
交互数据.该方法使用另一种数据包格式,即ChannelData message,信道数据报文.
ChannelData message
不使用STUN
头部,而使用一个4字节的头部,包含了一个称之为信道号的值(channel number
).每一个使用中的信道号都与一个特定的peer
绑定,即作为对等端地址的一个记号.
要将一个信道与对等端绑定,客户端首先发送一个信道绑定请求(ChannelBind Request
)到服务器,并且指定一个未绑定的信道号以及对等端的地址信息.
绑定后client
和server
都能通过ChannelData message
来发送和转发数据.信道绑定默认持续10分钟,并且可以通过重新发送ChannelBind Request
来刷新持续时间.和Allocation
不同的是,并没有直接删除绑定的方法,只能等待其超时自动失效.
TURN TURN Peer Peer
client server A B
| | | |
|-- ChannelBind req ---------------->| | |
| (Peer A to 0x4001) | | |
| | | |
|<---------- ChannelBind succ resp --| | |
| | | |
|-- [0x4001] data ------------------>| | |
| |=== data ===>| |
| | | |
| |<== data ====| |
|<------------------ [0x4001] data --| | |
| | | |
|--- Send ind (Peer A)-------------->| | |
| |=== data ===>| |
| | | |
| |<== data ====| |
|<------------------ [0x4001] data --| | |
| | | |
上图中0x4001
为信道号,即ChannelData message
的头部中头2字节,值得一提的是信道号的选取有如下要求:
0x0000-0x3FFF
: 这一段的值不能用来作为信道号0x4000-0x7FFF
: 这一段是可以作为信道号的值,一共有16383种不同值在目前来看是足够用的0x8000-0xFFFF
: 这一段是保留值,留给以后使用
3. TURN服务器搭建
3.1 编译安装
- 首先编译安装OpenSSL ,对应mac用户 macos10.12之前的系统都是系统自带的,macos10.12之后需要自己安装,openssl包含了很多加密,解密的处理算法。
打开终端,直接命令:
linux下:
sudo apt-get install libssl-dev
Mac下可以用brew install 安装:
brew install openssl
推荐你自己下载源码编译安装。
- 编译安装 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
- 安装 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
你也可以直接命令下载编译coturn:
git clone https://github.com/coturn/coturn
cd coturn
./configure
make
make install
输入which turnserver,如果打印出路径,说明安装成功
安装完成后在 bin 目录下生成六个可执行文件:
- turnserver - STUN/TURN 服务器
- turnadmin - 用于配置、管理账户
- turnutils_stunclient - 用于测试 STUN 服务
- turnutils_uclient 用于测试 TURN 服务,模拟多个UDP、TCP、TLS 或 DTLS 类型的客户端
- turnutils_peer
- turnutils_rfc5769check
3.2 配置使用
coturn 支持三种配置:命令行、conf文件和数据库, 数据库支持sqlite,mysql,postgresql,MongoDB,redis
。安装后的SQLite 数据库文件可能在 /usr/local/var/db
或 /var/db/turndb
路径下,名称为 turndb 。
STUN 定义了两种验证方式: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
/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
- 启动coturn
turnserver -o -a
3.2.1 配置 Long-Term 用户
首先使用下列命令添加一个 Long-Term 用户:
sudo turnadmin -a -u you_name -p you_password -r you_realm
这里默认使用了 SQLite 数据库,其中 -a 表示添加一个 long-term 用户, -u 为用户名,-p 为密码,-r 为该用户所属的 Realm。在启动 turnserver 时需要指定 Realm ,只有该 Realm 下的用户才能登录。
注意一定要使用 root 权限配置,否则会配置失败,但是还没有错误提示。
3.2.2 启动服务器
配置完用户后就可以启动 turnserver 了,第一次启动前需要一个配置文件,这是使用模板生成,然后就可以启动 turnserver 了。
sudo cp /usr/local/etc/turnserver.conf.default /usr/local/etc/turnserver.conf
sudo turnserver -a -f -v -r you_realm
其中 -a 表示使用 long-term 机制, -r 为指定的 Realm ,只有该 Realm 下的用户可以使用服务器。
3.2.3 测试 STUN
使用下面的命令即可测试 STUN 服务使用可用,唯一此参数是 STUN 服务器的 IP地址或域名。
# 测试 STUN
turnutils_stunclient 123.166.110.69
3.2.4 测试 TURN
使用下面的命令即可测试 TURN 服务使用可用,值得注意的是必须使用 turnserver 启动时指定 Realm 下的用户。
# 测试 TURN
turnutils_uclient -u cjx -w 123456 123.166.110.69
也可以使用 Google 提供的 WebRTC STUN/TURN 测试页面进行测试:https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/ 。
3.2.5 开机启动服务
如果想要开机启动 turnserver,首先需要修改配置文件 turnserver.config,一般情况需要修改以下的几个参数:
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>
在手动修改好配置文件 turnserver.config 后,Ubuntu 14.04 系统下可以使用下列命令添加开机启动项:
sudo update-rc.d turnserver defaults
如果发生 update-rc.d: /etc/init.d/turnserver : file does not exist 错误,执行下列命令创建一个软链接即可:
sudo ln -sf /usr/local/bin/turnsever /etc/init.d/turnserver
添加/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
然后执行以下命令:
# 使服务自动启动
sudo systemctl enable turnserver.service
# 启动服务
sudo systemctl start turnserver
# 停止服务
sudo systemctl stop turnserver
3.2.6 负载均衡
coturn 可以通过设置一个 master turn server ,配置若干个 slave turn server 的方式实现负载均衡。
master 服务器只需在启动时指定 --alternate-server 和 --tls-alternate-server 参数即可,具体使用方法可以查看 source/examples/scripts/loadbalance
目录下的几个文件。
参考文章:https://blog.csdn.net/kenn1117/article/details/72822803
https://github.com/rainzhaojy/blogs/issues/4
https://github.com/coturn/coturn/wiki/CoturnConfig