package resolver import ( "context" "fmt" "strings" "github.com/0xERR0R/blocky/config" "github.com/0xERR0R/blocky/model" "github.com/0xERR0R/blocky/util" "github.com/miekg/dns" "github.com/sirupsen/logrus" ) // ConditionalUpstreamResolver delegates DNS question to other DNS resolver dependent on domain name in question type ConditionalUpstreamResolver struct { configurable[*config.ConditionalUpstream] NextResolver typed mapping map[string]Resolver } // NewConditionalUpstreamResolver returns new resolver instance func NewConditionalUpstreamResolver( ctx context.Context, cfg config.ConditionalUpstream, upstreamsCfg config.Upstreams, bootstrap *Bootstrap, ) (*ConditionalUpstreamResolver, error) { m := make(map[string]Resolver, len(cfg.Mapping.Upstreams)) for domain, upstreams := range cfg.Mapping.Upstreams { name := fmt.Sprintf("", domain) cfg := config.NewUpstreamGroup(name, upstreamsCfg, upstreams) r, err := NewParallelBestResolver(ctx, cfg, bootstrap) if err != nil { return nil, err } m[strings.ToLower(domain)] = r } r := ConditionalUpstreamResolver{ configurable: withConfig(&cfg), typed: withType("conditional_upstream"), mapping: m, } return &r, nil } func (r *ConditionalUpstreamResolver) processRequest( ctx context.Context, request *model.Request, ) (bool, *model.Response, error) { domainFromQuestion := util.ExtractDomain(request.Req.Question[0]) domain := domainFromQuestion if strings.Contains(domainFromQuestion, ".") { // try with domain with and without sub-domains for len(domain) > 0 { if resolver, found := r.mapping[domain]; found { resp, err := r.internalResolve(ctx, resolver, domainFromQuestion, domain, request) return true, resp, err } if i := strings.Index(domain, "."); i >= 0 { domain = domain[i+1:] } else { break } } } else if resolver, found := r.mapping["."]; found { resp, err := r.internalResolve(ctx, resolver, domainFromQuestion, domain, request) return true, resp, err } return false, nil, nil } // Resolve uses the conditional resolver to resolve the query func (r *ConditionalUpstreamResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) { ctx, logger := r.log(ctx) if len(r.mapping) > 0 { resolved, resp, err := r.processRequest(ctx, request) if resolved { return resp, err } } logger.WithField("next_resolver", Name(r.next)).Trace("go to next resolver") return r.next.Resolve(ctx, request) } func (r *ConditionalUpstreamResolver) internalResolve(ctx context.Context, reso Resolver, doFQ, do string, req *model.Request, ) (*model.Response, error) { // internal request resolution ctx, logger := r.log(ctx) req.Req.Question[0].Name = dns.Fqdn(doFQ) response, err := reso.Resolve(ctx, req) if err == nil { response.Reason = "CONDITIONAL" response.RType = model.ResponseTypeCONDITIONAL if len(response.Res.Question) > 0 { response.Res.Question[0].Name = req.Req.Question[0].Name } } var answer string if response != nil { answer = util.AnswerToString(response.Res.Answer) } logger.WithFields(logrus.Fields{ "answer": answer, "domain": util.Obfuscate(do), "upstream": reso, }).Debugf("received response from conditional upstream") return response, err }