WebRTC源码研究(26)STUN协议
文章目录
-
WebRTC源码研究(26)STUN协议
-
1. STUN协议简介
-
-
1.1 RFC3489/STUN
-
1.2 RFC5389/STUN
-
1.3 RFC5389与RFC3489的区别
-
1.4 新特性
-
- 1.4.1 指纹机制
- 1.4.2 通过DNS发现服务器机制
- 1.4.3 认证和消息完整性机制
- 1.4.4 备份服务器机制
-
1.5 RFC5389与RFC3489的兼容
-
- 1.5.1 客户端处理的改变
- 1.5.2 服务器处理的改变
-
1.6 STUN
-
-
2. STUN协议作用
-
3. STUN协议分析
-
- 3.1 STUN包文结构
- 3.2 STUN包文属性字段
-
- 3.2.1 MAPPED-ADDRESS
- 3.2.2 XOR-MAPPED-ADDRESS
- 3.2.3 RESPONSE-ADDRESS
- 3.2.4 CHASNGE-REQUEST
- 3.2.5 SOURCE-ADDRESS
- 3.2.6 CHANGED-ADDRESS
- 3.2.3 ERROR-CODE
- 3.2.4 USERNAME
- 3.2.5 PASSWORD
- 3.2.6 MESSAGE-INTEGRITY
- 3.2.7 UNKNOWN-ATTRIBUTES
- 3.2.8 REFLECTED-FROM
- 3.2.9 SOFTWARE
- 3.2.10 REALM
- 3.2.11 NONCE
- 3.2.12 ALTERNATE-SERVER
-
4. STUN通信流程
-
-
4.1. 产生一个Request或Indication
-
4.2. 发送Requst或Indication
-
- 4.2.1 通过UDP发送
- 4.2.2 通过TCP或者TCP-over-TLS发送
-
4.3. 接收STUN消息
-
-
4.3.1 处理Request
-
- 4.3.1.1 生成Success Response或Error Response
-
4.3.2 处理Indication
-
4.3.3 处理Success Response
-
4.3.4 处理Error Response
-
-
WebRTC源码研究(26)STUN协议
前面的博客中有几篇博客中都有介绍到STUN协议, 因为WebRTC实现P2P打洞需要用STUN协议,在这篇“NAT打洞原理”博客中有详细讲解了STUN协议打洞的流程,接下来将详细讲解STUN协议相关的内容。
学习P2P打洞这块的知识,必须要对STUN协议,TURN协议非常熟悉,不然没有办法理解其中的原理。后续博客我还会详细讲解TURN协议。 更多详情可以自己查阅官方协议文档:STUN/RFC3489,STUN/RFC5389,
TURN/RFC5766以及ICE/RFC5245。
1. STUN协议简介
当前主要应用于P2P通信的几个标准协议,主要有STUN/RFC3489,STUN/RFC5389,
TURN/RFC5766以及ICE/RFC5245。
STUN/RFC3489和STUN/RFC5389的名称都是STUN,但其全称是不同的。在STUN/RFC3489和STUN/RFC5389里,
STUN的全称是Simple Traversal of User Datagram Protocol (UDP) Through Network Address Translators (NATs),
即穿越NAT的简单UDP传输,是一个轻量级的协议,允许应用程序发现自己和公网之间的中间件类型,
同时也能允许应用程序发现自己被NAT分配的公网IP。这个协议在2003年3月被提出,其介绍页面里
说到已经被STUN/RFC5389所替代,后者才是我们要详细介绍的。
STUN/RFC5389中,STUN的全称为Session Traversal Utilities for NAT,即NAT环境下的会话传输工具,
是一种处理NAT传输的协议,但主要作为一个工具来服务于其他协议。和STUN/RFC3489类似,可以被终端用来发现其公网IP和端口,同时可以检测端点间的连接性,也可以作为一种保活(keep-alive)协议来维持NAT的绑定。
和STUN/RFC3489最大的不同点在于,STUN本身不再是一个完整的NAT传输解决方案,而是在NAT传输环境中作为一个辅助的解决方法,同时也增加了TCP的支持。STUN/RFC5389废弃了STUN/RFC3489,因此后者通常称为classic STUN,但依旧是后向兼容的。
而完整的NAT传输解决方案则使用STUN的工具性质,ICE就是一个基于offer/answer方法的完整NAT传输方案,如SIP 。
STUN是一个C/S架构的协议,支持两种传输类型。一种是请求/响应(request/respond)类型,由客户端给服务器发送请求,
并等待服务器返回响应;另一种是指示类型(indication transaction),由服务器或者客户端发送指示,另一方不产生响应。
两种类型的传输都包含一个96位的随机数作为事务ID(transaction ID),对于请求/响应类型,事务ID允许客户端将响应和产生响应的请求连接起来;
对于指示类型,事务ID通常作为debugging aid使用。
所有的STUN报文信息都含有一个固定头部,包含了方法,类和事务ID。方法表示是具体哪一种传输类型(两种传输类型又分了很多具体类型),
STUN中只定义了一个方法,即binding(绑定),其他的方法可以由使用者自行拓展;Binding方法可以用于请求/响应类型和指示类型,
用于前者时可以用来确定一个NAT给客户端分配的具体绑定,用于后者时可以保持绑定的激活状态。类表示报文类型是请求/成功响应/错误响应/指示。
在固定头部之后是零个或者多个属性(attribute),长度也是不固定的。
接下来,简单介绍一下STUN的 STUN/RFC3489 和 STUN/RFC5389
1.1 RFC3489/STUN
STUN/RFC3489 全名有叫做:Simple Traversal of UDP Through NAT
STUN/RFC3489 就是将STUN定义成简单的通过UDP进行NAT穿越的一套规范,也就是告诉你如何一步一步通过UDP进行穿越,但是这套规范在穿越的过程中还是存在很多问题,尤其是现在的网络路由器对UDP的限制比较多,有的路由器甚至不允许进行UDP传输,所以这就导致了我们通过STUN/RFC3489 这套规范进行NAT穿越的时候它的失败率会非常高。所以为了 解决这个问题,又定义了另一套标准,STUN/RFC5389.
1.2 RFC5389/STUN
STUN/RFC5389中,STUN的全称为Session Traversal Utilities for NAT,即NAT环境下的会话传输工具,是一种处理NAT传输的协议,但主要作为一个工具来服务于其他协议。和STUN/RFC3489类似,可以被终端用来发现其公网IP和端口,同时可以检测端点间的连接性,也可以作为一种保活(keep-alive)协议来维持NAT的绑定。和STUN/RFC3489最大的不同点在于,STUN本身不再是一个完整的NAT传输解决方案,而是在NAT传输环境中作为一个辅助的解决方法,
同时也增加了TCP的支持。STUN/RFC5389废弃了STUN/RFC3489,因此后者通常称为classic STUN,但依旧是后向兼容的。
而完整的NAT传输解决方案则使用STUN的工具性质,ICE就是一个基于offer/answer方法的完整NAT传输方案,如SIP。
总的说来,RFC5389是在RFC3489的基础上又增加了一些功能,但是它对整个STUN的描述就不一样了, 它是把STUN描述成一系列穿越NAT的工具,所以都叫STUN,但是他们的含义完全就不一样了。RFC5389在UDP尝试可能失败的情况下,尝试使用TCP,也就是说RFC5389是包括UDP和TCP的两种协议进行NAT穿越的,这是两套规范最本质的区别。当然在协议的具体内容上,包括协议头还有协议体中的属性都有很多的变化,但是那些都不是最关键的,最关键的是RFC5389里面将TCP纳入进来。你可以通过TCP进行穿越。
1.3 RFC5389与RFC3489的区别
- 去掉
STUN是一种完整的NAT穿透方案的概念,现在是一种用于提供NAT穿透解决方案的工具。因而,协议的名称变为NAT会话穿透效用; - 定义了
STUN的用途; - 去掉了
STUN关于NAT类型检测和绑定生命期发现的用法,去掉了RESPONSE-ADDRESS、CHANGED-ADDRESS、CHANGE-REQUEST、SOURCE-ADDRESS和REFLECTED-FROM属性; - 增加了一个固定的32位的魔术字字段,事务ID字段减少了32位长度;
- 增加了
XOR-MAPPED-ADDRESS属性,若魔术字在捆绑请求中出现时,该属性包括在捆绑响应中。否则,RFC3489中的行为是保留的(换句话说,捆绑响应中包括MAPPED-ADDRESS); - 介绍了消息类型字段的正式结构,带有一对明确的位来标识
Request、Response、Error-Response或Indication消息。因此,消息类型字段被划分为类别和方法两部分; - 明确的指出了
STUN的最高2位是0b00 ,当用于ICE时可以简单的与RTP包区分开来; - 增加指纹属性来提供一种明确的方法来检测当
STUN协议多路复用时,STUN与其他协议之间的差异; - 增加支持
IPv6,IPv4客户端可以获取一个IPv6映射地址,反之亦然; - 增加一个
long-term-credential-based认证机制; - 增加了
SOFTWARE、REALM、NONCE和ALTERNATE-SERVER属性; - 去掉了共享密匙方法,因此
PASSWORD属性也去掉了; - 去掉了使用连续10秒侦听STUN响应来识别一个攻击的做法;
- 改变事务计时器来增加
TCP友好性; - 去掉了
STUN例子如集中分离控制和媒体面,代替的,在使用STUN协议时提供了更多的信息; - 定义了一类填充机制来改变长度属性的说明;
REALM、SERVER、原因语句和NONCE限制在127个字符,USERNAME限制在513个字节以内;- 为
TCP和TLS改变了DNS SRV规程,UDP仍然和以前保持一致。
1.4 新特性
1.4.1 指纹机制
FINGERPRINT机制是一种可选的用于其他协议多路复用STUN时发送给相同的传输地址时区分STUN数据包的机制,该机制不支持与RFC3489相兼容。
在一些用途中,基于相同的传输地址时多个协议会多路复用STUN消息,例如RTP协议。STUN消息必须首先和应用报文分离开。目前,在STUN报头中有3种固定的字段可以用于该目的。尽管如此,在一些案例中,三种固定字段仍然不能充分的区别开。
当扩展的指纹机制被使用时,STUN代理在发送给其他STUN代理的消息中包括FINGERPRINT属性。当其他STUN代理收到时,除基本的检查之外,还将检查是否包含FINGERPRINT属性及它是否包含正确的值,至此,它将相信这是一个STUN消息。指纹机制帮助STUN代理检查其他协议那些看起来像是STUN消息的消息。
1.4.2 通过DNS发现服务器机制
STUN客户端可以使用DNS来发现STUN服务器的IP地址和端口。客户端必须知道服务器的域名。
当客户端希望找出服务器在公网上的位置就采用捆绑请求/响应事务,SRV(资源记录表)中服务器名称是“stun”。当通过TLS会话采用捆绑请求/响应事务,SRV中服务器名称为“stuns”。STUN用户可以定义额外的DNS资源记录服务名称。
STUN请求的默认端口是3478,用于TCP和UDP。STUN在TLS上的默认端口是5349。服务器能够在TLS上STUN与STUN在TCP上时使用相同的端口,只有服务器软件支持决定初始消息是否是TLS或STUN消息。
如果SRV中没有记录可查,客户端执行A或AAAA记录查找域名。结果将会是1张IP地址表,每一个都可以使用TCP或UDP采用默认端口号连接。通常要求使用TLS,客户端使用STUN在TLS上的默认端口号连接其中一个IP地址。
1.4.3 认证和消息完整性机制
短期证书机制
短期证书机制假设在STUN事务之前,客户端和服务器已经使用了其他协议来交换了证书,以username和password形式。这个证书是有时间限制的。例如,在ICE用途中,两个终端使用带外方式交换信息来对username和password达成一致,并在媒体会话期间使用。这个证书被用来进行消息完整性检查,用于每个请求和多个响应中。与长期证书机制相比,没有挑战和响应方式,因此,这种证书的时间限制特性的优点是可以阻止重播。
长期证书机制
长期证书机制依赖于一个长期证书,username和password在客户端和服务器中是共用的。这个证书从它提供给 用户开始将一直是有效的,直到该用户不再是该系统的用户。这本质上是一个提供给用户username和password的传统的登入方式。
客户端初始发送一个请求,没有提供任何证书和任何完整性检测。服务器拒绝这个请求,并提供给用户一个范围(用于指导用户或代理选择username和password)和一个nonce。这个nonce提供重放保护。它是一个cookie,由服务器选择,以这样一种方式来标示有效时间或客户端身份是有效的。客户端重试这个请求,这次包括它的username和realm和服务器提供的nonce来回应。服务器确认这个nonce和检查这个message integrity。如果它们匹配,请求则通过认证。如果这个nonce不再有效,即过期了,服务器就拒绝该请求,并提供一个新的nonce。
在随后的到同一服务器的请求,客户端重新使用这个nonce、username和realm,和先前使用的password。这样,随后的请求不会被拒绝直到这个nonce变成无效的。需要注意的是,长期证书机制不能用来保护Indications,由于Indications不能被改变,因此,使用Indications时要么使用短期证书,要么就省略认证和消息完整性。因为长期证书机制对离线字典攻击敏感,部署的时候应该使用很难猜测的密码。
1.4.4 备份服务器机制
服务器使用增强的重定向功能将一个客户端转向另一个服务器,通过回应一个错误响应号为300(尝试备份)的错误响应。服务器在错误响应中携带一个ALTERNATE-SERVER属性。
客户端收到错误响应号为300的错误响应后,在该响应中查找ALTERNATE-SERVER属性。若找到一个,客户端就会将当前的事务作废,并重新尝试发送请求到该属性中列出的服务器。请求报文若已经通过认证,则必须使用与先前发送给执行重定向操作的服务器同样的证书。如果客户端在最后5分钟里已经重试发送请求时已经重定向到了一个服务器,它必须忽略重定向操作并将当前的事务作废,这是为了防止无限的重定向循环。
1.5 RFC5389与RFC3489的兼容
在RFC3489中:
- UDP是唯一支持的传输协议
- RFC5389中的魔术字字段是RFC3489中事务ID的一部分,事务ID长128位
- 没有XOR-MAPPED-ADDRESS属性,绑定方法是使用MAPPED-ADDRESS属性代替
- 有3个需要强制理解的属性,分别是:RESPONSE-ADDRESS、CHANGE-REQUEST、CHANGED-ADDRESS属性,而RFC5389中不再支持这些属性。
1.5.1 客户端处理的改变
客户端想要与RFC3489的服务器互操作,应发送一个使用绑定方法的请求消息,不包含任何消息,使用UDP协议发送给服务器。如果成功,将收到服务器发回的包含MAPPED-ADDRESS属性而不是XOR-MAPPED-ADDRESS属性的成功响应。客户端试图与基于RFC3489的应用服务器互操作必须准备好接收任意一个属性。此外,客户端必须忽略任何在响应中出现的保留的强制理解的属性。RFC3489中规定保留属性中的0x0002、0x0004、0x0005和0x000B可能出现在绑定响应中。
1.5.2 服务器处理的改变
服务器能够察觉由RFC3489中的客户端发送的携带有不正确的魔术字的捆绑请求消息。当服务器察觉到RFC3489中的客户端,它应该将捆绑请消息中魔术字域中的值拷贝到捆绑响应中的魔术字字段中,并且插入一个MAPPED-ADDRESS属性代替XOR-MAPPED-ADDRESS属性。
客户端在极少的环境下可能包括RESPONSE-ADDRESS或CHANGE-REQUEST属性中的一个。在这些情况下,服务器把这些属性看做是一个不认识的强制理解的属性,并回应一个错误响应。RFC3489版本中的STUN缺少魔术字和指纹属性这两种能够高可靠性的正确标识其他协议多路复用时的STUN消息。因此,STUN执行与RFC3489兼容时不应该被用于多个协议。
1.6 STUN
STUN是一个C/S架构的协议,支持两种传输类型。一种是请求/响应(request/respond)类型,由客户端给服务器发送请求,
并等待服务器返回响应;另一种是指示类型(indication transaction),由服务器或者客户端发送指示,另一方不产生响应。
两种类型的传输都包含一个96位的随机数作为事务ID(transaction ID),对于请求/响应类型,事务ID允许客户端将响应和产生响应的请求连接起来;
对于指示类型,事务ID通常作为debugging aid使用。
所有的STUN报文信息都含有一个固定头部,包含了方法,类和事务ID。方法表示是具体哪一种传输类型(两种传输类型又分了很多具体类型),
STUN中只定义了一个方法,即binding(绑定),其他的方法可以由使用者自行拓展;Binding方法可以用于请求/响应类型和指示类型,
用于前者时可以用来确定一个NAT给客户端分配的具体绑定,用于后者时可以保持绑定的激活状态。类表示报文类型是请求/成功响应/错误响应/指示。
在固定头部之后是零个或者多个属性(attribute),长度也是不固定的。
2. STUN协议作用
STUN存在的目的就是进行NAT穿越
STUN 存在的目的就是进行NAT穿越,NAT有四种类型,每种类型如何穿越它的基本原理是什么,都是属于STUN协议中的一部分。
STUN是典型的客户端/服务器模式。客户端发送请求,服务端进行响应
总的来说目前STUN有三种用途:
Interactive Connectivity Establishment(ICE)[MMUSIC-ICE],交互式连接建立Client-initiated connections for SIP[SIP-OUTBOUND],用于SIP的客户端初始化连接NAT Behavior Discovery[BEHAVE-NAT],NAT行为发现
3. STUN协议分析
在学习STUN协议之前,我们有必要先理解什么大端模式,小端模式:
- 大端模式 : 数据的高字节保存在内存的低地址中
- 小端模式 :数据的高字节保存在内存的高地址中
- 网络字节序 :采用大端排序方式
3.1 STUN包文结构
STUN报文和大多数网络类型的格式一样,是以大端编码(big-endian)的,即最高有效位在左边。所有的STUN报文都以20字节的头部开始,后面跟着若干个属性。下面来详细说说。
STUN头部包含了: STUN消息类型 , 消息长度 ,magic cookie ,事务ID ,如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0|STUN Message Type|Message Length|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Magic Cookie|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
||
|---|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
shell

