对应源文件 easytier-cf-relay-release/docs/部署手册.md · 在 GitHub 查看原文
阅读模式

EasyTier Cloudflare Relay — 部署手册

在 Cloudflare Workers + Durable Objects 上部署 EasyTier 自建 WebSocket 节点(纯 P2P 优先模式)。 已用 easytier-core 2.6.4 官方客户端实测验证:握手、路由同步、双节点互见、数据中继全部通过。


1. 架构概览

EasyTier 客户端 (任意平台)
        │  wss://et.example.com/  (标准 WebSocket + EasyTier 二进制协议)
        ▼
Cloudflare Edge (Worker 入口 src/index.js)
        │  按 URL/Header/Query 选择房间 → 转发到 Durable Object
        ▼
RelayRoom Durable Object (src/room.js)
        │  握手鉴权 → 路由同步(OSPF) → 包转发
        ▼
同一网络的其他客户端(经中继兜底;P2P 打洞成功后直连,不再经过本节点)

纯 P2P 模式:节点在路由协议中宣告 avoid_relay_data(OSPF 大代价边), 客户端之间优先直连;只有 P2P 打洞失败的流量才走中继兜底(可通过 RELAY_DATA=0 完全关闭数据面)。

2. 前置条件

项目 要求
Cloudflare 账号 免费计划即可(SQLite 后端 Durable Objects 已对免费计划开放)
Node.js ≥ 18(推荐 20+)
Wrangler v4.x(npm i -g wrangler 或使用项目内 npx)
自定义域名 可选;域名需托管在 Cloudflare(用 Workers 默认 *.workers.dev 域名则不需要)

3. 快速部署(5 分钟)

# 1. 进入项目目录
cd easytier-cf-relay

# 2. 安装依赖
npm install

# 3. 登录 Cloudflare(浏览器授权)
npx wrangler login

# 4. 部署
npx wrangler deploy

# 5. 验证(把 <name> 替换为 wrangler 输出的 Worker 名)
curl https://easytier-cf-relay.<你的子域>.workers.dev/health
# 期望: {"ok":true}

客户端即可用 wss://easytier-cf-relay.<你的子域>.workers.dev/ 作为节点地址。

可选:启用 KV 审计(记录 + 黑名单)——创建 KV namespace 并在 wrangler.toml 启用 AUDIT_KV 绑定(见 5.6 节)。不配置时功能自动降级为仅 DO storage 存储, 管理页功能不受影响。

本地验证(可选,无需账号):

npx wrangler dev --port 8787
# 另一个终端:完整协议测试(44 项)
node test/e2e_test.mjs http://127.0.0.1:8787

本地 wrangler dev 会自动读取 .dev.vars(已 gitignore,不随 deploy 上传), 其中预置了 E2E 测试所需的 METRICS_PATH / ADMIN_PATH 等 token。

4. 绑定自定义域名

方式 A:Custom Domain(推荐)

域名已托管在 Cloudflare 时,在 wrangler.toml 追加:

routes = [
  { pattern = "et.example.com", custom_domain = true }
]

重新 npx wrangler deploy。Cloudflare 会自动:

  • 创建 DNS 记录
  • 签发并轮换 TLS 证书(免费)

方式 B:Route + 手动 DNS

routes = [
  { pattern = "et.example.com/*", zone_name = "example.com" }
]

并在 Cloudflare DNS 面板为 et.example.com 添加任意 A/AAAA 记录(如 192.0.2.1,流量走 Worker 不回源)。

客户端 URL 格式

wss://et.example.com/

路径任意(/、/ws、/easytier 均可),与官方服务端行为一致——因为部分客户端 (含官方 GUI)会把用户配置的路径原样带到 WebSocket 升级请求里。

5. 配置参考

所有配置通过 wrangler.toml 的 [vars](或 wrangler secret)设置,改完重新 deploy 生效。

5.1 基础配置

变量 默认值 说明
SERVER_PEER_ID 10000001 服务端虚拟 Peer ID。避免与客户端随机 ID 冲突(客户端 ID 为大随机数)
SERVER_NETWORK_NAME public_server 握手响应中宣告的网络名。与客户端网络名不同 ⇒ 官方客户端按"外部网络中继"处理(行为与官方公共服务器一致)
SERVER_HOSTNAME easytier-cf-relay 节点主机名(路由表中显示)
SERVER_VERSION_STR easytier-cf-relay/1.4.0 节点版本串(easytier-cli peer 可见)

5.2 P2P 与转发行为

变量 默认值 说明
AVOID_RELAY_DATA 1 纯 P2P 模式:在 feature_flag 中宣告 avoid_relay_data,OSPF 给本节点边赋大代价,客户端优先直连
RELAY_DATA 1 P2P 失败时的兜底:1 转发 Data/KCP 数据包;0 丢弃(严格纯 P2P,只保留控制面)

5.3 容量与超时

