进阶:自定义 Mihomo 内核编译与插件开发

🔧 为什么要编写内核插件?

Clash Verge Rev 提供的规则引擎和策略组已经能应对大多数需求,但在某些高级场景下,你可能需要更底层的控制:比如在流量进入代理之前动态修改请求头、为特定应用注入认证令牌、或者实现私有加密协议。Mihomo 内核(即 Clash Meta)的 插件系统 正是为这些需求而生。它基于 Rust 的 trait 接口,允许开发者以安全、高性能的方式扩展内核功能,而无需修改核心代码。

通过编写插件,你可以将定制逻辑编译为动态链接库(.so/.dylib)或直接静态编译进内核,运行在 Clash Verge Rev 的底层。这意味着你不仅能定义“如何代理”,更能定义“代理之前发生了什么”。如果你已经阅读过 Mihomo 内核全景解读,那么接下来就是亲手挖掘其扩展能力的时候了。

💡 前置知识: 本文假定你已具备基本的 Rust 编程经验,了解 Cargo 包管理器,并熟悉 Clash Verge Rev 的基础配置(参考 配置教程)。

🧬 Mihomo 的插件架构

Mihomo 内核定义了一系列以 Plugin 为核心的 trait,常见扩展点包括:

  • OnRequest:在代理请求发出前调用,可修改目标地址、添加头部。
  • OnResponse:收到响应后调用,用于日志记录或内容过滤。
  • Authenticator:实现自定义认证逻辑,替代简单的用户名密码验证。
  • Transport:注册自定义传输协议,让内核支持非标准代理方式。

插件以独立的 Rust crate 形式存在,通过 mihomo-plugin 提供的 SDK 与内核通信。SDK 封装了异步运行时(Tokio)和必要的网络原语,确保插件不会阻塞主代理循环。这种设计与 Rule 模式 的规则引擎互不冲突,插件在规则匹配之前执行,可以看作是流量进入代理系统的第一道关卡。

🛠️ 开发环境搭建

首先确保你已安装 Rust 工具链(推荐通过 rustup 安装)。然后创建一个新的库项目:

# 创建 Rust 库项目 cargo new --lib mihomo-custom-plugin cd mihomo-custom-plugin

编辑 Cargo.toml,添加 Mihomo SDK 和 Tokio 依赖(假设 SDK crate 名为 mihomo-plugin,实际名称请查阅官方文档):

[package] name = "mihomo-custom-plugin" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] # 编译为动态库 [dependencies] mihomo-plugin = "0.3" tokio = { version = "1", features = ["full"] } tracing = "0.1" # 日志记录
⚠️ 注意: 由于 Mihomo 内核目前尚未完全稳定其 SDK 的公共 API,建议查看 GitHub 仓库 的最新源码以获取准确的 crate 名称和版本。

📝 第一个插件:流量日志注入

我们从最简单的场景入手:编写一个插件,每当有代理请求发出时,将目标域名和策略组名称输出到日志。在 src/lib.rs 中:

use mihomo_plugin::prelude::*; use tracing::info; // 定义插件结构体 pub struct TrafficLogger; // 实现 Plugin trait impl Plugin for TrafficLogger { fn name(&self) -> &'static str { "traffic-logger" } fn on_request(&self, req: &mut ProxyRequest) -> PluginResult<()> { info!( "请求: {} -> 策略组: {}", req.destination.hostname, req.policy_group ); Ok(()) } } // 导出插件注册函数 #[no_mangle] pub extern "C" fn mihomo_plugin_init() -> Box<dyn Plugin> { Box::new(TrafficLogger) }

这个插件通过 on_request 钩子获取每个代理请求的目标域名和所命中的策略组,并通过 tracing::info 写入日志。编译后会生成一个 .so 文件,Mihomo 内核在启动时加载并调用 mihomo_plugin_init 进行注册。更多关于策略组的细节,可参考 配置教程 中的 proxy-groups 说明。

🔐 高级实战:自定义认证插件

