之前写过使用netmaker的组网,今天就推荐类似的基于wireguard的另一个更优雅的组网工具开源版tailscale–>headscale。根据官网介绍可以支持内网打洞,经过实际使用后感觉打洞成功率还是可以的,移动、电信均成功打洞。严格来说headscale是tailscale的开源服务端(这个名字有点恶趣味2333),不过支持了绝大部分功能。

客户端连接的前几个数据包默认通过derp server进行中转,所以无论打洞成功与否都能保证网络网络联通。同时,客户端会尝试进行打洞,成功后两个客户端间即可P2P直连。

部署Headscale服务端

Tailscale的中继节点称为DERP Server,官方提供了免费节点但是都在国外。。。而Headscale内置了derp server,所以可以直接部署在同一服务器上。DERP Server需要自己来处理TLS,而如caddy、nginx等一般都只处理七层的http,对于4层的TLS(TCP)无法做到反向代理。而Let’s Encrypt自动TLS证书需要绑定443或者80端口,这就导致了如果作为DERP节点的服务器要么只能用非443端口手动管理证书,要么就是DERP独占443端口。而解决方案呢显而易见,就是4层代理\路由。如果使用caddy作为前端代理,可以配合插件caddy-l4实现,但是还有更优雅的组合就是docker与traefik。traefik约从2.0版本后支持了部分四层协议的路由,其中就包括TCP,对headscale的域名开启TLS Passthrough透传tls让headscale自己来处理。

docker部署:

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
version: '3'
services:
headscale:
image: "headscale/headscale"
container_name: "headscale"
hostname: "headscale"
restart: "unless-stopped"
networks:
- bridge
environment:
- TZ=Asia/Shanghai
ports:
- "443"
- "9090"
- "3478:3478/udp"
labels:
# auto update image
- "com.centurylinklabs.watchtower.enable=true"
# traefik
- "traefik.enable=true"
# svc
- "traefik.tcp.services.headscale.loadbalancer.server.port=443"
- "traefik.http.services.headscale-metrics.loadbalancer.server.port=9090"
# for headscale main port and derp
- "traefik.tcp.routers.headscale.entrypoints=websecure"
- "traefik.tcp.routers.headscale.tls.passthrough=true"
- "traefik.tcp.routers.headscale.rule=HostSNI(`headscale.infra.xxxx.dev`)"
# for headscale metrics api
- "traefik.http.routers.headscale-metrics.entrypoints=websecure"
- "traefik.http.routers.headscale-metrics.rule=Host(`metrics.headscale.infra.xxxx.dev`)"
- "traefik.http.routers.headscale-metrics.service=headscale-metrics"
volumes:
- "./headscale/config:/etc/headscale:ro"
- "./headscale/data:/var/lib/headscale:rw"
command:
- "headscale"
- "serve"
headscale-webui:
image: ifargle/headscale-webui:latest
container_name: headscale-webui
networks:
- bridge
labels:
# auto update image
- "com.centurylinklabs.watchtower.enable=true"
# traefik
- "traefik.enable=true"
- "traefik.http.routers.headscale-webui.entrypoints=websecure"
- "traefik.http.routers.headscale-webui.rule=Host(`ui.headscale.infra.xxxx.dev`) && (PathPrefix(`/admin/`) || PathPrefix(`/admin`))"
- "traefik.http.services.headscale-webui.loadbalancer.server.port=5000"
- "traefik.http.routers.headscale-webui.tls.certresolver=letsencrypt-prod"
environment:
- TZ=Asia/Shanghai
- COLOR=red # Use the base colors (ie, no darken-3, etc) -
- HS_SERVER=https://headscale.infra.xxxx.dev # Reachable endpoint for your Headscale server
- DOMAIN_NAME=https://ui.headscale.infra.xxxx.dev # The base domain name for this container.
- SCRIPT_NAME=/admin # This is your applications base path (wsgi requires the name "SCRIPT_NAME")
- KEY="key" # Generate with "openssl rand -base64 32" - used to encrypt your key on disk.
- AUTH_TYPE=Basic # AUTH_TYPE is either Basic or OIDC. Empty for no authentication
- LOG_LEVEL=info # Log level. "DEBUG", "ERROR", "WARNING", or "INFO". Default "INFO"
# ENV for Basic Auth (Used only if AUTH_TYPE is "Basic"). Can be omitted if you aren't using Basic Auth
- BASIC_AUTH_USER=user # Used for basic auth
- BASIC_AUTH_PASS=passwd # Used for basic auth
# ENV for OIDC (Used only if AUTH_TYPE is "OIDC"). Can be omitted if you aren't using OIDC
# - OIDC_AUTH_URL=https://auth.$DOMAIN/.well-known/openid-configuration # URL for your OIDC issuer's well-known endpoint
# - OIDC_CLIENT_ID=headscale-webui # Your OIDC Issuer's Client ID for Headscale-WebUI
# - OIDC_CLIENT_SECRET=YourSecretHere # Your OIDC Issuer's Secret Key for Headscale-WebUI
ports:
- "5000"
volumes:
- "./headscale/ui-data:/data" # Headscale-WebUI's storage. Make sure ./volume is readable by UID 1000 (chown 1000:1000 ./volume)
- "./headscale/config/:/etc/headscale/:ro" # Headscale's config storage location. Used to read your Headscale config.
traefik:
image: "traefik"
container_name: "traefik"
hostname: "traefik"
restart: "unless-stopped"
networks:
- bridge
ports:
- "80:80"
- "443:443"
- "8080:8080"
environment:
- TZ=Asia/Shanghai
labels:
# auto update image
- "com.centurylinklabs.watchtower.enable=true"
volumes:
- "./traefik/basic_auth:/basic_auth:ro"
- "./traefik/acme_prod.json:/acme_prod.json:rw"
- "./traefik/acme_stag.json:/acme_stag.json:rw"
- "./traefik/traefik.yaml:/traefik.yml:ro"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
watchtower:
image: "containrrr/watchtower"
container_name: "watchtower"
hostname: "watchtower"
networks:
- bridge
restart: "always"
environment:
- TZ=Asia/Shanghai
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:rw"
command: --label-enable --cleanup --schedule "0 0 4 * * *"

