From 3666d994a73d8ca9c12ddf6af708378b64abecd6 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 16 Jan 2018 15:08:22 +0000 Subject: [PATCH] Add 'keepalive' test and supporting changes to getdns library. Checking for server support for keepalive means we need to know if the server did send a keepalive option to the client. This information is not currently exposed in getdns, so add a flag 'server_keepalive_received' to call_reporting. This is 0 if not received, 1 if received. If received, the actual timeout is in 'idle timeout in ms', though watch out for the overflow alternative. --- src/context.c | 1 + src/context.h | 1 + src/stub.c | 1 + src/tools/getdns_server_mon.c | 117 +++++++++++++++++++++++++++++++--- src/util-internal.c | 4 ++ 5 files changed, 116 insertions(+), 8 deletions(-) diff --git a/src/context.c b/src/context.c index c8f14363..52956370 100644 --- a/src/context.c +++ b/src/context.c @@ -1011,6 +1011,7 @@ upstream_init(getdns_upstream *upstream, upstream->responses_timeouts = 0; upstream->keepalive_shutdown = 0; upstream->keepalive_timeout = 0; + upstream->server_keepalive_received = 0; /* How is this upstream doing on UDP? */ upstream->to_retry = 1; upstream->back_off = 1; diff --git a/src/context.h b/src/context.h index b4319403..73e6e519 100644 --- a/src/context.h +++ b/src/context.h @@ -193,6 +193,7 @@ typedef struct getdns_upstream { size_t responses_timeouts; size_t keepalive_shutdown; uint64_t keepalive_timeout; + int server_keepalive_received; /* Management of outstanding requests on stateful transports */ getdns_network_req *write_queue; diff --git a/src/stub.c b/src/stub.c index 07f22fcd..7879e0c7 100644 --- a/src/stub.c +++ b/src/stub.c @@ -341,6 +341,7 @@ process_keepalive( } return; } + upstream->server_keepalive_received = 1; /* Use server sent value unless the client specified a shorter one. Convert to ms first (wire value has units of 100ms) */ uint64_t server_keepalive = ((uint64_t)gldns_read_uint16(position))*100; diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 811a6e4e..53e06b7b 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -183,9 +183,12 @@ static void usage() "\n" "Tests:\n" " lookup [ []] Check lookup on server\n" +" keepalive [ []]\n" +" Check server support for EDNS0 keepalive in TCP/TLS\n" +" Timeout of 0 is off.\n" +" qname-min Check whether server supports QNAME minimisation\n" " rtt [warn-ms,crit-ms] [ []]\n" " Check server round trip time (default 500,250)\n" -" qname-min Check whether server supports QNAME minimisation\n" "\n" " tls-auth [ []] Check authentication of TLS server\n" " If both a SPKI pin and authentication name are\n" @@ -938,20 +941,109 @@ no_padding: return EXIT_CRITICAL; } +static exit_value_t test_keepalive(const struct test_info_s *test_info, + char ** av) +{ + getdns_dict *response; + exit_value_t xit; + long long timeout; + char *endptr; + const char USAGE[] = "keepalive takes arguments [ []]"; + + if (!*av || (timeout = strtoll(*av, &endptr, 10), *endptr != '\0' || timeout < 0)) { + fputs(USAGE, test_info->errout); + return EXIT_USAGE; + } + ++av; + + getdns_return_t ret; + if ((ret = getdns_context_set_idle_timeout(test_info->context, (uint64_t) timeout)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot set keepalive timeout: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + if ((xit = parse_search_check(test_info, + av, + USAGE, + &response, + NULL, + NULL, + NULL)) != EXIT_OK) + return xit; + + getdns_list *l; + + if ((ret = getdns_dict_get_list(response, "call_reporting", &l)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get call report: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + getdns_dict *d; + if ((ret = getdns_list_get_dict(l, 0, &d)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get call report first item: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + /* Search is forced to be TCP or TLS, so server keepalive flag must exist. */ + uint32_t server_keepalive_received; + if ((ret = getdns_dict_get_int(d, "server_keepalive_received", &server_keepalive_received)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get server keepalive flag: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + if (server_keepalive_received) { + uint32_t t; + bool overflow = false; + + if (!((ret = getdns_dict_get_int(d, "idle timeout in ms", &t)) == GETDNS_RETURN_GOOD || + (overflow = true, ret = getdns_dict_get_int(d, "idle timeout in ms (overflow)", &t)) == GETDNS_RETURN_GOOD)) { + fprintf(test_info->errout, + "Cannot get idle timeout: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + if (overflow) { + fputs("Server sent keepalive, idle timeout now (overflow)", test_info->errout); + } else { + fprintf(test_info->errout, "Server sent keepalive, idle timeout now %ums", t); + } + return EXIT_OK; + } else { + fputs("Server did not send keepalive", test_info->errout); + return EXIT_CRITICAL; + } +} + static struct test_funcs_s { const char *name; bool implies_tls; + bool implies_tcp; exit_value_t (*func)(const struct test_info_s *test_info, char **av); } TESTS[] = { - { "lookup", false, test_lookup }, - { "rtt", false, test_rtt }, - { "qname-min", false, test_qname_minimisation }, - { "tls-auth", true, test_authenticate }, - { "tls-cert-valid", true, test_certificate_valid }, - { "tls-padding", true, test_padding }, - { NULL, false, NULL } + { "lookup", false, false, test_lookup }, + { "rtt", false, false, test_rtt }, + { "qname-min", false, false, test_qname_minimisation }, + { "tls-auth", true, false, test_authenticate }, + { "tls-cert-valid", true, false, test_certificate_valid }, + { "tls-padding", true, false, test_padding }, + { "keepalive", false, true, test_keepalive }, + { NULL, false, false, NULL } }; int main(int ac, char *av[]) @@ -1151,6 +1243,15 @@ int main(int ac, char *av[]) exit(EXIT_UNKNOWN); } + if (f->implies_tcp) { + if (use_udp) { + fputs("Test requires TCP or TLS\n", test_info.errout); + exit(EXIT_UNKNOWN); + } + if (!use_tls) + use_tcp = true; + } + if (f->implies_tls) { if (use_udp | use_tcp) { fputs("Test requires TLS, or TLS authentication specified\n", test_info.errout); diff --git a/src/util-internal.c b/src/util-internal.c index 808944c3..3ec15995 100644 --- a/src/util-internal.c +++ b/src/util-internal.c @@ -880,6 +880,10 @@ _getdns_create_call_reporting_dict( return NULL; } } + if (getdns_dict_set_int( netreq_debug, "server_keepalive_received", netreq->upstream->server_keepalive_received)) { + getdns_dict_destroy(netreq_debug); + return NULL; + } /* The running totals are only updated when a connection is closed. Since it is open as we have just used it, calcualte the value on the fly */ if (getdns_dict_set_int( netreq_debug, "responses_on_this_connection",