修复3个问题: 1) setup-bind.sh确保服务启动 2) DNSSEC配置验证options块包裹 3) 添加IPv6开关功能
This commit is contained in:
@@ -1083,6 +1083,24 @@ def _parse_listen_on_v4(content):
|
|||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_listen_on_v6(content):
|
||||||
|
"""Return 'any'/'none'/'localhost' — the IPv6 listen mode.
|
||||||
|
Returns 'none' if directive is absent (disabled by default).
|
||||||
|
"""
|
||||||
|
m = re.search(r'listen-on-v6(?:\s+port\s+\d+)?\s*\{([^}]*)\}', content, re.DOTALL)
|
||||||
|
if not m:
|
||||||
|
return "none"
|
||||||
|
body = m.group(1)
|
||||||
|
# Look for the main token: { any; } or { none; } or { localhost; }
|
||||||
|
for tm in re.finditer(r'"([^"]+)"|([\w./:-]+)', body):
|
||||||
|
token = tm.group(1) or tm.group(2)
|
||||||
|
if token in ("any", "none", "localhost"):
|
||||||
|
return token
|
||||||
|
# Fallback: if the block is not empty, assume "custom" mode
|
||||||
|
body_stripped = body.strip().strip(';').strip()
|
||||||
|
return "any" if body_stripped else "none"
|
||||||
|
|
||||||
|
|
||||||
def _parse_recursion(content):
|
def _parse_recursion(content):
|
||||||
"""Return 'yes' (default), 'no', or specific value from 'recursion <v>;'."""
|
"""Return 'yes' (default), 'no', or specific value from 'recursion <v>;'."""
|
||||||
m = re.search(r'recursion\s+(\w+)\s*;', content)
|
m = re.search(r'recursion\s+(\w+)\s*;', content)
|
||||||
@@ -1296,6 +1314,7 @@ def config_view():
|
|||||||
dnssec_value = _parse_options_dnssec(options_content)
|
dnssec_value = _parse_options_dnssec(options_content)
|
||||||
listen_on_v4 = _parse_listen_on_v4(options_content)
|
listen_on_v4 = _parse_listen_on_v4(options_content)
|
||||||
recursion_value = _parse_recursion(options_content)
|
recursion_value = _parse_recursion(options_content)
|
||||||
|
listen_on_v6 = _parse_listen_on_v6(options_content)
|
||||||
|
|
||||||
return render_template("config.html",
|
return render_template("config.html",
|
||||||
options_content=options_content,
|
options_content=options_content,
|
||||||
@@ -1308,6 +1327,7 @@ def config_view():
|
|||||||
dnssec_value=dnssec_value,
|
dnssec_value=dnssec_value,
|
||||||
dnssec_values=_VALIDATION_VALUES,
|
dnssec_values=_VALIDATION_VALUES,
|
||||||
listen_on_v4=listen_on_v4,
|
listen_on_v4=listen_on_v4,
|
||||||
|
listen_on_v6=listen_on_v6,
|
||||||
recursion_value=recursion_value)
|
recursion_value=recursion_value)
|
||||||
|
|
||||||
|
|
||||||
@@ -1319,6 +1339,11 @@ def config_options_save():
|
|||||||
run_cmd(f"cp {BIND_CONF_OPTIONS} {backup}")
|
run_cmd(f"cp {BIND_CONF_OPTIONS} {backup}")
|
||||||
|
|
||||||
# Write to temp and validate
|
# Write to temp and validate
|
||||||
|
# NOTE: BIND_CONF_OPTIONS content must have 'options { ... }' wrapper (it's a complete file)
|
||||||
|
# that gets included from named.conf, so named-checkconf can validate it directly
|
||||||
|
if not re.search(r'^\s*options\s*\{', content, re.MULTILINE):
|
||||||
|
# User may have accidentally removed the options wrapper in text editor
|
||||||
|
content = f"options {{\n{content}\n}};\n"
|
||||||
with open("/tmp/named_check.tmp", 'w') as f:
|
with open("/tmp/named_check.tmp", 'w') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
rc, out, err = run_cmd("named-checkconf /tmp/named_check.tmp")
|
rc, out, err = run_cmd("named-checkconf /tmp/named_check.tmp")
|
||||||
@@ -1330,7 +1355,7 @@ def config_options_save():
|
|||||||
|
|
||||||
with open(BIND_CONF_OPTIONS, 'w') as f:
|
with open(BIND_CONF_OPTIONS, 'w') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
run_cmd(f"chown root:bind {BIND_CONF_OPTIONS}")
|
run_cmd(f"chown root:bind {BIND_CONF_OPTIONS} 2>/dev/null || chown root:named {BIND_CONF_OPTIONS} 2>/dev/null")
|
||||||
|
|
||||||
bind_reload()
|
bind_reload()
|
||||||
|
|
||||||
@@ -1392,6 +1417,9 @@ def config_upstream_save():
|
|||||||
raw_fwd = request.form.get("forwarders", "")
|
raw_fwd = request.form.get("forwarders", "")
|
||||||
raw_rc = request.form.get("recursion", "")
|
raw_rc = request.form.get("recursion", "")
|
||||||
raw_listen = request.form.get("listen_on_v4", "")
|
raw_listen = request.form.get("listen_on_v4", "")
|
||||||
|
listen_on_v6_in = (request.form.get("listen_on_v6") or "none").strip().lower()
|
||||||
|
if listen_on_v6_in not in ("any", "none", "localhost"):
|
||||||
|
listen_on_v6_in = "none"
|
||||||
dnssec_in = (request.form.get("dnssec") or "auto").strip().lower()
|
dnssec_in = (request.form.get("dnssec") or "auto").strip().lower()
|
||||||
if dnssec_in not in _VALIDATION_VALUES:
|
if dnssec_in not in _VALIDATION_VALUES:
|
||||||
dnssec_in = "auto"
|
dnssec_in = "auto"
|
||||||
@@ -1430,12 +1458,20 @@ def config_upstream_save():
|
|||||||
new_content, "listen-on",
|
new_content, "listen-on",
|
||||||
_format_list_block("listen-on port 53", new_listen),
|
_format_list_block("listen-on port 53", new_listen),
|
||||||
)
|
)
|
||||||
|
new_content = _replace_or_append_option(
|
||||||
|
new_content, "listen-on-v6",
|
||||||
|
_format_list_block("listen-on-v6 port 53", [listen_on_v6_in]),
|
||||||
|
)
|
||||||
new_content = _replace_or_append_option(
|
new_content = _replace_or_append_option(
|
||||||
new_content, "recursion",
|
new_content, "recursion",
|
||||||
f"recursion {recursion_in};",
|
f"recursion {recursion_in};\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate BEFORE writing to the live file
|
# Validate BEFORE writing to the live file
|
||||||
|
# Ensure content has options block wrapper (required for named-checkconf)
|
||||||
|
if not re.search(r'^\s*options\s*\{', new_content, re.MULTILINE):
|
||||||
|
# No options block found — wrap the entire content
|
||||||
|
new_content = f"options {{\n{new_content}\n}};\n"
|
||||||
tmp = "/tmp/named_check.tmp"
|
tmp = "/tmp/named_check.tmp"
|
||||||
with open(tmp, 'w') as f:
|
with open(tmp, 'w') as f:
|
||||||
f.write(new_content)
|
f.write(new_content)
|
||||||
@@ -1457,11 +1493,11 @@ def config_upstream_save():
|
|||||||
u = current_user()
|
u = current_user()
|
||||||
AuditLog.log(u.username, "修改上游DNS转发配置",
|
AuditLog.log(u.username, "修改上游DNS转发配置",
|
||||||
f"forwarders={new_fwd}; allow-recursion={new_rc}; "
|
f"forwarders={new_fwd}; allow-recursion={new_rc}; "
|
||||||
f"dnssec={dnssec_in}; listen-on={new_listen}; recursion={recursion_in}")
|
f"dnssec={dnssec_in}; listen-on={new_listen}; listen-on-v6={listen_on_v6_in}; recursion={recursion_in}")
|
||||||
flash(
|
flash(
|
||||||
f"已保存:forwarders {len(new_fwd)} 条,allow-recursion {len(new_rc)} 条,"
|
f"已保存:forwarders {len(new_fwd)} 条,allow-recursion {len(new_rc)} 条,"
|
||||||
f"dnssec-validation {dnssec_in},listen-on {len(new_listen)} 项,"
|
f"dnssec-validation {dnssec_in},listen-on {len(new_listen)} 项,"
|
||||||
f"recursion {recursion_in}",
|
f"listen-on-v6 {listen_on_v6_in},recursion {recursion_in}",
|
||||||
"success",
|
"success",
|
||||||
)
|
)
|
||||||
return redirect(url_for("config_view"))
|
return redirect(url_for("config_view"))
|
||||||
|
|||||||
+17
-11
@@ -145,21 +145,27 @@ ensure_include_in_named_conf() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restart_named() {
|
restart_named() {
|
||||||
log "Step 5: restart named so include takes effect"
|
log "Step 5: start/enable named service"
|
||||||
if ! systemctl is-active --quiet "$SERVICE_NAME"; then
|
# Always enable and start/restart the service
|
||||||
warn " $SERVICE_NAME not running; skipping restart"
|
if [[ $DRY_RUN -eq 1 ]]; then
|
||||||
|
printf ' [DRY-RUN] systemctl enable named\n'
|
||||||
|
printf ' [DRY-RUN] systemctl restart named (or start if not running)\n'
|
||||||
|
printf ' [DRY-RUN] sleep 1\n'
|
||||||
|
printf ' [DRY-RUN] systemctl is-active --quiet named && echo "named is active"\n'
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if [[ $DRY_RUN -eq 1 ]]; then
|
systemctl enable named 2>/dev/null || true
|
||||||
printf ' [DRY-RUN] systemctl restart %s\n' "$SERVICE_NAME"
|
if systemctl is-active --quiet named; then
|
||||||
|
systemctl restart named
|
||||||
else
|
else
|
||||||
run systemctl restart "$SERVICE_NAME"
|
systemctl start named
|
||||||
sleep 1
|
|
||||||
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
|
||||||
log " $SERVICE_NAME is active"
|
|
||||||
else
|
|
||||||
err " $SERVICE_NAME failed to start; check 'journalctl -xe -u $SERVICE_NAME'"
|
|
||||||
fi
|
fi
|
||||||
|
sleep 1
|
||||||
|
if systemctl is-active --quiet named; then
|
||||||
|
log " named is active and running"
|
||||||
|
else
|
||||||
|
err " named failed to start; check 'journalctl -xe -u named'"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-2
@@ -68,14 +68,25 @@
|
|||||||
placeholder="any 或具体网段,如: 192.168.1.0/24 10.0.0.0/8">{% for net in listen_on_v4 %}{{ net }}
|
placeholder="any 或具体网段,如: 192.168.1.0/24 10.0.0.0/8">{% for net in listen_on_v4 %}{{ net }}
|
||||||
{% endfor %}</textarea>
|
{% endfor %}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="listen_on_v6">
|
||||||
|
<strong>Listen-on-v6 (IPv6) 53 端口</strong>
|
||||||
|
<span class="text-muted text-sm">启用或禁用 IPv6 DNS 监听。内网无 IPv6 时选 <code>none</code>(关闭)。</span>
|
||||||
|
</label>
|
||||||
|
<select name="listen_on_v6" id="listen_on_v6" class="form-control">
|
||||||
|
<option value="none" {% if listen_on_v6 == 'none' %}selected{% endif %}>none — 关闭 IPv6 监听</option>
|
||||||
|
<option value="any" {% if listen_on_v6 == 'any' %}selected{% endif %}>any — 开启 IPv6 监听(所有接口)</option>
|
||||||
|
<option value="localhost" {% if listen_on_v6 == 'localhost' %}selected{% endif %}>localhost — 仅本机 IPv6</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="recursion_toggle">
|
<label for="recursion_toggle">
|
||||||
<strong>Recursion (递归查询)</strong>
|
<strong>Recursion (递归查询)</strong>
|
||||||
<span class="text-muted text-sm">是否允许本 DNS 代客户端去外网查询。内网递归服务器选 <code>yes</code>;纯权威服务器选 <code>no</code>。</span>
|
<span class="text-muted text-sm">是否允许本 DNS 代客户端去外网查询。内网递归服务器选 <code>yes</code>;纯权威服务器选 <code>no</code>。</span>
|
||||||
</label>
|
</label>
|
||||||
<select name="recursion_toggle" id="recursion_toggle" class="form-control">
|
<select name="recursion_toggle" id="recursion_toggle" class="form-control">
|
||||||
<option value="yes" {% if recursion_value == 'yes' %}selected{% endif %}>yes(允许递归)</option>
|
<option value="yes" {% if recursion_value == 'yes' %}selected{% endif %}>yes — 允许递归查询</option>
|
||||||
<option value="no" {% if recursion_value == 'no' %}selected{% endif %}>no(仅权威应答,不递归)</option>
|
<option value="no" {% if recursion_value == 'no' %}selected{% endif %}>no — 仅权威应答,不递归</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
|
|||||||
Reference in New Issue
Block a user