Pull request 1892: next-imp-dnssvc

Squashed commit of the following:

commit 770a3f338ecb270fcff7792a4ffe3cf95492d2ae
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 27 20:10:39 2023 +0300

    dnssvc: fix test for darwin

commit 6564abcc0904784ff3787e1a046d665519a108b3
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 27 19:57:19 2023 +0300

    all: fix .gitignore, tests

commit 3ff1be0462b3adea81d98b1f65eeb685d2d72030
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Jun 27 19:30:05 2023 +0300

    next: add conf example; imp dnssvc
This commit is contained in:
Ainar Garipov 2023-06-28 12:48:53 +03:00
parent d88181343c
commit cf7c12c97b
13 changed files with 234 additions and 138 deletions

5
.gitignore vendored
View File

@ -16,10 +16,13 @@
/dist/ /dist/
/filtering/tests/filtering.TestLotsOfRules*.pprof /filtering/tests/filtering.TestLotsOfRules*.pprof
/filtering/tests/top-1m.csv /filtering/tests/top-1m.csv
/internal/next/AdGuardHome.yaml
/launchpad_credentials /launchpad_credentials
/querylog.json* /querylog.json*
/snapcraft_login /snapcraft_login
AdGuardHome* AdGuardHome
AdGuardHome.exe
AdGuardHome.yaml*
coverage.txt coverage.txt
node_modules/ node_modules/

View File

@ -0,0 +1,26 @@
# This is a file showing example configuration for AdGuard Home.
#
# TODO(a.garipov): Move to the top level once the rewrite is over.
dns:
addresses:
- '0.0.0.0:53'
bootstrap_dns:
- '9.9.9.10'
- '149.112.112.10'
- '2620:fe::10'
- '2620:fe::fe:10'
upstream_dns:
- '8.8.8.8'
dns64_prefixes:
- '1234::/64'
upstream_timeout: 1s
bootstrap_prefer_ipv6: true
use_dns64: true
http:
addresses:
- '0.0.0.0:3000'
secure_addresses: []
timeout: 5s
force_https: true
verbose: true

View File

