内网穿透原理及实现一 CS、P2P模式原理

1.前言

最近一阵子在研究内网穿透,查了不少资料,所以今天就聊聊两种不同的穿透方式的原理,以及基于java的netty框架的实现,代码也已在我的github。

起因:突然花这么大力气研究这个虽然是头脑发热所为,但动机源于跟小伙伴联机打游戏,原来用nat123之类的做远程端口转发在最近一阵子巨慢无比,又没找好的替代方案,前一阵子公司同事又分享过nio,网络编程的知识又被过了遍,一拍脑子自己造轮子得了,正好自己又有个阿里云可以当中间节点,找了些参考文章就开搞了,陆陆续续摸索了三个周末,基本可以用了,这两篇文章就当总结了。

2.问题由来

在一般的网络应用中,简单拓扑模型如下

server有公网ip,server应用直接监听公网ip及端口,client访问可直接通过ip:port 直接建立链接,进行请求响应。

但如果服务端通过路由链接Internet,没有直接暴露公网ip就不同了,如以下拓扑

server在内网,没有公网ip,路由有公网ip,通过路由链接Internet,client请求抵达server路由后,除非在路由定义端口转发规则,否则数据将被抛弃,但很多时候路由的转发规则是无法被改的。

这样的模型可以细分为四种情况

  1. client有公网ip,server在内网;
  2. client在内网,server在公网;
  3. client,server均在公网;
  4. client,server均在内网。

对于server在公网的情况,client可以主动链接;但server在内网时,我们便需要内网穿透了。接下文章将以最复杂的第4种情况进行分析。

3.内网穿透原理

3.1 基于TCP的C/S模式

对于client和server均在内网的情况,直连是不行的,但如果我们有另外一台具有公网ip的服务器充当中间节点,便可以进行间接访问了,拓扑如下

中间服务器我们暂称为forwarder
我们详细描述下请求响应过程

  1. client由本机777端口通过路由向forward 11.11.11.11:666发起请求,路由分配公网ip及端口12.12.12.12:45464给client,这里的端口是随机分配的,也就内网机器可以访问Internet应用的原因。
  2. client连上forwarder后,对应forwarder来说,它获取到的客户端是12.12.12.12:45464,所有client的777端口的请求都会由路由的45464端口发送给forwarder,同时Forwarder对client的响应其实是发往路由的45464端口,再由路由转发给client的777端口;
  3. server的80端口经它的路由13.13.13.13:12454也同样与forwarder建立连接;
  4. 这时forwarder只需要将client发送请求数据转发给server的路由12454端口,然后server的响应也经server的路由,发给forwarder,forwarder再转发给client的路由,最后发送至client的777端口,这样就完成了一次穿透两个内网的请求与响应;
  5. 重复过程4,内网穿透也就成功了。

我们可以看到内网机器能访问Internet的原因在于它的路由为其分配了一个随机的公网端口。这里顺带解释概念 端口映射,此时的公网端口12.12.12.12:45464逻辑上可以认为就是内网机器777端口的映射。再抽象一点,对于client来说,forwarder的666端口其实也可以认为是server 80端口的映射,这个便是远程端口映射,因此,远程端口映射跟内网穿透,描述虽不同本质上是一致的。

这种模式非常简单直接,只要client和server能连上Internet,就能穿透彼此的内网相互访问。
诸如nat123的端口转发机制,穿透内网的远控软件TeamViewer,我们玩一些对战游戏的平台,甚至各种网络游戏都可以看成这种模型的实现。

它的优点在于forwarder服务器存在,可以协助穿透任何形式的内网,简单稳定,但同样由于存在中间服务器,网络上不仅受客户端网络限制,同样也受forwarder的网络限制,如果forwarder带宽不行,或者传输几g的大文件,效率就慢了。为了解决这个问题,基于P2P的机制也就提出来了。

3.2基于UDP的P2P模式

先回头看下C/S模式的网络拓扑

C跟S最终还是由各自路由随机分配的公网端口进行Internet访问,这样的话,如果它们能彼此知道对方的公网ip和端口,比如经forwarder将ip端口发给对方,是不是就可以直接TCP实现P2P访问呢,答案是比较困难的。

原因在于TCP是一种先连接后传输的通信协议,分配给client的45464只能与forwarder的666端口传输数据,这是在连接建立时确定的,如果client再想链接server的路由,此时的端口将是重新分配的。并且因为安全等问题考虑,端口是随机分配,无法固定。也有一些资料说可以通过某些算法猜测到公网端口从而实现基于TCP的P2P传输,但实现起来比较困难。我们这里就通过UDP来实现,因为它跟TCP不同,是在发送数据时指定目标ip端口。

下面来看看基于UDP的P2P模式的网络拓扑

以下是请求响应过程

  1. client开始向forwarder发送请求,由于使用udp,我们不需要建立连接,路由分配ip,端口后,数据包直接发往forwarder;
  2. forwarder由此可以得到client的公网ip及端口12.12.12.12:45464;
  3. 同样的forwarder得到server的公网ip及端口13.13.13.13:12454;
  4. forwarder将包含server的ip:端口的数据包发给client,将client的发给server;
  5. 由于是UDP,此时client可以直接将包含请求的数据包改为得到的server地址,直接发往server路由,server同样可以直接发给client了。
  6. 双方接受到对方数据后,可以认为一个虚拟的P2P连接就已经建立了, 至此内网穿透便实现了。

P2P方案优点也就明显的,数据传输不依赖于中间服务器,在连接建立后就不再受其限制,但同样由于UDP的特性,数据可靠性难以保证,所以得容忍误差,或者实现一些校验机制,而且对于Symmetric NAT,P2P是无法建立的,还是只能走C/S模式的穿透。

它的应用最广泛的就是各种BT客户端了,毕竟大数据的传输不需要经过中间服务器,效率会高很多,然后一些P2P的聊天,视频,游戏对战软件也可以用到。

至此,两种实现模式的原理已经说明了,下一篇将讲讲基于java netty框架造的两个轮子。

-源码 https://github.com/chulung/forwarder

参考文档

内网穿透并不是一个新话题,已经有很多成熟的协议和框架实现了,比如这个,但学习,还是自己造轮子得好。

NAT穿透解决方案介绍

Comments
Write a Comment