nat

一. Nat介绍

1.nat基本介绍

NAT分类

 

 

nat类型.png

NAPT类型介绍

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

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

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

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

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

二. Stun介绍

1.stun基本介绍

STUN是一种网络协议,它允许位于NAT(或多重NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口。这些信息被用来在两个同时处于NAT路由器之后的主机之间建立UDP通信。该协议由RFC 5389定义。

STUN由三部分组成:

  • STUN客户端;

  • STUN服务器端;

  • NAT路由器。

STUN服务端部署在一台有着两个公网IP的服务器上,大概的结构参考下图。STUN客户端通过向服务器端发送不同的消息类型,根据服务器端不同的响应来做出相应的判断,一旦客户端得知了Internet端的UDP端口,通信就可以开始了。

2. stun的检测过程

STUN协议定义了三类测试过程来检测NAT类型:

  • Test1:STUN Client通过端口{IP-C1:Port-C1}向STUN Server{IP-S1:Port-S1}发送一个Binding Request(没有设置任何属性)。STUN Server收到该请求后,通过端口{IP-S1:Port-S1}把它所看到的STUN Client的IP和端口{IP-M1,Port-M1}作为Binding Response的内容回送给STUN Client。

  • Test1#2:STUN Client通过端口{IP-C1:Port-C1}向STUN Server{IP-S2:Port-S2}发送一个Binding Request(没有设置任何属性)。STUN Server收到该请求后,通过端口{IP-S2:Port-S2}把它所看到的STUN Client的IP和端口{IP-M1#2,Port-M1#2}作为Binding Response的内容回送给STUN Client。

  • Test2:STUN Client通过端口{IP-C1:Port-C1}向STUN Server{IP-S1:Port-S1}发送一个Binding Request(设置了Change IP和Change Port属性)。STUN Server收到该请求后,通过端口{IP-S2:Port-S2}把它所看到的STUN Client的IP和端口{IP-M2,Port-M2}作为Binding Response的内容回送给STUN Client。

  • Test3:STUN Client通过端口{IP-C1:Port-C1}向STUN Server{IP-S1:Port-S1}发送一个Binding Request(设置了Change Port属性)。STUN Server收到该请求后,通过端口{IP-S1:Port-S2}把它所看到的STUN Client的IP和端口{IP-M3,Port-M3}作为Binding Response的内容回送给STUN Client。

STUN协议的输出是:

1)公网IP和Port; 2)防火墙是否设置; 3)客户端是否在NAT之后,及所处的NAT的类型。

因此我们进而整理出,通过STUN协议,我们可以检测的类型一共有以下七种:

  • A:公开的互联网IP:主机拥有公网IP,并且没有防火墙,可自由与外部通信;

  • B:完全锥形NAT;

  • C:受限制锥形NAT;

  • D:端口受限制形NAT;

  • E:对称型UDP防火墙:主机出口处没有NAT设备,但有防火墙,且防火墙规则如下:从主机UDP端口A发出的数据包保持源地址,但只有从之前该主机发出包的目的IP/PORT发出到该主机端口A的包才能通过防火墙;

  • F:对称型NAT;

  • G:防火墙限制UDP通信。

3. stun协议的判断过程

 

 

stun协议的判断过程.png

STEP1:检测客户端是否有能力进行UDP通信以及客户端是否位于NAT后 -- Test1

客户端建立UDP socket,然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器返回客户端的IP和Port,客户端发送请求后立即开始接受数据包。重复几次。

  • a)如果每次都超时收不到服务器的响应,则说明客户端无法进行UDP通信,可能是:G防火墙阻止UDP通信

  • b)如果能收到回应,则把服务器返回的客户端的(IP:PORT)同(Local IP: Local Port)比较:如果完全相同则客户端不在NAT后,这样的客户端是:A具有公网IP可以直接监听UDP端口接收数据进行通信或者E。否则客户端在NAT后要做进一步的NAT类型检测(继续)。

STEP2:检测客户端防火墙类型 -- Test2

STUN客户端向STUN服务器发送请求,要求服务器从其他IP和PORT向客户端回复包:

  • a)收不到服务器从其他IP地址的回复,认为包前被前置防火墙阻断,网络类型为E

  • b)收到则认为客户端处在一个开放的网络上,网络类型为A

STEP3:检测客户端NAT是否是FULL CONE NAT -- Test2

