Files
MoShiro/docs/design/architecture.md
T
justme d5ebf29e57
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline was successful
ci/woodpecker/push/e2e Pipeline was successful
docs: tighten wording, soften node capability claims, link architecture to code
- Remove the node capability paragraph from README; soften "pure collector"
  and "no self-update" statements in docs, AGENTS.md and READMEs
- architecture.md: drop scalability goals, design decisions and open items;
  add a component-to-code table and code links
- Replace rhetorical contrasts in security docs with direct statements
- AGENTS.md: add project mental model, change map and writing style rules
2026-10-21 06:52:01 +08:00

22 KiB
Raw Blame History

架构设计

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、远程执行、终端和文件读写;更新由节点宿主机上的更新器或服务管理器完成,节点进程只把面板转来的发布清单和更新请求写入数据目录。后续能力的规划见 security.md 第 9 节。

2. 拓扑

 操作员 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 拉最新镜像重启

各组件的代码位置:

组件 代码
Backend 宿主与组合根 MoShiro.Backend
Api(REST、SSE、OpenAPI) MoShiro.Backend.Api,实时推送 LiveEndpoints.cs
Auth(会话、passkey、API 令牌、审计) Application/Auth、Application/Audit
Trust(操作员集合、服务器身份) TrustService.cs、ServerIdentityService.cs;节点侧验证 OperatorTrust.cs、TrustAnchor.cs
Registry(Agent、Probe 注册与状态) AgentService.cs、ProbeService.cs、MoShiroFleetHooks.cs
Planner(有效任务集) TaskPlanner.cs、EffectiveTasks.cs
Links(哪些 Probe 连哪些 Agent) LinkService.cs
Releases(发布清单、可信发布密钥) Application/Updates,轮询 ReleasePoller.cs
Gateway(Fleet 网关、状态提供者、收件箱) MoShiro.Backend.Gateway,入库 RawChunkInbox.cs、IngestService.cs
Analysis(算子、指标、模型) MoShiro.Backend.Analysis,调度 AnalysisScheduler.cs
LiveHub(热层与 SSE 主题) LiveHub.cs
Store(段文件 + SQLite) MoShiro.Backend.Storage.Sqlite,原始块 SqliteRawStore.cs
Agent AgentApp.cs、任务 TaskManager.cs、Probe 会话 PeerHub.cs
Probe ProbeApp.cs、Agent 连接 Link/AgentConnection.cs
采集引擎与协议 MoShiro.Measurement、MoShiro.Protocol
Fleet 节点框架 backend/fleet,注册工具 fleet-enroll
  • 线路(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. 数据链路

 采集(只记录)                 传输(有界、至少一次)                分析(注册制、可重算)           展示
┌───────┐ 原始列(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(都是节点拨进来);验签和安装更新(在节点宿主机上)

角色对应的代码:Probe 见 ProbeApp.cs;Agent 见 AgentApp.cs,上传块由 ChunkAssembler.cs合并;Backend 接收见 FleetGateway.cs。

4. 关键设计(详见各专题文档)

主题 要点 文档 代码
节点框架 注册、身份、连接、RPC、可靠上报、状态同步、宿主都由与业务无关的 Fleet 提供;MoShiro 只写业务,其他产品也能复用 Fleet 文档 backend/fleet
注册与身份 使用 fleet-enroll 和加入令牌自动注册,之后用签名认证;Probe 用自己的身份直连面板,Agent 列表由面板下发 enrollment.md NodeIdentity.cs、fleet-enroll
更新 只接受发布密钥签名的清单,更新策略由节点本地决定,失败自动回滚。CI 对镜像 digest 签名;Backend 和一行脚本装的节点由宿主机上的 moshiro-update 验签、按 digest 拉取、失败回滚;Podman Quadlet 装的 Agent、Probe 更简单:节点有序退出,systemd 用 --pull always 重新拉最新镜像,节点自己不下载、不验证清单;纯二进制节点的更新见路线图 deployment.md moshiro-update.sh、RestartUpdater.cs、packaging/quadlet
任务模型 Task = {kind, target, params, schedule};持续采集、按需测试、目标任务统一建模;下发“期望任务集”,由节点自行调和 measurement.md TaskSet.cs、TaskPlanner.cs
采集扩展 Kind 由积木组合声明;采集密度靠参数调整;原始块自描述 measurement.md Kinds.cs、BlockCatalog.cs
统计扩展 指标注册制:少数几种累加器 + 统计算子;一行 MetricDef 就是一个新指标 analysis.md MetricRegistry.cs
协议 控制链路使用 Fleet(JSON-RPC 2.0 + 二进制通道);MoShiro 只定义 moshiro.* 方法、TaskSet 状态文档和 QUIC Datagram/TCP 数据面 protocol.md MoShiroMethodTable.cs、MethodTable.cs
存储 原始块追加写入段文件,SQLite 记录偏移量;指标窗口通用存储并降采样 storage.md Schema.cs
展示查询 任意时间段(相对或绝对);自动步长(每条曲线最多约 720 个点);1s 内存热层(1 小时)加上 10s/1m/5m/1h 各层;累加器现场合并,汇总精确;列式响应;SSE 实时跟随 query.md QueryService.cs、HotLayer.cs
API /api/public(只读,可设为公开)、/api/admin(登录)、/api/agent(注册与 RPC);实时数据用 SSE api.md v1.json、Api

Kind 目录编译在二进制中,节点不接受远程下发代码。节点在 fleet.hello 的 capabilities 中上报支持的 Kind,Backend 只下发它支持的类型。

4.0 节点的更新

发布清单(镜像 digest)由 CI 签名,放进镜像仓库(<仓库>-manifest 小镜像的标签里);Backend 定时取、验签后登记(也接受 CI 直接 POST /api/release/offer),再原样转发给在线的 Agent / Probe。之后分两条路:

Podman Quadlet(Agent、Probe、Backend,推荐)
  节点比对清单和自己的版本 ──▶ 心跳报「有新版本」──▶ 管理端显示
  自动(默认,运行满 10 分钟)或管理员点「更新」──▶ 节点有序退出
  ──▶ systemd 重启单元(Pull=always)──▶ 新镜像、新容器
  该路径下节点本身不下载、不替换文件,也不验证清单;最多被请求一次有序重启

Backend:不自动,只有管理员点「更新并重启」;退出之前先把数据库备份到 backups/

一行脚本装的节点
  宿主机 moshiro-update(root):用本机公钥验签 → 核对组件、仓库和 seq → podman pull 仓库@digest → 重启 → 不健康就回滚
  ──▶ 状态写进只读的 update-status/ ──▶ 节点随心跳上报 ──▶ Backend ──▶ 管理端(有新版本时点「更新」)

实现:宿主机更新器 moshiro-update.sh(安装 install-updater.sh),节点退出逻辑 RestartUpdater.cs,Backend 更新与备份 BackendUpdates.cs,清单登记 ReleaseService.cs,Quadlet 单元 packaging/quadlet。

细节见 deployment.md「Podman(Quadlet)的更新」(一行脚本装的节点另见「宿主机更新器」)。

4.1 回显的四个时间戳

回显类 Kind 记录 t1..t4,用于扣除 Agent 处理耗时、分别判断上下行丢包和计算各方向的抖动,不要求两端时钟同步。推导见 measurement.md 第 3.4 节。

4.2 NAT / CGNAT 适配

所有连接和 UDP 流都由 Probe 发起;Agent 把回复发往请求的实际源地址。会话以 session_id 和 MAC 认证,不绑定五元组,NAT 映射变化后自动跟随新地址。持续的 UDP 回显本身就起到保活作用。准入逻辑(重放窗口、令牌桶、源地址学习)见 Registries.cs,数据报路由见 MeasurementDatagramRouter.cs。

5. 安全边界

本节描述现状。Backend 或节点被攻破后的威胁模型、现有缺口(例如 Probe 会拨 Backend 给的任意地址、测量数据没有产生者签名)及状态见 security.md。

Agent / Probe 能做的   执行编译在二进制中的 Kind;监听和访问本地配置允许的端口与网段;受本地硬上限约束
当前未提供的能力        exec / shell / 终端 / 文件读写(配置目录除外)/ 加载外部代码 / 采集主机标识(型号、主机名、IP、进程);更新在节点进程之外完成
即使 Backend 被攻破    攻击者最多能在本地硬上限内发测量流量,拿不到主机权限,也无法把 Agent 当成扫描器或放大器;
                      也推不出恶意更新(更新清单必须由发布密钥签名,节点宿主机上的更新器用本机公钥验签,并且只认本机配置的镜像仓库)
  • 本地硬上限写在节点本机的配置文件中(AgentConfig.cs),由 BudgetGuard.cs 检查,Backend 下发的任务如果超出,节点直接拒绝:允许访问的目标网段(默认为空,即不开放目标任务)、总码率和包速率上限、允许监听的端口。
  • 构建期强制:常驻节点进程使用 BannedSymbols.Node.txt,禁用 System.Diagnostics.Process、Assembly.Load*、NativeLibrary.Load、HttpClient,以及 ConfigStore 以外的文件写入 API,详见 project-layout.md 第 7 节。
  • 权限分离:注册(fleet-enroll,一次性运行)、采集(moshiro-agent / moshiro-probe)、更新是不同的进程。容器节点里,采集进程在容器内以 UID 1654 运行(只读文件系统、不带额外能力),更新由宿主机上 root 运行的 moshiro-update 完成:它只读取节点放进 update/ 的普通文件、复制到自己的临时目录后验签,状态写在节点只读的 update-status/;采集进程改写不了自己的二进制,更新器也读不到节点身份私钥。纯二进制节点的更新见路线图。
  • 容器运行:Agent、Probe 的镜像基于 Alpine(musl,NativeAOT),宿主机用 rootful podman 加 systemd 运行:UID/GID 1654、只读文件系统、--cap-drop all、no-new-privileges、/tmp 用 tmpfs;配置和身份放在挂载的数据目录里。
  • 属主检查:OwnerGuard.cs;预握手敲门:KnockGate.cs。
  • 最小权限运行:非 root 用户;只在需要时授予 CAP_NET_RAW(ICMP);默认端口都高于 1024,不需要 CAP_NET_BIND_SERVICE,只有在 Agent 本地显式开启 443 时才授予。
  • 身份(NodeIdentity.cs):节点使用本地生成的 ECDSA P-256 密钥对签名认证(Agent 和 Probe 都自己生成;Probe 有两把独立的密钥,一把连面板、一把连 Agent),Backend 只存公钥,数据库泄露也无法冒充节点。
  • 防放大(Registries.cs):未认证的包静默丢弃;回复长度与请求相等;每个流按声明速率限速;使用重放窗口。
  • 容器里的主机指标:容器里的 Agent 通过只读挂载宿主机的 /proc、/sys、根目录和 /etc/os-release(环境变量 HOST_PROC、HOST_SYS、HOST_ROOT)读取宿主机的资源占用和系统名(采样见 HostSampler.cs);读不到的项返回空值,不会退回去读容器自己的数据。Probe 的 compose 也一样挂载这几项。
  • 自身状态:节点上报进程内的计数(会话数、包速率、队列、丢弃数);Agent 另外每隔几秒用 moshiro.host 上报一份主机资源占用的汇总数字(可以在 agent.json 里用 reporting.host_stats = false 整个关掉),最新值实时推给展示页,Backend 每分钟存一个样本(默认保留 7 天,retention.host_days),见 HostStatusService.cs。

6. 可靠性与资源

  • Probe:每个 Agent 未交付的原始列 ≤ 8 MB;Agent:上传队列 ≤ 64 MB,满时丢弃最旧的块,并通过 moshiro.event(gap) 上报缺口。
  • 至少一次投递:Backend 持久化之后才回复 ACK,按 chunk_id 去重(RawChunkInbox.cs)。
  • 节点重启会丢失未上传的数据,并体现为数据缺口。
  • 窗口时间以 Agent 时钟为准;时钟偏差超过 5 秒时记录告警事件。
  • 规模:10+ 个 Agent × 2~4 种持续 Kind × 每 10 秒一块,总计约每秒 2~6 块。