Pull request: 4079 fix hosts container aliases
Merge in DNS/adguard-home from 4079-hosts-again to master
Updates #4079.
Squashed commit of the following:
commit 6aa8cbf32e8e47ba46bf5fba7681a10b68b4bc01
Merge: 19dba371 34c95f99
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Jan 12 14:05:30 2022 +0500
Merge branch 'master' into 4079-hosts-again
commit 19dba371cc30ab8b75b0116833f4ecf0ef0f182f
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Jan 12 14:05:20 2022 +0500
aghnet: imp docs
commit 9f341eb8ee4ba8468240bc3eeeb4951a3f7f5e6d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Mon Jan 10 18:44:17 2022 +0500
aghnet: fix races
commit fd66191c7637c8584711e5bb8186494327ce0f87
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Jan 6 17:21:14 2022 +0500
aghnet: fix hosts container aliases
This commit is contained in:
parent
34c95f99f8
commit
1458600c37
|
@ -27,6 +27,11 @@ TODO(a.garipov): Remove this deprecation, if v0.108.0 is released before the Go
|
||||||
-->
|
-->
|
||||||
- Go 1.17 support. v0.109.0 will require at least Go 1.18 to build.
|
- Go 1.17 support. v0.109.0 will require at least Go 1.18 to build.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Omitted aliases of hosts specified by another line within the OS's hosts file
|
||||||
|
([#4079]).
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Go 1.16 support.
|
- Go 1.16 support.
|
||||||
|
@ -77,6 +82,7 @@ TODO(a.garipov): Remove this deprecation, if v0.108.0 is released before the Go
|
||||||
[#4008]: https://github.com/AdguardTeam/AdGuardHome/issues/4008
|
[#4008]: https://github.com/AdguardTeam/AdGuardHome/issues/4008
|
||||||
[#4016]: https://github.com/AdguardTeam/AdGuardHome/issues/4016
|
[#4016]: https://github.com/AdguardTeam/AdGuardHome/issues/4016
|
||||||
[#4027]: https://github.com/AdguardTeam/AdGuardHome/issues/4027
|
[#4027]: https://github.com/AdguardTeam/AdGuardHome/issues/4027
|
||||||
|
[#4079]: https://github.com/AdguardTeam/AdGuardHome/issues/4079
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,11 @@ func (rm *requestMatcher) MatchRequest(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate returns the source hosts-syntax rule for the generated dnsrewrite
|
// Translate returns the source hosts-syntax rule for the generated dnsrewrite
|
||||||
// rule or an empty string if the last doesn't exist.
|
// rule or an empty string if the last doesn't exist. The returned rules are in
|
||||||
|
// a processed format like:
|
||||||
|
//
|
||||||
|
// ip host1 host2 ...
|
||||||
|
//
|
||||||
func (rm *requestMatcher) Translate(rule string) (hostRule string) {
|
func (rm *requestMatcher) Translate(rule string) (hostRule string) {
|
||||||
rm.stateLock.RLock()
|
rm.stateLock.RLock()
|
||||||
defer rm.stateLock.RUnlock()
|
defer rm.stateLock.RUnlock()
|
||||||
|
@ -179,7 +183,7 @@ func NewHostsContainer(
|
||||||
return nil, fmt.Errorf("adding path: %w", err)
|
return nil, fmt.Errorf("adding path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("%s: file %q expected to exist but doesn't", hostsContainerPref, p)
|
log.Debug("%s: %s is expected to exist but doesn't", hostsContainerPref, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +203,7 @@ func (hc *HostsContainer) Close() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upd returns the channel into which the updates are sent. The receivable
|
// Upd returns the channel into which the updates are sent. The receivable
|
||||||
// map's values are guaranteed to be of type of *stringutil.Set.
|
// map's values are guaranteed to be of type of *aghnet.Hosts.
|
||||||
func (hc *HostsContainer) Upd() (updates <-chan *netutil.IPMap) {
|
func (hc *HostsContainer) Upd() (updates <-chan *netutil.IPMap) {
|
||||||
return hc.updates
|
return hc.updates
|
||||||
}
|
}
|
||||||
|
@ -228,8 +232,9 @@ func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error)
|
||||||
return patterns, nil
|
return patterns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleEvents concurrently handles the events. It closes the update channel
|
// handleEvents concurrently handles the file system events. It closes the
|
||||||
// of HostsContainer when finishes. Used to be called within a goroutine.
|
// update channel of HostsContainer when finishes. It's used to be called
|
||||||
|
// within a separate goroutine.
|
||||||
func (hc *HostsContainer) handleEvents() {
|
func (hc *HostsContainer) handleEvents() {
|
||||||
defer log.OnPanic(fmt.Sprintf("%s: handling events", hostsContainerPref))
|
defer log.OnPanic(fmt.Sprintf("%s: handling events", hostsContainerPref))
|
||||||
|
|
||||||
|
@ -254,17 +259,27 @@ func (hc *HostsContainer) handleEvents() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ipRules is the pair of generated A/AAAA and PTR rules with related IP.
|
||||||
|
type ipRules struct {
|
||||||
|
// rule is the A/AAAA $dnsrewrite rule.
|
||||||
|
rule string
|
||||||
|
// rulePtr is the PTR $dnsrewrite rule.
|
||||||
|
rulePtr string
|
||||||
|
// ip is the IP address related to the rules.
|
||||||
|
ip net.IP
|
||||||
|
}
|
||||||
|
|
||||||
// hostsParser is a helper type to parse rules from the operating system's hosts
|
// hostsParser is a helper type to parse rules from the operating system's hosts
|
||||||
// file. It exists for only a single refreshing session.
|
// file. It exists for only a single refreshing session.
|
||||||
type hostsParser struct {
|
type hostsParser struct {
|
||||||
// rulesBuilder builds the resulting rulesBuilder list content.
|
// rulesBuilder builds the resulting rules list content.
|
||||||
rulesBuilder *strings.Builder
|
rulesBuilder *strings.Builder
|
||||||
|
|
||||||
// translations maps generated $dnsrewrite rules to the hosts-translations
|
// rules stores the rules for main hosts to generate translations.
|
||||||
// rules.
|
rules []ipRules
|
||||||
translations map[string]string
|
|
||||||
|
|
||||||
// cnameSet prevents duplicating cname rules.
|
// cnameSet prevents duplicating cname rules, e.g. same hostname for
|
||||||
|
// different IP versions.
|
||||||
cnameSet *stringutil.Set
|
cnameSet *stringutil.Set
|
||||||
|
|
||||||
// table stores only the unique IP-hostname pairs. It's also sent to the
|
// table stores only the unique IP-hostname pairs. It's also sent to the
|
||||||
|
@ -272,13 +287,16 @@ type hostsParser struct {
|
||||||
table *netutil.IPMap
|
table *netutil.IPMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newHostsParser creates a new *hostsParser with buffers of size taken from the
|
||||||
|
// previous parse.
|
||||||
func (hc *HostsContainer) newHostsParser() (hp *hostsParser) {
|
func (hc *HostsContainer) newHostsParser() (hp *hostsParser) {
|
||||||
|
lastLen := hc.last.Len()
|
||||||
|
|
||||||
return &hostsParser{
|
return &hostsParser{
|
||||||
rulesBuilder: &strings.Builder{},
|
rulesBuilder: &strings.Builder{},
|
||||||
// For A/AAAA and PTRs.
|
rules: make([]ipRules, 0, lastLen),
|
||||||
translations: make(map[string]string, hc.last.Len()*2),
|
|
||||||
cnameSet: stringutil.NewSet(),
|
cnameSet: stringutil.NewSet(),
|
||||||
table: netutil.NewIPMap(hc.last.Len()),
|
table: netutil.NewIPMap(lastLen),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,9 +304,7 @@ func (hc *HostsContainer) newHostsParser() (hp *hostsParser) {
|
||||||
// never signs to stop walking and never returns any additional patterns.
|
// never signs to stop walking and never returns any additional patterns.
|
||||||
//
|
//
|
||||||
// See man hosts(5).
|
// See man hosts(5).
|
||||||
func (hp *hostsParser) parseFile(
|
func (hp *hostsParser) parseFile(r io.Reader) (patterns []string, cont bool, err error) {
|
||||||
r io.Reader,
|
|
||||||
) (patterns []string, cont bool, err error) {
|
|
||||||
s := bufio.NewScanner(r)
|
s := bufio.NewScanner(r)
|
||||||
for s.Scan() {
|
for s.Scan() {
|
||||||
ip, hosts := hp.parseLine(s.Text())
|
ip, hosts := hp.parseLine(s.Text())
|
||||||
|
@ -339,62 +355,79 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
|
||||||
return ip, hosts
|
return ip, hosts
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple types of hosts in hosts database. Zero value isn't used to be able
|
// Hosts is used to contain the main host and all it's aliases.
|
||||||
// quizzaciously emulate nil with 0.
|
type Hosts struct {
|
||||||
const (
|
// Aliases contains all the aliases for Main.
|
||||||
_ = iota
|
Aliases *stringutil.Set
|
||||||
hostAlias
|
// Main is the host itself.
|
||||||
hostMain
|
Main string
|
||||||
)
|
}
|
||||||
|
|
||||||
|
// Equal returns true if h equals hh.
|
||||||
|
func (h *Hosts) Equal(hh *Hosts) (ok bool) {
|
||||||
|
if h == nil || hh == nil {
|
||||||
|
return h == hh
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Main == hh.Main && h.Aliases.Equal(hh.Aliases)
|
||||||
|
}
|
||||||
|
|
||||||
// add tries to add the ip-host pair. It returns:
|
// add tries to add the ip-host pair. It returns:
|
||||||
//
|
//
|
||||||
// hostAlias if the host is not the first one added for the ip.
|
// main host if the host is not the first one added for the ip.
|
||||||
// hostMain if the host is the first one added for the ip.
|
// host itself if the host is the first one added for the ip.
|
||||||
// 0 if the ip-host pair has already been added.
|
// "" if the ip-host pair has already been added.
|
||||||
//
|
//
|
||||||
func (hp *hostsParser) add(ip net.IP, host string) (hostType int) {
|
func (hp *hostsParser) add(ip net.IP, host string) (mainHost string) {
|
||||||
v, ok := hp.table.Get(ip)
|
v, ok := hp.table.Get(ip)
|
||||||
switch hosts, _ := v.(*stringutil.Set); {
|
switch h, _ := v.(*Hosts); {
|
||||||
case ok && hosts.Has(host):
|
case !ok:
|
||||||
return 0
|
// This is the first host for the ip.
|
||||||
case hosts == nil:
|
hp.table.Set(ip, &Hosts{Main: host})
|
||||||
hosts = stringutil.NewSet(host)
|
|
||||||
hp.table.Set(ip, hosts)
|
|
||||||
|
|
||||||
return hostMain
|
return host
|
||||||
|
case h.Main == host:
|
||||||
|
// This is a duplicate. Go on.
|
||||||
|
case h.Aliases == nil:
|
||||||
|
// This is the first alias.
|
||||||
|
h.Aliases = stringutil.NewSet(host)
|
||||||
|
|
||||||
|
return h.Main
|
||||||
|
case !h.Aliases.Has(host):
|
||||||
|
// This is a new alias.
|
||||||
|
h.Aliases.Add(host)
|
||||||
|
|
||||||
|
return h.Main
|
||||||
default:
|
default:
|
||||||
hosts.Add(host)
|
// This is a duplicate. Go on.
|
||||||
|
|
||||||
return hostAlias
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 processed format like:
|
|
||||||
//
|
|
||||||
// ip host1 host2 ...
|
|
||||||
//
|
|
||||||
hostsLine := strings.Join(append([]string{ip.String()}, hosts...), " ")
|
|
||||||
var mainHost string
|
|
||||||
for _, host := range hosts {
|
for _, host := range hosts {
|
||||||
switch hp.add(ip, host) {
|
switch mainHost := hp.add(ip, host); mainHost {
|
||||||
case 0:
|
case "":
|
||||||
|
// This host is a duplicate.
|
||||||
continue
|
continue
|
||||||
case hostMain:
|
case host:
|
||||||
mainHost = host
|
// This host is main.
|
||||||
added, addedPtr := hp.writeMainHostRule(host, ip)
|
added, addedPtr := hp.writeMainRule(host, ip)
|
||||||
hp.translations[added], hp.translations[addedPtr] = hostsLine, hostsLine
|
hp.rules = append(hp.rules, ipRules{
|
||||||
case hostAlias:
|
rule: added,
|
||||||
|
rulePtr: addedPtr,
|
||||||
|
ip: ip,
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
// This host is an alias.
|
||||||
pair := fmt.Sprint(host, " ", mainHost)
|
pair := fmt.Sprint(host, " ", mainHost)
|
||||||
if hp.cnameSet.Has(pair) {
|
if hp.cnameSet.Has(pair) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Since the hostAlias couldn't be returned from add before the
|
hp.writeAliasRule(host, mainHost)
|
||||||
// hostMain the mainHost shouldn't appear empty.
|
|
||||||
hp.writeAliasHostRule(host, mainHost)
|
|
||||||
hp.cnameSet.Add(pair)
|
hp.cnameSet.Add(pair)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,9 +435,9 @@ func (hp *hostsParser) addPairs(ip net.IP, hosts []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeAliasHostRule writes the CNAME rule for the alias-host pair into
|
// writeAliasRule writes the CNAME rule for the alias-host pair into internal
|
||||||
// internal builders.
|
// builders.
|
||||||
func (hp *hostsParser) writeAliasHostRule(alias, host string) {
|
func (hp *hostsParser) writeAliasRule(alias, host string) {
|
||||||
const (
|
const (
|
||||||
nl = "\n"
|
nl = "\n"
|
||||||
sc = ";"
|
sc = ";"
|
||||||
|
@ -417,9 +450,9 @@ func (hp *hostsParser) writeAliasHostRule(alias, host string) {
|
||||||
stringutil.WriteToBuilder(hp.rulesBuilder, rules.MaskPipe, alias, rwSuccess, host, nl)
|
stringutil.WriteToBuilder(hp.rulesBuilder, rules.MaskPipe, alias, rwSuccess, host, nl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeMainHostRule writes the actual rule for the qtype and the PTR for the
|
// writeMainRule writes the actual rule for the qtype and the PTR for the
|
||||||
// host-ip pair into internal builders.
|
// host-ip pair into internal builders.
|
||||||
func (hp *hostsParser) writeMainHostRule(host string, ip net.IP) (added, addedPtr string) {
|
func (hp *hostsParser) writeMainRule(host string, ip net.IP) (added, addedPtr string) {
|
||||||
arpa, err := netutil.IPToReversedAddr(ip)
|
arpa, err := netutil.IPToReversedAddr(ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -484,12 +517,15 @@ func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
hp.table.Range(func(ip net.IP, val interface{}) (cont bool) {
|
hp.table.Range(func(ip net.IP, b interface{}) (cont bool) {
|
||||||
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 main
|
||||||
// have at least one discrepancy.
|
// hosts differ or the maps have at least one discrepancy.
|
||||||
ok = !hasIP || !v.(*stringutil.Set).Equal(val.(*stringutil.Set))
|
if a, hasIP := target.Get(ip); !hasIP {
|
||||||
|
ok = true
|
||||||
|
} else if hosts, aok := a.(*Hosts); aok {
|
||||||
|
ok = !hosts.Equal(b.(*Hosts))
|
||||||
|
}
|
||||||
|
|
||||||
// Continue only if maps has no discrepancies.
|
// Continue only if maps has no discrepancies.
|
||||||
return !ok
|
return !ok
|
||||||
|
@ -527,6 +563,35 @@ func (hp *hostsParser) newStrg(id int) (s *filterlist.RuleStorage, err error) {
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// translations generates the map to translate $dnsrewrite rules to
|
||||||
|
// hosts-syntax ones.
|
||||||
|
func (hp *hostsParser) translations() (trans map[string]string) {
|
||||||
|
l := len(hp.rules)
|
||||||
|
if l == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
trans = make(map[string]string, l*2)
|
||||||
|
for _, r := range hp.rules {
|
||||||
|
v, ok := hp.table.Get(r.ip)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var hosts *Hosts
|
||||||
|
hosts, ok = v.(*Hosts)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
strs := append([]string{r.ip.String(), hosts.Main}, hosts.Aliases.Values()...)
|
||||||
|
hostsLine := strings.Join(strs, " ")
|
||||||
|
trans[r.rule], trans[r.rulePtr] = hostsLine, hostsLine
|
||||||
|
}
|
||||||
|
|
||||||
|
return trans
|
||||||
|
}
|
||||||
|
|
||||||
// refresh gets the data from specified files and propagates the updates if
|
// refresh gets the data from specified files and propagates the updates if
|
||||||
// needed.
|
// needed.
|
||||||
//
|
//
|
||||||
|
@ -540,7 +605,7 @@ func (hc *HostsContainer) refresh() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if hp.equalSet(hc.last) {
|
if hp.equalSet(hc.last) {
|
||||||
log.Debug("%s: no updates detected", hostsContainerPref)
|
log.Debug("%s: no changes detected", hostsContainerPref)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -553,7 +618,7 @@ func (hc *HostsContainer) refresh() (err error) {
|
||||||
return fmt.Errorf("initializing rules storage: %w", err)
|
return fmt.Errorf("initializing rules storage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hc.resetEng(rulesStrg, hp.translations)
|
hc.resetEng(rulesStrg, hp.translations())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/fstest"
|
"testing/fstest"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
|
@ -129,24 +130,13 @@ func TestNewHostsContainer(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHostsContainer_Refresh(t *testing.T) {
|
func TestHostsContainer_refresh(t *testing.T) {
|
||||||
knownIP := net.IP{127, 0, 0, 1}
|
// TODO(e.burkov): Test the case with no actual updates.
|
||||||
|
|
||||||
const knownHost = "localhost"
|
ip := net.IP{127, 0, 0, 1}
|
||||||
const knownAlias = "hocallost"
|
ipStr := ip.String()
|
||||||
|
|
||||||
const dirname = "dir"
|
testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}}
|
||||||
const filename1 = "file1"
|
|
||||||
const filename2 = "file2"
|
|
||||||
|
|
||||||
p1 := path.Join(dirname, filename1)
|
|
||||||
p2 := path.Join(dirname, filename2)
|
|
||||||
|
|
||||||
testFS := fstest.MapFS{
|
|
||||||
p1: &fstest.MapFile{
|
|
||||||
Data: []byte(strings.Join([]string{knownIP.String(), knownHost}, sp) + nl),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// event is a convenient alias for an empty struct{} to emit test events.
|
// event is a convenient alias for an empty struct{} to emit test events.
|
||||||
type event = struct{}
|
type event = struct{}
|
||||||
|
@ -157,119 +147,117 @@ func TestHostsContainer_Refresh(t *testing.T) {
|
||||||
w := &aghtest.FSWatcher{
|
w := &aghtest.FSWatcher{
|
||||||
OnEvents: func() (e <-chan event) { return eventsCh },
|
OnEvents: func() (e <-chan event) { return eventsCh },
|
||||||
OnAdd: func(name string) (err error) {
|
OnAdd: func(name string) (err error) {
|
||||||
assert.Equal(t, dirname, name)
|
assert.Equal(t, "dir", name)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnClose: func() (err error) { panic("not implemented") },
|
OnClose: func() (err error) { panic("not implemented") },
|
||||||
}
|
}
|
||||||
|
|
||||||
hc, err := NewHostsContainer(0, testFS, w, dirname)
|
hc, err := NewHostsContainer(0, testFS, w, "dir")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
checkRefresh := func(t *testing.T, wantHosts *stringutil.Set) {
|
checkRefresh := func(t *testing.T, wantHosts Hosts) {
|
||||||
upd, ok := <-hc.Upd()
|
upd, ok := <-hc.Upd()
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.NotNil(t, upd)
|
require.NotNil(t, upd)
|
||||||
|
|
||||||
assert.Equal(t, 1, upd.Len())
|
assert.Equal(t, 1, upd.Len())
|
||||||
|
|
||||||
v, ok := upd.Get(knownIP)
|
v, ok := upd.Get(ip)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
var hosts *stringutil.Set
|
var hosts *Hosts
|
||||||
hosts, ok = v.(*stringutil.Set)
|
hosts, ok = v.(*Hosts)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
assert.True(t, hosts.Equal(wantHosts))
|
assert.Equal(t, wantHosts.Main, hosts.Main)
|
||||||
|
assert.True(t, hosts.Aliases.Equal(wantHosts.Aliases))
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("initial_refresh", func(t *testing.T) {
|
t.Run("initial_refresh", func(t *testing.T) {
|
||||||
checkRefresh(t, stringutil.NewSet(knownHost))
|
checkRefresh(t, Hosts{Main: "hostname"})
|
||||||
})
|
})
|
||||||
|
|
||||||
testFS[p2] = &fstest.MapFile{
|
|
||||||
Data: []byte(strings.Join([]string{knownIP.String(), knownAlias}, sp) + nl),
|
|
||||||
}
|
|
||||||
eventsCh <- event{}
|
|
||||||
|
|
||||||
t.Run("second_refresh", func(t *testing.T) {
|
t.Run("second_refresh", func(t *testing.T) {
|
||||||
checkRefresh(t, stringutil.NewSet(knownHost, knownAlias))
|
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + ` alias` + nl)}
|
||||||
|
eventsCh <- event{}
|
||||||
|
checkRefresh(t, Hosts{Main: "hostname", Aliases: stringutil.NewSet("alias")})
|
||||||
})
|
})
|
||||||
|
|
||||||
eventsCh <- event{}
|
t.Run("double_refresh", func(t *testing.T) {
|
||||||
|
// Make a change once.
|
||||||
|
testFS["dir/file1"] = &fstest.MapFile{Data: []byte(ipStr + ` alias` + nl)}
|
||||||
|
eventsCh <- event{}
|
||||||
|
|
||||||
t.Run("no_changes_refresh", func(t *testing.T) {
|
// Require the changes are written.
|
||||||
assert.Empty(t, hc.Upd())
|
require.Eventually(t, func() bool {
|
||||||
|
res, ok := hc.MatchRequest(urlfilter.DNSRequest{
|
||||||
|
Hostname: "hostname",
|
||||||
|
DNSType: dns.TypeA,
|
||||||
|
})
|
||||||
|
|
||||||
|
return !ok && res.DNSRewrites() == nil
|
||||||
|
}, 5*time.Second, time.Second/2)
|
||||||
|
|
||||||
|
// Make a change again.
|
||||||
|
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}
|
||||||
|
eventsCh <- event{}
|
||||||
|
|
||||||
|
// Require the changes are written.
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
res, ok := hc.MatchRequest(urlfilter.DNSRequest{
|
||||||
|
Hostname: "hostname",
|
||||||
|
DNSType: dns.TypeA,
|
||||||
|
})
|
||||||
|
|
||||||
|
return !ok && res.DNSRewrites() != nil
|
||||||
|
}, 5*time.Second, time.Second/2)
|
||||||
|
|
||||||
|
assert.Len(t, hc.Upd(), 1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHostsContainer_PathsToPatterns(t *testing.T) {
|
func TestHostsContainer_PathsToPatterns(t *testing.T) {
|
||||||
const (
|
|
||||||
dir0 = "dir"
|
|
||||||
dir1 = "dir_1"
|
|
||||||
fn1 = "file_1"
|
|
||||||
fn2 = "file_2"
|
|
||||||
fn3 = "file_3"
|
|
||||||
fn4 = "file_4"
|
|
||||||
)
|
|
||||||
|
|
||||||
fp1 := path.Join(dir0, fn1)
|
|
||||||
fp2 := path.Join(dir0, fn2)
|
|
||||||
fp3 := path.Join(dir0, dir1, fn3)
|
|
||||||
|
|
||||||
gsfs := fstest.MapFS{
|
gsfs := fstest.MapFS{
|
||||||
fp1: &fstest.MapFile{Data: []byte{1}},
|
"dir_0/file_1": &fstest.MapFile{Data: []byte{1}},
|
||||||
fp2: &fstest.MapFile{Data: []byte{2}},
|
"dir_0/file_2": &fstest.MapFile{Data: []byte{2}},
|
||||||
fp3: &fstest.MapFile{Data: []byte{3}},
|
"dir_0/dir_1/file_3": &fstest.MapFile{Data: []byte{3}},
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
wantErr error
|
paths []string
|
||||||
want []string
|
want []string
|
||||||
paths []string
|
|
||||||
}{{
|
}{{
|
||||||
name: "no_paths",
|
name: "no_paths",
|
||||||
wantErr: nil,
|
paths: nil,
|
||||||
want: nil,
|
want: nil,
|
||||||
paths: nil,
|
|
||||||
}, {
|
}, {
|
||||||
name: "single_file",
|
name: "single_file",
|
||||||
wantErr: nil,
|
paths: []string{"dir_0/file_1"},
|
||||||
want: []string{fp1},
|
want: []string{"dir_0/file_1"},
|
||||||
paths: []string{fp1},
|
|
||||||
}, {
|
}, {
|
||||||
name: "several_files",
|
name: "several_files",
|
||||||
wantErr: nil,
|
paths: []string{"dir_0/file_1", "dir_0/file_2"},
|
||||||
want: []string{fp1, fp2},
|
want: []string{"dir_0/file_1", "dir_0/file_2"},
|
||||||
paths: []string{fp1, fp2},
|
|
||||||
}, {
|
}, {
|
||||||
name: "whole_dir",
|
name: "whole_dir",
|
||||||
wantErr: nil,
|
paths: []string{"dir_0"},
|
||||||
want: []string{path.Join(dir0, "*")},
|
want: []string{"dir_0/*"},
|
||||||
paths: []string{dir0},
|
|
||||||
}, {
|
}, {
|
||||||
name: "file_and_dir",
|
name: "file_and_dir",
|
||||||
wantErr: nil,
|
paths: []string{"dir_0/file_1", "dir_0/dir_1"},
|
||||||
want: []string{fp1, path.Join(dir0, dir1, "*")},
|
want: []string{"dir_0/file_1", "dir_0/dir_1/*"},
|
||||||
paths: []string{fp1, path.Join(dir0, dir1)},
|
|
||||||
}, {
|
}, {
|
||||||
name: "non-existing",
|
name: "non-existing",
|
||||||
wantErr: nil,
|
paths: []string{path.Join("dir_0", "file_3")},
|
||||||
want: nil,
|
want: nil,
|
||||||
paths: []string{path.Join(dir0, "file_3")},
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
patterns, err := pathsToPatterns(gsfs, tc.paths)
|
patterns, err := pathsToPatterns(gsfs, tc.paths)
|
||||||
if tc.wantErr != nil {
|
|
||||||
assert.ErrorIs(t, err, tc.wantErr)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, tc.want, patterns)
|
assert.Equal(t, tc.want, patterns)
|
||||||
|
@ -290,16 +278,74 @@ func TestHostsContainer_PathsToPatterns(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHostsContainer_Translate(t *testing.T) {
|
||||||
|
testdata := os.DirFS("./testdata")
|
||||||
|
stubWatcher := aghtest.FSWatcher{
|
||||||
|
OnEvents: func() (e <-chan struct{}) { return nil },
|
||||||
|
OnAdd: func(name string) (err error) { return nil },
|
||||||
|
OnClose: func() (err error) { panic("not implemented") },
|
||||||
|
}
|
||||||
|
|
||||||
|
hc, err := NewHostsContainer(0, testdata, &stubWatcher, "etc_hosts")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
rule string
|
||||||
|
wantTrans []string
|
||||||
|
}{{
|
||||||
|
name: "simplehost",
|
||||||
|
rule: "|simplehost^$dnsrewrite=NOERROR;A;1.0.0.1",
|
||||||
|
wantTrans: []string{"1.0.0.1", "simplehost"},
|
||||||
|
}, {
|
||||||
|
name: "hello",
|
||||||
|
rule: "|hello^$dnsrewrite=NOERROR;A;1.0.0.0",
|
||||||
|
wantTrans: []string{"1.0.0.0", "hello", "hello.world", "hello.world.again"},
|
||||||
|
}, {
|
||||||
|
name: "simplehost_v6",
|
||||||
|
rule: "|simplehost^$dnsrewrite=NOERROR;AAAA;::1",
|
||||||
|
wantTrans: []string{"::1", "simplehost"},
|
||||||
|
}, {
|
||||||
|
name: "hello_v6",
|
||||||
|
rule: "|hello^$dnsrewrite=NOERROR;AAAA;::",
|
||||||
|
wantTrans: []string{"::", "hello", "hello.world", "hello.world.again"},
|
||||||
|
}, {
|
||||||
|
name: "simplehost_ptr",
|
||||||
|
rule: "|1.0.0.1.in-addr.arpa^$dnsrewrite=NOERROR;PTR;simplehost.",
|
||||||
|
wantTrans: []string{"1.0.0.1", "simplehost"},
|
||||||
|
}, {
|
||||||
|
name: "hello_ptr",
|
||||||
|
rule: "|0.0.0.1.in-addr.arpa^$dnsrewrite=NOERROR;PTR;hello.",
|
||||||
|
wantTrans: []string{"1.0.0.0", "hello", "hello.world", "hello.world.again"},
|
||||||
|
}, {
|
||||||
|
name: "simplehost_ptr_v6",
|
||||||
|
rule: "|1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" +
|
||||||
|
"^$dnsrewrite=NOERROR;PTR;simplehost.",
|
||||||
|
wantTrans: []string{"::1", "simplehost"},
|
||||||
|
}, {
|
||||||
|
name: "hello_ptr_v6",
|
||||||
|
rule: "|0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" +
|
||||||
|
"^$dnsrewrite=NOERROR;PTR;hello.",
|
||||||
|
wantTrans: []string{"::", "hello", "hello.world", "hello.world.again"},
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
want := stringutil.NewSet(tc.wantTrans...)
|
||||||
|
got := stringutil.NewSet(strings.Fields(hc.Translate(tc.rule))...)
|
||||||
|
assert.True(t, want.Equal(got))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHostsContainer(t *testing.T) {
|
func TestHostsContainer(t *testing.T) {
|
||||||
const listID = 1234
|
const listID = 1234
|
||||||
|
|
||||||
testdata := os.DirFS("./testdata")
|
testdata := os.DirFS("./testdata")
|
||||||
|
|
||||||
nRewrites := func(t *testing.T, res *urlfilter.DNSResult, n int) (rws []*rules.DNSRewrite) {
|
nRewrites := func(t *testing.T, res *urlfilter.DNSResult, n int) (rws []*rules.DNSRewrite) {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
rewrites := res.DNSRewrites()
|
rewrites := res.DNSRewrites()
|
||||||
assert.Len(t, rewrites, n)
|
require.Len(t, rewrites, n)
|
||||||
|
|
||||||
for _, rewrite := range rewrites {
|
for _, rewrite := range rewrites {
|
||||||
require.Equal(t, listID, rewrite.FilterListID)
|
require.Equal(t, listID, rewrite.FilterListID)
|
||||||
|
@ -346,6 +392,15 @@ func TestHostsContainer(t *testing.T) {
|
||||||
testTail: func(t *testing.T, res *urlfilter.DNSResult) {
|
testTail: func(t *testing.T, res *urlfilter.DNSResult) {
|
||||||
assert.Equal(t, "hello", nRewrites(t, res, 1)[0].NewCNAME)
|
assert.Equal(t, "hello", nRewrites(t, res, 1)[0].NewCNAME)
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
name: "other_line_alias",
|
||||||
|
req: urlfilter.DNSRequest{
|
||||||
|
Hostname: "hello.world.again",
|
||||||
|
DNSType: dns.TypeA,
|
||||||
|
},
|
||||||
|
testTail: func(t *testing.T, res *urlfilter.DNSResult) {
|
||||||
|
assert.Equal(t, "hello", nRewrites(t, res, 1)[0].NewCNAME)
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
name: "hello_subdomain",
|
name: "hello_subdomain",
|
||||||
req: urlfilter.DNSRequest{
|
req: urlfilter.DNSRequest{
|
||||||
|
@ -419,12 +474,8 @@ func TestHostsContainer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUniqueRules_ParseLine(t *testing.T) {
|
func TestUniqueRules_ParseLine(t *testing.T) {
|
||||||
const (
|
ip := net.IP{127, 0, 0, 1}
|
||||||
hostname = "localhost"
|
ipStr := ip.String()
|
||||||
alias = "hocallost"
|
|
||||||
)
|
|
||||||
|
|
||||||
knownIP := net.IP{127, 0, 0, 1}
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -433,39 +484,39 @@ func TestUniqueRules_ParseLine(t *testing.T) {
|
||||||
wantHosts []string
|
wantHosts []string
|
||||||
}{{
|
}{{
|
||||||
name: "simple",
|
name: "simple",
|
||||||
line: strings.Join([]string{knownIP.String(), hostname}, sp),
|
line: ipStr + ` hostname`,
|
||||||
wantIP: knownIP,
|
wantIP: ip,
|
||||||
wantHosts: []string{"localhost"},
|
wantHosts: []string{"hostname"},
|
||||||
}, {
|
}, {
|
||||||
name: "aliases",
|
name: "aliases",
|
||||||
line: strings.Join([]string{knownIP.String(), hostname, alias}, sp),
|
line: ipStr + ` hostname alias`,
|
||||||
wantIP: knownIP,
|
wantIP: ip,
|
||||||
wantHosts: []string{"localhost", "hocallost"},
|
wantHosts: []string{"hostname", "alias"},
|
||||||
}, {
|
}, {
|
||||||
name: "invalid_line",
|
name: "invalid_line",
|
||||||
line: knownIP.String(),
|
line: ipStr,
|
||||||
wantIP: nil,
|
wantIP: nil,
|
||||||
wantHosts: nil,
|
wantHosts: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "invalid_line_hostname",
|
name: "invalid_line_hostname",
|
||||||
line: strings.Join([]string{knownIP.String(), "#" + hostname}, sp),
|
line: ipStr + ` # hostname`,
|
||||||
wantIP: knownIP,
|
wantIP: ip,
|
||||||
wantHosts: nil,
|
wantHosts: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "commented_aliases",
|
name: "commented_aliases",
|
||||||
line: strings.Join([]string{knownIP.String(), hostname, "#" + alias}, sp),
|
line: ipStr + ` hostname # alias`,
|
||||||
wantIP: knownIP,
|
wantIP: ip,
|
||||||
wantHosts: []string{"localhost"},
|
wantHosts: []string{"hostname"},
|
||||||
}, {
|
}, {
|
||||||
name: "whole_comment",
|
name: "whole_comment",
|
||||||
line: strings.Join([]string{"#", knownIP.String(), hostname}, sp),
|
line: `# ` + ipStr + ` hostname`,
|
||||||
wantIP: nil,
|
wantIP: nil,
|
||||||
wantHosts: nil,
|
wantHosts: nil,
|
||||||
}, {
|
}, {
|
||||||
name: "partial_comment",
|
name: "partial_comment",
|
||||||
line: strings.Join([]string{knownIP.String(), hostname[:4] + "#" + hostname[4:]}, sp),
|
line: ipStr + ` host#name`,
|
||||||
wantIP: knownIP,
|
wantIP: ip,
|
||||||
wantHosts: []string{hostname[:4]},
|
wantHosts: []string{"host"},
|
||||||
}, {
|
}, {
|
||||||
name: "empty",
|
name: "empty",
|
||||||
line: ``,
|
line: ``,
|
||||||
|
@ -476,8 +527,8 @@ func TestUniqueRules_ParseLine(t *testing.T) {
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
hp := hostsParser{}
|
hp := hostsParser{}
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
ip, hosts := hp.parseLine(tc.line)
|
got, hosts := hp.parseLine(tc.line)
|
||||||
assert.True(t, tc.wantIP.Equal(ip))
|
assert.True(t, tc.wantIP.Equal(got))
|
||||||
assert.Equal(t, tc.wantHosts, hosts)
|
assert.Equal(t, tc.wantHosts, hosts)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,19 @@
|
||||||
1.0.0.3 *
|
1.0.0.3 *
|
||||||
1.0.0.4 *.com
|
1.0.0.4 *.com
|
||||||
|
|
||||||
|
# See https://github.com/AdguardTeam/AdGuardHome/issues/4079.
|
||||||
|
1.0.0.0 hello.world.again
|
||||||
|
|
||||||
|
# Duplicates of a main host and an alias.
|
||||||
|
1.0.0.1 simplehost
|
||||||
|
1.0.0.0 hello.world
|
||||||
|
|
||||||
# Same for IPv6.
|
# Same for IPv6.
|
||||||
::1 simplehost
|
::1 simplehost
|
||||||
:: hello hello.world
|
:: hello hello.world
|
||||||
::2 a.whole lot.of aliases for.testing
|
::2 a.whole lot.of aliases for.testing
|
||||||
::3 *
|
::3 *
|
||||||
::4 *.com
|
::4 *.com
|
||||||
|
:: hello.world.again
|
||||||
|
::1 simplehost
|
||||||
|
:: hello.world
|
|
@ -783,12 +783,17 @@ func (clients *clientsContainer) addFromHostsFile(hosts *netutil.IPMap) {
|
||||||
|
|
||||||
n := 0
|
n := 0
|
||||||
hosts.Range(func(ip net.IP, v interface{}) (cont bool) {
|
hosts.Range(func(ip net.IP, v interface{}) (cont bool) {
|
||||||
names, ok := v.(*stringutil.Set)
|
hosts, ok := v.(*aghnet.Hosts)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error("dns: bad type %T in ipToRC for %s", v, ip)
|
log.Error("dns: bad type %T in ipToRC for %s", v, ip)
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
names.Range(func(name string) (cont bool) {
|
if clients.addHostLocked(ip, hosts.Main, ClientSourceHostsFile) {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
hosts.Aliases.Range(func(name string) (cont bool) {
|
||||||
if clients.addHostLocked(ip, name, ClientSourceHostsFile) {
|
if clients.addHostLocked(ip, name, ClientSourceHostsFile) {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue