package app import ( "net" "net/netip" "net/url" "strings" ) func convertLegacyDNSServer(address string) (map[string]any, string, bool) { a := strings.TrimSpace(address) if a == "" { return nil, "empty address", false } low := strings.ToLower(a) switch low { case "local": return map[string]any{"type": "local"}, "", true case "fakeip": return map[string]any{"type": "fakeip"}, "", true } if strings.HasPrefix(low, "rcode://") { return nil, "rcode:// legacy server is not auto-migrated (use dns rule action)", false } if strings.HasPrefix(low, "dhcp://") { iface := strings.TrimSpace(a[len("dhcp://"):]) out := map[string]any{"type": "dhcp"} if iface != "" && !strings.EqualFold(iface, "auto") { out["interface"] = iface } return out, "", true } if strings.Contains(a, "://") { u, err := url.Parse(a) if err != nil { return nil, "invalid URL address", false } scheme := strings.ToLower(strings.TrimSpace(u.Scheme)) host := strings.TrimSpace(u.Hostname()) if host == "" { return nil, "empty host in URL address", false } port := parsePort(u.Port()) out := map[string]any{} switch scheme { case "tcp", "udp": out["type"] = scheme case "tls": out["type"] = "tls" case "quic": out["type"] = "quic" case "https": out["type"] = "https" if p := strings.TrimSpace(u.EscapedPath()); p != "" && p != "/dns-query" { out["path"] = p } case "h3": out["type"] = "h3" if p := strings.TrimSpace(u.EscapedPath()); p != "" && p != "/dns-query" { out["path"] = p } default: return nil, "unsupported address scheme: " + scheme, false } out["server"] = host if port > 0 { out["server_port"] = port } return out, "", true } // Plain host/ip, optional :port => UDP. host, port := splitHostPortLoose(a) if host == "" { return nil, "invalid host", false } out := map[string]any{ "type": "udp", "server": host, } if port > 0 { out["server_port"] = port } return out, "", true } func splitHostPortLoose(raw string) (string, int) { s := strings.TrimSpace(strings.Trim(raw, "[]")) if s == "" { return "", 0 } // IPv6 literal without brackets can't carry optional port unambiguously here. if strings.Count(s, ":") > 1 && !strings.Contains(raw, "]") { addr, err := netip.ParseAddr(s) if err != nil || !addr.Is6() { return "", 0 } return s, 0 } if h, p, err := net.SplitHostPort(raw); err == nil { return strings.TrimSpace(strings.Trim(h, "[]")), parsePort(p) } if i := strings.LastIndex(s, ":"); i > 0 && strings.Count(s, ":") == 1 { host := strings.TrimSpace(s[:i]) port := parsePort(s[i+1:]) if host != "" && port > 0 { return host, port } } return s, 0 }