内网穿透NAS最佳实践:兼顾安全与灵活性

背景

为了能在外部访问家里的NAS,需要将NAS内网穿透到公网。在大陆做NAS的内网穿透主要存在几个挑战:

  1. 无法控制光猫
    运营商仅提供光猫的普通帐号,而只有管理员帐号才能改桥接、禁防火墙、开端口映射……
  2. 没有公网IP
    光猫获取的IP是运营商的大内网IP(一般为10.X.X.X),无法跨网访问
  3. 公网IP周期性动态变化
    光猫获取的公网IP每周都会随机更改几次,无法直接通过记录IP访问
  4. 公网IP的主流端口被封
    运营商通常直接封禁80, 443等主流的端口,通过公网IP根本访问不到或容易被封[1]
  5. 禁止私设网站
    大陆禁止未备案的网站提供服务
    实践上,如果网站没备案
    大陆IP的云服务器只能通过IP提供服务,不能通过域名访问;
    大陆家宽不能将HTTP/HTTPS直接暴露在公网,即使不绑域名也不行,会被断网[2]
  6. 暴露在公网上的网站易被攻击
    NAS使用规模大,直接把NAS的服务暴露在公网,未及时更新容易被攻击,或直接被弱密码攻击[3]
  7. 使用VPN保护网站后不方便访问
    使用Tailscale、OpenVPN等VPN作为内网穿透方案,在外部访问时不够方便,需要切换到专用的VPN客户端,无法同时魔法分流访问外网

本文重点解决问题3,4,5,6,7。不解决1,2。

方案对比

todo

