From 2fe2d254b5c1a30e77b834b7fa135a4cfdf0c414 Mon Sep 17 00:00:00 2001 From: Stanislav Chzhen Date: Mon, 10 Feb 2025 16:07:11 +0300 Subject: [PATCH] Pull request 2338: AGDNS-2665-upstream-timeout Merge in DNS/adguard-home from AGDNS-2665-upstream-timeout to master Squashed commit of the following: commit b4041c97f2ccd371a9cff8312bda72f4c18496b9 Merge: d939b3b3e b92a3cfb8 Author: Ainar Garipov Date: Fri Feb 7 16:15:19 2025 +0300 Merge branch 'master' into AGDNS-2665-upstream-timeout commit d939b3b3ef2345d315c05a3730e81e1f1ea9f79f Author: Ainar Garipov Date: Fri Feb 7 16:10:06 2025 +0300 client: imp i18n commit 52089adb6c28d8c47f5a916d9df941c5fddea1fc Author: Stanislav Chzhen Date: Thu Feb 6 18:39:53 2025 +0300 all: fix typo commit aca08b1873c9f39b8c1da0439a9057117e1dacce Author: Ildar Kamalov Date: Thu Feb 6 15:19:30 2025 +0300 client: rearrange upstream timeout settings commit 48e75fabe2f65aeac7156a020cce3d4c2412e5f9 Author: Stanislav Chzhen Date: Tue Feb 4 20:48:53 2025 +0300 github: upd actions cache commit 64bac193fc834a975e6913bb86a6bf27e54c382a Author: Stanislav Chzhen Date: Tue Feb 4 20:28:54 2025 +0300 all: imp code commit 95c73698985dc528a4af6ff0d3b8a301da1f0d4a Author: Stanislav Chzhen Date: Mon Feb 3 16:25:35 2025 +0300 all: upstream timeout --- CHANGELOG.md | 4 ++ client/src/__locales/en.json | 7 +- .../components/Settings/Dns/Upstream/Form.tsx | 46 ++++++++++++- .../Settings/Dns/Upstream/index.tsx | 4 ++ client/src/initialState.ts | 2 + client/src/reducers/dnsConfig.ts | 1 + internal/dnsforward/dnsforward.go | 8 +++ internal/dnsforward/http.go | 30 +++++++++ .../TestDNSForwardHTTP_handleGetConfig.json | 3 + .../TestDNSForwardHTTP_handleSetConfig.json | 67 +++++++++++++++++++ internal/home/config.go | 1 + openapi/CHANGELOG.md | 4 ++ openapi/openapi.yaml | 4 ++ 13 files changed, 176 insertions(+), 5 deletions(-) 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'