变量 默认值 说明
MAX_PEERS_PER_ROOM 64 单房间最大 peer 数(含所有网络分组)
MAX_CONNS_PER_IP 6 单 IP 并发连接上限(v1.4.1 事前限流):同一 IP 的开放连接数达到上限后,新升级请求返回 429 + Retry-After。DO 升级层权威检查;同 NAT 多节点共享出口 IP 时按需调大;0 = 关闭。前提:与边缘 IP 黑名单一样,依赖 Cloudflare 边缘注入的 CF-Connecting-IP——非 CF 前置的直连链路或本地 dev 下该头缺失时自动跳过(DO 层黑名单仍生效)
MSG_RATE_LIMIT_PER_SEC 50 单连接消息速率上限(v1.5 事前限流):每连接每秒最大消息数,超限先告警、持续超限断开连接(4008 rate limited)并计入总览「速率限流」计数。EasyTier 心跳 8s 1 个、路由同步爆发每秒数十个,默认 50 宽裕;0 = 关闭
MAX_PEERS_PER_GROUP 32 每分组节点数上限(v1.5 事前限流):单个网络分组的在线节点数达到上限后,该网络的新节点握手被拒(4030 group full)并计入总览「分组限流」计数,其它网络不受影响;同 peerId 重连顶替不受限;0 = 不限
MAX_MESSAGE_BYTES 131072 单条 WS 消息上限(128 KiB,远大于 EasyTier MTU 1380 与路由同步包)
MAX_SYNC_ITEMS 256 资源滥用防线:单帧路由同步条目数上限,超出整帧拒绝(4009);0 = 关闭。默认 = 房间上限 64 的 4× 余量
MAX_ROUTE_INFO_BYTES 768 单条 RoutePeerInfo 原始字节上限,超出丢弃该条(防巨型条目类状态膨胀);0 = 关闭
MAX_ROUTES_PER_GROUP 128 每分组路由条目总数上限(transit/幽灵总量闸门,兼作出站全量推送体积上界);0 = 关闭。与 MAX_ROUTE_INFO_BYTES 乘积须 < MAX_MESSAGE_BYTES(不自洽时启动日志 warn)
MAX_DIRECT_PEERS_REPORT 64 PeerCenter 单上报者直连表条目上限,超出截断;0 = 关闭
FULL_RESYNC_COOLDOWN_MS 1000 全量重推最小间隔(会话重置 / GetGlobalPeerMap 全量响应共用,每连接);0 = 关闭
MAX_NETWORK_NAME_BYTES 128 握手网络名长度上限(空名/超长名拒绝,close 4002);0 = 关闭
HANDSHAKE_TIMEOUT_MS 15000 连接后未完成握手的清理超时(防幽灵连接第一道防线)
PEER_IDLE_TIMEOUT_MS 75000 空闲超时。必须 > 客户端 ping 最大间隔 32s(防幽灵节点第二道防线)
SERVER_PING_IDLE_MS 40000 服务端主动 ping 阈值,探测半开连接(第三道防线)
ROUTE_INFO_UNREACHABLE_MS 90000 路由条目老化(防线五):条目未刷新且节点不可达(无连接且 PeerCenter 无可达路径)超过该时长 → 删除。对齐官方 clear_expired_peer 语义
ROUTE_INFO_TTL_MS 3660000 路由条目老化(防线五):条目未刷新超过该时长 → 无条件删除(61 分钟)
GROUP_AUTO_DELETE_MS 60000 空分组自动删除宽限(防线六):分组内在线节点归零后持续该时长仍为空 → 整组删除(含路由条目与摘要注册);0 = 关闭
SWEEP_INTERVAL_MS 15000 alarm 清扫周期上限(老化与空分组删除也在此周期内执行)。有连接时按自适应排程取「该周期」与「最近 socket 事件时刻」的较小值
SWEEP_IDLE_INTERVAL_MS 300000 空闲退避(v1.4.0):房间无连接且无脏数据时 alarm 退避到该间隔(空房间消耗从 5,760 请求/天降至 288);0 = 关闭

5.4 安全

变量 默认值 说明
STRICT_DIGEST 1 1:同一网络名出现不同 network_secret 摘要 ⇒ 拒绝(防网络名抢占/嗅探);0:隔离为不同分组
TARPIT_UNKNOWN 1 未知路径 tar pit(v1.4.1):非 WS 升级请求访问未知路径时挂起不响应——不返回 404、不泄露行为指纹,让路径扫描器空等;EasyTier 客户端只走升级、保留路径(health/favicon/metrics/admin)各有响应,均不受影响;0 = 关闭(恢复 404)
NETWORK_SECRETS (空) 可选 JSON 映射 {"网络名":"密钥"}。配置后服务端用 SipHash-1-3(与官方同一算法)校验客户端摘要,密钥不匹配直接拒绝
# 示例:校验两个网络的密钥
[vars]
NETWORK_SECRETS = "{\"myteam\":\"s3cret!\",\"family\":\"home123\"}"

不配置 NETWORK_SECRETS 时,任何持有正确 (网络名, 密钥) 组合的客户端都可接入 ——与官方公共服务器 public.easytier.cn 的行为一致(不同密钥的网络天然隔离,互不可见)。

5.5 多房间分片(可选)

默认所有连接进同一个 DO 房间 "global"(同房间内按网络名+摘要自动分组隔离)。 需要物理隔离(例如不同租户)时:

[vars]
ROOM_HEADER = "X-Room"       # 按请求头分片
# 或
ROOM_QUERY = "room"          # 按 URL 查询参数分片,如 wss://et.example.com/?room=t1

5.6 KV 审计(记录 + 黑名单)

