
This commit adds an updated function that adjusts the TTL values of DNS records are based on the minimum TTL the value found in the records list so that all records share the same TTL value. This ensures consistency in the cache expiry time for all records to prevent caching issues.
167 lines
3.5 KiB
Go
167 lines
3.5 KiB
Go
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"
|
|
)
|
|
|
|
func minimalTTL(records []D.RR) uint32 {
|
|
return lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
|
return r1.Header().Ttl < r2.Header().Ttl
|
|
}).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
|
|
switch {
|
|
case len(msg.Answer) != 0:
|
|
ttl = minimalTTL(msg.Answer)
|
|
case len(msg.Ns) != 0:
|
|
ttl = minimalTTL(msg.Ns)
|
|
case len(msg.Extra) != 0:
|
|
ttl = minimalTTL(msg.Extra)
|
|
default:
|
|
log.Debugln("[DNS] response msg empty: %#v", msg)
|
|
return
|
|
}
|
|
|
|
c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Second*time.Duration(ttl)))
|
|
}
|
|
|
|
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
|
|
}
|