局域网内网穿透技术
一、内网穿透概述
1、传统内网穿透介绍
传统内网穿透的方式为:内网设备<——>中转服务器<———>网络设备(手机、电脑),例如frp,nps,可以参考:内网穿透工具;但是这种方法弊端也很明显
- 中转服务器需要一定的费用进行支撑(带公网的云服务器),如果是外网的服务器还可能存在被墙的风险
- 中转服务器直接决定了中转的"速度",而这个"速度"越快其对应的服务器带宽就越大,通常来说价格就越高
- 需要一定的知识储备来搭建内网穿透的服务端,而且只能转发某个特定的端口
2、ZeroTier和Tailscale
关于打洞的知识可以参考:NAT网络与内网穿透详解
一般来说会优先进行打洞,打洞的时候连接:内网设备<——>移动、PC设备(手机、电脑),通常情况下是端到端的传输,如果网络环境差或者无法打洞也会借助中转服务器进行传输数据(自带的中间服务器在国外,最好自己部署一个自己的)。
加入到一个局域网后,能够穿透转发所有端口,正常情况下不依赖服务器进行中转传输文件,端到端连接,理论可以达到满带宽,同时也可以进行流量转发,实现一台机器加入网络,实现整个局域网的设备访问
二、ZeroTier
1、概述
1.1 介绍
zerotier采用VLAN(虚拟局域网)技术将不同设备连接到一个“虚拟的局域网”中,从而让这些设备随时随地都可以互相访问,相比于frp等其他内网穿透来说,组建虚拟局域网更安全更便捷,每台服务器上只需要安装对应的客户端,连接到同一个网络,就可以实现 IP 互相访问。在此之上,还有自定义 DNS 服务器的功能,将通过 IP 这个步骤转换为通过域名进行访问,相当实用,甚至可以在局域网的一台主机搭建,实现整个局域网的访问
ZeroTier所有的设备都是客户端,连接方式是点对点。在路由器下面的话是用 uPnP 的方式进行转发实现客户端到客户端的直接连接。如果 uPnP 没有开启,会通过传统的服务器转发的方式进行连接。
1.2 相关概念
- Earth。根据其介绍,将地球上的所有设备连起来。那这里的 Earth 指的就是整体的一个服务
- Network。每一个 Network 包含的所有设备都在同一个网络里。每个网络有一个 Network ID。各客户端通过这个 ID 连接到此网络。当然,一个账号是可以创建多个网络的。网络氛围 Public 和Private。一般我们自己组网是要用 Private,需要在页面授权设备才可以进行访问。
- Planet。官方提供的服务器节点。各客户端都是通过这些服务来互相寻址的。相当于 zookeeper 的不同节点
- Moon。自定义的 Planet。由于 Zerotier 没有国内节点,在两个设备刚开始互连的时候有可能需要通过国外的节点寻址(不过我没发现有什么慢的)导致创建连接的速度偏慢。在自己的网络里搭建 Moon 可以使连接提速。
- Leaf。客户端。就是连接到网络上的每一个设备。其实经过测试,Moon 也是客户端的一种。这里特指没有额外功能,单纯用于连接的客户端
2、ZeroTier简单使用
首先登陆官网进行账号注册:https://www.zerotier.com/,完成后点击创建好的网络,进入设置界面进行设置,创建网络,记住NETWORK ID,进入后选择PRIVATE网络(public加入不需要授权,不安全),之后选择自己想要的网段,其他保持默认即可
然后根据不同平台下载客户端:zerotier.com/download/,举例linux
1 | curl -s https://install.zerotier.com | sudo bash |
开始授权,可以选择设置按钮自行设置设备的ip,默认就是系统随机分配
windows类似,加入网络授权后,即可访问加入这个局域网中的所有设备了
3、Moon搭建
3.1 介绍
官方手册部署 Moon
Moon 节点可以是具有固定 IP 的公网设备,也可以是具有物理 IP 的内网设备. 如果使用内网设备,依然需要借助官方根服务器作为中间链路. 因此,这里我们在云服务器上搭建 Moon 服务,在机器 C 上部署 ZeroTier Moon 首先需要安装 ZeroTier One 将其部署为为 ZeroTier Node(参考前一节);当然如果打洞不成功也需要走moon流量
3.2 部署Moon服务
1 | # 进入 root 账户 |
3.3 使用 Moon 服务
要想使用 Moon 服务,还需要在机器上添加签名文件,有两种方式:手动添加;命令行通过 ZeroTier Root Server 添加,添加后会发现延迟显著降低
手动添加签名文件
1 | # 在 ZeroTier One 的 HOME 目录新建 moons.d 目录, 将 1.3 小节中产生的 0000007818fa6036.moon 拷贝出来并放入 {ZeroTier_One_HOME}\moons.d 目录中 |
命令添加
1 | # windows下,填写自己的node id,然后重启 |
4、流量转发与局域网访问
4.1 概述
可以参考:zerotier转发客户端流量
那么如何让一台局域网外的电脑访问局域网内所有设备呢,但是又不想让所有设备都安装zerotier,这时,仅使用** Ubuntu 上的 linux 内核的数据转发和 iptables 控制路由**就能实现所需功能(相当于science上网的功能)
4.2 转发服务器配置
首先需要配置内网中一台 Linux 的数据转发和路由控制,Linux 系统内核可以通过 sysctl
和 iptables
两个命令控制网络数据转
1 | # 查看内核 IP 转发设置,0 表示处于关闭状态 |
然后需要配置 ZeroTier 网络路由管理,打开控制台:https://my.zerotier.com/,增加 1 条路由,Destination 填入 0.0.0.0/0
表示全网,(via) 填入 zt7u3fcxwr
的 IP 地址,点击 Submit
提交。意思就是将流量通过via转发,相当于把Via当作网关,也可以自定义特定的网关
这里再举个例子
1 | # 假设zerotier虚拟局域网的网段是192.168.88.0 局域网A 192.168.1.0 局域网B 192.168.2.0 |
4.3 客户端配置
Windows 可以使用客户端,需要勾选 Allow Global IP
.
- 开启转发代理:勾选
Allow Default Route
- 关闭转发代理:取消
Allow Default Route
对于Linux来说,启用 ZeroTIer转发代理需要设置内核,使内核允许发送数据的地址和接受数据的地址不同. 默认情况下,当发送地址与接受地址不一致的时候,内核会丢弃接收的数据.
1 | # 编辑 /etc/sysctl.conf 文件 |
最后进行测试,能ping通局域网表示成功,打开转发代理,访问curl http://myip.ipip.net/
,如果显示的isp和局域网的一样,说明成功
三、Tailscale
1、概述
1.1 Tailscale简介
Tailscale 是一种基于 WireGuard 的虚拟组网工具,和 Netmaker 类似,最大的区别在于 Tailscale 是在用户态实现了 WireGuard 协议,而 Netmaker 直接使用了内核态的 WireGuard。所以 Tailscale 相比于内核态 WireGuard 性能会有所损失,
Tailscale 就是一种利用 NAT 穿透(P2P 穿透)技术的 内网穿透工具.。Tailscale 客户端等是开源的, 不过遗憾的是中央控制服务器目前并不开源; Tailscale 目前也提供免费的额度给用户使用, 在 NAT 穿透成功的情况下也能保证满速运行。不过一旦无法 NAT 穿透需要做中转时, Tailscale 官方的服务器由于众所周知的原因,在国内访问速度很拉胯; 不过有一个开源版本的中央控制服务器(Headscale), 也就是说: 我们可以自己搭建中央服务器, 完全 "自主可控"
1.2 优势
- 开箱即用:无需配置防火墙;没有额外的配置
- 高安全性/私密性:自动密钥轮换;点对点连接;支持用户审查端到端的访问记录
- 在原有的 ICE、STUN 等 UDP 协议外,实现了 DERP TCP 协议来实现 NAT 穿透
- 基于公网的控制服务器下发 ACL 和配置,实现节点动态更新
- 通过第三方(如 Google) SSO 服务生成用户和私钥,实现身份认证
当然Tailscale 是一款商业产品,个人用户在接入设备不超过 20 台的情况下是可以免费使用的(虽然有一些限制,比如子网网段无法自定义,且无法设置多个子网)。除 Windows 和 macOS 的图形应用程序外,其他 Tailscale 客户端的组件(包含 Android 客户端)是在 BSD 许可下以开源项目的形式开发的,你可以在他们的 GitHub 仓库找到各个操作系统的客户端源码
1.3 Headscale 是什么
Tailscale 的控制服务器是不开源的,而且对免费用户有诸多限制。目前有一款开源的实现叫 Headscale,Headscale 由欧洲航天局的 Juan Font 使用 Go 语言开发,在 BSD 许可下发布,实现了 Tailscale 控制服务器的所有主要功能,可以部署在企业内部,没有任何设备数量的限制,且所有的网络流量都由自己控制。
2、基本使用
2.1 节点互相访问
对应软件安装教程:https://tailscale.com/download/linux
- 首先需要有个账号,使用自己已有的谷歌、Git或者微软账户登入系统
- 根据设备类型的不同,按照官方教程安装服务,注意,iOS需要有美区账户才能下载客户端
- 安装后,Linux执行
tailscale up
,会提示登录鉴权的url,其他客户端的Login或者Auth会拉起登录链接到浏览器,登入你的账户即可将节点加入局域网 - 最后,在 Tailscale 后台管理 即可查看节点的IP,并通过IP访问你的节点
2.2 访问局域网设备
只要局域网一台设备安装了Tailscale即可访问所有设备,这里以ubuntu20作为转发机器,一般来说也都是linux作为转发,参考:https://tailscale.com/kb/1019/subnets/
1 | # 常用操作可以参考:https://tailscale.com/kb/1080/cli/ |
设置完后打开控制台,首先关闭key过期,不然六个月就需要重新校验了,然后可以看到子网是灰色的,这需要控制台进行同意才行,点击Edit route settings
,打开对应的Subnet routes
子网即可
然后进行客户端的配置,下载对应的客户端
1 | # 首先加入对应局域网,授权登陆 |
2.3 出口流量转发
出口节点功能可以通过网络上的特定设备路由所有非tailscale访问互联网。运行出口路由的设备称为出口节点,局域网中的设备可以通过改节点访问互联网,流量都经过该台设备,参考:https://tailscale.com/kb/1103/exit-nodes
1 | # 和访问局域网设备一样,首先需要开启ip流量转发 |
3、Headscale 部署
3.1 服务端部署
Headscale 部署很简单,推荐直接在 Linux 主机上安装,理论上来说只要你的 Headscale 服务可以暴露到公网出口就行,但最好不要有 NAT,所以推荐将 Headscale 部署在有公网 IP 的云主机上。
1 | sudo su |
然后修改配置文件,主要是修改几个监听端口、文件位置(都放在/var/lib/headscale,注意权限)、子网段以及unix_socket
1 |
|
配置文件有几点需要说明,可能很多人希望使用 ACME 自动证书, 又不想占用 80/443 端口, 又想通过负载均衡器负载, ;这里详细说明一下 Headscale 证书相关配置和工作逻辑:
- Headscale 的 ACME 只支持 HTTP/TLS , 所以使用后必定占用 80/443
- 当配置了
tls_letsencrypt_hostname
时一定会进行 ACME 申请 - 在不配置
tls_letsencrypt_hostname
时如果配置了tls_cert_path
则使用自定义证书 - 两者都不配置则不使用任何证书, 服务端监听 HTTP 请求
- 三种情况下(ACME 证书、自定义证书、无证书)主服务都只监听
listen_addr
地址, 与server_url
没关系 - 只有在有证书(ACME 证书或自定义证书)的情况下或者手动开启了
grpc_allow_insecure
才会监听 grpc 远程调用服务
综上所述, 如果你想通过 Nginx、Caddy 反向代理 Headscale, 则你需要满足以下配置:
- 删除掉
tls_letsencrypt_hostname
或留空, 防止 ACME 启动 - 删除掉
tls_cert_path
或留空, 防止加载自定义证书 server_url
填写 Nginx 或 Caddy 被访问的 HTTPS 地址- 在你的 Nginx 或 Caddy 中反向代理填写
listen_addr
的 HTTP 地址
Nginx 配置参考 官方 Wiki, Caddy 只需要一行 reverse_proxy headscale:8080
即可(地址自行替换)。至于 ACME 证书你可以通过使用 acme.sh
自动配置 Nginx 或者使用 Caddy 自动申请等方式。
下一步创建 SystemD service 配置文件,让其能够自己开机自启,vim /etc/systemd/system/headscale.service
1 | # /etc/systemd/system/headscale.service |
最后环节
1 | # 创建 headscale 用户 |
3.2 Tailscale客户端接入
--login-server
: 指定使用的中央服务器地址 (必填)--advertise-routes
: 向中央服务器报告当前客户端处于哪个内网网段下, 便于中央服务器让同内网设备直接内网直连 (可选的) 或者将其他设备指定流量路由到当前内网(可选)--accept-routes
: 是否接受中央服务器下发的用于路由到其他客户端内网的路由规则 (可选)--accept-dns
: 是否使用中央服务器下发的 DNS 相关配置 (可选, 推荐关闭)--advertise-exit-node
:是否声明为出口节点,即流量都从这个节点转发(可选,根据自己需求来)--exit-node=<exit-node-ip>
:选择出口节点id(设置了才有)--exit-node-allow-lan-access=true
:是否还可以访问本地地址(设置了出口才可以有)
Linux
Tailscale 官方提供了各种 Linux 发行版的软件包,官方还提供了 静态编译的二进制文件,我们可以直接下载
1 | # 可以直接sh下载,不行就下面 |
Windows
Windows Tailscale 客户端想要使用 Headscale 作为控制服务器,只需在浏览器中打开 URL:http://<HEADSCALE_PUB_IP>:8080/windows
,按照其中的步骤操作即可
Android
Android 客户端从 1.30.0 版本开始支持自定义控制服务器(即 coordination server),你可以通过 Google Play 或者 F-Droid 下载最新版本的客户端。安装完成后打开 Tailscale App,开右上角的"三个点",你会看到只有一个 About
选项,你需要反复不停地点开再关闭右上角的"三个点",重复三四次之后,便会出现一个 Change server
选项,点击 Change server
,将 headscale 控制服务器的地址填进去,然后点击 Save and restart
重启,点击 Sign in with other
,将其中的命令粘贴到 Headscale 所在主机的终端,将 NAMESPACE 替换为之前创建的 namespace,然后执行命令即可。注册成功后可将该页面关闭
MacOS
可以参考:https://github.com/tailscale/tailscale/wiki/Tailscaled-on-macOS
3.3 通过 Pre-Authkeys 接入
前面的接入方法都需要服务端同意,步骤比较烦琐,其实还有更简单的方法,可以直接接入,不需要服务端同意。
1 | # 首先在服务端生成 pre-authkey 的 token,有效期可以设置为 24 小时 |
3.4 局域网互通
和之前一样,通过一台设备转发达到局域网互通
1 | # 首先需要设置 IPv4 与 IPv6 路由转发 |
最后是设置出口节点,如果要设置,登陆时加上--advertise-exit-node
即可,然后在headscale可以看到0.0.0.0/0
的路由,将其放行,最后在客户端选择--exit-node=<exit-node-ip>
以及--exit-node-allow-lan-access=true
自定义选项
3.5 常用命令总结
1 | headscale namespace list # 查看所有的namespace |
4、中继服务器概述(可选)
4.1 STUN 是什么
Tailscale 的终极目标是让两台处于网络上的任何位置的机器建立点对点连接(直连),但大部分情况下机器都位于 NAT 和防火墙后面,这时候就需要通过打洞来实现直连,也就是 NAT 穿透。NAT 按照 NAT 映射行为和有状态防火墙行为可以分为多种类型,但对于 NAT 穿透来说根本不需要关心这么多类型,只需要看 NAT 或者有状态防火墙是否会严格检查目标 Endpoint,根据这个因素,可以将 NAT 分为 Easy NAT 和 Hard NAT。
-
Easy NAT 及其变种称为 “Endpoint-Independent Mapping” (EIM,终点无关的映射)
这里的 Endpoint 指的是目标 Endpoint,也就是说,有状态防火墙只要看到有客户端自己发起的出向包,就会允许相应的入向包进入,不管这个入向包是谁发进来的都可以。
-
hard NAT 以及变种称为 “Endpoint-Dependent Mapping”(EDM,终点相关的映射)
这种 NAT 会针对每个目标 Endpoint 来生成一条相应的映射关系。 在这样的设备上,如果客户端向某个目标 Endpoint 发起了出向包,假设客户端的公网 IP 是 2.2.2.2,那么有状态防火墙就会打开一个端口,假设是 4242。那么只有来自该目标 Endpoint 的入向包才允许通过
2.2.2.2:4242
,其他客户端一律不允许。这种 NAT 更加严格,所以叫 Hard NAT。
对于 Easy NAT,我们只需要提供一个第三方的服务,它能够告诉客户端“它看到的客户端的公网 ip:port 是什么”,然后将这个信息以某种方式告诉通信对端(peer),后者就知道该和哪个地址建连了!这种服务就叫 STUN (Session Traversal Utilities for NAT,NAT会话穿越应用程序)。
对于 Hard NAT 来说,STUN 就不好使了,即使 STUN 拿到了客户端的公网 ip:port
告诉通信对端也于事无补,因为防火墙是和 STUN 通信才打开的缺口,这个缺口只允许 STUN 的入向包进入,其他通信对端知道了这个缺口也进不来。通常企业级 NAT 都属于 Hard NAT。这种情况下可以选择一种折衷的方式:创建一个中继服务器(relay server),客户端与中继服务器进行通信,中继服务器再将包中继(relay)给通信对端。至于中继的性能,那要看具体情况了:
- 如果能直连,那显然没必要用中继方式;
- 但如果无法直连,而中继路径又非常接近双方直连的真实路径,并且带宽足够大,那中继方式并不会明显降低通信质量。延迟肯定会增加一点,带宽会占用一些,但相比完全连接不上,还是可以接受的。
4.2 中继服务之TURN
TURN 即 Traversal Using Relays around NAT,这是一种经典的中继实现方式,核心理念是:
- 用户(人)先去公网上的 TURN 服务器认证,成功后后者会告诉你:“我已经为你分配了 ip:port,接下来将为你中继流量”,
- 然后将这个 ip:port 地址告诉对方,让它去连接这个地址,接下去就是非常简单的客户端/服务器通信模型了。
与 STUN 不同,这种协议没有真正的交互性,不是很好用,因此 Tailscale 并没有采用 TURN 作为中继协议。
4.3 DERP
DERP 即 Detoured Encrypted Routing Protocol,这是 Tailscale 自研的一个协议:
- 它是一个通用目的包中继协议,运行在 HTTP 之上,而大部分网络都是允许 HTTP 通信的。
- 它根据目的公钥(destination’s public key)来中继加密的流量(encrypted payloads)
Tailscale 使用的算法很有趣,所有客户端之间的连接都是先选择 DERP 模式(中继模式),这意味着连接立即就能建立(优先级最低但 100% 能成功的模式),用户不用任何等待。然后开始并行地进行路径发现,通常几秒钟之后,我们就能发现一条更优路径,然后将现有连接透明升级(upgrade)过去,变成点对点连接(直连)。因此,DERP 既是 Tailscale 在 NAT 穿透失败时的保底通信方式(此时的角色与 TURN 类似),也是在其他一些场景下帮助我们完成 NAT 穿透的旁路信道。 换句话说,它既是我们的保底方式,也是有更好的穿透链路时,帮助我们进行连接升级(upgrade to a peer-to-peer connection)的基础设施。
5、自建私有 DERP server(可选)
5.1 概述
Tailscale 的私钥只会保存在当前节点,因此 DERP server 无法解密流量,它只能和互联网上的其他路由器一样,呆呆地将加密的流量从一个节点转发到另一个节点,只不过 DERP 使用了一个稍微高级一点的协议来防止滥用。Tailscale 开源了 DERP 服务器的代码,如果你感兴趣,可以阅读 DERP 的源代码。
Tailscale 官方内置了很多 DERP 服务器,分步在全球各地,惟独不包含中国大陆。这就导致了一旦流量通过 DERP 服务器进行中继,延时就会非常高。而且官方提供的 DERP 服务器是万人骑,存在安全隐患。为了实现低延迟、高安全性,我们可以参考 Tailscale 官方文档自建私有的 DERP 服务器。有两种部署模式,一种是基于域名,另外一种不需要域名,可以直接使用 IP,可以直接参考:部署私有 DERP 中继服务器
5.2 中继服务器搭建
这里搭建的是官方推荐的,属于http搭建,docker搭建可以参考其他。首先需要注意的是, 在需要搭建 DERP Server 的服务器上, 请先安装一个 Tailscale 客户端并注册到 Headscale; 这样做的目的是让搭建的 DERP Server 开启客户端认证, 否则你的 DERP Server 可以被任何人白嫖
1 | # 目前 Tailscale 官方并未提供 DERP Server 的安装包, 所以需要我们自行编译安装; 在编译之前请确保安装了最新版本的 Go 语言及其编译环境. |
接下来创建一个 SystemD 配置,保证开机自启,这里有几个点。默认情况下 Derper Server 会监听在 :443
上, 同时会触发自动 ACME 申请证书. 关于证书逻辑如下:
- 如果不指定
-a
参数, 则默认监听:443
- 如果监听
:443
并且未指定--certmode=manual
则会强制使用--hostname
指定的域名进行 ACME 申请证书 - 如果指定了
--certmode=manual
则会使用--certmode
指定目录下的证书开启 HTTPS - 如果指定了
-a
为非:443
端口, 且没有指定--certmode=manual
则只监听 HTTP
如果期望使用 ACME 自动申请只需要不增加 -a
选项即可(占用 443 端口), 如果期望通过负载均衡器负载, 则需要将 -a
选项指定到非 443 端口, 然后配置 Nginx、Caddy 等 LB 软件即可. 最后一点 stun
监听的是 UDP 端口, 请确保防火墙打开此端口.
1 | # /lib/systemd/system/derper.service |
然后启动derp服务
1 | # 重新加载 |
5.3 配置 Headscale
在创建完 Derper 中继服务器后, 我们还需要配置 Headscale 来告诉所有客户端在必要时可以使用此中继节点进行通信; 为了达到这个目的, 我们需要在 Headscale 服务器上创建以下配置,vim /etc/headscale/derper.yaml
,配置说明:
regions
是 YAML 中的对象,下面的每一个对象表示一个可用区,每个可用区里面可设置多个 DERP 节点,即nodes
。- 每个可用区的
regionid
不能重复。 - 每个
node
的name
不能重复。 regionname
一般用来描述可用区,regioncode
一般设置成可用区的缩写。ipv4
字段不是必须的,如果你的域名可以通过公网解析到你的 DERP 服务器地址,这里可以不填。如果你使用了一个二级域名,而这个域名你并没有在公共 DNS server 中添加相关的解析记录,那么这里就需要指定 IP(前提是你的证书包含了这个二级域名,这个很好支持,搞个泛域名证书就行了)。stunonly: false
表示除了使用 STUN 服务,还可以使用 DERP 服务。
1 | # /etc/headscale/derper.yaml |
在创建好基本的 Derper Server 节点信息配置后, 我们需要调整主配置来让 Headscale 加载
1 | derp: |
5.4 客户端网络调试
1 | # 查看derp也没有成功,注意防火墙开放 |
5.5 Docker Compose 安装(可选)
该镜像默认开启了客户端验证, 所以请确保 /var/run/tailscale
内存在已加入 Headscale 成功的 tailscaled 实例的 sock 文件
1 | version: '3.9' |
参考文章:
https://www.jianshu.com/p/3a19483f2879
https://www.cnblogs.com/jonnyan/p/14175136.html
https://mritd.com/2022/10/19/use-headscale-to-build-a-p2p-network
https://icloudnative.io/posts/how-to-set-up-or-migrate-headscale