fix(socks5): 隧道转发阶段补 idle timeout,防慢客户端耗尽 fd
之前 _forward() 直接 await src.read(TUNNEL_CHUNK),无 timeout。
已通过 SOCKS5 握手的客户端可以一直挂着不发数据,占住 fd 不放,
直到 LimitNOFILE=65535 才被内核拒。
握手/认证/请求阶段已经全部用 asyncio.wait_for + config.timeout,
但同一个 timeout 字段没覆盖 tunnel 阶段,语义不一致。
修复:
engine/server.py:381-407 _forward 每次 read 包 wait_for, 触发
TimeoutError 时 break 走 _tunnel 的 finally
清理 (关闭 writer, 取消对端 forward 任务)
验证 (tests/smoke_tunnel_timeout.py):
- 客户端只握手不进 tunnel, 服务端在 config.timeout 秒后关闭
- 反向: git stash 掉修复, 客户端永远不被关闭, 服务端报
'Task was destroyed but it is pending' — 证明修复前后行为差
异真实存在
models.Instance.timeout 注释: 说明该字段覆盖 tunnel 阶段
This commit is contained in:
+16
-2
@@ -373,13 +373,27 @@ class ConnectionHandler:
|
||||
await self._record_stats()
|
||||
|
||||
async def _forward(self, src, dst, direction, speed_limit_mbps, user):
|
||||
"""单向转发,带限速。"""
|
||||
"""单向转发,带限速与 idle timeout。
|
||||
|
||||
timeout 行为:握手/认证/请求阶段和这里都共用 self.config.timeout。
|
||||
客户端在 tunnel 阶段不发数据超过 timeout 秒,会被服务端主动关闭,
|
||||
释放 fd + 关闭对端 writer + 触发 _tunnel 的 finally 清理。
|
||||
"""
|
||||
if src is None or dst is None:
|
||||
return
|
||||
total_transferred = 0
|
||||
# 读超时短于 config.timeout 时宁可提前踢,不放过慢客户端
|
||||
read_timeout = max(1, int(self.config.timeout))
|
||||
try:
|
||||
while True:
|
||||
data = await src.read(TUNNEL_CHUNK)
|
||||
try:
|
||||
data = await asyncio.wait_for(
|
||||
src.read(TUNNEL_CHUNK), timeout=read_timeout
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
log.info("[%s] %s idle timeout (%ds), closing tunnel",
|
||||
self.conn_id, direction, read_timeout)
|
||||
break
|
||||
if not data:
|
||||
break
|
||||
|
||||
|
||||
Reference in New Issue
Block a user