P2P学习(三)网络传输基本知识---TURN协议

一:TURN协议了解

TURN的全称为Traversal Using Relays around NAT,是STUN/RFC5389的一个拓展,主要添加了Relay中继功能。

那么在特定的情景下,有可能使得终端无法和其对等端(peer)进行直接的通信,这时就需要公网的服务器作为一个中继,对来往的数据进行转发。

这个转发的协议就被定义为TURN。TURN和其他中继协议的不同之处在于,它允许客户端使用同一个中继地址(relay address)与多个不同的peer进行通信。

使用TURN协议的客户端必须能够通过中继地址和对等端进行通讯,并且能够得知每个peer的的IP地址和端口(确切地说,应该是peer的服务器反射地址)。

而这些行为如何完成,是不在TURN协议范围之内的。其中一个可用的方式是客户端通过email来告知对等端信息,另一种方式是客户端使用一些指定的协议,如“introduction” 或 “rendezvous”,详见RFC5128

如果TURN使用于ICE协议中,relay地址会作为一个候选,由ICE在多个候选中进行评估,选取最合适的通讯地址。一般来说中继的优先级都是最低的

TURN协议被设计为ICE协议(Interactive Connectivity Establishment)的一部分,而且也强烈建议用户在他们的程序里使用ICE,但是也可以独立于ICE的运行。

值得一提的是,TURN协议本身是STUN的一个拓展,因此绝大部分TURN报文都是STUN类型的,作为STUN的一个拓展,TURN增加了新的方法(method)和属性(attribute)。

因此阅读本章时最好先了解一下STUN协议。

(一)TURN协议出现的目的(解决对称NAT无法穿越的问题)

在之前我们介绍过,NAT的四种类型(完全锥型NAT (Full Cone NAT)、地址限制锥型NAT(Address  Restricted Cone NAT)、端口限制锥型NAT (Port Restricted Cone NAT)、对称型NAT (Symmetric NAT))。

当我们检测到一端是端口受限锥型一端是对称型或者两端都是对称型,那肯定是无法穿越的;

在NAT无法穿越的时候,我们如何才能保证业务的运行呢?

那这个时候就要引入TURN协议,TURN协议实际上就是在服务端架设一个TURN服务,客户端在发送数据的时候无法NAT穿越的时候将这个媒体流数据首先传给TURN服务,通过

TURN的中介然后转给其他的接收者,或者其他接收者也可以发送数据给这个TURN服务,TURN在转给client端。

这就是TURN 出现的目的。

(二)TURN协议与STUN/RFC5389协议的关系

其建立在STUN/RFC5389之上,消息格式使用STUN格式消息

上次我们说了STUN协议的头和body是什么样子的,那TURN协议就是建立在STUN协议之上的,它的协议头和body几乎是一样的,只是里面的一些属性和内容不一样,外壳形式什么

的都是一样的,所以很多服务器都是将STUN协议和TURN协议放在一起形成了一个服务器,就是既提供STUN的功能又提供TURN的功能 。

TURN Client要求服务端分配一个公共IP和Port用于接收或发送数据

实际上在进行TURN协议的时候我们应该将它分成两类,一类是TURN Client,一类是服务端,这与我们之前介绍的STUN是一样的,就是客户端服务器模式。

那如何在这个TURN服务器上提供这种中继服务呢?

那首先要TURN Client向TURN 服务端发送一个请求,发送了请求之后就就在服务端根据这个请求建立一个公共的IP地址和端口用户接收和发送数据 。

那么对端(TURN client想要通讯的对端)其实是不需要是一个TURN Client端的,它只需要正常的发送UDP包就可以了。

(三)TURN使用案例

下面我们讲个具体的例子,下图是讲述整个TURN  Client端去请求服务,服务端创建相应的公网IP地址和端口提供服务,以及与各个Peer终端进行交互的一个案例:

由上图我们可以看的有个TURN client端和一个TURN Server端以及两个Peer对端 ,首先我们来看看他们是怎么通讯的:

整体流程:

在上图中,左边的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,这和上述规则是并不矛盾的。

Client<------>PeerB

首先一个TURN Client端是在一个NAT之后的,这个时候TURN Client端它要发送一个请求给这个TURN Server,那么TURN Server是在另外一个网络地址,端口是3487,

TURN Client在向TURN Server发送请求的时候会形成一组映射地址(出口)地址. 此时TURN Client发送一个Arrow的请求到TURN 服务端,TURN Server收到请求之后就会另外开通一个

服务地址 192.0.2.15的地址端口是50000来提供这种UDP中转的这种服务,

所以TURN Server对同一个TURN Client端来说有两个端口,一个是与TURN Client端连接的端口,另外一个是提供中转的端口50000,

如果它现在与Peer B进行通讯,Peer B与TURN Server是在同一个网段,地址是192.0.2.210端口是49191,这个时候它就可以向中转的TURN Server中转的去发数据了。

同样的他们建立连接之后 ,TURN  Server也可以给这个Peer B发送数据,那这个时候Peer B如果发送数据到 50000这个端口,在TURN server的内部就会转到3478然后最终中继给这个TURN Client端;

同样的如果TURN Client端想给Peer B发送消息的时候,它是先发到3478端口,然后经过内部的中转转成UDP然后再给Peer B,这就是它的一个逻辑。

Client<------>PeerA

那同样的在一个 NAT 之后的一个 Peer A也是可以通讯的,那么在通讯之前它首先要穿越NAT,在NAT上形成一个映射的IP地址也就是192.0.2.150端口是32102,
所以对TURN Server来说它可以识别这个IP地址和端口就是这个地址,那如果与Peer B 进行通讯的时候,它就通过50000这个端口向这个32102端口发送消息,那么Peer A就收到了。 相反的如果Peer A要给这个TURN Client端发送数据的时候,它就是往192.
0.2.15:50000这个端口发数据,从这个端口又转到3478这个端口,最终传给TURN Client端。

这就是整个TURN中转的基本的例子。通过这个例子大家就很清楚它是怎么工作的。

二:TURN协议通信流程

(一)TURN使用的传输协议

TURN Client 到TURN server使用的UDP、TCP包括加密后的TCP都是可以的,而对于TURN Server到Peer端,使用的都是UDP;

在协议中,TURN服务器与peer之间的连接都是基于UDP的,

但是服务器和客户端之间可以通过其他各种连接来传输STUN报文,比如TCP/UDP/TLS-over-TCP.

服务器与客户端之间通过中继传输数据时候,如果用了TCP,也会在服务端转换为UDP,因此建议客户端使用UDP来进行传输.

至于为什么要支持TCP,那是因为一部分防火墙会完全阻挡UDP数据,而对于三次握手的TCP数据则不做隔离.

(二)TURN Allocate请求

那TURN客户端如何让TURN Server提供通讯服务呢?

要在服务器端获得一个中继分配,客户端须使用分配事务,发送一个Allocate请求,在客户端首先发送一个Allocate请求到Server端,在Server首先是要做一些鉴权的处理,

如果发现请求没有相应的权限,就返回一个401,就是无权限未授权的 ,整个底层用的都是STUN的消息格式

TURN Client收到这个未授权的消息之后它会重新再发一个请求这次会把鉴权信息也带过来给Server端,Server端这时候鉴权就通过了,然后返回一个成功应答 ,就是我给你提供的IP地址和端口是多少。

这个时候Client就能和Peer  A和Peer B进行通讯了,那在这之前通过Peer A和Peer B它要通过一个信令服务器那要拿到他们相应的地址,否则的话还是没法通信的;

在这个前提之下如果中继服务已经开通了,TURN Client就可以源源不断的向TURN Server发送数据,然后TURN Server通过50000这个 端口给Peer A和Peer  B进行转发;

