1. 为什么你需要自建PLANET服务器:不止是内网直连

如果你用过ZeroTier,肯定体验过它的神奇:几台天南海北的设备,装个客户端,输入一个网络ID,瞬间就像插在了同一个路由器上,能直接用内网IP互相访问。这背后,官方的PLANET(行星)服务器功不可没,它就像虚拟网络的“总机”,负责所有设备的发现和初次握手。

但用久了,尤其是国内用户,总会遇到一些“糟心时刻”。比如,公司内网的一台开发服务器,死活连不上你自建的PLANET;或者手机在4G网络下,连接时断时续,延迟高得离谱。你可能会怀疑是客户端配置问题,或是防火墙没开对,折腾半天才发现,根子出在PLANET服务器本身。

官方PLANET服务器都在海外,对于国内网络环境来说,这本身就引入了不稳定因素。更棘手的是,在一些复杂的网络架构下(比如多层NAT、严格的企业防火墙策略),内网客户端根本无法直接访问PLANET服务器的公网IP和端口。客户端拼命向那个公网地址发送连接请求,但数据包要么在出口就被拦截,要么根本找不到回来的路。这时候,你自建的PLANET服务器就成了一个“摆设”,看得见,用不上。

所以,自建PLANET服务器的第一个核心价值,就是把控制权拿回来。你可以把它部署在阿里云、腾讯云等国内机房,获得更低的延迟和更稳定的连接。但更深层的需求,是解决 “内网设备无法通过公网地址连接PLANET” 这个经典难题。解决方案不是去修改复杂的网络路由策略,而是“欺骗”一下客户端:我们给PLANET服务器再发一张“身份证”,这张身份证上的地址,写的是它的内网IP。

这样一来,位于同一个物理内网的其他ZeroTier客户端,在寻找PLANET时,会发现它还有一个“内网身份”,于是优先通过内网链路直连。数据流不再需要绕到公网IP再折返,延迟直接降到毫秒级,稳定性也大幅提升。这尤其适用于企业办公网、机房内部、或家庭局域网内有多台设备需要组网的场景。自建PLANET,再配合这个“内网地址”优化,才算真正打造了一个完全自主、高效可控的虚拟专网。

2. 准备工作:选对服务器,装好环境

工欲善其事,必先利其器。自建PLANET服务器的过程并不复杂,但前期准备对了,后面能省下大量排查故障的时间。

首先,你需要一台具有公网IP地址的云服务器。这是整个方案的基石,所有分散的设备最终都要通过它与PLANET通信。配置要求非常亲民,1核CPU、1GB内存、20GB硬盘的入门级机型就完全足够。因为ZeroTier的PLANET服务本身资源消耗很小,瓶颈通常在于网络带宽。如果你预计有大量设备并发连接或高频数据交换,那么适当提升带宽(比如5Mbps以上)会比升级CPU内存更有效。

操作系统方面,我强烈推荐 Ubuntu 22.04 LTS 或 Debian 12。它们拥有长期的稳定支持,社区资源丰富,而且我们后面用到的Docker在这些系统上的兼容性最好。别担心,即便你是Linux新手,跟着命令一步步来也绝对没问题。

登录你的服务器后,第一件事是更新系统软件包并安装必要的工具。打开终端,依次执行:

# 更新软件包列表
sudo apt update && sudo apt upgrade -y

# 安装我们需要的工具:curl用于下载脚本,git用于拉取代码
sudo apt install curl git -y

接下来是安装Docker。Docker容器化部署是我们方案的核心,它能将PLANET服务及其所有依赖打包在一个隔离的环境里运行,避免污染主机系统,也使得安装和卸载变得异常简单。使用Docker官方提供的一键安装脚本是最快的方式:

# 下载并执行Docker安装脚本
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

安装完成后,启动Docker服务并设置开机自启:

sudo systemctl start docker
sudo systemctl enable docker

为了后续操作方便,建议将当前用户加入docker组,这样就不用每次都输入sudo了:

sudo usermod -aG docker $USER
# 执行后需要退出当前SSH会话,重新登录生效

最后,别忘了防火墙。ZeroTier PLANET需要开放两个关键端口:

  • 9994/tcp & 9994/udp: 这是ZeroTier节点间通信的主要端口,必须开放。
  • 3443/tcp: 这是Web管理后台的端口,用于创建网络、管理设备。

如果你使用的是云服务商(如阿里云、腾讯云),除了在服务器本身的防火墙(如ufw)中放行,务必在云服务商的安全组控制台也添加相应的入站规则,这是很多新手会忽略的关键一步。你可以用以下命令快速配置ufw:

sudo ufw allow 22/tcp comment 'SSH'  # 保留SSH端口
sudo ufw allow 9994/tcp comment 'ZeroTier TCP'
sudo ufw allow 9994/udp comment 'ZeroTier UDP'
sudo ufw allow 3443/tcp comment 'ZeroTier Controller Web'
sudo ufw --force enable  # 启用防火墙

3. 一键部署:使用Docker快速搭建PLANET服务器

环境准备好,我们就可以开始部署PLANET服务器了。手动编译源码、配置环境变量的过程比较繁琐,好在社区有非常优秀的开源项目帮我们做好了这一切。这里我选用的是 xubiaolin/docker-zerotier-planet 这个项目,它把PLANET服务器、网络控制器(Controller)和Web管理界面打包成了一个完整的Docker镜像,通过一个脚本就能完成所有部署工作,对新手极其友好。

首先,我们把项目代码克隆到服务器上。如果从GitHub直接拉取速度慢,可以使用国内的代理地址:

# 使用加速地址克隆项目
git clone https://ghproxy.imoyuapp.win/https://github.com/xubiaolin/docker-zerotier-planet.git

# 进入项目目录
cd docker-zerotier-planet

项目目录里有一个核心脚本 deploy.sh,它是我们所有操作的指挥中心。直接运行它:

./deploy.sh

你会看到一个清晰的中文菜单:

欢迎使用 zerotier-planet 脚本,请选择需要执行的操作:
1. 安装
2. 卸载
3. 更新
4. 查看信息
5. 退出
请输入数字:

输入 1 并回车,选择安装。脚本会提示你输入PLANET服务器的公网IP地址。这里非常关键:如果你要解决内网直连问题,通常需要输入两个IP。但在这个初步部署阶段,脚本一般会自动检测到你的服务器公网IP并填入,直接按回车确认即可。如果检测不到,你就需要手动输入服务器的公网IP地址。

