Sukka's Blog

童话只美在真实却从不续写

  1. 1. 上传配置文件
  2. 2. 处理配置文件
  3. 3. 启动 Clash
  4. 4. Fake DNS
  5. 5. Clash 看门狗
  6. 6. IP 数据库更新

KoolClash 经过了这么些个版本的迭代,内部逻辑也变化了很多次。我原本以为只要把使用步骤写成文档,用户看了就能够正常的使用。但是在 GitHub 上看到那些我根本看不懂的 issue 的时候,我觉得,还是需要水一篇文章科普一下 KoolClash 的工作机制。

本文具有很强的时效性,仅具有一定的参考意义。截至本文写成,KoolClash 的最新版本为 0.17.5-beta。本文描述的 KoolClash 的工作机制和行为都仅限于这个版本。KoolClash 在之后的开发和迭代中可能会更改行为。

上传配置文件

Clash 的配置文件包括了节点列表和基于域名、IP 归属国家、IP CIDR 的分流规则。用户只有上传了 Clash 的配置文件,Clash 才能启动。
KoolClash 提供两种配置文件上传方式,一种是用户直接上传 config.yml 文件,一种是用户提交远程托管配置的 URL,由 Clash 从这个 URL 下载配置文件。对于后者,KoolClash 会在 dbus 里将这个 URL 保存下来,然后从这个 URL 下载。用户下次打开 KoolClash 的界面时,保存的 URL 会从 dbus 里取出、然后填充进界面上的输入框中。 KoolClash 不提供定时更新托管配置的功能,用户每次点击按钮都会提交一次输入框中的 URL。如果用户没有改动输入框中的 URL,那么提交的还是之前保存的 URL。

不论用户选择什么方式,对于 KoolClash 来说,最终都会在 /koolshare/koolclash/config 目录中得到一份原始的、由用户上传的 Clash 配置文件。KoolClash 会将这个文件命名为 origin.yml。文件名设为 origin.yml 而不是 config.yml 是因为 KoolClash 还需要——

处理配置文件

因为 General 配置太过于自由,如果直接拿用户上传的配置文件直接启动 Clash 肯定是不现实的。
比如说,用户配置的 redir 端口不统一,DNS 配置也不统一,external-controller 监听在 127.0.0.1,有的 allow-lan 甚至值为 false。 KoolClash 在用户上传完一份 Clash 配置文件之后需要强制覆盖一些配置,比如讲 redir-port 修改为 23456,将 allow-lan 改成 true,将 external-controller 改为设备的 LAN IP 和 6170 端口。不过最头疼的还是 DNS 配置。

KoolClash 只有在 DNS 监听在一个固定的端口(KoolClash 使用的是 23453 端口,至于监听 0.0.0.0 还是 127.0.0.1 无所谓),设置为 fake-ip 模式下才可以正常运行。但是用户上传的配置文件,要么没有 DNS 配置,要么 DNS 监听在别的端口,要么 enhanced-mode 设置使用的是 redir-host 模式。如果用户设置的是 redir-host 模式还好,KoolClash 可以直接覆盖为 fake-ip,但是其它情况下就需要用户自行上传「自定义 DNS 配置」覆盖配置文件中的配置。

KoolClash 将配置文件中可能遇到的 DNS 配置分为 5 类。

  • 0 - 用户没有上传 Clash 配置文件,这时需要提示用户上传 Clash 配置文件
  • 1 - 用户上传的 Clash 配置文件的 DNS 配置符合需求
  • 2 - 用户上传的 Clash 配置文件的 DNS 配置符合需求,但是用户仍然想要用「自定义 DNS 配置」覆盖,一般常见于托管订阅
  • 3 - 用户上传的 Clash 配置文件的 DNS 配置不符合需求,而且此时用户尚未上传过「自定义 DNS 配置」,此时需要提示用户上传「自定义 DNS 配置」
  • 4 - 用户上传的 Clash 配置文件的 DNS 配置不符合需求,但是用户之前已经上传过「自定义 DNS 配置」了,KoolClash 只需要将之前的「自定义 DNS 配置」覆盖进 Clash 配置文件内就行了

用户上传的「自定义 DNS 配置」会保存在 dns.yml 文件中,经过修改后的 Clash 配置文件被命名为 config.yml。 有了 config.yml,现在就可以——

启动 Clash

