使用Tailscale搭建VPN专线

Tailscale 是什么

Tailscale 是一种基于 WireGuard 的虚拟组网工具.

与 OpenVPN 之流相比还是能甩好几十条街的,Tailscale 虽然在性能上做了些许取舍,但在功能和易用性上绝对是完爆其他工具:

  1. 开箱即用
  • 无需配置防火墙
  • 没有额外的配置
  1. 高安全性/私密性
  • 自动密钥轮换
  • 点对点连接
  • 支持用户审查端到端的访问记录
  1. 在原有的 ICE、STUN 等 UDP 协议外,实现了 DERP TCP 协议来实现 NAT 穿透
  2. 基于公网的控制服务器下发 ACL 和配置,实现节点动态更新
  3. 通过第三方(如 Google) SSO 服务生成用户和私钥,实现身份认证

简而言之,我们可以将 Tailscale 看成是更为易用、功能更完善的 WireGuard。

Headscale 是什么

Tailscale 的控制服务器是不开源的,而且对免费用户有诸多限制,这是人家的摇钱树,可以理解。好在目前有一款开源的实现叫 Headscale,这也是唯一的一款,希望能发展壮大。

Headscale 由欧洲航天局的 Juan Font 使用 Go 语言开发,在 BSD 许可下发布,实现了 Tailscale 控制服务器的所有主要功能,可以部署在企业内部,没有任何设备数量的限制,且所有的网络流量都由自己控制

Headscale 部署

Headscale 部署很简单,推荐直接在 Linux 主机上安装

首先需要到其 GitHub 仓库的 Release 页面下载最新版的二进制文件。

1
2
3
4
$ wget --output-document=/usr/local/bin/headscale \
https://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>
$ chmod +x /usr/local/bin/headscale

创建配置目录:

1
$ mkdir -p /etc/headscale

创建目录用来存储数据与证书:

1
$ mkdir -p /var/lib/headscale

创建空的 SQLite 数据库文件:

1
$ touch /var/lib/headscale/db.sqlite

创建 Headscale 配置文件:

1
$ wget https://github.com/juanfont/headscale/raw/main/config-example.yaml -O /etc/headscale/config.yaml
  • 修改配置文件,将 server_url 改为公网 IP 或域名。如果是国内服务器,域名必须要备案。我的域名无法备案,所以我就直接用公网 IP 了。

  • 如果暂时用不到 DNS 功能,可以先将 magic_dns 设为 false。

  • server_url 设置为 http://<PUBLIC_IP>:8080,将 <PUBLIC_IP> 替换为公网 IP 或者域名。

  • 可自定义私有网段,也可同时开启 IPv4 和 IPv6:

    1
    2
    3
    ip_prefixes:
    # - fd7a:115c:a1e0::/48
    - 10.1.0.0/16

创建 SystemD service 配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# /etc/systemd/system/headscale.service
[Unit]
Description=headscale controller
After=syslog.target
After=network.target
[Service]
Type=simple
User=headscale
Group=headscale
ExecStart=/usr/local/bin/headscale serve
Restart=always
RestartSec=5
# Optional security enhancements
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/headscale /var/run/headscale
AmbientCapabilities=CAP_NET_BIND_SERVICE
RuntimeDirectory=headscale
[Install]
WantedBy=multi-user.target

创建 headscale 用户:

1
$ useradd headscale -d /home/headscale -m

修改 /var/lib/headscale 目录的 owner:

1
$ chown -R headscale:headscale /var/lib/headscale

修改配置文件中的 unix_socket

1
unix_socket: /var/run/headscale/headscale.sock

Reload SystemD 以加载新的配置文件:

1
$ systemctl daemon-reload

启动 Headscale 服务并设置开机自启:

1
$ systemctl enable --now headscale

查看运行状态:

1
$ systemctl status headscale

查看占用端口:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ss -tulnp|grep headscale
tcp LISTEN 0 1024 [::]:9090 [::]:* users:(("headscale",pi
d=10899,fd=13))
tcp LISTEN 0 1024 [::]:50443 [::]:* users:(("headscale",pi
d=10899,fd=10))
tcp LISTEN 0 1024 [::]:8080 [::]:* users:(("headscale",pi
d=10899,fd=12))

ailscale 中有一个概念叫 tailnet,你可以理解成租户,租户与租户之间是相互隔离的,具体看参考 Tailscale 的官方文档: What is a tailnet。Headscale 也有类似的实现叫 namespace,即命名空间。我们需要先创建一个 namespace,以便后续客户端接入,例如

