From 6a85d0344e5df34c750ca3158e53b8b37a5ddf82 Mon Sep 17 00:00:00 2001 From: wonfen Date: Tue, 22 Sep 2026 10:17:19 +0800 Subject: [PATCH] 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 --- Changelog.md | 8 +++-- src-tauri/src/enhance/merge.rs | 62 ++++++++++++++++++++++++---------- src-tauri/src/enhance/mod.rs | 32 ++++++++++++++++++ 3 files changed, 82 insertions(+), 20 deletions(-) diff --git a/Changelog.md b/Changelog.md index b121ef609..8a8b94ee9 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,14 +6,16 @@ - 修复不同来源的订阅或规则集共用缓存路径时,服务模式无法启动内核的问题 - 优化文件错误日志,显示具体文件路径和失败原因 - 修复 DNS 覆写:空内容报错、初始值覆盖已有设置、已填写字段残留原配置、空字段在高级编辑器中缺失 +- 修复 Merge 覆写 DNS 策略、过滤条件和 hosts 时,旧条目仍被保留的问题 - 修复首次以管理员身份启动后,系统服务因数据目录所有权不符而拒绝启动内核的问题 +**🖥️ Windows** + +- 修复 Windows 服务因路径或目录权限问题无法启动时,缺少重新安装服务的提示 + **🐧 Linux** - Linux 修复安装或修复服务后仍无法启动内核、开启 TUN 模式的问题 -**🖥️ Windows** - -- 修复 Windows 服务因路径或目录权限问题无法启动时,缺少重新安装服务的提示 diff --git a/src-tauri/src/enhance/merge.rs b/src-tauri/src/enhance/merge.rs index db524b8aa..c1c00fc05 100644 --- a/src-tauri/src/enhance/merge.rs +++ b/src-tauri/src/enhance/merge.rs @@ -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::(merge)?; let config = serde_yaml_ng::from_str::(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::( + "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(()) } diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs index fb5e4892b..9807fe489 100644 --- a/src-tauri/src/enhance/mod.rs +++ b/src-tauri/src/enhance/mod.rs @@ -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(