2018-12-05 21:13:29 +08:00
|
|
|
package dns
|
|
|
|
|
|
|
|
import (
|
2021-11-17 16:03:47 +08:00
|
|
|
"context"
|
2022-08-24 21:36:19 +08:00
|
|
|
"errors"
|
2021-11-17 16:03:47 +08:00
|
|
|
"fmt"
|
2022-04-20 01:52:51 +08:00
|
|
|
"net/netip"
|
2022-05-15 13:16:45 +08:00
|
|
|
"strings"
|
2018-12-05 21:13:29 +08:00
|
|
|
"time"
|
|
|
|
|
2023-11-03 21:01:45 +08:00
|
|
|
"github.com/metacubex/mihomo/common/picker"
|
|
|
|
"github.com/metacubex/mihomo/component/resolver"
|
|
|
|
"github.com/metacubex/mihomo/log"
|
2018-12-05 21:13:29 +08:00
|
|
|
|
|
|
|
D "github.com/miekg/dns"
|
2023-06-06 09:45:05 +08:00
|
|
|
"github.com/samber/lo"
|
2018-12-05 21:13:29 +08:00
|
|
|
)
|
|
|
|
|
2022-11-12 11:14:51 +08:00
|
|
|
const (
|
|
|
|
MaxMsgSize = 65535
|
|
|
|
)
|
|
|
|
|
2023-10-16 09:21:06 +08:00
|
|
|
const serverFailureCacheTTL uint32 = 5
|
|
|
|
|
2023-06-06 09:45:05 +08:00
|
|
|
func minimalTTL(records []D.RR) uint32 {
|
2023-10-16 09:21:06 +08:00
|
|
|
rr := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool {
|
2023-06-06 09:45:05 +08:00
|
|
|
return r1.Header().Ttl < r2.Header().Ttl
|
2023-09-17 17:18:35 +08:00
|
|
|
})
|
2023-10-16 09:21:06 +08:00
|
|
|
if rr == nil {
|
|
|
|
return 0
|
2023-09-17 17:18:35 +08:00
|
|
|
}
|
2023-10-16 09:21:06 +08:00
|
|
|
return rr.Header().Ttl
|
2023-06-06 09:45:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-02 17:07:36 +08:00
|
|
|
func putMsgToCache(c dnsCache, key string, q D.Question, msg *D.Msg) {
|
2023-10-16 09:21:06 +08:00
|
|
|
// 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
|
2018-12-05 21:13:29 +08:00
|
|
|
}
|
|
|
|
|
2023-10-16 09:21:06 +08:00
|
|
|
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))
|
2019-02-23 20:31:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2018-12-05 21:13:29 +08:00
|
|
|
}
|
2019-06-28 12:29:08 +08:00
|
|
|
|
2023-06-06 09:45:05 +08:00
|
|
|
func updateMsgTTL(msg *D.Msg, ttl uint32) {
|
|
|
|
updateTTL(msg.Answer, ttl)
|
|
|
|
updateTTL(msg.Ns, ttl)
|
|
|
|
updateTTL(msg.Extra, ttl)
|
|
|
|
}
|
|
|
|
|
2019-06-28 12:29:08 +08:00
|
|
|
func isIPRequest(q D.Question) bool {
|
2023-03-12 15:00:59 +08:00
|
|
|
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA || q.Qtype == D.TypeCNAME)
|
2019-06-28 12:29:08 +08:00
|
|
|
}
|
|
|
|
|
2022-07-21 14:03:49 +08:00
|
|
|
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
2023-06-11 23:01:45 +08:00
|
|
|
ret := make([]dnsClient, 0, len(servers))
|
2019-06-28 12:29:08 +08:00
|
|
|
for _, s := range servers {
|
2025-06-06 00:24:57 +08:00
|
|
|
var c dnsClient
|
2021-09-06 23:07:34 +08:00
|
|
|
switch s.Net {
|
|
|
|
case "https":
|
2025-06-06 00:24:57 +08:00
|
|
|
c = newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName)
|
2021-09-06 23:07:34 +08:00
|
|
|
case "dhcp":
|
2025-06-06 00:24:57 +08:00
|
|
|
c = newDHCPClient(s.Addr)
|
2023-04-26 15:57:55 +08:00
|
|
|
case "system":
|
2025-06-06 00:24:57 +08:00
|
|
|
c = newSystemClient()
|
2023-06-11 23:01:45 +08:00
|
|
|
case "rcode":
|
2025-06-06 00:24:57 +08:00
|
|
|
c = newRCodeClient(s.Addr)
|
2022-01-27 12:25:53 +08:00
|
|
|
case "quic":
|
2025-06-06 00:52:12 +08:00
|
|
|
c = newDoQ(s.Addr, resolver, s.Params, s.ProxyAdapter, s.ProxyName)
|
2025-06-06 00:24:57 +08:00
|
|
|
default:
|
2025-06-06 00:52:12 +08:00
|
|
|
c = newClient(s.Addr, resolver, s.Net, s.Params, s.ProxyAdapter, s.ProxyName)
|
2025-06-06 00:24:57 +08:00
|
|
|
}
|
|
|
|
|
2025-06-06 00:45:58 +08:00
|
|
|
c = warpClientWithEdns0Subnet(c, s.Params)
|
|
|
|
|
2025-06-06 00:24:57 +08:00
|
|
|
if s.Params["disable-ipv4"] == "true" {
|
2025-06-06 00:45:58 +08:00
|
|
|
c = warpClientWithDisableType(c, D.TypeA)
|
2025-06-06 00:24:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if s.Params["disable-ipv6"] == "true" {
|
2025-06-06 00:45:58 +08:00
|
|
|
c = warpClientWithDisableType(c, D.TypeAAAA)
|
2019-06-28 12:29:08 +08:00
|
|
|
}
|
|
|
|
|
2025-06-06 00:24:57 +08:00
|
|
|
ret = append(ret, c)
|
2019-06-28 12:29:08 +08:00
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
2020-10-13 00:15:49 +08:00
|
|
|
|
2025-06-06 00:45:58 +08:00
|
|
|
type clientWithDisableType struct {
|
2025-06-06 00:24:57 +08:00
|
|
|
dnsClient
|
|
|
|
qType uint16
|
|
|
|
}
|
|
|
|
|
2025-06-06 00:45:58 +08:00
|
|
|
func (c clientWithDisableType) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
2025-06-06 00:24:57 +08:00
|
|
|
if len(m.Question) > 0 {
|
|
|
|
q := m.Question[0]
|
|
|
|
if q.Qtype == c.qType {
|
|
|
|
return handleMsgWithEmptyAnswer(m), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return c.dnsClient.ExchangeContext(ctx, m)
|
|
|
|
}
|
|
|
|
|
2025-06-06 00:45:58 +08:00
|
|
|
func warpClientWithDisableType(c dnsClient, qType uint16) dnsClient {
|
|
|
|
return clientWithDisableType{c, qType}
|
|
|
|
}
|
|
|
|
|
|
|
|
type clientWithEdns0Subnet struct {
|
|
|
|
dnsClient
|
|
|
|
ecsPrefix netip.Prefix
|
|
|
|
ecsOverride bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c clientWithEdns0Subnet) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
|
|
|
m = m.Copy()
|
|
|
|
setEdns0Subnet(m, c.ecsPrefix, c.ecsOverride)
|
|
|
|
return c.dnsClient.ExchangeContext(ctx, m)
|
|
|
|
}
|
|
|
|
|
|
|
|
func warpClientWithEdns0Subnet(c dnsClient, params map[string]string) dnsClient {
|
|
|
|
var ecsPrefix netip.Prefix
|
|
|
|
var ecsOverride bool
|
|
|
|
if ecs := params["ecs"]; ecs != "" {
|
|
|
|
prefix, err := netip.ParsePrefix(ecs)
|
|
|
|
if err != nil {
|
|
|
|
addr, err := netip.ParseAddr(ecs)
|
|
|
|
if err != nil {
|
|
|
|
log.Warnln("DNS [%s] config with invalid ecs: %s", c.Address(), ecs)
|
|
|
|
} else {
|
|
|
|
ecsPrefix = netip.PrefixFrom(addr, addr.BitLen())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ecsPrefix = prefix
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ecsPrefix.IsValid() {
|
|
|
|
log.Debugln("DNS [%s] config with ecs: %s", c.Address(), ecsPrefix)
|
|
|
|
if params["ecs-override"] == "true" {
|
|
|
|
ecsOverride = true
|
|
|
|
}
|
|
|
|
return clientWithEdns0Subnet{c, ecsPrefix, ecsOverride}
|
|
|
|
}
|
|
|
|
return c
|
2025-06-06 00:24:57 +08:00
|
|
|
}
|
|
|
|
|
2020-10-13 00:15:49 +08:00
|
|
|
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
|
|
|
|
}
|
2021-01-23 14:49:46 +08:00
|
|
|
|
2025-04-09 11:39:00 +08:00
|
|
|
func msgToIP(msg *D.Msg) (ips []netip.Addr) {
|
2021-01-23 14:49:46 +08:00
|
|
|
for _, answer := range msg.Answer {
|
2025-04-09 11:39:00 +08:00
|
|
|
var ip netip.Addr
|
2021-01-23 14:49:46 +08:00
|
|
|
switch ans := answer.(type) {
|
|
|
|
case *D.AAAA:
|
2025-04-09 11:39:00 +08:00
|
|
|
ip, _ = netip.AddrFromSlice(ans.AAAA)
|
2021-01-23 14:49:46 +08:00
|
|
|
case *D.A:
|
2025-04-09 11:39:00 +08:00
|
|
|
ip, _ = netip.AddrFromSlice(ans.A)
|
|
|
|
default:
|
|
|
|
continue
|
2021-01-23 14:49:46 +08:00
|
|
|
}
|
2025-04-09 11:39:00 +08:00
|
|
|
if !ip.IsValid() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
ip = ip.Unmap()
|
|
|
|
ips = append(ips, ip)
|
2021-01-23 14:49:46 +08:00
|
|
|
}
|
2025-04-09 11:39:00 +08:00
|
|
|
return
|
2021-01-23 14:49:46 +08:00
|
|
|
}
|
2021-11-17 16:03:47 +08:00
|
|
|
|
2022-05-15 13:16:45 +08:00
|
|
|
func msgToDomain(msg *D.Msg) string {
|
|
|
|
if len(msg.Question) > 0 {
|
|
|
|
return strings.TrimRight(msg.Question[0].Name, ".")
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:27:29 +08:00
|
|
|
func msgToQtype(msg *D.Msg) (uint16, string) {
|
|
|
|
if len(msg.Question) > 0 {
|
|
|
|
qType := msg.Question[0].Qtype
|
|
|
|
return qType, D.Type(qType).String()
|
|
|
|
}
|
|
|
|
return 0, ""
|
|
|
|
}
|
|
|
|
|
2023-06-11 20:58:51 +08:00
|
|
|
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
|
2023-07-14 09:55:43 +08:00
|
|
|
cache = true
|
2022-11-12 20:43:48 +08:00
|
|
|
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
|
2023-07-14 09:55:43 +08:00
|
|
|
defer fast.Close()
|
2023-01-28 22:33:03 +08:00
|
|
|
domain := msgToDomain(m)
|
2025-07-05 23:04:05 +08:00
|
|
|
_, qTypeStr := msgToQtype(m)
|
2022-08-24 21:36:19 +08:00
|
|
|
for _, client := range clients {
|
2023-07-14 09:55:43 +08:00
|
|
|
if _, isRCodeClient := client.(rcodeClient); isRCodeClient {
|
2023-10-25 18:07:45 +08:00
|
|
|
msg, err = client.ExchangeContext(ctx, m)
|
2023-07-14 09:55:43 +08:00
|
|
|
return msg, false, err
|
|
|
|
}
|
2023-06-12 10:01:13 +00:00
|
|
|
client := client // shadow define client to ensure the value captured by the closure will not be changed in the next loop
|
2022-11-19 10:35:45 +08:00
|
|
|
fast.Go(func() (*D.Msg, error) {
|
2024-07-19 19:27:29 +08:00
|
|
|
log.Debugln("[DNS] resolve %s %s from %s", domain, qTypeStr, client.Address())
|
2023-06-11 23:01:45 +08:00
|
|
|
m, err := client.ExchangeContext(ctx, m)
|
2022-08-24 21:36:19 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-06-11 20:58:51 +08:00
|
|
|
} else if cache && (m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused) {
|
|
|
|
// currently, cache indicates whether this msg was from a RCode client,
|
|
|
|
// so we would ignore RCode errors from RCode clients.
|
2023-07-14 09:55:43 +08:00
|
|
|
return nil, errors.New("server failure: " + D.RcodeToString[m.Rcode])
|
2022-08-24 21:36:19 +08:00
|
|
|
}
|
2024-07-19 19:27:29 +08:00
|
|
|
ips := msgToIP(m)
|
|
|
|
log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, qTypeStr, client.Address())
|
2022-08-24 21:36:19 +08:00
|
|
|
return m, nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-07-14 09:55:43 +08:00
|
|
|
msg = fast.Wait()
|
|
|
|
if msg == nil {
|
|
|
|
err = errors.New("all DNS requests failed")
|
2022-08-24 21:36:19 +08:00
|
|
|
if fErr := fast.Error(); fErr != nil {
|
2023-07-02 09:59:18 +08:00
|
|
|
err = fmt.Errorf("%w, first error: %w", err, fErr)
|
2022-08-24 21:36:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|