使用Headscale搭建开源版Tailscale服务端

相信大家对wireguard已经不陌生了。作为一种搭建虚拟局域网的工具,相比于传统VPN而言,所有节点之间都可以进行P2P连接,也就是全互联模式,效率更高。而wireguard本身只是一个内核级别的模块,只是一个数据平面,至于上层更高级的功能(比如密钥交换机制,UDP打洞,ACL等),需要通过用户空间的应用来实现。

本文主要介绍Headscale和Tailscale。

前言

为了基于wireguard实现更完美的VPN工具,现在已经出现了很多项目,如Netmaker,通过可视化界面来配置全互联模式,并支持UDP打洞、多租户等高端功能,几乎适配所有平台。然而现实世界是复杂的,无法保证所有的NAT都能打洞成功,而且Netmaker目前不支持fallback机制,如打洞失败,无法fallback中继节点。而Tailscale支持fallback,可以尽最大努力实现全互联模式,部分节点即使打洞不成功,也可通过中继节点在虚拟局域网内畅通无阻。

什么是Tailscale

Tailscale是一种基于Wireguard的虚拟组网工具,和Netmaker最大的区别是在用户态实现了wireguard协议,而netmaker直接实现了内核态的wireguard。想比内核态的wireguard性能会有些损失,但是功能性和易用性上完爆其他工具:

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

我们可以将Tailscale视为更易用、功能更完善的Wireguard

安装Tailscale

  1. 打开Tailscale官网

  1. 在不付费的情况下只能通过SSO进行登陆。

Mac OS客户端接入

  1. 安装Tailscale客户端,mac系统下可能需要一个美区的ID。

  • 如果没有美区ID,还可以下载安装包直接安装,传送门

⚠️两种方式只能安装一种。

  • 还可以安装开源命令行工具 tailscaletailscaled传送门

三种安装方式的比较

应用商店 独立应用 命令行版本
是否可用 yes yes, beta yes
图形界面 yes yes no; CLI
macOS 最低版本 macOS 10.13 macOS 10.15 macOS 10.13
后台运行 no; sandboxed 理论上支持; 尚未实现 yes
使用的钥匙串🔑 用户级 系统级 直接存放在文件中
沙盒隔离 yes no no
自动更新 yes; 应用商店直接更新 yes; Sparkle no
是否开源 no no yes
MagicDNS yes yes yes
Taildrop yes yes 未实现
  1. 登陆成功的页面

  1. 查看后台

Linux 客户端接入

  1. 下载编译好的Linux客户端,传送门

  2. 解压

1
2
3
4
5
6
7
deepsoft01@deepsoft-desktop:~$ tar zxvf tailscale_1.26.1_amd64.tgz
tailscale_1.26.1_amd64/
tailscale_1.26.1_amd64/tailscale
tailscale_1.26.1_amd64/tailscaled
tailscale_1.26.1_amd64/systemd/
tailscale_1.26.1_amd64/systemd/tailscaled.defaults
tailscale_1.26.1_amd64/systemd/tailscaled.service
  1. 复制二进制文件到PATH目录
1
2
sudo install tailscale_1.26.1_amd64/tailscaled /usr/sbin/tailscaled
sudo install tailscale_1.22.2_amd64/tailscale /usr/local/bin/tailscale
  1. 设置systemd单元文件
1
sudo cp tailscale_1.26.1_amd64/systemd/tailscaled.service /lib/systemd/system/tailscaled.service
  1. 设置环境变量配置文件
1
sudo cp tailscale_1.26.1_amd64/systemd/tailscaled.defaults /etc/default/tailscaled
  1. 启动tailscaled服务并设置开机自动启动
1
sudo systemctl enable --now tailscaled
  1. 查看服务启动状态
1
systemctl status tailscaled
  1. 接入网络
