一套跑在 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
69 lines
3.5 KiB
Python
69 lines
3.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""端口转发 / DMZ 渲染测试。"""
|
|
import sys
|
|
sys.path.insert(0, r"C:/Users/lyrz-pve-win10/WorkBuddy/2026-09-27-19-57-00/router-build/backend")
|
|
import render
|
|
|
|
fails = 0
|
|
def chk(label, cond, extra=''):
|
|
global fails
|
|
if not cond: fails += 1
|
|
print('[%s] %s %s' % ('OK' if cond else 'FAIL', label, extra))
|
|
|
|
cfg4 = {'wan_iface': 'ppp0', 'lan_ifaces': ['ens18'], 'mss_clamp': True, 'mss': 1452,
|
|
'portfwd': {'enable': True, 'rules': [
|
|
{'name': '群晖DSM', 'proto': 'tcp', 'ext_port': 5000, 'int_ip': '192.168.7.10', 'int_port': 5000, 'enable': True},
|
|
{'name': '游戏', 'proto': 'tcp/udp', 'ext_port': 27015, 'int_ip': '192.168.7.20', 'int_port': 27016, 'enable': True},
|
|
{'name': '禁用', 'proto': 'tcp', 'ext_port': 8080, 'int_ip': '192.168.7.30', 'int_port': 8080, 'enable': False},
|
|
], 'dmz': {'enable': False, 'host': ''}}}
|
|
o4 = render.render_nft('v4', cfg4)
|
|
chk('IPv4 DNAT 同端口省略端口号', 'dnat to 192.168.7.10 comment' in o4)
|
|
chk('IPv4 DNAT 异端口带端口号', 'dnat to 192.168.7.20:27016' in o4)
|
|
chk('IPv4 禁用规则不生成', '192.168.7.30' not in o4)
|
|
chk('IPv4 生成 nat_pre/dstnat 链', 'nat_pre' in o4 and 'priority dstnat' in o4)
|
|
chk('IPv4 放通规则在 forward 链', 'tcp dport 5000 ip daddr 192.168.7.10 accept' in o4)
|
|
chk('IPv4 tcp/udp 展开两条', o4.count('dport 27015') >= 2)
|
|
|
|
cfg6 = {'wan_iface': 'ppp0', 'lan_ifaces': ['ens18'],
|
|
'portfwd': {'enable': True, 'rules': [
|
|
{'name': 'NAS', 'proto': 'tcp', 'ext_port': 8443, 'int_ip': '2408:8207:1234::10', 'int_port': 443, 'enable': True}],
|
|
'dmz': {'enable': True, 'host': '2408:8207:1234::99'}}}
|
|
o6 = render.render_nft('v6', cfg6)
|
|
chk('IPv6 DNAT 方括号包裹', 'dnat to [2408:8207:1234::10]:443' in o6)
|
|
chk('IPv6 DMZ 全端口映射', 'DMZ 全端口映射' in o6)
|
|
chk('IPv6 DMZ 放通', 'ip6 daddr 2408:8207:1234::99 accept' in o6)
|
|
chk('IPv6 放通用 ip6 关键字', 'tcp dport 443 ip6 daddr 2408:8207:1234::10 accept' in o6)
|
|
|
|
# 关闭时不应有 nat_pre
|
|
o4off = render.render_nft('v4', {'wan_iface': 'ppp0', 'lan_ifaces': ['ens18'],
|
|
'portfwd': {'enable': False, 'rules': [], 'dmz': {'enable': False, 'host': ''}}})
|
|
chk('关闭时无 nat_pre 链', 'nat_pre' not in o4off)
|
|
|
|
# 校验错误
|
|
for bad, want in [(('999.1.1.1',), 'IPv4'), (('2408::gg',), 'IPv6')]:
|
|
try:
|
|
render.render_nft('v4' if want == 'IPv4' else 'v6', {'wan_iface': 'ppp0', 'lan_ifaces': ['ens18'],
|
|
'portfwd': {'enable': True, 'rules': [{'name': 'x', 'proto': 'tcp', 'ext_port': 80,
|
|
'int_ip': bad[0], 'int_port': 80}]}})
|
|
chk('拒绝非法 ' + want, False)
|
|
except render.ValidateError as e:
|
|
chk('拒绝非法 ' + want, True, '→ ' + e.msg_cn)
|
|
try:
|
|
render.render_nft('v6', {'wan_iface': 'ppp0', 'lan_ifaces': ['ens18'],
|
|
'portfwd': {'enable': True, 'rules': [{'name': 'x', 'proto': 'tcp', 'ext_port': 80,
|
|
'int_ip': 'fe80::1', 'int_port': 80}]}})
|
|
chk('拒绝链路本地 IPv6', False)
|
|
except render.ValidateError as e:
|
|
chk('拒绝链路本地 IPv6', True, '→ ' + e.msg_cn)
|
|
# 非法协议
|
|
try:
|
|
render.render_nft('v4', {'wan_iface': 'ppp0', 'lan_ifaces': ['ens18'],
|
|
'portfwd': {'enable': True, 'rules': [{'name': 'x', 'proto': 'sctp', 'ext_port': 80,
|
|
'int_ip': '192.168.7.1', 'int_port': 80}]}})
|
|
chk('拒绝非法协议', False)
|
|
except render.ValidateError as e:
|
|
chk('拒绝非法协议', True, '→ ' + e.msg_cn)
|
|
|
|
print('\n结果: %s' % ('全部通过' if fails == 0 else '%d 项失败' % fails))
|
|
sys.exit(1 if fails else 0)
|