相反的如果Peer A 和Peer B给50000这个端口发数据,它就会被传给这个TURN 客户端;

除此之外这个TURN Client端还要发送一个Refresh请求,实际上就是保活用的相当于心跳。

在TURN Client端要求这个TURN Server分配 一个服务之后它是有一定的超时时间的,有可能是10分钟有可能是20分钟或者你设置为1个小时,那这个在这个设置时间段之内它是活着的,超过十分钟如果没有这个Refresh request刷新请求的话,那这个服务就算是失效了。客户端也可以在刷新请求里指定一个更长的生命期,而服务器会返回一个实际上分配的时间. 当客户端想中指通信时,可以发送一个生命期为0的刷新请求.

那如果在失效之前它收到这个心跳,它就返回了一个成功,继续演示上面设置的一段时间,这就是它整体的一个分配和保活的一个逻辑

(三)TURN 发送机制

两种方法都通过某种方式告知服务器哪个peer应该接收数据,以及服务器告知client数据来自哪个peer.

1.Send和Data

整个服务开通之后就涉及到对数据的发送了,那TURN有两种发送数据的机制,第一种就是Send和Data。

首先我们看Send和Data,要求服务端开启这个服务之后呢,紧接着就可以发送数据了,那在发送数据之前首先要鉴权,检查是否有发送的权限,这个完成之后它就来发数据。

那么首先客户端向服务端发送数据,信令是Send,发送到服务端,服务端收到数据之后需要将TURN协议头给去掉,拿到里面的数据 ,

里面的数据就是原始的UDP数据,拿到这个数据之后直接通过刚才的 5000端口转给你想转发的对端Peer A,那么数据就到Peer A,

Peer A如果想给这个TURN Client发送数据,那它发的是UDP数据,那到了中继服务之后,中继服务STUN首先给它加一个消息头,加了消息头之后再通过这个TURN Client转给这个TURN Client端,

所以这个Send和Data一个是表示上行一个是表示下型行。

补充1:send方法

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支持两种方式来创建许可,一种是发送CreatePermission request

2.Channel

send方法相比Channel的一个缺点就是在每次发送消息的时候都要带一个30多个字节的头,这个对于一般的情况下其实是不受影响的,

但是因为我们传输的是流媒体数据,它的数据量非常大 ,如果每个数据包都带一个30多个的字节头那对整个带宽是有很大影响的。那如何去解决这个问题呢?

为改善这种情况,TURN提供了第二种方法来让client和peer交互数据.该方法使用另一种数据包格式,即ChannelData message,信道数据报文.

ChannelData message不使用STUN头部,而使用一个4字节的头部,包含了一个称之为信道号的值(channel number).

每一个使用中的信道号都与一个特定的peer绑定,即作为对等端地址的一个记号.

要将一个信道与对等端绑定,客户端首先发送一个信道绑定请求(ChannelBind Request)到服务器,并且指定一个未绑定的信道号以及对等端的地址信息.
绑定后client和server都能通过ChannelData message来发送和转发数据.信道绑定默认持续10分钟,并且可以通过重新发送
ChannelBind Request来刷新持续时间.和Allocation不同的是,并没有直接删除绑定的方法,只能等待其超时自动失效.

有了另外一种数据传输方式就是Channel,Channel方式就是大家规定一个Channel  Id,我们都相当于加入了这个管道中,有了这个管道我们就不用每次都带着这个头消息告诉你这是什么?

一些基本信息在这里我们一开始就创建好了,我们都在管道里,我们现在只要发送数据就好了

在Channel模式下,首先客户端要发送一个ChannelBind绑定请求,服务端收到这个请求之后给它一个响应,

绑定这个ChannelId就是这个16进制的0x4001(随机值),就是随便一个数它都能随机产生的,当然不能重复,那当整个Channel创建成功之后,

Client端就可以发送数据给Server,那这个Server就可以转发数据给这个Peer A,

