STUN和TURN技术浅析

在现实Internet网络环境中,大多数计算机主机都位于防火墙或NAT之后,只有少部分主机能够直接接入Internet。很多时候,我们希望网络中的两台主机能够直接进行通信,即所谓的P2P通信,而不需要其他公共服务器的中转。由于主机可能位于防火墙或NAT之后,在进行P2P通信之前,我们需要进行检测以确认它们之间能否进行P2P通信以及如何通信。这种技术通常称为NAT穿透(NAT Traversal)。最常见的NAT穿透是基于UDP的技术,如RFC3489中定义的STUN协议。

STUN,首先在RFC3489中定义,作为一个完整的NAT穿透解决方案,英文全称是Simple Traversal of UDP Through NATs,即简单的用UDP穿透NAT。

在新的RFC5389修订中把STUN协议定位于为穿透NAT提供工具,而不是一个完整的解决方案,英文全称是Session Traversal Utilities for NAT,即NAT会话穿透效用。RFC5389与RFC3489除了名称变化外,最大的区别是支持TCP穿透。

TURN,首先在RFC5766中定义,英文全称是Traversal Using Relays around NAT:Relay Extensions to Session Traversal Utilities for NAT,即使用中继穿透NAT:STUN的扩展。简单的说,TURN与STURN的共同点都是通过修改应用层中的私网地址达到NAT穿透的效果,异同点是TURN是通过两方通讯的“中间人”方式实现穿透。

 

1      STUN    

了解STUN之前,我们需要了解NAT的种类。

NAT对待UDP的实现方式有4种,分别如下:

1.    Full Cone NAT

完全锥形NAT,所有从同一个内网IP和端口号发送过来的请求都会被映射成同一个外网IP和端口号,并且任何一个外网主机都可以通过这个映射的外网IP和端口号向这台内网主机发送包。

2.    Restricted Cone NAT

限制锥形NAT,它也是所有从同一个内网IP和端口号发送过来的请求都会被映射成同一个外网IP和端口号。与完全锥形不同的是,外网主机只能够向先前已经向它发送过数据包的内网主机发送包。

3.    Port Restricted Cone NAT

端口限制锥形NAT,与限制锥形NAT很相似,只不过它包括端口号。也就是说,一台IP地址X和端口P的外网主机想给内网主机发送包,必须是这台内网主机先前已经给这个IP地址X和端口P发送过数据包。

4.    Symmetric NAT

对称NAT,所有从同一个内网IP和端口号发送到一个特定的目的IP和端口号的请求,都会被映射到同一个IP和端口号。如果同一台主机使用相同的源地址和端口号发送包,但是发往不同的目的地,NAT将会使用不同的映射。此外,只有收到数据的外网主机才可以反过来向内网主机发送包。

 

1.1      RFC3489/STUN

STUN(Simple Traversal of User Datagram Protocol Through Network Address Translators),即简单的用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(Transport Layer Security,即安全

传输层协议)发送,但没有在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-Cone NAT的情况下,在捆绑响应消息中的IP地址和端口是属于公网的,公网上的任何主机都可以使用这个IP地址和端口号向这个应用程序发送数据包,应用程序只需要在刚才发送捆绑请求的IP地址和端口上监听即可。

当然,客户可能并不在一个Full-Cone NAT的前面,实际上,它并不知道自己在一个什么类型的NAT的前面。为了确定NAT的类型,客户端使用附加的捆绑请求。具体过程是很灵活的,但一般都会像下面这样工作:客户端再发送一个捆绑请求,这次发往另一个IP地址,但是使用的是跟上一次同一个源IP地址和源端口号,如果返回的数据包里面的IP地址和端口号和第一次返回的数据包中的不同,客户端就会知道它是在一个对称NAT的前面。客户端为了确认自己是否在一个完全锥形NAT的前面,客户端可以发送一个带有标志的捆绑请求,这个标志告诉服务器使用另一个IP地址和端口发送捆绑响应。换句话说,如果客户端使X/Y的IP地址端口对向A/B的IP地址端口对发送捆绑请求,服务器就会使用源IP地址和源端口号为C/D的地址端口对向X/Y发送捆绑响应。如果客户端收到了这个响应,它就知道它是在一个Full-Cone NAT前面。

STUN协议允许客户端请求服务器从收到捆绑请求的IP地址往回发捆绑响应,但是要使用不同的端口号。这可以用来检查客户端是否在Port Restricted Cone NAT的前面还是在Restricted Cone NAT的前面。


http://blog.csdn.net/yu_xiang/article/details/9227023

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值