客户端建立UDP socket然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器用另一对(IP-2,Port-2)响应客户端的请求往回发一个数据包,客户端发送请求后立即开始接受数据包。 重复这个过程若干次。

  • a)如果每次都超时,无法接受到服务器的回应,则说明客户端的NAT不是一个Full Cone NAT,具体类型有待下一步检测(继续)。

  • b)如果能够接受到服务器从(IP-2,Port-2)返回的应答UDP包,则说明客户端是一个Full Cone NAT,这样的客户端能够进行UDP-P2P通信。

STEP4:检测客户端NAT是否是SYMMETRIC NAT -- Test1#2

客户端建立UDP socket然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器返回客户端的IP和Port, 客户端发送请求后立即开始接受数据包。 重复这个过程直到收到回应(一定能够收到,因为第一步保证了这个客户端可以进行UDP通信)。用同样的方法用一个socket向服务器的(IP-2,Port-2)发送数据包要求服务器返回客户端的IP和Port。比较上面两个过程从服务器返回的客户端(IP,Port),如果两个过程返回的(IP,Port)有一对不同则说明客户端为Symmetric NAT,这样的客户端无法进行UDP-P2P通信(检测停止)因为对称型NAT,每次连接端口都不一样,所以无法知道对称NAT的客户端,下一次会用什么端口。否则是Restricted Cone NAT,是否为Port Restricted Cone NAT有待检测(继续)。

STEP5:检测客户端NAT是Restricted Cone 还是 Port Restricted Cone -- Test3

客户端建立UDP socket然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器用IP-1和一个不同于Port-1的端口发送一个UDP 数据包响应客户端, 客户端发送请求后立即开始接受数据包。重复这个过程若干次。如果每次都超时,无法接受到服务器的回应,则说明客户端是一个Port Restricted Cone NAT,如果能够收到服务器的响应则说明客户端是一个Restricted Cone NAT。以上两种NAT都可以进行UDP-P2P通信。

由前述的一对多转换模型得知,除对称型NAT以外的模型,NAT网关对内部主机地址端口的映射都是相对固定的,所以比较容易实现NAT穿越。而对称型NAT为每个连接提供一个映射,使得转换后的公网地址和端口对不可预测。此时TURN可以与STUN绑定提供穿越NAT的服务,即在公网服务器上提供一个“地址端口对”,所有此“地址端口对”接收到的数据会经由探测建立的连接转发到内网主机上。TURN分配的这个映射“地址端口对”会通过STUN响应发给内部主机,后者将此信息放入建立连接的信令中通知通信的对端。这种探针技术是一种通用方法,不用在NAT设备上为每种应用协议开发功能,相对于ALG方式有一定普遍性。但是TURN中继服务会成为通信瓶颈。而且在客户端中增加探针功能要求每个应用都要增加代码才能支持。

我理解上述stun+turn的方式可以解决对称nat的nat穿越问题,因为turn提供公网ip+port,中继转发。 就相当于源服务器和turn只建立一个连接,所以映射的ip和port不会变。 所以这个方案其实是从源server到turn至少两层nat。 其实相当于turn服务器提供了port mapping服务?

三. 对称型nat打洞分析

实际上大部运营商提供的光猫上网服务都是锥形nat的。 而光纤入户,3g 4g网络,公共wifi登因为安全因素都是对称nat。 对称型nat是可以打洞的。只要请求链接的对方是非端口限制锥型nat就都能实现打洞p2p链接的。 只有双方都是对称是一定无法实现。 (ICE可以解决?) 或一方对称一方是端口限制锥型nat的情况也无法实现打洞 生日攻击算法(端口预测)确实可以解决对称型与端口限制型之间的穿透

