stun检查nat类型

nat(Session Traversal Utilities for NAT)会话穿越应用程序,可以让位于nat后的客户端找出自己的公网地址以及对应的Internet端口,最重要的是可以判断自己处于哪种nat类型之后,它是通过udp进行通信的。

Server在收到client的UDP包以后,Server将接收到该包的地址和端口利用udp传回来给client,client把这些地址和端口与本机的ip地址和端口进行比较,如果不同,说明在NAT后面,否则就位于NAT后面。

下面重点讲怎么判断nat的类型。

一般将nat类型可以分为四种:全锥形,受限锥形,端口受限锥形,对称形。

之所以是叫锥,可以想象一下圆锥,一个圆集中映射到一个点,也就是本地ip端口映射到nat服务器上面的公网ip和端口,其他任何远端ip和端口的网络都可以连接进来,这种就是全锥形。
全锥型(Full Cone NAT):所有来自同一个内部Tuple X的请求均被NAT转换至同一个外部Tuple Y,而不管这些请求是不是属于同一个应用或者是多个应用的。除此之外,当X-Y的转换关系建立之后,任意外部主机均可随时将Y中的地址和端口作为目标地址和目标端口,向内部主机发送UDP报文,由于对外部请求的来源无任何限制,因此这种方式虽然足够简单,但却不那么安全。

如果只有接收过连接的远端ip才能反向连接到本地ip,那这种就是受限形,其实就是ip受限形。
受限锥型(Restricted Cone NAT):Full Cone的受限版本,亦可认为是IP地址受限锥型。所有来自同一个内部Tuple X的请求均被NAT转换至同一个外部Tuple Y,这与Full Cone相同,但不同的是,只有当内部主机曾经发送过报文给外部主机(假设其IP地址为Z)后,外部主机才能以Y中的信息作为目标地址和目标端口,向内部 主机发送UDP请求报文,这意味着,NAT设备只向内转发(目标地址/端口转换)那些来自于当前已知的外部主机的UDP报文,从而保障了外部请求来源的安全性。

如果再进行限制,只有接受过连接的远端ip和端口才能反连到本地ip,那这种就叫端口受限形。
端口受限锥型(Port Restricted Cone NAT):Restricted Cone NAT的进一步受限版。只有当内部主机曾经发送过报文给外部主机(假设其IP地址为Z且端口为P)之后,外部主机才能以Y中的信息作为目标地址和目标端 口,向内部主机发送UDP报文,同时,其请求报文的源端口必须为P,这一要求进一步强化了对外部报文请求来源的限制,从而较Restrictd Cone更具安全性。

一般来说,本地服务器发起一个连接到任何远端ip,它对应的nat的端口都是不变的,如果发到不同的远端ip,它对应的nat的端口也不一样(公网ip也就是出口ip肯定是不变的),那这种就是对称形了,不同的远端ip对应不同的nat端口,形成一个对应关系,这种nat类型是不太好进行穿透的。打个比方,当内部主机以相同的内部Tuple对2个不同的目标Tuple发送UDP报文时,此时NAT将会为内部主机分配两个不同的外部Tuple,并且建立起两个不同的内、外部 Tuple转换关系。与此同时,只有接收到了内部主机所发送的数据包的外部主机才能向内部主机返回UDP报文,这里对外部返回报文来源的限制是与Port Restricted Cone一致的。

知道了上面的原理,检测nat的类型也就变得方便了。

  1. 首先本地程序发起一个udp连接到stun服务器(这个服务器你自己开发一个就行),stun接受的udp请求后,可以根据from记录下该请求的ip和端口,这个就是nat服务器所看到的本地程序出口ip了,然后根据这个ip和端口回复一条信息,把获取到的ip和端口作为信息回复回去,如果本地能接收到,说明网络是没有问题的,如果接收不了,说明有防火墙,被block了。
    这里获取到了出口ip后,可以和本地ip做对比,如果一样,那说明压根就没有nat了,可以直接bind一下,能绑定成功就说明没有nat。
  2. 本地重新发起一个请求到另外的stun服务器,通过同样的方法获取到出口ip和端口,将该端口和第一次的出口端口对比,如果变了,说明是对称形。
  3. 本地程序再发起一个请求给stun, stun服务器接收到该请求后,换一台机器(ip不一样)给回复回去(重新发起一次请求),如果本地能收到回复,说明是全锥形的,换了一个ip也能接收到请求。如果接收不到请求了,对于非nat模式,那说明本地可能存在防火墙,nat模式下那还得继续如下的探测。
  4. 本地发起一个请求到stun服务器,让stun换一个端口进行回复,如果本地能接收到,那说明是ip受限形,否则就是端口受限形。
