Phantom 是一个高性能加密代理隧道,基于 Rust 构建,采用 core/ + server/ + client/ 三级 workspace 结构。核心设计目标:
- 数据面 100% Rust:macOS/Android 原生客户端通过 FFI 一次性传递 TUN fd,之后零跨语言开销
- 控制面-数据面契约:配置声明的每一项必须在数据面有对应实现
- 零拷贝传输:基于
BytesMut复用的帧协议,消除每帧堆分配 - QUIC 多路复用:Noise-over-QUIC(quinn-hyphae,100% Rust),连接级 Noise IK 握手一次,后续 stream 复用 QUIC 原生多路复用
- 智能分流:DNS 劫持 + 规则引擎 + 热重载,配置即行为
┌─────────────────────────────────────────────────────────────────────────────┐
│ 客户端 (Client) │
│ │
│ ┌─────────────┐ SOCKS5 ┌──────────────────┐ TCP/QUIC ┌──────────┐ │
│ │ 应用程序 │ ──────────▶│ Phantom Client │ ────────────▶│ Server │ │
│ │ (浏览器等) │ │ (SOCKS5 + TUN) │ Noise+AEAD │ │ │
│ └─────────────┘ └──────────────────┘ └──────────┘ │
│ │ │ │
│ │ TUN (透明代理) │ │
│ └────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────┴──────────────────────────────┐ │
│ │ TUN 数据面 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │ │
│ │ │ HotReload│ │RuleEngine│ │ DnsProxy │ │ SystemProxy │ │ │
│ │ │ (Arc<Mux>)│ │ (rules) │ │ (UDP:53) │ │ (networksetup) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └───────────────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────────────────┐ │ │
│ │ │TrafficSt │ │UdpProxy │ │ Metrics HTTP :9150 │ │ │
│ │ │ ats │ │ FlowTable│ │ (Prometheus /metrics) │ │ │
│ │ └──────────┘ └──────────┘ └──────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ 平台层: │
│ macOS: utun7 + SwiftUI 菜单栏 (FFI: phantom_macos_start/stop) │
│ Android: VpnService fd + Jetpack Compose (JNI: phantom_android_start) │
│ HarmonyOS: VpnExtensionAbility fd + ArkUI (NAPI) │
│ Linux 路由器: phantom0 + ip rule 策略路由 + iptables (--tun --gateway) │
│ CLI: phantom-cli (tokio main, SOCKS5 或 --tun) │
└─────────────────────────────────────────────────────────────────────────────┘
↓ TCP/QUIC
┌─────────────────────────────────────────────────────────────────────────────┐
│ 服务端 (Server) │
│ ┌──────────────────┐ TCP relay / UDP relay ┌──────────┐ │
│ │ Phantom Server │ ──────────────────────────────▶│ 目标站点 │ │
│ │ (Noise responder)│ │ │ │
│ └──────────────────┘ └──────────┘ │
│ │ │
│ Linux io_uring (optional) │
└─────────────────────────────────────────────────────────────────────────────┘
| Crate | 路径 | 职责 | 依赖 |
|---|---|---|---|
phantom-core |
core/ |
共享类型、配置、密码套件、帧协议、传输抽象、URI 解析、错误、常量、BufferPool | serde, toml, snow, quinn |
phantom-client |
client/ |
SOCKS5 代理、TUN 透明代理、规则引擎、DNS 劫持、UDP relay、流量统计、热重载、Linux 网关(gateway) |
phantom-*, tun, etherparse, ipnet, regex |
phantom-cli |
client/cli |
CLI 入口 (phantom client / server),client 支持 SOCKS5 / --tun / --tun --gateway,server 支持 auto / interactive / load 三种模式 |
phantom-client, phantom-server, phantom-core |
phantom-server |
server/ |
服务端连接处理、TCP relay、UDP relay、io_uring、bootstrap 模块(自举 / 交互 / URI 反向解析) |
phantom-*, quinn, tokio-uring(opt) |
phantom-e2e |
tests/ |
E2E 测试、HTTP/TCP/UDP echo、Mock 百度、网络模拟 | phantom-*, axum |
phantom-bench |
tests/bench/ |
性能基准测试 | phantom-*, divan |
本地监听端口同时讲 SOCKS5 与 HTTP:http_proxy::handle_inbound peek 首字节,0x05 走 SOCKS5,否则走 HTTP。
应用 → SOCKS5 CONNECT / UDP ASSOCIATE ┐
→ HTTP CONNECT / 绝对 URI GET/POST ├─► Phantom Client inbound listener
│ (首字节嗅探,可选 proxy_auth 认证)
▼
┌──────────────────┐
│ NoiseInitiator │ ──► TCP/QUIC connect
│ IK handshake │ ◄── Cipher negotiation
│ split_after_ │ (QUIC: 池化连接+
│ handshake() │ 明文分帧)
└──────────────────┘
│
┌─────────┴──────────┐
│ FrameWriter::syn() │ ──► encrypted SYN frame
│ FrameReader read │ ◄── ACK / RST
└────────────────────┘
│
┌─────────┴──────────┐
│ relay_socks5_tunnel│ TCP 双向 relay
│ udp_relay 管道 │ UDP: per-target 流表
└────────────────────┘
- HTTP CONNECT:隧道建立后回
200 Connection Established,之后原样 relay。 - 绝对 URI GET/POST:请求行重写为 origin form,剥除
Proxy-*头(凭据不出本地),强制Connection: close(避免 keep-alive 复用连接跳 host 时无法重定向)。 - UDP ASSOCIATE:绑定控制连接本地 IP 的 UDP socket,回复真实 BND.ADDR/PORT;FRAG≠0 丢弃,仅接受首个数据报来源地址,控制连接 EOF 即销毁关联;每个 target 一条隧道流(SYN 携带首包,免额外 RTT)。
系统流量 → TUN device (utun7 / VpnService fd)
│
▼
┌───────────────┐
│ TunProxy::run │
└───────────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
TCP SYN UDP:53 其他 UDP
│ │ │
▼ ▼ ▼
HotReload DnsProxy HotReload
(proxy_mode) (upstream (proxy_mode)
│ DNS) │
▼ │ ▼
RuleEngine │ RuleEngine
(Smart) │ (Smart → Direct/Proxy/Reject)
│ │ │
▼ ▼ ▼
Direct → direct TCP Direct → local UdpSocket
Proxy → SOCKS5 → Noise tunnel → Server
Reject → RST packet Proxy → Noise tunnel (UDP SYN frame) → Server
客户端 服务端
│ │
│──── SYN|UDP|DATA ─────────────────────▶ │ payload = [TargetAddr][datagram]
│◀─── ACK ────────────────────────────── │
│ │ UdpSocket::send_to(datagram, target)
│ │◀─ recv_from() ── 目标
│◀─── UDP|DATA ────────────────────────── │ payload = response datagram
│──── UDP|DATA ────────────────────────▶ │ send_to(datagram, target)
│──── UDP|DATA ────────────────────────▶ │ ...
│──── FIN ──────────────────────────────▶ │ 关闭 UdpSocket
原则:控制面(
config.rs+toml)声明的每个字段,数据面必须有实现。
| 配置字段 | 控制面 | 数据面实现 | 状态 |
|---|---|---|---|
client.mode |
ProxyMode 枚举 (proxy/direct/smart/auto) |
tun.rs HotReloadState.proxy_mode |
已实现 |
client.dns |
String |
dns.rs DnsProxy 拦截 UDP:53 |
已实现 |
client.cipher |
CipherPreference |
Noise 握手 CipherOffer | 已实现 |
servers[].cipher |
CipherPreference |
服务器级密码覆盖 | 已实现 |
servers[].protocol |
TransportProtocol |
socks5.rs 选择 TCP/QUIC 传输 | 已实现 |
servers[].psk |
String (base64) |
config.rs decode_psk() → Noise psk1;缺失则报错 |
已实现 |
rules.* |
RulesConfig |
rules.rs RuleEngine (7种规则+GeoIP) |
已实现 |
rules.geoip |
HashMap<String, RuleAction> |
maxminddb 查询 + 国家码匹配 | 已实现 |
client.dns(热更) |
String |
dns.rs DnsProxy::set_upstream() |
已实现 |
client.dns_direct |
String(逗号/空白分隔的优先级列表,单值兼容) |
dns.rs 直连解析器池(物理网卡,net_tune::protect_socket);parse_dns_addrs() 解析,DIRECT_ATTEMPT_TIMEOUT(900ms) 无应答即换下一台,连续 3 次超时冷却 60s,set_direct_upstreams() 热更整个池。默认 223.5.5.5:53,119.29.29.29:53,180.76.76.76:53 |
已实现 |
client.dns_ipv6_via_tunnel |
bool(默认 false) |
tun.rs::handle_dns_query + dns.rs::build_nodata_response():隧道内域名不下发 AAAA,避免 App 死磕出口不可达的 IPv6 地址;直连域名不受影响 |
已实现 |
client.metrics_listen |
String |
client/src/stats.rs serve_metrics(),SOCKS5/TUN 共享 |
已实现 |
client.listen |
String |
入站监听地址,0.0.0.0 即局域网共享;默认 127.0.0.1:1080 |
已实现 |
client.proxy_auth |
Option<ProxyAuthConfig> |
SOCKS5 RFC1929 + HTTP Basic(407 质询),恒定时间比较,凭据不转发上游 | 已实现 |
servers[](热更) |
Vec<ServerEntry> |
failover.rs FailoverManager::reload() |
已实现 |
failover.health_check_interval |
u64 |
failover.rs run_health_check_loop() |
已实现 |
failover.failover_threshold |
u32 |
连续失败 N 次后切换服务器 | 已实现 |
failover.graceful_migration |
bool |
false 时切服广播 migration epoch,在飞隧道主动断连;true(默认)旧服隧道自然排空 |
已实现 |
hello.timeout |
u64 |
client/src/hello.rs Hello-ACK 等待超时 |
已实现 |
hello.targets |
Vec<String> |
随 Hello 帧下发,服务端优先探测,缺省回落 verification_url → 内置目标 |
已实现 |
server.verification_url |
Option<String> |
server/src/handler.rs 自定义外网探测目标 |
已实现 |
performance.workers |
u32 |
server/bin.rs 自定义 tokio runtime | 已实现 |
performance.io_uring |
bool |
linux_ext.rs tokio_uring | 已实现 |
quic.congestion |
CongestionAlgorithm |
quinn 传输配置 | 已实现 |
quic.max_streams |
u32 |
quinn max_concurrent_bidi_streams |
已实现 |
quic.keep_alive_interval |
u64 |
quinn keep_alive_interval(秒,0=禁用) |
已实现 |
客户端 ──► 服务端: Noise IKpsk1 第一条消息 + CipherOffer
[version:1][count:1][cipher_ids:count]
客户端 ◄── 服务端: Noise 响应消息 + CipherAccept
[version:1][cipher_id:1]
- Pattern:
Noise_IKpsk1_25519_ChaChaPoly_SHA256 - 密码套件协商嵌入握手载荷,零额外 RTT
- 服务端提取客户端静态公钥进行白名单校验(空白名单=开放)
Noise 的 psk1 把 PSK token 放在第一条消息末尾(-> e, es, s, ss, psk),
MixKeyAndHash(psk) 在该消息的 payload 被封装之前执行。因此持有不同 PSK 的
responder 无法解密第一条消息,直接断开且不回应任何数据 —— 探测者看到的是
early eof,与端口上无服务无法区分。
psk2 曾被先尝试并否定:它把 PSK 混入第二条消息,所以 responder 会正常回应
一个“公钥正确但无 PSK”的探测,仅 initiator 侧能发现不匹配 —— 这泄露了
“此处有服务”的信号,恰好是 PSK 要阻止的事。回归测试见
core/src/crypto/noise.rs 的 handshake_fails_when_psk_differs 与
pattern_binds_the_psk_to_the_first_message。
PSK 是叠加在 ephemeral DH 之上的第二因子,不替代 DH,因此前向保密不丢; 双向静态公钥认证仍由 IK 提供。对称 PSK 本身抗量子,构成向后量子时代的过渡手段。
Wire format: [ver:1][stream_id:4 BE][flags:1][payload_len:2 BE][payload]
Flags:
SYN = 0x01 SYN + DATA = TCP/UDP 连接打开
FIN = 0x02 优雅关闭
RST = 0x04 中断
ACK = 0x08 确认
DATA = 0x10 数据帧
PING = 0x20 保活探测
PONG = 0x40 保活响应
UDP = 0x80 UDP 模式(与 SYN/DATA 组合)
UDP SYN payload: [TargetAddr encoded][datagram bytes]
Noise IK 握手完成
│
▼
dangerously_get_raw_split() → (k1, k2)
│ │
▼ ▼
HKDF-SHA256 HKDF-SHA256
info: "phantom-v2-{cipher}-c2s" info: "phantom-v2-{cipher}-s2c"
│ │
▼ ▼
c2s_key + nonce_prefix s2c_key + nonce_prefix
│ │
▼ ▼
SessionWriter (加密) SessionReader (解密)
QUIC 的加密层由 Noise(quinn-hyphae + RustCrypto 后端)取代 TLS——零证书、零 C 依赖:
QuicAuth { local_secret, remote_public, psk, cipher }
│
▼
Noise_IK_25519_{AESGCM,ChaChaPoly}_SHA256 + prologue = PSK
│(hyphae 不支持 pskN 修饰符;prologue 在首条消息密封前混入握手哈希,
│ 与 TCP 的 psk1 等效:PSK 错误 → 握手失败 → 服务端零回应)
▼
连接级认证完成(服务端经 peer_static_key() 提取客户端公钥做白名单校验)
│
▼
每条 bi-stream 直接跑帧协议(长度前缀明文分帧)——QUIC 本身已加密,
不再叠加 SessionReader/SessionWriter,消除了 QUIC over QUIC 的双层加密开销
注意:AEAD 由 Noise pattern 字符串固定(TLS 语境的套件协商不适用于 Noise),双端必须配置一致的 cipher=;ascon-128 在 QUIC 上不可用(RustCrypto 后端无 ASCON),指定时会在 endpoint 创建期明确报错。
启动 SOCKS5 / TUN 数据面之前,客户端会先通过一条独立的 Noise 会话完成一次 Hello/Hello-ACK 握手,验证 客户端 → 服务端 → 外网 完整链路可用,避免"本地代理已启动就显示已连接"的误导。
客户端 服务端
│ │
│──── TCP/QUIC connect ─────────────▶ │
│──── Noise 握手 ──────────────────▶ │(TCP:IKpsk1 独立握手;QUIC:Noise 内嵌于 QUIC 握手)
│◄─── Noise 握手响应 ─────────────── │
│ │
│──── DATA stream_id=0 ─────────────▶ │ payload = "PH/HELLO" + {nonce}
│ │ http_probe(captive.apple.com)
│◄─── DATA stream_id=0 ────────────── │ payload = "PH/HELLO_ACK" + {ok, message, ts}
│ │
│ ok=true → 打开本地 SOCKS5 / TUN │
│ ok=false → 报错,不进入数据面 │
设计要点:
- 复用已建立的 Noise 加密会话,Hello 帧本身也是加密的。
- 使用
stream_id = 0作为保留控制流,与 SOCKS5 relay 的stream_id >= 1不冲突。 - 不新增 FrameFlags,Hello 帧是普通的
DATA帧,通过 payload 前缀PH/HELLO/PH/HELLO_ACK识别,保持与旧版客户端的最大兼容(旧版服务端会把该帧当普通 SYN 处理并关闭连接,不会误判)。 - 服务端默认探测
http://captive.apple.com/hotspot-detect.html,失败时回退http://detectportal.firefox.com/success.txt;可通过server.toml的verification_url自定义。
| 平台 | TUN 创建 | FFI 入口 | 系统代理 | 打包方式 |
|---|---|---|---|---|
| macOS | tun::create_as_async("utun7") |
phantom_macos_start/stop |
networksetup SOCKS5 自动设置/恢复 | cdylib + SwiftUI |
| Android | VpnService fd → AsyncFd | phantom_android_start/stop |
VpnService 路由规则 | cdylib + Kotlin |
| HarmonyOS | VpnExtensionAbility fd → AsyncFd | NAPI 模块 | VpnExtensionAbility 路由 | cdylib + ArkTS |
| Linux 路由器 | TunDevice::create_with(TunSettings) |
N/A(直跑 CLI) | ip rule 策略路由 + iptables |
aarch64 + armv7 musl 静态二进制 |
| koolshare 插件 | 同上,由插件脚本拉起 | N/A(软件中心 ASP + sh) | 同上 | 软件中心离线包(双架构) |
| CLI | 同上(--tun 可选) |
phantom-cli main |
无 | 统一二进制 |
client/src/gateway.rs 把转发的 LAN 流量引入 TUN:
LAN 客户端 ──转发──▶ ip rule iif br0 ─▶ table 200 ─▶ default dev phantom0
│
优先级 9040:to <私有网段> lookup main(放行)
优先级 9050:iif br0 lookup 200(入隧道)
关键设计:按入向接口 iif 选路,而不是按源网段。路由器自身发起的流量
(包括 Phantom 到服务端的隧道连接)没有 iif,因此永不匹配 table 200,
从根源上消除了回环,无需为服务端 IP 或 WAN 网关做特例路由。
指令集的构造(GatewayConfig::plan / teardown_plan)与执行分离,因此可在
无 root、无真实网卡的情况下单测;Gateway 的 Drop 负责完整回滚。
路由器形态有两种把 LAN 流量送进隧道的方式,差别是判定发生在内核还是用户态:
| 模式 | 规则 | 数据流 | 直连流量 |
|---|---|---|---|
kernel-split(默认) |
ipset phantom_proxy + iptables -t mangle … MARK + ip rule fwmark 0x1/0x1;另单独标记 53 端口进隧道。标记与路由一律带掩码(--set-xmark 0x1/0x1、CONNMARK --nfmask/--ctmask 0x1、fwmark 0x1/0x1),因为固件 QoS 会往同一个 mark 写自己的位,无掩码匹配会只剩首个 SYN 进隧道。标记只作用于新建连接(--ctstate NEW + CONNMARK --save-mark),已建立连接靠 CONNMARK --restore-mark 走回它创建时的路 |
命中白名单/53 的新连接才进 phantom0 |
不进 TUN,留在 main 表,走内核(Broadcom 上还有 Runner/Flow Cache 硬件加速) |
relay(兼容) |
ip rule iif br0 lookup 200 |
全部 LAN 转发流量进 phantom0,逐连接在用户态判定 |
也进 TUN,由 tcp_direct_relay_task 明文转发 |
为什么默认 kernel-split:实测同一台 RT-AX86U Pro,本机内核路径 8 路并行 420 Mbps,
而全部流量经 TUN 的用户态 relay 只有 114–140 Mbps 且吃 1.2 核,/proc/fcache/nflist
的 125 条流硬件命中全为 0。家用场景里直连流量占绝大多数(实测 17593 条连接里 15263
条是 direct),把它们从用户态摘出去,才不会"晚上人多时连非 VPN 的网站都慢"。
白名单 IP 的三个来源(不预热、不写死单 IP):DNS 实时学习(隧道解析白名单域名后把
A 记录批量写入 ipset,1s 合并、30 min 超时)、内置 CIDR(client/data/proxy_cidrs.txt
= Telegram 段 + Google 官方 goog.json 段,覆盖 YouTube/googlevideo)、以及默认开启的
加密 DNS 拦截(DoT/DoH,保证学习链不断)。缺 ipset/xt_set 时自动回退 relay。
直连是否被加密,可用数据自证:phantom_direct_bytes_* 与 phantom_tunnel_bytes_*
是两套独立计数,只有后者进 AEAD 与服务端。
为什么标记要带 conntrack:白名单地址是会话中途由 DNS 学到的。如果规则逐包
匹配 ipset,一条本来走直连的连接会被逐包拽进 TUN,而用户态从没见过它的 SYN,
只能回 RST(phantom_tun_unknown_flow_rst_total 会一路涨)—— 现象是"视频看着看着
断一下再自己重连"。改成「只标记新连接 + 把 mark 存进 conntrack + 已建立连接恢复
自己的 mark」之后,在途连接不再被打断,新连接照旧按白名单进隧道。
固件缺 xt_conntrack/CONNMARK 时自动退回逐包标记,并由 phantom_gateway_connmark
= 0 暴露出来。
client/koolshare/ 是路由器形态的控制面外壳,不引入任何新的数据面代码:
Module_phantom.asp ──GET/POST /_api/──▶ /koolshare/scripts/phantom_config.sh
│ │ 生成 client.toml + proxy_domains.txt
│ ▼
└──GET /_temp/phantom_status.txt── phantom_status.sh(2s 采样 /metrics)
phantom client -c … --server … --tun --gateway
| 约定 | 值 |
|---|---|
| 配置存储 | dbus(skipd);无软件中心时降级为 /jffs/phantom/etc/phantom.conf |
| 服务器来源 | --server <URI> 传入,覆盖 TOML 的 servers 段,避免两处不一致 |
| 白名单 | 写 proxy_domains.txt,用 PHANTOM_PROXY_DOMAINS 注入(dbus 不支持多行) |
| 指标 | 复用 client.metrics_listen(默认 127.0.0.1:9150),两次采样求差得速率 |
| 二进制 | 包内同时带 phantom-aarch64 与 phantom-armv7,install.sh 按 uname -m 选 |
| 平台检测 | /koolshare + /usr/bin/skipd + 内核 ≥ 4.1,否则拒绝安装或降级 /jffs |
| 软件中心调用 | POST /_api/ {id, method, params, fields} → httpdb 先落 dbus,再执行 scripts/<method> <id> <params...>;脚本必须回包 127.0.0.1:3030/_resp/<id> |
| 状态与日志通道 | 物理文件 /tmp/upload/phantom_{status,log}.txt,页面经 httpdb 的 /_temp/<name> 读(docroot 下的 .txt 一律 404) |
| 开机 / nat 钩子 | init.d/S98phantom.sh start(wan-start)、init.d/N98phantom.sh start_nat(iptables 被重建时补规则) |
细节与排障见 client/koolshare/README.md。
启动隧道后自动执行:
networksetup -setsocksfirewallproxy "Wi-Fi" 127.0.0.1 11080
networksetup -setsocksfirewallproxystate "Wi-Fi" on停止时恢复之前的代理设置(保存/恢复机制)。
菜单栏分段选择器:
- Global →
client.mode = "proxy"(所有流量走隧道) - Auto →
client.mode = "smart"(规则引擎分流) - Direct →
client.mode = "direct"(全局直连)
phantom://<base64_public_key>@<host>:<port>[?<query>][#<name>]
Query 参数:cipher=, proto=, congestion=
CLI 支持 --server URI 与 --config TOML 组合使用。
TunProxy 持有 Arc<Mutex<HotReloadState>> 共享状态:
struct HotReloadState {
proxy_mode: ProxyMode,
rule_engine: Option<Arc<RuleEngine>>,
server: Option<ServerEntry>, // 隧道化 UDP 流使用的服务器
}后台任务每 5 秒轮询配置文件 mtime,变更时由 apply_reload() 统一下发:
| 目标 | 载体 | 行为 |
|---|---|---|
proxy_mode |
HotReloadState |
直接替换 |
rule_engine |
HotReloadState |
重建;解析失败时保留旧引擎,避免退化为“全代理” |
server |
HotReloadState |
新 UDP 隧道流指向新首位服务器 |
| DNS 上游 | DnsProxy.upstream(RwLock) |
重定向;不重建 socket,飞行中的 pending 查询仍能回流 |
| 服务器池 | FailoverManager.pool(RwLock) |
整体替换;当前活跃服务器存活则保持不动 |
| failover 调优 | FailoverManager.tuning |
健康检查间隔变更时重建 ticker |
handle_tcp / handle_udp 每次查询时克隆 Arc 快照,不持有锁跳 await。
FailoverManager::select_server() 返回拥有权快照而非引用,这是池可被热替换的前提。
飞行中的健康探测结果会校验槽位身份,避免重载后误伤新池。
TrafficStats 使用 AtomicU64 计数器,零锁开销:
| 计数器 | 说明 |
|---|---|
tcp_bytes_up/down |
TCP 上下行字节数 |
udp_bytes_up/down |
UDP 上下行字节数 |
tcp_connections |
TCP 连接总数 |
udp_datagrams_up/down |
UDP 数据报总数 |
暴露为 Prometheus 端点(client.metrics_listen 可配,默认 127.0.0.1:9150/metrics);SOCKS5 与 TUN 运行时共享同一 TrafficStats 实例与同一个 HTTP 端点。
| 模块 | 限制 | 优先级 |
|---|---|---|
| SOCKS5 UDP | UDP ASSOCIATE 未实现(TUN UDP proxy 已覆盖主场景) | Deferred |
| 网关 IPv6 | gateway.rs 只下发 IPv4 策略路由,ip -6 rule 未实现 |
P3 |
| 网关规则寿命 | Asuswrt 重建 NAT 时会清掉 iptables 条目,现靠 nat-start 钩子重启进程补回 |
P3 |
| 多用户/限速 | 无 | P3 |
| ACME | 无自动证书申请 | P3 |
| 层级 | 测试数 | 覆盖范围 |
|---|---|---|
| L0 单元测试 | 140 | 帧协议边界、URI 构建/解析、规则引擎查询、DNS 解析与上游热切换、failover 池热重载/探测切换/migration epoch 广播、stats、hello.targets 解析过滤、QUIC max_streams 行为上限、decode_udp_syn、bootstrap |
| L0 CLI 单测 | 6 | --tun-addr CIDR 解析、clap 参数依赖关系 |
| L0 网关单测(Linux) | 12 | ip rule / iptables 指令集、优先级排序、回滚对称性、保留路由表校验 |
| L1 配置生效 | 9 | 白名单开放/限制、密码协商矩阵 |
| L1 模块交互 | 15 | DNS→规则、规则→路由、stats→Prometheus |
| L1 全链路 | 7 | TCP echo/大数据/并发、UDP relay、HTTP 隧道 |
| L1 QUIC 复用 | 2 | 8 隧道共享 1 连接无串流、错误 PSK 零握手(quic_mux) |
| L1 本地入站 | 9 | UDP ASSOCIATE echo(TCP/QUIC)、多 target 流表、FRAG/陌生源丢弃、HTTP CONNECT、绝对 URI 重写、同端口嗅探分流、RFC1929/Basic 认证 |
| L1 真实场景 | 4 | Mock 百度、真百度(ignored) |
| L1 性能 | 17 | 吞吐量、延迟、并发(10 ignored) |
| L2 系统 | 11 | CLI 自举、端口递增 fallback、keygen 已删除、version、密钥复用、TUN/网关参数校验与平台限制 |