Pull request: filtering: fix rw to subdomain
Updates #4016. Squashed commit of the following: commit 83bb15c5a5098103cd17e76b49f456fb4fa73408 Merge: 81905503313555b1
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: 29cf83de661f4ece
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:
parent
313555b10c
commit
dea8a585f8
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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{}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue