第 11 章:RPC 与安全边界 —— 同一个端口上的控制面与 Web 站点
tr_rpc_server同时提供静态 Web UI 与 JSON-RPC。它不仅做 HTTP 转发,还按固定顺序实施地址、Host、认证、CSRF 和暴力破解防护,再把动态值树交给rpcimpl。
一、HTTP 请求的分层路径
检查顺序影响安全语义。IP 黑名单在解析 body 前拒绝;认证失败累计 login attempts;Web 静态路由也防路径穿越;RPC 在真正执行前必须通过 session id。
二、CSRF 的 409 重试协议
客户端第一次通常没有 X-Transmission-Session-Id。Server 返回 HTTP 409,并在响应 header 放正确 token。客户端保存 token,原样重发请求。
Web Remote.sendRequest() 与 transmission-remote 都实现了这条逻辑。Token 过期后也走同样重试。
它防的是浏览器第三方站点借用户身份发跨站请求;它不是登录凭证。远程暴露仍应启用认证,并优先置于 TLS reverse proxy/VPN 后。
三、四道外部访问限制
| 机制 | 检查对象 | 默认/行为 |
|---|---|---|
| bind address | Server 监听在哪个接口 | 决定网络可达范围 |
| IP whitelist | TCP 远端地址 | 可用 wildcard 规则限制来源 |
| Host whitelist | HTTP Host | 防 DNS rebinding;localhost/IP 有特殊规则 |
| Basic auth | 用户名/密码 | 密码保存为 salted SHA-1 表达式 |
此外 anti-brute-force 在失败次数达到限制后拒绝后续认证。源码中启用 password auth 时允许任意 Host,但这不等于任意 IP;两种 whitelist 面向不同攻击面。
四、静态文件服务也有安全工作
/transmission/web/ 被映射到安装的 Web client 目录。Server 会:
- 只允许 GET;
- 从 URI 去掉固定 base path;
- 规范并验证子路径,拒绝 directory traversal;
- 按扩展名设置 MIME;
- 根据请求头压缩响应;
- 找不到资产时返回诊断页,提示
TRANSMISSION_WEB_HOME。
Web UI 与 RPC 同源,浏览器无需额外 CORS 配置,也能共享 Basic auth 和 CSRF token。
五、tr_variant:JSON 与 bencode 的共同中间表示
RPC 请求先由 RapidJSON streaming handler 转成 tr_variant。它能保存 null、bool、int、double、string、Vector 与 Map;Map key 使用 tr_quark intern id。
同一值树还用于:
- settings.json;
.resumebencode;.torrent/LTEP bencode;- RPC JSON;
- C++ settings 结构的 serializer。
这避免为每种数据格式复制领域映射。tr_variant_serde::json() 与 .benc() 只负责边缘 parse/write。
六、Quark 为什么存在
RPC 与设置里大量重复字段名,如 download_dir、peer_limit、torrent_get。tr_quark_new() 把字符串驻留为小整数:
- Map 比较与 switch 更快;
- 常用 key 由生成的
TR_KEY_*常量统一; - JSON/bencode 序列化时再映射回 string。
代价是调试时多一层转换,动态未知 key 也会进入 intern table,因此 parser 仍要限制输入规模。
七、JSON-RPC 2.0 与旧协议兼容
当前 RPC 版本为 6.1.0。请求包含 jsonrpc: "2.0" 时按标准处理:
params必须是 Object,不支持 positional params;- id 只能是 String、Number 或 Null;
- 无 id 是 notification;
- 支持 batch array;
- 响应使用
result或error {code,message,data}。
没有 jsonrpc 字段时走旧 Transmission 协议兼容层,api-compat 负责旧字段命名与响应形态转换。这条路径被明确视为 deprecated,但保留了现有客户端迁移窗口。
八、同步与异步 Handler 分开
rpcimpl.cc 的 dispatch table 分两类:
8.1 20 个同步方法
包括 session_get/set/stats/close、torrent_get/set/start/stop/verify/remove/reannounce、queue move、group get/set、free space 等。函数返回 (Error::Code, message),结果写入 output Map。
8.2 4 个异步方法
torrent_add、torrent_rename_path、blocklist_update、port_test 需要文件移动、HTTP fetch 或异步 torrent 创建,因此持有 tr_rpc_idle_data,完成时调用 tr_rpc_idle_done()。
dispatch table 还标记 has_side_effects。JSON-RPC notification 对无副作用查询不会白做昂贵工作,因为没有响应接收者;有副作用动作即使无 id 仍要执行。
九、Batch 如何等待不同步的结果
批请求为每个 item 调用同一执行器,结果写入共享 vector 对应位置;计数达到总请求数后删除 notification 的空响应,再一次性回调。
这保证响应顺序与请求顺序一致,即使异步方法完成顺序不同。共享状态用 shared_ptr 延长到最后一个完成回调。
十、字段表驱动减少 getter/setter 重复
session_accessors() 把每个 quark 映射到可选 getter/setter。session_get 根据 fields 集合调用 getter;session_set 遍历输入调用 setter。只读字段的 setter 为 null。
Torrent fields 也用集中 switch/accessor 把 tr_stat、tr_info、Torrent 直接字段和动态计算结果映射到 RPC。
这比为每个字段写一个 RPC 方法更适合高维状态查询,也解释了 rpcimpl.cc 为什么成为最大文件:它是协议 schema 与领域模型之间的显式映射层。
十一、Web UI 的表格响应优化
torrent_get 支持 objects 与 table:
[
["id", "name", "status"],
[1, "A", 4],
[2, "B", 6]
]table 格式只发送一次字段名,减少大量 torrents 轮询时的 JSON 体积和解析对象数量。Web UI 默认请求 table,并把首行当 schema。
recently_active 还会返回 removed ids,使客户端无需每次获取全量列表就能清理已删除条目。
十二、RPC 边界的设计评价
优点:
- 所有产品共享同一控制语义;
- HTTP 安全检查集中;
- sync/async 对调用方保持统一 response;
- 兼容层与新 JSON-RPC 主线分离;
- 动态字段查询适合 UI 增量刷新。
风险点:
rpcimpl.cc体积大,schema 不由机器可读定义生成;- Basic auth 本身不提供加密;
- notification、batch、异步完成与 Session 关闭组合复杂;
- 旧/新字段风格共存增加测试矩阵。
源码锚点
libtransmission/rpc-server.cc:HTTP 路由、认证、whitelist、409libtransmission/rpcimpl.cc:字段映射与 handler dispatchlibtransmission/rpcimpl.h:RPC 版本与错误码libtransmission/variant.h:动态值树libtransmission/variant-json.cc、variant-benc.cc:两种序列化边缘web/src/remote.js:浏览器 409 重试与 RPC 封装docs/rpc-spec.md:官方协议规范