如上图所示的是STUN header,STUN包文结构 包括20字节的STUN header 然后Body 中可以有0个或多个Attribute
STUN header 中主要分为四块:
STUN消息类型 , 消息长度 ,magic cookie ,事务ID
下面简单介绍一下每个块的作用:
- STUN消息类型
STUN消息类型为2个字节(16bit)类型。最高的2位必须置零,这可以在当STUN和其他协议复用的时候,用来区分STUN包和其他数据包。
STUN Message Type字段定义了消息的类型(请求/成功响应/失败响应/指示)和消息的主方法。
虽然我们有4个消息类别 ,但在STUN中只有两种类型的事务 ,即请求/响应类型和指示类型。
响应类型分为成功和出错两种,用来帮助快速处理STUN信息。Message Type字段又可以进一步分解为如下结构:
0 1
2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+-+-+-+-+-+-+-+-+-+-+-+-+
|M|M|M|M|M|C|M|M|M|C|M|M|M|M|
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
+--+--+-+-+-+-+-+-+-+-+-+-+-+-+
shell
其中显示的位为从最高有效位M11到最低有效位M0,M11到M0表示方法的12位编码。C1和C0两位表示类的编码。比如对于binding方法来说,
0b00表示request,0b01表示indication,0b10表示success response,0b11表示error response,每一个method都有可能对应不同的传输类别。
拓展定义新方法的时候注意要指定该方法允许哪些类型的消息。
消息类型许可的值如下:
| 消息类型 | 含义 |
|---|---|
| 0x0001 | 捆绑请求 |
| 0x0101 | 捆绑响应 |
| 0x0111 | 捆绑错误响应 |
| 0x0002 | 共享私密请求 |
| 0x0102 | 共享私密响应 |
| 0x0112 | 共享私密错误响应 |
- magic cookie
字段包含固定值0x2112A442 ,这是为了前向兼容STUN/RFC3489,因为在classic STUN中,这一区域是事务ID的一部分。
另外选择固定数值也是为了服务器判断客户端是否能识别特定的属性。还有一个作用就是在协议多路复用时候也可以将其作为判断标志之一。
事务ID
Transaction ID字段是个96位 的标识符,用来区分不同的STUN传输事务。对于request/response传输,事务ID由客户端选择,
服务器收到后以同样的事务ID返回response;对于indication则由发送方自行选择。事务ID的主要功能是把request和response联系起来,
同时也在防止攻击方面有一定作用。服务端也把事务ID当作一个Key来识别不同的STUN客户端,因此必须格式化且随机在**0~2^(96-1)**之间。
重发同样的request请求时可以重用相同的事务ID,但是客户端进行新的传输时,必须选择一个新的事务ID。
Message Length :
Message Length字段存储了信息的长度,以字节为单位,不包括20字节的STUN头部。由于所有的STUN属性都是都是4字节对齐(填充)的,
因此这个字段最后两位应该恒等于零,这也是辨别STUN包的一个方法之一。
在网络协议中有很多的协议规范,它的这个消息头中都有长度,有的是包括这个头的有的是不包括这个头的,这个大家一定要记清楚 。
对于STUN来说,消息头的长度length是不包括消息头的;
具体来说:
-
4字节,32位,固定值0x2112A442. 通过它可以判断客户端是否可以识别某些属性 :
新的RFC5389将它分成了两部分,前四个字节也就是32位是一个magic cookie,magic就是一个魔法树了,在这里是固定的0x2112A442,所以我们看到固定的值是这个的话,就是RFC5389否则就是STUN/RFC3489。
通过它是可以识别客户端是否可以识别某些属性,因为不同的规范属性实际上是有变化的,所以你看到 这个魔法树之后就是RFC5389, RFC5389定义了一些新的属性,如果不是的话,这些新的属性就可以忽略掉。 -
12字节 ,96位,标识同一个事物的请求和响应 :
剩下的12个字节,96位,标识同一个事物的请求和响应,当我们客户端发送多个请求的时候,就能通过这个事物ID将服务端返回的响应与之前发送的请求进行一一匹配。
3.2 STUN包文属性字段
在STUN报文头部之后,通常跟着0个或者多个属性,每个属性必须是TLV编码的(Type-Length-Value)。其中Type字段和Length字段都是16位,
Value字段为为32位表示,如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Type|Length|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Value (variable) ....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
shell
Length字段 必须包含Value部分需要补齐的长度,以字节为单位。由于STUN属性以32bit边界对齐,因此属性内容不足4字节的都会以padding bit进行补齐。
padding bit会被忽略,但可以是任何值。
Type字段 为属性的类型。任何属性类型都有可能在一个STUN报文中出现超过一次。除非特殊指定,否则其出现的顺序是有意义的:
即只有第一次出现的属性会被接收端解析,而其余的将被忽略。为了以后版本的拓展和改进,属性区域被分为两个部分。
Type值 在0x0000-0x7FFF之间的属性被指定为强制理解,意思是STUN终端必须要理解此属性,否则将返回错误信息;而0x8000-0xFFFF之间的属性为选择性理解,即如果STUN终端不识别此属性则将其忽略。目前STUN的属性类型由IANA维护。
3.2.1 MAPPED-ADDRESS
MAPPED-ADDRESS同时也是classic STUN的一个属性,之所以还存在也是为了前向兼容。其包含了NAT客户端的反射地址,
Family为IP类型,即IPV4(0x01)或IPV6(0x02),Port为端口,Address为32位或128位的IP地址。注意高8位必须全部置零,
而且接收端必须要将其忽略掉。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0|Family|Port|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
||
|---|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
shell
3.2.2 XOR-MAPPED-ADDRESS
XOR-MAPPED-ADDRESS和MAPPED-ADDRESS基本相同,不同点是反射地址部分经过了一次异或(XOR)处理。对于X-Port字段,
是将NAT的映射端口以小端形式与magic cookie的高16位进行异或,再将结果转换成大端形式而得到的,X-Address也是类似。之所以要经过这么一次转换,是因为在实践中发现很多NAT会修改payload中自身公网IP的32位数据,从而导致NAT打洞失败。
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|x x x x x x x x|Family|X-Port|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| X-Address (Variable)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
shell
3.2.3 RESPONSE-ADDRESS
RESPONSE-ADDRESS属性表示响应的目的地址
3.2.4 CHASNGE-REQUEST
客户使用32位的CHANGE-REQUEST属性来请求服务器使用不同的地址或端口号来发送响应。
3.2.5 SOURCE-ADDRESS
SOURCE-ADDRESS属性出现在捆绑响应中,它表示服务器发送响应的源IP地址和端口。
3.2.6 CHANGED-ADDRESS
如果捆绑请求的CHANGE-REQUEST属性中的“改变IP”和“改变端口”标志设置了,则CHANGED-ADDRESS属性表示响应发出的IP地址和端口号。
3.2.3 ERROR-CODE
ERROR-CODE属性用于error response报文中。其包含了300-699表示的错误代码,以及一个UTF-8格式的文字出错信息(Reason Phrase)。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Reserved, should be 0|Class|Number|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reason Phrase (variable) ..
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
shell
错误代码在语义上还与SIP和HTTP协议保持一致。如下表:
| 错误码 | 含义 | 说明 |
|---|---|---|
| 300 | 尝试代替(Try Alternate), | 客户端应该使用该请求联系一个代替的服务器。这个错误响应仅在请求包括一个USERNAME属性和一个有效的MESSAGE-INTEGRITY属性时发送;否则它不会被发送,而是发送错误代码为400的错误响应; |
| 400 | 错误请求(Bad Request), | 请求变形了,客户端在修改先前的尝试前不应该重试该请求。 |
| 401 | 未授权(Unauthorized), | 请求未包括正确的资格来继续。客户端应该采用一个合适的资格来重试该请求。 |
| 420 | 未知属性(Unknown Attribute), | 服务器收到一个STUN包包含一个强制理解的属性但是它不会理解。服务器必须将不认识的属性放在错误响应的UNKNOWN-ATTRIBUTE属性中。 |
| 430 | 过期资格 | 捆绑请求没有包含MESSAGE-INTEGRITY属性,但它使用过期的共享私密。客户应该获得新的共享私密并再次重试。 |
| 431 | 完整性检查失败 | 捆绑请求包含MESSAGE-INTEGRITY属性,但HMAC验证失败。这可能是潜在攻击的表现,或者客户端实现错误 |
| 432 | 丢失用户名 | 捆绑请求包含MESSAGE-INTEGRITY属性,但没有USERNAME属性。完整性检查中两项都必须存在。 |
| 433 | 使用TLS | 共享私密请求已经通过TLS(Transport Layer Security,即安全传输层协议)发送,但没有在TLS上收到。 |
| 438 | 过期Nonce(Stale Nonce), | 客户端使用的Nonce不再有效,应该使用响应中提供的Nonce来重试。 |
| 500 | 服务器错误(Server Error), | 服务器遇到临时错误,客户端应该再次尝试。 |
| 600 | 全局失败 | 服务器拒绝完成请求,客户不应该重试。 |
3.2.4 USERNAME
USERNAME属性用于消息的完整性检查,用于消息完整性检查中标识共享私密。USERNAME通常出现在共享私密响应中,与PASSWORD一起。当使用消息完整性检查时,可有选择地出现在捆绑请求中
3.2.5 PASSWORD
PASSWORD属性用在共享私密响应中,与USERNAME一起。PASSWORD的值是变长的,用作共享私密,它的长度必须是4字节的倍数,以保证属性与边界对齐。
3.2.6 MESSAGE-INTEGRITY
MESSAGE-INTEGRITY属性包含STUN消息的HMAC-SHA1,它可以出现在捆绑请求或捆绑响应中;MESSAGE-INTEGRITY属性必须是任何STUN消息的最后一个属性。它的内容决定了HMAC输入的Key值。
3.2.7 UNKNOWN-ATTRIBUTES
UNKNOWN-ATTRIBUTES属性只存在于其ERROR-CODE属性中的响应号为420的捆绑错误响应或共享私密错误响应中。
3.2.8 REFLECTED-FROM
REFLECTED-FROM属性只存在于其对应的捆绑请求包含RESPONSE-ADDRESS属性的捆绑响应中。属性包含请求发出的源IP地址,它的目的是提供跟踪能力,这样STUN就不能被用作DOS攻击的反射器。
3.2.9 SOFTWARE
3.2.10 REALM
3.2.11 NONCE
3.2.12 ALTERNATE-SERVER
4. STUN通信流程
我在前面的博客“[WebRTC源码研究(25)NAT打洞原理]” 中有讲解到NAT的四种类型,这里回顾一下:
NAT 主要分为4种类型,即Full Cone、Restricted Cone、Port Restricted Cone和Symmetric。其中Full Cone、Restricted Cone、Port Restricted Cone可以统称为Cone NAT,它们有一个共同点就是,只要是从同一个内网的地址和端口出来的包,NAT转换后的公网地址和端口一定是相同的。Symmetric 是如果是同一个内网的地址和端口出来的包,到同一个外部目标地址和端口,那么NAT转换后的公网地址和端口号也是相同的,但是如果如果到不同的外部目标地址和端口,NAT会转换成不同的端口号(公网地址是不变的,因为只有一个)
1.Full Cone NAT
从内网主机 in ipx和端口in portx发送的数据会映射为相同的公网ip x 和端口 port x。从其他机器上如果通过UDP发送数据到公网ip x和端口 port x上,最终数据都会被转发到内网主机上(in ipx :in portx)。 发送给内网的ip 和 port 都不受限。
2.Restricted Cone
从内网主机 in ipx和端口in portx发送的数据会映射为相同的公网ip x 和端口 port x。外部机器主动请求通信的源IP地址必须和内部主机主动向这个外部机器发送请求时的外部机器接收ip地址一致。即ip地址受限,端口不限。内网能接收信息的外部机器必须是内网主动发送请求过的外部机器。
3.Port Restricted Cone
从内网主机 in ipx和端口in portx发送的数据会映射为相同的公网ip x 和端口 port x。外部机器主动请求通信的源IP地址、端口必须和内部主机主动向这个外部机器发送请求时外部机器接收的ip地址、端口一致。即ip地址受限,端口都受限。内网能接收信息的外部机器必须是内网主动发送请求过的外部机器(ip相同),同时外网给内网发送数据包的端口还必须是接收内网数据包时所采用的端口号(端口相同)。
4.对称NAT
发送包的目的ip和port 相同,那么NAT 映射的ip和port会相同。如果目的地址不同,即使同一台内网机器、同一个端口,mapping的端口也不同,但是ip还是相同(因为同一个公网ip)。所以只有它主动连的服务器才会知道它的端口。
我们再来看看通过STUN协议 来探测NAT类型。
stun 的NAT 类型探测流程图如下:

