Repository navigation
Features
Zephyr 提供了一套完整的代理管理功能集,涵盖实时流量监控、智能节点选择、高级安全防护与跨平台适配。本文档描述 v2.5.0 的功能实现,并通过 Mermaid 图表直观展示数据流与处理逻辑。标注
(v2.5.0)的章节为该版本新增功能。
- 实时流量监控
- Core 生命周期
- 首页 (v2.5.0)
- 智能节点选择
- 订阅管理
- TUN 虚拟网卡
- 安全设计
- 配置热重载
- 快速延迟测试
- 自动切换与定时测速 (v2.5.0)
- 多种运行模式
- 跨平台支持
- 高级功能
- 规则库 (v2.5.0)
- 连接监控
- 日志查看 (v2.5.0)
- 键盘操作与无障碍 (v2.5.0)
- Deep Link
- 全局快捷键
- 通知与提示
- 更新 (v2.5.0)
- Prism Engine
- 覆写系统 (v2.3.0)
- 全局用户偏好 (v2.3.0)
- 后端事件系统 (v2.3.1)
- 设置页 (v2.5.0)
- 国际化与 RTL (v2.4.3)
- 界面与体验
Zephyr 内置了一套完全自研的实时流量可视化引擎,零第三方图表库依赖,基于原生 Canvas 2D API 构建双色面积图,提供流畅、低开销的实时网络流量展示。
流量数据从 Mihomo 内核出发,经过 HTTP Streaming 传输、逐行解析、滑动窗口缓冲,最终由 Canvas 2D 渲染引擎绘制为平滑曲线面积图:
%%{init: {'themeVariables': {'fontSize': '10px', 'actorTextColor': '#ccc', 'signalColor': '#666', 'labelTextColor': '#999', 'labelBoxBkgColor': '#333', 'labelBoxBorderColor': '#555', 'lineColor': '#666', 'actorBkg': '#333', 'actorBorder': '#555', 'signalTextColor': '#ccc', 'noteBkgColor': '#333', 'noteTextColor': '#ccc', 'noteBorderColor': '#555'}}}%%
sequenceDiagram
participant MC as Mihomo Core
participant HS as HTTP Stream<br/>(/traffic)
participant WS as websocket.js<br/>(ReadableStream)
participant SW as 滑动窗口<br/>(60 数据点)
participant TC as modules/traffic-chart.js<br/>(渲染引擎)
participant CV as Canvas 2D
MC->>HS: GET /traffic (chunked JSON)
loop 持续推送 (约每秒 1 次)
HS-->>WS: {"up": 1024, "down": 51200}
WS->>WS: TextDecoder 解码 + 按换行分割
WS->>SW: JSON.parse(line)
SW->>SW: 超过 60 点时移除最旧数据
SW->>TC: 通知数据更新
TC->>TC: requestAnimationFrame 调度
TC->>CV: 绘制下载面积图 (蓝 #0A84FF)
TC->>CV: 绘制上传面积图 (珊瑚红 #FF6B6B)
end
Note over HS,WS: 断开时指数退避重连
WS->>HS: 重连 (delay = min(1s × 2^attempt, 30s))
流量数据通过 HTTP Streaming(非 WebSocket)从 Mihomo 内核的 /traffic 端点获取,前端使用 fetch + ReadableStream 逐块读取,TextDecoder 解码后按换行拆分为 JSON 行,每行包含 up / down(bytes/s)。
| 特性 | 说明 |
|---|---|
| 双通道面积图 | 下载为蓝色 #0A84FF,上传为珊瑚红 #FF6B6B,半透明填充叠加 |
| 曲线平滑 | 相邻点之间以中点为终点的二次贝塞尔曲线(quadraticCurveTo)连接 |
| 60 点滑动窗口 |
MAX_DATA_POINTS = 60,旧数据自动移出视口 |
requestAnimationFrame 节流 |
渲染频率与显示器刷新率同步,避免无效重绘 |
| DPR 适配 | Canvas 物理像素按 devicePixelRatio 缩放,高分屏下保持锐利 |
| 窗口隐藏暂停 | 窗口隐藏或最小化时暂停渲染,可见时恢复 |
曲线绘制核心(modules/traffic-chart.js):
target.quadraticCurveTo(prev.x, prev.y, (prev.x + cur.x) / 2, (prev.y + cur.y) / 2);formatBytes / formatSpeed 采用**十进制(base-1000)**单位,从 B 到 TB 自动切换,保留 2 位小数:
| 速度范围 | 显示格式 | 示例 |
|---|---|---|
| < 1000 B/s | XXX B/s |
512 B/s |
| < 1000 KB/s | X.XX KB/s |
86.42 KB/s |
| < 1000 MB/s | X.XX MB/s |
3.14 MB/s |
| < 1000 GB/s | X.XX GB/s |
1.20 GB/s |
| 更大 | X.XX TB/s |
— |
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[输入:bytes/s] --> B{值 < 1000?}
B -->|是| C[显示 B/s]
B -->|否| D[除以 1000,单位升一级<br/>KB → MB → GB → TB]
D --> E{值 < 1000 或已到 TB?}
E -->|否| D
E -->|是| F[保留 2 位小数显示]
style A fill:#0A84FF,stroke:#0060DF,color:#fff
style C fill:#10B981,stroke:#059669,color:#fff
style F fill:#10B981,stroke:#059669,color:#fff
采用指数退避重连策略(websocket.js),在连接中断时自动恢复,同时避免对内核造成请求风暴:
| 参数 | 值 | 说明 |
|---|---|---|
| 初始延迟 | 1 秒 | BASE_DELAY = 1000 |
| 最大延迟 | 30 秒 | MAX_DELAY = 30000 |
| 最大重试次数 | 15 次 | 超过后停止重连 |
| 内核未运行时 | 3 次 | 连接被拒绝(core 未启动)时 MAX_RETRIES_REFUSED = 3
|
| 心跳超时 | 30 秒 | 30 秒无数据视为断流,主动重连 |
| 解析错误阈值 | 连续超过 10 次 | 触发强制重连(应对流格式异常) |
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[fetch /traffic] --> B[读取 chunk]
B --> C{stream done 或<br/>30s 无数据?}
C -->|是| H[指数退避等待]
H --> I{超过重试上限?<br/>15 次 / 内核未运行 3 次}
I -->|是| J[停止重连]
I -->|否| A
C -->|否| K[解码 + 按行 JSON.parse]
K --> N{解析成功?}
N -->|否| P{连续错误 > 10?}
P -->|是| A
P -->|否| B
N -->|是| R[推入 60 点窗口并通知渲染]
R --> B
style A fill:#3B82F6,stroke:#2563EB,color:#fff
style H fill:#F59E0B,stroke:#D97706,color:#fff
style J fill:#EF4444,stroke:#DC2626,color:#fff
style R fill:#10B981,stroke:#059669,color:#fff
Zephyr 对 Mihomo Core 的启动、健康检查和终止流程做了针对性设计,兼顾启动速度与优雅退出。
启动内核后,后端以带 Authorization: Bearer <secret> 的 GET /version 进行健康检查(core/core_process.rs):
| 参数 | 值 | 说明 |
|---|---|---|
| 退避间隔 | 50ms 起步,每次翻倍,上限 1s |
HEALTH_CHECK_INITIAL_INTERVAL_MS / HEALTH_CHECK_MAX_INTERVAL_MS
|
| 最大重试 | 20 次 | HEALTH_CHECK_MAX_RETRIES = 20 |
| 单次超时 | 500ms | 异步 I/O,不阻塞运行时 |
| 密钥校验 | 正确 token 须返回 200,错误 token 须返回 401 | 区分自家内核与同端口上未设密钥的其他内核 |
启动前运行 mihomo -t -f 验证配置有效性,防止无效配置导致崩溃重启循环。
终止 Core 前先调用 DELETE /connections 关闭所有活跃连接(2 秒超时),帮助 Mihomo 在繁忙时更快、更干净地退出。
Unix 平台对内核所在进程组发送 SIGTERM,最多等待约 500ms(5 × 100ms 轮询),仍未退出再发送 SIGKILL:
SIGTERM(进程组)→ 最多等待 500ms → SIGKILL
-
Windows:Job Object(
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE),Zephyr 退出(包括从任务管理器强制结束)时自动终止内核 -
Linux:
PR_SET_PDEATHSIG使 mihomo 在父进程退出时收到 SIGTERM;独立 process group 防止误杀主进程 - macOS:独立 process group + 父进程 PID 监控
系统从睡眠唤醒后(RunEvent::Resumed),后端以 3 次 TCP 探测(每次 2s 超时、间隔 1s)检查内核 API;探测失败则以最近一次配置与自定义参数重启内核,并发出 core-restarted-after-resume 事件通知前端刷新。
首页有两种视图,由设置项 home_page_mode 控制:minimal(默认)与 console。
精简首页顶部为一条极简统计条(ui/home-stats.js,每 2 秒轮询):
| 指标 | 说明 |
|---|---|
| 总流量 | 本次运行累计的下载/上传总量 |
| 活跃连接 | 当前连接数,附 TCP / UDP 拆分 |
| 节点延迟 | 当前所选节点的延迟 |
信息密集的控制台风格仪表盘(ui/console-home.js):
- KPI 卡片:下载/上传速度(含峰值)、总流量(↓↑ 明细)、活跃连接数(TCP/UDP 拆分)、节点延迟(快/中/慢徽章)
- 实时流量趋势图与实时连接列表(可排序,显示进程图标、进程名与目标)
- 节点卡片 + 快捷控制:模式切换(规则/全局/直连)、节点选择器、点击复制主机
- 订阅用量面板 + 最近日志面板
- 头部:显示内核真实运行时长与版本号
- 缓存延迟优先:进入页面时先显示已缓存的延迟,再异步刷新,避免空白闪烁
连接行显示发起连接的进程图标(后端 process_icon.rs,前端 ui/process-icon.js):
| 平台 | 图标来源 |
|---|---|
| Windows | 从 exe 中提取图标(SHDefExtractIconW,64px) |
| macOS | 定位所属 .app,读取 Info.plist 指定的 .icns
|
| Linux | 通过 .desktop 文件(含 Flatpak)在 hicolor 主题与 pixmaps 中查找 |
- 安全与性能:跳过网络挂载路径(读取挂载表判断);符号链接逐跳解析;单个图标上限 512 KB
- 缓存:最多 256 条;查找失败的条目 5 分钟后才会重试
尚未添加任何订阅时,首页显示引导式空状态(ui/no-subscription.js),直接提供添加订阅入口,而不是空白仪表盘。
内核启动失败时,首页内嵌错误卡片(ui/core-error.js)展示错误原因与重试入口,而不仅仅依赖一次性提示。
Zephyr 提供直观、高效的节点选择体验,结合自适应网格、延迟可视化与 3D 交互效果,让用户在大量节点中快速定位最优选择。
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[加载节点列表] --> B[GET /proxies]
B --> C[解析代理组与节点]
C --> D[渲染节点卡片网格]
D --> E{用户操作}
E -->|点击节点卡片| F[切换节点]
E -->|悬停 300ms / 点击 / Enter| G[打开节点轮盘]
G --> F
F --> I[PUT /proxies/group_name]
I --> J[关闭该组现有连接]
J --> L[更新 UI 状态与延迟标签]
L --> N[同步托盘菜单]
style A fill:#0A84FF,stroke:#0060DF,color:#fff
style F fill:#3B82F6,stroke:#2563EB,color:#fff
style J fill:#F59E0B,stroke:#D97706,color:#fff
style N fill:#10B981,stroke:#059669,color:#fff
节点列表使用 CSS Grid 自动填充,列数随可用宽度自然变化,无固定断点:
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));| 延迟范围 | 等级 | 含义 |
|---|---|---|
| < 200ms | good(绿色) | 体验优良 |
| 200ms - 500ms | ok(黄色) | 可用,可能有轻微延迟 |
| ≥ 500ms | 慢(红色) | 延迟较高 |
| 测试中 | 加载动画 | 正在测量延迟 |
| 不可达 | 超时标记 | 延迟 ≤ 0 或 ≥ 999999 |
节点卡片显示由代理类型映射而来的协议与传输标签(TRANSPORT_MAP),例如 SS · TCP,帮助快速识别节点类型。
节点面板顶部的下拉菜单提供以下排序方式:
- 默认 -- 按 Mihomo 返回的原始顺序
- 名称 -- 按名称排序,同一地区的节点归为一组
- 延迟 -- 从低到高,不可达节点排末尾
- 智能 -- 基于节点评分排序,仅在开启「节点评分」时可选
节点卡片悬停时呈现 3D 透视倾斜(ui/3d-effect.js):mouseenter 时缓存 getBoundingClientRect(),之后在 requestAnimationFrame 中按指针相对卡片中心的偏移计算变换,避免每帧强制 reflow:
`perspective(1000px) rotateX(${-y / 40}deg) rotateY(${x / 40}deg) translateY(-3px)`所有动效在 prefers-reduced-motion: reduce 时禁用。
名称超出卡片宽度时,.node-name 添加 is-marquee 类,播放 node-marquee 关键帧水平滚动,两端以渐变遮罩淡出。
首页顶部的节点胶囊显示当前实际使用的出口节点名,点击、按 Enter 或悬停 300ms 即可展开轮盘(ui/node-wheel.js):
| 操作 | 说明 |
|---|---|
| 打开 | 悬停 300ms、点击或 Enter |
| 键盘导航 | ↑ / ↓ 移动,Home / End 跳到首尾,Enter 确认,Escape 关闭 |
| 组行信息 | 代理组行显示该组当前所选节点的延迟 |
| 切换行为 | 切换节点后关闭现有连接,使新节点立即生效 |
| 加载中 | Provider 尚在加载时显示加载提示,而非空白列表 |
- 竞态防护:session 计数器防止快速连续操作导致的状态错乱
- 自动同步:切换成功后同步到托盘菜单和代理列表页
%%{init: {'themeVariables': {'fontSize': '10px', 'actorTextColor': '#ccc', 'signalColor': '#666', 'lineColor': '#666', 'actorBkg': '#333', 'actorBorder': '#555', 'signalTextColor': '#ccc', 'noteBkgColor': '#333', 'noteTextColor': '#ccc', 'noteBorderColor': '#555'}}}%%
sequenceDiagram
participant U as 用户
participant RO as 节点轮盘
participant API as Mihomo API
participant TM as 托盘菜单
U->>RO: 悬停 300ms / 点击 / Enter
RO->>API: GET /proxies
API-->>RO: 组与节点列表
U->>RO: ↑↓ 选择 + Enter
RO->>API: PUT /proxies/{group}
API-->>RO: 切换成功
RO->>API: 关闭现有连接
RO-->>U: 更新胶囊并关闭轮盘
RO->>TM: 同步托盘节点状态
- 过滤逻辑:延迟 ≤ 0 或 ≥ 999999 视为超时并隐藏
- 保留当前节点:当前选中节点始终显示
- 安全阀:若某组过滤后为空,则保留原始列表
- GLOBAL 组:作为 include-all 处理,不参与过滤
-
持久化:
settings.json的hide_timeout_nodes字段
代理组从 Provider 异步加载时,前端以 1.5 秒间隔轮询;每轮携带代际 token 防止陈旧请求,同一时间仅一个请求在途;连续 20 次(约 30 秒)仍无节点则显示重试按钮;离开代理页时自动清理。
Zephyr 记住每个配置文件的「代理组 + 节点」选择,切换配置时自动恢复:
-
v2 格式:同时保存
group+node;parseSelection兼容旧版纯字符串格式 -
主组偏好:
savePrimaryGroupPreference/getPrimaryGroupPreference独立保存用户选择的主组 - 恢复优先链:主组偏好 > 已保存组 > 当前 UI 组
- 重命名迁移:重命名配置时自动迁移相关记录
确定性的 7 级优先链解析主代理组:
| 优先级 | 来源 | 说明 |
|---|---|---|
| 1 |
preferredGroupName(UI 显式选择) |
必须是可写组 |
| 2 |
primaryGroupPreference(持久化偏好) |
通过 savePrimaryGroupPreference 保存 |
| 3 |
effectiveGroup(FINAL/MATCH 规则) |
从 run_config.yaml 的 rules 中提取 |
| 4 |
orderedGroups[0](YAML 定义顺序) |
proxy-groups 中第一个可写组 |
| 5 |
topLevelGroups[0](GLOBAL.all) |
GLOBAL 成员中第一个可写组 |
| 6 | 关键词评分最佳匹配 | proxy/节点/选/代理 加分;auto/url-test 减分 |
| 7 | 第一个可写组 | 按名称排序的绝对回退 |
辅助模块 run-config-cache.js 提供 5 秒 TTL 缓存、请求合并与代际计数器。
后台根据连接数据检测用户实际使用的代理组和节点(ui/observed-group.js):
- 采样:取最近 30 条连接,遍历 chains 找第一个可写组
- 阈值:频率 ≥ 3 且比率 ≥ 0.3 才视为有效
- 连续确认:连续 3 次一致结果后才更新
- 轮询间隔:2 秒
- 特殊组排除:DIRECT、REJECT、PASS 等不参与检测
Zephyr 提供完善的订阅管理系统,支持多种订阅格式、客户端伪装与三级下载回退,确保在各种网络环境下都能获取订阅。
订阅下载从不使用系统代理或环境变量代理,而是依次尝试以下三级:
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[订阅 URL] --> B[SSRF 防护验证]
B --> C{URL 合法?}
C -->|否| ERR1[拒绝请求]
C -->|是| D[第一级:直连下载]
D --> E{成功?}
E -->|是| K
E -->|否| F[第二级:经内核 mixed 端口下载]
F --> G{成功?}
G -->|是| K
G -->|否| H[第三级:写入恢复标记<br/>临时切换 GLOBAL 模式下载]
H --> I{成功?}
I -->|是| J[恢复原模式] --> K
I -->|否| J2[恢复原模式] --> ERR2[返回下载失败]
K[Base64 检测与解码] --> O[YAML 解析]
O --> P{解析成功?}
P -->|否| ERR3[返回解析错误]
P -->|是| Q[安全清洗<br/>危险键 + 控制面键]
Q --> T[AES-256-GCM 加密元数据]
T --> W[write_file_secure 写入<br/>0600 权限]
style B fill:#EF4444,stroke:#DC2626,color:#fff
style D fill:#3B82F6,stroke:#2563EB,color:#fff
style F fill:#0A84FF,stroke:#0060DF,color:#fff
style H fill:#F59E0B,stroke:#D97706,color:#fff
style Q fill:#EF4444,stroke:#DC2626,color:#fff
style W fill:#10B981,stroke:#059669,color:#fff
style ERR1 fill:#991B1B,color:#fff
style ERR2 fill:#991B1B,color:#fff
style ERR3 fill:#991B1B,color:#fff
第三级在切换前写入恢复标记,即使下载过程中应用异常退出,下次启动也能把模式恢复为原值。
| 格式 | 处理方式 | 说明 |
|---|---|---|
| Clash YAML | 直接解析 | 标准 Mihomo / Clash 配置格式 |
| Base64 编码 | 自动检测并解码 | 兼容分享链接格式;解码后须为包含 proxies: 的有效 YAML |
支持自定义 User-Agent,内置 Clash Verge Rev、mihomo-party、FlClash、Shadowrocket 等预设,也可输入任意 UA。每个订阅可单独设置 UA(覆盖全局 subscription_user_agent),长度上限 512 字符,仅允许 ASCII 可见字符;自动更新调度器同样优先使用订阅级 UA。
自动解析 subscription-userinfo 响应头,展示流量使用进度与到期时间:
subscription-userinfo: upload=1073741824; download=3221225472; total=107374182400; expire=1735689600
| 字段 | 说明 |
|---|---|
upload / download
|
已用上传 / 下载流量(字节) |
total |
总流量额度(字节) |
expire |
到期时间(Unix 时间戳) |
添加订阅使用独立的链接对话框(ui/link-dialog.js),输入时实时校验 URL(仅 http/https、长度与格式),不合法时即时提示并禁用确认按钮。导入远程覆写也复用同一对话框。
| 功能 | 说明 |
|---|---|
| 拖拽排序 | 通过拖拽手柄调整顺序,结果自动持久化 |
| 键盘排序 | 焦点在订阅行时按 Alt + ↑ / ↓ 移动 |
| 下次更新时间 | 开启自动更新的订阅显示下次计划更新时间 |
| 更新失败徽章 | 最近一次更新失败的订阅显示失败徽章,便于排查 |
| 下载排队 | 后端 download_sub 有 5 秒限流;手动下载超出时排队等待共享下载槽位,而不是直接报错 |
| 重命名 | 自动同步 last_config 与代理节点记忆 |
| 编辑面板 | 内联编辑 URL、名称、UA 与更新间隔,保留用户自定义名称 |
| 批量更新 | 一键更新所有订阅 |
每个订阅可独立设置自动更新间隔:关闭 / 12 小时 / 1 天 / 3 天。若订阅已有不在预设中的旧值,则保留该值。后台 tokio 定时任务(subscription_scheduler.rs)负责执行。
-
智能命名:优先使用
Content-Disposition文件名,其次提取策略组名称 - 重复名称自动后缀:文件名已存在时追加递增数字
-
拖拽导入本地配置:把
.yaml/.yml文件拖入窗口即导入profiles/,文件名经净化与目录边界校验,内容经过与订阅相同的危险字段清洗
Zephyr 支持通过 TUN 虚拟网卡实现系统级透明代理,接管所有网络流量。TUN 需要提升权限,各平台的提权方式不同。TUN 开关位于首页 / 模式控制区域。
| 平台 | 权限获取方式 | 说明 |
|---|---|---|
| macOS |
osascript 提权 |
弹出系统授权对话框,确认后以 root 身份启动内核 |
| Windows | UAC 以管理员身份重启 (v2.5.0) | 需要管理员权限,见下文 |
| Linux |
pkexec 授权 |
为 mihomo 授予 CAP_NET_ADMIN 并安装 polkit 规则,后续无需重复授权 |
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[用户启用 TUN] --> B{内核正在启动?}
B -->|是| ERR1[返回:内核启动中]
B -->|否| G[停止当前 Mihomo]
G --> K[构造 osascript 提权命令]
K --> L[系统授权对话框]
L --> M{用户确认?}
M -->|取消| N[返回:用户取消]
M -->|确认| O[以 root 启动 Mihomo]
O --> S[认证健康检查]
S --> T{通过?}
T -->|否| R[清理并返回错误]
T -->|是| V[标记 TUN 激活]
V --> W[托盘切换为 TUN 图标]
W --> X[通知前端]
style A fill:#0A84FF,stroke:#0060DF,color:#fff
style K fill:#F59E0B,stroke:#D97706,color:#fff
style O fill:#EF4444,stroke:#DC2626,color:#fff
style V fill:#10B981,stroke:#059669,color:#fff
style ERR1 fill:#991B1B,color:#fff
style R fill:#991B1B,color:#fff
Windows 上 TUN 需要管理员权限。Zephyr 通过 elevation.rs 以 ShellExecuteExW(runas 动词)请求 UAC,以管理员身份重新启动自身:
-
按需提供:仅当当前用户属于拆分令牌的管理员(可以提升为自身)时才提供「以管理员身份重启」;相关命令为
is_process_elevated、can_elevate_as_self、restart_as_admin_cmd - 交接:新实例等待旧实例完全退出后再接管
- 后续启动转发:提权实例运行期间,普通方式再次启动 Zephyr 会被转发给已提权实例
- 安全默认:未提权的普通启动一律以 TUN 关闭状态启动,避免无权限时启动失败
- 错误码:提权重启相关错误为 6025–6026
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[用户开启 TUN] --> B{进程已提权?}
B -->|是| C[直接启用 TUN]
B -->|否| D{可提升为自身?<br/>拆分令牌管理员}
D -->|否| E[提示需要管理员权限]
D -->|是| F[确认后 ShellExecuteExW runas]
F --> G{UAC 同意?}
G -->|否| H[保持当前实例,TUN 关闭]
G -->|是| I[新实例等待旧实例退出]
I --> J[以管理员身份启动并启用 TUN]
style C fill:#10B981,stroke:#059669,color:#fff
style J fill:#10B981,stroke:#059669,color:#fff
style F fill:#F59E0B,stroke:#D97706,color:#fff
style E fill:#6B7280,stroke:#4B5563,color:#fff
style H fill:#6B7280,stroke:#4B5563,color:#fff
| 状态 | 图标 | 说明 |
|---|---|---|
| TUN 激活 | 红橙色(tray-tun.png) |
系统级透明代理已启用 |
| 系统代理 | 绿色(tray-proxy.png) |
系统代理模式运行 |
| 空闲 | 平台默认 | macOS 为模板图像;Windows 按任务栏主题(注册表)显示浅色/深色字形;Linux 使用应用图标 |
托盘菜单还提供「复制环境变量」,以及代理子菜单(最多 15 个节点,取自主组;全局模式下取自 GLOBAL)。
启用 TUN 时,Zephyr 确保 dns-hijack 同时包含 UDP 与 TCP 两项,劫持所有 53 端口的 DNS 流量到 Mihomo:
tun:
enable: true
dns-hijack:
- any:53 # UDP DNS 劫持
- tcp://any:53 # TCP DNS 劫持- 已存在
dns-hijack时补充缺失项;类型不正确时替换为正确格式 - DNS 防泄漏设置仅通过内核 API 应用,不会写回用户的 profile 文件
- 与 Fake-IP 模式配合,防止应用使用硬编码 DNS 绕过代理
Zephyr 实现了多层纵深防御体系,覆盖网络请求、供应链、数据加密存储、文件系统与前端渲染等维度。
每次网络请求(包括重定向的每一跳)都经过严格校验,最多跟踪 5 次重定向:
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[解析目标 URL] --> B{HTTP/HTTPS?}
B -->|否| ERR1[拒绝:非法协议]
B -->|是| D{主机名为私有地址?<br/>localhost / .local 等}
D -->|是| ERR2[拒绝]
D -->|否| E[DNS 预解析]
E --> F{解析结果为私有 IP?}
F -->|是| ERR3[拒绝]
F -->|否| G[DNS Pinning 固定解析结果]
G --> H[发起请求]
H --> I{收到重定向?}
I -->|否| J[返回响应]
I -->|是| K{次数 ≤ 5?}
K -->|否| ERR4[拒绝:重定向过多]
K -->|是| A
style E fill:#F59E0B,stroke:#D97706,color:#fff
style G fill:#3B82F6,stroke:#2563EB,color:#fff
style J fill:#10B981,stroke:#059669,color:#fff
style ERR1 fill:#991B1B,color:#fff
style ERR2 fill:#991B1B,color:#fff
style ERR3 fill:#991B1B,color:#fff
style ERR4 fill:#991B1B,color:#fff
| 防护层 | 机制 | 说明 |
|---|---|---|
| DNS 解析验证 | 预解析 + IP 校验 | 请求前解析域名并校验目标 IP |
| 私有 IP 拦截 | CIDR 匹配 | 拦截 RFC 1918 / 环回 / 链路本地等地址 |
| 重定向链校验 | 最多 5 跳 | 每跳重新执行全部校验 |
| 协议白名单 | HTTP/HTTPS only | 仅允许 http / https
|
| DNS Pinning |
reqwest::resolve 固定 IP |
防止 DNS Rebinding(TOCTOU) |
IPv4:
| 范围 | CIDR | 说明 |
|---|---|---|
| 私有地址 | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 | RFC 1918 |
| 环回地址 | 127.0.0.0/8 | 本机回环 |
| 链路本地 | 169.254.0.0/16 | 自动分配地址 |
| 广播 / 未指定 | 255.255.255.255, 0.0.0.0 | — |
| 文档用途 | 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 | RFC 5737 |
IPv6:
| 范围 | CIDR | 说明 |
|---|---|---|
| 环回 / 未指定 | ::1, :: | — |
| 唯一本地地址 | fc00::/7 | ULA |
| 链路本地 | fe80::/10 | — |
| 校验项 | 策略 | 说明 |
|---|---|---|
| SHA-256 校验 | 哈希比对 | 内核与客户端更新包完整性 |
| Minisign 签名 | Ed25519 | 客户端更新包必须同时通过 SHA-256 与 Minisign 签名验证 |
| 可信域名白名单 | 域名匹配 | 内核更新仅从 updater.rs 的 TRUSTED_HOSTS 下载;订阅下载使用 IP 级 SSRF 防护 |
| 大小限制 | 10MB | 超限订阅直接丢弃 |
| 路径遍历防护 | 多轮 URL 解码 + 文件名净化 | 防止目录穿越 |
订阅元数据(metadata.json 中的订阅 URL、流量信息)使用 AES-256-GCM 加密:
| 参数 | 值 | 说明 |
|---|---|---|
| 加密算法 | AES-256-GCM | 认证加密 |
| 密钥派生 | PBKDF2-HMAC-SHA256 | 从机器标识派生 |
| 迭代次数 | 100,000 次 | 抵御暴力破解 |
| 密钥绑定 | 机器标识 | 元数据文件迁移到其他设备后无法解密 |
⚠️ 注意:代理配置文件(.yaml)默认明文存储,支持可选的机器绑定加密;启用后使用相同的 AES-256-GCM 方案,密文以djI6(v2:的 base64)前缀标识。run_config.yaml始终明文。
| 平台 | 权限设置 | 说明 |
|---|---|---|
| Unix (macOS/Linux) | 0600 |
仅文件所有者可读写 |
| Windows | DACL | 通过访问控制列表限制访问 |
| 命令 | 限流间隔 | 说明 |
|---|---|---|
start_core |
3 秒 | 防止内核频繁启停 |
download_sub |
5 秒 | 手动下载超限时在前端排队 |
exempt_uwp_apps |
5 分钟 | UWP 环回免除操作较重 |
send_notification |
1 秒 | 防止系统通知轰炸 |
限流器基于 DashMap<String, Arc<Mutex<Bucket>>>,不同 key 之间无锁竞争。Prism 命令另有一套滑动窗口限流(见 Prism Engine)。
加载外部配置(订阅、规则库导入、Prism YAML 覆写)时,crates/core 的 sanitizer 会移除可能导致代码执行、路径遍历或暴露控制面的字段:
| 类别 | 被移除的键 |
|---|---|
| 代码执行 |
script、script-path
|
| 路径遍历 | provider 中的 path
|
| 客户端私有字段 | cfw-* |
控制面(CONTROL_PLANE_KEYS) |
external-controller-unix、external-controller-pipe、external-controller-tls、external-controller-cors、external-doh-server、external-ui、external-ui-url、external-ui-name
|
控制面键的移除会沿 YAML << 合并键继续追踪:mihomo 会把合并源展平到同一映射,因此藏在合并源里的键同样会被清除。
-
escapeHtml:对&<>"'做纯字符串映射转义,无需转义时走快速路径直接返回 -
sanitizeHtml:入口执行 NFKC 规范化,并循环剥离标签直到结果稳定,防止<<script>>重组 -
原型污染防护:
deepMerge()使用hasOwnProperty+Object.defineProperty -
外链限制:
openUrl只接受http(s)链接 - 长度上限:Prism 中的名称最长 1,024 字节
配置修改无需重启 Mihomo 即可生效。前端通过 Tauri IPC 传递变更,后端完成安全键保护与 YAML 递归合并后写入 run_config.yaml,再通知内核从该文件重新加载。
%%{init: {'themeVariables': {'fontSize': '10px', 'actorTextColor': '#ccc', 'signalColor': '#666', 'lineColor': '#666', 'actorBkg': '#333', 'actorBorder': '#555', 'signalTextColor': '#ccc', 'noteBkgColor': '#333', 'noteTextColor': '#ccc', 'noteBorderColor': '#555'}}}%%
sequenceDiagram
participant UI as 前端 UI
participant CM as config_manager<br/>(Rust)
participant FS as 文件系统
participant API as Mihomo API
UI->>CM: invoke("update_config", {patch})
Note over CM: JSON 转 YAML
CM->>CM: 备份 external-controller / secret
CM->>CM: merge_yaml(existing, patch, 0)
Note over CM: 深度上限 50<br/>null 值表示删除键
CM->>CM: 恢复安全键
CM->>FS: 写入 run_config.yaml
CM->>API: PUT /configs?force=true<br/>{"path": run_config.yaml}
API-->>CM: 204 / 200
CM-->>UI: Ok(())
crates/core/src/config/merge.rs 中的 merge_yaml:
pub fn merge_yaml(base: &mut YamlValue, patch: &YamlValue, depth: usize) -> Result<(), String> {
if depth > 50 {
return Err("YAML nesting depth exceeded limit".to_owned());
}
match (base, patch) {
(YamlValue::Mapping(a), YamlValue::Mapping(b)) => {
for (k, v) in b {
if v.is_null() {
a.remove(k);
} else if let Some(a_v) = a.get_mut(k) {
merge_yaml(a_v, v, depth + 1)?;
} else {
a.insert(k.clone(), v.clone());
}
}
}
(a, b) => {
*a = b.clone();
}
}
Ok(())
}| 安全键 | 保护原因 | 恢复值 |
|---|---|---|
external-controller |
防止 API 绑定到非 loopback 地址 | 127.0.0.1:{port} |
secret |
防止移除 API 认证 | 保留原值;原配置无 secret 时不会自动添加 |
PUT /configs?force=true
Content-Type: application/json
{"path": "<run_config.yaml 绝对路径>"}
内核从指定文件完整重载配置,force=true 确保强制生效。
通过 Mihomo RESTful API 对节点发起延迟探测:
GET /proxies/{proxy_name}/delay?timeout=5000&url=https://www.gstatic.com/generate_204
| 参数 | 值 | 说明 |
|---|---|---|
| 测试 URL | https://www.gstatic.com/generate_204 |
轻量 204 端点 |
| 超时时间 | 默认 5000ms | 可自定义;前端额外留 1s 余量后中止请求 |
| 并发策略 | 并行请求 | 多节点同时测试 |
| 统一中止 | 共享 AbortController | 重置测试时一次性取消所有在途请求 |
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[触发延迟测试] --> B[乐观 UI:标签进入测试中]
B --> E[并行发起请求]
E --> F1[GET /proxies/node_1/delay]
E --> F2[GET /proxies/node_2/delay]
E --> F3[GET /proxies/node_N/delay]
F1 --> H[逐个更新延迟标签]
F2 --> H
F3 --> H
H --> J[颜色编码刷新]
J --> K{按延迟排序中?}
K -->|是| L[重新排序]
K -->|否| N[完成]
L --> N
style A fill:#0A84FF,stroke:#0060DF,color:#fff
style E fill:#3B82F6,stroke:#2563EB,color:#fff
style N fill:#10B981,stroke:#059669,color:#fff
触发测试后界面立即进入「测试中」状态,结果逐个到达逐个更新,无需等待全部完成。开启「隐藏超时节点」时,单节点测试超时会立即移除该节点。
设置中的自动切换区域(ui/settings/auto-switch.js)提供三个相互独立的开关:
| 开关 | 作用 | 依赖 |
|---|---|---|
| 故障转移 | 当前节点连续测试失败时自动切换到最优候选节点 | 无 |
| 节点评分 | 基于测试历史(延迟、稳定性)为节点打分,启用「智能」排序 | 无 |
| 定时测速 | 后台按计划测试节点并更新评分 | 需开启节点评分 |
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart LR
S[节点评分] -->|前置条件| T[定时测速]
T -->|更新| S
S -->|提供| R[智能排序]
F[故障转移] -->|独立工作| X[切换最优节点]
故障转移集成在代理测试流水线中,具备并发锁防重入、嵌套组解析与中止信号感知。
| 模式 | 说明 | 适用场景 |
|---|---|---|
| Rule(规则分流) | 按配置中的规则分流 | 日常使用,推荐 |
| Global(全局代理) | 所有流量经 GLOBAL 组转发 | 需要全部走代理 |
| Direct(直连) | 所有流量直连 | 排查问题、访问本地服务 |
前端调用 switch_mode 命令,由后端向内核发送:
PATCH /configs
Content-Type: application/json
{"mode": "rule"}
内核接受后才记录并持久化该模式;内核未运行时只记录设置,下次启动生效。全局模式下切换节点使用 switch_global_node 命令。
三段式分段控件带平滑滑块动画,当前模式高亮,切换即时生效。
Zephyr 基于 Tauri v2,在 Windows、macOS 和 Linux 上提供一致的功能体验,并针对各平台特性深度适配。
| 平台 | 架构 | 格式 |
|---|---|---|
| Windows | x64 / ARM64 | NSIS (.exe) + MSI (.msi),另有便携版 zip |
| macOS | Intel / Apple Silicon | DMG |
| Linux | x64 / ARM64 | DEB + AppImage + RPM,另有便携版 tar.gz |
每个平台提供 Full 与 Lite 两种构建:Full 内置 mihomo 内核与 Geo 数据,Lite 不内置,需在应用内下载内核。
- 系统代理:通过注册表写入系统代理
- TUN 提权:UAC 以管理员身份重启(见 Windows UAC 提权)
-
WebView2 崩溃恢复:监听
ProcessFailed;Render 进程崩溃时限流reload(),5 分钟内 3 次则升级为完整窗口重建;Browser 进程退出时立即重建窗口 - UWP 环回免除:为非框架 UWP 应用添加环回免除,使其可以走代理;操作前确认,5 分钟限流
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[用户点击 UWP 环回豁免] --> B{距上次操作 > 5 分钟?}
B -->|否| C[提示操作过于频繁]
B -->|是| D[确认对话框]
D --> E{用户确认?}
E -->|取消| F[操作取消]
E -->|确认| G[枚举非框架 UWP 应用]
G --> K[CheckNetIsolation LoopbackExempt -a]
K --> L{全部成功?}
L -->|否| M[报告失败的应用]
L -->|是| N[完成]
style G fill:#3B82F6,stroke:#2563EB,color:#fff
style N fill:#10B981,stroke:#059669,color:#fff
style C fill:#6B7280,stroke:#4B5563,color:#fff
style F fill:#6B7280,stroke:#4B5563,color:#fff
-
TUN 提权:通过
osascript弹出系统授权对话框 - 托盘模板图像:空闲状态使用模板图像,随菜单栏明暗自动适配
- 多桌面环境系统代理:同时应用到所有检测到的桌面环境:
| 桌面环境 | 配置工具 |
|---|---|
| GNOME | gsettings |
| KDE Plasma | kwriteconfig6 |
| XFCE | xfconf-query |
-
托盘剪贴板:WebKit2GTK 下托盘点击无法保证窗口焦点,剪贴板写入改由 Rust
arboard(启用wayland-data-control)在spawn_blocking中完成 - AppImage 打包:使用 linuxdeploy-plugin-gtk 提升 GTK/WebKitGTK 兼容性
-
AUR:Release 发布时自动更新
zephyr-clash-bin
所有平台均为无边框透明窗口 + 自定义标题栏。窗口位置、大小与最大化状态通过 tauri-plugin-window-state 跨会话持久化;窗口创建时先隐藏、恢复状态后再显示,避免位置闪烁。
支持创建 TCP/UDP 端口转发隧道,将本地端口流量经指定代理节点转发到目标地址(协议、监听地址/端口、目标地址/端口、代理节点)。
独立的 DNS 配置面板,可覆盖内核默认 DNS:Fake-IP 模式、自定义 nameserver、按域名指定解析服务器。
高级配置页(ui/advanced.js)根据配置的嵌套结构递归生成编辑控件,按值类型(字符串、数字、布尔、数组、对象)自动选择输入控件,修改经热重载流程生效。
可视化编辑 mixed-port、socks-port、redir-port、tproxy-port:
- 范围 0–65535(0 表示禁用),仅接受纯数字
-
mixed-port与socks-port不能同时为 0 - 所有启用的端口不得重复
- 保存时将旧版
port置 0
关闭窗口时销毁 WebView 以释放内存,仅保留托盘图标;点击托盘恢复窗口。关闭「最小化到托盘」时自动禁用轻量模式。更新进行中会持有页面(update hold),避免安装过程中页面被销毁。
启动时主窗口保持隐藏,仅显示托盘图标。与轻量模式互补:前者控制启动时的可见性,后者控制关闭后的内存占用。
基于 Tauri autostart 插件,支持三平台(便携版不支持)。
- 启用系统代理时写入所有权标记(含代理地址)
- 每 10 秒检测:若标记存在但系统代理被外部关闭,自动重新启用
- 正常退出时删除标记;崩溃后标记保留,下次启动自动恢复(
restore_sys_proxy)
一键复制代理环境变量到剪贴板,设置页与托盘菜单均可触发:
- 支持 Bash/Zsh、Fish、CMD、PowerShell、Nushell
- 同时输出大小写变量(
http_proxy+HTTP_PROXY等) - 平台默认格式:Windows 为 PowerShell,其他为 Bash
- 自动读取当前
mixed-port/socks-port
后端日志(内核输出 + 结构化事件)可持久化到磁盘:按日轮转、可配置最低级别、UI 中一键导出。
-
导出:将
settings.json、run_config.yaml与全部 profile 打包为 ZIP,内含manifest.json(SHA-256 校验 + 版本 + 时间戳) - 导入:验证(manifest + 校验和 + zip bomb 检测 + 路径遍历防护)→ 暂存 → 原子提交,失败自动回滚
-
限制:最大 200MB 解压、200:1 压缩比;拒绝
..、绝对路径与符号链接
settings.json 带 schema_version,启动时自动迁移;JSON 损坏时备份为 settings.corrupt.<timestamp>.json 并写入默认值;迁移失败时备份原文件并重置。
程序目录存在 .portable 标记文件时,所有数据(配置、订阅、内核、缓存)存放在程序目录,否则使用系统标准目录。
Zephyr/
├── Zephyr.exe / Zephyr.AppImage
├── .portable ← 便携模式标记
├── core/ ← mihomo 内核与 Geo 数据
├── profiles/ ← 订阅配置
├── prism/ ← Prism 数据
├── settings.json ← 应用设置
└── .machine_key ← 加密密钥
| 特性 | 便携版 | 安装版 |
|---|---|---|
| 安装步骤 | 解压即用 | 需要安装 |
| 数据位置 | 程序目录 | 系统目录 |
| 开机自启 | ❌ | ✅ |
| 客户端更新 | 跳转 Releases 页面手动下载 | ✅ 应用内安装 |
| 内核更新 | ✅ | ✅ |
| 系统代理 / TUN | ✅ | ✅ |
| 多实例 | ❌(单实例插件同样生效) | ❌ |
外观设置中的四档分段控件:100% / 110% / 125% / 150%。缩放值通过 --ui-scale 变量应用在 body 上,下拉菜单与右键菜单定位会按缩放比例换算;后端接受 0.5–2.0 范围内的值并持久化到 settings.json。
三层网络性能优化,遵循 Google Cloud TCP 最佳实践:
| 参数 | 默认值 | 说明 |
|---|---|---|
tcp-concurrent |
true |
并行连接所有解析 IP,选最快 |
keep-alive-interval |
30 |
防止中间设备断开空闲连接 |
keep-alive-idle |
600 |
保持持久连接 |
find-process-mode |
always |
精确进程路由 |
profile.store-fake-ip |
true |
持久化 fake-ip 映射 |
default-nameserver |
223.5.5.5, 119.29.29.29 |
避免 DoH 解析循环依赖 |
所有默认值都尊重订阅/用户已有配置(仅在键不存在时写入)。
| 平台 | 参数 |
|---|---|
| Linux |
tcp_slow_start_after_idle=0、tcp_rto_min_us=5000、tcp_fastopen=3、tcp_ecn=1、hystart_detect=2、rmem_max/wmem_max=4MB、tcp_rmem 4K/256K/16MB、tcp_wmem 4K/256K/32MB
|
| macOS |
tcp.fastopen=3、tcp.ecn.enable=1
|
| Windows |
autotuninglevel=normal、initialRto=300、fastopen=enabled
|
扩展 fake-ip-filter(覆盖 captive portal、游戏主机、STUN、LAN 发现等),并添加 default-nameserver。
设置中的网络优化行提供 Apply(显示变更详情与警告)/ Revert(从备份恢复原值)/ 状态指示。Linux 写入 /etc/sysctl.d/(重启保留);macOS 重启后失效;Windows 为系统级持久设置。
运行时检测 x86_64-v3(AVX2、BMI1/2、FMA),按「v3 优化版 → 兼容版 → 通用版」选择 mihomo 资产;检测失败回退兼容版;ARM64 直接使用通用版。
start_core、write_config_file、update_core 等关键函数以 AssertUnwindSafe + catch_unwind 包裹,panic 转换为友好的错误字符串,避免整个应用崩溃。
共享业务逻辑位于独立的 crates/core(配置合并/清洗、订阅下载/SSRF 防护、限流器、状态管理等),可选启用 UniFFI 生成 Swift/Kotlin 绑定,为移动端复用做准备。
规则库(ui/rule-library.js + ui/rule-library/)集中管理 Prism 规则文件,也是 Zephyr 的自定义规则编辑入口。
| 功能 | 说明 |
|---|---|
| 插入规则表单 | 通过表单添加规则,支持 12 种规则类型 |
| 内联编辑器 | 基于 CodeMirror 6 直接编辑规则文件 |
| 启用 / 禁用 | 停用的文件以 .disabled 后缀保存,不参与编译,可随时恢复 |
| 从订阅提取 | 从某个 profile 中提取其规则,生成规则库文件 |
| 导入 | 支持从文本、本地文件或 URL 导入 |
| 安全保存 | 保存前经过清洗:无法解析的 YAML 直接拒绝;若没有任何内容被移除,则保留用户原始文本(含注释与格式) |
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[文本 / 文件 / URL / 从订阅提取] --> B[YAML 解析]
B --> C{可解析?}
C -->|否| ERR[拒绝保存]
C -->|是| D[清洗危险键与控制面键]
D --> E{有内容被移除?}
E -->|否| F[保存原始文本]
E -->|是| G[保存清洗后的 YAML]
F --> H[规则库文件]
G --> H
H --> I{启用?}
I -->|是| J[参与 Prism 编译并热重载]
I -->|否| K[以 .disabled 保存]
style D fill:#EF4444,stroke:#DC2626,color:#fff
style J fill:#10B981,stroke:#059669,color:#fff
style ERR fill:#991B1B,color:#fff
URL 导入经过与订阅相同的 SSRF 防护,并受 Prism 限流约束(rule_import_url 5 次 / 10 秒)。
Zephyr 内置实时连接监控页面(modules/connections.js),对 Mihomo 的所有活跃连接进行细粒度管理。
%%{init: {'themeVariables': {'fontSize': '9px'}, 'sequence': {'messageAlign': 'center'}}}%%
sequenceDiagram
participant Timer as 轮询定时器<br/>(2s)
participant API as api.js
participant Mihomo as Mihomo Core<br/>/connections
participant Acc as 累积器<br/>(connAccumulators)
participant UI as 渲染
Timer->>API: 页面可见且未暂停时触发
API->>Mihomo: GET /connections (Bearer secret)
Mihomo-->>API: 连接列表 (累计字节数)
API-->>Acc: 检测消失的连接 → 归档到关闭历史
Acc-->>Acc: delta 计算与速率
Acc-->>UI: 更新统计栏与列表
Mihomo 返回的 download / upload 为累计字节数。Zephyr 每次轮询计算增量:
const dlDelta = Math.max(0, curDl - acc.prevDl);
acc.dl += dlDelta;
if (dt > 0.05) {
acc.dlSpeed = dlDelta / dt;
}
acc.prevDl = curDl;-
正向 delta 防护:
Math.max(0, …)防御计数器重置导致的负值 -
边界保护:
dt > 0.05防止轮询过快导致速率失真 -
关闭历史:消失的连接归档,上限 200 条(
MAX_CLOSED_CONNS)
| 功能 | 说明 |
|---|---|
| 实时轮询 | 2 秒间隔,仅页面可见时运行 |
| 暂停 | 一键暂停刷新,便于查看 |
| Active / Closed 标签 | 活跃连接与关闭历史分页 |
| 进程图标 | 连接行显示进程图标(见 进程图标) |
| 搜索过滤 | 按主机名、目标 IP、端口、规则、链路、进程名搜索 |
| 多列排序 | 三态循环(降序 → 升序 → 关闭) |
| 连接详情面板 | 显示完整元数据;活跃连接只更新数值,不重建 DOM |
| 关闭匹配连接 | 关闭当前过滤结果中的连接,或关闭全部 |
| 清除历史 | 清空关闭历史 |
| 统计栏 | 3 项汇总:连接总数、↓ 总量、↑ 总量 |
所有来自 API 的动态数据均经 escapeHtml() 转义后插入 DOM。
日志页(ui/logs.js)统一展示应用日志与内核日志,二者共用同一过滤栏。
%%{init: {'themeVariables': {'fontSize': '9px'}, 'sequence': {'messageAlign': 'center'}}}%%
sequenceDiagram
participant Timer as 轮询定时器<br/>(1s)
participant Rust as Rust 后端<br/>read_core_log
participant File as 内核日志文件
participant UI as logs.js
Timer->>Rust: invoke("read_core_log", offset, limit)
Rust->>File: 从 offset 增量读取
File-->>Rust: 新日志行
Rust-->>UI: { lines, rotated, offset }
UI->>UI: rotated 时重置缓冲区
UI->>UI: 追加到环形缓冲区 (2,000 行)
UI->>UI: 按过滤条件增量渲染
| 功能 | 说明 |
|---|---|
| 统一过滤栏 | 应用日志与内核日志共用 |
| 级别过滤 | 全部 / Info 及以上 / Warn 及以上 / 仅错误 |
| 行数显示 | 显示当前匹配行数 |
| 暂停 | 暂停追加新行 |
| 跳到最新 | 向上滚动后出现「跳到最新」胶囊,并显示新增行数 |
| 结构化行 | 每条日志按字段结构化渲染,而非原始文本 |
| 搜索高亮 |
ui/log-highlight.js 在已转义文本上标记匹配项 |
| 增量读取 |
offset 增量读取,自动检测日志轮转 |
| 环形缓冲区 | 内存中最多 2,000 行;轮询间隔 1 秒 |
- 无路径遍历风险:日志路径由后端生成,不接受用户路径输入
-
XSS 防护:日志文本先经
escapeHtml转义,再在转义结果上插入高亮标记 - 本地显示:日志仅在本地窗口展示,不发送到远程服务器
Zephyr 的核心功能均可仅用键盘完成,并为屏幕阅读器提供完整语义。
| 模块 | 职责 |
|---|---|
utils/focus-trap.js |
模态框内 Tab 循环,Escape 关闭 |
utils/roving-tabindex.js |
代理列表方向键导航 + Enter/Space 激活 |
ui/tablist-keys.js |
标签页列表的方向键 / Home / End 切换 |
ui/input-modality.js |
仅在键盘操作时显示焦点环,鼠标点击不显示 |
ui/nav-tooltip.js |
侧边导航图标的悬停 / 聚焦提示 |
ui/header-fit.js |
页面头部在窄窗口下自适应,避免内容被截断 |
ui/seg-control.js |
分段控件的键盘与 ARIA 支持 |
utils/page-visibility.js |
页面不可见时暂停轮询与动画 |
| 功能 | 说明 |
|---|---|
| ARIA 语义 |
listbox / option / dialog / tablist / status、aria-live="polite"、aria-modal、aria-selected
|
| 键盘排序 | 订阅列表 Alt + ↑ / ↓
|
| 节点轮盘 | ↑↓ / Home / End / Enter / Escape |
| 折叠面板 |
role="button" + aria-expanded + aria-controls,Enter/Space 切换 |
| 统一禁用态 | 按钮、表单控件与下拉项的禁用态及 hover 保护统一处理 |
| 减少动效 | 遵循 prefers-reduced-motion,禁用 3D、导航与滚动动画 |
| 主题感知焦点环 |
--zephyr-focus-ring 通过 color-mix 跟随主题色 |
Zephyr 注册了 clash:// 自定义 URL 协议,支持从浏览器或其他应用一键导入订阅。
clash://install-config?url=https://example.com/subscribe&name=myconfig
| 参数 | 说明 | 限制 |
|---|---|---|
url |
订阅 URL | 仅 http:// / https://,最大 2048 字符 |
name |
配置名称 | 拒绝路径分隔符与危险字符;Windows 保留设备名加 _ 前缀;清理尾部空格;最大 128 字符 |
%%{init: {'themeVariables': {'fontSize': '10px', 'actorTextColor': '#ccc', 'signalColor': '#666', 'lineColor': '#666', 'actorBkg': '#333', 'actorBorder': '#555', 'signalTextColor': '#ccc', 'noteBkgColor': '#333', 'noteTextColor': '#ccc', 'noteBorderColor': '#555'}}}%%
sequenceDiagram
participant OS as 操作系统
participant BE as Rust 后端
participant FE as 前端页面
participant U as 用户
OS->>BE: clash://install-config?...
BE->>BE: 解析校验后入队 (最多 8 条)
BE-->>FE: deep-link 事件 (无载荷)
FE->>BE: take_pending_deep_links
BE-->>FE: 待处理链接
FE->>U: 确认对话框
U->>FE: 确认导入
FE->>BE: 下载订阅
- 冷启动或页面尚未就绪时到达的链接不会丢失,页面就绪后主动拉取
- 每条链接导入前都需用户确认
- 第二个实例启动时,链接经单实例插件转发给已运行的实例
-
URL 解析:使用
url::Url::parse(),防止 scheme 注入 -
协议白名单:仅
clash://;订阅 URL 仅http(s):// -
路径遍历防护:name 拒绝
./\\0\n\r及< > : " | ? * - 输入截断:URL 2048、name 128 字符硬上限
- 快照测试:insta 快照覆盖正常/异常路径、路径遍历、保留设备名、危险字符、长名称与尾部空格
基于 tauri-plugin-global-shortcut,应用在后台时也能响应。
| 功能 | 说明 |
|---|---|
| 自定义快捷键 | 为不同操作绑定全局快捷键 |
| 平台感知显示 | macOS 显示 ⌘,Windows/Linux 显示 Ctrl |
| 快捷键录制 | 按下组合键自动识别并填入 |
| 数量限制 | 最多 20 个 |
| 输入验证 | action ≤ 64 字符,accelerator ≤ 128 字符 |
| 速率限制 | 注册/注销 500ms 冷却 |
快捷键触发的结果在窗口不可见时以系统通知告知,否则使用应用内提示。
| 特性 | 说明 |
|---|---|
| 级别 |
info / success / warning / error
|
| 显示时长 | 有标题 3.5 秒,无标题 2.5 秒,常驻型 7 秒 |
| 悬停暂停 | 鼠标悬停时暂停倒计时 |
| 队列 | 最多 5 条排队,按优先级排序 |
确认对话框同样排队显示,多个确认请求依次弹出而不会互相覆盖。
send_notification 命令通过 tauri-plugin-notification 发送 OS 原生通知:标题截断 128 字符、正文 1024 字符,使用固定 id(1) 保持单条,1 秒限流。主要用于窗口不可见时的场景,例如内核启动失败、更新检查结果、全局快捷键反馈;发送失败时回退为应用内提示。
内核与客户端更新统一在更新对话框(ui/update-dialog.js)中完成。
%%{init: {'themeVariables': {'fontSize': '9px'}}}%%
stateDiagram-v2
state "检查中" as Checking
state "已是最新" as UpToDate
state "有可用更新" as Available
state "安装中" as Installing
state "完成" as Done
state "失败" as Failed
[*] --> Checking
Checking --> UpToDate
Checking --> Available
Checking --> Failed
Available --> Installing: 用户确认
Installing --> Done
Installing --> Failed
Failed --> Checking: 重试
UpToDate --> [*]
Done --> [*]
- 发行说明:「有可用更新」状态显示 Release Notes,经安全的 Markdown 渲染(内容先转义)
- 安装顺序:内核与客户端同时有更新时,先安装内核
- 便携版:客户端更新改为跳转 Releases 页面;内核更新照常在应用内完成
-
更新保持:安装期间持有页面(
end_update_hold结束),轻量模式下页面也不会被销毁 - 下载路径:先直连,失败时回退为经由 Zephyr 管理的内核代理下载
-
自动检查:
auto_update(内核)/auto_update_client(客户端)开启后,启动 5 秒后检查并通知,不会自动安装 -
双源检查:同时请求 GitHub REST API 与 Atom Feed,
tokio::select!取先成功者
%%{init: {'themeVariables': {'fontSize': '9px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[用户确认安装] --> B[按平台与 CPU 选择资产<br/>Windows .zip / 其他 .gz]
B --> C[从可信域名下载]
C --> D{SHA-256 校验通过?}
D -->|否| E[删除文件并报错]
D -->|是| F[解压新二进制]
F --> G[停止当前内核]
G --> H[备份旧二进制]
H --> I[原子重命名替换]
I --> J[启动新内核]
J --> K{健康检查通过?}
K -->|是| L[完成,显示新版本]
K -->|否| M[用备份回滚并报错]
style C fill:#3B82F6,stroke:#2563EB,color:#fff
style I fill:#F59E0B,stroke:#D97706,color:#fff
style L fill:#10B981,stroke:#059669,color:#fff
style E fill:#991B1B,color:#fff
style M fill:#991B1B,color:#fff
- 安装包下载到
zephyr_update_{uuid}临时目录 -
必须同时通过 SHA-256 与 Minisign(Ed25519)签名验证;公钥内置于
minisign_verify.rs,.minisig文件限 4KB;任一失败即删除安装包并报错 - 复用
build_github_client()(User-Agent + DNS pinning)与download_release_asset()(大小限制 + 校验 + Zip Slip 防护)
Zephyr 集成了 Clash Prism Engine,一个基于 .prism.yaml 声明式配置的规则引擎/插件系统,由 5 个独立的 clash-prism-* crate 组成。
| 功能 | 说明 |
|---|---|
| 声明式规则引擎 |
.prism.yaml 声明规则,支持条件匹配、变量模板、规则分组 |
| 规则库 | 规则文件管理,见 规则库 |
| 插件系统 | JavaScript 插件加载/卸载/调用,插件级沙箱权限控制 |
| 脚本沙箱 | 完整的 JavaScript 沙箱运行时,多维资源限制(执行时间/内存/输出/日志/脚本大小/字符串长度/循环/递归) |
| 智能路由 | 基于节点测试历史的评分,见 自动切换与定时测速 |
| 配置热重载 |
PUT /configs?force=true 携带 run_config.yaml 路径 |
| 速率限制 | 滑动窗口:script_execute 10 次/10s、rule_import_url 5 次/10s;Smart Score IPC 并发上限 2 |
| 配置验证 | 应用前通过 mihomo -t -f 预检 |
| YAML 覆写清洗 | Prism YAML 覆写同样经过危险键与控制面键清洗 |
| WiFi SSID 条件 |
__when__.ssid 按当前 WiFi 应用不同规则 |
| 网络切换协调器 | 自动检测网络切换并重新编译应用配置 |
| 沙盒边界验证 |
script_set_limits 强制边界(执行时间 100ms–300s、内存 1MB–512MB、循环 1K–10M、递归 8–512),权限提升时发出 PRISM_SANDBOX_CHANGED(3007)警告 |
-
平台检测:
netsh/networksetup/iwconfig获取当前 SSID -
缓存:4 秒 TTL(含
None结果),断网时不会反复 spawn 子进程 -
解析:跳过 BSSID 行,仅匹配
SSID前缀 - 联动:集成到 NetworkChangeCoordinator,网络切换时自动重新编译
NetworkChangeCoordinator:Windows 使用 NotifyAddrChange,全平台另有回退轮询;2 秒去抖动;单飞行状态机;fail-closed 验证(HTTP 2xx + ACK 双确认)。
%%{init: {'themeVariables': {'fontSize': '9px'}}}%%
graph TB
subgraph Crates["clash-prism-* Crates"]
CORE["clash-prism-core<br/>配置解析 + 规则引擎"]
EXT["clash-prism-extension<br/>扩展 + Watcher"]
PLUGIN["clash-prism-plugin<br/>插件加载器"]
SCRIPT["clash-prism-script<br/>脚本沙箱运行时"]
SMART["clash-prism-smart<br/>智能节点选择"]
end
subgraph ZephyrPrism["Zephyr 集成层"]
HOST["host.rs<br/>PrismHost trait 桥接"]
CMDS["prism/ 子模块<br/>95 个 Tauri Commands"]
TESTS["prism_tests.rs<br/>121 个测试"]
end
subgraph Frontend["前端"]
PRISM_UI["ui/prism.js"]
RULE_LIB["ui/rule-library.js"]
PLUGINS["ui/plugins.js"]
EDITOR["ui/editor/<br/>CodeMirror 6"]
end
CORE --> EXT
EXT --> PLUGIN
EXT --> SCRIPT
EXT --> SMART
HOST --> EXT
CMDS --> HOST
PRISM_UI -->|invoke| CMDS
RULE_LIB -->|invoke| CMDS
PLUGINS -->|invoke| CMDS
| 安全措施 | 实现 |
|---|---|
| 文件名净化 |
sanitize_filename — URL 解码 + 拒绝 / \ .. \0
|
| 输入大小限制 |
check_input_size — 10MB 上限 |
| 名称长度 | 名称最长 1,024 字节 |
| 插件 ID 验证 |
validate_plugin_id — URL 解码 + 128 字符 + 拒绝路径分隔符 |
| YAML 清洗 | 导入规则与 YAML 覆写时剥离危险键与控制面键 |
| 沙箱默认安全 |
SandboxConfig::strict() — network/filesystem/child_process/workers 全部关闭 |
| URL 验证 |
validate_subscription_url_with_ip — DNS 解析 + IP pinning |
配置覆写支持两种格式。
# 作用域:仅 SubA 和 SubB
__when__:
profile: [SubA, SubB]
rules:
$prepend:
- "DOMAIN-SUFFIX,netflix.com,{{proxy}}"
dns:
$default:
enable: true
enhanced-mode: fake-ip支持的操作符:$prepend、$append、$override、$default、$filter、$transform、$remove。
QuickJS 沙箱执行,适合动态分组、条件判断、链式代理注入等复杂逻辑:
function main(config) {
config['mixed-port'] = 7890;
return config;
}Prism Patches → JS 覆写(按 order 排序)→ 写回 run_config → 热重载
-
CRUD 与作用域(全局或
__when__.profile指定订阅) - 远程覆写:通过链接对话框输入 URL 导入
- 导入导出:JSON 批量导入导出
-
失败指示:每个覆写记录
lastError,执行失败时状态显示为失败并给出错误信息 - 仅保存:可只保存不立即应用
- 面包屑:编辑器顶部显示路径,存在未保存修改时带标记
-
持久化:
{app_data}/prism/overrides/
全局偏好统一保存在后端 settings.json,通过 patch_settings 原子更新,并在运行时注入配置、覆盖 YAML 默认值。涵盖 mode、tun_enabled、mixed_port、socks_port、ipv6、allow_lan、unified_delay、dns_rewrite_enabled、theme_mode、app_opacity、home_page_mode 等。
基于 WAL 模式:DashMap 无锁并发读取 + mpsc channel 异步持久化,IPC 非阻塞,崩溃可恢复。
tauri-plugin-single-instance 确保同时只运行一个实例:第二次启动会聚焦已有窗口并转发 clash:// 链接;Windows 提权实例运行时,普通启动会被转发给提权实例。
统一的后端事件系统替代散落的 eprintln!/println!。
| 组件 | 文件 | 说明 |
|---|---|---|
| 事件定义 | backend_event.rs |
BackendEvent 结构体 + 4 级日志 + 11 模块 + 97 错误码 |
| 前端监听 | backend-events.js |
监听 backend-event + prism-event,缓冲事件,Fatal/Error 自动提示 |
后端共向前端发出 15 种事件,包括 core-restarted-after-resume(休眠恢复后重启内核)与无载荷的 deep-link 通知(页面随后从队列拉取)。
- 4 个级别:Fatal / Error / Warn / Info
- 11 个模块:Core、Subscription、Prism、Config、Plugin、System、Updater、Override、Rule、Smart、Frontend
- 97 个错误码:按模块分段(Core 1000+、Subscription 2000+、Prism 3000+、Config 4000+、Plugin 5000+、System 6000+、Updater 7000+、Override 8000+、Rule 9000+、Smart 10000+、Frontend 11000+)
| 错误码 | 含义 |
|---|---|
| 1012–1014 | GLOBAL 组切换与恢复 |
| 2007–2016 | 订阅下载细粒度诊断 |
| 4006–4013 | 配置加密/解密 |
| 4015 |
CONFIG_WRITE_FAILED 配置写入失败 |
| 6006–6021 | WebView2 崩溃恢复与系统休眠恢复 |
| 6025–6026 | Windows 提权重启 |
| 11001–11004 | 前端日志转发 |
前端 console 错误、Toast、未捕获异常与未处理的 Promise rejection 上报到后端应用日志:
-
frontend-log.jsfire-and-forget,不阻塞 UI;限流(每 5 秒 50 条非错误 + 100 条错误)+ 内容哈希去重 - Frontend 模块事件不再触发 Toast,避免反馈循环
- 前端截断 16 KiB,后端 UTF-8 安全截断
redact_error_message() 将 core_dir 和 profiles_dir 替换为 [CORE_DIR] / [PROFILES_DIR],支持正斜杠、反斜杠与转义反斜杠,大小写不敏感。
emit_error!(Core, CORE_START_FAILED, "mihomo failed to start: {e}");设置页分为 7 个分区,另有独立的「高级配置」页面:
| 分区 | 主要内容 |
|---|---|
| 外观 | 语言、主题模式、主题色、不透明度、UI 缩放、首页视图(精简 / 控制台) |
| 通用 | 最小化到托盘、开机自启、静默启动、轻量模式、全局快捷键 |
| 代理 | 故障转移、节点评分与参数、定时测速、隐藏超时节点、统一延迟、高级配置入口 |
| 网络 | 端口设置、局域网连接、IPv6、DNS 覆写、复制代理环境变量、端口转发、网络优化、UWP 环回免除(Windows) |
| 订阅与规则 | 订阅客户端伪装(UA)、规则库自动应用与文件管理、Geo 数据更新 |
| 数据与安全 | 备份与恢复、配置加密、配置文件夹、日志设置与导出 |
| 关于 | 版本信息与检查更新、自动检查更新、第三方许可、GitHub 主页、恢复默认设置 |
TUN 开关不在设置页,统一在首页/模式控制区域切换。
ui/settings-nav.js 提供分区索引栏(点击跳转到对应分区)与设置搜索(按名称查找并定位设置项)。
位于「关于」分区。确认对话框会分别列出将被重置的内容与会被保留的内容,由用户确认后执行。
「关于」分区内置第三方许可查看器(get_third_party_licenses),无需离开应用即可浏览依赖的许可证文本。
支持 14 种语言,每种约 1,020 个翻译键。
-
CSS 逻辑属性:
margin-inline、padding-inline、inset-inline、text-align: end替代物理属性 -
RTL 变量:
--rtl-sign/--scroll-fade-dir,:root[dir=rtl]级联 -
动画适配:开关、模式滑块、shimmer、文本滚动均用
var(--rtl-sign)反转方向 - LTR 锁定:代码编辑器与日志面板始终 LTR
- 上下文菜单:RTL 下坐标镜像定位
-
工具函数:
getDirection()、getRTLSign()
| 代码 | 语言 | 代码 | 语言 |
|---|---|---|---|
zh |
中文 | tk |
土库曼语 |
en |
英语 | my |
缅甸语 |
ja |
日语 | ar |
阿拉伯语(RTL) |
ko |
韩语 | ur |
乌尔都语(RTL) |
ru |
俄语 | sw |
斯瓦希里语 |
es |
西班牙语 | tr |
土耳其语 |
fa |
波斯语(RTL) | vi |
越南语 |
前端基于原生 JavaScript + UnoCSS(presetWind4)构建,不依赖任何前端框架。
全局采用 Glassmorphism:半透明背景、backdrop-filter 模糊、细边框与柔和阴影,配合透明无边框窗口。
| 模式 | 说明 |
|---|---|
| 深色 | 适合夜间使用 |
| 浅色 | 适合日间使用 |
| 自动 | 跟随系统主题 |
4 种预设主题色,另支持自定义十六进制颜色:
| 预设 | 色值 |
|---|---|
| 蓝(默认) | #007AFF |
| 绿 | #34C759 |
| 橙 | #FF9500 |
| 粉 | #FF2D55 |
主题色经 Style Dictionary Design Token 管道(primitive → semantic → component)生成 CSS 自定义属性,--accent-primary 全局生效。
滑块范围 10%–100%。
圆角(packages/tokens/src/primitive.json):
| Token | 值 | 典型用途 |
|---|---|---|
radius.control |
8px | 按钮、输入框 |
radius.surface |
12px | 卡片 |
radius.md |
16px | 中型容器 |
radius.dropdown-option |
18px | 下拉选项 |
radius.overlay |
24px | 模态框、下拉菜单、浮层 |
radius.full |
9999px | 圆形元素 |
动效时长:
| Token | 值 | 用途 |
|---|---|---|
time.micro |
0.12s | 按下 / 悬停 |
time.standard |
0.2s | 展开 / 切换 |
time.slow |
0.32s | 大型面板 |
侧边栏图标采用 Draw-On 描绘动画(pathLength=1 + stroke-dashoffset),每个页面有独立的动画;is-active / is-leaving 状态切换,prefers-reduced-motion 时禁用。
-
按钮状态矩阵:disabled(降低不透明度 + not-allowed)、loading(
aria-busy="true"+ spinner) -
统一表单控件
.form-control:sm/md/lg尺寸 +mono变体 - 分段控件:模式切换、UI 缩放等
- Status Dot / Latency Badge / Danger Zone
- Status Ring:SVG 圆形进度,用于 Geo 数据库与内核更新
-
自定义下拉菜单:挂载到独立容器,避免被
overflow: hidden裁切
内置 Catrinity 子集国旗字体,节点名称中的国旗 emoji 在所有平台(包括默认不显示国旗 emoji 的 Windows)上一致渲染。
v2.5.0 启用新 Logo 与横排组合标(icons/lockup.svg、icons/lockup-white.svg)。
返回 Home