networks:
bridge: {}

./headscale/config目录下的config.yaml为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
252
253
254
255
256
257
258
259
260
261
# 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: https://headscale.infra.xxxx.dev:443

# Address to listen to / bind to on the server
#
listen_addr: :443

# 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

noise:
# The Noise private key is used to encrypt the
# traffic between headscale and Tailscale clients when
# using the new Noise-based protocol. It must be different
# from the legacy private key.
private_key_path: /var/lib/headscale/noise_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
- 198.19.0.0/16

# 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: true

# 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: ":3478"

# List of externally available DERP maps encoded in JSON
urls: []

# 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:
- /etc/headscale/derp.yaml

# 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: "email@gmail.com"

# Domain name to request a TLS certificate for:
tls_letsencrypt_hostname: "headscale.infra.xxxx.dev"

# 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/tls_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: TLS-ALPN-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:
override_local_dns: false
# List of DNS servers to expose to clients.
nameservers:
- 223.5.5.5
- 119.29.29.29

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

# Search domains to inject.
domains: ['clemon']

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

# 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: headscale.clemon

# 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

配置文件中所有字段按部就班填就好。需要注意的是如果指定特定域名的内网dns服务器,不用启用覆盖本地dns,应该这么写

1
2
3
4
5
6
7
8
9
10
dns_config:
override_local_dns: false

# Split DNS (see https://tailscale.com/kb/1054/dns/),
# list of search domains and the DNS to query for each one.
restricted_nameservers:
clemon:
- 192.168.99.254

domains: ['clemon']

安装tailscale客户端

官网安装方法基本包含了全平台、全架构。mac os直接源码编译安装,linux发行版直接包管理器安装,windows直接下载。linux下包管理器会从pkgs.tailscale.com拉去文件,速度极慢,可以单独给apt指定proxy。

客户端加入tailscale up --login-server=https://headscale.infra.xxxx.dev --accept-routes=true --accept-dns=true

image-20230506230222775

把shell上给的的这个链接复制到浏览器打开

image-20230506230404951

网页上给出注册该节点的命令,接着到headscale服务器上运行

image-20230506230637991

显示registered就是注册成功了,查看节点

image-20230506230755684

客户端这检查一下

image-20230506230946281

tailscale status里节点显示direct即为直连的节点,可以使用ping直接去测试。

Trouble shooting

  1. 100.64.0.0/10网段不通:https://nyan.im/p/troubleshoot-tailscale