假设我们现在有:
A机器在内网(192.168.0.4)
NAT服务器 (210.21.12.140)
B机器在公网(210.15.27.166)
C机器在公网(210.15.27.140)
shell
如果 A 连接过机器B,假设是如下:A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:8000)-> B(210.15.27.166:2000)。A和C从来没有通讯过。
不同类型的NAT,分析:
Full Cone NAT
C 将数据发送到 210.21.12.140:8000,NAT 会将数据包送到A(192.168.0.4:5000)。因为NAT上已经有192.168.0.4:5000到210.21.12.140:8000的映射。也就是说只要机器A与任何公网上机器通讯过,其它任何公网上机器都能发送数据给A,通过发送数据包给NAT上转换后的ip和端口之后,NAT会自动将数据包送到机器A。NAT 对发送给A的数据包来者不拒,不过滤。
Restricted Cone
C无法和A进行通信,因为A从来没有和C通信过,NAT会拒绝C试图与A连接的动作,但是B可以通过发送数据到210.21.12.140:8000和A的192.168.0.4:5000通信,这里机器B自己可以使用任何端口和A通信,譬如210.15.27.166:2001 -> 210.21.12.140:8000,NAT会将数据送到A的5000端口上。注意这里B使用的端口是2001 不是初始A连接B时,B接收A数据使用的端口2000。即只能是通信过的机器才能够进行通信,但是再通信时端口不需要固定。
Port Restricted Cone
C无法和A进行通信,因为A从来没有和C通信过,NAT会拒绝C试图与A连接的动作,而且机器B只能用它的210.15.27.166:2000与A的192.168.0.4:5000通信。即只有曾经收到内网地址A发送过数据包的公网机器,才能通过NAT映射后的地址向内网机制发送UDP包。
Symmetric NAT
如果A机器还想连接C机器,则NAT上产生一个新的映射,对应的转换可能为A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:8001)-> C(210.15.27.140:2000)。
B与A通信:B(210.15.27.166:2000)-> NAT(转换后210.21.12.140:8000)-> C(192.168.0.4:5000)。
C与A通信:B(210.15.27.140:2000)-> NAT(转换后210.21.12.140:8001)-> C(192.168.0.4:5000)。
4.1. 产生一个Request或Indication
当产生一个Request或者Indication报文时,终端必须根据上文提到的规则来生成头部,class字段必须是Request或者Indication,
而method字段为Binding或者其他用户拓展的方法。属性部分选择该方法所需要的对应属性,比如在一些
情景下我们会需要authenticaton属性或FINGERPRINT属性,注意在发送Request报文时候,需要加上SOFTWARE属性(内含软件版本描述)。
4.2. 发送Requst或Indication
目前,STUN报文可以通过UDP,TCP以及TLS-over-TCP的方法发送,其他方法在以后也会添加进来。STUN的使用者必须指定其使用的传输协议,
以及终端确定接收端IP地址和端口的方式,比如通过基于DNS的方法来确定服务器的IP和端口。
4.2.1 通过UDP发送
当使用UDP协议STUN时,STUN的报文可能会由于网络问题而丢失。可靠的STUN请求/响应传输是通过客户端重发request请求来实现的,
因此,在UDP时,Indication报文是不可靠的。STUN客户端通过RTO(Retransmission TimeOut)
来决定是否重传Requst,并且在每次重传后将RTO翻倍。具体重传时间的选取可以参考相关文章,如RFC2988。
重传直到接收到Response才停止,或者重传次数到达指定次数Rc,Rc应该是可配置的,且默认值为7。
4.2.2 通过TCP或者TCP-over-TLS发送
对于这种情景,客户端打开对服务器的连接。在某些情况下,此TCP链接只传输STUN报文,而在其他拓展中,
在一个TCP链接里可能STUN报文和其他协议的报文会进行多路复用(Multiplexed)。数据传输的可靠性由TCP协议本身来保证。
值得一提的是,在一次TCP连接中,STUN客户端可能发起多个传输,有可能在前一个Request的Response还没收到时就再次发送了一个新的Request,
因此客户端应该保持TCP链接打开,认所有STUN事务都已完成。
4.3. 接收STUN消息
当STUN终端接收到一个STUN报文时,首先检查报文的规则是否合法,即前两位是否为0,magic cookie是否为0x2112A442,报文长度是否正确以及对应的方法是否支持。
如果消息类别为Success/Error Response,终端会检测其事务ID是否与当前正在处理的事务ID相同。如果使用了FINGERPRINT拓展的话还会检查FINGERPRINT属性是否正确。
完成身份认证检查之后,STUN终端会接着检查其余未知属性。
4.3.1 处理Request
如果请求包含一个或者多个强制理解的未知属性,接收端会返回error response,错误代码420(ERROR-CODE属性),
而且包含一个UNKNOWN-ATTRIBUTES属性来告知发送方哪些强制理解的属性是未知的。服务端接着检查方法和其他指定要求,如果所有检查都成功,
则会产生一个Success Response给客户端。
4.3.1.1 生成Success Response或Error Response
如果服务器通过某种验证方法(authentication mechanism)通过了请求方的验证,那么在响应报文里最好也加上对应的验证属性。
服务器端也应该加上指定方法所需要的属性信息,另外协议建议服务器返回时也加上SOFTWARE属性。
对于Binding方法,除非特别指明,一般不要求进行额外的检查。当生成Success Response时,服务器在响应里加上XOR-MAPPED-ADDRESS属性。
对于UDP,这是其源IP和端口信息,对于TCP或TLS-over-TCP,这就是服务器端所看见的此次TCP连接的源IP和端口。
发送响应时候如果是用UDP协议,则发往其源IP和端口,如果是TCP则直接用相同的TCP链接回发即可。
4.3.2 处理Indication
如果Indication报文包含未知的强制理解属性,则此报文会被接收端忽略并丢弃。如果对Indication报文的检查都没有错误,则服务端会进行相应的处理,
但是不会返回Response。对于Binding方法,一般不需要额外的检查或处理。收到信息的服务端仅需要刷新对应NAT的端口绑定。
由于Indication报文在用UDP协议传输时不会进行重传,因此发送方也不需要处理重传的情况。
4.3.3 处理Success Response
如果Success Response包含了未知的强制理解属性,则响应会被忽略并且认为此次传输失败。客户端对报文进行检查通过之后,就可以开始处理此次报文。
以Binding方法为例,客户端会检查报文中是否包含XOR-MAPPED-ADDRESS属性,然后是地址类型,如果是不支持的地址类型,则这个属性会被忽略掉。
4.3.4 处理Error Response
如果Error Response包含了未知的强制理解属性,或者没有包含ERROR-CODE属性,则响应会被忽略并且认为此次传输失败。
随后客户端会对验证方法进行处理,这有可能会产生新的传输。
到目前为止,对错误响应的处理主要基于ERROR-CODE属性的值,并遵循如下规则:
如果error code在300到399之间,客户端被建议认为此次传输失败,除非用了ALTERNATE-SERVER拓展;
如果error code在400到499之间,客户端认为此次传输失败;
如果error code在500到599之间,客户端可能会需要重传请求,并且必须限制重传的次数。
任何其他的error code值都会导致客户端认为此次传输失败。
参考:
http://www.52im.net/thread-557-1-1.html
https://www.cnblogs.com/mlgjb/p/8243690.html
https://www.cnblogs.com/pannengzhi/p/5041546.html
stun
https://baike.baidu.com/item/stun/3131387?fr=aladdin
STUN
https://en.wikipedia.org/wiki/STUN
图
https://upload.wikimedia.org/wikipedia/commons/6/63/STUN_Algorithm3.svg
STUN解决的过程
STUN简介
https://www.cnblogs.com/idignew/p/7357731.html
STUN解决的过程
P2P通信标准协议(一)之STUN
https://www.cnblogs.com/pannengzhi/p/5041546.html
STUN服务器
https://baike.baidu.com/item/STUN服务器/2173892
STUN详解
https://segmentfault.com/a/1190000008056434
XMAPP
https://baike.baidu.com/item/XMPP/3430617
xmpp协议详解一:xmpp基本概念
https://www.jianshu.com/p/a94749385755
HTTP协议和XMPP协议
xmpp学习
XMPP
XMPP协议的原理介绍
