FilesCodeBox 生态的 P2P 联邦注册中心:让任意多个 FilesCodeBox 节点(server / fnos / desktop)互相发现,口令分享跨站可达——文件始终从源节点直出,注册中心不落盘、不见明文。
The federated registry for FilesCodeBox: nodes register, passcodes resolve across sites, files flow directly from the source node — the registry stores no files and never sees plaintext passcodes.
| 能力 | 说明 | 状态 |
|---|---|---|
| 节点注册与心跳 | Ed25519 签名租约(node_id 即公钥),TTL 到期自动清扫 | ✅ v0.1 |
| 口令联邦路由 | SHA-256(口令) → 源节点,取件方直连源节点、源节点本地校验口令,零跨节点信任 |
✅ v0.1 |
| 信令信道 | WS 同口令双方配对 + 不透明握手帧转发(节点签名准入,服务端零知识) | ✅ v0.2 |
| 设备直传 | PAKE → 加密候选交换 → UDP 同时开洞 → 失败走加密中继;AEAD 传输+断点续传+sha256 校验 | ✅ v0.3(p2pc 参考客户端,六平台二进制随 Release 发布) |
| 安全加固 | wire AEAD 跨方向 nonce 重用修复、注册 token 恒时比较、注册中心容量治理、中继每 IP 限流、信令会话存活检查、HKDF 密钥派生 | ✅ v0.4 |
非目标:内容 DHT 去中心化、离线传输、文件中转存储。
docker run -d --name fcb-p2p -p 12346:12346 \
-e FCB_P2P_ADMIN_PASSWORD=$(openssl rand -hex 16) \
ghcr.io/filescodebox/p2p:latest源码构建:
make build && ./bin/p2pd # 服务端:12346(HTTP/WS/反射器)+12347(中继,默认关)
make smoke # 冒烟:注册/公告/解析/信令/直传回环全流程# 发送方(自动生成口令 XXXX-XXXX-XXXX 并等待对方取走)
p2pc send 报告.pdf --registry http://p2p.example.com:12346
# 接收方(输入同一口令,文件直连下载到当前目录)
p2pc recv XXXX-XXXX-XXXX --registry http://p2p.example.com:12346流程:节点注册+口令公告 → WS 信令配对(接收方与 resolve 源身份交叉核对)→ PAKE(口令派生会话密钥)→ 候选地址交换(PAKE 派生密钥加密)→ UDP 同时开洞(反射器提供公网映射;成功走 QUIC 指纹钉定 mTLS 直连)→ 失败回落加密中继(12347,令牌配对+限速)→ AEAD 消息传输(meta/ready/chunk/final,断点续传)→ sha256 全量校验。服务端全程只见密文。
优先级:FCB_P2P_* 环境变量 > 配置文件(--config / CONFIG_PATH)> 内置默认。完整样例见 configs/config.yaml。
| 环境变量 | 默认 | 说明 |
|---|---|---|
FCB_P2P_SERVER_PORT |
12346 |
HTTP/信令端口 |
FCB_P2P_SERVER_BEHIND_PROXY |
false |
反代部署置 true(取 X-Forwarded-For 参与限流) |
FCB_P2P_REGISTRATION_MODE |
open |
open 开放注册 / token 邀请制 |
FCB_P2P_REGISTRATION_TOKEN |
— | token 模式的共享注册密钥 |
FCB_P2P_ADMIN_PASSWORD |
— | 留空 = 管理 API 整体禁用;生产必须注入 |
FCB_P2P_ANNOUNCE_MAX_PER_NODE |
1000 |
单节点公告配额 |
FCB_P2P_ANNOUNCE_MAX_TTL |
168h |
公告最大存活 |
FCB_P2P_SIGNALING_ENABLED |
true |
信令信道开关(关闭仅影响直传配对) |
FCB_P2P_SIGNALING_SESSION_TTL |
10m |
信令会话最长生命周期 |
FCB_P2P_SIGNALING_IDLE_TIMEOUT |
2m |
连接空闲上限 |
FCB_P2P_SIGNALING_MAX_PER_NODE |
8 |
单节点并发信令会话上限 |
FCB_P2P_REFLECTOR_ENABLED |
true |
UDP 地址反射器(打洞前提) |
FCB_P2P_RELAY_ENABLED |
false |
加密中继开关(打洞失败兜底) |
FCB_P2P_RELAY_PORT |
12347 |
中继 TCP 端口 |
FCB_P2P_RELAY_MBPS |
10 |
单信道带宽上限(Mbps,0=不限) |
FCB_P2P_LOG_LEVEL |
info |
debug / info / warn / error |
| 方法 路径 | 说明 |
|---|---|
POST /v1/nodes/register |
注册/续租(/v1/nodes/heartbeat 同语义别名) |
DELETE /v1/nodes/{id} |
注销并级联撤公告 |
GET /v1/nodes/{id} |
节点公开信息 |
POST /v1/announces |
宣告 code_hash → 本节点(先到先得,冲突 409) |
DELETE /v1/announces/{hash} |
撤销(仅宣告节点) |
GET /v1/resolve/{hash} |
解析 → {node_id, url, ...};未接入 404 |
WS /v1/channel/{hash} |
信令信道(M3):同 hash 双方配对,转发不透明握手帧(见下节契约) |
GET /health · GET /metrics |
健康检查(显式 GET)/ Prometheus 指标 |
GET/DELETE /v1/admin/* |
统计/列表/强制下线(Bearer 管理口令) |
所有写操作带 Ed25519 签名(base64 std),负载为字段按序 "\n" 连接,签名公钥即 node_id:
注册/心跳: node_id | url | name | version | caps(逗号连接) | ttl_seconds | nonce | ts
公告: node_id | code_hash | expires_at | size_hint | ts
注销节点: node_id | ts
撤销公告: node_id | code_hash | ts
ts 为 unix 秒,允许 ±300s 偏移。参考实现见 scripts/smokegen(生态侧已由 core federation 域服务封装)。
口令即房间号:发送方设备与接收方以同一口令的 SHA-256 接入 WS /v1/channel/{code_hash},服务端按到达顺序配对(首个收 waiting,第二个配对成功双方各收 paired),之后在两 peer 间转发不透明帧。服务端职责止步于配对+转发+治理;PAKE/打洞/传输全部在对等端完成。
准入(hello 帧,接入后 10s 内必须发出):
{"type":"hello","node_id":"<hex>","ts":1700000000,"sig":"<base64 std>"}签名负载逐字节为 "channel-join\n<node_id>\n<code_hash>\n<ts>"(Ed25519,私钥即节点身份密钥;node_id 必须处于有效租约内)。匿名者与未注册节点进不了门。
服务端帧:
| 帧 | 说明 |
|---|---|
{"type":"waiting"} |
首个接入者:等待对方 |
{"type":"paired","peer_id":"<对方 node_id>"} |
配对成功(双方各收一份;接收方必须与 resolve 返回的源 node_id 交叉核对,防换人) |
{"type":"data","from":"<发送方 node_id>","payload":"<base64>"} |
转发的握手帧 |
{"type":"peer_left"} |
对方断开 |
{"type":"error","message":"…"} |
协议违规/会话超时 |
关闭码:4001 hello 缺失或非法 · 4002 信道占用或超配额 · 4003 未授权(签名/身份/租约)。
客户端义务(安全契约,p2pc 已全部实现):
- 配对后先跑 PAKE(p2pc 用 schollz/pake SIEC 曲线 2 消息流),data 帧里传不透明 PAKE 消息——服务端可见但无法推导密钥;
- PAKE 完成后再传候选地址等敏感信息,且必须用 PAKE 派生密钥加密——明文候选=可被注入劫持;
- 打洞:反射器取公网映射 + LAN 候选,UDP 同时开洞(探针须可解密=身份确认);成功走 QUIC(会话级证书指纹钉定 mTLS)直连;
- 失败走中继:令牌=PAKE 派生凭据,传输首帧 AEAD 认证(令牌被窃仅构成 DoS);
- 响应服务端 ping(主流 WS 库默认自动回 pong;pong 与数据帧均续空闲期)。
服务端治理(均可配置):data 帧负载 ≤16KB · 会话 TTL 10 分钟 · 连接空闲 2 分钟 · 单节点并发会话 8 · 全局 1024;已满信道拒绝第三人(4002)。
- 注册中心不接触明文口令:公告只存
SHA-256(口令);解析方须持有口令才能计算哈希查询。 - 离线枚举防线在宣布侧:生态客户端(core)只对熵 ≥40bit 的口令发联邦公告,短数字取件码不出站;解析接口按 IP 严格限流。
- 身份不可冒名:node_id 即 Ed25519 公钥,全部写操作验签;管理端可强制下线作恶节点。
- 限流分层:读/写/管理路径独立令牌桶,超限 429。
- 信令信道准入:WS 接入须持节点 Ed25519 签名(channel-join 负载)+ 有效租约;帧限 16KB、会话 TTL/空闲上限、单节点与全局配额——不存在匿名可占的房间。
- 中继(M3)默认关闭,开启后仅 PAKE 会话凭据放行且限带宽——不存在可被滥用的开放代理。
| 仓库 | 角色 |
|---|---|
| filescodebox | 装配 hub:go.work + 文档 + 部署清单 |
| contracts · core | 契约层 · 业务核心库(federation 域服务对接本服务) |
| server · frontend | 独立部署壳 · Web 前端 |
| fnos · desktop | 飞牛 fnOS 适配 · 桌面客户端(M3 直传对等端) |
| charts | Kubernetes Helm Chart |
依赖方向:server / fnos / frontend → core → contracts;p2p 为叶子仓,业务链零生态依赖(stdlib net/http,无 Hertz,CI 守卫强制),不在依赖链上。唯一允许的地基层依赖是 kit(共享工具库,本身零生态依赖,依赖方向恒为 p2p → kit 单向无环;2026-10-05 起按键限流器采用 kit/ratelimit)。
Apache-2.0 — Copyright 2026 FilesCodeBox。本项目为独立实现,未移植 vastsa/FileCodeBox(LGPL-3.0)或 schollz/croc(MIT,仅以依赖方式参考其 PAKE 思路)的源码。