Tailscale DERP 中继节点搭建

24

通常,我会将自己nas上搭建的一些服务通过内网穿透的方式暴露到公网中,以供自己在外使用。此前我所使用内网穿透服务一直都是frp,但是frp需要为每个服务暴露一个公网端口,这一定程度上增加了被扫描和攻击的风险。为了降低潜在的风险,保证服务的稳定性,我逐渐产生了组建大内网的想法,通过虚拟组网从云服务器上直连本地nas,通过nginx+域名提供对外服务的想法。在这里我采用了tailscale作为组网工具,通过自建derp节点保证服务可用性,整体搭建流程如下:

1. 在服务器上安装Tailscale

1.1 安装

首先使用官方提供的安装脚本,一键安装Tailscale,同时自动注册service进行开机自启:

sudo curl -fsSL https://tailscale.com/install.sh | sh

这一步的目的是为了后续限制Derp中继节点的访问权限。Tailscale Derp可以限制是否只允许本地Tailscale登录的账户下的机器访问,否则任意账户和机器都可以使用你搭建的Derp节点作为中继。

1.2 启动

安装完成后,通过tailscale up命令登录自己的tailscale账号,接入虚拟内网。

需要注意的是,如果使用的是阿里云的云服务器,或者其它使用100.64.0.0/10内网网段对内提供DNS等服务的机器,需要添加命令禁止tailscale修改iptables规则。否则会因为两者使用的内网网段冲突,tailscale会添加规则把所有对外的100.64.0.0/10请求都Drop掉,只允许走自己搭建的虚拟内网,导致一些服务出现故障。修改后的命令如下:

sudo tailscale up --netfilter-mode=off --accept-dns=false

2. 安装Golang

Tailscale Derp服务基于golang编写,官网推荐始终使用最新版本的golang允许Derp中继服务。如果你的服务器上有其他软件依赖旧版本的 go,将其升级为新版本可能会发生不可预料的后果,请务必仔细评估与衡量。

1.下载最新版本的安装包,请自行修改下述命令中的版本号:

wget https://go.dev/dl/go<version>.linux-amd64.tar.gz

2.将安装包解压到/usr/local下:

rm -rf /usr/local/go && tar -C /usr/local -xzf go1.22.2.linux-amd64.tar.gz

3.添加相关环境变量:

export GOROOT=/usr/local/go
export GOPATH=/usr/local/gopath
export PATH=$PATH:$GOROOT/bin
export PATH=$PATH:$GOPATH/bin

4.然后输入go version检查是否安装成功。

3. 安装derper服务

3.1 安装

sudo mkdir -p /usr/local/gopath/bin
go install tailscale.com/cmd/derper@main

3.2 建立启动和关闭脚本

cd /usr/local/gopath/bin
sudo mkdir derper-scripts && cd derper-scripts/

启动脚本sudo vim derper-run.sh

#!/bin/sh
cd /usr/local/gopath/bin/derper-scripts
nohup ../derper -hostname <指向服务器的域名> -c=derper.conf -a :30001 -http-port -1 -verify-clients -stun > console.log 2>&1 &
echo $! > derper.pid
systemctl restart tailscaled

关闭脚本sudo vim derper-stop.sh:

#!/bin/sh
kill `cat derper.pid`
rm -rf derper.pid

添加可执行权限:

chmod +x derper-run.sh derper-stop.sh

3.3 创建service

sudo vim /etc/systemd/system/derper.service

derper.service内容如下:

[Unit]
Description=derper
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/gopath/bin/derper-scripts/derper-run.sh
ExecStop=/usr/local/gopath/bin/derper-scripts/derper-stop.sh

[Install]
WantedBy=multi-user.target

创建完成后,启动服务:

sudo systemctl enable derper.service
sudo systemctl start derper.service

3.4 在nginx中配置域名(略)

配置域名指向30001端口,同时在防火墙中开启3478(stun-udp)端口。配置完成后访问域名,如果出现This is a Tailscale DERP server.字样就表示搭建成功了。

4. 在Tailscale中设置中继节点

进入Tailscale网页控制台,打开Access Controls,在ssh配置前添加derpMap配置信息:

// Define private derp
"derpMap": {
    "OmitDefaultRegions": true, // 关闭官方Derp中继节点
    "Regions": {                
        "900": {                // 900-999为预留区域
            "RegionID":   900,
            "RegionCode": "HK",
            "RegionName": "Hong Kong",
            "Nodes": [          // 每个区域中可以有多个中继节点
                {
                    "Name":     "1",
                    "RegionID": 900,
                    "HostName": "域名",
                },
            ],
        },
    },
},

配置完成后,客户端开启命令行执行tailscale netcheck即可看到自定义的derp中继节点,及对应区域的延迟了。

如果可以看到自定义的derp中继节点和延迟,但是实际访问不通,建议重启服务器和客户端tailscale,同时更新至最新版本,然后重试。

搭建完成后,就可以关闭原有的frp内网穿透服务及端口,将原来指向内网穿透端口的域名改为指向虚拟内网中的nas设备。所有请求先通过cdn到达云服务器,再走虚拟内网转发到本地nas,就可以最大限度地避免泄露和攻击了。

5.注意事项

1.一个域名可以同时绑定一个ipv4地址(A)和一个ipv6地址(AAAA)。如果你的域名只绑定了ipv4地址,那么就会出现ipv6 only的机器无法连接上DERP中继节点的情况,如下所示:

alt text

这种情况下,这台ipv6 only的机器在虚拟内网中是无法访问的。因此,如果你用来搭建DERP中继节点的机器有独立ipv6地址,那么最好一起绑定到域名上。

2.搭建完成后,你可以通过下述命令来简单查看从当前机器到任意节点的路由情况:

tailscale ping <虚拟内网ip>

此命令有两种可能的响应结果:

  • 如果两端之一有公网ip,那么理想的状态是打洞直连,这种情况的响应如下:
    ping from <目标节点> via <公网ip> in XXms
    
  • 如果两端均没有公网ip,且不在同一个内网环境下,那么就会通过DERP中继节点转发请求,结果如下:
    ping from <目标节点> via DERP<中继节点> in XXms
    

但是,如果你是在阿里云等通过私有网卡映射,实现公网访问的服务器上安装Tailscale。你就会发现你本地节点(无公网)和服务器节点(有公网)之间的访问是走DERP中继节点的,而且有些情况下会绕路选择较远的DERP中继节点,导致连接速率和稳定性都不尽如人意。这是因为服务器端Tailscale打洞所需的端口没有被开放,尝试打洞时,外网请求无法通过网卡映射到内网ip端口上,导致端对端直连失败。

Tailscale打洞所需的udp端口在/etc/default/tailscaled中配置,默认为41641端口(udp),记得在服务台的防火墙中开放一下。这种情况适用于所有内网设备,如果你拥有公网ip,记得将内网中Tailscale设备的41641端口(udp)通过端口映射等方式放行出去,这样能够极大增加打洞成功的概率,提高连接的速率和稳定性。