Pull request: * all: allow multiple hosts in reverse lookups

Merge in DNS/adguard-home from 2269-multiple-hosts to master

For #2269.

Squashed commit of the following:

commit f8ae452540b106f2d5b130b8edb08c4e76b003f4
Merge: 8dd06f7cc 3e1f92225
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Fri Nov 6 17:28:12 2020 +0300

    Merge branch 'master' into 2269-multiple-hosts

commit 8dd06f7cca27ec32a4690e2673603b166f82af0a
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Nov 5 20:28:33 2020 +0300

    * all: allow multiple hosts in reverse lookups
This commit is contained in:
Ainar Garipov 2020-11-06 17:34:40 +03:00
parent 3e1f922252
commit 298f74ba81
5 changed files with 116 additions and 65 deletions

View File

@ -1,3 +1,4 @@
// Package dnsfilter implements a DNS filter.
package dnsfilter package dnsfilter
import ( import (
@ -281,7 +282,7 @@ type Result struct {
CanonName string `json:",omitempty"` // CNAME value CanonName string `json:",omitempty"` // CNAME value
// for RewriteEtcHosts: // for RewriteEtcHosts:
ReverseHost string `json:",omitempty"` ReverseHosts []string `json:",omitempty"`
// for ReasonRewrite & RewriteEtcHosts: // for ReasonRewrite & RewriteEtcHosts:
IPList []net.IP `json:",omitempty"` // list of IP addresses IPList []net.IP `json:",omitempty"` // list of IP addresses
@ -325,18 +326,9 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
// Now check the hosts file -- do we have any rules for it? // Now check the hosts file -- do we have any rules for it?
// just like DNS rewrites, it has higher priority than filtering rules. // just like DNS rewrites, it has higher priority than filtering rules.
if d.Config.AutoHosts != nil { if d.Config.AutoHosts != nil {
ips := d.Config.AutoHosts.Process(host, qtype) matched, err := d.checkAutoHosts(host, qtype, &result)
if ips != nil { if matched {
result.Reason = RewriteEtcHosts return result, err
result.IPList = ips
return result, nil
}
revHost := d.Config.AutoHosts.ProcessReverse(host, qtype)
if len(revHost) != 0 {
result.Reason = RewriteEtcHosts
result.ReverseHost = revHost + "."
return result, nil
} }
} }
@ -401,6 +393,31 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
return Result{}, nil return Result{}, nil
} }
func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool, err error) {
ips := d.Config.AutoHosts.Process(host, qtype)
if ips != nil {
result.Reason = RewriteEtcHosts
result.IPList = ips
return true, nil
}
revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype)
if len(revHosts) != 0 {
result.Reason = RewriteEtcHosts
// TODO(a.garipov): Optimize this with a buffer.
result.ReverseHosts = make([]string, len(revHosts))
for i := range revHosts {
result.ReverseHosts[i] = revHosts[i] + "."
}
return true, nil
}
return false, nil
}
// Process rewrites table // Process rewrites table
// . Find CNAME for a domain name (exact match or by wildcard) // . Find CNAME for a domain name (exact match or by wildcard)
// . if found and CNAME equals to domain name - this is an exception; exit // . if found and CNAME equals to domain name - this is an exception; exit

View File

@ -54,24 +54,28 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
} else if res.IsFiltered { } else if res.IsFiltered {
log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rule) log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rule)
d.Res = s.genDNSFilterMessage(d, &res) d.Res = s.genDNSFilterMessage(d, &res)
} else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 { } else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 {
ctx.origQuestion = d.Req.Question[0] ctx.origQuestion = d.Req.Question[0]
// resolve canonical name, not the original host name // resolve canonical name, not the original host name
d.Req.Question[0].Name = dns.Fqdn(res.CanonName) d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
} else if res.Reason == dnsfilter.RewriteEtcHosts && len(res.ReverseHosts) != 0 {
} else if res.Reason == dnsfilter.RewriteEtcHosts && len(res.ReverseHost) != 0 {
resp := s.makeResponse(req) resp := s.makeResponse(req)
ptr := &dns.PTR{} for _, h := range res.ReverseHosts {
ptr.Hdr = dns.RR_Header{ hdr := dns.RR_Header{
Name: req.Question[0].Name, Name: req.Question[0].Name,
Rrtype: dns.TypePTR, Rrtype: dns.TypePTR,
Ttl: s.conf.BlockedResponseTTL, Ttl: s.conf.BlockedResponseTTL,
Class: dns.ClassINET, Class: dns.ClassINET,
}
ptr := &dns.PTR{
Hdr: hdr,
Ptr: h,
}
resp.Answer = append(resp.Answer, ptr)
} }
ptr.Ptr = res.ReverseHost
resp.Answer = append(resp.Answer, ptr)
d.Res = resp d.Res = resp
} else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteEtcHosts { } else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteEtcHosts {
resp := s.makeResponse(req) resp := s.makeResponse(req)

View File

@ -606,22 +606,25 @@ func (clients *clientsContainer) rmHosts(source clientSource) int {
return n return n
} }
// Fill clients array from system hosts-file // addFromHostsFile fills the clients hosts list from the system's hosts files.
func (clients *clientsContainer) addFromHostsFile() { func (clients *clientsContainer) addFromHostsFile() {
hosts := clients.autoHosts.List() hosts := clients.autoHosts.List()
clients.lock.Lock() clients.lock.Lock()
defer clients.lock.Unlock() defer clients.lock.Unlock()
_ = clients.rmHosts(ClientSourceHostsFile) _ = clients.rmHosts(ClientSourceHostsFile)
n := 0 n := 0
for ip, name := range hosts { for ip, names := range hosts {
ok, err := clients.addHost(ip, name, ClientSourceHostsFile) for _, name := range names {
if err != nil { ok, err := clients.addHost(ip, name, ClientSourceHostsFile)
log.Debug("Clients: %s", err) if err != nil {
} log.Debug("Clients: %s", err)
if ok { }
n++ if ok {
n++
}
} }
} }

View File

@ -20,9 +20,14 @@ type onChangedT func()
// AutoHosts - automatic DNS records // AutoHosts - automatic DNS records
type AutoHosts struct { type AutoHosts struct {
lock sync.Mutex // serialize access to table // lock protects table and tableReverse.
table map[string][]net.IP // 'hostname -> IP' table lock sync.Mutex
tableReverse map[string]string // "IP -> hostname" table for reverse lookup // table is the host-to-IPs map.
table map[string][]net.IP
// tableReverse is the IP-to-hosts map.
//
// TODO(a.garipov): Make better use of newtypes. Perhaps a custom map.
tableReverse map[string][]string
hostsFn string // path to the main hosts-file hostsFn string // path to the main hosts-file
hostsDirs []string // paths to OS-specific directories with hosts-files hostsDirs []string // paths to OS-specific directories with hosts-files
@ -127,40 +132,44 @@ func (a *AutoHosts) Process(host string, qtype uint16) []net.IP {
return ipsCopy return ipsCopy
} }
// ProcessReverse - process PTR request // ProcessReverse processes a PTR request. It returns nil if nothing is found.
// Return "" if not found or an error occurred func (a *AutoHosts) ProcessReverse(addr string, qtype uint16) (hosts []string) {
func (a *AutoHosts) ProcessReverse(addr string, qtype uint16) string {
if qtype != dns.TypePTR { if qtype != dns.TypePTR {
return "" return nil
} }
ipReal := DNSUnreverseAddr(addr) ipReal := DNSUnreverseAddr(addr)
if ipReal == nil { if ipReal == nil {
return "" // invalid IP in question return nil
} }
ipStr := ipReal.String() ipStr := ipReal.String()
a.lock.Lock() a.lock.Lock()
host := a.tableReverse[ipStr] defer a.lock.Unlock()
a.lock.Unlock()
if len(host) == 0 { hosts = a.tableReverse[ipStr]
return "" // not found
if len(hosts) == 0 {
return nil // not found
} }
log.Debug("AutoHosts: reverse-lookup: %s -> %s", addr, host) log.Debug("AutoHosts: reverse-lookup: %s -> %s", addr, hosts)
return host
return hosts
} }
// List - get "IP -> hostname" table. Thread-safe. // List returns an IP-to-hostnames table. It is safe for concurrent use.
func (a *AutoHosts) List() map[string]string { func (a *AutoHosts) List() (ipToHosts map[string][]string) {
table := make(map[string]string)
a.lock.Lock() a.lock.Lock()
defer a.lock.Unlock()
ipToHosts = make(map[string][]string, len(a.tableReverse))
for k, v := range a.tableReverse { for k, v := range a.tableReverse {
table[k] = v ipToHosts[k] = v
} }
a.lock.Unlock()
return table return ipToHosts
} }
// update table // update table
@ -187,19 +196,30 @@ func (a *AutoHosts) updateTable(table map[string][]net.IP, host string, ipAddr n
} }
} }
// update "reverse" table // updateTableRev updates the reverse address table.
func (a *AutoHosts) updateTableRev(tableRev map[string]string, host string, ipAddr net.IP) { func (a *AutoHosts) updateTableRev(tableRev map[string][]string, newHost string, ipAddr net.IP) {
ipStr := ipAddr.String() ipStr := ipAddr.String()
_, ok := tableRev[ipStr] hosts, ok := tableRev[ipStr]
if !ok { if !ok {
tableRev[ipStr] = host tableRev[ipStr] = []string{newHost}
log.Debug("AutoHosts: added reverse-address %s -> %s", ipStr, host) log.Debug("AutoHosts: added reverse-address %s -> %s", ipStr, newHost)
return
} }
for _, host := range hosts {
if host == newHost {
return
}
}
tableRev[ipStr] = append(tableRev[ipStr], newHost)
log.Debug("AutoHosts: added reverse-address %s -> %s", ipStr, newHost)
} }
// Read IP-hostname pairs from file // Read IP-hostname pairs from file
// Multiple hostnames per line (per one IP) is supported. // Multiple hostnames per line (per one IP) is supported.
func (a *AutoHosts) load(table map[string][]net.IP, tableRev map[string]string, fn string) { func (a *AutoHosts) load(table map[string][]net.IP, tableRev map[string][]string, fn string) {
f, err := os.Open(fn) f, err := os.Open(fn)
if err != nil { if err != nil {
log.Error("AutoHosts: %s", err) log.Error("AutoHosts: %s", err)
@ -306,7 +326,7 @@ func (a *AutoHosts) updateLoop() {
// updateHosts - loads system hosts // updateHosts - loads system hosts
func (a *AutoHosts) updateHosts() { func (a *AutoHosts) updateHosts() {
table := make(map[string][]net.IP) table := make(map[string][]net.IP)
tableRev := make(map[string]string) tableRev := make(map[string][]string)
a.load(table, tableRev, a.hostsFn) a.load(table, tableRev, a.hostsFn)

View File

@ -15,7 +15,7 @@ import (
func prepareTestDir() string { func prepareTestDir() string {
const dir = "./agh-test" const dir = "./agh-test"
_ = os.RemoveAll(dir) _ = os.RemoveAll(dir)
_ = os.MkdirAll(dir, 0755) _ = os.MkdirAll(dir, 0o755)
return dir return dir
} }
@ -50,17 +50,24 @@ func TestAutoHostsResolution(t *testing.T) {
// Test hosts file // Test hosts file
table := ah.List() table := ah.List()
name, ok := table["127.0.0.1"] names, ok := table["127.0.0.1"]
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, "host", name) assert.Equal(t, []string{"host", "localhost"}, names)
// Test PTR // Test PTR
a, _ := dns.ReverseAddr("127.0.0.1") a, _ := dns.ReverseAddr("127.0.0.1")
a = strings.TrimSuffix(a, ".") a = strings.TrimSuffix(a, ".")
assert.True(t, ah.ProcessReverse(a, dns.TypePTR) == "host") hosts := ah.ProcessReverse(a, dns.TypePTR)
if assert.Len(t, hosts, 2) {
assert.Equal(t, hosts[0], "host")
}
a, _ = dns.ReverseAddr("::1") a, _ = dns.ReverseAddr("::1")
a = strings.TrimSuffix(a, ".") a = strings.TrimSuffix(a, ".")
assert.True(t, ah.ProcessReverse(a, dns.TypePTR) == "localhost") hosts = ah.ProcessReverse(a, dns.TypePTR)
if assert.Len(t, hosts, 1) {
assert.Equal(t, hosts[0], "localhost")
}
} }
func TestAutoHostsFSNotify(t *testing.T) { func TestAutoHostsFSNotify(t *testing.T) {