详细解读NAT和内网穿透

详细解读NAT和内网穿透

0.前言

多是由于以前折腾过搭网站之类的事情的缘由,我我的对计算机网络比较感兴趣。半年以前试过花生壳的内网穿透服务以后写过一篇文章谈内网穿透的原理,可是最近发给同窗看时候他说打不开,才知道被博客园和谐了,趁机回顾再看才发现以前写的内容也有问题(那时候尚未学计算机网络),因此今天又参考了许多资料,力图正确、准确地解读一下NAT和内网穿透,这两个随着ipv6的普及极可能会被淘汰、可是我我的以为短期不可能彻底淘汰的技术。html

1.NAT-从实际案例讲起

用我本身的状况作个例子吧,个人网络状况是:java

  1. 一栋楼有一个电信的公网ip,绑定到一台提供NAT服务的路由器上
  2. 每层楼有一个交换机,与1中所说的路由器链接
  3. 交换机使用不一样的端口链接每一个房间的路由器
  4. 房间的路由器又组建了设备间的局域网

我来详细描述一下个人设备和一台公网ip下的服务器通讯的过程,也算是复习计算机网络了吧,这里假设个人电脑已经知道目标设备的ip地址了(也就是不算DNS部分了):api

  1. 设备发送数据包,源ip是房间路由器建立的内网下的ip,端口号是socket随机选择的,其余的目标ip、源mac都是肯定的,目标mac填的是本身路由器的mac地址服务器

    设备会用本机配置的24位子网掩码与目标地址进行“与”运算,得出目标地址与本机不是同一网段,所以发送目标的数据包须要通过路由器的转发。 -[4]网络

  2. 路由器先把源ip地址改为本身在楼中使用的那个内网ip,为设备分配一个惟一的端口号,并在路由表中记录下这一映射关系。根据路由表中的记录(肯定要访问的地址不是楼里的地址,是公网的地址)获取下一跳的ip地址, 并根据ARP协议肯定下一跳(也就是每层的交换机)的MAC地址 ,将目标mac地址修改成下一跳的mac地址,而后在数据链路层和网络层中进行传输session

  3. 传到每一层的交换机时,不须要修改源ip地址(由于是交换机,没有修改Ip的功能),只把目标mac改成NAT路由器的地址,而后再经过数据链路层和网络层传输socket

  4. 到了NAT服务器,服务器随机为其分配一个端口[3],并在Track Table中保存这个[内网ip:端口号->目标ip:端口号]的映射,这一过程称为链接跟踪。注意此时的内网ip指的是路由器的ip,端口号是路由器为设备分配的端口号tcp

  5. 服务器收到数据包,再向NAT路由器返回数据包,NAT路由器经过查询Track table肯定内网ip(是路由器的内网ip)和端口号,并改变目标Ip为此,返回发送给每层楼的交换机,交换机经过arp协议借助路由器的ip肯定路由器的mac地址,并将目标mac地址改成此,再发送给路由器网站

  6. 路由器经过端口号肯定是哪一台内部设备,转发给该设备.net

这里对于NAT,关键的一步是4,NAT只会接受在Track table中有ip和端口记录的外部访问,其余的都一律不转发,这也就是咱们常说的NAT只能内网访问外网,不能外网访问内网

2.内网穿透

可是不得不说,P2P的需求是真实存在的,为了解决NAT带来的问题,内网穿透诞生了。

这篇文章里比较好地提到了内网穿透的原理,现摘录出来:

假设如今有内网客户端A和内网客户端B,有公网服务端S。
若是A和B想要进行UDP通讯,则必须穿透双方的NAT路由。假设为NAT-A和NAT-B。

A发送数据包到公网S,B发送数据包到公网S,则S分别获得了A和B的公网IP,
S也和A B 分别创建了会话,由S发到NAT-A的数据包会被NAT-A直接转发给A,
由S发到NAT-B的数据包会被NAT-B直接转发给B,除了S发出的数据包以外的则会被丢弃。
因此:如今A B 都能分别和S进行全双工通信了,可是A B之间还不能直接通信。

