Skip to content

Commit

Permalink
Pull request: 4216 simpl hosts
Browse files Browse the repository at this point in the history
Merge in DNS/adguard-home from 4216-hosts-explode to master

Updates #4216.

Squashed commit of the following:

commit a6ed131
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 27 19:11:23 2022 +0300

    aghnet: imp docs

commit 25cca06
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Jan 27 18:53:16 2022 +0300

    aghnet: simpl hosts container
  • Loading branch information
EugeneOne1 committed Jan 27, 2022
1 parent 642d68c commit f9aa5ae
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 203 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -56,6 +56,11 @@ In this release, the schema version has changed from 12 to 13.
-->
- Go 1.17 support. v0.109.0 will require at least Go 1.18 to build.

## Fixed

- Unnecessarily complex hosts-related logic leading to infinite recursion in
some cases ([#4216]).

### Removed

- Go 1.16 support.
Expand Down
206 changes: 43 additions & 163 deletions internal/aghnet/hostscontainer.go
Expand Up @@ -46,13 +46,8 @@ type requestMatcher struct {
}

// MatchRequest processes the request rewriting hostnames and addresses read
// from the operating system's hosts files.
//
// res is nil for any request having not an A/AAAA or PTR type. Results
// containing CNAME information may be queried again with the same question type
// and the returned CNAME for Host field of request. Results are guaranteed to
// be direct, i.e. any returned CNAME resolves into actual address like an alias
// in hosts does, see man hosts (5).
// from the operating system's hosts files. res is nil for any request having
// not an A/AAAA or PTR type, see man 5 hosts.
//
// It's safe for concurrent use.
func (rm *requestMatcher) MatchRequest(
Expand Down Expand Up @@ -203,7 +198,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 *aghnet.Hosts.
// map's values are guaranteed to be of type of *stringutil.Set.
func (hc *HostsContainer) Upd() (updates <-chan *netutil.IPMap) {
return hc.updates
}
Expand Down Expand Up @@ -259,28 +254,14 @@ 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 rules list content.
rulesBuilder *strings.Builder

// rules stores the rules for main hosts to generate translations.
rules []ipRules

// cnameSet prevents duplicating cname rules, e.g. same hostname for
// different IP versions.
cnameSet *stringutil.Set
// translations maps generated rules into actual hosts file lines.
translations map[string]string

// table stores only the unique IP-hostname pairs. It's also sent to the
// updates channel afterwards.
Expand All @@ -290,13 +271,10 @@ type hostsParser struct {
// 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{},
rules: make([]ipRules, 0, lastLen),
cnameSet: stringutil.NewSet(),
table: netutil.NewIPMap(lastLen),
translations: map[string]string{},
table: netutil.NewIPMap(hc.last.Len()),
}
}

Expand Down Expand Up @@ -342,6 +320,8 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
// Make sure that invalid hosts aren't turned into rules.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/3946.
//
// TODO(e.burkov): Investigate if hosts may contain DNS-SD domains.
err := netutil.ValidateDomainName(f)
if err != nil {
log.Error("%s: host %q is invalid, ignoring", hostsContainerPref, f)
Expand All @@ -355,107 +335,45 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
return ip, hosts
}

// 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
// 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) {
v, ok := hp.table.Get(ip)
if !ok {
// This ip is added at the first time.
v = stringutil.NewSet()
hp.table.Set(ip, v)
}

return h.Main == hh.Main && h.Aliases.Equal(hh.Aliases)
}
var set *stringutil.Set
set, ok = v.(*stringutil.Set)
if !ok {
log.Debug("%s: adding pairs: unexpected value type %T", hostsContainerPref, v)

// add tries to add the ip-host pair. It returns:
//
// 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) (mainHost string) {
v, ok := hp.table.Get(ip)
switch h, _ := v.(*Hosts); {
case !ok:
// This is the first host for the ip.
hp.table.Set(ip, &Hosts{Main: host})

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:
// This is a duplicate. Go on.
return
}

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) {
for _, host := range hosts {
switch mainHost := hp.add(ip, host); mainHost {
case "":
// This host is a duplicate.
processed := strings.Join(append([]string{ip.String()}, hosts...), " ")
for _, h := range hosts {
if set.Has(h) {
continue
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
}
hp.writeAliasRule(host, mainHost)
hp.cnameSet.Add(pair)
}

log.Debug("%s: added ip-host pair %q-%q", hostsContainerPref, ip, host)
}
}

// writeAliasRule writes the CNAME rule for the alias-host pair into internal
// builders.
func (hp *hostsParser) writeAliasRule(alias, host string) {
const (
nl = "\n"
sc = ";"
set.Add(h)

rwSuccess = rules.MaskSeparator + "$dnsrewrite=NOERROR" + sc + "CNAME" + sc
constLen = len(rules.MaskPipe) + len(rwSuccess) + len(nl)
)
rule, rulePtr := hp.writeRules(h, ip)
hp.translations[rule], hp.translations[rulePtr] = processed, processed

hp.rulesBuilder.Grow(constLen + len(host) + len(alias))
stringutil.WriteToBuilder(hp.rulesBuilder, rules.MaskPipe, alias, rwSuccess, host, nl)
log.Debug("%s: added ip-host pair %q-%q", hostsContainerPref, ip, h)
}
}

// writeMainRule writes the actual rule for the qtype and the PTR for the
// writeRules writes the actual rule for the qtype and the PTR for the
// host-ip pair into internal builders.
func (hp *hostsParser) writeMainRule(host string, ip net.IP) (added, addedPtr string) {
func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string) {
arpa, err := netutil.IPToReversedAddr(ip)
if err != nil {
return
return "", ""
}

const (
Expand All @@ -482,28 +400,20 @@ func (hp *hostsParser) writeMainRule(host string, ip net.IP) (added, addedPtr st

ruleBuilder := &strings.Builder{}
ruleBuilder.Grow(modLen + len(host) + len(qtype) + len(ipStr))
stringutil.WriteToBuilder(
ruleBuilder,
rules.MaskPipe,
host,
rwSuccess,
qtype,
";",
ipStr,
)
added = ruleBuilder.String()
stringutil.WriteToBuilder(ruleBuilder, rules.MaskPipe, host, rwSuccess, qtype, ";", ipStr)
rule = ruleBuilder.String()

ruleBuilder.Reset()

ruleBuilder.Grow(modLenPTR + len(arpa) + len(fqdn))
stringutil.WriteToBuilder(ruleBuilder, rules.MaskPipe, arpa, rwSuccessPTR, fqdn)

addedPtr = ruleBuilder.String()
rulePtr = ruleBuilder.String()

hp.rulesBuilder.Grow(len(added) + len(addedPtr) + 2*len(nl))
stringutil.WriteToBuilder(hp.rulesBuilder, added, nl, addedPtr, nl)
hp.rulesBuilder.Grow(len(rule) + len(rulePtr) + 2*len(nl))
stringutil.WriteToBuilder(hp.rulesBuilder, rule, nl, rulePtr, nl)

return added, addedPtr
return rule, rulePtr
}

// equalSet returns true if the internal hosts table just parsed equals target.
Expand All @@ -519,12 +429,11 @@ func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {

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 main
// hosts differ or the maps have at least one discrepancy.
// appropriate hosts set isn't equal to the checked one.
if a, hasIP := target.Get(ip); !hasIP {
ok = true
} else if hosts, aok := a.(*Hosts); aok {
ok = !hosts.Equal(b.(*Hosts))
} else if hosts, aok := a.(*stringutil.Set); aok {
ok = !hosts.Equal(b.(*stringutil.Set))
}

// Continue only if maps has no discrepancies.
Expand Down Expand Up @@ -563,35 +472,6 @@ 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.
//
Expand All @@ -618,7 +498,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
}

0 comments on commit f9aa5ae

Please sign in to comment.