Skip to content

Nfrp v0.6.0

Choose a tag to compare

@Nu0vo1212 Nu0vo1212 released this 18 Oct 12:58
· 29 commits to main since this release

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

完整修复记录

累计九轮安全审计修复 137 项,全部带回归测试。