打通大内网第四期 部署并穿透DERP服务器
qaz741wsd856 Lv2

​#网络#​ ​#OpenWrt#​

前言

上一期介绍了如何通过frp的stcp/sudp​来固定访问端口,虽然可靠性和安全性都得到了保障,配置还是麻烦了一点,每添加一个服务都要修改两处frpc的配置文件,并需要每个访问者都手动更新。并且对于访问者而言,处理端口冲突、注册服务也需要一点知识储备,显然不如无脑安装软件来的方便。如果你只打算小范围内共享,那么直接使用tailscale也是个不错的选择。

理论上只要有一方为NAT1,是一定能直连的,但由于tailscale的服务器都在墙外,内陆的网络环境也比较复杂,偶尔会出现打不通的情况。然而我们可以另辟蹊径,在本地自建DERP中继服务器,并通过LUCKY穿透到公网上。

第一步 安装并配置Tailscale

Tailscale官网:https://tailscale.com/

关于Tailscale基本的安装、配置在此不多赘述,教程非常多,在此贴一篇OpenWrt的教程:
openwrt使用tailscale组建网对网的一些补充
如果你需要在外访问整个内网,请正确配置子网路由。

如果可能的话,尽量将Tailscale和DERP安装在同一台机器上。即使不行,最好也在部署DERP的机器上安装Tailscale,用于身份验证。
如果用于验证的Tailscale客户端是通过docker安装的,为了让DERP能够与Tailscale交互,请添加路径映射
-v /run/tailscale:/var/run/tailscale

我将Tailscale装在路由器上,DERP安装在路由器下面的Unraid上。如果以插件的形式直接安装,则无法使用本地局域网地址访问Unraid,只能使用Tailscale的100开头的地址,因此我最后用Docker安装,网络选择br0并指定了另外一个ip。

当配置完成,已经可以在外网通过Tailscale访问后,再注册一个用于共享的小号,并将其加入大号的Tailscale网络,理由有以下两点:

  1. Tailscale免费计划只允许3个用户,但是允许100台设备。通过共享账户的方式,可以允许更多人连接。
  2. DERP服务器若不开启验证,那么谁都可以白嫖;但一旦开启验证,只有本机当前登录Tailscale的账户有权访问,其他用户均无法访问。

然后将所有Tailscale客户端上登录的账号换成小号,加入大号的网络(也就是在最后一步,选择connect device的时候,点击大号邮箱)。

第二步 安装并配置DERP服务器

首先先给DERP服务器准备一个域名,并解析到STUN穿透的ipv4公网地址,如果你之前没有用过ddns,直接使用Lucky的DDNS,通过接口获取ipv4即可。

接着申请证书,使用Lucky申请非常方便,并且可以映射证书路径供其它服务使用,以及在证书更新时调用自定义脚本;如果Lucky和DERP没有部署在同一个机器上,可以用Lucky的webdav功能将映射证书的路径分享出来,或者通过自定义脚本运行scp和ssh命令,上传证书并重启DERP。具体过程略过不表。

接下来安装DERP,我使用docker安装,记得修改证书的映射路径、端口映射和域名。我这里将8051作为derp端口,3478作为stun端口。如果不想开启身份验证,请将第七行的true​改为false​。

1
2
3
4
5
6
7
8
9
docker run --restart always \
-p 8051:443 -p 3478:3478/udp \
-e DERP_CERT_MODE=manual \
-v /your/cert/path:/app/certs \
-e DERP_ADDR=:443 \
-e DERP_DOMAIN=derp.xxxx.com \
-e DERP_VERIFY_CLIENTS=true \
-v /var/run/tailscale:/var/run/tailscale \
fredliang/derper

然后为DERP服务器创建STUN穿透规则,需要两条。一条给derp端口,协议选择tcp,一条给stun端口,协议选择udp,不会的朋友可以去看看第一、第二期教程。

image

如果一切顺利,就可以在浏览器里访问https://derp域名​:derp穿透后的公网端口​。

image

看到以上界面说明成功。

第三步 将DERP服务器添加至Tailscale中

用大号进入Tailscale后台,在顶部切换到Access Controls​选项卡,在acls​后添加derpMAP​规则。

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
	// 前面有一些注释,不修改
"acls": [
// Allow all connections.
// Comment this section out if you want to define specific restrictions.
{"action": "accept", "src": ["*"], "dst": ["*:*"]},
],
"derpMap": {
"Regions": {
"900": {
"RegionID": 900,
"RegionCode": "myderp",
"RegionName": "home",
"Nodes": [{
"Name": "1",
"RegionID": 900,
"HostName": "derp.xxxxx.xxx",
"DERPPort": 25814,
"STUNPort": 25876,
"InsecureForTests": true,
}],
},
},
},
// Define users and devices that can use Tailscale SSH.
//...后面是ssh的内容,不修改

修改Hostname​为你DERP服务器的域名,DERPPort​和STUNPort​分别改为对应的公网端口(不知道改成哪个端口就把我的规则和我之前的Lucky截图对比一下)。确认无误后点击保存。

在安装了Tailscale的机器(或容器)上执行

1
tailscale netcheck

如果没有出现测速结果里没有出现myderp​,请检查规则是否编写正确,如果没有这个节点没有测速结果,可以稍等一会儿再试。当看到myderp​节点的延迟后,证明配置成功。

第四步 自动更新DERP服务器端口

Tailscale支持通过网页后台面板、GitOps和api更新acl规则。使用GitOps可以清晰的看到每一次更新的时间以及是否成功,使用api则更加简单,本文使用api进行更新。

  • 生成OAuth client并获取id和secret

