2026.2.5组会:仅基于客户端修改的 Stream 级流控可控速率方案说明
1. 原理
1.1 问题视角
一次下载过程中,数据方向主要是 Exit → Client。
我想在下载进行时,主动让吞吐出现明显的“升速”和“降速”。
要求是 只改客户端代码,Exit 端不改。
1.2 协议与设计依据
Tor 的协议层面已经定义了 Stream 级的 XON/XOFF 控制命令。
XON/XOFF 属于 RELAY 命令的一部分,可以双向发送。
在 Conflux/Traffic Splitting 的语境下,文档也明确提到 Congestion control Stream XON/XOFF 可以在任一方向发送,并影响该 stream 的传输。
我采用的核心假设是:
- Client 可以通过发送 XON 携带“建议速率”,影响对端对该 stream 的发送节奏。
- 对端(Exit)无需改动,因为其已有实现会把 XON 的建议速率映射为 token bucket 的读写速率。
1.3 代码闭环
我关注的闭环是:
- Client 计算 stream 的 drain 速率
- 文件:
congestion_control_flow.c - 逻辑:根据 outbuf 的 drain 速度更新
stream->ewma_drain_rate。
- Client 发送 XON,并在 XON 中携带速率建议
- 文件:
congestion_control_flow.c - 函数:
circuit_send_stream_xon(edge_connection_t *stream) - 关键写入:
1 | xon_cell_set_kbps_ewma(&xon, stream->ewma_drain_rate); |
- Exit 收到 XON 后调整 token bucket
- Exit 端已有逻辑:解析
kbps_ewma,再调用token_bucket_rw_adjust(...)。 - 因此,只要我改变 Client 发出的 kbps_ewma,Exit 的实际发送速率就会跟着变化。
1.4 为什么“只改客户端”可行
关键点在于:
- XON 是一个协议级信号。
- Exit 的处理逻辑已经存在。
- 我只需要改变 Client 发出去的 XON 的速率字段。
- Exit 不需要知道“为什么变”,只会按字段值执行限速。
2. 进行的改动方案
2.1 总体思路
我在客户端加入一个 FlowCtl 速率塑形器。
它不改变 Exit。
它只改变 Client 发出去的 XON 里携带的速率建议。
我把改动分为三部分。
- A. 改写 XON 里的
kbps_ewma(核心) - B. 让“旋钮变化”立即触发新的 XON(保证可演示)
- C. 增加运行时控制入口(文件热加载)
2.2 A:改写 XON 速率字段
文件:congestion_control_flow.c
函数:circuit_send_stream_xon(edge_connection_t *stream)
我引入两个值:
measured_kbps:原本的stream->ewma_drain_rateadvertised_kbps:我塑形后的对外建议值
核心替换逻辑:
1 | uint32_t measured_kbps = stream->ewma_drain_rate; |
补充:强暂停用 XOFF。
我不使用 kbps=0 表示暂停。
当配置为暂停时,客户端直接发送 XOFF。
1 | if (TO_CONN(stream)->type == CONN_TYPE_AP && flowctl_cfg.mode == FLOWCTL_MODE_XOFF) { |
2.3 B:旋钮变化立即生效
我引入一个全局版本号 flowctl_epoch。
每次配置变化我让 flowctl_epoch++。
为了让变化尽快反映到下一次 XON,我在 XON 触发判断中加入 epoch 判据。
我在 edge_connection_t 增加字段:
1 | uint64_t flowctl_epoch_last_sent; |
并在 stream_drain_rate_changed() 增加:
1 | if (TO_CONN(stream)->type == CONN_TYPE_AP) { |
当 XON 发送成功后,我更新:
1 | stream->flowctl_epoch_last_sent = flowctl_epoch; |
这样做的效果是:
- 我一改配置文件,epoch 立刻变化。
- 下一次触发判断必然认为“速率显著变化”。
- 新的 XON 会立刻发出。
2.4 C:运行时控制入口
我用一个文件热加载来实现运行时控制。
默认文件:/tmp/tor-flowctl.conf
也支持:TOR_FLOWCTL_FILE 环境变量。
我支持的模式:
off:不干预fixed:固定速率cap:上限钳制scale:倍率缩放square:高低速方波切换xoff:强暂停
3. 使用说明
3.1 前置条件
- 客户端 tor 打开日志输出(至少 notice)。
- 客户端开启 ControlPort(用于 BW 事件观测)。
- 准备一个足够大的下载文件与足够快的源站。
3.2 配置文件示例
固定速率:
1 | mode=fixed |
方波模式(组会演示推荐):
1 | mode=square |
强暂停:
1 | mode=xoff |
3.3 运行与监控命令
终端 A:看 flowctl 日志
1 | tail -f /tmp/tor-client.log | grep flowctl |
终端 B:订阅 BW 事件
1 | { echo 'AUTHENTICATE'; echo 'SETEVENTS BW'; cat; } | nc 127.0.0.1 9051 |
终端 C:启动下载
1 | curl --socks5-hostname 127.0.0.1:9050 -L -o /dev/null https://example.com/large.bin |
终端 D:切换配置(写入方波)
1 | cat > /tmp/tor-flowctl.conf <<'EOF' |
4. 预期看到的结果
4.1 日志层面
我预期在客户端日志中看到:
- 配置 reload 提示(epoch 增加)
- 发送 XON 的提示,其中
adv_kbps按 high/low 跳变
示例输出形态:
reload ... epoch=...sent XON ... measured_kbps=... adv_kbps=50000 epoch=...sent XON ... measured_kbps=... adv_kbps=500 epoch=...
4.2 Tor 层面(BW 事件)
我预期 ControlPort 的 BW 事件输出出现同步跳变。
- 高速档时每秒写入字节显著变大。
- 低速档时每秒写入字节显著变小。
我预期跳变相对配置切换有 0 到 2 秒延迟。
延迟来自缓冲区排空与 XON 发送周期。
4.3 应用层面(下载速度)
我预期 curl 或 wget 的下载速度明显出现方波。
- 高速档接近
high_kbps对应的吞吐上限(受其它瓶颈影响)。 - 低速档接近
low_kbps。 - 若切到
xoff,下载应明显停滞并在恢复后继续。
4.4 可能的上限与边界
升速未必线性。
原因包括电路级拥塞控制和源站限速。
但是降速应当非常稳定。
因为 Exit 的 token bucket 会直接约束发送节奏。
参考文档
- Tor Spec: Relay cells 中的 XON/XOFF 定义
- Tor Spec: Flow control 文档
- Proposal 324: RTT-based congestion control(包含 FlowCtrl/CC 的整体语境)
- Proposal 329: Traffic splitting(明确提到 Congestion control Stream XON/XOFF)