diff --git a/CHANGELOG.md b/CHANGELOG.md index b4866ada..f72bb092 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,10 @@ NOTE: Add new changes BELOW THIS COMMENT. - Go version has been updated to prevent the possibility of exploiting the Go vulnerabilities fixed in [1.23.6][go-1.23.6]. +### Added + +- The ability to specify the upstream timeout in the Web UI. + ### Changed - The *Fastest IP adddress* upstream mode now collects statistics for the all upstream DNS servers. diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index c7428cdc..9634cf70 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -6,7 +6,7 @@ "upstream_parallel": "Use parallel queries to speed up resolving by querying all upstream servers simultaneously.", "parallel_requests": "Parallel requests", "load_balancing": "Load-balancing", - "load_balancing_desc": "Query one upstream server at a time. AdGuard Home uses a weighted random algorithm to select servers with the lowest number of failed lookups and the lowest average lookup time.", + "load_balancing_desc": "Query one upstream server at a time.
AdGuard Home uses a weighted random algorithm to select servers with the lowest number of failed lookups and the lowest average lookup time.", "bootstrap_dns": "Bootstrap DNS servers", "bootstrap_dns_desc": "IP addresses of DNS servers used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams. Comments are not permitted.", "fallback_dns_title": "Fallback DNS servers", @@ -294,6 +294,9 @@ "blocked_response_ttl": "Blocked response TTL", "blocked_response_ttl_desc": "Specifies for how many seconds the clients should cache a filtered response", "form_enter_blocked_response_ttl": "Enter blocked response TTL (seconds)", + "upstream_timeout": "Upstream timeout", + "upstream_timeout_desc": "Specifies the number of seconds to wait for a response from the upstream server", + "form_enter_upstream_timeout": "Enter the upstream server timeout duration in seconds", "dnscrypt": "DNSCrypt", "dns_over_https": "DNS-over-HTTPS", "dns_over_tls": "DNS-over-TLS", @@ -598,7 +601,7 @@ "disable_ipv6": "Disable resolving of IPv6 addresses", "disable_ipv6_desc": "Drop all DNS queries for IPv6 addresses (type AAAA) and remove IPv6 hints from HTTPS responses.", "fastest_addr": "Fastest IP address", - "fastest_addr_desc": "Query all DNS servers and return the fastest IP address among all responses. This slows down DNS queries as AdGuard Home has to wait for responses from all DNS servers, but improves the overall connectivity.", + "fastest_addr_desc": "Wait for responses from all DNS servers, measure the TCP connection speed for each server, and return the IP address of the server with the fastest connection speed.
This mode can significantly slow down DNS queries, if one or more upstream servers are not responding. Make sure that your upstream servers are stable and your upstream timeout is low.", "autofix_warning_text": "If you click \"Fix\", AdGuard Home will configure your system to use AdGuard Home DNS server.", "autofix_warning_list": "It will perform these tasks: <0>Deactivate system DNSStubListener <0>Set DNS server address to 127.0.0.1 <0>Replace symbolic link target of /etc/resolv.conf with /run/systemd/resolve/resolv.conf <0>Stop DNSStubListener (reload systemd-resolved service)", "autofix_warning_result": "As a result all DNS requests from your system will be processed by AdGuard Home by default.", diff --git a/client/src/components/Settings/Dns/Upstream/Form.tsx b/client/src/components/Settings/Dns/Upstream/Form.tsx index f53a7038..db367507 100644 --- a/client/src/components/Settings/Dns/Upstream/Form.tsx +++ b/client/src/components/Settings/Dns/Upstream/Form.tsx @@ -7,8 +7,19 @@ import classnames from 'classnames'; import Examples from './Examples'; -import { renderRadioField, renderTextareaField, CheckboxField } from '../../../../helpers/form'; -import { DNS_REQUEST_OPTIONS, FORM_NAME, UPSTREAM_CONFIGURATION_WIKI_LINK } from '../../../../helpers/constants'; +import { + renderRadioField, + renderTextareaField, + CheckboxField, + renderInputField, + toNumber, +} from '../../../../helpers/form'; +import { + DNS_REQUEST_OPTIONS, + FORM_NAME, + UINT32_RANGE, + UPSTREAM_CONFIGURATION_WIKI_LINK, +} from '../../../../helpers/constants'; import { testUpstreamWithFormValues } from '../../../../actions'; @@ -17,6 +28,7 @@ import { removeEmptyLines, trimLinesAndRemoveEmpty } from '../../../../helpers/h import { getTextareaCommentsHighlight, syncScroll } from '../../../../helpers/highlightTextareaComments'; import '../../../ui/texareaCommentsHighlight.css'; import { RootState } from '../../../../initialState'; +import { validateRequiredValue } from '../../../../helpers/validators'; const UPSTREAM_DNS_NAME = 'upstream_dns'; const UPSTREAM_MODE_NAME = 'upstream_mode'; @@ -301,7 +313,7 @@ const Form = ({ submitting, invalid, handleSubmit }: FormProps) => {
-
+
{ disabled={processingSetConfig} />
+ +
+
+
+ +
+
+ + +
+ upstream_timeout_desc +
+ + +
+
diff --git a/client/src/components/Settings/Dns/Upstream/index.tsx b/client/src/components/Settings/Dns/Upstream/index.tsx index 62237da0..4b316492 100644 --- a/client/src/components/Settings/Dns/Upstream/index.tsx +++ b/client/src/components/Settings/Dns/Upstream/index.tsx @@ -19,6 +19,7 @@ const Upstream = () => { resolve_clients, local_ptr_upstreams, use_private_ptr_resolvers, + upstream_timeout, } = useSelector((state: RootState) => state.dnsConfig, shallowEqual); const upstream_dns_file = useSelector((state: RootState) => state.dnsConfig.upstream_dns_file); @@ -32,6 +33,7 @@ const Upstream = () => { resolve_clients, local_ptr_upstreams, use_private_ptr_resolvers, + upstream_timeout, } = values; const dnsConfig = { @@ -41,6 +43,7 @@ const Upstream = () => { resolve_clients, local_ptr_upstreams, use_private_ptr_resolvers, + upstream_timeout, ...(upstream_dns_file ? null : { upstream_dns }), }; @@ -64,6 +67,7 @@ const Upstream = () => { resolve_clients, local_ptr_upstreams, use_private_ptr_resolvers, + upstream_timeout, }} onSubmit={handleSubmit} /> diff --git a/client/src/initialState.ts b/client/src/initialState.ts index 94be25ff..1e8dea27 100644 --- a/client/src/initialState.ts +++ b/client/src/initialState.ts @@ -304,6 +304,7 @@ export type DnsConfigData = { blocking_ipv4: string; blocking_ipv6: string; blocked_response_ttl: number; + upstream_timeout: number; edns_cs_enabled: boolean; disable_ipv6: boolean; dnssec_enabled: boolean; @@ -489,6 +490,7 @@ export const initialState: RootState = { blocking_ipv4: DEFAULT_BLOCKING_IPV4, blocking_ipv6: DEFAULT_BLOCKING_IPV6, blocked_response_ttl: 10, + upstream_timeout: 10, edns_cs_enabled: false, disable_ipv6: false, dnssec_enabled: false, diff --git a/client/src/reducers/dnsConfig.ts b/client/src/reducers/dnsConfig.ts index 0b5cbb72..20495771 100644 --- a/client/src/reducers/dnsConfig.ts +++ b/client/src/reducers/dnsConfig.ts @@ -66,6 +66,7 @@ const dnsConfig = handleActions( blocking_ipv4: DEFAULT_BLOCKING_IPV4, blocking_ipv6: DEFAULT_BLOCKING_IPV6, blocked_response_ttl: 10, + upstream_timeout: 10, edns_cs_enabled: false, disable_ipv6: false, dnssec_enabled: false, diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index 9413e828..759e5c25 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -329,6 +329,14 @@ func (s *Server) AddrProcConfig() (c *client.DefaultAddrProcConfig) { } } +// UpstreamTimeout returns the current upstream timeout configuration. +func (s *Server) UpstreamTimeout() (t time.Duration) { + s.serverLock.RLock() + defer s.serverLock.RUnlock() + + return s.conf.UpstreamTimeout +} + // Resolve gets IP addresses by host name from an upstream server. No // request/response filtering is performed. Query log and Stats are not // updated. This method may be called before [Server.Start]. diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index ad438a23..ab12524f 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -18,6 +18,7 @@ import ( "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/stringutil" + "github.com/AdguardTeam/golibs/validate" ) // jsonDNSConfig is the JSON representation of the DNS server configuration. @@ -53,6 +54,9 @@ type jsonDNSConfig struct { // rate limiting requests. RatelimitSubnetLenIPv6 *int `json:"ratelimit_subnet_len_ipv6"` + // UpstreamTimeout is an upstream timeout in seconds. + UpstreamTimeout *int `json:"upstream_timeout"` + // RatelimitWhitelist is a list of IP addresses excluded from rate limiting. RatelimitWhitelist *[]netip.Addr `json:"ratelimit_whitelist"` @@ -147,6 +151,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) { ratelimitSubnetLenIPv4 := s.conf.RatelimitSubnetLenIPv4 ratelimitSubnetLenIPv6 := s.conf.RatelimitSubnetLenIPv6 ratelimitWhitelist := append([]netip.Addr{}, s.conf.RatelimitWhitelist...) + upstreamTimeout := int(s.conf.UpstreamTimeout.Seconds()) customIP := s.conf.EDNSClientSubnet.CustomIP enableEDNSClientSubnet := s.conf.EDNSClientSubnet.Enabled @@ -192,6 +197,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) { RatelimitSubnetLenIPv4: &ratelimitSubnetLenIPv4, RatelimitSubnetLenIPv6: &ratelimitSubnetLenIPv6, RatelimitWhitelist: &ratelimitWhitelist, + UpstreamTimeout: &upstreamTimeout, EDNSCSCustomIP: customIP, EDNSCSEnabled: &enableEDNSClientSubnet, EDNSCSUseCustom: &useCustom, @@ -302,6 +308,12 @@ func (req *jsonDNSConfig) validate( return err } + err = req.checkUpstreamTimeout() + if err != nil { + // Don't wrap the error since it's informative enough as is. + return err + } + return nil } @@ -437,6 +449,16 @@ func (req *jsonDNSConfig) checkRatelimitSubnetMaskLen() (err error) { return nil } +// checkUpstreamTimeout returns an error if the configuration of the upstream +// timeout is invalid. +func (req *jsonDNSConfig) checkUpstreamTimeout() (err error) { + if req.UpstreamTimeout == nil { + return nil + } + + return validate.NoLessThan("upstream_timeout", *req.UpstreamTimeout, 1) +} + // checkInclusion returns an error if a ptr is not nil and points to value, // that not in the inclusive range between minN and maxN. func checkInclusion(ptr *int, minN, maxN int) (err error) { @@ -588,6 +610,14 @@ func (s *Server) setConfigRestartable(dc *jsonDNSConfig) (shouldRestart bool) { shouldRestart = true } + if dc.UpstreamTimeout != nil { + ut := time.Duration(*dc.UpstreamTimeout) * time.Second + if s.conf.UpstreamTimeout != ut { + s.conf.UpstreamTimeout = ut + shouldRestart = true + } + } + return shouldRestart } diff --git a/internal/dnsforward/testdata/TestDNSForwardHTTP_handleGetConfig.json b/internal/dnsforward/testdata/TestDNSForwardHTTP_handleGetConfig.json index 32265d2d..d4e76a20 100644 --- a/internal/dnsforward/testdata/TestDNSForwardHTTP_handleGetConfig.json +++ b/internal/dnsforward/testdata/TestDNSForwardHTTP_handleGetConfig.json @@ -24,6 +24,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -63,6 +64,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -102,6 +104,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, diff --git a/internal/dnsforward/testdata/TestDNSForwardHTTP_handleSetConfig.json b/internal/dnsforward/testdata/TestDNSForwardHTTP_handleSetConfig.json index 2ef81f35..d0967ece 100644 --- a/internal/dnsforward/testdata/TestDNSForwardHTTP_handleSetConfig.json +++ b/internal/dnsforward/testdata/TestDNSForwardHTTP_handleSetConfig.json @@ -29,6 +29,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -70,6 +71,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -112,6 +114,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -154,6 +157,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -196,6 +200,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -240,6 +245,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -285,6 +291,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -327,6 +334,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": true, "dnssec_enabled": false, "disable_ipv6": false, @@ -371,6 +379,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": true, "dnssec_enabled": false, "disable_ipv6": false, @@ -415,6 +424,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -457,6 +467,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": true, "disable_ipv6": false, @@ -499,6 +510,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -541,6 +553,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -583,6 +596,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -627,6 +641,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -671,6 +686,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -714,6 +730,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -756,6 +773,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -800,6 +818,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -847,6 +866,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -889,6 +909,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -935,6 +956,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -977,6 +999,7 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 11, + "upstream_timeout": 10, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, @@ -1022,6 +1045,50 @@ "blocking_ipv4": "", "blocking_ipv6": "", "blocked_response_ttl": 10, + "upstream_timeout": 10, + "edns_cs_enabled": false, + "dnssec_enabled": false, + "disable_ipv6": false, + "upstream_mode": "", + "cache_size": 0, + "cache_ttl_min": 0, + "cache_ttl_max": 0, + "cache_optimistic": false, + "resolve_clients": false, + "use_private_ptr_resolvers": false, + "local_ptr_upstreams": [], + "edns_cs_use_custom": false, + "edns_cs_custom_ip": "" + } + }, + "upstream_timeout": { + "req": { + "upstream_timeout": 11 + }, + "want": { + "upstream_dns": [ + "8.8.8.8:53", + "8.8.4.4:53" + ], + "upstream_dns_file": "", + "bootstrap_dns": [ + "9.9.9.10", + "149.112.112.10", + "2620:fe::10", + "2620:fe::fe:10" + ], + "fallback_dns": [], + "protection_enabled": true, + "protection_disabled_until": null, + "ratelimit": 0, + "ratelimit_subnet_len_ipv4": 24, + "ratelimit_subnet_len_ipv6": 56, + "ratelimit_whitelist": [], + "blocking_mode": "default", + "blocking_ipv4": "", + "blocking_ipv6": "", + "blocked_response_ttl": 10, + "upstream_timeout": 11, "edns_cs_enabled": false, "dnssec_enabled": false, "disable_ipv6": false, diff --git a/internal/home/config.go b/internal/home/config.go index 315a3345..f8b09551 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -692,6 +692,7 @@ func (c *configuration) write() (err error) { config.Clients.Sources.RDNS = addrProcConf.UseRDNS config.Clients.Sources.WHOIS = addrProcConf.UseWHOIS dns.UsePrivateRDNS = addrProcConf.UsePrivateRDNS + dns.UpstreamTimeout = timeutil.Duration(s.UpstreamTimeout()) } if Context.dhcpServer != nil { diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index f2bd9baf..0e6dbed0 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -4,6 +4,10 @@ ## v0.108.0: API changes +## v0.107.57: API changes + +- The new field `"upstream_timeout"` in `GET /control/dns_info` and `POST /control/dns_config` is the number of seconds to wait for a response from the upstream server. + ## v0.107.56: API changes ### Documentation fix of `NetInterface` diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index cbc07a68..633138c4 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1570,6 +1570,10 @@ 'example': - 'tls://1.1.1.1' - 'tls://1.0.0.1' + 'upstream_timeout': + 'type': 'integer' + 'minimum': 1 + 'description': 'The number of seconds to wait for a response from the upstream server' 'UpstreamsConfig': 'type': 'object' 'description': 'Upstream configuration to be tested'