Smail/test/tools/runtime_sql_bridge.py
smail 22d4a84db7
Some checks are pending
Security Checks / security (push) Waiting to run
补充单库首次安装和邮件协议实机验收器
2026-09-21 00:59:59 +08:00

65 lines
2.5 KiB
Python

"""只向隔离网络提供单个本机数据库端口,不向宿主网络新增监听端口。"""
from contextlib import ExitStack
import os
from pathlib import Path
import select
import socket
import socketserver
import threading
class DatabaseBridge:
def __init__(self, pid, port):
assert isinstance(pid, int) and pid > 1
assert port in (3306, 5432)
namespace = Path('/proc') / str(pid) / 'ns/net'
assert namespace.readlink() != Path('/proc/self/ns/net').readlink()
limit = threading.BoundedSemaphore(24)
class Handler(socketserver.BaseRequestHandler):
def handle(self):
if not limit.acquire(blocking=False):
return
try:
with socket.create_connection(('127.0.0.1', port), timeout=15) as upstream:
streams = (self.request, upstream)
for stream in streams:
stream.settimeout(30)
while True:
ready, _, _ = select.select(streams, [], [], 30)
if not ready:
return
for source in ready:
data = source.recv(65536)
if not data:
return
target = upstream if source is self.request else self.request
target.sendall(data)
except (OSError, TimeoutError):
return
finally:
limit.release()
class Server(socketserver.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = False
with ExitStack() as stack:
host = stack.enter_context(open('/proc/self/ns/net', 'rb'))
isolated = stack.enter_context(open(namespace, 'rb'))
try:
os.setns(isolated.fileno(), 0)
self.server = Server(('127.0.0.1', port), Handler)
finally:
os.setns(host.fileno(), 0)
# 监听套接字属于隔离网络,后续工作线程在恢复后的宿主网络连接数据库。
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
self.thread.start()
def close(self):
self.server.shutdown()
self.server.server_close()
self.thread.join(timeout=5)
assert not self.thread.is_alive(), '数据库验收桥接未停止'