Pull request: 4865-refactor-dns-handlers

Updates #4865.

Squashed commit of the following:

commit b874088ee72dfd0cae5f4102fac87e1f26245ddf
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Sep 2 14:01:52 2022 +0300

    dnsforward: imp code, docs

commit a1b95fda58b777a54e7dcd57f0c419200f8fdc15
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Sep 1 19:37:45 2022 +0300

    all: refactor dns handler; opt
This commit is contained in:
Ainar Garipov
2022-09-02 14:52:19 +03:00
parent a9127c4a45
commit 3660b4810e
12 changed files with 272 additions and 410 deletions

View File

@@ -81,9 +81,9 @@ const (
const ddrHostFQDN = "_dns.resolver.arpa."
// handleDNSRequest filters the incoming DNS requests and writes them to the query log
func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
ctx := &dnsContext{
proxyCtx: d,
func (s *Server) handleDNSRequest(_ *proxy.Proxy, pctx *proxy.DNSContext) error {
dctx := &dnsContext{
proxyCtx: pctx,
result: &filtering.Result{},
startTime: time.Now(),
}
@@ -111,7 +111,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
s.processQueryLogsAndStats,
}
for _, process := range mods {
r := process(ctx)
r := process(dctx)
switch r {
case resultCodeSuccess:
// continue: call the next filter
@@ -120,13 +120,15 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
return nil
case resultCodeError:
return ctx.err
return dctx.err
}
}
if d.Res != nil {
d.Res.Compress = true // some devices require DNS message compression
if pctx.Res != nil {
// Some devices require DNS message compression.
pctx.Res.Compress = true
}
return nil
}
@@ -149,34 +151,38 @@ func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
// needed and enriches the ctx with some client-specific information.
//
// TODO(e.burkov): Decompose into less general processors.
func (s *Server) processInitial(ctx *dnsContext) (rc resultCode) {
d := ctx.proxyCtx
if s.conf.AAAADisabled && d.Req.Question[0].Qtype == dns.TypeAAAA {
_ = proxy.CheckDisabledAAAARequest(d, true)
func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx
q := pctx.Req.Question[0]
qt := q.Qtype
if s.conf.AAAADisabled && qt == dns.TypeAAAA {
_ = proxy.CheckDisabledAAAARequest(pctx, true)
return resultCodeFinish
}
if s.conf.OnDNSRequest != nil {
s.conf.OnDNSRequest(d)
s.conf.OnDNSRequest(pctx)
}
// disable Mozilla DoH
// https://support.mozilla.org/en-US/kb/canary-domain-use-application-dnsnet
if (d.Req.Question[0].Qtype == dns.TypeA || d.Req.Question[0].Qtype == dns.TypeAAAA) &&
d.Req.Question[0].Name == "use-application-dns.net." {
d.Res = s.genNXDomain(d.Req)
// Disable Mozilla DoH.
//
// See https://support.mozilla.org/en-US/kb/canary-domain-use-application-dnsnet.
if (qt == dns.TypeA || qt == dns.TypeAAAA) && q.Name == "use-application-dns.net." {
pctx.Res = s.genNXDomain(pctx.Req)
return resultCodeFinish
}
// Get the client's ID if any. It should be performed before getting
// client-specific filtering settings.
// Get the ClientID, if any, before getting client-specific filtering
// settings.
var key [8]byte
binary.BigEndian.PutUint64(key[:], d.RequestID)
ctx.clientID = string(s.clientIDCache.Get(key[:]))
binary.BigEndian.PutUint64(key[:], pctx.RequestID)
dctx.clientID = string(s.clientIDCache.Get(key[:]))
// Get the client-specific filtering settings.
ctx.protectionEnabled = s.conf.ProtectionEnabled
ctx.setts = s.getClientRequestFilteringSettings(ctx)
dctx.protectionEnabled = s.conf.ProtectionEnabled
dctx.setts = s.getClientRequestFilteringSettings(dctx)
return resultCodeSuccess
}
@@ -249,23 +255,23 @@ func (s *Server) onDHCPLeaseChanged(flags int) {
// supported by current user configuration.
//
// See https://www.ietf.org/archive/id/draft-ietf-add-ddr-06.html.
func (s *Server) processDDRQuery(ctx *dnsContext) (rc resultCode) {
d := ctx.proxyCtx
question := d.Req.Question[0]
func (s *Server) processDDRQuery(dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx
q := pctx.Req.Question[0]
if !s.conf.HandleDDR {
return resultCodeSuccess
}
if question.Name == ddrHostFQDN {
if q.Name == ddrHostFQDN {
if s.dnsProxy.TLSListenAddr == nil && s.conf.HTTPSListenAddrs == nil &&
s.dnsProxy.QUICListenAddr == nil || question.Qtype != dns.TypeSVCB {
d.Res = s.makeResponse(d.Req)
s.dnsProxy.QUICListenAddr == nil || q.Qtype != dns.TypeSVCB {
pctx.Res = s.makeResponse(pctx.Req)
return resultCodeFinish
}
d.Res = s.makeDDRResponse(d.Req)
pctx.Res = s.makeDDRResponse(pctx.Req)
return resultCodeFinish
}
@@ -400,10 +406,10 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
d := dctx.proxyCtx
pctx := dctx.proxyCtx
if !dctx.isLocalClient {
log.Debug("dns: %q requests for internal host", d.Addr)
d.Res = s.genNXDomain(req)
log.Debug("dns: %q requests for internal host", pctx.Addr)
pctx.Res = s.genNXDomain(req)
// Do not even put into query log.
return resultCodeFinish
@@ -413,7 +419,7 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
if !ok {
// TODO(e.burkov): Inspect special cases when user want to apply some
// rules handled by other processors to the hosts with TLD.
d.Res = s.genNXDomain(req)
pctx.Res = s.genNXDomain(req)
return resultCodeFinish
}
@@ -435,9 +441,9 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
// processRestrictLocal responds with NXDOMAIN to PTR requests for IP addresses
// in locally-served network from external clients.
func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
d := ctx.proxyCtx
req := d.Req
func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx
req := pctx.Req
q := req.Question[0]
if q.Qtype != dns.TypePTR {
// No need for restriction.
@@ -473,24 +479,24 @@ func (s *Server) processRestrictLocal(ctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
if !ctx.isLocalClient {
log.Debug("dns: %q requests an internal ip", d.Addr)
d.Res = s.genNXDomain(req)
if !dctx.isLocalClient {
log.Debug("dns: %q requests an internal ip", pctx.Addr)
pctx.Res = s.genNXDomain(req)
// Do not even put into query log.
return resultCodeFinish
}
// Do not perform unreversing ever again.
ctx.unreversedReqIP = ip
dctx.unreversedReqIP = ip
// There is no need to filter request from external addresses since this
// code is only executed when the request is for locally-served ARPA
// hostname so disable redundant filters.
ctx.setts.ParentalEnabled = false
ctx.setts.SafeBrowsingEnabled = false
ctx.setts.SafeSearchEnabled = false
ctx.setts.ServicesRules = nil
dctx.setts.ParentalEnabled = false
dctx.setts.SafeBrowsingEnabled = false
dctx.setts.SafeSearchEnabled = false
dctx.setts.ServicesRules = nil
// Nothing to restrict.
return resultCodeSuccess
@@ -523,13 +529,13 @@ func (s *Server) ipToHost(ip net.IP) (host string, ok bool) {
// Respond to PTR requests if the target IP is leased by our DHCP server and the
// requestor is inside the local network.
func (s *Server) processDHCPAddrs(ctx *dnsContext) (rc resultCode) {
d := ctx.proxyCtx
if d.Res != nil {
func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx
if pctx.Res != nil {
return resultCodeSuccess
}
ip := ctx.unreversedReqIP
ip := dctx.unreversedReqIP
if ip == nil {
return resultCodeSuccess
}
@@ -541,7 +547,7 @@ func (s *Server) processDHCPAddrs(ctx *dnsContext) (rc resultCode) {
log.Debug("dns: reverse-lookup: %s -> %s", ip, host)
req := d.Req
req := pctx.Req
resp := s.makeResponse(req)
ptr := &dns.PTR{
Hdr: dns.RR_Header{
@@ -553,20 +559,20 @@ func (s *Server) processDHCPAddrs(ctx *dnsContext) (rc resultCode) {
Ptr: dns.Fqdn(host),
}
resp.Answer = append(resp.Answer, ptr)
d.Res = resp
pctx.Res = resp
return resultCodeSuccess
}
// processLocalPTR responds to PTR requests if the target IP is detected to be
// inside the local network and the query was not answered from DHCP.
func (s *Server) processLocalPTR(ctx *dnsContext) (rc resultCode) {
d := ctx.proxyCtx
if d.Res != nil {
func (s *Server) processLocalPTR(dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx
if pctx.Res != nil {
return resultCodeSuccess
}
ip := ctx.unreversedReqIP
ip := dctx.unreversedReqIP
if ip == nil {
return resultCodeSuccess
}
@@ -579,16 +585,16 @@ func (s *Server) processLocalPTR(ctx *dnsContext) (rc resultCode) {
}
if s.conf.UsePrivateRDNS {
s.recDetector.add(*d.Req)
if err := s.localResolvers.Resolve(d); err != nil {
ctx.err = err
s.recDetector.add(*pctx.Req)
if err := s.localResolvers.Resolve(pctx); err != nil {
dctx.err = err
return resultCodeError
}
}
if d.Res == nil {
d.Res = s.genNXDomain(d.Req)
if pctx.Res == nil {
pctx.Res = s.genNXDomain(pctx.Req)
// Do not even put into query log.
return resultCodeFinish
@@ -638,17 +644,7 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
if pctx.Addr != nil && s.conf.GetCustomUpstreamByClient != nil {
// Use the ClientID first, since it has a higher priority.
id := stringutil.Coalesce(dctx.clientID, ipStringFromAddr(pctx.Addr))
upsConf, err := s.conf.GetCustomUpstreamByClient(id)
if err != nil {
log.Error("dns: getting custom upstreams for client %s: %s", id, err)
} else if upsConf != nil {
log.Debug("dns: using custom upstreams for client %s", id)
pctx.CustomUpstreamConfig = upsConf
}
}
s.setCustomUpstream(pctx, dctx.clientID)
req := pctx.Req
origReqAD := false
@@ -683,52 +679,78 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
// Apply filtering logic after we have received response from upstream servers
func (s *Server) processFilteringAfterResponse(ctx *dnsContext) (rc resultCode) {
d := ctx.proxyCtx
// setCustomUpstream sets custom upstream settings in pctx, if necessary.
func (s *Server) setCustomUpstream(pctx *proxy.DNSContext, clientID string) {
customUpsByClient := s.conf.GetCustomUpstreamByClient
if pctx.Addr == nil || customUpsByClient == nil {
return
}
switch res := ctx.result; res.Reason {
// Use the ClientID first, since it has a higher priority.
id := stringutil.Coalesce(clientID, ipStringFromAddr(pctx.Addr))
upsConf, err := customUpsByClient(id)
if err != nil {
log.Error("dns: getting custom upstreams for client %s: %s", id, err)
return
}
if upsConf != nil {
log.Debug("dns: using custom upstreams for client %s", id)
}
pctx.CustomUpstreamConfig = upsConf
}
// Apply filtering logic after we have received response from upstream servers
func (s *Server) processFilteringAfterResponse(dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx
switch res := dctx.result; res.Reason {
case filtering.NotFilteredAllowList:
// Go on.
return resultCodeSuccess
case
filtering.Rewritten,
filtering.RewrittenRule:
if len(ctx.origQuestion.Name) == 0 {
if dctx.origQuestion.Name == "" {
// origQuestion is set in case we get only CNAME without IP from
// rewrites table.
break
return resultCodeSuccess
}
d.Req.Question[0], d.Res.Question[0] = ctx.origQuestion, ctx.origQuestion
if len(d.Res.Answer) > 0 {
answer := append([]dns.RR{s.genAnswerCNAME(d.Req, res.CanonName)}, d.Res.Answer...)
d.Res.Answer = answer
pctx.Req.Question[0], pctx.Res.Question[0] = dctx.origQuestion, dctx.origQuestion
if len(pctx.Res.Answer) > 0 {
rr := s.genAnswerCNAME(pctx.Req, res.CanonName)
answer := append([]dns.RR{rr}, pctx.Res.Answer...)
pctx.Res.Answer = answer
}
return resultCodeSuccess
default:
// Check the response only if it's from an upstream. Don't check the
// response if the protection is disabled since dnsrewrite rules aren't
// applied to it anyway.
if !ctx.protectionEnabled || !ctx.responseFromUpstream || s.dnsFilter == nil {
break
}
return s.filterAfterResponse(dctx, pctx)
}
}
origResp := d.Res
result, err := s.filterDNSResponse(ctx)
if err != nil {
ctx.err = err
return resultCodeError
}
if result != nil {
ctx.result = result
ctx.origResp = origResp
}
// filterAfterResponse returns the result of filtering the response that wasn't
// explicitly allowed or rewritten.
func (s *Server) filterAfterResponse(dctx *dnsContext, pctx *proxy.DNSContext) (res resultCode) {
// Check the response only if it's from an upstream. Don't check the
// response if the protection is disabled since dnsrewrite rules aren't
// applied to it anyway.
if !dctx.protectionEnabled || !dctx.responseFromUpstream || s.dnsFilter == nil {
return resultCodeSuccess
}
if ctx.result == nil {
ctx.result = &filtering.Result{}
result, err := s.filterDNSResponse(pctx, dctx.setts)
if err != nil {
dctx.err = err
return resultCodeError
}
if result != nil {
dctx.result = result
dctx.origResp = pctx.Res
}
return resultCodeSuccess

View File

@@ -12,12 +12,16 @@ import (
"github.com/miekg/dns"
)
// filterDNSRewriteResponse handles a single DNS rewrite response entry.
// It returns the properly constructed answer resource record.
func (s *Server) filterDNSRewriteResponse(req *dns.Msg, rr rules.RRType, v rules.RRValue) (ans dns.RR, err error) {
// TODO(a.garipov): As more types are added, we will probably want to
// use a handler-oriented approach here. So, think of a way to decouple
// the answer generation logic from the Server.
// filterDNSRewriteResponse handles a single DNS rewrite response entry. It
// returns the properly constructed answer resource record.
func (s *Server) filterDNSRewriteResponse(
req *dns.Msg,
rr rules.RRType,
v rules.RRValue,
) (ans dns.RR, err error) {
// TODO(a.garipov): As more types are added, we will probably want to use a
// handler-oriented approach here. So, think of a way to decouple the
// answer generation logic from the Server.
switch rr {
case dns.TypeA,
@@ -77,9 +81,13 @@ func (s *Server) filterDNSRewriteResponse(req *dns.Msg, rr rules.RRType, v rules
}
}
// filterDNSRewrite handles dnsrewrite filters. It constructs a DNS
// response and sets it into d.Res.
func (s *Server) filterDNSRewrite(req *dns.Msg, res filtering.Result, d *proxy.DNSContext) (err error) {
// filterDNSRewrite handles dnsrewrite filters. It constructs a DNS response
// and sets it into pctx.Res. All parameters must not be nil.
func (s *Server) filterDNSRewrite(
req *dns.Msg,
res *filtering.Result,
pctx *proxy.DNSContext,
) (err error) {
resp := s.makeResponse(req)
dnsrr := res.DNSRewriteResult
if dnsrr == nil {
@@ -88,7 +96,7 @@ func (s *Server) filterDNSRewrite(req *dns.Msg, res filtering.Result, d *proxy.D
resp.Rcode = dnsrr.RCode
if resp.Rcode != dns.RcodeSuccess {
d.Res = resp
pctx.Res = resp
return nil
}
@@ -109,7 +117,7 @@ func (s *Server) filterDNSRewrite(req *dns.Msg, res filtering.Result, d *proxy.D
resp.Answer = append(resp.Answer, ans)
}
d.Res = resp
pctx.Res = resp
return nil
}

View File

@@ -42,11 +42,11 @@ func TestServer_FilterDNSRewrite(t *testing.T) {
}},
}
}
makeRes := func(rcode rules.RCode, rr rules.RRType, v rules.RRValue) (res filtering.Result) {
makeRes := func(rcode rules.RCode, rr rules.RRType, v rules.RRValue) (res *filtering.Result) {
resp := filtering.DNSRewriteResultResponse{
rr: []rules.RRValue{v},
}
return filtering.Result{
return &filtering.Result{
DNSRewriteResult: &filtering.DNSRewriteResult{
RCode: rcode,
Response: resp,

View File

@@ -49,69 +49,83 @@ func (s *Server) beforeRequestHandler(
}
// getClientRequestFilteringSettings looks up client filtering settings using
// the client's IP address and ID, if any, from ctx.
func (s *Server) getClientRequestFilteringSettings(ctx *dnsContext) *filtering.Settings {
// the client's IP address and ID, if any, from dctx.
func (s *Server) getClientRequestFilteringSettings(dctx *dnsContext) *filtering.Settings {
setts := s.dnsFilter.GetConfig()
setts.ProtectionEnabled = ctx.protectionEnabled
setts.ProtectionEnabled = dctx.protectionEnabled
if s.conf.FilterHandler != nil {
ip, _ := netutil.IPAndPortFromAddr(ctx.proxyCtx.Addr)
s.conf.FilterHandler(ip, ctx.clientID, &setts)
ip, _ := netutil.IPAndPortFromAddr(dctx.proxyCtx.Addr)
s.conf.FilterHandler(ip, dctx.clientID, &setts)
}
return &setts
}
// filterDNSRequest applies the dnsFilter and sets d.Res if the request was
// filtered.
func (s *Server) filterDNSRequest(ctx *dnsContext) (*filtering.Result, error) {
d := ctx.proxyCtx
req := d.Req
// filterDNSRequest applies the dnsFilter and sets dctx.proxyCtx.Res if the
// request was filtered.
func (s *Server) filterDNSRequest(dctx *dnsContext) (res *filtering.Result, err error) {
pctx := dctx.proxyCtx
req := pctx.Req
q := req.Question[0]
host := strings.TrimSuffix(q.Name, ".")
res, err := s.dnsFilter.CheckHost(host, q.Qtype, ctx.setts)
resVal, err := s.dnsFilter.CheckHost(host, q.Qtype, dctx.setts)
if err != nil {
return nil, fmt.Errorf("checking host %q: %w", host, err)
}
// TODO(a.garipov): Make CheckHost return a pointer.
res = &resVal
switch {
case err != nil:
return nil, fmt.Errorf("failed to check host %q: %w", host, err)
case res.IsFiltered:
log.Tracef("host %q is filtered, reason %q, rule: %q", host, res.Reason, res.Rules[0].Text)
d.Res = s.genDNSFilterMessage(d, &res)
pctx.Res = s.genDNSFilterMessage(pctx, res)
case res.Reason.In(filtering.Rewritten, filtering.RewrittenRule) &&
res.CanonName != "" &&
len(res.IPList) == 0:
// Resolve the new canonical name, not the original host name. The
// original question is readded in processFilteringAfterResponse.
ctx.origQuestion = q
dctx.origQuestion = q
req.Question[0].Name = dns.Fqdn(res.CanonName)
case res.Reason == filtering.Rewritten:
resp := s.makeResponse(req)
name := host
if len(res.CanonName) != 0 {
resp.Answer = append(resp.Answer, s.genAnswerCNAME(req, res.CanonName))
name = res.CanonName
}
for _, ip := range res.IPList {
switch q.Qtype {
case dns.TypeA:
a := s.genAnswerA(req, ip.To4())
a.Hdr.Name = dns.Fqdn(name)
resp.Answer = append(resp.Answer, a)
case dns.TypeAAAA:
a := s.genAnswerAAAA(req, ip)
a.Hdr.Name = dns.Fqdn(name)
resp.Answer = append(resp.Answer, a)
}
}
d.Res = resp
pctx.Res = s.filterRewritten(req, host, res, q.Qtype)
case res.Reason.In(filtering.RewrittenRule, filtering.RewrittenAutoHosts):
if err = s.filterDNSRewrite(req, res, d); err != nil {
if err = s.filterDNSRewrite(req, res, pctx); err != nil {
return nil, err
}
}
return &res, err
return res, err
}
// filterRewritten handles DNS rewrite filters. It returns a DNS response with
// the data from the filtering result. All parameters must not be nil.
func (s *Server) filterRewritten(
req *dns.Msg,
host string,
res *filtering.Result,
qt uint16,
) (resp *dns.Msg) {
resp = s.makeResponse(req)
name := host
if len(res.CanonName) != 0 {
resp.Answer = append(resp.Answer, s.genAnswerCNAME(req, res.CanonName))
name = res.CanonName
}
for _, ip := range res.IPList {
switch qt {
case dns.TypeA:
a := s.genAnswerA(req, ip.To4())
a.Hdr.Name = dns.Fqdn(name)
resp.Answer = append(resp.Answer, a)
case dns.TypeAAAA:
a := s.genAnswerAAAA(req, ip)
a.Hdr.Name = dns.Fqdn(name)
resp.Answer = append(resp.Answer, a)
}
}
return resp
}
// checkHostRules checks the host against filters. It is safe for concurrent
@@ -137,16 +151,17 @@ func (s *Server) checkHostRules(host string, rrtype uint16, setts *filtering.Set
}
// filterDNSResponse checks each resource record of the response's answer
// section from ctx and returns a non-nil res if at least one of canonnical
// section from pctx and returns a non-nil res if at least one of canonnical
// names or IP addresses in it matches the filtering rules.
func (s *Server) filterDNSResponse(ctx *dnsContext) (res *filtering.Result, err error) {
d := ctx.proxyCtx
setts := ctx.setts
func (s *Server) filterDNSResponse(
pctx *proxy.DNSContext,
setts *filtering.Settings,
) (res *filtering.Result, err error) {
if !setts.FilteringEnabled {
return nil, nil
}
for _, a := range d.Res.Answer {
for _, a := range pctx.Res.Answer {
host := ""
var rrtype uint16
switch a := a.(type) {
@@ -171,8 +186,8 @@ func (s *Server) filterDNSResponse(ctx *dnsContext) (res *filtering.Result, err
} else if res == nil {
continue
} else if res.IsFiltered {
d.Res = s.genDNSFilterMessage(d, res)
log.Debug("DNSFwd: Matched %s by response: %s", d.Req.Question[0].Name, host)
pctx.Res = s.genDNSFilterMessage(pctx, res)
log.Debug("DNSFwd: Matched %s by response: %s", pctx.Req.Question[0].Name, host)
return res, nil
}