
Regarding DNS cache, it's advisable to not rely on the TTL values mentioned in the Ns and Extra sections. Otherwise, any DNS queries that do not yield any results (such as for non-existent.example.com) will be accidentally cached. The need for the ACME challenge hack has been eliminated and as such, it has been removed.
153 lines
3.0 KiB
Go
153 lines
3.0 KiB
Go
package dns
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/Dreamacro/clash/common/cache"
|
|
"github.com/Dreamacro/clash/common/picker"
|
|
|
|
D "github.com/miekg/dns"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
func minimalTTL(records []D.RR) uint32 {
|
|
if len(records) == 0 {
|
|
return 0
|
|
}
|
|
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) {
|
|
ttl := minimalTTL(msg.Answer)
|
|
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
|
|
}
|