自建 Tailscale 服务并实现多设备组网互联

1. 简介

Tailscale 是基于 wireguard 的跨平台虚拟组网工具,可以非常方便地实现多个设备的 mesh 虚拟组网。组网后,设备间可以直接使用内网 IP / 域名进行通信,可以随意切换网络,不会引起地址变动。这在远程访问、自建服务等场景下是极为有用的。

举例而言,接入统一的 Tailscale 网络后,你可以在外随时随地访问家中的 NAS 设备而无需公网 IP 或 DDNS 服务,同时享受到 NAT 穿透带来的几乎满带宽的网速体验;你可以随处使用手机 / 平板通过 moonlight 串流家中的电脑进行 “云游戏 “,将强大的计算与渲染性能随身携带;对于基友小规模自建服务器联机游戏的场景,Tailscale 更是适合不过。

另外,Tailscale 还提供了不少非常实用的功能,如文件隔空投送 Taildrop、接入同网段内其他设备的 Subnet Router(可以远程共享 NAS、打印机)、内网 MagicDNS 模块、出口路由功能、内网暴露服务和分享目录、子网内部安全 SSH、与现有组网系统集成等等……Tailscale 通过 SDN 的设计,自动在各节点之间下发配置文件并协调密钥加密,解决了 wireguard 网络增删设备时需要手动配置所有节点,灵活性不够好的问题,并且还提供强大的端到端加密通信保护和基于零信任思想的 ACL 控制等安全机制。

唯一的缺点可能是,Tailscale 是国外服务,借用国外现成的 oauth 系统(比如谷歌账号等)进行账户管理,这在国内使用起来相对不是很方便。而且在国内,并没有现成的 Tailscale Derper 中继服务器,直接使用 Tailscale 默认的中继服务会出现延迟爆炸的问题。

我们可以通过在国内的服务器上自行搭建开源的控制服务端 Headscale,配合自建官方开源的 Derper 中继服务器,来彻底解决这些问题,将 Tailscale 的各种强大功能充分发挥出来。

2. 大致通信原理

处于同一个 Telnet 内的设备之间将要进行通信时,Tailscale 会自动尝试 NAT 打洞进行 P2P 直连,穿透后设备等同于直连,设备间通信延迟极低。Tailscale 团队对 NAT 穿透有较为深入的研究,其穿透技术对各种类型的 NAT 的都有相当不错的效果。

如果打洞无法成功,Tailscale 将通过 Derp 中继协议进行端到端加密的 TCP 中继,自动根据延迟等因素选用最优的中继服务器进行连接。TCP 中继有效解决了 wireguard UDP 公网大流量被 QOS 的问题。

3. 服务端部署

自建 Tailscale 需要搭建两个服务端:控制平面 Headscale 和中继服务器 Derper。

3.1. Headscale 控制平面部署

Headscale 是控制平面部分,负责作为中心组件协调各节点之间的连接、下发各种配置信息等。

这里使用 docker 部署,caddy 反代并自动申请证书,另外还部署了开源项目 Headscale Admin 面板,用来进行简单的服务端控制。

docker-compose 文件如下,这里使用了 Traefik 作为反代服务器,其配置非常适应 docker 环境下的部署:

services:
  headscale:
    container_name: headscale
    image: headscale/headscale:latest
    restart: unless-stopped
    volumes:
      - ./headscale-config:/etc/headscale
      - ./headscale-data:/var/lib/headscale
      - ./headscale-run:/var/run/headscale
    entrypoint: headscale serve
    networks:
      - traefik
    labels:
      traefik.enable: true
      traefik.http.routers.headscale.tls: true
      traefik.http.routers.headscale.tls.certresolver: letsencrypt
      traefik.http.routers.headscale.entrypoints: https
      traefik.http.routers.headscale.rule: Host(`<Headscale服务器域名>`)
      traefik.http.services.headscale.loadbalancer.server.port: 8080

  # admin panel
  headscale-admin:
    image: goodieshq/headscale-admin:latest
    container_name: headscale-admin
    restart: unless-stopped
    user: "1003"
    depends_on:
      - headscale
    networks:
      - traefik
    labels:
      traefik.enable: true
      traefik.http.routers.headscale-admin.tls: true
      traefik.http.routers.headscale-admin.tls.certresolver: letsencrypt
      traefik.http.routers.headscale-admin.entrypoints: https
      traefik.http.routers.headscale-admin.rule: Host(`<Headscale服务器域名>`) && PathPrefix(`/admin`)
      traefik.http.services.headscale-admin.loadbalancer.server.port: 80

