all: sync with master
This commit is contained in:
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/netip"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// DefaultHostsPaths returns the slice of paths default for the operating system
|
||||
@@ -106,10 +107,10 @@ type HostsContainer struct {
|
||||
done chan struct{}
|
||||
|
||||
// updates is the channel for receiving updated hosts.
|
||||
updates chan *netutil.IPMap
|
||||
updates chan HostsRecords
|
||||
|
||||
// last is the set of hosts that was cached within last detected change.
|
||||
last *netutil.IPMap
|
||||
last HostsRecords
|
||||
|
||||
// fsys is the working file system to read hosts files from.
|
||||
fsys fs.FS
|
||||
@@ -124,6 +125,25 @@ type HostsContainer struct {
|
||||
listID int
|
||||
}
|
||||
|
||||
// HostsRecords is a mapping of an IP address to its hosts data.
|
||||
type HostsRecords map[netip.Addr]*HostsRecord
|
||||
|
||||
// HostsRecord represents a single hosts file record.
|
||||
type HostsRecord struct {
|
||||
Aliases *stringutil.Set
|
||||
Canonical string
|
||||
}
|
||||
|
||||
// equal returns true if all fields of rec are equal to field in other or they
|
||||
// both are nil.
|
||||
func (rec *HostsRecord) equal(other *HostsRecord) (ok bool) {
|
||||
if rec == nil {
|
||||
return other == nil
|
||||
}
|
||||
|
||||
return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases)
|
||||
}
|
||||
|
||||
// ErrNoHostsPaths is returned when there are no valid paths to watch passed to
|
||||
// the HostsContainer.
|
||||
const ErrNoHostsPaths errors.Error = "no valid paths to hosts files provided"
|
||||
@@ -158,7 +178,7 @@ func NewHostsContainer(
|
||||
},
|
||||
listID: listID,
|
||||
done: make(chan struct{}, 1),
|
||||
updates: make(chan *netutil.IPMap, 1),
|
||||
updates: make(chan HostsRecords, 1),
|
||||
fsys: fsys,
|
||||
w: w,
|
||||
patterns: patterns,
|
||||
@@ -196,9 +216,8 @@ func (hc *HostsContainer) Close() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upd returns the channel into which the updates are sent. The receivable
|
||||
// map's values are guaranteed to be of type of *HostsRecord.
|
||||
func (hc *HostsContainer) Upd() (updates <-chan *netutil.IPMap) {
|
||||
// Upd returns the channel into which the updates are sent.
|
||||
func (hc *HostsContainer) Upd() (updates <-chan HostsRecords) {
|
||||
return hc.updates
|
||||
}
|
||||
|
||||
@@ -264,7 +283,7 @@ type hostsParser struct {
|
||||
|
||||
// table stores only the unique IP-hostname pairs. It's also sent to the
|
||||
// updates channel afterwards.
|
||||
table *netutil.IPMap
|
||||
table HostsRecords
|
||||
}
|
||||
|
||||
// newHostsParser creates a new *hostsParser with buffers of size taken from the
|
||||
@@ -273,7 +292,7 @@ func (hc *HostsContainer) newHostsParser() (hp *hostsParser) {
|
||||
return &hostsParser{
|
||||
rulesBuilder: &strings.Builder{},
|
||||
translations: map[string]string{},
|
||||
table: netutil.NewIPMap(hc.last.Len()),
|
||||
table: make(HostsRecords, len(hc.last)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +304,7 @@ func (hp *hostsParser) parseFile(r io.Reader) (patterns []string, cont bool, err
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
ip, hosts := hp.parseLine(s.Text())
|
||||
if ip == nil || len(hosts) == 0 {
|
||||
if ip == (netip.Addr{}) || len(hosts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -296,14 +315,15 @@ func (hp *hostsParser) parseFile(r io.Reader) (patterns []string, cont bool, err
|
||||
}
|
||||
|
||||
// parseLine parses the line having the hosts syntax ignoring invalid ones.
|
||||
func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
|
||||
func (hp *hostsParser) parseLine(line string) (ip netip.Addr, hosts []string) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
return nil, nil
|
||||
return netip.Addr{}, nil
|
||||
}
|
||||
|
||||
if ip = net.ParseIP(fields[0]); ip == nil {
|
||||
return nil, nil
|
||||
ip, err := netip.ParseAddr(fields[0])
|
||||
if err != nil {
|
||||
return netip.Addr{}, nil
|
||||
}
|
||||
|
||||
for _, f := range fields[1:] {
|
||||
@@ -321,7 +341,7 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/3946.
|
||||
//
|
||||
// TODO(e.burkov): Investigate if hosts may contain DNS-SD domains.
|
||||
err := netutil.ValidateDomainName(f)
|
||||
err = netutil.ValidateDomainName(f)
|
||||
if err != nil {
|
||||
log.Error("%s: host %q is invalid, ignoring", hostsContainerPref, f)
|
||||
|
||||
@@ -334,30 +354,13 @@ func (hp *hostsParser) parseLine(line string) (ip net.IP, hosts []string) {
|
||||
return ip, hosts
|
||||
}
|
||||
|
||||
// HostsRecord represents a single hosts file record.
|
||||
type HostsRecord struct {
|
||||
Aliases *stringutil.Set
|
||||
Canonical string
|
||||
}
|
||||
|
||||
// Equal returns true if all fields of rec are equal to field in other or they
|
||||
// both are nil.
|
||||
func (rec *HostsRecord) Equal(other *HostsRecord) (ok bool) {
|
||||
if rec == nil {
|
||||
return other == nil
|
||||
}
|
||||
|
||||
return rec.Canonical == other.Canonical && rec.Aliases.Equal(other.Aliases)
|
||||
}
|
||||
|
||||
// addRecord puts the record for the IP address to the rules builder if needed.
|
||||
// The first host is considered to be the canonical name for the IP address.
|
||||
// hosts must have at least one name.
|
||||
func (hp *hostsParser) addRecord(ip net.IP, hosts []string) {
|
||||
func (hp *hostsParser) addRecord(ip netip.Addr, hosts []string) {
|
||||
line := strings.Join(append([]string{ip.String()}, hosts...), " ")
|
||||
|
||||
var rec *HostsRecord
|
||||
v, ok := hp.table.Get(ip)
|
||||
rec, ok := hp.table[ip]
|
||||
if !ok {
|
||||
rec = &HostsRecord{
|
||||
Aliases: stringutil.NewSet(),
|
||||
@@ -365,14 +368,7 @@ func (hp *hostsParser) addRecord(ip net.IP, hosts []string) {
|
||||
|
||||
rec.Canonical, hosts = hosts[0], hosts[1:]
|
||||
hp.addRules(ip, rec.Canonical, line)
|
||||
hp.table.Set(ip, rec)
|
||||
} else {
|
||||
rec, ok = v.(*HostsRecord)
|
||||
if !ok {
|
||||
log.Error("%s: adding pairs: unexpected type %T", hostsContainerPref, v)
|
||||
|
||||
return
|
||||
}
|
||||
hp.table[ip] = rec
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
@@ -387,7 +383,7 @@ func (hp *hostsParser) addRecord(ip net.IP, hosts []string) {
|
||||
}
|
||||
|
||||
// addRules adds rules and rule translations for the line.
|
||||
func (hp *hostsParser) addRules(ip net.IP, host, line string) {
|
||||
func (hp *hostsParser) addRules(ip netip.Addr, host, line string) {
|
||||
rule, rulePtr := hp.writeRules(host, ip)
|
||||
hp.translations[rule], hp.translations[rulePtr] = line, line
|
||||
|
||||
@@ -396,8 +392,9 @@ func (hp *hostsParser) addRules(ip net.IP, host, line string) {
|
||||
|
||||
// writeRules writes the actual rule for the qtype and the PTR for the host-ip
|
||||
// pair into internal builders.
|
||||
func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string) {
|
||||
arpa, err := netutil.IPToReversedAddr(ip)
|
||||
func (hp *hostsParser) writeRules(host string, ip netip.Addr) (rule, rulePtr string) {
|
||||
// TODO(a.garipov): Add a netip.Addr version to netutil.
|
||||
arpa, err := netutil.IPToReversedAddr(ip.AsSlice())
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
@@ -415,7 +412,7 @@ func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string)
|
||||
var qtype string
|
||||
// The validation of the IP address has been performed earlier so it is
|
||||
// guaranteed to be either an IPv4 or an IPv6.
|
||||
if ip.To4() != nil {
|
||||
if ip.Is4() {
|
||||
qtype = "A"
|
||||
} else {
|
||||
qtype = "AAAA"
|
||||
@@ -442,51 +439,8 @@ func (hp *hostsParser) writeRules(host string, ip net.IP) (rule, rulePtr string)
|
||||
return rule, rulePtr
|
||||
}
|
||||
|
||||
// equalSet returns true if the internal hosts table just parsed equals target.
|
||||
// target's values must be of type *HostsRecord.
|
||||
func (hp *hostsParser) equalSet(target *netutil.IPMap) (ok bool) {
|
||||
if target == nil {
|
||||
// hp.table shouldn't appear nil since it's initialized on each refresh.
|
||||
return target == hp.table
|
||||
}
|
||||
|
||||
if hp.table.Len() != target.Len() {
|
||||
return false
|
||||
}
|
||||
|
||||
hp.table.Range(func(ip net.IP, recVal any) (cont bool) {
|
||||
var targetVal any
|
||||
targetVal, ok = target.Get(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
var rec *HostsRecord
|
||||
rec, ok = recVal.(*HostsRecord)
|
||||
if !ok {
|
||||
log.Error("%s: comparing: unexpected type %T", hostsContainerPref, recVal)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var targetRec *HostsRecord
|
||||
targetRec, ok = targetVal.(*HostsRecord)
|
||||
if !ok {
|
||||
log.Error("%s: comparing: target: unexpected type %T", hostsContainerPref, targetVal)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
ok = rec.Equal(targetRec)
|
||||
|
||||
return ok
|
||||
})
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// sendUpd tries to send the parsed data to the ch.
|
||||
func (hp *hostsParser) sendUpd(ch chan *netutil.IPMap) {
|
||||
func (hp *hostsParser) sendUpd(ch chan HostsRecords) {
|
||||
log.Debug("%s: sending upd", hostsContainerPref)
|
||||
|
||||
upd := hp.table
|
||||
@@ -524,14 +478,14 @@ func (hc *HostsContainer) refresh() (err error) {
|
||||
return fmt.Errorf("refreshing : %w", err)
|
||||
}
|
||||
|
||||
if hp.equalSet(hc.last) {
|
||||
if maps.EqualFunc(hp.table, hc.last, (*HostsRecord).equal) {
|
||||
log.Debug("%s: no changes detected", hostsContainerPref)
|
||||
|
||||
return nil
|
||||
}
|
||||
defer hp.sendUpd(hc.updates)
|
||||
|
||||
hc.last = hp.table.ShallowClone()
|
||||
hc.last = maps.Clone(hp.table)
|
||||
|
||||
var rulesStrg *filterlist.RuleStorage
|
||||
if rulesStrg, err = hp.newStrg(hc.listID); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user