本文需要读者对 Clash 配置文件有基本的了解,知道 proxy, proxy-group, rule订阅 的概念。

CFW 预处理

起因

我经常需要使用 ChatGPT 解决问题,但是由于 ChatGPT 的 Web 服务使用了 Cloudflare 服务拦截了所有大陆地区的访问和全球各地很多的代理节点,我的节点提供商提供了一批可以访问 ChatGPT 的节点,并在它们的名字中添加了 chatGPT 字段,如 【I】日本 VIP 4 - chatGPT。这让我可以在想要使用 ChatGPT 的时候,通过更换节点的方式访问,进而避免被 Cloudflare 拦截。

不过,这是一件非常麻烦的事情。首先,每次想要使用 ChatGPT,我都必须记得切换节点,否则会被 ChatGPT 警告:“所在地区不提供服务”、“违反规定使用代理访问” 等等,出现这种警告过后还需要清理浏览器缓存。其次,如果我在使用完 ChatGPT 的使用忘记切换回原本的节点,这会让我后续所有网络连接延迟增加。此外,我在使用 ChatGPT 的时候,所有节点的流量均从 chatGPT 节点流出,这对我当时其他 Web 服务的使用产生了影响。

其实,要想解决这个问题是非常简单的,只需要节点提供商在提供的配置文件中将这些节点整理到一个策略组,再让所有有关 openai.com 的流量从这个策略组走就解决了,不过它并没有这么做。既然节点提供商没有写这个策略组,那我就自己写。

需求分析

  • 将所有包含 chatGPT 字段的节点整理在一个叫 OpenAI 的策略组中
  • 将所有与 openai.com 的流量导向 OpenAI 策略组
  • 在节点提供商的配置文件更新时,OpenAI 策略组也需要更新

使用 Parsers

CFW 提供了一个处理 Profile 的工具 —— Parsers

2023-07-30_104323

路径:Settings => Profiles => Parsers

关于 Parsers

官方描述如下:

Modify and customize your profiles after download but before Diff

翻译:

在你的配置文件下载完成后、进行 Diff 操作前,修改和定制你的配置文件

文档描述得非常详细,也提供了很多操作配置文件的方法,这里简单列出几个:

参数说明

值类型 操作
append-rules 数组 数组合并至原配置rules数组
prepend-rules 数组 数组合并至原配置rules数组
append-proxies 数组 数组合并至原配置proxies数组
prepend-proxies 数组 数组合并至原配置proxies数组
append-proxy-groups 数组 数组合并至原配置proxy-groups数组
prepend-proxy-groups 数组 数组合并至原配置proxy-groups数组
mix-proxy-providers 对象 对象合并至原配置proxy-providers
mix-rule-providers 对象 对象合并至原配置rule-providers
mix-object 对象 对象合并至原配置最外层中
commands 数组 在上面操作完成后执行简单命令操作配置文件

Commands 使用方法(beta)

commands 是一组简单的命令,作为上面操作的补充

我的 Parsers

在这里我使用了 prepend-proxy-groupscommands 实现我的需求。配置如下:

parsers:
  - url: https://example.com/config.yaml # 必须和 Profile 的订阅链接一致
    yaml:
      prepend-proxy-groups: # 数组合并至原配置`proxies`数组**前**
        - name: OpenAI # 策略组的名字叫做 “OpenAI”
          type: select # 策略组的类型是 “手动选择”
      commands: # 命令
        - proxy-groups.OpenAI.proxies+[]proxyNames|.*chatGPT.* # 在所有策略组中找到 “OpenAI” 策略组,在该策略组中添加所有定义的节点名,并按“.*chatGPT.*”正则表达式过滤
        - rules.-18+DOMAIN-SUFFIX,openai.com,OpenAI # 在规则的倒数18的位置上添加 DOMAIN-SUFFIX,openai.com,OpenAI

简单解释一下每一行的作用是什么:

  • url 用于告诉 CFW 当这个订阅更新时执行预处理
  • prepend-proxy-groups 用于添加一个策略组,因为使用 commands 添加策略组并不方便
  • 正则表达式 .*chatGPT.* 的意思是包含 chatGPT 字段为真
  • -18 并不泛用,这是我根据节点提供商的配置文件确定的位置,关于如何确定位置,只需要避免相关域名被前面的规则拦截就好了
  • DOMAIN-SUFFIX 是判断域名中是否包括后面的字段,包括为真,不包括为假

