Skip to content

第 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 addressServer 监听在哪个接口决定网络可达范围
IP whitelistTCP 远端地址可用 wildcard 规则限制来源
Host whitelistHTTP 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;
  • .resume bencode;
  • .torrent/LTEP bencode;
  • RPC JSON;
  • C++ settings 结构的 serializer。

这避免为每种数据格式复制领域映射。tr_variant_serde::json().benc() 只负责边缘 parse/write。

六、Quark 为什么存在

RPC 与设置里大量重复字段名,如 download_dirpeer_limittorrent_gettr_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;
  • 响应使用 resulterror {code,message,data}

没有 jsonrpc 字段时走旧 Transmission 协议兼容层,api-compat 负责旧字段命名与响应形态转换。这条路径被明确视为 deprecated,但保留了现有客户端迁移窗口。

八、同步与异步 Handler 分开

rpcimpl.cc 的 dispatch table 分两类:

8.1 20 个同步方法

包括 session_get/set/stats/closetorrent_get/set/start/stop/verify/remove/reannounce、queue move、group get/set、free space 等。函数返回 (Error::Code, message),结果写入 output Map。

8.2 4 个异步方法

torrent_addtorrent_rename_pathblocklist_updateport_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_stattr_info、Torrent 直接字段和动态计算结果映射到 RPC。

这比为每个字段写一个 RPC 方法更适合高维状态查询,也解释了 rpcimpl.cc 为什么成为最大文件:它是协议 schema 与领域模型之间的显式映射层。

十一、Web UI 的表格响应优化

torrent_get 支持 objectstable

json
[
  ["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 关闭组合复杂;
  • 旧/新字段风格共存增加测试矩阵。

源码锚点

文档采用 CC BY-SA 4.0;源码片段保留 Transmission 上游许可。