mirror of
https://github.com/fankes/komari-agent.git
synced 2025-10-18 18:49:23 +08:00
fix: 纯IPv6环境的一些问题
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user