测试

在强制更新相关订阅之后,在 Proxies 栏下,我们可以找到这个名为 OpenAI 的策略组

2023-07-30_104625

在尝试访问 ChatGPT 的网页时

2023-07-30_105127

可以发现规则也生效了,实现了分流

2023-07-30_105042

痛点

经过一段时间的使用,我发现使用 Parsers 有一些不方便的地方

  • 只能将新的策略组添加在原策略组的首部或者尾部,不能在中间添加,也不能对原策略组排序
  • 新规则的添加要在原配置文件的基础上数列表的位置
  • commands 存在 bug,在使用 - 时可能失效
  • 不支持多个订阅合成为一个配置文件

Clash 配置文件的使用

虽然 CFW 预处理很强大,但是它现阶段并不能完全满足我的需求,详见痛点。

所以我打算自己写 Clash 原生的配置文件。

仿写

仿照我现有的三家节点提供商的配置文件,我写好了配置文件的基本结构

port: 7890
socks-port: 7891
allow-lan: true
mode: Rule
log-level: info
external-controller: :9090
dns:
    enable: true
    ipv6: false
    default-nameserver: [223.5.5.5, 119.29.29.29]
    fake-ip-range: 198.18.0.1/16
    use-hosts: true
    nameserver: ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query']
    fallback: ['https://doh.dns.sb/dns-query', 'https://dns.cloudflare.com/dns-query', 'https://dns.twnic.tw/dns-query', 'tls://8.8.4.4:853']
    fallback-filter: { geoip: true, ipcidr: [240.0.0.0/4, 0.0.0.0/32] }
proxies:
proxy-groups:
rules:

添加 Proxy-Providers

参考官方文档 的关于 provider1 的写法

2023-07-30_114541

我将两家节点提供商的所有节点添加进了我的配置文件中(为了避免广告嫌疑,我将节点提供商的信息简单处理了一下)

proxy-providers:
  Sxxxxxxm:
    type: http
    path: ./profile/proxies/sxxxxxxm.yml
    url: "https://example1.com/config1.yml"
    interval: 86400
    health-check:
      enable: true
      url: http://www.gstatic.com/generate_204
      interval: 1800
  nxxxxxxt:
    type: http
    path: ./profile/proxies/nxxxxxxt.yml
    url: "https://example2.com/config2.yml"
    interval: 86400
    health-check:
      enable: true
      url: http://www.gstatic.com/generate_204
      interval: 900

编写 Proxy-Groups

在查阅相关文档后,我决定在 Proxy-Groups 中使用 filter

官方示例:

  # select is used for selecting proxy or proxy group
  # you can use RESTful API to switch proxy is recommended for use in GUI.
  - name: Proxy
    type: select
    # disable-udp: true
    # filter: 'someregex'
    proxies:
      - ss1
      - ss2
      - vmess1
      - auto

我的 Proxy-Groups:

proxy-groups:
  - name: 手动选择
    type: select
    use:
      - Sxxxxxxm
    filter: '^((?!GAME).)*(VIP|IEPL).*$'
  - name: 节点选择
    type: select
    proxies:
      - 手动选择
      - 自动选择
      - 大流量
      - OpenAI
      - 回国
      - DIRECT
  - name: 大流量
    type: url-test
    url: 'http://www.gstatic.com/generate_204'
    interval: 300
    use:
      - nxxxxxxt
  - name: OpenAI
    type: url-test
    url: 'http://www.gstatic.com/generate_204'
    interval: 3600
    use:
      - Sxxxxxxm
    filter: '(.*chatGPT.*)'
  - name: 回国
    type: select
    proxies:
      - Vxxxxxxd
  - name: 自动选择
    type: url-test
    url: http://www.gstatic.com/generate_204
    interval: 1800
    use:
      - Sxxxxxxm
    filter: '^((?!GAME).)*(VIP|IEPL).*$'

我设置了六个策略组分别处理我的需求

