175 lines
20 KiB
Markdown
175 lines
20 KiB
Markdown
# 架构设计
|
||
|
||
## 1. 目标与定位
|
||
|
||
只关注网络层的线路质量面板:单体面板(Backend)、轻量节点(Probe、Agent)、长连接 RPC 与任务下发。
|
||
|
||
- 采集:多个 Probe(家宽等需要观测的出口)与多台海外 VPS Agent 之间记录逐包、逐事件的原始数据。哪些 Probe 连哪些 Agent 由面板决定,默认全连,也可以手动选择。
|
||
- 分析:Backend 的分析层推算在某条线路上部署 REALITY 类、Hysteria2 类或 Minecraft 服务的体验,并对多条线路排序和对比。
|
||
- 主机状态:Agent 和 Probe 读取 `/proc`,上报 CPU 占用、内存、根分区、网卡累计流量与瞬时网速、运行时长这类汇总数字。不采集 CPU 型号、主机名、网卡名、IP 和进程。Probe 的上报可在 `probe.json` 中用 `reporting.host_stats: false` 关闭。展示页以卡片呈现每个节点的主机状态,线路在卡片下方的面板中。
|
||
- 构建信息:节点连接面板时自报系统、架构和构建信息(版本、tag、提交、构建时间),用于展示和更新。国家和运营商由 Backend 依据节点来源地址查询,节点不访问任何外部服务,展示页不含任何 IP。
|
||
- 节点边界:Agent 和 Probe 是纯采集器,没有 SSH、远程执行、终端、文件读写和自更新。更新由节点宿主机上的更新器完成,节点进程只把面板转来的发布清单和更新请求写入数据目录。
|
||
|
||
## 2. 拓扑
|
||
|
||
```text
|
||
操作员 passkey ──签名「操作员集合」──┐ CI 发布私钥 ──签名「镜像 digest 清单」──▶ 镜像仓库(清单 + 镜像)
|
||
▼ ▲
|
||
Web(展示端 · 管理端) │ 定时取清单
|
||
▲ REST + SSE │
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ Backend(面板) │──────────────────────────────┘
|
||
│ Api · Auth(会话 · passkey · API 令牌 · 审计) │
|
||
│ Trust(操作员集合 · 服务器身份)· Registry · Planner · Links │
|
||
│ Releases(发布清单 · 可信发布密钥) │
|
||
│ Ingest · Analysis · LiveHub │
|
||
│ Store(段文件 + SQLite) │
|
||
└──────────────────────────────────────────────────────────────┘
|
||
▲ ▲
|
||
│ wss 长连接(节点发起,应用层加密) │ wss 长连接(节点发起,应用层加密)
|
||
│ 任务、数据、状态、更新通知 │ 身份、Agent 列表、状态、更新通知
|
||
┌────────────────────┐ ┌──────────────────────┐
|
||
│ Agent × 10+ │ ◀── 测量会话 ── │ Probe × N │
|
||
│ 有公网 IP,监听 │ 先敲门再 TLS │ 家宽等出口,NAT 后 │
|
||
└────────────────────┘ └──────────────────────┘
|
||
测量会话:TLS 控制通道 + QUIC Datagram/TCP 数据通道,由 Probe 主动拨出(Agent 固定 QUIC 端口,Probe 本地源端口轮换)
|
||
更新(Agent、Probe、Backend):面板登记 CI 签名的清单 → 管理端确认 → 节点(Backend 先备份数据库)有序退出 → systemd Pull=always 拉最新镜像重启
|
||
```
|
||
|
||
- 线路(line):一对(Probe,Agent)。可以有多个 Probe(身份由 Probe 自己生成并用加入令牌注册,可以吊销、删除),每个 Probe 连接多个 Agent,每个 Agent 同时服务多个 Probe;一条线路的数据按 `line:<probe_id>` 单独存放,互不混合。对外的线路 id 是 `<agent_id>~<probe_id>`。
|
||
- 目标任务(target):某个 Agent → 指定的 IP:端口(ICMP、TCP/TLS 建连),只能访问 Agent 本地允许的网段。
|
||
|
||
## 3. 数据链路
|
||
|
||
```text
|
||
采集(只记录) 传输(有界、至少一次) 分析(注册制、可重算) 展示
|
||
┌───────┐ 原始列(10s) ┌────────┐ RawChunk(10s) ┌──────────────────────────────────────────┐
|
||
│ Probe │───────────>│ Agent │──outbox(16)──>│ 段文件(30天) → Analysis → 指标窗口(1m) │──> REST
|
||
│ 积木 │<══════════>│ 积木 │ wss │ ↓ 流式处理未满窗口 ↓ 降采样 5m/1h │──> SSE
|
||
└───────┘ 数据通道 └────────┘ │ LiveHub(内存快照) │
|
||
└──────────────────────────────────────────┘
|
||
```
|
||
|
||
| 角色 | 职责 | 明确不做 |
|
||
| --- | --- | --- |
|
||
| Probe | 连接 Backend(身份、Agent 列表、系统与构建信息、更新);连接面板指定的 Agent;执行线路任务中 Probe 一侧的角色;记录本端原始列并交给对应的 Agent | 统计;落盘测量数据;任何主机操作 |
|
||
| Agent | 注册;接受 Probe;执行 Agent 一侧的角色和目标任务;合并两侧的列并上传;上报主机资源的汇总数字 | 统计;落盘测量数据;exec/终端/文件/自更新(更新由宿主机上的更新器完成);采集主机的详细信息 |
|
||
| Backend | 注册与身份;任务编排;决定哪些 Probe 连哪些 Agent;持久化原始数据;分析;实时汇聚;API;分发 CI 签名的发布清单 | 主动连接 Probe 或 Agent(都是节点拨进来);验签和安装更新(在节点宿主机上) |
|
||
|
||
## 4. 关键设计(详见各专题文档)
|
||
|
||
| 主题 | 要点 | 文档 |
|
||
| --- | --- | --- |
|
||
| 节点框架 | 注册、身份、连接、RPC、可靠上报、状态同步、宿主都由与业务无关的 Fleet 提供;MoShiro 只写业务,其他产品也能复用 | [Fleet 文档](../../backend/fleet/README.md) |
|
||
| 注册与身份 | 使用 `fleet-enroll` 和加入令牌自动注册,之后用签名认证;Probe 用自己的身份直连面板,Agent 列表由面板下发 | [enrollment.md](../security/enrollment.md) |
|
||
| 更新 | 只接受发布密钥签名的清单,更新策略由节点本地决定,失败自动回滚。CI 对镜像 digest 签名;Backend 和一行脚本装的节点由宿主机上的 `moshiro-update` 验签、按 digest 拉取、失败回滚;Podman Quadlet 装的 Agent、Probe 更简单:节点有序退出,systemd 用 `--pull always` 重新拉最新镜像,节点自己不下载、不验证清单;纯二进制节点的更新见[路线图](../roadmap.md) | [deployment.md](../ops/deployment.md) |
|
||
| 任务模型 | `Task = {kind, target, params, schedule}`;持续采集、按需测试、目标任务统一建模;下发“期望任务集”,由节点自行调和 | [measurement.md](measurement.md) |
|
||
| 采集扩展 | Kind 由积木组合声明;采集密度靠参数调整;原始块自描述 | [measurement.md](measurement.md) |
|
||
| 统计扩展 | 指标注册制:少数几种累加器 + 统计算子;一行 `MetricDef` 就是一个新指标 | [analysis.md](analysis.md) |
|
||
| 协议 | 控制链路使用 Fleet(JSON-RPC 2.0 + 二进制通道);MoShiro 只定义 `moshiro.*` 方法、`TaskSet` 状态文档和 QUIC Datagram/TCP 数据面 | [protocol.md](protocol.md) |
|
||
| 存储 | 原始块追加写入段文件,SQLite 记录偏移量;指标窗口通用存储并降采样 | [storage.md](storage.md) |
|
||
| 展示查询 | 任意时间段(相对或绝对);自动步长(每条曲线最多约 720 个点);1s 内存热层(1 小时)加上 10s/1m/5m/1h 各层;累加器现场合并,汇总精确;列式响应;SSE 实时跟随 | [query.md](query.md) |
|
||
| API | `/api/public`(只读,可设为公开)、`/api/admin`(登录)、`/api/agent`(注册与 RPC);实时数据用 SSE | [api.md](../api/api.md) |
|
||
|
||
### 4.0 节点的更新
|
||
|
||
发布清单(镜像 digest)由 CI 签名,放进镜像仓库(`<仓库>-manifest` 小镜像的标签里);Backend 定时取、验签后登记(也接受 CI 直接 `POST /api/release/offer`),再原样转发给在线的 Agent / Probe。之后分两条路:
|
||
|
||
```text
|
||
Podman Quadlet(Agent、Probe、Backend,推荐)
|
||
节点比对清单和自己的版本 ──▶ 心跳报「有新版本」──▶ 管理端显示
|
||
自动(默认,运行满 10 分钟)或管理员点「更新」──▶ 节点有序退出
|
||
──▶ systemd 重启单元(Pull=always)──▶ 新镜像、新容器
|
||
节点不下载、不替换任何东西,也不验证清单;最多被请求一次有序重启
|
||
|
||
Backend:不自动,只有管理员点「更新并重启」;退出之前先把数据库备份到 backups/
|
||
|
||
一行脚本装的节点
|
||
宿主机 moshiro-update(root):用本机公钥验签 → 核对组件、仓库和 seq → podman pull 仓库@digest → 重启 → 不健康就回滚
|
||
──▶ 状态写进只读的 update-status/ ──▶ 节点随心跳上报 ──▶ Backend ──▶ 管理端(有新版本时点「更新」)
|
||
```
|
||
|
||
细节见 [deployment.md](../ops/deployment.md)「Podman(Quadlet)的更新」(一行脚本装的节点另见「宿主机更新器」)。
|
||
|
||
### 4.1 回显的四个时间戳
|
||
|
||
回显类 Kind 记录 `t1..t4`,用于扣除 Agent 处理耗时、分别判断上下行丢包和计算各方向的抖动,不要求两端时钟同步。推导见 [measurement.md](measurement.md) 第 3.4 节。
|
||
|
||
### 4.2 NAT / CGNAT 适配
|
||
|
||
所有连接和 UDP 流都由 Probe 发起;Agent 把回复发往请求的实际源地址。会话以 `session_id` 和 MAC 认证,不绑定五元组,NAT 映射变化后自动跟随新地址。持续的 UDP 回显本身就起到保活作用。
|
||
|
||
## 5. 安全边界:纯采集
|
||
|
||
> 本节描述现状。Backend 或节点被攻破后的威胁模型、现有缺口(例如 Probe 会拨 Backend 给的任意地址、测量数据没有产生者签名)及状态见 [security.md](../security/security.md)。
|
||
|
||
```text
|
||
Agent / Probe 能做的 执行编译在二进制中的 Kind;监听和访问本地配置允许的端口与网段;受本地硬上限约束
|
||
不能做的 exec / shell / 终端 / 文件读写(配置目录除外)/ 自更新 / 加载外部代码 / 采集主机标识(型号、主机名、IP、进程)
|
||
即使 Backend 被攻破 攻击者最多能在本地硬上限内发测量流量,拿不到主机权限,也无法把 Agent 当成扫描器或放大器;
|
||
也推不出恶意更新(更新清单必须由发布密钥签名,节点宿主机上的更新器用本机公钥验签,并且只认本机配置的镜像仓库)
|
||
```
|
||
|
||
- 本地硬上限写在节点本机的配置文件中,Backend 下发的任务如果超出,节点直接拒绝:允许访问的目标网段(默认为空,即不开放目标任务)、总码率和包速率上限、允许监听的端口。
|
||
- 构建期强制:常驻节点进程使用 `BannedSymbols.Node.txt`,禁用 `System.Diagnostics.Process`、`Assembly.Load*`、`NativeLibrary.Load`、`HttpClient`,以及 `ConfigStore` 以外的文件写入 API,详见 [project-layout.md](../dev/project-layout.md) 第 7 节。
|
||
- 权限分离:注册(`fleet-enroll`,一次性运行)、采集(`moshiro-agent` / `moshiro-probe`)、更新是不同的进程。容器节点里,采集进程在容器内以 UID 1654 运行(只读文件系统、不带额外能力),更新由宿主机上 root 运行的 `moshiro-update` 完成:它只读取节点放进 `update/` 的普通文件、复制到自己的临时目录后验签,状态写在节点只读的 `update-status/`;采集进程改写不了自己的二进制,更新器也读不到节点身份私钥。纯二进制节点的更新见[路线图](../roadmap.md)。
|
||
- 容器运行:Agent、Probe 的镜像基于 Alpine(musl,NativeAOT),宿主机用 rootful podman 加 systemd 运行:UID/GID 1654、只读文件系统、`--cap-drop all`、`no-new-privileges`、`/tmp` 用 tmpfs;配置和身份放在挂载的数据目录里。
|
||
- 最小权限运行:非 root 用户;只在需要时授予 `CAP_NET_RAW`(ICMP);默认端口都高于 1024,不需要 `CAP_NET_BIND_SERVICE`,只有在 Agent 本地显式开启 443 时才授予。
|
||
- 身份:节点使用本地生成的 ECDSA P-256 密钥对签名认证(Agent 和 Probe 都自己生成;Probe 有两把独立的密钥,一把连面板、一把连 Agent),Backend 只存公钥,数据库泄露也无法冒充节点。
|
||
- 防放大:未认证的包静默丢弃;回复长度与请求相等;每个流按声明速率限速;使用重放窗口。
|
||
- 容器里的主机指标:容器里的 Agent 通过只读挂载宿主机的 `/proc`、`/sys`、根目录和 `/etc/os-release`(环境变量 `HOST_PROC`、`HOST_SYS`、`HOST_ROOT`)读取宿主机的资源占用和系统名;读不到的项返回空值,不会退回去读容器自己的数据。Probe 的 compose 也一样挂载这几项。
|
||
- 自身状态:节点上报进程内的计数(会话数、包速率、队列、丢弃数);Agent 另外每隔几秒用 `moshiro.host` 上报一份主机资源占用的汇总数字(可以在 `agent.json` 里用 `reporting.host_stats = false` 整个关掉),最新值实时推给展示页,Backend 每分钟存一个样本(默认保留 7 天,`retention.host_days`)。
|
||
|
||
## 6. 可扩展性目标
|
||
|
||
| 变更 | 需要改动的地方 |
|
||
| --- | --- |
|
||
| 调整采集密度(例如 TCP 20 Hz → 100 Hz,`tcp_info` 100 ms → 10 ms) | 在 API 中修改任务参数,立即下发生效 |
|
||
| 新增采集方法(现有积木可以组合出来) | 在 Kind 目录中加 1 条声明,然后更新 Probe/Agent 版本 |
|
||
| 新增采集方法(需要新积木) | 1 个积木类 + 1 条 Kind 声明 |
|
||
| 新增统计指标 | 1 行 `MetricDef`(如需新算子,再加 1 个算子类);自动回填 30 天历史 |
|
||
| 调整服务评分 | 修改模型的评分函数;评分在查询时计算,立即对全部历史生效 |
|
||
|
||
Kind 目录编译在二进制中,节点不接受远程下发代码(安全边界)。节点在 `fleet.hello` 的 capabilities 中上报自己支持的 Kind,Backend 只下发它支持的类型。
|
||
|
||
## 7. 可靠性与资源
|
||
|
||
- Probe:每个 Agent 未交付的原始列 ≤ 8 MB;Agent:上传队列 ≤ 64 MB,满时丢弃最旧的块,并通过 `moshiro.event(gap)` 上报缺口。
|
||
- 至少一次投递:Backend 持久化之后才回复 ACK,按 `chunk_id` 去重。
|
||
- 节点重启会丢失未上传的数据,并体现为数据缺口。
|
||
- 窗口时间以 Agent 时钟为准;时钟偏差超过 5 秒时记录告警事件。
|
||
- 规模:10+ 个 Agent × 2~4 种持续 Kind × 每 10 秒一块,总计约每秒 2~6 块。
|
||
|
||
## 8. 设计决策记录
|
||
|
||
| # | 决策 | 理由 |
|
||
| --- | --- | --- |
|
||
| D1 | 只关注网络层;节点是纯采集器,没有远程执行能力 | 防止权限泄露 |
|
||
| D2 | 拓扑为多个 Probe 连接多个 Agent(10+);线路 = (Probe, Agent),数据按 Probe 隔离 | 拓扑需求 |
|
||
| D3 | Agent 用加入令牌自动注册,之后用密钥对签名认证 | 批量部署省事;Backend 不保存可用于冒充节点的秘密 |
|
||
| D4 | Probe 直连 Backend:身份、Agent 列表(期望状态 `probe_agents`)、状态和更新都走这条链路,测量仍然只走 Probe → Agent。哪些 Probe 连哪些 Agent 由面板决定,`fleet.auto_link` 打开时两两连,也可以手动选择,Probe 始终主动拨出 | 新增 Agent 后自动覆盖,不需要改 Probe 配置;Probe 的版本、更新和线路都能在面板上管理 |
|
||
| D5 | 只采集逐包、逐事件的原始数据,解释全部放在 Backend | 模型和指标可以事后重算;采集端最轻 |
|
||
| D6 | 统一的任务模型 + 期望任务集调和 | 新增任务类型时协议不变 |
|
||
| D7 | Kind 由积木组合声明;原始块自描述 | 调整密度或新增采集方法只改一处 |
|
||
| D8 | 指标注册制,使用固定的少数几种累加器 | 新增统计只改一行;存储、降采样、API 全部通用 |
|
||
| D9 | JSON-RPC 2.0 信封 + 二进制帧 | 新增能力只需要加方法 |
|
||
| D10 | 原始数据每 10 秒切一块,追加写入段文件,保留 30 天;指标长期保留 | 同一条链路同时服务实时和历史;避免海量小文件 |
|
||
| D11 | API 分为 public / admin / agent 三层;实时数据用 SSE | 前端只读场景足够;反向代理和 CDN 更友好 |
|
||
| D12 | 单仓库,三个独立项目加 Web(展示端、管理端);契约以 C# 代码为真源 | 改一处,编译期即可发现不一致 |
|
||
| D13 | Probe/Agent 运行时只依赖 BCL;.NET 10 NativeAOT | 轻量 |
|
||
| D14 | 节点机制抽成与业务无关的 Fleet 框架,放在同一仓库的 `backend/fleet/` 目录,暂不独立(不设拆分时间表,需要时再评估;不使用 PublicApiAnalyzers) | 可复用;不引用任何 `MoShiro.*` 的约束由 `ArchitectureTests` 在构建时检查 |
|
||
| D15 | 注册独立为 `fleet-enroll` 工具;常驻进程不包含注册代码 | 常驻进程去掉 HttpClient,边界更纯 |
|
||
| D16 | 更新与业务进程分离:发布密钥签名、本地策略、健康确认与回滚。已实现:宿主机 `moshiro-update`(Backend、脚本装的节点,清单里是镜像 digest),Quadlet 节点则靠退出后由 systemd 重新拉镜像;纯二进制节点的更新见[路线图](../roadmap.md) | 面板被攻破时也无法推送恶意二进制 |
|
||
| D17 | Probe 支持 OpenWrt:正式支持 `linux-musl-x64`、`linux-musl-arm64`;armv7 视工具链验证结果决定;MIPS 不支持,改为在局域网设备上运行 Probe | NativeAOT 不支持 MIPS |
|
||
| D18 | 默认测量端口不使用 443:控制 7443/tcp,QUIC Datagram 数据 7443/udp,TCP 数据 7444/tcp;443 只在 Agent 本地显式开启时才允许使用 | 避免与 VPS 上的真实服务冲突;结果会记录实际端口 |
|
||
| D19 | 展示查询最细 1s:最近 1 小时来自内存热层,更早的数据来自 10s/1m/5m/1h 各层,现场合并;时间段内的汇总由累加器精确合并得出 | 5 分钟、15 分钟这类视图足够精细且快 |
|
||
| D20 | 时序响应采用列式格式(`t[]` + `values{}`,时间轴使用 Unix 毫秒) | 体积小,可以直接交给图表库 |
|
||
| D21 | 国家和运营商只由 Backend 向 IP 归属服务查询(缺省 `https://ip.007995.xyz`,可关闭),只查公网地址;节点不访问任何外部服务;公开接口不返回 IP、城市、ASN | 客户的防火墙常常要审计节点的出站连接;访客拿不到敏感信息(有测试保证) |
|
||
| D22 | 构建信息(版本、tag、提交、构建时间)由节点自报,访客是否可见由设置 `site.show_build` 控制(默认关);提交号只在管理端 | 展示页可以选择不暴露版本;管理员能看出哪个节点没升级 |
|
||
| D23 | 零信任方向:信任锚点从 Backend 移到操作员 passkey(操作员集合,节点安装时固定创世指纹和属主);Probe 自己生成密钥;Agent 的 TCP 端口预握手敲门;敏感能力以后放进独立 extension,用属主 passkey 签名的能力令牌授权。不做进程沙箱(节点是 1 核 1 GB 的小机器),代码风险靠 PR 评审 | 做法和各缺口的状态见 [security.md](../security/security.md)、[access.md](../security/access.md) |
|
||
|
||
## 9. 未决事项
|
||
|
||
- 告警:首版不包含,是否预留 Webhook 见[路线图](../roadmap.md)。
|
||
- 与真实服务共用 443(例如 SNI 分流):默认使用非 443 端口,需要时再评估。
|