Files
elmprodvpn/selective-vpn-api/app/transport_singbox_dns_migration_convert.go

113 lines
2.7 KiB
Go

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
}