all: sync with master
This commit is contained in:
@@ -1,23 +1,15 @@
|
||||
package aghalg
|
||||
|
||||
import (
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
)
|
||||
|
||||
// RingBuffer is the implementation of ring buffer data structure.
|
||||
type RingBuffer[T any] struct {
|
||||
buf []T
|
||||
cur int
|
||||
cur uint
|
||||
full bool
|
||||
}
|
||||
|
||||
// NewRingBuffer initializes the new instance of ring buffer. size must be
|
||||
// greater or equal to zero.
|
||||
func NewRingBuffer[T any](size int) (rb *RingBuffer[T]) {
|
||||
if size < 0 {
|
||||
panic(errors.Error("ring buffer: size must be greater or equal to zero"))
|
||||
}
|
||||
|
||||
func NewRingBuffer[T any](size uint) (rb *RingBuffer[T]) {
|
||||
return &RingBuffer[T]{
|
||||
buf: make([]T, size),
|
||||
}
|
||||
@@ -30,7 +22,7 @@ func (rb *RingBuffer[T]) Append(e T) {
|
||||
}
|
||||
|
||||
rb.buf[rb.cur] = e
|
||||
rb.cur = (rb.cur + 1) % cap(rb.buf)
|
||||
rb.cur = (rb.cur + 1) % uint(cap(rb.buf))
|
||||
if rb.cur == 0 {
|
||||
rb.full = true
|
||||
}
|
||||
@@ -87,12 +79,12 @@ func (rb *RingBuffer[T]) splitCur() (before, after []T) {
|
||||
}
|
||||
|
||||
// Len returns a length of the buffer.
|
||||
func (rb *RingBuffer[T]) Len() (l int) {
|
||||
func (rb *RingBuffer[T]) Len() (l uint) {
|
||||
if !rb.full {
|
||||
return rb.cur
|
||||
}
|
||||
|
||||
return cap(rb.buf)
|
||||
return uint(cap(rb.buf))
|
||||
}
|
||||
|
||||
// Clear clears the buffer.
|
||||
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
)
|
||||
|
||||
// elements is a helper function that returns n elements of the buffer.
|
||||
func elements(b *aghalg.RingBuffer[int], n int, reverse bool) (es []int) {
|
||||
func elements(b *aghalg.RingBuffer[int], n uint, reverse bool) (es []int) {
|
||||
fn := b.Range
|
||||
if reverse {
|
||||
fn = b.ReverseRange
|
||||
}
|
||||
|
||||
i := 0
|
||||
var i uint
|
||||
fn(func(e int) (cont bool) {
|
||||
if i >= n {
|
||||
return false
|
||||
@@ -42,19 +42,14 @@ func TestNewRingBuffer(t *testing.T) {
|
||||
assert.Zero(t, b.Len())
|
||||
})
|
||||
|
||||
t.Run("negative_size", func(t *testing.T) {
|
||||
assert.PanicsWithError(t, "ring buffer: size must be greater or equal to zero", func() {
|
||||
aghalg.NewRingBuffer[int](-5)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("zero", func(t *testing.T) {
|
||||
b := aghalg.NewRingBuffer[int](0)
|
||||
for i := 0; i < 10; i++ {
|
||||
b.Append(i)
|
||||
assert.Equal(t, 0, b.Len())
|
||||
assert.Empty(t, elements(b, b.Len(), false))
|
||||
assert.Empty(t, elements(b, b.Len(), true))
|
||||
bufLen := b.Len()
|
||||
assert.EqualValues(t, 0, bufLen)
|
||||
assert.Empty(t, elements(b, bufLen, false))
|
||||
assert.Empty(t, elements(b, bufLen, true))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -62,9 +57,10 @@ func TestNewRingBuffer(t *testing.T) {
|
||||
b := aghalg.NewRingBuffer[int](1)
|
||||
for i := 0; i < 10; i++ {
|
||||
b.Append(i)
|
||||
assert.Equal(t, 1, b.Len())
|
||||
assert.Equal(t, []int{i}, elements(b, b.Len(), false))
|
||||
assert.Equal(t, []int{i}, elements(b, b.Len(), true))
|
||||
bufLen := b.Len()
|
||||
assert.EqualValues(t, 1, bufLen)
|
||||
assert.Equal(t, []int{i}, elements(b, bufLen, false))
|
||||
assert.Equal(t, []int{i}, elements(b, bufLen, true))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -78,7 +74,7 @@ func TestRingBuffer_Range(t *testing.T) {
|
||||
name string
|
||||
want []int
|
||||
count int
|
||||
length int
|
||||
length uint
|
||||
}{{
|
||||
name: "three",
|
||||
count: 3,
|
||||
@@ -163,11 +159,11 @@ func TestRingBuffer_Range_increment(t *testing.T) {
|
||||
for i, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
b.Append(i)
|
||||
|
||||
assert.Equal(t, tc.want, elements(b, b.Len(), false))
|
||||
bufLen := b.Len()
|
||||
assert.Equal(t, tc.want, elements(b, bufLen, false))
|
||||
|
||||
slices.Reverse(tc.want)
|
||||
assert.Equal(t, tc.want, elements(b, b.Len(), true))
|
||||
assert.Equal(t, tc.want, elements(b, bufLen, true))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// Package aghchan contains channel utilities.
|
||||
package aghchan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Receive returns an error if it cannot receive a value form c before timeout
|
||||
// runs out.
|
||||
func Receive[T any](c <-chan T, timeout time.Duration) (v T, ok bool, err error) {
|
||||
var zero T
|
||||
timeoutCh := time.After(timeout)
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
// TODO(a.garipov): Consider implementing [errors.Aser] for
|
||||
// os.ErrTimeout.
|
||||
return zero, false, fmt.Errorf("did not receive after %s", timeout)
|
||||
case v, ok = <-c:
|
||||
return v, ok, nil
|
||||
}
|
||||
}
|
||||
|
||||
// MustReceive panics if it cannot receive a value form c before timeout runs
|
||||
// out.
|
||||
func MustReceive[T any](c <-chan T, timeout time.Duration) (v T, ok bool) {
|
||||
v, ok, err := Receive(c, timeout)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return v, ok
|
||||
}
|
||||
@@ -1,65 +1,23 @@
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/netip"
|
||||
"path"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/hostsfile"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// DefaultHostsPaths returns the slice of paths default for the operating system
|
||||
// to files and directories which are containing the hosts database. The result
|
||||
// is intended to be used within fs.FS so the initial slash is omitted.
|
||||
func DefaultHostsPaths() (paths []string) {
|
||||
return defaultHostsPaths()
|
||||
}
|
||||
|
||||
// MatchAddr returns the records for the IP address.
|
||||
func (hc *HostsContainer) MatchAddr(ip netip.Addr) (recs []*hostsfile.Record) {
|
||||
cur := hc.current.Load()
|
||||
if cur == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cur.addrs[ip]
|
||||
}
|
||||
|
||||
// MatchName returns the records for the hostname.
|
||||
func (hc *HostsContainer) MatchName(name string) (recs []*hostsfile.Record) {
|
||||
cur := hc.current.Load()
|
||||
if cur != nil {
|
||||
recs = cur.names[name]
|
||||
}
|
||||
|
||||
return recs
|
||||
}
|
||||
|
||||
// hostsContainerPrefix is a prefix for logging and wrapping errors in
|
||||
// HostsContainer's methods.
|
||||
const hostsContainerPrefix = "hosts container"
|
||||
|
||||
// Hosts is a map of IP addresses to the records, as it primarily stored in the
|
||||
// [HostsContainer]. It should not be accessed for writing since it may be read
|
||||
// concurrently, users should clone it before modifying.
|
||||
//
|
||||
// The order of records for each address is preserved from original files, but
|
||||
// the order of the addresses, being a map key, is not.
|
||||
//
|
||||
// TODO(e.burkov): Probably, this should be a sorted slice of records.
|
||||
type Hosts map[netip.Addr][]*hostsfile.Record
|
||||
|
||||
// HostsContainer stores the relevant hosts database provided by the OS and
|
||||
// processes both A/AAAA and PTR DNS requests for those.
|
||||
type HostsContainer struct {
|
||||
@@ -67,10 +25,10 @@ type HostsContainer struct {
|
||||
done chan struct{}
|
||||
|
||||
// updates is the channel for receiving updated hosts.
|
||||
updates chan Hosts
|
||||
updates chan *hostsfile.DefaultStorage
|
||||
|
||||
// current is the last set of hosts parsed.
|
||||
current atomic.Pointer[hostsIndex]
|
||||
current atomic.Pointer[hostsfile.DefaultStorage]
|
||||
|
||||
// fsys is the working file system to read hosts files from.
|
||||
fsys fs.FS
|
||||
@@ -111,7 +69,7 @@ func NewHostsContainer(
|
||||
|
||||
hc = &HostsContainer{
|
||||
done: make(chan struct{}, 1),
|
||||
updates: make(chan Hosts, 1),
|
||||
updates: make(chan *hostsfile.DefaultStorage, 1),
|
||||
fsys: fsys,
|
||||
watcher: w,
|
||||
patterns: patterns,
|
||||
@@ -152,11 +110,25 @@ func (hc *HostsContainer) Close() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Upd returns the channel into which the updates are sent.
|
||||
func (hc *HostsContainer) Upd() (updates <-chan Hosts) {
|
||||
// Upd returns the channel into which the updates are sent. The updates
|
||||
// themselves must not be modified.
|
||||
func (hc *HostsContainer) Upd() (updates <-chan *hostsfile.DefaultStorage) {
|
||||
return hc.updates
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ hostsfile.Storage = (*HostsContainer)(nil)
|
||||
|
||||
// ByAddr implements the [hostsfile.Storage] interface for *HostsContainer.
|
||||
func (hc *HostsContainer) ByAddr(addr netip.Addr) (names []string) {
|
||||
return hc.current.Load().ByAddr(addr)
|
||||
}
|
||||
|
||||
// ByName implements the [hostsfile.Storage] interface for *HostsContainer.
|
||||
func (hc *HostsContainer) ByName(name string) (addrs []netip.Addr) {
|
||||
return hc.current.Load().ByName(name)
|
||||
}
|
||||
|
||||
// pathsToPatterns converts paths into patterns compatible with fs.Glob.
|
||||
func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error) {
|
||||
for i, p := range paths {
|
||||
@@ -167,7 +139,7 @@ func pathsToPatterns(fsys fs.FS, paths []string) (patterns []string, err error)
|
||||
continue
|
||||
}
|
||||
|
||||
// Don't put a filename here since it's already added by fs.Stat.
|
||||
// Don't put a filename here since it's already added by [fs.Stat].
|
||||
return nil, fmt.Errorf("path at index %d: %w", i, err)
|
||||
}
|
||||
|
||||
@@ -209,7 +181,7 @@ func (hc *HostsContainer) handleEvents() {
|
||||
}
|
||||
|
||||
// sendUpd tries to send the parsed data to the ch.
|
||||
func (hc *HostsContainer) sendUpd(recs Hosts) {
|
||||
func (hc *HostsContainer) sendUpd(recs *hostsfile.DefaultStorage) {
|
||||
log.Debug("%s: sending upd", hostsContainerPrefix)
|
||||
|
||||
ch := hc.updates
|
||||
@@ -226,67 +198,6 @@ func (hc *HostsContainer) sendUpd(recs Hosts) {
|
||||
}
|
||||
}
|
||||
|
||||
// hostsIndex is a [hostsfile.Set] to enumerate all the records.
|
||||
type hostsIndex struct {
|
||||
// addrs maps IP addresses to the records.
|
||||
addrs Hosts
|
||||
|
||||
// names maps hostnames to the records.
|
||||
names map[string][]*hostsfile.Record
|
||||
}
|
||||
|
||||
// walk is a file walking function for hostsIndex.
|
||||
func (idx *hostsIndex) walk(r io.Reader) (patterns []string, cont bool, err error) {
|
||||
return nil, true, hostsfile.Parse(idx, r, nil)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ hostsfile.Set = (*hostsIndex)(nil)
|
||||
|
||||
// Add implements the [hostsfile.Set] interface for *hostsIndex.
|
||||
func (idx *hostsIndex) Add(rec *hostsfile.Record) {
|
||||
idx.addrs[rec.Addr] = append(idx.addrs[rec.Addr], rec)
|
||||
for _, name := range rec.Names {
|
||||
idx.names[name] = append(idx.names[name], rec)
|
||||
}
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ hostsfile.HandleSet = (*hostsIndex)(nil)
|
||||
|
||||
// HandleInvalid implements the [hostsfile.HandleSet] interface for *hostsIndex.
|
||||
func (idx *hostsIndex) HandleInvalid(src string, _ []byte, err error) {
|
||||
lineErr := &hostsfile.LineError{}
|
||||
if !errors.As(err, &lineErr) {
|
||||
// Must not happen if idx passed to [hostsfile.Parse].
|
||||
return
|
||||
} else if errors.Is(lineErr, hostsfile.ErrEmptyLine) {
|
||||
// Ignore empty lines.
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("%s: warning: parsing %q: %s", hostsContainerPrefix, src, lineErr)
|
||||
}
|
||||
|
||||
// equalRecs is an equality function for [*hostsfile.Record].
|
||||
func equalRecs(a, b *hostsfile.Record) (ok bool) {
|
||||
return a.Addr == b.Addr && a.Source == b.Source && slices.Equal(a.Names, b.Names)
|
||||
}
|
||||
|
||||
// equalRecSlices is an equality function for slices of [*hostsfile.Record].
|
||||
func equalRecSlices(a, b []*hostsfile.Record) (ok bool) { return slices.EqualFunc(a, b, equalRecs) }
|
||||
|
||||
// Equal returns true if indexes are equal.
|
||||
func (idx *hostsIndex) Equal(other *hostsIndex) (ok bool) {
|
||||
if idx == nil {
|
||||
return other == nil
|
||||
} else if other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return maps.EqualFunc(idx.addrs, other.addrs, equalRecSlices)
|
||||
}
|
||||
|
||||
// refresh gets the data from specified files and propagates the updates if
|
||||
// needed.
|
||||
//
|
||||
@@ -294,63 +205,22 @@ func (idx *hostsIndex) Equal(other *hostsIndex) (ok bool) {
|
||||
func (hc *HostsContainer) refresh() (err error) {
|
||||
log.Debug("%s: refreshing", hostsContainerPrefix)
|
||||
|
||||
var addrLen, nameLen int
|
||||
last := hc.current.Load()
|
||||
if last != nil {
|
||||
addrLen, nameLen = len(last.addrs), len(last.names)
|
||||
}
|
||||
idx := &hostsIndex{
|
||||
addrs: make(Hosts, addrLen),
|
||||
names: make(map[string][]*hostsfile.Record, nameLen),
|
||||
}
|
||||
|
||||
_, err = aghos.FileWalker(idx.walk).Walk(hc.fsys, hc.patterns...)
|
||||
// The error is always nil here since no readers passed.
|
||||
strg, _ := hostsfile.NewDefaultStorage()
|
||||
_, err = aghos.FileWalker(func(r io.Reader) (patterns []string, cont bool, err error) {
|
||||
// Don't wrap the error since it's already informative enough as is.
|
||||
return nil, true, hostsfile.Parse(strg, r, nil)
|
||||
}).Walk(hc.fsys, hc.patterns...)
|
||||
if err != nil {
|
||||
// Don't wrap the error since it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(e.burkov): Serialize updates using time.
|
||||
if !last.Equal(idx) {
|
||||
hc.current.Store(idx)
|
||||
hc.sendUpd(idx.addrs)
|
||||
// TODO(e.burkov): Serialize updates using [time.Time].
|
||||
if !hc.current.Load().Equal(strg) {
|
||||
hc.current.Store(strg)
|
||||
hc.sendUpd(strg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ upstream.Resolver = (*HostsContainer)(nil)
|
||||
|
||||
// LookupNetIP implements the [upstream.Resolver] interface for *HostsContainer.
|
||||
func (hc *HostsContainer) LookupNetIP(
|
||||
ctx context.Context,
|
||||
network string,
|
||||
hostname string,
|
||||
) (addrs []netip.Addr, err error) {
|
||||
// TODO(e.burkov): Think of extracting this logic to a golibs function if
|
||||
// needed anywhere else.
|
||||
var isDesiredProto func(ip netip.Addr) (ok bool)
|
||||
switch network {
|
||||
case "ip4":
|
||||
isDesiredProto = (netip.Addr).Is4
|
||||
case "ip6":
|
||||
isDesiredProto = (netip.Addr).Is6
|
||||
case "ip":
|
||||
isDesiredProto = func(ip netip.Addr) (ok bool) { return true }
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported network: %q", network)
|
||||
}
|
||||
|
||||
idx := hc.current.Load()
|
||||
recs := idx.names[strings.ToLower(hostname)]
|
||||
|
||||
addrs = make([]netip.Addr, 0, len(recs))
|
||||
for _, rec := range recs {
|
||||
if isDesiredProto(rec.Addr) {
|
||||
addrs = append(addrs, rec.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
return slices.Clip(addrs), nil
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||
)
|
||||
|
||||
func defaultHostsPaths() (paths []string) {
|
||||
paths = []string{"etc/hosts"}
|
||||
|
||||
if aghos.IsOpenWrt() {
|
||||
paths = append(paths, "tmp/hosts")
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
//go:build !(windows || linux)
|
||||
|
||||
package aghnet
|
||||
|
||||
func defaultHostsPaths() (paths []string) {
|
||||
return []string{"etc/hosts"}
|
||||
}
|
||||
@@ -3,13 +3,11 @@ package aghnet_test
|
||||
import (
|
||||
"net/netip"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghchan"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
@@ -20,139 +18,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// nl is a newline character.
|
||||
const nl = "\n"
|
||||
|
||||
// Variables mirroring the etc_hosts file from testdata.
|
||||
var (
|
||||
addr1000 = netip.MustParseAddr("1.0.0.0")
|
||||
addr1001 = netip.MustParseAddr("1.0.0.1")
|
||||
addr1002 = netip.MustParseAddr("1.0.0.2")
|
||||
addr1003 = netip.MustParseAddr("1.0.0.3")
|
||||
addr1004 = netip.MustParseAddr("1.0.0.4")
|
||||
addr1357 = netip.MustParseAddr("1.3.5.7")
|
||||
addr4216 = netip.MustParseAddr("4.2.1.6")
|
||||
addr7531 = netip.MustParseAddr("7.5.3.1")
|
||||
|
||||
addr0 = netip.MustParseAddr("::")
|
||||
addr1 = netip.MustParseAddr("::1")
|
||||
addr2 = netip.MustParseAddr("::2")
|
||||
addr3 = netip.MustParseAddr("::3")
|
||||
addr4 = netip.MustParseAddr("::4")
|
||||
addr42 = netip.MustParseAddr("::42")
|
||||
addr13 = netip.MustParseAddr("::13")
|
||||
addr31 = netip.MustParseAddr("::31")
|
||||
|
||||
hostsSrc = "./" + filepath.Join("./testdata", "etc_hosts")
|
||||
|
||||
testHosts = map[netip.Addr][]*hostsfile.Record{
|
||||
addr1000: {{
|
||||
Addr: addr1000,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"hello", "hello.world"},
|
||||
}, {
|
||||
Addr: addr1000,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"hello.world.again"},
|
||||
}, {
|
||||
Addr: addr1000,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"hello.world"},
|
||||
}},
|
||||
addr1001: {{
|
||||
Addr: addr1001,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"simplehost"},
|
||||
}, {
|
||||
Addr: addr1001,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"simplehost"},
|
||||
}},
|
||||
addr1002: {{
|
||||
Addr: addr1002,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"a.whole", "lot.of", "aliases", "for.testing"},
|
||||
}},
|
||||
addr1003: {{
|
||||
Addr: addr1003,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"*"},
|
||||
}},
|
||||
addr1004: {{
|
||||
Addr: addr1004,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"*.com"},
|
||||
}},
|
||||
addr1357: {{
|
||||
Addr: addr1357,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"domain4", "domain4.alias"},
|
||||
}},
|
||||
addr7531: {{
|
||||
Addr: addr7531,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"domain4.alias", "domain4"},
|
||||
}},
|
||||
addr4216: {{
|
||||
Addr: addr4216,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"domain", "domain.alias"},
|
||||
}},
|
||||
addr0: {{
|
||||
Addr: addr0,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"hello", "hello.world"},
|
||||
}, {
|
||||
Addr: addr0,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"hello.world.again"},
|
||||
}, {
|
||||
Addr: addr0,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"hello.world"},
|
||||
}},
|
||||
addr1: {{
|
||||
Addr: addr1,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"simplehost"},
|
||||
}, {
|
||||
Addr: addr1,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"simplehost"},
|
||||
}},
|
||||
addr2: {{
|
||||
Addr: addr2,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"a.whole", "lot.of", "aliases", "for.testing"},
|
||||
}},
|
||||
addr3: {{
|
||||
Addr: addr3,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"*"},
|
||||
}},
|
||||
addr4: {{
|
||||
Addr: addr4,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"*.com"},
|
||||
}},
|
||||
addr42: {{
|
||||
Addr: addr42,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"domain.alias", "domain"},
|
||||
}},
|
||||
addr13: {{
|
||||
Addr: addr13,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"domain6", "domain6.alias"},
|
||||
}},
|
||||
addr31: {{
|
||||
Addr: addr31,
|
||||
Source: hostsSrc,
|
||||
Names: []string{"domain6.alias", "domain6"},
|
||||
}},
|
||||
}
|
||||
)
|
||||
|
||||
func TestNewHostsContainer(t *testing.T) {
|
||||
const dirname = "dir"
|
||||
const filename = "file1"
|
||||
@@ -267,7 +132,21 @@ func TestHostsContainer_refresh(t *testing.T) {
|
||||
anotherIPStr := "1.2.3.4"
|
||||
anotherIP := netip.MustParseAddr(anotherIPStr)
|
||||
|
||||
testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}}
|
||||
r1 := &hostsfile.Record{
|
||||
Addr: ip,
|
||||
Source: "file1",
|
||||
Names: []string{"hostname"},
|
||||
}
|
||||
r2 := &hostsfile.Record{
|
||||
Addr: anotherIP,
|
||||
Source: "file2",
|
||||
Names: []string{"alias"},
|
||||
}
|
||||
|
||||
r1Data, _ := r1.MarshalText()
|
||||
r2Data, _ := r2.MarshalText()
|
||||
|
||||
testFS := fstest.MapFS{"dir/file1": &fstest.MapFile{Data: r1Data}}
|
||||
|
||||
// event is a convenient alias for an empty struct{} to emit test events.
|
||||
type event = struct{}
|
||||
@@ -289,172 +168,47 @@ func TestHostsContainer_refresh(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
testutil.CleanupAndRequireSuccess(t, hc.Close)
|
||||
|
||||
checkRefresh := func(t *testing.T, want aghnet.Hosts) {
|
||||
t.Helper()
|
||||
|
||||
upd, ok := aghchan.MustReceive(hc.Upd(), 1*time.Second)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, want, upd)
|
||||
}
|
||||
strg, _ := hostsfile.NewDefaultStorage()
|
||||
strg.Add(r1)
|
||||
|
||||
t.Run("initial_refresh", func(t *testing.T) {
|
||||
checkRefresh(t, aghnet.Hosts{
|
||||
ip: {{
|
||||
Addr: ip,
|
||||
Source: "file1",
|
||||
Names: []string{"hostname"},
|
||||
}},
|
||||
})
|
||||
upd, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.True(t, strg.Equal(upd))
|
||||
})
|
||||
|
||||
strg.Add(r2)
|
||||
|
||||
t.Run("second_refresh", func(t *testing.T) {
|
||||
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(anotherIPStr + ` alias` + nl)}
|
||||
testFS["dir/file2"] = &fstest.MapFile{Data: r2Data}
|
||||
eventsCh <- event{}
|
||||
|
||||
checkRefresh(t, aghnet.Hosts{
|
||||
ip: {{
|
||||
Addr: ip,
|
||||
Source: "file1",
|
||||
Names: []string{"hostname"},
|
||||
}},
|
||||
anotherIP: {{
|
||||
Addr: anotherIP,
|
||||
Source: "file2",
|
||||
Names: []string{"alias"},
|
||||
}},
|
||||
})
|
||||
upd, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.True(t, strg.Equal(upd))
|
||||
})
|
||||
|
||||
t.Run("double_refresh", func(t *testing.T) {
|
||||
// Make a change once.
|
||||
testFS["dir/file1"] = &fstest.MapFile{Data: []byte(ipStr + ` alias` + nl)}
|
||||
testFS["dir/file1"] = &fstest.MapFile{Data: []byte(ipStr + " alias\n")}
|
||||
eventsCh <- event{}
|
||||
|
||||
// Require the changes are written.
|
||||
require.Eventually(t, func() bool {
|
||||
ips := hc.MatchName("hostname")
|
||||
current, ok := testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
|
||||
require.True(t, ok)
|
||||
|
||||
return len(ips) == 0
|
||||
}, 5*time.Second, time.Second/2)
|
||||
require.Empty(t, current.ByName("hostname"))
|
||||
|
||||
// Make a change again.
|
||||
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + ` hostname` + nl)}
|
||||
testFS["dir/file2"] = &fstest.MapFile{Data: []byte(ipStr + " hostname\n")}
|
||||
eventsCh <- event{}
|
||||
|
||||
// Require the changes are written.
|
||||
require.Eventually(t, func() bool {
|
||||
ips := hc.MatchName("hostname")
|
||||
current, ok = testutil.RequireReceive(t, hc.Upd(), 1*time.Second)
|
||||
require.True(t, ok)
|
||||
|
||||
return len(ips) > 0
|
||||
}, 5*time.Second, time.Second/2)
|
||||
|
||||
assert.Len(t, hc.Upd(), 1)
|
||||
require.NotEmpty(t, current.ByName("hostname"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestHostsContainer_MatchName(t *testing.T) {
|
||||
require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
|
||||
|
||||
stubWatcher := aghtest.FSWatcher{
|
||||
OnEvents: func() (e <-chan struct{}) { return nil },
|
||||
OnAdd: func(name string) (err error) { return nil },
|
||||
OnClose: func() (err error) { return nil },
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
req string
|
||||
name string
|
||||
want []*hostsfile.Record
|
||||
}{{
|
||||
req: "simplehost",
|
||||
name: "simple",
|
||||
want: append(testHosts[addr1001], testHosts[addr1]...),
|
||||
}, {
|
||||
req: "hello.world",
|
||||
name: "hello_alias",
|
||||
want: []*hostsfile.Record{
|
||||
testHosts[addr1000][0],
|
||||
testHosts[addr1000][2],
|
||||
testHosts[addr0][0],
|
||||
testHosts[addr0][2],
|
||||
},
|
||||
}, {
|
||||
req: "hello.world.again",
|
||||
name: "other_line_alias",
|
||||
want: []*hostsfile.Record{
|
||||
testHosts[addr1000][1],
|
||||
testHosts[addr0][1],
|
||||
},
|
||||
}, {
|
||||
req: "say.hello",
|
||||
name: "hello_subdomain",
|
||||
want: nil,
|
||||
}, {
|
||||
req: "say.hello.world",
|
||||
name: "hello_alias_subdomain",
|
||||
want: nil,
|
||||
}, {
|
||||
req: "for.testing",
|
||||
name: "lots_of_aliases",
|
||||
want: append(testHosts[addr1002], testHosts[addr2]...),
|
||||
}, {
|
||||
req: "nonexistent.example",
|
||||
name: "non-existing",
|
||||
want: nil,
|
||||
}, {
|
||||
req: "domain",
|
||||
name: "issue_4216_4_6",
|
||||
want: append(testHosts[addr4216], testHosts[addr42]...),
|
||||
}, {
|
||||
req: "domain4",
|
||||
name: "issue_4216_4",
|
||||
want: append(testHosts[addr1357], testHosts[addr7531]...),
|
||||
}, {
|
||||
req: "domain6",
|
||||
name: "issue_4216_6",
|
||||
want: append(testHosts[addr13], testHosts[addr31]...),
|
||||
}}
|
||||
|
||||
hc, err := aghnet.NewHostsContainer(testdata, &stubWatcher, "etc_hosts")
|
||||
require.NoError(t, err)
|
||||
testutil.CleanupAndRequireSuccess(t, hc.Close)
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
recs := hc.MatchName(tc.req)
|
||||
assert.Equal(t, tc.want, recs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostsContainer_MatchAddr(t *testing.T) {
|
||||
require.NoError(t, fstest.TestFS(testdata, "etc_hosts"))
|
||||
|
||||
stubWatcher := aghtest.FSWatcher{
|
||||
OnEvents: func() (e <-chan struct{}) { return nil },
|
||||
OnAdd: func(name string) (err error) { return nil },
|
||||
OnClose: func() (err error) { return nil },
|
||||
}
|
||||
|
||||
hc, err := aghnet.NewHostsContainer(testdata, &stubWatcher, "etc_hosts")
|
||||
require.NoError(t, err)
|
||||
testutil.CleanupAndRequireSuccess(t, hc.Close)
|
||||
|
||||
testCases := []struct {
|
||||
req netip.Addr
|
||||
name string
|
||||
want []*hostsfile.Record
|
||||
}{{
|
||||
req: netip.AddrFrom4([4]byte{1, 0, 0, 1}),
|
||||
name: "reverse",
|
||||
want: testHosts[addr1001],
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
recs := hc.MatchAddr(tc.req)
|
||||
assert.Equal(t, tc.want, recs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package aghnet
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func defaultHostsPaths() (paths []string) {
|
||||
sysDir, err := windows.GetSystemDirectory()
|
||||
if err != nil {
|
||||
log.Error("aghnet: getting system directory: %s", err)
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Split all the elements of the path to join them afterwards. This is
|
||||
// needed to make the Windows-specific path string returned by
|
||||
// windows.GetSystemDirectory to be compatible with fs.FS.
|
||||
pathElems := strings.Split(sysDir, string(os.PathSeparator))
|
||||
if len(pathElems) > 0 && pathElems[0] == filepath.VolumeName(sysDir) {
|
||||
pathElems = pathElems[1:]
|
||||
}
|
||||
|
||||
return []string{path.Join(append(pathElems, "drivers/etc/hosts")...)}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
package aghnet_test
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
@@ -18,9 +16,6 @@ func TestMain(m *testing.M) {
|
||||
testutil.DiscardLogOutput(m)
|
||||
}
|
||||
|
||||
// testdata is the filesystem containing data for testing the package.
|
||||
var testdata fs.FS = os.DirFS("./testdata")
|
||||
|
||||
func TestParseAddrPort(t *testing.T) {
|
||||
const defaultPort = 1
|
||||
|
||||
|
||||
38
internal/aghnet/testdata/etc_hosts
vendored
38
internal/aghnet/testdata/etc_hosts
vendored
@@ -1,38 +0,0 @@
|
||||
#
|
||||
# Test /etc/hosts file
|
||||
#
|
||||
|
||||
1.0.0.1 simplehost
|
||||
1.0.0.0 hello hello.world
|
||||
|
||||
# See https://github.com/AdguardTeam/AdGuardHome/issues/3846.
|
||||
1.0.0.2 a.whole lot.of aliases for.testing
|
||||
|
||||
# See https://github.com/AdguardTeam/AdGuardHome/issues/3946.
|
||||
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
|
||||
|
||||
# See https://github.com/AdguardTeam/AdGuardHome/issues/4216.
|
||||
4.2.1.6 domain domain.alias
|
||||
::42 domain.alias domain
|
||||
1.3.5.7 domain4 domain4.alias
|
||||
7.5.3.1 domain4.alias domain4
|
||||
::13 domain6 domain6.alias
|
||||
::31 domain6.alias domain6
|
||||
1
internal/aghnet/testdata/ifaces
vendored
1
internal/aghnet/testdata/ifaces
vendored
@@ -1 +0,0 @@
|
||||
iface sample_name inet static
|
||||
5
internal/aghnet/testdata/include-subsources
vendored
5
internal/aghnet/testdata/include-subsources
vendored
@@ -1,5 +0,0 @@
|
||||
# The "testdata" part is added here because the test is actually run from the
|
||||
# parent directory. Real interface files usually contain only absolute paths.
|
||||
|
||||
source ./testdata/ifaces
|
||||
source ./testdata/*
|
||||
@@ -262,8 +262,7 @@ func (p *DefaultAddrProc) processRDNS(ip netip.Addr) (host string) {
|
||||
// shouldResolve returns false if ip is a loopback address, or ip is private and
|
||||
// resolving of private addresses is disabled.
|
||||
func (p *DefaultAddrProc) shouldResolve(ip netip.Addr) (ok bool) {
|
||||
return !ip.IsLoopback() &&
|
||||
(p.usePrivateRDNS || !p.privateSubnets.Contains(ip.AsSlice()))
|
||||
return !ip.IsLoopback() && (p.usePrivateRDNS || !p.privateSubnets.Contains(ip))
|
||||
}
|
||||
|
||||
// processWHOIS looks up the information about clients' IP addresses in the
|
||||
|
||||
@@ -7,6 +7,8 @@ package client
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/whois"
|
||||
)
|
||||
|
||||
// Source represents the source from which the information about the client has
|
||||
@@ -15,8 +17,7 @@ type Source uint8
|
||||
|
||||
// Clients information sources. The order determines the priority.
|
||||
const (
|
||||
SourceNone Source = iota
|
||||
SourceWHOIS
|
||||
SourceWHOIS Source = iota + 1
|
||||
SourceARP
|
||||
SourceRDNS
|
||||
SourceDHCP
|
||||
@@ -52,3 +53,107 @@ var _ encoding.TextMarshaler = Source(0)
|
||||
func (cs Source) MarshalText() (text []byte, err error) {
|
||||
return []byte(cs.String()), nil
|
||||
}
|
||||
|
||||
// Runtime is a client information from different sources.
|
||||
type Runtime struct {
|
||||
// whois is the filtered WHOIS information of a client.
|
||||
whois *whois.Info
|
||||
|
||||
// arp is the ARP information of a client. nil indicates that there is no
|
||||
// information from the source. Empty non-nil slice indicates that the data
|
||||
// from the source is present, but empty.
|
||||
arp []string
|
||||
|
||||
// rdns is the RDNS information of a client. nil indicates that there is no
|
||||
// information from the source. Empty non-nil slice indicates that the data
|
||||
// from the source is present, but empty.
|
||||
rdns []string
|
||||
|
||||
// dhcp is the DHCP information of a client. nil indicates that there is no
|
||||
// information from the source. Empty non-nil slice indicates that the data
|
||||
// from the source is present, but empty.
|
||||
dhcp []string
|
||||
|
||||
// hostsFile is the information from the hosts file. nil indicates that
|
||||
// there is no information from the source. Empty non-nil slice indicates
|
||||
// that the data from the source is present, but empty.
|
||||
hostsFile []string
|
||||
}
|
||||
|
||||
// Info returns a client information from the highest-priority source.
|
||||
func (r *Runtime) Info() (cs Source, host string) {
|
||||
info := []string{}
|
||||
|
||||
switch {
|
||||
case r.hostsFile != nil:
|
||||
cs, info = SourceHostsFile, r.hostsFile
|
||||
case r.dhcp != nil:
|
||||
cs, info = SourceDHCP, r.dhcp
|
||||
case r.rdns != nil:
|
||||
cs, info = SourceRDNS, r.rdns
|
||||
case r.arp != nil:
|
||||
cs, info = SourceARP, r.arp
|
||||
case r.whois != nil:
|
||||
cs = SourceWHOIS
|
||||
}
|
||||
|
||||
if len(info) == 0 {
|
||||
return cs, ""
|
||||
}
|
||||
|
||||
// TODO(s.chzhen): Return the full information.
|
||||
return cs, info[0]
|
||||
}
|
||||
|
||||
// SetInfo sets a host as a client information from the cs.
|
||||
func (r *Runtime) SetInfo(cs Source, hosts []string) {
|
||||
if len(hosts) == 1 && hosts[0] == "" {
|
||||
hosts = []string{}
|
||||
}
|
||||
|
||||
switch cs {
|
||||
case SourceARP:
|
||||
r.arp = hosts
|
||||
case SourceRDNS:
|
||||
r.rdns = hosts
|
||||
case SourceDHCP:
|
||||
r.dhcp = hosts
|
||||
case SourceHostsFile:
|
||||
r.hostsFile = hosts
|
||||
}
|
||||
}
|
||||
|
||||
// WHOIS returns a WHOIS client information.
|
||||
func (r *Runtime) WHOIS() (info *whois.Info) {
|
||||
return r.whois
|
||||
}
|
||||
|
||||
// SetWHOIS sets a WHOIS client information. info must be non-nil.
|
||||
func (r *Runtime) SetWHOIS(info *whois.Info) {
|
||||
r.whois = info
|
||||
}
|
||||
|
||||
// Unset clears a cs information.
|
||||
func (r *Runtime) Unset(cs Source) {
|
||||
switch cs {
|
||||
case SourceWHOIS:
|
||||
r.whois = nil
|
||||
case SourceARP:
|
||||
r.arp = nil
|
||||
case SourceRDNS:
|
||||
r.rdns = nil
|
||||
case SourceDHCP:
|
||||
r.dhcp = nil
|
||||
case SourceHostsFile:
|
||||
r.hostsFile = nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsEmpty returns true if there is no information from any source.
|
||||
func (r *Runtime) IsEmpty() (ok bool) {
|
||||
return r.whois == nil &&
|
||||
r.arp == nil &&
|
||||
r.rdns == nil &&
|
||||
r.dhcp == nil &&
|
||||
r.hostsFile == nil
|
||||
}
|
||||
|
||||
5
internal/configmigrate/configmigrate.go
Normal file
5
internal/configmigrate/configmigrate.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package configmigrate provides a way to upgrade the YAML configuration file.
|
||||
package configmigrate
|
||||
|
||||
// LastSchemaVersion is the most recent schema version.
|
||||
const LastSchemaVersion uint = 28
|
||||
@@ -1,9 +1,10 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
@@ -1646,3 +1647,84 @@ func TestUpgradeSchema26to27(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgradeSchema27to28(t *testing.T) {
|
||||
const newSchemaVer = 28
|
||||
|
||||
testCases := []struct {
|
||||
in yobj
|
||||
want yobj
|
||||
name string
|
||||
}{{
|
||||
name: "empty",
|
||||
in: yobj{},
|
||||
want: yobj{
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
}, {
|
||||
name: "load_balance",
|
||||
in: yobj{
|
||||
"dns": yobj{
|
||||
"all_servers": false,
|
||||
"fastest_addr": false,
|
||||
},
|
||||
},
|
||||
want: yobj{
|
||||
"dns": yobj{
|
||||
"upstream_mode": dnsforward.UpstreamModeLoadBalance,
|
||||
},
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
}, {
|
||||
name: "parallel",
|
||||
in: yobj{
|
||||
"dns": yobj{
|
||||
"all_servers": true,
|
||||
"fastest_addr": false,
|
||||
},
|
||||
},
|
||||
want: yobj{
|
||||
"dns": yobj{
|
||||
"upstream_mode": dnsforward.UpstreamModeParallel,
|
||||
},
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
}, {
|
||||
name: "parallel_fastest",
|
||||
in: yobj{
|
||||
"dns": yobj{
|
||||
"all_servers": true,
|
||||
"fastest_addr": true,
|
||||
},
|
||||
},
|
||||
want: yobj{
|
||||
"dns": yobj{
|
||||
"upstream_mode": dnsforward.UpstreamModeParallel,
|
||||
},
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
}, {
|
||||
name: "load_balance",
|
||||
in: yobj{
|
||||
"dns": yobj{
|
||||
"all_servers": false,
|
||||
"fastest_addr": true,
|
||||
},
|
||||
},
|
||||
want: yobj{
|
||||
"dns": yobj{
|
||||
"upstream_mode": dnsforward.UpstreamModeFastestAddr,
|
||||
},
|
||||
"schema_version": newSchemaVer,
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := migrateTo28(tc.in)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.want, tc.in)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
// Package confmigrate provides a way to upgrade the YAML configuration file.
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -9,9 +8,6 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// LastSchemaVersion is the most recent schema version.
|
||||
const LastSchemaVersion uint = 27
|
||||
|
||||
// Config is a the configuration for initializing a [Migrator].
|
||||
type Config struct {
|
||||
// WorkingDir is an absolute path to the working directory of AdGuardHome.
|
||||
@@ -123,6 +119,7 @@ func (m *Migrator) upgradeConfigSchema(current, target uint, diskConf yobj) (err
|
||||
24: migrateTo25,
|
||||
25: migrateTo26,
|
||||
26: migrateTo27,
|
||||
27: migrateTo28,
|
||||
}
|
||||
|
||||
for i, migrate := range upgrades[current:target] {
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate_test
|
||||
package configmigrate_test
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/confmigrate"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/configmigrate"
|
||||
"github.com/AdguardTeam/golibs/testutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -200,7 +200,7 @@ func TestMigrateConfig_Migrate(t *testing.T) {
|
||||
wantBody, err := fs.ReadFile(testdata, path.Join(t.Name(), outputFileName))
|
||||
require.NoError(t, err)
|
||||
|
||||
migrator := confmigrate.New(&confmigrate.Config{
|
||||
migrator := configmigrate.New(&configmigrate.Config{
|
||||
WorkingDir: t.Name(),
|
||||
})
|
||||
newBody, upgraded, err := migrator.Migrate(body, tc.targetVersion)
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo11 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import (
|
||||
"time"
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo13 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo14 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo15 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo16 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo17 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo18 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import "github.com/AdguardTeam/golibs/log"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import (
|
||||
"time"
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo21 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo24 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo25 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo26 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo27 performs the following changes:
|
||||
//
|
||||
47
internal/configmigrate/v28.go
Normal file
47
internal/configmigrate/v28.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package configmigrate
|
||||
|
||||
import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
)
|
||||
|
||||
// migrateTo28 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
// 'dns':
|
||||
// 'all_servers': true
|
||||
// 'fastest_addr': true
|
||||
// # …
|
||||
// # …
|
||||
//
|
||||
// # AFTER:
|
||||
// 'dns':
|
||||
// 'upstream_mode': 'parallel'
|
||||
// # …
|
||||
// # …
|
||||
func migrateTo28(diskConf yobj) (err error) {
|
||||
diskConf["schema_version"] = 28
|
||||
|
||||
dns, ok, err := fieldVal[yobj](diskConf, "dns")
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
allServers, _, _ := fieldVal[bool](dns, "all_servers")
|
||||
fastestAddr, _, _ := fieldVal[bool](dns, "fastest_addr")
|
||||
|
||||
var upstreamModeType dnsforward.UpstreamMode
|
||||
if allServers {
|
||||
upstreamModeType = dnsforward.UpstreamModeParallel
|
||||
} else if fastestAddr {
|
||||
upstreamModeType = dnsforward.UpstreamModeFastestAddr
|
||||
} else {
|
||||
upstreamModeType = dnsforward.UpstreamModeLoadBalance
|
||||
}
|
||||
|
||||
dns["upstream_mode"] = upstreamModeType
|
||||
|
||||
delete(dns, "all_servers")
|
||||
delete(dns, "fastest_addr")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo3 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo4 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
import "fmt"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo7 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo8 performs the following changes:
|
||||
//
|
||||
@@ -1,4 +1,4 @@
|
||||
package confmigrate
|
||||
package configmigrate
|
||||
|
||||
// migrateTo9 performs the following changes:
|
||||
//
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user