Skip to content

v1.0bug fix

vanyongqi edited this page Nov 23, 2025 · 1 revision

gotunnel Bug Fixes (English Version)

This document summarizes real and critical bug cases faced during gotunnel development and deployment: their root causes, code changes, and why such fixes are correct.


1. Data channel prematurely closed, causing HTTP 502

Symptoms

  • In the example, client exports 8080, server listens public 10086.
  • "Data channel established (e.g., 17ms)" is shown, but curl http://server-ip:10086 returns HTTP 502.
  • Client logs [DEBUG][client] Starting relay: local 8080 immediately followed by [DEBUG][client] Relay finished: local 8080 — relay did not persist for the user's actual HTTP request.

Causes

  • In handleControlConn, using defer conn.Close() on all incoming TCP — including data_channel — causes the connection to close as soon as the handler returns.
  • But for data_channel registration, the connection must live on (it's passed into a channel for relay).
  • RelayConn needs a live TCP stream, not a prematurely closed one.

How to Fix

  • Remove the defer for data_channel path; leave connection lifecycle entirely to RelayConn.
  • Only close in error/validation-paths (e.g., mapping/DataChan full), not in the happy path.

Why This Works

  • Ownership: who uses the connection, must close it. RelayConn manages the lifecycle for data_channel.
  • Ensures end-to-end traffic actually completes before closure, which is critical for HTTP and SSH semantics.

2. Data channel timeout too short (causes failed channel setup)

Symptoms

  • Server frequently logs: [WARN][server] data channel connection timeout: port 10086
  • Under slow/unstable networks, data channel is not established in time.

Causes

  • listenAndForwardWithStop timeout was set to only 10s.
  • Real-world sequence needs server notify, client creates a new TCP, handshake, registration.
  • In fast network, is OK; in higher-latency/proxy/WANs, may be too short.

How to Fix

  • Bumped timeout from 10s → 60s.
  • Log and monitor waiting time.

Why This Works

  • 60s is practical for both normal and slightly slow networks— prevents false timeouts while still enforcing a reasonable resource limit.

3. Logging of duration parameters (ms) fails in some cases

Symptoms

  • Log lines show [DEBUG][client] Data channel dialed: <no value>ms instead of the real value.

Causes

  • Logger template handling did not support int64 (from Milliseconds()) everywhere.
  • Type assertion chain missed several cases.

How to Fix

  • Add explicit int64/int support to argsToMap.
  • Ensure any ms value, int64, int is mapped to the correct template.

Why This Works

  • Logging is much more robust and informative — can directly trace transit/relay delays.

Fix Result

All these changes enable stable, long-lived, fully bi-directional relay for HTTP/SSH and other protocols in all tested scenarios. End-to-end debugging, troubleshooting, and performance tracking have also improved.


gotunnel 典型问题与修复记录

本文档总结了 gotunnel 开发及实际部署过程中遇到的关键错误案例、产生原因、具体修复方式及设计思考,供后续开发和运维参考。


1. 数据通道提前关闭导致 HTTP 502(关键)

问题现象

  • example 场景,服务端映射端口 10086,客户端本地端口 8080。
  • 客户端与服务端均提示“数据通道已建立(如 17ms)”,但通过公网 http://server-ip:10086 访问,浏览器返回 502 Bad Gateway。
  • 客户端日志 [DEBUG][client] 开始转发数据: 本地端口 8080 后瞬间又 [DEBUG][client] 转发完成: 本地端口 8080,说明连接未正常保持直到 HTTP 响应结束。

产生原因

  • 服务端 cmd/server/main.gohandleControlConn 处理数据通道注册时,误用 defer conn.Close()
  • data_channel 类型注册后直接 return,导致 defer 立即关闭了 TCP 连接。
  • 此连接刚放入 mapping.DataChan,还未来得及被 RelayConn 正式转发,RelayConn 拿到的就是一个“已关闭”连接。

修复方式

  • 移除 handleControlConn 针对数据通道分支的 defer conn.Close(),只对控制通道保留(即普通注册/心跳/业务处理流程使用)。
  • 只在 channel 放入失败、校验失败等异常路径关闭数据通道连接,正常数据通道生命周期交给 RelayConn 统一释放。

为什么这样修

  • 谁用连接,谁关连接。RelayConn 全权负责数据通道的释放,handleControlConn 只关心控制通道的闭环。
  • 保证整个转发链路的连接状态始终有效,数据流互通不中断,否则 http/tcp 请求立刻被 reset/close。

2. 数据通道超时时间过短,导致建立失败

问题现象

  • 服务端日志 [WARN][server] 等待数据通道连接超时: 端口 10086
  • 网络有波动或客户端反应稍慢时,大概率触发超时断开。

产生原因

  • 服务端 listenAndForwardWithStop/select 触发数据通道等待:原超时硬编码为 10 秒钟。
  • 真实链路建立涉及多次网络往返:服务端通知客户端、客户端新建 TCP 连接、注册、握手等环节。
  • 公网延迟波动大,10 秒不足以容忍极端慢场景。

修复方式

  • 超时从 10 秒提升到 60 秒。
  • 并增加统计日志,记录实际建立通道所用耗时。

为什么这样修

  • 综合考虑普通网络与弱网环境下的业务可用性。
  • 60 秒不会导致资源占用失控,同时基本保证合理链路都能建立。
  • 日志监控便于实测优化后续参数。

3. 日志耗时参数未显示,便于排查实际链路瓶颈

问题现象

  • 日志格式如 [DEBUG][client] 数据通道连接建立: <no value>ms
  • 应显示实际建立所用时间,方便链路瓶颈定位,否则都为 不利调试。

产生原因

  • pkg/log/log.go 的 argsToMap 函数未全面兼容 int64 类型(Milliseconds 返回 int64)。
  • 类型转换链不兼容,导致日志模板不能正确渲染参数。

修复方式

  • 增加对 int64/int 类型的判断和 value fallback 兼容逻辑。
  • 所有涉及 .Milliseconds() 的日志结构都应调用为 ms 格式数字。

为什么这样修

  • 日志参数需要类型全兼容,不能只支持 int,否则表现为模板渲染失效。
  • 实际数值有助于性能调优与问题定位。

修复效果总结:

  • 经过上述修复,example 模式下 http/ssh/端口转发,均能稳定长时间转发、页面连续访问不断游,客户端和服务端日志可完整跟踪端到端通路及性能信息。