fix: SOCKS5 隧道修复 - 子线程事件循环兼容性
1. 去掉 asyncio.wait_for 包装 open_connection,改用直接调用 2. ConnectionHandler 增加 flask_app 参数,数据库操作使用 app context 3. InstanceManager 传递 flask_app 到 Socks5Server 4. 修复 _reply 中 writer.write 不需要 await 的问题 验证: - 直连测试: HTTP/1.1 200 OK (ifconfig.me) - 端口监听: 10800 正常工作
This commit is contained in:
@@ -44,7 +44,7 @@ def create_app():
|
|||||||
app.user_service = user_service
|
app.user_service = user_service
|
||||||
|
|
||||||
# 实例管理器
|
# 实例管理器
|
||||||
instance_manager = InstanceManager(user_service)
|
instance_manager = InstanceManager(user_service, flask_app=app)
|
||||||
app.instance_manager = instance_manager
|
app.instance_manager = instance_manager
|
||||||
|
|
||||||
# 备份目录
|
# 备份目录
|
||||||
|
|||||||
+7
-4
@@ -31,9 +31,10 @@ class InstanceConfig:
|
|||||||
class Socks5Server:
|
class Socks5Server:
|
||||||
"""单个 SOCKS5 服务器的 asyncio 事件循环。"""
|
"""单个 SOCKS5 服务器的 asyncio 事件循环。"""
|
||||||
|
|
||||||
def __init__(self, config: InstanceConfig, user_service):
|
def __init__(self, config: InstanceConfig, user_service, flask_app=None):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.user_service = user_service
|
self.user_service = user_service
|
||||||
|
self.flask_app = flask_app
|
||||||
self.server = None
|
self.server = None
|
||||||
self.loop = None
|
self.loop = None
|
||||||
self.thread = None
|
self.thread = None
|
||||||
@@ -94,6 +95,7 @@ class Socks5Server:
|
|||||||
self._active_connections += 1
|
self._active_connections += 1
|
||||||
handler = ConnectionHandler(
|
handler = ConnectionHandler(
|
||||||
reader, writer, self.config, self.user_service,
|
reader, writer, self.config, self.user_service,
|
||||||
|
flask_app=self.flask_app,
|
||||||
on_close_cb=self._on_connection_close
|
on_close_cb=self._on_connection_close
|
||||||
)
|
)
|
||||||
await handler.handle()
|
await handler.handle()
|
||||||
@@ -113,8 +115,9 @@ import time # noqa: E402
|
|||||||
class InstanceManager:
|
class InstanceManager:
|
||||||
"""管理所有 SOCKS5 实例。"""
|
"""管理所有 SOCKS5 实例。"""
|
||||||
|
|
||||||
def __init__(self, user_service):
|
def __init__(self, user_service, flask_app=None):
|
||||||
self.user_service = user_service
|
self.user_service = user_service
|
||||||
|
self.flask_app = flask_app
|
||||||
self._instances = {} # name -> Socks5Server
|
self._instances = {} # name -> Socks5Server
|
||||||
self._config_cache = {} # name -> InstanceConfig
|
self._config_cache = {} # name -> InstanceConfig
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
@@ -151,7 +154,7 @@ class InstanceManager:
|
|||||||
# 启动缺失的实例
|
# 启动缺失的实例
|
||||||
for name in current_names - running_names:
|
for name in current_names - running_names:
|
||||||
cfg = self._config_cache[name]
|
cfg = self._config_cache[name]
|
||||||
srv = Socks5Server(cfg, self.user_service)
|
srv = Socks5Server(cfg, self.user_service, self.flask_app)
|
||||||
srv.start()
|
srv.start()
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._instances[name] = srv
|
self._instances[name] = srv
|
||||||
@@ -192,7 +195,7 @@ class InstanceManager:
|
|||||||
max_concurrent=inst.max_concurrent,
|
max_concurrent=inst.max_concurrent,
|
||||||
)
|
)
|
||||||
self._config_cache[inst.name] = cfg
|
self._config_cache[inst.name] = cfg
|
||||||
srv = Socks5Server(cfg, self.user_service)
|
srv = Socks5Server(cfg, self.user_service, self.flask_app)
|
||||||
srv.start()
|
srv.start()
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._instances[inst.name] = srv
|
self._instances[inst.name] = srv
|
||||||
|
|||||||
+33
-31
@@ -4,7 +4,6 @@ import logging
|
|||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
import socket as _socket
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
@@ -43,11 +42,13 @@ TUNNEL_CHUNK = 65536
|
|||||||
class ConnectionHandler:
|
class ConnectionHandler:
|
||||||
"""单个 SOCKS5 连接的处理器。"""
|
"""单个 SOCKS5 连接的处理器。"""
|
||||||
|
|
||||||
def __init__(self, reader, writer, instance_config, user_service, on_close_cb):
|
def __init__(self, reader, writer, instance_config, user_service,
|
||||||
|
flask_app=None, on_close_cb=None):
|
||||||
self.reader = reader
|
self.reader = reader
|
||||||
self.writer = writer
|
self.writer = writer
|
||||||
self.config = instance_config
|
self.config = instance_config
|
||||||
self.user_service = user_service
|
self.user_service = user_service
|
||||||
|
self.flask_app = flask_app
|
||||||
self.on_close = on_close_cb
|
self.on_close = on_close_cb
|
||||||
|
|
||||||
self.username = None
|
self.username = None
|
||||||
@@ -274,12 +275,9 @@ class ConnectionHandler:
|
|||||||
|
|
||||||
# 连接目标
|
# 连接目标
|
||||||
try:
|
try:
|
||||||
# 注意:不用 asyncio.wait_for 包装 open_connection,
|
# 直接用 open_connection,不预连接 socket
|
||||||
# 在子线程事件循环中 wait_for 会导致连接成功后读取被取消
|
# 在子线程事件循环中 asyncio.wait_for 会导致读取被取消
|
||||||
sock = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM)
|
reader, writer = await asyncio.open_connection(addr, port)
|
||||||
sock.settimeout(self.config.timeout)
|
|
||||||
sock.connect((addr, port))
|
|
||||||
reader, writer = await asyncio.open_connection(sock=sock)
|
|
||||||
except (OSError, Exception) as e:
|
except (OSError, Exception) as e:
|
||||||
log.warning("[%s] 无法连接 %s:%d: %s", self.conn_id, addr, port, e)
|
log.warning("[%s] 无法连接 %s:%d: %s", self.conn_id, addr, port, e)
|
||||||
rep = REP_CONN_REFUSED if "refused" in str(e).lower() else REP_HOST_UNREACHABLE
|
rep = REP_CONN_REFUSED if "refused" in str(e).lower() else REP_HOST_UNREACHABLE
|
||||||
@@ -372,19 +370,21 @@ class ConnectionHandler:
|
|||||||
async def _record_connection(self, state="active"):
|
async def _record_connection(self, state="active"):
|
||||||
"""记录连接到数据库(异步)。"""
|
"""记录连接到数据库(异步)。"""
|
||||||
try:
|
try:
|
||||||
models.db.session.add(models.Connection(
|
ctx = self.flask_app.app_context() if self.flask_app else None
|
||||||
id=self.conn_id,
|
with ctx:
|
||||||
instance_id=self.config.instance_id,
|
models.db.session.add(models.Connection(
|
||||||
username=self.username,
|
id=self.conn_id,
|
||||||
src_ip=self.src_ip,
|
instance_id=self.config.instance_id,
|
||||||
src_port=self.src_port,
|
username=self.username,
|
||||||
dst_addr=self.dst_addr,
|
src_ip=self.src_ip,
|
||||||
dst_port=self.dst_port,
|
src_port=self.src_port,
|
||||||
protocol="SOCKS5",
|
dst_addr=self.dst_addr,
|
||||||
state=state,
|
dst_port=self.dst_port,
|
||||||
started_at=datetime.now(timezone.utc),
|
protocol="SOCKS5",
|
||||||
))
|
state=state,
|
||||||
models.db.session.commit()
|
started_at=datetime.now(timezone.utc),
|
||||||
|
))
|
||||||
|
models.db.session.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("记录连接失败: %s", e)
|
log.error("记录连接失败: %s", e)
|
||||||
|
|
||||||
@@ -392,17 +392,19 @@ class ConnectionHandler:
|
|||||||
"""记录最终连接统计。"""
|
"""记录最终连接统计。"""
|
||||||
duration = time.time() - self.start_time
|
duration = time.time() - self.start_time
|
||||||
try:
|
try:
|
||||||
conn = models.db.session.query(models.Connection).filter_by(id=self.conn_id).first()
|
ctx = self.flask_app.app_context() if self.flask_app else None
|
||||||
if conn:
|
with ctx:
|
||||||
conn.state = "closed"
|
conn = models.db.session.query(models.Connection).filter_by(id=self.conn_id).first()
|
||||||
conn.ended_at = datetime.now(timezone.utc)
|
if conn:
|
||||||
conn.bytes_in = self.bytes_in
|
conn.state = "closed"
|
||||||
conn.bytes_out = self.bytes_out
|
conn.ended_at = datetime.now(timezone.utc)
|
||||||
models.db.session.commit()
|
conn.bytes_in = self.bytes_in
|
||||||
|
conn.bytes_out = self.bytes_out
|
||||||
|
models.db.session.commit()
|
||||||
|
|
||||||
# 更新用户流量
|
# 更新用户流量
|
||||||
if self.username and (self.bytes_in or self.bytes_out):
|
if self.username and (self.bytes_in or self.bytes_out):
|
||||||
await self.user_service.add_traffic(self.username, self.bytes_in, self.bytes_out)
|
await self.user_service.add_traffic(self.username, self.bytes_in, self.bytes_out)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("记录统计失败: %s", e)
|
log.error("记录统计失败: %s", e)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user