NAT介绍以及穿透各种类型nat的技术实现包括对称型nat

穿透各种类型nat的技术实现包括对称型nat,对于对称型nat,我们采用猜端口方式确定彼此nat后的分配所得端口号,测试数据可达95%以上的成功率。

NAT介绍

NAT(Network Address Translation)即网络地址转换技术。是为了解决IP地址不够用而产生的路由器解决方案。路由器内部的局域网内的设备访问外部网络时,路由器将用自己从isp处分配而来的公网ip来替换这次访问请求包的源ip,并根据不同的nat类型算法来用一新的端口号来替换请求包的源端口号。这样在外面看来,这个请求包就是这个路由器设备发出来的,外部设备在TCP/IP消息头里并不会发现关于一点这台内网计算机的内网地址信息。当远程服务端返回消息包时,所返回的消息包也是把路由器的公网ip和端口作为目标地址的。当路由器收到这个回包,将会到自己的缓存中查找到之前给那台内网主机的替换ip和端口时的记录也可称这种记录为内外地址关系映射表。

根据映射表的不同,我们可以将nat算法大体上分为两大类,锥型和对称型nat。

锥型NAT

锥型NAT根据安全策略权限的不同又可以分为3类分别是全锥型(Full Cone),IP限制锥型(IP Restricted Cone),端口受限锥型(Port Restricted Cone)。

其中安全策略限制也是由弱到强的。

全锥型nat当路由器收到外网发过来的数据包时,其根据路由器内部缓存的映射表查找目的ip与port,找到了就把包向内网转发,找不到就丢弃。

ip限制锥型,某外网主机ip向此类型的路由器发送数据包时,路由器在只有先由内网某个计算机已经先向该外网主机ip发送过数据包的前题条件下,路由器才会把此外网主机ip发送过来的数据包向内网转发,否则直接丢弃。且一般来说,先向外网ip发送数据包的操作不能太旧了,一旦超时了路由器缓存就被自动删除了。即会导致路由器在缓存里找不到内网对外网ip的发送记录。我们测试数据来看,这个超时间一般在2分钟左右,不同厂商实现的路由器这个时间设定可能不同。

端口限制锥型,外网主机发过来的数据包的源地址ip:port,如果此数据包要想成功通过路由器进入内网。那么必须是在之前先有内网主机向此外网地址ip:port发送过数据包才可以。而ip限制型的前题条件是向此外网ip发送过数据包,也就是说如果是ip限制型外网在收到请求包后,可以通过另一个端口来回应数据包。对应到socket编程里就是可以通过另一个socket来回送数据,他们所使用的源端口可以是不一样的。然而对于端口限制型,则一定是收到消息的这个socket本身进行回复。

对称型NAT

对称型(Symmetric),对称型nat在安全策略上即在对收到外网发送过来的数据所做的操作与端口限制型是一样的。它与端口限制锥型主要的区别就是当内网对外网发送包时,在对内网地址进行转换时对不同目的外网地址ip:port,分配不同的端口。如下图所示:

还有些NAT不属于这四种中的任何一种,就不在本文的讨论范围了。

NAT类型判断

路由器nat类型判断可以用排除法按下面几个步骤来实现。 首先我们应该明确IETF提供的标准一共有4种常见NAT。分别是:

  • 全锥型(full cone nat)

  • 地址限制锥型(ip restrict cone nat)

  • 端口限制锥型(port restrict cone nat)

  • 对称型(symmetric nat) 对于市场上面一些厂商自已实现其它的nat我们不做过多的探讨,以实际测试经验来看,这类nat的路由器非不的少,但也不是不存在,我们过往测试中也发现过。比如中国移动在给宽带用户安装宽带时使用了一种集成了光猫和路由功能的盒子。他的安全性像全锥型,即外来连接消息包都允许进来,但对外端口映射时又表现为对称型。

NAT类型判断算法整体流程大至是,先判断防火墙是否阻止所有udp包进来,再判断是否是公网ip,再判断是否为全锥型,然后判断是否是对称型,最后判断是否是端口或地址限制型。

下面描述详细的判断过程,过程中需要与3台具有不同IP的服务器交互辅助。我们把这3台服务器称之为“协调服务器”,分别称为s1,s2,s3。

  1. 判断路由器防火墙是否防止所有UDP包进入 客户端向s1:30017发送一UDP包,s1收到后直接返回一udp数据包,如果客记端收不到s1的回包,那么判定为阻止所有udp,判断结束,否则进入一下判断。为了降低udp在网络中丢失,此过程可以重复多次。

  2. 判断主机是否直连公网 客户端收到s1回复,s1在处理时取出客户端的ip放入回复包里,客户商解析出数据区的ip与本机本地取出的所有ip对比,看是否存在匹配。存在匹配即认定当前主机是直接连接在公网上的,中间没有经过任何网关路由器,判断结束,否则进入一下判断。

  3. 判断当前网络的网关路由器是否为全锥型 客户端向s1:30017端口发送消息请求s1通知s3从不同于30017端口回一个udp消息包,如果客户端能收到s3的回复。那判定为全锥型nat,否则进入下一步判断。

  4. 判断是否为对称型nat 客户端分别向s1:30018,s2:30018发送udp消息,s1,s2收到包后,取出客户端的ip:port放入数据包发送回给客户端。客户端这样就取回自己的两份公网ip:port数据,客户端对比这两个地址,如果这两ip:port不一样那么就是客户端网关就是对称型。否则就是锥型,要么是地址限制型,要么是端口限制型。就需要继续向下判断了。

  5. 判断是地址限制型还是端口限制型nat 客户端向s1:30018发消息,请求s1从另一个端口回复一个消息比如端口6000。如果客户端收到了s1所回复的消息,那么判定为地址限制型而非端口限制型。否则收不到回复就判定为端口限制型。因为s1从非30018端口回复消息补路由器阻止了。