解决办法是:A向B的公网IP发送一个数据包,则NAT-A能接收来自NAT-B的数据包
并转发给A了(即B如今能访问A了);再由S命令B向A的公网IP发送一个数据包,则
NAT-B能接收来自NAT-A的数据包并转发给B了(即A如今能访问B了)。

以上就是“打洞”的原理。

为了保证A的路由器有与B的session,A要定时与B作心跳包,一样,B也要定时与A作心跳,这样,双方的通讯通道都是通的,就能够进行任意的通讯了。

图解以下:

img

上面说的就是UDP打洞的原理,可是为何是UDP呢?

UDP的socket容许多个socket绑定到同一个本地端口,而TCP的socket则不容许。
这是这样一个意思:A B要链接到S,确定首先A B双方都会在本地建立一个socket,
去链接S上的socket。建立一个socket必然会绑定一个本地端口(就算应用程序里面没写
端口,实际上也是绑定了的,至少java确实如此),假设为8888,这样A和B才分别创建了到
S的通讯信道。接下来就须要打洞了,打洞则须要A和B分别发送数据包到对方的公网IP。可是
问题就在这里:由于NAT设备是根据端口号来肯定session,若是是UDP的socket,A B能够
分别再建立socket,而后将socket绑定到8888,这样打洞就成功了。可是若是是TCP的
socket,则不能再建立socket并绑定到8888了,这样打洞就没法成功。

道理的确是这么个道理,可是博主说的还不够清楚,我再解读下,就用上面原博主给的例子了:

因为NAT的外部端口是随机指定的,若是A和B分别和服务器通讯,都使用8888端口的话,若是A要和B直接打洞且不用8888端口,就会遇到一个问题:A不知道B未来包发出来NAT-B会给它分配什么接口,因此A就没办法指定目标Ip的端口号(由于NAT-B是随机分配端口的,B即便知道了A用了哪一个端口打洞也没办法让NAT-B去使用这个特定的端口。综上,A和B能使用的,只有和服务器链接时已经建立在track table中的那个端口,也就是咱们例子中的8888了。

注意track table中的记录是有有效期的,因为咱们不知道外部设备何时会访问咱们在内网中的设备,因此咱们须要保证设备和服务器之间的链接不能断开,track table中的映射不能被销毁,因此须要在必定的时间间隔以后发包来维持NAT中的映射关系,这就是为何咱们用花生壳的时候它一直要求咱们“保持在线”的缘由了

可是TCP也不是不能进行穿透,这就须要用到端口重用了:

tcp打洞也须要NAT设备支持才行。
tcp的打洞流程和udp的基本同样,但tcp的api决定了tcp打洞的实现过程和udp不同。
tcp按cs方式工做,一个端口只能用来connect或listen,因此须要使用端口重用,才能利用本地nat的端口映射关系。(设置SO_REUSEADDR,在支持SO_REUSEPORT的系统上,要设置这两个参数。)

链接过程:(以udp打洞的第2种状况为例(典型状况))
nat后的两个peer,A和B,A和B都bind本身listen的端口,向对方发起链接(connect),即便用相同的端口同时链接和等待链接。由于A和B发出链接的顺序有时间差,假设A的syn包到达B的nat时,B的syn包尚未发出,那么B的nat映射尚未创建,会致使A的链接请求失败(链接失败或没法链接,若是nat返回RST或者icmp差错,api上可能表现为被RST;有些nat不返回信息直接丢弃syn包(反而更好)),(应用程序发现失败时,不能关闭socket,closesocket()可能会致使NAT删除端口映射;隔一段时间(1-2s)后未链接还要继续尝试);但后发B的syn包在到达A的nat时,因为A的nat已经创建的映射关系,B的syn包会经过A的nat,被nat转给A的listen端口,从而进去三次握手,完成tcp链接。

另外,NAT还有许多其余功能,更详细的介绍能够看这篇文章

3.Reference

  1. https://www.zhihu.com/question/37712168
  2. https://www.zhihu.com/question/55015810
  3. http://www.javashuo.com/article/p-sftxytpx-r.html
  4. http://dengqi.blog.51cto.com/5685776/1223132
相关文章
相关标签/搜索