Lucky内网穿透实践
前言
根据 RFC1918 和 RFC4193 规范,IPv4 有 3 段保留地址段,不能在公网中路由传播,只用于局域网通信。
地址段 | 掩码 | 地址范围 | 典型用途 |
---|---|---|---|
10.0.0.0/8 | 255.0.0.0 | 10.0.0.0 ~ 10.255.255.255 | 企业内部大型网络 |
172.16.0.0/12 | 255.240.0.0 | 172.16.0.0 ~ 172.31.255.255 | 云服务默认网段(如:AWS) |
192.168.0.0/16 | 255.255.0.0 | 192.168.0.0 ~ 192.168.255.255 | 家庭/小型办公局域网最常用✅ |
家庭路由器常见的地址:192.168.1.1, 而局域网中的设备 IP 通常是 192.168.1.x。我们知道,在脱离了自家局域网环境,就无法访问自家 192.168.1.x 的任何地址了。
能不能做到离开家呢,我还能访问到自家 192.168.1.x 的任何地址呢?当然可以,VPN(Virtual Private Network,虚拟专用网络) 就是用来做这个的,它是一种通过公用网络(如互联网)安全地连接到另一个网络的技术,核心就是:让你像在一个远程局域网/蜂窝数据网中一样访问资源,同时保证通信加密、安全。
通过 VPN 我们可以连通家庭内部服务,如:Nas、Docker、GitLab、数据库、打印机、远程桌面,还支持虚拟局域网,让多个异地设备在一个虚拟局域网中相互访问。
不过,我们今天的目的是做内网穿透,虚拟局域网不在我们的需求范围中。我的做法总结起来就是在 OpenWrt 中搭建 Shadowsocks➕Shadow-TLS 服务,来实现需求。
1. 内网穿透
这一部分利用 Lucky 实现非常简单,只需要略微配置就开始了,只不过新手会比较懵逼,不知道怎么配置,这里我就将自己摸索出来的经验分享出来。
关于 Lucky 是什么,我就不多说了,官网上有介绍,接下来我们按步骤操作,安装 Lucky 并设置好内网穿透。
1.1 Lucky 安装
Lucky 适配多种平台:绿联NAS、宝塔、群晖矿神源、iStoreOS、飞牛NAS、CasaOS,在这些平台下你可以通过软件商店直接安装,也可以用 Docker 方式安装。而我使用的是 OpenWrt 系统,需要手动安装,接下来说安装步骤:
- 查看 CPU 核心架构,可以使用命令:
uname -m
或cat /proc/cpuinfo
或grep "model name" /proc/cpuinfo
、grep "Processor" /proc/cpuinfo
。 - 打开GitHub release,下载对应 CPU 架构的核心包。比如,我的 CPU 架构是
aarch64/armv8/arm64
,名称中带wanji
的是万吉
版本,否则就是lucky
。考虑到 R2S 的硬件资源有限,且万吉多出来的功能我也用不上,因此就下载了 Lucky 版本。 - 再下载
luci-app-lucky_XXX_all.ipk
和luci-i18n-lucky-zh-cn_XXX_all.ipk
,按照下载顺序依次安装。
安装完成后,我们在后台页面启动它,并访问http://192.168.0.1:16601
1.2 STUN内网穿透
1.2.1 STUN穿透原理
先说说STUN内网穿透是个什么原理,而在讲它之前,我们还需要了解下常见的网络 NAT 类型(从最开放到最严格):
NAT编号 | NAT类型 | 说明 | 是否易穿透 | 示例 |
---|---|---|---|---|
NAT0 | 无 NAT(Public IP) | 设备本身有公网 IP,无需转换 | ✅ 不用穿透 | VPS、光猫桥接模式 |
NAT1 | 全锥形 NAT(Full Cone NAT) | 所有外部主机都可以通过固定端口访问你,只要你先发过一次数据 | ✅ 最容易穿透 | 家用公网宽带 |
NAT2 | 受限锥形 NAT(Restricted Cone NAT) | 外部主机必须是你发过数据的 IP,端口无关 | ✅ 可穿透 | 一些家庭宽带 |
NAT3 | 端口受限锥形 NAT(Port Restricted Cone NAT) | 外部主机必须是你发过数据的 IP+端口 | ⚠️ 较难穿透 | 大多数运营商 NAT |
NAT4 | 对称 NAT(Symmetric NAT) | 每次连接不同目标,映射的端口不同,无法打洞,只能用中继 | ❌ 难穿透(打洞基本无效) | 手机热点、校园网、企业网 |
我们可以通过NatTypeTester工具来查看自己的网络处于哪种 NAT 类型。
可以看到,我的网络类型是 NAT1,是最容易穿透的。
再多说一点,Full Cone NAT
的网络类型需要运营商和路由器同时支持才行,在 OpenWrt 后台中要启用FullCone NAT
相关的设置,如果启用了设置,测试出的结果依然不是Full Cone NAT
,那就说明是运行商给你的网络就不是Full Cone NAT
。
而 Lucky 支持穿透的网络类型为NAT1,如果不是 NAT1,就不用继续操作了。不过可以试试Tailscale,它不挑网络。
通过上面可以了解 STUN 穿透的原理就是:通过请求 STUN 服务器,然后 STUN 服务会告诉我们,它看到我的 ip 和 port 是多少,外部网络通过这个 ip:port 就可以访问到我们的拨号设备了。
1.2.2 STUN穿透设置
在 Lucky 后台的STUN内网穿透
->设置
下,启用Stun穿透模块
,然后点击查看最新可用STUN服务器列表
,全选复制到全局Stun服务器列表
,保存修改。
然后,添加穿透规则
,这里穿透通道本地端口443
就表示将外部流量转发到了 WAN 网卡的 443 端口,你也可以指定其他任意端口。
这里勾选了不使用Lucky内置端口转发
,而是在软路由的防火墙配置端口转发,因为软路由上是内核级转发,性能会好一些。
另外,可以看到我启用了Webhook
,它支持标准的 HTTP 请求,当公网的 IP 和 PORT 发生变化时,会触发这个 Webhook ,也就会发起我们配置的 HTTP 请求。这个请求可以随意发挥,比如可以是执行本地脚本(后面会给出)、通知推送到手机。
至此,内网穿透就配置好了,是不是超级简单!下图表示,当外网访问 ip:5243 端口,流量就会被转发到我们拨号设备的 WAN 网卡的 443 端口。
1.2.3 防火墙配置
内网穿透可以让我们从外网访问到内网,可是互联网毕竟是公开的,其他人也能访问。并且,对于在内网中的一些服务,我们多数设置的都是弱密码,自己使用很方便,可是其他人恶意访问,攻破弱密码也很方便。所以,就需要配置防火墙,阻止外网直接访问到内网。那我们自己访问怎么?没关系,配置端口转发就好了。
1.3 动态域名
动态域名,也就是 DDNS(Dynamic DNS,动态域名系统),是一种将动态 IP 地址自动绑定到一个固定域名的服务。
如果没有域名,可以去dynv6申请一个,纯免费。如果已有域名,那就直接用,不过最好是能托管到Cloudflare上,或者直接在Cloudflare申请一个(付费的)。
在 Lucky 后台的动态域名
菜单下,点击添加任务
。
选择托管服务商
,你的域名托管在哪就选哪个,然后根据提示去获取一个 Token 填在这里。实际上就是托管服务商开放了接口,允许外部修改 DNS 记录,Token 是接口鉴权用的。
上一部完成后,点击添加记录
,例如我们申请的域名是lucky.dynv6.net
,那么添加的解析记录如下。
至少需要添加两条解析记录,主域名:lucky.dynv6.net
、泛域名:*.lucky.dynv6.net
。这表示会将你的公网 IP 解析到lucky.dynv6.net
和*.lucky.dynv6.net
下面,也就是说你可以是使用lucky.dynv6.net
和*.lucky.dynv6.net
来访问,*
表示任意值,如abc.lucky.dynv6.net
、123.lucky.dynv6.net
、abc-123.lucky.dynv6.net
等,可以任意创建。如果你不想使用泛域名,那就不要用*
,改成一个固定的值即可。
还要注意的是记录类型
,这里我们是将 IPv6 地址记录到了我们的域名,你也可以更改/新增一个记录,记录类型
选择为A(IPv4)
,这样就可以将 IPv4 的地址记录到域名了。
如果你只是把 IPv6 的地址记录到了域名,那么访问时需要客户端也具有 IPv6 的地址才行,否则无法访问。一般使用蜂窝网络(数据流量)都是有 IPv6 地址的。如果你介意这一点,那就配置把 IPv4 的地址一同记录到你的域名即可。
访问的时候要注意,由于运营商给的 IPv6 地址通常是公网 IP,哪怕它是动态的也是公网 IP,通过这个 IPv6 是可以直接放访问到你的设备,没有经过 NAT 转换,流量就到达了你的 WAN 网卡,这意味着你开放一个固定的端口就可以了,访问时可以通过域名:固定端口。
但是通过 IPv4 访问就不一样了,由于 IPv4 地址不是公网 IP,它是一个经过了 NAT 转换的运营商局域网 IP,IP 和端口都是会随机变化的。所以,即使你把 IPv4 记录到了域名,访问的时候也需要使用域名:动态端口才可以。在我的使用场景下,我认为把 IPv4 记录到域名是没必要的,所以只记录了 IPv6 地址。
注意:我发现通过 IPv6 访问会被严重限速,但是使用 IPv4 可以跑满上行。这个跟运营商策略有关,可能你的 IPv6 不会限速。不过也有说可以打电话投诉,但不一定有用。在我的使用方式中,只要 IPv6 能通就行,这点限速不影响,因为我主要使用 IPv4 访问内网服务。
1.4 Web服务
其实就是反向代理,跟你起一个 Nginx 做是一个意思,只是 Lucky 提供给你更便捷的操作。我们在Web服务
下点击添加Web服务规则
。
这里监听端口 443
,表示监听 WAN 网卡的 443 端口,并且启用了防火墙自动放行
,会自动放行 WAN 网卡的 443 端口。
接着选择标记默认规则
,服务类型选择关闭连接
,这一点很重要。它表示当未匹配到Web服务
中任一条规则时的行为,我们让他关闭连接,防止别人恶意试探,导致内网服务意暴露,非常危险。
至此,我们的网络设置部分就完成了,接下来开始搭建服务器节点。
2. 服务节点
我们通过搭建一个Shadowsocks
服务器节点,让客户端通过Shadowsocks
加密协议连接到服务器(即软路由),后续与内网的通信都将这条加密隧道中往来,保证通信安全。
有多种节点搭建方式,主要是这么多种方式我都尝试过了,记下来只是证明我曾经做过。
只是跟着实操的话,推荐选择shadowsocks-rust-ssserver
或者sing-box
。这俩区别在于,前者只能用来搭建shadowsocks
节点,如果需要伪装/混淆还需要额外安装其他软件包;后者是一个大而全的包,除shadowsocks
外还可以搭建其他协议的节点,如TUIC
、VLESS
、Hysteria2
、AnyTLS
等。
实际运行起来,sing-box
的内存占用可能稍大一点点,但我感觉是没多大区别,所以更推荐使用sing-box
。
当然,如果不想自己搭建,还有更简单的方式,ImmortalWrt
的作者开发了HomeProxy
,它底层其实就是用的sing-box
,可以在ImmortalWrt
源中直接下载。如果不想用HomeProxy
也可以使用passwall2,用起来都差不多。
HomeProxy 软件包含了客户端和服务端,客户端就是让设备实现科学强国的,服务端就是提供出来让其他设备连接,让其他设备实现科学强国。它支持多种协议,包含shadowsocks
,我们可以直接用这个服务端实现就好了。
要注意的是,它的服务端仅仅是一个桥接功能,需要客户端保持启用状态,并且客户端有能够连通的节点,否则服务端就不能正常提供服务。这种方式是能用,但是我个人不推荐使用,因为过于依赖客户端,一旦客户端的节点不通了,那从外网穿透回内网就也不通了,不稳定的方式我不用。
2.1 Shadowsocks-libev
这是一个用 C 语言开发的软件包,软件包说明,软件包下载地址,选择对应的 CPU 架构下载即可。 可以看到有两个下载选项shadowsocks-libev
和shadowsocks-libev-sever
,前者是包含服务端与客户端,后者只有服务端,所以按需下载即可。
另外,它还可以与luci-app-shadowsocks配套使用,提供控制界面,方便操作。不过要使用它你必须安装shadowsocks-libev
。我个人感觉是没必要,只安装shadowsocks-libev-sever
即可,最小化配置提供服务,配置好后开机自启就好了,又不会经常动它。
不过这软件包最近一次发布版本在 2020 年了,作者应该都不维护了,这个我就没做过多尝试。再加上我发现ImmortalWrt
源中提供了shadowsocks-rust-ssserver
软件包,它是用 Rust 语言实现的 Shadowsocks 服务端,性能更高、资源占用更低,所以我安装并尝试了这个。
2.2 shadowsocks-rust-ssserver(推荐)
2.2.1 shadowsocks 节点配置
因为我的软路由安装的是ImmortalWrt
系统,所以直接在系统
->软件包
下搜索并安装shadowsocks-rust-ssserver
。
软件包的安装位置在/usr/bin/ssserver
,接着我们创建配置文件/etc/ss-rust/config.json
,内容如下:
{
"server": "192.168.0.1",
"server_port": 5505,
"password": "ltx0Rml9z91CmLX8Ff7KZCggiYQcTnhxNzHM4Lm0B8w=",
"timeout": 300,
"method": "aes-256-gcm",
"fast_open": true,
"no_delay": true,
"reuse_port": true,
"mode": "tcp_and_udp",
"ipv6_first": false
}
系统自启配置,创建文件/etc/init.d/ss-rust
(文件名随意):
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
LOGFILE="/var/log/ss-rust.log"
start_service() {
# 启动 shadowsocks 基础服务
procd_open_instance
procd_set_param command /usr/bin/ssserver --config /etc/ss-rust/config.json
procd_set_param stdout "$LOGFILE"
procd_set_param stderr "$LOGFILE"
procd_set_param respawn
procd_close_instance
}
参考如下命令,赋予执行权限、允许开机自启,然后启动服务。
chmod +x /etc/init.d/ss-rust # 赋予执行权限
service /etc/init.d/ss-rust enable # 允许开机自启
service /etc/init.d/ss-rust start # 启动服务
service /etc/init.d/ss-rust status # 查看服务状态
service /etc/init.d/ss-rust stop # 停止服务
此时,就可以在客户端通过config.json
中配置的值,通过内网来连接了。如果测试能连通,说明服务没问题,那就可以配置外网连接,否则需要检查服务端运行问题。
假定通过内网可以连通,接下来就配置通过外网来访问。
由于我们前面在STUN内网穿透中指定将外网流量穿透到本地 443 端口,所以这里设置端口转发,将 443 端口转发到 LAN 网卡的 5055 端口。然后在客户端将地址和端口都修改为STUN内网穿透
那显示的地址和端口,再次测试连接。能连通那就没问题了,此时已经可以从外网穿透回内网了,不通的话再检查防火墙配置。
2.2.2 obfs 混淆(不推荐)
经过前面的配置,已经有一个shadowsocks
协议的节点可以通过公网连接了,但这还没结束。虽然shadowsocks
是加密连接协议,可直接在公网中连接无异于是在裸奔。因为在shadowsocks
连接中传输的数据是安全的,可这个协议本身的特征非常明显:没有 TLS 握手(流量不像 HTTPS,容易被 DPI 识别)、可被主动探测(审查方主动连接验证为代理)、SNI 为空(一看就不是浏览器发起的 TLS 请求),容易被GFW识别为代理协议流量,审查系统可以用 DPI(深度包检测)识别这些特征,从而可能降速甚至是阻断网络连接。因此需要做一些额外的安全处理,较为简单的做法是利用obfs
混淆,它的主要作用是让代理流量“看起来不像代理”,从而躲避 DPI(深度包检测)或主动探测,但它并不是shadowsocks
的一部分,而是作为独立插件的形式存在,流量会->obfs
->shadowsocks
。
目前主流的代理软件:Shadowrocket
、Surge
、Loon
、Quantumult X
、Stash
、Egern
、Mihomo
(Clash.Meta)均支持 obfs。
直接在系统
->软件包
下搜索并安装simple-obfs-server
,然后编写如下启动文件/etc/init.d/obfs
:
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
LOGFILE="/var/log/obfs.log"
start_service() {
# 启动 obfs-server 混淆服务
procd_open_instance
procd_set_param command /usr/bin/obfs-server -s 192.168.0.1 -p 8675 -r 127.0.0.1:5505 -t 300 --obfs tls --fast-open
procd_set_param stdout "$LOGFILE"
procd_set_param stderr "$LOGFILE"
procd_set_param respawn
procd_close_instance
}
然后参考之前的做法,赋予执行权限和开机自启。接着可以改防火墙端口转发到 lan 的8675端口,不出意外的话是可以通的,出意外就自行排查。
用 Quantumult X 连接 obfs 混淆的节点,疑似遇到了阻断问题,节点无法连接,即使换其他软件连 Shadow-TLS 伪装节点也不通,重启软路由后 IP 和端口变化了,此时又都能通了,所以最好还是不要用 obfs 混淆。
2.2.3 shadow-tls 伪装(推荐)
你不会以为到上面就安全了吧?还没有,obfs 是轻度混淆,它只是让代理流量看起来像是 tls 流量而已,但它没有三次握手行为,如果 DPI 主动探测还是会露馅的,只是比裸奔强一点,算不上安全。如果要对安全更上一层楼的话,就得上shadow-tls
。
shadow-tls是ihciah开发的一款中间层协议,它通过模拟真实的 TLS 握手过程,将代理流量隐藏在 TLS 握手中,使代理流量看起来像是访问正常的 HTTPS 网站,从而可以抵御主动探测、DPI 深度检测,增强shadowsocks
流量的隐蔽性。它依靠密钥机制,未知密钥的连接会被丢弃,单向握手伪装,不需要真实证书。
流量会->shadow-tls
->shadowsocks
,客户端发起一段看起来像真实网站握手(包含SNI)的请求,审查系统看到的是标准的 TLS 连接,实际数据在 shadow-tls 之下传输 shadowsocks 流量,shadow-tls 服务端识别并解封装,还原流量为 shadowsocks 并转发。
下载对应 CPU 架构的安装包,上传到/ust/bin
,并重命名为shadow-tls
赋予执行权限。接着编写如下启动文件/etc/init.d/shadow-tls
:
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
LOGFILE="/var/log/shadow-tls.log"
start_service() {
# shadow-tls 伪装 tls 握手服务
procd_open_instance
procd_set_param command sh -c "MONOIO_FORCE_LEGACY_DRIVER=1 RUST_LOG=error /usr/bin/shadow-tls --fastopen --v3 --strict server --server 127.0.0.1:5505 --tls 'toutiao.com;coding.net;mp.weixin.qq.com;cdn.bootcdn.net;feishu.cn;cloud.tencent.com' --password he4IM5QvPR1wiAZ0RL5HCA== --listen 192.168.0.1:55345"
procd_set_param stdout "$LOGFILE"
procd_set_param stderr "$LOGFILE"
procd_set_param respawn
procd_close_instance
}
然后参考之前的做法,赋予执行权限和开机自启。接着可以改防火墙端口转发到 lan 的55345端口,不出意外的话是可以通的,出意外就自行排查。
客户端可以配置多个节点,每个节点的 SNI 不同,然后轮询使用。目前主流的代理软件就Quantumult X
不支持它。
2.3 sing-box(推荐)
2.3.1 服务配置
sing-box是一个全能选手,原生支持shadow-tls
无需额外插件,但不支持obfs
因为旧技术效果不太好。
安装简单,直接在系统
->软件包
下搜索并安装sing-box
,然后编写如下配置文件/etc/sing-box/config.json
:
{
"inbounds": [
{
"tag": "st1-in",
"type": "shadowtls",
"listen": "127.0.0.1",
"listen_port": 55315,
"version": 3,
"users": [
{
"name": "mayee",
"password": "he1IM5QvPR1wiAZ0RL5HCA=="
}
],
"handshake":{
"server": "toutiao.com",
"server_port": 443
},
"strict_mode": true,
"detour": "ss-in"
},
{
"tag": "st2-in",
"type": "shadowtls",
"listen": "127.0.0.1",
"listen_port": 55325,
"version": 3,
"users": [
{
"name": "mayee",
"password": "he2IM5QvPR1wiAZ0RL5HCA=="
}
],
"handshake":{
"server": "coding.net",
"server_port": 443
},
"strict_mode": true,
"detour": "ss-in"
},
{
"tag": "st3-in",
"type": "shadowtls",
"listen": "127.0.0.1",
"listen_port": 55335,
"version": 3,
"users": [
{
"name": "mayee",
"password": "he3IM5QvPR1wiAZ0RL5HCA=="
}
],
"handshake":{
"server": "mp.weixin.qq.com",
"server_port": 443
},
"strict_mode": true,
"detour": "ss-in"
},
{
"tag": "st4-in",
"type": "shadowtls",
"listen": "127.0.0.1",
"listen_port": 55345,
"version": 3,
"users": [
{
"name": "mayee",
"password": "he4IM5QvPR1wiAZ0RL5HCA=="
}
],
"handshake":{
"server": "cdn.bootcdn.net",
"server_port": 443
},
"strict_mode": true,
"detour": "ss-in"
},
{
"tag": "st5-in",
"type": "shadowtls",
"listen": "127.0.0.1",
"listen_port": 55355,
"version": 3,
"users": [
{
"name": "mayee",
"password": "he5IM5QvPR1wiAZ0RL5HCA=="
}
],
"handshake":{
"server": "feishu.cn",
"server_port": 443
},
"strict_mode": true,
"detour": "ss-in"
},
{
"tag":"ss-in",
"type": "shadowsocks",
"listen": "127.0.0.1",
"listen_port": 5505,
"network": "tcp",
"method": "2022-blake3-aes-128-gcm",
"password": "ZO0zrrHOaS8P4zQsV46EPg==",
"tcp_fast_open": true,
"multiplex": {
"enabled": true,
"padding": true
},
"detour": "dt-out"
}
],
"outbounds": [
{
"tag": "dt-out",
"type": "direct"
}
]
}
接着编写如下启动文件/etc/init.d/sing-box
:
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
LOGFILE="/var/log/ss-box.log"
start_service() {
# 启动 shadowsocks 基础服务
procd_open_instance
procd_set_param command /usr/bin/sing-box run -c /etc/sing-box/config.json
procd_set_param stdout "$LOGFILE"
procd_set_param stderr "$LOGFILE"
procd_set_param respawn
procd_close_instance
}
然后参考之前的做法,赋予执行权限和开机自启。接着,,,,接着怎么办呢?内网访问还好,客户端配置不同的端口连接即可,但通过外网访问就不行了,外网流量只转发到了一个端口,如何对应到 5 个不同端口的 shadow-tls。此时就需要一个反向代理的服务,根据不同 sni 将流量转发到不同端口。
2.3.2 HAProxy 分流
Nginx
和HAProxy
是两种广泛使用的高性能反向代理服务器和负载均衡器,它们在功能上有重叠,但各自有明显的优势和适用场景。
- Nginx: Web 服务器出身,擅长静态内容、反向代理、HTTPS、应用层代理等,功能全面。
- HAProxy: 专注于负载均衡和高可用,性能极致,适合做 TCP/HTTP 层的高并发负载均衡。
实际使用中可以选择任一个,或者将二者结合:流量 -> HAProxy -> Nginx。
安装简单,直接在系统
->软件包
下搜索并安装haproxy
,然后编写如下配置文件/etc/haproxy.cfg
:
# Example configuration file for HAProxy 2.0, refer to the url below for
# a full documentation and examples for configuration:
# https://cbonte.github.io/haproxy-dconv/2.0/configuration.html
# Global parameters
global
# Log events to a remote syslog server at given address using the
# specified facility and verbosity level. Multiple log options
# are allowed.
#log 10.0.0.1 daemon info
# Specifiy the maximum number of allowed connections.
maxconn 32000
# Raise the ulimit for the maximum allowed number of open socket
# descriptors per process. This is usually at least twice the
# number of allowed connections (maxconn * 2 + nb_servers + 1) .
ulimit-n 65535
# Drop privileges (setuid, setgid), default is "root" on OpenWrt.
uid 0
gid 0
# Perform chroot into the specified directory.
#chroot /var/run/haproxy/
# Daemonize on startup
daemon
nosplice
# Enable debugging
#debug
# Spawn given number of processes and distribute load among them,
# used for multi-core environments or to circumvent per-process
# limits like number of open file descriptors. Default is 1.
#nbproc 2
# Default parameters
defaults
# Default timeouts
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
# 自定义 sni 分流
frontend shadowsocks_sni_in
bind 192.168.0.1:55305
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 }
# shadow-tls
acl sni_is_st_1 req.ssl_sni -i toutiao.com
acl sni_is_st_2 req.ssl_sni -i coding.net
acl sni_is_st_3 req.ssl_sni -i mp.weixin.qq.com
acl sni_is_st_4 req.ssl_sni -i cdn.bootcdn.net
acl sni_is_st_5 req.ssl_sni -i feishu.cn
# obfs
acl sni_is_obfs_1 req.ssl_sni -i cloud.tencent.com
# shadow-tls
use_backend st_ss_1 if sni_is_st_1
use_backend st_ss_2 if sni_is_st_2
use_backend st_ss_3 if sni_is_st_3
use_backend st_ss_4 if sni_is_st_4
use_backend st_ss_5 if sni_is_st_5
# obfs
use_backend obfs_ss_1 if sni_is_obfs_1
# shadow-tls
backend st_ss_1
mode tcp
server shadow_tls_1 127.0.0.1:55315
backend st_ss_2
mode tcp
server shadow_tls_2 127.0.0.1:55325
backend st_ss_3
mode tcp
server shadow_tls_3 127.0.0.1:55335
backend st_ss_4
mode tcp
server shadow_tls_4 127.0.0.1:55345
backend st_ss_5
mode tcp
server shadow_tls_5 127.0.0.1:55355
# obfs
backend obfs_ss_1
mode tcp
server shadow_obfs_1 127.0.0.1:8675
# Special health check listener for integration with external load
# balancers.
listen local_health_check
# Listen on port 60000
bind :60000
# This is a health check
mode http
http-request return status 200 content-type "text/plain" string "OK"
# Enable HTTP-style responses: "HTTP/1.0 200 OK"
# else just print "OK".
#option httpchk
然后参考之前的做法,赋予执行权限和开机自启。这里我同时兼容了shadow-tls
和obfs
两种入口方式:
只要把防火墙的端口转发至55305
即可。
3. 节点订阅
前面做了这么多,还是得手动去配置多麻烦,偶然间看到 GitHub 上一个项目GoHomeEasy,受到了启发,于是有了我的做法,订阅更安全、客户端适应性更强。
3.1 WebHook
前面说了在 STUN内网穿透可以配置 WebHook,可以触发一个标准的 HTTP 请求。我的做法是启动一个本地 HTTP 服务,当公网的 IP 或端口变更则触发,然后生成节点订阅。外网通过域名(IPv6)请求来获取订阅,虽然 IPv6 限速了,不过我们的文本很小,这点限速基本没影响。
编写如下脚本/usr/local/bin/homelan.py
:
# -*- coding: utf-8 -*-
import argparse
from Crypto.Cipher import AES
import base64
import http.server
import json
import yaml
import os
import re
import requests
import sys
import socketserver
from datetime import datetime
from pathlib import Path
from urllib.parse import urlparse, parse_qs, quote, quote_plus
# 文件根目录(只允许访问超出目录下的文件)
FILE_ROOT='/opt/homelan'
# 节点文件
NODE_FILE=f"{FILE_ROOT}/Node.yaml"
# Clash.Meta(Mihomo) 配置文件
META_FILE=f"{FILE_ROOT}/Meta.yaml"
# 服务启动端口
PORT=7034
# 是否启用 bark 通知
BARK_NOTIFY= True
# 设备的 key, 用于 bark 通知
DEVICE_KEY="xxxxxxxxx"
# aes 密钥, 必须 16 位
AES_KEY="xxxxxxxxx"
# aes 向量
AES_IV="xxxxxxxxx"
# 运行快捷指令. 注意 ios 上必须要有这个指定名称的快捷指令
BARK_URL_SCHEME = None
class SubscriptionHandler(http.server.BaseHTTPRequestHandler):
# 重写 version_string 方法,返想改响应头的 Server 值. 目的是伪装自己,隐藏服务器信息
def version_string(self):
return "cloudflare"
# http.server 模块内置了自动日志记录,BaseHTTPRequestHandler类默认实现了一个 log_message 方法,该方法会自动记录请求信息
# 但是它的格式和内容不是我们期望的,所以这里重写这个方法
def log_message(self, format, *args):
"""重写默认的日志方法,自定义日志格式并从 X-Real-IP 获取 IP"""
# 尝试从 X-Real-IP 头获取真实 IP
real_ip = self.headers.get('X-Real-IP')
if not real_ip:
real_ip = self.client_address[0]
request_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
message = format % args
# 自定义日志格式
msg = f"时间: {request_time} | IP: {real_ip} | {message}"
# print() 默认输出到标准输出(stdout), 这里将其输出刷新到标准错误
print(msg, file=sys.stderr)
def respJson(self, code, content):
self.send_response(code)
self.send_header('Content-type', "application/json; charset=utf-8")
self.end_headers()
self.wfile.write(json.dumps(content).encode())
def respText(self, code, content):
self.send_response(code)
self.send_header('Content-type', "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(content.encode())
def respFile(self, code:int, file:Path):
self.send_response(code)
self.send_header('Content-type', "application/octet-stream")
# 从 Path 对象中获取文件名,替换固定的 "yourfile.txt"
self.send_header('Content-Disposition', f'attachment; filename="{file.name}"') # 设置下载文件名为实际文件名
self.end_headers()
self.wfile.write(file.read_bytes())
def do_GET(self):
file = Path(f"{FILE_ROOT}{self.path}")
if not file.exists():
# 如果请求的文件不存在,返回 404
self.respJson(404, {"msg": "File not exists."})
return
else:
self.respFile(200, Path(file))
def do_POST(self):
try:
# 读取参数
length = int(self.headers.get('Content-Length', 0))
body = json.loads(self.rfile.read(length).decode())
if not all(k in body for k in ['ip', 'port']):
self.respJson(400, {"msg": "Missing required."})
return
ip = body['ip']
port = int(body['port'])
# 要判断下,有时候获取到的不是公网 ip, 但也会触发 webhook, 此时应该忽略掉
if ip.startswith('10.') or ip.startswith('192.168.') or ip.startswith('172.'):
self.respJson(200, {"msg": "Ignore success."})
return
ndir = os.path.dirname(NODE_FILE)
if not os.path.exists(ndir):
os.makedirs(ndir)
# 写入文件
proxies = yamlNode(ip, port)
with open(NODE_FILE, 'w',encoding="utf-8") as f:
f.write(proxies)
# 填充 meta 节点 (暂时不填充了)
# file = Path(META_FILE)
# if file.exists():
# content = file.read_text(encoding="UTF-8")
# content = re.sub(r'(?<=# 填充节点 - 起\n)(.*?)(?=\n# 填充节点 - 止)',proxies,content,1,re.DOTALL)
# file.write_text(content, encoding="utf-8")
# else:
# print(f"{META_FILE} not exists.", file=sys.stderr)
if BARK_NOTIFY:
barkPush(ip, port)
self.respJson(200, {"msg": "Update success."})
except json.JSONDecodeError:
self.respJson(400, {"msg": "Invalid JSON."})
def barkPush(ip:str,port:int) -> None:
"""
用 AES-128-CBC 加密 raw_json, Base64 编码后 URL 编码,利用 Bark 推送到手机
"""
msg = {
"title": "服务器信息变化",
"subtitle": "请更新订阅",
"body": f"当前公网 ip: {ip}, port: {port}",
"group": "HomeLan通知",
"badge": 1,
}
if BARK_URL_SCHEME:
msg['url'] = BARK_URL_SCHEME
# 将消息转为JSON字符串,然后编码为字节
json_str = json.dumps(msg, separators=(',', ':')).encode('utf-8')
# 将密钥和IV转换为十六进制格式(模拟 xxd -ps -c 200)
key_hex = ''.join(f'{ord(c):02x}' for c in AES_KEY)
iv_hex = ''.join(f'{ord(c):02x}' for c in AES_IV)
# 将十六进制字符串转换为字节
key_bytes = bytes.fromhex(key_hex)
iv_bytes = bytes.fromhex(iv_hex)
# 使用 AES-128-CBC 加密(无需手动填充,PyCrypto 会处理)
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
# 补齐 PKCS7 padding
def pad(s):
pad_len = 16 - len(s) % 16
return s + bytes([pad_len] * pad_len)
# 加密并 Base64 编码
ciphertext = base64.b64encode(cipher.encrypt(pad(json_str))).decode('utf-8')
# 发送请求
response = requests.get(f"https://api.day.app/{DEVICE_KEY}?ciphertext={quote_plus(ciphertext)}")
# 打印响应,便于调试
print(f"Bark 推送响应: {response.status_code} {response.text}", file=sys.stderr)
def tagNum(tag:str) -> int:
match = re.search(r"st(\d+)-in", tag)
if match:
return int(match.group(1))
return 0
# 生成 yaml 格式节点
def yamlNode(ip:str, port:int) -> str:
text = Path('/etc/sing-box/config.json').read_text(encoding="UTF-8")
config = json.loads(text)
# 找出 shadowsocks 配置
item_ss = [item for item in config["inbounds"] if item.get("type") == "shadowsocks"][0]
# 找出 shadowtls 配置
sts = [item for item in config["inbounds"] if item.get("type") == "shadowtls"]
proxies = []
for item_st in sts:
n = tagNum(item_st["tag"])
proxy = {
'name': f'MC之家{n}',
'type': 'ss',
'server': ip,
'port': port,
'cipher': item_ss['method'],
'password': item_ss['password'],
'fast-open': True,
'client-fingerprint': 'chrome',
'plugin': 'shadow-tls',
'plugin-opts':{
'host': item_st['handshake']['server'],
'password': item_st['users'][0]['password'],
'version': 3,
'padding': True,
},
}
# ensure_ascii 不转义中文,separators 去掉空格,使 json 字符串变得紧凑
proxies.append(json.dumps(proxy, ensure_ascii=False, separators=(',', ':')))
# obfs 节点淘汰了,容易被运营商识别
# proxy = {
# 'name': 'MC之家',
# 'type': 'ss',
# 'server': ip,
# 'port': port,
# 'cipher': item_ss['method'],
# 'password': f"{item_ss['password']}",
# 'plugin': 'obfs',
# 'plugin-opts': {
# 'mode': 'tls',
# 'host': 'cloud.tencent.com'
# },
# }
return "proxies:\n" + "\n".join(f" - {p}" for p in proxies)
# 生成 sing-box 节点(原生支持 shadow-tls 配置, 推荐使用)
def singBoxNode(ip:str, port:int) -> str:
def node(item_ss:dict,item_st:dict,ip:str,port:int) -> tuple:
n = tagNum(item_st["tag"])
node_ss = {
"tag": f'MC之家{n}',
"type": item_ss['type'],
"method": item_ss['method'],
"password": item_ss['password'], # 如果服务端配置了多用户,再考虑做额外兼容
"multiplex":{
"enabled": True,
},
"detour": f'st_{n}',
}
node_st = {
"tag": f'st_{n}',
"type": "shadowtls",
"server": ip,
"server_port": port,
"version": 3,
"password": item_st['users'][0]['password'],
"tls": {
"enabled": True,
"server_name": item_st['handshake']['server'],
"utls": {
"enabled": True,
"fingerprint": "chrome"
}
},
"udp_fragment": True,
"tcp_fast_open": True,
}
return (node_ss, node_st)
text = Path('/etc/sing-box/config.json').read_text(encoding="UTF-8")
config = json.loads(text)
# 找出 shadowsocks 配置
item_ss = [item for item in config["inbounds"] if item.get("type") == "shadowsocks"][0]
# 找出 shadowtls 配置
sts = [item for item in config["inbounds"] if item.get("type") == "shadowtls"]
outbounds = []
for item_st in sts:
outbounds.extend(node(item_ss,item_st,ip,port))
client = {
"outbounds": outbounds
}
return json.dumps(client, indent=2,ensure_ascii=False)
# 生成通用节点(通用节点不含 shadow-tls, 那是客户端配置,但可以在 Sub-Store 中通过脚本加上去)
def universalNode(ip:str, port:int) -> str:
text = Path('/etc/sing-box/config.json').read_text(encoding="UTF-8")
config = json.loads(text)
# 找出 shadowsocks 配置
item_ss = [item for item in config["inbounds"] if item.get("type") == "shadowsocks"][0]
core = f"{item_ss['method']}:{item_ss['password']}@{ip}:{port}"
return f"ss://{base64.b64encode(core.encode()).decode()}#{quote('MC之家')}"
def run(port:int) -> None:
# 只允许内网访问. 或者改为 127.0.0.1 仅允许本机访问
server_address = ('192.168.0.1', port)
httpd = socketserver.TCPServer(server_address, SubscriptionHandler)
print(f"时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}|启动服务器在端口:{port}", file=sys.stderr)
httpd.serve_forever()
"""
httpd.server_close()
print(f"时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}|服务器已停止")
"""
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='启动回家订阅服务')
parser.add_argument('--port', type=int, default=PORT, help='服务器端口')
args = parser.parse_args()
run(port=args.port)
当POST
请求触发功能后会生成如下格式的节点文件:
proxies:
- {"name":"MC之家1","type":"ss","server":"110.110.1.1","port":9527,"cipher":"2022-blake3-aes-128-gcm","password":"ZO0zrrHOaS8P4zQsV46EPg==","fast-open":true,"client-fingerprint":"chrome","plugin":"shadow-tls","plugin-opts":{"host":"toutiao.com","password":"he1IM5QvPR1wiAZ0RL5HCA==","version":3,"padding":true}}
- {"name":"MC之家2","type":"ss","server":"110.110.1.1","port":9527,"cipher":"2022-blake3-aes-128-gcm","password":"ZO0zrrHOaS8P4zQsV46EPg==","fast-open":true,"client-fingerprint":"chrome","plugin":"shadow-tls","plugin-opts":{"host":"coding.net","password":"he2IM5QvPR1wiAZ0RL5HCA==","version":3,"padding":true}}
- {"name":"MC之家3","type":"ss","server":"110.110.1.1","port":9527,"cipher":"2022-blake3-aes-128-gcm","password":"ZO0zrrHOaS8P4zQsV46EPg==","fast-open":true,"client-fingerprint":"chrome","plugin":"shadow-tls","plugin-opts":{"host":"mp.weixin.qq.com","password":"he3IM5QvPR1wiAZ0RL5HCA==","version":3,"padding":true}}
- {"name":"MC之家4","type":"ss","server":"110.110.1.1","port":9527,"cipher":"2022-blake3-aes-128-gcm","password":"ZO0zrrHOaS8P4zQsV46EPg==","fast-open":true,"client-fingerprint":"chrome","plugin":"shadow-tls","plugin-opts":{"host":"cdn.bootcdn.net","password":"he4IM5QvPR1wiAZ0RL5HCA==","version":3,"padding":true}}
- {"name":"MC之家5","type":"ss","server":"110.110.1.1","port":9527,"cipher":"2022-blake3-aes-128-gcm","password":"ZO0zrrHOaS8P4zQsV46EPg==","fast-open":true,"client-fingerprint":"chrome","plugin":"shadow-tls","plugin-opts":{"host":"feishu.cn","password":"he5IM5QvPR1wiAZ0RL5HCA==","version":3,"padding":true}}
这里实现了两个功能:POST(生成订阅)、GET(获取订阅)。客户端mihomo
(Clash.Meta)的配置(yaml格式)原生可以配置如obfs
、shadow-tls
,所以就生成了这种配置,这也是Sub-Store支持的输入。另外,Sub-Store 还支持脚本操作。
如果生成的是 ss 标准格式的链接,则需要在Sub-Store
中使用 JS 脚本处理,加上插件信息:
// Sub-Store 订阅处理, 为每个节点加上客户端参数
function operator(proxies) {
const sni = ['toutiao.com','coding.net','mp.weixin.qq.com','cdn.bootcdn.net','feishu.cn'];
proxies.forEach(proxy => {
proxy['plugin'] = 'shadow-tls'
proxy['plugin-opts'] = {'host': sni.pop(), 'password': 'xxx123', 'version': 3}
// 或
proxy['shadow-tls-sni'] = sni.pop()
proxy['shadow-tls-password'] = 'xxx123'
proxy['shadow-tls-version'] = 3
})
}
如果订阅只有一个节点的话,无需写operator
函数:
// 直接使用 $server 变量即表示当前的输入的节点信息
$server['plugin'] = 'shadow-tls'
$server['plugin-opts'] = {'host': sni.pop(), 'password': 'xxx123', 'version': 3}
Sub-Store 的后端我们自己使用 Docker 搭建,后端路径一定要设置的复杂些,这是后端唯一的保护方式,目前还没有鉴权。
Sub-Store 的前端我们可以使用公用的地址,配置的后端信息保存在浏览器的 cookie 中,所以要保护好 cookie。另外,一定要记得通过 Sub-Store 的分享功能,这样分享出去的链接中才不会有后端路径,避免隐私泄露,同时还能灵活的设置分享有效期,并随时撤销分享。
如果想自建前端,建议 Fork 项目,然后通过 Vercel 部署即可。
3.2 Web服务
在 Lucky后台的Web服务
下点击添加子规则
,其中http://127.0.0.1:3001
是 Sub-Store 的后端地址:
3.3 SSL/TLS证书
在 Lucky后台的SSL/TLS证书
下点击添加证书
:
之后就可以在 Sub-Store 的前端设置中配置后端地址https://sub.lucky.dynv6.net:443/<后端路径>
,然后从 Sub-Store 分享的订阅来更新获获取节点了。
当公网 IP 或端口发生变化时,要先在 Sub-Store 更新下,以拉取最新节点信息,再到代理软件中更新,将 Sub-Store 中的最新节点信息拉取到软件中。
3.4 分流规则
在代理软件中新增一条分流规则ip-cidr, 192.168.1.0/24, homelan
。
- 其中
ip-cidr
表示规则类型。 192.168.1.0/24
是一种简写,表示网络为192.168.1.0
子网掩码为255.255.255.0
,因为在 IP 段中,1
通常为网关地址,255
为广播地址,0
表示这个网络,而/24
是因为子网掩码255.255.255.0
为十进制表示,对应二进制是11111111.11111111.11111111.00000000
,掩码掩住的位(用 1 表示掩盖了)是不能变的,一共 4 段,每一段是 8 位,有 3 个 8,所以是 24。- homelan 就是你的策略组名,策略组中应包含你订阅的内网节点。
附加
我们前面在homelan.py
中生成了mihomo
格式化的客户端节点配置,托管在了Sub-store上,其实,,,我们还可以更进一步。
目前Windows/Macos/Linux/Android/IOS/OpenWrt
等各端免费的代理软件客户端,通常Clash核心的比较流行。既然如此,我们完全可以通过维护一套配置托管在Sub-store上,实现一处维护,处处开箱即用。
贴上一套简单的配置模板:
proxy-providers:
SubStack:
type: http
proxy: DIRECT
url: <机场链接>
override:
skip-cert-verify: false
udp: true
health-check:
enable: true
url: https://www.gstatic.com/generate_204
interval: 300
interval: 86400
filter: ^(?!.*(到期|探针|监控|导航))
# 用于下载订阅时指定UA
global-ua: clash.meta
# 全局配置
mixed-port: 7890
ipv6: true
allow-lan: false
unified-delay: true
tcp-concurrent: true
# interface-name: eth0 (路由器下根据情况指定出站接口)
authentication:
# 密码设置选项默认无
- ""
skip-auth-prefixes:
- 127.0.0.1/8
- ::1/128
geodata-mode: false
# GEO 文件加载模式(standard:标准加载器/memconservative:专为内存受限 (小内存) 设备优化的加载器 (默认值))
geodata-loader: standard
geo-auto-update: true
geo-update-interval: 48
geox-url:
geosite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat"
mmdb: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.metadb"
geoip: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip-lite.dat"
asn: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb"
# 控制面板
external-controller: 0.0.0.0:9090
secret: ""
# 密码设置选项默认无
external-ui: ui
external-ui-url: "https://github.com/Zephyruso/zashboard/archive/refs/heads/gh-pages.zip"
# 下载面板地址可更换:https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip
# 匹配进程 always/strict/off
find-process-mode: strict
# 可选:"chrome","firefox","safari","ios","random","none" options
global-client-fingerprint: chrome
keep-alive-idle: 600
keep-alive-interval: 30
# 策略组选择和fakeip缓存
profile:
store-selected: true
store-fake-ip: false
# 流量嗅探
sniffer:
enable: true
sniff:
HTTP:
ports: [80, 8080-8880]
override-destination: true
TLS:
ports: [443, 8443]
QUIC:
ports: [443, 8443]
force-domain:
- "*.v2ex.com"
skip-domain:
- "Mijia Cloud"
- "dlg.io.mi.com"
- "*.push.apple.com"
- "*.apple.com"
- "*.wechat.com"
- "*.qpic.cn"
- "*.qq.com"
- "*.wechatapp.com"
- "*.vivox.com"
# 向日葵服务
- "*.oray.com"
- "*.sunlogin.net"
# 代理模式
tun:
enable: true
stack: gvisor
mtu: 9000
dns-hijack:
- "any:53"
- "tcp://any:53"
auto-route: true
auto-redirect: true
auto-detect-interface: true
# 规则(分流)模式
mode: rule
# 一定要启用,下面配置的 hosts 才生效
use-hosts: true
# 域名映射
hosts:
router.mc: 192.168.0.1
'*.mc': 192.168.0.1
# 查询系统的 hosts,默认为 true
use-system-hosts: true
# DNS模块
dns:
enable: true
listen: 0.0.0.0:1053
ipv6: true
prefer-h3: false # 是否开启 DoH 支持 HTTP/3,将并发尝试
# 模式切换 redir-host / fake-ip
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
# 模式切换 whitelist/blacklist
# 黑名单模式表示如果匹配成功则不返回 Fake-IP, 白名单模式时只有匹配成功才返回 Fake-IP
fake-ip-filter-mode: blacklist
# 匹配项表示不使用 fake-ip
fake-ip-filter:
- '192.168.0.0/16'
- 'rule-set:STUN'
# 用于解析 nameserver,fallback 以及其他 DNS 服务器配置的,DNS 服务域名(必须是 ip)
default-nameserver:
- 223.5.5.5 # 阿里
- 119.29.29.29 # 腾讯
# DNS 连接遵守 rules 规则(非必要不开启)
respect-rules: false
# 专用于节点域名解析的 DNS 服务器
proxy-server-nameserver:
- 'https://dns.google/dns-query#skip-cert-verify=true' # 8.8.8.8
- 'https://cloudflare-dns.com/dns-query#skip-cert-verify=true' # 1.1.1.1
# 专用于 direct 出口域名解析的 DNS 服务器
direct-nameserver:
- 223.5.5.5 # 阿里
- 119.29.29.29 # 腾讯
# 主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS
nameserver:
- 'https://dns.alidns.com/dns-query#skip-cert-verify=true' # 223.5.5.5
- 'https://doh.pub/dns-query#skip-cert-verify=true' # 119.29.29.29
# 填充节点 - 起
# proxies: (暂时不填充了,因为策略组必须要指定 use 或 proxies,否则就要写具体的节点名了,不灵活)
# 填充节点 - 止
# 锚点 - 策略组
url_test: &utest {type: url-test, tolerance: 20, interval: 300, hidden: true, use: [SubStack]}
fallback: &fback {type: fallback, interval: 300, hidden: true, use: [SubStack]}
# 策略组
proxy-groups:
- {name: 出国策略, type: select, proxies: [美国线路, 日本线路, 狮城线路, 全球线路], icon: 'https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Unlock.png'}
- {name: 人工智能, type: select, proxies: [美国线路, 日本线路, 狮城线路], icon: 'https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/ChatGPT.png'}
- {name: 美国线路, <<: *utest, filter: '(?i)(美|美国|US)', icon: 'https://gitlab.com/lodepuly/iconlibrary/-/raw/main/Flag_icon/120px/US.png'}
- {name: 日本线路, <<: *utest, filter: '(?i)(日|日本|JP)', icon: 'https://gitlab.com/lodepuly/iconlibrary/-/raw/main/Flag_icon/120px/JP.png'}
- {name: 狮城线路, <<: *utest, filter: '(?i)(狮城|新加坡|SG)', icon: 'https://gitlab.com/lodepuly/iconlibrary/-/raw/main/Flag_icon/120px/SG.png'}
- {name: 全球线路, <<: *fback, exclude-filter: '(?i)(美|美国|US|日|日本|JP|狮城|新加坡|SG)', icon: 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/Global.png'}
- {name: 兜底策略, type: select, proxies: [出国策略, DIRECT], icon: 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/Final.png'}
rules:
# 本地规则
- DOMAIN-SUFFIX,bing.com,DIRECT
# 远程规则
- RULE-SET,115,DIRECT
- RULE-SET,12306,DIRECT
# 内置规则
- GEOIP,CN,DIRECT
- MATCH,兜底策略
# 锚点 - 规则
ipcidr_mrs: &ipm {type: http, interval: 86400, behavior: ipcidr, format: mrs}
ipcidr_text: &ipt {type: http, interval: 86400, behavior: ipcidr, format: text}
ipcidr_yaml: &ipy {type: http, interval: 86400, behavior: ipcidr, format: yaml}
domain_mrs: &dmm {type: http, interval: 86400, behavior: domain, format: mrs}
domain_text: &dmt {type: http, interval: 86400, behavior: domain, format: text}
domain_yaml: &dmy {type: http, interval: 86400, behavior: domain, format: yaml}
classical_yaml: &ccy {type: http, interval: 86400, behavior: classical, format: yaml}
rule-providers:
115: {<<: *ccy, url: 'https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Clash/115/115.yaml'}
12306: {<<: *ccy, url: 'https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/rule/Clash/12306/12306.yaml'}
参考配置文件语法,参考配置示例,参考贡献者博客,参考综合教程,快速生成配置。
这套配置示例可以任意增减机场数量,自定义嵌套策略组,自定义增减规则。将这套配置文件托管在Sub-Store然后分享,其他客户端直接订阅分享链接即可实现开箱即用。
各端代理软件推荐:
- Windows:clash-verge-rev(推荐)简约美观没有太多额外功能、mihomo-party功能适中、FClash类似安卓上的
Surfboard
、GUI.for.Clash有较多插件扩展功能。 - Linix:同上,如果有对应的 Linux 包的话。
- OpenWrt:OpenWrt-nikki推荐、OpenClash。
- IOS;Stash(非国区下载)。
- Android:ClashMetaForAndroid。
版权所有
版权归属:Mayee