问题
ZeroTier能P2P穿透,但由于其官方根服务器位于海外,延迟高,P2P成功率较低。
思路
个人理解,如果将ZeroTier组成的内网系统比作太阳系,那么ZeroTier官方的根服务器就是太阳,客户端就是除太阳以外的其他星体。正常情况下,客户端之间的通信都需要官方根服务器这个“太阳”,这就是所谓的公有服务器。
如果不作任何配置直接安装客户端,那么客户端之间的通信走的都是“太阳”这个公有服务器。虽然ZeroTier官方根服务器有多个,但都在海外,于是就会出现延迟高、P2P握手成功率低的问题。
不过,除了根服务器外,ZeroTier官方还提供了私有服务器:moon和planet。顾名思义,moon就是月亮,而月亮属于卫星,planet就是行星。实际上,ZeroTier官方服务器本质上也属于planet,是一种特殊的planet。planet的私有服务器稳定性比moon更高,因此,可通过在国内的云服务器上搭建一个私有的planet服务器,实现低延迟和高握手成功率。
优点
部署在云服务器上的ZeroTier服务器在P2P内网穿透时,只起到握手的作用,客户端之间的流量并不经过服务器,因此对云服务器的带宽要求不高,可以选择2-3M带宽的服务器。比如本人使用的是京东云55元/年的3M轻量云主机。
方法
服务端环境
ubuntu 22.04 server
在国内云服务器上安装ZeroTier服务端
ZeroTier提供了官方安装方法(见<https://zerotier.com/download/>):
curl -s https://install.zerotier.com | sudo bash
更改默认根服务器
使用下列命令可以查看根服务器:
zerotier-cli peers

官方提供了4个根服务器,延迟都比较高:

根服务器的信息存储在/var/lib/zerotier-one目录下的planet文件中。我在查找相关资料时发现,有的人直接将这个目录下planet文件拷贝替换客户端的planet,认为这样就可以通过私有云服务器实现P2P握手。
然而,这么做是大错特错的!因为这个planet指向的仍然是官方根服务器,如果不对这个planet做修改而替换客户端的planet,那么客户端连接的仍然是官方根服务器,而非你自己的云服务器!
接下来是修改planet的具体方法:
首先,下载zerotier源码:
$ git clone https://github.com/zerotier/ZeroTierOne
Cloning into 'ZeroTierOne'...
fatal: unable to access 'https://github.com/zerotier/ZeroTierOne/': GnuTLS recv error (-110): The TLS connection was non-properly terminated.
源码200M左右,国内的云服务器连接github很慢,所以我在自己电脑上挂代理下载了zip包,然后通过scp上传到云服务器(如果本地安装了linux环境,也可以在本地进行下列步骤),传输速度还不错:
$ scp d:\ZeroTierOne.zip root@xx.xx.xx.xx:~
root@xx.xx.xx.xx's password:
ZeroTierOne.zip 100% 172MB 1.3MB/s 02:10
切换到云服务器上,进入~目录,解压zip文件:
$ cd ~
# root @ lavm-et9uciusyk in ~ [11:12:56]
$ ls
ZeroTierOne.zip
$ unzip ZeroTierOne.zip
如果没有安装unzip,先安装:
sudo apt install unzip
ZeroTier的官方根服务器信息保存在ZeroTierOne/attic/world下的mkworld.cpp文件:
$ cd ~/ZeroTierOne/attic/world
$ ls
build.sh mkworld.cpp README.md world.bin world.c
$ vi mkworld.cpp
其次,修改mkworld.cpp文件
打开mkworld.cpp文件,里面有根服务器的信息:
// =========================================================================
// EDIT BELOW HERE
std::vector<World::Root> roots;
const uint64_t id = ZT_WORLD_ID_EARTH;
const uint64_t ts = 1567191349589ULL; // August 30th, 2019
// Los Angeles
roots.push_back(World::Root());
roots.back().identity = Identity("3a46f1bf30:0:76e66fab33e28549a62ee2064d1843273c2c300ba45c3f20bef02dbad225723bb59a9bb4b13535730961aeecf5a163ace477cceb0727025b99ac14a5166a09a3");
roots.back().stableEndpoints.push_back(InetAddress("185.180.13.82/9993"));
roots.back().stableEndpoints.push_back(InetAddress("2a02:6ea0:c815::/9993"));
// Miami
roots.push_back(World::Root());
roots.back().identity = Identity("de8950a8b2:0:1b3ada8251b91b6b6fa6535b8c7e2460918f4f729abdec97d3c7f3796868fb02f0de0b0ee554b2d59fc3524743eebfcf5315e790ed6d92db5bd10c28c09b40ef");
roots.back().stableEndpoints.push_back(InetAddress("207.246.73.245/443"));
roots.back().stableEndpoints.push_back(InetAddress("2001:19f0:9002:5cb:ec4:7aff:fe8f:69d9/443"));
// Tokyo
roots.push_back(World::Root());
roots.back().identity = Identity("34e0a5e174:0:93efb50934788f856d5cfb9ca5be88e85b40965586b75befac900df77352c145a1ba7007569d37c77bfe52c0999f3bdc67a47a4a6000b720a883ce47aa2fb7f8");
roots.back().stableEndpoints.push_back(InetAddress("147.75.92.2/443"));
roots.back().stableEndpoints.push_back(InetAddress("2604:1380:3000:7100::1/443"));
// Amsterdam
roots.push_back(World::Root());
roots.back().identity = Identity("992fcf1db7:0:206ed59350b31916f749a1f85dffb3a8787dcbf83b8c6e9448d4e3ea0e3369301be716c3609344a9d1533850fb4460c50af43322bcfc8e13d3301a1f1003ceb6");
roots.back().stableEndpoints.push_back(InetAddress("195.181.173.159/443"));
roots.back().stableEndpoints.push_back(InetAddress("2a02:6ea0:c024::/443"));
```
mkworld.cpp文件中有4个官方根服务器:洛杉矶、迈阿密、东京和阿姆斯特丹,可以直接删掉或注释掉。
在安装zerotier时,mkworld.cpp文件中的根服务器信息将写入planet,所以要修改planet文件,就要从mkworld.cpp文件入手。
打开/var/lib/zerotier-one/identity.public文件
$ vi /var/lib/zerotier-one/identity.public
b556e06c57:0:ba1e04bc5ecb946193315678d0372889c62abeaaaf397b0e929a09926b07d846ffdf1f4650593d85b43cff6962332621f3be609569eea8d527bd0cbad2fe60ce
将identity.public文件中的字符串替换mkworld.cpp文件Identity("")双引号中的内容,例如:
Identity("b556e06c57:0:ba1e04bc5ecb946193315678d0372889c62abeaaaf397b0e929a09926b07d846ffdf1f4650593d85b43cff6962332621f3be609569eea8d527bd0cbad2fe60ce")
IP地址改成云服务器的地址,例如:
roots.back().stableEndpoints.push_back(InetAddress("17.211.14.7/9993"));
服务器默认端口是9993,也可以根据实际情况修改。
再次,编译生成新的配置文件planet
修改好mkworld.cpp并保存后,执行下列命令:
$ ./build.sh
如果出现“permission denied: ./build.sh”,则修改build.sh的权限:
$ chmod +x build.sh
再次运行:
$ ./build.sh
出现如下错误:
zsh: ./build.sh: bad interpreter: /bin/bash^M: no such file or directory
这是由于build.sh文件格式为dos格式导致,解决方法如下:
$ vi build.sh
$ :set ff
$ :set fileformat=unix
$ :wq
重新运行安装命令:
$ ./build.sh
$ ./mkworld
命令运行后,将在目录中生成world.bin文件,将这个文件重命名为planet,这就是包含私有服务器地址信息的planet:
$ mv world.bin planet
##### 最后,将编译生成的planet复制到/var/lib/zerotier-one/目录中
$ cp planet /var/lib/zerotier-one/
<u>**这个planet需要保存好,之后用来替换客户端的planet**</u>
##### 重启服务
$ systemctl restart zerotier-one.service
查看服务状态
$ systemctl status zerotier-one.service
● zerotier-one.service - ZeroTier One
Loaded: loaded (/lib/systemd/system/zerotier-one.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2025-04-03 13:44:09 CST; 10s ago
Main PID: 151332 (zerotier-one)
Tasks: 23 (limit: 2234)
Memory: 4.1M
CPU: 56ms
CGroup: /system.slice/zerotier-one.service
└─151332 /usr/sbin/zerotier-one
Apr 03 13:44:09 lavm-et9uciusyk systemd[1]: Started ZeroTier One.
Apr 03 13:44:09 lavm-et9uciusyk zerotier-one[151332]: Starting Control Plane...
Apr 03 13:44:09 lavm-et9uciusyk zerotier-one[151332]: Starting V6 Control Plane...
安装控制台服务
参考官方文档(<https://key-networks.com/ztncui/#installation>):
$ curl -O https://s3-us-west-1.amazonaws.com/key-networks/deb/ztncui/1/x86_64/ztncui_0.8.14_amd64.deb
$ sudo apt install ./ztncui_0.8.14_amd64.deb
$ sudo sh -c "echo ZT_TOKENsudo cat /var/lib/zerotier-one/authtoken.secret > /opt/key-networks/ztncui/.env"
$ sudo sh -c "echo HTTPS_PORT=53443 >> /opt/key-networks/ztncui/.env"
(注:3443是控制台的端口,可以根据实际情况修改)
$ sudo sh -c "echo NODE_ENV=production >> /opt/key-networks/ztncui/.env"
$ sudo chmod 400 /opt/key-networks/ztncui/.env
$ sudo chown ztncui.ztncui /opt/key-networks/ztncui/.env
$ sudo systemctl restart ztncui
开放服务端口
在云服务器的防火墙中,放开9993的tcp和udp端口,以及3443的tcp端口。
在浏览器中输入:
https://云服务器IP:3443
注意:必须用“https",否则打不开!
进入控制台,登陆,用户名默认admin,密码默认password。
由于ip地址比较难记,为了方便,可以修改本机hosts文件,用一个容易记住的域名影射到云服务器ip上,例如:
myname.com xx.xx.xx.xx
之后,就可以通过https://myname.com:3443来访问控制台了。
本地客户端配置
windows
1.安装完成后,进入C:\ProgramData\ZeroTier\One,用之前编译生成的planet替换目录中的planet,然后运行“services.msc”进入服务,重启zerotier服务。
2.客户端join new network,输入控制台中的16位字符串,设置开机启动。
ubuntu
1.安装完成后,用之前编译生成的planet替换/var/lib/zerotier-one目录下的planet,然后运行下列命令:
sudo zerotier-cli join <控制台中的16位字符串,没有尖括号>
2.重启服务
sudo systemtcl restart zerotier-one.service
最后,通过ping命令试验一下是否握手成功
我设置的局域网如下图:

在本机(10.10.10.100)ping:


101是一台windows server,基本可以判定握手成功。

102是工作笔记本,从116ms骤降到6ms,可以判定握手成功。


103是一台ubuntu server,不知道是什么原因导致的问题。
在云服务器上,运行zerotier-cli peers命令:

可以看到,服务器本机不显示,四个客户端的角色均为leaf。
在本地ubuntu servers运行zerotier-cli peers:

可以看到云服务器角色是planet,除本机外的另外三个客户端角色均为leaf。
从客户端的端口来看,只有服务器和一台客户端的端口是9993,因为是在同一个路由下,所以我猜测是否跟这个有关系?但同一局域网的客户端难道不能都是同一端口?会有冲突?这个真不了解。
等去单位再试试,看看端口会不会有变化。