六类信息(网络分组 / 节点在线 / 路由信息 / 全局互联 / 连接列表 / 摘要注册表)的 连接与事件记录进 Workers KV,每类只占一条 KV 键(记录 7 条 + 黑名单 4 条,共 11 条); 管理端另有黑名单(分四类:节点 PeerId / 网络分组 / 摘要注册 / 客户端 IP)。 记录查询页支持按类型筛选,类型下拉首项为**「全部」**(跨类型按时间合并视图,无需额外 KV 键)。

启用步骤(可选;不配置 KV 绑定时功能优雅降级——记录与黑名单仅存 Durable Object storage,管理页功能不变):

# 1. 创建 KV namespace(免费计划自带)
npx wrangler kv namespace create AUDIT_KV
# 输出 id 后填入 wrangler.toml:

[[kv_namespaces]]
binding = "AUDIT_KV"
id = "<上面输出的 id>"

# 2. 重新部署
npx wrangler deploy
变量 默认值 说明
RECORD_FLUSH_MS 600000 KV 镜像最小间隔(毫秒)。事件先写内存 + DO storage,按此间隔节流写 KV(控制免费额度写入量)
RECORD_DEFAULT_LIMIT 100 各类记录默认条数上限(管理页「记录设置」可单独调整)
BLACKLIST_LIMIT 1000 各类黑名单条数上限(硬设置)
ADMIN_AUDIT 1 硬设置:管理端审计开关。记录管理员登录(IP/时间)与操作;管理页不可关闭,admin 类记录不可删除(v1.3.0 起不再记录"查看"事件——自动刷新不刷屏、省写入)
ADMIN_AUDIT_LIMIT 200 管理端审计记录上限(硬设置)
IP_BLOCK_CACHE_MS 30000 边缘层 IP 黑名单缓存(v1.4.0):isolate 内缓存 bl:socket 的 KV 读结果,重连风暴时 KV 读从每次连接 1 次降为每 TTL 1 次。只延后拦截、绝不漏放(缓存未命中照旧放行走 DO 权威检查);0 = 关闭
AUDIT_IP_MASK 0 审计 IP 打码(v1.6.0):1 时记录中的客户端 IP 打码——IPv4 保留 /16 前缀(1.2.x.x)、IPv6 保留前 4 组。仅影响记录展示(旧记录不回填);黑名单存储、踢人拉黑、边缘拦截仍使用完整 IP,功能不受影响
AUDIT_RETENTION_DAYS 0 记录留存天数(v1.6.0):0 = 不限(默认,仅受各类条数上限约束);配置 N 后 alarm 每小时清理一次超过 N 天的运行记录(admin 硬审计豁免),清理数写入日志并同步 KV

KV 键布局与写入策略详见技术文档 §9。免费额度核算: KV 免费计划 1000 写/天——默认 10 分钟节流下每键理论最大 144 写/天, 实际仅在有新事件的周期产生写入(典型小网络全类合计每天数十次),远低于限额; 读取主要为两处——冷启动且 DO storage 为空时(每键一次)与 WebSocket 升级时的 边缘黑名单检查(只读 bl:socket 一条键,v1.4.0 起有 isolate 缓存:默认每 30s 至多一次 KV 读,重连风暴时也只消耗每 TTL 一次读取)。

黑名单行为:管理页执行「踢出节点 / 删除分组 / 删除摘要注册 / 断开连接」时, 操作对象(PeerId / 网络名 / IP)自动进入对应类别黑名单并被拦截后续接入; 黑名单页可手工添加、移除(解除封锁)或清空。断开连接会拉黑该客户端 IP—— 若多个节点共用同一出口 IP,请谨慎使用(可先移除该 IP 再让其重连)。

IP 黑名单为双层拦截(v1.3.0):Worker 入口先读 KV 直接 403(不唤醒 DO, 被拉黑客户端的重连风暴不消耗 DO 请求与时长计费),DO 层兜底。v1.4.0 起边缘 层 KV 读取带 isolate 缓存(IP_BLOCK_CACHE_MS,默认 30s),只延后拦截、 绝不漏放——缓存未命中(含 KV 读失败)照旧放行走 DO 层权威检查。因此:

  • 解除 IP 封锁后,KV 传播完成前(通常数秒,最长约 1 分钟)+ 缓存 TTL 内, 边缘层可能仍拒绝该 IP;被新拉黑的 IP 在此窗口内会打到 DO 层被权威拦截;
  • 黑名单拒绝不写入记录(防重连风暴刷爆记录列表),拒绝次数见总览页 「黑名单拦截」计数(DO 层口径;边缘层拒绝不经过 DO,不计数)。

事前限流(v1.4.1):黑名单是事后拦截(需管理员操作或攻击已发生), 单 IP 并发连接上限(MAX_CONNS_PER_IP,DO 升级层)补上事前缺口——同一 IP 的开放连接数达到上限后直接 429,无需先进入黑名单;拦截次数见总览页 「IP 限流」计数(ipLimited)。另:未知路径的非升级请求会被挂起不响应 (tar pit,TARPIT_UNKNOWN),不泄露 404 与行为指纹。

KV 键前缀(v1.4.1 起更名为 easytier-cf-relay:,此前为 et-relay:): 首次启动新版本时自动把旧键数据迁移到新键(旧键保留不删,确认运行正常后可 在 Cloudflare 控制台手动清理);无需任何手工操作。