同样的如果Peer A如果想转发数据给这个TURN Server,那TURN Server再中继通过channel就转给这个Client端了。

另外Send和Data这种方式其实是可以继续发送的,也就是说Send、Data和Channel这两种方式是可以并存的,可以混着发的,这就是Channel。

上图中0x4001为信道号,即ChannelData message的头部中头2字节,值得一提的是信道号的选取有如下要求:

  • 0x0000-0x3FFF : 这一段的值不能用来作为信道号
  • 0x4000-0x7FFF : 这一段是可以作为信道号的值,一共有16383种不同值在目前来看是足够用的
  • 0x8000-0xFFFF : 这一段是保留值,留给以后使用

还是那句老话,关于协议具体的细节可以去翻阅RFC5766的草稿,其中每个属性以及其格式都介绍得很详细.

三:TURN的使用

TURN的使用,尤其是在WEBRTC中我们怎么使用:

首先是发送一个绑定,要进行这个打通,就是客户端到服务端要打通要拿到它的映射IP地址,

之后发起方Caller要调用allocation,就是让TURN Server开辟一个服务接收我发送数据的IP地址和端口,就是一个中继的IP地址和端口,那整个服务开通之后,

这个Caller获取了基本的信息,然后通过信令将这个SDP也就是说一些媒体信息还有网络信息通过这个 SDP的offer发送该这个被调用者;

对方收到这个信息之后也要给这个TURN服务发送一个allocation,也要创建一个这样的服务,用来接收对方的数据,

那这个创建成功之后,因为这个Caller给它发了一个Offer然后它要回一个answer,这样他们整个数据交换就交换完了,

在交换的过程中实际就是将这个candidate,我们说SE的时候回说到这个candidate,它是一个候选者,相当于我每一个IP地址和端口都是一个候选者,就是有可能进行网络传输的一个地址,所以把它叫做candidate,就是将这个 candidate的这个地址进行交换,那它这个信息就交换了,

那交换完成之后通过这个ICE这个框架,首先检查P2P能否成功,因为最高的传输效率还是端对端之间,它不经过第三方的服务也不受第三方的服务带宽的影响,只要双方的带宽够就好了,这个是最基本的,所以它是最高效的,所以首先检查 P2P是否能够打通NAT,如果打不通的话,这个时候就要通过中继,向对方所开通的端口去发送数据,这样另一方就能收到,同样的,另一方想要发送数据也给对方的中继服务发送数据就好了

那么以上就是整个TURN相关的一些基本的知识。

四: 协议交互过程详细举例

(一)相关术语

  • TURN client:遵循RFC5766的STUN客户端。
  • TURN server:遵循RFC5766的STUN服务器。
  • Peer:TURN客户端希望连接的主机。TURN服务器为TURN客户端和它的对端中继流量,但Peer并不与TURN服务器使用TURN协议进行交互,它接收从TURN服务器发送过来的数据,并向TURN服务器发送数据。
  • Transport Address:IP地址与端口号的组合。
  • Host Transport Address:客户端或对端的传输地址。
  • Server-Reflexive Transport Address:NAT公网侧的传输地址,该地址由NAT分配,相当于一个特定的主机传输地址。
  • Relayed Transport Address:TURN服务器上的传输地址,用于客户端和对端中继数据。
  • TURN Server Transport Address:TURN服务器上的传输地址,用于客户端发送STUN消息给服务器。
  • Peer Transport Address:服务器看到的对端的传输地址,当对端是在NAT后面,则是对端的服务器反射传输地址。
  • Allocation:通过Allocate请求将中继传输地址提供给客户端,除了中继状态外,还有许可和超时定时器等。
  • 5-tuple:五元组,包括客户端IP地址和端口,服务器IP地址和端口和传输协议(包括UDP、TCP、TLS)的组合。
  • Channel:通道号与对端传输地址的关联,一旦一个通道号与一个对端的传输地址绑定,客户端和服务器就能够利用带宽效应更大的通道数据消息来交换数据。
  • Permission:一个对端允许使用它的IP地址和传输协议来发送数据到TURN服务器,服务器只为从对端发来的并且匹配一个已经存在的许可的流量中继到相应的客户端。
  • Realm:服务器内用于描述服务器或内容的一个字符串,这个realm告诉客户端哪些用户名和密码的组合可用于认证请求。
  • Nonce:服务器随机选择的一个字符串,包含在报文摘要中。为了防止中继攻击,服务器应该有规律的改变这个nonce。

(二)交互过程

以上图为例进行讲解,每个消息中,多个属性包含在消息中并显示它们的值。

交互过程1:分配Allocate空间

1.客户端使用10.1.1.2:49271作为传输地址向服务器的传输地址发送Allocate请求。

客户端随机选择一个96位的事务ID。----因为TURN是基于STUN/RFC5389

该Allocate请求消息:

包括SOFTWARE属性提供客户端的软件版本信息;

包括LIFETIME属性,指明客户端希望该allocation具有1小时的生命期而非缺省的10分钟;

包括REQUESTED-TRANSPORT属性来告诉服务器与对端之间采用UDP协议来传输;

包括DONT-FRAGMENT属性因为客户端希望在随后的Send indications中使用DON’T-FRAGMENT属性

2.服务器需要任何请求必须是经过认证的,因此服务器拒绝了该最初的Allocation请求,并且回应了携带有错误响应号为401(未授权)的Allocate错误响应;该响应包括一个REALM属性,指明认证的域;还包括一个NONCE属性和一个SOFTWARE属性。

3.客户端收到了错误响应号为401的Allocate错误响应,将重新尝试发送Allocate请求,此时将包括认证属性

客户端在新的请求中重新选择一个新的事务ID。

客户端包括一个USERNAME属性,使用从服务器那收到的realm值来帮助它决定使用哪个值;

请求还包括REALM和NONCE属性,这两个属性是从收到的错误响应中拷贝出来的。

最后,客户端包括一个MESSAGE-INTEGRITY属性。

4.服务器收到认证的Allocate请求后,检查每个属性是否正确;然后,产生一个allocation,并给客户端回应Allocate成功响应。

服务器在该成功响应中携带一个LIFETIME属性,本例中服务器将客户端请求的1小时生命期减小为20分钟,这是因为这个特定的服务器可能不允许超过20分钟的生命期;

该响应包括XOR-RELAYED-ADDRESS属性,值为该allocation的中继传输地址

该响应还包括XOR-MAPPED-ADDRESS属性,值为客户端的server-reflexive地址(公网地址)

该响应也包含一个SOFTWARE属性;

最后,包括一个MESSAGE-INTEGRITY属性来证明该响应,确保它的完整性。

  XOR-MAPPED-ADDRESS和MAPPED-ADDRESS基本相同,不同点是反射地址部分经过一次异或(XOR)处理。
  X-port字段:是将NAT的映射端口以小端形式与magic cookie的高16位进行异或,再将结果转换成大段形式而得到的,X-Address也是类似。之所以要经过这么一次转换,是因为在实践中发现很多NAT会修改payload中自身公网IP的32位数据,从而导致NAT打洞失败。
XOR-MAPPED-ADDRESS和MAPPED-ADDRESS基本相同

交互过程2:创建许可

5.接着,客户端为了准备向对端A发送一些应用数据而创建一个permission

这里通过一个CreatePermission请求来做到。

该请求携带XOR-PEER-ADDRESS属性包含有确定的请求的IP地址(对端A的公网地址),这里为对端A的地址

需要注意的是,属性中地址的端口号被设置为0在CreatePermission请求中,并且客户端使用的是对端A的server-reflexive地址而不是它的主机地址(私网地址)

客户端在该请求中携带与之前的Allocate请求中一样的username、realm和nonce值,因此该请求被服务器认可

此时在该请求中,客户端没有携带SOFTWARE属性。

