con un clic
tunnel-audit
修改双向隧道转发、数据中继循环、空闲超时、关闭顺序、流量统计刷入逻辑时触发。
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
修改双向隧道转发、数据中继循环、空闲超时、关闭顺序、流量统计刷入逻辑时触发。
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
写入新模块文档、重构模块、或用户要求分析模块时触发。
Bug 修复或对接问题排查确认有效后,将经验记录到知识库。
修改 PMR 分配器、内存池配置、热路径容器、对象生命周期管理代码后触发。
编写或修改性能基准测试、分析 benchmark 结果、优化热路径性能时触发。
新增或修改 enable_shared_from_this 类、co_spawn 调用、shared_ptr 捕获的 lambda、co_await 后的成员访问时触发。
新增或修改原子操作、异步定时器、无锁通道、跨线程状态同步、CAS 竞争相关代码时触发。
| name | tunnel-audit |
| description | 修改双向隧道转发、数据中继循环、空闲超时、关闭顺序、流量统计刷入逻辑时触发。 |
双向隧道转发是代理服务器的核心操作——将客户端数据透传到目标服务器,反之亦然。虽然逻辑上只是"两个方向的读写循环",但 EOF 传播、部分写入、空闲超时、流量统计、关闭顺序等边界条件的正确处理,直接决定数据是否完整传输、资源是否正确释放。
两个方向的中继循环独立运行,任一方向退出时需要通知另一方向。但"退出"有多种含义:
每种退出方式的处理不同,且两个方向的退出可能同时发生、也可能先后发生。两个退出信号之间必须正确协调,不能丢失也不能重复处理。
当传输层使用加密(AEAD)时,数据被分成固定或可变的帧。部分写入(写操作只完成了一部分)可能切断加密帧的边界,导致对端收到不完整的帧。写入策略必须与加密层的帧语义兼容。
| 检查项 | 说明 |
|---|---|
| 半关闭语义 | 读 EOF 后,已缓冲的数据是否完整写入?读方向的 EOF 不意味着写方向可以立即关闭——已从对端读取但尚未发送给下游的数据必须先完成写入 |
| 双向退出的通知 | 任一方向的循环退出后,另一方向是否收到通知?通知方式(取消 transport、发送信号、设置标志)是否在所有场景下都能生效。如果只取消了一个方向,另一个方向可能永久挂起 |
| EOF 不等于错误 | 读 EOF 是正常的连接关闭,不应按错误处理。错误日志级别应为 info 或 debug,而非 error。将 EOF 视为错误会导致日志中充斥大量无意义的错误信息 |
| 双向 EOF 的处理 | 如果两个方向都收到了 EOF,连接是否正确关闭?是否存在"一个方向 EOF 后等待另一个方向,但另一个方向永远不会 EOF"的死锁 |
| 检查项 | 说明 |
|---|---|
| 部分写入的剩余处理 | 写入操作返回"部分完成"时,剩余数据如何处理?是否正确推进写入偏移并重试?如果简单丢弃剩余数据,会导致传输截断 |
| 部分写入与加密帧兼容 | 如果传输层使用帧加密(AEAD),部分写入是否可能切断帧边界?部分写入策略是否与加密层的"一次写入一帧"语义冲突。如果加密层要求整帧写入,中间转发层不应使用部分写入 |
| 写入失败的传播 | 写入失败(对端关闭、连接重置)是否正确传播到读方向?如果写入失败后读方向继续读取,读取的数据无处可去——是缓冲直到内存耗尽,还是通知读方向停止 |
| 缓冲区大小合理性 | 转发缓冲区大小是否足够?过小的缓冲区导致频繁的 read/write 系统调用,增加 CPU 开销。过大的缓冲区浪费内存。合理的下限是单次 TLS 记录的最大大小(约 16KB) |
| 检查项 | 说明 |
|---|---|
| 超时值可配置 | 空闲超时是否可配置?代理场景中不同用途的连接(HTTP 短连接 vs SSH 长隧道)需要不同的超时值。硬编码的超时值无法适应所有场景 |
| 双向空闲的定义 | 空闲超时是"两个方向都无数据"还是"任一方向无数据"?如果只监控一个方向的空闲,另一个方向可能有持续数据流——此时不应超时。正确语义通常是"两个方向都无数据超过阈值" |
| 超时后的关闭穿透 | 超时触发后的连接关闭是否穿透所有装饰器层?如果传输层被加密/预读/快照等装饰器包装,超时后的 cancel 必须穿透到最底层的 socket,否则连接可能不会被真正关闭 |
| 超时重置的时机 | 每次 read/write 后是否正确重置超时定时器?重置操作是否与 read/write 原子化(否则可能在重置前就超时) |
| 检查项 | 说明 |
|---|---|
| 逐字节累加 | 流量统计是每次 read/write 后精确累加,还是估算?估算(如乘以系数)会引入统计偏差,影响负载均衡和容量规划的准确性 |
| 所有退出路径的 flush | 统计数据是否在所有退出路径(正常完成、超时、错误、取消)都正确刷入上层?如果取消路径跳过了 flush,该连接的流量数据会丢失,导致统计偏低 |
| 方向正确性 | 上行(客户端→目标)和下行(目标→客户端)的流量是否正确对应?统计方向混淆会导致负载均衡做出错误的决策 |
| 统计与资源释放的顺序 | 流量统计的刷入是否在资源释放(transport 关闭、连接归还池)之前?如果先释放资源再刷统计,统计模块可能已经无法访问必要的数据 |
| 检查项 | 说明 |
|---|---|
| shutdown 后 close 的竞态 | 半关闭(发送 shutdown_write)后执行 close,对端可能在 shutdown 和 close 之间发送数据。如果 close 在 shutdown 的数据到达对端之前执行,对端会收到连接重置而非优雅关闭 |
| 多层装饰器的关闭穿透 | 关闭操作是否穿透所有装饰器层?加密装饰器需要先 SSL shutdown 再 TCP close;预读装饰器需要先释放缓冲区再关闭底层 transport。跳过任何一层都可能导致资源泄漏或数据丢失 |
| close 的幂等性 | 多次调用 close 是否安全?如果两个方向的退出都触发了 close,第二次调用不应崩溃或产生副作用。使用 exchange 标志保证"只执行一次"是标准模式 |
| 资源释放后的悬挂检查 | transport 释放(reset() shared_ptr)后,是否还有其他代码路径持有该 transport 的引用?释放后再访问是悬挂引用 |
// ❌ 读 EOF 后立即退出,未等待写方向完成
auto [ec, n] = co_await async_read_some(upstream, buffer);
if (ec == eof)
{
co_return; // 下游可能还有未写入的缓冲数据
}
// ✅ 读 EOF 后通知写方向,让写方向自然完成
auto [ec, n] = co_await async_read_some(upstream, buffer);
if (ec == eof)
{
write_side->notify_eof(); // 通知写方向:不再有新数据
co_await write_side->complete(); // 等待写方向完成
}
// ❌ 只重置读方向的定时器 — 写方向持续有数据但读方向空闲时不应超时
co_await async_read_some(...);
idle_timer.expires_after(timeout); // 每次读取后重置
// ✅ 两个方向都重置 — 只有两个方向都空闲时才超时
// 读循环
co_await async_read_some(...);
idle_signal_.store(true);
// 写循环
co_await async_write_some(...);
idle_signal_.store(true);
// 独立的超时检查协程检查两个方向的活跃状态
// ❌ 取消路径不刷入统计
void on_cancel()
{
transport_->close();
// 跳过了 flush_traffic()
}
// ✅ 所有路径都刷入统计
void on_cancel()
{
flush_traffic(read_bytes_, written_bytes_);
transport_->close();
}
// ❌ 只关闭最底层 socket — 装饰器层未正确清理
void close_connection()
{
socket_.close(); // SSL 层未 shutdown,可能导致对端收到 RST
}
// ✅ 按层关闭,从外到内
void close_connection()
{
if (ssl_layer) ssl_layer->shutdown();
socket_.close();
}
// ❌ 两个方向都调用 close — 第二次可能访问已释放资源
void close_transport()
{
transport_->close();
transport_.reset(); // 释放
// 如果另一个方向再次调用,transport_ 为 null → 崩溃
}
// ✅ 使用标志保证幂等
void close_transport()
{
if (closed_.exchange(true)) return; // 第二次调用直接返回
transport_->close();
transport_.reset();
}
coroutine-audit 覆盖了协程取消、异步等待、deadline 竞速维度co-lifecycle-audit 覆盖了 shared_ptr 保活、co_await 后引用失效维度concurrency-audit 覆盖了定时器生命周期、通道关闭语义维度error-chain-audit 覆盖了错误传播、无人监听任务的异常安全维度probe-audit 覆盖了连接关闭行为与标准服务器一致性维度