多房间分片注意:KV 审计键为全局共享(各房间 DO 刷盘同一组键,以最后 刷盘的房间为准)。KV 审计与边缘 IP 拦截面向默认单房间部署;使用 ROOM_HEADER / ROOM_QUERY 分片时不建议启用 AUDIT_KV。

5.7 日志

变量 默认值 说明
LOG_LEVEL info debug / info / warn / error。debug 会打印每个包的类型与路由,仅排障时开启

5.8 监控与管理端

生产环境默认全部禁用(不配置即 404,零攻击面)。启用时务必使用 随机长路径 + 强 token 双重防线:

变量 默认值 说明
HEALTH_PATH /health 健康检查路径(v1.4.0)。配置后仅该路径返回 {"ok":true},原 /health 不再暴露(404 或随 tar pit 挂起;无需 token)
METRICS_PATH (空) 统计端点自定义安全路径。不配置则 /metrics 完全禁用
METRICS_TOKEN (空) 统计端点 token(Authorization: Bearer <t> 或 ?token=<t>)。与 METRICS_PATH 同时配置才启用(fail-closed)。⚠️ ?token= 会进入 URL 日志/浏览器历史/Referer——生产建议只用 Bearer 头(v1.4.2 起文档标注,兼容保留)
ADMIN_PATH (空) Web 管理端路径。与 ADMIN_TOKEN 同时配置才启用(页面壳公开、数据 API 鉴权)
ADMIN_TOKEN (空) 管理端 token(状态查看 / 踢出节点 / 删除分组)。安全审计建议:token 属敏感信息,生产建议 npx wrangler secret put ADMIN_TOKEN 注入而非写进 toml 明文(与 METRICS_TOKEN/CF_API_TOKEN 同模式)
[vars]
HEALTH_PATH = "/h-a1b2c3d4"                # 可选,默认 /health
METRICS_PATH = "/m-a1b2c3d4"              # 用 openssl rand -hex 8 生成
METRICS_TOKEN = "长随机字符串"
ADMIN_PATH = "/console-e5f6a7"
ADMIN_TOKEN = "另一个长随机字符串"

