一套跑在 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
67 lines
2.7 KiB
Python
67 lines
2.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""DDNS 纯逻辑测试:公网/私网判定 + 四种组合 + 记录名拼接。"""
|
|
import re, sys, io
|
|
|
|
SRC = r"C:/Users/lyrz-pve-win10/WorkBuddy/2026-09-27-19-57-00/router-build/backend/drouter-helper.py"
|
|
src = io.open(SRC, encoding='utf-8').read()
|
|
|
|
def grab(name):
|
|
m = re.search(r'^def %s\(.*?(?=\n(?:def |[A-Z_]+ *=|# ---|class ))'
|
|
% re.escape(name), src, re.S | re.M)
|
|
if not m:
|
|
raise SystemExit('NOT FOUND: ' + name)
|
|
return m.group(0)
|
|
|
|
def grab_var(name):
|
|
m = re.search(r'^%s = \{.*?^\}' % re.escape(name), src, re.S | re.M)
|
|
if not m:
|
|
m = re.search(r'^%s = \[.*?^\]' % re.escape(name), src, re.S | re.M)
|
|
if not m:
|
|
raise SystemExit('NOT FOUND VAR: ' + name)
|
|
return m.group(0)
|
|
|
|
NS = {'re': re}
|
|
exec(grab('_is_private_v4'), NS)
|
|
exec(grab('_is_global_v6'), NS)
|
|
exec(grab('_ddns_record_name'), NS)
|
|
exec(grab_var('PUBIP_COMBO_NOTE'), NS)
|
|
|
|
P, G, R, NOTE = NS['_is_private_v4'], NS['_is_global_v6'], NS['_ddns_record_name'], NS['PUBIP_COMBO_NOTE']
|
|
|
|
fails = 0
|
|
def chk(label, got, want):
|
|
global fails
|
|
ok = got == want
|
|
if not ok: fails += 1
|
|
print('[%s] %-46s got=%-6s want=%s' % ('OK' if ok else 'FAIL', label, got, want))
|
|
|
|
print('=== IPv4 私网/公网判定 ===')
|
|
for ip, want in [('192.168.1.1', True), ('10.0.0.1', True), ('172.16.5.5', True),
|
|
('172.32.5.5', False), ('100.64.0.1', True), ('100.128.0.1', False),
|
|
('169.254.1.1', True), ('127.0.0.1', True), ('8.8.8.8', False),
|
|
('1.1.1.1', False), ('223.5.5.5', False), ('224.0.0.1', True),
|
|
('116.25.100.7', False)]:
|
|
chk('v4 ' + ip, P(ip), want)
|
|
|
|
print('\n=== IPv6 全球单播判定 ===')
|
|
for ip, want in [('2408:8207:1234::1', True), ('2001:4860:4860::8888', True),
|
|
('fe80::1', False), ('fc00::1', False), ('fd12:3456::1', False),
|
|
('ff02::1', False), ('::1', False), ('2400:3200::1', True),
|
|
('240e:3b7:1234::abcd', True)]:
|
|
chk('v6 ' + ip, G(ip), want)
|
|
|
|
print('\n=== DDNS 记录名拼接 ===')
|
|
for cfg, want in [({'subdomain': 'home', 'domain': 'example.com'}, 'home.example.com'),
|
|
({'subdomain': '', 'domain': 'example.com'}, 'example.com'),
|
|
({'subdomain': '@', 'domain': 'example.com'}, 'example.com'),
|
|
({'subdomain': '.www.', 'domain': 'a.cn'}, 'www.a.cn')]:
|
|
chk('rec ' + str(cfg), R(cfg), want)
|
|
|
|
print('\n=== 四种组合说明齐全性 ===')
|
|
for k in ('both', 'v4only', 'v6only', 'neither'):
|
|
has = k in NOTE and NOTE[k].get('conclusion') and len(NOTE[k].get('advice') or []) >= 3
|
|
chk('combo ' + k, bool(has), True)
|
|
|
|
print('\n结果: %s' % ('全部通过' if fails == 0 else '%d 项失败' % fails))
|
|
sys.exit(1 if fails else 0)
|