1
2
3
4
5
6
7
8
9
10
11
// 采用两个fd进行处理
if (!respI) stunSendTest(fd1, dest, content, 1); // 测试是否阻塞
if ((!respI2) && respI )
{
if ((I2dest.addr != 0) && (I2dest.port != 0)) // I2dest为测试是否为对称性,需要发到别的ip
{
stunSendTest(fd1, I2dest, content, 10);
}
}
if (!respII) stunSendTest(fd2, dest, content, 2); // 测试ip限制
if (!respIII) stunSendTest(fd2, dest, content, 3); // 测试端口限制

至此,nat类型检测完成,这是udp通信下测试的结果,对于全锥形,需要使用tcp进行反连接探测,如果连接不上,可能服务器的tcp nat做了限制。如果能连接上,说明可以正常穿透使用,可以将nat改为0,注意,本地要有保活的措施,定期对外发起tcp连接,以获取公网ip和端口,反连就是对公网ip和端口重新发起连接的。
tips:
设置SO_REUSEADDR可以在服务端主动断开连接后,断开TIME_WAIT期间重用该端口,解决服务端异常退出后立即重启的问题。但无法解决connect报Cannot assign requested address的问题。
设置SO_REUSEPORT是为了解决绑定了该端口作为服务端后,还能使用该端口作为客户端发起连接,用在tcp反连探测的场景。但是设置这个需要放在bind之后,否则意义就变成多个进程共用一个端口了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
addr.v4.sin_family = AF_INET;
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
long reuseaddr = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&reuseaddr, sizeof(reuseaddr)) == -1)
{
printf("SO_REUSEADDR error = %d", errno);
}
reuseaddr = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (char*)&reuseaddr, sizeof(reuseaddr)) == -1) // 需要重复使用端口,之前已经监听了
{
printf("SO_REUSEPORT error = %d", errno);
}
struct sockaddr_in bindAddr;
bindAddr.sin_family = AF_INET;
bindAddr.sin_addr.s_addr = INADDR_ANY;
bindAddr.sin_port = htons(internalPort); // 绑定的是内网端口,这个之前已经监听了

nat不为1,就一定不能被外网访问吗?
在上游只有一个路由器的情况下,nat类型=路由器的nat & wan口网关的nat,比如路由器nat为3,wan口为公网ip,最后测出来nat也为3。
参考:https://help.onethingcloud.com/9dd6/e9ec, 有的路由器可以设置nat类型,可以把它改为nat1,大部分路由器都有upnp的功能,开启upnp后,可以对端口做映射转发了,此时如果wan口是公网的话,无论路由器的nat是什么类型,upnp映射成功后服务就一定可以被外网访问了,如果路由器没有upnp功能,就开启DMZ,让路由器的某个lan口ip上的设备允许被外网访问。当然,如果wan口的nat不是1,也就是运营商有限制,那么哪怕路由器的nat变为1了也是没用的,路由器下的设备的服务还是没办法穿透给外网访问,所以能不能被外网访问的大前提就是,wan口为公网ip或者wan口的nat为1。

怎么知道wan口的nat类型?
如果直接探测的话,那是路由器的nat和wan口的nat集成起来的,要只探测wan口的nat的话,只能是让路由器的nat先变为1,这样探测出来的结果就是wan口的nat了,因为探测时用到了两个udp的端口,可以先把这两个端口做upnp映射,再进行映射就行了。

如果nat为1,不进行upnp映射可以被外网访问吗?
不可以,端口不能做映射,没办法被外网访问。UPnP映射与路由转发有着密切的联系,UPnP映射能够让NAT内网用户可以通过发起连接的方式来获取公网IP地址,只要这个地址具有转发功能,即可做UPnP映射。从NAT路由器上实现UPnP映射的基本思想就是向路由器放入UPnP映射规则,也就是要求路由器在收到内网用户发出的IP数据包时能够按照UPnP映射规则将数据转发到公网上。

p2p穿透的原理?nat是什么?
我们虚拟机有三种网络模式,一种桥接,代表和你电脑主机上在同一个网段,一般选这种,路由器开启dhcp就能用了,一个nat,代表重新开辟一个网段,类似于重新路由,一个主机模式,这种只能后主机通信,不能上外网。




