From c847a54dc9a7e7999113fe4aacb54c9b663751b3 Mon Sep 17 00:00:00 2001 From: VIAOTO <1392726643@qq.com> Date: Thu, 2 Jul 2026 19:41:27 +0800 Subject: [PATCH] fix: preserve subscription dns resolvers --- frontend/src/stores/subscribes.ts | 43 ++++++++- frontend/src/types/app.d.ts | 1 + frontend/src/utils/generator.ts | 146 ++++++++++++++++++++---------- frontend/src/utils/migration.ts | 4 + 4 files changed, 145 insertions(+), 49 deletions(-) diff --git a/frontend/src/stores/subscribes.ts b/frontend/src/stores/subscribes.ts index 25ae6c00..58b94c10 100644 --- a/frontend/src/stores/subscribes.ts +++ b/frontend/src/stores/subscribes.ts @@ -22,6 +22,42 @@ import { migrateSubscribes, } from '@/utils' +const collectDomainResolverTags = (value: any): string[] => { + if (!value || typeof value !== 'object') return [] + if (Array.isArray(value)) return value.flatMap(collectDomainResolverTags) + + return Object.entries(value).flatMap(([key, val]) => { + if (key === 'domain_resolver' && typeof val === 'string' && val) { + return [val] + } + return collectDomainResolverTags(val) + }) +} + +const collectReferencedDnsServers = ( + outbounds: Recordable[], + dnsServers: Recordable[], +): Recordable[] => { + const serverMap = new Map( + dnsServers.flatMap((server) => (server.tag ? [[server.tag, server]] : [])), + ) + const result = new Map() + const pending = outbounds.flatMap(collectDomainResolverTags) + + while (pending.length > 0) { + const tag = pending.shift()! + if (result.has(tag)) continue + + const server = serverMap.get(tag) + if (!server) continue + + result.set(tag, server) + pending.push(...collectDomainResolverTags(server)) + } + + return Array.from(result.values()) +} + export const useSubscribesStore = defineStore('subscribes', () => { const subscribes = ref([]) @@ -86,6 +122,7 @@ export const useSubscribesStore = defineStore('subscribes', () => { const userInfo: Recordable = {} let body = '' let proxies: Record[] = [] + let dnsServers: Recordable[] = [] if (s.type === 'Manual') { body = await ReadFile(s.path) @@ -124,7 +161,9 @@ export const useSubscribesStore = defineStore('subscribes', () => { } if (isValidSubJson(body)) { - proxies = JSON.parse(body).outbounds + const config = JSON.parse(body) + proxies = config.outbounds + dnsServers = config.dns?.servers || [] } else if (isValidSubYAML(body)) { proxies = parse(body).proxies } else if (isValidBase64(body)) { @@ -187,6 +226,7 @@ export const useSubscribesStore = defineStore('subscribes', () => { const { proxies: _proxies, subscription } = await fn(proxies, s) Object.assign(s, subscription) + s.dnsServers = collectReferencedDnsServers(_proxies, dnsServers) s.proxies = _proxies.map(({ tag, type }) => { // Keep the original ID value of the proxy unchanged const id = s.proxies.find((v) => v.tag === tag)?.id || sampleID() @@ -284,6 +324,7 @@ export const useSubscribesStore = defineStore('subscribes', () => { response: {}, }, proxies: [], + dnsServers: [], script: DefaultSubscribeScript, } } diff --git a/frontend/src/types/app.d.ts b/frontend/src/types/app.d.ts index 178ae5a7..2a953b30 100644 --- a/frontend/src/types/app.d.ts +++ b/frontend/src/types/app.d.ts @@ -326,6 +326,7 @@ declare namespace App { response: Recordable } script: string + dnsServers: Recordable[] // Not Config updating?: boolean } diff --git a/frontend/src/utils/generator.ts b/frontend/src/utils/generator.ts index 1ed495dc..a9f7182d 100644 --- a/frontend/src/utils/generator.ts +++ b/frontend/src/utils/generator.ts @@ -92,10 +92,19 @@ const generateInbounds = (inbounds: App.Inbound[]) => { const generateOutbounds = async (outbounds: App.Outbound[]) => { const result: Recordable[] = [] const SubscriptionCache: Recordable = {} + const SubscriptionDnsServersCache: Recordable = {} const proxiesSet = new Set() const builtInProxiesSet = new Set() + const dnsServersMap = new Map() const subscribesStore = useSubscribesStore() + const collectSubscriptionDnsServers = (subId: string) => { + SubscriptionDnsServersCache[subId]?.forEach((server) => { + if (server.tag && !dnsServersMap.has(server.tag)) { + dnsServersMap.set(server.tag, server) + } + }) + } for (const outbound of outbounds) { const _outbound: Recordable = { @@ -125,6 +134,7 @@ const generateOutbounds = async (outbounds: App.Outbound[]) => { const subStr = await ReadFile(sub.path) const proxies = JSON.parse(subStr) SubscriptionCache[subId] = proxies + SubscriptionDnsServersCache[subId] = sub.dnsServers || [] } } if (proxy.type === 'Subscription') { @@ -132,11 +142,13 @@ const generateOutbounds = async (outbounds: App.Outbound[]) => { ...SubscriptionCache[subId]!.map((v) => v.tag).filter((tag) => isTagMatching(tag)), ) SubscriptionCache[subId]!.forEach((v) => proxiesSet.add(v)) + collectSubscriptionDnsServers(subId) } else { const _proxy = SubscriptionCache[subId]!.find((v) => v.tag === proxy.tag) if (_proxy && isTagMatching(_proxy.tag)) { _outbound.outbounds.push(_proxy.tag) proxiesSet.add(_proxy) + collectSubscriptionDnsServers(subId) } } } @@ -148,7 +160,24 @@ const generateOutbounds = async (outbounds: App.Outbound[]) => { result.push(...proxiesSet) result.push(...Array.from(builtInProxiesSet).map((v) => ({ type: v, tag: v }))) - return result + return { + outbounds: result, + dnsServers: Array.from(dnsServersMap.values()), + } +} + +const normalizeSubscriptionDnsServers = ( + dnsServers: Recordable[], + generatedOutbounds: Recordable[], +) => { + const outboundTags = new Set(generatedOutbounds.map((outbound) => outbound.tag)) + return dnsServers.map((server) => { + const normalized = { ...server } + if (normalized.detour && !outboundTags.has(normalized.detour)) { + delete normalized.detour + } + return normalized + }) } const generateRoute = (route: App.Route, inbounds: App.Inbound[], outbounds: App.Outbound[], dns: App.Dns) => { @@ -230,6 +259,7 @@ const generateDns = ( rule_set: App.ProfileRuleSet[], inbounds: App.Inbound[], outbounds: App.Outbound[], + subscriptionDnsServers: Recordable[] = [], ) => { const getOutbound = (id: string) => outbounds.find((v) => v.id === id) const getDnsServer = (id: string) => dns.servers.find((v) => v.id === id)?.tag @@ -240,63 +270,72 @@ const generateDns = ( if (dns.client_subnet) { extra.client_subnet = dns.client_subnet } - return { - servers: dns.servers.flatMap((server) => { - const extra: Recordable = {} + const servers: Recordable[] = dns.servers.flatMap((server) => { + const extra: Recordable = {} + if ( + [ + DnsServer.Local, + DnsServer.Tcp, + DnsServer.Udp, + DnsServer.Tls, + DnsServer.Quic, + DnsServer.Https, + DnsServer.H3, + DnsServer.Dhcp, + ].includes(server.type as any) + ) { + if (server.detour) { + const outbound = getOutbound(server.detour) + if (outbound?.type !== Outbound.Direct) { + extra.detour = outbound?.tag + } + } + server.domain_resolver && (extra.domain_resolver = getDnsServer(server.domain_resolver)) if ( [ - DnsServer.Local, DnsServer.Tcp, DnsServer.Udp, DnsServer.Tls, DnsServer.Quic, DnsServer.Https, DnsServer.H3, - DnsServer.Dhcp, ].includes(server.type as any) ) { - if (server.detour) { - const outbound = getOutbound(server.detour) - if (outbound?.type !== Outbound.Direct) { - extra.detour = outbound?.tag - } + server.server_port && (extra.server_port = Number(server.server_port)) + extra.server = server.server + if ([DnsServer.Https, DnsServer.H3].includes(server.type as any)) { + server.path && (extra.path = server.path) } - server.domain_resolver && (extra.domain_resolver = getDnsServer(server.domain_resolver)) - if ( - [ - DnsServer.Tcp, - DnsServer.Udp, - DnsServer.Tls, - DnsServer.Quic, - DnsServer.Https, - DnsServer.H3, - ].includes(server.type as any) - ) { - server.server_port && (extra.server_port = Number(server.server_port)) - extra.server = server.server - if ([DnsServer.Https, DnsServer.H3].includes(server.type as any)) { - server.path && (extra.path = server.path) - } - } - } - if (server.type === DnsServer.Hosts) { - extra.path = server.hosts_path.reduce((p, c) => p.concat(c.split(',')), [] as string[]) - extra.predefined = Object.entries(server.predefined).reduce( - (p, [k, v]) => ({ ...p, [k]: v.split(',') }), - {}, - ) - } else if (server.type === DnsServer.Dhcp) { - server.interface && (extra.interface = server.interface) - } else if (server.type === DnsServer.FakeIP) { - server.inet4_range && (extra.inet4_range = server.inet4_range) - server.inet6_range && (extra.inet6_range = server.inet6_range) } - return { - tag: server.tag, - type: server.type, - ...extra, - } - }), + } + if (server.type === DnsServer.Hosts) { + extra.path = server.hosts_path.reduce((p, c) => p.concat(c.split(',')), [] as string[]) + extra.predefined = Object.entries(server.predefined).reduce( + (p, [k, v]) => ({ ...p, [k]: v.split(',') }), + {}, + ) + } else if (server.type === DnsServer.Dhcp) { + server.interface && (extra.interface = server.interface) + } else if (server.type === DnsServer.FakeIP) { + server.inet4_range && (extra.inet4_range = server.inet4_range) + server.inet6_range && (extra.inet6_range = server.inet6_range) + } + return { + tag: server.tag, + type: server.type, + ...extra, + } + }) + const serverTags = new Set(servers.map((server) => server.tag)) + subscriptionDnsServers.forEach((server) => { + if (server.tag && !serverTags.has(server.tag)) { + servers.push(server) + serverTags.add(server.tag) + } + }) + + return { + servers, rules: dns.rules.flatMap((rule) => { if (rule.type === RuleType.InsertionPoint || !rule.enable) { return [] @@ -385,14 +424,25 @@ export const generateConfig = async ( } = options const profile = deepClone(originalProfile) + const generatedOutbounds = await generateOutbounds(profile.outbounds) + const subscriptionDnsServers = normalizeSubscriptionDnsServers( + generatedOutbounds.dnsServers, + generatedOutbounds.outbounds, + ) // step 1 let config: Recordable = { log: profile.log, experimental: generateExperimental(profile.experimental, profile.outbounds), inbounds: generateInbounds(profile.inbounds), - outbounds: await generateOutbounds(profile.outbounds), + outbounds: generatedOutbounds.outbounds, route: generateRoute(profile.route, profile.inbounds, profile.outbounds, profile.dns), - dns: generateDns(profile.dns, profile.route.rule_set, profile.inbounds, profile.outbounds), + dns: generateDns( + profile.dns, + profile.route.rule_set, + profile.inbounds, + profile.outbounds, + subscriptionDnsServers, + ), } // adapt to stable branch diff --git a/frontend/src/utils/migration.ts b/frontend/src/utils/migration.ts index 63a3db90..ed6b764a 100644 --- a/frontend/src/utils/migration.ts +++ b/frontend/src/utils/migration.ts @@ -37,6 +37,10 @@ export const migrateSubscribes = async ( subscribe.customProxy = '' needSync = true } + if (!subscribe.dnsServers) { + subscribe.dnsServers = [] + needSync = true + } }) if (needSync) await save()