Merge in DNS/adguard-home from upd-urlfilter to master Updates #6050. Squashed commit of the following: commit 80337ab02d616e25fa455e46c9535c088b5c5ea5 Merge: fb2cfd1a531f7aaeccAuthor: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Wed Aug 23 16:50:49 2023 +0300 Merge branch 'master' into upd-urlfilter commit fb2cfd1a5c94d92030fc8832615764f100d010e5 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Wed Aug 23 16:22:43 2023 +0300 dnsforward: imp code, docs commit 2900333bb85d4e064db9de27bd5bfe7c3ef00747 Merge: 977ed35e42bfc9fcb1Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Aug 22 18:06:05 2023 +0300 Merge branch 'master' into upd-urlfilter commit 977ed35e4ed377f1031721d58e0fcb58de1e74ac Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Aug 22 17:06:30 2023 +0300 all: log changes commit 1228a0770485799bf50bbe68005dbb0ba9a96a9c Merge: 78305eb2e4b4036fa6Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Aug 22 16:51:42 2023 +0300 Merge branch 'master' into upd-urlfilter commit 78305eb2ebc3854dd11ce35d6b4c7eecccd7cc78 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Tue Aug 22 15:55:05 2023 +0300 all: upd urlfilter commit 63a29e18d5034e5f9433121ff7e7c45aebfa1f0f Merge: 748c53430762e5be97Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Aug 21 20:12:49 2023 +0300 Merge branch 'master' into upd-urlfilter commit 748c5343020b0c6d4d4f16eb3d30b875c0a94e0f Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Aug 21 20:07:44 2023 +0300 all: imp code, docs commit 91975140f3305a6793e07142f7c9a75120a4ce8c Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Thu Aug 17 16:16:19 2023 +0300 all: upd urlfilter
227 lines
5.5 KiB
Go
227 lines
5.5 KiB
Go
package filtering
|
|
|
|
import (
|
|
"fmt"
|
|
"net/netip"
|
|
"strings"
|
|
|
|
"github.com/AdguardTeam/golibs/errors"
|
|
"github.com/AdguardTeam/golibs/log"
|
|
"github.com/AdguardTeam/golibs/mathutil"
|
|
"github.com/miekg/dns"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
// Legacy DNS rewrites
|
|
|
|
// LegacyRewrite is a single legacy DNS rewrite record.
|
|
//
|
|
// Instances of *LegacyRewrite must never be nil.
|
|
type LegacyRewrite struct {
|
|
// Domain is the domain pattern for which this rewrite should work.
|
|
Domain string `yaml:"domain"`
|
|
|
|
// Answer is the IP address, canonical name, or one of the special
|
|
// values: "A" or "AAAA".
|
|
Answer string `yaml:"answer"`
|
|
|
|
// IP is the IP address that should be used in the response if Type is
|
|
// dns.TypeA or dns.TypeAAAA.
|
|
IP netip.Addr `yaml:"-"`
|
|
|
|
// Type is the DNS record type: A, AAAA, or CNAME.
|
|
Type uint16 `yaml:"-"`
|
|
}
|
|
|
|
// equal returns true if the rw is equal to the other.
|
|
func (rw *LegacyRewrite) equal(other *LegacyRewrite) (ok bool) {
|
|
return rw.Domain == other.Domain && rw.Answer == other.Answer
|
|
}
|
|
|
|
// matchesQType returns true if the entry matches the question type qt.
|
|
func (rw *LegacyRewrite) matchesQType(qt uint16) (ok bool) {
|
|
// Add CNAMEs, since they match for all types requests.
|
|
if rw.Type == dns.TypeCNAME {
|
|
return true
|
|
}
|
|
|
|
// Reject types other than A and AAAA.
|
|
if qt != dns.TypeA && qt != dns.TypeAAAA {
|
|
return false
|
|
}
|
|
|
|
// If the types match or the entry is set to allow only the other type,
|
|
// include them.
|
|
return rw.Type == qt || rw.IP == netip.Addr{}
|
|
}
|
|
|
|
// normalize makes sure that the new or decoded entry is normalized with regards
|
|
// to domain name case, IP length, and so on.
|
|
//
|
|
// If rw is nil, it returns an errors.
|
|
func (rw *LegacyRewrite) normalize() (err error) {
|
|
if rw == nil {
|
|
return errors.Error("nil rewrite entry")
|
|
}
|
|
|
|
// TODO(a.garipov): Write a case-agnostic version of strings.HasSuffix and
|
|
// use it in matchDomainWildcard instead of using strings.ToLower
|
|
// everywhere.
|
|
rw.Domain = strings.ToLower(rw.Domain)
|
|
|
|
switch rw.Answer {
|
|
case "AAAA":
|
|
rw.IP = netip.Addr{}
|
|
rw.Type = dns.TypeAAAA
|
|
|
|
return nil
|
|
case "A":
|
|
rw.IP = netip.Addr{}
|
|
rw.Type = dns.TypeA
|
|
|
|
return nil
|
|
default:
|
|
// Go on.
|
|
}
|
|
|
|
ip, err := netip.ParseAddr(rw.Answer)
|
|
if err != nil {
|
|
log.Debug("normalizing legacy rewrite: %s", err)
|
|
rw.Type = dns.TypeCNAME
|
|
|
|
return nil
|
|
}
|
|
|
|
rw.IP = ip
|
|
if ip.Is4() {
|
|
rw.Type = dns.TypeA
|
|
} else {
|
|
rw.Type = dns.TypeAAAA
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// isWildcard returns true if pat is a wildcard domain pattern.
|
|
func isWildcard(pat string) bool {
|
|
return len(pat) > 1 && pat[0] == '*' && pat[1] == '.'
|
|
}
|
|
|
|
// matchDomainWildcard returns true if host matches the wildcard pattern.
|
|
func matchDomainWildcard(host, wildcard string) (ok bool) {
|
|
return isWildcard(wildcard) && strings.HasSuffix(host, wildcard[1:])
|
|
}
|
|
|
|
// Compare is used to sort rewrites according to the following priority:
|
|
//
|
|
// 1. A and AAAA > CNAME;
|
|
// 2. wildcard > exact;
|
|
// 3. lower level wildcard > higher level wildcard;
|
|
func (rw *LegacyRewrite) Compare(b *LegacyRewrite) (res int) {
|
|
if rw.Type == dns.TypeCNAME && b.Type != dns.TypeCNAME {
|
|
return -1
|
|
} else if rw.Type != dns.TypeCNAME && b.Type == dns.TypeCNAME {
|
|
return 1
|
|
}
|
|
|
|
aIsWld, bIsWld := isWildcard(rw.Domain), isWildcard(b.Domain)
|
|
if aIsWld == bIsWld {
|
|
// Both are either wildcards or both aren't.
|
|
return len(rw.Domain) - len(b.Domain)
|
|
}
|
|
|
|
if aIsWld {
|
|
return 1
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
// prepareRewrites normalizes and validates all legacy DNS rewrites.
|
|
func (d *DNSFilter) prepareRewrites() (err error) {
|
|
for i, r := range d.Rewrites {
|
|
err = r.normalize()
|
|
if err != nil {
|
|
return fmt.Errorf("at index %d: %w", i, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// findRewrites returns the list of matched rewrite entries. If rewrites are
|
|
// empty, but matched is true, the domain is found among the rewrite rules but
|
|
// not for this question type.
|
|
//
|
|
// The result priority is: CNAME, then A and AAAA; exact, then wildcard. If the
|
|
// host is matched exactly, wildcard entries aren't returned. If the host
|
|
// matched by wildcards, return the most specific for the question type.
|
|
func findRewrites(
|
|
entries []*LegacyRewrite,
|
|
host string,
|
|
qtype uint16,
|
|
) (rewrites []*LegacyRewrite, matched bool) {
|
|
for _, e := range entries {
|
|
if e.Domain != host && !matchDomainWildcard(host, e.Domain) {
|
|
continue
|
|
}
|
|
|
|
matched = true
|
|
if e.matchesQType(qtype) {
|
|
rewrites = append(rewrites, e)
|
|
}
|
|
}
|
|
|
|
if len(rewrites) == 0 {
|
|
return nil, matched
|
|
}
|
|
|
|
slices.SortFunc(rewrites, (*LegacyRewrite).Compare)
|
|
|
|
for i, r := range rewrites {
|
|
if isWildcard(r.Domain) {
|
|
// Don't use rewrites[:0], because we need to return at least one
|
|
// item here.
|
|
rewrites = rewrites[:mathutil.Max(1, i)]
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
return rewrites, matched
|
|
}
|
|
|
|
// setRewriteResult sets the Reason or IPList of res if necessary. res must not
|
|
// be nil.
|
|
func setRewriteResult(res *Result, host string, rewrites []*LegacyRewrite, qtype uint16) {
|
|
for _, rw := range rewrites {
|
|
if rw.Type == qtype && (qtype == dns.TypeA || qtype == dns.TypeAAAA) {
|
|
if rw.IP == (netip.Addr{}) {
|
|
// "A"/"AAAA" exception: allow getting from upstream.
|
|
res.Reason = NotFilteredNotFound
|
|
|
|
return
|
|
}
|
|
|
|
res.IPList = append(res.IPList, rw.IP)
|
|
|
|
log.Debug("rewrite: a/aaaa for %s is %s", host, rw.IP)
|
|
}
|
|
}
|
|
}
|
|
|
|
// cloneRewrites returns a deep copy of entries.
|
|
func cloneRewrites(entries []*LegacyRewrite) (clone []*LegacyRewrite) {
|
|
clone = make([]*LegacyRewrite, len(entries))
|
|
for i, rw := range entries {
|
|
clone[i] = &LegacyRewrite{
|
|
Domain: rw.Domain,
|
|
Answer: rw.Answer,
|
|
IP: rw.IP,
|
|
Type: rw.Type,
|
|
}
|
|
}
|
|
|
|
return clone
|
|
}
|