TURN协议简要介绍
参考:https://www.rfc-editor.org/rfc/rfc5766.html
https://www.rfc-editor.org/rfc/rfc5389.html
目录
TURN,在RFC5766中定义,英文全称Traversal Using Relays around NAT(TURN):Relay Extensions to Session Traversal Utilities for NAT(STUN),即使用中继穿透NAT:STUN的中继扩展。简单的说,TURN与STUN的共同点都是通过修改应用层中的私网地址达到NAT穿透的效果,异同点是TURN是通过两方通讯的“中间人”方式实现穿透。
如果一个主机位于NAT的后面,在某些情况下它不能够与其他主机点对点直接连接。在这些情况下,它需要使用中间网点提供的中继连接服务。TURN协议就是用来允许主机控制中继的操作并且使用中继与对端交换数据。TURN与其他中继控制协议不同的是它能够允许一个客户端使用一个中继地址与多个对端连接。
TURN协议被设计为ICE的一部分,用于NAT穿越,虽然如此,它也可以在没有ICE的地方单独使用。
在典型配置中,TURN客户端连接到专用网络[RFC1918]并通过一个或多个NAT连接到公共互联网。公共互联网上有一个TURN服务器。互联网上的其他地方是TURN客户端希望与之通信的一个或多个对等体。这些对等体可能支持也可能不支持一个或多个NAT。客户端使用服务器作为中继向这些对等体发送数据包,并从这些对等体接收数据包。
图1显示了一个典型的部署。在此图中,TURN客户端和TURN服务器由NAT分开,客户端位于NAT的私有端,服务器位于NAT的公共端。这个NAT被认为是一个“坏”NAT;例如,它可能具有“地址和端口相关映射”的映射属性(参见[RFC4787])。
由于客户端位于NAT之后,服务器将来自客户端的数据包视为来自NAT本身的传输地址。这个地址被称为客户端的服务器反射传输地址;由服务器发送到客户机的服务器反射传输地址的数据包将由NAT转发到客户机的主机传输地址。
客户端使用TURN命令在服务器上创建和操作ALLOCATION。ALLOCATION是服务器上的数据结构。该数据结构包含分配的中继(RELAYED)传输地址等。中继传输地址是服务器上的传输地址,对等方可以使用它让服务器将数据中继到客户端。分配由其中继传输地址唯一标识。
一旦创建了allocation,客户端可以向服务器发送应用程序数据以及数据将被发送到哪个对等体的指示,并且服务器将把该数据中继到适当的对等体。客户端在TURN消息中向服务器发送应用数据;在服务器上,数据从TURN消息中提取,并以UDP数据报的形式发送给对等方。相反,对等体可以将UDP数据报中的应用数据发送到中继传输地址进行分配;然后,服务器将把这些数据封装在TURN消息中,并将其发送给客户端,同时指明是哪个对等方发送了这些数据。因为TURN消息总是包含客户端正在与哪个对等体通信的指示,所以客户端可以使用单个分配来与多个对等体通信。
当对等点位于NAT之后时,客户端必须使用其服务器反射传输地址而不是主机传输地址来识别对等点。例如,在上面的示例中,要向对Peer A发送应用程序数据,客户端必须指定192.0.2.150:32102(Peer A的服务器反射传输地址),而不是192.168.100.2: 49582(Peer A的主机传输地址)。
在图2中,客户端向服务器发送一个没有凭据的Allocate请求。由于服务器要求使用stun的长期凭据机制对所有请求进行身份验证,因此服务器会使用401(未授权)错误代码拒绝该请求。客户端然后再次尝试,这次包括凭证(未示出)。这一次,服务器接受Allocate请求,并返回Allocate成功响应,其中包含分配给分配的中继传输地址。稍后,客户端决定刷新Allocate,从而向服务器发送刷新请求。刷新被接受,服务器回复刷新成功响应。
1、新的STUN方法
本节列出了本规范中定义的新STUN方法的代码点。有关这些新方法的语义,请参见本文的其他部分。
0x003 : Allocate (only request/response semantics defined)
0x004 : Refresh (only request/response semantics defined)
0x006 : Send (only indication semantics defined)
0x007 : Data (only indication semantics defined)
0x008 : CreatePermission (only request/response semantics defined
0x009 : ChannelBind (only request/response semantics defined)
2、新的STUN属性
这个STUN扩展定义了以下新属性:
0x000C: CHANNEL-NUMBER
0x000D: LIFETIME
0x0010: Reserved (was BANDWIDTH)
0x0012: XOR-PEER-ADDRESS
0x0013: DATA
0x0016: XOR-RELAYED-ADDRESS
0x0018: EVEN-PORT
0x0019: REQUESTED-TRANSPORT
0x001A: DONT-FRAGMENT
0x0021: Reserved (was TIMER-VAL)
0x0022: RESERVATION-TOKEN
其中一些属性的长度不是4的倍数。根据STUN规则,任何长度不是4字节倍数的属性都必须紧跟1到3个填充字节,以确保下一个属性(如果有的话)从4字节边界开始(参见[RFC5389])。
2.1、CHANNEL-NUMBER
CHANNEL-NUMBER属性包含频道号。该属性的值部分为4字节长,由一个16位无符号整数组成,后跟一个二进制八位数RFFU (保留供将来使用)字段,该字段在传输时必须设置为0,在接收时必须忽略。
2.2、LIFETIME
LIFETIME属性表示在没有refresh的情况下服务器将维持allocation的持续时间。该属性的值部分为4字节长,由一个32位无符号整数值组成,该整数值表示截止前剩余的秒数。
2.3、XOR-PEER-ADDRESS
XOR-PEER-ADDRESS指定TURN服务器上看到的peer的地址和端口。(例如,如果peer在NAT之后,则为peer的服务器反射传输地址。)其编码方式与XOR-MAPPED-ADDRESS [RFC5389]相同
2.4、DATA
DATA属性出现在所有Send和Data indication中。该属性的值部分是可变长度的,由应用程序数据组成(也就是说,如果数据是在客户端和peer之间直接发送的,将紧跟在UDP报头之后的数据)。如果该属性的长度不是4的倍数,则必须在该属性之后添加填充。
2.5、XOR-RELAYED-ADDRESS
XOR-RELAYED-ADDRESS出现在Allocate响应中。它指定服务器分配给客户端的地址和端口。它的编码方式与XOR-MAPPED-ADDRESS[RFC5389]相同。
2.6、EVEN-PORT
此属性允许客户端请求中继传输地址中的端口是偶数,并且(可选地)服务器保留下一个更高的端口号。该属性的值部分长度为1字节。其格式是:
该值包含一个1位标志:
R: 如果为1,则请求服务器保留下一个更高的端口号(在同一IP地址上)用于后续分配。如果为0,则不请求此类预订。
属性值的其他7位必须在传输时设置为零,在接收时忽略。
由于该属性的长度不是4的倍数,填充必须紧跟在该属性之后。
2.7、REQUESTED-TRANSPORT
客户端使用该属性为分配的传输地址请求特定的传输协议。此属性的值为4字节,格式如下:
Protocol字段指定所需的协议。此字段中使用的代码点取自IPv4报头的协议字段和IPv4报头[Protocol-Numbers]中的NextHeader字段中允许的代码点。该规范只允许使用码点17(User Datagram Protocol,UDP)。
RFFU字段在传输时必须设置为零,在接收时必须忽略。它是留作将来使用的。
2.8、DONT-FRAGMENT
客户端使用该属性请求服务器在将应用程序数据转发给peer时设置IP报头中的DF (Don't Fragment,不分段)位。该属性没有值部分,因此属性长度字段为0。
2.9、RESERVATION-TOKEN
RESERVATION-TOKEN属性包含唯一标识服务器保留的中继传输地址的token。服务器在成功响应中包括该属性,以告诉客户端关于token的信息,并且客户端在随后的Allocate请求中包括该属性,以请求服务器使用该中继传输地址进行分配。
属性值为8字节,包含token值。
3、新的STUN错误返回码
本文档定义了以下新的错误响应代码:
403 (Forbidden,禁止):请求有效,但由于管理或类似限制而无法执行。
437 (Allocation Mismatch,Allocation不匹配):服务器收到请求,要求allocation到位,但不存在allocation;或者收到请求, 不要求allocation,但存在allocation。
441 (Wrong Credentials,错误的凭据):(非Allocate)请求中的凭据与用于创建allocation的凭据不匹配。
442 (Unsupported Transport Protocol,不支持的传输协议): Allocate请求要求服务器在服务器和peer之间使用服务器不支持 的传输协议。注意:这不是指5元组中使用的传输协议。
486 (Allocation Quota Reached,已达到Allocation配额):目前无法使用此用户名创建更多Allocation。
508 (Insufficient Capacity,容量不足):由于达到了某些容量限制,服务器无法执行请求。在Allocate响应中,这可能是因为 服务器当时没有可用的中继传输地址,没有具有所请求属性的地址,或者对应于指定预留token的地址不可用。
4、详细示例
本节给出了TURN的使用示例,详细显示了所交换消息的内容。该示例使用概述中显示的网络图(图1)。
对于每条消息,都会显示消息中包含的属性及其值。为了方便起见,值以人类可读的格式显示,而不是显示实际的八位字节;例如,"XOR-RELAYED-ADDRESS=192.0.2.15:9000"表示XOR-RELAYED-ADDRESS属性包括192.0.2.15地址和9000端口,这里地址和端口在异或操作完成之前显示。对于具有类似字符串值的属性(例如,SOFTWARE="Example client, version 1.03"和NONCE="adl7W7PeDU4hKE72jdaQvbAMcr6h39sm"),属性的值以引号显示,以提高可读性,但这些引号不会出现在实际值中。
客户端首先选择一个用于TURN会话的主机传输地址;在这个例子中,客户端选择了10.1.1.2: 49721,如图1所示。客户端然后在服务器传输地址向服务器发送Allocate请求。客户端为此事务随机选择96位事务0xA56250D3F17ABE679422DE85;这在固定头的事务id字段中编码。客户端包括SOFTWARE属性,该属性给出关于客户端软件的信息;这里的值是"Example client, version 1.03",表示这是被称为示例客户端的1.03版。客户端包含LIFETIME属性,因为它希望分配具有比默认的10分钟更长的生命周期;该属性的值为3600秒,相当于1小时。客户端必须始终在分配请求中包含REQUESTED-TRANSPORT属性,并且该规范允许的唯一值是17,这表示服务器和对等方之间的UDP传输。客户端还包括DONT-FRAGMENT属性,因为它希望稍后在发送指示中使用DONT-FRAGMENT属性;这个属性只包含一个属性头,没有值部分。我们假设客户端最近没有与服务器交互,因此客户端不包括USERNAME, REALM, NONCE, 或 MESSAGE-INTEGRITY属性。最后,注意消息中属性的顺序是任意的(除了MESSAGE-INTEGRITY 和 FINGERPRINT),客户端可能使用了不同的顺序。
服务器要求对任何请求进行身份验证。因此,当服务器收到初始Allocate请求时,它会拒绝该请求,因为该请求不包含身份验证属性。遵循STUN [RFC5389]的长期凭证机制的过程,服务器包括值为401(Unauthorized)的ERROR-CODE属性、指定服务器所使用的认证领域的REALM属性(在本例中是服务器的域“example.com”),以及NONCE属性中的nonce值。服务器还包括一个SOFTWARE属性,它提供了关于服务器软件的信息。如下图:
客户端在接收到401错误后,重新尝试Allocate请求,这次包括认证属性。客户端选择一个新的事务id,然后用与以前相同的属性填充新的Allocate请求。客户端包括USERNAME属性,并使用从服务器接收的realm值来帮助它确定使用哪个值;在这里,客户端被配置为将用户名“george”用于领域“example.com”。客户端还包括REALM和NONCE属性,它们是从401错误响应中复制的。最后,客户端将MESSAGE-INTEGRITY属性作为消息中的最后一个属性,其值是Hashed消息。如下图:
认证码-安全哈希算法1 (HMAC-SHA1)对消息内容进行哈希处理(显示为“...”以上);该HMAC-SHA1计算包括密码值。因此,攻击者不知道秘密密码就无法计算message integrity。
服务器在收到认证的分配请求后,检查一切正常,然后创建allocation。服务器以Allocate成功响应进行回复。服务器包括给出Allocate寿命的LIFETIME命属性;这里,服务器已经将客户端请求的1小时生命周期减少到20分钟,因为该特定服务器不允许超过20分钟的生命周期。服务器包括XOR-RELAYED-ADDRESS属性,其值是分配的中继传输地址。服务器包括XOR-MAPPED-ADDRESS属性,其值是客户端的服务器反射地址;该值不会反过来被使用,而是为了方便客户端而返回。服务器包括MESSAGE-INTEGRITY属性,以认证响应并确保其完整性;请注意,响应不包含USERNAME, REALM, 和 NONCE属性。服务器还包括软件属性。如下图:
然后,客户端创建对Peer A的许可,为向其发送一些应用程序数据做准备。这是通过CreatePermission请求完成的。XOR-PEER-ADDRESS属性包含为其建立许可的IP地址(peer A的ip地址);请注意,属性中的端口号在CreatePermission请求中使用时会被忽略,这里它被设置为0;此外,请注意客户端如何使用Peer A的服务器反射IP地址,而不是其(私有)主机地址。客户端使用与上一次分配请求中相同的username, realm, 和 nonce。虽然允许这样做,但客户端选择不在该请求中包含SOFTWARE属性。
服务器接收CreatePermission请求,创建相应的权限,然后用CreatePermission成功响应进行回复。像客户端一样,服务器选择不在其回复中包含SOFTWARE属性。同样,请注意成功响应如何包含MESSAGE-INTEGRITY属性(假设服务器使用长期凭据机制),但不包含用USERNAME, REALM, 和NONCE属性。
客户端现在使用发送指示(indication)向Peer A发送应用程序数据。Peer A的服务器反射传输地址在XOR-PEER-ADDRESS属性中指定,应用程序数据(此处仅显示为“...”)在DATA属性中指定。客户端在应用层执行一种路径MTU发现,因此指定(通过包含DONT-FRAGMENT属性),服务器应该在UDP数据报中设置DF位以发送给peer。无法使用STUN的长期凭据机制对指示进行身份验证,因此消息中不包含MESSAGE-INTEGRITY属性。希望确保其数据不被更改或伪造的应用程序必须在应用程序级别完整保护其数据。
当接收到Send指示时,服务器提取应用数据,并在UDP数据报中将其发送到Peer A,中继的传输地址作为数据报的源传输地址,DF位按请求设置。请注意,如果客户端之前没有为对Peer A的服务器反射IP地址建立权限,那么服务器会默默地丢弃发送指示。
Peer A然后用它自己的包含应用程序数据的UDP数据报进行回复。数据报被发送到服务器上的中继传输地址。当它到达时,服务器创建一个Data指示,在XOR-PEER-ADDRESS属性中包含UDP数据报的源,在DATA属性中包含来自UDP数据报的数据。结果DATA指示随后被发送到客户端。
客户端现在将channel绑定到Peer B,在CHANNEL-NUMBER属性中指定空闲channel号(0x4000),在XOR-PEER-ADDRESS属性中指定Peer B的传输地址。如前所述,客户端重新使用消息中最后一次请求的username, realm, 和 nonce。
收到请求后,服务器将channel号绑定到peer,为Peer B的IP地址安装权限,然后用ChannelBind成功响应进行回复。
客户端现在向服务器发送一个包含去往Peer B的数据的ChannelData消息,ChannelData消息不是STUN消息,因此没有事务ID。相反,它只有三个字段: channel号、数据和数据长度;这里的channel号字段是0x4000(客户端刚刚绑定到对Peer B的通道)。当服务器接收到ChannelData消息时,它检查channel当前是否被绑定(它是绑定的),然后使用中继的传输地址作为源传输地址,使用192.0.2.210:49191(ChannelBind请求中XOR-PEER-ADDRESS属性的值)作为目的传输地址,将数据以UDP数据报的形式向前发送到Peer B。
稍后,Peer B将UDP数据报发送回中继传输地址。这导致服务器向客户端发送包含UDP数据报数据的ChannelData消息。由于UDP数据报到达的中继传输地址,服务器知道向哪个客户端发送ChannelData消息,并且知道使用channel 0x4000,因为这是绑定到192.0.2.210:49191的channel。请注意,如果没有任何channel号绑定到该地址,服务器会使用Data指示来代替。
在20分钟生命周期结束之前,客户端会刷新allocation。这是使用Refresh请求完成的。和以前一样,客户端在请求中包含最新的username, realm, 和 nonce。客户端还包括SOFTWARE属性,遵循推荐的做法,总是在Allocate and Refresh消息中包括该属性。当服务器收到刷新请求时,它会注意到随机数值已经过期,因此在给定新随机数值的情况下,会以438(Stale Nonce)错误进行回复。然后,客户端重新尝试请求,这次使用新的nonce值。第二次尝试被接受,服务器以成功响应进行回复。请注意,客户端在请求中没有包含LIFETIME属性,因此服务器会刷新默认生命周期10分钟的分配(从成功响应中的LIFETIME属性可以看出)。