使用大号登录Tailscale后台,在Settings​ - OAuth clients​中点击Generate OAuth client​。OAuth client的名称随便填,勾选对ACL​的read和write权限。然后点击Generate client​,并复制生成的id和secret。

不同于最大有效期为90天的api key,OAuth clients不会过期,免去了手动更新key的麻烦。

  • 配置更新脚本

回到Lucky后台,编辑derp端口的穿透规则,将以下脚本填入自定义脚本​中。
记得修改client_id​和client_secret​为你刚才获取到的值。

考虑到可能会存在多个derp服务器,使用"DERPPort":​后面的空格个数来区分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#set your oauth client here
client_id=
client_secret=

api_key=`curl -d "client_id=$client_id" \
-d "client_secret=$client_secret" \
"https://api.tailscale.com/api/v2/oauth/token"`
tskey=`echo $api_key | awk -F "\"" '{print $4}'`

DERPPort=${port}
tmp='/tmp/tailscale.hujson'
curl "https://api.tailscale.com/api/v2/tailnet/-/acl" \
-H "Authorization: Bearer $tskey" > $tmp
sed -i -r 's/"DERPPort":\s{8,9}[0-9]+/"DERPPort": '$DERPPort'/' $tmp
curl "https://api.tailscale.com/api/v2/tailnet/-/acl" \
-H "Authorization: Bearer $tskey" --data-binary @$tmp

编辑STUN端口的穿透规则,将以下脚本填入自定义脚本​中。
记得修改client_id​和client_secret​。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#set your oauth client here
client_id=
client_secret=

api_key=`curl -d "client_id=$client_id" \
-d "client_secret=$client_secret" \
"https://api.tailscale.com/api/v2/oauth/token"`
tskey=`echo $api_key | awk -F "\"" '{print $4}'`

STUNPort=${port}
tmp='/tmp/tailscale.hujson'
curl "https://api.tailscale.com/api/v2/tailnet/-/acl" \
-H "Authorization: Bearer $tskey" > $tmp
sed -i -r 's/"STUNPort":\s{8,9}[0-9]+/"STUNPort": '$STUNPort'/' $tmp
curl "https://api.tailscale.com/api/v2/tailnet/-/acl" \
-H "Authorization: Bearer $tskey" --data-binary @$tmp

目前Lucky并未提供自定义脚本的测试功能,不过只是填写两个变量而已,应该不会出什么幺蛾子…吧。


经过实测,在晚高峰时,于NAT4的电脑上开启Tailscale,会出现无法跨运营商直连NAT1的另一客户端的情况;然而此时可以通过STUN穿透出去的自建DERP中继来建立连接。

第五步 为Tailscale添加本地DNS并劫持域名

愿意折腾到这一步的朋友,有很大概率已经开启了ipv6,并配置好了反向代理。如果想要实现“一个域名,在内网/Tailscale中用内网ipv4,在外网中用ipv6”的效果,可以尝试一下添加本地DNS。如果不想配置反向代理,那么可以跳过这步。

本地劫持泛域名

首先在你是用的dns服务中添加HOSTS,劫持你要访问的泛域名。如果不想在内网劫持,仅想在Tailscale中劫持,可以用Docker再装一个SmartDNS,记得用网桥(如br0)把容器IP绑定到与宿主机同网段,并使用53端口。

  • 对于dnsmasq(也就是OpenWrt的默认dns)

    打开/etc/dnsmasq.conf​,添加

    1
    address = /mydomain.com/192.168.0.1

    并重启dnsmasq,这会劫持mydomain.com​和它的所有子域名到192.168.0.1。

  • 对于SmartDNS
    自定义规则​或域名地址​中添加

    1
    address /mydomain.com/192.168.0.1

    并重启SmartDNS,这会劫持mydomain.com​和它的所有子域名到192.168.0.1。

然后在内网机器上清除dns缓存,使用nslookup​查询刚才劫持的域名。

由于OpenWrt会默认劫持所有访问53端口的流量到OpenWrt的53端口,因此如果你是用OpenWrt作为主路由,但自定义DNS不在OpenWrt的ip上,则需要在网络​ - 防火墙​ - 自定义规则​中注释掉所有跟53端口相关的规则。

如果还不成功,大概率是某些科学插件修改或劫持dns导致的,请关闭劫持功能或者直接在科学插件中设置DNS劫持。

在Tailscale中添加本地DNS

进入Tailscale后台,进入DNS​,点击Add nameserver​ - Custom​,输入你内网DNS的IP地址,可以为Tailscale的100开头的地址,也可以是添加进Tailscale的子网地址,并开启Restrict to domain​,填入需要劫持的顶级域名。(如填入mydomain.com​,会使用自定义DNS解析该域名及其子域名)请注意,Tailscale不支持指定协议和端口,默认使用53端口、udp协议。

可能部分DNS服务默认拒绝外网请求,需要手动开启。以OpenWrt为例,需要在网络​​ - DHCP/DNS​​ - 基本设置​​中取消勾选仅本地服务​​。

结语

至此Tailscale和DERP服务器的配置就结束了,经过这一番折腾,我们实现了100%直连率,并统一了内网、Tailscale、ipv6的使用体验。在SVCB/HTTPS记录普及之前,对STUN穿透的折腾大约就止步如此了吧。

如果你仍对安全性抱有担忧,可以自行修改acl规则,默认拒绝共享账户访问子网,仅允许访问特定的ip和端口。如果你想让特定的设备不受用户权限的限制,可以给它打上tag,再对该tag单独设置权限。