除了 docker-compose 文件之外,还需要将 headscale 项目的 config-example.yaml 复制到./headscale-config 目录下,改名 config.yaml。对下面这些项做修改:

...
server_url: https://<Headscale服务器域名>:<工作端口>
...
prefixes:
  v4: 100.64.0.0/10
  # v6: fd7a:115c:a1e0::/48		# 把IPv6注释掉,按需
...
derp:
  server:
    enabled: false				# 禁用内置derper服务器(直接用官方的会更好)
...

全部配置完成之后,启动容器,Headscale 即可在 https://<Headscale服务器域名>:<工作端口> 访问到。记得要在 Traefik 中配置泛域名 HTTPS 证书申请

3.1.1. 配置管理面板

前面的 docker-compose 中已经部署了 goodieshq/headscale-admin 管理面板的镜像,可以通过 https://<Headscale服务器域名>:<工作端口>/admin 地址访问到。通过管理面板可以快速添加、管理与删除节点、修改配置,不用再每次上服务器跑命令行。

管理面板需要 Headscale 的 API Key 以访问其接口。在 docker 容器部署完毕开始运行后,在服务器执行如下命令:

docker exec headscale headscale apikey create

这样会创建一个新的 Headscale API Key。将此 Key 填入管理面板的 Settings 页面并保存,即可开始使用管理面板。

3.2. Derper 中继部署

Derper 中继服务器的部署仍然使用 docker,采取 tailscale 官方的镜像搭建。在环境变量配置中打开 verify-client 验证客户端,防止自己的 Derper 中继服务器被别人白嫖。

3.2.1. 部署 Derper 中继

Derper 服务器的 docker-compose 文件如下:

derper:
  container_name: derper
  image: fredliang/derper:latest
  restart: unless-stopped
  ports:
    - <Derper工作端口>:<Derper工作端口>
    - <STUN工作端口>:<STUN工作端口>/udp
  environment:
    DERP_DOMAIN: "<Derper所在服务器IP>"
    DERP_CERT_MODE: "manual"
    DERP_CERT_DIR: "/cert"
    DERP_ADDR: "0.0.0.0:<Derper工作端口>"
    DERP_STUN: "true"
    DERP_STUN_PORT: "<STUN工作端口>"
    DERP_HTTP_PORT: "-1"
    DERP_VERIFY_CLIENTS: "false"
    DERP_VERIFY_CLIENT_URL: "https://<Headscale服务器域名>:<工作端口>/verify"
  volumes:
    - "./cert:/cert"

其中,

  • DERP_ADDR 中设置 Derper 中继的工作端口(TCP)
  • DERP_STUN_PORT 中设置 Derper STUN 服务的工作端口(UDP)
  • 将这两个端口暴露给外部以对外提供服务
  • DERP_DOMAIN 设置当前所在服务器的 IP 地址,以使用内置的自签名证书服务

全部设置完毕后,启动容器即可。接下来使用浏览器打开 http://<Derper所在服务器IP>:<工作端口>,观察其是否有正常的 HTTP response(应该会报 HTTPS 证书错误,是正常现象),如果能访问到表明部署成功。

3.2.1.1. DERP_DOMAIN 为什么设置为 IP?为什么用自签名证书?

一般来说,Derper 中继需要一个正常的 TLS 证书用于中继 - 客户端之间的加密通信。但是由于免费的 TLS 证书必须绑定域名,而国内对于未备案服务器绑定域名提供 HTTPS 服务控制相当严格,导致自建 Derper 中继也受到影响。