1
sudo tailscale up --accept-routes=true --accept-dns=false --advertise-routes=192.168.31.0/24 --authkey xxx
  • accept-routes:允许别的设备路由到此设备
  • advertise-routes:向所有设备通告本地路由

  • authkey:非交互接入的key,在控制台Setting可以添加

  1. 查看网络接入状态

Windows 客户端接入

下载地址:https://pkgs.tailscale.com/stable/#static

什么是Headscale

Tailscale是一款商用产品,对个人提供免费服务,就个人而言完全够用,但是后端服务器全在国外,而且免费用户只能使用SSO登陆,非常不方便。

有人认为Tailscale后台不开源也不透明,于是自行开发了一套后端,命名为Headscale。这套后端完全开源,可自行部署。

部署Headscale

项目地址:https://github.com/juanfont/headscale

理论上说只要你的Headscale服务可以暴露到公网出口就行,但是不要有NAT,所以推荐部署到有公网IP的云主机上。

systemd部署

  1. 下载服务端

这里请根据服务器的架构进行选择

1
wget https://github.com/juanfont/headscale/releases/download/v0.15.0/headscale_0.15.0_linux_amd64
  1. 创建配置目录
1
mkdir -p /etc/headscale
  1. 创建目录用来存储数据与证书
1
mkdir -p /var/lib/headscale
  1. 创建空的sqlite文件
1
touch /var/lib/headscale/db.sqlite
  1. 创建Headscale配置文件

配置文件的示例

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
---
# headscale will look for a configuration file named `config.yaml` (or `config.json`) in the following order:
#
# - `/etc/headscale`
# - `~/.headscale`
# - current working directory

# The url clients will connect to.
# Typically this will be a domain like:
#
# https://myheadscale.example.com:443
#
server_url: http://127.0.0.1:8080

# Address to listen to / bind to on the server
#
listen_addr: 0.0.0.0:8080

# Address to listen to /metrics, you may want
# to keep this endpoint private to your internal
# network
#
metrics_listen_addr: 127.0.0.1:9090

# Address to listen for gRPC.
# gRPC is used for controlling a headscale server
# remotely with the CLI
# Note: Remote access _only_ works if you have
# valid certificates.
grpc_listen_addr: 0.0.0.0:50443

# Allow the gRPC admin interface to run in INSECURE
# mode. This is not recommended as the traffic will
# be unencrypted. Only enable if you know what you
# are doing.
grpc_allow_insecure: false

# Private key used encrypt the traffic between headscale
# and Tailscale clients.
# The private key file which will be
# autogenerated if it's missing
private_key_path: /var/lib/headscale/private.key

# List of IP prefixes to allocate tailaddresses from.
# Each prefix consists of either an IPv4 or IPv6 address,
# and the associated prefix length, delimited by a slash.
ip_prefixes:
- fd7a:115c:a1e0::/48
- 100.64.0.0/10

# DERP is a relay system that Tailscale uses when a direct
# connection cannot be established.
# https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp
#
# headscale needs a list of DERP servers that can be presented
# to the clients.
derp:
server:
# If enabled, runs the embedded DERP server and merges it into the rest of the DERP config
# The Headscale server_url defined above MUST be using https, DERP requires TLS to be in place
enabled: false

# Region ID to use for the embedded DERP server.
# The local DERP prevails if the region ID collides with other region ID coming from
# the regular DERP config.
region_id: 999

# Region code and name are displayed in the Tailscale UI to identify a DERP region
region_code: "headscale"
region_name: "Headscale Embedded DERP"

# Listens in UDP at the configured address for STUN connections to help on NAT traversal.
# When the embedded DERP server is enabled stun_listen_addr MUST be defined.
#
# For more details on how this works, check this great article: https://tailscale.com/blog/how-tailscale-works/
stun_listen_addr: "0.0.0.0:3478"

# List of externally available DERP maps encoded in JSON
urls:
- https://controlplane.tailscale.com/derpmap/default

