由于国内网络环境问题, 普遍家庭用户宽带都没有分配到公网 IP(我有固定公网 IP, 嘿嘿); 这时候一般我们需要从外部访问家庭网络时就需要通过一些魔法手段, 比如 VPN、远程软件(向日葵…)等; 但是这些工具都有一个普遍存在的问题: 慢+卡!
究其根本因素在于, 在传统架构中如果两个位于多层 NAT(简单理解为多个路由器)之后的设备, 只能通过一些中央(VPN/远程软件)中转服务器进行链接, 这时网络连接速度取决于中央服务器带宽和速度; 这种网络架构我这里简称为: 星型拓扑
从这张图上可以看出,你的 “工作笔记本” 和 “家庭 NAS” 之间通讯的最大传输速度为 Up/Down: 512K/s; 因为流量经过了中央服务器中转, 由于网络木桶效应存在, 即使你两侧的网络速度再高也没用, 整体的速度取决于这个链路中最低的一个设备网速而不是你两端的设备.
在这种拓扑下, 想提高速度只有一个办法: 加钱!在不使用 “钞能力” 的情况下, 普遍免费的软件提供商不可能给予过多的资源来让用户白嫖, 而自己弄大带宽的中央服务器成本又过高.
本部分只做简述, 具体里面有大量细节和规则可能描述不准确, 细节部分推荐阅读 How NAT traversal works.
既然传统的星型拓扑有这么多问题, 那么有没有其他骚操作可以解决呢? 答案是有的, 简单来说就是利用 NAT 穿透原理. NAT 穿透简单理解如下:在 A 设备主动向 B 设备发送流量后, 整个链路上的防火墙会短时间打开一个映射规则, 该规则允许 B 设备短暂的从这个路径上反向向 A 设备发送流量.更通俗的讲大概就是所谓的:“顺着网线来打你”
搞清了这个规则以后, 我们就可以弄一台 “低配” 的中央服务器, 让中央服务器来帮助我们协商两边的设备谁先访问谁(或者说是访问规则); 两个设备一起无脑访问对方, 然后触发防火墙的 NAT 穿透规则(防火墙打开), 此后两个设备就可以不通过中央服务器源源不断的通讯了. 在这种架构下我们的设备其实就组成了一个非标准的网状拓扑:
在这种拓扑下, 两个设备之间的通讯速度已经不在取决于中央服务器, 而是直接取决于两端设备的带宽, 也就是说达到了设备网络带宽峰值.当然 NAT 穿透也不是百分百能够成功的, 在复杂网络情况下有些防火墙不会按照预期工作或者说有更严格的限制;比如 IP、端口、协议限制等等, 所以为了保证可靠性可以让中央服务器中转做后备方案, 即尽量尝试 NAT 穿透, 如果不行走中央服务器中继.
第一部分是为了方便读者理解一些新型内网穿透的大致基本原理, 现在回到本文重点: Tailscale
Tailscale 就是一种利用 NAT 穿透(aka: P2P 穿透)技术的 VPN 工具. Tailscale 客户端等是开源的, 不过遗憾的是中央控制服务器目前并不开源; Tailscale 目前也提供免费的额度给用户使用, 在 NAT 穿透成功的情况下也能保证满速运行.
不过一旦无法 NAT 穿透需要做中转时, Tailscale 官方的服务器由于众所周知的原因在国内访问速度很拉胯; 不过万幸的是开源社区大佬们搓了一个开源版本的中央控制服务器(Headscale), 也就是说:我们可以自己搭建中央服务器啦, 完全 “自主可控” 啦.
以下命令假设安装系统为 Ubuntu 22.04, 其他系统请自行调整.
下载完成后为了安全性我们需要创建单独的用户和目录用于 Headscale 运行
可能很多人和我一样, 希望使用 ACME 自动证书, 又不想占用 80/443 端口, 又想通过负载均衡器负载, 配置又看的一头雾水; 所以这里详细说明一下 Headscale 证书相关配置和工作逻辑:
6、只有在有证书(ACME 证书或自定义证书)的情况下或者手动开启了 grpc_allow_insecure 才会监听 grpc 远程调用服务
请尽量不要将 ip_prefixes 配置为默认的 100.64.0.0/10 网段, 如果你有兴趣查询了该地址段, 那么你应该明白它叫 CGNAT; 很不幸的是例如 Aliyun 底层的 apt 源等都在这个范围内, 可能会有一些奇怪问题.
再啰嗦一嘴, 如果你期望使用 Headscale ACME 自动申请证书, 你的关键配置应该像这样:
你需要在与 docker-compose.yaml 同级目录下创建 conf 目录用于存储配置文件; 具体配置请参考上面的配置详解等部分, 最后不要忘记你的 Compose 文件端口映射需要和配置文件保持一致.
对于客户端来说, Tailscale 提供了多个平台和发行版的预编译安装包, 并且部分客户端直接支持设置自定义的中央控制服务器.
默认该脚本会检测相关的 Linux 系统发行版并使用对应的包管理器安装 Tailscale, 安装完成后使用以下命令启动:
--advertise-routes : 向中央服务器报告当前客户端处于哪个内网网段下, 便于中央服务器让同内网设备直接内网直连(可选的)或者将其他设备指定流量路由到当前内网(可选)
--accept-routes : 是否接受中央服务器下发的用于路由到其他客户端内网的路由规则(可选)
启动完成后,tailscale 将会卡住, 并打印一个你的服务器访问地址; 浏览器访问该地址后将会得到一条命令:
注意: 浏览器上显示的命令需要在中央控制服务器执行(Headscale), NAMESAPCE 位置应该替换为一个具体的 Namespace, 可以使用以下命令创建 Namespace (名字随意)并让设备加入:
在 Headscale 服务器上执行命令成功后客户端命令行在稍等片刻便会执行完成, 此时该客户端已经被加入 Headscale 网络并分配了特定的内网 IP; 多个客户端加入后在 NAT 穿透成功时就可以互相 ping 通, 如果出现问题请阅读后面的调试细节, 只要能注册成功就算是成功了一半, 暂时不要慌.
MacOS 客户端安装目前有两种方式, 一种是使用标准的 AppStore 版本(好像还有一个可以直接下载的), 需要先设置服务器地址然后再启动 App:
第二种方式也是比较推荐的方式, 直接编译客户端源码安装, 体验与 Linux 版本一致:
安装完成后同样通过 tailscale up 命令启动并注册即可, 具体请参考 Linux 客户端安装部分.
关于 Windows 客户端大致流程就是创建一个注册表, 然后同样安装官方 App 启动, 接着浏览器复制命令注册即可. 至于移动端本人没有需求, 所以暂未研究.Windows 具体的安装流程请访问 地址查看(基本与 MacOS AppStore 版本安装类似).
在上面的 Headscale 搭建完成并添加客户端后, 某些客户端可能无法联通; 这是由于网络复杂情况下导致了 NAT 穿透失败; 为此我们可以搭建一个中继服务器来进行传统的星型拓扑通信.
目前 Tailscale 官方并未提供 DERP Server 的安装包, 所以需要我们自行编译安装; 在编译之前请确保安装了最新版本的 Go 语言及其编译环境.
如果期望使用 ACME 自动申请只需要不增加 -a 选项即可(占用 443 端口), 如果期望通过负载均衡器负载, 则需要将 -a 选项指定到非 443 端口, 然后配置 Nginx、Caddy 等 LB 软件即可. 最后一点 stun 监听的是 UDP 端口, 请确保防火墙打开此端口.
在创建完 Derper 中继服务器后, 我们还需要配置 Headscale 来告诉所有客户端在必要时可以使用此中继节点进行通信; 为了达到这个目的, 我们需要在 Headscale 服务器上创建以下配置:
在调试中继节点或者不确定网络情况时, 可以使用一些 Tailscale 内置的命令来调试网络.
tailscale ping 命令可以用于测试 IP 连通性, 同时可以看到时如何连接目标节点的.默认情况下 Ping 命令首先会使用 Derper 中继节点通信, 然后尝试 P2P 连接; 一旦 P2P 连接成功则自动停止 Ping:
通过 tailscale status 命令可以查看当前节点与其他对等节点的连接方式, 通过此命令可以查看到当前节点可连接的节点以及是否走了 Derper 中继:
有些情况下我们可以确认是当前主机的网络问题导致没法走 P2P 连接, 但是我们又想了解一下当前的网络环境; 此时可以使用 tailscale netcheck 命令来检测当前的网络环境, 此命令将会打印出详细的网络环境报告:
MacOS 下使用一些增强代理工具时, 如果安装 App Store 的官方图形化客户端, 则可能与这些软件冲突, 推荐使用纯命令行版本并添加进程规则匹配 tailscale 和 tailscaled 两个进程, 让它们始终走 DIRECT 规则即可.
在使用一些网络代理工具时, 网络工具会设置默认路由; 这可能导致 tailscaled 无法获取到默认路由接口, 然后进入死循环并把 CPU 吃满, 同时会与 Derper 服务器产生大量上传流量.截止本文发布此问题已修复, 请使用 mian 分支编译安装, 具体见 ISSUE/5879.
Tailscale 默认使用 CGNAT( 100.64.0.0/10 ) 网段作为内部地址分配网段,目前 Tailscale 仅允许自己的接口使用此网段, 不巧的是阿里云的 DNS、Apt 源等也采用此网段.这会导致阿里云服务器安装客户端后 DNS、Apt 等不可用, 解决方案目前只能修改源码删除掉这两个 DROP 规则并重新编译.
大多数时候我们可能并不会在每个服务器上都安装 Tailscale 客户端, 通常只安装 2、3 台, 然后想通过这两三台转发该内网的所有流量.此时你需要
其他节点启动时需要增加 --accept-routes=true 选项来声明 “我接受外部其他节点发布的路由”
以上也只是我个人遇到的一些问题, 如果有其他问题推荐先搜索然后查看 ISSUE, 最后不行可以看看源码. 目前来说 Tailscale 很多选项很模糊, 可能需要阅读源码以后才能知道到底应该怎么做.