2026-02-05 组会

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 代码闭环

我关注的闭环是:

  1. Client 计算 stream 的 drain 速率
  • 文件:congestion_control_flow.c
  • 逻辑:根据 outbuf 的 drain 速度更新 stream->ewma_drain_rate
  1. 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);
  1. 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_rate
  • advertised_kbps:我塑形后的对外建议值

核心替换逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
uint32_t measured_kbps = stream->ewma_drain_rate;
uint32_t advertised_kbps = measured_kbps;

if (TO_CONN(stream)->type == CONN_TYPE_AP) {
/* client only */
advertised_kbps = flowctl_compute_advertised_kbps(measured_kbps);
}

xon_cell_set_kbps_ewma(&xon, advertised_kbps);

/* very important: store what we actually sent */
stream->ewma_rate_last_sent = advertised_kbps;

补充:强暂停用 XOFF。
我不使用 kbps=0 表示暂停。
当配置为暂停时,客户端直接发送 XOFF。

1
2
3
4
if (TO_CONN(stream)->type == CONN_TYPE_AP && flowctl_cfg.mode == FLOWCTL_MODE_XOFF) {
circuit_send_stream_xoff(stream);
return;
}

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
2
3
4
5
6
if (TO_CONN(stream)->type == CONN_TYPE_AP) {
if (stream->flowctl_epoch_last_sent != flowctl_epoch &&
flowctl_cfg.mode != FLOWCTL_MODE_OFF) {
return true;
}
}

当 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
2
3
mode=fixed
target_kbps=50000
log=1

方波模式(组会演示推荐):

1
2
3
4
5
mode=square
high_kbps=50000
low_kbps=500
period_ms=2000
log=1

强暂停:

1
2
mode=xoff
log=1

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
2
3
4
5
6
7
cat > /tmp/tor-flowctl.conf <<'EOF'
mode=square
high_kbps=50000
low_kbps=500
period_ms=2000
log=1
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)