all: sync with master; upd chlog

This commit is contained in:
Ainar Garipov
2023-02-15 16:53:29 +03:00
parent 80eb339896
commit 66b831072c
55 changed files with 24817 additions and 798 deletions

View File

@@ -9,7 +9,7 @@ import (
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/lucas-clemente/quic-go"
"github.com/quic-go/quic-go"
)
// ValidateClientID returns an error if id is not a valid ClientID.
@@ -151,25 +151,7 @@ func (s *Server) clientIDFromDNSContext(pctx *proxy.DNSContext) (clientID string
func clientServerName(pctx *proxy.DNSContext, proto proxy.Proto) (srvName string, err error) {
switch proto {
case proxy.ProtoHTTPS:
// github.com/lucas-clemente/quic-go seems to not populate the TLS
// field. So, if the request comes over HTTP/3, use the Host header
// value as the server name.
//
// See https://github.com/lucas-clemente/quic-go/issues/2879.
//
// TODO(a.garipov): Remove this crutch once they fix it.
r := pctx.HTTPRequest
if r.ProtoAtLeast(3, 0) {
var host string
host, err = netutil.SplitHost(r.Host)
if err != nil {
return "", fmt.Errorf("parsing host: %w", err)
}
srvName = host
} else if connState := r.TLS; connState != nil {
srvName = r.TLS.ServerName
}
srvName = pctx.HTTPRequest.TLS.ServerName
case proxy.ProtoQUIC:
qConn := pctx.QUICConnection
conn, ok := qConn.(quicConnection)

View File

@@ -9,7 +9,7 @@ import (
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/testutil"
"github.com/lucas-clemente/quic-go"
"github.com/quic-go/quic-go"
"github.com/stretchr/testify/assert"
)
@@ -55,7 +55,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID string
wantErrMsg string
strictSNI bool
useHTTP3 bool
}{{
name: "udp",
proto: proxy.ProtoUDP,
@@ -64,7 +63,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "",
wantErrMsg: "",
strictSNI: false,
useHTTP3: false,
}, {
name: "tls_no_clientid",
proto: proxy.ProtoTLS,
@@ -73,7 +71,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "",
wantErrMsg: "",
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_no_client_server_name",
proto: proxy.ProtoTLS,
@@ -83,7 +80,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantErrMsg: `clientid check: client server name "" ` +
`doesn't match host server name "example.com"`,
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_no_client_server_name_no_strict",
proto: proxy.ProtoTLS,
@@ -92,7 +88,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "",
wantErrMsg: "",
strictSNI: false,
useHTTP3: false,
}, {
name: "tls_clientid",
proto: proxy.ProtoTLS,
@@ -101,7 +96,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "cli",
wantErrMsg: "",
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_clientid_hostname_error",
proto: proxy.ProtoTLS,
@@ -111,7 +105,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantErrMsg: `clientid check: client server name "cli.example.net" ` +
`doesn't match host server name "example.com"`,
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_invalid_clientid",
proto: proxy.ProtoTLS,
@@ -121,7 +114,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantErrMsg: `clientid check: invalid clientid "!!!": ` +
`bad domain name label rune '!'`,
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_clientid_too_long",
proto: proxy.ProtoTLS,
@@ -133,7 +125,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
`pqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789": ` +
`domain name label is too long: got 72, max 63`,
strictSNI: true,
useHTTP3: false,
}, {
name: "quic_clientid",
proto: proxy.ProtoQUIC,
@@ -142,7 +133,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "cli",
wantErrMsg: "",
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_clientid_issue3437",
proto: proxy.ProtoTLS,
@@ -152,7 +142,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantErrMsg: `clientid check: client server name "cli.myexample.com" ` +
`doesn't match host server name "example.com"`,
strictSNI: true,
useHTTP3: false,
}, {
name: "tls_case",
proto: proxy.ProtoTLS,
@@ -161,7 +150,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "insensitive",
wantErrMsg: ``,
strictSNI: true,
useHTTP3: false,
}, {
name: "quic_case",
proto: proxy.ProtoQUIC,
@@ -170,7 +158,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "insensitive",
wantErrMsg: ``,
strictSNI: true,
useHTTP3: false,
}, {
name: "https_no_clientid",
proto: proxy.ProtoHTTPS,
@@ -179,7 +166,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "",
wantErrMsg: "",
strictSNI: true,
useHTTP3: false,
}, {
name: "https_clientid",
proto: proxy.ProtoHTTPS,
@@ -188,16 +174,6 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
wantClientID: "cli",
wantErrMsg: "",
strictSNI: true,
useHTTP3: false,
}, {
name: "https_clientid_quic",
proto: proxy.ProtoHTTPS,
hostSrvName: "example.com",
cliSrvName: "cli.example.com",
wantClientID: "cli",
wantErrMsg: "",
strictSNI: true,
useHTTP3: true,
}}
for _, tc := range testCases {
@@ -219,7 +195,7 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
switch tc.proto {
case proxy.ProtoHTTPS:
httpReq = newHTTPReq(tc.cliSrvName, tc.useHTTP3)
httpReq = newHTTPReq(tc.cliSrvName)
case proxy.ProtoQUIC:
qconn = testQUICConnection{
serverName: tc.cliSrvName,
@@ -246,21 +222,11 @@ func TestServer_clientIDFromDNSContext(t *testing.T) {
}
// newHTTPReq is a helper to create HTTP requests for tests.
func newHTTPReq(cliSrvName string, useHTTP3 bool) (r *http.Request) {
func newHTTPReq(cliSrvName string) (r *http.Request) {
u := &url.URL{
Path: "/dns-query",
}
if useHTTP3 {
return &http.Request{
ProtoMajor: 3,
ProtoMinor: 0,
URL: u,
Host: cliSrvName,
TLS: &tls.ConnectionState{},
}
}
return &http.Request{
ProtoMajor: 1,
ProtoMinor: 1,

View File

@@ -5,6 +5,7 @@ import (
"crypto/x509"
"fmt"
"net"
"net/netip"
"os"
"sort"
"strings"
@@ -225,7 +226,7 @@ type ServerConfig struct {
LocalPTRResolvers []string
// DNS64Prefixes is a slice of NAT64 prefixes to be used for DNS64.
DNS64Prefixes []string
DNS64Prefixes []netip.Prefix
// ResolveClients signals if the RDNS should resolve clients' addresses.
ResolveClients bool
@@ -271,6 +272,8 @@ func (s *Server) createProxyConfig() (conf proxy.Config, err error) {
RequestHandler: s.handleDNSRequest,
EnableEDNSClientSubnet: srvConf.EnableEDNSClientSubnet,
MaxGoroutines: int(srvConf.MaxGoroutines),
UseDNS64: srvConf.UseDNS64,
DNS64Prefs: srvConf.DNS64Prefixes,
}
if srvConf.CacheSize != 0 {

View File

@@ -10,6 +10,8 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"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"
@@ -17,6 +19,9 @@ import (
)
// To transfer information between modules
//
// TODO(s.chzhen): Add lowercased, non-FQDN version of the hostname from the
// question of the request.
type dnsContext struct {
proxyCtx *proxy.DNSContext
@@ -419,7 +424,7 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
}
resp.Answer = append(resp.Answer, a)
case dns.TypeAAAA:
if len(s.dns64Prefs) > 0 {
if s.dns64Pref != (netip.Prefix{}) {
// Respond with DNS64-mapped address for IPv4 host if DNS64 is
// enabled.
aaaa := &dns.AAAA{
@@ -468,15 +473,6 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}
if s.shouldStripDNS64(ip) {
// Strip the prefix from the address to get the original IPv4.
ip = ip[nat64PrefixLen:]
// Treat a DNS64-prefixed address as a locally served one since those
// queries should never be sent to the global DNS.
dctx.unreversedReqIP = ip
}
// 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.
@@ -655,13 +651,7 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
s.setCustomUpstream(pctx, dctx.clientID)
origReqAD := false
if s.conf.EnableDNSSEC {
origReqAD = req.AuthenticatedData
if !req.AuthenticatedData {
req.AuthenticatedData = true
}
}
reqWantsDNSSEC := s.setReqAD(req)
// Process the request further since it wasn't filtered.
prx := s.proxy()
@@ -671,23 +661,73 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
return resultCodeError
}
if dctx.err = prx.Resolve(pctx); dctx.err != nil {
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 s.performDNS64(prx, dctx) == resultCodeError {
return resultCodeError
}
dctx.responseFromUpstream = true
dctx.responseAD = pctx.Res.AuthenticatedData
if s.conf.EnableDNSSEC && !origReqAD {
s.setRespAD(pctx, reqWantsDNSSEC)
return resultCodeSuccess
}
// setReqAD changes the request based on the server settings. wantsDNSSEC is
// false if the response should be cleared of the AD bit.
//
// TODO(a.garipov, e.burkov): This should probably be done in module dnsproxy.
func (s *Server) setReqAD(req *dns.Msg) (wantsDNSSEC bool) {
if !s.conf.EnableDNSSEC {
return false
}
origReqAD := req.AuthenticatedData
req.AuthenticatedData = true
// Per [RFC 6840] says, validating resolvers should only set the AD bit when
// the response has the AD bit set and the request contained either a set DO
// bit or a set AD bit. So, if neither of these is true, clear the AD bits
// in [Server.setRespAD].
//
// [RFC 6840]: https://datatracker.ietf.org/doc/html/rfc6840#section-5.8
return origReqAD || hasDO(req)
}
// hasDO returns true if msg has EDNS(0) options and the DNSSEC OK flag is set
// in there.
//
// TODO(a.garipov): Move to golibs/dnsmsg when it's there.
func hasDO(msg *dns.Msg) (do bool) {
o := msg.IsEdns0()
if o == nil {
return false
}
return o.Do()
}
// setRespAD changes the request and response based on the server settings and
// the original request data.
func (s *Server) setRespAD(pctx *proxy.DNSContext, reqWantsDNSSEC bool) {
if s.conf.EnableDNSSEC && !reqWantsDNSSEC {
pctx.Req.AuthenticatedData = false
pctx.Res.AuthenticatedData = false
}
return resultCodeSuccess
}
// isDHCPClientHostQ returns true if q is from a request for a DHCP client

View File

@@ -1,34 +1,10 @@
package dnsforward
import (
"fmt"
"net"
"net/netip"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/mathutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
)
const (
// maxNAT64PrefixBitLen is the maximum length of a NAT64 prefix in bits.
// See https://datatracker.ietf.org/doc/html/rfc6147#section-5.2.
maxNAT64PrefixBitLen = 96
// nat64PrefixLen is the length of a NAT64 prefix in bytes.
nat64PrefixLen = net.IPv6len - net.IPv4len
// maxDNS64SynTTL is the maximum TTL for synthesized DNS64 responses with no
// SOA records in seconds.
//
// If the SOA RR was not delivered with the negative response to the AAAA
// query, then the DNS64 SHOULD use the TTL of the original A RR or 600
// seconds, whichever is shorter.
//
// See https://datatracker.ietf.org/doc/html/rfc6147#section-5.1.7.
maxDNS64SynTTL uint32 = 600
)
// setupDNS64 initializes DNS64 settings, the NAT64 prefixes in particular. If
@@ -38,227 +14,22 @@ const (
// is specified explicitly. Each prefix also validated to be a valid IPv6
// CIDR with a maximum length of 96 bits. The first specified prefix is then
// used to synthesize AAAA records.
func (s *Server) setupDNS64() (err error) {
func (s *Server) setupDNS64() {
if !s.conf.UseDNS64 {
return nil
return
}
l := len(s.conf.DNS64Prefixes)
if l == 0 {
s.dns64Prefs = []netip.Prefix{dns64WellKnownPref}
if len(s.conf.DNS64Prefixes) == 0 {
// dns64WellKnownPref is the default prefix to use in an algorithmic
// mapping for DNS64.
//
// See https://datatracker.ietf.org/doc/html/rfc6052#section-2.1.
dns64WellKnownPref := netip.MustParsePrefix("64:ff9b::/96")
return nil
s.dns64Pref = dns64WellKnownPref
} else {
s.dns64Pref = s.conf.DNS64Prefixes[0]
}
prefs := make([]netip.Prefix, 0, l)
for i, pref := range s.conf.DNS64Prefixes {
var p netip.Prefix
p, err = netip.ParsePrefix(pref)
if err != nil {
return fmt.Errorf("prefix at index %d: %w", i, err)
}
addr := p.Addr()
if !addr.Is6() {
return fmt.Errorf("prefix at index %d: %q is not an IPv6 prefix", i, pref)
}
if p.Bits() > maxNAT64PrefixBitLen {
return fmt.Errorf("prefix at index %d: %q is too long for DNS64", i, pref)
}
prefs = append(prefs, p.Masked())
}
s.dns64Prefs = prefs
return nil
}
// checkDNS64 checks if DNS64 should be performed. It returns a DNS64 request
// to resolve or nil if DNS64 is not desired. It also filters resp to not
// contain any NAT64 excluded addresses in the answer section, if needed. Both
// req and resp must not be nil.
//
// See https://datatracker.ietf.org/doc/html/rfc6147.
func (s *Server) checkDNS64(req, resp *dns.Msg) (dns64Req *dns.Msg) {
if len(s.dns64Prefs) == 0 {
return nil
}
q := req.Question[0]
if q.Qtype != dns.TypeAAAA || q.Qclass != dns.ClassINET {
// DNS64 operation for classes other than IN is undefined, and a DNS64
// MUST behave as though no DNS64 function is configured.
return nil
}
rcode := resp.Rcode
if rcode == dns.RcodeNameError {
// A result with RCODE=3 (Name Error) is handled according to normal DNS
// operation (which is normally to return the error to the client).
return nil
}
if rcode == dns.RcodeSuccess {
// If resolver receives an answer with at least one AAAA record
// containing an address outside any of the excluded range(s), then it
// by default SHOULD build an answer section for a response including
// only the AAAA record(s) that do not contain any of the addresses
// inside the excluded ranges.
var hasAnswers bool
if resp.Answer, hasAnswers = s.filterNAT64Answers(resp.Answer); hasAnswers {
return nil
}
// Any other RCODE is treated as though the RCODE were 0 and the answer
// section were empty.
}
return &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: dns.Id(),
RecursionDesired: req.RecursionDesired,
AuthenticatedData: req.AuthenticatedData,
CheckingDisabled: req.CheckingDisabled,
},
Question: []dns.Question{{
Name: req.Question[0].Name,
Qtype: dns.TypeA,
Qclass: dns.ClassINET,
}},
}
}
// filterNAT64Answers filters out AAAA records that are within one of NAT64
// exclusion prefixes. hasAnswers is true if the filtered slice contains at
// least a single AAAA answer not within the prefixes or a CNAME.
func (s *Server) filterNAT64Answers(rrs []dns.RR) (filtered []dns.RR, hasAnswers bool) {
filtered = make([]dns.RR, 0, len(rrs))
for _, ans := range rrs {
switch ans := ans.(type) {
case *dns.AAAA:
addr, err := netutil.IPToAddrNoMapped(ans.AAAA)
if err != nil {
log.Error("dnsforward: bad AAAA record: %s", err)
continue
}
if s.withinDNS64(addr) {
// Filter the record.
continue
}
filtered, hasAnswers = append(filtered, ans), true
case *dns.CNAME, *dns.DNAME:
// If the response contains a CNAME or a DNAME, then the CNAME or
// DNAME chain is followed until the first terminating A or AAAA
// record is reached.
//
// Just treat CNAME and DNAME responses as passable answers since
// AdGuard Home doesn't follow any of these chains except the
// dnsrewrite-defined ones.
filtered, hasAnswers = append(filtered, ans), true
default:
filtered = append(filtered, ans)
}
}
return filtered, hasAnswers
}
// synthDNS64 synthesizes a DNS64 response using the original response as a
// basis and modifying it with data from resp. It returns true if the response
// was actually modified.
func (s *Server) synthDNS64(origReq, origResp, resp *dns.Msg) (ok bool) {
if len(resp.Answer) == 0 {
// If there is an empty answer, then the DNS64 responds to the original
// querying client with the answer the DNS64 received to the original
// (initiator's) query.
return false
}
// The Time to Live (TTL) field is set to the minimum of the TTL of the
// original A RR and the SOA RR for the queried domain. If the original
// response contains no SOA records, the minimum of the TTL of the original
// A RR and [maxDNS64SynTTL] should be used. See [maxDNS64SynTTL].
soaTTL := maxDNS64SynTTL
for _, rr := range origResp.Ns {
if hdr := rr.Header(); hdr.Rrtype == dns.TypeSOA && hdr.Name == origReq.Question[0].Name {
soaTTL = hdr.Ttl
break
}
}
newAns := make([]dns.RR, 0, len(resp.Answer))
for _, ans := range resp.Answer {
rr := s.synthRR(ans, soaTTL)
if rr == nil {
// The error should have already been logged.
return false
}
newAns = append(newAns, rr)
}
origResp.Answer = newAns
origResp.Ns = resp.Ns
origResp.Extra = resp.Extra
return true
}
// dns64WellKnownPref is the default prefix to use in an algorithmic mapping for
// DNS64. See https://datatracker.ietf.org/doc/html/rfc6052#section-2.1.
var dns64WellKnownPref = netip.MustParsePrefix("64:ff9b::/96")
// withinDNS64 checks if ip is within one of the configured DNS64 prefixes.
//
// TODO(e.burkov): We actually using bytes of only the first prefix from the
// set to construct the answer, so consider using some implementation of a
// prefix set for the rest.
func (s *Server) withinDNS64(ip netip.Addr) (ok bool) {
for _, n := range s.dns64Prefs {
if n.Contains(ip) {
return true
}
}
return false
}
// shouldStripDNS64 returns true if DNS64 is enabled and ip has either one of
// custom DNS64 prefixes or the Well-Known one. This is intended to be used
// with PTR requests.
//
// The requirement is to match any Pref64::/n used at the site, and not merely
// the locally configured Pref64::/n. This is because end clients could ask for
// a PTR record matching an address received through a different (site-provided)
// DNS64.
//
// See https://datatracker.ietf.org/doc/html/rfc6147#section-5.3.1.
func (s *Server) shouldStripDNS64(ip net.IP) (ok bool) {
if len(s.dns64Prefs) == 0 {
return false
}
addr, err := netutil.IPToAddr(ip, netutil.AddrFamilyIPv6)
if err != nil {
return false
}
switch {
case s.withinDNS64(addr):
log.Debug("dnsforward: %s is within DNS64 custom prefix set", ip)
case dns64WellKnownPref.Contains(addr):
log.Debug("dnsforward: %s is within DNS64 well-known prefix", ip)
default:
return false
}
return true
}
// mapDNS64 maps ip to IPv6 address using configured DNS64 prefix. ip must be a
@@ -267,79 +38,12 @@ func (s *Server) shouldStripDNS64(ip net.IP) (ok bool) {
func (s *Server) mapDNS64(ip netip.Addr) (mapped net.IP) {
// Don't mask the address here since it should have already been masked on
// initialization stage.
pref := s.dns64Prefs[0].Addr().As16()
pref := s.dns64Pref.Masked().Addr().As16()
ipData := ip.As4()
mapped = make(net.IP, net.IPv6len)
copy(mapped[:nat64PrefixLen], pref[:])
copy(mapped[nat64PrefixLen:], ipData[:])
copy(mapped[:proxy.NAT64PrefixLength], pref[:])
copy(mapped[proxy.NAT64PrefixLength:], ipData[:])
return mapped
}
// performDNS64 processes the current state of dctx assuming that it has already
// been tried to resolve, checks if it contains any acceptable response, and if
// it doesn't, performs DNS64 request and the following synthesis. It returns
// the [resultCodeError] if there was an error set to dctx.
func (s *Server) performDNS64(prx *proxy.Proxy, dctx *dnsContext) (rc resultCode) {
pctx := dctx.proxyCtx
req := pctx.Req
dns64Req := s.checkDNS64(req, pctx.Res)
if dns64Req == nil {
return resultCodeSuccess
}
log.Debug("dnsforward: received an empty AAAA response, checking DNS64")
origReq := pctx.Req
origResp := pctx.Res
origUps := pctx.Upstream
pctx.Req = dns64Req
defer func() { pctx.Req = origReq }()
if dctx.err = prx.Resolve(pctx); dctx.err != nil {
return resultCodeError
}
dns64Resp := pctx.Res
pctx.Res = origResp
if dns64Resp != nil && s.synthDNS64(origReq, pctx.Res, dns64Resp) {
log.Debug("dnsforward: synthesized AAAA response for %q", origReq.Question[0].Name)
} else {
pctx.Upstream = origUps
}
return resultCodeSuccess
}
// synthRR synthesizes a DNS64 resource record in compliance with RFC 6147. If
// rr is not an A record, it's returned as is. A records are modified to become
// a DNS64-synthesized AAAA records, and the TTL is set according to the
// original TTL of a record and soaTTL. It returns nil on invalid A records.
func (s *Server) synthRR(rr dns.RR, soaTTL uint32) (result dns.RR) {
aResp, ok := rr.(*dns.A)
if !ok {
return rr
}
addr, err := netutil.IPToAddr(aResp.A, netutil.AddrFamilyIPv4)
if err != nil {
log.Error("dnsforward: bad A record: %s", err)
return nil
}
aaaa := &dns.AAAA{
Hdr: dns.RR_Header{
Name: aResp.Hdr.Name,
Rrtype: dns.TypeAAAA,
Class: aResp.Hdr.Class,
Ttl: mathutil.Min(aResp.Hdr.Ttl, soaTTL),
},
AAAA: s.mapDNS64(addr),
}
return aaaa
}

View File

@@ -15,6 +15,16 @@ import (
"github.com/stretchr/testify/require"
)
// maxDNS64SynTTL is the maximum TTL for synthesized DNS64 responses with no SOA
// records in seconds.
//
// If the SOA RR was not delivered with the negative response to the AAAA query,
// then the DNS64 SHOULD use the TTL of the original A RR or 600 seconds,
// whichever is shorter.
//
// See https://datatracker.ietf.org/doc/html/rfc6147#section-5.1.7.
const maxDNS64SynTTL uint32 = 600
// newRR is a helper that creates a new dns.RR with the given name, qtype, ttl
// and value. It fails the test if the qtype is not supported or the type of
// value doesn't match the qtype.

View File

@@ -80,10 +80,17 @@ type Server struct {
privateNets netutil.SubnetSet
localResolvers *proxy.Proxy
sysResolvers aghnet.SystemResolvers
recDetector *recursionDetector
// dns64Prefix is the set of NAT64 prefixes used for DNS64 handling.
dns64Prefs []netip.Prefix
// recDetector is a cache for recursive requests. It is used to detect
// and prevent recursive requests only for private upstreams.
//
// See https://github.com/adguardTeam/adGuardHome/issues/3185#issuecomment-851048135.
recDetector *recursionDetector
// dns64Pref is the NAT64 prefix used for DNS64 response mapping. The major
// part of DNS64 happens inside the [proxy] package, but there still are
// some places where response mapping is needed (e.g. DHCP).
dns64Pref netip.Prefix
// anonymizer masks the client's IP addresses if needed.
anonymizer *aghnet.IPMut
@@ -246,8 +253,8 @@ func (s *Server) Resolve(host string) ([]net.IPAddr, error) {
// RDNSExchanger is a resolver for clients' addresses.
type RDNSExchanger interface {
// Exchange tries to resolve the ip in a suitable way, e.g. either as
// local or as external.
// Exchange tries to resolve the ip in a suitable way, i.e. either as local
// or as external.
Exchange(ip net.IP) (host string, err error)
// ResolvesPrivatePTR returns true if the RDNSExchanger is able to
@@ -256,13 +263,13 @@ type RDNSExchanger interface {
}
const (
// rDNSEmptyAnswerErr is returned by Exchange method when the answer
// section of respond is empty.
rDNSEmptyAnswerErr errors.Error = "the answer section is empty"
// ErrRDNSNoData is returned by [RDNSExchanger.Exchange] when the answer
// section of response is either NODATA or has no PTR records.
ErrRDNSNoData errors.Error = "no ptr data in response"
// rDNSNotPTRErr is returned by Exchange method when the response is not
// of PTR type.
rDNSNotPTRErr errors.Error = "the response is not a ptr"
// ErrRDNSFailed is returned by [RDNSExchanger.Exchange] if the received
// response is not a NOERROR or NXDOMAIN.
ErrRDNSFailed errors.Error = "failed to resolve ptr"
)
// type check
@@ -317,17 +324,24 @@ func (s *Server) Exchange(ip net.IP) (host string, err error) {
return "", err
}
// Distinguish between NODATA response and a failed request.
resp := ctx.Res
if len(resp.Answer) == 0 {
return "", fmt.Errorf("lookup for %q: %w", arpa, rDNSEmptyAnswerErr)
if resp.Rcode != dns.RcodeSuccess && resp.Rcode != dns.RcodeNameError {
return "", fmt.Errorf(
"received %s response: %w",
dns.RcodeToString[resp.Rcode],
ErrRDNSFailed,
)
}
ptr, ok := resp.Answer[0].(*dns.PTR)
if !ok {
return "", fmt.Errorf("type checking: %w", rDNSNotPTRErr)
for _, ans := range resp.Answer {
ptr, ok := ans.(*dns.PTR)
if ok {
return strings.TrimSuffix(ptr.Ptr, "."), nil
}
}
return strings.TrimSuffix(ptr.Ptr, "."), nil
return "", ErrRDNSNoData
}
// ResolvesPrivatePTR implements the RDNSExchanger interface for *Server.
@@ -477,6 +491,8 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
return fmt.Errorf("preparing proxy: %w", err)
}
s.setupDNS64()
err = s.prepareInternalProxy()
if err != nil {
return fmt.Errorf("preparing internal proxy: %w", err)
@@ -493,18 +509,18 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
s.registerHandlers()
err = s.setupDNS64()
if err != nil {
return fmt.Errorf("preparing DNS64: %w", err)
}
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
// TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
err = s.setupResolvers(s.conf.LocalPTRResolvers)
if err != nil {
return fmt.Errorf("setting up resolvers: %w", err)
}
if s.conf.UsePrivateRDNS {
proxyConfig.PrivateRDNSUpstreamConfig = s.localResolvers.UpstreamConfig
}
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
s.recDetector.clear()
return nil

View File

@@ -89,9 +89,14 @@ func createTestServer(
s.serverLock.Lock()
defer s.serverLock.Unlock()
// TODO(e.burkov): Try to move it higher.
if localUps != nil {
s.localResolvers.UpstreamConfig.Upstreams = []upstream.Upstream{localUps}
ups := []upstream.Upstream{localUps}
s.localResolvers.UpstreamConfig.Upstreams = ups
s.conf.UsePrivateRDNS = true
s.dnsProxy.PrivateRDNSUpstreamConfig = &proxy.UpstreamConfig{
Upstreams: ups,
}
}
return s
@@ -1216,6 +1221,9 @@ func TestServer_Exchange(t *testing.T) {
errUpstream := aghtest.NewErrorUpstream()
nonPtrUpstream := aghtest.NewBlockUpstream("some-host", true)
refusingUpstream := aghtest.NewUpstreamMock(func(req *dns.Msg) (resp *dns.Msg, err error) {
return new(dns.Msg).SetRcode(req, dns.RcodeRefused), nil
})
srv := &Server{
recDetector: newRecursionDetector(0, 1),
@@ -1260,15 +1268,21 @@ func TestServer_Exchange(t *testing.T) {
}, {
name: "empty_answer_error",
want: "",
wantErr: rDNSEmptyAnswerErr,
wantErr: ErrRDNSNoData,
locUpstream: locUpstream,
req: net.IP{192, 168, 1, 2},
}, {
name: "not_ptr_error",
name: "invalid_answer",
want: "",
wantErr: rDNSNotPTRErr,
wantErr: ErrRDNSNoData,
locUpstream: nonPtrUpstream,
req: localIP,
}, {
name: "refused",
want: "",
wantErr: ErrRDNSFailed,
locUpstream: refusingUpstream,
req: localIP,
}}
for _, tc := range testCases {

View File

@@ -22,9 +22,11 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
shouldLog := true
msg := pctx.Req
q := msg.Question[0]
host := strings.ToLower(strings.TrimSuffix(q.Name, "."))
// don't log ANY request if refuseAny is enabled
if len(msg.Question) >= 1 && msg.Question[0].Qtype == dns.TypeANY && s.conf.RefuseAny {
if q.Qtype == dns.TypeANY && s.conf.RefuseAny {
shouldLog = false
}
@@ -41,11 +43,20 @@ func (s *Server) processQueryLogsAndStats(dctx *dnsContext) (rc resultCode) {
// Synchronize access to s.queryLog and s.stats so they won't be suddenly
// uninitialized while in use. This can happen after proxy server has been
// stopped, but its workers haven't yet exited.
if shouldLog && s.queryLog != nil {
if shouldLog &&
s.queryLog != nil &&
s.queryLog.ShouldLog(host, q.Qtype, q.Qclass) {
s.logQuery(dctx, pctx, elapsed, ip)
} else {
log.Debug(
"dnsforward: request %s %s from %s ignored; not logging",
dns.Type(q.Qtype),
host,
ip,
)
}
if s.stats != nil {
if s.stats != nil && s.stats.ShouldCount(host, q.Qtype, q.Qclass) {
s.updateStats(dctx, elapsed, *dctx.result, ip)
}

View File

@@ -16,34 +16,44 @@ import (
"github.com/stretchr/testify/require"
)
// testQueryLog is a simple querylog.QueryLog implementation for tests.
// testQueryLog is a simple [querylog.QueryLog] implementation for tests.
type testQueryLog struct {
// QueryLog is embedded here simply to make testQueryLog
// a querylog.QueryLog without actually implementing all methods.
// a [querylog.QueryLog] without actually implementing all methods.
querylog.QueryLog
lastParams *querylog.AddParams
}
// Add implements the querylog.QueryLog interface for *testQueryLog.
// Add implements the [querylog.QueryLog] interface for *testQueryLog.
func (l *testQueryLog) Add(p *querylog.AddParams) {
l.lastParams = p
}
// testStats is a simple stats.Stats implementation for tests.
// ShouldLog implements the [querylog.QueryLog] interface for *testQueryLog.
func (l *testQueryLog) ShouldLog(string, uint16, uint16) bool {
return true
}
// testStats is a simple [stats.Interface] implementation for tests.
type testStats struct {
// Stats is embedded here simply to make testStats a stats.Stats without
// actually implementing all methods.
// Stats is embedded here simply to make testStats a [stats.Interface]
// without actually implementing all methods.
stats.Interface
lastEntry stats.Entry
}
// Update implements the stats.Stats interface for *testStats.
// Update implements the [stats.Interface] interface for *testStats.
func (l *testStats) Update(e stats.Entry) {
l.lastEntry = e
}
// ShouldCount implements the [stats.Interface] interface for *testStats.
func (l *testStats) ShouldCount(string, uint16, uint16) bool {
return true
}
func TestProcessQueryLogsAndStats(t *testing.T) {
testCases := []struct {
name string