准备

  1. 一个Cloudflare免费帐号
  2. 一个域名,已经托管到了Cloudflare(可参考[4]

    最好注册一个收费域名
    免费域名不稳定,一旦域名失效更换新域名过于折腾
    Namecheap 注册6位数字.xyz域名或aabbbbb.xyz类数字域名,仅需$0.85/年

  3. 光猫已设置内网穿透

    光猫不会主动把局域网内的设备映射到公网
    需要超级密码以:

    1. 修改光猫为桥接模式,让下级路由器拨号拿到公网IP
    2. 或设置DMZ主机,把下级路由器整个映射到公网(本文的选择)
  4. 一个支持端口映射的路由器(近年的路由器绝大多数都支持)
  5. 一台NAS,本文以群晖为例
参数项预设值
光猫内网IP192.168.1.1
路由器内网IP192.168.1.100(光猫分配)
192.168.49.1(路由器分配)
NAS IP192.168.49.100
SS Server端口2333
VMess Server端口2334
L2TP/IPSec端口500 1701 4500
OpenVPN端口12345

路由器以小米路由器为例。更改默认网段192.168.31.1192.168.49.1是为了避免网段冲突:
如果不更改默认网段,外网环境也使用了小米路由器,其网段为默认192.168.31.1时,Clash无法正确将流量基于IP分流,大部分回源功能失效。

实战方案

方案简介

配置域名DNS解析

登录Cloudflare Dashboard,找到domain.xyz的DNS管理页面:

MainWebsitesdomain.xyzDNSRecords

按照下图配置DNS:

注意:图中内容需要替换为你的真实域名、IP

DNS Record用途
domain.xyzNAS的内网IP,后续通过Clash, VPN均可基于内网域名访问到NAS
**.domain.xyz的通配解析
配合群晖反向代理服务器解析emby.domain.xyz等域名
frpFRP Server的公有云服务器IP
v4, v6, v家庭网络的公网IP,无需手动配置,后续由ddns-go自动维护更新;
v.domain.xyz是双线域名,根据接入网络自动选择IPv4/IPv6

配置完DNS后,需要等待旧的的DNS配置过期,新的DNS配置生效,一般不超过3~5分钟。

配置反向代理

从Synology DSM → 控制面板登录门户高级反向代理服务器新增 添加新的反向代理配置,如图所示:

border

以Emby为例,这里来源选HTTP协议和80端口,后续配置SSL证书后,这里可以配置为HTTPS协议和443端口。

目的地主机名一般填localhost,端口注意是通过NAS主机访问的端口,如果服务是Docker容器启动,需要填映射到宿主机的端口,而非容器内端口。

现在,可以用http://emby.domain.xyz在内网访问到Emby。

(可选)配置SSL通配符证书

HTTPS协议的推广对明文HTTP网站越来越不友好,经常看到浏览器对明文HTTP网站的不安全警告。尽管本方案实际上由SS, VPN等协议的加密来实现信息安全性,但为每个网站加一层HTTPS访问也是不错的。

下面介绍一种自动更新SSL证书的方法,且是通配符证书,即可以为所有*.domain.xyz均配备SSL证书,无需在新增子域名时额外申请证书。

点击查看

参考[5],配置自动申请并安装Let’s Encrypt通配符证书。

注意syno-acme项目中的部分配置需要改为Cloudflare:

syno-acme/config
1
2
3
4
5
6
7
8
9
# DNS类型,根据域名服务商而定
export DNS=dns_cf

# 参考https://github.com/acmesh-official/acme.sh/blob/master/dnsapi/dns_cf.sh
export CF_Key="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export CF_Email="[email protected]"
export CF_Token="xxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx_"
export CF_Account_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export CF_Zone_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
字段来源
CF_EmailCloudflare帐号
CF_Account_ID
CF_Zone_ID
可在Cloudflare Dashboard找到
MainWebsitesdomain.xyzDNSRecords
CF_Key
CF_Token
点击Get your API token去创建
Create TokenEdit zone DNS Use template → 编辑Token nameZone Resources添加domain.xyz

创建Cloudflare API Token如下图所示:

border

配置完成后,可以添加/更新群晖反向代理配置,

从Synology DSM → 控制面板登录门户高级反向代理服务器新增/编辑HTTP 80改为HTTPS 443

之后可以通过https://emby.domain.xyz在内网访问Emby。

配置DDNS

DDNS用于解决公网IP周期性动态变化的问题,实时检测家庭网络的公网IP并更新DNS记录,可以通过DNS解析获取家庭的公网IP。

如图所示,从Synology DSM → 套件中心设置套件来源 新增矿神源(对应DSM 7.x版本)

https://spk7.imnks.com/

border

安装并打开DDNS-GO套件。

如图所示,添加Cloudflare的DDNS配置:

border

这里的Cloudflare Token需要从Cloudflare Dashborad中添加

需要为domain.xyz授予DNS编辑权限:

border

配置完成后保存,可以在Cloudflare Dashborad的DNS管理页面看到由DDNS-GO自动添加的DNS记录:

Cloudflare Dashboard → MainWebsitesdomain.xyzDNSRecords

配置完成后,打开电脑的CMD,ping v4.domain.xyz应该能解析到公网IP并正确ping通。

如果未正确解析,可以稍等5分钟;

如果不能ping通,需要检查DDNS-GO提交是否为公网IP、光猫防火墙/路由器防火墙是否关闭。

最好在外网测试是否能ping通,建议用手机流量测试。Android使用终端模拟器,iOS使用网络调试助手

配置SS Server

如图所示,SS是集成到Clash实现分流、回源的关键。所有回源流量经由Clash转发到SS server,再解析到家庭内网资源。

大陆内部传输的非HTTP流量没有任何阻断措施,直接使用最基本的SS配置即可。

下面使用Docker部署SS Server:

1
2
3
4
5
6
7
8
9
10
docker pull gists/shadowsocks-libev:latest

docker run -d \
--net=host \
--name=ss_server \
-e METHOD=aes-128-gcm \
-e PASSWORD=your_password \
-e SERVER_ADDR=0.0.0.0 \
-e SERVER_PORT=2333 \
gists/shadowsocks-libev:latest

配置端口映射

本节假设光猫已经改桥接/设置路由器为DMZ主机,可以通过公网访问。

测试链路连通性:

登录群晖SSH,用Python3在2334端口启用一个简单的http服务器,并保持在后台运行:

1
python3 -m http.server 2334

之后登录路由器后台 → 高级设置端口映射,添加 2334192.168.49.100:2334 的TCP端口映射。

某些小米路由器型号,更改完端口映射后,需要在页面底部应用更改,否则不会生效。

之后用手机访问http://v4.domain.xyz:2334/,能显示网页说明链路联通。

将端口映射改为2333即可将SS Server的端口暴露到公网。

(可选)frp内网穿透

长期在外地,一旦遇到光猫配置下发、失去公网IP/端口映射失效,会立刻失去回家途径,无法远程修复网络。

FRP是家宽无公网IP的回家方案,还可以作为公网无法直连时的临时备用解决方案。

点击查看

搭建FRP Server需要公网云服务器。从性价比考虑,作为日常用不到的备用方案,建议使用国内免费的FRP服务。

免费FRP服务往往限制了端口映射数量,并禁止建站。

将FRP套在SS server/VPN前,正好规避了上述两个问题:

  • 端口数量限制
    所有服务均通过前置的SS/VPN路由到局域网内访问,SS/VPN只需要一个端口
  • 禁止建站
    所有服务均藏在SS/VPN后,仅对局域网开放,类似于防火墙,暴露SS/VPN并不存在安全和监管风险

在群晖中安装FRPC客户端套件,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[common]
server_addr = frp.mydomain.xyz
server_port = 12345
tcp_mux = true
protocol = tcp
user = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
token = xxxxxxxx
dns_server = 114.114.114.114
tls_enable = false

[ss]
privilege_mode = true
type = tcp
local_ip = 127.0.0.1
local_port = 2333
remote_port = 45678

[common] 部分配置按照云服务器的配置或免费FRP服务提供的配置,重点是[ss]配置要注意映射到NAS的SS端口 2333 上。

VPN也可添加到FRPC做穿透。

L2TP/IPSec不支持自定义非标准端口,所以这里一般只能穿透OpenVPN。

保存配置后,后续可以使用 frp.mydomain.xyz:45678 连接到SS Server。

(可选)Cloudflare Tunnel

Cloudflare Tunnel是另一种家宽无公网IP的(临时)回家方案,也可以作为公网无法直连时的临时备用解决方案。相比于FRP方案,这种方式不需要我们自备FRP公网服务器,但延迟(≥1s)和带宽(~1MB/s)较差,仅适用于无法直连回家时,避免NAS彻底与外部失联的备选方案。

点击查看

如图,Cloudflare Tunnel有2种用法:

  1. 直接暴露内网服务
    将内网的SSH HTTP等服务直接暴露在由Cloudflare转发的公网上。这种方式在外部访问简单,但将服务暴露在公网易受到外部攻击,例如群晖、Emby弱密码/漏洞攻击等
  2. 通过魔法间接暴露服务
    由于第一种方法存在安全问题,仍然建议前置SS等代理服务,在接入代理后再访问内网服务。

本文介绍第二种用法。

Cloudflare Tunnel支持任意TCP转发[6],但无法通过Cloudflare CDN直接访问TCP连接,需要在客户端使用Cloudflared建立本地TCP映射端口,通过端口间接访问远程TCP代理。这大大限制了连接的灵活性。

为了保持灵活性,需要使用Cloudflare Tunnel的HTTP/HTTPS映射功能,这种方式可以直接访问而无需安装Cloudflared客户端。这就要求代理服务本身是HTTP/HTTPS协议或伪装成相关协议的类型,例如Trojan, VMess, Hysteria2等。

本文以VMess为例展示,其也是绝大多数Clash客户端最广泛支持的一种协议。

Cloudflare Dashboard → Zero TrustNetworksTunnelsCreate a TunnelCloudflaredCloudflaredSave your tunnel

此时,你需要在页面中的安装指令里拿到token字段。

页面显示不全,需要手动复制指令,将指令中的token字段完整复制出来

然后,在群晖套件中心安装来自第三方社区SynoCommunity的Cloudflare Tunnel套件。

对于非群晖NAS或无法安装套件的情况,可以按照页面提示,使用Docker指令一键启动Cloudflared容器。

如果搜索不到相关套件,请手动添加SynoCommunity第三方套件源再搜索,地址为https://packages.synocommunity.com/

安装参数配置略。重点是token字段需要填写上文获得的值。

启动后,Cloudflare配置页会同步显示一个Connector上线,然后点击Next

现在Cloudflared已经正确配置上线,类似于FRP,但在Cloudflare后台设置、远程下发端口映射配置,比FRP操作更灵活。

点击Add a public hostname,按照实际情况填写。本文以如下配置为例:

  • Domain:
    vm.domain.xyz
  • Service:
    HTTP://localhost:2334

其中2334是VMess Server的Docker容器暴露的端口,不需要用路由器端口映射到公网。

border

保存配置后,后续可以使用 https://vm.domain.xyz(默认443端口) 连接到VMess Server。

由于Cloudflare Tunnel暴露的服务位于境外,直连可能会被检测屏蔽,连带影响整个域名。因此建议参考下面的Clash分流规则,配置Relay代理链,先通过前置节点出国,再连接Cloudflare Tunnel,就不会被检测。

Clash分流规则

如图所示,基于Clash方案的核心优势就是可以自动分流各类流量,在回家的同时不影响正常在外部网络中的其他流量,及其他特殊流量需求。

下面是一个简单的Clash分流回源规则片段,将所有 *.domain.xyz192.168.49.0/24 的流量路由回家(仅节选proxies, proxy-groups, rules字段):

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
# ...
proxies:
- name: 回家节点
type: ss
server: v4.domain.xyz
port: 2333
cipher: aes-128-gcm
password: your_password
- name: 代理回家节点
type: ss
server: frp.domain.xyz
port: 2333
cipher: aes-128-gcm
password: your_password
- name: Cloudflare回家节点
type: vmess
server: vm.domain.xyz
port: 443
uuid: 12345678-abcd-abcd-abcd-123456789012
alterId: 0
cipher: auto
network: ws
tls: true
skip-cert-verify: true


proxy-groups:
- name: 回家
type: select
proxies:
- 回家节点
- 代理回家节点
- 代理->CF回家
- DIRECT

- name: 代理->CF回家
type: relay
proxies:
- 出国节点
- Cloudflare回家节点


rules:
- DOMAIN-SUFFIX,domain.xyz,回家
- IP-CIDR,192.168.49.0/24,回家,no-resolve
  • 在家
    回家代理组切换到DIRECT,直连高速访问内网资源
  • 在外
    回家代理组切换到回家节点,通过外网IP直连访问资源
  • 在外+公网IP不通
    回家 代理组切换到 代理回家节点,通过公网的FRP Server代理访问资源,适用于DDNS故障、光猫桥接失效等多种故障临时修复场景
    或切换到代理->CF回家,先通过出国节点出国,然后通过Cloudflare Tunnel代理的VMess节点回家

    注意这里VMess Server本没有启用TLS(即HTTP协议),但经过Cloudflare Tunnel代理后提供HTTPS协议,因此需要使用tls: true参数。

(可选)配置VPN

VPN不能分流,但仍有使用价值:

  1. 针对无法代理的流量,VPN可以强制全局回源
  2. VPN会维持TCP连接,可规避某些网络对后续建立的TCP连接的限速,最开始建立的TCP连接不限速
  3. L2TP/IPSec等VPN协议由Win, Android, iOS系统内置支持,无需下载客户端。

点击查看

群晖套件中心默认找不到VPN Server套件,需要前往英文官网下载:

https://www.synology.com/en-global/support/download/

注意DSM版本要匹配,区分6, 7.1, 7.2

Packages选项卡下可以找到VPN Server,下载套件并在套件中心手动安装启用。

VPN Server中,L2TP/IPSec对应iOS系统中的IPsec类型。

其中,VPN Server配置项预共享密钥对应iOS系统中的密钥字段,群晖系统登录帐户/密码对应iOS系统中的账户/密码字段。

服务器地址填v4.domain.xyz

OpenVPN使用较为麻烦,有兴趣的读者可以自行探索。此处列出后续使用可能遇到的证书过期问题及解决方案[7]

客户端推荐

  • Windows: Clash for Windows
    虽然已经删库跑路,也不支持Hysteria2等新协议,但符合使用习惯,异常重启电脑后打开网页不会出现“无法连接到代理”的问题
  • iOS: Stash
    对Relay代理链支持较好;配置文件基本与CFW相同,可以共用一份配置文件;价格友好
  • MacOS: Stash
    对于Arm Mac,直接运行iOS购买的Stash就行
  • Android: Clash for Android
    跨平台选择
  • NAS:
    后端服务 - clash-premium[8]
    Web面板 - YACD[9]
    对于有NAS上部分需要代理的服务,可以用clash-premium做后端,YACD做前端,配置文件可以复用,proxy-providers, rule-providers也可以自动更新

特殊APP需求

  • Emby的Windows客户端不支持Clash系统代理,需要开启TUN模式(仅播放时需要,查看刮削信息可以被代理)
  • 音流暂不支持Clash系统代理,需要开启TUN模式

其他注意事项

  • 暂未研究IPV6,除L2TP/IPSec外其他部分理应均可支持IPV6
  • todo

  1. 1.家用宽带不能使用 443/80 端口,大家是怎样实现不加端口号,仅域名访问服务的? - V2EX
  2. 2.有关近期热议的“私设 web 被查”的解决方案 - V2EX
  3. 3.记一次群辉中勒索病毒后的应急响应-腾讯云开发者社区-腾讯云
  4. 4.如何将NameSilo域名解析到Cloudflare - Beecommercer
  5. 5.群晖 Let's Encrypt 泛域名证书自动更新
  6. 6.Arbitrary TCP | Cloudflare Zero Trust doc
  7. 7.Synology 证书更新 - kiraKo学习笔记
  8. 8.dreamacro/clash-premium - Docker Image | Docker Hub
  9. 9.haishanh/yacd - Docker Image | Docker Hub