Pull request: filtering: fix rw to subdomain

Updates #4016.

Squashed commit of the following:

commit 83bb15c5a5098103cd17e76b49f456fb4fa73408
Merge: 81905503 313555b1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Dec 27 19:36:44 2021 +0300

    Merge branch 'master' into 4016-rw-subdomain

commit 81905503c977c004d7ddca1d4e7537bf76443a6e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Dec 27 19:35:51 2021 +0300

    filtering: fix self reqs

commit b706f481f00232d28dade0bd747a7496753c7deb
Merge: 29cf83de 661f4ece
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Dec 27 19:13:08 2021 +0300

    Merge branch 'master' into 4016-rw-subdomain

commit 29cf83de8e3ff60ea1c471c2a161055b1377392d
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Dec 27 19:07:08 2021 +0300

    all: fix docs

commit 9213fd8ec2b81e65b1198ab241400065f14684b1
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Dec 27 18:44:06 2021 +0300

    filtering: fix rw to subdomain
This commit is contained in:
Ainar Garipov 2021-12-27 19:40:39 +03:00
parent 313555b10c
commit dea8a585f8
19 changed files with 193 additions and 105 deletions

View File

@ -29,6 +29,7 @@ and this project adheres to
### Fixed ### Fixed
- Legacy DNS rewrites from a wildcard pattern to a subdomain ([#4016]).
- Service not being stopped before running the `uninstall` service action - Service not being stopped before running the `uninstall` service action
([#3868]). ([#3868]).
- Broken `reload` service action on FreeBSD. - Broken `reload` service action on FreeBSD.
@ -44,6 +45,7 @@ and this project adheres to
[#3868]: https://github.com/AdguardTeam/AdGuardHome/issues/3868 [#3868]: https://github.com/AdguardTeam/AdGuardHome/issues/3868
[#3987]: https://github.com/AdguardTeam/AdGuardHome/issues/3987 [#3987]: https://github.com/AdguardTeam/AdGuardHome/issues/3987
[#4008]: https://github.com/AdguardTeam/AdGuardHome/issues/4008 [#4008]: https://github.com/AdguardTeam/AdGuardHome/issues/4008
[#4016]: https://github.com/AdguardTeam/AdGuardHome/issues/4016

View File

@ -373,7 +373,7 @@ func (hp *hostsParser) add(ip net.IP, host string) (hostType int) {
// addPair puts the pair of ip and host to the rules builder if needed. For // addPair puts the pair of ip and host to the rules builder if needed. For
// each ip the first member of hosts will become the main one. // each ip the first member of hosts will become the main one.
func (hp *hostsParser) addPairs(ip net.IP, hosts []string) { func (hp *hostsParser) addPairs(ip net.IP, hosts []string) {
// Put the rule in a preproccesed format like: // Put the rule in a processed format like:
// //
// ip host1 host2 ... // ip host1 host2 ...
// //
@ -488,14 +488,14 @@ func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
v, hasIP := target.Get(ip) v, hasIP := target.Get(ip)
// ok is set to true if the target doesn't contain ip or if the // ok is set to true if the target doesn't contain ip or if the
// appropriate hosts set isn't equal to the checked one, i.e. the maps // appropriate hosts set isn't equal to the checked one, i.e. the maps
// have at least one disperancy. // have at least one discrepancy.
ok = !hasIP || !v.(*stringutil.Set).Equal(val.(*stringutil.Set)) ok = !hasIP || !v.(*stringutil.Set).Equal(val.(*stringutil.Set))
// Continue only if maps has no discrepancies. // Continue only if maps has no discrepancies.
return !ok return !ok
}) })
// Return true if every value from the IP map has no disperancies with the // Return true if every value from the IP map has no discrepancies with the
// appropriate one from the target. // appropriate one from the target.
return !ok return !ok
} }

View File

@ -8,8 +8,8 @@ import (
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
) )
// IPVersion is a documentational alias for int. Use it when the integer means // IPVersion is a alias for int for documentation purposes. Use it when the
// IP version. // integer means IP version.
type IPVersion = int type IPVersion = int
// IP version constants. // IP version constants.
@ -67,7 +67,7 @@ func IfaceIPAddrs(iface NetIface, ipv IPVersion) (ips []net.IP, err error) {
// //
// It makes up to maxAttempts attempts to get the addresses if there are none, // It makes up to maxAttempts attempts to get the addresses if there are none,
// each time using the provided backoff. Sometimes an interface needs a few // each time using the provided backoff. Sometimes an interface needs a few
// seconds to really ititialize. // seconds to really initialize.
// //
// See https://github.com/AdguardTeam/AdGuardHome/issues/2304. // See https://github.com/AdguardTeam/AdGuardHome/issues/2304.
func IfaceDNSIPAddrs( func IfaceDNSIPAddrs(

View File

@ -14,8 +14,8 @@ import (
// //
// It is safe for concurrent use. // It is safe for concurrent use.
// //
// TODO(a.garipov): Perhaps create an optimised version with uint32 for // TODO(a.garipov): Perhaps create an optimized version with uint32 for IPv4
// IPv4 ranges? Or use one of uint128 packages? // ranges? Or use one of uint128 packages?
type ipRange struct { type ipRange struct {
start *big.Int start *big.Int
end *big.Int end *big.Int

View File

@ -31,8 +31,8 @@ func (c testTLSConn) ConnectionState() (cs tls.ConnectionState) {
// testQUICSession is a quicSession for tests. // testQUICSession is a quicSession for tests.
type testQUICSession struct { type testQUICSession struct {
// Session is embedded here simply to make testQUICSession // Session is embedded here simply to make testQUICSession a quic.Session
// a quic.Session without acctually implementing all methods. // without actually implementing all methods.
quic.Session quic.Session
serverName string serverName string

View File

@ -293,9 +293,9 @@ func (s *Server) processInternalHosts(dctx *dnsContext) (rc resultCode) {
req := dctx.proxyCtx.Req req := dctx.proxyCtx.Req
q := req.Question[0] q := req.Question[0]
// Go on processing the AAAA request despite the fact that we don't // Go on processing the AAAA request despite the fact that we don't support
// support it yet. The expected behavior here is to respond with an // it yet. The expected behavior here is to respond with an empty answer
// empty asnwer and not NXDOMAIN. // and not NXDOMAIN.
if q.Qtype != dns.TypeA && q.Qtype != dns.TypeAAAA { if q.Qtype != dns.TypeA && q.Qtype != dns.TypeAAAA {
return resultCodeSuccess return resultCodeSuccess
} }

View File

@ -443,7 +443,7 @@ func (s *Server) setupResolvers(localAddrs []string) (err error) {
&upstream.Options{ &upstream.Options{
Bootstrap: bootstraps, Bootstrap: bootstraps,
Timeout: defaultLocalTimeout, Timeout: defaultLocalTimeout,
// TODO(e.burkov): Should we verify server's ceritificates? // TODO(e.burkov): Should we verify server's certificates?
}, },
) )
if err != nil { if err != nil {
@ -559,7 +559,7 @@ func (s *Server) IsRunning() bool {
return s.isRunning return s.isRunning
} }
// srvClosedErr is returned when the method can't complete without inacessible // srvClosedErr is returned when the method can't complete without inaccessible
// data from the closing server. // data from the closing server.
const srvClosedErr errors.Error = "server is closed" const srvClosedErr errors.Error = "server is closed"

View File

@ -892,7 +892,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
func TestRewrite(t *testing.T) { func TestRewrite(t *testing.T) {
c := &filtering.Config{ c := &filtering.Config{
Rewrites: []filtering.RewriteEntry{{ Rewrites: []*filtering.LegacyRewrite{{
Domain: "test.com", Domain: "test.com",
Answer: "1.2.3.4", Answer: "1.2.3.4",
Type: dns.TypeA, Type: dns.TypeA,

View File

@ -78,8 +78,8 @@ func newRecursionDetector(ttl time.Duration, suspectsNum uint) (rd *recursionDet
// msgToSignature converts msg into it's signature represented in bytes. // msgToSignature converts msg into it's signature represented in bytes.
func msgToSignature(msg dns.Msg) (sig []byte) { func msgToSignature(msg dns.Msg) (sig []byte) {
sig = make([]byte, uint16sz*2+netutil.MaxDomainNameLen) sig = make([]byte, uint16sz*2+netutil.MaxDomainNameLen)
// The binary.BigEndian byte order is used everywhere except when the // The binary.BigEndian byte order is used everywhere except when the real
// real machine's endianess is needed. // machine's endianness is needed.
byteOrder := binary.BigEndian byteOrder := binary.BigEndian
byteOrder.PutUint16(sig[0:], msg.Id) byteOrder.PutUint16(sig[0:], msg.Id)
q := msg.Question[0] q := msg.Question[0]

View File

@ -28,7 +28,7 @@ import (
// The IDs of built-in filter lists. // The IDs of built-in filter lists.
// //
// Keep in sync with client/src/helpers/contants.js. // Keep in sync with client/src/helpers/constants.js.
const ( const (
CustomListID = -iota CustomListID = -iota
SysHostsListID SysHostsListID
@ -80,7 +80,7 @@ type Config struct {
ParentalCacheSize uint `yaml:"parental_cache_size"` // (in bytes) ParentalCacheSize uint `yaml:"parental_cache_size"` // (in bytes)
CacheTime uint `yaml:"cache_time"` // Element's TTL (in minutes) CacheTime uint `yaml:"cache_time"` // Element's TTL (in minutes)
Rewrites []RewriteEntry `yaml:"rewrites"` Rewrites []*LegacyRewrite `yaml:"rewrites"`
// Names of services to block (globally). // Names of services to block (globally).
// Per-client settings can override this configuration. // Per-client settings can override this configuration.
@ -161,9 +161,14 @@ type DNSFilter struct {
// Filter represents a filter list // Filter represents a filter list
type Filter struct { type Filter struct {
ID int64 // auto-assigned when filter is added (see nextFilterID) // FilePath is the path to a filtering rules list file.
Data []byte `yaml:"-"` // List of rules divided by '\n' FilePath string `yaml:"-"`
FilePath string `yaml:"-"` // Path to a filtering rules file
// Data is the content of the file.
Data []byte `yaml:"-"`
// ID is automatically assigned when filter is added using nextFilterID.
ID int64
} }
// Reason holds an enum detailing why it was filtered or not filtered // Reason holds an enum detailing why it was filtered or not filtered
@ -281,8 +286,14 @@ func (d *DNSFilter) WriteDiskConfig(c *Config) {
c.Rewrites = cloneRewrites(c.Rewrites) c.Rewrites = cloneRewrites(c.Rewrites)
} }
func cloneRewrites(entries []RewriteEntry) (clone []RewriteEntry) { // cloneRewrites returns a deep copy of entries.
return append([]RewriteEntry(nil), entries...) func cloneRewrites(entries []*LegacyRewrite) (clone []*LegacyRewrite) {
clone = make([]*LegacyRewrite, len(entries))
for i, rw := range entries {
clone[i] = rw.clone()
}
return clone
} }
// SetFilters - set new filters (synchronously or asynchronously) // SetFilters - set new filters (synchronously or asynchronously)
@ -477,10 +488,8 @@ func (d *DNSFilter) matchSysHosts(
} }
// matchSysHostsIntl actually matches the request. It's separated to avoid // matchSysHostsIntl actually matches the request. It's separated to avoid
// perfoming checks twice. // performing checks twice.
func (d *DNSFilter) matchSysHostsIntl( func (d *DNSFilter) matchSysHostsIntl(req *urlfilter.DNSRequest) (res Result, err error) {
req *urlfilter.DNSRequest,
) (res Result, err error) {
dnsres, _ := d.EtcHosts.MatchRequest(*req) dnsres, _ := d.EtcHosts.MatchRequest(*req)
if dnsres == nil { if dnsres == nil {
return res, nil return res, nil
@ -530,15 +539,25 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
cnames := stringutil.NewSet() cnames := stringutil.NewSet()
origHost := host origHost := host
for matched && len(rewrites) > 0 && rewrites[0].Type == dns.TypeCNAME { for matched && len(rewrites) > 0 && rewrites[0].Type == dns.TypeCNAME {
rwAns := rewrites[0].Answer rw := rewrites[0]
rwPat := rw.Domain
rwAns := rw.Answer
log.Debug("rewrite: cname for %s is %s", host, rwAns) log.Debug("rewrite: cname for %s is %s", host, rwAns)
if host == rwAns { if origHost == rwAns || rwPat == rwAns {
// Rewrite of a domain onto itself is an exception rule. // Either a request for the hostname itself or a rewrite of
res.Reason = NotFilteredNotFound // a pattern onto itself, both of which are an exception rules.
// Return a not filtered result.
return Result{}
} else if host == rwAns && isWildcard(rwPat) {
// An "*.example.com → sub.example.com" rewrite matching in a loop.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/4016.
return res res.CanonName = host
break
} }
host = rwAns host = rwAns
@ -560,7 +579,7 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) {
// setRewriteResult sets the Reason or IPList of res if necessary. res must not // setRewriteResult sets the Reason or IPList of res if necessary. res must not
// be nil. // be nil.
func setRewriteResult(res *Result, host string, rewrites []RewriteEntry, qtype uint16) { func setRewriteResult(res *Result, host string, rewrites []*LegacyRewrite, qtype uint16) {
for _, rw := range rewrites { for _, rw := range rewrites {
if rw.Type == qtype && (qtype == dns.TypeA || qtype == dns.TypeAAAA) { if rw.Type == qtype && (qtype == dns.TypeA || qtype == dns.TypeAAAA) {
if rw.IP == nil { if rw.IP == nil {
@ -932,12 +951,18 @@ func New(c *Config, blockFilters []Filter) (d *DNSFilter) {
err := d.initSecurityServices() err := d.initSecurityServices()
if err != nil { if err != nil {
log.Error("filtering: initialize services: %s", err) log.Error("filtering: initialize services: %s", err)
return nil return nil
} }
if c != nil { if c != nil {
d.Config = *c d.Config = *c
d.prepareRewrites() err = d.prepareRewrites()
if err != nil {
log.Error("rewrites: preparing: %s", err)
return nil
}
} }
bsvcs := []string{} bsvcs := []string{}

View File

@ -4,19 +4,24 @@ package filtering
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net" "net"
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
// RewriteEntry is a rewrite array element // LegacyRewrite is a single legacy DNS rewrite record.
type RewriteEntry struct { //
// Domain is the domain for which this rewrite should work. // 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"` Domain string `yaml:"domain"`
// Answer is the IP address, canonical name, or one of the special // Answer is the IP address, canonical name, or one of the special
@ -24,77 +29,96 @@ type RewriteEntry struct {
Answer string `yaml:"answer"` Answer string `yaml:"answer"`
// IP is the IP address that should be used in the response if Type is // IP is the IP address that should be used in the response if Type is
// A or AAAA. // dns.TypeA or dns.TypeAAAA.
IP net.IP `yaml:"-"` IP net.IP `yaml:"-"`
// Type is the DNS record type: A, AAAA, or CNAME. // Type is the DNS record type: A, AAAA, or CNAME.
Type uint16 `yaml:"-"` Type uint16 `yaml:"-"`
} }
// equal returns true if the entry is considered equal to the other. // clone returns a deep clone of rw.
func (e *RewriteEntry) equal(other RewriteEntry) (ok bool) { func (rw *LegacyRewrite) clone() (cloneRW *LegacyRewrite) {
return e.Domain == other.Domain && e.Answer == other.Answer return &LegacyRewrite{
Domain: rw.Domain,
Answer: rw.Answer,
IP: netutil.CloneIP(rw.IP),
Type: rw.Type,
}
} }
// matchesQType returns true if the entry matched qtype. // equal returns true if the rw is equal to the other.
func (e *RewriteEntry) matchesQType(qtype uint16) (ok bool) { 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. // Add CNAMEs, since they match for all types requests.
if e.Type == dns.TypeCNAME { if rw.Type == dns.TypeCNAME {
return true return true
} }
// Reject types other than A and AAAA. // Reject types other than A and AAAA.
if qtype != dns.TypeA && qtype != dns.TypeAAAA { if qt != dns.TypeA && qt != dns.TypeAAAA {
return false return false
} }
// If the types match or the entry is set to allow only the other type, // If the types match or the entry is set to allow only the other type,
// include them. // include them.
return e.Type == qtype || e.IP == nil return rw.Type == qt || rw.IP == nil
} }
// normalize makes sure that the a new or decoded entry is normalized with // normalize makes sure that the a new or decoded entry is normalized with
// regards to domain name case, IP length, and so on. // regards to domain name case, IP length, and so on.
func (e *RewriteEntry) normalize() { //
// TODO(a.garipov): Write a case-agnostic version of strings.HasSuffix // If rw is nil, it returns an errors.
// and use it in matchDomainWildcard instead of using strings.ToLower 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. // everywhere.
e.Domain = strings.ToLower(e.Domain) rw.Domain = strings.ToLower(rw.Domain)
switch e.Answer { switch rw.Answer {
case "AAAA": case "AAAA":
e.IP = nil rw.IP = nil
e.Type = dns.TypeAAAA rw.Type = dns.TypeAAAA
return return nil
case "A": case "A":
e.IP = nil rw.IP = nil
e.Type = dns.TypeA rw.Type = dns.TypeA
return return nil
default: default:
// Go on. // Go on.
} }
ip := net.ParseIP(e.Answer) ip := net.ParseIP(rw.Answer)
if ip == nil { if ip == nil {
e.Type = dns.TypeCNAME rw.Type = dns.TypeCNAME
return return nil
} }
ip4 := ip.To4() ip4 := ip.To4()
if ip4 != nil { if ip4 != nil {
e.IP = ip4 rw.IP = ip4
e.Type = dns.TypeA rw.Type = dns.TypeA
} else { } else {
e.IP = ip rw.IP = ip
e.Type = dns.TypeAAAA rw.Type = dns.TypeAAAA
} }
return nil
} }
func isWildcard(host string) bool { // isWildcard returns true if pat is a wildcard domain pattern.
return len(host) > 1 && host[0] == '*' && host[1] == '.' func isWildcard(pat string) bool {
return len(pat) > 1 && pat[0] == '*' && pat[1] == '.'
} }
// matchDomainWildcard returns true if host matches the wildcard pattern. // matchDomainWildcard returns true if host matches the wildcard pattern.
@ -110,16 +134,16 @@ func matchDomainWildcard(host, wildcard string) (ok bool) {
// wildcard > exact // wildcard > exact
// lower level wildcard > higher level wildcard // lower level wildcard > higher level wildcard
// //
type rewritesSorted []RewriteEntry type rewritesSorted []*LegacyRewrite
// Len implements the sort.Interface interface for legacyRewritesSorted. // Len implements the sort.Interface interface for legacyRewritesSorted.
func (a rewritesSorted) Len() int { return len(a) } func (a rewritesSorted) Len() (l int) { return len(a) }
// Swap implements the sort.Interface interface for legacyRewritesSorted. // Swap implements the sort.Interface interface for legacyRewritesSorted.
func (a rewritesSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a rewritesSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
// Less implements the sort.Interface interface for legacyRewritesSorted. // Less implements the sort.Interface interface for legacyRewritesSorted.
func (a rewritesSorted) Less(i, j int) bool { func (a rewritesSorted) Less(i, j int) (less bool) {
if a[i].Type == dns.TypeCNAME && a[j].Type != dns.TypeCNAME { if a[i].Type == dns.TypeCNAME && a[j].Type != dns.TypeCNAME {
return true return true
} else if a[i].Type != dns.TypeCNAME && a[j].Type == dns.TypeCNAME { } else if a[i].Type != dns.TypeCNAME && a[j].Type == dns.TypeCNAME {
@ -136,14 +160,20 @@ func (a rewritesSorted) Less(i, j int) bool {
} }
} }
// both are wildcards // Both are wildcards.
return len(a[i].Domain) > len(a[j].Domain) return len(a[i].Domain) > len(a[j].Domain)
} }
func (d *DNSFilter) prepareRewrites() { // prepareRewrites normalizes and validates all legacy DNS rewrites.
for i := range d.Rewrites { func (d *DNSFilter) prepareRewrites() (err error) {
d.Rewrites[i].normalize() 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 // findRewrites returns the list of matched rewrite entries. If rewrites are
@ -154,10 +184,10 @@ func (d *DNSFilter) prepareRewrites() {
// host is matched exactly, wildcard entries aren't returned. If the host // host is matched exactly, wildcard entries aren't returned. If the host
// matched by wildcards, return the most specific for the question type. // matched by wildcards, return the most specific for the question type.
func findRewrites( func findRewrites(
entries []RewriteEntry, entries []*LegacyRewrite,
host string, host string,
qtype uint16, qtype uint16,
) (rewrites []RewriteEntry, matched bool) { ) (rewrites []*LegacyRewrite, matched bool) {
for _, e := range entries { for _, e := range entries {
if e.Domain != host && !matchDomainWildcard(host, e.Domain) { if e.Domain != host && !matchDomainWildcard(host, e.Domain) {
continue continue
@ -224,23 +254,32 @@ func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
} }
func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
jsent := rewriteEntryJSON{} rwJSON := rewriteEntryJSON{}
err := json.NewDecoder(r.Body).Decode(&jsent) err := json.NewDecoder(r.Body).Decode(&rwJSON)
if err != nil { if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err) aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
return return
} }
ent := RewriteEntry{ rw := &LegacyRewrite{
Domain: jsent.Domain, Domain: rwJSON.Domain,
Answer: jsent.Answer, Answer: rwJSON.Answer,
} }
ent.normalize()
err = rw.normalize()
if err != nil {
// Shouldn't happen currently, since normalize only returns a non-nil
// error when a rewrite is nil, but be change-proof.
aghhttp.Error(r, w, http.StatusBadRequest, "normalizing: %s", err)
return
}
d.confLock.Lock() d.confLock.Lock()
d.Config.Rewrites = append(d.Config.Rewrites, ent) d.Config.Rewrites = append(d.Config.Rewrites, rw)
d.confLock.Unlock() d.confLock.Unlock()
log.Debug("rewrite: added element: %s -> %s [%d]", ent.Domain, ent.Answer, len(d.Config.Rewrites)) log.Debug("rewrite: added element: %s -> %s [%d]", rw.Domain, rw.Answer, len(d.Config.Rewrites))
d.Config.ConfigModified() d.Config.ConfigModified()
} }
@ -254,11 +293,12 @@ func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request)
return return
} }
entDel := RewriteEntry{ entDel := &LegacyRewrite{
Domain: jsent.Domain, Domain: jsent.Domain,
Answer: jsent.Answer, Answer: jsent.Answer,
} }
arr := []RewriteEntry{} arr := []*LegacyRewrite{}
d.confLock.Lock() d.confLock.Lock()
for _, ent := range d.Config.Rewrites { for _, ent := range d.Config.Rewrites {
if ent.equal(entDel) { if ent.equal(entDel) {

View File

@ -15,7 +15,7 @@ func TestRewrites(t *testing.T) {
d := newForTest(t, nil, nil) d := newForTest(t, nil, nil)
t.Cleanup(d.Close) t.Cleanup(d.Close)
d.Rewrites = []RewriteEntry{{ d.Rewrites = []*LegacyRewrite{{
// This one and below are about CNAME, A and AAAA. // This one and below are about CNAME, A and AAAA.
Domain: "somecname", Domain: "somecname",
Answer: "somehost.com", Answer: "somehost.com",
@ -66,8 +66,12 @@ func TestRewrites(t *testing.T) {
}, { }, {
Domain: "BIGHOST.COM", Domain: "BIGHOST.COM",
Answer: "1.2.3.7", Answer: "1.2.3.7",
}, {
Domain: "*.issue4016.com",
Answer: "sub.issue4016.com",
}} }}
d.prepareRewrites()
require.NoError(t, d.prepareRewrites())
testCases := []struct { testCases := []struct {
name string name string
@ -153,6 +157,20 @@ func TestRewrites(t *testing.T) {
wantIPs: nil, wantIPs: nil,
wantReason: Rewritten, wantReason: Rewritten,
dtyp: dns.TypeHTTPS, dtyp: dns.TypeHTTPS,
}, {
name: "issue4016",
host: "www.issue4016.com",
wantCName: "sub.issue4016.com",
wantIPs: nil,
wantReason: Rewritten,
dtyp: dns.TypeA,
}, {
name: "issue4016_self",
host: "sub.issue4016.com",
wantCName: "",
wantIPs: nil,
wantReason: NotFilteredNotFound,
dtyp: dns.TypeA,
}} }}
for _, tc := range testCases { for _, tc := range testCases {
@ -173,7 +191,7 @@ func TestRewritesLevels(t *testing.T) {
d := newForTest(t, nil, nil) d := newForTest(t, nil, nil)
t.Cleanup(d.Close) t.Cleanup(d.Close)
// Exact host, wildcard L2, wildcard L3. // Exact host, wildcard L2, wildcard L3.
d.Rewrites = []RewriteEntry{{ d.Rewrites = []*LegacyRewrite{{
Domain: "host.com", Domain: "host.com",
Answer: "1.1.1.1", Answer: "1.1.1.1",
Type: dns.TypeA, Type: dns.TypeA,
@ -186,7 +204,8 @@ func TestRewritesLevels(t *testing.T) {
Answer: "3.3.3.3", Answer: "3.3.3.3",
Type: dns.TypeA, Type: dns.TypeA,
}} }}
d.prepareRewrites()
require.NoError(t, d.prepareRewrites())
testCases := []struct { testCases := []struct {
name string name string
@ -219,7 +238,7 @@ func TestRewritesExceptionCNAME(t *testing.T) {
d := newForTest(t, nil, nil) d := newForTest(t, nil, nil)
t.Cleanup(d.Close) t.Cleanup(d.Close)
// Wildcard and exception for a sub-domain. // Wildcard and exception for a sub-domain.
d.Rewrites = []RewriteEntry{{ d.Rewrites = []*LegacyRewrite{{
Domain: "*.host.com", Domain: "*.host.com",
Answer: "2.2.2.2", Answer: "2.2.2.2",
}, { }, {
@ -229,7 +248,8 @@ func TestRewritesExceptionCNAME(t *testing.T) {
Domain: "*.sub.host.com", Domain: "*.sub.host.com",
Answer: "*.sub.host.com", Answer: "*.sub.host.com",
}} }}
d.prepareRewrites()
require.NoError(t, d.prepareRewrites())
testCases := []struct { testCases := []struct {
name string name string
@ -253,7 +273,7 @@ func TestRewritesExceptionCNAME(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
r := d.processRewrites(tc.host, dns.TypeA) r := d.processRewrites(tc.host, dns.TypeA)
if tc.want == nil { if tc.want == nil {
assert.Equal(t, NotFilteredNotFound, r.Reason) assert.Equal(t, NotFilteredNotFound, r.Reason, "got %s", r.Reason)
return return
} }
@ -269,7 +289,7 @@ func TestRewritesExceptionIP(t *testing.T) {
d := newForTest(t, nil, nil) d := newForTest(t, nil, nil)
t.Cleanup(d.Close) t.Cleanup(d.Close)
// Exception for AAAA record. // Exception for AAAA record.
d.Rewrites = []RewriteEntry{{ d.Rewrites = []*LegacyRewrite{{
Domain: "host.com", Domain: "host.com",
Answer: "1.2.3.4", Answer: "1.2.3.4",
Type: dns.TypeA, Type: dns.TypeA,
@ -290,7 +310,8 @@ func TestRewritesExceptionIP(t *testing.T) {
Answer: "A", Answer: "A",
Type: dns.TypeA, Type: dns.TypeA,
}} }}
d.prepareRewrites()
require.NoError(t, d.prepareRewrites())
testCases := []struct { testCases := []struct {
name string name string

View File

@ -424,7 +424,7 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
} }
var remoteAddr string var remoteAddr string
// realIP cannot be used here without taking TrustedProxies into accound due // realIP cannot be used here without taking TrustedProxies into account due
// to security issues. // to security issues.
// //
// See https://github.com/AdguardTeam/AdGuardHome/issues/2799. // See https://github.com/AdguardTeam/AdGuardHome/issues/2799.
@ -528,7 +528,7 @@ func optionalAuthThird(w http.ResponseWriter, r *http.Request) (authFirst bool)
cookie, err := r.Cookie(sessionCookieName) cookie, err := r.Cookie(sessionCookieName)
if glProcessCookie(r) { if glProcessCookie(r) {
log.Debug("auth: authentification was handled by GL-Inet submodule") log.Debug("auth: authentication was handled by GL-Inet submodule")
ok = true ok = true
} else if err == nil { } else if err == nil {
r := Context.auth.checkSession(cookie.Value) r := Context.auth.checkSession(cookie.Value)

View File

@ -96,7 +96,7 @@ type clientsContainer struct {
dnsServer *dnsforward.Server dnsServer *dnsforward.Server
// etcHosts contains list of rewrite rules taken from the operating system's // etcHosts contains list of rewrite rules taken from the operating system's
// hosts databse. // hosts database.
etcHosts *aghnet.HostsContainer etcHosts *aghnet.HostsContainer
testing bool // if TRUE, this object is used for internal tests testing bool // if TRUE, this object is used for internal tests
@ -175,7 +175,7 @@ type clientObject struct {
UseGlobalBlockedServices bool `yaml:"use_global_blocked_services"` UseGlobalBlockedServices bool `yaml:"use_global_blocked_services"`
} }
// addFromConfig initializes the clients containter with objects from the // addFromConfig initializes the clients container with objects from the
// configuration file. // configuration file.
func (clients *clientsContainer) addFromConfig(objects []*clientObject) { func (clients *clientsContainer) addFromConfig(objects []*clientObject) {
for _, o := range objects { for _, o := range objects {

View File

@ -164,7 +164,7 @@ type tlsConfigSettings struct {
// config is the global configuration structure. // config is the global configuration structure.
// //
// TODO(a.garipov, e.burkov): This global is afwul and must be removed. // TODO(a.garipov, e.burkov): This global is awful and must be removed.
var config = &configuration{ var config = &configuration{
BindPort: 3000, BindPort: 3000,
BetaBindPort: 0, BetaBindPort: 0,

View File

@ -37,7 +37,7 @@ func appendDNSAddrs(dst []string, addrs ...net.IP) (res []string) {
// appendDNSAddrsWithIfaces formats and appends all DNS addresses from src to // appendDNSAddrsWithIfaces formats and appends all DNS addresses from src to
// dst. It also adds the IP addresses of all network interfaces if src contains // dst. It also adds the IP addresses of all network interfaces if src contains
// an unspecified IP addresss. // an unspecified IP address.
func appendDNSAddrsWithIfaces(dst []string, src []net.IP) (res []string, err error) { func appendDNSAddrsWithIfaces(dst []string, src []net.IP) (res []string, err error) {
ifacesAdded := false ifacesAdded := false
for _, h := range src { for _, h := range src {

View File

@ -69,7 +69,7 @@ type Config struct {
// addresses. // addresses.
AnonymizeClientIP bool AnonymizeClientIP bool
// Anonymizer proccesses the IP addresses to anonymize those if needed. // Anonymizer processes the IP addresses to anonymize those if needed.
Anonymizer *aghnet.IPMut Anonymizer *aghnet.IPMut
} }

View File

@ -85,7 +85,7 @@ func ctDomainOrClientCaseNonStrict(
// quickMatch quickly checks if the line matches the given search criterion. // quickMatch quickly checks if the line matches the given search criterion.
// It returns false if the like doesn't match. This method is only here for // It returns false if the like doesn't match. This method is only here for
// optimisation purposes. // optimization purposes.
func (c *searchCriterion) quickMatch(line string, findClient quickMatchClientFunc) (ok bool) { func (c *searchCriterion) quickMatch(line string, findClient quickMatchClientFunc) (ok bool) {
switch c.criterionType { switch c.criterionType {
case ctTerm: case ctTerm:

View File

@ -32,7 +32,7 @@ type quickMatchClientFunc = func(clientID, ip string) (c *Client)
// quickMatch quickly checks if the line matches the given search parameters. // quickMatch quickly checks if the line matches the given search parameters.
// It returns false if the line doesn't match. This method is only here for // It returns false if the line doesn't match. This method is only here for
// optimisation purposes. // optimization purposes.
func (s *searchParams) quickMatch(line string, findClient quickMatchClientFunc) (ok bool) { func (s *searchParams) quickMatch(line string, findClient quickMatchClientFunc) (ok bool) {
for _, c := range s.searchCriteria { for _, c := range s.searchCriteria {
if !c.quickMatch(line, findClient) { if !c.quickMatch(line, findClient) {