威联通官方提供的公网访问服务仅针对 QTS 中的部分应用,曾经想过用 Browser Station 是不是能访问内网应用,但是由于网络原因无法更新 Browser Station 中的 Chrome,只能放弃这个思路。最后只能通过内网穿透的方式实现目标了,最开始用 frp 进行内网穿透,是一种简单有效的方式,但是安全性着实堪忧,用了一个月,期间不断地被人扫端口、爆破密码,不堪其扰只能另寻他法。就我而言,想要达到的目标很简单,就是在内网和外网中都能够访问特定域名使用 NAS 上部署的服务。在网上搜索了一番,确定可以通过 Wireguard + SmartDNS 实现我的目标。
基本思路
Wireguard 和 SmartDNS 的基本信息就不多介绍了,简单来说就是 VPN 和本地 DNS 服务器。为了实现目标,主要的步骤有二:
- 通过 Wireguard 为需要相互访问的客户端组网;
- 使用 SmartDNS 将客户端 IP 和自定义域名进行绑定。
接下来分别说明这两步如何实现。
使用 Wireguard 进行组网
为了使用 Wireguard 进行组网,请确保有一台能在公网中访问的服务器,我自己在用的是腾讯云 99 元一年的轻量服务器,装个 Docker 部署 Wireguard 足够用了,注意:如果买国内云厂商的服务器,最好买境内的服务器,境外的话分分钟封端口。
在服务器上安装完 Docker 后可以直接使用 wg-easy
部署 Wireguard:
volumes:
etc_wireguard:
services:
wg-easy:
environment:
- LANG=chs
- PASSWORD_HASH=$$2y$$10$$hBCoykrB95WSzuV4fafBzOHWKu9sbyVa34GJr8VV5R/pIelfEMYyG
- WG_HOST=<your_host>
- WG_PORT=51820
- WG_DEFAULT_ADDRESS=10.8.0.x
- WG_DEFAULT_DNS=114.114.114.114
- WG_ALLOWED_IPS=10.8.0.0/24
- WG_PERSISTENT_KEEPALIVE=30
image: ghcr.io/wg-easy/wg-easy
container_name: wg-easy
volumes:
- etc_wireguard:/etc/wireguard
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
docker-compose.yaml
在上面的 compose 文件中,需要关注的是几个环境变量:
LANG
:设置 UI 的语言;PASSWORD_HASH
:设置访问 UI 的密码,可以通过docker run --rm -it ghcr.io/wg-easy/wg-easy wgpw 'YOUR_PASSWORD'
指令生成,在 compose 文件中需要将$
替换成$$
;WG_HOST
:Wireguard 的地址,如果有域名的话可以填写域名,没有的话填写服务器的 IP;WG_PORT
:Wireguard 的 UDP 端口。该选项和WG_HOST
在 UI 中创建客户端配置时会作为配置项——Endpoint
的值;WG_DEFAULT_ADDRESS
:组网中的客户端的 IP 地址范围,例如示例中的值意味着在 UI 中创建客户端配置时Address
的值可能是 10.8.0.2~10.8.0.255(1 默认被占用)中的一个;WG_DEFAULT_DNS
:Wireguard 客户端使用的 DNS 服务地址。网络中的客户端会通过该 DNS 服务对访问域名进行解析,然后再决定是否需要通过 Wireguard 隧道进行访问。暂时先写一个国内可以访问的 DNS 地址即可,后面会设置为 SmartDNS 的地址。该选项在 UI 中创建客户端配置时会作为配置项——DNS
的默认值;WG_ALLOWED_IPS
:一个关键配置,在 Wireguard 中AllowedIPs
用于定义哪些 IP 地址或网段的流量应该通过 WireGuard 隧道传输,例如示例中的配置意味着只有那些访问 10.8.0.1~10.8.0.255 地址的流量会通过 Wireguard 隧道进行传输,其他的访问依旧通过本地网络进行请求。该选项在 UI 中创建客户端配置时会作为配置项——AllowedIPs
的默认值;WG_PERSISTENT_KEEPALIVE
:链接保活时间,设置为 0 则表示不需要保活。该选项在 UI 中创建客户端配置时会作为配置项——PersistentKeepalive
的默认值。
在防火墙中开放 51820
端口的 UDP 访问(51821
是 wg-easy
控制台地址,按需开放),然后使用 docker-compose up -d
启动 wg-easy
,在浏览器中访问 <服务器 IP>:51821
(如果有配置 DNS 解析和反代的话直接通过域名访问即可),输入密码即可访问 wg-easy
控制台,用法很简单,点击“添加(New)”,输入一个名称,但后点击确定即可创建:
在这里下载对应平台(Windows、macOS、iOS、Android、Linux 等)的客户端程序,然后导入刚刚添加的配置即可。注意:每个客户端都应该创建一个配置。在 QTS 中,可以在直接在 QVPN Service 3
中添加一个 Wireguard 配置,Wireguard 配置文件的字段对应关系是:
- 接口-服务器名称:随便填
- 接口-私钥:Wireguard 配置文件中 [Interface] 的
PrivateKey
字段(填写后点击“生成密钥对”自动填写公钥字段); - 接口-IP 地址:Wireguard 配置文件中 [Interface] 的
Address
字段 - 接口-侦听端口:留空即可
- 接口-DNS服务器地址:Wireguard 配置文件中 [Interface] 的
DNS
字段 - 对等设置-公钥:Wireguard 配置文件中 [Peer] 的
PublicKey
字段 - 对等设置-端点:Wireguard 配置文件中 [Peer] 的
Endpoint
字段 - 对等设置-预共享密钥:Wireguard 配置文件中 [Peer] 的
PresharedKey
字段 - 对等设置-允许的 IP:Wireguard 配置文件中 [Peer] 的
AllowedIPs
字段 - 对等设置-永久有效:Wireguard 配置文件中 [Peer] 的
PersistentKeepalive
字段
通过 SmartDNS 将客户端 IP 和特定域名绑定
通过上面的步骤,加入同一个 Wireguard 网络中的客户端之间可以通过 IP 地址互相访问。此时其实已经可以通过在 wg-easy
的网络上部署 Web 服务器(如 Caddy 或 Nginx),并将对特定域名的访问直接反向代理到该网络中其他客户端的端口上,最后将 Web 服务器的 80 映射到服务器的 80 端口即可实现在公网中通过域名访问特定服务,但是这么做有两个问题:
- 网络中部署的服务如果没有必要的安全措施可能存在风险;
- 如果通过域名访问,那么即使在家庭网络中依旧需要通过公网服务器,对访问速度有一定的影响。
如前所属,Wireguard 网络中的客户端首先会根据 DNS
设置对域名进行解析,然后根据解析得到的 IP 地址判断是否在 AllowedIPs
内,在的话才会通过隧道进行传输。所以前面的问题可以通过部署私人 DNS 服务器来解决。具体来说有以下三个步骤:
- 需要互相访问的客户端都加入 Wireguard 网络;
- 在 Wireguard 网络中部署 DNS 服务,并通过 DNS 配置将特定域名和网络内部的客户端 IP 绑定;
- 在客户端上部署并配置反向代理。
对于问题 1,因为反向代理是在客户端上部署的,只有连接到 Wireguard 网络内部的客户端才能通过域名进行访问。
对于问题 2,可以通过在 DNS 上为域名配置多个 IP 地址(家庭网络中的 192.168.x.x 或 Wireguard 内网中的 10.8.x.x)在不同的网络环境中获取到访问速度更快的 IP 地址。不过由于查询 DNS 的时候依旧需要访问一次服务器,因此速度还是不及直接访问来的快,虽然也可以在客户端部署 DNS 服务,但是考虑到移动端的使用需求,还是直接在公网服务器上配置比较方便。
下面是一个简单的 SmartDNS 配置文件,具体的 compose 文件见后文:
bind [::]:53
server 114.114.114.114
server 202.96.128.166:53
server-tls 1.1.1.1
address /*.example.localhost/192.168.x.y,10.8.x.y
smartdns.conf
通过上面的配置文件中,所有针对 *.example.localhost 泛域名的查询都会返回 192.168.x.y 和 10.8.x.y 这两个 IP 地址。
一份完整的配置
在下面的配置文件中,启动了三个服务 caddy、wg-easy 和 smartdns,其中:
- caddy:用于进行反向代理,除了可以反向代理 Wireguard 网络中的服务,也可以通过配置一个额外的 network 反向代理其他容器启动的服务(需要购买域名并配置 DNS 解析);
- wg-easy:快速配置并启动 Wiregaurd 网络;
- smartdns:自定义 DNS 域名解析。
这些配置并不复杂,唯一需要注意的是 caddy 和 smartdns 服务应该直接使用 wg-easy 的网络配置(即 `network_mode: "container:network_wg-easy"),这样才能顺利让这两个服务加入到 Wireguard 网络中。除此之外 wg-easy 容器需要将 80、443 和 51820 端口映射到宿主机,其中 80 和 443 是为 caddy 准备的,51820 则是默认的 Wireguard 端口,至于 wg-easy 的 Web 界面(51821 端口)可以通过添加一个 Caddy 配置进行反代,因此不映射也没关系。
services:
caddy:
container_name: "caddy"
cap_add:
- NET_ADMIN
restart: always
image: "library/caddy:alpine"
network_mode: "container:network_wg-easy"
depends_on:
- network_wg-easy
volumes:
- ./volumes/caddy/data:/data
- ./volumes/caddy/config:/config
- ./volumes/caddy/site:/srv
- ./volumes/caddy/Caddyfile:/etc/caddy/Caddyfile
network_wg-easy:
container_name: "network_wg-easy"
cap_add:
- NET_ADMIN
- SYS_MODULE
dns:
- 127.0.0.1
environment:
- LANG=chs
- PASSWORD_HASH=<your_password_hash>
- WG_HOST=<your_host>
- WG_PORT=51820
- WG_DEFAULT_DNS=10.8.0.2
- WG_ALLOWED_IPS=10.8.0.0/24
- WG_PERSISTENT_KEEPALIVE=30
image: ghcr.io/wg-easy/wg-easy
ports:
- 51820:51820/udp
- "80:80"
- "443:443"
- "443:443/udp"
restart: "unless-stopped"
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv4.ip_forward=1
volumes:
- ./volumes/network/.wg-easy:/etc/wireguard
network_smartdns:
container_name: "network_smartdns"
image: "pymumu/smartdns"
network_mode: "container:network_wg-easy"
restart: "unless-stopped"
volumes:
- ./volumes/network/smartdns:/etc/smartdns
# 其他服务 ...
docker-compose.yaml