Headscale+Tailscale+DERP虚拟组网全流程(下)

默认分类 · 2023-08-07

上篇介绍了Headscale+Tailscale的部署过程,也提到了DERP是中转流量的,Headscale默认使用的是Tailscale公司提供的公共DERP服务器组,这些服务器都在大陆地区以外,离大陆最近的是香港、东京两个节点,延迟和带宽都不太理想,在Tailscale打洞失败的情况下连接体验非常差,要改善连接体验就需要自建DERP服务。本篇就着重介绍自建DERP的过程和其中要踩的坑。

DERP工作方式

在开始搭建之前,需要了解一下DERP的工作方式,为后面的流程避坑。
DERP服务器提供两种服务:

  • STUN:以udp方式工作,告知节点通过层层nat后最终对应的公网ip和端口号
  • DERP:以http+websocket方式工作,为节点提供流量转发服务

Tailscale的工作逻辑是先走DERP中转尽快建立连接,再尝试打通NAT走直连。
Tailscale会先尝试与DERP建立http连接,并升级协议到websocket,打开ws连接后,再尝试通过STUN服务提供的ip端口来打通NAT直连。

操作环境

  • 云服务器一台,有nginx做反向代理,配置好https证书
  • 如果域名不需要备案,服务器80、443端口没有其他服务占用,也可以让DERP服务端自己申请证书完成https加密

DERP部署

Tailscale官方并没有提供DERP的镜像,所以这里选用的是第三方的DERP镜像。

docker run -d \
        --name derp \
        --restart unless-stopped \
        -p 8800:80 \       
        -p 3478:3478/udp \ 
        --log-opt max-size=10M \
        -v /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock \
        sparanoid/derp:edge \
        derper \
            -a :80 \
            -verify-clients true
  • derper是DERP服务的二进制文件
  • 上面打开的803478分别是DERPSTUN的端口。
  • 指定的-v选项和下面给derper传递的-verify-clients选项是用于验证连接DERP的节点身份的,-verify-clients参数是默认不开启的,谁都可以通过你自建的DERP中转流量,对于流量有限的服务器来说难以接受,这就需要在derper所在的服务器上安装Tailscale,并加入到同一个虚拟网络中,打开derper的-verify-clients选项,同时设置-v参数,将Tailscale的socket套接字映射给derper,这样derper就会通过宿主机上的Tailscale来验证节点的身份,只有和derper在同一个虚拟网络中的节点才有权通过本DERP转发流量。
  • 指定log的max-size是因为在打开-verify-clients选项时,每一条认证失败的信息都会被打印到日志里,在一些未授权节点只设置了一个DERP服务器的情况下,未授权节点会不停尝试连接DERP服务器,然后被DERP服务器拒绝连接,时间长了DERP服务器写的日志可能会占用太多的存储空间。
  • -a参数指定非443端口时,内置的证书申请服务就会被禁用,derp会以http方式提供服务,https加密就需要前端的nginx来实现。
    ❗这里有一个坑,-a参数必须在-verify-clients前面,不然指定端口不生效,derper会以443端口https模式提供服务

其他derper的参数可以参考本文末尾。

在部署完成后,访问8800端口,会看到以下内容:

DERP
This is a Tailscale DERP server.

Nginx反向代理配置

这一步给DERP配置反代,Nginx已经做好了https,直接将http请求转发给DERP

server {
    listen       443;
    server_name  derp.example.domain;

    location / {
        proxy_pass http://127.0.0.1:8800;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header REMOTE-HOST $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # websocket握手
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection Upgrade;

    }
}

❗这里又是一个坑,DERP服务是通过http+websocket的方式工作的,如果不把ws的请求头转发,虽然能访问到DERP的页面,但Tailscale无法通过DERP转发流量。网上很多教程都说看见页面就可以了,那只是http可以了,websocket还不行,需要在nginx的反代配置里加上相应的参数,让Tailscale的ws握手请求能转发给DERP,Tailscale才能通过DERP转发流量。

Headscale设置DERP列表

在Headscale的设置文件夹中新建一个derp.yaml文件,写入DERP的配置信息

regions:
  901:
    regionid: 901
    regioncode: DERP
    regionname: Custom DERP
    nodes:
      - name: DERP_SERVER_1 
        regionid: 901
        hostname: derp.example.domain
        stunport: 3478
        stunonly: false
        derpport: 443

regionid900-999之间任选,这一段是Tailscale官方为自建DERP保留的,这里选择901。
regioncoderegionnamenodes.name都可以自己取,然后根据自建DERP的信息修改配置文件的内容。

然后修改Headscale的主配置文件,添加自建DERP:

