mihomo/dns/resolver.go

532 lines
13 KiB
Go
Raw Normal View History

2019-06-28 12:29:08 +08:00
package dns
import (
"context"
"errors"
2022-04-06 04:25:53 +08:00
"net/netip"
"strings"
2019-06-28 12:29:08 +08:00
"time"
"github.com/Dreamacro/clash/common/atomic"
2019-06-28 12:29:08 +08:00
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/fakeip"
2021-11-17 16:03:47 +08:00
"github.com/Dreamacro/clash/component/geodata/router"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log"
2019-06-28 12:29:08 +08:00
D "github.com/miekg/dns"
"github.com/samber/lo"
2019-07-14 19:29:58 +08:00
"golang.org/x/sync/singleflight"
2019-06-28 12:29:08 +08:00
)
type dnsClient interface {
2019-06-28 12:29:08 +08:00
Exchange(m *D.Msg) (msg *D.Msg, err error)
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
2023-01-28 22:33:03 +08:00
Address() string
2019-06-28 12:29:08 +08:00
}
type result struct {
Msg *D.Msg
Error error
}
type geositePolicyRecord struct {
matcher fallbackDomainFilter
policy *Policy
inversedMatching bool
}
type domainSetPolicyRecord struct {
domainSetProvider provider.RuleProvider
policy *Policy
}
2019-06-28 12:29:08 +08:00
type Resolver struct {
ipv6 bool
2023-03-10 23:38:16 +08:00
ipv6Timeout time.Duration
hosts *trie.DomainTrie[resolver.HostValue]
main []dnsClient
fallback []dnsClient
fallbackDomainFilters []fallbackDomainFilter
fallbackIPFilters []fallbackIPFilter
group singleflight.Group
2022-04-05 20:23:16 +08:00
lruCache *cache.LruCache[string, *D.Msg]
2022-04-06 04:25:53 +08:00
policy *trie.DomainTrie[*Policy]
domainSetPolicy []domainSetPolicyRecord
geositePolicy []geositePolicyRecord
2022-03-28 00:44:13 +08:00
proxyServer []dnsClient
2019-06-28 12:29:08 +08:00
}
func (r *Resolver) LookupIPPrimaryIPv4(ctx context.Context, host string) (ips []netip.Addr, err error) {
ch := make(chan []netip.Addr, 1)
2019-06-28 12:29:08 +08:00
go func() {
defer close(ch)
ip, err := r.lookupIP(ctx, host, D.TypeAAAA)
2019-06-28 12:29:08 +08:00
if err != nil {
return
}
ch <- ip
}()
ips, err = r.lookupIP(ctx, host, D.TypeA)
2019-06-28 12:29:08 +08:00
if err == nil {
return
}
ip, open := <-ch
if !open {
return nil, resolver.ErrIPNotFound
2019-06-28 12:29:08 +08:00
}
return ip, nil
}
func (r *Resolver) LookupIP(ctx context.Context, host string) (ips []netip.Addr, err error) {
ch := make(chan []netip.Addr, 1)
go func() {
defer close(ch)
ip, err := r.lookupIP(ctx, host, D.TypeAAAA)
if err != nil {
return
}
ch <- ip
}()
ips, err = r.lookupIP(ctx, host, D.TypeA)
2023-03-10 23:38:16 +08:00
var waitIPv6 *time.Timer
if r != nil && r.ipv6Timeout > 0 {
2023-03-10 23:38:16 +08:00
waitIPv6 = time.NewTimer(r.ipv6Timeout)
} else {
waitIPv6 = time.NewTimer(100 * time.Millisecond)
}
defer waitIPv6.Stop()
select {
case ipv6s, open := <-ch:
if !open && err != nil {
return nil, resolver.ErrIPNotFound
}
ips = append(ips, ipv6s...)
2023-03-10 23:38:16 +08:00
case <-waitIPv6.C:
2022-05-28 09:58:45 +08:00
// wait ipv6 result
}
return ips, nil
}
2022-11-12 21:31:07 +08:00
// LookupIPv4 request with TypeA
func (r *Resolver) LookupIPv4(ctx context.Context, host string) ([]netip.Addr, error) {
return r.lookupIP(ctx, host, D.TypeA)
}
2022-11-12 21:31:07 +08:00
// LookupIPv6 request with TypeAAAA
func (r *Resolver) LookupIPv6(ctx context.Context, host string) ([]netip.Addr, error) {
return r.lookupIP(ctx, host, D.TypeAAAA)
2019-09-11 17:00:55 +08:00
}
2019-06-28 12:29:08 +08:00
2022-04-20 01:52:51 +08:00
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
for _, filter := range r.fallbackIPFilters {
2019-09-15 13:36:45 +08:00
if filter.Match(ip) {
return true
}
}
return false
}
2019-06-28 12:29:08 +08:00
// Exchange a batch of dns request, and it use cache
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return r.ExchangeContext(context.Background(), m)
}
// ExchangeContext a batch of dns request with context.Context, and it use cache
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
2019-06-28 12:29:08 +08:00
if len(m.Question) == 0 {
return nil, errors.New("should have one question at least")
}
continueFetch := false
defer func() {
if continueFetch || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
go func() {
2022-11-16 17:53:52 +08:00
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
defer cancel()
_, _ = r.exchangeWithoutCache(ctx, m) // ignore result, just for putMsgToCache
}()
}
}()
2019-06-28 12:29:08 +08:00
q := m.Question[0]
2022-04-20 01:52:51 +08:00
cacheM, expireTime, hit := r.lruCache.GetWithExpire(q.String())
if hit {
now := time.Now()
2022-04-20 01:52:51 +08:00
msg = cacheM.Copy()
if expireTime.Before(now) {
setMsgTTL(msg, uint32(1)) // Continue fetch
continueFetch = true
} else {
// updating TTL by subtracting common delta time from each DNS record
updateMsgTTL(msg, uint32(time.Until(expireTime).Seconds()))
}
2019-06-28 12:29:08 +08:00
return
}
return r.exchangeWithoutCache(ctx, m)
}
// ExchangeWithoutCache a batch of dns request, and it do NOT GET from cache
func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
q := m.Question[0]
retryNum := 0
retryMax := 3
fn := func() (result any, err error) {
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) // reset timeout in singleflight
defer cancel()
2023-06-11 20:58:51 +08:00
cache := false
defer func() {
if err != nil {
result = retryNum
retryNum++
return
}
msg := result.(*D.Msg)
2023-06-11 20:58:51 +08:00
if cache {
// OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master files.
msg.Extra = lo.Filter(msg.Extra, func(rr D.RR, index int) bool {
return rr.Header().Rrtype != D.TypeOPT
})
putMsgToCache(r.lruCache, q.String(), q, msg)
2023-06-11 20:58:51 +08:00
}
}()
isIPReq := isIPRequest(q)
if isIPReq {
cache = true
return r.ipExchange(ctx, m)
2019-06-28 12:29:08 +08:00
}
if matched := r.matchPolicy(m); len(matched) != 0 {
2023-06-11 20:58:51 +08:00
result, cache, err = r.batchExchange(ctx, matched, m)
return
}
2023-06-11 20:58:51 +08:00
result, cache, err = r.batchExchange(ctx, r.main, m)
return
}
ch := r.group.DoChan(q.String(), fn)
2019-07-14 19:29:58 +08:00
var result singleflight.Result
select {
case result = <-ch:
break
case <-ctx.Done():
select {
case result = <-ch: // maybe ctxDone and chFinish in same time, get DoChan's result as much as possible
break
default:
go func() { // start a retrying monitor in background
result := <-ch
ret, err, shared := result.Val, result.Err, result.Shared
if err != nil && !shared && ret.(int) < retryMax { // retry
r.group.DoChan(q.String(), fn)
}
}()
return nil, ctx.Err()
}
}
ret, err, shared := result.Val, result.Err, result.Shared
if err != nil && !shared && ret.(int) < retryMax { // retry
r.group.DoChan(q.String(), fn)
}
2019-07-14 19:29:58 +08:00
if err == nil {
msg = ret.(*D.Msg)
2020-03-24 10:13:53 +08:00
if shared {
msg = msg.Copy()
}
2019-06-28 12:29:08 +08:00
}
return
}
2023-06-11 20:58:51 +08:00
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, cache bool, err error) {
2022-08-24 21:36:19 +08:00
ctx, cancel := context.WithTimeout(ctx, resolver.DefaultDNSTimeout)
defer cancel()
2019-06-28 12:29:08 +08:00
2022-08-24 21:36:19 +08:00
return batchExchange(ctx, clients, m)
2019-06-28 12:29:08 +08:00
}
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
if r.policy == nil {
return nil
}
domain := msgToDomain(m)
if domain == "" {
return nil
}
record := r.policy.Search(domain)
if record != nil {
p := record.Data()
return p.GetData()
}
for _, geositeRecord := range r.geositePolicy {
matched := geositeRecord.matcher.Match(domain)
if matched != geositeRecord.inversedMatching {
return geositeRecord.policy.GetData()
}
}
metadata := &C.Metadata{Host: domain}
for _, domainSetRecord := range r.domainSetPolicy {
if ok := domainSetRecord.domainSetProvider.Match(metadata); ok {
return domainSetRecord.policy.GetData()
}
}
return nil
}
func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
if r.fallback == nil || len(r.fallbackDomainFilters) == 0 {
return false
}
domain := msgToDomain(m)
if domain == "" {
return false
}
for _, df := range r.fallbackDomainFilters {
if df.Match(domain) {
return true
}
}
return false
}
func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if matched := r.matchPolicy(m); len(matched) != 0 {
res := <-r.asyncExchange(ctx, matched, m)
return res.Msg, res.Error
}
onlyFallback := r.shouldOnlyQueryFallback(m)
if onlyFallback {
res := <-r.asyncExchange(ctx, r.fallback, m)
return res.Msg, res.Error
}
msgCh := r.asyncExchange(ctx, r.main, m)
2021-11-17 16:03:47 +08:00
if r.fallback == nil || len(r.fallback) == 0 { // directly return if no fallback servers are available
2019-06-28 12:29:08 +08:00
res := <-msgCh
msg, err = res.Msg, res.Error
return
}
2019-06-28 12:29:08 +08:00
res := <-msgCh
if res.Error == nil {
if ips := msgToIP(res.Msg); len(ips) != 0 {
shouldNotFallback := lo.EveryBy(ips, func(ip netip.Addr) bool {
return !r.shouldIPFallback(ip)
})
if shouldNotFallback {
Merge remote-tracking branch 'yaling888/with-tun' into Alpha # Conflicts: # .github/workflows/codeql-analysis.yml # .github/workflows/linter.yml # .github/workflows/release.yml # Makefile # README.md # adapter/outbound/vless.go # component/geodata/memconservative/cache.go # component/geodata/router/condition.go # component/geodata/router/condition_geoip.go # component/geodata/standard/standard.go # component/geodata/utils.go # config/config.go # config/initial.go # constant/metadata.go # constant/path.go # constant/rule.go # constant/rule_extra.go # dns/client.go # dns/filters.go # dns/resolver.go # go.mod # go.sum # hub/executor/executor.go # hub/route/configs.go # listener/listener.go # listener/tproxy/tproxy_linux_iptables.go # listener/tun/dev/dev.go # listener/tun/dev/dev_darwin.go # listener/tun/dev/dev_linux.go # listener/tun/dev/dev_windows.go # listener/tun/dev/wintun/config.go # listener/tun/dev/wintun/dll_windows.go # listener/tun/dev/wintun/session_windows.go # listener/tun/dev/wintun/wintun_windows.go # listener/tun/ipstack/commons/dns.go # listener/tun/ipstack/gvisor/tun.go # listener/tun/ipstack/gvisor/tundns.go # listener/tun/ipstack/gvisor/utils.go # listener/tun/ipstack/stack_adapter.go # listener/tun/ipstack/system/dns.go # listener/tun/ipstack/system/tcp.go # listener/tun/ipstack/system/tun.go # listener/tun/tun_adapter.go # main.go # rule/common/base.go # rule/common/domain.go # rule/common/domain_keyword.go # rule/common/domain_suffix.go # rule/common/final.go # rule/common/geoip.go # rule/common/geosite.go # rule/common/ipcidr.go # rule/common/port.go # rule/parser.go # rule/process.go # test/go.mod # test/go.sum # transport/vless/xtls.go # tunnel/tunnel.go
2022-03-17 17:41:02 +08:00
msg, err = res.Msg, res.Error // no need to wait for fallback result
return
2019-06-28 12:29:08 +08:00
}
}
}
2021-11-17 16:03:47 +08:00
res = <-r.asyncExchange(ctx, r.fallback, m)
2019-06-28 12:29:08 +08:00
msg, err = res.Msg, res.Error
return
}
func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (ips []netip.Addr, err error) {
ip, err := netip.ParseAddr(host)
2022-04-20 01:52:51 +08:00
if err == nil {
isIPv4 := ip.Is4()
2019-09-27 15:26:07 +08:00
if dnsType == D.TypeAAAA && !isIPv4 {
return []netip.Addr{ip}, nil
2019-09-27 15:26:07 +08:00
} else if dnsType == D.TypeA && isIPv4 {
return []netip.Addr{ip}, nil
2020-02-17 20:11:46 +08:00
} else {
return []netip.Addr{}, resolver.ErrIPVersion
2019-09-11 17:00:55 +08:00
}
}
2019-06-28 12:29:08 +08:00
query := &D.Msg{}
query.SetQuestion(D.Fqdn(host), dnsType)
msg, err := r.ExchangeContext(ctx, query)
2019-06-28 12:29:08 +08:00
if err != nil {
return []netip.Addr{}, err
2019-06-28 12:29:08 +08:00
}
ips = msgToIP(msg)
ipLength := len(ips)
if ipLength == 0 {
return []netip.Addr{}, resolver.ErrIPNotFound
2019-06-28 12:29:08 +08:00
}
return
}
func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D.Msg) <-chan *result {
2020-03-13 00:11:54 +08:00
ch := make(chan *result, 1)
2019-06-28 12:29:08 +08:00
go func() {
2023-06-11 20:58:51 +08:00
res, _, err := r.batchExchange(ctx, client, msg)
2019-06-28 12:29:08 +08:00
ch <- &result{Msg: res, Error: err}
}()
return ch
}
// Invalid return this resolver can or can't be used
func (r *Resolver) Invalid() bool {
if r == nil {
return false
}
2022-03-28 00:44:13 +08:00
return len(r.main) > 0
}
2019-06-28 12:29:08 +08:00
type NameServer struct {
2021-11-17 16:03:47 +08:00
Net string
Addr string
2023-10-10 16:34:33 +08:00
Interface atomic.TypedValue[string]
ProxyAdapter C.ProxyAdapter
ProxyName string
Params map[string]string
PreferH3 bool
2019-06-28 12:29:08 +08:00
}
2019-09-15 13:36:45 +08:00
type FallbackFilter struct {
2021-08-25 15:15:13 +08:00
GeoIP bool
GeoIPCode string
2022-04-20 01:52:51 +08:00
IPCIDR []*netip.Prefix
2021-08-25 15:15:13 +08:00
Domain []string
2021-11-17 16:03:47 +08:00
GeoSite []*router.DomainMatcher
2019-09-15 13:36:45 +08:00
}
2019-06-28 12:29:08 +08:00
type Config struct {
Main, Fallback []NameServer
Default []NameServer
ProxyServer []NameServer
IPv6 bool
IPv6Timeout uint
EnhancedMode C.DNSMode
FallbackFilter FallbackFilter
Pool *fakeip.Pool
Hosts *trie.DomainTrie[resolver.HostValue]
Policy map[string][]NameServer
DomainSetPolicy map[provider.RuleProvider][]NameServer
GeositePolicy map[router.DomainMatcher][]NameServer
2019-06-28 12:29:08 +08:00
}
func NewResolver(config Config) *Resolver {
defaultResolver := &Resolver{
2023-03-10 23:38:16 +08:00
main: transform(config.Default, nil),
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
}
2019-06-28 12:29:08 +08:00
r := &Resolver{
2023-03-10 23:38:16 +08:00
ipv6: config.IPv6,
main: transform(config.Main, defaultResolver),
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
hosts: config.Hosts,
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
2019-06-28 12:29:08 +08:00
}
2019-09-15 13:36:45 +08:00
2019-06-28 12:29:08 +08:00
if len(config.Fallback) != 0 {
r.fallback = transform(config.Fallback, defaultResolver)
2019-06-28 12:29:08 +08:00
}
2019-09-15 13:36:45 +08:00
2022-03-28 00:44:13 +08:00
if len(config.ProxyServer) != 0 {
r.proxyServer = transform(config.ProxyServer, defaultResolver)
2022-03-28 00:44:13 +08:00
}
if len(config.Policy) != 0 {
2022-04-06 04:25:53 +08:00
r.policy = trie.New[*Policy]()
for domain, nameserver := range config.Policy {
if strings.HasPrefix(strings.ToLower(domain), "geosite:") {
groupname := domain[8:]
inverse := false
if strings.HasPrefix(groupname, "!") {
inverse = true
groupname = groupname[1:]
}
2023-02-19 01:23:06 +08:00
log.Debugln("adding geosite policy: %s inversed %t", groupname, inverse)
matcher, err := NewGeoSite(groupname)
if err != nil {
continue
}
r.geositePolicy = append(r.geositePolicy, geositePolicyRecord{
matcher: matcher,
policy: NewPolicy(transform(nameserver, defaultResolver)),
inversedMatching: inverse,
})
} else {
_ = r.policy.Insert(domain, NewPolicy(transform(nameserver, defaultResolver)))
}
}
r.policy.Optimize()
}
if len(config.DomainSetPolicy) > 0 {
for p, n := range config.DomainSetPolicy {
r.domainSetPolicy = append(r.domainSetPolicy, domainSetPolicyRecord{
domainSetProvider: p,
policy: NewPolicy(transform(n, defaultResolver)),
})
}
}
fallbackIPFilters := []fallbackIPFilter{}
2019-09-15 13:36:45 +08:00
if config.FallbackFilter.GeoIP {
2021-08-25 15:15:13 +08:00
fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{
code: config.FallbackFilter.GeoIPCode,
})
2019-09-15 13:36:45 +08:00
}
for _, ipnet := range config.FallbackFilter.IPCIDR {
fallbackIPFilters = append(fallbackIPFilters, &ipnetFilter{ipnet: ipnet})
}
r.fallbackIPFilters = fallbackIPFilters
2021-11-17 16:03:47 +08:00
fallbackDomainFilters := []fallbackDomainFilter{}
if len(config.FallbackFilter.Domain) != 0 {
2021-11-17 16:03:47 +08:00
fallbackDomainFilters = append(fallbackDomainFilters, NewDomainFilter(config.FallbackFilter.Domain))
}
if len(config.FallbackFilter.GeoSite) != 0 {
fallbackDomainFilters = append(fallbackDomainFilters, &geoSiteFilter{
matchers: config.FallbackFilter.GeoSite,
})
2019-09-15 13:36:45 +08:00
}
2021-11-17 16:03:47 +08:00
r.fallbackDomainFilters = fallbackDomainFilters
return r
}
2019-09-15 13:36:45 +08:00
2022-03-28 00:44:13 +08:00
func NewProxyServerHostResolver(old *Resolver) *Resolver {
2021-11-17 16:03:47 +08:00
r := &Resolver{
2023-03-10 23:38:16 +08:00
ipv6: old.ipv6,
main: old.proxyServer,
lruCache: old.lruCache,
hosts: old.hosts,
policy: trie.New[*Policy](),
2023-03-10 23:38:16 +08:00
ipv6Timeout: old.ipv6Timeout,
2021-11-17 16:03:47 +08:00
}
2019-06-28 12:29:08 +08:00
return r
}
var ParseNameServer func(servers []string) ([]NameServer, error) // define in config/config.go