组名 功能
手动选择 将我可能手动选择的节点放在这里,用以应对紧急情况
节点选择 这是所有其他策略组的集合,是规则的主干道,所有未分流的流量均从这里选定的策略组中经过
大流量 让所有较大的不考虑稳定性不考虑延迟的流量从这个策略组经过
OpenAI 包含了所有可以访问 ChatGPT 的节点
回国 顾名思义,用于回国的节点
自动选择 跟手动选择的内容几乎是一样的,区别在于自动选择会挑选延迟最低的那个,而手动不会

除了我上面使用 selecturl-test 之外,type 字段还有其他选择,这里简单整理一下:

字段 官方描述
relay relay chains the proxies. proxies shall not contain a relay. No UDP support.
url-test url-test select which proxy will be used by benchmarking speed to a URL.
fallback fallback selects an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group.
load-balance load-balance: The request of the same eTLD+1 will be dial to the same proxy.
select select is used for selecting proxy or proxy group

添加 Rule-Providers

Rule-Providers 的官方描述:

Rule Providers are pretty much the same compared to Proxy Providers. It enables users to load rules from external sources and overall cleaner configuration. This feature is currently Premium core only.

这是固定的配置,基本没有什么变化:

rule-providers:
  reject:
    type: http
    behavior: domain
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/reject.txt"
    path: ./ruleset/reject.yaml
    interval: 86400

  icloud:
    type: http
    behavior: domain
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/icloud.txt"
    path: ./ruleset/icloud.yaml
    interval: 86400

  apple:
    type: http
    behavior: domain
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/apple.txt"
    path: ./ruleset/apple.yaml
    interval: 86400

  google:
    type: http
    behavior: domain
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/google.txt"
    path: ./ruleset/google.yaml
    interval: 86400

  proxy:
    type: http
    behavior: domain
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/proxy.txt"
    path: ./ruleset/proxy.yaml
    interval: 86400

  direct:
    type: http
    behavior: domain
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/direct.txt"
    path: ./ruleset/direct.yaml
    interval: 86400

  private:
    type: http
    behavior: domain
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/private.txt"
    path: ./ruleset/private.yaml
    interval: 86400

  gfw:
    type: http
    behavior: domain
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/gfw.txt"
    path: ./ruleset/gfw.yaml
    interval: 86400

  tld-not-cn:
    type: http
    behavior: domain
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/tld-not-cn.txt"
    path: ./ruleset/tld-not-cn.yaml
    interval: 86400

  telegramcidr:
    type: http
    behavior: ipcidr
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/telegramcidr.txt"
    path: ./ruleset/telegramcidr.yaml
    interval: 86400

  cncidr:
    type: http
    behavior: ipcidr
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/cncidr.txt"
    path: ./ruleset/cncidr.yaml
    interval: 86400

  lancidr:
    type: http
    behavior: ipcidr
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/lancidr.txt"
    path: ./ruleset/lancidr.yaml
    interval: 86400

  applications:
    type: http
    behavior: classical
    url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/applications.txt"
    path: ./ruleset/applications.yaml
    interval: 86400

这里面所有的配置信息均来自 Loyalsoldier/clash-rules,作者使用 GitHub Actions 北京时间每天早上 6:30 自动构建,保证规则最新。

编写 Rules

按照我的理解,rule-providers 相当于把一些网站整理成了一个集合,进而方便我们在 rules 中调用

我的 Rules:

rules:
  - RULE-SET,applications,DIRECT
  - RULE-SET,private,DIRECT
  - RULE-SET,reject,REJECT
  - RULE-SET,icloud,DIRECT
  - RULE-SET,apple,DIRECT
  - DOMAIN-SUFFIX,openai.com,OpenAI
  - DOMAIN-SUFFIX,terabox.com,大流量
  - DOMAIN-KEYWORD,civitai,大流量
  - RULE-SET,proxy,节点选择
  - RULE-SET,direct,DIRECT
  - RULE-SET,lancidr,DIRECT
  - RULE-SET,cncidr,DIRECT
  - RULE-SET,telegramcidr,大流量
  - MATCH,节点选择

我使用的白名单的结构,这可以更好的保证访问的安全性

更多详细的内容可以参考 https://github.com/Loyalsoldier/clash-ruleshttps://dreamacro.github.io/clash/configuration/rules.html

踩坑

下载 Rule-Providers 超时

在我完成完成配置文件的编写之后,我尝试运行,却出现了报错 initial rule provider reject error: context deadline exceeded