6.服务器收到该CreatePermission请求,产生一个相应的许可,并以CreatePermission成功响应来回应。

该响应中只包含了Transaction-ID和MESSAGE-INTEGRITY属性。 

交互过程3:客户端发送数据给对端PeerA---使用Send和Data

 

7.现在客户端使用Send indication来发送应用数据到对端A。

对端的server-reflexive传输地址包含在XOR-PEER-ADDRESS属性中,应用数据包含在DATA属性中。

客户端已经在应用层上执行了路径MTU发现功能,因此通过DON’T-FRAGMENT属性来告知服务器当通过UDP方式来向对端发送数据时应设置DF位。

Indications不能使用长期证书机制来认证,所以该消息中没有MESSAGE-INTEGRITY属性。

8.服务器收到Send indication后,提取出应用数据封装成UDP格式发给对端A;UDP报文的源传输地址为中继传输地址,并设置DF位。

9.对端A回应它自己的包含有应用数据的UDP包给服务器。目的地址为服务器的中继传输地址。当服务器收到后,将生成Data indication消息给客户端,携带有XOR-PEER-ADDRESS属性(对端A的公网)。应用数据包含在DATA属性中。

交互过程4:客户端绑定一个通道到对端B----开始使用Channel

10.客户端现在若要绑定一个通道到对端B,将指定一个空闲的通道号(本例中为0x4000)包含在CHANNEL-NUMBER属性中,对端B的传输地址包含在XOR-PEER-ADDRESS属性中。与以前一样,客户端再次利用上次请求中的username、realm和nonce。

11.当服务器收到该请求后,服务器绑定这个对端的通道号,为对端B的IP地址安装一个permission,然后给客户端回应一个ChannelBind成功响应消息。

交互过程5:客户端发送数据给对端PeerB---使用Channel

12.客户端现在发送一个ChannelData消息给服务器,携带有发送给对端B的数据。

这个消息不是一个STUN消息,因此没有事务ID。它只有3个字段:通道号、数据、数据长度;服务器收到后,检查通道号后发现当前已经绑定了,就以UDP方式发送数据给对端B。

13.接着,对端B发送UDP数据包回应给服务器的中继传输地址。服务器收到后,回应给客户端ChannelData消息,包含UDP数据包中的数据。

服务器知道是给哪个客户端发送ChannelData消息,这是因为收到的UDP数据包中的目的地址(即服务器的中继传输地址),并且知道使用的是哪个通道号,这是因为通道已经与相应的传输地址绑定了。

交互过程6:客户端刷新allocation

 

14.有时候,20分钟的生命期已经到了,客户端需要刷新allocation。此时通过发送Refresh请求来进行。该请求包含最后一次使用的username、realm和nonce,还包含SOFTWARE属性。

15.当服务器收到这个Refresh请求时,它注意到这个nonce值已经超期了,则给客户端回应一个错误响应号为438(过期Nonce)的Refresh错误响应,并提供一个新的nonce值。

16.客户端将重试该请求,此时携带新的nonce值。

17若第二次尝试被接受,服务器将回应一个成功响应。

需要注意的是,此时客户端在请求中没有携带LIFETIME属性,所以服务器刷新客户端的allocation时采用缺省的10分钟生命期。

五:搭建TURN/STUN服务见:

(一)搭建步骤(下面都可以)---使用腾讯云(15天免费系列)

https://blog.csdn.net/qq_28880087/article/details/106960293

https://www.jianshu.com/p/cf194367c779

https://blog.csdn.net/xqj198404/article/details/48222273

https://blog.csdn.net/weixin_33196646/article/details/112224589(重点:包含防火墙设置!!!)

(二)注意事项

按照上面搭建,发现在校园网中居然不显示stun服务的结果!!!

但实际上在服务端是显示了结果的:

并且是对称型NAT!!!

在手机热点下,可以正常显示结果:

原文地址:https://www.cnblogs.com/ssyfj/p/14798399.html