p2pcpp 程序的安装与使用

p2pcpp版本分为国内版本和海外版本。两个版本主要不同在于所使用的协调服务器部署地不一样,搭建海外版本是因为海外社区用户经常连接不上国内服务器(可能是因为国家防火墙的原因)。

国内版本客户端运行说明: p2pcpp 运行说明:(macos版本) 1.在系统设置->安全与隐私->防火墙,暂时关闭防火墙 2.复制下面命令行粘贴到终端里自动执行 curl http://40.73.35.128:7656/download/p2pcpp/shell/download_and_run.sh | bash

p2pcpp 运行说明:(win10版本) 1.下载windows10最新版本的二进制包: http://40.73.35.128:7656/download/p2pcpp/binary/p2pcpp_v1.2.6.win10.tar.gz 2.右击p2pcpp/bin/allow_firewall.bat脚本,选择"以管理员身份运行"来设置防火墙添加网络访问(每次下载到新目录解压后只需执行一次); 3.双击 p2pcpp/bin/tester_client.bat脚本来获取公网nat信息。

海外版本客户端运行说明: p2pcpp 运行说明:(macos版本) 1.在系统设置->安全与隐私->防火墙,暂时关闭防火墙 2.复制下面命令行粘贴到终端里自动执行 curl http://40.73.35.128:7656/download/p2pcpp/shell/download_and_run_oversea.sh | bash

p2pcpp 运行说明:(win10版本) 1.下载windows10最新版本的二进制包: http://40.73.35.128:7656/download/p2pcpp/binary/p2pcpp_v1.2.6.win10.oversea.tar.gz 2.右击p2pcpp_v1.2.6.win10.oversea/bin/allow_firewall.bat脚本,选择"以管理员身份运行"来设置防火墙添加网络访问(每次下载到新目录解压后只需执行一次); 3.双击p2pcpp_v1.2.6.win10.oversea/bin/tester_client.bat脚本来获取公网nat信息。

p2pcpp程序结构

p2pcpp程序模块结构如下图所示:

p2pcpp底层使用boost asio进行网络通讯。主要流程都是使用异步读写接口进行数据收发。

net网络封装接口

uat::net类的声明

namespace unat
{
    class net
    {
        public:
            using callback = unat::callback_net;
            using id_t = unat::callback_net::id_t;

        public:
            net();
            ~net();

        public:
            //handler_t is: std::function<std::string (unat::event & evt)>;
            callback::id_t add(unsigned short port,
                    const callback::handler_t & handler,
                    const std::string & comment);

            void erase(callback::id_t id);
            bool exist(callback::id_t id) const;
            size_t size() const;

            callback::sp_socket_t get_sp_socket(callback::id_t id) const;
    };
}

net类以id,boost socket映射表为数据结构管理核心。提供add对本机端口注册监听以及回调函数。erase函数根据id来删除监听以及内部所创建的socket。exist判断某个id是否存在。 size返回内部 所存储的socket大小。

使用示例:

auto id = _sp_net->add(port,get_handler_by_callback(cb),"add multiple port");
auto ret_socket = _sp_net->get_sp_socket(id);

所有的服务端业务层功能逻辑实现都在service_nat目录中。

端口预测

这一部分是nat穿透的核心算法。对于两个要通信的计算机节点都在对称型nat的路由器后面,那这两个节点在路由器端的对应公共端口是存在一定随机性的,这就需要两个计算机相互猜测对方所被分配到的公网端口。为了提高猜中的概率,作为客户端的一方连续向对端发送大时udp包,且这些数据包从本地不同的端口发出。它们的目的端口,就是本节点猜对端的公网地址。

p2pcpp端口预测时序图

端口预测逻辑中都使用UDP包进行数据交互,所以为了应对UDP不可避免的网络丢包问题。程序中在本地维护了一套主机状态。在不同的运行阶段,将会在不同的状态这间切换。

enum status_t
{
    _min_default = 1000,
    online,
    ready_to_predict,
    predict,
    as_server_listen,
    as_client_send,
    failed_end,
    succeed_end,
    end,     //1009

    offline,
    idle,
    waiting,  //1010
    client_waiting,
    client_busy,
    server_idle,
    server_busy,
    server_return_hello_world,
    _max_default
};

p2pcpp进程通过定时器每2秒与协调服务器端进行一次数据交互,把自身主机状态通过UDP数据报文上报给协调服务器端。协调服务器根据主机上报过来的状态,进行配对算法。决定将哪两台主机配对进行端口互猜。并将结果下发给主机,主机 进行相关响应。

p2pcpp状态转换图

------------------------------------------------

(0)

相关推荐