尝试询问万能的 Google 后,我确定了这个问题的根源是下载下来的内容是不完整的,而且只有第一次使用时会出现这个问题

2023-07-30_174512

在尝试更换多个 cdn 无果后,我偶然发现了我的 Config 文件中有一部分在 url 字段上面显示 Show actual file 而另一部分显示 Copy URL MD5,点击 Show actual file 后在资源管理器中打开了 C:/Users/{Username}/.config/clash/providers/rule 这个路径,里面的所有 yaml 文件均是以 MD5 的格式命名

2023-07-30_175346

这意味着我可以使用以前的配置文件上网,将所有未下载完成的文件手动下载

2023-07-30_175543

再根据 URLMD5 值对文件重命名,并将 txt 文件拓展名修改为 yaml,最后复制到 C:/Users/{Username}/.config/clash/providers/rule 路径下

说人话会有点绕,换成 bash 命令可能容易理解,这里以 reject 为例:

COPY ./reject.txt C:/Users/{Username}/.config/clash/providers/rule/a9362c87ee45f5f47434d44f5aab0c33.yaml

记得请将 {Username} 替换成你自己的 Username

虽然有点麻烦,但是一次成功,配置文件就不在这里报错了

正则表达式

事先说明一下,我当时的版本是 CFW v0.20.24

问题总是一个接着一个,刚解决 Rule-Providers 的问题,又出现了新的报错 Could not switch to this profile

246159060-67d0429c-112a-455e-93f1-e2431439ffeb

找不到当时报错的图了,这是别人在相关 ISSUES 中提供的图片

报错的意思是语法不支持,我当时很疑惑,因为这段正则是我从 Parsers 原样搬过来的,在 Parsers 中都是正常的,怎么在 Config 中就报错了呢?

众所周知,Clash 内核是使用 Go 语言编写的

2023-07-30_155138

它处理正则表达式时会调用 regexp 包,然而这个包不支持零宽断言,诸如 ^((?!GAME).)*(VIP|IEPL).*$ 的语法是会报错的,这意味着我们不能使用 “包含A且不包含B” 的逻辑,对于筛选节点的操作而言影响是比较大的

在查找了大量资料后,我找到了 [Feature] 希望 clash 可以支持使用 regex2 第三方库过滤节点 #2673,提出这个 Issue 的网友遇到了和我一样的问题

Description

用的机场有个节点一直会出问题,希望可以在 proxy-providers 的 filter 中过滤掉。但由于 golang 的 regex 库不支持零宽断言,导致 (!proxy1|proxy2) 这样的正则表达式语法也不被支持,而 Parser 功能似乎也只是 Clash for Windows 才有的功能,所以这边希望将 regex 库替换为 regex2,谢谢

然后很顺利地找到了 替换正则库为github.com/dlclark/regexp2 #2802 这个 PR

不过当时的我依然非常不解,因为这个 IssuePr 已经 Closed 了,而且看上去非常顺利,这意味着 Clash 内核已经使用了支持零宽断言的 regex2 包,而我的配置文件确实是在零宽断言处报错了

直到我注意到了 Pr 的时间是 Jun 21, 好家伙,差点赶上首发了

Clash v1.17.0 是更新日志中写道:

Change

Clash Premium 的更新日志有:

2023.06.30

  • upgrade to v1.17.0

CFW v 0.20.28 的更新日志里写道:

v 0.20.28

Clash Core: Upgraded to the v2023.06.30 Premium.

这链式关系瞬间就清晰了

在升级了 CFW 的版本之后,我成功使用上了自己的配置文件

后记

为什么会有多个节点提供商

主要是为了省钱,C站上面随便一个 checkpoint 模型就是几个G。

由于一些不能说的原因,节点分优质和普通,优质节点的流量比较贵,相反,普通节点的流量比较便宜,所以我选择了两家节点提供商,一家能够提供相对宽松的专线,一家基本不限流,通过使用 Clash 的静态分流,我可以在省钱的同时,获得不错的冲浪体验。

当然,使用多个节点提供商也有一定的扛风险能力。

想法

喜欢折腾、讨厌麻烦真的是我不断学习的动力,也是我愿意将这份经历记录下来的原因,希望这篇关于 CFW 配置文件的博客能够帮到你。

Be a Neutral Listener, Dialectical Thinker, and Practitioner of Knowledge