大家好,又见面了,我是你们的朋友全栈君。
1. 简介
stun协议本身是用来进行NAT穿透使用,其本身实际上是NAT内部设备获取外部IP地址的一种协议。STUN协议在RFC上目前经过三种演变,其中RFC3489上定义的STUN和之后的RFC5389和8489上定义的stun在概念上存在明显区分: RFC3489定义:Simple Traversal of User Datagram Protocol (UDP) Through Network Address Translators (NATs) (STUN) RFC5389和RFC8489:Session Traversal Utilities for NAT (STUN) 可以看到STUN协议的英文描述本身就已经发生了变化,3489中定义的是通过UDP进行NAT穿越的方式,而在RFC5389上定义的是对于NAT穿越的一整套工具集,这个工具集不在局限于UDP而是同时适用于UDP和TCP协议。
2. NAT类型
NAT类型是在RFC3489上提出的一个概念,这个概念旨在将NAT对于内部网络和外部网络数据包的不同处理上,对NAT进行一个系统的分类。但是在RFC5389上明确说明了RFC3489对于NAT分类的类型是不准确的,在很多场景下NAT设备的行为不能吻合到任何一个类型上。 尽管如此,但是将NAT设备进行分类有助于对于NAT类型的理解,以下是典型NAT的分类: full cone:不受限的NAT处理模式,在这种NAT模式中,来自外部网络的数据包将被无条件路由到内部网络; Restricted cone:IP受限型NAT,会将来自内部设备相同的IP 端口的数据包,统一映射成同一个外部IP 端口进行发送。当收到外部的数据包时,如果收到数据包的IP是之前发送数据包的目标IP,则转发到内部设备,否则丢弃。 restricted port cone:在IP受限型的基础上,添加了端口的约束。 Symmetric NAT:对称型NAT,则是在端口受限型的基础上,内部设备使用相同的IP 端口向不同的外部IP 端口(IP不同或端口不同或两者均不同)发送数据包时,NAT会分配不同的公网IP 端口。
这里多说一句,RFC3489将NAT类型分成以上几种模式之后,还定义了一整套的NAT发现的信令流程,通过stun请求去发现不同的NAT模式,不过这整个NAT发现的信令流程在RFC5389上已经被完全废弃,原因就在于实际工作在网络中NAT设备的行为比定义的远远要多样化,所以去发现NAT类型的实际意义不大。
3. Stun相关信令
在RFC3489中定义了许多stun使用的信令,但是在RFC5389中已经将其中的很多信令废弃,但是一些基本的信令依然存在。
binding request:stun中获取外部网络地址的请求,该请求获取的地址被称为server-reflex address。
binding request的格式如下:
binding response:binding response是binding request的响应,不同于很多协议的是,binding response直接在请求类型中就将response分为了两类,成功响应0x0101.失败响应0x0111。
stun indiction(RFC5389加入):这个stun信令在设计上就是为了简化传统stun流程中冗余的request-response的逻辑,在初始的stun请求获取server-reflex address成功之后,后续可能仅仅只需要维持这个地址,而不需要任何响应,这时候,终端就会使用indiction去对这个地址信息进行保活。信令值为0x0011。
3.1 RFC5389和RFC3489关于stun信令的区分
RFC5389和RFC3489最关键的stun信令区分就是RFC5389将RFC3489中规定的128transation Id字段中的前32位改为了magic cookie:0x2112a442。
同时RFC5389由于将和其他协议进行多路复用,所以RFC5389额外将stun 的请求类型字段从RFC3489的16位变为14位,而使前两位变为全0,用于和其他协议进行区分。
同时RFC5389为了兼容RFC3489中的stun请求类型,特意将14位message type做了一下特殊处理,从而使其和RFC3489的请求类型进行兼容: 如图所示:在16位的字段中,已知前两位为了和其他协议进行区分从而为0,然后将第七位和地11位抽出作为class位。
class 0b00: 作为请求类别 class 0b01: 作为indiction(指示)类别 class 0b10: 作为成功响应类别 class 0b11: 作为失败响应类别 将其余剩下的位作为stun 的method: stun协议仅定义了一种method binding, 其余的method会被其他协议拓展使用。
3.2 stun信令的组成部分
stun信令由stun头 stun属性两部分组成。其中stun头描述了基本的stun信息。stun属性则是对于这个stun信令的功能拓展。
4. stun协议工作流程
4.1 获取外部网络地址
stun协议的最简单的工作流程就是获取外部网络地址,这个流程有时被称为打洞,又被称为获取绑定地址,相关流程如下:
- client 发送stun请求至外部网络的server
- 外部网络的server获取到这个请求之后,将数据包的实际来源地址填入到response信令中
- client解析response信令,获取该设备的外部网络地址
这里运用的一个原理是,所有的NAT只要内部网络的设备使用IP 端口向外部网络的某个设备的IP 端口发送过数据包,那么来自这个外部网络IP 端口的数据包就一定能到达内部网络的设备。 在第一点中client发送的请求时stun request, 在第二点中外部的server会将数据包的实际来源地址,填入到stun 响应的属性中,这个属性在RFC3489中位mapped address, 而在RFC5389中为xor-mapped address。很多服务器的实现都会同时在这两个属性中将本地地址添加上,进而对终端进行更好的兼容。
4.2 维持地址绑定
当内部设备获取到自身的外部网络地址后,此时NAT上的处理就是建立了一个地址绑定关系,但是NAT对于这个地址绑定关系的存在时间具有限制,所以在一段时间后,这个绑定关系会被NAT回收,如果想一直持有这个绑定关系,就需要内部设备不断通过这个地址绑定关系向外部发送数据包。 对于一个stun client来说,这里最简单的处理就是发送stun request,通过stun request就可以不断刷新这个绑定关系(很多文章中称为NAT上的洞)的持续时间。 当然,这里还可以使用stun indiction去进行刷新,这样会更加轻量化,但是使用stun indction将会存在一个,如果NAT上这个绑定关系由于某些原因被重置,这将会导致内部设备无法感知到这一变化,所以交替使用stun request和stun indiction可能是一个更优的选择,不过在webRTC的实现中,一直都使用stun request去刷新绑定关系。
4.3 stun client的鉴权流程
stun 协议定义了两套鉴权流程,分别是短期凭证机制和长期凭证机制。这二者实际上都是运用了账号密码这个机制,所不同的是短期凭证机制是通过其他协议传递stun的用户名和密码,这个用户名和密码的有效期的使用期限非常短,可能仅在一次通话中使用。而长期凭证机制是直接获取到的,可以长期使用。
短期凭证机制和长期凭证机制的逻辑实际上都是通过stun协议的附加属性来完成,在鉴权流程中需要用到的属性是MESSAGE-INTEGRITY和USERNAME属性。
4.3.1 短期凭证机制
短期凭证机制实际的应用场景是ICE处理流程(ICE是定义了交互式连接的相关规范)。
4.3.1.1 请求的构建
短期凭证机制的使用很简单,就是在USERNAME和MESSAFE-INTERGRITY属性上填充相关的数据即可。 USERNAME只要填充用户名属性即可,而MESSAGE-INTERGRITY属性的填充则需要遵循如下规则:
- 消息完整性属性可以在任何stun请求中出现;
- 消息完整性属性使用SHA1算法得到
- 进行消息完整性属性极端的数据包含stun头,以及所有在MESSAGE-INTERGRITY属性前的数据,如果存在MESSAGE-INTERGRITY属性之后的属性,那么不在计入到消息完整性计算中;
- stun头中的长度在计算消息完整性之前,长度需要忽略MESSAGE-INTERGRITY属性之后的内容,消息完整性计算完毕之后,需要重新设置stun头中的长度为stun请求的实际长度;
- 计算出key,这个key=(密码的SASLPrep的格式)
- 运用这个key和stun请求中内容计算HMACSHA1的结果,然后添加到MESSAGE-INTERGRITY属性中
4.3.1.2 请求的处理
收到请求后的处理如下:
当请求不包含USERNAME和MESSAFE-INTERGRITY属性时,如果时stun request则回复400, 如果时indiction则丢弃不再处理; 当用户名非法,请求返回401, indiction则丢弃; 当消息完整性校验失败,请求返回401,indiction则直接丢弃;
成功的响应需要按照请求中的消息完整性生成逻辑添加MESSAFE-INTERGRITY属性,失败则不需要。
4.3.1.3 响应的处理
计算消息完整性是否匹配,匹配则可以继续其他流程,失败则继续请求的重传流程。
4.3.2 长期凭证机制
长期凭证机制的处理其实和短期凭证机制的差不多,只不过由于长期凭证机制的密码和用户名的使用上添加了额外的机制来进行保护,其实长期凭证机制的处理和SIP的鉴权流程差不多。
- 发送请求,不携带任何账户和密码信息;
- 接收到401 响应后,提取响应中的nonce值和realm值,nonce值是对一次鉴权流程的标记,可以一直在鉴权成功之后的流程使用,realm标记的是所属的域; 获取计算消息完整性时HMAC-SHA1流程需要使用的key,在长期凭证机制中为: key = MD5(username “:” realm “:” SASLprep(password)) 后续流程和短期凭证机制相同
说明一下nonce值和realm,nonce值本身时一个随机数,用来对这个鉴权流程和后续的处理流程进行绑定,负责确认后续的stun请求是经过之前的鉴权的,不需要重复鉴权。realm标志的是需要使用那个用户名和密码,realm是一个域,终端可以根据相关的域选用对应域的账户名和密码(不过没有终端这么实现,基本上都是配置一个用户名和密码),在安全性上也是对相关的参数进行保护。
需要注意的是长期凭证机制不能使用indication,因其不能进行响应,让终端进行鉴权。当nonce值改变的使用,终端需要重新进行鉴权。
4.3.2.1 请求处理
- 当请求不包含消息完整性,则回复401,响应需包含realm和nonce,不需包含消息完整性和用户名;
- 当请求包含消息完整性,但是不包含realm和nonce,则回复400,,该响应不要包含realm,消息完整性,用户名和nonce;
- nonce值非法需要回复436响应需包含realm和nonce,不需包含消息完整性和用户名;
- 如果不包含用户名,则回复401,响应需包含realm和nonce,不需包含消息完整性和用户名;
- 消息完整性校验失败,回复401,响应需包含realm和nonce,不需包含消息完整性和用户名;
4.3.2.2 响应处理
和短期凭证机制类似,不再赘述。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/160540.html原文链接:https://javaforall.cn