platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
112
selective-vpn-api/app/transport_singbox_dns_migration_convert.go
Normal file
112
selective-vpn-api/app/transport_singbox_dns_migration_convert.go
Normal file
@@ -0,0 +1,112 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user