From 09e170e13d022755838506209366e74a436fbf81 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Wed, 29 Nov 2023 12:48:09 +0000 Subject: [PATCH] feat(localdns): auto-detect local DNS resolvers - Local DNS resolvers default to system DNS servers if not set - Local DNS middleware enabled by default - Add environment variable `MIDDLEWARE_LOCALDNS_ENABLED=on` --- Dockerfile | 1 + README.md | 3 +- internal/config/localdns.go | 16 ++++- internal/config/settings.go | 1 + internal/setup/dns.go | 4 +- pkg/middlewares/localdns/middleware_test.go | 67 ++++++++------------- pkg/middlewares/localdns/settings.go | 14 +---- 7 files changed, 50 insertions(+), 56 deletions(-) diff --git a/Dockerfile b/Dockerfile index c4740498..75cbcf8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,6 +81,7 @@ ENV \ MIDDLEWARE_LOG_DIRECTORY=/var/log/dns/ \ MIDDLEWARE_LOG_REQUESTS=on \ MIDDLEWARE_LOG_RESPONSES=off \ + MIDDLEWARE_LOCALDNS_ENABLED=on \ MIDDLEWARE_LOCALDNS_RESOLVERS= \ CACHE_TYPE=lru \ CACHE_LRU_MAX_ENTRIES=10000 \ diff --git a/README.md b/README.md index 890813a1..414d8014 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,8 @@ If you're running Kubernetes, there is a separate article on [how to set up K8s] | `METRICS_TYPE` | `noop` | `noop` or `prometheus` | | `METRICS_PROMETHEUS_ADDRESS` | `:9090` | HTTP Prometheus server listening address | | `METRICS_PROMETHEUS_SUBSYSTEM` | `dns` | Prometheus metrics prefix/subsystem | -| `MIDDLEWARE_LOCALDNS_RESOLVERS` | | Comma separated list of local DNS resolvers to use for local names DNS requests | +| `MIDDLEWARE_LOCALDNS_ENABLED` | `on` | Enable or disable the local DNS middleware | +| `MIDDLEWARE_LOCALDNS_RESOLVERS` | Local DNS servers | Comma separated list of local DNS resolvers to use for local names DNS requests | | `CHECK_DNS` | `on` | `on` or `off`. Check resolving github.com using `127.0.0.1:53` at start | | `UPDATE_PERIOD` | `24h` | Period to update block lists and restart Unbound. Set to `0` to disable. | diff --git a/internal/config/localdns.go b/internal/config/localdns.go index 665c9cbc..e36af0b7 100644 --- a/internal/config/localdns.go +++ b/internal/config/localdns.go @@ -5,11 +5,14 @@ import ( "fmt" "net/netip" + "github.com/qdm12/dns/v2/pkg/nameserver" + "github.com/qdm12/gosettings" "github.com/qdm12/gosettings/reader" "github.com/qdm12/gotree" ) type LocalDNS struct { + Enabled *bool Resolvers []netip.AddrPort } @@ -18,6 +21,12 @@ var ( ErrLocalResolverPortIsZero = errors.New("local resolver port is zero") ) +func (l *LocalDNS) setDefault() { + l.Enabled = gosettings.DefaultPointer(l.Enabled, true) + l.Resolvers = gosettings.DefaultSlice(l.Resolvers, + nameserver.GetDNSServers()) +} + func (l *LocalDNS) validate() (err error) { for _, resolver := range l.Resolvers { switch { @@ -38,7 +47,7 @@ func (l *LocalDNS) String() string { } func (l *LocalDNS) ToLinesNode() (node *gotree.Node) { - if len(l.Resolvers) == 0 { + if !*l.Enabled { return gotree.New("Local DNS middleware: disabled") } @@ -52,6 +61,11 @@ func (l *LocalDNS) ToLinesNode() (node *gotree.Node) { } func (l *LocalDNS) read(reader *reader.Reader) (err error) { + l.Enabled, err = reader.BoolPtr("MIDDLEWARE_LOCALDNS_ENABLED") + if err != nil { + return err + } + l.Resolvers, err = reader.CSVNetipAddrPorts("MIDDLEWARE_LOCALDNS_RESOLVERS") if err != nil { return err diff --git a/internal/config/settings.go b/internal/config/settings.go index 69527ce5..4926d05b 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -46,6 +46,7 @@ func (s *Settings) SetDefaults() { s.Log.setDefaults() s.MiddlewareLog.setDefaults() s.Metrics.setDefaults() + s.LocalDNS.setDefault() s.CheckDNS = gosettings.DefaultPointer(s.CheckDNS, true) const defaultUpdaterPeriod = 24 * time.Hour s.UpdatePeriod = gosettings.DefaultPointer(s.UpdatePeriod, defaultUpdaterPeriod) diff --git a/internal/setup/dns.go b/internal/setup/dns.go index acd50208..126c33f5 100644 --- a/internal/setup/dns.go +++ b/internal/setup/dns.go @@ -61,9 +61,9 @@ func setupMiddlewares(userSettings config.Settings, cache Cache, } middlewares = append(middlewares, cacheMiddleware) - if len(userSettings.LocalDNS.Resolvers) > 0 { + if *userSettings.LocalDNS.Enabled { localDNSMiddleware, err := localdns.New(localdns.Settings{ - Resolvers: userSettings.LocalDNS.Resolvers, + Resolvers: userSettings.LocalDNS.Resolvers, // possibly auto-detected Logger: logger, }) if err != nil { diff --git a/pkg/middlewares/localdns/middleware_test.go b/pkg/middlewares/localdns/middleware_test.go index ef4e59cb..527ed241 100644 --- a/pkg/middlewares/localdns/middleware_test.go +++ b/pkg/middlewares/localdns/middleware_test.go @@ -12,45 +12,30 @@ import ( func Test_New(t *testing.T) { t.Parallel() - t.Run("resolvers_not_set", func(t *testing.T) { - t.Parallel() - - settings := Settings{} - - middleware, err := New(settings) - assert.ErrorIs(t, err, ErrResolversNotSet) - require.EqualError(t, err, "validating settings: resolvers are not set") - assert.Nil(t, middleware) - }) - - t.Run("success", func(t *testing.T) { - t.Parallel() - - settings := Settings{ - Resolvers: []netip.AddrPort{ - netip.AddrPortFrom(netip.MustParseAddr("1.2.3.4"), 53), - }, - Logger: NewMockLogger(nil), - } - - middleware, err := New(settings) - require.NoError(t, err) - - expectedMiddleware := &Middleware{ - settings: settings, - } - assert.Equal(t, expectedMiddleware, middleware) - - next := dns.HandlerFunc(func(rw dns.ResponseWriter, m *dns.Msg) {}) - handler := middleware.Wrap(next) - - request := &dns.Msg{Question: []dns.Question{ - {Name: "domain.com."}, - }} - writer := NewMockResponseWriter(nil) - handler.ServeDNS(writer, request) - - err = middleware.Stop() - require.NoError(t, err) - }) + settings := Settings{ + Resolvers: []netip.AddrPort{ + netip.AddrPortFrom(netip.MustParseAddr("1.2.3.4"), 53), + }, + Logger: NewMockLogger(nil), + } + + middleware, err := New(settings) + require.NoError(t, err) + + expectedMiddleware := &Middleware{ + settings: settings, + } + assert.Equal(t, expectedMiddleware, middleware) + + next := dns.HandlerFunc(func(rw dns.ResponseWriter, m *dns.Msg) {}) + handler := middleware.Wrap(next) + + request := &dns.Msg{Question: []dns.Question{ + {Name: "domain.com."}, + }} + writer := NewMockResponseWriter(nil) + handler.ServeDNS(writer, request) + + err = middleware.Stop() + require.NoError(t, err) } diff --git a/pkg/middlewares/localdns/settings.go b/pkg/middlewares/localdns/settings.go index f17b44e3..2f658f05 100644 --- a/pkg/middlewares/localdns/settings.go +++ b/pkg/middlewares/localdns/settings.go @@ -1,11 +1,10 @@ package localdns import ( - "errors" - "fmt" "net/netip" "github.com/qdm12/dns/v2/pkg/log/noop" + "github.com/qdm12/dns/v2/pkg/nameserver" "github.com/qdm12/gosettings" "github.com/qdm12/gotree" ) @@ -14,7 +13,7 @@ type Settings struct { // Resolvers is the list of resolvers to use to resolve the // local domain names. They are each tried after the other // in order, until one returns an answer for the question. - // This field must be set and non empty. + // If left empty, the local nameservers found are used. Resolvers []netip.AddrPort // Logger is the logger to use. // It defaults to a No-op implementation. @@ -22,18 +21,11 @@ type Settings struct { } func (s *Settings) SetDefaults() { + s.Resolvers = gosettings.DefaultSlice(s.Resolvers, nameserver.GetDNSServers()) s.Logger = gosettings.DefaultComparable[Logger](s.Logger, noop.New()) } -var ( - ErrResolversNotSet = errors.New("resolvers are not set") -) - func (s *Settings) Validate() (err error) { - if len(s.Resolvers) == 0 { - return fmt.Errorf("%w", ErrResolversNotSet) - } - return nil }