1
headscale namespaces create vpn

查看命名空间:

1
2
3
4
5
$ headscale namespaces list
ID | Name | Created
1 | vpn | 2022-03-09 06:12:06

Tailscale 客户端接入

Linux

Tailscale 官方提供了各种 Linux 发行版的软件包,但国内的网络你懂得,软件源根本用不了。好在官方还提供了 静态编译的二进制文件,我们可以直接下载。例如:

1
$ wget https://pkgs.tailscale.com/stable/tailscale_1.22.2_amd64.tgz

解压:

1
2
3
4
5
6
7
$ tar zxvf tailscale_1.22.2_amd64.tgz
x tailscale_1.22.2_amd64/
x tailscale_1.22.2_amd64/tailscale
x tailscale_1.22.2_amd64/tailscaled
x tailscale_1.22.2_amd64/systemd/
x tailscale_1.22.2_amd64/systemd/tailscaled.defaults
x tailscale_1.22.2_amd64/systemd/tailscaled.service

将二进制文件复制到官方软件包默认的路径下:

1
2
$ cp tailscale_1.22.2_amd64/tailscaled /usr/sbin/tailscaled
$ cp tailscale_1.22.2_amd64/tailscale /usr/bin/tailscale

将 systemD service 配置文件复制到系统路径下:

1
$ cp tailscale_1.22.2_amd64/systemd/tailscaled.service /lib/systemd/system/tailscaled.service

将环境变量配置文件复制到系统路径下:

1
$ cp tailscale_1.22.2_amd64/systemd/tailscaled.defaults /etc/default/tailscaled

启动 tailscaled.service 并设置开机自启:

1
$ systemctl enable --now tailscaled

查看服务状态:

1
$ systemctl status tailscaled

Tailscale 接入 Headscale:

1
2
# 将 <HEADSCALE_PUB_IP> 换成你的 Headscale 公网 IP 或域名
$ tailscale up --login-server=http://xx.xx.xx.xx:8080 --accept-dns=false --advertise-routes=192.168.xx.0/24

这里推荐将 DNS 功能关闭,因为它会覆盖系统的默认 DNS。如果你对 DNS 有需求,可自己研究官方文档,这里不再赘述。

执行完上面的命令后,会出现下面的信息:

1
2
3
4
5
To authenticate, visit:
http://xxxxxx:8080/register?key=905cf165204800247fbd33989dbc22be95c987286c45aac303393704
1150d846

在浏览器中打开该链接,就会出现如下的界面

将其中的命令复制粘贴到 headscale 所在机器的终端中,并将 NAMESPACE 替换为前面所创建的 namespace。

1
2
$ headscale -n vpn nodes register --key 905cf165204800247fbd33989dbc22be95c987286c45aac3033937041150d846
Machine register

注册成功,查看注册的节点:

1
2
3
4
5
6
7
8
9
$ headscale nodes list
ID | Name | NodeKey | Namespace | IP addresses | Ephemeral | Last seen | Onlin
e | Expired
1 | coredns | [Ew3RB] | vpn | 10.1.0.1 | false | 2022-03-20 09:08:58 | onlin
e | no

回到 Tailscale 客户端所在的 Linux 主机,可以看到 Tailscale 会自动创建相关的路由表和 iptables 规则。路由表可通过以下命令查看

1
ip route show table 52

macOS

macOS 有 3 种安装方法:

这三种安装包的核心数据包处理代码是相同的,唯一的区别在于在于打包方式以及与系统的交互方式

安装完 GUI 版应用后还需要做一些骚操作,才能让 Tailscale 使用 Headscale 作为控制服务器。当然,Headscale 已经给我们提供了详细的操作步骤,你只需要在浏览器中打开 URL:http://<HEADSCALE_PUB_IP>:8080/apple,便会出现如下的界面:

非应用商店版本的 macOS 客户端需要将 io.tailscale.ipn.macos 替换为 io.tailscale.ipn.macsys。即:defaults write io.tailscale.ipn.macsys ControlURL http://<HEADSCALE_PUB_IP>:8080

修改完成后重启 Tailscale 客户端,在 macOS 顶部状态栏中找到 Tailscale 并点击,然后再点击 Log in

然后立马就会跳转到浏览器并打开一个页面。

接下来与之前 Linux 客户端相同,回到 Headscale 所在的机器执行浏览器中的命令即可,注册成功

