github.com/igoogolx/clash@v1.19.8/dns/util.go (about) 1 package dns 2 3 import ( 4 "context" 5 "crypto/tls" 6 "errors" 7 "fmt" 8 C "github.com/igoogolx/clash/constant" 9 "net" 10 "strings" 11 "time" 12 13 "github.com/igoogolx/clash/common/cache" 14 "github.com/igoogolx/clash/common/picker" 15 "github.com/igoogolx/clash/log" 16 17 D "github.com/miekg/dns" 18 "github.com/samber/lo" 19 ) 20 21 const serverFailureCacheTTL uint32 = 5 22 23 func minimalTTL(records []D.RR) uint32 { 24 rr := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool { 25 return r1.Header().Ttl < r2.Header().Ttl 26 }) 27 if rr == nil { 28 return 0 29 } 30 return rr.Header().Ttl 31 } 32 33 func updateTTL(records []D.RR, ttl uint32) { 34 if len(records) == 0 { 35 return 36 } 37 delta := minimalTTL(records) - ttl 38 for i := range records { 39 records[i].Header().Ttl = lo.Clamp(records[i].Header().Ttl-delta, 1, records[i].Header().Ttl) 40 } 41 } 42 43 func putMsgToCache(c *cache.LruCache, key string, q D.Question, msg *D.Msg) { 44 // skip dns cache for acme challenge 45 if q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge.") { 46 log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name) 47 return 48 } 49 50 var ttl uint32 51 if msg.Rcode == D.RcodeServerFailure { 52 // [...] a resolver MAY cache a server failure response. 53 // If it does so it MUST NOT cache it for longer than five (5) minutes [...] 54 ttl = serverFailureCacheTTL 55 } else { 56 ttl = minimalTTL(append(append(msg.Answer, msg.Ns...), msg.Extra...)) 57 } 58 if ttl == 0 { 59 return 60 } 61 c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(ttl)*time.Second)) 62 } 63 64 func setMsgTTL(msg *D.Msg, ttl uint32) { 65 for _, answer := range msg.Answer { 66 answer.Header().Ttl = ttl 67 } 68 69 for _, ns := range msg.Ns { 70 ns.Header().Ttl = ttl 71 } 72 73 for _, extra := range msg.Extra { 74 extra.Header().Ttl = ttl 75 } 76 } 77 78 func updateMsgTTL(msg *D.Msg, ttl uint32) { 79 updateTTL(msg.Answer, ttl) 80 updateTTL(msg.Ns, ttl) 81 updateTTL(msg.Extra, ttl) 82 } 83 84 func isIPRequest(q D.Question) bool { 85 return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) 86 } 87 88 func transform(servers []NameServer, getDialer func() (C.Proxy, error)) []dnsClient { 89 ret := []dnsClient{} 90 for _, s := range servers { 91 switch s.Net { 92 case "https": 93 ret = append(ret, newDoHClient(s.Addr, s.Interface, getDialer)) 94 continue 95 case "dhcp": 96 ret = append(ret, newDHCPClient(s.Addr, getDialer)) 97 continue 98 } 99 100 host, port, _ := net.SplitHostPort(s.Addr) 101 ret = append(ret, &client{ 102 Client: &D.Client{ 103 Net: s.Net, 104 TLSConfig: &tls.Config{ 105 ServerName: host, 106 }, 107 UDPSize: 4096, 108 Timeout: 5 * time.Second, 109 }, 110 port: port, 111 host: host, 112 iface: s.Interface, 113 getDialer: getDialer, 114 }) 115 } 116 return ret 117 } 118 119 func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg { 120 msg := &D.Msg{} 121 msg.Answer = []D.RR{} 122 123 msg.SetRcode(r, D.RcodeSuccess) 124 msg.Authoritative = true 125 msg.RecursionAvailable = true 126 127 return msg 128 } 129 130 func msgToIP(msg *D.Msg) []net.IP { 131 ips := []net.IP{} 132 133 for _, answer := range msg.Answer { 134 switch ans := answer.(type) { 135 case *D.AAAA: 136 ips = append(ips, ans.AAAA) 137 case *D.A: 138 ips = append(ips, ans.A) 139 } 140 } 141 142 return ips 143 } 144 145 func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { 146 fast, ctx := picker.WithContext(ctx) 147 for _, client := range clients { 148 r := client 149 fast.Go(func() (any, error) { 150 m, err := r.ExchangeContext(ctx, m) 151 if err != nil { 152 return nil, err 153 } else if m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused { 154 return nil, errors.New("server failure") 155 } 156 return m, nil 157 }) 158 } 159 160 elm := fast.Wait() 161 if elm == nil { 162 err := errors.New("all DNS requests failed") 163 if fErr := fast.Error(); fErr != nil { 164 err = fmt.Errorf("%w, first error: %s", err, fErr.Error()) 165 } 166 return nil, err 167 } 168 169 msg = elm.(*D.Msg) 170 return 171 }