From: chux0519 Date: Sat, 9 Jan 2021 09:19:27 +0000 (+0800) Subject: evdns: add max-probe-timeout/probe-backoff-factor settings X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=617ba838746287974505c6a9c4de6c65172ea112;p=libevent evdns: add max-probe-timeout/probe-backoff-factor settings I recently found that when the network status changed when calling bufferevent_socket_connect_hostname (e.g. switching between several WIFIs), all DNS servers would fail, and the timeout of probe would be very long if there were many DNS requests. I want libevent to support manual setting of MAX_PROBE_TIMEOUT and TIMEOUT_BACKOFF_FACTOR So move hardcoded MAX_PROBE_TIMEOUT and TIMEOUT_BACKOFF_FACTOR into struct, and allow changing them. --- diff --git a/evdns.c b/evdns.c index 14a64398..66b627f3 100644 --- a/evdns.c +++ b/evdns.c @@ -416,6 +416,13 @@ struct evdns_base { #endif int disable_when_inactive; + + /* Maximum timeout between two probe packets + * will change `global_nameserver_probe_initial_timeout` + * when this value is smaller */ + int ns_max_probe_timeout; + /* Backoff factor of probe timeout */ + int ns_timeout_backoff_factor; }; struct hosts_entry { @@ -671,21 +678,18 @@ nameserver_probe_failed(struct nameserver *const ns) { return; } -#define MAX_PROBE_TIMEOUT 3600 -#define TIMEOUT_BACKOFF_FACTOR 3 - memcpy(&timeout, &ns->base->global_nameserver_probe_initial_timeout, sizeof(struct timeval)); - for (i=ns->failed_times; i > 0 && timeout.tv_sec < MAX_PROBE_TIMEOUT; --i) { - timeout.tv_sec *= TIMEOUT_BACKOFF_FACTOR; - timeout.tv_usec *= TIMEOUT_BACKOFF_FACTOR; + for (i = ns->failed_times; i > 0 && timeout.tv_sec < ns->base->ns_max_probe_timeout; --i) { + timeout.tv_sec *= ns->base->ns_timeout_backoff_factor; + timeout.tv_usec *= ns->base->ns_timeout_backoff_factor; if (timeout.tv_usec > 1000000) { timeout.tv_sec += timeout.tv_usec / 1000000; timeout.tv_usec %= 1000000; } } - if (timeout.tv_sec > MAX_PROBE_TIMEOUT) { - timeout.tv_sec = MAX_PROBE_TIMEOUT; + if (timeout.tv_sec > ns->base->ns_max_probe_timeout) { + timeout.tv_sec = ns->base->ns_max_probe_timeout; timeout.tv_usec = 0; } @@ -4344,6 +4348,26 @@ evdns_base_set_option_impl(struct evdns_base *base, val); memcpy(&base->global_nameserver_probe_initial_timeout, &tv, sizeof(tv)); + } else if (str_matches_option(option, "max-probe-timeout:")) { + const int max_probe_timeout = strtoint_clipped(val, 1, 3600); + if (max_probe_timeout == -1) return -1; + if (!(flags & DNS_OPTION_MISC)) return 0; + log(EVDNS_LOG_DEBUG, "Setting maximum probe timeout to %d", + max_probe_timeout); + base->ns_max_probe_timeout = max_probe_timeout; + if (base->global_nameserver_probe_initial_timeout.tv_sec > max_probe_timeout) { + base->global_nameserver_probe_initial_timeout.tv_sec = max_probe_timeout; + base->global_nameserver_probe_initial_timeout.tv_usec = 0; + log(EVDNS_LOG_DEBUG, "Setting initial probe timeout to %s", + val); + } + } else if (str_matches_option(option, "probe-backoff-factor:")) { + const int backoff_backtor = strtoint_clipped(val, 1, 10); + if (backoff_backtor == -1) return -1; + if (!(flags & DNS_OPTION_MISC)) return 0; + log(EVDNS_LOG_DEBUG, "Setting probe timeout backoff factor to %d", + backoff_backtor); + base->ns_timeout_backoff_factor = backoff_backtor; } else if (str_matches_option(option, "so-rcvbuf:")) { int buf = strtoint(val); if (buf == -1) return -1; @@ -4820,6 +4844,8 @@ evdns_base_new(struct event_base *event_base, int flags) base->global_getaddrinfo_allow_skew.tv_usec = 0; base->global_nameserver_probe_initial_timeout.tv_sec = 10; base->global_nameserver_probe_initial_timeout.tv_usec = 0; + base->ns_max_probe_timeout = 3600; + base->ns_timeout_backoff_factor = 3; base->global_tcp_idle_timeout.tv_sec = CLIENT_IDLE_CONN_TIMEOUT; TAILQ_INIT(&base->hostsdb); diff --git a/include/event2/dns.h b/include/event2/dns.h index a2c724d5..8bed3f9f 100644 --- a/include/event2/dns.h +++ b/include/event2/dns.h @@ -202,6 +202,8 @@ extern "C" { * - attempts: * - randomize-case: * - initial-probe-timeout: + * - max-probe-timeout: + * - probe-backoff-factor: * - tcp-idle-timeout: * - edns-udp-size: * - use-vc @@ -470,9 +472,16 @@ void evdns_cancel_request(struct evdns_base *base, struct evdns_request *req); The currently available configuration options are: ndots, timeout, max-timeouts, max-inflight, attempts, randomize-case, - bind-to, initial-probe-timeout, getaddrinfo-allow-skew, - so-rcvbuf, so-sndbuf, tcp-idle-timeout, use-vc, ignore-tc, - edns-udp-size. + bind-to, initial-probe-timeout, max-probe-timeout, probe-backoff-factor, + getaddrinfo-allow-skew, so-rcvbuf, so-sndbuf, tcp-idle-timeout, use-vc, + ignore-tc, edns-udp-size. + + - probe-backoff-factor + Backoff factor of probe timeout + + - max-probe-timeout + Maximum timeout between two probe packets will change initial-probe-timeout + when this value is smaller In versions before Libevent 2.0.3-alpha, the option name needed to end with a colon. diff --git a/test/regress_dns.c b/test/regress_dns.c index 4e3d6f16..fff111a8 100644 --- a/test/regress_dns.c +++ b/test/regress_dns.c @@ -848,6 +848,92 @@ dns_retry_disable_when_inactive_test(void *arg) dns_retry_test_impl(arg, EVDNS_BASE_DISABLE_WHEN_INACTIVE); } +static void +dns_probe_settings_test(void *arg) +{ + struct basic_test_data *data = arg; + struct event_base *base = data->base; + struct evdns_server_port *port = NULL; + struct evdns_base *dns = NULL; + int drop_count = 1; + ev_uint16_t portnum = 0; + char buf[64]; + + struct generic_dns_callback_result r1, r2; + struct timeval tval_before, tval_after; + + port = regress_get_udp_dnsserver(base, &portnum, NULL, + fail_server_cb, &drop_count); + tt_assert(port); + evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); + + dns = evdns_base_new(base, 0); + tt_assert(!evdns_base_nameserver_ip_add(dns, buf)); + tt_assert(!evdns_base_set_option(dns, "timeout", "0.2")); + tt_assert(!evdns_base_set_option(dns, "max-timeouts", "1")); + tt_assert(!evdns_base_set_option(dns, "attempts", "1")); + tt_assert(!evdns_base_set_option(dns, "initial-probe-timeout", "10")); + // it will also set initial-probe-timeout to 1s (10s > 1s) + tt_assert(!evdns_base_set_option(dns, "max-probe-timeout", "1")); + + evdns_base_resolve_ipv4(dns, "host.example.com", 0, + generic_dns_callback, &r1); + n_replies_left = 2; + exit_base = base; + gettimeofday(&tval_before, NULL); + // this will wait until the probe request done + // should be around 1s instead of 10s + event_base_dispatch(base); + gettimeofday(&tval_after, NULL); + tt_int_op(r1.result, ==, DNS_ERR_TIMEOUT); + test_timeval_diff_leq(&tval_before, &tval_after, 1000, 500); + + // should be ok now + evdns_base_resolve_ipv4(dns, "host.example.com", 0, + generic_dns_callback, &r1); + n_replies_left = 1; + event_base_dispatch(base); + + tt_int_op(r1.result, ==, DNS_ERR_NONE); + tt_int_op(r1.type, ==, DNS_IPv4_A); + tt_int_op(r1.count, ==, 1); + tt_int_op(((ev_uint32_t*)r1.addrs)[0], ==, htonl(0x10204080)); + + // dns server down again + drop_count = 3; + tt_assert(!evdns_base_set_option(dns, "max-probe-timeout", "3600")); + tt_assert(!evdns_base_set_option(dns, "initial-probe-timeout", "0.2")); + tt_assert(!evdns_base_set_option(dns, "probe-backoff-factor", "10")); + evdns_base_resolve_ipv4(dns, "host.example.com", 0, generic_dns_callback, &r1); + evdns_base_resolve_ipv4(dns, "host.example.com", 0, generic_dns_callback, &r2); + // probe timeout should be around 2s now + n_replies_left = 3; + + gettimeofday(&tval_before, NULL); + // wait dns server up + event_base_dispatch(base); + tt_int_op(r1.result, ==, DNS_ERR_TIMEOUT); + tt_int_op(r2.result, ==, DNS_ERR_TIMEOUT); + gettimeofday(&tval_after, NULL); + test_timeval_diff_leq(&tval_before, &tval_after, 2000, 1000); + + // should be ok now + evdns_base_resolve_ipv4(dns, "host.example.com", 0, generic_dns_callback, &r1); + n_replies_left = 1; + event_base_dispatch(base); + + tt_int_op(r1.result, ==, DNS_ERR_NONE); + tt_int_op(r1.type, ==, DNS_IPv4_A); + tt_int_op(r1.count, ==, 1); + tt_int_op(((ev_uint32_t*)r1.addrs)[0], ==, htonl(0x10204080)); + + end: + if (dns) + evdns_base_free(dns, 0); + if (port) + evdns_close_server_port(port); +} + static struct regress_dns_server_table internal_error_table[] = { /* Error 4 (NOTIMPL) makes us reissue the request to another server if we can. @@ -2836,6 +2922,7 @@ struct testcase_t dns_testcases[] = { { "retry", dns_retry_test, TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, { "retry_disable_when_inactive", dns_retry_disable_when_inactive_test, TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, + { "probe_settings", dns_probe_settings_test, TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, { "reissue", dns_reissue_test, TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL }, { "reissue_disable_when_inactive", dns_reissue_disable_when_inactive_test, TT_FORK|TT_NEED_BASE|TT_NO_LOGS, &basic_setup, NULL },