Skip to content

Commit

Permalink
aghnet: fix hosts container aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
EugeneOne1 committed Jan 8, 2022
1 parent e9c59b0 commit fd66191
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 83 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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



Expand Down
192 changes: 128 additions & 64 deletions internal/aghnet/hostscontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ type requestMatcher struct {
// engine serves rulesStrg.
engine *urlfilter.DNSEngine

// translator maps generated $dnsrewrite rules into hosts-syntax rules.
// translator maps generated $dnsrewrite rules into hosts-syntax rules. The
// rules are in a processed format like:
//
// ip host1 host2 ...
//
// TODO(e.burkov): Store the filename from which the rule was parsed.
translator map[string]string
Expand Down Expand Up @@ -179,7 +182,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)
}
}

Expand All @@ -199,7 +202,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
}
Expand Down Expand Up @@ -254,41 +257,53 @@ 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
// updates channel afterwards.
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),
cnameSet: stringutil.NewSet(),
table: netutil.NewIPMap(hc.last.Len()),
rules: make([]ipRules, 0, lastLen),
cnameSet: stringutil.NewSet(),
table: netutil.NewIPMap(lastLen),
}
}

// parseFile is a aghos.FileWalker for parsing the files with hosts syntax. It
// 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())
Expand Down Expand Up @@ -339,72 +354,89 @@ 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)

return hostMain
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:
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)
}

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

// 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 = ";"
Expand All @@ -417,9 +449,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
Expand Down Expand Up @@ -484,12 +516,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
Expand Down Expand Up @@ -527,6 +562,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.
//
Expand All @@ -540,7 +604,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
}
Expand All @@ -553,7 +617,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
}
Loading

0 comments on commit fd66191

Please sign in to comment.