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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// DNS服务器列表,按优先级排序
|
|
||||||
DNSServers = []string{
|
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,中国大陆
|
"114.114.114.114:53", // 114DNS,中国大陆
|
||||||
"8.8.8.8:53", // Google DNS,全球
|
"1.1.1.1:53", // Cloudflare IPv4
|
||||||
"8.8.4.4:53", // Google DNS备用,全球
|
"8.8.8.8:53", // Google IPv4
|
||||||
"1.1.1.1:53", // Cloudflare DNS,全球
|
"8.8.4.4:53", // Google IPv4 备用
|
||||||
"223.5.5.5:53", // 阿里DNS,中国大陆
|
"223.5.5.5:53", // 阿里DNS,中国大陆
|
||||||
"119.29.29.29:53", // DNSPod,中国大陆
|
"119.29.29.29:53", // DNSPod,中国大陆
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomDNSServer 自定义DNS服务器,可以通过命令行参数设置
|
// CustomDNSServer 自定义DNS服务器,可以通过命令行参数设置
|
||||||
CustomDNSServer string
|
CustomDNSServer string
|
||||||
|
|
||||||
|
preferV4Once sync.Once
|
||||||
|
hasIPv4 bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetCustomDNSServer 设置自定义DNS服务器
|
// SetCustomDNSServer 设置自定义DNS服务器
|
||||||
func SetCustomDNSServer(dnsServer string) {
|
func SetCustomDNSServer(dnsServer string) {
|
||||||
if dnsServer != "" {
|
if dnsServer == "" {
|
||||||
// 检查是否已包含端口,如果没有则添加默认端口53
|
return
|
||||||
if !strings.Contains(dnsServer, ":") {
|
|
||||||
dnsServer = dnsServer + ":53"
|
|
||||||
}
|
|
||||||
CustomDNSServer = dnsServer
|
|
||||||
}
|
}
|
||||||
|
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服务器
|
// getCurrentDNSServer 获取当前要使用的DNS服务器
|
||||||
@@ -67,7 +93,7 @@ func GetCustomResolver() *net.Resolver {
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Printf("Custom DNS server %s is unreachable, trying fallback servers", dnsServer)
|
||||||
// 如果自定义DNS不可用,则尝试内置列表作为兜底
|
// 如果自定义DNS不可用,则尝试内置列表作为兜底
|
||||||
for _, server := range DNSServers {
|
for _, server := range DNSServers {
|
||||||
if server == dnsServer {
|
if server == dnsServer {
|
||||||
@@ -103,6 +129,20 @@ func GetHTTPClient(timeout time.Duration) *http.Client {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
for _, ip := range ips {
|
||||||
dialer := &net.Dialer{
|
dialer := &net.Dialer{
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
@@ -137,3 +177,91 @@ func GetNetDialer(timeout time.Duration) *net.Dialer {
|
|||||||
Resolver: GetCustomResolver(),
|
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
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@@ -91,12 +92,15 @@ func EstablishWebSocketConnection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func connectWebSocket(websocketEndpoint string) (*ws.SafeConn, error) {
|
func connectWebSocket(websocketEndpoint string) (*ws.SafeConn, error) {
|
||||||
// 使用dnsresolver获取自定义网络拨号器
|
// 使用自定义解析和连接策略(IPv4 优先,较长超时)
|
||||||
netDialer := dnsresolver.GetNetDialer(5 * time.Second)
|
|
||||||
|
|
||||||
dialer := &websocket.Dialer{
|
dialer := &websocket.Dialer{
|
||||||
HandshakeTimeout: 5 * time.Second,
|
HandshakeTimeout: 15 * time.Second,
|
||||||
NetDialContext: netDialer.DialContext,
|
NetDialContext: dnsresolver.GetDialContext(15 * time.Second),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选:忽略 TLS 证书(当用户显式设置)
|
||||||
|
if flags.IgnoreUnsafeCert {
|
||||||
|
dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建请求头并添加Cloudflare Access头部
|
// 创建请求头并添加Cloudflare Access头部
|
||||||
@@ -165,12 +169,13 @@ func establishTerminalConnection(token, id, endpoint string) {
|
|||||||
endpoint = strings.TrimSuffix(endpoint, "/") + "/api/clients/terminal?token=" + token + "&id=" + id
|
endpoint = strings.TrimSuffix(endpoint, "/") + "/api/clients/terminal?token=" + token + "&id=" + id
|
||||||
endpoint = "ws" + strings.TrimPrefix(endpoint, "http")
|
endpoint = "ws" + strings.TrimPrefix(endpoint, "http")
|
||||||
|
|
||||||
// 使用dnsresolver获取自定义网络拨号器
|
// 使用与主 WS 相同的拨号策略
|
||||||
netDialer := dnsresolver.GetNetDialer(5 * time.Second)
|
|
||||||
|
|
||||||
dialer := &websocket.Dialer{
|
dialer := &websocket.Dialer{
|
||||||
HandshakeTimeout: 5 * time.Second,
|
HandshakeTimeout: 15 * time.Second,
|
||||||
NetDialContext: netDialer.DialContext,
|
NetDialContext: dnsresolver.GetDialContext(15 * time.Second),
|
||||||
|
}
|
||||||
|
if flags.IgnoreUnsafeCert {
|
||||||
|
dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建请求头并添加Cloudflare Access头部
|
// 创建请求头并添加Cloudflare Access头部
|
||||||
|
Reference in New Issue
Block a user