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.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Omitted aliases of hosts specified by another line within the OS's hosts file
|
||||
([#4079]).
|
||||
|
||||
### Removed
|
||||
|
||||
- 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
|
||||
[#4016]: https://github.com/AdguardTeam/AdGuardHome/issues/4016
|
||||
[#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
|
||||
// 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) {
|
||||
rm.stateLock.RLock()
|
||||
defer rm.stateLock.RUnlock()
|
||||
|
@ -179,7 +183,7 @@ func NewHostsContainer(
|
|||
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
|
||||
// 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) {
|
||||
return hc.updates
|
||||
}
|
||||
|
@ -228,8 +232,9 @@ func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error)
|
|||
return patterns, nil
|
||||
}
|
||||
|
||||
// handleEvents concurrently handles the events. It closes the update channel
|
||||
// of HostsContainer when finishes. Used to be called within a goroutine.
|
||||
// handleEvents concurrently handles the file system events. It closes the
|
||||
// update channel of HostsContainer when finishes. It's used to be called
|
||||
// within a separate goroutine.
|
||||
func (hc *HostsContainer) handleEvents() {
|
||||
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
|
||||
// file. It exists for only a single refreshing session.
|
||||
type hostsParser struct {
|
||||
// rulesBuilder builds the resulting rulesBuilder list content.
|
||||
// rulesBuilder builds the resulting rules list content.
|
||||
rulesBuilder *strings.Builder
|
||||
|
||||
// translations maps generated $dnsrewrite rules to the hosts-translations
|
||||
// rules.
|
||||
translations map[string]string
|
||||
// rules stores the rules for main hosts to generate translations.
|
||||
rules []ipRules
|
||||
|
||||
// cnameSet prevents duplicating cname rules.
|
||||
// cnameSet prevents duplicating cname rules, e.g. same hostname for
|
||||
// different IP versions.
|
||||
cnameSet *stringutil.Set
|
||||
|
||||
// table stores only the unique IP-hostname pairs. It's also sent to the
|
||||
|
@ -272,13 +287,16 @@ type hostsParser struct {
|
|||
table *netutil.IPMap
|
||||
}
|
||||
|
||||
// newHostsParser creates a new *hostsParser with buffers of size taken from the
|
||||
// previous parse.
|
||||
func (hc *HostsContainer) newHostsParser() (hp *hostsParser) {
|
||||
lastLen := hc.last.Len()
|
||||
|
||||
return &hostsParser{
|
||||
rulesBuilder: &strings.Builder{},
|
||||
// For A/AAAA and PTRs.
|
||||
translations: make(map[string]string, hc.last.Len()*2),
|
||||
rules: make([]ipRules, 0, lastLen),
|
||||
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.
|
||||
//
|
||||
// See man hosts(5).
|
||||
func (hp *hostsParser) parseFile(
|
||||
r io.Reader,
|
||||
) (patterns []string, cont bool, err error) {
|
||||
func (hp *hostsParser) parseFile(r io.Reader) (patterns []string, cont bool, err error) {
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
ip, hosts := hp.parseLine(s.Text())
|
||||
|
@ -339,62 +355,79 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
|
|||
return ip, hosts
|
||||
}
|
||||
|
||||
// Simple types of hosts in hosts database. Zero value isn't used to be able
|
||||
// quizzaciously emulate nil with 0.
|
||||
const (
|
||||
_ = iota
|
||||
hostAlias
|
||||
hostMain
|
||||
)
|
||||
// Hosts is used to contain the main host and all it's aliases.
|
||||
type Hosts struct {
|
||||
// Aliases contains all the aliases for Main.
|
||||
Aliases *stringutil.Set
|
||||
// Main is the host itself.
|
||||
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:
|
||||
//
|
||||
// hostAlias if the host is not the first one added for the ip.
|
||||
// hostMain if the host is the first one added for the ip.
|
||||
// 0 if the ip-host pair has already been added.
|
||||
// main host if the host is not the first one added for the ip.
|
||||
// host itself if the host is the first one added for the ip.
|
||||
// "" 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)
|
||||
switch hosts, _ := v.(*stringutil.Set); {
|
||||
case ok && hosts.Has(host):
|
||||
return 0
|
||||
case hosts == nil:
|
||||
hosts = stringutil.NewSet(host)
|
||||
hp.table.Set(ip, hosts)
|
||||
switch h, _ := v.(*Hosts); {
|
||||
case !ok:
|
||||
// This is the first host for the ip.
|
||||
hp.table.Set(ip, &Hosts{Main: host})
|
||||
|
||||
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:
|
||||
hosts.Add(host)
|
||||
|
||||
return hostAlias
|
||||
// This is a duplicate. Go on.
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// 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.
|
||||
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 {
|
||||
switch hp.add(ip, host) {
|
||||
case 0:
|
||||
switch mainHost := hp.add(ip, host); mainHost {
|
||||
case "":
|
||||
// This host is a duplicate.
|
||||
continue
|
||||
case hostMain:
|
||||
mainHost = host
|
||||
added, addedPtr := hp.writeMainHostRule(host, ip)
|
||||
hp.translations[added], hp.translations[addedPtr] = hostsLine, hostsLine
|
||||
case hostAlias:
|
||||
case host:
|
||||
// This host is main.
|
||||
added, addedPtr := hp.writeMainRule(host, ip)
|
||||
hp.rules = append(hp.rules, ipRules{
|
||||
rule: added,
|
||||
rulePtr: addedPtr,
|
||||
ip: ip,
|
||||
})
|
||||
default:
|
||||
// This host is an alias.
|
||||
pair := fmt.Sprint(host, " ", mainHost)
|
||||
if hp.cnameSet.Has(pair) {
|
||||
continue
|
||||
}
|
||||
// Since the hostAlias couldn't be returned from add before the
|
||||
// hostMain the mainHost shouldn't appear empty.
|
||||
hp.writeAliasHostRule(host, mainHost)
|
||||
hp.writeAliasRule(host, mainHost)
|
||||
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
|
||||
// internal builders.
|
||||
func (hp *hostsParser) writeAliasHostRule(alias, host string) {
|
||||
// writeAliasRule writes the CNAME rule for the alias-host pair into internal
|
||||
// builders.
|
||||
func (hp *hostsParser) writeAliasRule(alias, host string) {
|
||||
const (
|
||||
nl = "\n"
|
||||
sc = ";"
|
||||
|
@ -417,9 +450,9 @@ func (hp *hostsParser) writeAliasHostRule(alias, host string) {
|
|||
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.
|
||||
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)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -484,12 +517,15 @@ func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
|
|||
return false
|
||||
}
|
||||
|
||||
hp.table.Range(func(ip net.IP, val interface{}) (cont bool) {
|
||||
v, hasIP := target.Get(ip)
|
||||
hp.table.Range(func(ip net.IP, b interface{}) (cont bool) {
|
||||
// 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
|
||||
// have at least one discrepancy.
|
||||
ok = !hasIP || !v.(*stringutil.Set).Equal(val.(*stringutil.Set))
|
||||
// appropriate hosts set isn't equal to the checked one, i.e. the main
|
||||
// hosts differ or the maps have at least one discrepancy.
|
||||
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.
|
||||
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
|
||||
// needed.
|
||||
//
|
||||
|
@ -540,7 +605,7 @@ func (hc *HostsContainer) refresh() (err error) {
|
|||
}
|
||||
|
||||
if hp.equalSet(hc.last) {
|
||||
log.Debug("%s: no updates detected", hostsContainerPref)
|
||||
log.Debug("%s: no changes detected", hostsContainerPref)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -553,7 +618,7 @@ func (hc *HostsContainer) refresh() (err error) {
|
|||
return fmt.Errorf("initializing rules storage: %w", err)
|
||||
}
|
||||
|
||||
hc.resetEng(rulesStrg, hp.translations)
|
||||
hc.resetEng(rulesStrg, hp.translations())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"sync/atomic"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
|
@ -129,24 +130,13 @@ func TestNewHostsContainer(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestHostsContainer_Refresh(t *testing.T) {
|
||||
knownIP := net.IP{127, 0, 0, 1}
|
||||
func TestHostsContainer_refresh(t *testing.T) {
|
||||
// TODO(e.burkov): Test the case with no actual updates.
|
||||
|
||||
const knownHost = "localhost"
|
||||
const knownAlias = "hocallost"
|
||||
ip := net.IP{127, 0, 0, 1}
|
||||
ipStr := ip.String()
|
||||
|
||||
const dirname = "dir"
|
||||
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),
|
||||
},
|
||||
}
|
||||
testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}}
|
||||
|
||||
// event is a convenient alias for an empty struct{} to emit test events.
|
||||
type event = struct{}
|
||||
|
@ -157,119 +147,117 @@ func TestHostsContainer_Refresh(t *testing.T) {
|
|||
w := &aghtest.FSWatcher{
|
||||
OnEvents: func() (e <-chan event) { return eventsCh },
|
||||
OnAdd: func(name string) (err error) {
|
||||
assert.Equal(t, dirname, name)
|
||||
assert.Equal(t, "dir", name)
|
||||
|
||||
return nil
|
||||
},
|
||||
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)
|
||||
|
||||
checkRefresh := func(t *testing.T, wantHosts *stringutil.Set) {
|
||||
checkRefresh := func(t *testing.T, wantHosts Hosts) {
|
||||
upd, ok := <-hc.Upd()
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, upd)
|
||||
|
||||
assert.Equal(t, 1, upd.Len())
|
||||
|
||||
v, ok := upd.Get(knownIP)
|
||||
v, ok := upd.Get(ip)
|
||||
require.True(t, ok)
|
||||
|
||||
var hosts *stringutil.Set
|
||||
hosts, ok = v.(*stringutil.Set)
|
||||
var hosts *Hosts
|
||||
hosts, ok = v.(*Hosts)
|
||||
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) {
|
||||
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) {
|
||||
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")})
|
||||
})
|
||||
|
||||
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) {
|
||||
assert.Empty(t, hc.Upd())
|
||||
// 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)
|
||||
|
||||
// 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) {
|
||||
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{
|
||||
fp1: &fstest.MapFile{Data: []byte{1}},
|
||||
fp2: &fstest.MapFile{Data: []byte{2}},
|
||||
fp3: &fstest.MapFile{Data: []byte{3}},
|
||||
"dir_0/file_1": &fstest.MapFile{Data: []byte{1}},
|
||||
"dir_0/file_2": &fstest.MapFile{Data: []byte{2}},
|
||||
"dir_0/dir_1/file_3": &fstest.MapFile{Data: []byte{3}},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
wantErr error
|
||||
want []string
|
||||
paths []string
|
||||
want []string
|
||||
}{{
|
||||
name: "no_paths",
|
||||
wantErr: nil,
|
||||
want: nil,
|
||||
paths: nil,
|
||||
want: nil,
|
||||
}, {
|
||||
name: "single_file",
|
||||
wantErr: nil,
|
||||
want: []string{fp1},
|
||||
paths: []string{fp1},
|
||||
paths: []string{"dir_0/file_1"},
|
||||
want: []string{"dir_0/file_1"},
|
||||
}, {
|
||||
name: "several_files",
|
||||
wantErr: nil,
|
||||
want: []string{fp1, fp2},
|
||||
paths: []string{fp1, fp2},
|
||||
paths: []string{"dir_0/file_1", "dir_0/file_2"},
|
||||
want: []string{"dir_0/file_1", "dir_0/file_2"},
|
||||
}, {
|
||||
name: "whole_dir",
|
||||
wantErr: nil,
|
||||
want: []string{path.Join(dir0, "*")},
|
||||
paths: []string{dir0},
|
||||
paths: []string{"dir_0"},
|
||||
want: []string{"dir_0/*"},
|
||||
}, {
|
||||
name: "file_and_dir",
|
||||
wantErr: nil,
|
||||
want: []string{fp1, path.Join(dir0, dir1, "*")},
|
||||
paths: []string{fp1, path.Join(dir0, dir1)},
|
||||
paths: []string{"dir_0/file_1", "dir_0/dir_1"},
|
||||
want: []string{"dir_0/file_1", "dir_0/dir_1/*"},
|
||||
}, {
|
||||
name: "non-existing",
|
||||
wantErr: nil,
|
||||
paths: []string{path.Join("dir_0", "file_3")},
|
||||
want: nil,
|
||||
paths: []string{path.Join(dir0, "file_3")},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
patterns, err := pathsToPatterns(gsfs, tc.paths)
|
||||
if tc.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tc.wantErr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
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) {
|
||||
const listID = 1234
|
||||
|
||||
testdata := os.DirFS("./testdata")
|
||||
|
||||
nRewrites := func(t *testing.T, res *urlfilter.DNSResult, n int) (rws []*rules.DNSRewrite) {
|
||||
t.Helper()
|
||||
|
||||
rewrites := res.DNSRewrites()
|
||||
assert.Len(t, rewrites, n)
|
||||
require.Len(t, rewrites, n)
|
||||
|
||||
for _, rewrite := range rewrites {
|
||||
require.Equal(t, listID, rewrite.FilterListID)
|
||||
|
@ -346,6 +392,15 @@ func TestHostsContainer(t *testing.T) {
|
|||
testTail: func(t *testing.T, res *urlfilter.DNSResult) {
|
||||
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",
|
||||
req: urlfilter.DNSRequest{
|
||||
|
@ -419,12 +474,8 @@ func TestHostsContainer(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUniqueRules_ParseLine(t *testing.T) {
|
||||
const (
|
||||
hostname = "localhost"
|
||||
alias = "hocallost"
|
||||
)
|
||||
|
||||
knownIP := net.IP{127, 0, 0, 1}
|
||||
ip := net.IP{127, 0, 0, 1}
|
||||
ipStr := ip.String()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
@ -433,39 +484,39 @@ func TestUniqueRules_ParseLine(t *testing.T) {
|
|||
wantHosts []string
|
||||
}{{
|
||||
name: "simple",
|
||||
line: strings.Join([]string{knownIP.String(), hostname}, sp),
|
||||
wantIP: knownIP,
|
||||
wantHosts: []string{"localhost"},
|
||||
line: ipStr + ` hostname`,
|
||||
wantIP: ip,
|
||||
wantHosts: []string{"hostname"},
|
||||
}, {
|
||||
name: "aliases",
|
||||
line: strings.Join([]string{knownIP.String(), hostname, alias}, sp),
|
||||
wantIP: knownIP,
|
||||
wantHosts: []string{"localhost", "hocallost"},
|
||||
line: ipStr + ` hostname alias`,
|
||||
wantIP: ip,
|
||||
wantHosts: []string{"hostname", "alias"},
|
||||
}, {
|
||||
name: "invalid_line",
|
||||
line: knownIP.String(),
|
||||
line: ipStr,
|
||||
wantIP: nil,
|
||||
wantHosts: nil,
|
||||
}, {
|
||||
name: "invalid_line_hostname",
|
||||
line: strings.Join([]string{knownIP.String(), "#" + hostname}, sp),
|
||||
wantIP: knownIP,
|
||||
line: ipStr + ` # hostname`,
|
||||
wantIP: ip,
|
||||
wantHosts: nil,
|
||||
}, {
|
||||
name: "commented_aliases",
|
||||
line: strings.Join([]string{knownIP.String(), hostname, "#" + alias}, sp),
|
||||
wantIP: knownIP,
|
||||
wantHosts: []string{"localhost"},
|
||||
line: ipStr + ` hostname # alias`,
|
||||
wantIP: ip,
|
||||
wantHosts: []string{"hostname"},
|
||||
}, {
|
||||
name: "whole_comment",
|
||||
line: strings.Join([]string{"#", knownIP.String(), hostname}, sp),
|
||||
line: `# ` + ipStr + ` hostname`,
|
||||
wantIP: nil,
|
||||
wantHosts: nil,
|
||||
}, {
|
||||
name: "partial_comment",
|
||||
line: strings.Join([]string{knownIP.String(), hostname[:4] + "#" + hostname[4:]}, sp),
|
||||
wantIP: knownIP,
|
||||
wantHosts: []string{hostname[:4]},
|
||||
line: ipStr + ` host#name`,
|
||||
wantIP: ip,
|
||||
wantHosts: []string{"host"},
|
||||
}, {
|
||||
name: "empty",
|
||||
line: ``,
|
||||
|
@ -476,8 +527,8 @@ func TestUniqueRules_ParseLine(t *testing.T) {
|
|||
for _, tc := range testCases {
|
||||
hp := hostsParser{}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ip, hosts := hp.parseLine(tc.line)
|
||||
assert.True(t, tc.wantIP.Equal(ip))
|
||||
got, hosts := hp.parseLine(tc.line)
|
||||
assert.True(t, tc.wantIP.Equal(got))
|
||||
assert.Equal(t, tc.wantHosts, hosts)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -12,9 +12,19 @@
|
|||
1.0.0.3 *
|
||||
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.
|
||||
::1 simplehost
|
||||
:: hello hello.world
|
||||
::2 a.whole lot.of aliases for.testing
|
||||
::3 *
|
||||
::4 *.com
|
||||
:: hello.world.again
|
||||
::1 simplehost
|
||||
:: hello.world
|
|
@ -783,12 +783,17 @@ func (clients *clientsContainer) addFromHostsFile(hosts *netutil.IPMap) {
|
|||
|
||||
n := 0
|
||||
hosts.Range(func(ip net.IP, v interface{}) (cont bool) {
|
||||
names, ok := v.(*stringutil.Set)
|
||||
hosts, ok := v.(*aghnet.Hosts)
|
||||
if !ok {
|
||||
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) {
|
||||
n++
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue