内网穿透(3/4) - 基于UDP的打洞穿透方法
打洞
前面提到了有利用转发编写的“穿透”工具,但是实际上我们都是搭建了一个反向代理,把我们的请求转发了出去。
那么比如说我在办公室远程编译了一套东西,打包以后占地面积10GB,如果我通过代理转发这个压缩包,就会在主机上产生10G流量。按照带宽计费的话,带宽低了速度太慢,带宽高了价格又贵,流量计费的话一个月流量的总费用也贵,都不如我去办公室跑一趟复制回来。话说回来了,既然要跑一趟,干嘛还远程。
针对这种情况,我们需要想办法尽可能让两个在不同内网里的主机可以直接通信。这种方法就叫做打洞。
原理
说打洞原理就不得不说到NAT
IPv4、内网和NAT
IPv4是目前来说用的最广的IP协议和地址格式,由32bit组成,通过八位一个数字来表示,比如说1.2.3.4。分成了不同的地址段,刨去内网地址段、环回地址段等等,公网IP数量相对来说很少,前两天最后一个未分配的地址段也分配出去了。
那既然所有地址段都分配出去了,我们是如何让新的设备加入IPv4网络呢?
通过NAT。我们通过俄罗斯套娃NAT,把一批机器套在一个本地的子网里面,通过地址和端口转换,把多个设备套在一个IP上面。而且这种转换可以一层套一层,就可以在一个公网IP下套非常多的设备。
除了可以容纳更多设备以外,nat还有一些其他好处,比如说增加内网机器的安全性,还有共享带宽的同时可以QoS。
nat类型
nat的类型大致来说分为两类:
- 网络地址转换NAT
- 网络地址端口转换
- 完全锥形NAT
- 受限锥形NAT(端口or地址受限)
- 对称NAT
网络地址转换
就是简单的翻译一下两个IP地址,不改变端口,但是由于IP变了,checksum什么的也需要改变。
通常外层IP是一个IP池,内网机器和外层网络只有IP的对应关系。
网络地址端口转换
这类地址转换会同时转换地址和端口。
比如说我在家,网络拓扑比较简单,就是几层子网套起来:
电信:
光猫(....<->192.168.1.1/24):
CentOS路由器(192.168.1.2/24<->192.168.2.1/24):
我的设备1(192.168.2.x/24)
我的设备2(2.x/24)
虚拟机子网网关(2.30/24<->172.100.0.0/16)
虚拟机1(172.100.0.2/24)
....
k8s/docker(2.31/24 <->172.200.0.0/16)
......
如果我要从192.168.2.100向114.114.114.114:53/udp发送DNS请求。那么路径会是这样的:
- 我的机器从
192.168.2.100:<随机a>
发送数据包到1.1.1.1:53
- 路由器收到这个包发现确实不是内网里的机器
- 建立临时的映射关系
192.168.1.2:<随机b>↔192.168.2.100:<随机a>
- 修改数据包源地址为自己的出口IP
192.168.1.2:<随机b>
,修改对应的checksum等信息 - 按照路由发往出口IP
- 建立临时的映射关系
- 光猫也发现不是自己内网的
- 建立映射
<光猫>:<随机b>↔192.168.1.2:<随机a>
- 修改源地址并且发送出去
- 建立映射
- 经历了不知道多少层,假设我们直接到了公网网关,那么最后由公网网关再修改源地址为
<公网>:<随机z><-><光猫>:<随机c>
- 114收到并回复给了源地址
<公网>:<随机z>
- 公网网关按照映射,修改回复数据包的目的地址为
<光猫>:<随机c>
- 光猫按照映射关系,把目的地址修改为
192.168.1.2:<随机b>
- 路由器按照映射关系,修改目的地址为
192.168.2.100:<随机a>
- 我的机器收到回复数据包
看起来挺复杂的,其实每一次路由就涉及这几件事情:
- 出口流量:
- 建立内外IP:端口的端口映射
- 修改源地址
- 按照路由转发
- 进口流量:
- 找到映射的IP端口
- 找得到:修改目的地址
- 按照路由转发
- 找到映射的IP端口
这种端口是动态的,当然我们也可以建立静态的端口映射,路由器支持就可以做。
按照映射关系的特点,我们又把NAPT分为了完全锥形、受限锥形和对称NAT。
锥形NAT
锥形NAT就是映射关系会保持一段时间、只在特定的情况下失效的NAT。
比如说上面的例子,就会形成一个映射的链:每一级都建立映射关系,最后变成一个链:192.168.2.100:<a> ↔ 192.168.1.2:<b> ↔ .... ↔ <公网>:<z> ↔ 1.1.1.1:53
,在一段时间内,这个映射链都是可靠的,只要我是从192.168.2.100:<a>
发给1.1.1.1:53
、或者反过来,都会走这条路。
我们家里用的nat一般都是这类。按照一些配置细节不同,还分了完全锥形NAT、地址受限NAT和端口受限NAT。
对称NAT
对称NAT就是映射关系不保持、每次都映射到新的端口上的NAT。
一般企业里面用的多一些。
打洞
上面说NAT的时候,我们提到了锥形NAT会维持一个映射关系的链。
有的时候路由不可靠、或者是IP会浮动,或者有的机房有多个出口IP,偶尔回复数据会换个IP发回来。所以一般回复时不要求数据包源地址在链上,只要目的地址没问题就行。
那么其实换句话说,只要往公网的这个端口发送数据包就可以打洞了。
问题:但是我们要怎么样知道我们的公网IP呢?
我们需要一个或者多个在公网上有IP的服务器,专门用来接收数据包,能收到数据包一般就可以看到公网IP。比如说迅雷的tracker、zerotier的planet/moon
问题:不是映射关系会失效吗
一般来说失效速度不会那么快,我们只要定时发一次心跳包就好了。如果映射关系改变了,我们也可以很快的更新端口信息,之后重发没有收到的数据包就好了
问题:我试了TCP不能打洞
是的,我看到的很老的资料也提到了TCP打洞,但是现在的网关上一般都会解析TCP包头,如果是从外向内建立连接的请求一般都会过滤掉。所以我们现在只能用UDP打洞了。
那如果公司用的是对称NAT怎么办
那只能走转发了。用FRP、ssh或者是类似的工具吧~
实现
自己写这个实现比较麻烦,之后再自己写吧。先列举一下常见的利用了打洞的工具
- P2P
- bt/eMule
- frp的xtcp模式
- 虚拟网络
- zerotier