github.com/sagernet/sing-box@v1.2.7/route/router_dns.go (about)

     1  package route
     2  
     3  import (
     4  	"context"
     5  	"net/netip"
     6  	"strings"
     7  
     8  	"github.com/sagernet/sing-box/adapter"
     9  	C "github.com/sagernet/sing-box/constant"
    10  	"github.com/sagernet/sing-box/log"
    11  	"github.com/sagernet/sing-dns"
    12  	E "github.com/sagernet/sing/common/exceptions"
    13  	F "github.com/sagernet/sing/common/format"
    14  
    15  	mDNS "github.com/miekg/dns"
    16  )
    17  
    18  func (r *Router) matchDNS(ctx context.Context) (context.Context, dns.Transport, dns.DomainStrategy) {
    19  	metadata := adapter.ContextFrom(ctx)
    20  	if metadata == nil {
    21  		panic("no context")
    22  	}
    23  	for i, rule := range r.dnsRules {
    24  		if rule.Match(metadata) {
    25  			if rule.DisableCache() {
    26  				ctx = dns.ContextWithDisableCache(ctx, true)
    27  			}
    28  			detour := rule.Outbound()
    29  			r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
    30  			if transport, loaded := r.transportMap[detour]; loaded {
    31  				if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
    32  					return ctx, transport, domainStrategy
    33  				} else {
    34  					return ctx, transport, r.defaultDomainStrategy
    35  				}
    36  			}
    37  			r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour)
    38  		}
    39  	}
    40  	if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded {
    41  		return ctx, r.defaultTransport, domainStrategy
    42  	} else {
    43  		return ctx, r.defaultTransport, r.defaultDomainStrategy
    44  	}
    45  }
    46  
    47  func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
    48  	if len(message.Question) > 0 {
    49  		r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String()))
    50  	}
    51  	ctx, metadata := adapter.AppendContext(ctx)
    52  	if len(message.Question) > 0 {
    53  		metadata.QueryType = message.Question[0].Qtype
    54  		switch metadata.QueryType {
    55  		case mDNS.TypeA:
    56  			metadata.IPVersion = 4
    57  		case mDNS.TypeAAAA:
    58  			metadata.IPVersion = 6
    59  		}
    60  		metadata.Domain = fqdnToDomain(message.Question[0].Name)
    61  	}
    62  	ctx, transport, strategy := r.matchDNS(ctx)
    63  	ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
    64  	defer cancel()
    65  	response, err := r.dnsClient.Exchange(ctx, transport, message, strategy)
    66  	if err != nil && len(message.Question) > 0 {
    67  		r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String())))
    68  	}
    69  	if len(message.Question) > 0 && response != nil {
    70  		LogDNSAnswers(r.dnsLogger, ctx, message.Question[0].Name, response.Answer)
    71  	}
    72  	return response, err
    73  }
    74  
    75  func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
    76  	r.dnsLogger.DebugContext(ctx, "lookup domain ", domain)
    77  	ctx, metadata := adapter.AppendContext(ctx)
    78  	metadata.Domain = domain
    79  	ctx, transport, transportStrategy := r.matchDNS(ctx)
    80  	if strategy == dns.DomainStrategyAsIS {
    81  		strategy = transportStrategy
    82  	}
    83  	ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
    84  	defer cancel()
    85  	addrs, err := r.dnsClient.Lookup(ctx, transport, domain, strategy)
    86  	if len(addrs) > 0 {
    87  		r.dnsLogger.InfoContext(ctx, "lookup succeed for ", domain, ": ", strings.Join(F.MapToString(addrs), " "))
    88  	} else {
    89  		r.dnsLogger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
    90  		if err == nil {
    91  			err = dns.RCodeNameError
    92  		}
    93  	}
    94  	return addrs, err
    95  }
    96  
    97  func (r *Router) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) {
    98  	return r.Lookup(ctx, domain, dns.DomainStrategyAsIS)
    99  }
   100  
   101  func LogDNSAnswers(logger log.ContextLogger, ctx context.Context, domain string, answers []mDNS.RR) {
   102  	for _, answer := range answers {
   103  		logger.InfoContext(ctx, "exchanged ", domain, " ", mDNS.Type(answer.Header().Rrtype).String(), " ", formatQuestion(answer.String()))
   104  	}
   105  }
   106  
   107  func fqdnToDomain(fqdn string) string {
   108  	if mDNS.IsFqdn(fqdn) {
   109  		return fqdn[:len(fqdn)-1]
   110  	}
   111  	return fqdn
   112  }
   113  
   114  func formatQuestion(string string) string {
   115  	if strings.HasPrefix(string, ";") {
   116  		string = string[1:]
   117  	}
   118  	string = strings.ReplaceAll(string, "\t", " ")
   119  	for strings.Contains(string, "  ") {
   120  		string = strings.ReplaceAll(string, "  ", " ")
   121  	}
   122  	return string
   123  }