Files
Vole/docs/controller-api.md
T
yiguodev 608a8f255c feat: add client protocols, TLS capabilities, and platform validation (#1)
* chore: download latest official mihomo for interop tests

* test: capture N0 baseline and protocol feasibility probes

* docs: record N0 hybrid REALITY fork verification

* test: verify N0 controlled Hysteria2 auth and streams

Add an isolated Quinn adapter over existing DIRECT and SOCKS5 datagrams, bounded queues, protect rejection probes, and official latest peer fixtures. Record passing auth/TCP checks and the native Hysteria half-close failure separately. N0 remains incomplete; production sources and dependency locks are unchanged.

* test: validate N0 native QUIC hopping and XHTTP H3

Keep the probes isolated from production. Verify one-state Hysteria hopping, protected rebind rejection, Xray H3 entry and existing HY2 regressions. Set only the owned Linux peer MTU to avoid Initial-packet EMSGSIZE; retain native half-close failures as separate gates.

* fix: align XHTTP shutdown with Mihomo connection close

* test: verify N0 supplied-stream transport interfaces

Add isolated TLS, ordinary WebSocket and gRPC probes with owned I/O, bounded buffers and cancellation barriers.

Match Mihomo close behavior: preserve TLS/WS reads, distinguish clean WS underlay EOF from truncation, and close both gRPC directions. Use separate official SOCKS5 peers to avoid fixture loop detection.

Record 12 Debug/Release tests, 42 official Mihomo checks, dependency audits and Apple/Android compile checks. Keep production dependencies, third-party sources and remaining N0 gates unchanged.

* fix: upgrade h2 and retain completed responses after reset

* chore: use the current OneXray rustls fork origin

* chore: adopt published rustls 0.23.45 and official TLS binding

* chore: refresh N1 foundation and spike dependencies

* feat: validate next-protocol field and combination catalogs

* feat: share immutable TLS policies and bounded close semantics

* feat: add shared stream transports and decouple XUDP framing

* feat: add controlled datagram foundations and N1 acceptance harness

* chore: finalize N1 with verified x25519 dependency

* chore: publish N1 against the rustls release branch

* feat: add bounded Trojan request and datagram codecs

* feat: connect Trojan TLS TCP and UDP through the runtime

* feat: support Trojan WebSocket and duplex gRPC transports

* feat: enable Trojan and complete N2 consumer acceptance

* feat(vmess): add bounded AEAD header and body codecs

* fix(vmess): align close acceptance with Mihomo transports

* feat(vmess): add UDP codecs and isolated peer comparisons

* feat(vmess): complete public runtime and N3 acceptance

* docs: bind N3 acceptance and refresh validation guidance

* feat(vless): complete N4 transports, Vision and UDP support

Extract VLESS assembly, add plain/TLS stream transports, HTTPUpgrade, node-owned gRPC pooling, mTLS and classic REALITY, and implement Vision through public TLS record boundaries. Preserve XHTTP and the XUDP default while adding raw and packetaddr.

Complete 145 required groups and 39 field rows against isolated latest official peers, with 160 lifecycle/resource rounds, independent coverage and Apple/Android release builds. Preserve the three explicit WS/REALITY layered close comparisons and historical failures in the acceptance report. No third-party or dependency-lock changes.

* feat(vless): complete N5 XHTTP and sing-mux support

Add H1/H2/H3 request fields, independent download security and pools, bounded packet-up batching, and h2mux/smux/yamux over controlled transports.

Keep Mihomo-aligned close and path semantics, node-owned shutdown barriers, and isolated official-peer fixtures including the approved Caddy mTLS gateway.

Validate 416 required groups, 57 fields, 112 two-leg security checks, 240 lifecycle/resource cycles, independent coverage, and Apple/Android release builds. Record native-peer gaps and leave N6-N10, devices, CI, and release gates separate.

* feat: integrate boring TLS profiles and retire rustls fork

* test(tls): freeze selected fingerprint reference baseline

* docs(tls): record selected-profile library acceptance

* feat(tls): integrate selected client fingerprints and REALITY profiles

* feat(tls): complete selected fingerprint CF5 acceptance

* feat(hysteria2): complete N6 client support and acceptance

Implement Hysteria2 over official Quinn/h3/rustls and shared controlled IO, including TCP/UDP, bandwidth pacing, Salamander, port hopping, upstream snapshots and synchronous Stop.

Keep old-path receives active for the full bounded handover window, preserve independent reader wakeups, and require the exact nested TCP evidence assertions.

Validate one frozen N6 run: 36 required groups, 20 field rows, native Mihomo/Hysteria peers, H3 regression, 40 lifecycle rounds and Apple/Android production builds. Record independent coverage, artifact hashes and native-peer limitations.

* test(tls): record N7 hybrid REALITY prerequisite evidence

Preserve the locked classic-only API failure and distinguish the locally accepted boring hybrid extension from pending VCore S03/D16 integration. Record wrapper feasibility and publication boundaries without changing production configuration or dependencies.

* feat(vless): support explicitly required hybrid REALITY

* feat(crypto): wrap official BLAKE3 binary-context derivation

* feat(vless): implement authenticated Encryption wire transport

* feat(vless): enable authenticated Encryption across public transports

* build(tls): pin the authenticated ShadowTLS ClientHello hook

* feat(vless): add authenticated JLS transport security

Pin the separately approved boring JLS revision and expose strict main/download credentials under schema 24. Preserve native CertificateVerify and Finished, cancellation, and controlled IO ownership. Accept the JLS field slice with 122 isolated cases, 80 lifecycle/resource cycles, and Apple/Android release libraries; keep broader N7 gates separate.

* docs(n7): reconcile current JLS acceptance progress

* chore(vless): retire Restls while retaining JLS

* feat(vless): complete N7 static ECH and security combinations

Add static ECH for VLESS main and XHTTP download legs using existing TLS backends and official Rust HPKE. Keep Invoke v5 and advance the internal schema to revision 27.

Accept the narrowed N7 scope with 43 required groups, 11 fields, 301 isolated native cases, 240 lifecycle/resource rounds, and Apple/Android release builds. Record frozen inputs, 470 container cleanups, explicit upstream comparison gaps, and prior failed runs in docs/acceptance/next-protocols/N7.md.

* chore(protocols): retire WireGuard scope and foundations

* test: complete N9 protocol integration and resource gates

* build: add N10 production artifact evidence gates

* fix: restore Windows snapshot encoding and fail-closed delivery CI

* fix: use native ARM64 LLVM assembly and scoped delivery CI

* build: keep delivery lockfile bytes stable across platforms

* fix: preserve production guard lifecycle and isolate CI feature checks

* build: adopt released boring and registry shadowsocks

* ci: use release-oriented workflow names

* docs: consolidate contracts and streamline test tooling

Merge current protocol and platform contracts, remove milestone reports and obsolete spikes, and share core checks between local development and CI. Preserve all 884 executable case definitions and unique legacy assertions.

Validation: core Debug/Release, feature checks, Clippy, netstack, offline script tests, formatting and documentation links passed locally. Container interop smoke attempts failed during official peer downloads; business paths remain NOT RUN.

* test: replace milestone names with capability-based validation

Rename protocol and fingerprint suites, fixtures, and evidence identifiers by capability. Preserve acceptance cases and golden wire data while documenting reference, wire, and interoperability evidence separately.

* fix: address review findings and stabilize core CI

Flush accepted gRPC/H2 data through the supplied IO before reset, and wait for pending smux writes before FIN. Sanitize AnyTLS peer failures, reject XHTTP userinfo, and remove stale Android delivery artifacts.

Close container log capture even when inventory fails. Keep legacy host suites blocked before resource acquisition.

Separate Shadowsocks adapter codec checks from upstream strict server acceptance. Lock the random empty-request zero-padding failure with deterministic negative and positive fixtures, without changing the dependency or production behavior.

* test: isolate delivery fixture from Windows home lookup

* test: preserve UDP socket semantics during peer teardown

The memory peer treated its counterpart closing an mpsc channel as a local UDP BrokenPipe. That could terminate Quinn endpoint IO without waking a pending wait_idle, despite all four datagrams and CONNECTION_CLOSE having arrived.

Keep a closed peer link silent and late sends best-effort, add a deterministic fixture regression, and preserve the existing data and shutdown deadlines. Debug/Release, Clippy, and 300 consecutive regression iterations pass.

* fix: drain XHTTP uploads before releasing connection drivers

* fix: enforce lazy setup deadlines and safe delivery paths

Check the original deadline before accepting ready gRPC/H2 headers or sing-mux status, without limiting established reads. Reject symlinks and Windows reparse points in every delivery output path before filesystem mutations. Add deterministic memory and temporary-filesystem regressions.

* test: join XHTTP H3 peer tasks during cleanup

Quinn retains closed connections for three probe timeouts, so a delayed handshake can legitimately exceed the fixture's two-second wait_idle budget. Own, abort, and join the passive peer tasks after upload and client shutdown instead. Preserve upload integrity and client close bounds, and cover delayed handshakes in both streaming modes.
2026-09-28 12:50:01 +08:00

153 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 运行时 Controller API
Controller 是当前 Running Session 的回环 HTTP 接口。它提供一次性 TUN 流量快照,以及静态 `select` 代理组的状态查询和实时选择;它不是 Invoke API,也不承诺完整 Mihomo Dashboard 兼容。
## 配置与生命周期
```yaml
external-controller: 127.0.0.1:9090
secret: "vcore-runtime-secret"
```
- 省略 `external-controller` 表示不启动 Controller,此时不能单独配置 `secret`。含代理组但没有 Controller 的配置合法,组在本次 session 内保持初始选择。
- `external-controller` 只接受带显式非零端口的回环 IP 地址。它要求启用 TUN 或至少定义一个 `proxy-groups`;非 TUN 配置须通过 `port` 或 `socks-port` 满足运行配置的入站要求。
- 只要同时配置代理组与 Controller,`secret` 就必填;仅有 TUN 流量接口时可省略。`secret` 出现时必须为 1–255 UTF-8 字节,并保护本文定义的全部路由。
- `measureDelay` 的 node-only 配置不能包含 Controller 或代理组字段。
- `validateConfig` 只校验字段;`prepare` 不监听端口;`start` 绑定端口,绑定失败则启动失败。`stop` 和 `destroyInstance` 关闭监听器。
Controller 与公共运行时 session 同生共灭。代理组选择只保存在 VCore 当前 session 的内存中;VCore 不写回配置。宿主如需跨 session 保留选择,必须自行持久化,并在下次 `configYaml` 中提供对应的 `default-selected`。
## Bearer 鉴权
配置 `secret` 后,每个请求都必须携带:
```http
Authorization: Bearer vcore-runtime-secret
```
要求:
- scheme 必须精确为 `Bearer`,token 必须与当前 session 配置完全一致;
- 缺失、重复、格式错误或不匹配都返回 `401 Unauthorized`;
- 比较过程不能通过日志、响应或明显的提前返回时序泄漏 token;
- 未配置 `secret` 时不要求 `Authorization`;Controller 不使用 HTTP Basic,顶层 `authentication` 只属于 HTTP/SOCKS5 业务代理。
- `allow-lan` 只控制业务代理监听,不改变 Controller 的回环限制或 Bearer 鉴权。
## `GET /traffic`
授权请求立即返回一次 `application/json` 快照:
```json
{
"up": 1024,
"down": 4096,
"upTotal": 65536,
"downTotal": 262144
}
```
字段均为非负整数,单位为字节:
- `up`:最近一个完整的一秒窗口内,从宿主 TUN 进入 VCore 的原始 IP 包字节数;
- `down`:最近一个完整的一秒窗口内,由 VCore 写回宿主 TUN 的原始 IP 包字节数;
- `upTotal`:本次 TUN session 的累计上行字节数;
- `downTotal`:本次 TUN session 的累计下行字节数。
Apple utun 的四字节包信息头不计入统计。统计只覆盖跨过 TUN L3 边界的包,包括 TUN DNS 和 ICMP;不重复计算 TLS、XHTTP 或其他代理封装,也不包含 HTTP/SOCKS5 代理入站、GeoData 下载、Controller 请求和 `measureDelay`。
请求不会等待下一次采样,不保持分块响应,不升级 WebSocket,也不为调用方保存“上次读取”状态。首个采样窗口完成前和空闲窗口内,`up`/`down` 为 0;累计值在同一 session 内单调不减并采用饱和语义。没有 TUN 的代理组 Controller 对该路由返回 `404 Not Found`。
## 代理组查询
`GET /group` 按 YAML 声明顺序返回全部代理组:
```json
{
"proxies": [
{
"name": "main-select",
"type": "Selector",
"all": ["edge-a", "fallback-select", "DIRECT", "edge-a"],
"now": "edge-a"
}
]
}
```
以下两个请求返回同一个组状态对象:
```http
GET /group/{name}
GET /proxies/{name}
```
- `name` 是组定义名,`type` 固定为 `Selector`;
- `all` 是配置中的直接成员,严格保留顺序和重复项;
- `now` 是当前选中的直接成员名。若该成员是嵌套组,`now` 仍返回组名,不展开为最终叶节点;
- 名称和值大小写敏感并精确匹配,不做大小写折叠或 Unicode normalization。
## `PUT /proxies/{name}`
切换组的当前直接成员:
```http
PUT /proxies/main-select HTTP/1.1
Authorization: Bearer vcore-runtime-secret
Content-Type: application/json
Content-Length: 26
{"name":"fallback-select"}
```
成功返回 `204 No Content` 和空正文。`name` 必须是该组 `all` 中的直接成员;重复名称命中第一项。请求不接受传递成员、数组、索引、CAS/version 或其他字段。
请求正文边界:
- `Content-Type` 必须是唯一且不带参数的 `application/json`;
- 必须有且只有一个十进制 `Content-Length`,不接受 `Transfer-Encoding` 或 chunked;
- 正文最大 1 KiB,读取 header 和 body 各有 5 秒期限;
- JSON 必须是 UTF-8 对象并且恰好包含一个字符串字段 `name`;缺失、未知或重复字段、malformed JSON 和尾随 JSON 都会失败;
- 未知组、未知成员和任何无效请求都不得改变当前选择。
同一组的成功写入是线性化的,最后一个成功提交的请求胜出;不同组独立更新,不提供跨组事务或 CAS。
## 名称与路径
节点和代理组定义名共享一个命名空间。名称必须为 1–64 UTF-8 字节,首尾不能是任何 Unicode 空白字符,不能含控制字符或 `, # / ? & = % \\`,也不能是 `.` 或 `..`。定义名额外保留 `DIRECT`、`REJECT` 和 `RULES`;前两者仍可作为组成员,`RULES` 只作为 DNS sentinel。内部普通空格、CJK 和 emoji 可以使用。
路径中的 `{name}` 必须是单个 segment。Controller 对 percent-encoding 严格解码一次,再执行相同的 UTF-8 和名称校验;无效 `%`、无效 UTF-8、解码后的 `/`、二次编码形式、query 和 fragment 都会失败。客户端应对非 ASCII 或路径保留字节执行一次标准 percent-encoding;`+` 不会被当作空格。
## 选择生效边界
代理组成员列表在配置期固定,只有当前选择可在 Running Session 内改变:
- 新建的物理 TCP 连接使用切换后的当前叶节点;既有 TCP 连接保持原路径;
- 新建的 UDP association/物理 transport 使用切换后的当前叶节点;既有 association 不迁移;
- 新建的 DNS transport 使用切换后的当前叶节点;DNS cache、singleflight 状态和已在池中的 TCP transport 不清空、不迁移;
- 切换不主动断开连接、不刷新缓存、不重启运行时,也不修改 YAML 或下一次 session 的默认值;
- 选中节点、`DIRECT` 或 `REJECT` 时按原样执行;选中嵌套组时继续解析其当前成员。失败按原样返回,不自动选择其他成员。
`dialer-proxy` 与规则、DNS 共享同一组选择,但经共享连接器直接建链,不重新执行业务规则。上游组的 `DIRECT` 连接当前节点预解析的服务器,`REJECT` 使建链失败。单次建链对同一组只读取一次选择,SOCKS5 UDP 的控制连接和 relay 路径共享该快照;不同组之间不提供原子快照。已经读取旧选择的在途握手继续原路径。复用旧 AnyTLS 会话的新业务流也保留原路径,只有真正新建底层 transport 才读取新选择。
Hysteria2 已认证 QUIC 会话也保留其 `dialer-proxy` 上游组快照,包括复用该会话的新业务流和端口跳跃所需的新物理 socket;这些不是新的认证会话,不能因切组而迁移原路径。只有重新建立的认证会话才读取新的上游组选择。节点本身作为路由组成员时,后续业务的路由选节点仍按当前组选择执行。
## HTTP 状态与协议边界
| 状态 | 含义 |
| --- | --- |
| `200 OK` | 查询成功,返回 `application/json` |
| `204 No Content` | 选择成功,正文为空 |
| `400 Bad Request` | 路径、JSON、长度格式或成员无效 |
| `401 Unauthorized` | Bearer 鉴权失败 |
| `403 Forbidden` | 请求来源不是回环地址 |
| `404 Not Found` | 路由、组或当前 session 中的 TUN 流量资源不存在 |
| `405 Method Not Allowed` | 路由存在但方法不允许 |
| `408 Request Timeout` | header 或 body 读取超时 |
| `411 Length Required` | PUT 缺少 `Content-Length` |
| `413 Payload Too Large` | PUT 正文超过 1 KiB |
| `415 Unsupported Media Type` | PUT 不是严格 `application/json` |
第一版不提供 provider/`use`、health check、自动 failover、`url-test`/`fallback`/`load-balance`、delay 测试、连接管理、配置修改、WebSocket 推送、Controller 版本协商或完整 Dashboard response。除 `GET /traffic`、`GET /group`、`GET /group/{name}`、`GET /proxies/{name}` 和 `PUT /proxies/{name}` 外,其他路径和方法均不属于公共协议。
Windows 的完整运行时和 Controller 位于独立 Session Host。App 通过回环 HTTP 直接访问实际 Running Session;组查询和切换不经过 `VCoreInvoke`、Windows bridge 或 Provider 控制管道,不携带 `instanceId`,也不占用 Invoke 命令锁。