假设你的代理节点需要使用动态 Token 认证,而 Token 通过外部 HTTP API 定期刷新。我们可以实现 Authenticator trait 来接管认证流程:

use mihomo_plugin::auth::Authenticator; use tokio::sync::Mutex; use std::sync::Arc; pub struct DynamicTokenAuth { // 缓存 token,使用 Arc 和 Mutex 保证线程安全 token_cache: Arc<Mutex<String>>, } impl DynamicTokenAuth { pub fn new() -> Self { Self { token_cache: Arc::new(Mutex::new(String::new())), } } // 后台异步任务,定期刷新 token async fn refresh_token(cache: Arc<Mutex<String>>) { loop { // 调用外部 API 获取新 token if let Ok(new_token) = fetch_token_from_api().await { let mut token = cache.lock().await; *token = new_token; } // 每 5 分钟刷新一次 tokio::time::sleep(std::time::Duration::from_secs(300)).await; } } } impl Authenticator for DynamicTokenAuth { fn authenticate(&self, req: &mut AuthRequest) -> PluginResult<bool> { // 将最新的 token 注入到代理请求的头部 let token = self.token_cache.clone(); let token = tokio::task::block_in_place(|| { tokio::runtime::Handle::current().block_on(async { token.lock().await.clone() }) }); req.headers.insert( "Authorization".parse().unwrap(), token.parse().unwrap() ); Ok(true) } }

该插件在每次代理请求前都动态注入最新的授权头,完全替代了配置文件中静态的 password 字段。配合 TUN 模式,所有出站流量的认证都能自动保持最新。这对于需要频繁更新密钥的企业环境或自建节点非常实用。

🧠 线程安全提示: 在异步上下文中使用同步 block_in_place 是必要的,因为 Mihomo 的某些钩子可能运行在同步工作线程上。请根据实际 SDK 文档调整。

📦 编译、部署与调试

1

编译插件

执行 cargo build --release,在 target/release/ 下得到 .so.dylib 文件。

2

配置内核

在配置文件 plugin 字段中指定插件路径:plugin: ./libmy_plugin.so

3

重启客户端

重新打开 Clash Verge Rev,查看日志确认插件已加载。

4

调试与日志

通过 Clash 的日志面板或设置 RUST_LOG=debug 环境变量查看插件输出。

如果插件未被加载,检查内核版本是否匹配 SDK 版本,以及文件权限是否正确。在 Linux 上,确保 .so 文件具有执行权限。你也可以将插件静态编译进内核,这需要修改 Mihomo 的源码并重新构建整个项目——具体流程可参考社区提供的构建脚本,或查阅 GitHub Wiki 文档

⚠️ 常见陷阱与最佳实践

  • 异步阻塞:切勿在插件钩子中执行长时间阻塞操作(如同步 HTTP 请求),必须使用 Tokio 异步客户端。否则会拖慢所有代理连接。
  • 内存管理:插件以动态库形式加载,注意 Box::leak 等操作可能导致内存泄漏。推荐使用 Arc 共享状态。
  • 版本兼容性:SDK 与内核之间没有稳定的 ABI,升级 Mihomo 后可能需要重新编译插件。关注 更新日志 中的 breaking changes。
  • 安全边界:插件运行在内核进程中,拥有完全的网络访问权限。仅加载可信来源的插件,避免加载未知的 .so 文件。

当遇到难以排查的问题时,可以先用 tracing::debug 在插件中输出详细上下文,结合 Clash Verge Rev 的连接面板(v2.5.2+ 支持,见 版本新特性)观察流量是否经过了插件处理。这样能快速定位是代码逻辑错误还是加载配置问题。

插件开发为你打开了自定义代理世界的无限可能。从简单的请求标记到全链路的传输层改造,Rust 的零成本抽象让这些操作不会牺牲代理本身的高性能。如果你想继续深入,推荐阅读 Rule Provider 进阶Fake‑IP 原理,理解内核的规则引擎和 DNS 系统如何与插件协同工作。

📖 继续阅读