Pull request 2193: AGDNS-1982 Upd proxy

Closes #6854.Updates #6875.

Squashed commit of the following:

commit b98adbc0cc6eeaffb262d57775c487e03b1d5ba5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 10 19:21:44 2024 +0300

    dnsforward: upd proxy, imp code, docs

commit 4de1eb2bca1047426e02ba680c212f46782e5616
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 10 16:09:58 2024 +0300

    WIP

commit afa9d61e8dc129f907dc681cd2f831cb5c3b054a
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 9 19:24:09 2024 +0300

    all: log changes

commit c8340676a448687a39acd26bc8ce5f94473e441f
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 9 19:06:10 2024 +0300

    dnsforward: move code

commit 08bb7d43d2a3f689ef2ef2409935dc3946752e94
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 9 18:09:46 2024 +0300

    dnsforward: imp code

commit b27547ec806dd9bce502d3c6a7c28f33693ed575
Merge: b7efca788 6f36ebc06
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 9 17:33:19 2024 +0300

    Merge branch 'master' into AGDNS-1982-upd-proxy

commit b7efca788b66aa672598b088040d4534ce2e55b0
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Apr 9 17:27:14 2024 +0300

    all: upd proxy finally

commit 3e16fa87befe4c0ef3a3e7a638d7add28627f9b6
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Apr 5 18:20:13 2024 +0300

    dnsforward: upd proxy

commit f3cdfc86334a182effcd0de22fac5e678fa53ea7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 4 20:37:32 2024 +0300

    all: upd proxy, golibs

commit a79298d6d0504521893ee11fdc3a23c098aea911
Merge: 9feeba5c7 fd25dcacb
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 4 20:34:01 2024 +0300

    Merge branch 'master' into AGDNS-1982-upd-proxy

commit 9feeba5c7f24ff1d308a216608d985cb2a7b7588
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 4 20:25:57 2024 +0300

    all: imp code, docs

commit 6c68d463db64293eb9c5e29ff91879fd68920a77
Merge: d8108e651 ee619b2db
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Apr 4 18:46:11 2024 +0300

    Merge branch 'master' into AGDNS-1982-upd-proxy

commit d8108e65164df8d67aa4e95154a8768a06255b78
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 3 19:25:27 2024 +0300

    all: imp code

commit 20461565801c9fcd06a652c6066b524b06c80433
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Apr 3 17:10:33 2024 +0300

    all: remove private rdns logic
This commit is contained in:
Eugene Burkov
2024-04-11 14:03:37 +03:00
parent 6f36ebc06c
commit ff7c715c5f
19 changed files with 765 additions and 1365 deletions

View File

