# 架构
简体中文 | English
## 架构图
### 0. 文件存储层 —— 插件写什么、留多久
```mermaid
flowchart TB
subgraph DATA_DIR["~/.dsh/dsh-network-settings/"]
LR["last-report.json
~25KB · 每次检测覆盖
写入前脱敏"]
AH["action-history.json
最近 50 条 · 只追加
用于「最近执行过」徽标"]
DSH_CFG["dsh-config.json
DSH 进程代理持久化"]
SNAP["snapshots/
每次修复一个 JSON
裁剪到 50 个(删最旧)
脱敏 · 原子写入"]
end
BAK["系统文件备份
hosts → .dsh-network-settings.bak
shell 配置文件 → .bak(sed)"]
CHECK["执行检测"] -->|"覆盖"| LR
REPAIR["应用修复"] -->|"创建"| SNAP
SNAP -->|"裁剪 >50"| SNAP
REPAIR --> BAK
ADVANCED["高级操作"] -->|"追加并裁剪到 50"| AH
DSH_PROXY["dsh.process 配置"] --> DSH_CFG
```
保留策略:`last-report.json` 单槽(永远是最新一份);操作历史上限 50 条;
快照每次保存后裁剪到 50 个;系统文件备份(`.bak`)放在原文件旁边,由用户
自行清理。
### 1. 系统分层 —— 两个半区,一条 RPC 通道
```mermaid
flowchart LR
subgraph CLIENT["客户端半区 · src/client(React,无平台依赖)"]
direction TB
UI["设置界面
NetworkTab · NetworkGraph · NetworkConfig
RepairSection · AdvancedSection"]
SVC["service.ts
类型化 RPC 客户端"]
REP["report.ts
Agent 报告生成器"]
end
subgraph HOST["宿主半区 · src/host(DSH Node 进程)"]
direction TB
RPC["index.ts
RPC 分发 · authority: loopback"]
CORE["Network Core
采集 · 链路图 · 诊断 · 修复"]
end
UI --> SVC
SVC -- "Connection RPC /dsh-network-settings" --> RPC
RPC --> CORE
CORE -- "脱敏后的 JSON" --> RPC
SVC --> REP
```
客户端从不执行平台命令;所有系统操作都跨越 RPC 边界。线上契约以
`src/host/network/types.ts` 为权威定义,在客户端镜像为
`src/client/contract.ts`。
### 2. 检测管线 —— 每条边都是数据契约
```mermaid
flowchart TB
RT["network/runtime.ts
detectRuntime()"]
INS["inspect.ts · inspectNetwork()
一个硬性总时限(45–60s)"]
WIN["windows/inspect.ts
一次 PowerShell 扫描"]
WSL["wsl/inspect.ts
wsl.exe 发现 + 本地 /bin/sh 事实"]
MAC["mac/inspect.ts
scutil · networksetup · route · lsof
+ shell 配置文件残留扫描"]
PRB["probe/* · DNS → TCP → TLS → HTTP
Node 侧 + 发行版内,分层超时"]
INSPECTION["NetworkInspection
(可序列化数据契约)"]
GRAPH["network/build-{windows,wsl}.ts
+ network/shared.ts 词汇表"]
DRIFT["network/drift.ts
5 条漂移规则"]
RULES["diagnose/rules.ts
9 条确定性规则"]
GATE["repair/catalog.ts
置信度 ≥ 0.85 + 白名单
+ 平台过滤"]
REPORT["BuiltNetworkReport
链路图 · 诊断 · 摘要 · 目标"]
RT -->|"DetectedRuntime"| INS
INS --> WIN --> INSPECTION
INS --> WSL --> INSPECTION
INS --> MAC --> INSPECTION
INS --> PRB --> INSPECTION
INSPECTION --> GRAPH
GRAPH -->|"NetworkPathGraph"| DRIFT
INSPECTION --> RULES
GRAPH --> REPORT
DRIFT --> REPORT
RULES --> REPORT
REPORT --> GATE
```
### 3. 模块依赖分层(宿主半区)
```mermaid
flowchart TD
subgraph L0["L0 · 基础设施(带副作用的原语)"]
CMD["runtime/command"]:::inf
PS["runtime/powershell"]:::inf
STORE["runtime/store"]:::inf
REDACT["redact.ts"]:::inf
end
subgraph L1["L1 · 采集器(平台事实,带副作用)"]
WINI["windows/inspect"]:::col
WSLI["wsl/*"]:::col
MACI["mac/inspect"]:::col
PROXY["proxy/*"]:::col
end
subgraph L2["L2 · 探测(带副作用,限时)"]
PNET["probe/net · 纯 Node"]:::probe
PWSL["probe/wsl · 发行版脚本"]:::probe
end
subgraph L3["L3 · 核心(数据契约上的纯函数)"]
SHARED["network/shared · 词汇表"]:::core
BUILD["network/build-*"]:::core
DRIFTM["network/drift"]:::core
RULESM["diagnose/rules"]:::core
CATM["repair/catalog"]:::core
end
subgraph L4["L4 · 效果(持久化修改)"]
CONF["configure/*"]:::eff
REPM["repair/* · advanced/hosts/wsl-proxy"]:::eff
SNAP["snapshot/* · diff + store"]:::eff
end
ENTRY["index.ts · RPC 入口"]:::entry
INSPECT["inspect.ts · 编排"]:::entry
NETIDX["network/index · 报告组装"]:::entry
ENTRY --> INSPECT & NETIDX & CONF & REPM & CATM
INSPECT --> WINI & WSLI & MACI & PROXY & PNET & PWSL & NETIDX
NETIDX --> SHARED --> BUILD --> DRIFTM
NETIDX --> RULESM
DRIFTM & RULESM --> CATM
CONF & REPM --> SNAP
WINI & WSLI & PWSL & PS & CONF & REPM --> CMD
WINI & CONF & REPM --> PS
SNAP & STORE & ENTRY --> REDACT
classDef inf fill:#eee
classDef col fill:#dfd
classDef probe fill:#ddf
classDef core fill:#fdd
classDef eff fill:#fed
classDef entry fill:#fff
```
分层规则:L3 是纯的(无 spawn、无 fs),用录制的 fixtures 做单元测试;
L1/L2 把每条平台命令收在一个门面函数后面;L4 是唯一修改系统的地方,
且总是经过快照。
**Deep Module 标注** —— 接口窄、隐藏了大量复杂度的模块(Ousterhout):
| 模块 | 接口 | 隐藏的复杂度 |
|---|---|---|
| `runtime/command.ts` | `runCommand(file, args, opts)` | 超时、中止、SIGKILL 升级、输出上限、编码 |
| `windows/inspect.ts` | `inspectWindowsFacts()` | 一段 PowerShell 脚本、UTF-8 契约、netsh 解析 |
| `probe/probe.ts` | `probeTarget(target, path, opts)` | 四层递进、CONNECT 隧道、代理 DNS 旁路、采样 |
| `mac/inspect.ts` | `inspectMacFacts()` | scutil、networksetup、route、lsof、sw_vers、shell 配置文件扫描 |
| `inspect.ts` | `inspectNetwork()` | 硬性总时限、按模型驱动采集、端点合并、监听进程标注 |
有意**不做深模块**的部分(薄的数据变换器):`network/shared.ts`(词汇表,
不是逻辑)、`redact.ts`(纯函数)、`snapshot/diff.ts`(JSON diff)。
### 4. 运行时模型选择
```mermaid
flowchart TB
PLAT{"process.platform"}
PLAT -->|win32| WN["WINDOWS_NATIVE"]
PLAT -->|linux| K{"/proc/version 中有
WSL 内核标记?"}
K -->|"microsoft + WSL_DISTRO_NAME"| WD["WSL_DISTRIBUTION
(事实:本地 /bin/sh + interop)"]
K -->|"容器 cgroup"| UNS["UNSUPPORTED_RUNTIME"]
K -->|"普通 linux"| UNS
PLAT -->|darwin| MAC["MACOS_NATIVE
(事实:scutil + shell 配置文件)"]
WN --> BW["build-windows.ts"]
WD --> BWS["build-wsl.ts"]
MAC --> BWM["build-mac.ts"]
```
### 5. 修复推荐门控与生命周期
```mermaid
flowchart LR
D["诊断
(code · severity · confidence · actions)"]
T{"置信度 ≥
RECOMMEND_CONFIDENCE_THRESHOLD
(0.85)?"}
M["diagnosisActionOperations()
精确作用域映射"]
W{"操作在常用
白名单内?"}
REC["推荐按钮
(flush-dns · 清理环境变量 ·
关闭系统代理 · 清理 DSH 环境变量)"]
MAN["仅手动目录
(管理员 · 重启 · 不可恢复)"]
PREV["预览 diff"] --> CONFIRM["用户确认"] --> APPLY["应用"] --> SNAP["快照"] --> RERUN["重新检测"] --> VER["验证"]
D --> T -->|是| M --> W
T -->|否| MAN
W -->|是| REC --> PREV
W -->|否| MAN
```
### 6. 测试缝
```mermaid
flowchart LR
FL["scripts/fault-lab.ts
进程内环境变量注入
(可安全中断、零残留)"]
PIPE["真实管线
采集 → 链路图 → 诊断 → 门控"]
A["断言诊断码
+ 出口模式 + 推荐操作"]
UT["tests/unit/*
录制的 fixtures
(Windows · WSL · macOS)"]
PARSERS["导出的解析器
(登记为测试缝)"]
FL --> PIPE --> A
UT --> PARSERS
UIT["tests/ui/*
mock 原语组件与 service"]
E2E["tests/e2e · Playwright 驱动真实 DSH"]
```
## 总览
```text
DSH 设置界面(React) ←── 无平台依赖,不执行系统命令
│ 类型化 RPC(/dsh-network-settings,authority: loopback)
▼
宿主半区(DSH Node 进程)
│
├─ 运行时检测:WINDOWS_NATIVE / WSL_DISTRIBUTION / MACOS_NATIVE
├─ 平台采集器(L1):windows/wsl/mac —— 各一个门面
├─ 分层探测(L2):DNS → TCP → TLS → HTTP,硬性超时
├─ 纯核心(L3):链路图构建 + 诊断规则 + 修复目录
├─ 效果层(L4):configure + repair + snapshot —— 唯一的修改层
└─ 文件存储:last-report(单槽)+ snapshots(裁剪到 50 个)
```
客户端从不执行平台命令。所有系统操作都通过宿主的 RPC 通道完成,
`authority: loopback`。
## 模块
### `src/host/network`
| 文件 | 职责 |
|---|---|
| `types.ts` | JSON 安全的链路图类型:`NetworkPathGraph`、`NetworkPath`、`PathNode`、`PathEdge`、`Evidence`、`ProxyConfiguration`、`ProxyEndpoint`、`NetworkDiagnostic`、`NetworkPathSummary` |
| `runtime.ts` | 基于 `process.platform`、`/proc/version`、`WSL_DISTRO_NAME`、`/etc/os-release`、cgroup 的静态运行时检测 |
| `survey.ts` | 供构建器消费的只读勘察 |
| `build-windows.ts` | `WINDOWS_NATIVE` 的 DSH 链路构建器 |
| `build-wsl.ts` | `WSL_DISTRIBUTION` 的 DSH 链路构建器 |
| `build-mac.ts` | `MACOS_NATIVE` 的 DSH 链路构建器(直连 + 代理) |
| `drift.ts` | 配置漂移诊断 + 修复提示 |
| `index.ts` | 编排:目标列表、链路图构建、摘要 |
### `src/host`
| 文件/区域 | 职责 |
|---|---|
| `windows/inspect.ts` | PowerShell 采集:网卡、路由、WinINet、WinHTTP、环境变量、监听端口、Hosts、网关 ICMP/邻居发现 |
| `mac/inspect.ts` | macOS 事实:scutil 代理、networksetup 网卡、route、lsof 监听、shell 配置文件残留扫描 |
| `wsl/*` | `wsl.exe` 列表解析、`.wslconfig`、`/etc/wsl.conf`、发行版事实 |
| `probe/net.ts` | DNS/TCP/TLS/HTTP 探测,稳定性模式的重复采样 |
| `probe/probe.ts` | DIRECT/PROXY 编排与首层失败映射 |
| `probe/wsl.ts` | 基于 `runWslScript` 的发行版内探测(当前发行版走本地 `/bin/sh`,其它发行版走 `wsl.exe`) |
| `network/shared.ts` | 共享链路图工具:代理解析、端点/监听匹配、网卡选择(出口 + 物理上联)、网关证据 |
| `diagnose/rules.ts` | 确定性诊断规则 |
| `configure/*` | 带预览/快照/应用的作用域配置 |
| `repair/*` | 操作目录、推荐、WSL/Hosts 修复、高级操作 |
| `redact.ts` | 报告与快照的密钥脱敏 |
### `src/client`
| 文件 | 职责 |
|---|---|
| `NetworkTab.tsx` | 设置页签入口、操作、目标切换、报告复制 |
| `NetworkGraph.tsx` | 诊断摘要、首层失败详情、DSH 链路泳道 |
| `NetworkConfig.tsx` | 渐进披露的分层配置面板 |
| `RepairSection.tsx` | 推荐修复 + 手动操作 + 回滚/历史 |
| `AdvancedSection.tsx` | 高风险系统急救操作(显式确认) |
| `service.ts` | 基于 DSH Connection 通道的类型化 RPC 客户端 |
| `contract.ts` | 客户端线上类型 |
## 三种运行时模型
```text
WINDOWS_NATIVE
DSH → Windows → [代理] → 网卡(TUN/VPN 或物理) → [物理上联]
→ 网关 → 互联网 → 目标
WSL_DISTRIBUTION
DSH → 发行版 → WSL 网络(NAT/Mirrored/…) → Windows 宿主
→ [代理] → 网卡(TUN/VPN 或物理) → [物理上联]
→ 网关 → 互联网 → 目标
MACOS_NATIVE
DSH → macOS → [代理] → 网卡(en0/utun) → 网关 → 互联网 → 目标
```
发行版身份与 WSL 网络层是两个独立概念。NAT 是一种边缘翻译语义,绝不画成
假的服务器节点。Mirrored/Bridged/VirtioProxy 各自保持自己的边关系。
当 TUN/VPN 网卡持有默认路由时(例如代理客户端的 `198.18.0.0/15` 虚拟网段),
它仍是出口网卡——流量确实经过它——但链路图还会在其后串出物理上联网卡与
物理网关,Windows 宿主节点显示物理 IP。ICMP/邻居发现的网关证据只对实际
测量过的网关成立。
### 各模型的命令执行方式
- `WINDOWS_NATIVE`:Windows 事实一次 PowerShell 调用完成;WSL 事实经
`wsl.exe`。
- `WSL_DISTRIBUTION`:当前发行版的探测与事实走本地 `/bin/sh`(无 interop
往返,不会重入挂起);其它发行版经 `cmd.exe → wsl.exe`。Windows 侧操作
(WinINet、WinHTTP、环境变量、DNS 缓存)仍需 Windows interop;interop
不可用时给出可操作的错误信息而不是裸 ENOENT,当前发行版由本地文件合成,
链路图照常工作。
## 探测分层
```text
DNS
↓ 成功
TCP(稳定性模式 3 次尝试)
↓ 成功
TLS
↓ 成功
HTTP HEAD
```
代理路径把 DNS 委托给代理解析,HTTPS 目标使用 CONNECT 隧道。
超时在每一层都强制生效:每层有独立预算(DNS 4s、TCP 4s、TLS 6s、HTTP 8s
——覆盖响应头和响应体),被取消的探测立即返回而不是挂起(DNS 使用可取消
的 Resolver),整次采集运行在一个硬性总时限内(RPC 入口起 60s,默认 45s),
网络故障永远不会让检测无限停摆。
## 配置漂移
配置不同不等于错误。只有满足以下条件,漂移才升级为诊断:
- DSH 的代理端点已被证实消失/不可达;
- WSL 能到达 Windows 宿主,却到不了配置的代理;
- WinHTTP 仍指向一个没有监听的端口。
健康的配置差异报告为 `info`。
## 修复推荐策略
一个修复按钮被标为「推荐」必须同时满足三条:
- 驱动它的诊断置信度 ≥ 0.85(`RECOMMEND_CONFIDENCE_THRESHOLD`);
- 映射到的操作在常用操作白名单内;
- 操作的平台标签匹配当前运行时(`operationsForPlatform(process.platform)`
过滤目录与推荐——Windows 专属操作在 macOS 上不可见,反之亦然)。
白名单包含平台中立操作(`clear-dsh-process-proxy`、`flush-dns`/
`mac-flush-dns`),以及只在各自平台被推荐的平台专属操作(Windows 的
`clear-user-env-proxy`,macOS 的 `mac-clear-shell-proxy`/
`mac-clear-scutil-proxy`)。
管理员/UAC、需要重启、不可恢复的操作(机器级环境变量、WinHTTP 机器级
重置、Winsock/TCP-IP 重置、`wsl-autoproxy-enable`)绝不作为推荐出现;
它们只保留在手动目录中。多个诊断产生的重复操作在服务端去重。
## 修复保证
每次持久化修改都遵循:
```text
读取当前值 → 快照 → diff 预览 → 用户确认
→ 应用 → 重新检测 → 验证
```
命令执行成功永远不等于网络修复成功。
### 文件保留策略
| 文件 | 策略 | 上限 |
|---|---|---|
| `last-report.json` | 每次检测覆盖 | 1 个文件(约 25KB) |
| `action-history.json` | 追加,删最旧 | 50 条 |
| `snapshots/*.json` | 每次修复一个,保存后裁剪 | 50 个文件 |
| 系统 `.bak` 文件 | 放在原文件旁 | 用户自管 |
所有文件写入前脱敏;快照使用原子写入(tmp + rename)。