package app import ( "fmt" "strings" ) func transportMigrateSingBoxDNSConfigMap(root map[string]any) (bool, []string) { if root == nil { return false, nil } rawDNS, ok := root["dns"].(map[string]any) if !ok || rawDNS == nil { return false, nil } rawServers, ok := rawDNS["servers"].([]any) if !ok || len(rawServers) == 0 { return false, nil } changed := false warnings := make([]string, 0) migratedFakeIP := false fakeIPCfg, _ := rawDNS["fakeip"].(map[string]any) for i, raw := range rawServers { srv, ok := raw.(map[string]any) if !ok || srv == nil { continue } if strings.EqualFold(strings.TrimSpace(asString(srv["detour"])), "direct") { delete(srv, "detour") rawServers[i] = srv changed = true } if strings.TrimSpace(asString(srv["type"])) != "" { continue } addr := strings.TrimSpace(asString(srv["address"])) if addr == "" { continue } converted, warn, ok := convertLegacyDNSServer(addr) if !ok { warnings = append(warnings, fmt.Sprintf("dns.servers[%d]: %s", i, warn)) continue } legacyAddrResolver := strings.TrimSpace(asString(srv["address_resolver"])) legacyAddrStrategy := strings.TrimSpace(asString(srv["address_strategy"])) legacyStrategy := strings.TrimSpace(asString(srv["strategy"])) legacySubnet := strings.TrimSpace(asString(srv["client_subnet"])) tag := strings.TrimSpace(asString(srv["tag"])) // Required new fields for typed DNS server. for k, v := range converted { srv[k] = v } delete(srv, "address") if legacyAddrResolver != "" && strings.TrimSpace(asString(srv["domain_resolver"])) == "" { srv["domain_resolver"] = legacyAddrResolver } delete(srv, "address_resolver") if legacyAddrStrategy != "" && strings.TrimSpace(asString(srv["domain_strategy"])) == "" { srv["domain_strategy"] = legacyAddrStrategy } delete(srv, "address_strategy") if legacyStrategy != "" { if strings.TrimSpace(asString(rawDNS["strategy"])) == "" { rawDNS["strategy"] = legacyStrategy } else if tag != "" { transportAppendDNSRule(rawDNS, map[string]any{ "server": tag, "strategy": legacyStrategy, }) } else { warnings = append(warnings, fmt.Sprintf("dns.servers[%d]: strategy moved partially (missing tag)", i)) } delete(srv, "strategy") } if legacySubnet != "" { if tag != "" { transportAppendDNSRule(rawDNS, map[string]any{ "server": tag, "client_subnet": legacySubnet, }) } else if strings.TrimSpace(asString(rawDNS["client_subnet"])) == "" { rawDNS["client_subnet"] = legacySubnet } else { warnings = append(warnings, fmt.Sprintf("dns.servers[%d]: client_subnet moved partially (missing tag)", i)) } delete(srv, "client_subnet") } if strings.EqualFold(asString(srv["type"]), "fakeip") && fakeIPCfg != nil { if _, ok := srv["inet4_range"]; !ok { if v, ok := fakeIPCfg["inet4_range"]; ok { srv["inet4_range"] = v } } if _, ok := srv["inet6_range"]; !ok { if v, ok := fakeIPCfg["inet6_range"]; ok { srv["inet6_range"] = v } } migratedFakeIP = true } rawServers[i] = srv changed = true } if changed { rawDNS["servers"] = rawServers if migratedFakeIP { delete(rawDNS, "fakeip") } root["dns"] = rawDNS } return changed, dedupeStrings(warnings) }