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/
/filtering/tests/filtering.TestLotsOfRules*.pprof
/filtering/tests/top-1m.csv
/internal/next/AdGuardHome.yaml
/launchpad_credentials
/querylog.json*
/snapcraft_login
AdGuardHome*
AdGuardHome
AdGuardHome.exe
AdGuardHome.yaml*
coverage.txt
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.
var commandLineOptions = []*commandLineOption{
confFileIdx: {
// TODO(a.garipov): Remove the ".1" when the new code is ready.
defaultValue: "AdGuardHome.1.yaml",
// TODO(a.garipov): Remove the directory when the new code is ready.
defaultValue: "internal/next/AdGuardHome.yaml",
description: "Path to the config file.",
long: "config",
short: "c",

View File

@ -23,10 +23,13 @@ type config struct {
//
// TODO(a.garipov): Validate.
type dnsConfig struct {
Addresses []netip.AddrPort `yaml:"addresses"`
BootstrapDNS []string `yaml:"bootstrap_dns"`
UpstreamDNS []string `yaml:"upstream_dns"`
UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"`
Addresses []netip.AddrPort `yaml:"addresses"`
BootstrapDNS []string `yaml:"bootstrap_dns"`
UpstreamDNS []string `yaml:"upstream_dns"`
DNS64Prefixes []netip.Prefix `yaml:"dns64_prefixes"`
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.

View File

@ -14,7 +14,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"gopkg.in/yaml.v3"
)
@ -64,12 +63,6 @@ func New(
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
// if it's incorrect.
@ -102,10 +95,13 @@ func (m *Manager) assemble(
start time.Time,
) (err error) {
dnsConf := &dnssvc.Config{
Addresses: conf.DNS.Addresses,
BootstrapServers: conf.DNS.BootstrapDNS,
UpstreamServers: conf.DNS.UpstreamDNS,
UpstreamTimeout: conf.DNS.UpstreamTimeout.Duration,
Addresses: conf.DNS.Addresses,
BootstrapServers: conf.DNS.BootstrapDNS,
UpstreamServers: conf.DNS.UpstreamDNS,
DNS64Prefixes: conf.DNS.DNS64Prefixes,
UpstreamTimeout: conf.DNS.UpstreamTimeout.Duration,
BootstrapPreferIPv6: conf.DNS.BootstrapPreferIPv6,
UseDNS64: conf.DNS.UseDNS64,
}
err = m.updateDNS(ctx, dnsConf)
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"
)
// 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
// [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 {
proxy *proxy.Proxy
bootstraps []string
upstreams []string
upsTimeout time.Duration
running atomic.Bool
proxy *proxy.Proxy
bootstraps []string
upstreams []string
dns64Prefixes []netip.Prefix
upsTimeout time.Duration
running atomic.Bool
bootstrapPreferIPv6 bool
useDNS64 bool
}
// New returns a new properly initialized *Service. If c is nil, svc is a nil
@ -64,23 +44,22 @@ func New(c *Config) (svc *Service, err error) {
}
svc = &Service{
bootstraps: c.BootstrapServers,
upstreams: c.UpstreamServers,
upsTimeout: c.UpstreamTimeout,
bootstraps: c.BootstrapServers,
upstreams: c.UpstreamServers,
dns64Prefixes: c.DNS64Prefixes,
upsTimeout: c.UpstreamTimeout,
bootstrapPreferIPv6: c.BootstrapPreferIPv6,
useDNS64: c.UseDNS64,
}
var upstreams []upstream.Upstream
if len(c.Upstreams) > 0 {
upstreams = c.Upstreams
} else {
upstreams, err = addressesToUpstreams(
c.UpstreamServers,
c.BootstrapServers,
c.UpstreamTimeout,
)
if err != nil {
return nil, fmt.Errorf("converting upstreams: %w", err)
}
upstreams, err := addressesToUpstreams(
c.UpstreamServers,
c.BootstrapServers,
c.UpstreamTimeout,
c.BootstrapPreferIPv6,
)
if err != nil {
return nil, fmt.Errorf("converting upstreams: %w", err)
}
svc.proxy = &proxy.Proxy{
@ -90,6 +69,8 @@ func New(c *Config) (svc *Service, err error) {
UpstreamConfig: &proxy.UpstreamConfig{
Upstreams: upstreams,
},
UseDNS64: c.UseDNS64,
DNS64Prefs: c.DNS64Prefixes,
},
}
@ -108,12 +89,14 @@ func addressesToUpstreams(
upsStrs []string,
bootstraps []string,
timeout time.Duration,
preferIPv6 bool,
) (upstreams []upstream.Upstream, err error) {
upstreams = make([]upstream.Upstream, len(upsStrs))
for i, upsStr := range upsStrs {
upstreams[i], err = upstream.AddressToUpstream(upsStr, &upstream.Options{
Bootstrap: bootstraps,
Timeout: timeout,
Bootstrap: bootstraps,
Timeout: timeout,
PreferIPv6: preferIPv6,
})
if err != nil {
return nil, fmt.Errorf("upstream at index %d: %w", i, err)
@ -206,10 +189,13 @@ func (svc *Service) Config() (c *Config) {
}
c = &Config{
Addresses: addrs,
BootstrapServers: svc.bootstraps,
UpstreamServers: svc.upstreams,
UpstreamTimeout: svc.upsTimeout,
Addresses: addrs,
BootstrapServers: svc.bootstraps,
UpstreamServers: svc.upstreams,
DNS64Prefixes: svc.dns64Prefixes,
UpstreamTimeout: svc.upsTimeout,
BootstrapPreferIPv6: svc.bootstrapPreferIPv6,
UseDNS64: svc.useDNS64,
}
return c

View File

@ -6,10 +6,7 @@ import (
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
@ -21,36 +18,55 @@ func TestMain(m *testing.M) {
}
// testTimeout is the common timeout for tests.
const testTimeout = 100 * time.Millisecond
const testTimeout = 1 * time.Second
func TestService(t *testing.T) {
const (
bootstrapAddr = "bootstrap.example"
listenAddr = "127.0.0.1:0"
bootstrapAddr = "127.0.0.1:0"
upstreamAddr = "upstream.example"
closeErr errors.Error = "closing failed"
)
ups := &aghtest.UpstreamMock{
OnAddress: func() (addr string) {
return upstreamAddr
},
OnExchange: func(req *dns.Msg) (resp *dns.Msg, err error) {
resp = (&dns.Msg{}).SetReply(req)
upstreamErrCh := make(chan error, 1)
upstreamStartedCh := make(chan struct{})
upstreamSrv := &dns.Server{
Addr: bootstrapAddr,
Net: "udp",
Handler: dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
pt := testutil.PanicT{}
return resp, nil
},
OnClose: func() (err error) {
return closeErr
},
resp := (&dns.Msg{}).SetReply(req)
resp.Answer = append(resp.Answer, &dns.A{
Hdr: dns.RR_Header{},
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{
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:0")},
Upstreams: []upstream.Upstream{ups},
BootstrapServers: []string{bootstrapAddr},
UpstreamServers: []string{upstreamAddr},
UpstreamTimeout: testTimeout,
Addresses: []netip.AddrPort{netip.MustParseAddrPort(listenAddr)},
BootstrapServers: []string{upstreamSrv.PacketConn.LocalAddr().String()},
UpstreamServers: []string{upstreamAddr},
DNS64Prefixes: nil,
UpstreamTimeout: testTimeout,
BootstrapPreferIPv6: false,
UseDNS64: false,
}
svc, err := dnssvc.New(c)
@ -82,8 +98,14 @@ func TestService(t *testing.T) {
defer cancel()
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)
})
@ -92,5 +114,12 @@ func TestService(t *testing.T) {
defer cancel()
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

@ -17,10 +17,13 @@ import (
type ReqPatchSettingsDNS struct {
// TODO(a.garipov): Add more as we go.
Addresses []netip.AddrPort `json:"addresses"`
BootstrapServers []string `json:"bootstrap_servers"`
UpstreamServers []string `json:"upstream_servers"`
UpstreamTimeout JSONDuration `json:"upstream_timeout"`
Addresses []netip.AddrPort `json:"addresses"`
BootstrapServers []string `json:"bootstrap_servers"`
UpstreamServers []string `json:"upstream_servers"`
DNS64Prefixes []netip.Prefix `json:"dns64_prefixes"`
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
@ -28,10 +31,13 @@ type ReqPatchSettingsDNS struct {
type HTTPAPIDNSSettings struct {
// TODO(a.garipov): Add more as we go.
Addresses []netip.AddrPort `json:"addresses"`
BootstrapServers []string `json:"bootstrap_servers"`
UpstreamServers []string `json:"upstream_servers"`
UpstreamTimeout JSONDuration `json:"upstream_timeout"`
Addresses []netip.AddrPort `json:"addresses"`
BootstrapServers []string `json:"bootstrap_servers"`
UpstreamServers []string `json:"upstream_servers"`
DNS64Prefixes []netip.Prefix `json:"dns64_prefixes"`
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
@ -53,10 +59,13 @@ func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Reques
}
newConf := &dnssvc.Config{
Addresses: req.Addresses,
BootstrapServers: req.BootstrapServers,
UpstreamServers: req.UpstreamServers,
UpstreamTimeout: time.Duration(req.UpstreamTimeout),
Addresses: req.Addresses,
BootstrapServers: req.BootstrapServers,
UpstreamServers: req.UpstreamServers,
DNS64Prefixes: req.DNS64Prefixes,
UpstreamTimeout: time.Duration(req.UpstreamTimeout),
BootstrapPreferIPv6: req.BootstrapPreferIPv6,
UseDNS64: req.UseDNS64,
}
ctx := r.Context()
@ -76,9 +85,12 @@ func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Reques
}
writeJSONOKResponse(w, r, &HTTPAPIDNSSettings{
Addresses: newConf.Addresses,
BootstrapServers: newConf.BootstrapServers,
UpstreamServers: newConf.UpstreamServers,
UpstreamTimeout: JSONDuration(newConf.UpstreamTimeout),
Addresses: newConf.Addresses,
BootstrapServers: newConf.BootstrapServers,
UpstreamServers: newConf.UpstreamServers,
DNS64Prefixes: newConf.DNS64Prefixes,
UpstreamTimeout: JSONDuration(newConf.UpstreamTimeout),
BootstrapPreferIPv6: newConf.BootstrapPreferIPv6,
UseDNS64: newConf.UseDNS64,
})
}

View File

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

View File

@ -27,10 +27,13 @@ func (svc *Service) handleGetSettingsAll(w http.ResponseWriter, r *http.Request)
// TODO(a.garipov): Add all currently supported parameters.
writeJSONOKResponse(w, r, &RespGetV1SettingsAll{
DNS: &HTTPAPIDNSSettings{
Addresses: dnsConf.Addresses,
BootstrapServers: dnsConf.BootstrapServers,
UpstreamServers: dnsConf.UpstreamServers,
UpstreamTimeout: JSONDuration(dnsConf.UpstreamTimeout),
Addresses: dnsConf.Addresses,
BootstrapServers: dnsConf.BootstrapServers,
UpstreamServers: dnsConf.UpstreamServers,
DNS64Prefixes: dnsConf.DNS64Prefixes,
UpstreamTimeout: JSONDuration(dnsConf.UpstreamTimeout),
BootstrapPreferIPv6: dnsConf.BootstrapPreferIPv6,
UseDNS64: dnsConf.UseDNS64,
},
HTTP: &HTTPAPIHTTPSettings{
Addresses: httpConf.Addresses,

View File

@ -20,10 +20,11 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
// TODO(a.garipov): Add all currently supported parameters.
wantDNS := &websvc.HTTPAPIDNSSettings{
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:53")},
BootstrapServers: []string{"94.140.14.140", "94.140.14.141"},
UpstreamServers: []string{"94.140.14.14", "1.1.1.1"},
UpstreamTimeout: websvc.JSONDuration(1 * time.Second),
Addresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:53")},
BootstrapServers: []string{"94.140.14.140", "94.140.14.141"},
UpstreamServers: []string{"94.140.14.14", "1.1.1.1"},
UpstreamTimeout: websvc.JSONDuration(1 * time.Second),
BootstrapPreferIPv6: true,
}
wantWeb := &websvc.HTTPAPIHTTPSettings{
@ -36,10 +37,11 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
confMgr := newConfigManager()
confMgr.onDNS = func() (s agh.ServiceWithConfig[*dnssvc.Config]) {
c, err := dnssvc.New(&dnssvc.Config{
Addresses: wantDNS.Addresses,
UpstreamServers: wantDNS.UpstreamServers,
BootstrapServers: wantDNS.BootstrapServers,
UpstreamTimeout: time.Duration(wantDNS.UpstreamTimeout),
Addresses: wantDNS.Addresses,
UpstreamServers: wantDNS.UpstreamServers,
BootstrapServers: wantDNS.BootstrapServers,
UpstreamTimeout: time.Duration(wantDNS.UpstreamTimeout),
BootstrapPreferIPv6: true,
})
require.NoError(t, err)

View File

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