fix(enhance): replace DNS fields in merge overrides

Replace DNS map fields and hosts instead of retaining old entries, while preserving unspecified DNS fields. Keep DNS override priority and explicit empty values intact.

Refs #7993
This commit is contained in:
wonfen
2026-09-22 10:17:19 +08:00
parent bffe91db5e
commit 6a85d0344e
3 changed files with 82 additions and 20 deletions
+5 -3
View File
@@ -6,14 +6,16 @@
- 修复不同来源的订阅或规则集共用缓存路径时,服务模式无法启动内核的问题
- 优化文件错误日志,显示具体文件路径和失败原因
- 修复 DNS 覆写:空内容报错、初始值覆盖已有设置、已填写字段残留原配置、空字段在高级编辑器中缺失
- 修复 Merge 覆写 DNS 策略、过滤条件和 hosts 时,旧条目仍被保留的问题
- 修复首次以管理员身份启动后,系统服务因数据目录所有权不符而拒绝启动内核的问题
**🖥️ Windows**
- 修复 Windows 服务因路径或目录权限问题无法启动时,缺少重新安装服务的提示
**🐧 Linux**
- Linux 修复安装或修复服务后仍无法启动内核、开启 TUN 模式的问题
**🖥️ Windows**
- 修复 Windows 服务因路径或目录权限问题无法启动时,缺少重新安装服务的提示
</details>
+45 -17
View File
@@ -1,5 +1,3 @@
use clash_verge_logging::{Type, logging};
use super::field::use_lowercase;
use serde_yaml_ng::{self, Mapping, Value};
@@ -18,20 +16,19 @@ fn deep_merge(a: &mut Value, b: Value) {
}
}
pub fn use_merge(merge: &Mapping, config: Mapping) -> Mapping {
let mut config = Value::from(config);
let merge = use_lowercase(merge);
deep_merge(&mut config, Value::from(merge));
config.as_mapping().cloned().unwrap_or_else(|| {
logging!(
error,
Type::Core,
"Failed to convert merged config to mapping, using empty mapping"
);
Mapping::new()
})
pub fn use_merge(merge: &Mapping, mut config: Mapping) -> Mapping {
for (key, value) in use_lowercase(merge) {
match (config.get_mut(&key), value) {
(Some(Value::Mapping(existing)), Value::Mapping(overlay)) if key.as_str() == Some("dns") => {
existing.extend(overlay);
}
(Some(existing), value) if key.as_str() != Some("hosts") => deep_merge(existing, value),
(_, value) => {
config.insert(key, value);
}
}
}
config
}
#[test]
@@ -54,18 +51,49 @@ fn test_merge() -> anyhow::Result<()> {
enable: true
dns:
enable: true
nameserver-policy:
new.example: 1.1.1.1
fallback-filter:
geoip: false
hosts:
new.example: 192.0.2.2
";
let config = r"
rules:
- aaaaa
script1: test
tun:
mtu: 1500
dns:
nameserver: [9.9.9.9]
nameserver-policy:
old.example: 8.8.8.8
fallback-filter:
geoip: true
domain: [old.example]
hosts:
old.example: 192.0.2.1
";
let merge = serde_yaml_ng::from_str::<Mapping>(merge)?;
let config = serde_yaml_ng::from_str::<Mapping>(config)?;
let _ = serde_yaml_ng::to_string(&use_merge(&merge, config))?;
let result = use_merge(&merge, config);
assert_eq!(result["dns"]["nameserver-policy"], merge["dns"]["nameserver-policy"]);
assert_eq!(result["dns"]["fallback-filter"], merge["dns"]["fallback-filter"]);
assert_eq!(result["hosts"], merge["hosts"]);
assert_eq!(result["dns"]["nameserver"], serde_yaml_ng::to_value(["9.9.9.9"])?);
assert_eq!(result["tun"]["mtu"], Value::from(1500));
assert_eq!(result["rules"], merge["rules"]);
assert_eq!(result["script1"], Value::from("test"));
let cleared = serde_yaml_ng::from_str::<Mapping>(
"dns: {enable: false, nameserver-policy: {}, fallback-filter: {}, nameserver: []}\nhosts: {}",
)?;
let result = use_merge(&cleared, result);
assert_eq!(result["dns"], cleared["dns"]);
assert_eq!(result["hosts"], cleared["hosts"]);
Ok(())
}
+32
View File
@@ -1433,6 +1433,38 @@ mod tests {
serde_yaml_ng::from_str(yaml).expect("test config should be valid")
}
#[test]
fn merge_replaces_dns_fields_with_or_without_dns_settings() {
let profile = mapping(
"dns: {nameserver: [9.9.9.9], nameserver-policy: {profile.example: 9.9.9.9}}\n\
hosts: {profile.example: 192.0.2.1}",
);
let settings = mapping(
"dns: {ipv6: true, nameserver: [8.8.8.8], nameserver-policy: {settings.example: 8.8.8.8}}\n\
hosts: {settings.example: 192.0.2.2}",
);
let merge = mapping(
"dns: {ipv6: false, nameserver-policy: {merge.example: 1.1.1.1}}\n\
hosts: {merge.example: 192.0.2.3}",
);
for enabled in [false, true] {
let (config, owns_ipv6) = if enabled {
super::merge_dns_config(profile.clone(), settings.clone())
} else {
(profile.clone(), false)
};
let authoritative = AuthoritativeFields::capture(&config, &[], owns_ipv6);
let result = authoritative.enforce(super::use_merge(&merge, config));
assert_eq!(result["dns"]["nameserver-policy"], merge["dns"]["nameserver-policy"]);
assert_eq!(result["hosts"], merge["hosts"]);
let source = if enabled { &settings } else { &profile };
assert_eq!(result["dns"]["nameserver"], source["dns"]["nameserver"]);
assert_eq!(result["dns"]["ipv6"], serde_yaml_ng::Value::from(enabled));
}
}
#[tokio::test]
async fn manual_overrides_follow_expected_priority() {
let mut config = mapping(