家庭网络内网穿透

1,659 阅读7分钟

需求

日常使用过程中对家庭网络有内网穿透的需求,比如访问家里的nas、异地玩游戏串流以及远程桌面等。如果有公网IP这些都很好解决,但是公网IPv4的资源比较稀缺,不一定能申请下来,而IPv6还不成熟,像是公司内部往往是没有IPv6的,因此需要在没有公网的情况下进行内网穿透。

方案

一种常见的方案是买个有公网IP的vps,然后在上面搭建frp等转发软件进行流量转发,但是国内的vps说实话其实不便宜,而且买来往往没啥用,只偶尔进行流量转发的话太浪费了。

因此这里介绍两种不购买额外服务的方法:

  1. zerotier/tailscale
  2. Nat1内网穿透

zerotier/tailscale

这两种方案比较类似,tailscale写过相关的比较,我两种都深度使用过,都很好用,两者都是基于UDP的,但是zerotier自己实现了一套协议,而tailscale是基于wireguard(在Linux5.6被合入主线)的包装,tailscale的界面更漂亮一些。

两者都是用一个中间服务器充当握手者,握手成功后流量就不再经过中间服务器,一般同城使用没什么问题,但是跨地区跨运营商可能会有些问题,由此国庆回老家在高峰期连接在深圳的机器发现多少还是有点卡顿(国内运营商对udp的qos比较严重),有时会出现握手不成功的情况,流量就全通过中间服务器中转,延迟就会上升了。

这里简单介绍一下在mikrotik路由器上的routerOS配置zerotier的方法,通过在路由器上配置zerotier,可以让路由器下的子网不用再下载zerotier客户端,对其透明。

详细的方法这里有介绍:help.mikrotik.com/docs/displa…

  1. 注册zerotier账号,获得一个network ID
  2. 下载 并安装zerotier客户端,这里吧Extra packages解压后把对应的NPK包上传到路由器并重启即可。
  3. 启用对应的实例: [admin@mikrotik] > zerotier/enable zt1
  4. 添加第1步的网络: [admin@mikrotik] zerotier/interface/add network=1d71939404912b40 instance=zt1
  5. 验证配置:
[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
  1. 如果配置了防火墙,需要把下列两条则放在最前面,以便流量能正常进来:
/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
  1. 在zerotier控制台允许路由器的访问: image.png

  2. 通过路由器访问家里内网配置: image.png 并在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 会做转换。

  1. 本地发起请求,来源端口为 100.64.32.10:3456 ,想建立 (100.64.32.10:3456 至 109.244.211.100:80) 的连接。
  2. ISP NAT Server 察觉到本地想要请求,在公网 IP 空闲端口中挑一个,在映射表中新增 (100.64.32.10:3456 ~ 203.0.113.10:14500) 映射关系。
  3. 100.64.32.10:3456 转发 203.0.113.10:14500 至 109.244.211.100:80 ,本机和 www.qq.com 成功建立连接。
  4. 本机断开与 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 端口,就无法继续“趁虚而入”了。
  5. 然而,这是上帝视角看整个连接的过程。实际操作中,我们不知道出口的 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服务,再从对应的公网地址进行访问: image.png 成功!

总结

一般同城的情况使用zerotier/tailscale即可,如果udp访问不理想就可以使用nat1穿透的方式,并且使用nat1穿透还可以在自己家建立中转服务器提供给zerotier/tailscale使用,效果也很好。