]> granicus.if.org Git - libevent/commitdiff
evdns: add max-probe-timeout/probe-backoff-factor settings
authorchux0519 <chuxdesign@hotmail.com>
Sat, 9 Jan 2021 09:19:27 +0000 (17:19 +0800)
committerAzat Khuzhin <azat@libevent.org>
Tue, 12 Jan 2021 07:56:13 +0000 (10:56 +0300)
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.

evdns.c
include/event2/dns.h
test/regress_dns.c

diff --git a/evdns.c b/evdns.c
index 14a6439841fb546371e7721d78574d3440cf6db2..66b627f3d993a5089f1f1cd9349c8e47a4a6b852 100644 (file)
--- 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);
index a2c724d53c7aee7093399f3d58e661b4f7e90618..8bed3f9fd78f21f7613add88efd6ab084e3792f2 100644 (file)
@@ -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.
index 4e3d6f165268f82cb4e9194d15591a59fd6c01dc..fff111a8f0410ae074253e85621246bc9edd34bb 100644 (file)
@@ -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 },