@@ -1,20 +1,17 @@
package dnsforward
import (
"cmp"
"encoding/binary"
"net"
"net/netip"
"strconv"
"strings"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
"github.com/miekg/dns"
)
@@ -34,11 +31,6 @@ type dnsContext struct {
// response is modified by filters.
origResp *dns.Msg
// unreversedReqIP stores an IP address obtained from a PTR request if it
// was parsed successfully and belongs to one of the locally served IP
// ranges.
unreversedReqIP netip.Addr
// err is the error returned from a processing function.
err error
@@ -63,10 +55,6 @@ type dnsContext struct {
// responseAD shows if the response had the AD bit set.
responseAD bool
// isLocalClient shows if client's IP address is from locally served
// network.
isLocalClient bool
// isDHCPHost is true if the request for a local domain name and the DHCP is
// available for this request.
isDHCPHost bool
@@ -109,15 +97,11 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, pctx *proxy.DNSContext) error
// (*proxy.Proxy).handleDNSRequest method performs it before calling the
// appropriate handler.
mods := []modProcessFunc{
s.processRecursion,
s.processInitial,
s.processDDRQuery,
s.processDetermineLocal,
s.processDHCPHosts,
s.processRestrictLocal,
s.processDHCPAddrs,
s.processFilteringBeforeRequest,
s.processLocalPTR,
s.processUpstream,
s.processFilteringAfterResponse,
s.ipset.process,
@@ -145,24 +129,6 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, pctx *proxy.DNSContext) error
return nil
}
// processRecursion checks the incoming request and halts its handling by
// answering NXDOMAIN if s has tried to resolve it recently.
func (s *Server) processRecursion(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing recursion")
defer log.Debug("dnsforward: finished processing recursion")
pctx := dctx.proxyCtx
if msg := pctx.Req; msg != nil && s.recDetector.check(*msg) {
log.Debug("dnsforward: recursion detected resolving %q", msg.Question[0].Name)
pctx.Res = s.genNXDomain(pctx.Req)
return resultCodeFinish
}
return resultCodeSuccess
}
// mozillaFQDN is the domain used to signal the Firefox browser to not use its
// own DoH server.
//
@@ -199,14 +165,14 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
}
if (qt == dns.TypeA || qt == dns.TypeAAAA) && q.Name == mozillaFQDN {
pctx.Res = s.genNXDomain(pctx.Req)
pctx.Res = s.NewMsgNXDOMAIN(pctx.Req)
return resultCodeFinish
}
if q.Name == healthcheckFQDN {
// Generate a NODATA negative response to make nslookup exit with 0.
pctx.Res = s.makeResponse(pctx.Req)
pctx.Res = s.replyCompressed(pctx.Req)
return resultCodeFinish
}
@@ -272,7 +238,7 @@ func (s *Server) processDDRQuery(dctx *dnsContext) (rc resultCode) {
//
// [draft standard]: https://www.ietf.org/archive/id/draft-ietf-add-ddr-10.html.
func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
resp = s.makeResponse(req)
resp = s.replyCompressed(req)
if req.Question[0].Qtype != dns.TypeSVCB {
return resp
}
@@ -339,19 +305,6 @@ func (s *Server) makeDDRResponse(req *dns.Msg) (resp *dns.Msg) {
return resp
}
// processDetermineLocal determines if the client's IP address is from locally
// served network and saves the result into the context.
func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing local detection")
defer log.Debug("dnsforward: finished processing local detection")
rc = resultCodeSuccess
dctx.isLocalClient = s.privateNets.Contains(dctx.proxyCtx.Addr.Addr())
return rc
}
// processDHCPHosts respond to A requests if the target hostname is known to
// the server. It responds with a mapped IP address if the DNS64 is enabled and
// the request is for AAAA.
@@ -370,9 +323,9 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
if !dctx.isLocalClient {
if !pctx.IsPrivateClient {
log.Debug("dnsforward: %q requests for dhcp host %q", pctx.Addr, dhcpHost)
pctx.Res = s.genNXDomain(req)
pctx.Res = s.NewMsgNXDOMAIN(req)
// Do not even put into query log.
return resultCodeFinish
@@ -389,7 +342,7 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: dhcp record for %q is %s", dhcpHost, ip)
resp := s.makeResponse(req)
resp := s.replyCompressed(req)
switch q.Qtype {
case dns.TypeA:
a := &dns.A{
@@ -416,141 +369,6 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
// indexFirstV4Label returns the index at which the reversed IPv4 address
// starts, assuming the domain is pre-validated ARPA domain having in-addr and
// arpa labels removed.
func indexFirstV4Label(domain string) (idx int) {
idx = len(domain)
for labelsNum := 0; labelsNum < net.IPv4len && idx > 0; labelsNum++ {
curIdx := strings.LastIndexByte(domain[:idx-1], '.') + 1
_, parseErr := strconv.ParseUint(domain[curIdx:idx-1], 10, 8)
if parseErr != nil {
return idx
}
idx = curIdx
}
return idx
}
// indexFirstV6Label returns the index at which the reversed IPv6 address
// starts, assuming the domain is pre-validated ARPA domain having ip6 and arpa
// labels removed.
func indexFirstV6Label(domain string) (idx int) {
idx = len(domain)
for labelsNum := 0; labelsNum < net.IPv6len*2 && idx > 0; labelsNum++ {
curIdx := idx - len("a.")
if curIdx > 1 && domain[curIdx-1] != '.' {
return idx
}
nibble := domain[curIdx]
if (nibble < '0' || nibble > '9') && (nibble < 'a' || nibble > 'f') {
return idx
}
idx = curIdx
}
return idx
}
// extractARPASubnet tries to convert a reversed ARPA address being a part of
// domain to an IP network. domain must be an FQDN.
//
// TODO(e.burkov): Move to golibs.
func extractARPASubnet(domain string) (pref netip.Prefix, err error) {
err = netutil.ValidateDomainName(strings.TrimSuffix(domain, "."))
if err != nil {
// Don't wrap the error since it's informative enough as is.
return netip.Prefix{}, err
}
const (
v4Suffix = "in-addr.arpa."
v6Suffix = "ip6.arpa."
)
domain = strings.ToLower(domain)
var idx int
switch {
case strings.HasSuffix(domain, v4Suffix):
idx = indexFirstV4Label(domain[:len(domain)-len(v4Suffix)])
case strings.HasSuffix(domain, v6Suffix):
idx = indexFirstV6Label(domain[:len(domain)-len(v6Suffix)])
default:
return netip.Prefix{}, &netutil.AddrError{
Err: netutil.ErrNotAReversedSubnet,
Kind: netutil.AddrKindARPA,
Addr: domain,
}
}
return netutil.PrefixFromReversedAddr(domain[idx:])
}
// processRestrictLocal responds with NXDOMAIN to PTR requests for IP addresses
// in locally served network from external clients.
func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing local restriction")
defer log.Debug("dnsforward: finished processing local restriction")
pctx := dctx.proxyCtx
req := pctx.Req
q := req.Question[0]
if q.Qtype != dns.TypePTR {
// No need for restriction.
return resultCodeSuccess
}
subnet, err := extractARPASubnet(q.Name)
if err != nil {
if errors.Is(err, netutil.ErrNotAReversedSubnet) {
log.Debug("dnsforward: request is not for arpa domain")
return resultCodeSuccess
}
log.Debug("dnsforward: parsing reversed addr: %s", err)
return resultCodeError
}
// Restrict an access to local addresses for external clients. We also
// assume that all the DHCP leases we give are locally served or at least
// shouldn't be accessible externally.
subnetAddr := subnet.Addr()
if !s.privateNets.Contains(subnetAddr) {
return resultCodeSuccess
}
log.Debug("dnsforward: addr %s is from locally served network", subnetAddr)
if !dctx.isLocalClient {
log.Debug("dnsforward: %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.
dctx.unreversedReqIP = subnetAddr
// 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.
dctx.setts.ParentalEnabled = false
dctx.setts.SafeBrowsingEnabled = false
dctx.setts.SafeSearchEnabled = false
dctx.setts.ServicesRules = nil
// Nothing to restrict.
return resultCodeSuccess
}
// processDHCPAddrs responds to PTR requests if the target IP is leased by the
// DHCP server.
func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
@@ -562,20 +380,21 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
ipAddr := dctx.unreversedReqIP
if ipAddr == (netip.Addr{}) {
pref := pctx.RequestedPrivateRDNS
if pref == (netip.Prefix{}) {
return resultCodeSuccess
}
host := s.dhcpServer.HostByIP(ipAddr)
addr := pref.Addr()
host := s.dhcpServer.HostByIP(addr)
if host == "" {
return resultCodeSuccess
}
log.Debug("dnsforward: dhcp client %s is %q", ipAddr, host)
log.Debug("dnsforward: dhcp client %s is %q", addr, host)
req := pctx.Req
resp := s.makeResponse(req)
resp := s.replyCompressed(req)
ptr := &dns.PTR{
Hdr: dns.RR_Header{
Name: req.Question[0].Name,
@@ -593,62 +412,20 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
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(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing local ptr")
defer log.Debug("dnsforward: finished processing local ptr")
pctx := dctx.proxyCtx
if pctx.Res != nil {
return resultCodeSuccess
}
ip := dctx.unreversedReqIP
if ip == (netip.Addr{}) {
return resultCodeSuccess
}
s.serverLock.RLock()
defer s.serverLock.RUnlock()
if s.conf.UsePrivateRDNS {
s.recDetector.add(*pctx.Req)
if err := s.localResolvers.Resolve(pctx); err != nil {
log.Debug("dnsforward: resolving private address: %s", err)
// Generate the server failure if the private upstream configuration
// is empty.
//
// This is a crutch, see TODO at [Server.localResolvers].
if errors.Is(err, upstream.ErrNoUpstreams) {
pctx.Res = s.genServerFailure(pctx.Req)
// Do not even put into query log.
return resultCodeFinish
}
dctx.err = err
return resultCodeError
}
}
if pctx.Res == nil {
pctx.Res = s.genNXDomain(pctx.Req)
// Do not even put into query log.
return resultCodeFinish
}
return resultCodeSuccess
}
// Apply filtering logic
func (s *Server) processFilteringBeforeRequest(dctx *dnsContext) (rc resultCode) {
log.Debug("dnsforward: started processing filtering before req")
defer log.Debug("dnsforward: finished processing filtering before req")
if dctx.proxyCtx.RequestedPrivateRDNS != (netip.Prefix{}) {
// There is no need to filter request for locally served ARPA hostname
// so disable redundant filters.
dctx.setts.ParentalEnabled = false
dctx.setts.SafeBrowsingEnabled = false
dctx.setts.SafeSearchEnabled = false
dctx.setts.ServicesRules = nil
}
if dctx.proxyCtx.Res != nil {
// Go on since the response is already set.
return resultCodeSuccess
@@ -695,7 +472,7 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
// local domain name if there is one.
name := req.Question[0].Name
log.Debug("dnsforward: dhcp client hostname %q was not filtered", name[:len(name)-1])
pctx.Res = s.genNXDomain(req)
pctx.Res = s.NewMsgNXDOMAIN(req)
return resultCodeFinish
}
@@ -712,21 +489,7 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
return resultCodeError
}
if err := prx.Resolve(pctx); err != nil {
if errors.Is(err, upstream.ErrNoUpstreams) {
// Do not even put into querylog. Currently this happens either
// when the private resolvers enabled and the request is DNS64 PTR,
// or when the client isn't considered local by prx.
//
// TODO(e.burkov): Make proxy detect local client the same way as
// AGH does.
pctx.Res = s.genNXDomain(req)
return resultCodeFinish
}
dctx.err = err
if dctx.err = prx.Resolve(pctx); dctx.err != nil {
return resultCodeError
}
@@ -810,7 +573,7 @@ func (s *Server) setCustomUpstream(pctx *proxy.DNSContext, clientID string) {
}
// Use the ClientID first, since it has a higher priority.
id := stringutil.Coalesce(clientID, pctx.Addr.Addr().String())
id := cmp.Or(clientID, pctx.Addr.Addr().String())
upsConf, err := s.conf.ClientsContainer.UpstreamConfigByID(id, s.bootstrap)
if err != nil {
log.Error("dnsforward: getting custom upstreams for client %s: %s", id, err)