使用 Tailscale + Headscale + 自建 DERP 中继
官方仓库: https://github.com/juanfont/headscale
官方教程:https://headscale.net/stable/setup/requirements/
部署
基于 ecs 公网 域名配置,防火墙相关端口自行放行:
协议 端口
tcp headscale web server 端口
tcp derp 的 https 端口
udp derp 的 stun 端口
#headscale 部署和设置
# 下载Headscale
wget -O /usr/bin/headscale https://github.com/juanfont/headscale/releases/download/v0.23.0/headscale_0.23.0_linux_amd64
chmod a+x /usr/bin/headscale
# 配置文件
mkdir /etc/headscale/
# 下载配置,注意版本选择
# 下载配置 headscale/config-example.yaml
wget -O /etc/headscale/config.yaml https://raw.githubusercontent.com/juanfont/headscale/refs/heads/main/config-example.yaml
# 下载二进制同版本的示例配置文件
wget -O /etc/headscale/config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.23.0/config-example.yaml
创建 headscale daemon systemd 后台文件,官方示例文件:
headscale/docs/packaging/headscale.systemd.service
wget -O /etc/systemd/system/headscale.systemd.service https://raw.githubusercontent.com/juanfont/headscale/refs/heads/main/docs/packaging/headscale.systemd.service
运行在非 root 用户下,添加用户:
useradd \
--create-home \
--home-dir /var/lib/headscale/ \
--system \
--user-group \
--shell /usr/sbin/nologin \
headscale
mkdir -p /var/run/headscale/
#创建空的 SQLite 数据库文件和 derp 文件:
touch /var/lib/headscale/db.sqlite /etc/headscale/derp.yaml
chown -R headscale:headscale /var/run/headscale/ /var/lib/headscale
chmod a+r /etc/headscale/config.yaml /etc/headscale/derp.yam
接下来 vi /etc/headscale/config.yaml 修改配置文件一些内容:
# server_url 写外网访问的 ip+端口
# 由于是ecs,所以写公网 IP,以及后面的端口要和 listen_addr 的一致
# 80 443 8080 需要备案
server_url: http://<ecs_public_ip>:8081
# 改为四个0,或者对应公网 IP 的内网网卡 IP 都行
listen_addr: 0.0.0.0:8081
# ip_prefixes 下的 ipv6 关闭了
ip_prefixes:
# - fd7a:115c:a1e0::/48
# 如果使用 Aliyun ecs,ipv4 网段需要修改成其他的,因为 Aliyun 底层的 apt 源等都在这个范围内
# 如果 Aliyun ecs 修改此网段,建议看完本章文章再操作,因为后续 tailscale 要修改源码
- 100.64.0.0/10
derp:
server:
# 不适用 headscale 内嵌的 derper 服务器
enabled: false
# 注释掉不使用官方的 derp 服务
# urls:
# - https://controlplane.tailscale.com/derpmap/default
# 把 paths: [] 修改成使用本地 derp 信息
paths: [/etc/headscale/derp.yaml] # 参考:https://raw.githubusercontent.com/juanfont/headscale/refs/heads/main/derp-example.yaml
# 关闭 headscale 自动更新,避免 break change 无法启动
disable_check_updates: true
dns_config:
# 改为 false 不覆盖本地 DNS
override_local_dns: false
# 关闭 magic_dns
magic_dns: false
# 设置为你自己的标识,否则后续 tailscale 端连接上显示是 user@example.com
base_domain: xxx
# 随机端口要打开, tailscale 客户端会使用41641 端口建立 wireguard 链接,这个端口会被中间网络设备阻止
randomize_client_port: true
安装和设置 headscale 补全,因为 headscale 是 daemon 和 cli 两部分,daemon 起来后 cli 很多命令可以操作
apt update
apt install -y bash-completion
headscale completion bash > /etc/bash_completion.d/headscale
. /etc/bash_completion.d/headscale
启动 headscale daemon 进程:
# 测试文件
headscale configtest
headscale serve
# 配置文件没问题就 ctrl +c 取消掉使用 systemd 启动
chown -R headscale:headscale /var/lib/headscale
systemctl daemon-reload
systemctl enable --now headscale
derper 部署
Tailscale Derp 服务器见:https://tailscale.com/kb/1118/custom-derp-servers
headscale 是控制层面,下发信息和路由配置,而 derper 是中继和打洞服务器,利用修改版本的 stun 协议打洞,例如两个无公网 IP 但是可以访问到公网的客户端,客户端和另一个客户端建立连接都是先 连 derp 看看自己和对端能否打洞成功,成功就直连对方,否则就走 derper 中继来转发。
先查看机器上的 iptables 模式:
$ iptables -w -V
iptables v1.4.21
iptables v1.8.4 (legacy)
#----
iptables v1.8.9 (nf_tables)
安装Golang
- 从 golang 官网下载
安装文档:https://golang.org/doc/install 解压和安装
sudo tar -C /usr/local -xzf <文件路径>
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.23.4.linux-amd64.tar.gzsudo mkdir -p /usr/local/gopath/bin
编辑文件,配置环境变量
sudo vim /etc/profile
export GOROOT=/usr/local/go
export GOPATH=/usr/local/gopath
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOROOT/bin
export PATH=$PATH:$GOPATH/bin
source /etc/profile完成安装,运行go version检查是否成功
go version
配置代理
go env -w GOPROXY=https://goproxy.cn,direct
安装derper
go install tailscale.com/cmd/derper@main
无域名IP配置,无需求跳过
进入 /usr/local/gopath/pkg/mod/tailscale.com@xxx/cmd/derper,其中xxx会根据版本变化,可以用tab补全
编辑 cert.go,注释掉下面三行并保存
同路径下执行go build -o /etc/derp/derpercd /etc/derp,查看是否成功
自签一个假的域名,用来启动derper服务(其中derp.myself.com为假域名,可以替换):
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout /etc/derp/derp.myself.com.key -out /etc/derp/derp.myself.com.crt -subj "/CN=derp.myself.com" -addext "subjectAltName=DNS:derp.myself.com"
启动服务
systemctl start derper
systemctl enable derper
在云服务管理中开启 udp 3478 端口,以及tcp 33445端口
在本地浏览器打开 https://<ip地址>:33445/ ,查看是否成功
为了防止 derper 被白嫖,所以 官方文档 推荐开启 --verify-clients 选项,然后同时部署一个 tailscale 客户端,derper 会从这个 tailscale 的 sock 文件获取所有认证过的 peer 信息,所以上面挂载 sock 和 depends_on 以及使用同一版本的 derper 和 tailscale 。
修改 headscale 指定的 derp 文件 vi /etc/headscale/derp.yaml 的内容:
# https://github.com/tailscale/tailscale/blob/main/tailcfg/derpmap.go#L71
regions:
# 900-999 的 region id 是给预留的
# 有条件的可以多个 derper
900:
regionid: 900
regioncode: custom
regionname: custom_name
nodes:
- name: 900a
regionid: 900
hostname: my.mydomain.no
ipv4: <ecs_public_ip>
#ipv6: "2604:a880:400:d1::828:b001"
derpport: 12345 # https 端口
stunport: 3478 # udp port
stunonly: false
insecurefortests: true
# 901:
# regionid: 901
# regioncode: hs
# regionname: Huawei Shanghai
# nodes:
# - name: 901a
# regionid: 901
# hostname: xxxx
# ipv4: xxxx
# stunport: 3478
# stunonly: false
# derpport: 12345
# insecurefortests: false
创建 authkeys
Tailscale 中有一个概念叫 tailnet,你可以理解成租户,租户与租户之间是相互隔离的,具体看参考 Tailscale 的官方文档: What is a tailnet。
Headscale 也有类似的实现叫 user,即用户。我们需要先创建一个 user,以便后续客户端接入,例如:
headscale user create default
其他客户端接入需要首先在服务端生成 pre-authkey 的 key :
生成一个过期时间 365d 且可以重复使用的 authkey
headscale preauthkeys --user default create --reusable --expiration 365d
查看已经生成的 key:
headscale preauthkeys --user default list
tailscale 也是分为 tailscaled 的 daemon 和 tailscale 的 cli 工具,windows、Linux 以及安卓的 Magisk 模块等都可以使用 cli 工具操作和排查,这点很重要。
下面是 tailscale up 时候一些常用通用选项:
- --login-server: 指定使用的中央服务器地址(必填)
--advertise-routes: 向中央服务器报告当前客户端处于哪个内网网段下, 便于中央服务器让同内网设备直接内网直连(可选的)或者将其他设备指定流量路由到当前内网(可选),多条路由英文逗号隔开
--accept-routes: 是否接受中央服务器下发的用于路由到其他客户端内网的路由规则(可选)
--accept-dns: 是否使用中央服务器下发的 DNS 相关配置(可选, 推荐关闭)
--hostname: 设置 machine name,否则默认会以 hostname 注册上去,特别安卓的 hostname 无法修改
tailscale cli 官方文档 https://tailscale.com/kb/1080/cli,也可以自己 tailscale --help 看命令帮助。
Linux 接入
derp 上的客户端
先接入 derp 也就是 ecs 上那个 tailscale:
tailscale up --login-server=http://<HEADSCALE_PUB_ENDPOINT>:8081 --accept-routes=true --hostname ecs --accept-dns=false --authkey 49f9cd....
查看信息
headscale node list
Linux 上 tailscale 会利用 tun 创建网卡,路由表在 52 里:
ip route show table 52
另外要注意,由于 derper 依赖这个 tailscale,所以这个 tailscale 不要乱重启(因为是授权信息来源),可能会导致其他端会断开一下子,例如我就遇到安卓端玩游戏会断开下
Linux 客户端
官方相关文档:
https://tailscale.com/kb/1031/install-linux
https://pkgs.tailscale.com/stable/#static
curl -fsSL https://tailscale.com/install.sh | sh
安卓
https://github.com/tailscale/tailscale-android
推荐 https://f-droid.org/packages/com.tailscale.ipn/ 下载,安装后在 右上角 - Accounts - 三个点 - Settings Accounts Use an alternate server,输入 http://xxx:8081,然后下面 Use an auth key 可能会没使用 authkey 跳转到浏览器出现一个 headscale nodes register ... --key nodekey:xxxx ,所以我们需要回到 headscale 上命令授权:
日志里会打印长串 key,所以不需要在其他端复制 nodekey
journalctl -xe --no-pager -u headscale | grep nodekey
headscale nodes register --user default --key nodekey:xxxxx
对于一些没有浏览器也没 tailscale cli 的都可以这样手动授权下。安卓上点击每个 peer 进去的右上角图标等于 tailscale ping xxx ,会显示能否直连和延迟。也可以后续使用 Magisk tailscale,那样可以有 cli 了,另外 apk 是使用 V-P-N 形式,断网和切换流量会断开,而 Magisk tailscale 则不会。
tailscale client 使用
每个 tailscale 端都可以执行命令来查看和排查一些信息。
# debug 命令隐藏在 --help 了,可以 tailscale debug --help 自行查看
# 打印 derp-map 信息
tailscale debug derp-map
查看和检测当前网络,会输出当前 derp 服务器信息:
$ tailscale netcheck
Report:
* UDP: true
* IPv4: yes, xx.x.xx.xxx:54417
* IPv6: no, but OS has support
* MappingVariesByDestIP:
* PortMapping:
* Nearest DERP: tx
* DERP latency:
- custom: 500µs (tx)
tailscale ping 命令可以用于测试 IP 连通性, 同时可以看到时如何连接目标节点的. 默认情况下 Ping 命令首先会使用 Derper 中继节点通信, 然后尝试 P2P 连接; 一旦 P2P 连接成功则自动停止 Ping:
$ tailscale ping 100.64.0.3
pong from redmi8 (100.64.0.3) via DERP(custom) in 30ms
pong from redmi8 (100.64.0.3) via 192.168.0.107:47316 in 32ms
status 查看以及 peer 的信息:
tailscale status
tailscale status --json
修改当前节点信息,支持修改的属性 –help 自行查看:
# tailscale set --help
tailscale set --hostname=xxx
或者可以 down 后 up 带单一需要修改的参数执行下:
tailscale down
tailscale up --xxx
# 然后会打印全部参数,复制执行下
derp 调试
curl -vk https://127.0.0.1:12345/debug/vars
打通内网
Linux 端都要开启转发,windows 和安卓转发自行查找怎么配置。
echo 'net.ipv4.ip_forward = 1' | tee /etc/sysctl.d/ipforwarding.conf
echo 'net.ipv6.conf.all.forwarding = 1' | tee -a /etc/sysctl.d/ipforwarding.conf
sysctl -p /etc/sysctl.d/ipforwarding.conf
然后在 server 端查看 node ID :
$ headscale node list
An updated version of Headscale has been found (0.23.0-beta1 vs. your current v0.22.3). Check it out https://github.com/juanfont/headscale/releases
ID | Hostname | Name | MachineKey | NodeKey | User | IP addresses | Ephemeral | Last seen | Expiration | Online | Expired
1 | ecs | ecs | [3ixoT] | [VFZTB] | default | 100.64.0.1, | false | 2024-07-26 07:12:50 | 0001-01-01 00:00:00 | online | no
2 | localhost | ax18 | [egfcx] | [LzS5J] | default | 100.64.0.2, | false | 2024-07-26 07:13:14 | 0001-01-01 00:00:00 | online | no
3 | localhost | redmi8 | [uqIVP] | [l6sL7] | default | 100.64.0.3, | false | 2024-07-26 07:13:06 | 0001-01-01 00:00:00 | online | no
假设 ID==1 的局域网是 192.168.31.0/24 网段,我们希望其他 ID 设备上能访问到,先查看路由:
$ headscale routes list
ID | Machine | Prefix | Advertised | Enabled | Primary
.....
1 | ax18 | 192.168.31.0/24 | true | false | false
headscale routes enable -r 1
其他节点查看路由结果:
$ ip route show table 52 | grep "192.168.31.0/24"
192.168.31.0/24 dev tailscale0
其他节点启动时需要增加 --accept-routes=true 选项来声明 “我接受外部其他节点发布的路由”。
现在你在任何一个 Tailscale 客户端所在的节点都可以 ping 通家庭内网的机器了,你在公司或者星巴克也可以像在家里一样用同样的 IP 随意访问家中的任何一个设备。
一个正在运行的节点增加路由可以使用 set 命令:
多条用英文逗号间隔
tailscale set --advertise-routes xx.xx.xx.0/24,xx,xxx.xxx.00.00/16
信息修改
例如安卓的 hostname 由于没有 cli 无法修改,而默认 db 使用 sqlite,可以修改:
$ sqlite3 /var/lib/headscale/db.sqlite
.tables
update machines set given_name="redmi8" where id=3;
# ip_addresses
修改 ip 需要先 tailscale down 改好后再 tailscale up
其他的修改自己琢磨。
源码修改 tailscale 网段
修改 tailscale 避免阿里云 100.64.0.0/16 上出问题,见漠然博客阿里云安装客户端后无法更新软件
https://zhangguanzhang.github.io/2024/07/25/headscale/#/%E4%BF%A1%E6%81%AF%E4%BF%AE%E6%94%B9
