书接 (Tailscale 组网记录)[./22f71dfd]
# 为什么需要 Derp 服务
这是由于 Tailscale 机制决定的。
tailscale 只会在需要与 peer 建立连接的时候才会尝试打洞,而且最开始的流量一定是会经过 DERP 中转服务器。
优点:懒加载机制无需预先维护与其他节点的任何打洞连接,无需预先维护任何状态。
缺点:每次通过 tailscale 创建虚拟连接时,初始所创建的连接其延迟很高,这会极大的影响使用体验;tailscale 极其依赖中继节点。
当然官方是有提供默认的一系列 Derp 服务器,可以通过 tailscale netcheck
来查看
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 Report: * UDP: true * IPv4: yes , xxxx * IPv6: no, but OS has support * MappingVariesByDestIP: true * HairPinning: false * PortMapping: * CaptivePortal: false * Nearest DERP: lax * DERP latency: - lax: 165.8ms (Los Angeles) - sfo: 173ms (San Francisco) - tok: 173.8ms (Tokyo) - sea: 175.6ms (Seattle) - den: 181.5ms (Denver) - dfw: 184.1ms (Dallas) - ord: 200.7ms (Chicago) - hkg: 207.5ms (Hong Kong) - hnl: 209ms (Honolulu) - nyc: 213.5ms (New York City) - mia: 220.6ms (Miami) - iad: 223.6ms (Ashburn) - par: 229ms (Paris) - lhr: 230.2ms (London) - tor: 231.1ms (Toronto) - sin: 234.8ms (Singapore) - fra: 238.3ms (Frankfurt) - ams: 248.9ms (Amsterdam) - nue: 253.4ms (Nuremberg) - mad: 266.3ms (Madrid) - waw: 266.6ms (Warsaw) - blr: 271.1ms (Bangalore) - syd: 322ms (Sydney) - sao: 330.8ms (São Paulo) - dbi: 348.1ms (Dubai) - jnb: 384.2ms (Johannesburg) - nai: 393.9ms (Nairobi)
可以看到基本上都是国外的服务器,这也就意味着,如果打洞失败,走 derp 服务器中转的话,会导致延迟非常之高。
# 搭建 Derp 服务
本次搭建教程不基于网上的 Docker 版本,是基于官网的文档来进行搭建的。
# 环境准备
基于 Tailscale 官方提供的文档
以下为一些硬性要求:
需要能够公网访问。这是为了让各个 Tailscale 节点可以直接访问到该 DERP 服务器
需要运行 HTTPS 服务。本质上是为了在传输数据给 DERP 服务器时数据可以通过 TLS 加密。
分配 80 端口来运行 HTTP 服务。
需要额外暴露两个端口来运行 HTTPS 和 STUN 服务。
必须允许 ICMP 流量的出入。
# 官方安装
官方提供的安装步骤就两步
安装 Derper 程序
1 go install tailscale.com/cmd/derper@latest
运行 Derper
1 sudo derper --hostname=your-hostname.com
# 安装运行依赖
当然大多数干净的系统执行第一步就会执行不下去,这个是需要依赖 Go 环境,而 tailscale 的 Go 版本总是会依赖最新的 Go。
以下提供当前的 go 1.23.4
的安装,其他版本或者更新的版本可以到 Go 官网下载安装:https://go.dev/doc/install
1 2 3 4 5 wget https://go.dev/dl/go1.23.4.linux-amd64.tar.gz rm -rf /usr/local/go && tar -C /usr/local -xzf go1.23.4.linux-amd64.tar.gzexport PATH=$PATH :/usr/local/go/bingo version go env -w GOPROXY=https://goproxy.cn,direct
Note: Changes made to a profile file may not apply until the next time you log into your computer. To apply the changes immediately, just run the shell commands directly or execute them from the profile using a command such as source $HOME/.profile.
上面执行完,就安装好了 Go 环境,同时指定了 Go 代理地址,因为阿里云服务器下载 github 包大概率会失败。
# 用 IP 代替域名的原理(技术细节,可以不看)
因为 Derper 需要指定证书,并且验证证书,因此需要一个域名来启动 Derper。
而看到很多博主教程中是修改了 Derper 的源码后重新编译来跳过域名验证,从而实现 IP 启动。
我看了下 Derper 的源码,发现新版本中并不需要这样处理了。
1 2 3 4 5 6 7 8 9 10 11 12 func (m *manualCertManager) getCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error ) { if hi.ServerName != m.hostname && !m.noHostname { return nil , fmt.Errorf("cert mismatch with hostname: %q" , hi.ServerName) } certCopy := new (tls.Certificate) *certCopy = *m.cert certCopy.Certificate = certCopy.Certificate[:len (certCopy.Certificate):len (certCopy.Certificate)] return certCopy, nil }
Derper 源码中新增了 && !m.noHostname
这样的一个逻辑判断可以绕过域名的检测。
而这个 noHostname
参数则是在前面运行的 hostname 指定的 derper --hostname=your-hostname.com
1 2 3 4 5 6 7 8 if err := x509Cert.VerifyHostname(hostname); err != nil { return nil , fmt.Errorf("cert invalid for hostname %q: %w" , hostname, err) } return &manualCertManager{ cert: &cert, hostname: hostname, noHostname: net.ParseIP(hostname) != nil , }, nil
也就是说,只要我们提供的证书能够通过 x509Cert.VerifyHostname
那么我们就能直接传一个 IP 作为 hostname
# 用 IP 代替域名
只要用 IP 生成一个自签名证书即可,如果直接尝试 openssl x509
可不太行。
需要将 IP 地址作为证书的 Subject Alternative Name (SAN) 添加到证书中。
因为前面已经安装了 Go 的运行环境了,所以我这里提供一个 Go 版本的生成方案
1 2 mkdir -p ~/certdir cd ~/certdir
将下面这段代码复制, 然后 vim main.go
粘贴后改成你的 IP 保存。
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 package mainimport ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "fmt" "log" "math/big" "net" "os" "time" ) func main () { ip := "127.0.0.1" priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { log.Fatalf("Failed to generate private key: %v" , err) } template := x509.Certificate{ SerialNumber: big.NewInt(12345 ), Subject: pkix.Name{Organization: []string {"Example Org" }}, NotBefore: time.Now(), NotAfter: time.Now().Add(365 * 24 * time.Hour * 100 ), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true , DNSNames: []string {ip}, IPAddresses: []net.IP{net.ParseIP(ip)}, } certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { log.Fatalf("Failed to create certificate: %v" , err) } certFile, err := os.Create(fmt.Sprintf("%s.crt" , ip)) if err != nil { log.Fatalf("Failed to create cert file: %v" , err) } defer certFile.Close() err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE" , Bytes: certDER}) if err != nil { log.Fatalf("Failed to write cert to file: %v" , err) } keyFile, err := os.Create(fmt.Sprintf("%s.key" , ip)) if err != nil { log.Fatalf("Failed to create key file: %v" , err) } defer keyFile.Close() privBytes, err := x509.MarshalECPrivateKey(priv) if err != nil { log.Fatalf("Failed to marshal private key: %v" , err) } err = pem.Encode(keyFile, &pem.Block{Type: "EC PRIVATE KEY" , Bytes: privBytes}) if err != nil { log.Fatalf("Failed to write key to file: %v" , err) } fmt.Println("Certificate and key saved successfully!" ) }
在执行 go run main.go
就会在当前目录下生成以你填的 IP 命名的 .crt
和 .key
密钥
这时,官方的第二步就可以改成
1 sudo derper -a :8888 -http-port -1 -stun-port 8889 -hostname x.x.x.x --certmode manual -certdir ~/certdir
参数解释:
x.x.x.x
换成你的 IP
-1
表示禁用了 http 的端口
8888
为 https 的端口
8889
为 stun 端口
当你能看到下面这段日志输出,就说明启动成功了。
1 2 2025/01/11 11:36:28 derper: serving on :8888 with TLS 2025/01/11 11:36:28 running STUN server on [::]:8889
不要结束服务,此时你自己的电脑或者需要连接到 tailscale 的机器上输入 curl --insecure "https://x.x.x.x:8888"
看到下面这串输出,就说明 derp 搭建成功了。
1 2 3 4 5 6 7 8 9 10 11 <html><body> <h1>DERP</h1> <p> This is a <a href="https://tailscale.com/">Tailscale</a> DERP server. </p> <p> It provides STUN, interactive connectivity establishment, and relaying of end-to-end encrypted traffic for Tailscale clients. </p> ...
# 注册自己的 Derp 服务到列表中
# 如果你用的 tailscale 官方服务,
到 https://login.tailscale.com/admin/acls/file 添加你的 Derp 服务器,这里给出参考配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 "derpMap" : { "Regions" : { "901" : { "RegionID" : 901 , "RegionCode" : "ali-sz" , "RegionName" : "Aliyun Shenzhen" , "Nodes" : [ { "Name" : "901a" , "RegionID" : 901 , "DERPPort" : 8888 , "STUNPort" : 8889 , "IPv4" : "x.x.x.x" , "InsecureForTests" : true } ] } } }
# 如果你用的 Headscale
你可以参考我上篇关于 headscale 搭建的部分,配置文件默认在这个目录 /etc/headscale/config.yaml
修改其中的 urls 部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 derp: urls: - https://controlplane.tailscale.com/derpmap/default - http://xxxx/derp.json auto_update_enabled: true update_frequency: 24h
http://xxxx.com/derp.json
这个路径可以修改你 derp 服务器能够访问的地址,自己搭个 nginx 或者放到 oss 上之类的。
返回的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "Regions" : { "901" : { "RegionID" : 901 , "RegionCode" : "ali-sz" , "RegionName" : "Aliyun Shenzhen" , "Nodes" : [ { "Name" : "901a" , "RegionID" : 901 , "DERPPort" : 8888 , "STUNPort" : 8889 , "IPv4" : "x.x.x.x" , "InsecureForTests" : true } ] } } }
# 检查自定义 Derp 是否生效
检查你的客户端是否能够正常检索到自己的 Derp 服务器
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 tailscale netcheck Report: * UDP: true * IPv4: yes , xxxxx * IPv6: no, but OS has support * MappingVariesByDestIP: true * HairPinning: false * PortMapping: * CaptivePortal: false * Nearest DERP: Aliyun Shenzhen * DERP latency: - ali-sz: 13ms (Aliyun Shenzhen) - lax: 165.8ms (Los Angeles) - sfo: 173ms (San Francisco) - tok: 173.8ms (Tokyo) - sea: 175.6ms (Seattle) - den: 181.5ms (Denver) - den: 181.5ms (Denver) - den: 181.5ms (Denver) - den: 181.5ms (Denver) - den: 181.5ms (Denver) - dfw: 184.1ms (Dallas) - ord: 200.7ms (Chicago) - den: 181.5ms (Denver) - dfw: 184.1ms (Dallas) - ord: 200.7ms (Chicago) - hkg: 207.5ms (Hong Kong) - hnl: 209ms (Honolulu) - nyc: 213.5ms (New York City) - mia: 220.6ms (Miami) - iad: 223.6ms (Ashburn) - par: 229ms (Paris) - lhr: 230.2ms (London) - tor: 231.1ms (Toronto) - sin: 234.8ms (Singapore) - fra: 238.3ms (Frankfurt) - ams: 248.9ms (Amsterdam) - nue: 253.4ms (Nuremberg) - mad: 266.3ms (Madrid) - waw: 266.6ms (Warsaw) - blr: 271.1ms (Bangalore) - syd: 322ms (Sydney) - sao: 330.8ms (São Paulo) - dbi: 348.1ms (Dubai) - jnb: 384.2ms (Johannesburg) - nai: 393.9ms (Nairobi)
# Derp 服务安全
前面跑起来的 Derp 服务,其他人只要知道了 IP 和端口是可以访问的。
因此需要额外进行一些处理。
你需要在 Derp 服务器上安装 Tailscale 客户端
1 curl -fsSL https://tailscale.com/install.sh | sh
然后注册到你的 Tailscale 中
之后在 Derper 启动时添加参数 --verify-clients
这个只允许你的 Tailscale 中的机器才能访问。
完整的命令如下
1 derper -a :8888 -http-port -1 -stun-port 8889 -hostname x.x.x.x --certmode manual -certdir ~/certdir --verify-clients
最后可以将 Derper 注册成服务,自动启动。
ubuntu 参考
1 2 3 4 5 6 7 8 9 10 11 12 [Unit] Description =DerperAfter =network.targetWants =network.target[Service] User =rootRestart =alwaysExecStart =derper -a :8888 -http-port -1 -stun-port 8889 -hostname x.x.x.x --certmode manual -certdir ~/certdir --verify-clientsRestartPreventExitStatus =1 [Install] WantedBy =multi-user.target
1 2 3 4 5 6 # 重新加载Systemd配置 sudo systemctl daemon-reload # 启动服务并设置开机自启动 sudo systemctl start derp sudo systemctl enable derp