From 1511fabeecf24067b1bb36af943826d91712d502 Mon Sep 17 00:00:00 2001 From: Dimitry Kolyshev Date: Mon, 25 Dec 2023 14:16:48 +0300 Subject: [PATCH] Pull request: AG-28771 conf upstream mode Squashed commit of the following: commit afb5a0d8a499bccf7761baea40910f39c92b8a20 Merge: 09ac43c85 abf20c6de Author: Dimitry Kolyshev Date: Mon Dec 25 12:55:45 2023 +0200 Merge remote-tracking branch 'origin/master' into conf-ups-mode commit 09ac43c859ef8cbd3bb0488d1a945589cd59ca19 Author: Dimitry Kolyshev Date: Fri Dec 22 14:36:07 2023 +0200 openapi: imp docs commit d0fbd4349e4bddde73c6e92f75854acfc481ac0d Author: Dimitry Kolyshev Date: Fri Dec 22 11:47:10 2023 +0200 all: changelog commit 105f9c50738733b0736a768fb9ee09d2e7fbf42e Merge: 62a2cf12d 4bc5c346a Author: Dimitry Kolyshev Date: Fri Dec 22 11:27:21 2023 +0200 Merge remote-tracking branch 'origin/master' into conf-ups-mode # Conflicts: # openapi/CHANGELOG.md commit 62a2cf12df694611888e840a5041a9c517cdfddb Author: Dimitry Kolyshev Date: Fri Dec 22 10:52:59 2023 +0200 openapi: imp docs commit 87956c49240da44b216489920feff69996e3502b Author: Dimitry Kolyshev Date: Thu Dec 21 12:08:07 2023 +0200 dnsforward: imp code commit bf74d67ad112735d557be3d8fac75964cd99e375 Author: Dimitry Kolyshev Date: Wed Dec 20 15:46:38 2023 +0200 dnsforward: imp code commit 3a98dee88809a25118a14a1f07eeecbfccb14cd9 Author: Dimitry Kolyshev Date: Wed Dec 20 15:41:06 2023 +0200 dnsforward: imp code commit 1499da1fa0319ac3ad914171e807446f2c4d2fdb Author: Dimitry Kolyshev Date: Wed Dec 20 13:36:28 2023 +0200 dnsforward: imp code commit 228c61a5a0f73cc13655cef8bdaa1995b3f7fced Author: Dimitry Kolyshev Date: Wed Dec 20 13:06:11 2023 +0200 dnsforward: imp code commit 069ee22c6d904db4e983135ce87a9fe8d12b7e9a Author: Dimitry Kolyshev Date: Tue Dec 19 12:39:25 2023 +0200 dnsforward: imp code commit 90919f99a975862dcb07ac82fb740e4404e48bae Author: Dimitry Kolyshev Date: Tue Dec 19 12:10:43 2023 +0200 confmigrate: fix commit a8c329950423b59098d1f2b16d1da7100dd54f8d Author: Dimitry Kolyshev Date: Tue Dec 19 12:08:05 2023 +0200 dnsforward: imp code commit 58b53ccd97d353fab0df29f13425b5e341c8fdeb Author: Dimitry Kolyshev Date: Mon Dec 18 15:10:01 2023 +0200 all: conf upstream mode --- CHANGELOG.md | 25 ++++++ internal/configmigrate/configmigrate.go | 2 +- .../configmigrate/migrations_internal_test.go | 82 +++++++++++++++++++ internal/configmigrate/migrator.go | 1 + internal/configmigrate/v28.go | 47 +++++++++++ internal/dnsforward/config.go | 28 ++++--- internal/dnsforward/dns64_test.go | 1 + internal/dnsforward/dnsforward.go | 10 +-- internal/dnsforward/dnsforward_test.go | 29 ++++++- internal/dnsforward/dnsrewrite_test.go | 1 + internal/dnsforward/filter_test.go | 1 + internal/dnsforward/http.go | 73 +++++++++++++---- internal/dnsforward/http_test.go | 7 +- internal/dnsforward/process_internal_test.go | 4 + internal/dnsforward/svcbmsg_test.go | 1 + internal/dnsforward/upstreams.go | 16 ++-- internal/home/config.go | 2 +- openapi/CHANGELOG.md | 7 ++ openapi/openapi.yaml | 11 ++- 19 files changed, 297 insertions(+), 51 deletions(-) create mode 100644 internal/configmigrate/v28.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e185910..e5c5ec9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,31 @@ NOTE: Add new changes BELOW THIS COMMENT. - Ability to disable plain-DNS serving via UI if an encrypted protocol is already used ([#1660]). +### Changed + +- The field `"upstream_mode"` in `POST /control/dns_config` and + `GET /control/dns_info` HTTP APIs now accepts `load_balance` value. Check + `openapi/CHANGELOG.md` for more details. + +#### Configuration changes + +- The properties `dns.'all_servers` and `dns.fastest_addr` were removed, their + values migrated to newly added field `dns.upstream_mode` that describes the + logic through which upstreams will be used. + + ```yaml + # BEFORE: + 'dns': + # … + 'all_servers': true + 'fastest_addr': true + + # AFTER: + 'dns': + # … + 'upstream_mode': 'parallel' + ``` + ### Fixed - Statistics for 7 days displayed as 168 hours on the dashboard. diff --git a/internal/configmigrate/configmigrate.go b/internal/configmigrate/configmigrate.go index 05198cc5..6e8845e0 100644 --- a/internal/configmigrate/configmigrate.go +++ b/internal/configmigrate/configmigrate.go @@ -2,4 +2,4 @@ package configmigrate // LastSchemaVersion is the most recent schema version. -const LastSchemaVersion uint = 27 +const LastSchemaVersion uint = 28 diff --git a/internal/configmigrate/migrations_internal_test.go b/internal/configmigrate/migrations_internal_test.go index 4531ea68..5349102f 100644 --- a/internal/configmigrate/migrations_internal_test.go +++ b/internal/configmigrate/migrations_internal_test.go @@ -4,6 +4,7 @@ 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) + }) + } +} diff --git a/internal/configmigrate/migrator.go b/internal/configmigrate/migrator.go index f0cab769..ebdf6ba7 100644 --- a/internal/configmigrate/migrator.go +++ b/internal/configmigrate/migrator.go @@ -119,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] { diff --git a/internal/configmigrate/v28.go b/internal/configmigrate/v28.go new file mode 100644 index 00000000..c32f9f3f --- /dev/null +++ b/internal/configmigrate/v28.go @@ -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 +} diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index ec20096b..e37e9376 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -89,12 +89,8 @@ type Config struct { // servers are not responding. FallbackDNS []string `yaml:"fallback_dns"` - // AllServers, if true, parallel queries to all configured upstream servers - // are enabled. - AllServers bool `yaml:"all_servers"` - - // FastestAddr, if true, use Fastest Address algorithm. - FastestAddr bool `yaml:"fastest_addr"` + // UpstreamMode determines the logic through which upstreams will be used. + UpstreamMode UpstreamMode `yaml:"upstream_mode"` // FastestTimeout replaces the default timeout for dialing IP addresses // when FastestAddr is true. @@ -294,6 +290,16 @@ type ServerConfig struct { ServePlainDNS bool } +// UpstreamMode is a enumeration of upstream mode representations. See +// [proxy.UpstreamModeType]. +type UpstreamMode string + +const ( + UpstreamModeLoadBalance UpstreamMode = "load_balance" + UpstreamModeParallel UpstreamMode = "parallel" + UpstreamModeFastestAddr UpstreamMode = "fastest_addr" +) + // newProxyConfig creates and validates configuration for the main proxy. func (s *Server) newProxyConfig() (conf *proxy.Config, err error) { srvConf := s.conf @@ -328,12 +334,10 @@ func (s *Server) newProxyConfig() (conf *proxy.Config, err error) { conf.CacheSizeBytes = int(srvConf.CacheSize) } - setProxyUpstreamMode( - conf, - srvConf.AllServers, - srvConf.FastestAddr, - srvConf.FastestTimeout.Duration, - ) + err = setProxyUpstreamMode(conf, srvConf.UpstreamMode, srvConf.FastestTimeout.Duration) + if err != nil { + return nil, fmt.Errorf("upstream mode: %w", err) + } conf.BogusNXDomain, err = parseBogusNXDOMAIN(srvConf.BogusNXDomain) if err != nil { diff --git a/internal/dnsforward/dns64_test.go b/internal/dnsforward/dns64_test.go index 55c08db7..ad89098c 100644 --- a/internal/dnsforward/dns64_test.go +++ b/internal/dnsforward/dns64_test.go @@ -290,6 +290,7 @@ func TestServer_HandleDNSRequest_dns64(t *testing.T) { TCPListenAddrs: []*net.TCPAddr{{}}, UseDNS64: true, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index cdd2c240..65ed5766 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -703,12 +703,10 @@ func (s *Server) prepareInternalProxy() (err error) { MaxGoroutines: int(s.conf.MaxGoroutines), } - setProxyUpstreamMode( - conf, - srvConf.AllServers, - srvConf.FastestAddr, - srvConf.FastestTimeout.Duration, - ) + err = setProxyUpstreamMode(conf, srvConf.UpstreamMode, srvConf.FastestTimeout.Duration) + if err != nil { + return fmt.Errorf("invalid upstream mode: %w", err) + } // TODO(a.garipov): Make a proper constructor for proxy.Proxy. p := &proxy.Proxy{ diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index bdd3308d..479b5528 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -177,6 +177,7 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte) UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, @@ -305,6 +306,7 @@ func TestServer(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, @@ -344,6 +346,7 @@ func TestServer_timeout(t *testing.T) { srvConf := &ServerConfig{ UpstreamTimeout: testTimeout, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, @@ -362,6 +365,7 @@ func TestServer_timeout(t *testing.T) { s, err := NewServer(DNSCreateParams{DNSFilter: createTestDNSFilter(t)}) require.NoError(t, err) + s.conf.Config.UpstreamMode = UpstreamModeLoadBalance s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{ Enabled: false, } @@ -379,6 +383,7 @@ func TestServer_Prepare_fallbacks(t *testing.T) { "#tls://1.1.1.1", "8.8.8.8", }, + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, @@ -401,6 +406,7 @@ func TestServerWithProtectionDisabled(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, @@ -478,7 +484,8 @@ func TestServerRace(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ - UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + UpstreamMode: UpstreamModeLoadBalance, + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, }, ConfigModified: func() {}, ServePlainDNS: true, @@ -531,6 +538,7 @@ func TestSafeSearch(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -614,6 +622,7 @@ func TestInvalidRequest(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -643,6 +652,7 @@ func TestBlockedRequest(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -678,7 +688,8 @@ func TestServerCustomClientUpstream(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ - CacheSize: defaultCacheSize, + CacheSize: defaultCacheSize, + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -756,6 +767,7 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -789,6 +801,7 @@ func TestBlockCNAME(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -864,6 +877,7 @@ func TestClientRulesForCNAMEMatching(t *testing.T) { FilterHandler: func(_ netip.Addr, _ string, settings *filtering.Settings) { settings.FilteringEnabled = false }, + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -909,6 +923,7 @@ func TestNullBlockedRequest(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -974,7 +989,8 @@ func TestBlockedCustomIP(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ - UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -1027,6 +1043,7 @@ func TestBlockedByHosts(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -1078,6 +1095,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -1136,7 +1154,8 @@ func TestRewrite(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ - UpstreamDNS: []string{"8.8.8.8:53"}, + UpstreamDNS: []string{"8.8.8.8:53"}, + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, @@ -1265,6 +1284,7 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) { s.conf.TCPListenAddrs = []*net.TCPAddr{{}} s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false} + s.conf.Config.UpstreamMode = UpstreamModeLoadBalance err = s.Prepare(&s.conf) require.NoError(t, err) @@ -1347,6 +1367,7 @@ func TestPTRResponseFromHosts(t *testing.T) { s.conf.TCPListenAddrs = []*net.TCPAddr{{}} s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.Config.EDNSClientSubnet = &EDNSClientSubnet{Enabled: false} + s.conf.Config.UpstreamMode = UpstreamModeLoadBalance err = s.Prepare(&s.conf) require.NoError(t, err) diff --git a/internal/dnsforward/dnsrewrite_test.go b/internal/dnsforward/dnsrewrite_test.go index 1022388f..5204c2f2 100644 --- a/internal/dnsforward/dnsrewrite_test.go +++ b/internal/dnsforward/dnsrewrite_test.go @@ -38,6 +38,7 @@ func TestServer_FilterDNSRewrite(t *testing.T) { BlockingMode: filtering.BlockingModeDefault, }, ServerConfig{ Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, diff --git a/internal/dnsforward/filter_test.go b/internal/dnsforward/filter_test.go index 6559b308..9e172a32 100644 --- a/internal/dnsforward/filter_test.go +++ b/internal/dnsforward/filter_test.go @@ -31,6 +31,7 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{ Enabled: false, }, diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index ac82ea76..9c1eb89f 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -70,7 +70,7 @@ type jsonDNSConfig struct { DisableIPv6 *bool `json:"disable_ipv6"` // UpstreamMode defines the way DNS requests are constructed. - UpstreamMode *string `json:"upstream_mode"` + UpstreamMode *jsonUpstreamMode `json:"upstream_mode"` // BlockedResponseTTL is the TTL for blocked responses. BlockedResponseTTL *uint32 `json:"blocked_response_ttl"` @@ -114,6 +114,21 @@ type jsonDNSConfig struct { DefaultLocalPTRUpstreams []string `json:"default_local_ptr_upstreams,omitempty"` } +// jsonUpstreamMode is a enumeration of upstream modes. +type jsonUpstreamMode string + +const ( + // jsonUpstreamModeEmpty is the default value on frontend, it is used as + // jsonUpstreamModeLoadBalance mode. + // + // Deprecated: Use jsonUpstreamModeLoadBalance instead. + jsonUpstreamModeEmpty jsonUpstreamMode = "" + + jsonUpstreamModeLoadBalance jsonUpstreamMode = "load_balance" + jsonUpstreamModeParallel jsonUpstreamMode = "parallel" + jsonUpstreamModeFastestAddr jsonUpstreamMode = "fastest_addr" +) + func (s *Server) getDNSConfig() (c *jsonDNSConfig) { protectionEnabled, protectionDisabledUntil := s.UpdatedProtectionStatus() @@ -145,11 +160,16 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) { usePrivateRDNS := s.conf.UsePrivateRDNS localPTRUpstreams := stringutil.CloneSliceOrEmpty(s.conf.LocalPTRResolvers) - var upstreamMode string - if s.conf.FastestAddr { - upstreamMode = "fastest_addr" - } else if s.conf.AllServers { - upstreamMode = "parallel" + var upstreamMode jsonUpstreamMode + switch s.conf.UpstreamMode { + case UpstreamModeLoadBalance: + // TODO(d.kolyshev): Support jsonUpstreamModeLoadBalance on frontend instead + // of jsonUpstreamModeEmpty. + upstreamMode = jsonUpstreamModeEmpty + case UpstreamModeParallel: + upstreamMode = jsonUpstreamModeParallel + case UpstreamModeFastestAddr: + upstreamMode = jsonUpstreamModeFastestAddr } defPTRUps, err := s.defaultLocalPTRUpstreams() @@ -222,18 +242,22 @@ func (req *jsonDNSConfig) checkBlockingMode() (err error) { return validateBlockingMode(*req.BlockingMode, req.BlockingIPv4, req.BlockingIPv6) } -// checkUpstreamsMode returns an error if the upstream mode is invalid. -func (req *jsonDNSConfig) checkUpstreamsMode() (err error) { +// checkUpstreamMode returns an error if the upstream mode is invalid. +func (req *jsonDNSConfig) checkUpstreamMode() (err error) { if req.UpstreamMode == nil { return nil } - mode := *req.UpstreamMode - if ok := slices.Contains([]string{"", "fastest_addr", "parallel"}, mode); !ok { - return fmt.Errorf("upstream_mode: incorrect value %q", mode) + switch um := *req.UpstreamMode; um { + case + jsonUpstreamModeEmpty, + jsonUpstreamModeLoadBalance, + jsonUpstreamModeParallel, + jsonUpstreamModeFastestAddr: + return nil + default: + return fmt.Errorf("upstream_mode: incorrect value %q", um) } - - return nil } // checkBootstrap returns an error if any bootstrap address is invalid. @@ -297,7 +321,7 @@ func (req *jsonDNSConfig) validate(privateNets netutil.SubnetSet) (err error) { return err } - err = req.checkUpstreamsMode() + err = req.checkUpstreamMode() if err != nil { // Don't wrap the error since it's informative enough as is. return err @@ -446,8 +470,9 @@ func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) { } if dc.UpstreamMode != nil { - s.conf.AllServers = *dc.UpstreamMode == "parallel" - s.conf.FastestAddr = *dc.UpstreamMode == "fastest_addr" + s.conf.UpstreamMode = mustParseUpstreamMode(*dc.UpstreamMode) + } else { + s.conf.UpstreamMode = UpstreamModeLoadBalance } if dc.EDNSCSUseCustom != nil && *dc.EDNSCSUseCustom { @@ -460,6 +485,22 @@ func (s *Server) setConfig(dc *jsonDNSConfig) (shouldRestart bool) { return s.setConfigRestartable(dc) } +// mustParseUpstreamMode returns an upstream mode parsed from jsonUpstreamMode. +// Panics in case of invalid value. +func mustParseUpstreamMode(mode jsonUpstreamMode) (um UpstreamMode) { + switch mode { + case jsonUpstreamModeEmpty, jsonUpstreamModeLoadBalance: + return UpstreamModeLoadBalance + case jsonUpstreamModeParallel: + return UpstreamModeParallel + case jsonUpstreamModeFastestAddr: + return UpstreamModeFastestAddr + default: + // Should never happen, since the value should be validated. + panic(fmt.Errorf("unexpected upstream mode: %q", mode)) + } +} + // setIfNotNil sets the value pointed at by currentPtr to the value pointed at // by newPtr if newPtr is not nil. currentPtr must not be nil. func setIfNotNil[T any](currentPtr, newPtr *T) (hasSet bool) { diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go index 4b6987bb..64b27833 100644 --- a/internal/dnsforward/http_test.go +++ b/internal/dnsforward/http_test.go @@ -77,6 +77,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { FallbackDNS: []string{"9.9.9.10"}, RatelimitSubnetLenIPv4: 24, RatelimitSubnetLenIPv6: 56, + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ConfigModified: func() {}, @@ -103,7 +104,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { }, { conf: func() ServerConfig { conf := defaultConf - conf.FastestAddr = true + conf.UpstreamMode = UpstreamModeFastestAddr return conf }, @@ -111,7 +112,7 @@ func TestDNSForwardHTTP_handleGetConfig(t *testing.T) { }, { conf: func() ServerConfig { conf := defaultConf - conf.AllServers = true + conf.UpstreamMode = UpstreamModeParallel return conf }, @@ -157,6 +158,7 @@ func TestDNSForwardHTTP_handleSetConfig(t *testing.T) { UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, RatelimitSubnetLenIPv4: 24, RatelimitSubnetLenIPv6: 56, + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ConfigModified: func() {}, @@ -523,6 +525,7 @@ func TestServer_HandleTestUpstreamDNS(t *testing.T) { TCPListenAddrs: []*net.TCPAddr{{}}, UpstreamTimeout: upsTimeout, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, diff --git a/internal/dnsforward/process_internal_test.go b/internal/dnsforward/process_internal_test.go index 18b04b3f..bec9c98e 100644 --- a/internal/dnsforward/process_internal_test.go +++ b/internal/dnsforward/process_internal_test.go @@ -79,6 +79,7 @@ func TestServer_ProcessInitial(t *testing.T) { c := ServerConfig{ Config: Config{ AAAADisabled: tc.aaaaDisabled, + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, @@ -179,6 +180,7 @@ func TestServer_ProcessFilteringAfterResponse(t *testing.T) { c := ServerConfig{ Config: Config{ AAAADisabled: tc.aaaaDisabled, + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, @@ -694,6 +696,7 @@ func TestServer_ProcessRestrictLocal(t *testing.T) { // TODO(s.chzhen): Add tests where EDNSClientSubnet.Enabled is true. // Improve Config declaration for tests. Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, @@ -770,6 +773,7 @@ func TestServer_ProcessLocalPTR_usingResolvers(t *testing.T) { UDPListenAddrs: []*net.UDPAddr{{}}, TCPListenAddrs: []*net.TCPAddr{{}}, Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, diff --git a/internal/dnsforward/svcbmsg_test.go b/internal/dnsforward/svcbmsg_test.go index 58275ef4..2c2b7b0b 100644 --- a/internal/dnsforward/svcbmsg_test.go +++ b/internal/dnsforward/svcbmsg_test.go @@ -17,6 +17,7 @@ func TestGenAnswerHTTPS_andSVCB(t *testing.T) { BlockingMode: filtering.BlockingModeDefault, }, ServerConfig{ Config: Config{ + UpstreamMode: UpstreamModeLoadBalance, EDNSClientSubnet: &EDNSClientSubnet{Enabled: false}, }, ServePlainDNS: true, diff --git a/internal/dnsforward/upstreams.go b/internal/dnsforward/upstreams.go index 3f877ac7..c9fc6834 100644 --- a/internal/dnsforward/upstreams.go +++ b/internal/dnsforward/upstreams.go @@ -136,18 +136,22 @@ func UpstreamHTTPVersions(http3 bool) (v []upstream.HTTPVersion) { // based on provided parameters. func setProxyUpstreamMode( conf *proxy.Config, - allServers bool, - fastestAddr bool, + upstreamMode UpstreamMode, fastestTimeout time.Duration, -) { - if allServers { +) (err error) { + switch upstreamMode { + case UpstreamModeParallel: conf.UpstreamMode = proxy.UModeParallel - } else if fastestAddr { + case UpstreamModeFastestAddr: conf.UpstreamMode = proxy.UModeFastestAddr conf.FastestPingTimeout = fastestTimeout - } else { + case UpstreamModeLoadBalance: conf.UpstreamMode = proxy.UModeLoadBalance + default: + return fmt.Errorf("unexpected value %q", upstreamMode) } + + return nil } // createBootstrap returns a bootstrap resolver based on the configuration of s. diff --git a/internal/home/config.go b/internal/home/config.go index 5ddcf2ab..231e6521 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -315,7 +315,7 @@ var config = &configuration{ RatelimitSubnetLenIPv4: 24, RatelimitSubnetLenIPv6: 56, RefuseAny: true, - AllServers: false, + UpstreamMode: dnsforward.UpstreamModeLoadBalance, HandleDDR: true, FastestTimeout: timeutil.Duration{ Duration: fastip.DefaultPingWaitTimeout, diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index 66966200..de87f6fa 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -6,6 +6,13 @@ ## v0.107.44: API changes +### The field `"upstream_mode"` in `DNSConfig` + +* The field `"upstream_mode"` in `POST /control/dns_config` and + `GET /control/dns_info` now accepts `load_balance` value. Note that, the usage + of an empty string or field absence is considered to as deprecated and is not + recommended. Use `load_balance` instead. + ### Type correction in `Client` * Field `upstreams_cache_size` of object `Client` now correctly has type diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 781959ff..300311c1 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1524,10 +1524,15 @@ 'cache_optimistic': 'type': 'boolean' 'upstream_mode': + 'type': 'string' 'enum': - - '' - - 'parallel' - - 'fastest_addr' + - const: '' + deprecated: true + description: Use `load_balance` instead. + - const: 'fastest_addr' + - const: 'load_balance' + - const: 'parallel' + 'description': Upstream modes enumeration. 'use_private_ptr_resolvers': 'type': 'boolean' 'resolve_clients':