接下来是端口设置,除非你有特殊冲突,否则建议全部使用默认端口(9994, 3443)。脚本会开始拉取Docker镜像并创建容器,整个过程大概需要1-3分钟,取决于你的网络速度。当看到类似“安装成功”或“Successfully”的提示,并且最后给出Web管理后台的访问地址(如 http://你的服务器IP:3443)时,就表示PLANET服务器基础容器已经运行起来了。

你可以用命令 docker ps 查看容器状态,确认名为 zerotier-planet 的容器状态是 Up。至此,一个标准的、使用公网IP的PLANET服务器就部署完成了。但我们的目标还没达成,接下来才是解决内网连接问题的关键步骤。

4. 核心改造:修改源码,生成支持内网地址的PLANET文件

现在我们来攻克核心难题:让内网客户端能通过内网IP找到PLANET。ZeroTier客户端依靠一个叫做 planet 的配置文件来认识这个网络的“根服务器”。默认生成的 planet 文件里,只记录了PLANET服务器的公网IP。我们需要修改ZeroTier的源码,重新编译生成一个“双地址”的 planet 文件,让它同时包含公网和内网地址。

首先,我们需要获取ZeroTier的官方源码,并安装编译环境:

# 安装编译所需的GCC等工具
sudo apt install gcc g++ make -y

# 克隆 ZeroTierOne 官方源码仓库
git clone https://github.com/zerotier/ZeroTierOne.git
cd ZeroTierOne

关键修改位于 attic/world/mkworld.cpp 这个文件。它的作用是定义和生成“世界”(即planet)文件。我们需要找到定义根服务器(Root)的地方,添加我们自己的服务器信息,并注释掉或删除原有的官方测试服务器定义,否则编译后的程序可能会因为存在多个根定义而崩溃。

用你熟悉的文本编辑器(如 vim 或 nano)打开这个文件:

nano attic/world/mkworld.cpp

找到文件中类似以下结构的代码段(通常通过搜索 “EDIT BELOW HERE” 可以快速定位):

// =========================================================================
// EDIT BELOW HERE
std::vector<World::Root> roots;
const uint64_t id = ZT_WORLD_ID_EARTH;
const uint64_t ts = 1567191349589ULL; // August 30th, 2019

// Test.com
roots.push_back(World::Root());
roots.back().identity = Identity("xxxxxxxxxxxx:0:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
roots.back().stableEndpoints.push_back(InetAddress("1.2.3.4/9994"));
// END WORLD DEFINITION
// =========================================================================

我们需要用我们自建PLANET服务器的信息替换掉它。这里有两个关键信息需要获取:

  1. PLANET服务器的Identity:这是服务器的唯一身份标识。
  2. 服务器的内网IP地址和端口。

Identity可以从我们之前Docker容器的数据卷中找到。首先找出数据卷的挂载路径:

docker volume inspect zerotier-one

在输出信息中找到 Mountpoint 字段,其值类似于 /var/lib/docker/volumes/zerotier-one/_data。进入该目录,查看 identity.public 文件:

cat /var/lib/docker/volumes/zerotier-one/_data/identity.public

你会得到一串很长的字符串,格式如 xxxxxxxxxxxx:0:xxxxxxxxxx...,这就是我们需要的Identity。

假设你的PLANET服务器在内网中的IP是 192.168.1.100,通信端口是默认的 9994。那么,将 mkworld.cpp 中的代码修改为如下内容(注意注释掉或删除原有的 roots.push_back 部分):

// =========================================================================
// EDIT BELOW HERE
std::vector<World::Root> roots;
const uint64_t id = ZT_WORLD_ID_EARTH;
const uint64_t ts = 1567191349589ULL; // August 30th, 2019

// 你的自建 PLANET 服务器
roots.push_back(World::Root());
roots.back().identity = Identity("这里填入你的identity.public字符串");
// 添加内网地址作为稳定端点
roots.back().stableEndpoints.push_back(InetAddress("192.168.1.100/9994"));
// 如果需要,也可以保留公网地址端点,但内网客户端会优先使用内网地址
// roots.back().stableEndpoints.push_back(InetAddress("你的公网IP/9994"));
// END WORLD DEFINITION
// =========================================================================

保存并退出编辑器。现在,进入源码中的特定目录,编译生成我们自定义的 planet 文件:

cd attic/world/
# 执行编译脚本
source ./build.sh
# 运行生成世界的程序
./mkworld
# 将生成的文件改名为 planet
mv world.bin planet

如果一切顺利,当前目录下就会生成一个新的 planet 文件。这个文件包含了指向你服务器内网地址的根服务器信息。

5. 替换与验证:让客户端“认识”新的PLANET

新的 planet 文件生成后,我们需要用它替换掉Docker容器中原有的文件,并分发给所有客户端。

首先,找到Docker容器内 planet 文件的存放位置。通常它会在 /app/dist 或 /var/lib/zerotier-one 目录下。我们可以直接复制进去:

# 假设你当前在 ZeroTierOne/attic/world/ 目录,且新的planet文件在此
# 将新planet文件复制到Docker容器的相应目录
docker cp planet zerotier-planet:/app/dist/planet
# 通常也需要替换客户端使用的那个位置
docker cp planet zerotier-planet:/var/lib/zerotier-one/planet

然后,重启Docker容器使更改生效:

docker restart zerotier-planet

接下来,你需要将这个新的 planet 文件分发到每一个要加入此私有网络的客户端设备上,并替换掉它们原有的 planet 文件。各平台路径如下:

  • Windows: C:\ProgramData\ZeroTier\One\planet (隐藏目录)
  • Linux: /var/lib/zerotier-one/planet
  • macOS: /Library/Application Support/ZeroTier/One/planet
  • OpenWRT: /etc/config/zero/planet 或相关目录

替换后,务必重启客户端的ZeroTier服务。在Windows上是重启“ZeroTier One”服务,在Linux上是 sudo systemctl restart zerotier-one。

完成替换后,关键的验证时刻到了。在任意一个位于同一内网的客户端上,打开命令行,执行:

zerotier-cli peers

你会看到类似这样的输出:

200 peers
<ztaddr>   <ver>  <role> <lat> <link> <lastTX> <lastRX> <path>
fcbaeb9b6c 1.12.2 PLANET   0.3 DIRECT 12       45       192.168.1.100/9994

请重点关注 <link> 列和 <path> 列。如果 <link> 显示为 DIRECT(直连),并且 <path> 显示的IP地址是你PLANET服务器的内网IP(如192.168.1.100),那么恭喜你,内网直连优化成功了!这意味着该客户端是通过局域网链路直接与PLANET通信的,延迟极低,且不依赖公网出口。

如果显示的是RELAY(中转)或路径是公网IP,则可能是客户端没有正确加载新的planet文件,或者防火墙阻止了内网端口的UDP通信,需要回头检查。

6. 网络管理与客户端配置全攻略

PLANET服务器改造好后,我们还需要通过它的Web界面来创建和管理虚拟网络。在浏览器访问 http://你的服务器IP:3443,使用默认账号 admin 和密码 password 登录。登录后第一件事就是立即修改密码。

点击 “Networks” 选项卡,然后点击 “Add Network” 创建一个新网络。给你的网络起个名字,比如 “My-Office-LAN”。创建成功后,你会得到一个16位的网络ID(例如 a1b2c3d4e5f67890),这个ID就是所有客户端需要加入的“房间号”。

接下来进行关键的IP地址分配设置。点击你刚创建的网络,找到 “IPv4 Auto-Assign” 配置区域。这里我建议手动设置一个你容易记忆的网段,避免与现有物理网络冲突。例如,可以点击 “Easy Setup” 或手动在 “IPv4 Assignment Pool” 中添加 10.10.10.0/24。这样,所有授权加入的设备都会被自动分配一个 10.10.10.x 的IP地址。

现在,可以让客户端加入了。以Windows为例,在替换了新的planet文件并重启服务后,以管理员身份打开PowerShell或命令提示符,执行:

zerotier-cli.bat join a1b2c3d4e5f67890

看到 200 join OK 的回应后,回到Web管理界面,刷新页面。你会在网络成员列表里看到一个未授权的设备,名称可能是其硬件ID。你可以给它重命名(如“张三的笔记本”),然后勾选 “Authorized” 复选框授权它加入。稍等片刻,该设备就会获得一个你预设网段内的IP地址(如 10.10.10.10)。

其他平台的操作逻辑完全一致:安装官方客户端 -> 替换planet文件 -> 重启服务 -> 使用 zerotier-cli join <网络ID> 命令加入 -> 回到Web后台授权。对于安卓设备,由于官方客户端可能无法替换planet文件,可以使用第三方修改版客户端如 ZerotierFix。

当所有设备都加入并授权后,它们之间就可以像在同一个物理局域网内一样,使用分配的 10.10.10.x IP地址进行互访了,无论是ping测试、文件共享(SMB)、远程桌面(RDP)还是SSH连接。

7. 进阶优化与故障排查指南

基础搭建完成,但想用得顺手,还需要一些进阶优化和知道如何排错。

稳定性优化:除了内网直连,你还可以自建MOON服务器。MOON可以理解为PLANET的辅助节点,部署在另一个地理位置或网络。当某些设备无法直连PLANET时,可以通过MOON进行中转,提升连接成功率。在 docker-zerotier-planet 项目的生成目录里,通常也会有一个 moon.json 文件,这就是你的MOON配置,可以将其部署到另一台服务器上。

性能监控:zerotier-cli peers 命令是你最好的朋友。定期检查各节点间的连接状态(DIRECT/RELAY)和延迟(lat)。如果发现本该直连的设备变成了RELAY,就需要检查两者之间的NAT穿透情况,或者考虑调整网络拓扑。

常见故障排查:

  1. 客户端加入失败,提示超时或找不到网络:

    • 检查:Web管理后台的 3443 端口是否可从客户端访问?服务器防火墙和云服务商安全组是否放行了 9994/tcp/udp 和 3443/tcp?
    • 检查:客户端是否正确替换了新的 planet 文件?服务是否已重启?
    • 检查:zerotier-cli join 使用的网络ID是否正确?
  2. 设备间无法ping通:

    • 检查:双方设备是否都已显示为 “Authorized” 且分配到了IP?
    • 检查:设备本地的防火墙(如Windows Defender防火墙)是否允许了ZeroTier虚拟网卡的入站ICMP(ping)请求?
    • 检查:使用 zerotier-cli peers 查看设备间是否建立了 DIRECT 连接。如果是RELAY,可能需要等待或检查NAT类型。
  3. Web管理界面无法访问:

    • 检查:Docker容器是否正在运行?(docker ps)
    • 检查:3443 端口是否被其他进程占用?
    • 检查:尝试直接访问 http://服务器IP:3443,而非HTTPS。

安全建议:务必修改默认的Web管理密码。定期备份你的 planet 文件和Docker数据卷。如果服务器公网IP变更,你需要重新运行部署脚本并生成新的 planet 文件,因为IP地址是硬编码在其中的。

整个方案实施下来,我最深的体会是,技术问题的解决往往需要回到原理层面去思考。内网客户端连不上PLANET,表面是网络不通,实质是“寻址信息”不对。通过修改源码这个“笨办法”,我们直接修正了最底层的寻址数据,从而优雅地绕过了复杂的网络配置难题。这种完全掌控网络基础设施的感觉,以及优化后带来的毫秒级内网延迟,正是自建PLANET服务器最大的乐趣和价值所在。

更多推荐