package dns import ( "context" "crypto/tls" "errors" "fmt" "net" "strings" "time" "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/picker" "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" "github.com/samber/lo" ) const serverFailureCacheTTL uint32 = 5 func minimalTTL(records []D.RR) uint32 { rr := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool { return r1.Header().Ttl < r2.Header().Ttl }) if rr == nil { return 0 } return rr.Header().Ttl } func updateTTL(records []D.RR, ttl uint32) { if len(records) == 0 { return } delta := minimalTTL(records) - ttl for i := range records { records[i].Header().Ttl = lo.Clamp(records[i].Header().Ttl-delta, 1, records[i].Header().Ttl) } } func putMsgToCache(c *cache.LruCache, key string, q D.Question, msg *D.Msg) { // skip dns cache for acme challenge if q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge.") { log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name) return } var ttl uint32 if msg.Rcode == D.RcodeServerFailure { // [...] a resolver MAY cache a server failure response. // If it does so it MUST NOT cache it for longer than five (5) minutes [...] ttl = serverFailureCacheTTL } else { ttl = minimalTTL(append(append(msg.Answer, msg.Ns...), msg.Extra...)) } if ttl == 0 { return } c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(ttl)*time.Second)) } func setMsgTTL(msg *D.Msg, ttl uint32) { for _, answer := range msg.Answer { answer.Header().Ttl = ttl } for _, ns := range msg.Ns { ns.Header().Ttl = ttl } for _, extra := range msg.Extra { extra.Header().Ttl = ttl } } func updateMsgTTL(msg *D.Msg, ttl uint32) { updateTTL(msg.Answer, ttl) updateTTL(msg.Ns, ttl) updateTTL(msg.Extra, ttl) } func isIPRequest(q D.Question) bool { return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) } func transform(servers []NameServer, resolver *Resolver) []dnsClient { ret := []dnsClient{} for _, s := range servers { switch s.Net { case "https": ret = append(ret, newDoHClient(s.Addr, s.Interface, resolver)) continue case "dhcp": ret = append(ret, newDHCPClient(s.Addr)) continue } host, port, _ := net.SplitHostPort(s.Addr) ret = append(ret, &client{ Client: &D.Client{ Net: s.Net, TLSConfig: &tls.Config{ ServerName: host, }, UDPSize: 4096, Timeout: 5 * time.Second, }, port: port, host: host, iface: s.Interface, r: resolver, }) } return ret } func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg { msg := &D.Msg{} msg.Answer = []D.RR{} msg.SetRcode(r, D.RcodeSuccess) msg.Authoritative = true msg.RecursionAvailable = true return msg } func msgToIP(msg *D.Msg) []net.IP { ips := []net.IP{} for _, answer := range msg.Answer { switch ans := answer.(type) { case *D.AAAA: ips = append(ips, ans.AAAA) case *D.A: ips = append(ips, ans.A) } } return ips } func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { fast, ctx := picker.WithContext(ctx) for _, client := range clients { r := client fast.Go(func() (any, error) { m, err := r.ExchangeContext(ctx, m) if err != nil { return nil, err } else if m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused { return nil, errors.New("server failure") } return m, nil }) } elm := fast.Wait() if elm == nil { err := errors.New("all DNS requests failed") if fErr := fast.Error(); fErr != nil { err = fmt.Errorf("%w, first error: %s", err, fErr.Error()) } return nil, err } msg = elm.(*D.Msg) return }