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 }