fix: 纯IPv6环境的一些问题

This commit is contained in:
Akizon77
2025-10-15 12:07:47 +08:00
parent a78756f101
commit 8e31514e9c
2 changed files with 155 additions and 22 deletions

View File

@@ -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
}