From 8e31514e9c3569376aa95adbc3b46506c2d48612 Mon Sep 17 00:00:00 2001 From: Akizon77 Date: Wed, 15 Oct 2025 12:07:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BA=AFIPv6=E7=8E=AF=E5=A2=83=E7=9A=84?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dnsresolver/resolver.go | 152 ++++++++++++++++++++++++++++++++++++---- server/websocket.go | 25 ++++--- 2 files changed, 155 insertions(+), 22 deletions(-) diff --git a/dnsresolver/resolver.go b/dnsresolver/resolver.go index b7db093..8505caf 100644 --- a/dnsresolver/resolver.go +++ b/dnsresolver/resolver.go @@ -3,37 +3,63 @@ package dnsresolver import ( "context" "fmt" + "log" "net" "net/http" + "sort" "strings" + "sync" "time" ) var ( - // DNS服务器列表,按优先级排序 DNSServers = []string{ + "[2606:4700:4700::1111]:53", // Cloudflare IPv6 + "[2606:4700:4700::1001]:53", // Cloudflare IPv6 备用 + "[2001:4860:4860::8888]:53", // Google IPv6 + "[2001:4860:4860::8844]:53", // Google IPv6 备用 + "114.114.114.114:53", // 114DNS,中国大陆 - "8.8.8.8:53", // Google DNS,全球 - "8.8.4.4:53", // Google DNS备用,全球 - "1.1.1.1:53", // Cloudflare DNS,全球 + "1.1.1.1:53", // Cloudflare IPv4 + "8.8.8.8:53", // Google IPv4 + "8.8.4.4:53", // Google IPv4 备用 "223.5.5.5:53", // 阿里DNS,中国大陆 "119.29.29.29:53", // DNSPod,中国大陆 - } // CustomDNSServer 自定义DNS服务器,可以通过命令行参数设置 CustomDNSServer string + + preferV4Once sync.Once + hasIPv4 bool ) // SetCustomDNSServer 设置自定义DNS服务器 func SetCustomDNSServer(dnsServer string) { - if dnsServer != "" { - // 检查是否已包含端口,如果没有则添加默认端口53 - if !strings.Contains(dnsServer, ":") { - dnsServer = dnsServer + ":53" - } - CustomDNSServer = dnsServer + if dnsServer == "" { + return } + CustomDNSServer = normalizeDNSServer(dnsServer) +} + +// normalizeDNSServer 将输入的 DNS 服务器字符串规范化为 host:port 形式: +// - IPv6 地址自动加方括号并补全端口 :53(若未提供) +// - IPv4/域名未提供端口时补全 :53 +func normalizeDNSServer(s string) string { + s = strings.TrimSpace(s) + // 已是 [ipv6]:port 或 host:port 形式 + if (strings.HasPrefix(s, "[") && strings.Contains(s, "]:")) || (strings.Count(s, ":") == 1 && !strings.Contains(s, "]")) { + return s + } + // 纯 IPv6(未加端口/括号) + if strings.Count(s, ":") >= 2 && !strings.Contains(s, "]") { + return "[" + s + "]:53" + } + // 其它情况:若未包含端口则补 53 + if !strings.Contains(s, ":") { + return s + ":53" + } + return s } // getCurrentDNSServer 获取当前要使用的DNS服务器 @@ -67,7 +93,7 @@ func GetCustomResolver() *net.Resolver { return conn, nil } } - + log.Printf("Custom DNS server %s is unreachable, trying fallback servers", dnsServer) // 如果自定义DNS不可用,则尝试内置列表作为兜底 for _, server := range DNSServers { if server == dnsServer { @@ -103,6 +129,20 @@ func GetHTTPClient(timeout time.Duration) *http.Client { if err != nil { return nil, err } + // 根据本机是否具备 IPv4 动态排序 + preferIPv4 := preferIPv4First() + sort.SliceStable(ips, func(i, j int) bool { + ip1 := net.ParseIP(ips[i]) + ip2 := net.ParseIP(ips[j]) + if ip1 == nil || ip2 == nil { + return false + } + if preferIPv4 { + return ip1.To4() != nil && ip2.To4() == nil + } + // IPv6 优先 + return ip1.To4() == nil && ip2.To4() != nil + }) for _, ip := range ips { dialer := &net.Dialer{ Timeout: 30 * time.Second, @@ -137,3 +177,91 @@ func GetNetDialer(timeout time.Duration) *net.Dialer { Resolver: GetCustomResolver(), } } + +// GetDialContext 返回一个自定义 DialContext: +// - 使用自定义解析器解析主机名 +// - 优先尝试 IPv4,再尝试 IPv6 +// - 逐个 IP 进行连接尝试,直到成功或全部失败 +func GetDialContext(timeout time.Duration) func(ctx context.Context, network, addr string) (net.Conn, error) { + if timeout <= 0 { + timeout = 15 * time.Second + } + + resolver := GetCustomResolver() + + return func(ctx context.Context, network, addr string) (net.Conn, error) { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + // 为解析设置一个带超时的子 context,避免整体拨号过快超时 + lookupCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + ips, err := resolver.LookupHost(lookupCtx, host) + if err != nil { + return nil, err + } + + // 根据本机是否具备 IPv4 动态排序 + preferIPv4 := preferIPv4First() + sort.SliceStable(ips, func(i, j int) bool { + ip1 := net.ParseIP(ips[i]) + ip2 := net.ParseIP(ips[j]) + if ip1 == nil || ip2 == nil { + return false + } + if preferIPv4 { + return ip1.To4() != nil && ip2.To4() == nil + } + // IPv6 优先 + return ip1.To4() == nil && ip2.To4() != nil + }) + + // 逐个 IP 尝试连接 + for _, ip := range ips { + d := &net.Dialer{ + Timeout: timeout, + KeepAlive: 30 * time.Second, + DualStack: true, + } + c, err := d.DialContext(ctx, network, net.JoinHostPort(ip, port)) + if err == nil { + return c, nil + } + } + return nil, fmt.Errorf("failed to dial to any of the resolved IPs") + } +} + +// preferIPv4First 检测本机是否存在可用的 IPv4 地址,若没有则在连接尝试中优先 IPv6 +func preferIPv4First() bool { + preferV4Once.Do(func() { + ifaces, _ := net.Interfaces() + for _, iface := range ifaces { + if (iface.Flags&net.FlagUp) == 0 || (iface.Flags&net.FlagLoopback) != 0 { + continue + } + addrs, _ := iface.Addrs() + for _, a := range addrs { + var ip net.IP + switch v := a.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip == nil || ip.IsLoopback() { + continue + } + if ip.To4() != nil { + hasIPv4 = true + return + } + } + } + hasIPv4 = false + }) + return hasIPv4 +} diff --git a/server/websocket.go b/server/websocket.go index 1afece5..e00c4ef 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -1,6 +1,7 @@ package server import ( + "crypto/tls" "encoding/json" "fmt" "log" @@ -91,12 +92,15 @@ func EstablishWebSocketConnection() { } func connectWebSocket(websocketEndpoint string) (*ws.SafeConn, error) { - // 使用dnsresolver获取自定义网络拨号器 - netDialer := dnsresolver.GetNetDialer(5 * time.Second) - + // 使用自定义解析和连接策略(IPv4 优先,较长超时) dialer := &websocket.Dialer{ - HandshakeTimeout: 5 * time.Second, - NetDialContext: netDialer.DialContext, + HandshakeTimeout: 15 * time.Second, + NetDialContext: dnsresolver.GetDialContext(15 * time.Second), + } + + // 可选:忽略 TLS 证书(当用户显式设置) + if flags.IgnoreUnsafeCert { + dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} } // 创建请求头并添加Cloudflare Access头部 @@ -165,12 +169,13 @@ func establishTerminalConnection(token, id, endpoint string) { endpoint = strings.TrimSuffix(endpoint, "/") + "/api/clients/terminal?token=" + token + "&id=" + id endpoint = "ws" + strings.TrimPrefix(endpoint, "http") - // 使用dnsresolver获取自定义网络拨号器 - netDialer := dnsresolver.GetNetDialer(5 * time.Second) - + // 使用与主 WS 相同的拨号策略 dialer := &websocket.Dialer{ - HandshakeTimeout: 5 * time.Second, - NetDialContext: netDialer.DialContext, + HandshakeTimeout: 15 * time.Second, + NetDialContext: dnsresolver.GetDialContext(15 * time.Second), + } + if flags.IgnoreUnsafeCert { + dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} } // 创建请求头并添加Cloudflare Access头部