---
server_url: http://*.*.*.*:*
listen_addr: 0.0.0.0:*
metrics_listen_addr: 0.0.0.0:9090
grpc_listen_addr: 127.0.0.1:50443
grpc_allow_insecure: false
ip_prefixes:
  - 100.64.0.0/10
derp:
  urls: []  # 如果不希望使用Tailscale提供的公共DERP组,可以像我一样注释掉
  #  - https://controlplane.tailscale.com/derpmap/default
  paths: 
  - /etc/headscale/derp.yaml  # 上面添加的配置文件

在任意节点上执行命令,检查DERP配置下发状态

root@centos:~# tailscale netcheck
Report:
        * UDP: true
        * IPv4: yes, *.*.*.*:*
        * IPv6: no, but OS has support
        * MappingVariesByDestIP: 
        * HairPinning: false
        * PortMapping: UPnP, NAT-PMP, PCP
        * Nearest DERP: Custom DERP
        * DERP latency:
                - DERP: 29.3ms  (Custom DERP)

测试DERP转发

首先选任意两节点,在一节点上ping另一节点,等待NAT穿透后,通过tailscale命令获取直连ip:

root@ubuntu:~# tailscale status
100.64.0.1 ubuntu        root         linux   -
100.64.0.3 centos        root         linux   active; direct 1.1.1.1:11111, tx 2120992 rx 453744

在iptables中将直连ip的数据包drop掉:

iptables -A OUTPUT -d 1.1.1.1 -j DROP

有时候这一步drop掉还能直连,是因为直连可以是双向的,drop掉一个节点的ip,还有另一个节点的ip可以直连,再ping一次拿到新的直连ip设置drop即可。
当drop掉直连ip时,再次ping另一节点,如果延迟稳定在几十ms(取决于节点到DERP服务器的延迟),则说明当前连接走DERP中继,服务搭建成功,如果出现网络不可达等,则说明DERP的部署有问题。
也可以通过tailscale命令来确定流量是否走转发:

root@ubuntu:~# tailscale status
100.64.0.1 ubuntu        root         linux   -
100.64.0.3 centos        root         linux   active; relay "DERP", tx 2122484 rx 455116

如上,显示relay "DERP",则说明当前流量走自建DERP。
测试完成后,删除iptables的drop规则:

iptables -D OUTPUT -d 1.1.1.1 -j drop

克隆机器时避免重复加入

克隆虚拟机时会把ts的配置一起克隆,两个机器同时加入网络会冲突,需要在新的机器上面删除ts的配置后再重新加入网络

rm -rf /var/lib/tailscale/*

官方文档

参考链接

@米开朗基杨 的博客: 链接
Tailscale的自建DERP教程:链接

derper的参数说明

/app # derper -h
Usage of derper:
  -a string
        server HTTP/HTTPS listen address, in form ":port", "ip:port", or for IPv6 "[ip]:port". If the IP is omitted, it defaults to all interfaces. Serves HTTPS if the port is 443 and/or -certmode is manual, otherwise HTTP. (default ":443")
  -accept-connection-burst int
        burst limit for accepting new connection (default 9223372036854775807)
  -accept-connection-limit float
        rate limit for accepting new connection (default +Inf)
  -bootstrap-dns-names string
        optional comma-separated list of hostnames to make available at /bootstrap-dns
  -c string
        config file path
  -certdir string
        directory to store LetsEncrypt certs, if addr's port is :443 (default "/root/.cache/tailscale/derper-certs")
  -certmode string
        mode for getting a cert. possible options: manual, letsencrypt (default "letsencrypt")
  -derp
        whether to run a DERP server. The only reason to set this false is if you're decommissioning a server but want to keep its bootstrap DNS functionality still running. (default true)
  -dev
        run in localhost development mode (overrides -a)
  -hostname string
        LetsEncrypt host name, if addr's port is :443 (default "derp.tailscale.com")
  -http-port int
        The port on which to serve HTTP. Set to -1 to disable. The listener is bound to the same IP (if any) as specified in the -a flag. (default 80)
  -mesh-psk-file string
        if non-empty, path to file containing the mesh pre-shared key file. It should contain some hex string; whitespace is trimmed.
  -mesh-with string
        optional comma-separated list of hostnames to mesh with; the server's own hostname can be in the list
  -stun
        whether to run a STUN server. It will bind to the same IP (if any) as the --addr flag value. (default true)
  -stun-port int
        The UDP port on which to serve STUN. The listener is bound to the same IP (if any) as specified in the -a flag. (default 3478)
  -unpublished-bootstrap-dns-names string
        optional comma-separated list of hostnames to make available at /bootstrap-dns and not publish in the list
  -verify-clients
        verify clients to this DERP server through a local tailscaled instance.
Theme Jasmine by Kent Liao