From fa314d0562fd254abdca158691537ee470cc22f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=AE=87=E5=9D=A4?= Date: Tue, 20 Oct 2026 11:46:46 +0800 Subject: [PATCH] feat: merge subscription DNS policy into the main configuration Node extraction kept only `proxies`, so an airport's `dns.nameserver-policy` and `dns.proxy-server-nameserver-policy` were dropped and its DNS routing never reached the running configuration. Carry both tables into the derived node document instead of discarding them. The existing override merge unions these maps with the user's own patch taking precedence, so domains the user configured stay under their control while the airport's remaining domains are still honoured. A payload without a dns section produces the same document as before. --- .../AppArchitecture/ProxyImportBridge.swift | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/apple/ClashClient/Sources/AppArchitecture/ProxyImportBridge.swift b/apple/ClashClient/Sources/AppArchitecture/ProxyImportBridge.swift index acecec3..fb97148 100644 --- a/apple/ClashClient/Sources/AppArchitecture/ProxyImportBridge.swift +++ b/apple/ClashClient/Sources/AppArchitecture/ProxyImportBridge.swift @@ -118,6 +118,13 @@ struct ProxyImportResult { let identities: [String] let shape: ProxyPayloadShape? + /// Split-DNS routing tables lifted from a mihomo configuration payload + /// (`dns.nameserver-policy` / `dns.proxy-server-nameserver-policy`). They let the airport's + /// DNS routing policy be append-merged into the user's main configuration instead of being + /// discarded when only nodes are extracted. `nil` when the payload carries no DNS policy. + var dnsNameServerPolicy: [String: Any]? = nil + var dnsProxyServerNameserverPolicy: [String: Any]? = nil + var issues: [ProxyImportIssue] { skipped } @@ -344,10 +351,19 @@ enum ProxyImportBridge { + issues(root["recognizedUnsupported"]), notHonoured: issues(root["notHonoured"]), identities: root["identities"] as? [String] ?? [], - shape: shape(root["payloadShape"]) + shape: shape(root["payloadShape"]), + dnsNameServerPolicy: dnsPolicy(root["dnsNameServerPolicy"]), + dnsProxyServerNameserverPolicy: dnsPolicy(root["dnsProxyServerNameserverPolicy"]) ) } + /// Normalises one split-DNS table from the kernel report into a JSON-safe dictionary. + /// Returns `nil` for missing or empty tables so callers can fall back to their own policy. + private static func dnsPolicy(_ raw: Any?) -> [String: Any]? { + guard let table = raw as? [String: Any], !table.isEmpty else { return nil } + return table + } + @@ -527,7 +543,18 @@ enum ProxyImportBridge { } log(skipped: result.issues, keeping: result.proxies.count) log(notHonoured: result.notHonoured) - let data = try JSONSerialization.data(withJSONObject: ["proxies": result.proxies]) + var document: [String: Any] = ["proxies": result.proxies] + // Carry the airport's split-DNS routing tables into the node document so they reach the + // main configuration instead of being dropped by node extraction. `mergeOverride` unions + // maps, and the user's own patch is the winning side, so conflicting domains stay under + // the user's control while the airport's remaining domains are still honoured. + var dns: [String: Any] = [:] + if let policy = result.dnsNameServerPolicy { dns["nameserver-policy"] = policy } + if let policy = result.dnsProxyServerNameserverPolicy { + dns["proxy-server-nameserver-policy"] = policy + } + if !dns.isEmpty { document["dns"] = dns } + let data = try JSONSerialization.data(withJSONObject: document) let yaml = try ConfigTransforms.jsonToYAML(String(decoding: data, as: UTF8.self)) return (yaml, result.proxies.count, result.issues) }