@ -129,8 +129,8 @@ type commandLineOption struct {
// AdGuard Home. // AdGuard Home.
var commandLineOptions = []*commandLineOption{ var commandLineOptions = []*commandLineOption{
confFileIdx: { confFileIdx: {
// TODO(a.garipov): Remove the ".1" when the new code is ready. // TODO(a.garipov): Remove the directory when the new code is ready.
defaultValue: "AdGuardHome.1.yaml", defaultValue: "internal/next/AdGuardHome.yaml",
description: "Path to the config file.", description: "Path to the config file.",
long: "config", long: "config",
short: "c", short: "c",

View File

@ -26,7 +26,10 @@ type dnsConfig struct {
Addresses []netip.AddrPort `yaml:"addresses"` Addresses []netip.AddrPort `yaml:"addresses"`
BootstrapDNS []string `yaml:"bootstrap_dns"` BootstrapDNS []string `yaml:"bootstrap_dns"`
UpstreamDNS []string `yaml:"upstream_dns"` UpstreamDNS []string `yaml:"upstream_dns"`
DNS64Prefixes []netip.Prefix `yaml:"dns64_prefixes"`
UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"` UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"`
BootstrapPreferIPv6 bool `yaml:"bootstrap_prefer_ipv6"`
UseDNS64 bool `yaml:"use_dns64"`
} }
// httpConfig is the on-disk web API configuration. // httpConfig is the on-disk web API configuration.

View File

@ -14,7 +14,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc" "github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc" "github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -64,12 +63,6 @@ func New(
return nil, err return nil, err
} }
// TODO(a.garipov): Move into a separate function and add other logging
// settings.
if conf.Verbose {
log.SetLevel(log.DEBUG)
}
// TODO(a.garipov): Validate the configuration structure. Return an error // TODO(a.garipov): Validate the configuration structure. Return an error
// if it's incorrect. // if it's incorrect.
@ -105,7 +98,10 @@ func (m *Manager) assemble(
Addresses: conf.DNS.Addresses, Addresses: conf.DNS.Addresses,
BootstrapServers: conf.DNS.BootstrapDNS, BootstrapServers: conf.DNS.BootstrapDNS,
UpstreamServers: conf.DNS.UpstreamDNS, UpstreamServers: conf.DNS.UpstreamDNS,
DNS64Prefixes: conf.DNS.DNS64Prefixes,
UpstreamTimeout: conf.DNS.UpstreamTimeout.Duration, UpstreamTimeout: conf.DNS.UpstreamTimeout.Duration,
BootstrapPreferIPv6: conf.DNS.BootstrapPreferIPv6,
UseDNS64: conf.DNS.UseDNS64,
} }
err = m.updateDNS(ctx, dnsConf) err = m.updateDNS(ctx, dnsConf)
if err != nil { if err != nil {

View File

@ -0,0 +1,35 @@
package dnssvc
import (
"net/netip"
"time"
)
// Config is the AdGuard Home DNS service configuration structure.
//
// TODO(a.garipov): Add timeout for incoming requests.
type Config struct {
// Addresses are the addresses on which to serve plain DNS queries.
Addresses []netip.AddrPort
// BootstrapServers are the addresses of DNS servers used for bootstrapping
// the upstream DNS server addresses.
BootstrapServers []string
// UpstreamServers are the upstream DNS server addresses to use.
UpstreamServers []string
// DNS64Prefixes is a slice of NAT64 prefixes to be used for DNS64. See
// also [Config.UseDNS64].
DNS64Prefixes []netip.Prefix
// UpstreamTimeout is the timeout for upstream requests.
UpstreamTimeout time.Duration
// BootstrapPreferIPv6, if true, instructs the bootstrapper to prefer IPv6
// addresses to IPv4 ones when bootstrapping.
BootstrapPreferIPv6 bool
// UseDNS64, if true, enables DNS64 protection for incoming requests.
UseDNS64 bool
}

View File

@ -19,40 +19,20 @@ import (
"github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/dnsproxy/upstream"
) )
// Config is the AdGuard Home DNS service configuration structure.
//
// TODO(a.garipov): Add timeout for incoming requests.
type Config struct {
// Addresses are the addresses on which to serve plain DNS queries.
Addresses []netip.AddrPort
// Upstreams are the DNS upstreams to use. If not set, upstreams are
// created using data from BootstrapServers, UpstreamServers, and
// UpstreamTimeout.
//
// TODO(a.garipov): Think of a better scheme. Those other three parameters
// are here only to make Config work properly.
Upstreams []upstream.Upstream
// BootstrapServers are the addresses for bootstrapping the upstream DNS
// server addresses.
BootstrapServers []string
// UpstreamServers are the upstream DNS server addresses to use.
UpstreamServers []string
// UpstreamTimeout is the timeout for upstream requests.
UpstreamTimeout time.Duration
}
// Service is the AdGuard Home DNS service. A nil *Service is a valid // Service is the AdGuard Home DNS service. A nil *Service is a valid
// [agh.Service] that does nothing. // [agh.Service] that does nothing.
//
// TODO(a.garipov): Consider saving a [*proxy.Config] instance for those
// fields that are only used in [New] and [Service.Config].
type Service struct { type Service struct {
proxy *proxy.Proxy proxy *proxy.Proxy
bootstraps []string bootstraps []string
upstreams []string upstreams []string
dns64Prefixes []netip.Prefix
upsTimeout time.Duration upsTimeout time.Duration
running atomic.Bool running atomic.Bool
bootstrapPreferIPv6 bool
useDNS64 bool
} }
// New returns a new properly initialized *Service. If c is nil, svc is a nil // New returns a new properly initialized *Service. If c is nil, svc is a nil
@ -66,22 +46,21 @@ func New(c *Config) (svc *Service, err error) {
svc = &Service{ svc = &Service{
bootstraps: c.BootstrapServers, bootstraps: c.BootstrapServers,
upstreams: c.UpstreamServers, upstreams: c.UpstreamServers,
dns64Prefixes: c.DNS64Prefixes,
upsTimeout: c.UpstreamTimeout, upsTimeout: c.UpstreamTimeout,
bootstrapPreferIPv6: c.BootstrapPreferIPv6,
useDNS64: c.UseDNS64,
} }
var upstreams []upstream.Upstream upstreams, err := addressesToUpstreams(
if len(c.Upstreams) > 0 {
upstreams = c.Upstreams
} else {
upstreams, err = addressesToUpstreams(
c.UpstreamServers, c.UpstreamServers,
c.BootstrapServers, c.BootstrapServers,
c.UpstreamTimeout, c.UpstreamTimeout,
c.BootstrapPreferIPv6,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("converting upstreams: %w", err) return nil, fmt.Errorf("converting upstreams: %w", err)
} }
}
svc.proxy = &proxy.Proxy{ svc.proxy = &proxy.Proxy{
Config: proxy.Config{ Config: proxy.Config{
@ -90,6 +69,8 @@ func New(c *Config) (svc *Service, err error) {
UpstreamConfig: &proxy.UpstreamConfig{ UpstreamConfig: &proxy.UpstreamConfig{
Upstreams: upstreams, Upstreams: upstreams,
}, },
UseDNS64: c.UseDNS64,
DNS64Prefs: c.DNS64Prefixes,
}, },
} }
@ -108,12 +89,14 @@ func addressesToUpstreams(
upsStrs []string, upsStrs []string,
bootstraps []string, bootstraps []string,
timeout time.Duration, timeout time.Duration,
preferIPv6 bool,
) (upstreams []upstream.Upstream, err error) { ) (upstreams []upstream.Upstream, err error) {
upstreams = make([]upstream.Upstream, len(upsStrs)) upstreams = make([]upstream.Upstream, len(upsStrs))
for i, upsStr := range upsStrs { for i, upsStr := range upsStrs {
upstreams[i], err = upstream.AddressToUpstream(upsStr, &upstream.Options{ upstreams[i], err = upstream.AddressToUpstream(upsStr, &upstream.Options{
Bootstrap: bootstraps, Bootstrap: bootstraps,
Timeout: timeout, Timeout: timeout,
PreferIPv6: preferIPv6,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("upstream at index %d: %w", i, err) return nil, fmt.Errorf("upstream at index %d: %w", i, err)
@ -209,7 +192,10 @@ func (svc *Service) Config() (c *Config) {
Addresses: addrs, Addresses: addrs,
BootstrapServers: svc.bootstraps, BootstrapServers: svc.bootstraps,
UpstreamServers: svc.upstreams, UpstreamServers: svc.upstreams,
DNS64Prefixes: svc.dns64Prefixes,
UpstreamTimeout: svc.upsTimeout, UpstreamTimeout: svc.upsTimeout,
BootstrapPreferIPv6: svc.bootstrapPreferIPv6,
UseDNS64: svc.useDNS64,
} }
return c return c

View File

@ -6,10 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc" "github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -21,36 +18,55 @@ func TestMain(m *testing.M) {
} }
// testTimeout is the common timeout for tests. // testTimeout is the common timeout for tests.
const testTimeout = 100 * time.Millisecond const testTimeout = 1 * time.Second
func TestService(t *testing.T) { func TestService(t *testing.T) {
const ( const (
bootstrapAddr = "bootstrap.example" listenAddr = "127.0.0.1:0"
bootstrapAddr = "127.0.0.1:0"
upstreamAddr = "upstream.example" upstreamAddr = "upstream.example"
closeErr errors.Error = "closing failed"
) )
ups := &aghtest.UpstreamMock{ upstreamErrCh := make(chan error, 1)
OnAddress: func() (addr string) { upstreamStartedCh := make(chan struct{})
return upstreamAddr upstreamSrv := &dns.Server{
}, Addr: bootstrapAddr,
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) { Net: "udp",
resp = (&dns.Msg{}).SetReply(req) Handler: dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
pt := testutil.PanicT{}
return resp, nil resp := (&dns.Msg{}).SetReply(req)
}, resp.Answer = append(resp.Answer, &dns.A{
OnClose: func() (err error) { Hdr: dns.RR_Header{},
return closeErr A: netip.MustParseAddrPort(bootstrapAddr).Addr().AsSlice(),
}, })
writeErr := w.WriteMsg(resp)
require.NoError(pt, writeErr)
}),
NotifyStartedFunc: func() { close(upstreamStartedCh) },
} }
go func() {
listenErr := upstreamSrv.ListenAndServe()
if listenErr != nil {
// Log these immediately to see what happens.
t.Logf("upstream listen error: %s", listenErr)
}
upstreamErrCh <- listenErr
}()
_, _ = testutil.RequireReceive(t, upstreamStartedCh, testTimeout)
c := &dnssvc.Config{ c := &dnssvc.Config{
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:0")}, Addresses: []netip.AddrPort{netip.MustParseAddrPort(listenAddr)},
Upstreams: []upstream.Upstream{ups}, BootstrapServers: []string{upstreamSrv.PacketConn.LocalAddr().String()},
BootstrapServers: []string{bootstrapAddr},
UpstreamServers: []string{upstreamAddr}, UpstreamServers: []string{upstreamAddr},
DNS64Prefixes: nil,
UpstreamTimeout: testTimeout, UpstreamTimeout: testTimeout,
BootstrapPreferIPv6: false,
UseDNS64: false,
} }
svc, err := dnssvc.New(c) svc, err := dnssvc.New(c)
@ -82,8 +98,14 @@ func TestService(t *testing.T) {
defer cancel() defer cancel()
cli := &dns.Client{} cli := &dns.Client{}
resp, _, excErr := cli.ExchangeContext(ctx, req, addr.String())
require.NoError(t, excErr) var resp *dns.Msg
require.Eventually(t, func() (ok bool) {
var excErr error
resp, _, excErr = cli.ExchangeContext(ctx, req, addr.String())
return excErr == nil
}, testTimeout, testTimeout/10)
assert.NotNil(t, resp) assert.NotNil(t, resp)
}) })
@ -92,5 +114,12 @@ func TestService(t *testing.T) {
defer cancel() defer cancel()
err = svc.Shutdown(ctx) err = svc.Shutdown(ctx)
require.ErrorIs(t, err, closeErr) require.NoError(t, err)
err = upstreamSrv.Shutdown()
require.NoError(t, err)
err, ok := testutil.RequireReceive(t, upstreamErrCh, testTimeout)
require.True(t, ok)
require.NoError(t, err)
} }

View File

@ -20,7 +20,10 @@ type ReqPatchSettingsDNS struct {
Addresses []netip.AddrPort `json:"addresses"` Addresses []netip.AddrPort `json:"addresses"`
BootstrapServers []string `json:"bootstrap_servers"` BootstrapServers []string `json:"bootstrap_servers"`
UpstreamServers []string `json:"upstream_servers"` UpstreamServers []string `json:"upstream_servers"`
DNS64Prefixes []netip.Prefix `json:"dns64_prefixes"`
UpstreamTimeout JSONDuration `json:"upstream_timeout"` UpstreamTimeout JSONDuration `json:"upstream_timeout"`
BootstrapPreferIPv6 bool `json:"bootstrap_prefer_ipv6"`
UseDNS64 bool `json:"use_dns64"`
} }
// HTTPAPIDNSSettings are the DNS settings as used by the HTTP API. See the // HTTPAPIDNSSettings are the DNS settings as used by the HTTP API. See the
@ -31,7 +34,10 @@ type HTTPAPIDNSSettings struct {
Addresses []netip.AddrPort `json:"addresses"` Addresses []netip.AddrPort `json:"addresses"`
BootstrapServers []string `json:"bootstrap_servers"` BootstrapServers []string `json:"bootstrap_servers"`
UpstreamServers []string `json:"upstream_servers"` UpstreamServers []string `json:"upstream_servers"`
DNS64Prefixes []netip.Prefix `json:"dns64_prefixes"`
UpstreamTimeout JSONDuration `json:"upstream_timeout"` UpstreamTimeout JSONDuration `json:"upstream_timeout"`
BootstrapPreferIPv6 bool `json:"bootstrap_prefer_ipv6"`
UseDNS64 bool `json:"use_dns64"`
} }
// handlePatchSettingsDNS is the handler for the PATCH /api/v1/settings/dns HTTP // handlePatchSettingsDNS is the handler for the PATCH /api/v1/settings/dns HTTP
@ -56,7 +62,10 @@ func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Reques
Addresses: req.Addresses, Addresses: req.Addresses,
BootstrapServers: req.BootstrapServers, BootstrapServers: req.BootstrapServers,
UpstreamServers: req.UpstreamServers, UpstreamServers: req.UpstreamServers,
DNS64Prefixes: req.DNS64Prefixes,
UpstreamTimeout: time.Duration(req.UpstreamTimeout), UpstreamTimeout: time.Duration(req.UpstreamTimeout),
BootstrapPreferIPv6: req.BootstrapPreferIPv6,
UseDNS64: req.UseDNS64,
} }
ctx := r.Context() ctx := r.Context()
@ -79,6 +88,9 @@ func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Reques
Addresses: newConf.Addresses, Addresses: newConf.Addresses,
BootstrapServers: newConf.BootstrapServers, BootstrapServers: newConf.BootstrapServers,
UpstreamServers: newConf.UpstreamServers, UpstreamServers: newConf.UpstreamServers,
DNS64Prefixes: newConf.DNS64Prefixes,
UpstreamTimeout: JSONDuration(newConf.UpstreamTimeout), UpstreamTimeout: JSONDuration(newConf.UpstreamTimeout),
BootstrapPreferIPv6: newConf.BootstrapPreferIPv6,
UseDNS64: newConf.UseDNS64,
}) })
} }

View File

@ -23,7 +23,10 @@ func TestService_HandlePatchSettingsDNS(t *testing.T) {
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.1.1:53")}, Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.1.1:53")},
BootstrapServers: []string{"1.0.0.1"}, BootstrapServers: []string{"1.0.0.1"},
UpstreamServers: []string{"1.1.1.1"}, UpstreamServers: []string{"1.1.1.1"},
DNS64Prefixes: []netip.Prefix{netip.MustParsePrefix("1234::/64")},
UpstreamTimeout: websvc.JSONDuration(2 * time.Second), UpstreamTimeout: websvc.JSONDuration(2 * time.Second),
BootstrapPreferIPv6: true,
UseDNS64: true,
} }
var started atomic.Bool var started atomic.Bool
@ -54,7 +57,10 @@ func TestService_HandlePatchSettingsDNS(t *testing.T) {
"addresses": wantDNS.Addresses, "addresses": wantDNS.Addresses,
"bootstrap_servers": wantDNS.BootstrapServers, "bootstrap_servers": wantDNS.BootstrapServers,
"upstream_servers": wantDNS.UpstreamServers, "upstream_servers": wantDNS.UpstreamServers,
"dns64_prefixes": wantDNS.DNS64Prefixes,
"upstream_timeout": wantDNS.UpstreamTimeout, "upstream_timeout": wantDNS.UpstreamTimeout,
"bootstrap_prefer_ipv6": wantDNS.BootstrapPreferIPv6,
"use_dns64": wantDNS.UseDNS64,
} }
respBody := httpPatch(t, u, req, http.StatusOK) respBody := httpPatch(t, u, req, http.StatusOK)

View File

@ -30,7 +30,10 @@ func (svc *Service) handleGetSettingsAll(w http.ResponseWriter, r *http.Request)
Addresses: dnsConf.Addresses, Addresses: dnsConf.Addresses,
BootstrapServers: dnsConf.BootstrapServers, BootstrapServers: dnsConf.BootstrapServers,
UpstreamServers: dnsConf.UpstreamServers, UpstreamServers: dnsConf.UpstreamServers,
DNS64Prefixes: dnsConf.DNS64Prefixes,
UpstreamTimeout: JSONDuration(dnsConf.UpstreamTimeout), UpstreamTimeout: JSONDuration(dnsConf.UpstreamTimeout),
BootstrapPreferIPv6: dnsConf.BootstrapPreferIPv6,
UseDNS64: dnsConf.UseDNS64,
}, },
HTTP: &HTTPAPIHTTPSettings{ HTTP: &HTTPAPIHTTPSettings{
Addresses: httpConf.Addresses, Addresses: httpConf.Addresses,

View File

@ -24,6 +24,7 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
BootstrapServers: []string{"94.140.14.140", "94.140.14.141"}, BootstrapServers: []string{"94.140.14.140", "94.140.14.141"},
UpstreamServers: []string{"94.140.14.14", "1.1.1.1"}, UpstreamServers: []string{"94.140.14.14", "1.1.1.1"},
UpstreamTimeout: websvc.JSONDuration(1 * time.Second), UpstreamTimeout: websvc.JSONDuration(1 * time.Second),
BootstrapPreferIPv6: true,
} }
wantWeb := &websvc.HTTPAPIHTTPSettings{ wantWeb := &websvc.HTTPAPIHTTPSettings{
@ -40,6 +41,7 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
UpstreamServers: wantDNS.UpstreamServers, UpstreamServers: wantDNS.UpstreamServers,
BootstrapServers: wantDNS.BootstrapServers, BootstrapServers: wantDNS.BootstrapServers,
UpstreamTimeout: time.Duration(wantDNS.UpstreamTimeout), UpstreamTimeout: time.Duration(wantDNS.UpstreamTimeout),
BootstrapPreferIPv6: true,
}) })
require.NoError(t, err) require.NoError(t, err)

View File

@ -6,7 +6,6 @@ import (
"sync/atomic" "sync/atomic"
"testing" "testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghchan"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest" "github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -26,9 +25,6 @@ func TestWaitListener_Accept(t *testing.T) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(1) wg.Add(1)
done := make(chan struct{})
go aghchan.MustReceive(done, testTimeout)
go func() { go func() {
var wrapper net.Listener = &waitListener{ var wrapper net.Listener = &waitListener{
Listener: l, Listener: l,
@ -39,7 +35,6 @@ func TestWaitListener_Accept(t *testing.T) {
}() }()
wg.Wait() wg.Wait()
close(done)
assert.True(t, accepted.Load()) assert.Eventually(t, accepted.Load, testTimeout, testTimeout/10)
} }