一套跑在 Debian 13 (trixie) 上的软路由管理系统,提供 43 个功能页面的 Web 管理面板:WAN 接入、PPPoE 多拨、DHCP/DNS、IPv6、防火墙、QoS 限速、 访问控制、DDNS、文件共享、Docker 管理等。 核心设计原则:不重新实现任何协议 PPPoE 交给 ppp,DHCP 交给 dnsmasq,IPv6 RA 交给 radvd,NAT 交给 nftables。 Web 面板只做三件事:渲染原生配置文件 → 语法预检 → 原子写入并 reload。 面板删掉后,系统仍是一台配置完好的 Debian 路由器。 安全默认值 · 安装后不接管网络:不改默认路由、不开 IP 转发、不启动 DHCP 服务 · 所有改网络的动作都需要用户在面板上亲手点「应用」 · 避开 5900 端口,不触碰 VNC / X 会话 · 配置写入前先做语法预检,失败则回滚 架构 · backend/ Python 3 标准库,零第三方依赖(9 个模块) · web/ 原生 JS/CSS,无构建步骤 · 常驻 helper 通过 Unix socket 提供 75 个动作,socket 不可用时回退 subprocess · 鉴权走 X-Token 请求头 打包 · packaging/build-deb.sh → drouter_1.0.0_all.deb(含 6 个 systemd 单元) · scripts/make-offline-deps.sh → 236 个依赖 .deb + 本地仓库索引 · packaging/build-offline-bundle.sh → 自包含离线包,目标机零外网可装 · packaging/build-docker.sh → 容器镜像(体验用) 测试 40 套单元测试 + 3 个前端检查脚本(菜单映射 / 离线渲染 / 手机适配), 全部挂在 devtools/sync.sh 的预检链里。 许可:MIT
100 lines
4.1 KiB
Python
100 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""分层定位终端长轮询为什么没挂起:shelld → helper → helpd → HTTP。"""
|
|
import json
|
|
import os
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
SHELL_SOCK = '/run/drouter/shell.sock'
|
|
HELPD_SOCK = '/run/drouter/helper.sock'
|
|
|
|
|
|
def sock_call(path, req, timeout=30):
|
|
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
s.settimeout(timeout)
|
|
s.connect(path)
|
|
s.sendall((json.dumps(req) + '\n').encode())
|
|
buf = b''
|
|
while not buf.endswith(b'\n'):
|
|
c = s.recv(65536)
|
|
if not c:
|
|
break
|
|
buf += c
|
|
s.close()
|
|
return json.loads(buf.decode('utf-8', 'replace'))
|
|
|
|
|
|
for p in (SHELL_SOCK, HELPD_SOCK):
|
|
print('%-28s 存在=%s' % (p, os.path.exists(p)))
|
|
|
|
print('\n=== 1) 直连 shelld ===')
|
|
r = sock_call(SHELL_SOCK, {'op': 'connect', 'user': 'root', 'cols': 100, 'rows': 30})
|
|
sid = (r.get('data') or {}).get('sid')
|
|
print('connect ok=%s sid=%s' % (r.get('ok'), sid))
|
|
if sid:
|
|
for _ in range(5):
|
|
rr = sock_call(SHELL_SOCK, {'op': 'read', 'sid': sid, 'wait': 0})
|
|
if not ((rr.get('data') or {}).get('data') or ''):
|
|
break
|
|
t0 = time.time()
|
|
rr = sock_call(SHELL_SOCK, {'op': 'read', 'sid': sid, 'wait': 1.2})
|
|
print('wait=1.2 空读耗时 %.0f ms ok=%s data=%r'
|
|
% ((time.time() - t0) * 1000, rr.get('ok'),
|
|
((rr.get('data') or {}).get('data') or '')[:40]))
|
|
sock_call(SHELL_SOCK, {'op': 'disconnect', 'sid': sid})
|
|
|
|
print('\n=== 2) 走 helper 子进程 ===')
|
|
r = json.loads(subprocess.run(
|
|
['sudo', '-n', '/usr/bin/python3', '/opt/drouter/backend/drouter-helper.py',
|
|
'webshell', json.dumps({'op': 'connect', 'cols': 100, 'rows': 30})],
|
|
capture_output=True, text=True, timeout=60).stdout.strip().splitlines()[-1])
|
|
sid = (r.get('data') or {}).get('sid')
|
|
print('connect ok=%s sid=%s' % (r.get('ok'), sid))
|
|
if sid:
|
|
for _ in range(5):
|
|
out = subprocess.run(
|
|
['sudo', '-n', '/usr/bin/python3', '/opt/drouter/backend/drouter-helper.py',
|
|
'webshell', json.dumps({'op': 'read', 'sid': sid, 'wait': 0})],
|
|
capture_output=True, text=True, timeout=60).stdout.strip().splitlines()[-1]
|
|
if not ((json.loads(out).get('data') or {}).get('data') or ''):
|
|
break
|
|
t0 = time.time()
|
|
out = subprocess.run(
|
|
['sudo', '-n', '/usr/bin/python3', '/opt/drouter/backend/drouter-helper.py',
|
|
'webshell', json.dumps({'op': 'read', 'sid': sid, 'wait': 1.2})],
|
|
capture_output=True, text=True, timeout=60).stdout.strip().splitlines()[-1]
|
|
print('wait=1.2 空读耗时 %.0f ms' % ((time.time() - t0) * 1000))
|
|
subprocess.run(['sudo', '-n', '/usr/bin/python3',
|
|
'/opt/drouter/backend/drouter-helper.py', 'webshell',
|
|
json.dumps({'op': 'disconnect', 'sid': sid})],
|
|
capture_output=True, text=True, timeout=60)
|
|
|
|
print('\n=== 3) 走 helpd 常驻守护 ===')
|
|
if os.path.exists(HELPD_SOCK):
|
|
r = sock_call(HELPD_SOCK, {'action': 'webshell',
|
|
'payload': {'op': 'connect', 'cols': 100, 'rows': 30}})
|
|
sid = (r.get('data') or {}).get('sid')
|
|
print('connect ok=%s sid=%s' % (r.get('ok'), sid))
|
|
if sid:
|
|
for _ in range(5):
|
|
rr = sock_call(HELPD_SOCK, {'action': 'webshell',
|
|
'payload': {'op': 'read', 'sid': sid, 'wait': 0}})
|
|
if not ((rr.get('data') or {}).get('data') or ''):
|
|
break
|
|
t0 = time.time()
|
|
rr = sock_call(HELPD_SOCK, {'action': 'webshell',
|
|
'payload': {'op': 'read', 'sid': sid, 'wait': 1.2}})
|
|
print('wait=1.2 空读耗时 %.0f ms' % ((time.time() - t0) * 1000))
|
|
sock_call(HELPD_SOCK, {'action': 'webshell',
|
|
'payload': {'op': 'disconnect', 'sid': sid}})
|
|
else:
|
|
print('helpd socket 不存在')
|
|
|
|
print('\n=== 4) helpd 是否并发处理(单线程会串行阻塞)===')
|
|
print('检查 drouter-helpd 主线程模型:')
|
|
os.system("grep -n 'Threading\|Thread\|serve_forever\|fork' "
|
|
"/opt/drouter/backend/drouter-helpd.py | head -10")
|