Repository navigation
Nfrp v0.6.0
Nfrp v0.6.0
Rust 实现的内网穿透工具,完整兼容官方 frp 协议(wire protocol v1 / v2,与 frp 0.71.0 实测互通)
本次为测试健壮性版本:修复了在 Linux 上会导致 cargo test 失败的一批跨平台测试缺陷,
其中最有价值的一项是照官方 frp 的做法重构了 HTTP 管理界面的测试结构。
产品行为零变更,可直接替换二进制升级。
主要改动
1. HTTP 管理界面测试改为「纯逻辑」(照官方 frp 的做法)
原先测 client/src/web.rs 的辅助函数 handle_over_socket 会开真实 TCP socket、
tokio::spawn 服务端、客户端再 connect 过去 —— 这在 Linux 上自己造出了一个竞态:
| 跑法 | 改前 | 改后 |
|---|---|---|
web::tests 连跑 8 次(云端 Linux) |
0/6 通过 | ✅ 8/8 通过 |
| 4 个关键测试单独跑 | 全红,各卡满 5.00s | ✅ 全绿,各 0.00s |
官方怎么做的(frp-0.71.0/server/http/controller_v2_test.go):
resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req) // 直接调 handler,内存 recorder更彻底的写法(pkg/plugin/client/http_proxy_test.go)用 net.Pipe(),纯内存管道。
我们的改法:把 handle 里与 socket 无关的判定逻辑抽成纯函数 decide(),
handle 只剩「读请求 → decide → 发送」三步;测试直接调 decide()。
判定顺序与内容逐字未变:
/api/healthz 免鉴权 → Basic Auth 401 → Origin 403 → CSRF 头 403 → route。
2. logfile 测试用了 Windows 专有的错误路径
原测试用 Z:/nfrp-no-such-drive/x.log 构造"打不开的路径"。
Windows 上盘符不存在所以打不开;Linux 上 Z:/... 是合法的相对路径
(名为 Z: 的目录),create_dir_all 会真的建目录成功 ⇒ 断言失败。
改成「父路径是一个已存在的文件」—— 两个平台都必然 ENOTDIR。
3. tun_linux 测试缺 tokio runtime
#[test] 下调用 Tun::create,在有权限环境(root + /dev/net/tun)会走到
AsyncFd::new,没有 reactor 时 panic(no reactor running)。
改为 #[tokio::test]。
4. dial_server 测试的 accept 只等一次
tokio::spawn(async move { let _ = l.accept().await; }); // ← 只 accept 一次测试会拨两次号,第二次时 listener 已 drop ⇒ Connection refused(Linux 上稳定复现)。
改成常驻 accept 循环(与产品 web::run / serve.rs 一致)。
5. 附带:服务端 task 的 panic 不再被吞掉
let _ = tokio::time::timeout(2s, srv).await; 会把 JoinError(task panic)丢弃,
导致测试只报"响应为空"、看不到任何线索。现在区分「task 异常 / 超时 / 读取超时」并给出明确信息。
质量门
| 项 | 结果 |
|---|---|
cargo test --workspace(本地串行) |
✅ 751 通过 0 失败 |
cargo fmt --all -- --check |
✅ 干净 |
cargo clippy --workspace --all-targets -D warnings(Windows) |
✅ 0 告警 |
cargo clippy(Linux 云端,覆盖 Windows 编不到的代码) |
✅ 0 告警 |
cargo audit |
✅ exit 0 零漏洞(184 crates) |
| 端到端冒烟 | ✅ 28 / 28 |
云端 web::tests × 8 |
✅ 8/8 |
云端 logfile / tun_linux / dial_server |
✅ 12 / 3 / 3-of-3 |
兼容性
- 无配置文件变更
- 无协议变更
- 无行为变更(
decide只是把handle里的判定逻辑原样搬出) frps -v/frpc -v仍输出裸0.71.0,与官方 frpc 0.71.0 互通不变- 直接替换二进制即可升级
未解决(如实记录)
云端 Linux 上仍有 6 个测试偶发失败,判定为「真实 socket 依赖 + 环境敏感」:
| 测试 | 通过率(云端) |
|---|---|
plugin_bridge::端到端_http2http_… |
3/5 |
plugin_bridge::端到端_http2https_… |
1/5 |
plugin_bridge::端到端_https2https_… |
4/5 |
plugin_bridge::上游悬挂时…释放上游连接 |
3/5 |
p2p::full_p2p_over_rendezvous |
8/10 |
p2p::fake_provider_without_secret_is_rejected |
偶发 |
这些测试必须依赖真实网络:dial_upstream() 硬编码 TcpStream::connect,
p2p 打洞本质就是 UDP socket 交互 —— 官方 net.Pipe() 的思路套不上。
它们在 Windows 与本地 Linux 上稳定通过。
尝试过并失败的方案(都已回滚):mini_rendezvous 改常驻循环(A/B 对照无改善)、
全部改 multi_thread runtime、加 yield_now()。
另有一条只记录、未下结论的产品观察:p2p 打洞对 window(端口预测)参数敏感 ——
window=4 时 QUIC 8/10、KCP 3/10;window=0 时两者均 10/10。
资产
| 文件 | 说明 |
|---|---|
nfrp-windows-amd64.zip |
Windows amd64(frps.exe / frpc.exe + 示例配置 + 文档) |
nfrp-linux-amd64.tar.gz |
Linux amd64 |
nfrp-linux-arm64.tar.gz |
Linux arm64(全静态) |
nfrp-android-v0.6.0.zip |
Android aarch64 + x86_64 |
SHA256SUMS.txt |
各包 sha256 |
SHA256SUMS.txt.sig |
Ed25519 分离签名 |
release.pub |
验签公钥(指纹 763896b5d759c6cbe1f208c3c35c9f71) |
校验方式
python scripts/release.py verify --dir .或用 OpenSSL 独立复验:
python - <<'PY'
import pathlib
hexline = lambda p: [l for l in pathlib.Path(p).read_text().splitlines()
if l and not l.startswith('#')][0].strip()
pathlib.Path('release.pub.der').write_bytes(
bytes.fromhex('302a300506032b6570032100' + hexline('release.pub')))
pathlib.Path('sums.sig.bin').write_bytes(
bytes.fromhex(hexline('SHA256SUMS.txt.sig')))
PY
openssl pkeyutl -verify -pubin -inkey release.pub.der -keyform DER -rawin \
-in SHA256SUMS.txt -sigfile sums.sig.bin
# 期望输出:Signature Verified Successfully完整修复记录
SECURITY-FIXES-ROUND11.md—— 本轮(测试健壮性)SECURITY-FIXES-ROUND10.md—— 第十轮(全量复扫 + 体积性能实验)SECURITY-FIXES-ROUND9.md—— 第九轮(ACL 三条缺口 / KCP 会话表)SECURITY-FIXES.md—— 第一~三轮汇总SECURITY-FIXES-ROUND4..8.md—— 第四~八轮
累计九轮安全审计修复 137 项,全部带回归测试。