STUN和TURN技术.docx
《STUN和TURN技术.docx》由会员分享,可在线阅读,更多相关《STUN和TURN技术.docx(26页珍藏版)》请在冰豆网上搜索。
STUN和TURN技术
STUN和TURN技术浅析
作者:
| 上传时间:
2012-06-01 | 关键字:
网络大爬虫5-NAT专题
在现实Internet网络环境中,大多数计算机主机都位于防火墙或NAT之后,只有少部分主机能够直接接入Internet。
很多时候,我们希望网络中的两台主机能够直接进行通信,即所谓的P2P通信,而不需要其他公共服务器的中转。
由于主机可能位于防火墙或NAT之后,在进行P2P通信之前,我们需要进行检测以确认它们之间能否进行P2P通信以及如何通信。
这种技术通常称为NAT穿透(NATTraversal)。
最常见的NAT穿透是基于UDP的技术,如RFC3489中定义的STUN协议。
STUN,首先在RFC3489中定义,作为一个完整的NAT穿透解决方案,英文全称是SimpleTraversalofUDPThroughNATs,即简单的用UDP穿透NAT。
在新的RFC5389修订中把STUN协议定位于为穿透NAT提供工具,而不是一个完整的解决方案,英文全称是SessionTraversalUtilitiesforNAT,即NAT会话穿透效用。
RFC5389与RFC3489除了名称变化外,最大的区别是支持TCP穿透。
TURN,首先在RFC5766中定义,英文全称是TraversalUsingRelaysaroundNAT:
RelayExtensionstoSessionTraversalUtilitiesforNAT,即使用中继穿透NAT:
STUN的扩展。
简单的说,TURN与STURN的共同点都是通过修改应用层中的私网地址达到NAT穿透的效果,异同点是TURN是通过两方通讯的“中间人”方式实现穿透。
1 STUN
了解STUN之前,我们需要了解NAT的种类。
NAT对待UDP的实现方式有4种,分别如下:
1. FullConeNAT
完全锥形NAT,所有从同一个内网IP和端口号发送过来的请求都会被映射成同一个外网IP和端口号,并且任何一个外网主机都可以通过这个映射的外网IP和端口号向这台内网主机发送包。
2. RestrictedConeNAT
限制锥形NAT,它也是所有从同一个内网IP和端口号发送过来的请求都会被映射成同一个外网IP和端口号。
与完全锥形不同的是,外网主机只能够向先前已经向它发送过数据包的内网主机发送包。
3. PortRestrictedConeNAT
端口限制锥形NAT,与限制锥形NAT很相似,只不过它包括端口号。
也就是说,一台IP地址X和端口P的外网主机想给内网主机发送包,必须是这台内网主机先前已经给这个IP地址X和端口P发送过数据包。
4. SymmetricNAT
对称NAT,所有从同一个内网IP和端口号发送到一个特定的目的IP和端口号的请求,都会被映射到同一个IP和端口号。
如果同一台主机使用相同的源地址和端口号发送包,但是发往不同的目的地,NAT将会使用不同的映射。
此外,只有收到数据的外网主机才可以反过来向内网主机发送包。
1.1 RFC3489/STUN
STUN(SimpleTraversalofUserDatagramProtocolThroughNetworkAddressTranslators),即简单的用UDP穿透NAT,是个轻量级的协议,是基于UDP的完整的穿透NAT的解决方案。
它允许应用程序发现它们与公共互联网之间存在的NAT和防火墙及其他类型。
它也可以让应用程序确定NAT分配给它们的公网IP地址和端口号。
STUN是一种Client/Server的协议,也是一种Request/Response的协议,默认端口号是3478。
1.1.1 报文结构
Ø 消息头
所有的STUN消息都包含20个字节的消息头,包括16位的消息类型,16位的消息长度和128位的事务ID。
字节
0 1 2 3
消息类型
消息长度
事务ID
消息类型许可的值如下:
0x0001:
捆绑请求
0x0101:
捆绑响应
0x0111:
捆绑错误响应
0x0002:
共享私密请求
0x0102:
共享私密响应
0x0112:
共享私密错误响应
消息长度,是消息大小的字节数,但不包括20字节的头部。
事务ID,128位的标识符,用于随机请求和响应,请求与其相应的所有响应具有相同的标识符。
Ø 消息属性
消息头之后是0或多个属性,每个属性进行TLV编码,包括16位的属性类型、16位的属性长度和变长属性值。
字节
0 1 2 3
属性类型
属性长度
属性值
……
属性类型定义如下:
MAPPED-ADDRESS
MAPPED-ADDRESS属性表示映射过的IP地址和端口。
它包括8位的地址族,16位的端口号及长度固定的IP地址。
RESPONSE-ADDRESS
RESPONSE-ADDRESS属性表示响应的目的地址
CHASNGE-REQUEST
客户使用32位的CHANGE-REQUEST属性来请求服务器使用不同的地址或端口号来发送响应。
SOURCE-ADDRESS
SOURCE-ADDRESS属性出现在捆绑响应中,它表示服务器发送响应的源IP地址和端口。
CHANGED-ADDRESS
如果捆绑请求的CHANGE-REQUEST属性中的“改变IP”和“改变端口”标志设置了,则CHANGED-ADDRESS属性表示响应发出的IP地址和端口号。
USERNAME
USERNAME属性用于消息的完整性检查,用于消息完整性检查中标识共享私密。
USERNAME通常出现在共享私密响应中,与PASSWORD一起。
当使用消息完整性检查时,可有选择地出现在捆绑请求中。
PASSWORD
PASSWORD属性用在共享私密响应中,与USERNAME一起。
PASSWORD的值是变长的,用作共享私密,它的长度必须是4字节的倍数,以保证属性与边界对齐。
MESSAGE-INTEGRITY
MESSAGE-INTEGRITY属性包含STUN消息的HMAC-SHA1,它可以出现在捆绑请求或捆绑响应中;MESSAGE-INTEGRITY属性必须是任何STUN消息的最后一个属性。
它的内容决定了HMAC输入的Key值。
ERROR-CODE
ERROR-CODE属性出现在捆绑错误响应或共享私密错误响应中。
它的响应号数值范围从100到699。
下面的响应号,与它们缺省的原因语句一起,目前定义如下:
400(错误请求):
请求变形了。
客户在修改先前的尝试前不应该重试该请求。
401(未授权):
捆绑请求没有包含MESSAGE-INTERITY属性。
420(未知属性):
服务器不认识请求中的强制属性。
430(过期资格):
捆绑请求没有包含MESSAGE-INTEGRITY属性,但它使用过期
的共享私密。
客户应该获得新的共享私密并再次重试。
431(完整性检查失败):
捆绑请求包含MESSAGE-INTEGRITY属性,但HMAC验
证失败。
这可能是潜在攻击的表现,或者客户端实现错误
432(丢失用户名):
捆绑请求包含MESSAGE-INTEGRITY属性,但没有
USERNAME属性。
完整性检查中两项都必须存在。
433(使用TLS):
共享私密请求已经通过TLS(TransportLayerSecurity,即安全
传输层协议)发送,但没有在TLS上收到。
500(服务器错误):
服务器遇到临时错误,客户应该再次尝试。
600(全局失败):
服务器拒绝完成请求,客户不应该重试。
UNKNOWN-ATTRIBUTES
UNKNOWN-ATTRIBUTES属性只存在于其ERROR-CODE属性中的响应号为420的捆绑错误响应或共享私密错误响应中。
REFLECTED-FROM
REFLECTED-FROM属性只存在于其对应的捆绑请求包含RESPONSE-ADDRESS属性的捆绑响应中。
属性包含请求发出的源IP地址,它的目的是提供跟踪能力,这样STUN就不能被用作DOS攻击的反射器。
属性空间分为可选部分与强制部分,值超过0x7fff的属性是可选的,即客户或服务器即使不认识该属性也能够处理该消息;值小于或等于0x7fff的属性是强制理解的,即除非理解该属性,否则客户或服务器就不能处理该消息。
1.1.2 实现原理
图1:
STUN
STUN协议的完整交互过程如上,下面我们来介绍具体实现步骤。
一般情况下,客户会配置STUN服务器提供者的域名,该域名被解析为IP地址和SRV过程的端口号。
服务器名是“stun”,使用UDP协议发送捆绑请求,使用TCP协议发送共享私密请求。
STUN协议的缺省端口号为3478。
若要提供完整性检查,STUN在客户和服务器间使用128位的共享私密,作为在捆绑请求和捆绑响应中的密匙。
首先,客户通过发现过程获得它将与之建立TCP连接的IP地址和端口号。
客户打开该地址和端口的连接,开始TLS协商,验证服务器的标识。
客户发送共享私密请求。
该请求没有属性,只有头。
服务器生成响应。
客户会在该连接上生成多个请求,但在获得用户名和密码后关闭该连接。
服务器收到共享私密请求,验证从TLS连接上到达的该请求;如果不是通过TLS收到的请求,则生成共享私密错误响应,并设置ERROR-CODE属性为响应号433;这里区分两种情况:
若通过TCP收到请求,则错误响应通过收到请求的相同连接发送;若通过UDP收到请求,则错误响应发送回请求送出的源IP和端口。
服务器检查请求中的任何属性,当其中有不理解的小于或等于0x7fff的值,则生成共享私密错误响应,设置ERROR-CODE属性为响应号420,并包括UNKNOWN-ATTRIBUTE属性,列出它不理解的小于或等于0x7fff的属性的值。
该错误响应通过TLS连接发送。
若请求正确,服务器创建共享私密响应,包含与请求中相同的事务ID,并包含USERNAME和PASSWORD属性。
用户名在10分钟内有效。
共享私密响应通过与收到请求的相同的TLS连接发送,服务器保持连接打开状态,由客户关闭它。
接着,客户发送捆绑请求,携带的属性包括:
可选属性:
RESPONSE-ADDRESS属性和CHANGE-REQUEST属性;
强制属性:
MESSAGE-INTEGRITY属性和USERNAME属性。
客户发送捆绑请求,通过客户重传来提供可靠性。
客户开始用100ms的间隔重传,每次
重传间隔加倍,直至1.6秒。
之间间隔1.6秒的重传继续,直到收到响应或总共已经发送了9次。
因此,若9500ms后,还未收到响应,客户认为传输已经失败。
服务器检查捆绑请求的MESSAGE-INTEGRITY属性,不存在则生成捆绑错误响应,设置ERROR-CODE属性为响应号401;若存在,计算请求的HMACKey值。
服务器检查USERNAME属性,不存在则生成捆绑错误响应,设置ERROR-CODE属性为响应号432;若存在,但不认识该USERNAME的共享私密(例如,它超时了),生成捆绑错误响应,设置ERROR-CODE属性为响应号430。
若服务器知道该共享私密,但所计算的HMAC与请求的不同,生成捆绑错误响应,设置ERROR-CODE属性为响应号431。
假设消息完整性检查通过了,服务器检查请求中的任何属性的值,若遇到不理解的小于或等于0x7fff的值,生成捆绑错误响应,设置ERROR-CODE属性为响应号420,该响应包含UNKNOWN-ATTRIBUTE属性,并列出不理解的小于或等于0x7fff的属性。
若请求正确,服务器生成单个捆绑响应,包含与捆绑请求相同的事务ID。
服务器在捆绑响应中加入MAPPED-ADDRESS属性,该属性的IP地址和端口号为捆绑请求的源IP地址和端口号。
捆绑响应的源地址和端口号取决于捆绑请求中CHANGE-REQUEST属性的值及捆绑请求收到的地址和端口号相关。
总结如下:
标志
源地址
源端口号
CHANGED-ADDRESS
无
Da
Dp
Ca:
Cp
改变IP
Ca
Dp
Ca:
Cp
改变端口号
Da
Cp
Ca:
Cp
改变IP且改变端口号
Ca
Cp
Ca:
Cp
表1:
标志对数据包源和CHANGED-ADDRESS的影响
服务器在捆绑响应中加入SOURCE-ADDRESS属性,包含用于发送捆绑响应的源地址和端口号;加入CHANGED-ADDRESS属性,包含源IP地址和端口号。
如果捆绑请求中包含了USERNAME和MESSAGE-INTEGRITY属性,则服务器在捆绑响应中加入MESSAGE-INTEGRITY属性。
如果捆绑请求包含RESPONSE-ADDRESS属性,则服务器在捆绑响应中加入REFLECTED-FROM属性:
如果捆绑请求使用从共享私密请求获得的用户名进行认证,则REFLECTED-FROM属性包含共享私密请求到达的源IP地址和端口号;若请求中的用户名不是使用共享私密分配的,则REFLECTED-FROM属性包含获得该用户名的实体的源IP地址和端口号;若请求中没有用户名,且服务器愿意处理该请求,则REFLECTED-FROM属性包含请求发出的源IP地址和端口号。
服务器不会重传响应,可靠性通过客户周期性地重发请求来保障,每个请求都会触发服务器进行响应。
客户端判断响应的类型是捆绑错误响应还是捆绑响应。
捆绑错误响应通常在请求发送的源地址和端口收到;捆绑响应通常在请求中的RESPONSE-ADDRESS属性的地址和端口收到,若没有该属性,则捆绑响应将在请求发送的源地址和端口号收到。
若是捆绑错误响应,客户检查响应中的ERROR-CODE属性的响应号:
400至499之间的未知属性按属性400处理,500至599之间的未知属性按500处理,600至699之间的未知属性按600处理。
任何100和399之间的响应都会使请求重传中止,但其他则忽略;若客户收到响应的属性类型大于0x7fff,则忽略该属性,若小于或等于0x7fff,则请求重传停止,并忽略整个响应;
若是捆绑响应,客户检查响应的MESSAGE-INTEGRITY属性:
如果不存在,客户在请求中加入MESSAGE-INTEGRITY属性,并放弃该响应;如果存在,客户计算响应的HMAC。
如果计算出的HMAC与响应中的不同,则放弃该响应,并警告客户可能受到了攻击;若计算出的HMAC与响应中的匹配,则过程继续;
不论收到捆绑响应还是捆绑错误响应,都将中止该请求的重传。
客户在第一次响应后继续监听捆绑请求的响应10秒钟,如果这期间它收到任何消息类型不同的响应或不同的MAPPED-ADDRESS属性,它将警告用户可能受到攻击;并且,如果客户收到的捆绑响应次数超过它发送的捆绑请求数的两倍,它将警告用户可能受到攻击;若捆绑响应经过认证,上述攻击并未导致客户丢弃MAPPED-ADDRESS,则客户可以使用该MAPPED-ADDRESS和SOURCE-ADDRESS属性。
1.1.3 STUN功能举例
客户通过带外方式获得STUN服务器信息后,就打开对应的地址和端口的连接,并开始与STUN服务器进行TLS协商。
一旦打开了连接,客户就通过TCP协议发送共享私密请求,服务器生成共享私密响应。
STUN在客户和服务器间使用共享私密,用作捆绑请求和捆绑响应中的密匙。
之后,客户使用UDP协议向STUN服务器发送捆绑请求,当捆绑请求消息到达服务器的时候,它可能经过了一个或者多个NAT。
结果是STUN服务器收到的捆绑请求消息的源IP地址被映射成最靠近STUN服务器的NAT的IP地址,STUN服务器把这个源IP地址和端口号复制到一个捆绑响应消息中,发送回拥有这个IP地址和端口号的客户端。
当STUN客户端收到捆绑响应消息之后,它会将自己发送捆绑请求时绑定的本地IP地址和端口号同捆绑响应消息中的IP地址和端口号进行比较,如果不匹配,就表示客户端正处于一个或者多个NAT的前面。
在Full-ConeNAT的情况下,在捆绑响应消息中的IP地址和端口是属于公网的,公网上的任何主机都可以使用这个IP地址和端口号向这个应用程序发送数据包,应用程序只需要在刚才发送捆绑请求的IP地址和端口上监听即可。
当然,客户可能并不在一个Full-ConeNAT的前面,实际上,它并不知道自己在一个什么类型的NAT的前面。
为了确定NAT的类型,客户端使用附加的捆绑请求。
具体过程是很灵活的,但一般都会像下面这样工作:
客户端再发送一个捆绑请求,这次发往另一个IP地址,但是使用的是跟上一次同一个源IP地址和源端口号,如果返回的数据包里面的IP地址和端口号和第一次返回的数据包中的不同,客户端就会知道它是在一个对称NAT的前面。
客户端为了确认自己是否在一个完全锥形NAT的前面,客户端可以发送一个带有标志的捆绑请求,这个标志告诉服务器使用另一个IP地址和端口发送捆绑响应。
换句话说,如果客户端使X/Y的IP地址端口对向A/B的IP地址端口对发送捆绑请求,服务器就会使用源IP地址和源端口号为C/D的地址端口对向X/Y发送捆绑响应。
如果客户端收到了这个响应,它就知道它是在一个Full-ConeNAT前面。
STUN协议允许客户端请求服务器从收到捆绑请求的IP地址往回发捆绑响应,但是要使用不同的端口号。
这可以用来检查客户端是否在PortRestrictedConeNAT的前面还是在RestrictedConeNAT的前面。
1.2 RFC5389/STUN
STUN协议在RFC5389中被重新命名为SessionTraversalUtilitiesforNAT,即NAT会话穿透效用。
在这里,NAT会话穿透效用被定位为一个用于其他解决NAT穿透问题协议的协议。
它可以用于终端设备检查由NAT分配给终端的IP地址和端口号。
同时,它也被用来检查两个终端之间的连接性,好比是一种维持NAT绑定表项的保活协议。
STUN可以用于多种NAT类型,并不需要它们提供特殊的行为。
STUN本身不再是一种完整的NAT穿透解决方案,它相当于是一种NAT穿透解决方案中的工具。
这是与RFC3489/STUN版本相比最重要的改变。
1.2.1 STUN用途
目前定义了三种STUN用途:
InteractiveConnectivityEstablishment(ICE)[MMUSIC-ICE],交互式连接建立
Client-initiatedconnectionsforSIP[SIP-OUTBOUND],用于SIP的客户端初始化连接
NATBehaviorDiscovery[BEHAVE-NAT],NAT行为发现
1.2.2 报文结构
Ø 消息头
STUN消息头为20字节,后面紧跟0或多个属性。
STUN头部包含一STUN消息类型、magiccookie、事务ID和消息长度。
0 1 2 3
00
STUN消息类型
消息长度
魔术字
事务ID(96位)
每个STUN消息的最高位前2位必须为0。
当STUN协议为多个协议多路复用时若使用的是同一个端口,这可以用于与其他协议区分STUN数据包。
消息类型确定消息的类别(如请求、成功回应、失败回应、标志)。
虽然这里有四种消息类型,但可以分为2类事务:
请求/响应事务、标志事务。
消息类型字段可进一步划分为下面结构:
M11
M10
M9
M8
M7
C1
M6
M5
M4
C0
M3
M2
M1
M0
消息类型定义如下:
0b00,表示请求
0b01,表示标志
0b10,表示成功响应
0b11,表示错误响应
魔术字域必须包含固定的值0x2112A442。
在RFC3489中,该域是事务ID的一部分。
配置魔术字允许服务器检测客户是否理解某些在改进的版本中增加的属性。
另外,还可用于STUN多路复用时与其他协议的包进行区分。
96位的事务ID用于唯一的识别STUN事务。
对于请求/响应事务,事务ID由STUN客户端来选择;对于标志事务,由代理(代理指支持STUN的客户端或服务器)来选择并发送。
它主要服务于与请求相关的响应,因此它也扮演着一个帮助阻止确定类型的攻击的角色。
服务器使用事务ID来唯一的标识出所有客户端的每一个事务。
事务ID本身必须是唯一的,并且随机的从0到2的96-1次方中选择。
重新发送相同的请求时,也必须使用新的事务ID。
成功或错误响应必须携带与相对应的请求相同的事务ID。
消息长度字段不包括20字节的STUN头部。
所有的STUN属性必须填充为4字节的倍数。
消息长度字段的最后2位总是为0,这为区分STUN包与其他协议的包提供了另外一种方法。
Ø 消息属性
STUN头之后是0或多个属性。
每个属性都采用TLV编码,16位的类型、16位的长度及可变长度的值。
每个STUN属性必须是4字节边界对齐。
字节
0 1 2 3
属性类型
属性长度
属性值
……
属性空间被划分为2个范围。
属性的类型值在0x0000到0x7fff是强制理解属性,这意味着除非STUN代理能够理解这些属性,否则将不能正常处理包含该属性的消息;属性的类型值在0x8000到0xffff范围是可选理解属性,这意味着如果STUN代理不能理解它们的话这些属性可以被忽略。
STUN属性类型集由IANA维护。
MAPPED-ADDRESS
MAPPED-ADDRESS属性标识了客户端反向传输地址(映射后的地址),这个属性只用于服务器向后兼容RFC3489的客户端。
XOR-MAPPED-ADDRESS
XOR-MAPPED-ADDRESS属性与MAPPED-ADDRESS属性是相同的,除了这映射后的地址经过了异或处理。
(注意,异或运算是其自身的逆运算,再异或一下就可以得出真实的MAPPED-ADDRESS)
USERNAME
USERNAME属性用于消息完整性。
它采用USERNAME和PASSWORD组合方式用于消息完整性检查。
MESSAGE-INTEGRITY
MESSAGE-INTEGRITY属性包含STUN消息的HMAC-SHA1。
它可以出现在任何类型的STUN消息中。
由于使用SHA1散列算法,HMAC将会是20字节。
用作HMAC输入的文本是STUN消息,包括头部,直到且包括MESSAGE-INTEGRITY属性前面的属性。
除了FINGERPRINT属性外,代理必须忽略其他出现在MESSAGE