2026.03.05 组会-Tor 客户端选择 Rendezvous Point (RP) 机制汇报
核心问题:
Tor 客户端在建立隐藏服务连接时,是如何选择 Rendezvous Point (RP) 的?
分析基于 circuitbuild.c 源码。
问题背景
在 v3 Hidden Service 连接过程中,客户端需要:
- 构建到 RP 的电路
- 通知隐藏服务连接该 RP
- 通过 RP 汇合形成 6-hop 通路
从逻辑上看,RP 是一个特殊角色节点。
但从源码实现上看,RP 并不是一种特殊类型。
核心结论
在当前实现中:
RP 本质上就是这条 internal 电路的“exit”节点。
换句话说:
1 | RP == chosen_exit(当 purpose = C_ESTABLISH_REND) |
因此:
- 如果未指定 exit,则随机选择一个节点作为 RP
- 如果指定了 exit_ei,则该节点直接成为 RP
即:
1 | 指定 exit == 指定 RP |
前提条件:
- 电路用途为
CIRCUIT_PURPOSE_C_ESTABLISH_REND - 电路必须是 internal
源码路径分析
客户端建立 RP 电路的调用链如下:
1 | origin_circuit_init() |
下面逐步说明。
Step 1:internal 标记的来源
1 | origin_circuit_t * |
关键点:
is_internal由 flags 决定- 建立 RP 电路时必须设置
CIRCLAUNCH_IS_INTERNAL - 否则后续会触发断言
internal 电路的含义是:
不用于访问外网,而用于内部目的(如 HS 或目录)
Step 2:决定最后一跳
1 | // onion_pick_cpath_exit() |
逻辑非常清晰:
- 如果调用方给了 exit_ei
- 则直接使用该节点
- 否则调用选择逻辑
对于 rendezvous 电路来说:
1 | chosen_exit == RP |
Step 3:RP 的实际选择逻辑
1 | // choose_good_exit_server(origin_circuit_t *circ, |
逐条解释:
- 必须是 internal 电路
- 标记该节点用于 Hidden Service
- 调用
router_choose_random_node()随机选择
因此可以得出结论:
RP 是在满足过滤条件的节点中随机选出的一个普通 relay。
它不是特殊类型节点。
七、RP 电路的结构
客户端侧电路结构:
1 | guard → middle → RP |
这是一条三跳 internal 电路。
RP 只是最后一跳。
当隐藏服务连接到该 RP 后,形成:
1 | client: guard → middle → RP |
最终构成 6-hop 通路。
八、设计思想分析
为什么 Tor 这样设计?
原因很简单:
- RP 不需要访问外网
- 它只是汇合点
- 因此不需要单独设计一种“RP 类型节点”
实现上直接复用 exit 选择逻辑。
只是在逻辑层面限制为 internal。
九、实验启示
对于源码修改或实验:
如果希望固定 RP:
- 可以在建立电路时指定 exit_ei
- 无需修改随机选择函数
如果希望统计 RP 行为:
- 可以在
choose_good_exit_server()中添加日志
十、总结
本次分析说明:
- RP 不是特殊节点类型
- RP 是 internal 电路的 chosen_exit
- 未指定时随机选择
- 指定 exit 等价于指定 RP
一句话概括:
在客户端建立 Rendezvous 电路时,RP 就是这条 internal 电路的最后一跳。
十一、6-hop 电路结构解析
在 v3 Hidden Service 连接完成后,客户端与隐藏服务之间形成如下结构:
客户端侧电路:
1 | guard_c → middle_c → RP |
隐藏服务侧电路:
1 | guard_s → middle_s → RP |
合并后形成完整 6-hop 通路:
1 | client |
注意:
- RP 是双方电路的汇合点
- RP 不知道 client 或 service 的真实身份
- 两侧电路在密码学上彼此独立
因此:
6-hop 结构本质上是两条 3-hop internal 电路在 RP 处拼接。
十二、Tor Proposal 中的原文描述
在 Tor Proposal 224(Next-Generation Hidden Services)中,对 rendezvous 机制的描述如下:
“The client builds a circuit to a randomly chosen rendezvous point (RP) and sends a RENDEZVOUS1 cell.”
以及:
“The service builds a circuit to the rendezvous point and sends a RENDEZVOUS2 cell to complete the handshake.”
关键点可以提炼为:
- Client 随机选择 RP
- Client 先建立到 RP 的电路
- Service 再建立到同一个 RP 的电路
- 双方在 RP 处完成握手
这与我们在源码中看到的逻辑完全一致:
- RP 是随机选取
- RP 是 internal 电路的最后一跳
- 双方各自构建独立电路
十三、Proposal 与源码的对应关系
| Proposal 描述 | 源码实现 |
|---|---|
| Client randomly chooses RP | router_choose_random_node() |
| Client builds circuit to RP | CIRCUIT_PURPOSE_C_ESTABLISH_REND |
| Service connects to RP | 服务端构建 internal 电路 |
| Rendezvous handshake | RP 处 relay cell 交换 |
可以看到:
Proposal 是协议层描述,源码是工程实现。
RP 并不是协议层的特殊节点类型。
它只是一个普通 relay,被选作汇合点。
十四、结构总结
结合协议与源码,可以得到完整理解:
- RP 是普通 relay
- RP 通过 internal 电路被选出
- 6-hop 是两条 3-hop 电路拼接
- 指定 exit 等价于指定 RP
从实现角度看:
Tor 没有专门的 RP 类型或 RP 选择函数。
RP 只是 exit 选择逻辑在特定 purpose 下的复用。
以上为结合 Proposal 与源码的完整机制说明。