在启动 Clash 之前还需要进行一次检查:检查 DNS 配置、检查 redir-port 配置等。检查完了以后就可以删除 iptables 旧链、重启 dnsmasq,然后启动 Clash 进程了。
由于 Clash 目前还在使用 start-stop-daemon(不过也许以后还是会改成使用 nohup 的),所以拿不到 Clash 的 stdout 的日志。因此 KoolClash 使用一个更简单的方法检查 Clash 是否正常启动:先 sleep 5 然后 pidof 检查 Clash 进程还在不在;如果进程不在了就中止 Clash 启动然后回滚,如果进程还在就可以往 iptables 添加 koolclash 链了。

KoolClash 主要会添加几条 iptables:

  • 一条将目标是设备 LAN IP 的流量全部 ACCEPT,避免 Clash 崩溃以后用户被锁在 Koolshare OpenWrt 外面
  • 一条重定向所有 PREROUTING 的 TCP 流量给 23456 端口(Clash 的 redir-port)。旧版本 Clash 的 Bug 会导致往 redir 端口里扔 UDP 流量时 Clash 会崩溃。反正 Clash 的 redir 还不支持 UDP,所以 KoolClash 直接不转发 UDP
  • 一条有关 Fake DNS 的 DNAT,将目标为 198.19.0.0/24、53 端口的 UDP/TCP 流量全部劫持到 23453 端口(Clash 的 DNS 端口上)。

Fake DNS

KoolClash 之前将 Clash DNS 设为 dnsmasq 的上游,即 Clash 不仅会接管外部 DNS 查询请求、还会接管设备自身的 DNS 查询请求。但是在 fake-ip 模式下,这会导致一些严重的问题:

  • 在 Clash 0.15.0 版本以前,节点域名是通过系统 DNS 解析的,如果 Clash DNS 的 fake-ip 接管了整个设备,那么节点域名就没法解析了
  • 虽然 Clash 在 0.15.0 以后由 Clash 自己解析节点域名,不会出现节点被解析到 fake-ip 的情况了,但是一些用户在 Clash DNS 里配置了 DoT,而 DoT 的域名本身的解析还是需要系统 DNS。如果系统 DNS 已经被 Clash DNS 接管,就会引起解析回环。

因此,不论如何需要将 Clash 的 DNS 和系统的 DNS 区分开,只把需要代理的设备的 DNS 查询交给 Clash。因此 KoolClash 引入了 Fake DNS 的概念——要求用户设备的 DNS 设置为保留 IP 段内的 IP(198.19.0.0/24),这样就不会发给设备的 dnsmasq,而 iptables 会把目标地址为 198.19.0.0/24、目标端口为 53 的所有流量转发给 Clash。

Clash 看门狗

之前因为 KoolClash 的流量转发没有白名单,因此访问 Koolshare OpenWrt 的流量也会被交给 Clash 处理;如果 Clash 进程丢失,那么用户就进不了 Koolshare OpenWrt 了。所以 KoolClash 就做了一个看门狗脚本,用来确保在 Clash 进程丢失后将 Clash 进程重新拉起来。
现在 KoolClash 做了个白名单 IP 的功能,不会再把用户锁在 Koolshare OpenWrt 外面了,但是这个功能保留了下来。

Clash 看门狗脚本的原理很简单,本质也是一个 daemon 进程;用 while 循环每 sleep 20 后用 pidof 检查一次 Clash 进程在不在。我本来想给看门狗做一个间隔时间自定义的功能,但是我偷懒,结果就没做。

IP 数据库更新

更新 GeoLite 数据库很容易,用 wget 然后解压就行了。我在 KoolClash 里加了一个获取 GeoLite 版本号的方法——解压下载完的 tar.gz 压缩包会得到一个文件名为 GeoLite2-Country_{Date} 的文件夹,其中 {Date} 就是 GeoLite 的版本号(发布日期)。只需要用 ls grep sed 就可以得到 IP 数据库的版本号。


说几句题外话,我蹭听说好几个编译 LEDE、OpenWrt 和写插件的人,总是说 Koolshare OpenWrt 如何如何不好。我对此不置可否,不过毫无疑问的一点是,Koolshare OpenWrt 内置的 httpdb 大大降低插件开发的难度,并且能够实现非常多 OpenWrt 和 Lua 实现不了的功能。

本文作者 : Sukka
本文采用 CC BY-NC-SA 4.0 许可协议。转载和引用时请注意遵守协议!
本文链接 :https://blog.skk.moe/post/how-koolclash-worked/

本文最后更新于 天前,文中所描述的信息可能已发生改变