Web 端 '上游 DNS 转发' 卡片新增两个字段:
1. Listen-on (IPv4) 53 端口 — textarea,每行一个 CIDR/IP/ACL 名。
留空 = any(任意来源)。对应 BIND 指令 listen-on port 53 { ... };。
2. Recursion — yes/no 下拉,控制本 DNS 是否代客户端去外网递归。
内网递归服务器选 yes;纯权威服务器选 no。
修了一个 regex bug:之前 _replace_or_append_option 用 \\b(?!\\-) 做
lookahead 但没用 lookbehind,导致在内容含 allow-recursion 时匹配
recursion 时也会匹配到 allow- 后面的 recursion,把 allow-recursion
破坏。改为 (?<!\\w)(?<!\\-)keyword\\b(?!\\w)(?!\\-) 双向拒绝,
确保 keyword 必须是独立单词,不能是其它带连字符 directive 的子串。
测试覆盖:
- parse listen-on (IPv4 list)
- parse recursion (yes/no/default)
- 完整 round-trip:5 个字段同时更新,allow-recursion / listen-on-v6
等未触及的 directives 正确保留
DNS Web Manager
基于 BIND9 的 DNS 服务器 Web 管理界面,使用 Flask + SQLite 构建。
功能概览
| 功能 | 说明 |
|---|---|
| 仪表盘 | BIND 服务状态、域名/记录统计、服务控制(启动/停止/重启/重载) |
| 域名管理 | 创建/删除 Zone(支持 master/slave)、查看 Zone 详情、编辑原始 zone 文件 |
| 记录管理 | 添加/删除 DNS 记录(A、AAAA、CNAME、MX、TXT、NS、PTR、SRV、CAA) |
| DNS 查询 | 在线 dig 测试,支持所有记录类型 |
| 配置管理 | 在线编辑 named.conf.options 和 named.conf.local,带语法校验 |
| 操作日志 | 全部操作的审计日志,支持分页 |
| 用户认证 | 登录/登出/修改密码 |
截图
仪表盘
- BIND 服务状态卡片(运行状态、开机自启)
- 域名数量、记录总数统计
- 服务控制按钮(启动/停止/重启/重载)
- 域名概览表格
- 最近操作日志
域名管理
- 域名列表(域名、类型、Zone 文件、记录数)
- 创建域名表单(域名、类型、NS 服务器、管理员邮箱)
- Zone 详情页:SOA 信息、记录列表、添加记录表单、原始文件编辑
DNS 查询
- 输入域名 + 记录类型 + DNS 服务器,执行 dig 查询
- 显示完整 dig 输出
技术栈
- DNS 服务器: BIND 9.18+(named)
- Web 框架: Flask 3.0
- 数据库: SQLite(用户认证 + 审计日志)
- 前端: Jinja2 模板 + 原生 CSS(无前端框架依赖)
- WSGI 服务器: Gunicorn
目录结构
dns-service/
├── app.py # Flask 应用主文件(路由、模型、BIND 操作)
├── wsgi.py # Gunicorn 入口
├── init_db.py # 数据库初始化脚本
├── requirements.txt # Python 依赖
├── README.md
├── templates/ # Jinja2 模板
│ ├── base.html # 布局模板(导航栏 + 页脚)
│ ├── login.html # 登录页
│ ├── dashboard.html # 仪表盘
│ ├── zones.html # 域名列表
│ ├── zone_form.html # 创建域名表单
│ ├── zone_detail.html # Zone 详情 + 记录管理
│ ├── zone_raw.html # 原始 Zone 文件编辑
│ ├── query.html # DNS 查询测试
│ ├── config.html # BIND 配置管理
│ ├── logs.html # 操作日志
│ ├── change_password.html
│ └── error.html # 错误页
├── static/
│ └── style.css # 全部样式
└── instance/ # SQLite 数据库(运行时生成)
└── dns_web.db
BIND 配置文件
| 文件 | 用途 | 默认路径(Debian/Ubuntu) |
|---|---|---|
named.conf |
主配置 | /etc/bind/named.conf |
named.conf.options |
全局选项(监听端口、转发器、查询权限) | /etc/bind/named.conf.options |
named.conf.local |
Zone 声明(由 Web UI 自动管理) | /etc/bind/named.conf.local |
| zone files | DNS 记录文件 | /etc/bind/zones/db.* |
路径自动探测:服务启动时按常见路径顺序探测实际存在的目录/文件,覆盖 Debian/Ubuntu、RHEL/CentOS/Rocky、AlmaLinux、FreeBSD 等发行版默认布局。如需强制指定,可用环境变量覆盖(见"路径优先级")。
安装部署
1. 安装 BIND9
apt-get update
apt-get install -y bind9 bind9utils dnsutils
2. 配置 BIND
不同发行版默认路径、默认 include 的文件都不同,下面分别给出全新机器的初始化步骤。如果机器上已经有 BIND 在跑,只看对应小节的"追加 include"步骤即可。
核心要点:Web UI 写入 zone 声明的文件(named.conf.local)必须被 named 主配置 include,否则 zone 文件存在但 named 不会加载。
Debian / Ubuntu(bind9)
默认路径:/etc/bind/named.conf、/etc/bind/named.conf.options、/etc/bind/named.conf.local、/etc/bind/zones/。
apt-get update
apt-get install -y bind9 bind9utils dnsutils
# 创建 zone 文件目录(项目代码默认就写这里)
mkdir -p /etc/bind/zones
chown bind:bind /etc/bind/zones
# named.conf.options(监听 53 端口)
cat > /etc/bind/named.conf.options << 'EOF'
options {
directory "/var/cache/bind";
listen-on port 53 { any; };
listen-on-v6 { none; };
forwarders { 8.8.8.8; 8.8.4.4; };
allow-query { any; };
allow-recursion { 127.0.0.0/8; 10.168.1.0/24; };
dnssec-validation auto;
auth-nxdomain no;
};
EOF
# named.conf(bind9 包默认就是这套,确认一下即可)
grep -q '/etc/bind/named.conf.local' /etc/bind/named.conf || \
echo 'include "/etc/bind/named.conf.local";' >> /etc/bind/named.conf
# named.conf.local(空,由 Web UI 自动填充;首次部署建一个空文件)
touch /etc/bind/named.conf.local
chown bind:bind /etc/bind/named.conf.local
systemctl enable named
systemctl restart named
RHEL / CentOS 7 / Rocky Linux / AlmaLinux
默认路径:/etc/named.conf、/var/named/、/etc/named.conf.local(默认不创建)、/etc/rndc.key。
yum install -y bind bind-utils
# /var/named 默认就有;如果没建过,手动建
mkdir -p /var/named
chown root:named /var/named
chmod 2770 /var/named
# 关键:RHEL 的 /etc/named.conf 默认不 include /etc/named.conf.local,
# 必须手动追加,否则 Web UI 写的 zone 声明不会生效
grep -q '/etc/named.conf.local' /etc/named.conf || \
echo 'include "/etc/named.conf.local";' >> /etc/named.conf
# 空白入口文件,让 named 重启时不报"include 文件不存在"
touch /etc/named.conf.local
chown root:named /etc/named.conf.local
chmod 640 /etc/named.conf.local
systemctl enable named
systemctl restart named
3. 安装 Web 应用
git clone ssh://git@git.cnbugs.com:10022/AI-Agent/dns-service.git
cd dns-service
# 一键初始化 BIND(自动识别 Debian/Ubuntu vs RHEL/CentOS):
# - 装 bind 包
# - 建 zone 目录、设权限
# - 建 named.conf.local、确保被主配置 include
# - 重启 named
# 默认是干跑模式,先看清楚会做什么:
sudo bash scripts/setup-bind.sh --dry-run
# 确认无误后真跑:
sudo bash scripts/setup-bind.sh
# 安装 Python 依赖
pip install -r requirements.txt
# 创建数据库目录
mkdir -p instance
# 初始化数据库(创建默认 admin/admin 账号)
python3 init_db.py
# 启动(开发模式)
python3 app.py
4. 生产部署(Gunicorn + Systemd)
# 创建 systemd 服务
cat > /etc/systemd/system/dns-web.service << 'EOF'
[Unit]
Description=DNS Web Manager
After=network.target named.service
[Service]
Type=notify
User=root
WorkingDirectory=/root/dns-service
ExecStart=/usr/local/bin/gunicorn --workers 2 --bind 0.0.0.0:5300 wsgi:app
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable dns-web
systemctl start dns-web
5. 路径优先级
服务启动时按下面的顺序确定每个 BIND 路径:
- 环境变量(最高优先级,用于强制指定非默认路径)
- 自动探测(按常见路径顺序,找到第一个存在的目录/文件)
- 内置默认值(都没探测到时使用,主要为 Debian/Ubuntu)
自动探测覆盖以下发行版默认布局:
| 变量 | 探测顺序 |
|---|---|
BIND_CONF_DIR |
/etc/bind → /etc → /etc/named |
BIND_ZONES_DIR |
/etc/bind/zones → /var/named → /var/named/data |
BIND_CONF_LOCAL |
/etc/named.conf.local → /etc/bind/named.conf.local |
BIND_CONF_OPTIONS |
/etc/named.conf → /etc/bind/named.conf.options |
BIND_RNDC_KEY |
/etc/bind/rndc.key → /etc/rndc.key → /var/named/key |
BIND_SERVICE |
固定 named |
BIND_CONF_LOCAL/BIND_CONF_OPTIONS探测顺序说明:RHEL/CentOS 的/etc/named.conf默认 include 的是/etc/named.conf.local,Debian/Ubuntu 默认 include 的是/etc/bind/named.conf.local。代码里优先探测 RHEL 路径——这样可以避免"两个文件都存在但 named 只读其中一个"的歧义场景(典型踩坑:zone 文件写出来了,但 named 不加载)。
大多数情况下无需任何配置——Debian 上自然走 /etc/bind/*,CentOS/RHEL 上自然走 /var/named + /etc/named.conf*。
只有以下两种场景需要用环境变量强制覆盖:
- BIND 装在非标准路径(比如容器里 mount 到
/opt/bind) - 同机多实例 / 测试场景,需要指向特定路径
覆盖示例(systemd):
cat > /etc/systemd/system/dns-web.service << 'EOF'
[Unit]
Description=DNS Web Manager
After=network.target named.service
[Service]
Type=notify
User=root
WorkingDirectory=/root/dns-service
Environment=BIND_ZONES_DIR=/opt/bind/zones
ExecStart=/usr/local/bin/gunicorn --workers 2 --bind 0.0.0.0:5300 wsgi:app
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl restart dns-web
注意:覆盖
BIND_CONF_LOCAL后,必须让 named 主配置 include 这个文件。CentOS 7 的/etc/named.conf默认不会 include,需要按上面"配置 BIND / RHEL"一节里的命令追加include "/etc/named.conf.local";。
使用说明
登录
- 访问
http://<服务器IP>:5300 - 默认账号:
admin/ 密码:admin - 首次登录后请修改密码
创建域名
- 进入「域名管理」→「+ 添加域名」
- 填写域名(如
example.com)、选择类型(master/slave) - 可选填写 NS 服务器和管理员邮箱(留空则自动生成)
- 点击「创建域名」
添加 DNS 记录
- 进入域名详情页
- 点击「+ 添加记录」
- 填写记录名称、类型、TTL、数据
- 点击「添加」
记录类型说明:
| 类型 | 数据格式 | 示例 |
|---|---|---|
| A | IPv4 地址 | 10.168.1.100 |
| AAAA | IPv6 地址 | 2001:db8::1 |
| CNAME | 别名(FQDN,以 . 结尾) |
www.example.com. |
| MX | 优先级 + 邮件服务器(FQDN) | 10 mail.example.com. |
| TXT | 文本内容 | "v=spf1 ~all" |
| NS | NS 服务器(FQDN) | ns1.example.com. |
| PTR | 反向解析目标(FQDN) | host.example.com. |
| SRV | 优先级 权重 端口 目标 | 10 5 5060 sip.example.com. |
DNS 查询测试
- 进入「DNS 查询」页面
- 输入域名和记录类型
- DNS 服务器留空默认查询本地 BIND(127.0.0.1:53)
- 点击「查询」查看结果
配置管理
- 进入「配置管理」页面
- 可在线编辑
named.conf.options(监听端口、转发器等) - 可在线编辑
named.conf.local(Zone 声明) - 保存时自动执行
named-checkconf校验,通过后自动rndc reload
服务控制
在「仪表盘」页面可控制 BIND 服务:
- 启动 -
systemctl start named - 停止 -
systemctl stop named - 重启 -
systemctl restart named - 重载配置 -
rndc reload(不中断服务,热加载 zone 变更)
注意事项
- 权限要求: Web 应用需要 root 权限运行(用于操作 BIND 配置文件和 systemctl 命令)
- 端口冲突: 如果 53 端口被其他服务占用(如 dnsmasq),需先停掉对应服务
- Zone 文件权限: zone 文件需要
bind:bind所有权,Web UI 自动设置 - Serial 自动递增: 每次添加/删除记录时,SOA Serial 自动递增(YYYYMMDDNN 格式)
- 配置备份: 编辑配置文件时自动创建
.bak备份
API 接口
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/status |
获取 BIND 服务状态(需登录) |
License
MIT