Merge in DNS/adguard-home from 2499-rewrites-1 to master
Squashed commit of the following:
commit 6303107d6ca7dd88175e4e123128189e5958f060
Merge: e2040b95 09f88cf2
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Mon Dec 5 13:38:01 2022 +0200
Merge remote-tracking branch 'origin/master' into 2499-rewrites-1
commit e2040b95dd3157d033d929bb45fc7662b9918a78
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Mon Dec 5 12:00:21 2022 +0200
rewrite: item
commit c7278e8adeec1ba3a090cc93db30c80699617c52
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Sun Dec 4 12:57:59 2022 +0200
rewrite: imp code
commit d23a740262a4fbdd9b25f7449ced707c0d2be634
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Fri Dec 2 13:08:25 2022 +0200
rewrite: imp code
commit 773a5211b6662afd03a34219e7114c6f1e2bb579
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Fri Dec 2 13:05:20 2022 +0200
rewrite: imp code
commit 48b54e19da9844d9b868d0be7e428ad6bacae6a5
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Fri Dec 2 13:02:47 2022 +0200
rewrite: tests item
commit 62af2bd91f5559840e7948ac4bf7c36b1bee1dc2
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 17:11:21 2022 +0200
rewrite: tests
commit f040b609391cb2275b11d4732bbac0380c01de07
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 17:04:59 2022 +0200
rewrite: imp code
commit 4592b8c4e6107e5a746261d3335282827ce36b74
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 17:02:31 2022 +0200
rewrite: imp code
commit cc1660695341c558dbac6acaa31ac160a45f6105
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 16:19:27 2022 +0200
rewrite: imp code
commit cf3840b76d45bf319630256c01586159dd1e85fe
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 13:16:40 2022 +0200
rewrite: tests
commit 6fd6f03ca4320d4345032139b43cb457b3ae4278
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Thu Dec 1 11:14:44 2022 +0200
rewrite: imp code
commit 2ebd2a1e79afc8f486cf6533968c51ca61bc03ab
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Wed Nov 30 11:43:24 2022 +0200
rewrite: tests
commit 7da987994303a3e7b16eb6b0baaa4b59a52b97be
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Wed Nov 30 10:54:57 2022 +0200
filtering: imp code
commit ab98efc6710fac7cba28dab5bca9b60e9ec34ef7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date: Mon Nov 28 13:31:12 2022 +0200
filtering: rewritehttp
220 lines
5.2 KiB
Go
220 lines
5.2 KiB
Go
// DNS Rewrites
|
|
|
|
package filtering
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/AdguardTeam/golibs/errors"
|
|
"github.com/miekg/dns"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
// 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 net.IP `yaml:"-"`
|
|
|
|
// Type is the DNS record type: A, AAAA, or CNAME.
|
|
Type uint16 `yaml:"-"`
|
|
}
|
|
|
|
// clone returns a deep clone of rw.
|
|
func (rw *LegacyRewrite) clone() (cloneRW *LegacyRewrite) {
|
|
return &LegacyRewrite{
|
|
Domain: rw.Domain,
|
|
Answer: rw.Answer,
|
|
IP: slices.Clone(rw.IP),
|
|
Type: rw.Type,
|
|
}
|
|
}
|
|
|
|
// 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 == nil
|
|
}
|
|
|
|
// normalize makes sure that the a 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 = nil
|
|
rw.Type = dns.TypeAAAA
|
|
|
|
return nil
|
|
case "A":
|
|
rw.IP = nil
|
|
rw.Type = dns.TypeA
|
|
|
|
return nil
|
|
default:
|
|
// Go on.
|
|
}
|
|
|
|
ip := net.ParseIP(rw.Answer)
|
|
if ip == nil {
|
|
rw.Type = dns.TypeCNAME
|
|
|
|
return nil
|
|
}
|
|
|
|
ip4 := ip.To4()
|
|
if ip4 != nil {
|
|
rw.IP = ip4
|
|
rw.Type = dns.TypeA
|
|
} else {
|
|
rw.IP = ip
|
|
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:])
|
|
}
|
|
|
|
// rewritesSorted is a slice of legacy rewrites for sorting.
|
|
//
|
|
// The sorting priority:
|
|
//
|
|
// 1. A and AAAA > CNAME
|
|
// 2. wildcard > exact
|
|
// 3. lower level wildcard > higher level wildcard
|
|
//
|
|
// TODO(a.garipov): Replace with slices.Sort.
|
|
type rewritesSorted []*LegacyRewrite
|
|
|
|
// Len implements the sort.Interface interface for rewritesSorted.
|
|
func (a rewritesSorted) Len() (l int) { return len(a) }
|
|
|
|
// Swap implements the sort.Interface interface for rewritesSorted.
|
|
func (a rewritesSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
// Less implements the sort.Interface interface for rewritesSorted.
|
|
func (a rewritesSorted) Less(i, j int) (less bool) {
|
|
ith, jth := a[i], a[j]
|
|
if ith.Type == dns.TypeCNAME && jth.Type != dns.TypeCNAME {
|
|
return true
|
|
} else if ith.Type != dns.TypeCNAME && jth.Type == dns.TypeCNAME {
|
|
return false
|
|
}
|
|
|
|
if iw, jw := isWildcard(ith.Domain), isWildcard(jth.Domain); iw != jw {
|
|
return jw
|
|
}
|
|
|
|
// Both are either wildcards or not.
|
|
return len(ith.Domain) > len(jth.Domain)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
sort.Sort(rewritesSorted(rewrites))
|
|
|
|
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[:max(1, i)]
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
return rewrites, matched
|
|
}
|
|
|
|
func max(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
|
|
return b
|
|
}
|