我们知道使用WebRTC进行端对端进行通话时,最便捷的方式就是通话的双方通过ip直连,摆脱原始的直播服务器中转的方式。
但是在现实的应用场景中,我们的客户端都是处于各大局域网内部的,比如局域网A中的192.168.2.231和局域网B中的192.168.2.162之间是不能直接连接通讯的,那么我们能不能通过某种方式让这两个处于不同局域网内的ip能够直连互相进行通讯呢?这是这NAT穿墙打洞就出场了。
NAT是什么
NAT(Network Address Translation),网络地址转换协议。NAT是1994年提出的,当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址),但现在又想和因特网上的主机通信时,就可以使用NAT技术。
比如说内网ip地址192.168.1.155与外网ip地址150.158.106.96是无法直接通信,如果想要让这两个ip能够直接通信,就需要用到NAT技术将ip地址192.168.1.155先转换成公网ip地址和端口之后就可以进行通信了。
NAT的出现主要是因为IP4地址耗尽,理论上当全球IP6全面普及使用之后,NAT是可以退出历史舞台的。可见NAT的本质就是让一群机器公用同一个IP,这样就暂时解决了IP短缺的问题。
NAT的出现还有一个出于网络安全的原因,外网的主机要想攻击内网的主机,必须要经过防火墙,经过NAT转换才能找到内网的主机。
NAT是如何工作的
NAT中介图
我们前面了解到NAT的作用是把内网的私有地址,转化成外网的公有地址。使得内部网络上的(被设置为私有IP地址的)主机可以与外部的Internet进行网络通信。
那么NAT是如何工作的呢?
NAT设备有个NAT转换表,这个表记录了NAT设备收到的回包,接下来应该转发给哪台内部主机。
当内网通过NAT设备访问外网时会随机产生一个不同于其他内网机器的端口号,当NAT收到回包的时候,回包同样也会携带这个端口号,然后NAT设备就可以通过这个端口号映射出内网ip来判断这个回包应该交给哪个内部主机处理。
比如说172.16.10.6这台内部主机,在访问服务器100.100.100.69的时候,会随机产生一个不同于其他内网机器的端口号,比如说产生的的端口号是47690。
那么当NAT收到端口号为47690的回包的时候就会交给172.16.10.6这台内部主机进行处理。利用这种端口映射的方式就可以实现一个公网ip可以同时给数千台私有终端提供网络服务。
NAT的四种模式
为什么需要讲解NAT的四种模式呢?
因为在后面的讲解NAT打洞穿透的时候需要用到这些知识点。
NAT有4种不同的类型:
Full Cone(完全锥形)
这种模式NAT内部的机器A连接过外网机器C后,NAT会打开一个端口。然后外网的任何发到这个打开的端口的UDP数据报都可以到达A。不管是不是C发过来的。
例如:
A:192.168.8.100
NAT:202.100.100.100
C:292.88.88.88
A(192.168.8.100:5000) -> NAT(202.100.100.100:8000) -> C(292.88.88.88:2000),
任何发送到 NAT(202.100.100.100:8000)的数据都可以到达A(192.168.8.100:5000)
Address Restricted Cone(地址受限制锥形)
这种NAT内部的机器A连接过外网的机器C后,NAT打开一个端口。然后C可以用任何端口和A通信,其他的外网机器不行。
例如:
A:192.168.8.100
NAT:202.100.100.100
C:292.88.88.88
A(192.168.8.100:5000) -> NAT(202.100.100.100 : 8000) -> C(292.88.88.88:2000),
任何从C发送到 NAT(202.100.100.100:8000)的数据都可以到达A(192.168.8.100:5000)
Port Restricted Cone(端口受限制锥形)
这种NAT内部的机器A连接过外网的机器C后,NAT打开一个端口。然后C可以用原来的端口和A通信,其他的外网机器不行。
例如:
A:192.168.8.100
NAT:202.100.100.100
C:292.88.88.88
A(192.168.8.100:5000) -> NAT(202.100.100.100 : 8000) -> C(292.88.88.88:2000)
C(202.88.88.88:2000)发送到 NAT(202.100.100.100:8000)的数据都可以到达A(192.168.8.100:5000)
Symmetric(对称式)
对称式NAT与端口限制性克隆类似,唯一不同的是当同一内部主机使用相同的端口与不同IP地址或端口的外部主机进行通信时,
NAT对该内部主机的端口映射会有所不同,这种情况下NAT会针对不同IP地址或端口的外部主机为内部主机的相同端口分配新的外部端口号。
对称式NAT不保证所有会话中的私有地址端口和公开地址端口之间绑定的一致性。相反,它为每个新的会话分配一个新的端口号。
这种情况下内部主机的“内网IP地址和端口”与“NAT IP地址和端口”之间会形成一对多关系。
NAT类型检测
通过使用STUN服务器检测设备所处网络的NAT类型,STUN服务器需要两个公网IP。
我们假定client是要检测的设备,server是STUN服务器,并假设server的两公网SOCKET的IP地址和端口号分别是IP1:port和IP2:port。
步骤1:
client向server IP1:port发送请求,server使用IP1:port将收到请求的源IP和port(即设备的公网IP地址和端口号)回复给client,如果得到的公网IP和设备自身的IP一样,则判定设备自身处在公网,无NAT,检测结束,否则进行下一步。
步骤2:
client向server IP1:port发送请求,server使用IP2:port来回复,如果设备能收到回复,则判定client的NAT为完全锥形(Full Cone NAT),检测结束,否则进行下一步。
步骤3:
client向server IP2:port发送请求,server使用IP2:port将收到的源IP和port回复给client,client判断此port和步骤1得到的port是否一样,如果不一样,则为对称型(Symmetric NAT),检测结束;
如果一样,则为限制型(Restricted Cone NAT),这时候需要进一步检查是地址限制型NAT还是端口限制型NAT。
步骤4:
client向server IP2:port发送请求,要求server使用该IP的另一个端口来回复,server使用IP2:port2回复请求,如果client能收到回复,则判断为地址限制型(Address Restricted Cone) NAT,否则为端口限制型(Port Restricted Cone) NAT
如何实现NAT打洞
在了解如何打洞之前我们来说一说为什么需要打洞?
NAT设备允许私网内主机主动向公网内主机发送数据,但却禁止反方向的主动传递,但在一些特殊的场合需要不同私网内的主机进行互联(例如P2P软件、WebRTC等),打洞穿越NAT的问题必须解决。
在这里需要强调的一点打洞并不是P2P所特有的,当我们打开电脑,连上网络,打开几个不同网站的时候,打洞和穿越时时刻刻都在发生着。
比如你打开腾讯的时候,你就在自己的NAT上打了一个洞,这个洞只允许腾讯特定的端口的数据穿越回来,在这里如果没有你先打的洞,腾讯官网是不可能成功给你发消息的。
既然说打洞无时无刻不在,那么为什么我们在平时的开发中很少提到打洞,而到了P2P或者WebRTC时却要一而再再而三地提起打洞呢?
这是因为在传统的cs模型的网络编程中,服务器都架设在公网,服务器端不用打洞(服务端不用为客户端打洞,客户端的数据也能过来),客户端就能向服务器发送请求。
其次,client向服务器发送请求时,client的打洞和服务器响应的穿越是自动实现的。还有一点就是在传统的cs模型中,打洞是单方向的,即只需客户端打洞。而在P2P或者WebRTC等一些应用中打洞是双向的。
当两个在不同内网机器A和机器B想要进行直接通信时,就需要A在自己的NAT上为B打一个洞,让B的数据能过来;B在自己的NAT上为A打一个洞,让A的数据能过来,而AB双方的打洞的操作得由我们自己完成,
所以不得不提及穿越打洞的概念。
假设现在有两个处于不同内网的设备A和B需要进行点对点通信,那么它们的打洞流程大概是怎么样的呢?
首先我们知道A和B目前并不能直接进行链接,因为它们互不知道对方的ip地址以及端口。如果需要在A和B之间进行打洞就需要借助一个公网的中介服务器S。
我们通过中介服务器S拿到A和B背后的NAT-A和NAT-B所在公网地址和端口即可完成打洞。
大致的打洞流程是:
1、 建立起设备A和设备B与中介服务器S的长连接关系;
2、 A向S发起请求,申请与B建立直连关系;
3、 S收到A的申请后,保存好A所在的公网NAT-A的ip和端口,并发送给B,要求B向A发送一条信息;
4、 B收到S的指令后向NAT-A发送一条信息,绝大数情况下这条信息是没有回复的,因为NAT-A中并没有关于B的映射规则。既然知道没有回复那么为什么还要发送这么一条无谓的信息呢?
其实这就是打洞,B向A发送消息并不是为了让A回复什么,而是为了让NAT-B给NAT-A打开一个端口,让后面NAT-A的消息能够进来给B接收得到。至此虽然B还是连接不上A,设备B的穿透洞是打好了,A是可以通过NAT-B连接B设备了。
5、 B在向NAT-A发送消息完成后,告诉中介服务器S:"我已经准备好了,来吧。。。",S收到B准备好的消息之后就通知A,让A发送消息给B。
6、 A收到S的指令后向NAT-B发送消息,因为NAT-B在第4步中已经给A打开了端口,所以是可以连接上的。同时在A给B发送消息的同时,NAT-A也打开了一个端口,用于接收B的回应。至此A和B的打洞就完成了,A与B直接就可以利用NAT-A和NAT-B进行点对点通信了。
注意:路由器和防火墙的UDP打洞的端口有个时间限制的,在一定时间内如果没有数据通讯会自动关闭。
笔者整理了一个两个终端的NAT类型能否打洞成功的对照表:
通过这个对照表我们可以看到当两端都是对称型或者一端是对称型,另外一段是端口限制型的时候NAT打洞是无法成功的,那么这种情况下,如何进行端对端的通话呢?
这个问题我们后面再探讨。
好了关于NAT的学习先到此结束了,关注我,一起进步,人生不止coding!!!
领取专属 10元无门槛券
私享最新 技术干货