对于一端是对称nat,一端是端口限制性Cone nat的情况是可以打洞成功的,特别是我们实验的对称nat的端口变化还是有规律的(加1),我们使用端口猜测的方法进行打洞成功率还是非常高的。对于端口变化无规律的对称nat,这个猜测还是靠算法的设计,你可以看看A New Method for Symmetric NAT Traversal in UDP and TCP (http://www.goto.info.waseda.ac.jp/~wei/file/wei-apan-v10.pdf)另外如果你是做应用的话建议不要始终把自己局限于一定要打洞成功的思路上,对于一些路由器是可以通过配置支持穿透的,比如upnp;对于实在打洞不成功的情况你可以通过设计一个中转服务器来完成自己的应用;

UDP hole punching

对于Cone NAT要采用UDP打洞.需要一个公网机器C来充当”介绍人”. 内网的A,B先分别和C通信.打开各自的NAT端口. C这个时候知道A,B的公网IP: Port. 现在A和B想直接连接.比如A给B发.除非B是Full Cone.否则不能通信.反之亦然. 但是我们可以这样: A要连接B.A给B发一个UDP包.同时.A让那个介绍人给B发一个命令,让B同时给A发一个UDP包.这样双方的NAT都会记录对方的IP,然后就会允许互相通信.

同一个NAT后面的情况

如果A,B在同一个NAT后面.如果用上面的技术来进行互连.那么如果NAT支持loopback(就是本地到本地的转换),A,B可以连接,但是比较浪费带宽和NAT.有一种办法是,A,B和介绍人通信的时候,同时把自己的local IP也告诉服务器.A,B通信的时候,同时发local ip和公网IP.谁先到就用哪个IP.但是local ip就有可能不知道发到什么地方去了.比如A,B在不同的NAT后面但是他们各自的local ip段一样.A给B的local IP发的UDP就可能发给自己内部网里面的了.

还有一个办法是服务器来判断A,B是否在一个NAT后面.(网络拓朴不同会不会有问题?)

把4种类型分别标为1234,有两台主机A:portA和B:portB(port都为外网端口,是与打洞服务器通信的端口),以及打洞服务器S,情景是B拿到了A:portA的信息,要与A通信。 (1)、A为类型1;无论B为哪种类型,都可以直接与A:portA udp通信; (2)、A为类型2;无论B为哪种类型,在A知道B之前都无法直接连接,B给S发一个打洞请求,S转发该请求到A。若B的类型为1,则A:portA可直接与B:portB udp通信;若B的类型为2或3,则A:portA和B:portB各自向对方发送一个一字节的udp包,分别在自己的路由器上打洞,从此A:portA和B:portB可进行udp通信;若B为类型4,则portB在与不同的ip:port通信时会不一样,所以A:portA先向B发送一个一字节的udp包,在路由器上打洞,然后等待B:portB先发送数据,A:portA接收到B:portB的数据后,即知道portB,也可互通数据了; (3)、A为类型3;无论B为哪种类型,在A知道B之前都无法直接连接,B给S发一个打洞请求,S转发该请求到A。若B的类型为1,则A:portA可直接与B:portB udp通信;若B的类型为2或3,则A:portA和B:portB各自向对方发送一个一字节的udp包,分别在自己的路由器上打下洞,从此A:portA和B:portB可进行udp通信;若B为类型4,则portB在与不同的ip:port通信时会不一样,而A又要求知道portB的才可让B:portB连进来,所以这种情况A只能猜测与A:portA通信的portB,通信概率小; (4)、A为类型4;无论B为哪种类型,在A知道B之前都无法直接连接,B给S发一个打洞请求,S转发该请求到A。若B的类型为1,则A:portA可直接与B:portB udp通信;若B的类型为2,则B:portB先向A发送一个一字节的udp包,在路由器上打洞,然后等待A:portA先发送数据,B:portB接收到A:portA的数据后,即知道portA,也可互通数据了;若B的类型为3,见(3)中B为类型4的描述;若B为4,双方无法知道对方的端口,无法通信。

STUN :RFC 5389 - Session Traversal Utilities for (NAT) (STUN) ICE:RFC 5245 - Interactive Connectivity Establishment (ICE): A Methodology for Network Address Translator (NAT) Traversal for Offer/Answer Protocols

测试结果记录

目前在公司和在家中测试,stun返回结果都是对称型nat。

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n186" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: monospace, monospace; font-size: 0.9rem; white-space: pre; line-height: 1.71429em; display: block; break-inside: avoid; text-align: left; background-image: ; background-position: var(--code-block-bg-color); background-size: ; background-repeat: var(--code-block-bg-color); background-attachment: ; background-origin: ; background-clip: ; position: relative !important; margin-bottom: 3em; padding-left: 1ch; padding-right: 1ch; margin-left: 2em; width: inherit;">
bolen@DESKTOP-G4LODA2 MINGW64 /c/projects/gocode/src/github.com/ccding/go-stun (master)
./go-stun.exe -v 2019/01/06 08:00:42 Do Test1 2019/01/06 08:00:42 Send To: 18.191.223.12:3478 2019/01/06 08:00:43 Received: {packet nil: false, local: 42.101.65.132:5031, remote: 18.191.223.12:3478, cha nged: <nil>, other: 18.219.110.18:3479, identical: false} 2019/01/06 08:00:43 Do Test2 2019/01/06 08:00:43 Send To: 18.191.223.12:3478 2019/01/06 08:00:52 Received: Nil 2019/01/06 08:00:52 Do Test1 2019/01/06 08:00:52 Send To: 18.219.110.18:3479 2019/01/06 08:00:53 Received: {packet nil: false, local: 42.101.65.134:28392, remote: 18.219.110.18:3479, ch anged: <nil>, other: 18.191.223.12:3478, identical: false} NAT Type: Symmetric NAT External IP Family: 1 External IP: 42.101.65.132 External Port: 5031 ​ bolen@DESKTOP-G4LODA2 MINGW64 /c/projects/gocode/src/github.com/ccding/go-stun (master) go build

bolen@DESKTOP-G4LODA2 MINGW64 /c/projects/gocode/src/github.com/ccding/go-stun (master)
./go-stun.exe -v 2019/01/06 08:03:57 Do Test1 2019/01/06 08:03:57 Send To: 217.10.68.145:10000 2019/01/06 08:03:58 Received: {packet nil: false, local: 42.101.65.133:18231, remote: 217.10.68.145:10000, c hanged: 217.116.122.141:10001, other: <nil>, identical: false} 2019/01/06 08:03:58 Do Test2 2019/01/06 08:03:58 Send To: 217.10.68.145:10000 2019/01/06 08:04:07 Received: Nil 2019/01/06 08:04:07 Do Test1 2019/01/06 08:04:07 Send To: 217.116.122.141:10001 2019/01/06 08:04:07 Received: {packet nil: false, local: 42.101.65.134:19019, remote: 217.116.122.141:10001, changed: 217.10.68.145:10000, other: <nil>, identical: false} NAT Type: Symmetric NAT External IP Family: 1 External IP: 42.101.65.133 External Port: 18231 ​ bolen@DESKTOP-G4LODA2 MINGW64 /c/projects/gocode/src/github.com/ccding/go-stun (master) ./go-stun.exe -v
2019/01/06 08:05:34 Do Test1
2019/01/06 08:05:34 Send To: 217.10.68.145:10000
2019/01/06 08:05:34 Received: {packet nil: false, local: 42.101.65.133:23317, remote: 217.10.68.145:10000, changed: 217.116 .122.141:10001, other: <nil>, identical: false}
2019/01/06 08:05:34 Do Test2
2019/01/06 08:05:34 Send To: 217.10.68.145:10000
2019/01/06 08:05:44 Received: Nil
2019/01/06 08:05:44 Do Test1
2019/01/06 08:05:44 Send To: 217.116.122.141:10001
2019/01/06 08:05:44 Received: {packet nil: false, local: 42.101.65.134:44004, remote: 217.116.122.141:10001, changed: 217.1 0.68.145:10000, other: <nil>, identical: false}
NAT Type: Symmetric NAT
External IP Family: 1
External IP: 42.101.65.133
External Port: 23317

bolen@DESKTOP-G4LODA2 MINGW64 /c/projects/gocode/src/github.com/ccding/go-stun (master)
$ ./go-stun.exe -v
2019/01/06 08:06:02 Do Test1
2019/01/06 08:06:02 Send To: 217.10.68.145:10000
2019/01/06 08:06:03 Received: {packet nil: false, local: 42.101.65.133:13858, remote: 217.10.68.145:10000, changed: 217.116 .122.141:10001, other: <nil>, identical: false}
2019/01/06 08:06:03 Do Test2
2019/01/06 08:06:03 Send To: 217.10.68.145:10000
2019/01/06 08:06:12 Received: Nil
2019/01/06 08:06:12 Do Test1
2019/01/06 08:06:12 Send To: 217.116.122.141:10001
2019/01/06 08:06:13 Received: {packet nil: false, local: 42.101.65.134:25117, remote: 217.116.122.141:10001, changed: 217.1 0.68.145:10000, other: <nil>, identical: false}
NAT Type: Symmetric NAT
External IP Family: 1
External IP: 42.101.65.133
External Port: 13858</pre>

解决方案汇总

  1. stun 配合upnp(爱奇艺等内容服务商的方案)

    https://github.com/jflyup/nat_traversal (c语言实现的,repo里声称解决了including symmetric NATs问题,网上有测试成功案例。)

    https://github.com/sjtcumt/wp/tree/master/p2p/p2psrv(解决了对称nat-对称nat,对称nat-端口限制锥形nat的问题,但不稳定)

    https://github.com/ccding/go-stun

    以上stun解决对称nat的问题,基本上都是利用了端口猜测和生日攻击算法。

  2. ICE,可解决全部nat穿越问题。

posted @ 2019-05-23 15:52  苦逼码农2014  阅读(2317)  评论(0编辑  收藏  举报