STUN(Simple Traversal of User Datagram Protocol Through Network Address Translators),即简单的用UDP穿透NAT,是个轻量级的协议,是基于UDP的完整的穿透NAT的解决方案。它允许应用程序发现它们与公共互联网之间存在的NAT和防火墙及其他类型。它也可以让应用程序确定NAT分配给它们的公网IP地址和端口号。STUN是一种Client/Server的协议,也是一种Request/Response的协议,默认端口号是3478。
了解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将会使用不同的映射。此外,只有收到数据的外网主机才可以反过来向内网主机发送包。
所有的STUN消息都包含20个字节的消息头,包括16位的消息类型,16位的消息长度和128位的事务ID。
上图一共20字节
消息类型许可的值如下:
0x0001:捆绑请求
0x0101:捆绑响应
0x0111:捆绑错误响应
0x0002:共享私密请求
0x0102:共享私密响应
0x0112:共享私密错误响应
消息长度,是消息大小的字节数,但不包括20字节的头部。
事务ID,128位的标识符,用于随机请求和响应,请求与其相应的所有响应具有相同的标识符。16个字节。
消息头之后是0或多个属性,每个属性进行TLV编码,包括16位的属性类型、16位的属性长度和变长属性值。
属性类型定义如下:
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的属性是强制理解的,即除非理解该属性,否则客户或服务器就不能处理该消息。
过程:
客户端位于NAT后面, STUN Server位于公网中。 具体的IP和端口如下:
Client的IP地址和端口号为: IP_C 和 Port_C
NAT的映射后的IP和端口号为:IP_N 和 Port_N
STUN的IP地址和端口为: IP_S1 IP_S2 和 Port_S1 Port_S2
STEP1:验证客户端是否在NAT后面
从客户端发送一个UDP包到 IP_S1 + Port_S1, STUN收到后会把 IP_N+Port_N 保存到UDP包中作为对客户端请求的响应。然后把这个响应发送到 IP_N+Port_N。
无论客户端前面的NAT是什么类型,客户端都应该能够收到这个响应。 如果没有收到,要么是STUN Server不存在(或者你写错了地址或端口), 要么是你的NAT拒绝一切UDP包从外部进入。
当客户端收到响应数据时,我们就能从中得到 IP_N+Port_N, 也就是说我们从这一步能够得到NAT的外网地址和端口号。
拿到NAT的外网地址和端口号后,用其IP和client自身的IP地址进行比较:
如果相同,说明client具有公网地址,它没有在NAT后面。
如果不同,说明client在NAT后面,我们还要进行更多验证。
STEP2:验证我们的NAT是不是 Full Cone NAT
从客户端发送一个UDP包到 IP_S1 + Port_S1,请求Stun Server使用 IP_S2 + Port_S2 对我们的请求进行响应。
如果能收到响应数据: 说明NAT是 Full Cone NAT, 因为NAT来者不拒,不对数据进行任何过滤。
如果未收到响应数据: 我还需要进一步验证NAT的类型
STEP3:验证我们的NAT是对称NAT还是Cone NAT
从客户端发送一个UDP包到 IP_S2 + Port_S2, STUN收到后会把 IP_N+Port_N 保存到UDP包中作为对客户端请求的响应。然后把这个响应发送到 IP_N+Port_N。
这个测试中,我们关心的是UPD包中的数据, 如果包中的 Port_N 与 STEP1中的 Port_N是相同的,那么我们就可以肯定,这是个一个Cone NAT,如果不同那么就说明是个对称NAT。
STEP4:区分 Restict Cone NAT 和 Port Restrict Cone NAT
从客户端发送一个UDP包到 IP_S1 + Port_S1, 要求 Stun Server 用 IP_S1 + Port_S2 返回响应给client。
如果client能收到:说明是 Restrict Cone NAT
如果收不到: 说明时 Port Restrict Cone NAT
整个过程就结束了,除了对称NAT外,我们都能得到NAT的外网IP和端口,以及NAT的类型。