不过很巧的是,在 Issue #11776 · tailscale/tailscale 中,bradfitz 最终决定为 Derper 中继提供自签名证书支持 —— 你可以让 Derper 自动生成基于自身 IP 的自签名证书,然后把证书指纹发布给客户端,这样就可以正常进行中继通信了。

这么做的优点有哪些呢?

首先最重要的,就是不用再操心证书续期问题。其次,由于不需要绑定域名,因此 Derper 服务可以在国内服务器上随意自建,不用再担心服务被封锁的问题。

另外,Tailscale 各节点间的通信本身就是端到端加密的,引入自签名证书并不会破坏系统本身的安全性,因此这里自签名证书可以放心使用。

第一次运行 Derper 中继服务器时,它会检测到 DERP_DOMAIN 为 IP 地址,随后自动以此 IP 地址生成自签名证书,储存到 cert 目录下。并且 Derper 会在控制台上输出指导信息,告诉你如何为客户端配置自签名证书指纹。

在 Derper 容器启动后,请使用 docker compose logs -f 命令查看指导信息,并将 sha256-raw:xxxxxxxxxxxxxxxx 的格式的证书指纹保存下来备用

3.2.2. 将自建 Derper 加入中继节点列表

Derper 部署完成之后,需要修改 headscale 的 derp-map 配置,将此 Derper 中继加入中继服务器列表中。

./headscale-config/config.yaml 中,设置:

derp:
...
  paths:
    - /etc/headscale/derp.yaml

然后在./headscale-config/derp.yaml 中添加中继区域和中继节点信息:

Regions:
  900:
    RegionId: 900
    RegionCode: <区域缩写(两个字母)>
    RegionName: <区域全名>
    Nodes:
      - Name: <给节点起个名字>
        RegionId: 900
        HostName: <Derper所在服务器IP>
        CertName: "sha256-raw:xxxxxxxxxxxxxxxxx"	# 刚刚记录下来的自签名证书指纹
        IPv4: <Derper所在服务器IPv4地址>
        # IPv6: <Derper所在服务器IPv6地址>
        DERPPort: <Derper工作端口>
        STUNPort: <STUN工作端口>
        STUNOnly: false
        InsecureForTests: true

其中区域名称、区域编号等可以自行设置。CertName 中填写的是 Derper 启动时在控制台输出的自签名证书指纹。

3.2.3. 防止 Derper 被白嫖

为了避免自己的 Derper 服务器被别人白嫖,我们需要进一步启用客户端认证。所有 v0.23.1 及以后的 Headscale 服务端提供了一个 /verify 接口用于进行客户端身份验证。相比于传统的 Tailscale 客户端验证,这个方法要简单方便得多,具体的配置方法如下:

在 Derper 中继的 docker-compose.yml 的环境变量部分,

  • 设置 DERP_VERIFY_CLIENTS: "false"
  • 设置 DERP_VERIFY_CLIENT_URLhttps://<Headscale服务器域名>:<工作端口>/verify

这样,每当有客户端试图连接到 Derper 时,它会通过 Headscale 的接口查询验证客户端身份,进而决定接收 / 拒绝请求,有效防止被其它未知客户端白嫖。

3.3. 服务器防火墙放行

服务器的防火墙 / 安全组需要放行如下这些端口,以允许 Headscale 和中继 Derper 对外提供服务:

  • Headscale 服务端工作端口(TCP)
  • Derper 中继工作端口(TCP)
  • Derper STUN 服务工作端口(UDP)

4. 客户端接入

4.1. Windows & Mac & Linux

直接安装官方客户端,按通过命令行登录自定义 headscale 服务器即可

tailscale up --login-server=https://<Headscale服务器域名>:<工作端口> --unattended

4.2. 安卓 & iOS

正常情况下,可以使用官方客户端,其 UI 十分美观好用。

在国内的网络环境下,需要对梯子进行兼容,详见下面” 梯子兼容问题 “

5. 其他细节问题

5.1. 梯子兼容问题

