mihomo/dns/resolver.go

390 lines
8.7 KiB
Go
Raw Normal View History

2019-06-28 12:29:08 +08:00
package dns
import (
"context"
"errors"
2020-04-16 18:31:40 +08:00
"fmt"
"math/rand"
2019-06-28 12:29:08 +08:00
"net"
"strings"
2019-06-28 12:29:08 +08:00
"time"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/picker"
"github.com/Dreamacro/clash/component/fakeip"
"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"
2019-06-28 12:29:08 +08:00
D "github.com/miekg/dns"
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)
}
type result struct {
Msg *D.Msg
Error error
}
type Resolver struct {
ipv6 bool
hosts *trie.DomainTrie
main []dnsClient
fallback []dnsClient
fallbackDomainFilters []fallbackDomainFilter
fallbackIPFilters []fallbackIPFilter
group singleflight.Group
lruCache *cache.LruCache
policy *trie.DomainTrie
2019-06-28 12:29:08 +08:00
}
2020-02-17 20:11:46 +08:00
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
2019-06-28 12:29:08 +08:00
func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
2020-02-17 20:11:46 +08:00
ch := make(chan net.IP, 1)
2019-06-28 12:29:08 +08:00
go func() {
defer close(ch)
2020-02-17 20:11:46 +08:00
ip, err := r.resolveIP(host, D.TypeAAAA)
2019-06-28 12:29:08 +08:00
if err != nil {
return
}
ch <- ip
}()
2020-02-17 20:11:46 +08:00
ip, err = r.resolveIP(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
}
// ResolveIPv4 request with TypeA
func (r *Resolver) ResolveIPv4(host string) (ip net.IP, err error) {
2019-09-11 17:00:55 +08:00
return r.resolveIP(host, D.TypeA)
}
2019-06-28 12:29:08 +08:00
2019-09-11 17:00:55 +08:00
// ResolveIPv6 request with TypeAAAA
func (r *Resolver) ResolveIPv6(host string) (ip net.IP, err error) {
return r.resolveIP(host, D.TypeAAAA)
2019-06-28 12:29:08 +08:00
}
func (r *Resolver) shouldIPFallback(ip net.IP) 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")
}
q := m.Question[0]
cache, expireTime, hit := r.lruCache.GetWithExpire(q.String())
if hit {
now := time.Now()
2019-06-28 12:29:08 +08:00
msg = cache.(*D.Msg).Copy()
if expireTime.Before(now) {
setMsgTTL(msg, uint32(1)) // Continue fetch
go r.exchangeWithoutCache(ctx, m)
} else {
2020-08-25 22:19:59 +08:00
setMsgTTL(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]
ret, err, shared := r.group.Do(q.String(), func() (result interface{}, err error) {
defer func() {
if err != nil {
return
}
msg := result.(*D.Msg)
putMsgToCache(r.lruCache, q.String(), msg)
}()
isIPReq := isIPRequest(q)
if isIPReq {
return r.ipExchange(ctx, m)
2019-06-28 12:29:08 +08:00
}
if matched := r.matchPolicy(m); len(matched) != 0 {
return r.batchExchange(ctx, matched, m)
}
return r.batchExchange(ctx, r.main, m)
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
}
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout)
2019-08-02 09:22:09 +08:00
for _, client := range clients {
r := client
2019-07-02 19:18:03 +08:00
fast.Go(func() (interface{}, error) {
2020-02-25 21:51:48 +08:00
m, err := r.ExchangeContext(ctx, m)
if err != nil {
return nil, err
2020-03-08 13:00:42 +08:00
} else if m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused {
2020-02-25 21:51:48 +08:00
return nil, errors.New("server failure")
}
return m, nil
2019-07-02 19:18:03 +08:00
})
2019-06-28 12:29:08 +08:00
}
elm := fast.Wait()
2019-07-02 19:18:03 +08:00
if elm == nil {
2020-08-25 22:19:59 +08:00
err := errors.New("all DNS requests failed")
2020-04-16 18:31:40 +08:00
if fErr := fast.Error(); fErr != nil {
err = fmt.Errorf("%w, first error: %s", err, fErr.Error())
}
return nil, err
2019-06-28 12:29:08 +08:00
}
msg = elm.(*D.Msg)
return
}
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
if r.policy == nil {
return nil
}
domain := r.msgToDomain(m)
if domain == "" {
return nil
}
record := r.policy.Search(domain)
if record == nil {
return nil
}
return record.Data.([]dnsClient)
}
func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
if r.fallback == nil || len(r.fallbackDomainFilters) == 0 {
return false
}
domain := r.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)
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 {
if !r.shouldIPFallback(ips[0]) {
msg = res.Msg // no need to wait for fallback result
2020-03-08 13:00:42 +08:00
err = res.Error
2019-06-28 12:29:08 +08:00
return msg, err
}
}
}
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) resolveIP(host string, dnsType uint16) (ip net.IP, err error) {
2019-09-11 17:00:55 +08:00
ip = net.ParseIP(host)
if ip != nil {
2019-09-27 15:26:07 +08:00
isIPv4 := ip.To4() != nil
if dnsType == D.TypeAAAA && !isIPv4 {
return ip, nil
2019-09-27 15:26:07 +08:00
} else if dnsType == D.TypeA && isIPv4 {
return ip, nil
2020-02-17 20:11:46 +08:00
} else {
return nil, 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.Exchange(query)
if err != nil {
return nil, err
}
ips := msgToIP(msg)
ipLength := len(ips)
if ipLength == 0 {
return nil, resolver.ErrIPNotFound
2019-06-28 12:29:08 +08:00
}
ip = ips[rand.Intn(ipLength)]
2019-06-28 12:29:08 +08:00
return
}
func (r *Resolver) msgToDomain(msg *D.Msg) string {
if len(msg.Question) > 0 {
return strings.TrimRight(msg.Question[0].Name, ".")
}
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() {
res, err := r.batchExchange(ctx, client, msg)
2019-06-28 12:29:08 +08:00
ch <- &result{Msg: res, Error: err}
}()
return ch
}
type NameServer struct {
Net string
Addr string
Interface string
ProxyAdapter string
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
IPCIDR []*net.IPNet
Domain []string
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
2019-06-28 12:29:08 +08:00
IPv6 bool
EnhancedMode C.DNSMode
2019-09-15 13:36:45 +08:00
FallbackFilter FallbackFilter
2019-06-28 12:29:08 +08:00
Pool *fakeip.Pool
Hosts *trie.DomainTrie
Policy map[string]NameServer
2019-06-28 12:29:08 +08:00
}
func NewResolver(config Config) *Resolver {
defaultResolver := &Resolver{
main: transform(config.Default, nil),
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
}
2019-06-28 12:29:08 +08:00
r := &Resolver{
ipv6: config.IPv6,
main: transform(config.Main, defaultResolver),
lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)),
hosts: config.Hosts,
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
if len(config.Policy) != 0 {
r.policy = trie.New()
for domain, nameserver := range config.Policy {
r.policy.Insert(domain, transform([]NameServer{nameserver}, 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
fallbackDomainFilters := []fallbackDomainFilter{}
if len(config.FallbackFilter.Domain) != 0 {
fallbackDomainFilters = append(fallbackDomainFilters, NewDomainFilter(config.FallbackFilter.Domain))
2019-09-15 13:36:45 +08:00
}
if len(config.FallbackFilter.GeoSite) != 0 {
fallbackDomainFilters = append(fallbackDomainFilters, &geoSiteFilter{
matchers: config.FallbackFilter.GeoSite,
})
}
r.fallbackDomainFilters = fallbackDomainFilters
return r
}
func NewMainResolver(old *Resolver) *Resolver {
r := &Resolver{
ipv6: old.ipv6,
main: old.main,
lruCache: old.lruCache,
hosts: old.hosts,
policy: old.policy,
}
2019-06-28 12:29:08 +08:00
return r
}