需求
日常使用过程中对家庭网络有内网穿透的需求,比如访问家里的nas、异地玩游戏串流以及远程桌面等。如果有公网IP这些都很好解决,但是公网IPv4的资源比较稀缺,不一定能申请下来,而IPv6还不成熟,像是公司内部往往是没有IPv6的,因此需要在没有公网的情况下进行内网穿透。
方案
一种常见的方案是买个有公网IP的vps,然后在上面搭建frp等转发软件进行流量转发,但是国内的vps说实话其实不便宜,而且买来往往没啥用,只偶尔进行流量转发的话太浪费了。
因此这里介绍两种不购买额外服务的方法:
- zerotier/tailscale
- Nat1内网穿透
zerotier/tailscale
这两种方案比较类似,tailscale写过相关的比较,我两种都深度使用过,都很好用,两者都是基于UDP的,但是zerotier自己实现了一套协议,而tailscale是基于wireguard(在Linux5.6被合入主线)的包装,tailscale的界面更漂亮一些。
两者都是用一个中间服务器充当握手者,握手成功后流量就不再经过中间服务器,一般同城使用没什么问题,但是跨地区跨运营商可能会有些问题,由此国庆回老家在高峰期连接在深圳的机器发现多少还是有点卡顿(国内运营商对udp的qos比较严重),有时会出现握手不成功的情况,流量就全通过中间服务器中转,延迟就会上升了。
这里简单介绍一下在mikrotik路由器上的routerOS配置zerotier的方法,通过在路由器上配置zerotier,可以让路由器下的子网不用再下载zerotier客户端,对其透明。
详细的方法这里有介绍:help.mikrotik.com/docs/displa…
- 注册zerotier账号,获得一个network ID
- 下载 并安装zerotier客户端,这里吧Extra packages解压后把对应的NPK包上传到路由器并重启即可。
- 启用对应的实例:
[admin@mikrotik] > zerotier/enable zt1
- 添加第1步的网络:
[admin@mikrotik] zerotier/interface/add network=1d71939404912b40 instance=zt1
- 验证配置:
[admin@MikroTik] > zerotier/interface/print
Flags: R - RUNNING
Columns: NAME, MAC-ADDRESS, NETWORK, NETWORK-NAME, STATUS
# NAME MAC-ADDRESS NETWORK NETWORK-NAME STATUS
0 R zerotier1 42:AC:0D:0F:C6:F6 1d71939404912b40 modest_metcalfe OK
- 如果配置了防火墙,需要把下列两条则放在最前面,以便流量能正常进来:
/ip firewall filter add action=accept chain=forward in-interface=zerotier1 place-before=0
/ip firewall filter add action=accept chain=input in-interface=zerotier1 place-before=0
-
在zerotier控制台允许路由器的访问:
-
通过路由器访问家里内网配置:
并在zerotier的路由器分配的ip那里勾选Allow Ethernet Bridging。
至此就可以了,家里内网可以直接访问外面,外面也可以直接访问内网,内网还不用下载任何客户端。
Nat1 穿透
上述方案在绝大多数情况下是够用的了,但是偶尔udp会有点不稳定,这里介绍一种nat1网络下的内网穿透,可以真正拥有一个公网的ip:port组合。
原理
这里主要是使用MikeWang的natter项目,原理如下(引用原文):
假设本机获得了运营商分配的局域网 IP (100.64.32.10): 当本地 100.64.32.10 向 www.qq.com (109.244.211.100) 发起 HTTP 请求时,NAT 会做转换。
- 本地发起请求,来源端口为 100.64.32.10:3456 ,想建立 (100.64.32.10:3456 至 109.244.211.100:80) 的连接。
- ISP NAT Server 察觉到本地想要请求,在公网 IP 空闲端口中挑一个,在映射表中新增 (100.64.32.10:3456 ~ 203.0.113.10:14500) 映射关系。
- 100.64.32.10:3456 转发 203.0.113.10:14500 至 109.244.211.100:80 ,本机和 www.qq.com 成功建立连接。
- 本机断开与 www.qq.com 的连接,NAT Server 释放端口,清除 (100.64.32.10:3456 ~ 203.0.113.10:14500) 映射关系 因此,在第 4 步之前,第 3 步的时候 (100.64.32.10:3456 ~ 203.0.113.10:14500) 已经成立了映射关系,NAT1 下,两者是等同关系。此时,端口被“临时地”暴露至公网。 当该端口还提供服务时,相当于开了一个“洞”,外部的连接就能“趁虚而入”。 一旦到达第 4 步,NAT Server 回收 14500 端口,就无法继续“趁虚而入”了。
- 然而,这是上帝视角看整个连接的过程。实际操作中,我们不知道出口的 IP 和端口号,www.qq.com 也不会告诉我。 因此,我们需要问一个路人:“打扰一下,您看看我外面 IP 和端口号是多少”。这个路人就是 STUN 服务器。 STUN 服务器没那么多闲工夫,告诉你 IP 端口之后就立即关闭连接了。而 www.qq.com 比较客气,只要你提出我想要保持连接不关闭,它就不会关闭,外部端口也就不会被回收。 Natter 会每 10 秒说一次:“求求你别关连接”。
routerOS操作
开启container
和zerotier类似,将extra package里的npk包上传到路由器,然后在命令行里开启: /system/device-mode/update container=yes
,需要注意的是container不支持mips架构的路由器。
为container创建网络
首先添加虚拟网卡:/interface/veth/add name=veth1 address=172.17.0.2/24 gateway=172.17.0.1
再创建一个网桥,这样以后创建多个docker虚拟网卡时,可以让容器之间都能通信:
/interface/bridge/add name=dockers
/ip/address/add address=172.17.0.1/24 interface=dockers
/interface/bridge/port add bridge=dockers interface=veth1
创建环境变量和磁盘挂载(optional)
这里的环境变量主要是telegram的一些token,可以让网络发生变动时通知到我,直接在图形界面点envs和mounts即可
build docker镜像 & 安装
这里可以参考我的github代码
dockerfile:
FROM alpine:latest
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories \
&& apk add python3 && apk add curl
CMD cd disk1/natter_etc/ && \
python3 natter.py -c config.json > run.log 2>&1
这里使用alpine因为他比较轻量,再安装下python3运行环境以及curl(发telegram通知)。
build:
#!/bin/bash
docker buildx build --platform linux/arm/v7 -t natter .
docker save natter > natter.tar
scp natter.tar mikrotik:disk1/
根据自己的路由器平台编译一下,再上传到磁盘里
路由器添加container:
/container/add file=disk1/natter.tar root-dir=disk1/natter interface=veth1 envlist=telegram_echo mounts=natter
file是刚刚上传的image,root-dir是要保存到的位置,interface是刚刚创建的网卡,envlist和mounts是刚刚创建的环境变量和磁盘挂载。
路由器设置
如果你的natter打洞端口是3456,那么你就需要将路由器的3456端口的回程转发到你想打洞的IP:PORT上去:
/ip firewall nat
add action=dst-nat chain=dstnat dst-port=3456 in-interface=all-ppp protocol=\
tcp to-addresses=192.168.88.251 to-ports=8000
这样局域网的192.168.88.251的8000端口就对外暴露了。
这里取巧了一下,只对回程做了处理,其实还需要对去程做处理的,也就是要保证去程时docker的3456端口被映射到路由器的3456端口,但是连接数不多的情况下nat表似乎是直接按照端口号进行对应的。
telegram通知
如果发生了重播需要对外进行通知,那telegram的机器人就是一个很好的工具,natterv0.9版本已经集成通知功能了,我们只要写对应的通知脚本即可,这里可参照我的代码:
# Write your upload script below...
curl --request GET \
--url https://api.telegram.org/bot${token}/sendMessage \
--header 'Content-Type: application/json' \
--data "{
\"chat_id\": ${my_chat_id},
\"text\": \"${protocol}: ${inner_ip}:${inner_port} -> ${outter_ip}:${outter_port}\"
}"
实验
让192.168.88.251在8000开启一个http服务,再从对应的公网地址进行访问:
成功!
总结
一般同城的情况使用zerotier/tailscale即可,如果udp访问不理想就可以使用nat1穿透的方式,并且使用nat1穿透还可以在自己家建立中转服务器提供给zerotier/tailscale使用,效果也很好。