From 0fe058254cce304c5cacf2bf8ab2efc2dcc2b91c Mon Sep 17 00:00:00 2001 From: liqianjie <3745983@qq.com> Date: Fri, 20 Mar 2026 22:32:21 +0800 Subject: [PATCH] fix: add fallback DNS resolver for Android with multi-DNS support (#1835) On Android, /etc/resolv.conf does not exist, causing Go's default DNS resolution to fail. This adds an init() hook that: 1. Detects missing /etc/resolv.conf (Android environment) 2. Configures a custom resolver with PreferGo: true 3. Supports multiple DNS servers via PICOCLAW_DNS_SERVER env var - Semicolon-separated: "8.8.8.8:53;1.1.1.1:53" - Single server also works: "8.8.8.8" - Auto-appends :53 if port omitted 4. Round-robin rotation across configured servers 5. Defaults to Google DNS + Cloudflare DNS Also patches http.DefaultTransport to use the custom resolver. --- cmd/picoclaw/dns_noresolv.go | 64 ++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 cmd/picoclaw/dns_noresolv.go diff --git a/cmd/picoclaw/dns_noresolv.go b/cmd/picoclaw/dns_noresolv.go new file mode 100644 index 000000000..ba4ae1f4f --- /dev/null +++ b/cmd/picoclaw/dns_noresolv.go @@ -0,0 +1,64 @@ +package main + +import ( + "context" + "net" + "net/http" + "os" + "strings" + "sync/atomic" + "time" +) + +func init() { + // 仅在 /etc/resolv.conf 不存在时才覆盖(即 Android 环境) + if _, err := os.Stat("/etc/resolv.conf"); err == nil { + return + } + + // 从环境变量获取 DNS server 列表,多个用 ; 隔开 + // 例如: PICOCLAW_DNS_SERVER="8.8.8.8:53;1.1.1.1:53;223.5.5.5:53" + dnsEnv := os.Getenv("PICOCLAW_DNS_SERVER") + if dnsEnv == "" { + dnsEnv = "8.8.8.8:53;1.1.1.1:53" + } + + var dnsServers []string + for _, s := range strings.Split(dnsEnv, ";") { + s = strings.TrimSpace(s) + if s != "" { + // 如果没有带端口号,自动补上 :53 + if _, _, err := net.SplitHostPort(s); err != nil { + s = s + ":53" + } + dnsServers = append(dnsServers, s) + } + } + + // 轮询索引,在多个 DNS 服务器之间轮转 + var idx uint64 + + customResolver := &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + d := net.Dialer{Timeout: 5 * time.Second} + // Round-robin: 依次尝试不同的 DNS 服务器 + server := dnsServers[atomic.AddUint64(&idx, 1)%uint64(len(dnsServers))] + return d.DialContext(ctx, "udp", server) + }, + } + + // 覆盖全局 DefaultResolver + net.DefaultResolver = customResolver + + // 覆盖 http.DefaultTransport 使用自定义 DNS 解析的 DialContext + dialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + Resolver: customResolver, + } + + if tr, ok := http.DefaultTransport.(*http.Transport); ok { + tr.DialContext = dialer.DialContext + } +}