WebRTC源码研究(27)TURN协议

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

rfc5766-turn-server

1. TURN协议简介

TURN协议允许NAT或者防火墙后面的对象可以通过TCP或者UDP接收到数据。这在使用了对称式的NAT(或者防火墙)的网络中尤其具有实用价值 。

TURN方式解决NAT问题的思路与STUN相似,是基于私网接入用户通过某种机制预先得到其私有地址对应在公网的地址(STUN方式得到的地址为出口NAT上的地址,TURN方式得到地址为TURNServer上的地址),然后在报文负载中所描述的地址信息直接填写该公网地址的方式,实际应用原理也是一样的。

TURN的全称为Traversal Using Relay NAT,即通过Relay方式穿越NATTURN应用模型通过分配TURNServer的地址和端口作为客户端对外的接受地址和端口,即私网用户发出的报文都要经过TURNServer进行Relay转发,这种方式应用模型除了具有STUN方式的优点外,还解决了STUN应用无法穿透对称NAT(SymmetricNAT)以及类似的Firewall设备的缺陷,即无论企业网/驻地网出口为哪种类型的NAT/FW,都可以实现NAT的穿透,同时TURN支持基于TCP的应用,如H323协议。此外TURNServer控制分配地址和端口,能分配RTP/RTCP地址对(RTCP端口号为RTP端口号加1)作为本端客户的接受地址,避免了STUN应用模型下出口NATRTP/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命令创建或管理ALLOCATIONallocation是服务器上的一个数据结构,包含了中继地址的信息。
服务器随后会给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)到服务器,然后服务器返回分配成功响应,并包含了分配的地址.客户端可以在属性字段描述其想要的分配类型(比如生命周期).由于中继数据实现了安全传输,服务器会要求对客户端进行验证,主要使用STUNlong-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 消息发送机制

ClientPeer之间有两种方法通过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提供了第二种方法来让clientpeer交互数据.该方法使用另一种数据包格式,即ChannelData message,信道数据报文.

ChannelData message不使用STUN头部,而使用一个4字节的头部,包含了一个称之为信道号的值(channel number).每一个使用中的信道号都与一个特定的peer绑定,即作为对等端地址的一个记号.

要将一个信道与对等端绑定,客户端首先发送一个信道绑定请求(ChannelBind Request)到服务器,并且指定一个未绑定的信道号以及对等端的地址信息.

绑定后clientserver都能通过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 编译安装

  1. 首先编译安装OpenSSL ,对应mac用户 macos10.12之前的系统都是系统自带的,macos10.12之后需要自己安装,openssl包含了很多加密,解密的处理算法。

打开终端,直接命令:

linux下:

sudo apt-get install libssl-dev

Mac下可以用brew install 安装:

brew install openssl

推荐你自己下载源码编译安装。

  1. 编译安装 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
  1. 安装 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 命令解压;
    依次使用 ./configuremakemake 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 目录下生成六个可执行文件:

  1. turnserver - STUN/TURN 服务器
  2. turnadmin - 用于配置、管理账户
  3. turnutils_stunclient - 用于测试 STUN 服务
  4. turnutils_uclient 用于测试 TURN 服务,模拟多个UDP、TCP、TLS 或 DTLS 类型的客户端
  5. turnutils_peer
  6. turnutils_rfc5769check

3.2 配置使用

coturn 支持三种配置:命令行、conf文件和数据库, 数据库支持sqlite,mysql,postgresql,MongoDB,redis。安装后的SQLite 数据库文件可能在 /usr/local/var/db/var/db/turndb 路径下,名称为 turndb 。

STUN 定义了两种验证方式:Long-Term CredentialShort-Term Credential

具体可以参考 STUN 标准 http://tools.ietf.org/html/rfc5389#section-15.4 。但是对于 WebRTC 而言,仅支持 Long-Term Credential 。

使用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
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

搭建WebRtc服务器

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值