# Locally available DERP map files encoded in YAML
#
# This option is mostly interesting for people hosting
# their own DERP servers:
# https://tailscale.com/kb/1118/custom-derp-servers/
#
# paths:
# - /etc/headscale/derp-example.yaml
paths: []

# If enabled, a worker will be set up to periodically
# refresh the given sources and update the derpmap
# will be set up.
auto_update_enabled: true

# How often should we check for DERP updates?
update_frequency: 24h

# Disables the automatic check for headscale updates on startup
disable_check_updates: false

# Time before an inactive ephemeral node is deleted?
ephemeral_node_inactivity_timeout: 30m

# SQLite config
db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite

# # Postgres config
# db_type: postgres
# db_host: localhost
# db_port: 5432
# db_name: headscale
# db_user: foo
# db_pass: bar

### TLS configuration
#
## Let's encrypt / ACME
#
# headscale supports automatically requesting and setting up
# TLS for a domain with Let's Encrypt.
#
# URL to ACME directory
acme_url: https://acme-v02.api.letsencrypt.org/directory

# Email to register with ACME provider
acme_email: ""

# Domain name to request a TLS certificate for:
tls_letsencrypt_hostname: ""

# Client (Tailscale/Browser) authentication mode (mTLS)
# Acceptable values:
# - disabled: client authentication disabled
# - relaxed: client certificate is required but not verified
# - enforced: client certificate is required and verified
tls_client_auth_mode: relaxed

# Path to store certificates and metadata needed by
# letsencrypt
tls_letsencrypt_cache_dir: /var/lib/headscale/cache

# Type of ACME challenge to use, currently supported types:
# HTTP-01 or TLS-ALPN-01
# See [docs/tls.md](docs/tls.md) for more information
tls_letsencrypt_challenge_type: HTTP-01
# When HTTP-01 challenge is chosen, letsencrypt must set up a
# verification endpoint, and it will be listning on:
# :http = port 80
tls_letsencrypt_listen: ":http"

## Use already defined certificates:
tls_cert_path: ""
tls_key_path: ""

log_level: info

# Path to a file containg ACL policies.
# ACLs can be defined as YAML or HUJSON.
# https://tailscale.com/kb/1018/acls/
acl_policy_path: ""

## DNS
#
# headscale supports Tailscale's DNS configuration and MagicDNS.
# Please have a look to their KB to better understand the concepts:
#
# - https://tailscale.com/kb/1054/dns/
# - https://tailscale.com/kb/1081/magicdns/
# - https://tailscale.com/blog/2021-09-private-dns-with-magicdns/
#
dns_config:
# List of DNS servers to expose to clients.
nameservers:
- 1.1.1.1

# Split DNS (see https://tailscale.com/kb/1054/dns/),
# list of search domains and the DNS to query for each one.
#
# restricted_nameservers:
# foo.bar.com:
# - 1.1.1.1
# darp.headscale.net:
# - 1.1.1.1
# - 8.8.8.8

# Search domains to inject.
domains: []

# Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
# Only works if there is at least a nameserver defined.
magic_dns: true

# Defines the base domain to create the hostnames for MagicDNS.
# `base_domain` must be a FQDNs, without the trailing dot.
# The FQDN of the hosts will be
# `hostname.namespace.base_domain` (e.g., _myhost.mynamespace.example.com_).
base_domain: example.com

# Unix socket used for the CLI to connect without authentication
# Note: for local development, you probably want to change this to:
# unix_socket: ./headscale.sock
unix_socket: /var/run/headscale.sock
unix_socket_permission: "0770"
#
# headscale supports experimental OpenID connect support,
# it is still being tested and might have some bugs, please
# help us test it.
# OpenID Connect
# oidc:
# issuer: "https://your-oidc.issuer.com/path"
# client_id: "your-oidc-client-id"
# client_secret: "your-oidc-client-secret"
#
# Customize the scopes used in the OIDC flow, defaults to "openid", "profile" and "email" and add custom query
# parameters to the Authorize Endpoint request. Scopes default to "openid", "profile" and "email".
#
# scope: ["openid", "profile", "email", "custom"]
# extra_params:
# domain_hint: example.com
#
# List allowed principal domains and/or users. If an authenticated user's domain is not in this list, the
# authentication request will be rejected.
#
# allowed_domains:
# - example.com
# allowed_users:
# - alice@example.com
#
# If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed.
# This will transform `first-name.last-name@example.com` to the namespace `first-name.last-name`
# If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following
# namespace: `first-name.last-name.example.com`
#
# strip_email_domain: true