国内普遍网络环境问题,导致平时经常需要使用梯子。tailscale 与梯子应用之间存在一些协调性问题,比如安卓平台只能同时运行一个 VPN 应用,导致 tailscale 应用和梯子 APP 无法共存,只能同时开启一个;另外,Windows 平台上梯子启用 TUN 模式时,若不对路由等进行额外处理,tailscale 也无法工作。

5.1.1. 桌面平台:修改梯子配置

要使 Tailscale 和梯子互不冲突,可以通过设定梯子的规则和 TUN 绕过 Tailscale 网段工作,以避免错误地路由其流量。以 Clash.Meta 为例,如下设置配置文件:

tun:
  ...
  route-exclude-address:	# 绕过tailscale网段
    - "100.64.0.0/10"
...
hosts:
  # 可以在这里加Tailscale内网DNS解析条目,以代替MagicDNS的功能,类似
  # pc.YOUR-DOMAIN.com: 100.64.111.111
  # phone.YOUR-DOMAIN.com: 100.64.111.112
...
dns:
  ...
  use-hosts: true
  fake-ip-filter:       # fake ip 白名单列表
    - "<Headscale服务器域名>"
    ...
...
rules:
  # 不路由Tailscale流量
  - IP-CIDR,100.64.0.0/10,DIRECT,no-resolve
  - DOMAIN-SUFFIX,<Headscale服务器域名>,DIRECT,no-resolve
  ...

5.1.2. 安卓平台

安卓平台的 Tailscale 与梯子共存,已经有较为完善的方案。详见:《安卓平台实现 Tailscale 与代理 APP 共存》

5.1.3. iOS 平台

没有苹果设备,因此缺乏这方面经验,欢迎评论区补充可用方案~

5.2. MagicDNS 相关问题

Tailscale 官方提供了 MagicDNS 功能,在 admin 面板自定义各个主机名后,就可以通过 MagicDNS 用自定义的主机域名来访问到各主机。

不过事实上,考虑到和梯子的协同问题,为了避免出现 BUG,目前各设备上推荐暂不启用 MagicDNS。有两种方法实现类似 MagicDNS 的效果:

  1. 在梯子配置文件的 hosts 段中,针对各主机手动设置 hosts 条目
  2. 在 headscale 域名所在服务商处(如 Cloudflare)手动添加对应主机的 DNS 条目

这些都可以达到类似的效果。

6. 补:zerotier 和 tailscale 的区别

粗略来看,两者都是虚拟组网方案,在性能上也区别不大。不过在技术实现细节上有一定的差异:

6.1. 网络底层实现

  • zerotier 是二层(数据链路层)虚拟组网方案,更贴近理解上的 “虚拟局域网” 的概念,对于大部分协议都有比较好的支持
  • tailscale 是基于 wireguard 的三层(网络层)点对点虚拟组网方案,其主要支持三层协议的点对点通信。某些需要二层支持的协议 Tailscale 无法使用。比如,截至目前(2024.10)并不支持 UDP 广播、mDNS、Bonjour 等,导致一些设备互联 / 游戏等场景下的的局域网设备发现无法正常工作。

6.2. 通信协议实现

  • zerotier 通过 TCP 协议直连通信。自建 moon 服务器进行中继时默认使用 UDP 通信,不过可以在客户端手动切换到 TCP 中继模式
  • tailscale 基于 wireguard,直连时使用 UDP,高峰期或者大流量下可能受到运营商 QoS。自建 derp 服务器中继则是使用 TCP 进行中继通信,相对较为稳定。

6.3. 附属生态与功能

  • tailscale 有非常丰富的附属功能,如文件投送、子网路由、出口路由、自定义 DNS 等等。
  • zerotier 专注于实现虚拟组网,附属功能较少。

另外,似乎有评价说相较于 Zerotier,Tailscale 的易用性、UI 美观度等各方面都要更强一些。这个仁者见仁智者见智吧,有需求的话可以亲自测试,感受一下两者的区别。


自建 Tailscale 服务并实现多设备组网互联
https://blog.openyq.top/posts/64921/
作者
yqs112358
许可协议