由于ipv4的地址不足,内部网络采用的nat形式进行地址转换,一台连接在路由器下的电脑和外界服务器进行通信时,服务器看到的ip和端口是路由器网关的ip和端口,而不是电脑端的ip和端口,相当于在网关有一个独立的程序在与外界进行通信,这里所做的转换就是nat。
当知道这台电脑的外界ip和端口后,其他人就有可能来和你通信了,这里需要一台中间服务器,注意p2p并不是完全不需要第三方服务器,只是传输数据的时候不需要第三方服务器参与,是peer2peer端对端的,这些转换后的ip和端口需要第三方服务器来传达给其他人,其他人知道ip和端口就可以来连接你了。
当然,并不是知道ip和端口就一定能够连接得上了,路由器也有一定的策略来防范这些,这就是所说的nat类型,nat为1时,所有人都可以来连接,nat为2时,只有之前通信过的ip才可以来连接。
这里有一个比喻,假设nat网关就是女生宿舍的宿管阿姨,你要去803寝室给一个女生修电脑,如果nat为1,则你给宿管阿姨说要去803,她就让你去了,如果是nat为2,需要你是女生的家人(一系列的人)才能进去,如果nat为3,需要你是女生的男朋友(之前女生交代给阿姨过的)才能进去,如果nat为4,则好比这个女生有很多朋友,每个朋友都要有自己的口令才能进去。

受限锥型NAT打洞原理:
主机A和B同样需要各自连接服务器C,同时把A和B的地址告诉B和A,但一般情况下它们只能与服务器通信。要想直接通信需要发送消息给服务器C,如主机A发送一个UDP消息到主机B的公网地址上,与此同时,A又通过服务器C中转发送一个邀请信息给主机B,请求主机B也给主机A发送一个UDP消息到主机A的公网地址上。这时主机A向主机B的公网IP发送的信息导致NAT A打开一个处于主机A的和主机B之间的会话,与此同时,NAT B 也打开了一个处于主机B和主机A的会话。一旦这个新的UDP会话各自向对方打开了,主机A和主机B之间才可以直接通信。IP相同,port随便,就能和先前内部节点被映射的外部Tuple进行通信。
端口受限锥型NAT打洞原理:与受限制锥型类似,与之不同的是还要指定端口号。IP,port都得一样;只能用先前内部节点被映射的外部Tuple与内部节点进行通信.

对称型NAT穿透方式(4只能和1,2打洞):
1).同时开放TCP(SimultaneousTCP open)策略
如果 Client A-1和 Client B-1能够彼此正确的预知对方的NAT将会给下一个TCP连接分配的公网TCP端口,并且两个客户端能够同时地发起一个面向对方的“外出”的TCP连接请求,并在对方的 SYN 包到达之前,自己刚发送出去的SYN包都能顺利的穿过自己的NAT的话,一条端对端的TCP连接就能成功地建立了。
问题:时钟严格一致,很难做到。
2).UDP端口猜测策略
通常,对称NAT分配端口有两种策略,一种是按顺序增加,一种是随机分配。如果这里对称NAT使用顺序增加策略,那么,ClientB-1将两次被分配的Tuples发送给Server后,Server就可以通知ClientA-1在这个端口范围内猜测刚才ClientB-1发送给它的socket-1中被NAT映射后的Tuples,ClientA-1很有可能在孔有效期内成功猜测到端口号,从而和ClientB-1成功通信。
问题:不能为随机分配端口的对称型NAT打洞。

突然想明白为啥nat4只能和nat1/2通信了,对于nat4:内网主机每次访问不同的外网主机时,都会生成一个新的洞(我测试,访问同一个外网主机,外部端口也会变)。而前面3种类型(nat1/2/3)使用的是同一个洞。所以nat4哪怕洞打成功了,后续真正发起请求时外部端口可能就变了,如果对端是也是nat3/4,源端口变了,就请求不上了,而如果是nat1/2,源端口变了也没关系。
当两端路由器的NAT类型是:(端口限制锥型,对称型)或者(对称型,对称型)时,使用STUN协议就无法穿越,这个时候就需要使用TURN协议。TUAN协议简单来说就是进行数据转发,两个内网主机分别把内容发给TURN服务器,TURN服务器再将内容分别转发给对端,以此来实现通信。

nephen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!