回到 Headscale 所在主机,查看注册的节点:

1
2
3
4
5
6
7
8
9
10
$ headscale nodes list
ID | Name | NodeKey | Namespace | IP addresses | Ephemeral | Last seen | Onlin
e | Expired
1 | coredns | [Ew3RB] | default | 10.1.0.1 | false | 2022-03-20 09:08:58 | onlin
e | no
2 | carsondemacbook-pro | [k7bzX] | default | 10.1.0.2 | false | 2022-03-20 09:48:30 | online | no

回到 macOS,测试是否能 ping 通对端节点:

1
2
3
4
5
6
7
8
$ ping -c 2 10.1.0.1
PING 10.1.0.1 (10.1.0.1): 56 data bytes
64 bytes from 10.1.0.1: icmp_seq=0 ttl=64 time=37.025 ms
64 bytes from 10.1.0.1: icmp_seq=1 ttl=64 time=38.181 ms
--- 10.1.0.1 ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 37.025/37.603/38.181/0.578 ms

也可以使用 Tailscale CLI 来测试:

1
2
$ /Applications/Tailscale.app/Contents/MacOS/Tailscale ping 10.1.0.1
pong from coredns (10.1.0.1) via xxxx:41641 in 36ms

Windows

Windows Tailscale 客户端想要使用 Headscale 作为控制服务器,只需在浏览器中打开 URL:http://<HEADSCALE_PUB_IP>:8080/windows,便会出现如下的界面

按照其中的步骤操作即可。

通过 Pre-Authkeys 接入

前面的接入方法都需要服务端同意,步骤比较烦琐,其实还有更简单的方法,可以直接接入,不需要服务端同意。

首先在服务端生成 pre-authkey 的 token,有效期可以设置为 24 小时:

1
$ headscale preauthkeys create -e 24h -n default

查看已经生成的 key:

1
2
3
$ headscale -n default preauthkeys list
ID | Key | Reusable | Ephemeral | Used | Expiration | Created
1 | 57e419c40e30b0dxxxxxxxf15562c18a8c6xxxx28ae76f57 | false | false | false | 2022-05-30 07:14:17 | 2022-05-29 07:14:17

现在新节点就可以无需服务端同意直接接入了:

1
$ tailscale up --login-server=http://<HEADSCALE_PUB_IP>:8080 --accept-routes=true --accept-dns=false --authkey $KEY

打通局域网

到目前为止我们只是打造了一个点对点的 Mesh 网络,各个节点之间都可以通过 WireGuard 的私有网络 IP 进行直连。但我们可以更大胆一点,还记得我在文章开头提到的访问家庭内网的资源吗?我们可以通过适当的配置让每个节点都能访问其他节点的局域网 IP。这个使用场景就比较多了,你可以直接访问家庭内网的 NAS,或者内网的任何一个服务,更高级的玩家可以使用这个方法来访问云上 Kubernetes 集群的 Pod IP 和 Service IP。

假设你的家庭内网有一台 Linux 主机(比如 OpenWrt)安装了 Tailscale 客户端,我们希望其他 Tailscale 客户端可以直接通过家中的局域网 IP(例如 192.168.100.0/24) 访问家庭内网的任何一台设备。

配置方法很简单,首先需要设置 IPv4 与 IPv6 路由转发:

1
2
3
$ 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

客户端修改注册节点的命令,在原来命令的基础上加上参数 --advertise-routes=192.168.100.0/24

1
$ tailscale up --login-server=http://<HEADSCALE_PUB_IP>:8080 --accept-routes=true --accept-dns=false --advertise-routes=192.168.100.0/24

在 Headscale 端查看路由,可以看到相关路由是关闭的。

1
2
3
4
5
6
7
8
9
10
11
$ headscale nodes list|grep openwrt
6 | openwrt | [7LdVc] | default | 10.1.0.6 | false | 2022-03-20 15:50:46 | onlin
e | no
$ headscale routes list -i 6
Route | Enabled
192.168.100.0/24 | false

开启路由:

1
2
3
4
5
$ headscale routes enable -i 6 -r "192.168.100.0/24"
Route | Enabled
192.168.100.0/24 | true

其他节点查看路由结果:

1
2
$ ip route show table 52|grep "192.168.100.0/24"
192.168.100.0/24 dev tailscale0

现在你在任何一个 Tailscale 客户端所在的节点都可以 ping 通家庭内网的机器.