# Logtail configuration
# Logtail is Tailscales logging and auditing infrastructure, it allows the control panel
# to instruct tailscale nodes to log their activity to a remote server.
logtail:
# Enable logtail for this headscales clients.
# As there is currently no support for overriding the log server in headscale, this is
# disabled by default. Enabling this will make your clients send logs to Tailscale Inc.
enabled: false

# Enabling this option makes devices prefer a random port for WireGuard traffic over the
# default static port 41641. This option is intended as a workaround for some buggy
# firewall devices. See https://tailscale.com/kb/1181/firewalls/ for more information.
randomize_client_port: false
  1. 下载配置文件
1
wget https://github.com/juanfont/headscale/raw/main/config-example.yaml -O /etc/headscale/config.yaml
  • 修改配置文件,将server_url改为公网域名或者IP,国内服务器域名必须要备案;
  • 如果暂时用不到DNS功能,可以将magin_dns设置为false;
  • server_url 设置为 http://<PUBLIC_IP>:8080,将 <PUBLIC_IP> 替换为公网 IP 或者域名;

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

    1
    2
    3
    ip_prefixes:
    - fd7a:115c:a1e0::/48
    - 192.168.7.1/24
  1. 创建SystemD service配置文件
1
vim /etc/systemd/system/headscale.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
[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
  1. 创建headscale用户
1
useradd headscale -d /home/headscale -m
  1. 修改配置文件的owner
1
chown -R headscale:headscale /var/lib/headscale
  1. 创建必须的文件夹
1
mkdir -p /var/run/headscale/
  1. 修改配置文件unix_socket
1
unix_socket: /var/run/headscale/headscale.sock
  1. 重载新的配置文件
1
sudo systemctl daemon-reload
  1. 启动Headscale并设置开机自动启动
1
sudo systemctl enable --now headscale
  1. 查看运行状态
1
sudo systemctl status headscale
  1. 查看占用端口
1
ss -tulnp|grep headscale
  1. 创建namespace

Tailscale中有有一个概念叫做tailnet,可以理解为租户。租户和租户之间是相互隔离的,具体可查看tailscale的官方文档.

Headscale中也有类似的实现叫做namespace.

需要先创建一个namespace,以便后续客户端接入。

1
headscale namespace create default
  1. 查看命名空间
1
headscale namespaces list

docker-compose部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '3'
services:
headscale:
user: root
image: headscale/headscale
container_name: headscale
network_mode: host
cap_add:
- CAP_NET_BIND_SERVICE
command: headscale serve
volumes:
- ./config.yaml:/etc/headscale/config.yaml
- ./db.sqlite:/var/lib/headscale/db.sqlite
- ./ssl/221.12.170.100.crt:/221.12.170.100.crt
- ./ssl/221.12.170.100.key:/221.12.170.100.key
#- ./headscale.sock:/var/run/headscale.sock

接入Headscale

Mac OS客户端接入

首先需要在服务端创建命名空间

1
headscale namespaces create default

客户端下载参考Tailscale。

安装完毕以后打开http://<headscale_ip>:<port>/apple,会出现如下页面

按照上图所说,安装描述文件,然后重新打开Tailscale APP,点击登录,会自动打开如下的页面:

在安装headscale的机器上,执行上面所示的命令,就可以把节点加入到网络中。

Linux 客户端接入

首先在headscale生成api key,-n 用于指定命名空间

1
headscale preauthkeys create -e 24h -n default

然后在tailscale客户端接入,将authkey替换成上文生成的key

1
2
3
4
login_server=http://xx.xx
authkey=xxxx
route=xxxx
sudo tailscale up --login-server=${login_server} --accept-routes=true --accept-dns=false --advertise-routes=$route --authkey ${authkey}

打通局域网

假设内网有一台 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
2
3
4
login_server=http://xx.xx
authkey=xxxx
route=xxxx
sudo tailscale up --login-server=${login_server} --accept-routes=true --accept-dns=false --advertise-routes=$route --authkey ${authkey}

在headscale服务端启动路由,首先需要查看要启用节点的id,假设这个id是1

1
headscale routes list -i 1

启动路由

1
headscale routes enable -i 1 -r "192.168.100.0/24"

Windows 客户端接入

访问以下连接

1
https://<headscale_ip>:<port>/windows

一些常见的命令

查看链接状态

1
2
3
4
$tailscale status 
192.168.7.1 mac-mini-bj4beb75 default macOS -
deepsoft-ci-0cf7bgtj default linux active; direct 221.12.170.99:41642; offline, tx 24760 rx 98248
deepsoft-desktop-xdcbicir default linux active; direct 221.12.170.98:41641; offline, tx 16404 rx 21900

查看darp连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$tailscale status 
2022/06/25 14:17:34 portmap: [v1] Got PMP response; IP:xxx, epoch: 683007
2022/06/25 14:17:34 portmap: [v1] Got PCP response: epoch: 683007
2022/06/25 14:17:34 portmap: [v1] UPnP reply {Location:http://192.168.15.1:5000/rootDesc.xml Server:OpenWRT/5.10.107 UPnP/1.1 MiniUPnPd/2.2.3 USN:uuid:d38cfa50-cc98-48d8-8e37-2c97c660b8aa::urn:schemas-upnp-org:device:InternetGatewayDevice:2}, "HTTP/1.1 200 OK\r\nCACHE-CONTROL: max-age=120\r\nST: urn:schemas-upnp-org:device:InternetGatewayDevice:2\r\nUSN: uuid:d38cfa50-cc98-48d8-8e37-2c97c660b8aa::urn:schemas-upnp-org:device:InternetGatewayDevice:2\r\nEXT:\r\nSERVER: OpenWRT/5.10.107 UPnP/1.1 MiniUPnPd/2.2.3\r\nLOCATION: http://192.168.15.1:5000/rootDesc.xml\r\nOPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n01-NLS: 1655692518\r\nBOOTID.UPNP.ORG: 1655692518\r\nCONFIGID.UPNP.ORG: 1337\r\n\r\n"
2022/06/25 14:17:34 portmap: UPnP meta changed: {Location:http://192.168.15.1:5000/rootDesc.xml Server:OpenWRT/5.10.107 UPnP/1.1 MiniUPnPd/2.2.3 USN:uuid:d38cfa50-cc98-48d8-8e37-2c97c660b8aa::urn:schemas-upnp-org:device:InternetGatewayDevice:2}

Report:
* UDP: false
* IPv4: (no addr found)
* IPv6: no
* MappingVariesByDestIP:
* HairPinning:
* PortMapping: UPnP, NAT-PMP, PCP
* Nearest DERP: unknown (no response to latency probes)

由于我这里通过p2p打洞就建立了连接,同时没有导入Tailscale的中继节点,因此这里无法获得darp连接。

其他情况应该是

1
2
3
4
5
6
7
8
9
10
11
12
$ tailscale netcheck

Report:
* UDP: true
* IPv4: yes, xxxxx:57068
* IPv6: no
* MappingVariesByDestIP: false
* HairPinning: false
* PortMapping:
* Nearest DERP: Tencent Hongkong
* DERP latency:
- thk: 39.7ms (Tencent Hongkong)