بنقرة واحدة
tunnel-audit
修改双向隧道转发、数据中继循环、空闲超时、关闭顺序、流量统计刷入逻辑时触发。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
修改双向隧道转发、数据中继循环、空闲超时、关闭顺序、流量统计刷入逻辑时触发。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف 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 覆盖了连接关闭行为与标准服务器一致性维度