要点:

  • 鉴权失败统一返回 404(不暴露端点存在性);token 比较为常数时间(防时序侧信道);
  • DO 内部端点(/internal/*)只接受 Worker 入口鉴权后的改写转发:带 Upgrade: websocket 头伪造内部路径的请求在 DO 层直接 404(v1.4.1, 防「升级头 + 内部路径」绕过 token 直达端点);
  • 管理端页面壳本身无数据(可公开访问),所有数据 API 均需 token;
  • token 属于敏感信息:生产建议用 npx wrangler secret put METRICS_TOKEN 注入而非写进 toml;
  • wrangler.toml 中的 [env.security] 测试环境变量不会随默认 wrangler deploy 上传;
  • 管理页自动刷新间隔 30s(v1.4.0 起;且页面不可见时自动暂停),长开页面的请求量 约 2.9k/天,对免费额度(100k/天)占用很小,不需要时可关闭自动刷新; v1.6.0 起「额度观测 / 趋势」在服务端报告未配置 CF 凭证后,前端会记住状态并 跳过后续轮询(卡内提供「重新检测」入口),避免空转请求。

Prometheus 文本输出(v1.6.0):启用 /metrics 后,追加 ?format=prometheus 即输出 Prometheus text exposition format(text/plain; version=0.0.4),25 个 easytier_ 命名空间指标(无 label 防高基数):easytier_peers / easytier_groups / easytier_uptime_seconds / easytier_sockets / easytier_sockets_handshaked(gauge) 与 easytier_msgs_in_total / easytier_msgs_out_total / easytier_bytes_in_total / easytier_bytes_out_total / easytier_forwards_total / easytier_conns_total / easytier_errors_total / easytier_forgeries_total / easytier_blacklist_rejected_total / easytier_ip_limited_total / easytier_rate_limited_total / easytier_group_limited_total / easytier_alarms_total(counter)。抓取示例(自托管 Prometheus):

scrape_configs:
  - job_name: easytier-relay
    scrape_interval: 60s
    metrics_path: /m-a1b2c3d4        # 你的 METRICS_PATH
    params:
      format: [prometheus]
    authorization:
      credentials: "<METRICS_TOKEN>"
    static_configs:
      - targets: ["<你的域名>"]

Grafana 可用 Prometheus 数据源直接画 easytier_peers、 rate(easytier_msgs_in_total[5m]) 等面板;拨测平台可继续用 JSON 端点或 /health。注意 counter 随 DO 重启归零(免费计划 DO 周期休眠回收),告警 建议基于 rate() / increase() 而非绝对值。

5.8.1 额度观测与趋势(v1.6,可选)

未配置时总览「额度观测」卡为自观测估算口径,行为与 v1.5 一致。配置下列项后 升级为账号级真实数据(全部可选,逐项独立生效):

项 类型 说明
CF_ACCOUNT_ID vars(非敏感) Cloudflare 账号 ID(Dashboard 首页右侧 / Workers 概览页可见)。真实额度与 AE 趋势查询共用
CF_API_TOKEN secret Cloudflare API Token,必须 npx wrangler secret put CF_API_TOKEN 注入,绝不写入 toml 明文。权限只给 Account → Account Analytics → Read(只读,可随时在 Dashboard → My Profile → API Tokens 吊销)。供 GraphQL 额度查询与 AE SQL 趋势查询共用
CF_SCRIPT_NAME vars(可选) 被查询的 Worker 脚本名,默认 easytier-cf-relay;用其它名字部署时需同步
AE 绑定 wrangler.toml Analytics Engine 绑定(趋势打点)。绑定即自动建数据集,无需控制台操作
AE_DATASET vars 趋势 SQL 查询的 AE 数据集名,必须与 AE 绑定的 dataset 一致(AE 绑定无法在运行时读取自身 dataset 名)。默认 easytier-cf-relay(与 Worker 名一致);绑定省略 dataset 时 wrangler 默认取 Worker 名;绑定改名/自定名的部署两处须一起改,否则趋势查询静默返回空(有节点也无数据)
[vars]
CF_ACCOUNT_ID = "你的账号 ID"
CF_SCRIPT_NAME = "easytier-cf-relay"
AE_DATASET = "easytier-cf-relay"   # 与下方 dataset 一致

[[analytics_engine_datasets]]
binding = "AE"
dataset = "easytier-cf-relay"

# Token 一律 secret 注入(不要写在这里):
#   npx wrangler secret put CF_API_TOKEN

行为说明:

  • 真实额度(管理端 /api/quota):服务端出站调 GraphQL Analytics API 统计当日 UTC 窗口的 workersInvocationsAdaptive 请求数(出站调用不占 100k 入站额度);结果缓存 5 分钟(失败缓存 1 分钟),管理页 30s 自动刷新 不会打爆 API;查询失败自动回退自观测估算并在卡片标注原因。Token 只在 服务端使用,浏览器 / 日志 / 审计中不出现。
  • 趋势图(管理端 /api/trends):DO 每 ≥60s 向 AE 写一个计数快照 (约 1,440 点/天,免费计划含 100,000 点/天,打点不占请求额度);查询走 AE SQL API,24h 窗口 15 分钟一桶、7d 窗口 1 小时一桶,SQL 分桶查询失败 自动回退原始点服务端聚合。未绑定 AE 时打点静默跳过、趋势卡提示未启用。
  • GraphQL 免费计划可用性已于 v1.6 立项前复核(账号级 Workers 数据集可用; zone 级站点分析受限与本功能无关);如官方 API 变更导致查询失败,功能 自动降级为估算口径,不影响其它功能。

6. 客户端接入

6.1 Linux / macOS(easytier-core CLI)

easytier-core \
  --network-name myteam \
  --network-secret s3cret! \
  --hostname node-a \
  -p wss://et.example.com/

实测输出(v2.6.4 官方客户端 + 本项目):

INFO CORE::INSTANCE::CONNECTION: new peer connection added conn_info=PeerConnInfo { my_peer_id: ..., dst_peer_id: 10000001, ... }
INFO CORE::INSTANCE: new peer added peer_id=10000001
$ easytier-cli peer
| ipv4          | hostname | cost  | lat(ms) | tunnel | version           |
|---------------|----------|-------|---------|--------|-------------------|
| 10.126.0.1/24 | nodeA    | Local | -       | -      | 2.6.4-8428a89d    |
|               | easytier-cf-relay | p2p   | 3.88    | ws     | easytier-cf-relay/1.0.0 |
| 10.126.0.2/24 | nodeB    | relay(2) | 18.00 |        | 2.6.4-8428a89d    |

6.2 Windows GUI / macOS GUI / 手机端

  • 网络名称:myteam
  • 网络密钥:s3cret!
  • 节点/Peer:wss://et.example.com/(新建一个节点条目填入即可)

6.3 Docker

docker run --name easytier -d --network host --cap-add NET_ADMIN \
  easytier/easytier:latest \
  --network-name myteam --network-secret 's3cret!' \
  -p wss://et.example.com/

6.4 兼容性矩阵

客户端 结果
easytier-core 2.6.4 Linux x86_64 ✅ 实测通过(握手/路由/双节点互见/数据中继/长时间稳定)
easytier-core 2.x 其它版本 ✅ 协议层面兼容(握手 + OSPF + PeerCenter 均按官方 2.6.x 语义实现)
secure mode(Noise,v2.6.0+ 可选功能) ❌ 不支持,客户端需使用默认(非 secure)模式

7. 成本与额度

免费计划即可运行本服务(SQLite 后端 Durable Objects 已对免费计划开放)。关键额度:

维度 免费计划 说明
请求 100,000 / 天 含 WS 连接建立、入站 WS 消息(20:1 折算)、alarm 唤醒
Duration 13,000 GB-s / 天 Hibernation 状态不计费 —— 本实现全用 Hibernation API,空闲时成本趋近零
存储 5 GB / 账户 本服务仅存路由元数据(KB 级)
KV 写 1,000 / 天 启用 KV 审计后:事件按 RECORD_FLUSH_MS(默认 10 分钟)节流镜像,每键理论最大 144 写/天;拒绝/查看事件不产生写入
KV 读 100,000 / 天 冷启动且 DO storage 为空时读取(每键一次)+ WS 升级的边缘黑名单检查(v1.4.0 起 isolate 缓存,默认每 30s 至多 1 次读)
单 DO 吞吐 ~1,000 req/s(软限制) 单房间 64 peer 远达不到

成本估算(10 个客户端、每客户端 8s 一次心跳的典型小网络):

  • 计费请求 ≈ 10 × (86400/8) / 20 ≈ 5,400 请求/天(v1.4.0 起 alarm 自适应 排程 + 空闲退避:有连接时 alarm 对齐最近事件时刻、至多 15s 一次;空房间退避 到 5 分钟一次 = 288 请求/天,较固定 15s 的 5,760 请求/天大幅下降);
  • duration:连接空闲即休眠,活跃处理时间每天累计不足 1 GB-s;
  • KV 审计:每天几十次连接波动 × 涉及的 2~3 个键 ÷ 10 分钟节流合并 ≈ 数十写/天, 远低于 1000/天限额(记录上限 100 条/类,存储占用 KB 级);
  • KV 读 ≈ 冷启动恢复 + 边缘黑名单检查(缓存后每 30s 至多 1 次)≈ 数千读/天, 远低于 10 万限额;
  • 被拉黑客户端的重连风暴:每次重连仍占 1 次 Worker 请求(客户端行为), 但 v1.3.0 起在 Worker 入口边缘层直接 403,不唤醒 DO、不产生审计写入, DO 请求与 KV 写入均零消耗;v1.4.0 起边缘层 KV 读有 30s 缓存, 风暴期间 KV 读也不再与重连次数成正比;
  • 结论:免费额度内稳定运行。超出时可升级 Workers Paid($5/月起,含 100 万请求/月 + 40 万 GB-s/月)。

计费细节参见 Cloudflare 官方文档:

8. 运维:日志、监控、升级

8.1 健康检查与指标

# 健康检查:零指纹(P0 整改后仅返回 {"ok":true})
curl https://et.example.com/health
# {"ok":true}
# 配置 HEALTH_PATH 后:curl https://et.example.com<HEALTH_PATH>(原 /health 不再暴露:404 或 tar pit 挂起)

# 统计:自定义安全路径 + token(未配置 METRICS_PATH/METRICS_TOKEN 时 404)
curl -H "Authorization: Bearer <METRICS_TOKEN>" https://et.example.com<m-metrics-path>
# {
#   "ok": true, "totalPeers": 2, "groupCount": 1,
#   "groups": { "myteam:<digest>": { "peers": [...], "knownInfos": 3 } },
#   "counters": { "msgsIn": 103, "msgsOut": 103, "forwards": 58, "errors": 0, ... }
# }

字段速查:forwards = 数据包中继次数;errors = 协议错误数(应恒为 0); forgeries = 源伪造拦截数(F-06 防线命中);groups 按网络名分组列出在线 peer ID。

8.2 Web 管理端

配置 ADMIN_PATH + ADMIN_TOKEN 后,浏览器打开 https://et.example.com<ADMIN_PATH> 进入控制台(输入 token 即可,浏览器本地保存)。

界面结构(侧边栏列表式导航,按功能块分区):

  • 总览(对照 easytier-cli stats / node):运行时长(自房间创建起累计,含休眠)、分组数、 在线节点、收发包/字节/转发/错误计数,以及审计概览(记录/黑名单各分类计数与 KV 状态);
  • 节点在线(对照 easytier-cli peer):各分组在线节点、连接时间、最近活动,可踢出节点;
  • 路由信息(对照 easytier-cli route):RoutePeerInfo 路由表,含 direct/transit 来源标记 与幽灵条目标记(transit 来源 + 无连接),可删除条目;
  • 全局互联(对照 easytier-cli peer-center):PeerCenter 互联表,可删除条目;
  • 连接列表:全部 socket(含未握手),含 socket ID 与客户端 IP,可断开连接;
  • 网络分组(对照 easytier-cli foreign-network):按网络名+摘要隔离的分组,可删除分组;
  • 摘要注册表:网络名 → 摘要注册(STRICT_DIGEST 判定依据),可删除注册;
  • 记录查询:七类记录(六类数据 + 管理端审计)分页浏览;类型下拉首项为 「全部」(跨类型按时间合并,附类型列),单条/批量删除与清空 (admin 类为硬审计,只读不可删——"全部"视图中 admin 行的勾选与删除均禁用);
  • 记录设置:六类数据记录各自的开/关与条数上限(管理端审计与黑名单上限为硬设置,只读展示);
  • 黑名单:四类黑名单(节点 PeerId / 网络分组 / 摘要注册 / 客户端 IP)分页浏览, 手工添加、单个/批量移除(解除封锁)与清空。

所有列表的**「操作」列按钮针对单行直接生效**(无需先勾选复选框;勾选用于顶部 批量按钮的批量操作)。总览页统计含「黑名单拦截」计数(DO 层拒绝次数, 用于观测被拉黑客户端的重连行为——拒绝事件本身不再写入记录)。

性能设计(节点多时页面不爆炸):

  • 服务端分页:所有列表 ?tab=&offset=&limit= 分页拉取,每页默认 50 条(可选 20/50/100/200,上限 200);
  • 独立滚动:列表区固定最大高度、内部滚动,表头 sticky;
  • 按块统计:每个功能块头部显示自身统计(总数/直连/中转/幽灵/空分组等), 侧边栏实时显示各块计数;
  • 批量操作:每个功能块支持勾选(单选/全选本页)+ 批量按钮 (删除分组 / 踢出节点 / 删除路由 / 删除互联 / 断开连接 / 删除摘要注册);
  • 自动刷新仅拉取当前 tab(30s,可关闭;页面不可见时自动暂停),开销可控。

删除分组:断开组内全部连接、清除路由/会话数据、解除摘要注册 (被恶意摘要抢占时可用此操作解锁)。注意:删除分组 / 踢出节点 / 删除摘要注册 / 断开连接会把操作对象(网络名 / PeerId / IP)自动加入对应类别的黑名单并拦截 后续接入,需要放行时到「黑名单」页移除即可(见 5.6 节)。

官方 CLI 中依赖 UDP/TUN/系统服务的命令(connector、vpn-portal、stun、acl 等) 在 Workers 运行时不可用,控制台底部有完整对照表。

也可直接调 API(鉴权方式同上):

# 查看状态(tab: overview/groups/peers/routes/peercenter/sockets/digests;分页 offset/limit)
curl -H "Authorization: Bearer <ADMIN_TOKEN>" \
  "https://et.example.com<ADMIN_PATH>/api/state?tab=routes&offset=0&limit=50"

# 踢出节点(单个或批量)
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"peers":[{"groupKey":"myteam:<digest>","peerId":12345678}]}' \
  https://et.example.com<ADMIN_PATH>/api/peer/kick

# 删除分组(支持 groupKeys 数组 / networkName / all)
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"networkName":"myteam"}' \
  https://et.example.com<ADMIN_PATH>/api/group/delete

# 删除路由条目(批量;在线节点的 direct 条目会被跳过)
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"groupKey":"myteam:<digest>","peerIds":[12345678,12345679]}' \
  https://et.example.com<ADMIN_PATH>/api/route/delete

# 删除互联表条目(批量)
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"groupKey":"myteam:<digest>","peerIds":[12345678]}' \
  https://et.example.com<ADMIN_PATH>/api/peercenter/delete

# 断开指定连接(批量,按 socketId)
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"socketIds":[3,5]}' \
  https://et.example.com<ADMIN_PATH>/api/socket/close

# 删除摘要注册(批量;连带清除使用该摘要的分组并断开其连接)
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"networkNames":["myteam"]}' \
  https://et.example.com<ADMIN_PATH>/api/digest/delete

# ---- KV 审计(记录 + 黑名单)----

# 查询记录(type: groups/peers/routes/peercenter/sockets/digests/admin;分页)
curl -H "Authorization: Bearer <ADMIN_TOKEN>" \
  "https://et.example.com<ADMIN_PATH>/api/records?type=peers&offset=0&limit=50"

# 删除记录(ids 为记录 id 数组或 "all";admin 类返回错误——硬审计不可删)
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"type":"peers","ids":"all"}' \
  https://et.example.com<ADMIN_PATH>/api/records/delete

# 读取 / 修改记录配置(每类开关 + 上限;admin 类不在可改范围)
curl -H "Authorization: Bearer <ADMIN_TOKEN>" \
  "https://et.example.com<ADMIN_PATH>/api/record/config"
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"types":{"peers":{"on":true,"limit":200}}}' \
  https://et.example.com<ADMIN_PATH>/api/record/config

# 查询黑名单(cat: peer/group/digest/socket)
curl -H "Authorization: Bearer <ADMIN_TOKEN>" \
  "https://et.example.com<ADMIN_PATH>/api/blacklist?cat=peer&offset=0&limit=50"

# 手工拉黑(cat + value;peer 为数字 PeerId,group/digest 为网络名,socket 为 IP)
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"cat":"peer","value":12345678,"reason":"abuse"}' \
  https://et.example.com<ADMIN_PATH>/api/blacklist/add

# 移除黑名单(解除封锁;ids 为条目 id 数组或 "all")
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -H "content-type: application/json" \
  -d '{"cat":"peer","ids":"all"}' \
  https://et.example.com<ADMIN_PATH>/api/blacklist/delete

8.3 实时日志

npx wrangler tail --format pretty

LOG_LEVEL=debug 时可看到每个包 msg type=N from=X to=Y 与 RPC 分发详情。

8.4 升级

git pull            # 或修改代码
npx wrangler deploy

部署即热更新。已连接的 WebSocket 会经历一次重连(客户端自动重试,秒级恢复)。 房间路由状态持久化在 DO storage,升级不丢。

8.5 Durable Object 迁移注意

本工程 wrangler.toml 使用:

[[migrations]]
tag = "v1"
new_sqlite_classes = ["RelayRoom"]
  • 全新部署:保持原样即可。
  • 接手已有 new_classes(KV 后端)的旧部署:不要删掉旧 migration 条目,改为追加:
[[migrations]]
tag = "v1"
new_classes = ["RelayRoom"]     # 旧条目原样保留

[[migrations]]
tag = "v2"
new_sqlite_classes = ["RelayRoom"]  # 追加:切换到 SQLite 后端

8.6 重新生成 Protobuf 静态代码(仅开发)

跟随官方 .proto 变更时使用(普通用户不需要):

bash tools/regen_proto.sh   # 需要本地有官方源码 checkout

9. 故障排查 FAQ

Q1:客户端一直 connect timeout after 20s?

  1. curl https://你的域名/health 确认 Worker 存活;
  2. 检查 URL scheme 是 wss://(而非 ws://,Workers 强制 TLS);
  3. npx wrangler tail 看握手是否到达。到达但被拒(digest mismatch)说明密钥不对。

Q2:客户端握手成功但 easytier-cli peer 看不到服务端节点? 通常是旧版本第三方实现的问题;本项目已实测 2.6.4 正常显示(hostname=easytier-cf-relay)。 若遇到,开启 LOG_LEVEL=debug 检查 rpc req ... service=OspfRouteRpc 日志行, 存在但无响应时请提 issue 附日志。

Q3:节点显示在线,但互相 ping 不通?

  • 确认两端 --network-name 与 --network-secret 完全一致(STRICT_DIGEST=1 时不同摘要会被拒绝,日志有 digest mismatch);
  • RELAY_DATA=0 时中继不转发数据面,必须依赖 P2P 直连(打洞失败的组合无法通信),需要兜底请设 RELAY_DATA=1。

Q4:本地 wrangler dev 报 Address already in use? 端口被占用:npx wrangler dev --port 8788 换端口,或杀掉旧进程。

Q5:wrangler deploy 报 DO 类已存在 / migration 冲突? 见 8.4 迁移章节。切勿删除 wrangler.toml 中已有的 [[migrations]] 条目。

Q6:两个客户端都能连上,但互相看不到对方(只看得到 easytier-cf-relay)? 检查是否误配置了 ROOM_HEADER/ROOM_QUERY——两个客户端进了不同 DO 房间。 同房间要求相同的 URL/Header。

Q7:想统计流量? 配置 METRICS_PATH + METRICS_TOKEN 后访问统计端点, counters.bytesIn/bytesOut/forwards 即为控制面+数据面的转发统计;或用 Web 管理端总览页。

Q8:同名网络被恶意抢占(digest mismatch)怎么办? 已配置 NETWORK_SECRETS 的网络不受影响(服务端以正确密钥摘要为准,注册表自愈)。 未配置的网络被抢占时,用管理端删除该分组即可解除注册,随后正常客户端可重新接入。 v1.6.0 起,此类握手被拒会写入 squat-suspect 审计事件(含攻击源 IP, 按网络名+IP 10 分钟去重)——「记录查询 → 摘要注册」页可见抢占者并可拉黑其 IP; 总览页「摘要抢占嫌疑」计数持续偏高即说明有人在尝试堵门。加固建议:公开部署 强烈建议配置 NETWORK_SECRETS(服务端密钥校验,抢占直接失效)。

Q9:如何复跑安全测试?

npx wrangler dev --env security --port 8788   # 使用 .dev.vars.security 中的测试 token
node test/security_test.mjs                    # 45 项黑盒安全测试
node test/audit_e2e_test.mjs                   # 34 项 KV 审计(记录/黑名单/边缘拦截)动态测试

Q10:踢出/删除的节点或网络重连不上? 这是黑名单在生效:踢出节点 / 删除分组 / 删除摘要注册 / 断开连接会把操作对象 (PeerId / 网络名 / IP)加入对应类别黑名单并拦截后续接入。 到管理端「黑名单」页移除对应条目(解除封锁)即可重新接入。 注意两点:

  • 断开连接拉黑的是客户端 IP——若多个节点共用同一出口 IP,会一并被拦, 需要时先移除该 IP 条目;
  • 配置了 AUDIT_KV 时,IP 拦截发生在 Worker 入口边缘层(KV 直读)。 解除封锁后 KV 传播完成前(通常数秒,最长约 1 分钟)边缘层可能仍拒绝该 IP, 稍候即可。

Q11:管理端记录为什么删不掉 / 关不掉?"查看"事件为什么不见了? 管理端审计(登录 IP/时间、操作)是硬设置(wrangler.toml ADMIN_AUDIT), 管理页不可关闭、admin 类记录不可删除(防审计自擦除)。 六类数据记录则可在「记录设置」页单独开关并设置上限。 v1.3.0 起管理端"查看"事件不再记录(管理页自动刷新会持续刷屏且徒增写入); 黑名单拒绝事件同样不再写入「节点在线」记录(被拉黑客户端会不断重连,逐条记录 会刷爆列表),拒绝次数改看总览页「黑名单拦截」计数。

Q12:被拉黑的客户端一直重连,会不会刷爆免费额度? 每次重连尝试本身占用 1 次 Worker 请求(客户端行为,服务端无法避免),但 v1.3.0 起:拒绝发生在 Worker 入口边缘层(读 KV 的 bl:socket 一条键), 不再唤醒 Durable Object——不消耗 DO 请求与时长计费,也不产生任何审计写入 (否则重连风暴每秒都会产生记录与 KV 镜像写入)。v1.4.0 起该 KV 读带 isolate 缓存(IP_BLOCK_CACHE_MS,默认 30s):重连风暴时 KV 读从每次连接 1 次降为每 30s 至多 1 次,只延后拦截、绝不漏放(DO 层权威兜底)。 若确有恶意 IP 长期轰炸,可在 Cloudflare 控制台配置 WAF/IP Access Rules 在更外层拦截。

本手册对应实现版本:easytier-cf-relay/1.4.0(协议对齐 easytier-core 2.6.x)。技术实现细节见《技术文档》。